Merge branch 'new_sahagin' into 'master'

new_sahagin

See merge request veloren/veloren!4467
This commit is contained in:
Samuel Keiffer 2024-05-29 00:01:46 +00:00
commit eeb68797e7
38 changed files with 1005 additions and 84 deletions

View File

@ -633,6 +633,9 @@
Simple(
"common.items.keys.quarry_keys.forge_key",
): "object-key-forge",
Simple(
"common.items.keys.sahagin_key",
): "object-key-sahagin",
Simple(
"common.items.weapons.shield.shield_1",
): "weapon-shield-wood-0",

View File

@ -0,0 +1,10 @@
ItemDef(
legacy_name: "Sahagin Key",
legacy_description: "Used to open doors. Will break after use.",
kind: Utility(
kind: Key,
),
quality: Common,
tags: [Utility],
)

View File

@ -0,0 +1,3 @@
[
(1.0, Item("common.items.keys.sahagin_key")),
]

BIN
assets/voxygen/element/ui/map/buttons/tidal_warrior.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/element/ui/map/buttons/tidal_warrior_bg.png (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

View File

@ -37,5 +37,6 @@ hud-map-chapel_site = Sea Chapel
hud-map-adlet = Adlet Stronghold
hud-map-haniwa = Haniwa Catacomb
hud-map-cultist = Cultist Dungeon
hud-map-sahagin = Sahagin Island
hud-map-terracotta = Terracotta Ruins
hud-map-placed_by = Placed by { $name }

View File

@ -24,3 +24,6 @@ object-key-terracotta_chest = Terracotta Chest Key
object-key-terracotta_door = Terracotta Door Key
.desc = Used to open doors. Will break after use.
object-key-sahagin = Sahagin Key
.desc = Used to open doors. Will break after use.

View File

@ -4986,6 +4986,10 @@
"voxel.object.key_haniwa",
(0.0, 0.0, 0.0), (-100.0, 250.0, 15.0), 1.0,
),
Simple("common.items.keys.sahagin_key"): VoxTrans(
"voxel.object.key_sahagin",
(0.0, 0.0, 0.0), (-100.0, 250.0, 15.0), 1.0,
),
Simple("common.items.keys.glass_key"): VoxTrans(
"voxel.object.key_glass",
(0.0, 0.0, 0.0), (-100.0, 250.0, 15.0), 1.0,

View File

@ -959,6 +959,7 @@
Simple("common.items.keys.rusty_tower_key"): "voxel.object.key_rusty-0",
Simple("common.items.keys.bone_key"): "voxel.object.key_bone",
Simple("common.items.keys.haniwa_key"): "voxel.object.key_haniwa",
Simple("common.items.keys.sahagin_key"): "voxel.object.key_sahagin",
Simple("common.items.keys.glass_key"): "voxel.object.key_glass",
Simple("common.items.utility.lockpick_0"): "voxel.object.lockpick",
Simple("common.items.keys.quarry_keys.forge_key"): "voxel.object.key_rusty-0",

BIN
assets/voxygen/voxel/object/key_sahagin.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/chests/chest_sahagin.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/furniture/sahagin_keydoor.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/furniture/sahagin_keyhole.vox (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -1419,6 +1419,37 @@
],
wind_sway: 0.0,
),
// Sahagin Door, Keyhole, Chest, Statue
(SahaginKeyDoor, ()): (
variations: [
(
model: "voxygen.voxel.sprite.furniture.sahagin_keydoor",
offset: (-5.5, -5.5, 0.0),
lod_axes: (0.0, 0.0, 0.0),
),
],
wind_sway: 0.0,
),
(SahaginKeyhole, ()): (
variations: [
(
model: "voxygen.voxel.sprite.furniture.sahagin_keyhole",
offset: (-5.5, -5.5, 0.0),
lod_axes: (0.0, 0.0, 0.0),
),
],
wind_sway: 0.0,
),
(SahaginChest, ()): (
variations: [
(
model: "voxygen.voxel.sprite.chests.chest_sahagin",
offset: (-7.0, -5.0, -0.0),
lod_axes: (1.0, 1.0, 1.0),
),
],
wind_sway: 0.0,
),
// Terracotta Door, Keyhole, Chest, Statue
(TerracottaKeyDoor, ()): (
variations: [

View File

@ -16,6 +16,5 @@
/// 1) Set every probability to 0.0 and left one with any other number
/// and you will have map full of dungeons of same level
([
(2, 0.20),
(4, 0.10),
])

View File

@ -155,6 +155,7 @@ pub enum SiteKind {
Haniwa,
DwarvenMine,
Cultist,
Sahagin,
}
#[derive(Debug, Clone, Serialize, Deserialize)]

View File

@ -206,6 +206,7 @@ impl From<SpriteKind> for Option<SpriteInteractKind> {
SpriteKind::Keyhole
| SpriteKind::BoneKeyhole
| SpriteKind::HaniwaKeyhole
| SpriteKind::SahaginKeyhole
| SpriteKind::GlassKeyhole
| SpriteKind::KeyholeBars
| SpriteKind::TerracottaKeyhole => Some(SpriteInteractKind::Unlock),

View File

@ -372,6 +372,7 @@ impl Block {
| SpriteKind::CoralChest
| SpriteKind::HaniwaUrn
| SpriteKind::TerracottaChest
| SpriteKind::SahaginChest
| SpriteKind::Crate => Some(rtsim::ChunkResource::Loot),
_ => None,
}
@ -533,6 +534,7 @@ impl Block {
| SpriteKind::HaniwaTrapTriggered
| SpriteKind::ChestBuried
| SpriteKind::TerracottaChest
| SpriteKind::SahaginChest
| SpriteKind::SeaDecorBlock
| SpriteKind::SeaDecorChain
| SpriteKind::SeaDecorWindowHor
@ -543,6 +545,8 @@ impl Block {
| SpriteKind::FireBlock
| SpriteKind::GlassBarrier
| SpriteKind::GlassKeyhole
| SpriteKind::SahaginKeyhole
| SpriteKind::SahaginKeyDoor
| SpriteKind::TerracottaKeyDoor
| SpriteKind::TerracottaKeyhole
| SpriteKind::TerracottaStatue

View File

@ -19,6 +19,7 @@ pub enum DungeonKindMeta {
SeaChapel,
Terracotta,
Cultist,
Sahagin,
}
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)]

View File

@ -128,11 +128,12 @@ sprites! {
CoralChest = 0x37,
HaniwaUrn = 0x38,
TerracottaChest = 0x39,
CommonLockedChest = 0x3A,
ChestBuried = 0x3B,
Crate = 0x3C,
Barrel = 0x3D,
CrateBlock = 0x3E,
SahaginChest = 0x3A,
CommonLockedChest = 0x3B,
ChestBuried = 0x3C,
Crate = 0x3D,
Barrel = 0x3E,
CrateBlock = 0x3F,
// Wall
HangingBasket = 0x50,
HangingSign = 0x51,
@ -312,6 +313,9 @@ sprites! {
HaniwaKeyhole = 0x0A,
TerracottaKeyDoor = 0x0B,
TerracottaKeyhole = 0x0C,
SahaginKeyhole = 0x0D,
SahaginKeyDoor = 0x0E,
// Windows
Window1 = 0x10,
Window2 = 0x11,
@ -438,6 +442,7 @@ impl SpriteKind {
SpriteKind::DungeonChest5 => 1.09,
SpriteKind::CoralChest => 1.09,
SpriteKind::HaniwaUrn => 1.09,
SpriteKind::SahaginChest => 1.09,
SpriteKind::TerracottaChest => 1.09,
SpriteKind::TerracottaStatue => 5.29,
SpriteKind::TerracottaBlock => 1.00,
@ -515,6 +520,8 @@ impl SpriteKind {
| SpriteKind::BoneKeyDoor
| SpriteKind::HaniwaKeyhole
| SpriteKind::HaniwaKeyDoor
| SpriteKind::SahaginKeyhole
| SpriteKind::SahaginKeyDoor
| SpriteKind::HaniwaTrap
| SpriteKind::HaniwaTrapTriggered
| SpriteKind::TerracottaKeyDoor
@ -690,6 +697,7 @@ impl SpriteKind {
SpriteKind::TerracottaChest => {
table("common.loot_tables.dungeon.terracotta.chest_terracotta")
},
SpriteKind::SahaginChest => table("common.loot_tables.dungeon.sahagin.key_chest"),
SpriteKind::Mud => table("common.loot_tables.sprite.mud"),
SpriteKind::Grave => table("common.loot_tables.sprite.mud"),
SpriteKind::Crate => table("common.loot_tables.sprite.crate"),
@ -705,6 +713,7 @@ impl SpriteKind {
| SpriteKind::HaniwaKeyhole
| SpriteKind::GlassKeyhole
| SpriteKind::KeyholeBars
| SpriteKind::SahaginKeyhole
| SpriteKind::TerracottaKeyhole => {
return Some(None);
},
@ -809,6 +818,9 @@ impl SpriteKind {
SpriteKind::GlassKeyhole => UnlockKind::Consumes(
ItemDefinitionId::Simple("common.items.keys.glass_key").to_owned(),
),
SpriteKind::SahaginKeyhole => UnlockKind::Consumes(
ItemDefinitionId::Simple("common.items.keys.sahagin_key").to_owned(),
),
SpriteKind::TerracottaChest => UnlockKind::Consumes(
ItemDefinitionId::Simple("common.items.keys.terracotta_key_chest").to_owned(),
),

View File

@ -49,10 +49,11 @@ make_case_elim!(
KeyholeBars(consumes: String) = 29,
HaniwaKeyhole(consumes: String) = 30,
TerracottaKeyhole(consumes: String) = 31,
MapleLeaves = 32,
CherryLeaves = 33,
AutumnLeaves = 34,
RedwoodWood = 35,
SahaginKeyhole(consumes: String) = 32,
MapleLeaves = 33,
CherryLeaves = 34,
AutumnLeaves = 35,
RedwoodWood = 36,
}
);

View File

@ -33,6 +33,7 @@ impl Site {
| SiteKind::Terracotta(_)
| SiteKind::Gnarling(_)
| SiteKind::Cultist(_)
| SiteKind::Sahagin(_)
| SiteKind::PirateHideout(_)
| SiteKind::JungleRuin(_)
| SiteKind::RockCircle(_)

View File

@ -4494,7 +4494,7 @@ impl<'a> AgentData<'a> {
read_data,
)
};
let home = agent.patrol_origin.unwrap_or(self.pos.0.round());
// Sets counter at start of combat, using `condition` to keep track of whether
// it was already initialized
if !agent.combat_state.conditions
@ -4544,15 +4544,14 @@ impl<'a> AgentData<'a> {
controller.push_basic_input(InputKind::Secondary);
}
}
// Always attempt to path towards target
self.path_toward_target(
agent,
controller,
tgt_data.pos.0,
read_data,
Path::Partial,
None,
);
let path = if tgt_data.pos.0.z < self.pos.0.z {
home
} else {
tgt_data.pos.0
};
// attempt to path towards target, move away from exiit if target is cheesing
// from below
self.path_toward_target(agent, controller, path, read_data, Path::Partial, None);
}
pub fn handle_yeti_attack(

View File

@ -411,6 +411,9 @@ impl ServerEvent for InventoryManipEvent {
Some(SpriteKind::Keyhole) => Some(SpriteKind::KeyDoor),
Some(SpriteKind::BoneKeyhole) => Some(SpriteKind::BoneKeyDoor),
Some(SpriteKind::HaniwaKeyhole) => Some(SpriteKind::HaniwaKeyDoor),
Some(SpriteKind::SahaginKeyhole) => {
Some(SpriteKind::SahaginKeyDoor)
},
Some(SpriteKind::GlassKeyhole) => Some(SpriteKind::GlassBarrier),
Some(SpriteKind::KeyholeBars) => Some(SpriteKind::DoorBars),
Some(SpriteKind::TerracottaKeyhole) => {

View File

@ -588,6 +588,9 @@ image_ids! {
mmap_site_cultist: "voxygen.element.ui.map.buttons.mindflayer",
mmap_site_cultist_hover: "voxygen.element.ui.map.buttons.mindflayer_hover",
mmap_site_cultist_bg: "voxygen.element.ui.map.buttons.mindflayer_bg",
mmap_site_sahagin: "voxygen.element.ui.map.buttons.tidal_warrior",
mmap_site_sahagin_hover: "voxygen.element.ui.map.buttons.tidal_warrior_hover",
mmap_site_sahagin_bg: "voxygen.element.ui.map.buttons.tidal_warrior_bg",
mmap_site_minotaur: "voxygen.element.ui.map.buttons.minotaur",
mmap_site_minotaur_hover: "voxygen.element.ui.map.buttons.minotaur_hover",
mmap_site_minotaur_bg: "voxygen.element.ui.map.buttons.minotaur_bg",

View File

@ -929,6 +929,7 @@ impl<'a> Widget for Map<'a> {
SiteKind::Adlet => i18n.get_msg("hud-map-adlet"),
SiteKind::Haniwa => i18n.get_msg("hud-map-haniwa"),
SiteKind::Cultist => i18n.get_msg("hud-map-cultist"),
SiteKind::Sahagin => i18n.get_msg("hud-map-sahagin"),
SiteKind::DwarvenMine => i18n.get_msg("hud-map-df_mine"),
});
let (difficulty, desc) = match &site.kind {
@ -960,6 +961,7 @@ impl<'a> Widget for Map<'a> {
SiteKind::Adlet => (Some(1), i18n.get_msg("hud-map-adlet")),
SiteKind::Haniwa => (Some(3), i18n.get_msg("hud-map-haniwa")),
SiteKind::Cultist => (Some(5), i18n.get_msg("hud-map-cultist")),
SiteKind::Sahagin => (Some(2), i18n.get_msg("hud-map-sahagin")),
SiteKind::DwarvenMine => (Some(5), i18n.get_msg("hud-map-df_mine")),
};
let desc = desc.into_owned() + &get_site_economy(site_rich);
@ -974,6 +976,7 @@ impl<'a> Widget for Map<'a> {
SiteKind::Adlet => self.imgs.mmap_site_adlet,
SiteKind::Haniwa => self.imgs.mmap_site_haniwa,
SiteKind::Cultist => self.imgs.mmap_site_cultist,
SiteKind::Sahagin => self.imgs.mmap_site_sahagin,
SiteKind::DwarvenMine => self.imgs.mmap_site_mine,
SiteKind::Dungeon { difficulty } => match difficulty {
4 => self.imgs.mmap_site_minotaur,
@ -999,6 +1002,7 @@ impl<'a> Widget for Map<'a> {
SiteKind::Adlet => self.imgs.mmap_site_adlet_hover,
SiteKind::Haniwa => self.imgs.mmap_site_haniwa_hover,
SiteKind::Cultist => self.imgs.mmap_site_cultist_hover,
SiteKind::Sahagin => self.imgs.mmap_site_sahagin_hover,
SiteKind::DwarvenMine => self.imgs.mmap_site_mine_hover,
SiteKind::Dungeon { difficulty } => match difficulty {
4 => self.imgs.mmap_site_minotaur_hover,
@ -1021,6 +1025,7 @@ impl<'a> Widget for Map<'a> {
| SiteKind::Adlet
| SiteKind::Haniwa
| SiteKind::Cultist
| SiteKind::Sahagin
| SiteKind::DwarvenMine => match difficulty {
Some(0) => QUALITY_LOW,
Some(1) => QUALITY_COMMON,
@ -1050,6 +1055,7 @@ impl<'a> Widget for Map<'a> {
| SiteKind::DwarvenMine
| SiteKind::Haniwa
| SiteKind::Cultist
| SiteKind::Sahagin
| SiteKind::Terracotta
| SiteKind::Adlet => show_dungeons,
SiteKind::Castle => show_castles,
@ -1113,6 +1119,7 @@ impl<'a> Widget for Map<'a> {
| SiteKind::ChapelSite
| SiteKind::Haniwa
| SiteKind::Cultist
| SiteKind::Sahagin
| SiteKind::Terracotta
| SiteKind::Adlet => {
if show_dungeons {

View File

@ -707,6 +707,7 @@ impl<'a> Widget for MiniMap<'a> {
SiteKind::Gnarling => Some(0),
SiteKind::Bridge => None,
SiteKind::Adlet => Some(1),
SiteKind::Sahagin => Some(2),
SiteKind::Haniwa => Some(3),
SiteKind::Cultist => Some(5),
SiteKind::DwarvenMine => Some(5),
@ -725,6 +726,7 @@ impl<'a> Widget for MiniMap<'a> {
SiteKind::Adlet => self.imgs.mmap_site_adlet_bg,
SiteKind::Haniwa => self.imgs.mmap_site_haniwa_bg,
SiteKind::Cultist => self.imgs.mmap_site_cultist_bg,
SiteKind::Sahagin => self.imgs.mmap_site_sahagin_bg,
SiteKind::DwarvenMine => self.imgs.mmap_site_mine_bg,
})
.x_y_position_relative_to(
@ -756,6 +758,7 @@ impl<'a> Widget for MiniMap<'a> {
SiteKind::Adlet => self.imgs.mmap_site_adlet,
SiteKind::Haniwa => self.imgs.mmap_site_haniwa,
SiteKind::Cultist => self.imgs.mmap_site_cultist,
SiteKind::Sahagin => self.imgs.mmap_site_sahagin,
SiteKind::DwarvenMine => self.imgs.mmap_site_mine,
})
.middle_of(state.ids.mmap_site_icons_bgs[i])

View File

@ -5312,6 +5312,7 @@ pub fn get_sprite_desc(sprite: SpriteKind, localized_strings: &Localization) ->
| SpriteKind::DungeonChest3
| SpriteKind::DungeonChest4
| SpriteKind::DungeonChest5
| SpriteKind::SahaginChest
| SpriteKind::TerracottaChest => "common-sprite-chest",
SpriteKind::Mud => "common-sprite-mud",
SpriteKind::Grave => "common-sprite-grave",

View File

@ -389,6 +389,17 @@ pub fn block_from_structure(
}),
));
},
StructureBlock::SahaginKeyhole(consumes) => {
return Some((
Block::air(SpriteKind::SahaginKeyhole),
Some(SpriteCfg {
unlock: Some(UnlockKind::Consumes(ItemDefinitionIdOwned::Simple(
consumes.clone(),
))),
..SpriteCfg::default()
}),
));
},
StructureBlock::RedwoodWood => {
let wpos = pos + structure_pos;
if (wpos.x / 2 + wpos.y) % 5 > 1 && ((wpos.x + 1) / 2 + wpos.y + 2) % 5 > 1 {

View File

@ -278,7 +278,7 @@ impl Civs {
let world_dims = ctx.sim.get_aabr();
for _ in 0..initial_civ_count * 3 {
attempt(5, || {
let (loc, kind) = match ctx.rng.gen_range(0..95) {
let (loc, kind) = match ctx.rng.gen_range(0..101) {
0..=4 => {
if index.features().site2_giant_trees {
(
@ -424,7 +424,17 @@ impl Civs {
)?,
SiteKind::Cultist,
),
/*97..=102 => (
91..=95 => (
find_site_loc(
&mut ctx,
&ProximityRequirementsBuilder::new()
.avoid_all_of(this.sahagin_enemies(), 40)
.finalize(&world_dims),
&SiteKind::Sahagin,
)?,
SiteKind::Sahagin,
),
/*96..=109 => (
find_site_loc(
&mut ctx,
&ProximityRequirementsBuilder::new()
@ -435,7 +445,7 @@ impl Civs {
)?,
SiteKind::Castle,
),
103..=108 => (SiteKind::Citadel, (&castle_enemies, 20)),
110..=115 => (SiteKind::Citadel, (&castle_enemies, 20)),
*/
_ => (
find_site_loc(
@ -491,6 +501,7 @@ impl Civs {
SiteKind::Camp => (4i32, 1.5),
SiteKind::DwarvenMine => (8i32, 3.0),
SiteKind::Cultist => (24i32, 10.0),
SiteKind::Sahagin => (8i32, 3.0),
};
let (raise, raise_dist, make_waypoint): (f32, i32, bool) = match &site.kind {
@ -679,6 +690,12 @@ impl Civs {
&mut rng,
wpos,
)),
SiteKind::Sahagin => WorldSite::sahagin(site2::Site::generate_sahagin(
&Land::from_sim(ctx.sim),
index_ref,
&mut rng,
wpos,
)),
}
});
sim_site.site_tmp = Some(site);
@ -1625,6 +1642,13 @@ impl Civs {
})
}
fn sahagin_enemies(&self) -> impl Iterator<Item = Vec2<i32>> + '_ {
self.sites().filter_map(|s| match s.kind {
SiteKind::Tree | SiteKind::GiantTree => None,
_ => Some(s.center),
})
}
fn rock_circle_enemies(&self) -> impl Iterator<Item = Vec2<i32>> + '_ {
self.sites().filter_map(|s| match s.kind {
SiteKind::Tree | SiteKind::GiantTree => None,
@ -1988,6 +2012,7 @@ pub enum SiteKind {
DwarvenMine,
JungleRuin,
Cultist,
Sahagin,
}
impl SiteKind {
@ -2069,6 +2094,10 @@ impl SiteKind {
SiteKind::PirateHideout => {
(0.5..3.5).contains(&(chunk.water_alt - CONFIG.sea_level))
},
SiteKind::Sahagin => {
matches!(chunk.get_biome(), BiomeKind::Ocean)
&& (40.0..45.0).contains(&(CONFIG.sea_level - chunk.alt))
},
SiteKind::JungleRuin => {
matches!(chunk.get_biome(), BiomeKind::Jungle)
},

View File

@ -218,6 +218,7 @@ impl World {
civ::SiteKind::Citadel => world_msg::SiteKind::Castle,
civ::SiteKind::Bridge(_, _) => world_msg::SiteKind::Bridge,
civ::SiteKind::Cultist => world_msg::SiteKind::Cultist,
civ::SiteKind::Sahagin => world_msg::SiteKind::Sahagin,
civ::SiteKind::Adlet => world_msg::SiteKind::Adlet,
civ::SiteKind::Haniwa => world_msg::SiteKind::Haniwa,
},

View File

@ -132,6 +132,7 @@ impl Environment {
SiteKind::Gnarling(_) => {},
SiteKind::Adlet(_) => {},
SiteKind::Cultist(_) => {},
SiteKind::Sahagin(_) => {},
SiteKind::Haniwa(_) => {},
SiteKind::JungleRuin(_) => {},
SiteKind::ChapelSite(_) => {},

View File

@ -85,6 +85,7 @@ pub enum SiteKind {
TrollCave(site2::Site),
Camp(site2::Site),
Cultist(site2::Site),
Sahagin(site2::Site),
}
impl Site {
@ -214,6 +215,13 @@ impl Site {
}
}
pub fn sahagin(sg: site2::Site) -> Self {
Self {
kind: SiteKind::Sahagin(sg),
economy: Economy::default(),
}
}
pub fn dwarven_mine(dm: site2::Site) -> Self {
Self {
kind: SiteKind::DwarvenMine(dm),
@ -274,6 +282,7 @@ impl Site {
SiteKind::Adlet(ad) => ad.radius(),
SiteKind::Haniwa(ha) => ha.radius(),
SiteKind::Cultist(cl) => cl.radius(),
SiteKind::Sahagin(sg) => sg.radius(),
}
}
@ -302,6 +311,7 @@ impl Site {
SiteKind::Adlet(ad) => ad.origin,
SiteKind::Haniwa(ha) => ha.origin,
SiteKind::Cultist(cl) => cl.origin,
SiteKind::Sahagin(sg) => sg.origin,
}
}
@ -330,6 +340,7 @@ impl Site {
SiteKind::Adlet(ad) => ad.spawn_rules(wpos),
SiteKind::Haniwa(ha) => ha.spawn_rules(wpos),
SiteKind::Cultist(cl) => cl.spawn_rules(wpos),
SiteKind::Sahagin(sg) => sg.spawn_rules(wpos),
}
}
@ -358,6 +369,7 @@ impl Site {
SiteKind::Adlet(ad) => ad.name(),
SiteKind::Haniwa(ha) => ha.name(),
SiteKind::Cultist(cl) => cl.name(),
SiteKind::Sahagin(sg) => sg.name(),
}
}
@ -406,6 +418,7 @@ impl Site {
SiteKind::Adlet(ad) => ad.render(canvas, dynamic_rng),
SiteKind::Haniwa(ha) => ha.render(canvas, dynamic_rng),
SiteKind::Cultist(cl) => cl.render(canvas, dynamic_rng),
SiteKind::Sahagin(sg) => sg.render(canvas, dynamic_rng),
}
}
@ -448,6 +461,7 @@ impl Site {
SiteKind::Adlet(ad) => ad.apply_supplement(dynamic_rng, wpos2d, supplement),
SiteKind::Haniwa(ha) => ha.apply_supplement(dynamic_rng, wpos2d, supplement),
SiteKind::Cultist(cl) => cl.apply_supplement(dynamic_rng, wpos2d, supplement),
SiteKind::Sahagin(sg) => sg.apply_supplement(dynamic_rng, wpos2d, supplement),
}
}
@ -490,6 +504,7 @@ impl Site {
SiteKind::Adlet(site2) => Some(site2),
SiteKind::Haniwa(site2) => Some(site2),
SiteKind::Cultist(site2) => Some(site2),
SiteKind::Sahagin(site2) => Some(site2),
}
}
}

View File

@ -114,6 +114,7 @@ impl Site {
PlotKind::TerracottaHouse(th) => Some(th.spawn_rules(wpos)),
PlotKind::TerracottaYard(ty) => Some(ty.spawn_rules(wpos)),
PlotKind::Cultist(cl) => Some(cl.spawn_rules(wpos)),
PlotKind::Sahagin(sg) => Some(sg.spawn_rules(wpos)),
PlotKind::DwarvenMine(dm) => Some(dm.spawn_rules(wpos)),
_ => None,
})
@ -1731,7 +1732,6 @@ impl Site {
let mut rng = reseed(rng);
let mut site = Site {
origin,
name: {
let name = NameGen::location(&mut rng).generate();
match rng.gen_range(0..5) {
@ -1742,8 +1742,6 @@ impl Site {
_ => format!("{} Pit", name),
}
},
//name: NameGen::location(&mut rng).generate_adlet(),
..Site::default()
};
let size = 22.0 as i32;
@ -1770,6 +1768,51 @@ impl Site {
site
}
pub fn generate_sahagin(
land: &Land,
index: IndexRef,
rng: &mut impl Rng,
origin: Vec2<i32>,
) -> Self {
let mut rng = reseed(rng);
let mut site = Site {
origin,
name: {
let name = NameGen::location(&mut rng).generate();
match rng.gen_range(0..4) {
0 => format!("{} Isle", name),
1 => format!("{} Islet", name),
2 => format!("{} Key", name),
3 => format!("{} Cay", name),
_ => format!("{} Rock", name),
}
},
..Site::default()
};
let size = 16.0 as i32;
let aabr = Aabr {
min: Vec2::broadcast(-size),
max: Vec2::broadcast(size),
};
{
let sahagin = plot::Sahagin::generate(land, index, &mut reseed(&mut rng), &site, aabr);
let sahagin_alt = sahagin.alt;
let plot = site.create_plot(Plot {
kind: PlotKind::Sahagin(sahagin),
root_tile: aabr.center(),
tiles: aabr_tiles(aabr).collect(),
seed: rng.gen(),
});
site.blit_aabr(aabr, Tile {
kind: TileKind::Building,
plot: Some(plot),
hard_alt: Some(sahagin_alt),
});
}
site
}
pub fn generate_bridge(
land: &Land,
index: IndexRef,
@ -2112,6 +2155,7 @@ impl Site {
PlotKind::Haniwa(haniwa) => haniwa.render_collect(self, canvas),
PlotKind::GiantTree(giant_tree) => giant_tree.render_collect(self, canvas),
PlotKind::CliffTower(cliff_tower) => cliff_tower.render_collect(self, canvas),
PlotKind::Sahagin(sahagin) => sahagin.render_collect(self, canvas),
PlotKind::SavannahPit(savannah_pit) => savannah_pit.render_collect(self, canvas),
PlotKind::SavannahHut(savannah_hut) => savannah_hut.render_collect(self, canvas),
PlotKind::SavannahWorkshop(savannah_workshop) => {

View File

@ -20,6 +20,7 @@ mod house;
mod jungle_ruin;
mod pirate_hideout;
mod rock_circle;
mod sahagin;
mod savannah_hut;
mod savannah_pit;
mod savannah_workshop;
@ -38,10 +39,11 @@ pub use self::{
desert_city_multiplot::DesertCityMultiPlot, desert_city_temple::DesertCityTemple,
dungeon::Dungeon, dwarven_mine::DwarvenMine, giant_tree::GiantTree,
gnarling::GnarlingFortification, haniwa::Haniwa, house::House, jungle_ruin::JungleRuin,
pirate_hideout::PirateHideout, rock_circle::RockCircle, savannah_hut::SavannahHut,
savannah_pit::SavannahPit, savannah_workshop::SavannahWorkshop, sea_chapel::SeaChapel,
tavern::Tavern, terracotta_house::TerracottaHouse, terracotta_palace::TerracottaPalace,
terracotta_yard::TerracottaYard, troll_cave::TrollCave, workshop::Workshop,
pirate_hideout::PirateHideout, rock_circle::RockCircle, sahagin::Sahagin,
savannah_hut::SavannahHut, savannah_pit::SavannahPit, savannah_workshop::SavannahWorkshop,
sea_chapel::SeaChapel, tavern::Tavern, terracotta_house::TerracottaHouse,
terracotta_palace::TerracottaPalace, terracotta_yard::TerracottaYard, troll_cave::TrollCave,
workshop::Workshop,
};
use super::*;
@ -103,6 +105,7 @@ pub enum PlotKind {
Haniwa(Haniwa),
GiantTree(GiantTree),
CliffTower(CliffTower),
Sahagin(Sahagin),
Citadel(Citadel),
SavannahPit(SavannahPit),
SavannahHut(SavannahHut),

View File

@ -229,7 +229,6 @@ impl Room {
// Toss mobs in the center of the room
if tile_pos == enemy_spawn_tile && wpos2d == tile_wcenter.xy() {
let entities = match self.difficulty {
2 => enemy_2(dynamic_rng, tile_wcenter),
4 => enemy_4(dynamic_rng, tile_wcenter),
_ => enemy_fallback(dynamic_rng, tile_wcenter),
};
@ -259,7 +258,6 @@ impl Room {
if tile_pos == miniboss_spawn_tile && tile_wcenter.xy() == wpos2d {
let entities = match self.difficulty {
2 => mini_boss_2(dynamic_rng, tile_wcenter),
4 => mini_boss_4(dynamic_rng, tile_wcenter),
_ => mini_boss_fallback(dynamic_rng, tile_wcenter),
};
@ -289,7 +287,6 @@ impl Room {
if tile_pos == boss_spawn_tile && wpos2d == tile_wcenter.xy() {
let entities = match self.difficulty {
2 => boss_2(dynamic_rng, tile_wcenter),
4 => boss_4(dynamic_rng, tile_wcenter),
_ => boss_fallback(dynamic_rng, tile_wcenter),
};
@ -669,32 +666,6 @@ impl Floor {
}
}
fn enemy_2(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3<i32>) -> Vec<EntityInfo> {
let number = dynamic_rng.gen_range(2..=4);
let mut entities = Vec::new();
entities.resize_with(number, || {
// TODO: give enemies health skills?
let entity = EntityInfo::at(tile_wcenter.map(|e| e as f32));
match dynamic_rng.gen_range(0..=4) {
0 => {
entity.with_asset_expect("common.entity.dungeon.sahagin.sniper", dynamic_rng, None)
},
1 => entity.with_asset_expect(
"common.entity.dungeon.sahagin.sorcerer",
dynamic_rng,
None,
),
_ => entity.with_asset_expect(
"common.entity.dungeon.sahagin.spearman",
dynamic_rng,
None,
),
}
});
entities
}
fn enemy_4(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3<i32>) -> Vec<EntityInfo> {
let number = dynamic_rng.gen_range(2..=4);
let mut entities = Vec::new();
@ -734,16 +705,6 @@ fn enemy_fallback(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3<i32>) -> Vec<En
entities
}
fn boss_2(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3<i32>) -> Vec<EntityInfo> {
vec![
EntityInfo::at(tile_wcenter.map(|e| e as f32)).with_asset_expect(
"common.entity.dungeon.sahagin.tidalwarrior",
dynamic_rng,
None,
),
]
}
fn boss_4(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3<i32>) -> Vec<EntityInfo> {
vec![
EntityInfo::at(tile_wcenter.map(|e| e as f32)).with_asset_expect(
@ -764,18 +725,6 @@ fn boss_fallback(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3<i32>) -> Vec<Ent
]
}
fn mini_boss_2(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3<i32>) -> Vec<EntityInfo> {
let mut entities = Vec::new();
entities.resize_with(dynamic_rng.gen_range(1..=2), || {
EntityInfo::at(tile_wcenter.map(|e| e as f32)).with_asset_expect(
"common.entity.dungeon.sahagin.hakulaq",
dynamic_rng,
None,
)
});
entities
}
fn mini_boss_4(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3<i32>) -> Vec<EntityInfo> {
vec![
EntityInfo::at(tile_wcenter.map(|e| e as f32)).with_asset_expect(
@ -1376,7 +1325,6 @@ mod tests {
fn test_creating_bosses() {
let mut dynamic_rng = thread_rng();
let tile_wcenter = Vec3::new(0, 0, 0);
boss_2(&mut dynamic_rng, tile_wcenter);
boss_4(&mut dynamic_rng, tile_wcenter);
boss_fallback(&mut dynamic_rng, tile_wcenter);
}
@ -1386,7 +1334,6 @@ mod tests {
fn test_creating_enemies() {
let mut dynamic_rng = thread_rng();
let random_position = Vec3::new(0, 0, 0);
enemy_2(&mut dynamic_rng, random_position);
enemy_4(&mut dynamic_rng, random_position);
enemy_fallback(&mut dynamic_rng, random_position);
}
@ -1396,7 +1343,6 @@ mod tests {
fn test_creating_minibosses() {
let mut dynamic_rng = thread_rng();
let tile_wcenter = Vec3::new(0, 0, 0);
mini_boss_2(&mut dynamic_rng, tile_wcenter);
mini_boss_4(&mut dynamic_rng, tile_wcenter);
mini_boss_fallback(&mut dynamic_rng, tile_wcenter);
}

View File

@ -0,0 +1,758 @@
use super::*;
use crate::{
site2::{plot::dungeon::spiral_staircase, util::gradient::WrapMode},
util::{sampler::Sampler, RandomField},
Land,
};
use common::generation::EntityInfo;
use rand::prelude::*;
use std::{f32::consts::TAU, sync::Arc};
use vek::*;
pub struct Sahagin {
bounds: Aabr<i32>,
pub(crate) alt: i32,
surface_color: Rgb<f32>,
sub_surface_color: Rgb<f32>,
pub(crate) center: Vec2<i32>,
pub(crate) rooms: Vec<Vec2<i32>>,
pub(crate) room_size: i32,
}
impl Sahagin {
pub fn generate(
land: &Land,
index: IndexRef,
_rng: &mut impl Rng,
site: &Site,
tile_aabr: Aabr<i32>,
) -> Self {
let bounds = Aabr {
min: site.tile_wpos(tile_aabr.min),
max: site.tile_wpos(tile_aabr.max),
};
let (surface_color, sub_surface_color) =
if let Some(sample) = land.column_sample(bounds.center(), index) {
(sample.surface_color, sample.sub_surface_color)
} else {
(Rgb::new(161.0, 116.0, 86.0), Rgb::new(88.0, 64.0, 64.0))
};
let room_size = 30;
let center = bounds.center();
let outer_room_radius = (room_size * 2) + (room_size / 3);
let outer_rooms = place_circular(center, outer_room_radius as f32, 5);
let mut rooms = vec![center];
rooms.extend(outer_rooms);
Self {
bounds,
alt: CONFIG.sea_level as i32,
surface_color,
sub_surface_color,
center,
rooms,
room_size,
}
}
pub fn spawn_rules(&self, wpos: Vec2<i32>) -> SpawnRules {
SpawnRules {
waypoints: false,
trees: wpos.distance_squared(self.bounds.center()) > (75_i32).pow(2),
..SpawnRules::default()
}
}
}
impl Structure for Sahagin {
#[cfg(feature = "use-dyn-lib")]
const UPDATE_FN: &'static [u8] = b"render_sahagin\0";
#[cfg_attr(feature = "be-dyn-lib", export_name = "render_sahagin")]
fn render_inner(&self, _site: &Site, _land: &Land, painter: &Painter) {
let room_size = self.room_size;
let center = self.center;
let base = self.alt - room_size + 1;
let rooms = &self.rooms;
let mut thread_rng = thread_rng();
let surface_color = self.surface_color.map(|e| (e * 255.0) as u8);
let sub_surface_color = self.sub_surface_color.map(|e| (e * 255.0) as u8);
let gradient_center = Vec3::new(center.x as f32, center.y as f32, (base + 1) as f32);
let gradient_var_1 = RandomField::new(0).get(center.with_z(base)) as i32 % 8;
let gradient_var_2 = RandomField::new(0).get(center.with_z(base + 1)) as i32 % 10;
let mut random_npcs = vec![];
let brick = Fill::Gradient(
util::gradient::Gradient::new(
gradient_center,
8.0 + gradient_var_1 as f32,
util::gradient::Shape::Point,
(surface_color, sub_surface_color),
)
.with_repeat(if gradient_var_2 > 5 {
WrapMode::Repeat
} else {
WrapMode::PingPong
}),
BlockKind::Rock,
);
let jellyfish = Fill::Gradient(
util::gradient::Gradient::new(
gradient_center,
8.0 + gradient_var_1 as f32,
util::gradient::Shape::Point,
(Rgb::new(180, 181, 227), Rgb::new(120, 160, 255)),
)
.with_repeat(if gradient_var_2 > 5 {
WrapMode::Repeat
} else {
WrapMode::PingPong
}),
BlockKind::GlowingRock,
);
let white = Fill::Sampling(Arc::new(|center| {
Some(match (RandomField::new(0).get(center)) % 37 {
0..=8 => Block::new(BlockKind::Rock, Rgb::new(251, 251, 227)),
9..=17 => Block::new(BlockKind::Rock, Rgb::new(245, 245, 229)),
18..=26 => Block::new(BlockKind::Rock, Rgb::new(250, 243, 221)),
27..=35 => Block::new(BlockKind::Rock, Rgb::new(240, 240, 230)),
_ => Block::new(BlockKind::GlowingRock, Rgb::new(255, 244, 193)),
})
}));
let wood = Fill::Brick(BlockKind::Wood, Rgb::new(71, 33, 11), 12);
let key_door = Fill::Block(Block::air(SpriteKind::SahaginKeyDoor));
let key_hole = Fill::Block(Block::air(SpriteKind::SahaginKeyhole));
let rope = Fill::Block(Block::air(SpriteKind::Rope));
let room_size = 30;
let cell_size_raw = room_size / 6;
let ground_floor = base - (room_size * 2);
let outer_room_radius = (room_size * 2) + (room_size / 3);
let tunnel_radius = (room_size * 3) + 6;
let tunnel_points = place_circular(center, tunnel_radius as f32, 25);
let scaler = -10;
let height_handle = -room_size;
let shell_radius = 3 * (room_size / 2) + scaler;
let shell_carve_radius = 6 * (room_size / 2) + scaler;
let shell_base = base + (room_size * 1) + height_handle;
let high_carve_base = base + (room_size * 7) + height_handle;
let low_carve_base = base + height_handle;
let shell_carve_limiter_1 = painter.aabb(Aabb {
min: (center - shell_radius - 6).with_z(shell_base),
max: (center + shell_radius + 6).with_z(shell_base + (5 * shell_radius)),
});
let shell_carve_limiter_2 = painter.aabb(Aabb {
min: (center - shell_radius).with_z(base + (room_size + 2) - 2),
max: (center + shell_radius).with_z(shell_base + (5 * shell_radius)),
});
painter
.cylinder_with_radius(
center.with_z(shell_base),
shell_radius as f32,
5.0 * shell_radius as f32,
)
.intersect(shell_carve_limiter_2)
.fill(white.clone());
// decor bubbles
let decor_radius = room_size / 3;
for b in 3..=7 {
let shell_decor = place_circular(center, (shell_radius - 2) as f32, 3 * b);
for pos in shell_decor {
let decor_var = 3 + RandomField::new(0).get(pos.with_z(base)) as i32 % 3;
painter
.sphere_with_radius(
pos.with_z(shell_base + (b * (shell_radius / 2))),
(decor_radius - decor_var) as f32,
)
.fill(white.clone());
}
}
// shell carves
painter
.sphere_with_radius(
(center - room_size).with_z(high_carve_base),
shell_carve_radius as f32,
)
.intersect(shell_carve_limiter_1)
.clear();
painter
.sphere_with_radius(
(center + (room_size / 2)).with_z(low_carve_base),
shell_carve_radius as f32,
)
.intersect(shell_carve_limiter_2)
.clear();
// clear room
painter
.cylinder_with_radius(
center.with_z(shell_base + (3 * (shell_radius / 2))),
(shell_radius - 8) as f32,
shell_radius as f32,
)
.clear();
painter
.sphere_with_radius(
center.with_z(shell_base + (5 * (shell_radius / 2)) - 5),
(shell_radius - 8) as f32,
)
.clear();
let boss_pos = center.with_z(shell_base + (3 * (shell_radius / 2)));
painter.spawn(EntityInfo::at(boss_pos.as_()).with_asset_expect(
"common.entity.dungeon.sahagin.tidalwarrior",
&mut thread_rng,
None,
));
// overground towers
let var_towers = 32 + RandomField::new(0).get(center.with_z(base)) as i32 % 6;
let tower_positions = place_circular(center, (5 * (room_size / 2)) as f32, var_towers);
for tower_center_pos in tower_positions {
for dir in CARDINALS {
let tower_center = tower_center_pos + dir * 5;
let var_height =
RandomField::new(0).get(tower_center.with_z(base)) as i32 % (room_size / 2);
painter
.rounded_aabb(Aabb {
min: (tower_center - 10).with_z(base - room_size),
max: (tower_center + 10).with_z(base + (3 * (room_size / 2)) + var_height),
})
.fill(brick.clone());
}
}
let bldg_base = base + room_size;
let bldgs = var_towers / 3;
let beam_th = 2.5;
let bldg_positions = place_circular(center, (5 * (room_size / 2)) as f32, bldgs);
// buildings
for bldg_center in &bldg_positions {
let bldg_size = ((room_size / 4) + 1)
+ RandomField::new(0).get(bldg_center.with_z(bldg_base)) as i32 % 3;
let points = 21;
let ring_0 = place_circular(*bldg_center, (4 * (bldg_size / 2)) as f32, points);
let ring_1 = place_circular(*bldg_center, (9 * (bldg_size / 2)) as f32, points);
let ring_2 = place_circular(*bldg_center, (4 * (bldg_size / 2)) as f32, points);
let ring_3 = place_circular(*bldg_center, (2 * (bldg_size / 2)) as f32, points);
let ring_4 = place_circular(*bldg_center, (6 * (bldg_size / 2)) as f32, points);
let ring_5 = place_circular(*bldg_center, (4 * (bldg_size / 2)) as f32, points);
let ring_6 = place_circular(*bldg_center, (2 * (bldg_size / 2)) as f32, points);
for b in 0..=(ring_0.len() - 1) {
painter
.cubic_bezier(
ring_0[b].with_z(bldg_base + (3 * (bldg_size / 2))),
ring_1[b].with_z(bldg_base + (5 * (bldg_size / 2))),
ring_2[b].with_z(bldg_base + (10 * (bldg_size / 2))),
ring_3[b].with_z(bldg_base + (14 * (bldg_size / 2))),
beam_th,
)
.fill(jellyfish.clone());
if b == (ring_0.len() - 2) {
painter
.cubic_bezier(
ring_4[b].with_z(bldg_base + (12 * (bldg_size / 2))),
ring_5[b + 1].with_z(bldg_base + (14 * (bldg_size / 2))),
ring_6[0].with_z(bldg_base + (16 * (bldg_size / 2))),
bldg_center.with_z(bldg_base + (18 * (bldg_size / 2))),
beam_th,
)
.fill(jellyfish.clone());
} else if b == (ring_0.len() - 1) {
painter
.cubic_bezier(
ring_4[b].with_z(bldg_base + (12 * (bldg_size / 2))),
ring_5[0].with_z(bldg_base + (14 * (bldg_size / 2))),
ring_6[1].with_z(bldg_base + (16 * (bldg_size / 2))),
bldg_center.with_z(bldg_base + (18 * (bldg_size / 2))),
beam_th,
)
.fill(jellyfish.clone());
} else {
painter
.cubic_bezier(
ring_4[b].with_z(bldg_base + (12 * (bldg_size / 2))),
ring_5[b + 1].with_z(bldg_base + (14 * (bldg_size / 2))),
ring_6[b + 2].with_z(bldg_base + (16 * (bldg_size / 2))),
bldg_center.with_z(bldg_base + (18 * (bldg_size / 2))),
beam_th,
)
.fill(jellyfish.clone());
}
}
}
let key_chest_index_1 =
RandomField::new(0).get(center.with_z(base)) as usize % bldgs as usize;
for (p, bldg_center) in bldg_positions.iter().enumerate() {
let bldg_size = ((room_size / 4) + 1)
+ RandomField::new(0).get(bldg_center.with_z(bldg_base)) as i32 % 3;
// passage
if p == (bldg_positions.len() - 1) {
painter
.line(
bldg_positions[p].with_z(bldg_base + (5 * (bldg_size / 2))),
bldg_positions[0].with_z(bldg_base + (5 * (bldg_size / 2))),
beam_th * 2.0,
)
.clear();
} else {
painter
.line(
bldg_positions[p].with_z(bldg_base + (5 * (bldg_size / 2))),
bldg_positions[p + 1].with_z(bldg_base + (5 * (bldg_size / 2))),
beam_th * 2.0,
)
.clear();
}
// floor
painter
.cylinder(Aabb {
min: (bldg_center - (2 * bldg_size) - 2).with_z(base),
max: (bldg_center + (2 * bldg_size) + 2)
.with_z(bldg_base + (5 * (bldg_size / 2)) - 4),
})
.fill(brick.clone());
let chest_pos = bldg_center - 4;
if p == key_chest_index_1 {
painter.sprite(
chest_pos.with_z(bldg_base + (9 * (bldg_size / 2))),
SpriteKind::SahaginChest,
);
}
painter
.cylinder(Aabb {
min: (chest_pos - 2).with_z(bldg_base + (9 * (bldg_size / 2)) - 1),
max: (chest_pos + 3).with_z(bldg_base + (9 * (bldg_size / 2))),
})
.fill(wood.clone());
random_npcs.push(chest_pos.with_z(bldg_base + (9 * (bldg_size / 2)) + 1));
}
for bldg_center in bldg_positions {
let bldg_size = ((room_size / 4) + 1)
+ RandomField::new(0).get(bldg_center.with_z(bldg_base)) as i32 % 3;
// center spear
painter
.cylinder(Aabb {
min: (bldg_center - 3).with_z(bldg_base),
max: (bldg_center + 3).with_z(bldg_base + (20 * (bldg_size / 2))),
})
.fill(wood.clone());
painter
.cone(Aabb {
min: (bldg_center - 4).with_z(bldg_base + (20 * (bldg_size / 2))),
max: (bldg_center + 4).with_z(bldg_base + (30 * (bldg_size / 2))),
})
.fill(wood.clone());
}
// underground
// rooms
for room_center in rooms {
painter
.rounded_aabb(Aabb {
min: (room_center - room_size - (room_size / 2)).with_z(ground_floor),
max: (room_center + room_size + (room_size / 2)).with_z(base + 5),
})
.fill(brick.clone());
}
let key_chest_index_2 = RandomField::new(0).get(center.with_z(base)) as usize % rooms.len();
for (r, room_center) in rooms.iter().enumerate() {
painter
.rounded_aabb(Aabb {
min: (room_center - room_size).with_z(ground_floor + 1),
max: (room_center + room_size).with_z(base - 2),
})
.clear();
let cells = place_circular(*room_center, room_size as f32, room_size / 2);
let spawns = place_circular(*room_center, (room_size + 2) as f32, room_size / 2);
let cell_floors = (room_size / 6) - 1;
for f in 0..cell_floors {
let cell_floor = ground_floor + (room_size / 2) + ((cell_size_raw * 2) * f);
for cell_pos in &cells {
let cell_var = RandomField::new(0).get(cell_pos.with_z(cell_floor)) as i32 % 2;
let cell_size = cell_size_raw + cell_var;
painter
.rounded_aabb(Aabb {
min: (cell_pos - cell_size).with_z(cell_floor - cell_size),
max: (cell_pos + cell_size).with_z(cell_floor + cell_size),
})
.clear();
}
for spawn_pos in &spawns {
painter
.cylinder(Aabb {
min: (spawn_pos - 3).with_z(cell_floor - cell_size_raw - 1),
max: (spawn_pos + 4).with_z(cell_floor - cell_size_raw),
})
.fill(brick.clone());
painter
.cylinder(Aabb {
min: (spawn_pos - 2).with_z(cell_floor - cell_size_raw),
max: (spawn_pos + 3).with_z(cell_floor - cell_size_raw + 1),
})
.fill(brick.clone());
painter
.cylinder(Aabb {
min: (spawn_pos - 1).with_z(cell_floor - cell_size_raw + 1),
max: (spawn_pos + 2).with_z(cell_floor - cell_size_raw + 2),
})
.fill(brick.clone());
painter.sprite(
spawn_pos.with_z(cell_floor - cell_size_raw + 2),
match (RandomField::new(0)
.get(spawn_pos.with_z(cell_floor - cell_size_raw)))
% 75
{
0 => SpriteKind::DungeonChest2,
_ => SpriteKind::FireBowlGround,
},
);
let npc_pos = spawn_pos.with_z(cell_floor - cell_size_raw + 3);
if RandomField::new(0).get(npc_pos) as i32 % 5 == 1 {
random_npcs.push(npc_pos);
}
}
}
// solid floor
painter
.aabb(Aabb {
min: (room_center - room_size).with_z(ground_floor),
max: (room_center + room_size).with_z(ground_floor + (room_size / 3)),
})
.fill(brick.clone());
for m in 0..2 {
let mini_boss_pos = room_center.with_z(ground_floor + (room_size / 3));
painter.spawn(
EntityInfo::at((mini_boss_pos + (1 * m)).as_()).with_asset_expect(
"common.entity.dungeon.sahagin.hakulaq",
&mut thread_rng,
None,
),
);
}
if r == key_chest_index_2 {
painter.sprite(
(room_center - 1).with_z(ground_floor + (room_size / 3)),
SpriteKind::SahaginChest,
);
}
let center_entry = RandomField::new(0).get(center.with_z(base)) % 4;
if r > 0 {
// overground - keep center clear
let rooms_center =
place_circular(center, (outer_room_radius - 15) as f32, room_size / 2);
let room_base = base - (room_size / 2) + (room_size / 2);
for room_center in &rooms_center {
let room_var =
RandomField::new(0).get(room_center.with_z(room_base)) as i32 % 10;
let room_var_size = room_size - room_var;
painter
.rounded_aabb(Aabb {
min: (room_center - room_var_size).with_z(room_base),
max: (room_center + room_var_size).with_z(room_base + room_var_size),
})
.fill(brick.clone());
}
if r == (center_entry + 1) as usize {
painter
.line(
room_center.with_z(ground_floor + room_size),
center.with_z(ground_floor + room_size),
15.0,
)
.clear();
}
}
}
// tunnels
for p in 0..tunnel_points.len() {
if p == tunnel_points.len() - 1 {
painter
.line(
tunnel_points[p].with_z(ground_floor + (room_size / 2)),
tunnel_points[0].with_z(ground_floor + (room_size / 2)),
5.0,
)
.clear();
} else {
painter
.line(
tunnel_points[p].with_z(ground_floor + (room_size / 2)),
tunnel_points[p + 1].with_z(ground_floor + (room_size / 2)),
5.0,
)
.clear();
}
}
// boss room
painter
.rounded_aabb(Aabb {
min: (center - room_size - 10).with_z(base - 2),
max: (center + room_size + 10).with_z(base + room_size),
})
.fill(brick.clone());
let clear_limiter = painter.aabb(Aabb {
min: (center - room_size - 8).with_z(base + (room_size / 5)),
max: (center + room_size + 8).with_z(base + room_size - 1),
});
painter
.rounded_aabb(Aabb {
min: (center - room_size - 8).with_z(base),
max: (center + room_size + 8).with_z(base + room_size - 1),
})
.intersect(clear_limiter)
.clear();
// lamps
let var_lamps = 25 + RandomField::new(0).get(center.with_z(base)) as i32 % 5;
let lamp_positions = place_circular(center, (room_size + 5) as f32, var_lamps);
for lamp_pos in lamp_positions {
painter.sprite(
lamp_pos.with_z(base + (room_size / 5)),
SpriteKind::FireBowlGround,
);
}
// top entry and stairs
let stair_radius = room_size / 3;
for e in 0..=1 {
let stairs_pos = center - (room_size / 2) + ((room_size * 2) * e);
// top entry foundation and door
if e > 0 {
painter
.rounded_aabb(Aabb {
min: (stairs_pos - stair_radius - 5).with_z(base - (room_size / 2)),
max: (stairs_pos + stair_radius + 5)
.with_z(base + (room_size / 5) + (3 * (room_size / 2))),
})
.fill(brick.clone());
// door clear
painter
.aabb(Aabb {
min: Vec2::new(stairs_pos.x - stair_radius - 8, stairs_pos.y - 2)
.with_z(base + (room_size / 5) + (2 * (room_size / 2))),
max: Vec2::new(stairs_pos.x - stair_radius, stairs_pos.y + 2)
.with_z(base + (room_size / 5) + (2 * (room_size / 2)) + 7),
})
.clear();
painter
.aabb(Aabb {
min: Vec2::new(stairs_pos.x - stair_radius - 8, stairs_pos.y - 1)
.with_z(base + (room_size / 5) + (2 * (room_size / 2)) + 7),
max: Vec2::new(stairs_pos.x - stair_radius, stairs_pos.y + 1)
.with_z(base + (room_size / 5) + (2 * (room_size / 2)) + 8),
})
.clear();
// door
painter
.aabb(Aabb {
min: Vec2::new(stairs_pos.x - stair_radius - 1, stairs_pos.y - 2)
.with_z(base + (room_size / 5) + (2 * (room_size / 2))),
max: Vec2::new(stairs_pos.x - stair_radius, stairs_pos.y + 2)
.with_z(base + (room_size / 5) + (2 * (room_size / 2)) + 7),
})
.fill(key_door.clone());
painter
.aabb(Aabb {
min: Vec2::new(stairs_pos.x - stair_radius - 1, stairs_pos.y - 1)
.with_z(base + (room_size / 5) + (2 * (room_size / 2)) + 7),
max: Vec2::new(stairs_pos.x - stair_radius, stairs_pos.y + 1)
.with_z(base + (room_size / 5) + (2 * (room_size / 2)) + 8),
})
.fill(key_door.clone());
painter
.aabb(Aabb {
min: Vec2::new(stairs_pos.x - stair_radius - 1, stairs_pos.y)
.with_z(base + (room_size / 5) + (2 * (room_size / 2)) + 2),
max: Vec2::new(stairs_pos.x - stair_radius, stairs_pos.y + 1)
.with_z(base + (room_size / 5) + (2 * (room_size / 2)) + 3),
})
.fill(key_hole.clone());
// steps
for s in 0..4 {
painter
.aabb(Aabb {
min: Vec2::new(stairs_pos.x - stair_radius - 2 - s, stairs_pos.y - 2)
.with_z(base + (room_size / 5) + (2 * (room_size / 2)) - 1 - s),
max: Vec2::new(stairs_pos.x - stair_radius - 1 - s, stairs_pos.y + 2)
.with_z(base + (room_size / 5) + (2 * (room_size / 2)) + 7),
})
.clear();
}
} else {
// boss entry 1
// tube
painter
.cylinder(Aabb {
min: (stairs_pos - stair_radius - 4).with_z(base + (room_size / 5)),
max: (stairs_pos + stair_radius + 4).with_z(base + room_size - 2),
})
.fill(brick.clone());
painter
.cylinder(Aabb {
min: (stairs_pos - stair_radius).with_z(base + (room_size / 5)),
max: (stairs_pos + stair_radius).with_z(base + room_size - 3),
})
.clear();
// door clear
painter
.aabb(Aabb {
min: Vec2::new(stairs_pos.x - stair_radius - 3, stairs_pos.y - 2)
.with_z(base + (room_size / 5) + 2),
max: Vec2::new(stairs_pos.x - stair_radius, stairs_pos.y + 2)
.with_z(base + (room_size / 5) + 9),
})
.clear();
painter
.aabb(Aabb {
min: Vec2::new(stairs_pos.x - stair_radius - 3, stairs_pos.y - 1)
.with_z(base + (room_size / 5) + 9),
max: Vec2::new(stairs_pos.x - stair_radius, stairs_pos.y + 1)
.with_z(base + (room_size / 5) + 10),
})
.clear();
// door
painter
.aabb(Aabb {
min: Vec2::new(stairs_pos.x - stair_radius - 4, stairs_pos.y - 2)
.with_z(base + (room_size / 5) + 2),
max: Vec2::new(stairs_pos.x - stair_radius - 3, stairs_pos.y + 2)
.with_z(base + (room_size / 5) + 9),
})
.fill(key_door.clone());
painter
.aabb(Aabb {
min: Vec2::new(stairs_pos.x - stair_radius - 4, stairs_pos.y - 1)
.with_z(base + (room_size / 5) + 9),
max: Vec2::new(stairs_pos.x - stair_radius - 3, stairs_pos.y + 1)
.with_z(base + (room_size / 5) + 10),
})
.fill(key_door.clone());
painter
.aabb(Aabb {
min: Vec2::new(stairs_pos.x - stair_radius - 4, stairs_pos.y)
.with_z(base + (room_size / 5) + 3),
max: Vec2::new(stairs_pos.x - stair_radius - 3, stairs_pos.y + 1)
.with_z(base + (room_size / 5) + 4),
})
.fill(key_hole.clone());
}
let stairs_clear = painter.cylinder(Aabb {
min: (stairs_pos - stair_radius).with_z(ground_floor + (room_size / 3)),
max: (stairs_pos + stair_radius)
.with_z(base + (room_size / 5) + (((3 * (room_size / 2)) - 6) * e)),
});
stairs_clear.clear();
stairs_clear
.sample(spiral_staircase(
stairs_pos.with_z(ground_floor + (room_size / 3)),
(stair_radius + 1) as f32,
2.5,
(room_size - 5) as f32,
))
.fill(wood.clone());
}
// boss entry 2
let boss_entry_pos = center + (room_size / 3);
let rope_pos = center + (room_size / 3) - 2;
let spike_pos = center + (room_size / 3) - 1;
painter
.cylinder(Aabb {
min: (boss_entry_pos - stair_radius).with_z(base + room_size - 5),
max: (boss_entry_pos + stair_radius).with_z(base + (room_size * 2) - 10),
})
.fill(wood.clone());
painter
.cylinder(Aabb {
min: (boss_entry_pos - 3).with_z(base + (room_size * 2) - 10),
max: (boss_entry_pos + 4).with_z(base + (room_size * 2) - 7),
})
.fill(wood.clone());
painter
.cylinder(Aabb {
min: (boss_entry_pos - 2).with_z(base + room_size - 5),
max: (boss_entry_pos + 3).with_z(base + (room_size * 2) - 7),
})
.clear();
painter
.aabb(Aabb {
min: rope_pos.with_z(base + (room_size / 4) + 2),
max: (rope_pos + 1).with_z(base + room_size - 5),
})
.fill(rope.clone());
painter
.cylinder(Aabb {
min: (spike_pos - 3).with_z(base + (room_size * 2) - 7),
max: (spike_pos + 4).with_z(base + (room_size * 2) - 5),
})
.fill(Fill::Block(Block::air(SpriteKind::IronSpike)));
// top room npcs
let npc_pos = boss_entry_pos;
let amount = 4 + RandomField::new(0).get(npc_pos.with_z(base)) as i32 % 4;
for a in 0..amount {
random_npcs.push((npc_pos + a).with_z(base + (room_size / 4)))
}
for m in 0..2 {
painter.spawn(
EntityInfo::at(((npc_pos - m).with_z(base + (room_size / 4))).as_())
.with_asset_expect(
"common.entity.dungeon.sahagin.hakulaq",
&mut thread_rng,
None,
),
);
}
// room npcs
for m in 0..2 {
let mini_boss_pos = center.with_z(base + room_size + 5);
painter.spawn(
EntityInfo::at((mini_boss_pos + (1 * m)).as_()).with_asset_expect(
"common.entity.dungeon.sahagin.hakulaq",
&mut thread_rng,
None,
),
);
}
for pos in random_npcs {
let entities = [
"common.entity.dungeon.sahagin.sniper",
"common.entity.dungeon.sahagin.sniper",
"common.entity.dungeon.sahagin.sniper",
"common.entity.dungeon.sahagin.sorcerer",
"common.entity.dungeon.sahagin.spearman",
];
let npc = entities[(RandomField::new(0).get(pos) % entities.len() as u32) as usize];
painter.spawn(EntityInfo::at(pos.as_()).with_asset_expect(npc, &mut thread_rng, None));
}
}
}
fn place_circular(center: Vec2<i32>, radius: f32, amount: i32) -> Vec<Vec2<i32>> {
let phi = TAU / amount as f32;
let mut positions = vec![];
for n in 1..=amount {
let pos = Vec2::new(
center.x + (radius * ((n as f32 * phi).cos())) as i32,
center.y + (radius * ((n as f32 * phi).sin())) as i32,
);
positions.push(pos);
}
positions
}