CliffTowns

This commit is contained in:
flo 2022-03-29 12:52:08 +00:00 committed by Isse
parent a7e8b22d1d
commit 3d24b1d635
34 changed files with 1423 additions and 48 deletions

View File

@ -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

View File

@ -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: [],
)

View File

@ -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),
]),
})

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

View File

@ -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: [

View File

@ -208,6 +208,7 @@ impl Block {
| SpriteKind::WallSconce
| SpriteKind::FireBowlGround
| SpriteKind::ChristmasOrnament
| SpriteKind::CliffDecorBlock
| SpriteKind::Orb => Some(16),
SpriteKind::Velorite
| SpriteKind::VeloriteFrag

View File

@ -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
)
}
}

View File

@ -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),
});
}
},
_ => {},
}
}

View File

@ -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<impl Rng>,
start_locations: &mut Vec<Vec2<i32>>,
) -> Option<Id<Civ>> {
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::<Vec<_>>();
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<i32>, 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,
}
})
}
}

View File

@ -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,

View File

@ -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,
},
};

View File

@ -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<common::trade::SiteInformation> {
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(_)
)
}
}

View File

@ -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)
}
}

View File

@ -742,6 +742,51 @@ impl Site {
site
}
pub fn generate_cliff_town(land: &Land, rng: &mut impl Rng, origin: Vec2<i32>) -> 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::<f32>().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<i32>) -> Vec2<i32> {
(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,
};

View File

@ -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),
}

View File

@ -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<i32>,
/// 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<i32>,
door_dir: Vec2<i32>,
tile_aabr: Aabr<i32>,
) -> 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);
}
}
}