diff --git a/world/src/layer/spot.rs b/world/src/layer/spot.rs index 39ca05a504..e3b9be1938 100644 --- a/world/src/layer/spot.rs +++ b/world/src/layer/spot.rs @@ -478,19 +478,19 @@ pub fn apply_spots_to(canvas: &mut Canvas, _dynamic_rng: &mut impl Rng) { base_structures: Some("site_structures.gnarling.totem"), entity_radius: 30.0, entities: &[ - (3..5, "common.entity.dungeon.tier-0.mugger"), - (3..5, "common.entity.dungeon.tier-0.stalker"), - (3..5, "common.entity.dungeon.tier-0.logger"), - (2..4, "common.entity.dungeon.tier-0.chieftain"), + (3..5, "common.entity.dungeon.gnarling.mugger"), + (3..5, "common.entity.dungeon.gnarling.stalker"), + (3..5, "common.entity.dungeon.gnarling.logger"), + (2..4, "common.entity.dungeon.gnarling.chieftain"), ], }, Spot::GnarlingTree => SpotConfig { base_structures: Some("spots_grasslands.gnarling_tree"), entity_radius: 64.0, entities: &[ - (1..5, "common.entity.dungeon.tier-0.mugger"), - (2..4, "common.entity.dungeon.tier-0.stalker"), - (1..2, "common.entity.dungeon.tier-0.logger"), + (1..5, "common.entity.dungeon.gnarling.mugger"), + (2..4, "common.entity.dungeon.gnarling.stalker"), + (1..2, "common.entity.dungeon.gnarling.logger"), (1..4, "common.entity.wild.aggressive.deadwood"), ], }, diff --git a/world/src/site2/plot/dungeon.rs b/world/src/site2/plot/dungeon.rs index d847eca12f..7e25b67895 100644 --- a/world/src/site2/plot/dungeon.rs +++ b/world/src/site2/plot/dungeon.rs @@ -227,7 +227,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 { - 0 => enemy_0(dynamic_rng, tile_wcenter), 1 => enemy_1(dynamic_rng, tile_wcenter), 2 => enemy_2(dynamic_rng, tile_wcenter), 3 => enemy_3(dynamic_rng, tile_wcenter), @@ -674,22 +673,6 @@ impl Floor { } } -fn enemy_0(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3) -> Vec { - 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.tier-0.mugger", dynamic_rng), - 1 => entity.with_asset_expect("common.entity.dungeon.tier-0.stalker", dynamic_rng), - _ => entity.with_asset_expect("common.entity.dungeon.tier-0.logger", dynamic_rng), - } - }); - - entities -} - fn enemy_1(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3) -> Vec { let number = dynamic_rng.gen_range(2..=4); let mut entities = Vec::new(); @@ -1471,7 +1454,6 @@ mod tests { fn test_creating_enemies() { let mut dynamic_rng = rand::thread_rng(); let random_position = Vec3::new(0, 0, 0); - enemy_0(&mut dynamic_rng, random_position); enemy_1(&mut dynamic_rng, random_position); enemy_2(&mut dynamic_rng, random_position); enemy_3(&mut dynamic_rng, random_position); diff --git a/world/src/site2/plot/gnarling.rs b/world/src/site2/plot/gnarling.rs index 10ffd47c2b..ebda046c18 100644 --- a/world/src/site2/plot/gnarling.rs +++ b/world/src/site2/plot/gnarling.rs @@ -1,5 +1,5 @@ use super::*; -use crate::{assets::AssetHandle, util::attempt, Land}; +use crate::{assets::AssetHandle, site2::util::Dir, util::attempt, Land}; use common::{ generation::{ChunkSupplement, EntityInfo}, terrain::{Structure as PrefabStructure, StructuresGroup}, @@ -24,15 +24,20 @@ pub struct GnarlingFortification { enum GnarlingStructure { Hut, + Longhut, Totem, ChieftainHut, WatchTower, + Banner, } impl GnarlingStructure { fn required_separation(&self, other: &Self) -> i32 { match (self, other) { (Self::Hut, Self::Hut) => 15, + (_, Self::Longhut) | (Self::Longhut, _) => 25, + (_, Self::Banner) | (Self::Banner, _) => 10, + (Self::Hut, Self::Totem) | (Self::Totem, Self::Hut) => 20, (Self::Totem, Self::Totem) => 50, // Chieftain hut and watch tower generated in separate pass without distance check @@ -126,6 +131,8 @@ impl GnarlingFortification { // Choose structure kind let structure_kind = match rng.gen_range(0..10) { 0 => GnarlingStructure::Totem, + 1..=3 => GnarlingStructure::Longhut, + //3..=8 => GnarlingStructure::Banner, _ => GnarlingStructure::Hut, }; @@ -282,27 +289,36 @@ impl GnarlingFortification { GnarlingStructure::Hut => { supplement.add_entity(random_gnarling(wpos, dynamic_rng)); }, + GnarlingStructure::Longhut => { + supplement.add_entity(random_gnarling(wpos, dynamic_rng)); + }, + GnarlingStructure::Banner => { + supplement.add_entity(random_gnarling(wpos, dynamic_rng)); + }, GnarlingStructure::ChieftainHut => { - supplement.add_entity(gnarling_chieftain(wpos)); + supplement.add_entity(gnarling_chieftain(wpos, dynamic_rng)); let left_inner_guard_pos = wpos + ori.dir() * 8 + ori.cw().dir() * 2; - supplement.add_entity(wood_golem(left_inner_guard_pos)); + supplement.add_entity(wood_golem(left_inner_guard_pos, dynamic_rng)); let right_inner_guard_pos = wpos + ori.dir() * 8 + ori.ccw().dir() * 2; - supplement.add_entity(wood_golem(right_inner_guard_pos)); + supplement.add_entity(wood_golem(right_inner_guard_pos, dynamic_rng)); let left_outer_guard_pos = wpos + ori.dir() * 16 + ori.cw().dir() * 2; supplement.add_entity(random_gnarling(left_outer_guard_pos, dynamic_rng)); let right_outer_guard_pos = wpos + ori.dir() * 16 + ori.ccw().dir() * 2; supplement.add_entity(random_gnarling(right_outer_guard_pos, dynamic_rng)); }, GnarlingStructure::WatchTower => { - supplement.add_entity(wood_golem(wpos)); + supplement.add_entity(wood_golem(wpos, dynamic_rng)); let spawn_pos = wpos.xy().with_z(wpos.z + 27); for _ in 0..4 { - supplement.add_entity(gnarling_stalker(spawn_pos + Vec2::broadcast(4))); + supplement.add_entity(gnarling_stalker( + spawn_pos + Vec2::broadcast(4), + dynamic_rng, + )); } }, GnarlingStructure::Totem => { let spawn_pos = wpos + pos.xy().map(|x| x.signum() * -5); - supplement.add_entity(wood_golem(spawn_pos)); + supplement.add_entity(wood_golem(spawn_pos, dynamic_rng)); }, } } @@ -312,7 +328,8 @@ impl GnarlingFortification { let wpos = *pos + self.origin; if area.contains_point(pos.xy()) { for _ in 0..4 { - supplement.add_entity(gnarling_stalker(wpos.xy().with_z(wpos.z + 21))) + supplement + .add_entity(gnarling_stalker(wpos.xy().with_z(wpos.z + 21), dynamic_rng)) } } } @@ -374,7 +391,7 @@ impl Structure for GnarlingFortification { .segment_prism(start, end, wall_mid_thickness, wall_mid_height) .fill(Fill::Block(Block::new( BlockKind::Wood, - Rgb::new(55, 25, 8), + Rgb::new(115, 58, 26), ))); // Top of wall @@ -521,8 +538,8 @@ impl Structure for GnarlingFortification { )) .intersect(roof_cyl) .fill(Fill::Block(Block::new( - BlockKind::Wood, - Rgb::new(55, 25, 8), + BlockKind::Leaves, + Rgb::new(22, 36, 20), ))); }); @@ -558,7 +575,7 @@ impl Structure for GnarlingFortification { .prim(Primitive::cylinder(floor_pos, hut_radius, hut_wall_height)) .fill(Fill::Block(Block::new( BlockKind::Wood, - Rgb::new(55, 25, 8), + Rgb::new(115, 58, 26), ))); painter .prim(Primitive::cylinder( @@ -602,10 +619,271 @@ impl Structure for GnarlingFortification { roof_radius, roof_height, )) + .fill(Fill::Block(Block::new( + BlockKind::Leaves, + Rgb::new(22, 36, 20), + ))); + } + + fn generate_longhut( + painter: &Painter, + wpos: Vec2, + alt: i32, + door_dir: Ori, + hut_radius: f32, + hut_wall_height: f32, + door_height: i32, + roof_height: f32, + roof_overhang: f32, + ) { + // Floor + let raise = 5; + let base = wpos.with_z(alt); + painter + .prim(Primitive::cylinder(base, hut_radius + 1.0, 2.0)) .fill(Fill::Block(Block::new( BlockKind::Wood, Rgb::new(55, 25, 8), ))); + + let platform = painter.aabb(Aabb { + min: (wpos - 13).with_z(alt + raise), + max: (wpos + 13).with_z(alt + raise + 1), + }); + + painter.fill( + platform, + Fill::Block(Block::new(BlockKind::Wood, Rgb::new(55, 25, 8))), + ); + + let support_1 = painter.line( + (wpos - 12).with_z(alt - 3), + (wpos - 12).with_z(alt + raise), + 2.0, + ); + let support_alt = painter.line( + (wpos - 12).with_z(alt - 3), + (wpos - 12).with_z(alt + raise), + 2.0, + ); + let support_2 = support_1.translate(Vec3::new(0, 23, 0)); + let support_3 = support_1.translate(Vec3::new(23, 0, 0)); + let support_4 = support_1.translate(Vec3::new(23, 23, 0)); + let support_5 = support_alt.translate(Vec3::new(0, 12, 0)); + let support_6 = support_alt.translate(Vec3::new(12, 0, 0)); + let support_7 = support_alt.translate(Vec3::new(12, 23, 0)); + let support_8 = support_alt.translate(Vec3::new(23, 12, 0)); + let supports = support_1 + .union(support_2) + .union(support_3) + .union(support_4) + .union(support_5) + .union(support_6) + .union(support_7) + .union(support_8); + + painter.fill( + supports, + Fill::Block(Block::new(BlockKind::Wood, Rgb::new(55, 25, 8))), + ); + let height_1 = 6.0; + let height_2 = 8.0; + let rad_1 = 11.0; + let rad_2 = 8.0; + + // Wall + let floor_pos = wpos.with_z(alt + 1 + raise); + painter + .prim(Primitive::cylinder(floor_pos, rad_1, height_1)) + .fill(Fill::Block(Block::new( + BlockKind::Wood, + Rgb::new(115, 58, 26), + ))); + painter + .prim(Primitive::cylinder(floor_pos, rad_1 - 1.0, height_1)) + .fill(Fill::Block(Block::empty())); + + let floor2_pos = wpos.with_z(alt + 1 + raise + height_1 as i32); + painter + .prim(Primitive::cylinder(floor2_pos, rad_2, height_2)) + .fill(Fill::Block(Block::new( + BlockKind::Wood, + Rgb::new(115, 58, 26), + ))); + + // Roof + let roof_radius = rad_1 + 4.0; + let roof1 = painter.prim(Primitive::cone( + wpos.with_z(alt + 1 + height_1 as i32 + raise), + roof_radius, + roof_height, + )); + roof1.fill(Fill::Block(Block::new( + BlockKind::Leaves, + Rgb::new(22, 36, 20), + ))); + let roof_radius = rad_2 + 1.0; + painter + .prim(Primitive::cone( + wpos.with_z(alt + 1 + height_1 as i32 + height_2 as i32 + raise), + roof_radius, + roof_height, + )) + .fill(Fill::Block(Block::new( + BlockKind::Leaves, + Rgb::new(22, 36, 20), + ))); + let roof_support_1 = painter.line( + (wpos + 1).with_z(alt + raise + height_1 as i32 - 3), + (wpos + 10).with_z(alt + raise + height_1 as i32), + 1.5, + ); + let roof_strut = painter.line( + (wpos + 1).with_z(alt + raise + height_1 as i32 + 2), + (wpos + 10).with_z(alt + raise + height_1 as i32 + 2), + 1.0, + ); + let wall2support = painter.line( + (wpos + rad_2 as i32 - 3).with_z(alt + raise + height_1 as i32 + 6), + (wpos + rad_2 as i32 - 3).with_z(alt + raise + height_1 as i32 + 8), + 1.0, + ); + let wall2roof = painter.line( + (wpos + rad_2 as i32 - 4).with_z(alt + raise + height_1 as i32 + 9), + (wpos + rad_2 as i32 - 3).with_z(alt + raise + height_1 as i32 + 9), + 2.0, + ); + + let roof_support_1 = roof_support_1 + .union(roof_strut) + .union(wall2support) + .union(wall2roof); + + let roof_support_2 = + roof_support_1.rotate(Mat3::new(1, 0, 0, 0, -1, 0, 0, 0, 1)); + let roof_support_3 = + roof_support_1.rotate(Mat3::new(-1, 0, 0, 0, 1, 0, 0, 0, 1)); + let roof_support_4 = + roof_support_1.rotate(Mat3::new(-1, 0, 0, 0, -1, 0, 0, 0, 1)); + let roof_support = roof_support_1 + .union(roof_support_2) + .union(roof_support_3) + .union(roof_support_4); + + painter.fill( + roof_support, + Fill::Block(Block::new(BlockKind::Wood, Rgb::new(55, 25, 8))), + ); + + let spot = painter.line( + (wpos + 1).with_z(alt + raise + height_1 as i32 + 2), + (wpos + 1).with_z(alt + raise + height_1 as i32 + 2), + 1.0, + ); + let spike_1 = painter.line( + (wpos + rad_2 as i32 - 1).with_z(alt + raise + height_1 as i32 + 2), + (wpos + rad_2 as i32 + 2).with_z(alt + raise + height_1 as i32 + 6), + 1.0, + ); + + let spike_1 = spot.union(spike_1); + + let spike_2 = spike_1.rotate(Mat3::new(1, 0, 0, 0, -1, 0, 0, 0, 1)); + let spike_3 = spike_1.rotate(Mat3::new(-1, 0, 0, 0, 1, 0, 0, 0, 1)); + let spike_4 = spike_1.rotate(Mat3::new(-1, 0, 0, 0, -1, 0, 0, 0, 1)); + let spikes = spike_1.union(spike_2).union(spike_3).union(spike_4); + + painter.fill( + spikes, + Fill::Block(Block::new(BlockKind::Wood, Rgb::new(184, 177, 134))), + ); + //Open doorways (top floor) + painter + .aabb(Aabb { + min: Vec2::new(wpos.x - 2, wpos.y - 15) + .with_z(alt + raise + height_1 as i32 + 3), + max: Vec2::new(wpos.x + 2, wpos.y + 15) + .with_z(alt + raise + height_1 as i32 + 8), + }) + .fill(Fill::Block(Block::empty())); + painter + .aabb(Aabb { + min: Vec2::new(wpos.x - 3, wpos.y - 15) + .with_z(alt + raise + height_1 as i32 + 4), + max: Vec2::new(wpos.x + 3, wpos.y + 15) + .with_z(alt + raise + height_1 as i32 + 7), + }) + .fill(Fill::Block(Block::empty())); + painter + .aabb(Aabb { + min: Vec2::new(wpos.x - 15, wpos.y - 2) + .with_z(alt + raise + height_1 as i32 + 3), + max: Vec2::new(wpos.x + 15, wpos.y + 2) + .with_z(alt + raise + height_1 as i32 + 8), + }) + .fill(Fill::Block(Block::empty())); + painter + .aabb(Aabb { + min: Vec2::new(wpos.x - 15, wpos.y - 3) + .with_z(alt + raise + height_1 as i32 + 4), + max: Vec2::new(wpos.x + 15, wpos.y + 3) + .with_z(alt + raise + height_1 as i32 + 7), + }) + .fill(Fill::Block(Block::empty())); + let rafter = painter + .aabb(Aabb { + min: Vec2::new(wpos.x - 15, wpos.y - 1) + .with_z(alt + raise + height_1 as i32 - 3), + max: Vec2::new(wpos.x + 15, wpos.y + 1) + .with_z(alt + raise + height_1 as i32 + 7), + }) + .intersect(roof1) + .translate(Vec3::new(0, 0, -1)) + .fill(Fill::Block(Block::new( + BlockKind::Leaves, + Rgb::new(55, 25, 8), + ))); + //Roofing details + painter + .aabb(Aabb { + min: Vec2::new(wpos.x - 15, wpos.y - 1) + .with_z(alt + raise + height_1 as i32 - 3), + max: Vec2::new(wpos.x + 15, wpos.y + 1) + .with_z(alt + raise + height_1 as i32 + 7), + }) + .intersect(roof1) + .fill(Fill::Block(Block::new( + BlockKind::Leaves, + Rgb::new(102, 31, 24), + ))); + painter + .aabb(Aabb { + min: Vec2::new(wpos.x - 1, wpos.y - 15) + .with_z(alt + raise + height_1 as i32 - 3), + max: Vec2::new(wpos.x + 1, wpos.y + 15) + .with_z(alt + raise + height_1 as i32 + 7), + }) + .intersect(roof1) + .translate(Vec3::new(0, 0, -1)) + .fill(Fill::Block(Block::new( + BlockKind::Leaves, + Rgb::new(55, 25, 8), + ))); + let color = painter + .aabb(Aabb { + min: Vec2::new(wpos.x - 1, wpos.y - 15) + .with_z(alt + raise + height_1 as i32 - 3), + max: Vec2::new(wpos.x + 1, wpos.y + 15) + .with_z(alt + raise + height_1 as i32 + 7), + }) + .intersect(roof1) + .fill(Fill::Block(Block::new( + BlockKind::Leaves, + Rgb::new(102, 31, 24), + ))); + painter + .prim(Primitive::cylinder(floor2_pos, rad_2 - 1.0, height_2)) + .fill(Fill::Block(Block::empty())); } match kind { @@ -628,6 +906,25 @@ impl Structure for GnarlingFortification { roof_overhang, ); }, + GnarlingStructure::Longhut => { + let hut_radius = 5.0; + let hut_wall_height = 15.0; + let door_height = 3; + let roof_height = 3.0; + let roof_overhang = 1.0; + + generate_longhut( + painter, + wpos, + alt, + *door_dir, + hut_radius, + hut_wall_height, + door_height, + roof_height, + roof_overhang, + ); + }, GnarlingStructure::Totem => { let totem_pos = wpos.with_z(alt); @@ -681,6 +978,42 @@ impl Structure for GnarlingFortification { roof_overhang, ); }, + + GnarlingStructure::Banner => { + painter + .aabb(Aabb { + min: (wpos - 1).with_z(alt - 3), + max: (wpos).with_z(alt + 30), + }) + .fill(Fill::Block(Block::new( + BlockKind::Leaves, + Rgb::new(55, 25, 8), + ))); + + painter + .line( + Vec2::new(wpos.x - 5, wpos.y - 1).with_z(alt + 25), + Vec2::new(wpos.x + 8, wpos.y - 1).with_z(alt + 38), + 0.9, + ) + .fill(Fill::Block(Block::new( + BlockKind::Leaves, + Rgb::new(55, 25, 8), + ))); + painter + .plane( + Aabr { + min: Vec2::new(wpos.x + 1, wpos.y - 1), + max: Vec2::new(wpos.x + 8, wpos.y), + }, + (wpos + 10).with_z(alt + 20), + Vec2::new(-1.0, 1.0), + ) + .fill(Fill::Block(Block::new( + BlockKind::Leaves, + Rgb::new(102, 31, 24), + ))); + }, GnarlingStructure::WatchTower => { let platform_1_height = 14; let platform_2_height = 20; @@ -834,7 +1167,7 @@ impl Structure for GnarlingFortification { max: (wpos + 9).with_z(alt + roof_height + 4), }, 1, - true, + Dir::Y, ) .without( painter.gable( @@ -845,7 +1178,7 @@ impl Structure for GnarlingFortification { .with_z(alt + roof_height + 3), }, 1, - true, + Dir::Y, ), ); @@ -894,48 +1227,50 @@ impl Structure for GnarlingFortification { } } -fn gnarling_mugger(pos: Vec3) -> EntityInfo { - EntityInfo::at(pos.map(|x| x as f32)).with_asset_expect("common.entity.dungeon.gnarling.mugger") -} - -fn gnarling_stalker(pos: Vec3) -> EntityInfo { +fn gnarling_mugger(pos: Vec3, rng: &mut R) -> EntityInfo { EntityInfo::at(pos.map(|x| x as f32)) - .with_asset_expect("common.entity.dungeon.gnarling.stalker") + .with_asset_expect("common.entity.dungeon.gnarling.mugger", rng) } -fn gnarling_logger(pos: Vec3) -> EntityInfo { - EntityInfo::at(pos.map(|x| x as f32)).with_asset_expect("common.entity.dungeon.gnarling.logger") +fn gnarling_stalker(pos: Vec3, rng: &mut R) -> EntityInfo { + EntityInfo::at(pos.map(|x| x as f32)) + .with_asset_expect("common.entity.dungeon.gnarling.stalker", rng) } -fn random_gnarling(pos: Vec3, dynamic_rng: &mut impl Rng) -> EntityInfo { - match dynamic_rng.gen_range(0..3) { - 0 => gnarling_logger(pos), - 1 => gnarling_mugger(pos), - _ => gnarling_stalker(pos), +fn gnarling_logger(pos: Vec3, rng: &mut R) -> EntityInfo { + EntityInfo::at(pos.map(|x| x as f32)) + .with_asset_expect("common.entity.dungeon.gnarling.logger", rng) +} + +fn random_gnarling(pos: Vec3, rng: &mut R) -> EntityInfo { + match rng.gen_range(0..3) { + 0 => gnarling_logger(pos, rng), + 1 => gnarling_mugger(pos, rng), + _ => gnarling_stalker(pos, rng), } } -fn gnarling_chieftain(pos: Vec3) -> EntityInfo { +fn gnarling_chieftain(pos: Vec3, rng: &mut R) -> EntityInfo { EntityInfo::at(pos.map(|x| x as f32)) - .with_asset_expect("common.entity.dungeon.gnarling.chieftain") + .with_asset_expect("common.entity.dungeon.gnarling.chieftain", rng) } -fn deadwood(pos: Vec3) -> EntityInfo { +fn deadwood(pos: Vec3, rng: &mut R) -> EntityInfo { EntityInfo::at(pos.map(|x| x as f32)) - .with_asset_expect("common.entity.dungeon.gnarling.deadwood") + .with_asset_expect("common.entity.dungeon.gnarling.deadwood", rng) } -fn mandragora(pos: Vec3) -> EntityInfo { +fn mandragora(pos: Vec3, rng: &mut R) -> EntityInfo { EntityInfo::at(pos.map(|x| x as f32)) - .with_asset_expect("common.entity.dungeon.gnarling.mandragora") + .with_asset_expect("common.entity.dungeon.gnarling.mandragora", rng) } -fn wood_golem(pos: Vec3) -> EntityInfo { +fn wood_golem(pos: Vec3, rng: &mut R) -> EntityInfo { EntityInfo::at(pos.map(|x| x as f32)) - .with_asset_expect("common.entity.dungeon.gnarling.woodgolem") + .with_asset_expect("common.entity.dungeon.gnarling.woodgolem", rng) } -fn harvester_boss(pos: Vec3) -> EntityInfo { +fn harvester_boss(pos: Vec3, rng: &mut R) -> EntityInfo { EntityInfo::at(pos.map(|x| x as f32)) - .with_asset_expect("common.entity.dungeon.gnarling.harvester") + .with_asset_expect("common.entity.dungeon.gnarling.harvester", rng) }