diff --git a/CHANGELOG.md b/CHANGELOG.md index 77265cb645..9eaa784d17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Admin command to reload all chunks on the server - Furniture and waypoints in site2 towns - text input for trading +- Themed Site CliffTown, hoodoo/arabic inspired stone structures inhabited by mountaineer NPCs. ### Changed diff --git a/assets/common/entity/village/mountaineer.ron b/assets/common/entity/village/mountaineer.ron new file mode 100644 index 0000000000..8dfddd9405 --- /dev/null +++ b/assets/common/entity/village/mountaineer.ron @@ -0,0 +1,22 @@ +( + name: Name("Mountaineer"), + body: RandomWith("orc"), + alignment: Alignment(Npc), + + loadout: Extended( + hands: TwoHanded(Choice([ + (1, Some(Item("common.items.weapons.tool.broom"))), + (1, Some(Item("common.items.weapons.tool.hoe"))), + (1, Some(Item("common.items.weapons.tool.pickaxe"))), + (1, Some(Item("common.items.weapons.tool.rake"))), + (1, Some(Item("common.items.weapons.tool.shovel-0"))), + (1, Some(Item("common.items.weapons.tool.shovel-1"))), + ])), + base_asset: Loadout("common.loadout.village.mountaineer"), + inventory: [ + (10, "common.items.consumable.potion_big"), + ], + ), + loot: LootTable("common.loot_tables.creature.humanoid"), + meta: [], +) diff --git a/assets/common/loadout/village/mountaineer.ron b/assets/common/loadout/village/mountaineer.ron new file mode 100644 index 0000000000..55080cd1b4 --- /dev/null +++ b/assets/common/loadout/village/mountaineer.ron @@ -0,0 +1,39 @@ +({ + Armor(Belt): Choice([ + (1.0, Some(Item("common.items.armor.cloth_blue.belt"))), + (1.0, Some(Item("common.items.armor.cloth_green.belt"))), + (1.0, Some(Item("common.items.armor.cloth_purple.belt"))), + ]), + Armor(Chest): Choice([ + (1.0, Some(Item("common.items.armor.cloth_blue.chest"))), + (1.0, Some(Item("common.items.armor.cloth_green.chest"))), + (1.0, Some(Item("common.items.armor.cloth_purple.chest"))), + ]), + Armor(Feet): Choice([ + (1.0, Some(Item("common.items.armor.cloth_blue.foot"))), + (1.0, Some(Item("common.items.armor.cloth_green.foot"))), + (1.0, Some(Item("common.items.armor.cloth_purple.foot"))), + ]), + Armor(Hands): Choice([ + (1.0, Some(Item("common.items.armor.cloth_blue.hand"))), + (1.0, Some(Item("common.items.armor.cloth_green.hand"))), + (1.0, Some(Item("common.items.armor.cloth_purple.hand"))), + ]), + Armor(Legs): Choice([ + (1.0, Some(Item("common.items.armor.cloth_blue.pants"))), + (1.0, Some(Item("common.items.armor.cloth_green.pants"))), + (1.0, Some(Item("common.items.armor.cloth_purple.pants"))), + ]), + Armor(Shoulders): Choice([ + (1.0, Some(Item("common.items.armor.cloth_blue.shoulder_0"))), + (1.0, Some(Item("common.items.armor.cloth_blue.shoulder_1"))), + (1.0, Some(Item("common.items.armor.cloth_green.shoulder"))), + (1.0, Some(Item("common.items.armor.cloth_purple.shoulder"))), + ]), + Armor(Head): Choice([ + // Christmas event + //(1.0, Some(Item("common.items.calendar.christmas.armor.misc.head.woolly_wintercap"))), + (1.0, Some(Item("common.items.armor.misc.head.headband"))), + (2.0, None), + ]), +}) diff --git a/assets/voxygen/voxel/sprite/furniture/books_arabic.vox b/assets/voxygen/voxel/sprite/furniture/books_arabic.vox new file mode 100644 index 0000000000..829fb3141b --- /dev/null +++ b/assets/voxygen/voxel/sprite/furniture/books_arabic.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:591e01ca25af0c62b6fdd5a7ed09a7600d7e397b64b7fbc1f3d5e57b900ed916 +size 15442 diff --git a/assets/voxygen/voxel/sprite/furniture/bookshelf_arabic.vox b/assets/voxygen/voxel/sprite/furniture/bookshelf_arabic.vox new file mode 100644 index 0000000000..2abf29095c --- /dev/null +++ b/assets/voxygen/voxel/sprite/furniture/bookshelf_arabic.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2af65cfac83689db2d9479402a644aded106aa73f6c15151b311cf8aa49bb257 +size 11608 diff --git a/assets/voxygen/voxel/sprite/furniture/canape_arabic.vox b/assets/voxygen/voxel/sprite/furniture/canape_arabic.vox new file mode 100644 index 0000000000..c8cfe2e6ad --- /dev/null +++ b/assets/voxygen/voxel/sprite/furniture/canape_arabic.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:26846b096b00907d0960e074d8eb51aa5a5f6f965426c1f54ad430dfc171b48f +size 20680 diff --git a/assets/voxygen/voxel/sprite/furniture/cliff_decor_block.vox b/assets/voxygen/voxel/sprite/furniture/cliff_decor_block.vox new file mode 100644 index 0000000000..2126ab7534 --- /dev/null +++ b/assets/voxygen/voxel/sprite/furniture/cliff_decor_block.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8ffe288a88cf4cb979c037aeee808a1391fb4deffc220747383a33918022948a +size 6420 diff --git a/assets/voxygen/voxel/sprite/furniture/cupboard_arabic.vox b/assets/voxygen/voxel/sprite/furniture/cupboard_arabic.vox new file mode 100644 index 0000000000..14a5733b23 --- /dev/null +++ b/assets/voxygen/voxel/sprite/furniture/cupboard_arabic.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:42ccbac411cd7aa1e00d82eeec5f6ba8b3f4eb976f0bd4d32313c1959308ce1e +size 13016 diff --git a/assets/voxygen/voxel/sprite/furniture/cushion_arabic.vox b/assets/voxygen/voxel/sprite/furniture/cushion_arabic.vox new file mode 100644 index 0000000000..a30c70c135 --- /dev/null +++ b/assets/voxygen/voxel/sprite/furniture/cushion_arabic.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eefc3c0297a510da3b1d1f79c547c0ffb9fa55f20245df93ad406ab838f48941 +size 2972 diff --git a/assets/voxygen/voxel/sprite/furniture/decor_set_arabic-0.vox b/assets/voxygen/voxel/sprite/furniture/decor_set_arabic-0.vox new file mode 100644 index 0000000000..a930b0c68b --- /dev/null +++ b/assets/voxygen/voxel/sprite/furniture/decor_set_arabic-0.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dd506b861fdc79fb35949971180027121dd43ec7b81dac974fc826dc520ef4cc +size 6572 diff --git a/assets/voxygen/voxel/sprite/furniture/forge_tools.vox b/assets/voxygen/voxel/sprite/furniture/forge_tools.vox new file mode 100644 index 0000000000..bae4eecfd3 --- /dev/null +++ b/assets/voxygen/voxel/sprite/furniture/forge_tools.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:af54e11fe7d9f0bb3ee66d0861a20ac4c6aa137b3f7597cc085cbb27002f01f5 +size 4100 diff --git a/assets/voxygen/voxel/sprite/furniture/fountain_arabic.vox b/assets/voxygen/voxel/sprite/furniture/fountain_arabic.vox new file mode 100644 index 0000000000..94c90fd3c2 --- /dev/null +++ b/assets/voxygen/voxel/sprite/furniture/fountain_arabic.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5a3d13abcf36fca0aa83cb6585a96b1f24894e093acbe24351cb6ecf22aa96e0 +size 44136 diff --git a/assets/voxygen/voxel/sprite/furniture/hearth.vox b/assets/voxygen/voxel/sprite/furniture/hearth.vox new file mode 100644 index 0000000000..b2f5706b13 --- /dev/null +++ b/assets/voxygen/voxel/sprite/furniture/hearth.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bf2906582c9e079c52f01665211a288719be33b78bfa0f3f744f9946eeff89d3 +size 21888 diff --git a/assets/voxygen/voxel/sprite/furniture/jug_and_bowl_arabic.vox b/assets/voxygen/voxel/sprite/furniture/jug_and_bowl_arabic.vox new file mode 100644 index 0000000000..c4f26a30b7 --- /dev/null +++ b/assets/voxygen/voxel/sprite/furniture/jug_and_bowl_arabic.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7c7cda95bc9385a062596e2dd5095391bc2a04a1df502b324db20ebb6749eb8f +size 3296 diff --git a/assets/voxygen/voxel/sprite/furniture/jug_arabic.vox b/assets/voxygen/voxel/sprite/furniture/jug_arabic.vox new file mode 100644 index 0000000000..a395600dd6 --- /dev/null +++ b/assets/voxygen/voxel/sprite/furniture/jug_arabic.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e37cd7b6d7cc68076e0f6f142302322874724fa6a4d7fbfe9086a2a69b049b09 +size 2688 diff --git a/assets/voxygen/voxel/sprite/furniture/melon_cut.vox b/assets/voxygen/voxel/sprite/furniture/melon_cut.vox new file mode 100644 index 0000000000..dbd7f4fc5b --- /dev/null +++ b/assets/voxygen/voxel/sprite/furniture/melon_cut.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cdcde7c122e3c7f64c21d775d9d2fb22afdb66c6c149b53424eeac6520801683 +size 3784 diff --git a/assets/voxygen/voxel/sprite/furniture/oven_arabic.vox b/assets/voxygen/voxel/sprite/furniture/oven_arabic.vox new file mode 100644 index 0000000000..fdf46d6e0d --- /dev/null +++ b/assets/voxygen/voxel/sprite/furniture/oven_arabic.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9df57fcfe249a3be70c40f4b81cd266ae2007611fd5d82d438010fa9ab37ab77 +size 13240 diff --git a/assets/voxygen/voxel/sprite/furniture/separe_arabic.vox b/assets/voxygen/voxel/sprite/furniture/separe_arabic.vox new file mode 100644 index 0000000000..091bfaf121 --- /dev/null +++ b/assets/voxygen/voxel/sprite/furniture/separe_arabic.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8653f4d2afd3855ea95b9cb3b3794e3f926da205ed5abd4d6144778abbbf46ec +size 5740 diff --git a/assets/voxygen/voxel/sprite/furniture/table_arabic_large.vox b/assets/voxygen/voxel/sprite/furniture/table_arabic_large.vox new file mode 100644 index 0000000000..e9431702a8 --- /dev/null +++ b/assets/voxygen/voxel/sprite/furniture/table_arabic_large.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:de5ea5cdccf66dfc836f83f2af673b01967fac34e56053217b1c31d7254aa61d +size 14920 diff --git a/assets/voxygen/voxel/sprite/furniture/table_arabic_small.vox b/assets/voxygen/voxel/sprite/furniture/table_arabic_small.vox new file mode 100644 index 0000000000..5895dd0926 --- /dev/null +++ b/assets/voxygen/voxel/sprite/furniture/table_arabic_small.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6f87bda0d2a8950b39950db2e58c015643f4ffab671b0f48e98f151073ffab4b +size 3216 diff --git a/assets/voxygen/voxel/sprite/furniture/wall_table_arabic.vox b/assets/voxygen/voxel/sprite/furniture/wall_table_arabic.vox new file mode 100644 index 0000000000..79cf462392 --- /dev/null +++ b/assets/voxygen/voxel/sprite/furniture/wall_table_arabic.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7f21271ba35775c51e01cf5d56a0c41323dbf53a0de64dac092d357867afad9d +size 6676 diff --git a/assets/voxygen/voxel/sprite/window/window_arabic.vox b/assets/voxygen/voxel/sprite/window/window_arabic.vox new file mode 100644 index 0000000000..8538246160 --- /dev/null +++ b/assets/voxygen/voxel/sprite/window/window_arabic.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fa3fc30b1829d4a1f48ff0f424aeb3dd1b2459750dabdfd2c979c3c2312dcc50 +size 1576 diff --git a/assets/voxygen/voxel/sprite_manifest.ron b/assets/voxygen/voxel/sprite_manifest.ron index 3b343f5e2b..4a35d97a50 100644 --- a/assets/voxygen/voxel/sprite_manifest.ron +++ b/assets/voxygen/voxel/sprite_manifest.ron @@ -3323,6 +3323,187 @@ Lantern: Some(( ], wind_sway: 0.0, )), +// CliffTown Decor +WindowArabic: Some(( + variations: [ + ( + model: "voxygen.voxel.sprite.window.window_arabic", + offset: (-5.5, -5.5, 0.0), + lod_axes: (0.0, 0.0, 0.0), + ), + ], + wind_sway: 0.0, +)), +BookshelfArabic: Some(( + variations: [ + ( + model: "voxygen.voxel.sprite.furniture.bookshelf_arabic", + offset: (-22.0, -5.5, -6.0), + lod_axes: (0.0, 0.0, 0.0), + ), + ], + wind_sway: 0.0, +)), +DecorSetArabic: Some(( + variations: [ + ( + model: "voxygen.voxel.sprite.furniture.decor_set_arabic-0", + offset: (-10.5, -10.5, 0.0), + lod_axes: (0.0, 0.0, 0.0), + ), + ], + wind_sway: 0.0, +)), +SepareArabic: Some(( + variations: [ + ( + model: "voxygen.voxel.sprite.furniture.separe_arabic", + offset: (-5.5, -5.5, 0.0), + lod_axes: (0.0, 0.0, 0.0), + ), + ], + wind_sway: 0.0, +)), +CushionArabic: Some(( + variations: [ + ( + model: "voxygen.voxel.sprite.furniture.cushion_arabic", + offset: (-5.5, -5.5, 0.0), + lod_axes: (0.0, 0.0, 0.0), + ), + ], + wind_sway: 0.0, +)), +JugArabic: Some(( + variations: [ + ( + model: "voxygen.voxel.sprite.furniture.jug_arabic", + offset: (-5.5, -5.5, 0.0), + lod_axes: (0.0, 0.0, 0.0), + ), + ], + wind_sway: 0.0, +)), +TableArabicSmall: Some(( + variations: [ + ( + model: "voxygen.voxel.sprite.furniture.table_arabic_small", + offset: (-7.5, -7.5, 0.0), + lod_axes: (0.0, 0.0, 0.0), + ), + ], + wind_sway: 0.0, +)), +TableArabicLarge: Some(( + variations: [ + ( + model: "voxygen.voxel.sprite.furniture.table_arabic_large", + offset: (-21.0, -13.0, 0.0), + lod_axes: (0.0, 0.0, 0.0), + ), + ], + wind_sway: 0.0, +)), +CanapeArabic: Some(( + variations: [ + ( + model: "voxygen.voxel.sprite.furniture.canape_arabic", + offset: (-12.5, -14.0, 0.0), + lod_axes: (0.0, 0.0, 0.0), + ), + ], + wind_sway: 0.0, +)), +CupboardArabic: Some(( + variations: [ + ( + model: "voxygen.voxel.sprite.furniture.cupboard_arabic", + offset: (-9.5, -5.5, 4.0), + lod_axes: (0.0, 0.0, 0.0), + ), + ], + wind_sway: 0.0, +)), +WallTableArabic: Some(( + variations: [ + ( + model: "voxygen.voxel.sprite.furniture.wall_table_arabic", + offset: (-15.0, -5.5, 2.0), + lod_axes: (0.0, 0.0, 0.0), + ), + ], + wind_sway: 0.0, +)), +JugAndBowlArabic: Some(( + variations: [ + ( + model: "voxygen.voxel.sprite.furniture.jug_and_bowl_arabic", + offset: (-15.0, -7.5, 0.0), + lod_axes: (0.0, 0.0, 0.0), + ), + ], + wind_sway: 0.0, +)), +MelonCut: Some(( + variations: [ + ( + model: "voxygen.voxel.sprite.furniture.melon_cut", + offset: (-15.0, -7.5, 0.0), + lod_axes: (0.0, 0.0, 0.0), + ), + ], + wind_sway: 0.0, +)), +FountainArabic: Some(( + variations: [ + ( + model: "voxygen.voxel.sprite.furniture.fountain_arabic", + offset: (-15.5, -15.5, 0.0), + lod_axes: (0.0, 0.0, 0.0), + ), + ], + wind_sway: 0.0, +)), +OvenArabic: Some(( + variations: [ + ( + model: "voxygen.voxel.sprite.furniture.oven_arabic", + offset: (-6.5, -8.5, 0.0), + lod_axes: (0.0, 0.0, 0.0), + ), + ], + wind_sway: 0.0, +)), +Hearth: Some(( + variations: [ + ( + model: "voxygen.voxel.sprite.furniture.hearth", + offset: (-19.0, -5.5, 0.0), + lod_axes: (0.0, 0.0, 0.0), + ), + ], + wind_sway: 0.0, +)), +ForgeTools: Some(( + variations: [ + ( + model: "voxygen.voxel.sprite.furniture.forge_tools", + offset: (-19.0, -5.0, 0.0), + lod_axes: (0.0, 0.0, 0.0), + ), + ], + wind_sway: 0.0, +)), +CliffDecorBlock: Some(( + variations: [ + ( + model: "voxygen.voxel.sprite.furniture.cliff_decor_block", + offset: (-5.5, -5.5, 0.0), + lod_axes: (0.0, 0.0, 0.0), + ), + ], + wind_sway: 0.0, +)), // Anvil Anvil: Some(( variations: [ diff --git a/common/src/terrain/block.rs b/common/src/terrain/block.rs index 1362f94d57..252031b1ba 100644 --- a/common/src/terrain/block.rs +++ b/common/src/terrain/block.rs @@ -208,6 +208,7 @@ impl Block { | SpriteKind::WallSconce | SpriteKind::FireBowlGround | SpriteKind::ChristmasOrnament + | SpriteKind::CliffDecorBlock | SpriteKind::Orb => Some(16), SpriteKind::Velorite | SpriteKind::VeloriteFrag diff --git a/common/src/terrain/sprite.rs b/common/src/terrain/sprite.rs index dbd42ceccc..5bf979c7f2 100644 --- a/common/src/terrain/sprite.rs +++ b/common/src/terrain/sprite.rs @@ -191,6 +191,24 @@ make_case_elim!( ChristmasOrnament = 0xA4, ChristmasWreath = 0xA5, EnsnaringWeb = 0xA6, + WindowArabic = 0xA7, + MelonCut = 0xA8, + BookshelfArabic = 0xA9, + DecorSetArabic = 0xAA, + SepareArabic = 0xAB, + CushionArabic = 0xAC, + JugArabic = 0xAD, + TableArabicSmall = 0xAE, + TableArabicLarge = 0xAF, + CanapeArabic = 0xB0, + CupboardArabic = 0xB1, + WallTableArabic = 0xB2, + JugAndBowlArabic = 0xB3, + OvenArabic = 0xB4, + FountainArabic = 0xB5, + Hearth = 0xB6, + ForgeTools = 0xB7, + CliffDecorBlock = 0xB8, } ); @@ -283,6 +301,23 @@ impl SpriteKind { | SpriteKind::CavernLillypadBlue | SpriteKind::EnsnaringWeb => 0.1, SpriteKind::LillyPads => 0.1, + SpriteKind::WindowArabic | SpriteKind::BookshelfArabic => 1.9, + SpriteKind::DecorSetArabic => 2.6, + SpriteKind::SepareArabic => 2.2, + SpriteKind::CushionArabic => 0.4, + SpriteKind::JugArabic => 1.4, + SpriteKind::TableArabicSmall => 0.9, + SpriteKind::TableArabicLarge => 1.0, + SpriteKind::CanapeArabic => 1.2, + SpriteKind::CupboardArabic => 4.5, + SpriteKind::WallTableArabic => 2.3, + SpriteKind::JugAndBowlArabic => 1.4, + SpriteKind::MelonCut => 0.7, + SpriteKind::OvenArabic => 3.2, + SpriteKind::FountainArabic => 2.4, + SpriteKind::Hearth => 2.3, + SpriteKind::ForgeTools => 2.8, + SpriteKind::CliffDecorBlock => 1.0, _ => return None, }) } @@ -458,6 +493,18 @@ impl SpriteKind { | SpriteKind::DismantlingBench | SpriteKind::ChristmasOrnament | SpriteKind::ChristmasWreath + | SpriteKind::WindowArabic + | SpriteKind::BookshelfArabic + | SpriteKind::TableArabicLarge + | SpriteKind::CanapeArabic + | SpriteKind::CupboardArabic + | SpriteKind::WallTableArabic + | SpriteKind::JugAndBowlArabic + | SpriteKind::JugArabic + | SpriteKind::MelonCut + | SpriteKind::OvenArabic + | SpriteKind::Hearth + | SpriteKind::ForgeTools ) } } diff --git a/server/src/rtsim/mod.rs b/server/src/rtsim/mod.rs index 0d6160a539..c9d15da28b 100644 --- a/server/src/rtsim/mod.rs +++ b/server/src/rtsim/mod.rs @@ -266,6 +266,31 @@ pub fn init( }); } }, + + SiteKind::CliffTown(site2) => { + for _ in 0..(site2.plazas().len() as f32 * 1.5) as usize { + rtsim.entities.insert(Entity { + is_loaded: false, + pos: site2 + .plazas() + .choose(&mut thread_rng()) + .map_or(site.get_origin(), |p| { + site2.tile_center_wpos(site2.plot(p).root_tile()) + + Vec2::new( + thread_rng().gen_range(-8..9), + thread_rng().gen_range(-8..9), + ) + }) + .with_z(0) + .map(|e| e as f32), + seed: thread_rng().gen(), + controller: RtSimController::default(), + last_time_ticked: 0.0, + kind: RtSimEntityKind::Merchant, + brain: Brain::merchant(site_id), + }); + } + }, _ => {}, } } diff --git a/world/src/civ/mod.rs b/world/src/civ/mod.rs index 88c943d2cc..17967b51da 100644 --- a/world/src/civ/mod.rs +++ b/world/src/civ/mod.rs @@ -161,6 +161,7 @@ impl Civs { SiteKind::Dungeon => (8i32, 3.0), SiteKind::Castle => (16i32, 5.0), SiteKind::Refactor => (32i32, 10.0), + SiteKind::CliffTown => (32i32, 10.0), SiteKind::Tree => (12i32, 8.0), SiteKind::GiantTree => (12i32, 8.0), SiteKind::Gnarling => (16i32, 10.0), @@ -239,6 +240,11 @@ impl Civs { &mut rng, wpos, )), + SiteKind::CliffTown => WorldSite::cliff_town(site2::Site::generate_cliff_town( + &Land::from_sim(ctx.sim), + &mut rng, + wpos, + )), SiteKind::Tree => { WorldSite::tree(Tree::generate(wpos, &Land::from_sim(ctx.sim), &mut rng)) }, @@ -479,7 +485,11 @@ impl Civs { ctx: &mut GenCtx, start_locations: &mut Vec>, ) -> Option> { - let kind = SiteKind::Refactor; + // TODO: specify SiteKind based on where a suitable location is found + let kind = match ctx.rng.gen_range(0..64) { + 0..=10 => SiteKind::CliffTown, + _ => SiteKind::Refactor, + }; let site = attempt(100, || { let loc = find_site_loc(ctx, (start_locations, 60), 0, kind)?; start_locations.push(loc); @@ -887,7 +897,10 @@ impl Civs { .filter(|(_, p)| { matches!( p.kind, - SiteKind::Refactor | SiteKind::Settlement | SiteKind::Castle + SiteKind::Refactor + | SiteKind::Settlement + | SiteKind::CliffTown + | SiteKind::Castle ) }) .map(|(id, p)| (id, (p.center.distance_squared(loc) as f32).sqrt())) @@ -895,7 +908,8 @@ impl Civs { .collect::>(); nearby.sort_by_key(|(_, dist)| *dist as i32); - if let SiteKind::Refactor | SiteKind::Settlement | SiteKind::Castle = self.sites[site].kind + if let SiteKind::Refactor | SiteKind::Settlement | SiteKind::CliffTown | SiteKind::Castle = + self.sites[site].kind { for (nearby, _) in nearby.into_iter().take(5) { // Find a novel path @@ -1149,6 +1163,7 @@ pub enum SiteKind { Dungeon, Castle, Refactor, + CliffTown, Tree, GiantTree, Gnarling, @@ -1156,36 +1171,8 @@ pub enum SiteKind { impl SiteKind { pub fn is_suitable_loc(&self, loc: Vec2, sim: &WorldSim) -> bool { - sim.get(loc).map_or(false, |chunk| match self { - SiteKind::Gnarling => (-0.3..0.4).contains(&chunk.temp) && chunk.tree_density > 0.75, - SiteKind::GiantTree | SiteKind::Tree => chunk.tree_density > 0.4, - SiteKind::Castle => { - if chunk.tree_density > 0.4 || chunk.river.near_water() || chunk.near_cliffs() { - return false; - } - const HILL_RADIUS: i32 = 3 * TERRAIN_CHUNK_BLOCKS_LG as i32; - for x in (-HILL_RADIUS)..HILL_RADIUS { - for y in (-HILL_RADIUS)..HILL_RADIUS { - let check_loc = loc + Vec2::new(x, y); - if let Some(true) = sim - .get_alt_approx(check_loc) - .map(|surrounding_alt| surrounding_alt > chunk.alt + 1.0) - { - return false; - } - // Castles are really big, so to avoid parts of them ending up underwater or - // in other awkward positions we have to do this - if sim - .get(check_loc) - .map_or(true, |c| c.is_underwater() || c.near_cliffs()) - { - return false; - } - } - } - true - }, - SiteKind::Refactor | SiteKind::Settlement => { + sim.get(loc).map_or(false, |chunk| { + let suitable_for_town = |score_threshold: f32| -> bool { const RESOURCE_RADIUS: i32 = 1; let mut river_chunks = 0; let mut lake_chunks = 0; @@ -1277,10 +1264,49 @@ impl SiteKind { + (trading_score as f32 + 1.0).log2(); has_potable_water && has_building_materials - && industry_score > 6.7 + && industry_score > score_threshold && warm_or_firewood - }, - _ => true, + }; + match self { + SiteKind::Gnarling => { + (-0.3..0.4).contains(&chunk.temp) && chunk.tree_density > 0.75 + }, + SiteKind::GiantTree | SiteKind::Tree => chunk.tree_density > 0.4, + SiteKind::CliffTown => { + (-0.6..0.4).contains(&chunk.temp) + && chunk.near_cliffs() + && suitable_for_town(4.0) + }, + SiteKind::Castle => { + if chunk.tree_density > 0.4 || chunk.river.near_water() || chunk.near_cliffs() { + return false; + } + const HILL_RADIUS: i32 = 3 * TERRAIN_CHUNK_BLOCKS_LG as i32; + for x in (-HILL_RADIUS)..HILL_RADIUS { + for y in (-HILL_RADIUS)..HILL_RADIUS { + let check_loc = loc + Vec2::new(x, y); + if let Some(true) = sim + .get_alt_approx(check_loc) + .map(|surrounding_alt| surrounding_alt > chunk.alt + 1.0) + { + return false; + } + // Castles are really big, so to avoid parts of them ending up + // underwater or in other awkward positions + // we have to do this + if sim + .get(check_loc) + .map_or(true, |c| c.is_underwater() || c.near_cliffs()) + { + return false; + } + } + } + true + }, + SiteKind::Refactor | SiteKind::Settlement => suitable_for_town(6.7), + _ => true, + } }) } } diff --git a/world/src/lib.rs b/world/src/lib.rs index 9ed4e3de9d..f2a6297a4b 100644 --- a/world/src/lib.rs +++ b/world/src/lib.rs @@ -146,7 +146,7 @@ impl World { name: site.site_tmp.map(|id| index.sites[id].name().to_string()), // TODO: Probably unify these, at some point kind: match &site.kind { - civ::SiteKind::Settlement => world_msg::SiteKind::Town, + civ::SiteKind::Settlement | civ::SiteKind::Refactor | civ::SiteKind::CliffTown => world_msg::SiteKind::Town, civ::SiteKind::Dungeon => world_msg::SiteKind::Dungeon { difficulty: match site.site_tmp.map(|id| &index.sites[id].kind) { Some(site::SiteKind::Dungeon(d)) => d.dungeon_difficulty().unwrap_or(0), @@ -154,7 +154,6 @@ impl World { }, }, civ::SiteKind::Castle => world_msg::SiteKind::Castle, - civ::SiteKind::Refactor => world_msg::SiteKind::Town, civ::SiteKind::Tree | civ::SiteKind::GiantTree => world_msg::SiteKind::Tree, // TODO: Maybe change? civ::SiteKind::Gnarling => world_msg::SiteKind::Gnarling, diff --git a/world/src/sim2/mod.rs b/world/src/sim2/mod.rs index 1d8876564e..709050a330 100644 --- a/world/src/sim2/mod.rs +++ b/world/src/sim2/mod.rs @@ -166,11 +166,12 @@ fn simulate_return(index: &mut Index, world: &mut WorldSim) -> Result<(), std::i for site in index.sites.ids() { let site = &index.sites[site]; match site.kind { + SiteKind::Settlement(_) | SiteKind::Refactor(_) | SiteKind::CliffTown(_) => { + towns += site.economy.pop + }, SiteKind::Dungeon(_) => dungeons += site.economy.pop, - SiteKind::Settlement(_) => towns += site.economy.pop, SiteKind::Castle(_) => castles += site.economy.pop, SiteKind::Tree(_) => (), - SiteKind::Refactor(_) => towns += site.economy.pop, SiteKind::GiantTree(_) => (), SiteKind::Gnarling(_) => {}, } @@ -360,7 +361,9 @@ mod tests { resources, neighbors, kind: match i.kind { - crate::site::SiteKind::Settlement(_) => { + crate::site::SiteKind::Settlement(_) + | crate::site::SiteKind::Refactor(_) + | crate::site::SiteKind::CliffTown(_) => { common::terrain::site::SitesKind::Settlement }, crate::site::SiteKind::Dungeon(_) => { @@ -369,9 +372,6 @@ mod tests { crate::site::SiteKind::Castle(_) => { common::terrain::site::SitesKind::Castle }, - crate::site::SiteKind::Refactor(_) => { - common::terrain::site::SitesKind::Settlement - }, _ => common::terrain::site::SitesKind::Void, }, }; diff --git a/world/src/site/mod.rs b/world/src/site/mod.rs index 259c2a4eb1..0c445feaff 100644 --- a/world/src/site/mod.rs +++ b/world/src/site/mod.rs @@ -64,6 +64,7 @@ pub enum SiteKind { Dungeon(site2::Site), Castle(Castle), Refactor(site2::Site), + CliffTown(site2::Site), Tree(tree::Tree), GiantTree(site2::Site), Gnarling(site2::Site), @@ -105,6 +106,13 @@ impl Site { } } + pub fn cliff_town(ct: site2::Site) -> Self { + Self { + kind: SiteKind::CliffTown(ct), + economy: Economy::default(), + } + } + pub fn tree(t: tree::Tree) -> Self { Self { kind: SiteKind::Tree(t), @@ -125,6 +133,7 @@ impl Site { SiteKind::Dungeon(d) => d.radius(), SiteKind::Castle(c) => c.radius(), SiteKind::Refactor(s) => s.radius(), + SiteKind::CliffTown(ct) => ct.radius(), SiteKind::Tree(t) => t.radius(), SiteKind::GiantTree(gt) => gt.radius(), SiteKind::Gnarling(g) => g.radius(), @@ -137,6 +146,7 @@ impl Site { SiteKind::Dungeon(d) => d.origin, SiteKind::Castle(c) => c.get_origin(), SiteKind::Refactor(s) => s.origin, + SiteKind::CliffTown(ct) => ct.origin, SiteKind::Tree(t) => t.origin, SiteKind::GiantTree(gt) => gt.origin, SiteKind::Gnarling(g) => g.origin, @@ -149,6 +159,7 @@ impl Site { SiteKind::Dungeon(d) => d.spawn_rules(wpos), SiteKind::Castle(c) => c.spawn_rules(wpos), SiteKind::Refactor(s) => s.spawn_rules(wpos), + SiteKind::CliffTown(ct) => ct.spawn_rules(wpos), SiteKind::Tree(t) => t.spawn_rules(wpos), SiteKind::GiantTree(gt) => gt.spawn_rules(wpos), SiteKind::Gnarling(g) => g.spawn_rules(wpos), @@ -161,6 +172,7 @@ impl Site { SiteKind::Dungeon(d) => d.name(), SiteKind::Castle(c) => c.name(), SiteKind::Refactor(s) => s.name(), + SiteKind::CliffTown(ct) => ct.name(), SiteKind::Tree(_) => "Giant Tree", SiteKind::GiantTree(gt) => gt.name(), SiteKind::Gnarling(g) => g.name(), @@ -172,7 +184,7 @@ impl Site { site_id: common::trade::SiteId, ) -> Option { match &self.kind { - SiteKind::Settlement(_) | SiteKind::Refactor(_) => { + SiteKind::Settlement(_) | SiteKind::Refactor(_) | SiteKind::CliffTown(_) => { Some(common::trade::SiteInformation { id: site_id, unconsumed_stock: self @@ -195,6 +207,7 @@ impl Site { SiteKind::Dungeon(d) => d.render(canvas, dynamic_rng), SiteKind::Castle(c) => c.apply_to(canvas.index, canvas.wpos, get_col, canvas.chunk), SiteKind::Refactor(s) => s.render(canvas, dynamic_rng), + SiteKind::CliffTown(ct) => ct.render(canvas, dynamic_rng), SiteKind::Tree(t) => t.render(canvas, dynamic_rng), SiteKind::GiantTree(gt) => gt.render(canvas, dynamic_rng), SiteKind::Gnarling(g) => g.render(canvas, dynamic_rng), @@ -220,6 +233,7 @@ impl Site { SiteKind::Dungeon(d) => d.apply_supplement(dynamic_rng, wpos2d, supplement), SiteKind::Castle(c) => c.apply_supplement(dynamic_rng, wpos2d, get_column, supplement), SiteKind::Refactor(_) => {}, + SiteKind::CliffTown(_) => {}, SiteKind::Tree(_) => {}, SiteKind::GiantTree(gt) => gt.apply_supplement(dynamic_rng, wpos2d, supplement), SiteKind::Gnarling(g) => g.apply_supplement(dynamic_rng, wpos2d, supplement), @@ -227,6 +241,9 @@ impl Site { } pub fn do_economic_simulation(&self) -> bool { - matches!(self.kind, SiteKind::Refactor(_) | SiteKind::Settlement(_)) + matches!( + self.kind, + SiteKind::Refactor(_) | SiteKind::CliffTown(_) | SiteKind::Settlement(_) + ) } } diff --git a/world/src/site/namegen.rs b/world/src/site/namegen.rs index 121dd6ca60..1b0dda89f1 100644 --- a/world/src/site/namegen.rs +++ b/world/src/site/namegen.rs @@ -622,4 +622,26 @@ impl<'a, R: Rng> NameGen<'a, R> { ]; self.generate_theme_from_parts(&start, &middle, &vowel, &end) } + + // arabic inspired location names for cliff towns + pub fn generate_cliff_town(mut self) -> String { + let start = [ + "zor", "el", "mas", "yaz", "ra", "boh", "mah", "ah", "lam", "mak", "mol", "wa", "bisk", + "moj", "bis", "ay", "sha", "rez", "bakh", "ta", "je", "ki", "mos", "asj", "meh", + ]; + let middle = [ + "d", "ph", "r", "st", "t", "s", "p", "th", "br", "tr", "m", "k", "cr", "dr", "pl", + "ch", "l", "ap", "akr", "ak", "ar", "ath", "asp", "al", "aph", "aphr", "oph", "or", + "ok", "on", "od", "om", "ep", "er", "em", "eph", "eth", "yph", "ach", "yp", "ik", "is", + "iph", "ith", "pr", "as", "asph", "ps", "b", "n", "z", "x", "kr", "kt", "cht", "chr", + "thr", "dr", "pr", "pl", "h", "in", "g", + ]; + let vowel = ["o", "e", "a", "i", "y", "ei", "ai", "io"]; + let end = [ + "wad", "tab", "med", "mad", "afa", "man", "oubi", "hir", "baz", "yen", "kh", "ah", + "dek", "fir", "ish", "rad", "iri", "am", "if", "van", "rik", "kat", "akan", "ikan", + "illah", "ulus", "fard", + ]; + self.generate_theme_from_parts(&start, &middle, &vowel, &end) + } } diff --git a/world/src/site2/mod.rs b/world/src/site2/mod.rs index a11e08c641..4f09f7e947 100644 --- a/world/src/site2/mod.rs +++ b/world/src/site2/mod.rs @@ -742,6 +742,51 @@ impl Site { site } + pub fn generate_cliff_town(land: &Land, rng: &mut impl Rng, origin: Vec2) -> Self { + let mut rng = reseed(rng); + + let mut site = Site { + origin, + name: NameGen::location(&mut rng).generate_cliff_town(), + ..Site::default() + }; + + site.make_plaza(land, &mut rng); + for _ in 0..30 { + // CliffTower + let size = (6.0 + rng.gen::().powf(5.0) * 1.0).round() as u32; + if let Some((aabr, door_tile, door_dir)) = attempt(32, || { + site.find_roadside_aabr(&mut rng, 6..(size + 1).pow(2), Extent2::broadcast(size)) + }) { + let cliff_tower = plot::CliffTower::generate( + land, + &mut reseed(&mut rng), + &site, + door_tile, + door_dir, + aabr, + ); + let cliff_tower_alt = cliff_tower.alt; + let plot = site.create_plot(Plot { + kind: PlotKind::CliffTower(cliff_tower), + 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(cliff_tower_alt), + }); + } else { + site.make_plaza(land, &mut rng); + } + } + + site + } + pub fn wpos_tile_pos(&self, wpos2d: Vec2) -> Vec2 { (wpos2d - self.origin).map(|e| e.div_euclid(TILE_SIZE as i32)) } @@ -991,6 +1036,7 @@ impl Site { PlotKind::Dungeon(dungeon) => dungeon.render_collect(self, canvas), PlotKind::Gnarling(gnarling) => gnarling.render_collect(self, canvas), PlotKind::GiantTree(giant_tree) => giant_tree.render_collect(self, canvas), + PlotKind::CliffTower(cliff_tower) => cliff_tower.render_collect(self, canvas), _ => continue, }; diff --git a/world/src/site2/plot.rs b/world/src/site2/plot.rs index 56f29f4cbe..9b6c2d4c4f 100644 --- a/world/src/site2/plot.rs +++ b/world/src/site2/plot.rs @@ -1,4 +1,5 @@ mod castle; +mod cliff_tower; pub mod dungeon; mod giant_tree; mod gnarling; @@ -6,8 +7,8 @@ mod house; mod workshop; pub use self::{ - castle::Castle, dungeon::Dungeon, giant_tree::GiantTree, gnarling::GnarlingFortification, - house::House, workshop::Workshop, + castle::Castle, cliff_tower::CliffTower, dungeon::Dungeon, giant_tree::GiantTree, + gnarling::GnarlingFortification, house::House, workshop::Workshop, }; use super::*; @@ -52,4 +53,5 @@ pub enum PlotKind { Dungeon(Dungeon), Gnarling(GnarlingFortification), GiantTree(GiantTree), + CliffTower(CliffTower), } diff --git a/world/src/site2/plot/cliff_tower.rs b/world/src/site2/plot/cliff_tower.rs new file mode 100644 index 0000000000..26d22d5e29 --- /dev/null +++ b/world/src/site2/plot/cliff_tower.rs @@ -0,0 +1,890 @@ +use super::*; +use crate::{ + util::{RandomField, Sampler, LOCALITY}, + Land, +}; +use common::{ + generation::EntityInfo, + terrain::{BlockKind, SpriteKind}, +}; +use rand::prelude::*; +use std::{mem, sync::Arc}; +use vek::*; + +/// Represents house data generated by the `generate()` method +pub struct CliffTower { + /// Axis aligned bounding region for the house + bounds: Aabr, + /// Approximate altitude of the door tile + pub(crate) alt: i32, +} + +impl CliffTower { + pub fn generate( + land: &Land, + _rng: &mut impl Rng, + site: &Site, + door_tile: Vec2, + door_dir: Vec2, + tile_aabr: Aabr, + ) -> Self { + let bounds = Aabr { + min: site.tile_wpos(tile_aabr.min), + max: site.tile_wpos(tile_aabr.max), + }; + Self { + bounds, + alt: land.get_alt_approx(site.tile_center_wpos(door_tile + door_dir)) as i32, + } + } +} + +impl Structure for CliffTower { + fn render(&self, _site: &Site, _land: &Land, painter: &Painter) { + let base = self.alt + 1; + let center = self.bounds.center(); + let variant_pos = center.with_z(base); + let variant = RandomField::new(0).get(variant_pos) as i32 % 10; + // common superquadric degree for rooms + let sq_type = 2.5; + let storeys = 5 + (variant / 2); + let mut length = 16 + (variant / 2); + let mut width = 7 * length / 8; + let mut height = 18 + variant / 2; + let (mut stair_pos1, mut stair_pos2) = (center - 3, center + 3); + let mut floor_level = base - 40; + let brick = Fill::Sampling(Arc::new(|variant_pos| { + Some( + match (RandomField::new(0).get(Vec3::new(variant_pos.z, 0, 0))) % 15 { + 0 => Block::new(BlockKind::Rock, Rgb::new(51, 89, 118)), + 1 => Block::new(BlockKind::Rock, Rgb::new(57, 96, 126)), + 2 => Block::new(BlockKind::Rock, Rgb::new(59, 103, 136)), + 3 => Block::new(BlockKind::Rock, Rgb::new(61, 109, 145)), + 4 => Block::new(BlockKind::Rock, Rgb::new(42, 66, 87)), + 5 => Block::new(BlockKind::Rock, Rgb::new(47, 76, 101)), + 6 => Block::new(BlockKind::Rock, Rgb::new(50, 84, 110)), + 7 => Block::new(BlockKind::Rock, Rgb::new(52, 85, 112)), + 8 => Block::new(BlockKind::Rock, Rgb::new(51, 60, 66)), + 9 => Block::new(BlockKind::Rock, Rgb::new(58, 74, 87)), + 10 => Block::new(BlockKind::Rock, Rgb::new(53, 104, 111)), + 11 => Block::new(BlockKind::Rock, Rgb::new(52, 63, 72)), + 12 => Block::new(BlockKind::Rock, Rgb::new(52, 63, 72)), + 13 => Block::new(BlockKind::Rock, Rgb::new(74, 128, 168)), + _ => Block::new(BlockKind::Rock, Rgb::new(69, 123, 162)), + }, + ) + })); + let wood = Fill::Brick(BlockKind::Wood, Rgb::new(106, 83, 51), 12); + let color = Fill::Block(Block::air(SpriteKind::CliffDecorBlock)); + let window = Fill::Block(Block::air(SpriteKind::WindowArabic)); + let window2 = Fill::Block(Block::air(SpriteKind::WindowArabic).with_ori(2).unwrap()); + for s in 0..storeys { + let x_offset = RandomField::new(0).get((center - length).with_z(base)) as i32 % 10; + let y_offset = RandomField::new(0).get((center + length).with_z(base)) as i32 % 10; + let super_center = Vec2::new(center.x - 3 + x_offset / 2, center.y - 3 + y_offset / 2); + let room1_type = RandomField::new(0).get((center - length).with_z(base)) as i32 % 2; + let room2_type = RandomField::new(0).get((center - length - 1).with_z(base)) as i32 % 2; + // CliffTower Hoodoo Overlay + painter + .cubic_bezier( + super_center.with_z(floor_level + (height / 2)), + (super_center - x_offset).with_z(floor_level + height), + (super_center - y_offset).with_z(floor_level + (height) + (height / 2)), + super_center.with_z(floor_level + (2 * height)), + (length - 1) as f32, + ) + .fill(brick.clone()); + // only inhabit towers with enough storeys to have entries above ground + if storeys > 3 { + // wood or rocky platforms + // only spawn on upper storeys + if floor_level > base + 35 { + match RandomField::new(0).get((super_center - floor_level).with_z(base)) as i32 + % 2 + { + 0 => { + painter + .superquadric( + Aabb { + min: (super_center - (5 * (length / 3)) + 3) + .with_z(floor_level), + max: (super_center + (5 * (length / 3)) - 3) + .with_z(floor_level + 2), + }, + 6.0, + ) + .fill(wood.clone()); + painter + .prim(Primitive::without( + painter.superquadric( + Aabb { + min: (super_center - (5 * (length / 3)) + 2) + .with_z(floor_level + 1), + max: (super_center + (5 * (length / 3)) - 1) + .with_z(floor_level + 5), + }, + 6.0, + ), + painter.superquadric( + Aabb { + min: (super_center - (5 * (length / 3)) + 2) + .with_z(floor_level + 3), + max: (super_center + (5 * (length / 3)) - 2) + .with_z(floor_level + 5), + }, + 6.0, + ), + )) + .fill(wood.clone()); + // lanterns & random sprites for wood platform corners + for dir in SQUARE_4 { + let corner_pos = super_center - (5 * (length / 4)); + let sprite_pos = (corner_pos + (dir * (10 * (length / 4)))) + .with_z(floor_level + 4); + painter.sprite( + sprite_pos, + match (RandomField::new(0).get(sprite_pos)) % 10 { + 0 => SpriteKind::FireBowlGround, + 1 => SpriteKind::Bowl, + 3 => SpriteKind::VialEmpty, + 4 => SpriteKind::Crate, + 5 => SpriteKind::Pot, + _ => SpriteKind::Lantern, + }, + ); + } + // planters for larger wood platforms + if length > 11 { + for r in 0..2 { + for p in 0..((length / 2) - 2) { + let planter_pos = Vec2::new( + super_center.x - (2 * (length / 3)) + + (p * (length / 3)), + super_center.y - ((4 * (length / 3)) + 2) + + (r * ((8 * (length / 3)) + 4)), + ); + painter + .aabb(Aabb { + min: Vec2::new(planter_pos.x - 1, planter_pos.y) + .with_z(floor_level + 4), + max: Vec2::new( + planter_pos.x + 2, + planter_pos.y + 1, + ) + .with_z(floor_level + 6), + }) + .clear(); + painter.rotated_sprite( + planter_pos.with_z(floor_level + 4), + SpriteKind::Planter, + (4 - (r * 4)) as u8, + ); + } + } + } + }, + _ => { + painter + .superquadric( + Aabb { + min: (center - length + 1).with_z(floor_level), + max: (center + length - 1).with_z(floor_level + 2), + }, + sq_type, + ) + .fill(brick.clone()); + painter + .prim(Primitive::without( + painter.superquadric( + Aabb { + min: (center - length).with_z(floor_level + 1), + max: (center + length).with_z(floor_level + 5), + }, + sq_type, + ), + painter.superquadric( + Aabb { + min: (center - length + 3).with_z(floor_level + 3), + max: (center + length - 3).with_z(floor_level + 5), + }, + sq_type, + ), + )) + .fill(brick.clone()); + }, + } + } + // room + painter + .superquadric( + Aabb { + min: Vec2::new(super_center.x - length - 1, super_center.y - width - 1) + .with_z(floor_level), + max: Vec2::new(super_center.x + length + 1, super_center.y + width + 1) + .with_z(floor_level + height), + }, + sq_type, + ) + .fill(brick.clone()); + // clear room - leave some floor + painter + .prim(Primitive::without( + painter.superquadric( + Aabb { + min: Vec2::new( + super_center.x - length + 1, + super_center.y + 1 - width, + ) + .with_z(floor_level + 1), + max: Vec2::new( + super_center.x + length - 1, + super_center.y - 1 + width, + ) + .with_z(floor_level + height - 1), + }, + sq_type, + ), + painter.aabb(Aabb { + min: Vec2::new(super_center.x - length + 1, super_center.y + 1 - width) + .with_z(floor_level + 1), + max: Vec2::new(super_center.x + length - 1, super_center.y - 1 + width) + .with_z(floor_level + 4), + }), + )) + .clear(); + // entries + painter + .aabb(Aabb { + min: Vec2::new(super_center.x - length, super_center.y - 2) + .with_z(floor_level + 3), + max: Vec2::new(super_center.x - length + 6, super_center.y + 2) + .with_z(floor_level + 4), + }) + .fill(brick.clone()); + painter + .aabb(Aabb { + min: Vec2::new(super_center.x + length - 6, super_center.y - 2) + .with_z(floor_level + 3), + max: Vec2::new(super_center.x + length, super_center.y + 2) + .with_z(floor_level + 4), + }) + .fill(brick.clone()); + // colored sills + painter + .aabb(Aabb { + min: Vec2::new(super_center.x - length - 1, super_center.y - 2) + .with_z(floor_level + 3), + max: Vec2::new(super_center.x - length, super_center.y + 2) + .with_z(floor_level + 4), + }) + .fill(color.clone()); + painter + .aabb(Aabb { + min: Vec2::new(super_center.x + length, super_center.y - 2) + .with_z(floor_level + 3), + max: Vec2::new(super_center.x + length + 1, super_center.y + 2) + .with_z(floor_level + 4), + }) + .fill(color.clone()); + if floor_level > base { + // clear entries + painter + .aabb(Aabb { + min: Vec2::new(super_center.x - length - 12, super_center.y - 2) + .with_z(floor_level + 4), + max: Vec2::new(super_center.x + length + 12, super_center.y + 2) + .with_z(floor_level + 7), + }) + .clear(); + // door sprites + painter + .prim(Primitive::without( + painter.aabb(Aabb { + min: Vec2::new(super_center.x - length + 1, super_center.y - 2) + .with_z(floor_level + 4), + max: Vec2::new(super_center.x - length + 2, super_center.y + 2) + .with_z(floor_level + 7), + }), + painter.aabb(Aabb { + min: Vec2::new(super_center.x - length + 1, super_center.y - 1) + .with_z(floor_level + 4), + max: Vec2::new(super_center.x - length + 2, super_center.y + 1) + .with_z(floor_level + 7), + }), + )) + .fill(window.clone()); + painter + .prim(Primitive::without( + painter.aabb(Aabb { + min: Vec2::new(super_center.x + length - 1, super_center.y - 2) + .with_z(floor_level + 4), + max: Vec2::new(super_center.x + length, super_center.y + 2) + .with_z(floor_level + 7), + }), + painter.aabb(Aabb { + min: Vec2::new(super_center.x + length - 1, super_center.y - 1) + .with_z(floor_level + 4), + max: Vec2::new(super_center.x + length, super_center.y + 1) + .with_z(floor_level + 7), + }), + )) + .fill(window.clone()); + // windows + painter + .aabb(Aabb { + min: Vec2::new(super_center.x - 4, super_center.y - width) + .with_z(floor_level + 5), + max: Vec2::new(super_center.x + 4, super_center.y - width + 3) + .with_z(floor_level + 6), + }) + .fill(brick.clone()); + painter + .aabb(Aabb { + min: Vec2::new(super_center.x - 4, super_center.y - width + 1) + .with_z(floor_level + 4), + max: Vec2::new(super_center.x + 4, super_center.y - width + 3) + .with_z(floor_level + 5), + }) + .fill(brick.clone()); + painter + .aabb(Aabb { + min: Vec2::new(super_center.x - 4, super_center.y + width - 3) + .with_z(floor_level + 5), + max: Vec2::new(super_center.x + 4, super_center.y + width + 1) + .with_z(floor_level + 6), + }) + .fill(brick.clone()); + painter + .aabb(Aabb { + min: Vec2::new(super_center.x - 4, super_center.y + width - 3) + .with_z(floor_level + 4), + max: Vec2::new(super_center.x + 4, super_center.y + width) + .with_z(floor_level + 5), + }) + .fill(brick.clone()); + painter + .aabb(Aabb { + min: Vec2::new(super_center.x - 3, super_center.y - width - 1) + .with_z(floor_level + 5), + max: Vec2::new(super_center.x + 3, super_center.y - width) + .with_z(floor_level + 6), + }) + .fill(color.clone()); + painter + .aabb(Aabb { + min: Vec2::new(super_center.x - 3, super_center.y + width + 1) + .with_z(floor_level + 5), + max: Vec2::new(super_center.x + 3, super_center.y + width + 2) + .with_z(floor_level + 6), + }) + .fill(color.clone()); + // clear windows + painter + .aabb(Aabb { + min: Vec2::new(super_center.x - 4, super_center.y - width - 12) + .with_z(floor_level + 6), + max: Vec2::new(super_center.x + 4, super_center.y + width + 12) + .with_z(floor_level + 9), + }) + .clear(); + // window sprites + painter + .prim(Primitive::without( + painter.aabb(Aabb { + min: Vec2::new(super_center.x - 4, super_center.y - width + 1) + .with_z(floor_level + 6), + max: Vec2::new(super_center.x + 4, super_center.y - width + 2) + .with_z(floor_level + 9), + }), + painter.aabb(Aabb { + min: Vec2::new(super_center.x - 1, super_center.y - width + 1) + .with_z(floor_level + 6), + max: Vec2::new(super_center.x + 1, super_center.y - width + 2) + .with_z(floor_level + 9), + }), + )) + .fill(window2.clone()); + painter + .prim(Primitive::without( + painter.aabb(Aabb { + min: Vec2::new(super_center.x - 4, super_center.y + width - 1) + .with_z(floor_level + 6), + max: Vec2::new(super_center.x + 4, super_center.y + width) + .with_z(floor_level + 9), + }), + painter.aabb(Aabb { + min: Vec2::new(super_center.x - 1, super_center.y + width - 1) + .with_z(floor_level + 6), + max: Vec2::new(super_center.x + 1, super_center.y + width) + .with_z(floor_level + 9), + }), + )) + .fill(window2.clone()); + } + // room wall lamps + for d in 0..2 { + let door_lamp_pos = Vec2::new( + super_center.x - length + 2 + (d * ((2 * length) - 4)), + super_center.y, + ) + .with_z(floor_level + 9); + painter.rotated_sprite( + door_lamp_pos, + SpriteKind::WallLampSmall, + 2 + ((d * 4) as u8), + ); + + let window_lamp_pos = Vec2::new( + super_center.x, + super_center.y - width + 2 + (d * ((2 * width) - 4)), + ) + .with_z(floor_level + 9); + painter.rotated_sprite( + window_lamp_pos, + SpriteKind::WallLampSmall, + 4 - ((d * 4) as u8), + ); + } + // furniture sprites in room1(living_room, workshop), room2(kitchen, bath) + // dont spawn sprites on stairways + let stairway_clear_1 = Aabb { + min: (stair_pos1 - 5).with_z(floor_level + 3), + max: (stair_pos1 + 5).with_z(floor_level + 5), + }; + let stairway_clear_2 = Aabb { + min: (stair_pos2 - 5).with_z(floor_level + 3), + max: (stair_pos2 + 5).with_z(floor_level + 5), + }; + if length > width { + match room1_type { + 0 => { + // living room + // distribute small sprites + let mut liv_sprites = vec![ + SpriteKind::DrawerSmall, + SpriteKind::CoatRack, + SpriteKind::TableArabicSmall, + SpriteKind::CushionArabic, + SpriteKind::JugArabic, + SpriteKind::SpinningWheel, + SpriteKind::TanningRack, + SpriteKind::Loom, + ]; + for dir in LOCALITY { + let pos = super_center + dir * (width / 3); + if !stairway_clear_1.contains_point(pos.with_z(floor_level + 4)) + && !stairway_clear_2.contains_point(pos.with_z(floor_level + 4)) + && !liv_sprites.is_empty() + { + let sprite = liv_sprites.swap_remove( + RandomField::new(0).get(pos.with_z(base)) as usize + % liv_sprites.len(), + ); + painter.sprite(pos.with_z(floor_level + 4), sprite); + } + } + // bookshelfs + for d in 0..2 { + let pos = Vec2::new( + super_center.x, + super_center.y + width - 3 + (d * ((-2 * width) + 6)), + ); + if !stairway_clear_1.contains_point(pos.with_z(floor_level + 4)) + && !stairway_clear_2.contains_point(pos.with_z(floor_level + 4)) + { + painter + .aabb(Aabb { + min: Vec2::new(pos.x - 2, pos.y - 2 + (2 * d)) + .with_z(floor_level + 4), + max: Vec2::new(pos.x + 3, pos.y + 1 + (2 * d)) + .with_z(floor_level + 6), + }) + .clear(); + painter.rotated_sprite( + pos.with_z(floor_level + 6), + SpriteKind::BookshelfArabic, + 0 + (4 * d) as u8, + ); + } + } + // canapes + for d in 0..2 { + let pos = Vec2::new( + super_center.x - length + 10 + (d * ((2 * length) - 20)), + super_center.y - width + 4 + (d * ((2 * width) - 8)), + ); + if !stairway_clear_1.contains_point(pos.with_z(floor_level + 4)) + && !stairway_clear_2.contains_point(pos.with_z(floor_level + 4)) + { + painter + .aabb(Aabb { + min: Vec2::new( + pos.x - 1 - (3 * d), + pos.y - 1 - (3 * d), + ) + .with_z(floor_level + 4), + max: Vec2::new( + pos.x + 5 - (3 * d), + pos.y + 5 - (3 * d), + ) + .with_z(floor_level + 8), + }) + .clear(); + painter.rotated_sprite( + pos.with_z(floor_level + 4), + SpriteKind::CanapeArabic, + 0 + (4 * d) as u8, + ); + } + } + // decor set / separe / table large + for d in 0..2 { + let pos = Vec2::new( + super_center.x - length + 8 + (d * ((2 * length) - 16)), + super_center.y + width - 8 + (d * ((-2 * width) + 16)), + ); + if !stairway_clear_1.contains_point(pos.with_z(floor_level + 4)) + && !stairway_clear_2.contains_point(pos.with_z(floor_level + 4)) + { + painter + .aabb(Aabb { + min: Vec2::new(pos.x - 2, pos.y - 1) + .with_z(floor_level + 4), + max: Vec2::new(pos.x + 3, pos.y + 2) + .with_z(floor_level + 6), + }) + .clear(); + painter.sprite( + pos.with_z(floor_level + 4), + match (RandomField::new(0).get(pos.with_z(floor_level - d))) + % 3 + { + 0 => SpriteKind::TableArabicLarge, + 1 => SpriteKind::DecorSetArabic, + _ => SpriteKind::SepareArabic, + }, + ) + }; + } + }, + _ => { + // workshop + for d in 0..2 { + // forge tools + let ft_pos = Vec2::new( + super_center.x - 4 + (d * 6), + super_center.y - width + 3 + (d * ((2 * width) - 6)), + ); + painter + .aabb(Aabb { + min: Vec2::new(ft_pos.x - 2 + d, ft_pos.y - (3 * d)) + .with_z(floor_level + 4), + max: Vec2::new(ft_pos.x + 2 + d, ft_pos.y + 4 - (3 * d)) + .with_z(floor_level + 7), + }) + .clear(); + painter.rotated_sprite( + ft_pos.with_z(floor_level + 4), + SpriteKind::ForgeTools, + 0 + (4 * d) as u8, + ); + // hearth + let pos = Vec2::new( + super_center.x + length - 12 + (d * ((-2 * length) + 24)), + super_center.y - width + 3 + (d * ((2 * width) - 6)), + ); + if !stairway_clear_1.contains_point(pos.with_z(floor_level + 4)) + && !stairway_clear_2.contains_point(pos.with_z(floor_level + 4)) + { + painter + .aabb(Aabb { + min: Vec2::new(pos.x - 2, pos.y - (4 * d)) + .with_z(floor_level + 4), + max: Vec2::new(pos.x + 3, pos.y + 5 - (4 * d)) + .with_z(floor_level + 6), + }) + .clear(); + painter.rotated_sprite( + pos.with_z(floor_level + 4), + SpriteKind::Hearth, + 0 + (4 * d) as u8, + ); + } + } + // crafting stations + let mut ws_sprites = vec![ + SpriteKind::CraftingBench, + SpriteKind::Forge, + SpriteKind::DismantlingBench, + SpriteKind::Anvil, + ]; + for dir in LOCALITY { + let pos = super_center + dir * (width / 3); + if !stairway_clear_1.contains_point(pos.with_z(floor_level + 4)) + && !stairway_clear_2.contains_point(pos.with_z(floor_level + 4)) + && !ws_sprites.is_empty() + { + let sprite = ws_sprites.swap_remove( + RandomField::new(0).get(pos.with_z(base)) as usize + % ws_sprites.len(), + ); + painter.sprite(pos.with_z(floor_level + 4), sprite); + } + } + }, + } + } else { + match room2_type { + 0 => { + // bath + // wall tables with varying items + for d in 0..2 { + let pos = Vec2::new( + super_center.x, + super_center.y - width + 4 + (d * ((2 * width) - 8)), + ); + if !stairway_clear_1.contains_point(pos.with_z(floor_level + 4)) + && !stairway_clear_2.contains_point(pos.with_z(floor_level + 4)) + { + painter + .aabb(Aabb { + min: Vec2::new(pos.x - 2, pos.y - (d * 3)) + .with_z(floor_level + 4), + max: Vec2::new(pos.x + 3, pos.y + 4 - (d * 3)) + .with_z(floor_level + 7), + }) + .clear(); + painter.rotated_sprite( + pos.with_z(floor_level + 4), + SpriteKind::WallTableArabic, + 0 + (4 * d) as u8, + ); + painter.rotated_sprite( + pos.with_z(floor_level + 5), + match (RandomField::new(0) + .get((pos - d).with_z(floor_level))) + % 4 + { + 0 => SpriteKind::Bowl, + 1 => SpriteKind::VialEmpty, + 2 => SpriteKind::JugArabic, + _ => SpriteKind::JugAndBowlArabic, + }, + 0 + (4 * d) as u8, + ); + } + } + // distribute smaller sprites + let mut ba_sprites = vec![ + SpriteKind::DrawerSmall, + SpriteKind::CoatRack, + SpriteKind::Crate, + SpriteKind::TableArabicSmall, + SpriteKind::SepareArabic, + SpriteKind::DecorSetArabic, + ]; + for dir in LOCALITY { + let pos = super_center + dir * (width / 3); + if !stairway_clear_1.contains_point(pos.with_z(floor_level + 4)) + && !stairway_clear_2.contains_point(pos.with_z(floor_level + 4)) + && !ba_sprites.is_empty() + { + let sprite = ba_sprites.swap_remove( + RandomField::new(0).get(pos.with_z(base)) as usize + % ba_sprites.len(), + ); + painter.sprite(pos.with_z(floor_level + 4), sprite) + } + } + // fountains + for d in 0..2 { + let pos = Vec2::new( + super_center.x - length + 8 + (d * ((2 * length) - 16)), + super_center.y + width - 8 + (d * ((-2 * width) + 16)), + ); + if !stairway_clear_1.contains_point(pos.with_z(floor_level + 4)) + && !stairway_clear_2.contains_point(pos.with_z(floor_level + 4)) + { + painter + .aabb(Aabb { + min: (pos - 1).with_z(floor_level + 4), + max: (pos + 2).with_z(floor_level + 5), + }) + .clear(); + painter.sprite( + pos.with_z(floor_level + 4), + SpriteKind::FountainArabic, + ) + }; + } + }, + _ => { + // kitchen + // cupboards / ovens / cushions / jugs + for d in 0..2 { + let pos = Vec2::new( + super_center.x + 3 - (d * 6), + super_center.y - width + 3, + ); + if !stairway_clear_1.contains_point(pos.with_z(floor_level + 4)) + && !stairway_clear_2.contains_point(pos.with_z(floor_level + 4)) + { + painter + .aabb(Aabb { + min: Vec2::new(pos.x - 1, pos.y) + .with_z(floor_level + 4), + max: Vec2::new(pos.x + 2, pos.y + 4) + .with_z(floor_level + 7), + }) + .clear(); + painter.rotated_sprite( + pos.with_z(floor_level + 4), + match (RandomField::new(0).get(pos.with_z(floor_level))) % 2 + { + 0 => SpriteKind::CupboardArabic, + _ => SpriteKind::OvenArabic, + }, + 4, + ); + } + } + for d in 0..2 { + let pos = Vec2::new( + super_center.x + 3 - (d * 6), + super_center.y + width - 3, + ); + if !stairway_clear_1.contains_point(pos.with_z(floor_level + 4)) + && !stairway_clear_2.contains_point(pos.with_z(floor_level + 4)) + { + painter + .aabb(Aabb { + min: Vec2::new(pos.x - 1, pos.y - 3) + .with_z(floor_level + 4), + max: Vec2::new(pos.x + 2, pos.y + 1) + .with_z(floor_level + 7), + }) + .clear(); + painter.rotated_sprite( + pos.with_z(floor_level + 4), + match (RandomField::new(0).get(pos.with_z(floor_level))) % 4 + { + 0 => SpriteKind::CupboardArabic, + 1 => SpriteKind::OvenArabic, + 2 => SpriteKind::CushionArabic, + _ => SpriteKind::JugArabic, + }, + 0, + ); + } + } + // wall tables with varying items + for d in 0..2 { + let pos = Vec2::new( + super_center.x, + super_center.y - width + 3 + (d * ((2 * width) - 6)), + ); + if !stairway_clear_1.contains_point(pos.with_z(floor_level + 4)) + && !stairway_clear_2.contains_point(pos.with_z(floor_level + 4)) + { + painter + .aabb(Aabb { + min: Vec2::new(pos.x - 2, pos.y - (3 * d)) + .with_z(floor_level + 4), + max: Vec2::new(pos.x + 2, pos.y + 4 - (3 * d)) + .with_z(floor_level + 7), + }) + .clear(); + painter.rotated_sprite( + pos.with_z(floor_level + 4), + SpriteKind::WallTableArabic, + 0 + (4 * d) as u8, + ); + painter.rotated_sprite( + pos.with_z(floor_level + 5), + match (RandomField::new(0).get(pos.with_z(floor_level))) % 5 + { + 0 => SpriteKind::MelonCut, + 1 => SpriteKind::JugAndBowlArabic, + 2 => SpriteKind::Bowl, + 3 => SpriteKind::JugArabic, + _ => SpriteKind::VialEmpty, + }, + 0 + (4 * d) as u8, + ); + } + } + // distribute small sprites + let mut kit_sprites = vec![ + SpriteKind::DrawerSmall, + SpriteKind::Crate, + SpriteKind::VialEmpty, + SpriteKind::Bowl, + SpriteKind::TableArabicSmall, + SpriteKind::JugArabic, + SpriteKind::CookingPot, + SpriteKind::Cauldron, + ]; + for dir in LOCALITY { + let pos = super_center + dir * (width / 3); + if !stairway_clear_1.contains_point(pos.with_z(floor_level + 4)) + && !stairway_clear_2.contains_point(pos.with_z(floor_level + 4)) + && !kit_sprites.is_empty() + { + let sprite = kit_sprites.swap_remove( + RandomField::new(0).get(pos.with_z(base)) as usize + % kit_sprites.len(), + ); + painter.sprite(pos.with_z(floor_level + 4), sprite); + } + } + }, + } + }; + // stairways starting from 2nd storey + if s > 0 { + //clear stairway + painter + .cylinder(Aabb { + min: (stair_pos1 - 3).with_z(floor_level - height + 4), + max: (stair_pos1 + 3).with_z(floor_level + 7), + }) + .clear(); + //stairway + let stair_radius1 = 4.0; + let stairs_clear1 = painter.prim(Primitive::Cylinder(Aabb { + min: (stair_pos1 - stair_radius1 as i32).with_z(floor_level - height), + max: (stair_pos1 + stair_radius1 as i32).with_z(floor_level + 4), + })); + painter + .prim(Primitive::sampling( + stairs_clear1, + crate::site2::plot::dungeon::spiral_staircase( + stair_pos1.with_z(floor_level + 4), + stair_radius1, + 0.5, + 7.0, + ), + )) + .fill(brick.clone()); + } + // spawn mountaineers in each room + let spawn_pos = super_center.with_z(floor_level + 4); + let npc_amount = RandomField::new(0).get(spawn_pos) % 4; + for _ in 0..npc_amount { + let mut rng = rand::thread_rng(); + painter.spawn( + EntityInfo::at(spawn_pos.map(|e| e as f32)) + .with_asset_expect("common.entity.village.mountaineer", &mut rng), + ); + } + } + // vary next storey + length += -1; + width += -1; + height += -1; + floor_level += height; + mem::swap(&mut length, &mut width); + mem::swap(&mut stair_pos1, &mut stair_pos2); + } + } +}