mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'zesterer/worldsim' into 'master'
World simulation, generation, and pathfinding improvements (including castles and caves) See merge request veloren/veloren!1282
This commit is contained in:
commit
75c1d44010
@ -54,6 +54,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Some Campfire, fireball & bomb; particle, light & sound effects.
|
||||
- Added firework recipe
|
||||
- Added setting to change resolution
|
||||
- Rare (unfinished) castles
|
||||
- Caves with monsters and treasure
|
||||
- Furniture and decals in towns
|
||||
|
||||
### Changed
|
||||
|
||||
@ -86,6 +89,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Dehardcoded many item variants
|
||||
- Tooltips avoid the mouse better and disappear when hovered
|
||||
- Improved social window functions and visuals
|
||||
- Changed agent behaviour to allow fleeing
|
||||
- Waypoints now spawn on dungeon staircases
|
||||
|
||||
### Removed
|
||||
|
||||
|
21
Cargo.lock
generated
21
Cargo.lock
generated
@ -1189,6 +1189,26 @@ version = "1.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3"
|
||||
|
||||
[[package]]
|
||||
name = "enum-iterator"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c79a6321a1197d7730510c7e3f6cb80432dfefecb32426de8cea0aa19b4bb8d7"
|
||||
dependencies = [
|
||||
"enum-iterator-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "enum-iterator-derive"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e94aa31f7c0dc764f57896dc615ddd76fc13b0d5dca7eb6cc5e018a5a09ec06"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.18",
|
||||
"quote 1.0.7",
|
||||
"syn 1.0.33",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "error-chain"
|
||||
version = "0.12.2"
|
||||
@ -4627,6 +4647,7 @@ dependencies = [
|
||||
"criterion",
|
||||
"crossbeam",
|
||||
"dot_vox",
|
||||
"enum-iterator",
|
||||
"find_folder",
|
||||
"hashbrown",
|
||||
"image",
|
||||
|
12
assets/common/cave_scatter.ron
Normal file
12
assets/common/cave_scatter.ron
Normal file
@ -0,0 +1,12 @@
|
||||
[
|
||||
(20, Velorite),
|
||||
(30, VeloriteFrag),
|
||||
(60, Stones),
|
||||
(15, PurpleFlower),
|
||||
(130, ShortGrass),
|
||||
(120, Mushroom),
|
||||
(8, ShinyGem),
|
||||
(15, Chest),
|
||||
(2, Crate),
|
||||
(0.25, Scarecrow),
|
||||
]
|
@ -13,11 +13,8 @@
|
||||
(0.01, "common.items.utility.bomb_pile"),
|
||||
(0.1, "common.items.utility.bomb"),
|
||||
// crafting ingredients
|
||||
(0.5, "common.items.crafting_ing.shiny_gem"),
|
||||
(2, "common.items.crafting_ing.leather_scraps"),
|
||||
(1, "common.items.crafting_ing.empty_vial"),
|
||||
(2, "common.items.crafting_ing.stones"),
|
||||
(3, "common.items.crafting_ing.twigs"),
|
||||
// swords
|
||||
(0.1, "common.items.weapons.sword.starter_sword"),
|
||||
(0.1, "common.items.weapons.sword.wood_sword"),
|
||||
|
BIN
assets/voxygen/voxel/sprite/castle/drop_gate_bars-0.vox
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/voxel/sprite/castle/drop_gate_bars-0.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/voxel/sprite/castle/drop_gate_bottom-0.vox
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/voxel/sprite/castle/drop_gate_bottom-0.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/voxel/sprite/furniture/drawer_large-0.vox
(Stored with Git LFS)
BIN
assets/voxygen/voxel/sprite/furniture/drawer_large-0.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/voxel/sprite/furniture/drawer_large-1.vox
(Stored with Git LFS)
BIN
assets/voxygen/voxel/sprite/furniture/drawer_large-1.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/voxel/sprite/furniture/lamp_wall-1.vox
(Stored with Git LFS)
BIN
assets/voxygen/voxel/sprite/furniture/lamp_wall-1.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/voxel/sprite/furniture/wardrobe_double-0.vox
(Stored with Git LFS)
BIN
assets/voxygen/voxel/sprite/furniture/wardrobe_double-0.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/voxel/sprite/furniture/wardrobe_double-1.vox
(Stored with Git LFS)
BIN
assets/voxygen/voxel/sprite/furniture/wardrobe_double-1.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/voxel/sprite/furniture/wardrobe_single-0.vox
(Stored with Git LFS)
BIN
assets/voxygen/voxel/sprite/furniture/wardrobe_single-0.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/voxel/sprite/furniture/wardrobe_single-1.vox
(Stored with Git LFS)
BIN
assets/voxygen/voxel/sprite/furniture/wardrobe_single-1.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/voxel/sprite/grass/grass_snow_0.vox
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/voxel/sprite/grass/grass_snow_0.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/voxel/sprite/grass/grass_snow_1.vox
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/voxel/sprite/grass/grass_snow_1.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/voxel/sprite/grass/grass_snow_2.vox
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/voxel/sprite/grass/grass_snow_2.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/voxel/sprite/grass/grass_snow_3.vox
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/voxel/sprite/grass/grass_snow_3.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/voxel/sprite/grass/grass_snow_4.vox
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/voxel/sprite/grass/grass_snow_4.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/voxel/sprite/grass/grass_snow_5.vox
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/voxel/sprite/grass/grass_snow_5.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/voxel/sprite/grass/grass_snow_6.vox
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/voxel/sprite/grass/grass_snow_6.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/voxel/sprite/grass/grass_snow_7.vox
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/voxel/sprite/grass/grass_snow_7.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/voxel/sprite/grass/grass_snow_8.vox
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/voxel/sprite/grass/grass_snow_8.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/voxel/sprite/grass/grass_snow_9.vox
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/voxel/sprite/grass/grass_snow_9.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -150,7 +150,7 @@ impl Client {
|
||||
recipe_book,
|
||||
} => {
|
||||
// TODO: Display that versions don't match in Voxygen
|
||||
if &server_info.git_hash != *common::util::GIT_HASH {
|
||||
if server_info.git_hash != *common::util::GIT_HASH {
|
||||
warn!(
|
||||
"Server is running {}[{}], you are running {}[{}], versions \
|
||||
might be incompatible!",
|
||||
@ -396,7 +396,7 @@ impl Client {
|
||||
}
|
||||
|
||||
pub fn pick_up(&mut self, entity: EcsEntity) {
|
||||
if let Some(uid) = self.state.read_component_copied(entity) {
|
||||
if let Some(uid) = self.state.read_component_cloned(entity) {
|
||||
self.singleton_stream
|
||||
.send(ClientMsg::ControlEvent(ControlEvent::InventoryManip(
|
||||
InventoryManip::Pickup(uid),
|
||||
@ -520,7 +520,7 @@ impl Client {
|
||||
}
|
||||
|
||||
pub fn mount(&mut self, entity: EcsEntity) {
|
||||
if let Some(uid) = self.state.read_component_copied(entity) {
|
||||
if let Some(uid) = self.state.read_component_cloned(entity) {
|
||||
self.singleton_stream
|
||||
.send(ClientMsg::ControlEvent(ControlEvent::Mount(uid)))
|
||||
.unwrap();
|
||||
@ -1294,7 +1294,7 @@ impl Client {
|
||||
pub fn entity(&self) -> EcsEntity { self.entity }
|
||||
|
||||
/// Get the player's Uid.
|
||||
pub fn uid(&self) -> Option<Uid> { self.state.read_component_copied(self.entity) }
|
||||
pub fn uid(&self) -> Option<Uid> { self.state.read_component_cloned(self.entity) }
|
||||
|
||||
/// Get the client state
|
||||
pub fn get_client_state(&self) -> ClientState { self.client_state }
|
||||
@ -1347,7 +1347,7 @@ impl Client {
|
||||
pub fn is_admin(&self) -> bool {
|
||||
let client_uid = self
|
||||
.state
|
||||
.read_component_copied::<Uid>(self.entity)
|
||||
.read_component_cloned::<Uid>(self.entity)
|
||||
.expect("Client doesn't have a Uid!!!");
|
||||
|
||||
self.player_list
|
||||
|
@ -31,6 +31,7 @@ indexmap = "1.3.0"
|
||||
sum_type = "0.2.0"
|
||||
authc = { git = "https://gitlab.com/veloren/auth.git", rev = "b943c85e4a38f5ec60cd18c34c73097640162bfe" }
|
||||
slab = "0.4.2"
|
||||
enum-iterator = "0.6"
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = "0.3"
|
||||
|
@ -121,12 +121,10 @@ impl<S: Clone + Eq + Hash, H: BuildHasher + Clone> Astar<S, H> {
|
||||
{
|
||||
let iter_limit = self.max_iters.min(self.iter + iters);
|
||||
while self.iter < iter_limit {
|
||||
if let Some(PathEntry { node, cost }) = self.potential_nodes.pop() {
|
||||
self.cheapest_cost = Some(cost);
|
||||
if let Some(PathEntry { node, .. }) = self.potential_nodes.pop() {
|
||||
if satisfied(&node) {
|
||||
return PathResult::Path(self.reconstruct_path_to(node));
|
||||
} else {
|
||||
self.cheapest_node = Some(node.clone());
|
||||
for neighbor in neighbors(&node) {
|
||||
let node_cheapest = self.cheapest_scores.get(&node).unwrap_or(&f32::MAX);
|
||||
let neighbor_cheapest =
|
||||
@ -136,12 +134,18 @@ impl<S: Clone + Eq + Hash, H: BuildHasher + Clone> Astar<S, H> {
|
||||
if cost < *neighbor_cheapest {
|
||||
self.came_from.insert(neighbor.clone(), node.clone());
|
||||
self.cheapest_scores.insert(neighbor.clone(), cost);
|
||||
let neighbor_cost = cost + heuristic(&neighbor);
|
||||
let h = heuristic(&neighbor);
|
||||
let neighbor_cost = cost + h;
|
||||
self.final_scores.insert(neighbor.clone(), neighbor_cost);
|
||||
|
||||
if self.cheapest_cost.map(|cc| h < cc).unwrap_or(true) {
|
||||
self.cheapest_node = Some(node.clone());
|
||||
self.cheapest_cost = Some(h);
|
||||
};
|
||||
|
||||
if self.visited.insert(neighbor.clone()) {
|
||||
self.potential_nodes.push(PathEntry {
|
||||
node: neighbor.clone(),
|
||||
node: neighbor,
|
||||
cost: neighbor_cost,
|
||||
});
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::{assets, comp, npc};
|
||||
use crate::{assets, comp, npc, terrain};
|
||||
use lazy_static::lazy_static;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
@ -56,6 +56,7 @@ pub enum ChatCommand {
|
||||
KillNpcs,
|
||||
Lantern,
|
||||
Light,
|
||||
MakeBlock,
|
||||
Motd,
|
||||
Object,
|
||||
Players,
|
||||
@ -98,6 +99,7 @@ pub static CHAT_COMMANDS: &[ChatCommand] = &[
|
||||
ChatCommand::KillNpcs,
|
||||
ChatCommand::Lantern,
|
||||
ChatCommand::Light,
|
||||
ChatCommand::MakeBlock,
|
||||
ChatCommand::Motd,
|
||||
ChatCommand::Object,
|
||||
ChatCommand::Players,
|
||||
@ -149,6 +151,11 @@ lazy_static! {
|
||||
.map(|s| s.to_string())
|
||||
.collect();
|
||||
|
||||
static ref BLOCK_KINDS: Vec<String> = terrain::block::BLOCK_KINDS
|
||||
.keys()
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
/// List of item specifiers. Useful for tab completing
|
||||
static ref ITEM_SPECS: Vec<String> = {
|
||||
let path = assets::ASSETS_PATH.join("common").join("items");
|
||||
@ -281,6 +288,11 @@ impl ChatCommand {
|
||||
"Spawn entity with light",
|
||||
Admin,
|
||||
),
|
||||
ChatCommand::MakeBlock => cmd(
|
||||
vec![Enum("block", BLOCK_KINDS.clone(), Required)],
|
||||
"Make a block",
|
||||
Admin,
|
||||
),
|
||||
ChatCommand::Motd => cmd(
|
||||
vec![Message(Optional)],
|
||||
"View the server description",
|
||||
@ -386,6 +398,7 @@ impl ChatCommand {
|
||||
ChatCommand::KillNpcs => "kill_npcs",
|
||||
ChatCommand::Lantern => "lantern",
|
||||
ChatCommand::Light => "light",
|
||||
ChatCommand::MakeBlock => "make_block",
|
||||
ChatCommand::Motd => "motd",
|
||||
ChatCommand::Object => "object",
|
||||
ChatCommand::Players => "players",
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::{path::Chaser, sync::Uid};
|
||||
use crate::{comp::Body, path::Chaser, sync::Uid};
|
||||
use specs::{Component, Entity as EcsEntity};
|
||||
use specs_idvs::IdvStorage;
|
||||
use vek::*;
|
||||
@ -54,6 +54,33 @@ impl Component for Alignment {
|
||||
type Storage = IdvStorage<Self>;
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct Psyche {
|
||||
pub aggro: f32, // 0.0 = always flees, 1.0 = always attacks
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Body> for Psyche {
|
||||
fn from(body: &'a Body) -> Self {
|
||||
Self {
|
||||
aggro: match body {
|
||||
Body::Humanoid(_) => 0.5,
|
||||
Body::QuadrupedSmall(_) => 0.35,
|
||||
Body::QuadrupedMedium(_) => 0.5,
|
||||
Body::QuadrupedLow(_) => 0.65,
|
||||
Body::BirdMedium(_) => 1.0,
|
||||
Body::BirdSmall(_) => 0.2,
|
||||
Body::FishMedium(_) => 0.15,
|
||||
Body::FishSmall(_) => 0.0,
|
||||
Body::BipedLarge(_) => 1.0,
|
||||
Body::Object(_) => 0.0,
|
||||
Body::Golem(_) => 1.0,
|
||||
Body::Critter(_) => 0.1,
|
||||
Body::Dragon(_) => 1.0,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct Agent {
|
||||
pub patrol_origin: Option<Vec3<f32>>,
|
||||
@ -61,6 +88,7 @@ pub struct Agent {
|
||||
/// Does the agent talk when e.g. hit by the player
|
||||
// TODO move speech patterns into a Behavior component
|
||||
pub can_speak: bool,
|
||||
pub psyche: Psyche,
|
||||
}
|
||||
|
||||
impl Agent {
|
||||
@ -69,11 +97,12 @@ impl Agent {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn new(origin: Vec3<f32>, can_speak: bool) -> Self {
|
||||
pub fn new(origin: Vec3<f32>, can_speak: bool, body: &Body) -> Self {
|
||||
let patrol_origin = Some(origin);
|
||||
Agent {
|
||||
patrol_origin,
|
||||
can_speak,
|
||||
psyche: Psyche::from(body),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
pub mod armor;
|
||||
pub mod lottery;
|
||||
pub mod tool;
|
||||
|
||||
// Reexports
|
||||
@ -8,6 +7,7 @@ pub use tool::{Hands, Tool, ToolCategory, ToolKind};
|
||||
use crate::{
|
||||
assets::{self, Asset},
|
||||
effect::Effect,
|
||||
lottery::Lottery,
|
||||
terrain::{Block, BlockKind},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
@ -171,7 +171,7 @@ impl Item {
|
||||
BlockKind::ShortGrass => Some(assets::load_expect_cloned("common.items.grasses.short")),
|
||||
BlockKind::Coconut => Some(assets::load_expect_cloned("common.items.food.coconut")),
|
||||
BlockKind::Chest => {
|
||||
let chosen = assets::load_expect::<lottery::Lottery<_>>("common.loot_table");
|
||||
let chosen = assets::load_expect::<Lottery<String>>("common.loot_table");
|
||||
let chosen = chosen.choose();
|
||||
|
||||
Some(assets::load_expect_cloned(chosen))
|
||||
|
@ -503,7 +503,7 @@ impl Inventory {
|
||||
}
|
||||
}
|
||||
|
||||
if missing.len() == 0 {
|
||||
if missing.is_empty() {
|
||||
Ok(slot_claims)
|
||||
} else {
|
||||
Err(missing)
|
||||
|
@ -80,7 +80,7 @@ pub struct PhysicsState {
|
||||
pub on_ceiling: bool,
|
||||
pub on_wall: Option<Vec3<f32>>,
|
||||
pub touch_entity: Option<Uid>,
|
||||
pub in_fluid: bool,
|
||||
pub in_fluid: Option<f32>, // Depth
|
||||
}
|
||||
|
||||
impl PhysicsState {
|
||||
|
@ -116,6 +116,8 @@ impl EntityInfo {
|
||||
},
|
||||
Body::Dragon(body) => Some(get_npc_name(&NPC_NAMES.dragon, body.species)),
|
||||
Body::QuadrupedLow(body) => Some(get_npc_name(&NPC_NAMES.quadruped_low, body.species)),
|
||||
Body::Golem(body) => Some(get_npc_name(&NPC_NAMES.golem, body.species)),
|
||||
Body::BipedLarge(body) => Some(get_npc_name(&NPC_NAMES.biped_large, body.species)),
|
||||
_ => None,
|
||||
}
|
||||
.map(|s| {
|
||||
|
@ -22,6 +22,7 @@ pub mod event;
|
||||
pub mod figure;
|
||||
pub mod generation;
|
||||
pub mod loadout_builder;
|
||||
pub mod lottery;
|
||||
pub mod msg;
|
||||
pub mod npc;
|
||||
pub mod outcome;
|
||||
|
@ -1,25 +1,19 @@
|
||||
use crate::assets::{self, Asset};
|
||||
use rand::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||
use std::{fs::File, io::BufReader};
|
||||
|
||||
// Generate a random float between 0 and 1
|
||||
pub fn rand() -> f32 {
|
||||
let mut rng = rand::thread_rng();
|
||||
rng.gen()
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Lottery<T> {
|
||||
items: Vec<(f32, T)>,
|
||||
total: f32,
|
||||
}
|
||||
|
||||
impl Asset for Lottery<String> {
|
||||
impl<T: DeserializeOwned + Send + Sync> Asset for Lottery<T> {
|
||||
const ENDINGS: &'static [&'static str] = &["ron"];
|
||||
|
||||
fn parse(buf_reader: BufReader<File>) -> Result<Self, assets::Error> {
|
||||
ron::de::from_reader::<BufReader<File>, Vec<(f32, String)>>(buf_reader)
|
||||
ron::de::from_reader::<BufReader<File>, Vec<(f32, T)>>(buf_reader)
|
||||
.map(|items| Lottery::from_rates(items.into_iter()))
|
||||
.map_err(assets::Error::parse_error)
|
||||
}
|
||||
@ -37,8 +31,8 @@ impl<T> Lottery<T> {
|
||||
Self { items, total }
|
||||
}
|
||||
|
||||
pub fn choose(&self) -> &T {
|
||||
let x = rand() * self.total;
|
||||
pub fn choose_seeded(&self, seed: u32) -> &T {
|
||||
let x = ((seed % 65536) as f32 / 65536.0) * self.total;
|
||||
&self.items[self
|
||||
.items
|
||||
.binary_search_by(|(y, _)| y.partial_cmp(&x).unwrap())
|
||||
@ -46,19 +40,18 @@ impl<T> Lottery<T> {
|
||||
.1
|
||||
}
|
||||
|
||||
pub fn choose(&self) -> &T { self.choose_seeded(thread_rng().gen()) }
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = &(f32, T)> { self.items.iter() }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{
|
||||
assets,
|
||||
comp::inventory::item::{lottery::Lottery, Item},
|
||||
};
|
||||
use super::*;
|
||||
use crate::{assets, comp::Item};
|
||||
#[test]
|
||||
fn test_loot_table() {
|
||||
let test = assets::load_expect::<Lottery<_>>("common.loot_table");
|
||||
let test = test;
|
||||
let test = assets::load_expect::<Lottery<String>>("common.loot_table");
|
||||
|
||||
for (_, item) in test.iter() {
|
||||
assert!(
|
@ -68,6 +68,17 @@ pub struct TraversalConfig {
|
||||
pub min_tgt_dist: f32,
|
||||
}
|
||||
|
||||
const DIAGONALS: [Vec2<i32>; 8] = [
|
||||
Vec2::new(1, 0),
|
||||
Vec2::new(1, 1),
|
||||
Vec2::new(0, 1),
|
||||
Vec2::new(-1, 1),
|
||||
Vec2::new(-1, 0),
|
||||
Vec2::new(-1, -1),
|
||||
Vec2::new(0, -1),
|
||||
Vec2::new(1, -1),
|
||||
];
|
||||
|
||||
impl Route {
|
||||
pub fn path(&self) -> &Path<Vec3<i32>> { &self.path }
|
||||
|
||||
@ -88,45 +99,29 @@ impl Route {
|
||||
V: BaseVol<Vox = Block> + ReadVol,
|
||||
{
|
||||
let (next0, next1, next_tgt, be_precise) = loop {
|
||||
// If we've reached the end of the path, stop
|
||||
self.next(0)?;
|
||||
|
||||
let next0 = self
|
||||
.next(0)
|
||||
.unwrap_or_else(|| pos.map(|e| e.floor() as i32));
|
||||
let next1 = self.next(1).unwrap_or(next0);
|
||||
|
||||
// Stop using obstructed paths
|
||||
if vol.get(next0).map(|b| b.is_solid()).unwrap_or(false) {
|
||||
if !walkable(vol, next1) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let diagonals = [
|
||||
Vec2::new(1, 0),
|
||||
Vec2::new(1, 1),
|
||||
Vec2::new(0, 1),
|
||||
Vec2::new(-1, 1),
|
||||
Vec2::new(-1, 0),
|
||||
Vec2::new(-1, -1),
|
||||
Vec2::new(0, -1),
|
||||
Vec2::new(1, -1),
|
||||
];
|
||||
|
||||
let next1 = self.next(1).unwrap_or(next0);
|
||||
|
||||
let be_precise = diagonals.iter().any(|pos| {
|
||||
!walkable(vol, next0 + Vec3::new(pos.x, pos.y, 0))
|
||||
&& !walkable(vol, next0 + Vec3::new(pos.x, pos.y, -1))
|
||||
&& !walkable(vol, next0 + Vec3::new(pos.x, pos.y, -2))
|
||||
&& !walkable(vol, next0 + Vec3::new(pos.x, pos.y, 1))
|
||||
let be_precise = DIAGONALS.iter().any(|pos| {
|
||||
(-1..2).all(|z| {
|
||||
vol.get(next0 + Vec3::new(pos.x, pos.y, z))
|
||||
.map(|b| !b.is_solid())
|
||||
.unwrap_or(false)
|
||||
})
|
||||
});
|
||||
|
||||
let next0_tgt = next0.map(|e| e as f32) + Vec3::new(0.5, 0.5, 0.0);
|
||||
let next1_tgt = next1.map(|e| e as f32) + Vec3::new(0.5, 0.5, 0.0);
|
||||
let next_tgt = next0_tgt;
|
||||
|
||||
// Maybe skip a node (useful with traversing downhill)
|
||||
let closest_tgt = if next0_tgt.distance_squared(pos) < next1_tgt.distance_squared(pos) {
|
||||
next0_tgt
|
||||
} else {
|
||||
next1_tgt
|
||||
};
|
||||
let next_tgt = next0.map(|e| e as f32) + Vec3::new(0.5, 0.5, 0.0);
|
||||
let closest_tgt = next_tgt.map2(pos, |tgt, pos| pos.clamped(tgt.floor(), tgt.ceil()));
|
||||
|
||||
// Determine whether we're close enough to the next to to consider it completed
|
||||
let dist_sqrd = pos.xy().distance_squared(closest_tgt.xy());
|
||||
@ -135,12 +130,12 @@ impl Route {
|
||||
&& (pos.z - closest_tgt.z < 1.2 || (pos.z - closest_tgt.z < 2.9 && vel.z < -0.05))
|
||||
&& vel.z <= 0.0
|
||||
// Only consider the node reached if there's nothing solid between us and it
|
||||
&& vol
|
||||
&& (vol
|
||||
.ray(pos + Vec3::unit_z() * 1.5, closest_tgt + Vec3::unit_z() * 1.5)
|
||||
.until(|block| block.is_solid())
|
||||
.cast()
|
||||
.0
|
||||
> pos.distance(closest_tgt) * 0.9
|
||||
> pos.distance(closest_tgt) * 0.9 || dist_sqrd < 0.5)
|
||||
&& self.next_idx < self.path.len()
|
||||
{
|
||||
// Node completed, move on to the next one
|
||||
@ -312,7 +307,7 @@ impl Route {
|
||||
#[derive(Default, Clone, Debug)]
|
||||
pub struct Chaser {
|
||||
last_search_tgt: Option<Vec3<f32>>,
|
||||
route: Option<Route>,
|
||||
route: Option<(Route, bool)>,
|
||||
/// We use this hasher (AAHasher) because:
|
||||
/// (1) we care about DDOS attacks (ruling out FxHash);
|
||||
/// (2) we don't care about determinism across computers (we can use
|
||||
@ -335,14 +330,24 @@ impl Chaser {
|
||||
let pos_to_tgt = pos.distance(tgt);
|
||||
|
||||
// If we're already close to the target then there's nothing to do
|
||||
if ((pos - tgt) * Vec3::new(1.0, 1.0, 2.0)).magnitude_squared()
|
||||
let end = self
|
||||
.route
|
||||
.as_ref()
|
||||
.and_then(|(r, _)| r.path.end().copied())
|
||||
.map(|e| e.map(|e| e as f32 + 0.5))
|
||||
.unwrap_or(tgt);
|
||||
if ((pos - end) * Vec3::new(1.0, 1.0, 2.0)).magnitude_squared()
|
||||
< traversal_cfg.min_tgt_dist.powf(2.0)
|
||||
{
|
||||
self.route = None;
|
||||
return None;
|
||||
}
|
||||
|
||||
let bearing = if let Some(end) = self.route.as_ref().and_then(|r| r.path().end().copied()) {
|
||||
let bearing = if let Some((end, complete)) = self
|
||||
.route
|
||||
.as_ref()
|
||||
.and_then(|(r, complete)| Some((r.path().end().copied()?, *complete)))
|
||||
{
|
||||
let end_to_tgt = end.map(|e| e as f32).distance(tgt);
|
||||
// If the target has moved significantly since the path was generated then it's
|
||||
// time to search for a new path. Also, do this randomly from time
|
||||
@ -350,21 +355,14 @@ impl Chaser {
|
||||
// theory this shouldn't happen, but in practice the world is full
|
||||
// of unpredictable obstacles that are more than willing to mess up
|
||||
// our day. TODO: Come up with a better heuristic for this
|
||||
if end_to_tgt > pos_to_tgt * 0.3 + 5.0
|
||||
/* || thread_rng().gen::<f32>() < 0.005 */
|
||||
if (end_to_tgt > pos_to_tgt * 0.3 + 5.0 && complete)
|
||||
|| thread_rng().gen::<f32>() < 0.001
|
||||
{
|
||||
None
|
||||
} else {
|
||||
self.route
|
||||
.as_mut()
|
||||
.and_then(|r| r.traverse(vol, pos, vel, traversal_cfg))
|
||||
// In theory this filter isn't needed, but in practice agents often try to take
|
||||
// stale paths that start elsewhere. This code makes sure that we're only using
|
||||
// paths that start near us, avoiding the agent doubling back to chase a stale
|
||||
// path.
|
||||
.filter(|(bearing, _)| bearing.xy()
|
||||
.magnitude_squared() < 1.75f32.powf(2.0)
|
||||
&& thread_rng().gen::<f32>() > 0.025)
|
||||
.and_then(|(r, _)| r.traverse(vol, pos, vel, traversal_cfg))
|
||||
}
|
||||
} else {
|
||||
None
|
||||
@ -373,6 +371,8 @@ impl Chaser {
|
||||
if let Some((bearing, speed)) = bearing {
|
||||
Some((bearing, speed))
|
||||
} else {
|
||||
let tgt_dir = (tgt - pos).xy().try_normalized().unwrap_or_default();
|
||||
|
||||
// Only search for a path if the target has moved from their last position. We
|
||||
// don't want to be thrashing the pathfinding code for targets that
|
||||
// we're unable to access!
|
||||
@ -383,16 +383,45 @@ impl Chaser {
|
||||
|| self.astar.is_some()
|
||||
|| self.route.is_none()
|
||||
{
|
||||
let (start_pos, path) = find_path(&mut self.astar, vol, pos, tgt);
|
||||
// Don't use a stale path
|
||||
if start_pos.distance_squared(pos) < 4.0f32.powf(2.0) {
|
||||
self.route = path.map(Route::from);
|
||||
} else {
|
||||
self.route = None;
|
||||
}
|
||||
self.last_search_tgt = Some(tgt);
|
||||
|
||||
let (path, complete) = find_path(&mut self.astar, vol, pos, tgt);
|
||||
|
||||
self.route = path.map(|path| {
|
||||
let start_index = path
|
||||
.iter()
|
||||
.enumerate()
|
||||
.min_by_key(|(_, node)| {
|
||||
node.xy()
|
||||
.map(|e| e as f32)
|
||||
.distance_squared(pos.xy() + tgt_dir)
|
||||
as i32
|
||||
})
|
||||
.map(|(idx, _)| idx);
|
||||
|
||||
(
|
||||
Route {
|
||||
path,
|
||||
next_idx: start_index.unwrap_or(0),
|
||||
},
|
||||
complete,
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
Some(((tgt - pos) * Vec3::new(1.0, 1.0, 0.0), 0.75))
|
||||
let walking_towards_edge = (-3..2).all(|z| {
|
||||
vol.get(
|
||||
(pos + Vec3::<f32>::from(tgt_dir) * 2.5).map(|e| e as i32) + Vec3::unit_z() * z,
|
||||
)
|
||||
.map(|b| !b.is_solid())
|
||||
.unwrap_or(false)
|
||||
});
|
||||
|
||||
if !walking_towards_edge {
|
||||
Some(((tgt - pos) * Vec3::new(1.0, 1.0, 0.0), 0.75))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -415,13 +444,14 @@ where
|
||||
.unwrap_or(true)
|
||||
}
|
||||
|
||||
#[allow(clippy::float_cmp)] // TODO: Pending review in #587
|
||||
/// Attempt to search for a path to a target, returning the path (if one was
|
||||
/// found) and whether it is complete (reaches the target)
|
||||
fn find_path<V>(
|
||||
astar: &mut Option<Astar<Vec3<i32>, DefaultHashBuilder>>,
|
||||
vol: &V,
|
||||
startf: Vec3<f32>,
|
||||
endf: Vec3<f32>,
|
||||
) -> (Vec3<f32>, Option<Path<Vec3<i32>>>)
|
||||
) -> (Option<Path<Vec3<i32>>>, bool)
|
||||
where
|
||||
V: BaseVol<Vox = Block> + ReadVol,
|
||||
{
|
||||
@ -443,29 +473,33 @@ where
|
||||
get_walkable_z(endf.map(|e| e.floor() as i32)),
|
||||
) {
|
||||
(Some(start), Some(end)) => (start, end),
|
||||
_ => return (startf, None),
|
||||
_ => return (None, false),
|
||||
};
|
||||
|
||||
let heuristic = |pos: &Vec3<i32>| (pos.distance_squared(end) as f32).sqrt();
|
||||
let neighbors = |pos: &Vec3<i32>| {
|
||||
let pos = *pos;
|
||||
const DIRS: [Vec3<i32>; 17] = [
|
||||
const DIRS: [Vec3<i32>; 21] = [
|
||||
Vec3::new(0, 1, 0), // Forward
|
||||
Vec3::new(0, 1, 1), // Forward upward
|
||||
Vec3::new(0, 1, 2), // Forward Upwardx2
|
||||
Vec3::new(0, 1, -1), // Forward downward
|
||||
Vec3::new(0, 1, -2), // Forward downwardx2
|
||||
Vec3::new(1, 0, 0), // Right
|
||||
Vec3::new(1, 0, 1), // Right upward
|
||||
Vec3::new(1, 0, 2), // Right Upwardx2
|
||||
Vec3::new(1, 0, -1), // Right downward
|
||||
Vec3::new(1, 0, -2), // Right downwardx2
|
||||
Vec3::new(0, -1, 0), // Backwards
|
||||
Vec3::new(0, -1, 1), // Backward Upward
|
||||
Vec3::new(0, -1, 2), // Backward Upwardx2
|
||||
Vec3::new(0, -1, -1), // Backward downward
|
||||
Vec3::new(0, -1, -2), // Backward downwardx2
|
||||
Vec3::new(-1, 0, 0), // Left
|
||||
Vec3::new(-1, 0, 1), // Left upward
|
||||
Vec3::new(-1, 0, 2), // Left Upwardx2
|
||||
Vec3::new(-1, 0, -1), // Left downward
|
||||
Vec3::new(-1, 0, -2), // Left downwardx2
|
||||
Vec3::new(0, 0, -1), // Downwards
|
||||
];
|
||||
|
||||
@ -541,19 +575,19 @@ where
|
||||
|
||||
*astar = Some(new_astar);
|
||||
|
||||
(startf, match path_result {
|
||||
match path_result {
|
||||
PathResult::Path(path) => {
|
||||
*astar = None;
|
||||
Some(path)
|
||||
(Some(path), true)
|
||||
},
|
||||
PathResult::None(path) => {
|
||||
*astar = None;
|
||||
Some(path)
|
||||
(Some(path), false)
|
||||
},
|
||||
PathResult::Exhausted(path) => {
|
||||
*astar = None;
|
||||
Some(path)
|
||||
(Some(path), false)
|
||||
},
|
||||
PathResult::Pending => None,
|
||||
})
|
||||
PathResult::Pending => (None, false),
|
||||
}
|
||||
}
|
||||
|
@ -11,12 +11,17 @@ pub struct Spiral2d {
|
||||
impl Spiral2d {
|
||||
#[allow(clippy::new_without_default)] // TODO: Pending review in #587
|
||||
pub fn new() -> Self { Self { layer: 0, i: 0 } }
|
||||
|
||||
pub fn radius(self, radius: i32) -> impl Iterator<Item = Vec2<i32>> {
|
||||
self.take((radius * 2 + 1).pow(2) as usize)
|
||||
.filter(move |pos| pos.magnitude_squared() < (radius + 1).pow(2))
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for Spiral2d {
|
||||
type Item = Vec2<i32>;
|
||||
|
||||
#[allow(clippy::erasing_op)]
|
||||
#[allow(clippy::erasing_op, clippy::identity_op)]
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let layer_size = (self.layer * 8 + 4 * self.layer.min(1) - 4).max(1);
|
||||
if self.i >= layer_size {
|
||||
|
@ -199,8 +199,8 @@ impl State {
|
||||
}
|
||||
|
||||
/// Read a component attributed to a particular entity.
|
||||
pub fn read_component_copied<C: Component + Copy>(&self, entity: EcsEntity) -> Option<C> {
|
||||
self.ecs.read_storage().get(entity).copied()
|
||||
pub fn read_component_cloned<C: Component + Copy>(&self, entity: EcsEntity) -> Option<C> {
|
||||
self.ecs.read_storage().get(entity).cloned()
|
||||
}
|
||||
|
||||
/// Get a read-only reference to the storage of a particular component type.
|
||||
|
@ -24,7 +24,12 @@ impl CharacterBehavior for Data {
|
||||
update.character = CharacterState::GlideWield;
|
||||
return update;
|
||||
}
|
||||
if data.physics.in_fluid {
|
||||
if data
|
||||
.physics
|
||||
.in_fluid
|
||||
.map(|depth| depth > 0.5)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
update.character = CharacterState::Idle;
|
||||
}
|
||||
// If there is a wall in front of character and they are trying to climb go to
|
||||
|
@ -19,7 +19,12 @@ impl CharacterBehavior for Data {
|
||||
if !data.physics.on_ground {
|
||||
update.character = CharacterState::Glide;
|
||||
}
|
||||
if data.physics.in_fluid {
|
||||
if data
|
||||
.physics
|
||||
.in_fluid
|
||||
.map(|depth| depth > 0.5)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
update.character = CharacterState::Idle;
|
||||
}
|
||||
|
||||
|
@ -8,7 +8,7 @@ use crate::{
|
||||
sys::{character_behavior::JoinData, phys::GRAVITY},
|
||||
util::Dir,
|
||||
};
|
||||
use vek::vec::Vec2;
|
||||
use vek::*;
|
||||
|
||||
pub const MOVEMENT_THRESHOLD_VEL: f32 = 3.0;
|
||||
const BASE_HUMANOID_AIR_ACCEL: f32 = 8.0;
|
||||
@ -67,8 +67,8 @@ impl Body {
|
||||
|
||||
/// Handles updating `Components` to move player based on state of `JoinData`
|
||||
pub fn handle_move(data: &JoinData, update: &mut StateUpdate, efficiency: f32) {
|
||||
if data.physics.in_fluid {
|
||||
swim_move(data, update, efficiency);
|
||||
if let Some(depth) = data.physics.in_fluid {
|
||||
swim_move(data, update, efficiency, depth);
|
||||
} else {
|
||||
basic_move(data, update, efficiency);
|
||||
}
|
||||
@ -104,7 +104,7 @@ pub fn handle_orientation(data: &JoinData, update: &mut StateUpdate, rate: f32)
|
||||
}
|
||||
|
||||
/// Updates components to move player as if theyre swimming
|
||||
fn swim_move(data: &JoinData, update: &mut StateUpdate, efficiency: f32) {
|
||||
fn swim_move(data: &JoinData, update: &mut StateUpdate, efficiency: f32, depth: f32) {
|
||||
// Update velocity
|
||||
update.vel.0 += Vec2::broadcast(data.dt.0)
|
||||
* data.inputs.move_dir
|
||||
@ -119,8 +119,9 @@ fn swim_move(data: &JoinData, update: &mut StateUpdate, efficiency: f32) {
|
||||
|
||||
// Swim
|
||||
if data.inputs.swimup.is_pressed() {
|
||||
update.vel.0.z =
|
||||
(update.vel.0.z + data.dt.0 * GRAVITY * 4.0).min(BASE_HUMANOID_WATER_SPEED);
|
||||
update.vel.0.z = (update.vel.0.z
|
||||
+ data.dt.0 * GRAVITY * 4.0 * depth.clamped(0.0, 1.0).powf(3.0))
|
||||
.min(BASE_HUMANOID_WATER_SPEED);
|
||||
}
|
||||
// Swim
|
||||
if data.inputs.swimdown.is_pressed() {
|
||||
@ -192,14 +193,28 @@ pub fn attempt_swap_loadout(data: &JoinData, update: &mut StateUpdate) {
|
||||
|
||||
/// Checks that player can wield the glider and updates `CharacterState` if so
|
||||
pub fn attempt_glide_wield(data: &JoinData, update: &mut StateUpdate) {
|
||||
if data.physics.on_ground && !data.physics.in_fluid && data.body.is_humanoid() {
|
||||
if data.physics.on_ground
|
||||
&& !data
|
||||
.physics
|
||||
.in_fluid
|
||||
.map(|depth| depth > 1.0)
|
||||
.unwrap_or(false)
|
||||
&& data.body.is_humanoid()
|
||||
{
|
||||
update.character = CharacterState::GlideWield;
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks that player can jump and sends jump event if so
|
||||
pub fn handle_jump(data: &JoinData, update: &mut StateUpdate) {
|
||||
if data.inputs.jump.is_pressed() && data.physics.on_ground && !data.physics.in_fluid {
|
||||
if data.inputs.jump.is_pressed()
|
||||
&& data.physics.on_ground
|
||||
&& !data
|
||||
.physics
|
||||
.in_fluid
|
||||
.map(|depth| depth > 1.0)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
update
|
||||
.local_events
|
||||
.push_front(LocalEvent::Jump(data.entity));
|
||||
|
@ -50,20 +50,17 @@ impl<T> Store<T> {
|
||||
}
|
||||
|
||||
pub fn ids(&self) -> impl Iterator<Item = Id<T>> {
|
||||
// NOTE: Assumes usize fits into 8 bytes.
|
||||
(0..self.items.len() as u64).map(|i| Id(i, PhantomData))
|
||||
(0..self.items.len()).map(|i| Id(i as u64, PhantomData))
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = &T> { self.items.iter() }
|
||||
pub fn values(&self) -> impl Iterator<Item = &T> { self.items.iter() }
|
||||
|
||||
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut T> { self.items.iter_mut() }
|
||||
pub fn values_mut(&mut self) -> impl Iterator<Item = &mut T> { self.items.iter_mut() }
|
||||
|
||||
pub fn iter_ids(&self) -> impl Iterator<Item = (Id<T>, &T)> {
|
||||
self.items
|
||||
.iter()
|
||||
.enumerate()
|
||||
// NOTE: Assumes usize fits into 8 bytes.
|
||||
.map(|(i, item)| (Id(i as u64, PhantomData), item))
|
||||
pub fn iter(&self) -> impl Iterator<Item = (Id<T>, &T)> { self.ids().zip(self.values()) }
|
||||
|
||||
pub fn iter_mut(&mut self) -> impl Iterator<Item = (Id<T>, &mut T)> {
|
||||
self.ids().zip(self.values_mut())
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, item: T) -> Id<T> {
|
||||
|
@ -151,6 +151,7 @@ impl<'a> System<'a> for Sys {
|
||||
const SEARCH_DIST: f32 = 48.0;
|
||||
const SIGHT_DIST: f32 = 128.0;
|
||||
const MIN_ATTACK_DIST: f32 = 3.5;
|
||||
const MAX_FLEE_DIST: f32 = 32.0;
|
||||
|
||||
let scale = scales.get(entity).map(|s| s.0).unwrap_or(1.0);
|
||||
|
||||
@ -158,7 +159,7 @@ impl<'a> System<'a> for Sys {
|
||||
// and so can afford to be less precise when trying to move around
|
||||
// the world (especially since they would otherwise get stuck on
|
||||
// obstacles that smaller entities would not).
|
||||
let node_tolerance = scale + vel.0.xy().magnitude() * 0.2;
|
||||
let node_tolerance = scale * 1.5;
|
||||
let slow_factor = body.map(|b| b.base_accel() / 250.0).unwrap_or(0.0).min(1.0);
|
||||
|
||||
let mut do_idle = false;
|
||||
@ -240,6 +241,8 @@ impl<'a> System<'a> for Sys {
|
||||
bearing.xy().try_normalized().unwrap_or(Vec2::zero())
|
||||
* speed.min(0.2 + (dist - AVG_FOLLOW_DIST) / 8.0);
|
||||
inputs.jump.set_state(bearing.z > 1.5);
|
||||
inputs.swimup.set_state(bearing.z > 0.5);
|
||||
inputs.swimdown.set_state(bearing.z < 0.5);
|
||||
}
|
||||
} else {
|
||||
do_idle = true;
|
||||
@ -297,7 +300,47 @@ impl<'a> System<'a> for Sys {
|
||||
}
|
||||
|
||||
let dist_sqrd = pos.0.distance_squared(tgt_pos.0);
|
||||
if dist_sqrd < (MIN_ATTACK_DIST * scale).powf(2.0) {
|
||||
|
||||
let damage = stats
|
||||
.get(entity)
|
||||
.map(|s| s.health.current() as f32 / s.health.maximum() as f32)
|
||||
.unwrap_or(0.5);
|
||||
|
||||
// Flee
|
||||
let flees = alignment
|
||||
.map(|a| !matches!(a, Alignment::Enemy | Alignment::Owned(_)))
|
||||
.unwrap_or(true);
|
||||
if 1.0 - agent.psyche.aggro > damage && flees {
|
||||
if dist_sqrd < MAX_FLEE_DIST.powf(2.0) {
|
||||
if let Some((bearing, speed)) = chaser.chase(
|
||||
&*terrain,
|
||||
pos.0,
|
||||
vel.0,
|
||||
// Away from the target (ironically)
|
||||
pos.0
|
||||
+ (pos.0 - tgt_pos.0)
|
||||
.try_normalized()
|
||||
.unwrap_or_else(Vec3::unit_y)
|
||||
* 8.0,
|
||||
TraversalConfig {
|
||||
node_tolerance,
|
||||
slow_factor,
|
||||
on_ground: physics_state.on_ground,
|
||||
min_tgt_dist: 1.25,
|
||||
},
|
||||
) {
|
||||
inputs.move_dir = Vec2::from(bearing)
|
||||
.try_normalized()
|
||||
.unwrap_or(Vec2::zero())
|
||||
* speed;
|
||||
inputs.jump.set_state(bearing.z > 1.5);
|
||||
inputs.swimup.set_state(bearing.z > 0.5);
|
||||
inputs.swimdown.set_state(bearing.z < 0.5);
|
||||
}
|
||||
} else {
|
||||
do_idle = true;
|
||||
}
|
||||
} else if dist_sqrd < (MIN_ATTACK_DIST * scale).powf(2.0) {
|
||||
// Close-range attack
|
||||
inputs.move_dir = Vec2::from(tgt_pos.0 - pos.0)
|
||||
.try_normalized()
|
||||
@ -360,6 +403,8 @@ impl<'a> System<'a> for Sys {
|
||||
.unwrap_or(Vec2::zero())
|
||||
* speed;
|
||||
inputs.jump.set_state(bearing.z > 1.5);
|
||||
inputs.swimup.set_state(bearing.z > 0.5);
|
||||
inputs.swimdown.set_state(bearing.z < 0.5);
|
||||
}
|
||||
|
||||
if dist_sqrd < 16.0f32.powf(2.0)
|
||||
@ -427,7 +472,7 @@ impl<'a> System<'a> for Sys {
|
||||
// Attack a target that's attacking us
|
||||
if let Some(my_stats) = stats.get(entity) {
|
||||
// Only if the attack was recent
|
||||
if my_stats.health.last_change.0 < 5.0 {
|
||||
if my_stats.health.last_change.0 < 3.0 {
|
||||
if let comp::HealthSource::Attack { by }
|
||||
| comp::HealthSource::Projectile { owner: Some(by) } =
|
||||
my_stats.health.last_change.1.cause
|
||||
@ -436,20 +481,26 @@ impl<'a> System<'a> for Sys {
|
||||
if let Some(attacker) = uid_allocator.retrieve_entity_internal(by.id())
|
||||
{
|
||||
if stats.get(attacker).map_or(false, |a| !a.is_dead) {
|
||||
if agent.can_speak {
|
||||
let msg = "npc.speech.villager_under_attack".to_string();
|
||||
event_bus.emit_now(ServerEvent::Chat(
|
||||
UnresolvedChatMsg::npc(*uid, msg),
|
||||
));
|
||||
}
|
||||
match agent.activity {
|
||||
Activity::Attack { target, .. } if target == attacker => {},
|
||||
_ => {
|
||||
if agent.can_speak {
|
||||
let msg =
|
||||
"npc.speech.villager_under_attack".to_string();
|
||||
event_bus.emit_now(ServerEvent::Chat(
|
||||
UnresolvedChatMsg::npc(*uid, msg),
|
||||
));
|
||||
}
|
||||
|
||||
agent.activity = Activity::Attack {
|
||||
target: attacker,
|
||||
chaser: Chaser::default(),
|
||||
time: time.0,
|
||||
been_close: false,
|
||||
powerup: 0.0,
|
||||
};
|
||||
agent.activity = Activity::Attack {
|
||||
target: attacker,
|
||||
chaser: Chaser::default(),
|
||||
time: time.0,
|
||||
been_close: false,
|
||||
powerup: 0.0,
|
||||
};
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ use crate::{
|
||||
use specs::{
|
||||
saveload::MarkerAllocator, Entities, Join, Read, ReadExpect, ReadStorage, System, WriteStorage,
|
||||
};
|
||||
use std::ops::Range;
|
||||
use vek::*;
|
||||
|
||||
pub const GRAVITY: f32 = 9.81 * 5.0;
|
||||
@ -93,7 +94,7 @@ impl<'a> System<'a> for Sys {
|
||||
let mut event_emitter = event_bus.emitter();
|
||||
|
||||
// Apply movement inputs
|
||||
for (entity, scale, sticky, collider, mut pos, mut vel, _ori, _) in (
|
||||
for (entity, _scale, sticky, collider, mut pos, mut vel, _ori, _) in (
|
||||
&entities,
|
||||
scales.maybe(),
|
||||
stickies.maybe(),
|
||||
@ -112,7 +113,8 @@ impl<'a> System<'a> for Sys {
|
||||
continue;
|
||||
}
|
||||
|
||||
let scale = scale.map(|s| s.0).unwrap_or(1.0);
|
||||
// TODO: Use this
|
||||
//let scale = scale.map(|s| s.0).unwrap_or(1.0);
|
||||
|
||||
let old_vel = *vel;
|
||||
// Integrate forces
|
||||
@ -123,7 +125,7 @@ impl<'a> System<'a> for Sys {
|
||||
} else {
|
||||
0.0
|
||||
})
|
||||
.max(if physics_state.in_fluid {
|
||||
.max(if physics_state.in_fluid.is_some() {
|
||||
FRIC_FLUID
|
||||
} else {
|
||||
0.0
|
||||
@ -133,7 +135,11 @@ impl<'a> System<'a> for Sys {
|
||||
.is_some();
|
||||
let downward_force = if !in_loaded_chunk {
|
||||
0.0 // No gravity in unloaded chunks
|
||||
} else if physics_state.in_fluid {
|
||||
} else if physics_state
|
||||
.in_fluid
|
||||
.map(|depth| depth > 0.75)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
(1.0 - BOUYANCY) * GRAVITY
|
||||
} else {
|
||||
GRAVITY
|
||||
@ -157,9 +163,9 @@ impl<'a> System<'a> for Sys {
|
||||
z_max,
|
||||
} => {
|
||||
// Scale collider
|
||||
let radius = *radius * scale;
|
||||
let z_min = *z_min * scale;
|
||||
let z_max = *z_max * scale;
|
||||
let radius = *radius; // * scale;
|
||||
let z_min = *z_min; // * scale;
|
||||
let z_max = *z_max; // * scale;
|
||||
|
||||
// Probe distances
|
||||
let hdist = radius.ceil() as i32;
|
||||
@ -175,18 +181,23 @@ impl<'a> System<'a> for Sys {
|
||||
.flatten()
|
||||
.flatten();
|
||||
|
||||
// Function for determining whether the player at a specific position collides
|
||||
// with the ground
|
||||
let collision_with = |pos: Vec3<f32>,
|
||||
hit: &dyn Fn(&Block) -> bool,
|
||||
near_iter| {
|
||||
for (i, j, k) in near_iter {
|
||||
// Function for iterating over the blocks the player at a specific position
|
||||
// collides with
|
||||
fn collision_iter<'a>(
|
||||
pos: Vec3<f32>,
|
||||
terrain: &'a TerrainGrid,
|
||||
hit: &'a dyn Fn(&Block) -> bool,
|
||||
near_iter: impl Iterator<Item = (i32, i32, i32)> + 'a,
|
||||
radius: f32,
|
||||
z_range: Range<f32>,
|
||||
) -> impl Iterator<Item = Aabb<f32>> + 'a {
|
||||
near_iter.filter_map(move |(i, j, k)| {
|
||||
let block_pos = pos.map(|e| e.floor() as i32) + Vec3::new(i, j, k);
|
||||
|
||||
if let Some(block) = terrain.get(block_pos).ok().copied().filter(hit) {
|
||||
let player_aabb = Aabb {
|
||||
min: pos + Vec3::new(-radius, -radius, z_min),
|
||||
max: pos + Vec3::new(radius, radius, z_max),
|
||||
min: pos + Vec3::new(-radius, -radius, z_range.start),
|
||||
max: pos + Vec3::new(radius, radius, z_range.end),
|
||||
};
|
||||
let block_aabb = Aabb {
|
||||
min: block_pos.map(|e| e as f32),
|
||||
@ -195,11 +206,21 @@ impl<'a> System<'a> for Sys {
|
||||
};
|
||||
|
||||
if player_aabb.collides_with_aabb(block_aabb) {
|
||||
return true;
|
||||
return Some(block_aabb);
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
|
||||
None
|
||||
})
|
||||
};
|
||||
|
||||
// Function for determining whether the player at a specific position collides
|
||||
// with blocks with the given criteria
|
||||
let collision_with = |pos: Vec3<f32>,
|
||||
hit: &dyn Fn(&Block) -> bool,
|
||||
near_iter| {
|
||||
collision_iter(pos, &terrain, hit, near_iter, radius, z_min..z_max).count()
|
||||
> 0
|
||||
};
|
||||
|
||||
let was_on_ground = physics_state.on_ground;
|
||||
@ -400,8 +421,16 @@ impl<'a> System<'a> for Sys {
|
||||
}
|
||||
|
||||
// Figure out if we're in water
|
||||
physics_state.in_fluid =
|
||||
collision_with(pos.0, &|block| block.is_fluid(), near_iter.clone());
|
||||
physics_state.in_fluid = collision_iter(
|
||||
pos.0,
|
||||
&terrain,
|
||||
&|block| block.is_fluid(),
|
||||
near_iter.clone(),
|
||||
radius,
|
||||
z_min..z_max,
|
||||
)
|
||||
.max_by_key(|block_aabb| (block_aabb.max.z * 100.0) as i32)
|
||||
.map(|block_aabb| block_aabb.max.z - pos.0.z);
|
||||
},
|
||||
Collider::Point => {
|
||||
let (dist, block) = terrain.ray(pos.0, pos.0 + pos_delta).ignore_error().cast();
|
||||
|
@ -1,9 +1,11 @@
|
||||
use crate::vol::Vox;
|
||||
use enum_iterator::IntoEnumIterator;
|
||||
use lazy_static::lazy_static;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::ops::Deref;
|
||||
use std::{collections::HashMap, convert::TryFrom, fmt, ops::Deref};
|
||||
use vek::*;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)]
|
||||
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize, IntoEnumIterator)]
|
||||
#[repr(u8)]
|
||||
pub enum BlockKind {
|
||||
Air,
|
||||
@ -86,6 +88,25 @@ pub enum BlockKind {
|
||||
Stones,
|
||||
Twigs,
|
||||
ShinyGem,
|
||||
DropGate,
|
||||
DropGateBottom,
|
||||
GrassSnow,
|
||||
}
|
||||
|
||||
impl fmt::Display for BlockKind {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:?}", self) }
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
pub static ref BLOCK_KINDS: HashMap<String, BlockKind> = BlockKind::into_enum_iter()
|
||||
.map(|bk| (bk.to_string(), bk))
|
||||
.collect();
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&'a str> for BlockKind {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(s: &'a str) -> Result<Self, Self::Error> { BLOCK_KINDS.get(s).copied().ok_or(()) }
|
||||
}
|
||||
|
||||
impl BlockKind {
|
||||
@ -173,6 +194,9 @@ impl BlockKind {
|
||||
BlockKind::Stones => true,
|
||||
BlockKind::Twigs => true,
|
||||
BlockKind::ShinyGem => true,
|
||||
BlockKind::DropGate => false,
|
||||
BlockKind::DropGateBottom => false,
|
||||
BlockKind::GrassSnow => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
@ -184,6 +208,16 @@ impl BlockKind {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_glow(&self) -> Option<u8> {
|
||||
// TODO: When we have proper volumetric lighting
|
||||
// match self {
|
||||
// BlockKind::StreetLamp | BlockKind::StreetLampTall => Some(20),
|
||||
// BlockKind::Velorite | BlockKind::VeloriteFrag => Some(10),
|
||||
// _ => None,
|
||||
// }
|
||||
None
|
||||
}
|
||||
|
||||
pub fn is_opaque(&self) -> bool {
|
||||
match self {
|
||||
BlockKind::Air => false,
|
||||
@ -261,6 +295,9 @@ impl BlockKind {
|
||||
BlockKind::Stones => false,
|
||||
BlockKind::Twigs => false,
|
||||
BlockKind::ShinyGem => false,
|
||||
BlockKind::DropGate => false,
|
||||
BlockKind::DropGateBottom => false,
|
||||
BlockKind::GrassSnow => false,
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
@ -335,14 +372,18 @@ impl BlockKind {
|
||||
BlockKind::Stones => false,
|
||||
BlockKind::Twigs => false,
|
||||
BlockKind::ShinyGem => false,
|
||||
BlockKind::DropGate => true,
|
||||
BlockKind::DropGateBottom => false,
|
||||
BlockKind::GrassSnow => false,
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_explodable(&self) -> bool {
|
||||
match self {
|
||||
BlockKind::Leaves | BlockKind::Grass | BlockKind::Rock => true,
|
||||
_ => false,
|
||||
BlockKind::Leaves | BlockKind::Grass | BlockKind::Rock | BlockKind::GrassSnow => true,
|
||||
BlockKind::Air => false,
|
||||
bk => bk.is_air(), // Temporary catch for terrain sprites
|
||||
}
|
||||
}
|
||||
|
||||
@ -363,9 +404,9 @@ impl BlockKind {
|
||||
BlockKind::Radish => 0.18,
|
||||
BlockKind::Door => 3.0,
|
||||
BlockKind::Bed => 1.54,
|
||||
BlockKind::Bench => 1.45,
|
||||
BlockKind::ChairSingle => 1.36,
|
||||
BlockKind::ChairDouble => 1.36,
|
||||
BlockKind::Bench => 0.5,
|
||||
BlockKind::ChairSingle => 0.5,
|
||||
BlockKind::ChairDouble => 0.5,
|
||||
BlockKind::CoatRack => 2.36,
|
||||
BlockKind::Crate => 0.90,
|
||||
BlockKind::DrawerSmall => 1.0,
|
||||
@ -438,6 +479,29 @@ impl Block {
|
||||
| BlockKind::Window2
|
||||
| BlockKind::Window3
|
||||
| BlockKind::Window4
|
||||
| BlockKind::Bed
|
||||
| BlockKind::Bench
|
||||
| BlockKind::ChairSingle
|
||||
| BlockKind::ChairDouble
|
||||
| BlockKind::CoatRack
|
||||
| BlockKind::Crate
|
||||
| BlockKind::DrawerLarge
|
||||
| BlockKind::DrawerMedium
|
||||
| BlockKind::DrawerSmall
|
||||
| BlockKind::DungeonWallDecor
|
||||
| BlockKind::HangingBasket
|
||||
| BlockKind::HangingSign
|
||||
| BlockKind::WallLamp
|
||||
| BlockKind::Planter
|
||||
| BlockKind::Shelf
|
||||
| BlockKind::TableSide
|
||||
| BlockKind::TableDining
|
||||
| BlockKind::TableDouble
|
||||
| BlockKind::WardrobeSingle
|
||||
| BlockKind::WardrobeDouble
|
||||
| BlockKind::Pot
|
||||
| BlockKind::DropGate
|
||||
| BlockKind::DropGateBottom
|
||||
| BlockKind::Door => Some(self.color[0] & 0b111),
|
||||
_ => None,
|
||||
}
|
||||
|
2
server-cli/.gitignore
vendored
2
server-cli/.gitignore
vendored
@ -1 +1 @@
|
||||
|
||||
economy.csv
|
||||
|
@ -13,13 +13,14 @@ use common::{
|
||||
npc::{self, get_npc_name},
|
||||
state::TimeOfDay,
|
||||
sync::{Uid, WorldSyncExt},
|
||||
terrain::TerrainChunkSize,
|
||||
terrain::{Block, BlockKind, TerrainChunkSize},
|
||||
util::Dir,
|
||||
vol::RectVolSize,
|
||||
LoadoutBuilder,
|
||||
};
|
||||
use rand::Rng;
|
||||
use specs::{Builder, Entity as EcsEntity, Join, WorldExt};
|
||||
use std::convert::TryFrom;
|
||||
use vek::*;
|
||||
use world::util::Sampler;
|
||||
|
||||
@ -83,6 +84,7 @@ fn get_handler(cmd: &ChatCommand) -> CommandHandler {
|
||||
ChatCommand::KillNpcs => handle_kill_npcs,
|
||||
ChatCommand::Lantern => handle_lantern,
|
||||
ChatCommand::Light => handle_light,
|
||||
ChatCommand::MakeBlock => handle_make_block,
|
||||
ChatCommand::Motd => handle_motd,
|
||||
ChatCommand::Object => handle_object,
|
||||
ChatCommand::Players => handle_players,
|
||||
@ -179,6 +181,39 @@ fn handle_give_item(
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_make_block(
|
||||
server: &mut Server,
|
||||
client: EcsEntity,
|
||||
target: EcsEntity,
|
||||
args: String,
|
||||
action: &ChatCommand,
|
||||
) {
|
||||
if let Some(block_name) = scan_fmt_some!(&args, &action.arg_fmt(), String) {
|
||||
if let Ok(bk) = BlockKind::try_from(block_name.as_str()) {
|
||||
match server.state.read_component_cloned::<comp::Pos>(target) {
|
||||
Some(pos) => server.state.set_block(
|
||||
pos.0.map(|e| e.floor() as i32),
|
||||
Block::new(bk, Rgb::broadcast(255)),
|
||||
),
|
||||
None => server.notify_client(
|
||||
client,
|
||||
ChatType::CommandError.server_msg(String::from("You have no position.")),
|
||||
),
|
||||
}
|
||||
} else {
|
||||
server.notify_client(
|
||||
client,
|
||||
ChatType::CommandError.server_msg(format!("Invalid block kind: {}", block_name)),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
server.notify_client(
|
||||
client,
|
||||
ChatType::CommandError.server_msg(action.help_string()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_motd(
|
||||
server: &mut Server,
|
||||
client: EcsEntity,
|
||||
@ -227,7 +262,7 @@ fn handle_jump(
|
||||
action: &ChatCommand,
|
||||
) {
|
||||
if let Ok((x, y, z)) = scan_fmt!(&args, &action.arg_fmt(), f32, f32, f32) {
|
||||
match server.state.read_component_copied::<comp::Pos>(target) {
|
||||
match server.state.read_component_cloned::<comp::Pos>(target) {
|
||||
Some(current_pos) => {
|
||||
server
|
||||
.state
|
||||
@ -252,7 +287,7 @@ fn handle_goto(
|
||||
if let Ok((x, y, z)) = scan_fmt!(&args, &action.arg_fmt(), f32, f32, f32) {
|
||||
if server
|
||||
.state
|
||||
.read_component_copied::<comp::Pos>(target)
|
||||
.read_component_cloned::<comp::Pos>(target)
|
||||
.is_some()
|
||||
{
|
||||
server
|
||||
@ -463,9 +498,9 @@ fn handle_tp(
|
||||
);
|
||||
return;
|
||||
};
|
||||
if let Some(_pos) = server.state.read_component_copied::<comp::Pos>(target) {
|
||||
if let Some(_pos) = server.state.read_component_cloned::<comp::Pos>(target) {
|
||||
if let Some(player) = opt_player {
|
||||
if let Some(pos) = server.state.read_component_copied::<comp::Pos>(player) {
|
||||
if let Some(pos) = server.state.read_component_cloned::<comp::Pos>(player) {
|
||||
server.state.write_component(target, pos);
|
||||
server.state.write_component(target, comp::ForceUpdate);
|
||||
} else {
|
||||
@ -510,7 +545,7 @@ fn handle_spawn(
|
||||
(Some(opt_align), Some(npc::NpcBody(id, mut body)), opt_amount, opt_ai) => {
|
||||
let uid = server
|
||||
.state
|
||||
.read_component_copied(target)
|
||||
.read_component_cloned(target)
|
||||
.expect("Expected player to have a UID");
|
||||
if let Some(alignment) = parse_alignment(uid, &opt_align) {
|
||||
let amount = opt_amount
|
||||
@ -521,7 +556,7 @@ fn handle_spawn(
|
||||
|
||||
let ai = opt_ai.unwrap_or_else(|| "true".to_string());
|
||||
|
||||
match server.state.read_component_copied::<comp::Pos>(target) {
|
||||
match server.state.read_component_cloned::<comp::Pos>(target) {
|
||||
Some(pos) => {
|
||||
let agent =
|
||||
if let comp::Alignment::Owned(_) | comp::Alignment::Npc = alignment {
|
||||
@ -631,7 +666,7 @@ fn handle_spawn_training_dummy(
|
||||
_args: String,
|
||||
_action: &ChatCommand,
|
||||
) {
|
||||
match server.state.read_component_copied::<comp::Pos>(target) {
|
||||
match server.state.read_component_cloned::<comp::Pos>(target) {
|
||||
Some(pos) => {
|
||||
let vel = Vec3::new(
|
||||
rand::thread_rng().gen_range(-2.0, 3.0),
|
||||
@ -672,7 +707,7 @@ fn handle_spawn_campfire(
|
||||
_args: String,
|
||||
_action: &ChatCommand,
|
||||
) {
|
||||
match server.state.read_component_copied::<comp::Pos>(target) {
|
||||
match server.state.read_component_cloned::<comp::Pos>(target) {
|
||||
Some(pos) => {
|
||||
server
|
||||
.state
|
||||
@ -1031,7 +1066,7 @@ fn handle_explosion(
|
||||
|
||||
let ecs = server.state.ecs();
|
||||
|
||||
match server.state.read_component_copied::<comp::Pos>(target) {
|
||||
match server.state.read_component_cloned::<comp::Pos>(target) {
|
||||
Some(pos) => {
|
||||
ecs.read_resource::<EventBus<ServerEvent>>()
|
||||
.emit_now(ServerEvent::Explosion {
|
||||
@ -1056,7 +1091,7 @@ fn handle_waypoint(
|
||||
_args: String,
|
||||
_action: &ChatCommand,
|
||||
) {
|
||||
match server.state.read_component_copied::<comp::Pos>(target) {
|
||||
match server.state.read_component_cloned::<comp::Pos>(target) {
|
||||
Some(pos) => {
|
||||
let time = server.state.ecs().read_resource();
|
||||
let _ = server
|
||||
@ -1092,7 +1127,7 @@ fn handle_adminify(
|
||||
Some(player) => {
|
||||
let is_admin = if server
|
||||
.state
|
||||
.read_component_copied::<comp::Admin>(player)
|
||||
.read_component_cloned::<comp::Admin>(player)
|
||||
.is_some()
|
||||
{
|
||||
ecs.write_storage::<comp::Admin>().remove(player);
|
||||
@ -1438,7 +1473,7 @@ fn handle_debug_column(
|
||||
let spawn_rate = sim.get_interpolated(wpos, |chunk| chunk.spawn_rate)?;
|
||||
let chunk_pos = wpos.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| e / sz as i32);
|
||||
let chunk = sim.get(chunk_pos)?;
|
||||
let col = sampler.get(wpos)?;
|
||||
let col = sampler.get((wpos, server.world.index()))?;
|
||||
let downhill = chunk.downhill;
|
||||
let river = &chunk.river;
|
||||
let flux = chunk.flux;
|
||||
@ -1636,7 +1671,7 @@ fn handle_remove_lights(
|
||||
action: &ChatCommand,
|
||||
) {
|
||||
let opt_radius = scan_fmt_some!(&args, &action.arg_fmt(), f32);
|
||||
let opt_player_pos = server.state.read_component_copied::<comp::Pos>(target);
|
||||
let opt_player_pos = server.state.read_component_cloned::<comp::Pos>(target);
|
||||
let mut to_delete = vec![];
|
||||
|
||||
match opt_player_pos {
|
||||
|
@ -2,9 +2,10 @@ use crate::{client::Client, Server, SpawnPoint, StateExt};
|
||||
use common::{
|
||||
assets,
|
||||
comp::{
|
||||
self, item::lottery::Lottery, object, Alignment, Body, Damage, DamageSource, Group,
|
||||
HealthChange, HealthSource, Player, Pos, Stats,
|
||||
self, object, Alignment, Body, Damage, DamageSource, Group, HealthChange, HealthSource,
|
||||
Player, Pos, Stats,
|
||||
},
|
||||
lottery::Lottery,
|
||||
msg::{PlayerListUpdate, ServerMsg},
|
||||
outcome::Outcome,
|
||||
state::BlockChange,
|
||||
@ -183,7 +184,7 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, cause: HealthSourc
|
||||
item_drops.remove(entity);
|
||||
item_drop.0
|
||||
} else {
|
||||
let chosen = assets::load_expect::<Lottery<_>>("common.loot_table");
|
||||
let chosen = assets::load_expect::<Lottery<String>>("common.loot_table");
|
||||
let chosen = chosen.choose();
|
||||
|
||||
assets::load_expect_cloned(chosen)
|
||||
@ -251,7 +252,7 @@ pub fn handle_respawn(server: &Server, entity: EcsEntity) {
|
||||
.is_some()
|
||||
{
|
||||
let respawn_point = state
|
||||
.read_component_copied::<comp::Waypoint>(entity)
|
||||
.read_component_cloned::<comp::Waypoint>(entity)
|
||||
.map(|wp| wp.get_pos())
|
||||
.unwrap_or(state.ecs().read_resource::<SpawnPoint>().0);
|
||||
|
||||
|
@ -168,10 +168,10 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
|
||||
thrown_items.push((
|
||||
*pos,
|
||||
state
|
||||
.read_component_copied::<comp::Vel>(entity)
|
||||
.read_component_cloned::<comp::Vel>(entity)
|
||||
.unwrap_or_default(),
|
||||
state
|
||||
.read_component_copied::<comp::Ori>(entity)
|
||||
.read_component_cloned::<comp::Ori>(entity)
|
||||
.unwrap_or_default(),
|
||||
*kind,
|
||||
));
|
||||
@ -186,7 +186,7 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
|
||||
state.read_storage::<comp::Pos>().get(entity)
|
||||
{
|
||||
let uid = state
|
||||
.read_component_copied(entity)
|
||||
.read_component_cloned(entity)
|
||||
.expect("Expected player to have a UID");
|
||||
if (
|
||||
&state.read_storage::<comp::Alignment>(),
|
||||
@ -342,7 +342,7 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
|
||||
dropped_items.push((
|
||||
*pos,
|
||||
state
|
||||
.read_component_copied::<comp::Ori>(entity)
|
||||
.read_component_cloned::<comp::Ori>(entity)
|
||||
.unwrap_or_default(),
|
||||
item,
|
||||
));
|
||||
@ -374,10 +374,10 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
|
||||
for _ in 0..amount {
|
||||
dropped_items.push((
|
||||
state
|
||||
.read_component_copied::<comp::Pos>(entity)
|
||||
.read_component_cloned::<comp::Pos>(entity)
|
||||
.unwrap_or_default(),
|
||||
state
|
||||
.read_component_copied::<comp::Ori>(entity)
|
||||
.read_component_cloned::<comp::Ori>(entity)
|
||||
.unwrap_or_default(),
|
||||
item.clone(),
|
||||
));
|
||||
@ -419,7 +419,7 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
|
||||
},
|
||||
};
|
||||
|
||||
let uid = state.read_component_copied::<Uid>(entity);
|
||||
let uid = state.read_component_cloned::<Uid>(entity);
|
||||
|
||||
let mut new_entity = state
|
||||
.create_object(Default::default(), match kind {
|
||||
|
@ -20,7 +20,7 @@ pub fn handle_exit_ingame(server: &mut Server, entity: EcsEntity) {
|
||||
// Note: If other `ServerEvent`s are referring to this entity they will be
|
||||
// disrupted
|
||||
let maybe_client = state.ecs().write_storage::<Client>().remove(entity);
|
||||
let maybe_uid = state.read_component_copied::<Uid>(entity);
|
||||
let maybe_uid = state.read_component_cloned::<Uid>(entity);
|
||||
let maybe_player = state.ecs().write_storage::<comp::Player>().remove(entity);
|
||||
let maybe_group = state
|
||||
.ecs()
|
||||
|
@ -184,7 +184,7 @@ impl Server {
|
||||
..WorldOpts::default()
|
||||
});
|
||||
#[cfg(feature = "worldgen")]
|
||||
let map = world.sim().get_map();
|
||||
let map = world.get_map_data();
|
||||
|
||||
#[cfg(not(feature = "worldgen"))]
|
||||
let world = World::generate(settings.world_seed);
|
||||
@ -219,11 +219,11 @@ impl Server {
|
||||
// get a z cache for the collumn in which we want to spawn
|
||||
let mut block_sampler = world.sample_blocks();
|
||||
let z_cache = block_sampler
|
||||
.get_z_cache(spawn_location)
|
||||
.get_z_cache(spawn_location, world.index())
|
||||
.expect(&format!("no z_cache found for chunk: {}", spawn_chunk));
|
||||
|
||||
// get the minimum and maximum z values at which there could be soild blocks
|
||||
let (min_z, _, max_z) = z_cache.get_z_limits(&mut block_sampler);
|
||||
let (min_z, _, max_z) = z_cache.get_z_limits(&mut block_sampler, world.index());
|
||||
// round range outwards, so no potential air block is missed
|
||||
let min_z = min_z.floor() as i32;
|
||||
let max_z = max_z.ceil() as i32;
|
||||
@ -239,6 +239,7 @@ impl Server {
|
||||
Vec3::new(spawn_location.x, spawn_location.y, *z),
|
||||
Some(&z_cache),
|
||||
false,
|
||||
world.index(),
|
||||
)
|
||||
.map(|b| b.is_air())
|
||||
.unwrap_or(false)
|
||||
|
@ -173,7 +173,7 @@ impl StateExt for State {
|
||||
self.write_component(entity, comp::CharacterState::default());
|
||||
self.write_component(
|
||||
entity,
|
||||
comp::Alignment::Owned(self.read_component_copied(entity).unwrap()),
|
||||
comp::Alignment::Owned(self.read_component_cloned(entity).unwrap()),
|
||||
);
|
||||
|
||||
// Set the character id for the player
|
||||
@ -213,7 +213,7 @@ impl StateExt for State {
|
||||
|
||||
// Notify clients of a player list update
|
||||
let client_uid = self
|
||||
.read_component_copied::<Uid>(entity)
|
||||
.read_component_cloned::<Uid>(entity)
|
||||
.map(|u| u)
|
||||
.expect("Client doesn't have a Uid!!!");
|
||||
|
||||
|
@ -319,12 +319,12 @@ impl<'a> System<'a> for Sys {
|
||||
pos: Pos(entity.pos),
|
||||
stats,
|
||||
loadout,
|
||||
body,
|
||||
agent: if entity.has_agency {
|
||||
Some(comp::Agent::new(entity.pos, can_speak))
|
||||
Some(comp::Agent::new(entity.pos, can_speak, &body))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
body,
|
||||
alignment,
|
||||
scale: comp::Scale(scale),
|
||||
drop_item: entity.loot_drop,
|
||||
|
@ -12,7 +12,6 @@ impl Animation for BetaAnimation {
|
||||
const UPDATE_FN: &'static [u8] = b"character_beta\0";
|
||||
|
||||
#[cfg_attr(feature = "be-dyn-lib", export_name = "character_beta")]
|
||||
#[allow(clippy::unnested_or_patterns)] // TODO: Pending review in #587
|
||||
fn update_skeleton_inner(
|
||||
skeleton: &Self::Skeleton,
|
||||
(active_tool_kind, second_tool_kind, _velocity, _global_time): Self::Dependency,
|
||||
@ -42,65 +41,60 @@ impl Animation for BetaAnimation {
|
||||
.sqrt())
|
||||
* ((anim_time as f32 * lab as f32 * 14.0).sin());
|
||||
|
||||
match active_tool_kind {
|
||||
//TODO: Inventory
|
||||
Some(ToolKind::Axe(_))
|
||||
| Some(ToolKind::Hammer(_))
|
||||
| Some(ToolKind::Sword(_))
|
||||
| Some(ToolKind::Dagger(_)) => {
|
||||
//INTENTION: SWORD
|
||||
next.head.offset =
|
||||
Vec3::new(0.0, -2.0 + skeleton_attr.head.0, skeleton_attr.head.1);
|
||||
next.head.ori = Quaternion::rotation_z(slow * -0.18)
|
||||
* Quaternion::rotation_x(-0.1 + slow * -0.28)
|
||||
* Quaternion::rotation_y(0.2 + slow * 0.18);
|
||||
next.head.scale = Vec3::one() * skeleton_attr.head_scale;
|
||||
if let Some(
|
||||
ToolKind::Axe(_) | ToolKind::Hammer(_) | ToolKind::Sword(_) | ToolKind::Dagger(_),
|
||||
) = active_tool_kind
|
||||
{
|
||||
//INTENTION: SWORD
|
||||
next.head.offset = Vec3::new(0.0, -2.0 + skeleton_attr.head.0, skeleton_attr.head.1);
|
||||
next.head.ori = Quaternion::rotation_z(slow * -0.18)
|
||||
* Quaternion::rotation_x(-0.1 + slow * -0.28)
|
||||
* Quaternion::rotation_y(0.2 + slow * 0.18);
|
||||
next.head.scale = Vec3::one() * skeleton_attr.head_scale;
|
||||
|
||||
next.chest.offset = Vec3::new(0.0 + foot * 2.0, 0.0, 7.0);
|
||||
next.chest.ori = Quaternion::rotation_z(slow * 0.2)
|
||||
* Quaternion::rotation_x(slow * 0.2)
|
||||
* Quaternion::rotation_y(slow * -0.1);
|
||||
next.chest.offset = Vec3::new(0.0 + foot * 2.0, 0.0, 7.0);
|
||||
next.chest.ori = Quaternion::rotation_z(slow * 0.2)
|
||||
* Quaternion::rotation_x(slow * 0.2)
|
||||
* Quaternion::rotation_y(slow * -0.1);
|
||||
|
||||
next.belt.offset = Vec3::new(0.0, 0.0, -2.0);
|
||||
next.belt.ori = Quaternion::rotation_z(slow * 0.1)
|
||||
* Quaternion::rotation_x(slow * 0.1)
|
||||
* Quaternion::rotation_y(slow * -0.04);
|
||||
next.belt.offset = Vec3::new(0.0, 0.0, -2.0);
|
||||
next.belt.ori = Quaternion::rotation_z(slow * 0.1)
|
||||
* Quaternion::rotation_x(slow * 0.1)
|
||||
* Quaternion::rotation_y(slow * -0.04);
|
||||
|
||||
next.shorts.offset = Vec3::new(0.0, 0.0, -5.0);
|
||||
next.shorts.ori = Quaternion::rotation_z(slow * 0.1)
|
||||
* Quaternion::rotation_x(slow * 0.1)
|
||||
* Quaternion::rotation_y(slow * -0.05);
|
||||
next.shorts.offset = Vec3::new(0.0, 0.0, -5.0);
|
||||
next.shorts.ori = Quaternion::rotation_z(slow * 0.1)
|
||||
* Quaternion::rotation_x(slow * 0.1)
|
||||
* Quaternion::rotation_y(slow * -0.05);
|
||||
|
||||
next.l_hand.offset = Vec3::new(-0.75, -1.0, -2.5);
|
||||
next.l_hand.ori = Quaternion::rotation_x(1.27);
|
||||
next.l_hand.scale = Vec3::one() * 1.04;
|
||||
next.r_hand.offset = Vec3::new(0.75, -1.5, -5.5);
|
||||
next.r_hand.ori = Quaternion::rotation_x(1.27);
|
||||
next.r_hand.scale = Vec3::one() * 1.05;
|
||||
next.main.offset = Vec3::new(0.0, 6.0, -1.0);
|
||||
next.main.ori = Quaternion::rotation_x(-0.3);
|
||||
next.l_hand.offset = Vec3::new(-0.75, -1.0, -2.5);
|
||||
next.l_hand.ori = Quaternion::rotation_x(1.27);
|
||||
next.l_hand.scale = Vec3::one() * 1.04;
|
||||
next.r_hand.offset = Vec3::new(0.75, -1.5, -5.5);
|
||||
next.r_hand.ori = Quaternion::rotation_x(1.27);
|
||||
next.r_hand.scale = Vec3::one() * 1.05;
|
||||
next.main.offset = Vec3::new(0.0, 6.0, -1.0);
|
||||
next.main.ori = Quaternion::rotation_x(-0.3);
|
||||
|
||||
next.control.offset = Vec3::new(-8.0 + slow * 1.5, 1.5 + slow * 1.0, 0.0);
|
||||
next.control.ori = Quaternion::rotation_x(-1.4)
|
||||
* Quaternion::rotation_y(slow * 2.0 + 0.7)
|
||||
* Quaternion::rotation_z(1.7 - slow * 0.4 + fast * 0.6);
|
||||
next.control.scale = Vec3::one();
|
||||
next.l_foot.offset = Vec3::new(
|
||||
-skeleton_attr.foot.0,
|
||||
footquick * -9.5,
|
||||
skeleton_attr.foot.2,
|
||||
);
|
||||
next.l_foot.ori = Quaternion::rotation_x(footquick * 0.3)
|
||||
* Quaternion::rotation_y(footquick * -0.6);
|
||||
next.control.offset = Vec3::new(-8.0 + slow * 1.5, 1.5 + slow * 1.0, 0.0);
|
||||
next.control.ori = Quaternion::rotation_x(-1.4)
|
||||
* Quaternion::rotation_y(slow * 2.0 + 0.7)
|
||||
* Quaternion::rotation_z(1.7 - slow * 0.4 + fast * 0.6);
|
||||
next.control.scale = Vec3::one();
|
||||
next.l_foot.offset = Vec3::new(
|
||||
-skeleton_attr.foot.0,
|
||||
footquick * -9.5,
|
||||
skeleton_attr.foot.2,
|
||||
);
|
||||
next.l_foot.ori =
|
||||
Quaternion::rotation_x(footquick * 0.3) * Quaternion::rotation_y(footquick * -0.6);
|
||||
|
||||
next.r_foot.offset =
|
||||
Vec3::new(skeleton_attr.foot.0, footquick * 9.5, skeleton_attr.foot.2);
|
||||
next.r_foot.ori = Quaternion::rotation_x(footquick * -0.3)
|
||||
* Quaternion::rotation_y(footquick * 0.2);
|
||||
next.torso.offset = Vec3::new(0.0, 0.0, 0.1) * skeleton_attr.scaler;
|
||||
next.torso.scale = Vec3::one() / 11.0 * skeleton_attr.scaler;
|
||||
},
|
||||
_ => {},
|
||||
next.r_foot.offset =
|
||||
Vec3::new(skeleton_attr.foot.0, footquick * 9.5, skeleton_attr.foot.2);
|
||||
next.r_foot.ori =
|
||||
Quaternion::rotation_x(footquick * -0.3) * Quaternion::rotation_y(footquick * 0.2);
|
||||
next.torso.offset = Vec3::new(0.0, 0.0, 0.1) * skeleton_attr.scaler;
|
||||
next.torso.scale = Vec3::one() / 11.0 * skeleton_attr.scaler;
|
||||
}
|
||||
|
||||
next.l_shoulder.offset = Vec3::new(
|
||||
|
@ -16,7 +16,6 @@ impl Animation for SpinAnimation {
|
||||
const UPDATE_FN: &'static [u8] = b"character_spin\0";
|
||||
|
||||
#[cfg_attr(feature = "be-dyn-lib", export_name = "character_spin")]
|
||||
#[allow(clippy::unnested_or_patterns)] // TODO: Pending review in #587
|
||||
fn update_skeleton_inner(
|
||||
skeleton: &Self::Skeleton,
|
||||
(active_tool_kind, second_tool_kind, _global_time): Self::Dependency,
|
||||
@ -39,60 +38,56 @@ impl Animation for SpinAnimation {
|
||||
let spin = (anim_time as f32 * 2.8 * lab as f32).sin();
|
||||
let spinhalf = (anim_time as f32 * 1.4 * lab as f32).sin();
|
||||
|
||||
match active_tool_kind {
|
||||
//TODO: Inventory
|
||||
Some(ToolKind::Axe(_))
|
||||
| Some(ToolKind::Hammer(_))
|
||||
| Some(ToolKind::Sword(_))
|
||||
| Some(ToolKind::Dagger(_)) => {
|
||||
//INTENTION: SWORD
|
||||
next.l_hand.offset = Vec3::new(-0.75, -1.0, -2.5);
|
||||
next.l_hand.ori = Quaternion::rotation_x(1.27);
|
||||
next.l_hand.scale = Vec3::one() * 1.04;
|
||||
next.r_hand.offset = Vec3::new(0.75, -1.5, -5.5);
|
||||
next.r_hand.ori = Quaternion::rotation_x(1.27);
|
||||
next.r_hand.scale = Vec3::one() * 1.05;
|
||||
next.main.offset = Vec3::new(0.0, 6.0, -1.0);
|
||||
next.main.ori = Quaternion::rotation_x(-0.3)
|
||||
* Quaternion::rotation_y(0.0)
|
||||
* Quaternion::rotation_z(0.0);
|
||||
next.main.scale = Vec3::one();
|
||||
if let Some(
|
||||
ToolKind::Axe(_) | ToolKind::Hammer(_) | ToolKind::Sword(_) | ToolKind::Dagger(_),
|
||||
) = active_tool_kind
|
||||
{
|
||||
//INTENTION: SWORD
|
||||
next.l_hand.offset = Vec3::new(-0.75, -1.0, -2.5);
|
||||
next.l_hand.ori = Quaternion::rotation_x(1.27);
|
||||
next.l_hand.scale = Vec3::one() * 1.04;
|
||||
next.r_hand.offset = Vec3::new(0.75, -1.5, -5.5);
|
||||
next.r_hand.ori = Quaternion::rotation_x(1.27);
|
||||
next.r_hand.scale = Vec3::one() * 1.05;
|
||||
next.main.offset = Vec3::new(0.0, 6.0, -1.0);
|
||||
next.main.ori = Quaternion::rotation_x(-0.3)
|
||||
* Quaternion::rotation_y(0.0)
|
||||
* Quaternion::rotation_z(0.0);
|
||||
next.main.scale = Vec3::one();
|
||||
|
||||
next.control.offset = Vec3::new(-4.5 + spinhalf * 4.0, 11.0, 8.0);
|
||||
next.control.ori = Quaternion::rotation_x(-1.7)
|
||||
* Quaternion::rotation_y(0.2 + spin * -2.0)
|
||||
* Quaternion::rotation_z(1.4 + spin * 0.1);
|
||||
next.control.scale = Vec3::one();
|
||||
next.head.offset = Vec3::new(
|
||||
0.0,
|
||||
-2.0 + skeleton_attr.head.0 + spin * -0.8,
|
||||
skeleton_attr.head.1,
|
||||
);
|
||||
next.head.ori = Quaternion::rotation_z(spin * -0.25)
|
||||
* Quaternion::rotation_x(0.0 + spin * -0.1)
|
||||
* Quaternion::rotation_y(spin * -0.2);
|
||||
next.chest.offset = Vec3::new(0.0, skeleton_attr.chest.0, skeleton_attr.chest.1);
|
||||
next.chest.ori = Quaternion::rotation_z(spin * 0.1)
|
||||
* Quaternion::rotation_x(0.0 + spin * 0.1)
|
||||
* Quaternion::rotation_y(decel * -0.2);
|
||||
next.chest.scale = Vec3::one();
|
||||
next.control.offset = Vec3::new(-4.5 + spinhalf * 4.0, 11.0, 8.0);
|
||||
next.control.ori = Quaternion::rotation_x(-1.7)
|
||||
* Quaternion::rotation_y(0.2 + spin * -2.0)
|
||||
* Quaternion::rotation_z(1.4 + spin * 0.1);
|
||||
next.control.scale = Vec3::one();
|
||||
next.head.offset = Vec3::new(
|
||||
0.0,
|
||||
-2.0 + skeleton_attr.head.0 + spin * -0.8,
|
||||
skeleton_attr.head.1,
|
||||
);
|
||||
next.head.ori = Quaternion::rotation_z(spin * -0.25)
|
||||
* Quaternion::rotation_x(0.0 + spin * -0.1)
|
||||
* Quaternion::rotation_y(spin * -0.2);
|
||||
next.chest.offset = Vec3::new(0.0, skeleton_attr.chest.0, skeleton_attr.chest.1);
|
||||
next.chest.ori = Quaternion::rotation_z(spin * 0.1)
|
||||
* Quaternion::rotation_x(0.0 + spin * 0.1)
|
||||
* Quaternion::rotation_y(decel * -0.2);
|
||||
next.chest.scale = Vec3::one();
|
||||
|
||||
next.belt.offset = Vec3::new(0.0, 0.0, -2.0);
|
||||
next.belt.ori = next.chest.ori * -0.1;
|
||||
next.belt.scale = Vec3::one();
|
||||
next.belt.offset = Vec3::new(0.0, 0.0, -2.0);
|
||||
next.belt.ori = next.chest.ori * -0.1;
|
||||
next.belt.scale = Vec3::one();
|
||||
|
||||
next.shorts.offset = Vec3::new(0.0, 0.0, -5.0);
|
||||
next.belt.ori = next.chest.ori * -0.08;
|
||||
next.shorts.scale = Vec3::one();
|
||||
next.torso.offset = Vec3::new(0.0, 0.0, 0.1) * skeleton_attr.scaler;
|
||||
next.torso.ori = Quaternion::rotation_z((spin * 7.0).max(0.3))
|
||||
* Quaternion::rotation_x(0.0)
|
||||
* Quaternion::rotation_y(0.0);
|
||||
next.torso.scale = Vec3::one() / 11.0 * skeleton_attr.scaler;
|
||||
},
|
||||
|
||||
_ => {},
|
||||
next.shorts.offset = Vec3::new(0.0, 0.0, -5.0);
|
||||
next.belt.ori = next.chest.ori * -0.08;
|
||||
next.shorts.scale = Vec3::one();
|
||||
next.torso.offset = Vec3::new(0.0, 0.0, 0.1) * skeleton_attr.scaler;
|
||||
next.torso.ori = Quaternion::rotation_z((spin * 7.0).max(0.3))
|
||||
* Quaternion::rotation_x(0.0)
|
||||
* Quaternion::rotation_y(0.0);
|
||||
next.torso.scale = Vec3::one() / 11.0 * skeleton_attr.scaler;
|
||||
}
|
||||
|
||||
next.l_foot.offset = Vec3::new(-skeleton_attr.foot.0, foot * 1.0, skeleton_attr.foot.2);
|
||||
next.l_foot.ori = Quaternion::rotation_x(foot * -1.2);
|
||||
next.l_foot.scale = Vec3::one();
|
||||
|
@ -1,3 +1,4 @@
|
||||
#![feature(or_patterns)]
|
||||
#[cfg(all(feature = "be-dyn-lib", feature = "use-dyn-lib"))]
|
||||
compile_error!("Can't use both \"be-dyn-lib\" and \"use-dyn-lib\" features at once");
|
||||
|
||||
|
@ -85,7 +85,7 @@ fn maps_idle() {
|
||||
on_ceiling: false,
|
||||
on_wall: None,
|
||||
touch_entity: None,
|
||||
in_fluid: false,
|
||||
in_fluid: None,
|
||||
},
|
||||
&PreviousEntityState {
|
||||
event: SfxEvent::Idle,
|
||||
@ -107,7 +107,7 @@ fn maps_run_with_sufficient_velocity() {
|
||||
on_ceiling: false,
|
||||
on_wall: None,
|
||||
touch_entity: None,
|
||||
in_fluid: false,
|
||||
in_fluid: None,
|
||||
},
|
||||
&PreviousEntityState {
|
||||
event: SfxEvent::Idle,
|
||||
@ -129,7 +129,7 @@ fn does_not_map_run_with_insufficient_velocity() {
|
||||
on_ceiling: false,
|
||||
on_wall: None,
|
||||
touch_entity: None,
|
||||
in_fluid: false,
|
||||
in_fluid: None,
|
||||
},
|
||||
&PreviousEntityState {
|
||||
event: SfxEvent::Idle,
|
||||
@ -151,7 +151,7 @@ fn does_not_map_run_with_sufficient_velocity_but_not_on_ground() {
|
||||
on_ceiling: false,
|
||||
on_wall: None,
|
||||
touch_entity: None,
|
||||
in_fluid: false,
|
||||
in_fluid: None,
|
||||
},
|
||||
&PreviousEntityState {
|
||||
event: SfxEvent::Idle,
|
||||
@ -176,7 +176,7 @@ fn maps_roll() {
|
||||
on_ceiling: false,
|
||||
on_wall: None,
|
||||
touch_entity: None,
|
||||
in_fluid: false,
|
||||
in_fluid: None,
|
||||
},
|
||||
&PreviousEntityState {
|
||||
event: SfxEvent::Run,
|
||||
@ -198,7 +198,7 @@ fn maps_land_on_ground_to_run() {
|
||||
on_ceiling: false,
|
||||
on_wall: None,
|
||||
touch_entity: None,
|
||||
in_fluid: false,
|
||||
in_fluid: None,
|
||||
},
|
||||
&PreviousEntityState {
|
||||
event: SfxEvent::Idle,
|
||||
@ -220,7 +220,7 @@ fn maps_glider_open() {
|
||||
on_ceiling: false,
|
||||
on_wall: None,
|
||||
touch_entity: None,
|
||||
in_fluid: false,
|
||||
in_fluid: None,
|
||||
},
|
||||
&PreviousEntityState {
|
||||
event: SfxEvent::Jump,
|
||||
@ -242,7 +242,7 @@ fn maps_glide() {
|
||||
on_ceiling: false,
|
||||
on_wall: None,
|
||||
touch_entity: None,
|
||||
in_fluid: false,
|
||||
in_fluid: None,
|
||||
},
|
||||
&PreviousEntityState {
|
||||
event: SfxEvent::Glide,
|
||||
@ -264,7 +264,7 @@ fn maps_glider_close_when_closing_mid_flight() {
|
||||
on_ceiling: false,
|
||||
on_wall: None,
|
||||
touch_entity: None,
|
||||
in_fluid: false,
|
||||
in_fluid: None,
|
||||
},
|
||||
&PreviousEntityState {
|
||||
event: SfxEvent::Glide,
|
||||
@ -287,7 +287,7 @@ fn maps_glider_close_when_landing() {
|
||||
on_ceiling: false,
|
||||
on_wall: None,
|
||||
touch_entity: None,
|
||||
in_fluid: false,
|
||||
in_fluid: None,
|
||||
},
|
||||
&PreviousEntityState {
|
||||
event: SfxEvent::Glide,
|
||||
@ -308,7 +308,7 @@ fn maps_quadrupeds_running() {
|
||||
on_ceiling: false,
|
||||
on_wall: None,
|
||||
touch_entity: None,
|
||||
in_fluid: false,
|
||||
in_fluid: None,
|
||||
},
|
||||
Vec3::new(0.5, 0.8, 0.0),
|
||||
);
|
||||
|
@ -505,7 +505,7 @@ impl<'a> Widget for Social<'a> {
|
||||
})
|
||||
.or_else(|| {
|
||||
self.selected_entity
|
||||
.and_then(|s| self.client.state().read_component_copied(s.0))
|
||||
.and_then(|s| self.client.state().read_component_cloned(s.0))
|
||||
})
|
||||
.filter(|selected| {
|
||||
// Prevent inviting entities already in the same group
|
||||
|
@ -4,7 +4,7 @@ use crate::{
|
||||
};
|
||||
use common::{
|
||||
terrain::{Block, BlockKind},
|
||||
vol::{ReadVol, RectRasterableVol, Vox},
|
||||
vol::{DefaultVolIterator, ReadVol, RectRasterableVol, Vox},
|
||||
volumes::vol_grid_2d::{CachedVolGrid2d, VolGrid2d},
|
||||
};
|
||||
use std::{collections::VecDeque, fmt::Debug};
|
||||
@ -26,13 +26,16 @@ impl Blendable for BlockKind {
|
||||
}
|
||||
}
|
||||
|
||||
const SUNLIGHT: u8 = 24;
|
||||
const MAX_LIGHT_DIST: i32 = SUNLIGHT as i32;
|
||||
|
||||
fn calc_light<V: RectRasterableVol<Vox = Block> + ReadVol + Debug>(
|
||||
bounds: Aabb<i32>,
|
||||
vol: &VolGrid2d<V>,
|
||||
lit_blocks: impl Iterator<Item = (Vec3<i32>, u8)>,
|
||||
) -> impl FnMut(Vec3<i32>) -> f32 + '_ {
|
||||
const UNKNOWN: u8 = 255;
|
||||
const OPAQUE: u8 = 254;
|
||||
const SUNLIGHT: u8 = 24;
|
||||
|
||||
let outer = Aabb {
|
||||
min: bounds.min - Vec3::new(SUNLIGHT as i32 - 1, SUNLIGHT as i32 - 1, 1),
|
||||
@ -47,7 +50,13 @@ fn calc_light<V: RectRasterableVol<Vox = Block> + ReadVol + Debug>(
|
||||
move |x, y, z| (z * h * w + x * h + y) as usize
|
||||
};
|
||||
// Light propagation queue
|
||||
let mut prop_que = VecDeque::new();
|
||||
let mut prop_que = lit_blocks
|
||||
.map(|(pos, light)| {
|
||||
let rpos = pos - outer.min;
|
||||
light_map[lm_idx(rpos.x, rpos.y, rpos.z)] = light;
|
||||
(rpos.x as u8, rpos.y as u8, rpos.z as u16)
|
||||
})
|
||||
.collect::<VecDeque<_>>();
|
||||
// Start sun rays
|
||||
for x in 0..outer.size().w {
|
||||
for y in 0..outer.size().h {
|
||||
@ -216,7 +225,13 @@ impl<'a, V: RectRasterableVol<Vox = Block> + ReadVol + Debug>
|
||||
&'a self,
|
||||
range: Self::Supplement,
|
||||
) -> (Mesh<Self::Pipeline>, Mesh<Self::TranslucentPipeline>) {
|
||||
let mut light = calc_light(range, self);
|
||||
// Find blocks that should glow
|
||||
let lit_blocks =
|
||||
DefaultVolIterator::new(self, range.min - MAX_LIGHT_DIST, range.max + MAX_LIGHT_DIST)
|
||||
.filter_map(|(pos, block)| block.get_glow().map(|glow| (pos, glow)));
|
||||
|
||||
// Calculate chunk lighting
|
||||
let mut light = calc_light(range, self, lit_blocks);
|
||||
|
||||
let mut lowest_opaque = range.size().d;
|
||||
let mut highest_opaque = 0;
|
||||
|
@ -581,7 +581,7 @@ impl FigureMgr {
|
||||
let target_base = match (
|
||||
physics.on_ground,
|
||||
vel.0.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving
|
||||
physics.in_fluid, // In water
|
||||
physics.in_fluid.is_some(), // In water
|
||||
) {
|
||||
// Standing
|
||||
(true, false, false) => anim::character::StandAnimation::update_skeleton(
|
||||
@ -823,7 +823,7 @@ impl FigureMgr {
|
||||
)
|
||||
},
|
||||
CharacterState::Wielding { .. } => {
|
||||
if physics.in_fluid {
|
||||
if physics.in_fluid.is_some() {
|
||||
anim::character::SwimWieldAnimation::update_skeleton(
|
||||
&target_base,
|
||||
(active_tool_kind, second_tool_kind, vel.0.magnitude(), time),
|
||||
@ -949,7 +949,7 @@ impl FigureMgr {
|
||||
let target_base = match (
|
||||
physics.on_ground,
|
||||
vel.0.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving
|
||||
physics.in_fluid, // In water
|
||||
physics.in_fluid.is_some(), // In water
|
||||
) {
|
||||
// Standing
|
||||
(true, false, false) => {
|
||||
@ -1047,7 +1047,7 @@ impl FigureMgr {
|
||||
let target_base = match (
|
||||
physics.on_ground,
|
||||
vel.0.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving
|
||||
physics.in_fluid, // In water
|
||||
physics.in_fluid.is_some(), // In water
|
||||
) {
|
||||
// Standing
|
||||
(true, false, false) => {
|
||||
@ -1143,7 +1143,7 @@ impl FigureMgr {
|
||||
let target_base = match (
|
||||
physics.on_ground,
|
||||
vel.0.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving
|
||||
physics.in_fluid, // In water
|
||||
physics.in_fluid.is_some(), // In water
|
||||
) {
|
||||
// Standing
|
||||
(true, false, false) => {
|
||||
@ -1237,7 +1237,7 @@ impl FigureMgr {
|
||||
let target_base = match (
|
||||
physics.on_ground,
|
||||
vel.0.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving
|
||||
physics.in_fluid, // In water
|
||||
physics.in_fluid.is_some(), // In water
|
||||
) {
|
||||
// Standing
|
||||
(true, false, false) => anim::bird_medium::IdleAnimation::update_skeleton(
|
||||
@ -1329,7 +1329,7 @@ impl FigureMgr {
|
||||
let target_base = match (
|
||||
physics.on_ground,
|
||||
vel.0.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving
|
||||
physics.in_fluid, // In water
|
||||
physics.in_fluid.is_some(), // In water
|
||||
) {
|
||||
// Standing
|
||||
(true, false, false) => anim::fish_medium::IdleAnimation::update_skeleton(
|
||||
@ -1404,7 +1404,7 @@ impl FigureMgr {
|
||||
let target_base = match (
|
||||
physics.on_ground,
|
||||
vel.0.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving
|
||||
physics.in_fluid, // In water
|
||||
physics.in_fluid.is_some(), // In water
|
||||
) {
|
||||
// Standing
|
||||
(true, false, false) => anim::dragon::IdleAnimation::update_skeleton(
|
||||
@ -1478,7 +1478,7 @@ impl FigureMgr {
|
||||
let target_base = match (
|
||||
physics.on_ground,
|
||||
vel.0.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving
|
||||
physics.in_fluid, // In water
|
||||
physics.in_fluid.is_some(), // In water
|
||||
) {
|
||||
// Standing
|
||||
(true, false, false) => anim::critter::IdleAnimation::update_skeleton(
|
||||
@ -1553,7 +1553,7 @@ impl FigureMgr {
|
||||
let target_base = match (
|
||||
physics.on_ground,
|
||||
vel.0.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving
|
||||
physics.in_fluid, // In water
|
||||
physics.in_fluid.is_some(), // In water
|
||||
) {
|
||||
// Standing
|
||||
(true, false, false) => anim::bird_small::IdleAnimation::update_skeleton(
|
||||
@ -1628,7 +1628,7 @@ impl FigureMgr {
|
||||
let target_base = match (
|
||||
physics.on_ground,
|
||||
vel.0.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving
|
||||
physics.in_fluid, // In water
|
||||
physics.in_fluid.is_some(), // In water
|
||||
) {
|
||||
// Standing
|
||||
(true, false, false) => anim::fish_small::IdleAnimation::update_skeleton(
|
||||
@ -1703,7 +1703,7 @@ impl FigureMgr {
|
||||
let target_base = match (
|
||||
physics.on_ground,
|
||||
vel.0.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving
|
||||
physics.in_fluid, // In water
|
||||
physics.in_fluid.is_some(), // In water
|
||||
) {
|
||||
// Standing
|
||||
(true, false, false) => anim::biped_large::IdleAnimation::update_skeleton(
|
||||
@ -1795,7 +1795,7 @@ impl FigureMgr {
|
||||
let target_base = match (
|
||||
physics.on_ground,
|
||||
vel.0.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving
|
||||
physics.in_fluid, // In water
|
||||
physics.in_fluid.is_some(), // In water
|
||||
) {
|
||||
// Standing
|
||||
(true, false, false) => anim::golem::IdleAnimation::update_skeleton(
|
||||
|
@ -19,6 +19,7 @@ use crossbeam::channel;
|
||||
use dot_vox::DotVoxData;
|
||||
use hashbrown::HashMap;
|
||||
use std::{f32, fmt::Debug, i32, marker::PhantomData, time::Duration};
|
||||
use tracing::warn;
|
||||
use treeculler::{BVol, Frustum, AABB};
|
||||
use vek::*;
|
||||
|
||||
@ -142,7 +143,7 @@ fn sprite_config_for(kind: BlockKind) -> Option<SpriteConfig> {
|
||||
wind_sway: 0.1,
|
||||
}),
|
||||
BlockKind::LargeGrass => Some(SpriteConfig {
|
||||
variations: 3,
|
||||
variations: 1,
|
||||
wind_sway: 0.5,
|
||||
}),
|
||||
|
||||
@ -350,6 +351,18 @@ fn sprite_config_for(kind: BlockKind) -> Option<SpriteConfig> {
|
||||
variations: 3,
|
||||
wind_sway: 0.0,
|
||||
}),
|
||||
BlockKind::DropGate => Some(SpriteConfig {
|
||||
variations: 1,
|
||||
wind_sway: 0.0,
|
||||
}),
|
||||
BlockKind::DropGateBottom => Some(SpriteConfig {
|
||||
variations: 1,
|
||||
wind_sway: 0.0,
|
||||
}),
|
||||
BlockKind::GrassSnow => Some(SpriteConfig {
|
||||
variations: 10,
|
||||
wind_sway: 0.2,
|
||||
}),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@ -2195,7 +2208,7 @@ impl<V: RectRasterableVol> Terrain<V> {
|
||||
(BlockKind::Bed, 0),
|
||||
make_models(
|
||||
"voxygen.voxel.sprite.furniture.bed-0",
|
||||
Vec3::new(-9.5, -6.0, 0.0),
|
||||
Vec3::new(-9.5, -14.5, 0.0),
|
||||
Vec3::one(),
|
||||
),
|
||||
),
|
||||
@ -2452,7 +2465,7 @@ impl<V: RectRasterableVol> Terrain<V> {
|
||||
(BlockKind::HangingBasket, 0),
|
||||
make_models(
|
||||
"voxygen.voxel.sprite.furniture.hanging_basket-0",
|
||||
Vec3::new(-6.5, -4.5, 0.0),
|
||||
Vec3::new(-6.5, -3.5, 0.0),
|
||||
Vec3::one(),
|
||||
),
|
||||
),
|
||||
@ -2469,7 +2482,7 @@ impl<V: RectRasterableVol> Terrain<V> {
|
||||
(BlockKind::HangingSign, 0),
|
||||
make_models(
|
||||
"voxygen.voxel.sprite.furniture.hanging_sign-0",
|
||||
Vec3::new(-3.5, -17.0, 0.0),
|
||||
Vec3::new(-3.5, -28.0, -4.0),
|
||||
Vec3::one(),
|
||||
),
|
||||
),
|
||||
@ -2486,7 +2499,7 @@ impl<V: RectRasterableVol> Terrain<V> {
|
||||
(BlockKind::WallLamp, 1),
|
||||
make_models(
|
||||
"voxygen.voxel.sprite.furniture.lamp_wall-1",
|
||||
Vec3::new(-9.0, -10.5, 0.0),
|
||||
Vec3::new(-10.5, -9.0, 0.0),
|
||||
Vec3::one(),
|
||||
),
|
||||
),
|
||||
@ -2603,7 +2616,7 @@ impl<V: RectRasterableVol> Terrain<V> {
|
||||
(BlockKind::TableDining, 0),
|
||||
make_models(
|
||||
"voxygen.voxel.sprite.furniture.table_dining-0",
|
||||
Vec3::new(-13.5, -13.5, 0.0),
|
||||
Vec3::new(-8.5, -8.5, 0.0),
|
||||
Vec3::one(),
|
||||
),
|
||||
),
|
||||
@ -2611,7 +2624,7 @@ impl<V: RectRasterableVol> Terrain<V> {
|
||||
(BlockKind::TableDining, 1),
|
||||
make_models(
|
||||
"voxygen.voxel.sprite.furniture.table_dining-1",
|
||||
Vec3::new(-13.5, -13.5, 0.0),
|
||||
Vec3::new(-8.5, -8.5, 0.0),
|
||||
Vec3::one(),
|
||||
),
|
||||
),
|
||||
@ -2629,7 +2642,7 @@ impl<V: RectRasterableVol> Terrain<V> {
|
||||
(BlockKind::WardrobeSingle, 0),
|
||||
make_models(
|
||||
"voxygen.voxel.sprite.furniture.wardrobe_single-0",
|
||||
Vec3::new(-6.0, -5.5, 0.0),
|
||||
Vec3::new(-5.5, -6.0, 0.0),
|
||||
Vec3::one(),
|
||||
),
|
||||
),
|
||||
@ -2637,7 +2650,7 @@ impl<V: RectRasterableVol> Terrain<V> {
|
||||
(BlockKind::WardrobeSingle, 1),
|
||||
make_models(
|
||||
"voxygen.voxel.sprite.furniture.wardrobe_single-1",
|
||||
Vec3::new(-6.5, -5.5, 0.0),
|
||||
Vec3::new(-5.5, -6.5, 0.0),
|
||||
Vec3::one(),
|
||||
),
|
||||
),
|
||||
@ -2646,7 +2659,7 @@ impl<V: RectRasterableVol> Terrain<V> {
|
||||
(BlockKind::WardrobeDouble, 0),
|
||||
make_models(
|
||||
"voxygen.voxel.sprite.furniture.wardrobe_double-0",
|
||||
Vec3::new(-6.5, -10.5, 0.0),
|
||||
Vec3::new(-10.5, -6.5, 0.0),
|
||||
Vec3::one(),
|
||||
),
|
||||
),
|
||||
@ -2654,7 +2667,7 @@ impl<V: RectRasterableVol> Terrain<V> {
|
||||
(BlockKind::WardrobeDouble, 1),
|
||||
make_models(
|
||||
"voxygen.voxel.sprite.furniture.wardrobe_double-1",
|
||||
Vec3::new(-6.0, -10.5, 0.0),
|
||||
Vec3::new(-10.5, -6.0, 0.0),
|
||||
Vec3::one(),
|
||||
),
|
||||
),
|
||||
@ -2733,6 +2746,104 @@ impl<V: RectRasterableVol> Terrain<V> {
|
||||
Vec3::one(),
|
||||
),
|
||||
),
|
||||
// Drop Gate Parts
|
||||
(
|
||||
(BlockKind::DropGate, 0),
|
||||
make_models(
|
||||
"voxygen.voxel.sprite.castle.drop_gate_bars-0",
|
||||
Vec3::new(-5.5, -5.5, 0.0),
|
||||
Vec3::one(),
|
||||
),
|
||||
),
|
||||
(
|
||||
(BlockKind::DropGateBottom, 0),
|
||||
make_models(
|
||||
"voxygen.voxel.sprite.castle.drop_gate_bottom-0",
|
||||
Vec3::new(-5.5, -5.5, 0.0),
|
||||
Vec3::one(),
|
||||
),
|
||||
),
|
||||
// Snow covered Grass
|
||||
(
|
||||
(BlockKind::GrassSnow, 0),
|
||||
make_models(
|
||||
"voxygen.voxel.sprite.grass.grass_snow_0",
|
||||
Vec3::new(-2.5, -2.5, 0.0),
|
||||
Vec3::one(),
|
||||
),
|
||||
),
|
||||
(
|
||||
(BlockKind::GrassSnow, 1),
|
||||
make_models(
|
||||
"voxygen.voxel.sprite.grass.grass_snow_1",
|
||||
Vec3::new(-2.5, -2.5, 0.0),
|
||||
Vec3::one(),
|
||||
),
|
||||
),
|
||||
(
|
||||
(BlockKind::GrassSnow, 2),
|
||||
make_models(
|
||||
"voxygen.voxel.sprite.grass.grass_snow_2",
|
||||
Vec3::new(-2.5, -2.5, 0.0),
|
||||
Vec3::one(),
|
||||
),
|
||||
),
|
||||
(
|
||||
(BlockKind::GrassSnow, 3),
|
||||
make_models(
|
||||
"voxygen.voxel.sprite.grass.grass_snow_3",
|
||||
Vec3::new(-2.5, -2.5, 0.0),
|
||||
Vec3::one(),
|
||||
),
|
||||
),
|
||||
(
|
||||
(BlockKind::GrassSnow, 4),
|
||||
make_models(
|
||||
"voxygen.voxel.sprite.grass.grass_snow_4",
|
||||
Vec3::new(-2.5, -2.5, 0.0),
|
||||
Vec3::one(),
|
||||
),
|
||||
),
|
||||
(
|
||||
(BlockKind::GrassSnow, 5),
|
||||
make_models(
|
||||
"voxygen.voxel.sprite.grass.grass_snow_5",
|
||||
Vec3::new(-2.5, -2.5, 0.0),
|
||||
Vec3::one(),
|
||||
),
|
||||
),
|
||||
(
|
||||
(BlockKind::GrassSnow, 6),
|
||||
make_models(
|
||||
"voxygen.voxel.sprite.grass.grass_snow_6",
|
||||
Vec3::new(-2.5, -2.5, 0.0),
|
||||
Vec3::one(),
|
||||
),
|
||||
),
|
||||
(
|
||||
(BlockKind::GrassSnow, 7),
|
||||
make_models(
|
||||
"voxygen.voxel.sprite.grass.grass_snow_7",
|
||||
Vec3::new(-2.5, -2.5, 0.0),
|
||||
Vec3::one(),
|
||||
),
|
||||
),
|
||||
(
|
||||
(BlockKind::GrassSnow, 8),
|
||||
make_models(
|
||||
"voxygen.voxel.sprite.grass.grass_snow_8",
|
||||
Vec3::new(-2.5, -2.5, 0.0),
|
||||
Vec3::one(),
|
||||
),
|
||||
),
|
||||
(
|
||||
(BlockKind::GrassSnow, 9),
|
||||
make_models(
|
||||
"voxygen.voxel.sprite.grass.grass_snow_9",
|
||||
Vec3::new(-2.5, -2.5, 0.0),
|
||||
Vec3::one(),
|
||||
),
|
||||
),
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
@ -2820,26 +2931,6 @@ impl<V: RectRasterableVol> Terrain<V> {
|
||||
.iter()
|
||||
.map(|(p, _)| *p)
|
||||
{
|
||||
let chunk_pos = scene_data.state.terrain().pos_key(pos);
|
||||
// Only mesh if this chunk has all its neighbors
|
||||
let mut neighbours = true;
|
||||
for i in -1..2 {
|
||||
for j in -1..2 {
|
||||
neighbours &= scene_data
|
||||
.state
|
||||
.terrain()
|
||||
.get_key(chunk_pos + Vec2::new(i, j))
|
||||
.is_some();
|
||||
}
|
||||
}
|
||||
if neighbours {
|
||||
self.mesh_todo.insert(chunk_pos, ChunkMeshState {
|
||||
pos: chunk_pos,
|
||||
started_tick: current_tick,
|
||||
active_worker: None,
|
||||
});
|
||||
}
|
||||
|
||||
// Handle block changes on chunk borders
|
||||
// Remesh all neighbours because we have complex lighting now
|
||||
// TODO: if lighting is on the server this can be updated to only remesh when
|
||||
@ -2850,26 +2941,24 @@ impl<V: RectRasterableVol> Terrain<V> {
|
||||
let neighbour_pos = pos + Vec3::new(x, y, 0);
|
||||
let neighbour_chunk_pos = scene_data.state.terrain().pos_key(neighbour_pos);
|
||||
|
||||
if neighbour_chunk_pos != chunk_pos {
|
||||
// Only remesh if this chunk has all its neighbors
|
||||
let mut neighbours = true;
|
||||
for i in -1..2 {
|
||||
for j in -1..2 {
|
||||
neighbours &= scene_data
|
||||
.state
|
||||
.terrain()
|
||||
.get_key(neighbour_chunk_pos + Vec2::new(i, j))
|
||||
.is_some();
|
||||
}
|
||||
}
|
||||
if neighbours {
|
||||
self.mesh_todo.insert(neighbour_chunk_pos, ChunkMeshState {
|
||||
pos: neighbour_chunk_pos,
|
||||
started_tick: current_tick,
|
||||
active_worker: None,
|
||||
});
|
||||
// Only remesh if this chunk has all its neighbors
|
||||
let mut neighbours = true;
|
||||
for i in -1..2 {
|
||||
for j in -1..2 {
|
||||
neighbours &= scene_data
|
||||
.state
|
||||
.terrain()
|
||||
.get_key(neighbour_chunk_pos + Vec2::new(i, j))
|
||||
.is_some();
|
||||
}
|
||||
}
|
||||
if neighbours {
|
||||
self.mesh_todo.insert(neighbour_chunk_pos, ChunkMeshState {
|
||||
pos: neighbour_chunk_pos,
|
||||
started_tick: current_tick,
|
||||
active_worker: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -3138,23 +3227,27 @@ impl<V: RectRasterableVol> Terrain<V> {
|
||||
let dist_sqrd = Vec2::from(focus_pos).distance_squared(chunk_center);
|
||||
if dist_sqrd < sprite_render_distance.powf(2.0) {
|
||||
for (kind, instances) in &chunk.sprite_instances {
|
||||
renderer.render_sprites(
|
||||
if dist_sqrd < sprite_high_detail_distance.powf(2.0) {
|
||||
&self.sprite_models[&kind][0]
|
||||
} else if dist_sqrd < sprite_hid_detail_distance.powf(2.0) {
|
||||
&self.sprite_models[&kind][1]
|
||||
} else if dist_sqrd < sprite_mid_detail_distance.powf(2.0) {
|
||||
&self.sprite_models[&kind][2]
|
||||
} else if dist_sqrd < sprite_low_detail_distance.powf(2.0) {
|
||||
&self.sprite_models[&kind][3]
|
||||
} else {
|
||||
&self.sprite_models[&kind][4]
|
||||
},
|
||||
globals,
|
||||
&instances,
|
||||
lights,
|
||||
shadows,
|
||||
);
|
||||
if let Some(models) = self.sprite_models.get(&kind) {
|
||||
renderer.render_sprites(
|
||||
if dist_sqrd < sprite_high_detail_distance.powf(2.0) {
|
||||
&models[0]
|
||||
} else if dist_sqrd < sprite_hid_detail_distance.powf(2.0) {
|
||||
&models[1]
|
||||
} else if dist_sqrd < sprite_mid_detail_distance.powf(2.0) {
|
||||
&models[2]
|
||||
} else if dist_sqrd < sprite_low_detail_distance.powf(2.0) {
|
||||
&models[3]
|
||||
} else {
|
||||
&models[4]
|
||||
},
|
||||
globals,
|
||||
&instances,
|
||||
lights,
|
||||
shadows,
|
||||
);
|
||||
} else {
|
||||
warn!("Sprite model for {:?} does not exists", kind);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ fn main() {
|
||||
let pos = focus + Vec2::new(i as i32, j as i32) * scale;
|
||||
|
||||
let (alt, place) = sampler
|
||||
.get(pos)
|
||||
.get((pos, world.index()))
|
||||
.map(|sample| {
|
||||
(
|
||||
sample.alt.sub(64.0).add(gain).mul(0.7).max(0.0).min(255.0) as u8,
|
||||
|
@ -85,7 +85,7 @@ fn main() {
|
||||
lakes,
|
||||
oceans,
|
||||
quads,
|
||||
} = config.generate(sampler, |pos, (r, g, b, a)| {
|
||||
} = config.generate(sampler, world.index(), |pos, (r, g, b, a)| {
|
||||
let i = pos.x;
|
||||
let j = pos.y;
|
||||
buf[j * W + i] = u32::from_le_bytes([b, g, r, a]);
|
||||
@ -104,7 +104,7 @@ fn main() {
|
||||
..config
|
||||
};
|
||||
let mut buf = vec![0u8; 4 * len];
|
||||
config.generate(sampler, |pos, (r, g, b, a)| {
|
||||
config.generate(sampler, world.index(), |pos, (r, g, b, a)| {
|
||||
let i = pos.x;
|
||||
let j = pos.y;
|
||||
(&mut buf[(j * x + i) * 4..]).write(&[r, g, b, a]).unwrap();
|
||||
|
@ -3,13 +3,13 @@ mod natural;
|
||||
use crate::{
|
||||
column::{ColumnGen, ColumnSample},
|
||||
util::{RandomField, Sampler, SmallCache},
|
||||
CONFIG,
|
||||
Index,
|
||||
};
|
||||
use common::{
|
||||
terrain::{structure::StructureBlock, Block, BlockKind, Structure},
|
||||
vol::{ReadVol, Vox},
|
||||
};
|
||||
use std::ops::{Add, Div, Mul, Neg};
|
||||
use std::ops::{Div, Mul};
|
||||
use vek::*;
|
||||
|
||||
pub struct BlockGen<'a> {
|
||||
@ -29,8 +29,11 @@ impl<'a> BlockGen<'a> {
|
||||
column_gen: &ColumnGen<'a>,
|
||||
cache: &'b mut SmallCache<Option<ColumnSample<'a>>>,
|
||||
wpos: Vec2<i32>,
|
||||
index: &Index,
|
||||
) -> Option<&'b ColumnSample<'a>> {
|
||||
cache.get(wpos, |wpos| column_gen.get(wpos)).as_ref()
|
||||
cache
|
||||
.get(wpos, |wpos| column_gen.get((wpos, index)))
|
||||
.as_ref()
|
||||
}
|
||||
|
||||
fn get_cliff_height(
|
||||
@ -40,11 +43,13 @@ impl<'a> BlockGen<'a> {
|
||||
close_cliffs: &[(Vec2<i32>, u32); 9],
|
||||
cliff_hill: f32,
|
||||
tolerance: f32,
|
||||
index: &Index,
|
||||
) -> f32 {
|
||||
close_cliffs.iter().fold(
|
||||
0.0f32,
|
||||
|max_height, (cliff_pos, seed)| match Self::sample_column(column_gen, cache, *cliff_pos)
|
||||
{
|
||||
|max_height, (cliff_pos, seed)| match Self::sample_column(
|
||||
column_gen, cache, *cliff_pos, index,
|
||||
) {
|
||||
Some(cliff_sample) if cliff_sample.is_cliffs && cliff_sample.spawn_rate > 0.5 => {
|
||||
let cliff_pos3d = Vec3::from(*cliff_pos);
|
||||
|
||||
@ -84,14 +89,14 @@ impl<'a> BlockGen<'a> {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get_z_cache(&mut self, wpos: Vec2<i32>) -> Option<ZCache<'a>> {
|
||||
pub fn get_z_cache(&mut self, wpos: Vec2<i32>, index: &'a Index) -> Option<ZCache<'a>> {
|
||||
let BlockGen {
|
||||
column_cache,
|
||||
column_gen,
|
||||
} = self;
|
||||
|
||||
// Main sample
|
||||
let sample = column_gen.get(wpos)?;
|
||||
let sample = column_gen.get((wpos, index))?;
|
||||
|
||||
// Tree samples
|
||||
let mut structures = [None, None, None, None, None, None, None, None, None];
|
||||
@ -101,7 +106,7 @@ impl<'a> BlockGen<'a> {
|
||||
.zip(structures.iter_mut())
|
||||
.for_each(|(close_structure, structure)| {
|
||||
if let Some(st) = *close_structure {
|
||||
let st_sample = Self::sample_column(column_gen, column_cache, st.pos);
|
||||
let st_sample = Self::sample_column(column_gen, column_cache, st.pos, index);
|
||||
if let Some(st_sample) = st_sample {
|
||||
let st_sample = st_sample.clone();
|
||||
let st_info = match st.meta {
|
||||
@ -111,6 +116,7 @@ impl<'a> BlockGen<'a> {
|
||||
st.pos,
|
||||
st.seed,
|
||||
&st_sample,
|
||||
index,
|
||||
),
|
||||
Some(meta) => Some(StructureInfo {
|
||||
pos: Vec3::from(st.pos) + Vec3::unit_z() * st_sample.alt as i32,
|
||||
@ -137,6 +143,7 @@ impl<'a> BlockGen<'a> {
|
||||
wpos: Vec3<i32>,
|
||||
z_cache: Option<&ZCache>,
|
||||
only_structures: bool,
|
||||
index: &Index,
|
||||
) -> Option<Block> {
|
||||
let BlockGen {
|
||||
column_cache,
|
||||
@ -156,16 +163,14 @@ impl<'a> BlockGen<'a> {
|
||||
//tree_density,
|
||||
//forest_kind,
|
||||
//close_structures,
|
||||
cave_xy,
|
||||
cave_alt,
|
||||
marble,
|
||||
marble_small,
|
||||
// marble,
|
||||
// marble_small,
|
||||
rock,
|
||||
//cliffs,
|
||||
cliff_hill,
|
||||
close_cliffs,
|
||||
temp,
|
||||
humidity,
|
||||
// temp,
|
||||
// humidity,
|
||||
stone_col,
|
||||
..
|
||||
} = sample;
|
||||
@ -175,7 +180,7 @@ impl<'a> BlockGen<'a> {
|
||||
let wposf = wpos.map(|e| e as f64);
|
||||
|
||||
let (block, _height) = if !only_structures {
|
||||
let (_definitely_underground, height, on_cliff, basement_height, water_height) =
|
||||
let (_definitely_underground, height, _on_cliff, basement_height, water_height) =
|
||||
if (wposf.z as f32) < alt - 64.0 * chaos {
|
||||
// Shortcut warping
|
||||
(true, alt, false, basement, water_level)
|
||||
@ -208,6 +213,7 @@ impl<'a> BlockGen<'a> {
|
||||
&close_cliffs,
|
||||
cliff_hill,
|
||||
0.0,
|
||||
index,
|
||||
);
|
||||
|
||||
(
|
||||
@ -269,81 +275,84 @@ impl<'a> BlockGen<'a> {
|
||||
},
|
||||
col.map(|e| (e * 255.0) as u8),
|
||||
))
|
||||
} else if (wposf.z as f32) < height + 0.9
|
||||
&& temp < CONFIG.desert_temp
|
||||
&& (wposf.z as f32 > water_height + 3.0)
|
||||
&& marble > 0.6
|
||||
&& marble_small > 0.55
|
||||
&& (marble * 3173.7).fract() < 0.6
|
||||
&& humidity > CONFIG.desert_hum
|
||||
{
|
||||
let treasures = [BlockKind::Chest, BlockKind::Velorite];
|
||||
// } else if (wposf.z as f32) < height + 0.9
|
||||
// && temp < CONFIG.desert_temp
|
||||
// && (wposf.z as f32 > water_height + 3.0)
|
||||
// && marble > 0.6
|
||||
// && marble_small > 0.55
|
||||
// && (marble * 3173.7).fract() < 0.6
|
||||
// && humidity > CONFIG.desert_hum
|
||||
// && false
|
||||
// {
|
||||
// let treasures = [BlockKind::Chest, BlockKind::Velorite];
|
||||
|
||||
let flowers = [
|
||||
BlockKind::BlueFlower,
|
||||
BlockKind::PinkFlower,
|
||||
BlockKind::PurpleFlower,
|
||||
BlockKind::RedFlower,
|
||||
BlockKind::WhiteFlower,
|
||||
BlockKind::YellowFlower,
|
||||
BlockKind::Sunflower,
|
||||
BlockKind::Mushroom, //TODO: Better spawnrules
|
||||
BlockKind::LeafyPlant,
|
||||
BlockKind::Blueberry,
|
||||
BlockKind::LingonBerry,
|
||||
BlockKind::Fern,
|
||||
/*BlockKind::Twigs, // TODO: Better spawnrules
|
||||
*BlockKind::Stones, // TODO: Better spawnrules
|
||||
*BlockKind::ShinyGem, // TODO: Better spawnrules */
|
||||
];
|
||||
let grasses = [
|
||||
BlockKind::LongGrass,
|
||||
BlockKind::MediumGrass,
|
||||
BlockKind::ShortGrass,
|
||||
];
|
||||
// let flowers = [
|
||||
// BlockKind::BlueFlower,
|
||||
// BlockKind::PinkFlower,
|
||||
// BlockKind::PurpleFlower,
|
||||
// BlockKind::RedFlower,
|
||||
// BlockKind::WhiteFlower,
|
||||
// BlockKind::YellowFlower,
|
||||
// BlockKind::Sunflower,
|
||||
// BlockKind::Mushroom, //TODO: Better spawnrules
|
||||
// BlockKind::LeafyPlant,
|
||||
// BlockKind::Blueberry,
|
||||
// BlockKind::LingonBerry,
|
||||
// BlockKind::Fern,
|
||||
// /*BlockKind::Twigs, // TODO: Better spawnrules
|
||||
// *BlockKind::Stones, // TODO: Better spawnrules
|
||||
// *BlockKind::ShinyGem, // TODO: Better spawnrules */
|
||||
// ];
|
||||
// let grasses = [
|
||||
// BlockKind::LongGrass,
|
||||
// BlockKind::MediumGrass,
|
||||
// BlockKind::ShortGrass,
|
||||
// ];
|
||||
|
||||
Some(Block::new(
|
||||
if on_cliff && (height * 1271.0).fract() < 0.015 {
|
||||
treasures[(height * 731.3) as usize % treasures.len()]
|
||||
} else if (height * 1271.0).fract() < 0.1 {
|
||||
flowers[(height * 0.2) as usize % flowers.len()]
|
||||
} else {
|
||||
grasses[(height * 103.3) as usize % grasses.len()]
|
||||
},
|
||||
Rgb::broadcast(0),
|
||||
))
|
||||
} else if (wposf.z as f32) < height + 0.9
|
||||
&& temp > CONFIG.desert_temp
|
||||
&& (marble * 4423.5).fract() < 0.0005
|
||||
{
|
||||
let large_cacti = [
|
||||
BlockKind::LargeCactus,
|
||||
BlockKind::MedFlatCactus,
|
||||
BlockKind::Welwitch,
|
||||
];
|
||||
// Some(Block::new(
|
||||
// if on_cliff && (height * 1271.0).fract() < 0.015 {
|
||||
// treasures[(height * 731.3) as usize %
|
||||
// treasures.len()] } else if (height *
|
||||
// 1271.0).fract() < 0.1 { flowers[(height *
|
||||
// 0.2) as usize % flowers.len()] } else {
|
||||
// grasses[(height * 103.3) as usize % grasses.len()]
|
||||
// },
|
||||
// Rgb::broadcast(0),
|
||||
// ))
|
||||
// } else if (wposf.z as f32) < height + 0.9
|
||||
// && temp > CONFIG.desert_temp
|
||||
// && (marble * 4423.5).fract() < 0.0005
|
||||
// && false
|
||||
// {
|
||||
// let large_cacti = [
|
||||
// BlockKind::LargeCactus,
|
||||
// BlockKind::MedFlatCactus,
|
||||
// BlockKind::Welwitch,
|
||||
// ];
|
||||
|
||||
let small_cacti = [
|
||||
BlockKind::BarrelCactus,
|
||||
BlockKind::RoundCactus,
|
||||
BlockKind::ShortCactus,
|
||||
BlockKind::ShortFlatCactus,
|
||||
BlockKind::DeadBush,
|
||||
];
|
||||
// let small_cacti = [
|
||||
// BlockKind::BarrelCactus,
|
||||
// BlockKind::RoundCactus,
|
||||
// BlockKind::ShortCactus,
|
||||
// BlockKind::ShortFlatCactus,
|
||||
// BlockKind::DeadBush,
|
||||
// ];
|
||||
|
||||
Some(Block::new(
|
||||
if (height * 1271.0).fract() < 0.5 {
|
||||
large_cacti[(height * 0.2) as usize % large_cacti.len()]
|
||||
} else {
|
||||
small_cacti[(height * 0.3) as usize % small_cacti.len()]
|
||||
},
|
||||
Rgb::broadcast(0),
|
||||
))
|
||||
// Some(Block::new(
|
||||
// if (height * 1271.0).fract() < 0.5 {
|
||||
// large_cacti[(height * 0.2) as usize %
|
||||
// large_cacti.len()] } else {
|
||||
// small_cacti[(height * 0.3) as usize %
|
||||
// small_cacti.len()] },
|
||||
// Rgb::broadcast(0),
|
||||
// ))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
.or_else(|| {
|
||||
// Rocks
|
||||
if (height + 2.5 - wposf.z as f32).div(7.5).abs().powf(2.0) < rock {
|
||||
#[allow(clippy::identity_op)]
|
||||
let field0 = RandomField::new(world.seed + 0);
|
||||
let field1 = RandomField::new(world.seed + 1);
|
||||
let field2 = RandomField::new(world.seed + 2);
|
||||
@ -361,23 +370,6 @@ impl<'a> BlockGen<'a> {
|
||||
None
|
||||
}
|
||||
})
|
||||
.and_then(|block| {
|
||||
// Caves
|
||||
// Underground
|
||||
let cave = cave_xy.powf(2.0)
|
||||
* (wposf.z as f32 - cave_alt)
|
||||
.div(40.0)
|
||||
.powf(4.0)
|
||||
.neg()
|
||||
.add(1.0)
|
||||
> 0.9993;
|
||||
|
||||
if cave && wposf.z as f32 > water_height + 3.0 {
|
||||
None
|
||||
} else {
|
||||
Some(block)
|
||||
}
|
||||
})
|
||||
.or_else(|| {
|
||||
// Water
|
||||
if (wposf.z as f32) < water_height {
|
||||
@ -412,15 +404,8 @@ pub struct ZCache<'a> {
|
||||
}
|
||||
|
||||
impl<'a> ZCache<'a> {
|
||||
pub fn get_z_limits(&self, block_gen: &mut BlockGen) -> (f32, f32, f32) {
|
||||
let cave_depth =
|
||||
if self.sample.cave_xy.abs() > 0.9 && self.sample.water_level <= self.sample.alt {
|
||||
(self.sample.alt - self.sample.cave_alt + 8.0).max(0.0)
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
let min = self.sample.alt - (self.sample.chaos.min(1.0) * 16.0 + cave_depth);
|
||||
pub fn get_z_limits(&self, block_gen: &mut BlockGen, index: &Index) -> (f32, f32, f32) {
|
||||
let min = self.sample.alt - (self.sample.chaos.min(1.0) * 16.0);
|
||||
let min = min - 4.0;
|
||||
|
||||
let cliff = BlockGen::get_cliff_height(
|
||||
@ -430,6 +415,7 @@ impl<'a> ZCache<'a> {
|
||||
&self.sample.close_cliffs,
|
||||
self.sample.cliff_hill,
|
||||
32.0,
|
||||
index,
|
||||
);
|
||||
|
||||
let rocks = if self.sample.rock > 0.0 { 12.0 } else { 0.0 };
|
||||
@ -550,7 +536,7 @@ pub fn block_from_structure(
|
||||
structure_seed: u32,
|
||||
sample: &ColumnSample,
|
||||
) -> Option<Block> {
|
||||
let field = RandomField::new(structure_seed + 0);
|
||||
let field = RandomField::new(structure_seed);
|
||||
|
||||
let lerp = ((field.get(Vec3::from(structure_pos)).rem_euclid(256)) as f32 / 255.0) * 0.85
|
||||
+ ((field.get(pos + std::i32::MAX / 2).rem_euclid(256)) as f32 / 255.0) * 0.15;
|
||||
|
@ -3,7 +3,7 @@ use crate::{
|
||||
all::ForestKind,
|
||||
column::{ColumnGen, ColumnSample},
|
||||
util::{RandomPerm, Sampler, SmallCache, UnitChooser},
|
||||
CONFIG,
|
||||
Index, CONFIG,
|
||||
};
|
||||
use common::terrain::Structure;
|
||||
use lazy_static::lazy_static;
|
||||
@ -20,6 +20,7 @@ pub fn structure_gen<'a>(
|
||||
st_pos: Vec2<i32>,
|
||||
st_seed: u32,
|
||||
st_sample: &ColumnSample,
|
||||
index: &'a Index,
|
||||
) -> Option<StructureInfo> {
|
||||
// Assuming it's a tree... figure out when it SHOULDN'T spawn
|
||||
let random_seed = (st_seed as f64) / (u32::MAX as f64);
|
||||
@ -27,7 +28,7 @@ pub fn structure_gen<'a>(
|
||||
|| st_sample.alt < st_sample.water_level
|
||||
|| st_sample.spawn_rate < 0.5
|
||||
|| st_sample.water_dist.map(|d| d < 8.0).unwrap_or(false)
|
||||
|| st_sample.path.map(|(d, _)| d < 12.0).unwrap_or(false)
|
||||
|| st_sample.path.map(|(d, _, _, _)| d < 12.0).unwrap_or(false)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
@ -39,6 +40,7 @@ pub fn structure_gen<'a>(
|
||||
&st_sample.close_cliffs,
|
||||
st_sample.cliff_hill,
|
||||
0.0,
|
||||
index,
|
||||
);
|
||||
|
||||
let wheight = st_sample.alt.max(cliff_height);
|
||||
|
@ -5,8 +5,9 @@ mod econ;
|
||||
use self::{Occupation::*, Stock::*};
|
||||
use crate::{
|
||||
sim::WorldSim,
|
||||
site::{Dungeon, Settlement, Site as WorldSite},
|
||||
util::{attempt, seed_expan, CARDINALS, NEIGHBORS},
|
||||
site::{Castle, Dungeon, Settlement, Site as WorldSite},
|
||||
util::{attempt, seed_expan, MapVec, CARDINALS, NEIGHBORS},
|
||||
Index,
|
||||
};
|
||||
use common::{
|
||||
astar::Astar,
|
||||
@ -70,11 +71,15 @@ impl<'a, R: Rng> GenCtx<'a, R> {
|
||||
}
|
||||
|
||||
impl Civs {
|
||||
pub fn generate(seed: u32, sim: &mut WorldSim) -> Self {
|
||||
pub fn generate(seed: u32, sim: &mut WorldSim, index: &mut Index) -> Self {
|
||||
let mut this = Self::default();
|
||||
let rng = ChaChaRng::from_seed(seed_expan::rng_state(seed));
|
||||
let mut ctx = GenCtx { sim, rng };
|
||||
|
||||
for _ in 0..100 {
|
||||
this.generate_cave(&mut ctx);
|
||||
}
|
||||
|
||||
for _ in 0..INITIAL_CIV_COUNT {
|
||||
debug!("Creating civilisation...");
|
||||
if this.birth_civ(&mut ctx.reseed()).is_none() {
|
||||
@ -85,9 +90,13 @@ impl Civs {
|
||||
|
||||
for _ in 0..INITIAL_CIV_COUNT * 3 {
|
||||
attempt(5, || {
|
||||
let loc = find_site_loc(&mut ctx, None)?;
|
||||
let (kind, size) = match ctx.rng.gen_range(0, 8) {
|
||||
0 => (SiteKind::Castle, 3),
|
||||
_ => (SiteKind::Dungeon, 0),
|
||||
};
|
||||
let loc = find_site_loc(&mut ctx, None, size)?;
|
||||
this.establish_site(&mut ctx.reseed(), loc, |place| Site {
|
||||
kind: SiteKind::Dungeon,
|
||||
kind,
|
||||
center: loc,
|
||||
place,
|
||||
|
||||
@ -103,7 +112,7 @@ impl Civs {
|
||||
|
||||
last_exports: Stocks::from_default(0.0),
|
||||
export_targets: Stocks::from_default(0.0),
|
||||
trade_states: Stocks::default(),
|
||||
//trade_states: Stocks::default(),
|
||||
coin: 1000.0,
|
||||
})
|
||||
});
|
||||
@ -116,7 +125,7 @@ impl Civs {
|
||||
}
|
||||
|
||||
// Flatten ground around sites
|
||||
for site in this.sites.iter() {
|
||||
for site in this.sites.values() {
|
||||
let radius = 48i32;
|
||||
|
||||
let wpos = site.center * TerrainChunkSize::RECT_SIZE.map(|e: u32| e as i32);
|
||||
@ -124,10 +133,12 @@ impl Civs {
|
||||
let flatten_radius = match &site.kind {
|
||||
SiteKind::Settlement => 10.0,
|
||||
SiteKind::Dungeon => 2.0,
|
||||
SiteKind::Castle => 5.0,
|
||||
};
|
||||
|
||||
let (raise, raise_dist): (f32, i32) = match &site.kind {
|
||||
SiteKind::Settlement => (10.0, 6),
|
||||
SiteKind::Castle => (0.0, 6),
|
||||
_ => (0.0, 0),
|
||||
};
|
||||
|
||||
@ -141,9 +152,11 @@ impl Civs {
|
||||
0.0
|
||||
}; // Raise the town centre up a little
|
||||
let pos = site.center + offs;
|
||||
let factor = (1.0
|
||||
let factor = ((1.0
|
||||
- (site.center - pos).map(|e| e as f32).magnitude() / flatten_radius)
|
||||
* 1.15;
|
||||
* 1.25)
|
||||
.min(1.0);
|
||||
let rng = &mut ctx.rng;
|
||||
ctx.sim
|
||||
.get_mut(pos)
|
||||
// Don't disrupt chunks that are near water
|
||||
@ -154,6 +167,7 @@ impl Civs {
|
||||
chunk.basement += diff;
|
||||
chunk.rockiness = 0.0;
|
||||
chunk.warp_factor = 0.0;
|
||||
chunk.surface_veg *= 1.0 - factor * rng.gen_range(0.25, 0.9);
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -161,33 +175,37 @@ impl Civs {
|
||||
|
||||
// Place sites in world
|
||||
let mut cnt = 0;
|
||||
for site in this.sites.iter() {
|
||||
for sim_site in this.sites.values() {
|
||||
cnt += 1;
|
||||
let wpos = site.center.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| {
|
||||
e * sz as i32 + sz as i32 / 2
|
||||
});
|
||||
let wpos = sim_site
|
||||
.center
|
||||
.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| {
|
||||
e * sz as i32 + sz as i32 / 2
|
||||
});
|
||||
|
||||
let mut rng = ctx.reseed().rng;
|
||||
let world_site = match &site.kind {
|
||||
let site = index.sites.insert(match &sim_site.kind {
|
||||
SiteKind::Settlement => {
|
||||
WorldSite::from(Settlement::generate(wpos, Some(ctx.sim), &mut rng))
|
||||
WorldSite::settlement(Settlement::generate(wpos, Some(ctx.sim), &mut rng))
|
||||
},
|
||||
SiteKind::Dungeon => {
|
||||
WorldSite::from(Dungeon::generate(wpos, Some(ctx.sim), &mut rng))
|
||||
WorldSite::dungeon(Dungeon::generate(wpos, Some(ctx.sim), &mut rng))
|
||||
},
|
||||
};
|
||||
SiteKind::Castle => {
|
||||
WorldSite::castle(Castle::generate(wpos, Some(ctx.sim), &mut rng))
|
||||
},
|
||||
});
|
||||
let site_ref = &index.sites[site];
|
||||
|
||||
let radius_chunks =
|
||||
(world_site.radius() / TerrainChunkSize::RECT_SIZE.x as f32).ceil() as usize;
|
||||
(site_ref.radius() / TerrainChunkSize::RECT_SIZE.x as f32).ceil() as usize;
|
||||
for pos in Spiral2d::new()
|
||||
.map(|offs| site.center + offs)
|
||||
.map(|offs| sim_site.center + offs)
|
||||
.take((radius_chunks * 2).pow(2))
|
||||
{
|
||||
ctx.sim
|
||||
.get_mut(pos)
|
||||
.map(|chunk| chunk.sites.push(world_site.clone()));
|
||||
ctx.sim.get_mut(pos).map(|chunk| chunk.sites.push(site));
|
||||
}
|
||||
debug!(?site.center, "Placed site at location");
|
||||
debug!(?sim_site.center, "Placed site at location");
|
||||
}
|
||||
info!(?cnt, "all sites placed");
|
||||
|
||||
@ -196,20 +214,84 @@ impl Civs {
|
||||
this
|
||||
}
|
||||
|
||||
// TODO: Move this
|
||||
fn generate_cave(&self, ctx: &mut GenCtx<impl Rng>) {
|
||||
let mut pos = ctx
|
||||
.sim
|
||||
.get_size()
|
||||
.map(|sz| ctx.rng.gen_range(0, sz as i32) as f32);
|
||||
let mut vel = pos
|
||||
.map2(ctx.sim.get_size(), |pos, sz| sz as f32 / 2.0 - pos)
|
||||
.try_normalized()
|
||||
.unwrap_or_else(Vec2::unit_y);
|
||||
|
||||
let path = (-100..100)
|
||||
.filter_map(|i: i32| {
|
||||
let depth = (i.abs() as f32 / 100.0 * std::f32::consts::PI / 2.0).cos();
|
||||
vel = (vel
|
||||
+ Vec2::new(
|
||||
ctx.rng.gen_range(-0.35, 0.35),
|
||||
ctx.rng.gen_range(-0.35, 0.35),
|
||||
))
|
||||
.try_normalized()
|
||||
.unwrap_or_else(Vec2::unit_y);
|
||||
let old_pos = pos.map(|e| e as i32);
|
||||
pos = (pos + vel * 0.5)
|
||||
.clamped(Vec2::zero(), ctx.sim.get_size().map(|e| e as f32 - 1.0));
|
||||
Some((pos.map(|e| e as i32), depth)).filter(|(pos, _)| *pos != old_pos)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for locs in path.windows(3) {
|
||||
let to_prev_idx = NEIGHBORS
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, dir)| **dir == locs[0].0 - locs[1].0)
|
||||
.expect("Track locations must be neighbors")
|
||||
.0;
|
||||
let to_next_idx = NEIGHBORS
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, dir)| **dir == locs[2].0 - locs[1].0)
|
||||
.expect("Track locations must be neighbors")
|
||||
.0;
|
||||
|
||||
ctx.sim.get_mut(locs[0].0).unwrap().cave.0.neighbors |=
|
||||
1 << ((to_prev_idx as u8 + 4) % 8);
|
||||
ctx.sim.get_mut(locs[1].0).unwrap().cave.0.neighbors |=
|
||||
(1 << (to_prev_idx as u8)) | (1 << (to_next_idx as u8));
|
||||
ctx.sim.get_mut(locs[2].0).unwrap().cave.0.neighbors |=
|
||||
1 << ((to_next_idx as u8 + 4) % 8);
|
||||
}
|
||||
|
||||
for loc in path.iter() {
|
||||
let mut chunk = ctx.sim.get_mut(loc.0).unwrap();
|
||||
let depth = loc.1 * 250.0 - 20.0;
|
||||
chunk.cave.1.alt =
|
||||
chunk.alt - depth + ctx.rng.gen_range(-4.0, 4.0) * (depth > 10.0) as i32 as f32;
|
||||
chunk.cave.1.width = ctx.rng.gen_range(6.0, 32.0);
|
||||
chunk.cave.0.offset = Vec2::new(ctx.rng.gen_range(-16, 17), ctx.rng.gen_range(-16, 17));
|
||||
|
||||
if chunk.cave.1.alt + chunk.cave.1.width + 5.0 > chunk.alt {
|
||||
chunk.spawn_rate = 0.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn place(&self, id: Id<Place>) -> &Place { self.places.get(id) }
|
||||
|
||||
pub fn sites(&self) -> impl Iterator<Item = &Site> + '_ { self.sites.iter() }
|
||||
pub fn sites(&self) -> impl Iterator<Item = &Site> + '_ { self.sites.values() }
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[allow(clippy::print_literal)] // TODO: Pending review in #587
|
||||
fn display_info(&self) {
|
||||
for (id, civ) in self.civs.iter_ids() {
|
||||
for (id, civ) in self.civs.iter() {
|
||||
println!("# Civilisation {:?}", id);
|
||||
println!("Name: {}", "<unnamed>");
|
||||
println!("Homeland: {:#?}", self.places.get(civ.homeland));
|
||||
}
|
||||
|
||||
for (id, site) in self.sites.iter_ids() {
|
||||
for (id, site) in self.sites.iter() {
|
||||
println!("# Site {:?}", id);
|
||||
println!("{:#?}", site);
|
||||
}
|
||||
@ -272,7 +354,7 @@ impl Civs {
|
||||
|
||||
fn birth_civ(&mut self, ctx: &mut GenCtx<impl Rng>) -> Option<Id<Civ>> {
|
||||
let site = attempt(5, || {
|
||||
let loc = find_site_loc(ctx, None)?;
|
||||
let loc = find_site_loc(ctx, None, 1)?;
|
||||
self.establish_site(ctx, loc, |place| Site {
|
||||
kind: SiteKind::Settlement,
|
||||
center: loc,
|
||||
@ -290,7 +372,7 @@ impl Civs {
|
||||
|
||||
last_exports: Stocks::from_default(0.0),
|
||||
export_targets: Stocks::from_default(0.0),
|
||||
trade_states: Stocks::default(),
|
||||
//trade_states: Stocks::default(),
|
||||
coin: 1000.0,
|
||||
})
|
||||
})?;
|
||||
@ -367,7 +449,7 @@ impl Civs {
|
||||
loc: Vec2<i32>,
|
||||
site_fn: impl FnOnce(Id<Place>) -> Site,
|
||||
) -> Option<Id<Site>> {
|
||||
const SITE_AREA: Range<usize> = 64..256;
|
||||
const SITE_AREA: Range<usize> = 1..4; //64..256;
|
||||
|
||||
let place = match ctx.sim.get(loc).and_then(|site| site.place) {
|
||||
Some(place) => place,
|
||||
@ -380,13 +462,14 @@ impl Civs {
|
||||
const MAX_NEIGHBOR_DISTANCE: f32 = 500.0;
|
||||
let mut nearby = self
|
||||
.sites
|
||||
.iter_ids()
|
||||
.iter()
|
||||
.filter(|(_, p)| matches!(p.kind, SiteKind::Settlement | SiteKind::Castle))
|
||||
.map(|(id, p)| (id, (p.center.distance_squared(loc) as f32).sqrt()))
|
||||
.filter(|(_, dist)| *dist < MAX_NEIGHBOR_DISTANCE)
|
||||
.collect::<Vec<_>>();
|
||||
nearby.sort_by_key(|(_, dist)| *dist as i32);
|
||||
|
||||
if let SiteKind::Settlement = self.sites[site].kind {
|
||||
if let SiteKind::Settlement | SiteKind::Castle = self.sites[site].kind {
|
||||
for (nearby, _) in nearby.into_iter().take(5) {
|
||||
// Find a novel path
|
||||
if let Some((path, cost)) = find_path(ctx, loc, self.sites.get(nearby).center) {
|
||||
@ -412,17 +495,15 @@ impl Civs {
|
||||
.expect("Track locations must be neighbors")
|
||||
.0;
|
||||
|
||||
ctx.sim.get_mut(locs[0]).unwrap().path.neighbors |=
|
||||
ctx.sim.get_mut(locs[0]).unwrap().path.0.neighbors |=
|
||||
1 << ((to_prev_idx as u8 + 4) % 8);
|
||||
ctx.sim.get_mut(locs[2]).unwrap().path.neighbors |=
|
||||
ctx.sim.get_mut(locs[2]).unwrap().path.0.neighbors |=
|
||||
1 << ((to_next_idx as u8 + 4) % 8);
|
||||
let mut chunk = ctx.sim.get_mut(locs[1]).unwrap();
|
||||
chunk.path.neighbors |=
|
||||
chunk.path.0.neighbors |=
|
||||
(1 << (to_prev_idx as u8)) | (1 << (to_next_idx as u8));
|
||||
chunk.path.offset = Vec2::new(
|
||||
ctx.rng.gen_range(-16.0, 16.0),
|
||||
ctx.rng.gen_range(-16.0, 16.0),
|
||||
);
|
||||
chunk.path.0.offset =
|
||||
Vec2::new(ctx.rng.gen_range(-16, 17), ctx.rng.gen_range(-16, 17));
|
||||
}
|
||||
|
||||
// Take note of the track
|
||||
@ -440,7 +521,7 @@ impl Civs {
|
||||
}
|
||||
|
||||
fn tick(&mut self, _ctx: &mut GenCtx<impl Rng>, years: f32) {
|
||||
for site in self.sites.iter_mut() {
|
||||
for site in self.sites.values_mut() {
|
||||
site.simulate(years, &self.places.get(site.place).nat_res);
|
||||
}
|
||||
|
||||
@ -557,7 +638,7 @@ fn walk_in_dir(sim: &WorldSim, a: Vec2<i32>, dir: Vec2<i32>) -> Option<f32> {
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
let wild_cost = if b_chunk.path.is_path() {
|
||||
let wild_cost = if b_chunk.path.0.is_way() {
|
||||
0.0 // Traversing existing paths has no additional cost!
|
||||
} else {
|
||||
2.0
|
||||
@ -589,6 +670,7 @@ fn loc_suitable_for_site(sim: &WorldSim, loc: Vec2<i32>) -> bool {
|
||||
if let Some(chunk) = sim.get(loc) {
|
||||
!chunk.river.is_ocean()
|
||||
&& !chunk.river.is_lake()
|
||||
&& !chunk.river.is_river()
|
||||
&& sim
|
||||
.get_gradient_approx(loc)
|
||||
.map(|grad| grad < 1.0)
|
||||
@ -599,9 +681,12 @@ fn loc_suitable_for_site(sim: &WorldSim, loc: Vec2<i32>) -> bool {
|
||||
}
|
||||
|
||||
/// Attempt to search for a location that's suitable for site construction
|
||||
#[allow(clippy::useless_conversion)] // TODO: Pending review in #587
|
||||
#[allow(clippy::or_fun_call)] // TODO: Pending review in #587
|
||||
fn find_site_loc(ctx: &mut GenCtx<impl Rng>, near: Option<(Vec2<i32>, f32)>) -> Option<Vec2<i32>> {
|
||||
fn find_site_loc(
|
||||
ctx: &mut GenCtx<impl Rng>,
|
||||
near: Option<(Vec2<i32>, f32)>,
|
||||
size: i32,
|
||||
) -> Option<Vec2<i32>> {
|
||||
const MAX_ATTEMPTS: usize = 100;
|
||||
let mut loc = None;
|
||||
for _ in 0..MAX_ATTEMPTS {
|
||||
@ -621,16 +706,16 @@ fn find_site_loc(ctx: &mut GenCtx<impl Rng>, near: Option<(Vec2<i32>, f32)>) ->
|
||||
),
|
||||
});
|
||||
|
||||
if loc_suitable_for_site(&ctx.sim, test_loc) {
|
||||
return Some(test_loc);
|
||||
for offset in Spiral2d::new().take((size * 2 + 1).pow(2) as usize) {
|
||||
if loc_suitable_for_site(&ctx.sim, test_loc + offset) {
|
||||
return Some(test_loc);
|
||||
}
|
||||
}
|
||||
|
||||
loc = ctx.sim.get(test_loc).and_then(|c| {
|
||||
Some(
|
||||
c.downhill?
|
||||
.map2(Vec2::from(TerrainChunkSize::RECT_SIZE), |e, sz: u32| {
|
||||
e / (sz as i32)
|
||||
}),
|
||||
.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| e / (sz as i32)),
|
||||
)
|
||||
});
|
||||
}
|
||||
@ -717,16 +802,13 @@ pub struct Site {
|
||||
|
||||
last_exports: Stocks<f32>,
|
||||
export_targets: Stocks<f32>,
|
||||
trade_states: Stocks<TradeState>,
|
||||
//trade_states: Stocks<TradeState>,
|
||||
coin: f32,
|
||||
}
|
||||
|
||||
impl fmt::Display for Site {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self.kind {
|
||||
SiteKind::Settlement => writeln!(f, "Settlement")?,
|
||||
SiteKind::Dungeon => writeln!(f, "Dungeon")?,
|
||||
}
|
||||
writeln!(f, "{:?}", self.kind)?;
|
||||
writeln!(f, "- population: {}", self.population.floor() as u32)?;
|
||||
writeln!(f, "- coin: {}", self.coin.floor() as u32)?;
|
||||
writeln!(f, "Stocks")?;
|
||||
@ -766,6 +848,7 @@ impl fmt::Display for Site {
|
||||
pub enum SiteKind {
|
||||
Settlement,
|
||||
Dungeon,
|
||||
Castle,
|
||||
}
|
||||
|
||||
impl Site {
|
||||
@ -996,79 +1079,3 @@ impl Default for TradeState {
|
||||
}
|
||||
|
||||
pub type Stocks<T> = MapVec<Stock, T>;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MapVec<K, T> {
|
||||
/// We use this hasher (FxHasher32) because
|
||||
/// (1) we don't care about DDOS attacks (ruling out SipHash);
|
||||
/// (2) we care about determinism across computers (ruling out AAHash);
|
||||
/// (3) we have 1-byte keys (for which FxHash is supposedly fastest).
|
||||
entries: HashMap<K, T, BuildHasherDefault<FxHasher32>>,
|
||||
default: T,
|
||||
}
|
||||
|
||||
/// Need manual implementation of Default since K doesn't need that bound.
|
||||
impl<K, T: Default> Default for MapVec<K, T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
entries: Default::default(),
|
||||
default: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: Copy + Eq + Hash, T: Clone> MapVec<K, T> {
|
||||
pub fn from_list<'a>(i: impl IntoIterator<Item = &'a (K, T)>, default: T) -> Self
|
||||
where
|
||||
K: 'a,
|
||||
T: 'a,
|
||||
{
|
||||
Self {
|
||||
entries: i.into_iter().cloned().collect(),
|
||||
default,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_default(default: T) -> Self {
|
||||
Self {
|
||||
entries: HashMap::default(),
|
||||
default,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_mut(&mut self, entry: K) -> &mut T {
|
||||
let default = &self.default;
|
||||
self.entries.entry(entry).or_insert_with(|| default.clone())
|
||||
}
|
||||
|
||||
pub fn get(&self, entry: K) -> &T { self.entries.get(&entry).unwrap_or(&self.default) }
|
||||
|
||||
pub fn map<U: Default>(self, mut f: impl FnMut(K, T) -> U) -> MapVec<K, U> {
|
||||
MapVec {
|
||||
entries: self
|
||||
.entries
|
||||
.into_iter()
|
||||
.map(|(s, v)| (s, f(s, v)))
|
||||
.collect(),
|
||||
default: U::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = (K, &T)> + '_ {
|
||||
self.entries.iter().map(|(s, v)| (*s, v))
|
||||
}
|
||||
|
||||
pub fn iter_mut(&mut self) -> impl Iterator<Item = (K, &mut T)> + '_ {
|
||||
self.entries.iter_mut().map(|(s, v)| (*s, v))
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: Copy + Eq + Hash, T: Clone> std::ops::Index<K> for MapVec<K, T> {
|
||||
type Output = T;
|
||||
|
||||
fn index(&self, entry: K) -> &Self::Output { self.get(entry) }
|
||||
}
|
||||
|
||||
impl<K: Copy + Eq + Hash, T: Clone> std::ops::IndexMut<K> for MapVec<K, T> {
|
||||
fn index_mut(&mut self, entry: K) -> &mut Self::Output { self.get_mut(entry) }
|
||||
}
|
||||
|
@ -1,9 +1,12 @@
|
||||
use crate::{
|
||||
all::ForestKind,
|
||||
block::StructureMeta,
|
||||
sim::{local_cells, uniform_idx_as_vec2, vec2_as_uniform_idx, RiverKind, SimChunk, WorldSim},
|
||||
sim::{
|
||||
local_cells, uniform_idx_as_vec2, vec2_as_uniform_idx, Cave, Path, RiverKind, SimChunk,
|
||||
WorldSim,
|
||||
},
|
||||
util::Sampler,
|
||||
CONFIG,
|
||||
Index, CONFIG,
|
||||
};
|
||||
use common::{terrain::TerrainChunkSize, vol::RectVolSize};
|
||||
use noise::NoiseFn;
|
||||
@ -11,7 +14,7 @@ use roots::find_roots_cubic;
|
||||
use std::{
|
||||
cmp::Reverse,
|
||||
f32, f64,
|
||||
ops::{Add, Div, Mul, Neg, Sub},
|
||||
ops::{Add, Div, Mul, Sub},
|
||||
};
|
||||
use tracing::error;
|
||||
use vek::*;
|
||||
@ -165,16 +168,18 @@ pub fn quadratic_nearest_point(
|
||||
min_root
|
||||
}
|
||||
|
||||
impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
type Index = Vec2<i32>;
|
||||
impl<'a, 'b> Sampler<'b> for ColumnGen<'a>
|
||||
where
|
||||
'a: 'b,
|
||||
{
|
||||
type Index = (Vec2<i32>, &'b Index);
|
||||
type Sample = Option<ColumnSample<'a>>;
|
||||
|
||||
#[allow(clippy::float_cmp)] // TODO: Pending review in #587
|
||||
#[allow(clippy::if_same_then_else)] // TODO: Pending review in #587
|
||||
#[allow(clippy::nonminimal_bool)] // TODO: Pending review in #587
|
||||
#[allow(clippy::single_match)] // TODO: Pending review in #587
|
||||
#[allow(clippy::bind_instead_of_map)] // TODO: Pending review in #587
|
||||
fn get(&self, wpos: Vec2<i32>) -> Option<ColumnSample<'a>> {
|
||||
fn get(&self, (wpos, index): Self::Index) -> Option<ColumnSample<'a>> {
|
||||
let wposf = wpos.map(|e| e as f64);
|
||||
let chunk_pos = wpos.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| e / sz as i32);
|
||||
|
||||
@ -193,6 +198,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
let tree_density = sim.get_interpolated(wpos, |chunk| chunk.tree_density)?;
|
||||
let spawn_rate = sim.get_interpolated(wpos, |chunk| chunk.spawn_rate)?;
|
||||
let alt = sim.get_interpolated_monotone(wpos, |chunk| chunk.alt)?;
|
||||
let surface_veg = sim.get_interpolated_monotone(wpos, |chunk| chunk.surface_veg)?;
|
||||
let chunk_warp_factor = sim.get_interpolated_monotone(wpos, |chunk| chunk.warp_factor)?;
|
||||
let sim_chunk = sim.get(chunk_pos)?;
|
||||
let neighbor_coef = TerrainChunkSize::RECT_SIZE.map(|e| e as f64);
|
||||
@ -608,7 +614,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
.unwrap_or_else(|| {
|
||||
max_border_river
|
||||
.river_kind
|
||||
.and_then(|river_kind| {
|
||||
.map(|river_kind| {
|
||||
match river_kind {
|
||||
RiverKind::Ocean => {
|
||||
let (
|
||||
@ -642,7 +648,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
let river_dist = wposf.distance(river_pos);
|
||||
let _river_height_factor =
|
||||
river_dist / (river_width * 0.5);
|
||||
return Some((
|
||||
return (
|
||||
true,
|
||||
Some((river_dist - river_width * 0.5) as f32),
|
||||
alt_for_river
|
||||
@ -650,10 +656,10 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
lake_water_alt - river_gouge,
|
||||
alt_for_river.max(lake_water_alt),
|
||||
0.0,
|
||||
));
|
||||
);
|
||||
}
|
||||
|
||||
Some((
|
||||
(
|
||||
river_scale_factor <= 1.0,
|
||||
Some(
|
||||
(wposf.distance(river_pos) - river_width * 0.5)
|
||||
@ -663,7 +669,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
downhill_water_alt,
|
||||
alt_for_river,
|
||||
river_scale_factor as f32,
|
||||
))
|
||||
)
|
||||
},
|
||||
RiverKind::Lake { .. } => {
|
||||
let lake_dist = (max_border_river_pos.map(|e| e as f64)
|
||||
@ -685,7 +691,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
|| in_bounds
|
||||
{
|
||||
let gouge_factor = 0.0;
|
||||
return Some((
|
||||
return (
|
||||
in_bounds
|
||||
|| downhill_water_alt
|
||||
.max(river_chunk.water_alt)
|
||||
@ -697,16 +703,16 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
alt_for_river,
|
||||
river_scale_factor as f32
|
||||
* (1.0 - gouge_factor),
|
||||
));
|
||||
);
|
||||
} else {
|
||||
return Some((
|
||||
return (
|
||||
false,
|
||||
Some(lake_dist as f32),
|
||||
alt_for_river,
|
||||
downhill_water_alt,
|
||||
alt_for_river,
|
||||
river_scale_factor as f32,
|
||||
));
|
||||
);
|
||||
};
|
||||
|
||||
let lake_dist = dist.y;
|
||||
@ -718,7 +724,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
river_t as f32,
|
||||
);
|
||||
if dist == Vec2::zero() {
|
||||
return Some((
|
||||
return (
|
||||
true,
|
||||
Some(lake_dist as f32),
|
||||
alt_for_river
|
||||
@ -726,7 +732,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
lake_water_alt - river_gouge,
|
||||
alt_for_river.max(lake_water_alt),
|
||||
0.0,
|
||||
));
|
||||
);
|
||||
}
|
||||
if lake_dist <= TerrainChunkSize::RECT_SIZE.x as f64 * 1.0
|
||||
|| in_bounds
|
||||
@ -739,7 +745,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
let in_bounds_ = lake_dist
|
||||
<= TerrainChunkSize::RECT_SIZE.x as f64 * 0.5;
|
||||
if gouge_factor == 1.0 {
|
||||
return Some((
|
||||
return (
|
||||
true,
|
||||
Some(lake_dist as f32),
|
||||
alt.min(lake_water_alt - 1.0 - river_gouge),
|
||||
@ -747,9 +753,9 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
- river_gouge,
|
||||
alt.max(lake_water_alt),
|
||||
0.0,
|
||||
));
|
||||
);
|
||||
} else {
|
||||
return Some((
|
||||
return (
|
||||
true,
|
||||
None,
|
||||
alt_for_river,
|
||||
@ -761,17 +767,17 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
alt_for_river,
|
||||
river_scale_factor as f32
|
||||
* (1.0 - gouge_factor),
|
||||
));
|
||||
);
|
||||
}
|
||||
}
|
||||
Some((
|
||||
(
|
||||
river_scale_factor <= 1.0,
|
||||
Some(lake_dist as f32),
|
||||
alt_for_river,
|
||||
downhill_water_alt,
|
||||
alt_for_river,
|
||||
river_scale_factor as f32,
|
||||
))
|
||||
)
|
||||
},
|
||||
RiverKind::River { .. } => {
|
||||
let (_, _, river_width, (_, (river_pos, _), _)) =
|
||||
@ -779,14 +785,14 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
let river_dist = wposf.distance(river_pos);
|
||||
|
||||
// FIXME: Make water altitude accurate.
|
||||
Some((
|
||||
(
|
||||
river_scale_factor <= 1.0,
|
||||
Some((river_dist - river_width * 0.5) as f32),
|
||||
alt_for_river,
|
||||
downhill_water_alt,
|
||||
alt_for_river,
|
||||
river_scale_factor as f32,
|
||||
))
|
||||
)
|
||||
},
|
||||
}
|
||||
})
|
||||
@ -857,8 +863,8 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
let cold_stone = Rgb::new(0.57, 0.67, 0.8);
|
||||
let hot_stone = Rgb::new(0.07, 0.07, 0.06);
|
||||
let warm_stone = Rgb::new(0.77, 0.77, 0.64);
|
||||
let beach_sand = Rgb::new(0.9, 0.82, 0.6);
|
||||
let desert_sand = Rgb::new(0.95, 0.75, 0.5);
|
||||
let beach_sand = Rgb::new(0.8, 0.75, 0.5);
|
||||
let desert_sand = Rgb::new(0.7, 0.4, 0.25);
|
||||
let snow = Rgb::new(0.8, 0.85, 1.0);
|
||||
|
||||
let stone_col = Rgb::new(195, 187, 201);
|
||||
@ -1031,34 +1037,6 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
(alt, ground, sub_surface_color)
|
||||
};
|
||||
|
||||
// Caves
|
||||
let cave_at = |wposf: Vec2<f64>| {
|
||||
(sim.gen_ctx.cave_0_nz.get(
|
||||
Vec3::new(wposf.x, wposf.y, alt as f64 * 8.0)
|
||||
.div(800.0)
|
||||
.into_array(),
|
||||
) as f32)
|
||||
.powf(2.0)
|
||||
.neg()
|
||||
.add(1.0)
|
||||
.mul((1.32 - chaos).min(1.0))
|
||||
};
|
||||
let cave_xy = cave_at(wposf);
|
||||
let cave_alt = alt - 24.0
|
||||
+ (sim
|
||||
.gen_ctx
|
||||
.cave_1_nz
|
||||
.get(Vec2::new(wposf.x, wposf.y).div(48.0).into_array()) as f32)
|
||||
* 8.0
|
||||
+ (sim
|
||||
.gen_ctx
|
||||
.cave_1_nz
|
||||
.get(Vec2::new(wposf.x, wposf.y).div(500.0).into_array()) as f32)
|
||||
.add(1.0)
|
||||
.mul(0.5)
|
||||
.powf(15.0)
|
||||
.mul(150.0);
|
||||
|
||||
let near_ocean = max_river.and_then(|(_, _, river_data, _)| {
|
||||
if (river_data.is_lake() || river_data.river_kind == Some(RiverKind::Ocean))
|
||||
&& ((alt <= water_level.max(CONFIG.sea_level + 5.0) && !is_cliffs) || !near_cliffs)
|
||||
@ -1076,6 +1054,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
};
|
||||
|
||||
let path = sim.get_nearest_path(wpos);
|
||||
let cave = sim.get_nearest_cave(wpos);
|
||||
|
||||
Some(ColumnSample {
|
||||
alt,
|
||||
@ -1085,11 +1064,15 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
water_level,
|
||||
warp_factor,
|
||||
surface_color: Rgb::lerp(
|
||||
Rgb::lerp(cliff, sand, alt.sub(basement).mul(0.25)),
|
||||
// Land
|
||||
ground,
|
||||
// Beach
|
||||
((ocean_level - 1.0) / 2.0).max(0.0),
|
||||
sub_surface_color,
|
||||
Rgb::lerp(
|
||||
Rgb::lerp(cliff, sand, alt.sub(basement).mul(0.25)),
|
||||
// Land
|
||||
ground,
|
||||
// Beach
|
||||
((ocean_level - 1.0) / 2.0).max(0.0),
|
||||
),
|
||||
surface_veg,
|
||||
),
|
||||
sub_surface_color,
|
||||
// No growing directly on bedrock.
|
||||
@ -1098,7 +1081,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
tree_density: if sim_chunk
|
||||
.sites
|
||||
.iter()
|
||||
.all(|site| site.spawn_rules(wpos).trees)
|
||||
.all(|site| index.sites[*site].spawn_rules(wpos).trees)
|
||||
{
|
||||
Lerp::lerp(0.0, tree_density, alt.sub(2.0).sub(basement).mul(0.5))
|
||||
} else {
|
||||
@ -1106,8 +1089,6 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
},
|
||||
forest_kind: sim_chunk.forest_kind,
|
||||
close_structures: self.gen_close_structures(wpos),
|
||||
cave_xy,
|
||||
cave_alt,
|
||||
marble,
|
||||
marble_small,
|
||||
rock,
|
||||
@ -1121,6 +1102,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
stone_col,
|
||||
water_dist,
|
||||
path,
|
||||
cave,
|
||||
|
||||
chunk: sim_chunk,
|
||||
})
|
||||
@ -1140,8 +1122,6 @@ pub struct ColumnSample<'a> {
|
||||
pub tree_density: f32,
|
||||
pub forest_kind: ForestKind,
|
||||
pub close_structures: [Option<StructureData>; 9],
|
||||
pub cave_xy: f32,
|
||||
pub cave_alt: f32,
|
||||
pub marble: f32,
|
||||
pub marble_small: f32,
|
||||
pub rock: f32,
|
||||
@ -1154,7 +1134,8 @@ pub struct ColumnSample<'a> {
|
||||
pub spawn_rate: f32,
|
||||
pub stone_col: Rgb<u8>,
|
||||
pub water_dist: Option<f32>,
|
||||
pub path: Option<(f32, Vec2<f32>)>,
|
||||
pub path: Option<(f32, Vec2<f32>, Path, Vec2<f32>)>,
|
||||
pub cave: Option<(f32, Vec2<f32>, Cave, Vec2<f32>)>,
|
||||
|
||||
pub chunk: &'a SimChunk,
|
||||
}
|
||||
|
36
world/src/index.rs
Normal file
36
world/src/index.rs
Normal file
@ -0,0 +1,36 @@
|
||||
use crate::site::Site;
|
||||
use common::store::Store;
|
||||
use noise::{Seedable, SuperSimplex};
|
||||
|
||||
pub struct Index {
|
||||
pub seed: u32,
|
||||
pub time: f32,
|
||||
pub noise: Noise,
|
||||
pub sites: Store<Site>,
|
||||
}
|
||||
|
||||
impl Index {
|
||||
pub fn new(seed: u32) -> Self {
|
||||
Self {
|
||||
seed,
|
||||
time: 0.0,
|
||||
noise: Noise::new(seed),
|
||||
sites: Store::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Noise {
|
||||
pub cave_nz: SuperSimplex,
|
||||
pub scatter_nz: SuperSimplex,
|
||||
}
|
||||
|
||||
impl Noise {
|
||||
#[allow(clippy::identity_op)]
|
||||
fn new(seed: u32) -> Self {
|
||||
Self {
|
||||
cave_nz: SuperSimplex::new().set_seed(seed + 0),
|
||||
scatter_nz: SuperSimplex::new().set_seed(seed + 1),
|
||||
}
|
||||
}
|
||||
}
|
@ -1,18 +1,156 @@
|
||||
use crate::{
|
||||
column::ColumnSample,
|
||||
sim::SimChunk,
|
||||
util::{RandomField, Sampler},
|
||||
Index,
|
||||
};
|
||||
use common::{
|
||||
assets, comp,
|
||||
generation::{ChunkSupplement, EntityInfo},
|
||||
lottery::Lottery,
|
||||
terrain::{Block, BlockKind},
|
||||
vol::{BaseVol, ReadVol, RectSizedVol, Vox, WriteVol},
|
||||
};
|
||||
use std::f32;
|
||||
use noise::NoiseFn;
|
||||
use rand::prelude::*;
|
||||
use std::{
|
||||
f32,
|
||||
ops::{Mul, Sub},
|
||||
};
|
||||
use vek::*;
|
||||
|
||||
fn close(x: f32, tgt: f32, falloff: f32) -> f32 {
|
||||
(1.0 - (x - tgt).abs() / falloff).max(0.0).powf(0.5)
|
||||
}
|
||||
|
||||
pub fn apply_scatter_to<'a>(
|
||||
wpos2d: Vec2<i32>,
|
||||
mut get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>,
|
||||
vol: &mut (impl BaseVol<Vox = Block> + RectSizedVol + ReadVol + WriteVol),
|
||||
index: &Index,
|
||||
chunk: &SimChunk,
|
||||
) {
|
||||
use BlockKind::*;
|
||||
#[allow(clippy::type_complexity)]
|
||||
let scatter: &[(_, bool, fn(&SimChunk) -> (f32, Option<(f32, f32)>))] = &[
|
||||
// (density, Option<(wavelen, threshold)>)
|
||||
(BlueFlower, false, |c| {
|
||||
(
|
||||
close(c.temp, -0.3, 0.7).min(close(c.humidity, 0.6, 0.35)) * 0.05,
|
||||
Some((48.0, 0.6)),
|
||||
)
|
||||
}),
|
||||
(PinkFlower, false, |c| {
|
||||
(
|
||||
close(c.temp, 0.15, 0.5).min(close(c.humidity, 0.6, 0.35)) * 0.05,
|
||||
Some((48.0, 0.6)),
|
||||
)
|
||||
}),
|
||||
(DeadBush, false, |c| {
|
||||
(
|
||||
close(c.temp, 0.8, 0.3).min(close(c.humidity, 0.0, 0.4)) * 0.015,
|
||||
None,
|
||||
)
|
||||
}),
|
||||
(Twigs, false, |c| {
|
||||
((c.tree_density - 0.5).max(0.0) * 0.0025, None)
|
||||
}),
|
||||
(Stones, false, |c| {
|
||||
((c.rockiness - 0.5).max(0.0) * 0.005, None)
|
||||
}),
|
||||
(ShortGrass, false, |c| {
|
||||
(
|
||||
close(c.temp, 0.3, 0.4).min(close(c.humidity, 0.6, 0.35)) * 0.05,
|
||||
Some((48.0, 0.4)),
|
||||
)
|
||||
}),
|
||||
(MediumGrass, false, |c| {
|
||||
(
|
||||
close(c.temp, 0.0, 0.6).min(close(c.humidity, 0.6, 0.35)) * 0.05,
|
||||
Some((48.0, 0.2)),
|
||||
)
|
||||
}),
|
||||
(LongGrass, false, |c| {
|
||||
(
|
||||
close(c.temp, 0.4, 0.4).min(close(c.humidity, 0.8, 0.2)) * 0.05,
|
||||
Some((48.0, 0.1)),
|
||||
)
|
||||
}),
|
||||
(GrassSnow, false, |c| {
|
||||
(
|
||||
close(c.temp, -0.4, 0.4).min(close(c.rockiness, 0.0, 0.5)) * 0.1,
|
||||
Some((48.0, 0.6)),
|
||||
)
|
||||
}),
|
||||
];
|
||||
|
||||
for y in 0..vol.size_xy().y as i32 {
|
||||
for x in 0..vol.size_xy().x as i32 {
|
||||
let offs = Vec2::new(x, y);
|
||||
|
||||
let wpos2d = wpos2d + offs;
|
||||
|
||||
// Sample terrain
|
||||
let col_sample = if let Some(col_sample) = get_column(offs) {
|
||||
col_sample
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let underwater = col_sample.water_level > col_sample.alt;
|
||||
|
||||
let bk = scatter
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find_map(|(i, (bk, is_underwater, f))| {
|
||||
let (density, patch) = f(chunk);
|
||||
let is_patch = patch
|
||||
.map(|(wavelen, threshold)| {
|
||||
index.noise.scatter_nz.get(
|
||||
wpos2d
|
||||
.map(|e| e as f64 / wavelen as f64 + i as f64 * 43.0)
|
||||
.into_array(),
|
||||
) < threshold as f64
|
||||
})
|
||||
.unwrap_or(false);
|
||||
if density <= 0.0
|
||||
|| is_patch
|
||||
|| !RandomField::new(i as u32)
|
||||
.chance(Vec3::new(wpos2d.x, wpos2d.y, 0), density)
|
||||
|| underwater != *is_underwater
|
||||
{
|
||||
None
|
||||
} else {
|
||||
Some(*bk)
|
||||
}
|
||||
});
|
||||
|
||||
if let Some(bk) = bk {
|
||||
let mut z = col_sample.alt as i32 - 4;
|
||||
for _ in 0..8 {
|
||||
if vol
|
||||
.get(Vec3::new(offs.x, offs.y, z))
|
||||
.map(|b| !b.is_solid())
|
||||
.unwrap_or(true)
|
||||
{
|
||||
let _ = vol.set(
|
||||
Vec3::new(offs.x, offs.y, z),
|
||||
Block::new(bk, Rgb::broadcast(0)),
|
||||
);
|
||||
break;
|
||||
}
|
||||
z += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply_paths_to<'a>(
|
||||
wpos2d: Vec2<i32>,
|
||||
mut get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>,
|
||||
vol: &mut (impl BaseVol<Vox = Block> + RectSizedVol + ReadVol + WriteVol),
|
||||
_index: &Index,
|
||||
) {
|
||||
for y in 0..vol.size_xy().y as i32 {
|
||||
for x in 0..vol.size_xy().x as i32 {
|
||||
@ -37,7 +175,9 @@ pub fn apply_paths_to<'a>(
|
||||
})
|
||||
};
|
||||
|
||||
if let Some((path_dist, path_nearest)) = col_sample.path.filter(|(dist, _)| *dist < 5.0)
|
||||
if let Some((path_dist, path_nearest, path, _)) = col_sample
|
||||
.path
|
||||
.filter(|(dist, _, path, _)| *dist < path.width)
|
||||
{
|
||||
let inset = 0;
|
||||
|
||||
@ -75,14 +215,14 @@ pub fn apply_paths_to<'a>(
|
||||
if bridge_offset >= 2.0 && path_dist >= 3.0 || z < inset - 1 {
|
||||
Block::new(BlockKind::Normal, noisy_color(Rgb::new(80, 80, 100), 8))
|
||||
} else {
|
||||
let path_color = col_sample
|
||||
.sub_surface_color
|
||||
.map(|e| (e * 255.0 * 0.7) as u8);
|
||||
let path_color = path.surface_color(
|
||||
col_sample.sub_surface_color.map(|e| (e * 255.0) as u8),
|
||||
);
|
||||
Block::new(BlockKind::Normal, noisy_color(path_color, 8))
|
||||
},
|
||||
);
|
||||
}
|
||||
let head_space = (8 - (path_dist * 0.25).powf(6.0).round() as i32).max(1);
|
||||
let head_space = path.head_space(path_dist);
|
||||
for z in inset..inset + head_space {
|
||||
let pos = Vec3::new(offs.x, offs.y, surface_z + z);
|
||||
if vol.get(pos).unwrap().kind() != BlockKind::Water {
|
||||
@ -93,3 +233,194 @@ pub fn apply_paths_to<'a>(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply_caves_to<'a>(
|
||||
wpos2d: Vec2<i32>,
|
||||
mut get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>,
|
||||
vol: &mut (impl BaseVol<Vox = Block> + RectSizedVol + ReadVol + WriteVol),
|
||||
index: &Index,
|
||||
) {
|
||||
for y in 0..vol.size_xy().y as i32 {
|
||||
for x in 0..vol.size_xy().x as i32 {
|
||||
let offs = Vec2::new(x, y);
|
||||
|
||||
let wpos2d = wpos2d + offs;
|
||||
|
||||
// Sample terrain
|
||||
let col_sample = if let Some(col_sample) = get_column(offs) {
|
||||
col_sample
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
let surface_z = col_sample.riverless_alt.floor() as i32;
|
||||
|
||||
if let Some((cave_dist, _, cave, _)) = col_sample
|
||||
.cave
|
||||
.filter(|(dist, _, cave, _)| *dist < cave.width)
|
||||
{
|
||||
let cave_x = (cave_dist / cave.width).min(1.0);
|
||||
|
||||
// Relative units
|
||||
let cave_floor = 0.0 - 0.5 * (1.0 - cave_x.powf(2.0)).max(0.0).sqrt() * cave.width;
|
||||
let cave_height = (1.0 - cave_x.powf(2.0)).max(0.0).sqrt() * cave.width;
|
||||
|
||||
// Abs units
|
||||
let cave_base = (cave.alt + cave_floor) as i32;
|
||||
let cave_roof = (cave.alt + cave_height) as i32;
|
||||
|
||||
for z in cave_base..cave_roof {
|
||||
if cave_x < 0.95
|
||||
|| index.noise.cave_nz.get(
|
||||
Vec3::new(wpos2d.x, wpos2d.y, z)
|
||||
.map(|e| e as f64 * 0.15)
|
||||
.into_array(),
|
||||
) < 0.0
|
||||
{
|
||||
let _ = vol.set(Vec3::new(offs.x, offs.y, z), Block::empty());
|
||||
}
|
||||
}
|
||||
|
||||
// Stalagtites
|
||||
let stalagtites = index
|
||||
.noise
|
||||
.cave_nz
|
||||
.get(wpos2d.map(|e| e as f64 * 0.125).into_array())
|
||||
.sub(0.5)
|
||||
.max(0.0)
|
||||
.mul(
|
||||
(col_sample.alt - cave_roof as f32 - 5.0)
|
||||
.mul(0.15)
|
||||
.clamped(0.0, 1.0) as f64,
|
||||
)
|
||||
.mul(45.0) as i32;
|
||||
|
||||
for z in cave_roof - stalagtites..cave_roof {
|
||||
let _ = vol.set(
|
||||
Vec3::new(offs.x, offs.y, z),
|
||||
Block::new(BlockKind::Rock, Rgb::broadcast(200)),
|
||||
);
|
||||
}
|
||||
|
||||
let cave_depth = (col_sample.alt - cave.alt).max(0.0);
|
||||
let difficulty = cave_depth / 100.0;
|
||||
|
||||
// Scatter things in caves
|
||||
if RandomField::new(index.seed).chance(wpos2d.into(), 0.002 * difficulty.powf(1.5))
|
||||
&& cave_base < surface_z as i32 - 25
|
||||
{
|
||||
let kind = *assets::load_expect::<Lottery<BlockKind>>("common.cave_scatter")
|
||||
.choose_seeded(RandomField::new(index.seed + 1).get(wpos2d.into()));
|
||||
let _ = vol.set(
|
||||
Vec3::new(offs.x, offs.y, cave_base),
|
||||
Block::new(kind, Rgb::zero()),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply_caves_supplement<'a>(
|
||||
rng: &mut impl Rng,
|
||||
wpos2d: Vec2<i32>,
|
||||
mut get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>,
|
||||
vol: &(impl BaseVol<Vox = Block> + RectSizedVol + ReadVol + WriteVol),
|
||||
index: &Index,
|
||||
supplement: &mut ChunkSupplement,
|
||||
) {
|
||||
for y in 0..vol.size_xy().y as i32 {
|
||||
for x in 0..vol.size_xy().x as i32 {
|
||||
let offs = Vec2::new(x, y);
|
||||
|
||||
let wpos2d = wpos2d + offs;
|
||||
|
||||
// Sample terrain
|
||||
let col_sample = if let Some(col_sample) = get_column(offs) {
|
||||
col_sample
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
let surface_z = col_sample.riverless_alt.floor() as i32;
|
||||
|
||||
if let Some((cave_dist, _, cave, _)) = col_sample
|
||||
.cave
|
||||
.filter(|(dist, _, cave, _)| *dist < cave.width)
|
||||
{
|
||||
let cave_x = (cave_dist / cave.width).min(1.0);
|
||||
|
||||
// Relative units
|
||||
let cave_floor = 0.0 - 0.5 * (1.0 - cave_x.powf(2.0)).max(0.0).sqrt() * cave.width;
|
||||
|
||||
// Abs units
|
||||
let cave_base = (cave.alt + cave_floor) as i32;
|
||||
|
||||
let cave_depth = (col_sample.alt - cave.alt).max(0.0);
|
||||
let difficulty = cave_depth / 200.0;
|
||||
|
||||
// Scatter things in caves
|
||||
if RandomField::new(index.seed).chance(wpos2d.into(), 0.00005 * difficulty)
|
||||
&& cave_base < surface_z as i32 - 40
|
||||
{
|
||||
let entity = EntityInfo::at(Vec3::new(
|
||||
wpos2d.x as f32,
|
||||
wpos2d.y as f32,
|
||||
cave_base as f32,
|
||||
))
|
||||
.with_alignment(comp::Alignment::Enemy)
|
||||
.with_body(match rng.gen_range(0, 6) {
|
||||
0 => {
|
||||
let species = match rng.gen_range(0, 2) {
|
||||
0 => comp::quadruped_small::Species::Truffler,
|
||||
_ => comp::quadruped_small::Species::Hyena,
|
||||
};
|
||||
comp::quadruped_small::Body::random_with(rng, &species).into()
|
||||
},
|
||||
1 => {
|
||||
let species = match rng.gen_range(0, 3) {
|
||||
0 => comp::quadruped_medium::Species::Tarasque,
|
||||
1 => comp::quadruped_medium::Species::Frostfang,
|
||||
_ => comp::quadruped_medium::Species::Bonerattler,
|
||||
};
|
||||
comp::quadruped_medium::Body::random_with(rng, &species).into()
|
||||
},
|
||||
2 => {
|
||||
let species = match rng.gen_range(0, 3) {
|
||||
0 => comp::quadruped_low::Species::Maneater,
|
||||
1 => comp::quadruped_low::Species::Rocksnapper,
|
||||
_ => comp::quadruped_low::Species::Salamander,
|
||||
};
|
||||
comp::quadruped_low::Body::random_with(rng, &species).into()
|
||||
},
|
||||
3 => {
|
||||
let species = match rng.gen_range(0, 3) {
|
||||
0 => comp::critter::Species::Fungome,
|
||||
1 => comp::critter::Species::Axolotl,
|
||||
_ => comp::critter::Species::Rat,
|
||||
};
|
||||
comp::critter::Body::random_with(rng, &species).into()
|
||||
},
|
||||
4 => {
|
||||
#[allow(clippy::match_single_binding)]
|
||||
let species = match rng.gen_range(0, 1) {
|
||||
_ => comp::golem::Species::StoneGolem,
|
||||
};
|
||||
comp::golem::Body::random_with(rng, &species).into()
|
||||
},
|
||||
_ => {
|
||||
let species = match rng.gen_range(0, 4) {
|
||||
0 => comp::biped_large::Species::Ogre,
|
||||
1 => comp::biped_large::Species::Cyclops,
|
||||
2 => comp::biped_large::Species::Wendigo,
|
||||
_ => comp::biped_large::Species::Troll,
|
||||
};
|
||||
comp::biped_large::Body::random_with(rng, &species).into()
|
||||
},
|
||||
})
|
||||
.with_automatic_name();
|
||||
|
||||
supplement.add_entity(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,8 +8,10 @@ mod block;
|
||||
pub mod civ;
|
||||
mod column;
|
||||
pub mod config;
|
||||
pub mod index;
|
||||
pub mod layer;
|
||||
pub mod sim;
|
||||
pub mod sim2;
|
||||
pub mod site;
|
||||
pub mod util;
|
||||
|
||||
@ -19,6 +21,7 @@ pub use crate::config::CONFIG;
|
||||
use crate::{
|
||||
block::BlockGen,
|
||||
column::{ColumnGen, ColumnSample},
|
||||
index::Index,
|
||||
util::{Grid, Sampler},
|
||||
};
|
||||
use common::{
|
||||
@ -39,26 +42,35 @@ pub enum Error {
|
||||
pub struct World {
|
||||
sim: sim::WorldSim,
|
||||
civs: civ::Civs,
|
||||
index: Index,
|
||||
}
|
||||
|
||||
impl World {
|
||||
pub fn generate(seed: u32, opts: sim::WorldOpts) -> Self {
|
||||
let mut sim = sim::WorldSim::generate(seed, opts);
|
||||
let civs = civ::Civs::generate(seed, &mut sim);
|
||||
Self { sim, civs }
|
||||
let mut index = Index::new(seed);
|
||||
let civs = civ::Civs::generate(seed, &mut sim, &mut index);
|
||||
|
||||
sim2::simulate(&mut index, &mut sim);
|
||||
|
||||
Self { sim, civs, index }
|
||||
}
|
||||
|
||||
pub fn sim(&self) -> &sim::WorldSim { &self.sim }
|
||||
|
||||
pub fn civs(&self) -> &civ::Civs { &self.civs }
|
||||
|
||||
pub fn index(&self) -> &Index { &self.index }
|
||||
|
||||
pub fn tick(&self, _dt: Duration) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
pub fn get_map_data(&self) -> Vec<u32> { self.sim.get_map(&self.index) }
|
||||
|
||||
pub fn sample_columns(
|
||||
&self,
|
||||
) -> impl Sampler<Index = Vec2<i32>, Sample = Option<ColumnSample>> + '_ {
|
||||
) -> impl Sampler<Index = (Vec2<i32>, &Index), Sample = Option<ColumnSample>> + '_ {
|
||||
ColumnGen::new(&self.sim)
|
||||
}
|
||||
|
||||
@ -77,7 +89,7 @@ impl World {
|
||||
let grid_border = 4;
|
||||
let zcache_grid = Grid::populate_from(
|
||||
TerrainChunkSize::RECT_SIZE.map(|e| e as i32) + grid_border * 2,
|
||||
|offs| sampler.get_z_cache(chunk_wpos2d - grid_border + offs),
|
||||
|offs| sampler.get_z_cache(chunk_wpos2d - grid_border + offs, &self.index),
|
||||
);
|
||||
|
||||
let air = Block::empty();
|
||||
@ -132,7 +144,8 @@ impl World {
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
let (min_z, only_structures_min_z, max_z) = z_cache.get_z_limits(&mut sampler);
|
||||
let (min_z, only_structures_min_z, max_z) =
|
||||
z_cache.get_z_limits(&mut sampler, &self.index);
|
||||
|
||||
(base_z..min_z as i32).for_each(|z| {
|
||||
let _ = chunk.set(Vec3::new(x, y, z), stone);
|
||||
@ -144,7 +157,7 @@ impl World {
|
||||
let only_structures = lpos.z >= only_structures_min_z as i32;
|
||||
|
||||
if let Some(block) =
|
||||
sampler.get_with_z_cache(wpos, Some(&z_cache), only_structures)
|
||||
sampler.get_with_z_cache(wpos, Some(&z_cache), only_structures, &self.index)
|
||||
{
|
||||
let _ = chunk.set(lpos, block);
|
||||
}
|
||||
@ -162,14 +175,15 @@ impl World {
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
// Apply site generation
|
||||
sim_chunk
|
||||
.sites
|
||||
.iter()
|
||||
.for_each(|site| site.apply_to(chunk_wpos2d, sample_get, &mut chunk));
|
||||
// Apply layers (paths, caves, etc.)
|
||||
layer::apply_scatter_to(chunk_wpos2d, sample_get, &mut chunk, &self.index, sim_chunk);
|
||||
layer::apply_paths_to(chunk_wpos2d, sample_get, &mut chunk, &self.index);
|
||||
layer::apply_caves_to(chunk_wpos2d, sample_get, &mut chunk, &self.index);
|
||||
|
||||
// Apply paths
|
||||
layer::apply_paths_to(chunk_wpos2d, sample_get, &mut chunk);
|
||||
// Apply site generation
|
||||
sim_chunk.sites.iter().for_each(|site| {
|
||||
self.index.sites[*site].apply_to(chunk_wpos2d, sample_get, &mut chunk)
|
||||
});
|
||||
|
||||
let gen_entity_pos = || {
|
||||
let lpos2d = TerrainChunkSize::RECT_SIZE
|
||||
@ -215,9 +229,24 @@ impl World {
|
||||
supplement.add_entity(EntityInfo::at(gen_entity_pos()).into_waypoint());
|
||||
}
|
||||
|
||||
// Apply layer supplement
|
||||
layer::apply_caves_supplement(
|
||||
&mut rng,
|
||||
chunk_wpos2d,
|
||||
sample_get,
|
||||
&chunk,
|
||||
&self.index,
|
||||
&mut supplement,
|
||||
);
|
||||
|
||||
// Apply site supplementary information
|
||||
sim_chunk.sites.iter().for_each(|site| {
|
||||
site.apply_supplement(&mut rng, chunk_wpos2d, sample_get, &mut supplement)
|
||||
self.index.sites[*site].apply_supplement(
|
||||
&mut rng,
|
||||
chunk_wpos2d,
|
||||
sample_get,
|
||||
&mut supplement,
|
||||
)
|
||||
});
|
||||
|
||||
Ok((chunk, supplement))
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
sim::{RiverKind, WorldSim, WORLD_SIZE},
|
||||
CONFIG,
|
||||
Index, CONFIG,
|
||||
};
|
||||
use common::{terrain::TerrainChunkSize, vol::RectVolSize};
|
||||
use std::{f32, f64};
|
||||
@ -114,6 +114,7 @@ impl MapConfig {
|
||||
pub fn generate(
|
||||
&self,
|
||||
sampler: &WorldSim,
|
||||
index: &Index,
|
||||
mut write_pixel: impl FnMut(Vec2<usize>, (u8, u8, u8, u8)),
|
||||
) -> MapDebug {
|
||||
let MapConfig {
|
||||
@ -156,6 +157,7 @@ impl MapConfig {
|
||||
downhill,
|
||||
river_kind,
|
||||
is_path,
|
||||
is_cave,
|
||||
near_site,
|
||||
) = sampler
|
||||
.get(pos)
|
||||
@ -168,9 +170,11 @@ impl MapConfig {
|
||||
sample.temp,
|
||||
sample.downhill,
|
||||
sample.river.river_kind,
|
||||
sample.path.is_path(),
|
||||
sample.path.0.is_way(),
|
||||
sample.cave.0.is_way(),
|
||||
sample.sites.iter().any(|site| {
|
||||
site.get_origin()
|
||||
index.sites[*site]
|
||||
.get_origin()
|
||||
.distance_squared(pos * TerrainChunkSize::RECT_SIZE.x as i32)
|
||||
< 64i32.pow(2)
|
||||
}),
|
||||
@ -186,6 +190,7 @@ impl MapConfig {
|
||||
None,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
));
|
||||
let humidity = humidity.min(1.0).max(0.0);
|
||||
let temperature = temperature.min(1.0).max(-1.0) * 0.5 + 0.5;
|
||||
@ -315,6 +320,8 @@ impl MapConfig {
|
||||
(0x57, 0x39, 0x33, 0xFF)
|
||||
} else if is_path {
|
||||
(0x37, 0x29, 0x23, 0xFF)
|
||||
} else if is_cave {
|
||||
(0x37, 0x37, 0x37, 0xFF)
|
||||
} else {
|
||||
rgba
|
||||
};
|
||||
|
@ -2,8 +2,8 @@ mod diffusion;
|
||||
mod erosion;
|
||||
mod location;
|
||||
mod map;
|
||||
mod path;
|
||||
mod util;
|
||||
mod way;
|
||||
|
||||
// Reexports
|
||||
use self::erosion::Compute;
|
||||
@ -15,12 +15,12 @@ pub use self::{
|
||||
},
|
||||
location::Location,
|
||||
map::{MapConfig, MapDebug},
|
||||
path::PathData,
|
||||
util::{
|
||||
cdf_irwin_hall, downhill, get_oceans, local_cells, map_edge_factor, neighbors,
|
||||
uniform_idx_as_vec2, uniform_noise, uphill, vec2_as_uniform_idx, InverseCdf, ScaleBias,
|
||||
NEIGHBOR_DELTA,
|
||||
},
|
||||
way::{Cave, Path, Way},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
@ -28,7 +28,7 @@ use crate::{
|
||||
civ::Place,
|
||||
site::Site,
|
||||
util::{seed_expan, FastNoise, RandomField, StructureGen2d, LOCALITY, NEIGHBORS},
|
||||
CONFIG,
|
||||
Index, CONFIG,
|
||||
};
|
||||
use common::{
|
||||
assets,
|
||||
@ -1305,14 +1305,14 @@ impl WorldSim {
|
||||
|
||||
/// Draw a map of the world based on chunk information. Returns a buffer of
|
||||
/// u32s.
|
||||
pub fn get_map(&self) -> Vec<u32> {
|
||||
pub fn get_map(&self, index: &Index) -> Vec<u32> {
|
||||
let mut v = vec![0u32; WORLD_SIZE.x * WORLD_SIZE.y];
|
||||
// TODO: Parallelize again.
|
||||
MapConfig {
|
||||
gain: self.max_height,
|
||||
..MapConfig::default()
|
||||
}
|
||||
.generate(&self, |pos, (r, g, b, a)| {
|
||||
.generate(&self, index, |pos, (r, g, b, a)| {
|
||||
v[pos.y * WORLD_SIZE.x + pos.x] = u32::from_le_bytes([r, g, b, a]);
|
||||
});
|
||||
v
|
||||
@ -1699,7 +1699,14 @@ impl WorldSim {
|
||||
Some(z0 + z1 + z2 + z3)
|
||||
}
|
||||
|
||||
pub fn get_nearest_path(&self, wpos: Vec2<i32>) -> Option<(f32, Vec2<f32>)> {
|
||||
/// Return the distance to the nearest way in blocks, along with the
|
||||
/// closest point on the way, the way metadata, and the tangent vector
|
||||
/// of that way.
|
||||
pub fn get_nearest_way<M: Clone + Lerp<Output = M>>(
|
||||
&self,
|
||||
wpos: Vec2<i32>,
|
||||
get_way: impl Fn(&SimChunk) -> Option<(Way, M)>,
|
||||
) -> Option<(f32, Vec2<f32>, M, Vec2<f32>)> {
|
||||
let chunk_pos = wpos.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| {
|
||||
e.div_euclid(sz as i32)
|
||||
});
|
||||
@ -1709,32 +1716,35 @@ impl WorldSim {
|
||||
})
|
||||
};
|
||||
|
||||
let get_way = &get_way;
|
||||
LOCALITY
|
||||
.iter()
|
||||
.filter_map(|ctrl| {
|
||||
let chunk = self.get(chunk_pos + *ctrl)?;
|
||||
let ctrl_pos =
|
||||
get_chunk_centre(chunk_pos + *ctrl).map(|e| e as f32) + chunk.path.offset;
|
||||
let (way, meta) = get_way(self.get(chunk_pos + *ctrl)?)?;
|
||||
let ctrl_pos = get_chunk_centre(chunk_pos + *ctrl).map(|e| e as f32)
|
||||
+ way.offset.map(|e| e as f32);
|
||||
|
||||
let chunk_connections = chunk.path.neighbors.count_ones();
|
||||
let chunk_connections = way.neighbors.count_ones();
|
||||
if chunk_connections == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let (start_pos, _start_idx) = if chunk_connections != 2 {
|
||||
(ctrl_pos, None)
|
||||
let (start_pos, _start_idx, start_meta) = if chunk_connections != 2 {
|
||||
(ctrl_pos, None, meta.clone())
|
||||
} else {
|
||||
let (start_idx, start_rpos) = NEIGHBORS
|
||||
.iter()
|
||||
.copied()
|
||||
.enumerate()
|
||||
.find(|(i, _)| chunk.path.neighbors & (1 << *i as u8) != 0)
|
||||
.find(|(i, _)| way.neighbors & (1 << *i as u8) != 0)
|
||||
.unwrap();
|
||||
let start_pos_chunk = chunk_pos + *ctrl + start_rpos;
|
||||
let (start_way, start_meta) = get_way(self.get(start_pos_chunk)?)?;
|
||||
(
|
||||
get_chunk_centre(start_pos_chunk).map(|e| e as f32)
|
||||
+ self.get(start_pos_chunk)?.path.offset,
|
||||
+ start_way.offset.map(|e| e as f32),
|
||||
Some(start_idx),
|
||||
start_meta,
|
||||
)
|
||||
};
|
||||
|
||||
@ -1742,11 +1752,12 @@ impl WorldSim {
|
||||
NEIGHBORS
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(move |(i, _)| chunk.path.neighbors & (1 << *i as u8) != 0)
|
||||
.filter(move |(i, _)| way.neighbors & (1 << *i as u8) != 0)
|
||||
.filter_map(move |(_, end_rpos)| {
|
||||
let end_pos_chunk = chunk_pos + *ctrl + end_rpos;
|
||||
let (end_way, end_meta) = get_way(self.get(end_pos_chunk)?)?;
|
||||
let end_pos = get_chunk_centre(end_pos_chunk).map(|e| e as f32)
|
||||
+ self.get(end_pos_chunk)?.path.offset;
|
||||
+ end_way.offset.map(|e| e as f32);
|
||||
|
||||
let bez = QuadraticBezier2 {
|
||||
start: (start_pos + ctrl_pos) / 2.0,
|
||||
@ -1759,13 +1770,28 @@ impl WorldSim {
|
||||
.clamped(0.0, 1.0);
|
||||
let pos = bez.evaluate(nearest_interval);
|
||||
let dist_sqrd = pos.distance_squared(wpos.map(|e| e as f32));
|
||||
Some((dist_sqrd, pos))
|
||||
let meta = if nearest_interval < 0.5 {
|
||||
Lerp::lerp(start_meta.clone(), meta.clone(), 0.5 + nearest_interval)
|
||||
} else {
|
||||
Lerp::lerp(meta.clone(), end_meta, nearest_interval - 0.5)
|
||||
};
|
||||
Some((dist_sqrd, pos, meta, move || {
|
||||
bez.evaluate_derivative(nearest_interval).normalized()
|
||||
}))
|
||||
}),
|
||||
)
|
||||
})
|
||||
.flatten()
|
||||
.min_by_key(|(dist_sqrd, _)| (dist_sqrd * 1024.0) as i32)
|
||||
.map(|(dist, pos)| (dist.sqrt(), pos))
|
||||
.min_by_key(|(dist_sqrd, _, _, _)| (dist_sqrd * 1024.0) as i32)
|
||||
.map(|(dist, pos, meta, calc_tangent)| (dist.sqrt(), pos, meta, calc_tangent()))
|
||||
}
|
||||
|
||||
pub fn get_nearest_path(&self, wpos: Vec2<i32>) -> Option<(f32, Vec2<f32>, Path, Vec2<f32>)> {
|
||||
self.get_nearest_way(wpos, |chunk| Some(chunk.path))
|
||||
}
|
||||
|
||||
pub fn get_nearest_cave(&self, wpos: Vec2<i32>) -> Option<(f32, Vec2<f32>, Cave, Vec2<f32>)> {
|
||||
self.get_nearest_way(wpos, |chunk| Some(chunk.cave))
|
||||
}
|
||||
}
|
||||
|
||||
@ -1787,10 +1813,14 @@ pub struct SimChunk {
|
||||
pub spawn_rate: f32,
|
||||
pub river: RiverData,
|
||||
pub warp_factor: f32,
|
||||
pub surface_veg: f32,
|
||||
|
||||
pub sites: Vec<Site>,
|
||||
pub sites: Vec<Id<Site>>,
|
||||
pub place: Option<Id<Place>>,
|
||||
pub path: PathData,
|
||||
|
||||
pub path: (Way, Path),
|
||||
pub cave: (Way, Cave),
|
||||
|
||||
pub contains_waypoint: bool,
|
||||
}
|
||||
|
||||
@ -2022,10 +2052,12 @@ impl SimChunk {
|
||||
spawn_rate: 1.0,
|
||||
river,
|
||||
warp_factor: 1.0,
|
||||
surface_veg: 1.0,
|
||||
|
||||
sites: Vec::new(),
|
||||
place: None,
|
||||
path: PathData::default(),
|
||||
path: Default::default(),
|
||||
cave: Default::default(),
|
||||
contains_waypoint: false,
|
||||
}
|
||||
}
|
||||
|
@ -1,21 +0,0 @@
|
||||
use vek::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PathData {
|
||||
pub offset: Vec2<f32>, /* Offset from centre of chunk: must not be more than half chunk
|
||||
* width in any direction */
|
||||
pub neighbors: u8, // One bit for each neighbor
|
||||
}
|
||||
|
||||
impl PathData {
|
||||
pub fn is_path(&self) -> bool { self.neighbors != 0 }
|
||||
}
|
||||
|
||||
impl Default for PathData {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
offset: Vec2::zero(),
|
||||
neighbors: 0,
|
||||
}
|
||||
}
|
||||
}
|
72
world/src/sim/way.rs
Normal file
72
world/src/sim/way.rs
Normal file
@ -0,0 +1,72 @@
|
||||
use vek::*;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
pub struct Way {
|
||||
/// Offset from chunk center in blocks (no more than half chunk width)
|
||||
pub offset: Vec2<i8>,
|
||||
/// Neighbor connections, one bit each
|
||||
pub neighbors: u8,
|
||||
}
|
||||
|
||||
impl Way {
|
||||
pub fn is_way(&self) -> bool { self.neighbors != 0 }
|
||||
|
||||
pub fn clear(&mut self) { self.neighbors = 0; }
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct Path {
|
||||
pub width: f32, // Actually radius
|
||||
}
|
||||
|
||||
impl Default for Path {
|
||||
fn default() -> Self { Self { width: 5.0 } }
|
||||
}
|
||||
|
||||
impl Lerp for Path {
|
||||
type Output = Self;
|
||||
|
||||
fn lerp_unclamped(from: Self, to: Self, factor: f32) -> Self::Output {
|
||||
Self {
|
||||
width: Lerp::lerp(from.width, to.width, factor),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Path {
|
||||
/// Return the number of blocks of headspace required at the given path
|
||||
/// distance
|
||||
/// TODO: make this generic over width
|
||||
pub fn head_space(&self, dist: f32) -> i32 {
|
||||
(8 - (dist * 0.25).powf(6.0).round() as i32).max(1)
|
||||
}
|
||||
|
||||
/// Get the surface colour of a path given the surrounding surface color
|
||||
pub fn surface_color(&self, col: Rgb<u8>) -> Rgb<u8> { col.map(|e| (e as f32 * 0.7) as u8) }
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct Cave {
|
||||
pub width: f32, // Actually radius
|
||||
pub alt: f32, // Actually radius
|
||||
}
|
||||
|
||||
impl Default for Cave {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
width: 32.0,
|
||||
alt: 0.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Lerp for Cave {
|
||||
type Output = Self;
|
||||
|
||||
fn lerp_unclamped(from: Self, to: Self, factor: f32) -> Self::Output {
|
||||
Self {
|
||||
width: Lerp::lerp(from.width, to.width, factor),
|
||||
alt: Lerp::lerp(from.alt, to.alt, factor),
|
||||
}
|
||||
}
|
||||
}
|
286
world/src/sim2/mod.rs
Normal file
286
world/src/sim2/mod.rs
Normal file
@ -0,0 +1,286 @@
|
||||
use crate::{
|
||||
sim::WorldSim,
|
||||
site::{
|
||||
economy::{Good, Labor},
|
||||
Site,
|
||||
},
|
||||
util::MapVec,
|
||||
Index,
|
||||
};
|
||||
use common::store::Id;
|
||||
use tracing::debug;
|
||||
|
||||
const MONTH: f32 = 30.0;
|
||||
const YEAR: f32 = 12.0 * MONTH;
|
||||
const TICK_PERIOD: f32 = 3.0 * MONTH; // 3 months
|
||||
const HISTORY_DAYS: f32 = 500.0 * YEAR; // 500 years
|
||||
|
||||
const GENERATE_CSV: bool = false;
|
||||
|
||||
pub fn simulate(index: &mut Index, world: &mut WorldSim) {
|
||||
use std::io::Write;
|
||||
let mut f = if GENERATE_CSV {
|
||||
let mut f = std::fs::File::create("economy.csv").unwrap();
|
||||
write!(f, "Population,").unwrap();
|
||||
for g in Good::list() {
|
||||
write!(f, "{:?} Value,", g).unwrap();
|
||||
}
|
||||
for g in Good::list() {
|
||||
write!(f, "{:?} LaborVal,", g).unwrap();
|
||||
}
|
||||
for g in Good::list() {
|
||||
write!(f, "{:?} Stock,", g).unwrap();
|
||||
}
|
||||
for g in Good::list() {
|
||||
write!(f, "{:?} Surplus,", g).unwrap();
|
||||
}
|
||||
for l in Labor::list() {
|
||||
write!(f, "{:?} Labor,", l).unwrap();
|
||||
}
|
||||
for l in Labor::list() {
|
||||
write!(f, "{:?} Productivity,", l).unwrap();
|
||||
}
|
||||
for l in Labor::list() {
|
||||
write!(f, "{:?} Yields,", l).unwrap();
|
||||
}
|
||||
writeln!(f).unwrap();
|
||||
Some(f)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
for i in 0..(HISTORY_DAYS / TICK_PERIOD) as i32 {
|
||||
if (index.time / YEAR) as i32 % 50 == 0 && (index.time % YEAR) as i32 == 0 {
|
||||
debug!("Year {}", (index.time / YEAR) as i32);
|
||||
}
|
||||
|
||||
tick(index, world, TICK_PERIOD);
|
||||
|
||||
if let Some(f) = f.as_mut() {
|
||||
if i % 5 == 0 {
|
||||
let site = index.sites.values().next().unwrap();
|
||||
write!(f, "{},", site.economy.pop).unwrap();
|
||||
for g in Good::list() {
|
||||
write!(f, "{:?},", site.economy.values[*g].unwrap_or(-1.0)).unwrap();
|
||||
}
|
||||
for g in Good::list() {
|
||||
write!(f, "{:?},", site.economy.labor_values[*g].unwrap_or(-1.0)).unwrap();
|
||||
}
|
||||
for g in Good::list() {
|
||||
write!(f, "{:?},", site.economy.stocks[*g]).unwrap();
|
||||
}
|
||||
for g in Good::list() {
|
||||
write!(f, "{:?},", site.economy.marginal_surplus[*g]).unwrap();
|
||||
}
|
||||
for l in Labor::list() {
|
||||
write!(f, "{:?},", site.economy.labors[*l] * site.economy.pop).unwrap();
|
||||
}
|
||||
for l in Labor::list() {
|
||||
write!(f, "{:?},", site.economy.productivity[*l]).unwrap();
|
||||
}
|
||||
for l in Labor::list() {
|
||||
write!(f, "{:?},", site.economy.yields[*l]).unwrap();
|
||||
}
|
||||
writeln!(f).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tick(index: &mut Index, _world: &mut WorldSim, dt: f32) {
|
||||
for site in index.sites.ids() {
|
||||
tick_site_economy(index, site, dt);
|
||||
}
|
||||
|
||||
index.time += dt;
|
||||
}
|
||||
|
||||
/// Simulate a site's economy. This simulation is roughly equivalent to the
|
||||
/// Lange-Lerner model's solution to the socialist calculation problem. The
|
||||
/// simulation begins by assigning arbitrary values to each commodity and then
|
||||
/// incrementally updates them according to the final scarcity of the commodity
|
||||
/// at the end of the tick. This results in the formulation of values that are
|
||||
/// roughly analgous to prices for each commodity. The workforce is then
|
||||
/// reassigned according to the respective commodity values. The simulation also
|
||||
/// includes damping terms that prevent cyclical inconsistencies in value
|
||||
/// rationalisation magnifying enough to crash the economy. We also ensure that
|
||||
/// a small number of workers are allocated to every industry (even inactive
|
||||
/// ones) each tick. This is not an accident: a small amount of productive
|
||||
/// capacity in one industry allows the economy to quickly pivot to a different
|
||||
/// prodution configuration should an additional commodity that acts as
|
||||
/// production input become available. This means that the economy will
|
||||
/// dynamically react to environmental changes. If a product becomes available
|
||||
/// through a mechanism such as trade, an entire arm of the economy may
|
||||
/// materialise to take advantage of this.
|
||||
pub fn tick_site_economy(index: &mut Index, site: Id<Site>, dt: f32) {
|
||||
let site = &mut index.sites[site];
|
||||
|
||||
let orders = site.economy.get_orders();
|
||||
let productivity = site.economy.get_productivity();
|
||||
|
||||
let mut demand = MapVec::from_default(0.0);
|
||||
for (labor, orders) in &orders {
|
||||
let scale = if let Some(labor) = labor {
|
||||
site.economy.labors[*labor]
|
||||
} else {
|
||||
1.0
|
||||
} * site.economy.pop;
|
||||
for (good, amount) in orders {
|
||||
demand[*good] += *amount * scale;
|
||||
}
|
||||
}
|
||||
|
||||
let mut supply = site.economy.stocks.clone(); //MapVec::from_default(0.0);
|
||||
for (labor, (output_good, _)) in productivity.iter() {
|
||||
supply[*output_good] +=
|
||||
site.economy.yields[labor] * site.economy.labors[labor] * site.economy.pop;
|
||||
}
|
||||
|
||||
let stocks = &site.economy.stocks;
|
||||
site.economy.surplus = demand
|
||||
.clone()
|
||||
.map(|g, demand| supply[g] + stocks[g] - demand);
|
||||
site.economy.marginal_surplus = demand.clone().map(|g, demand| supply[g] - demand);
|
||||
|
||||
// Update values according to the surplus of each stock
|
||||
// Note that values are used for workforce allocation and are not the same thing
|
||||
// as price
|
||||
let values = &mut site.economy.values;
|
||||
site.economy.surplus.iter().for_each(|(good, surplus)| {
|
||||
// Value rationalisation
|
||||
let val = 2.0f32.powf(1.0 - *surplus / demand[good]);
|
||||
let smooth = 0.8;
|
||||
values[good] = if val > 0.001 && val < 1000.0 {
|
||||
Some(smooth * values[good].unwrap_or(val) + (1.0 - smooth) * val)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
});
|
||||
|
||||
// Update export targets based on relative values
|
||||
// let value_avg = values
|
||||
// .iter()
|
||||
// .map(|(_, v)| (*v).unwrap_or(0.0))
|
||||
// .sum::<f32>()
|
||||
// .max(0.01)
|
||||
// / values.iter().filter(|(_, v)| v.is_some()).count() as f32;
|
||||
//let export_targets = &mut site.economy.export_targets;
|
||||
//let last_exports = &self.last_exports;
|
||||
// site.economy.values.iter().for_each(|(stock, value)| {
|
||||
// let rvalue = (*value).map(|v| v - value_avg).unwrap_or(0.0);
|
||||
// //let factor = if export_targets[stock] > 0.0 { 1.0 / rvalue } else {
|
||||
// rvalue }; //export_targets[stock] = last_exports[stock] - rvalue *
|
||||
// 0.1; // + (trade_states[stock].sell_belief.price -
|
||||
// trade_states[stock].buy_belief.price) * 0.025; });
|
||||
|
||||
//let pop = site.economy.pop;
|
||||
|
||||
// Redistribute workforce according to relative good values
|
||||
let labor_ratios = productivity.clone().map(|labor, (output_good, _)| {
|
||||
site.economy.values[output_good].unwrap_or(0.0)
|
||||
* site.economy.productivity[labor]
|
||||
//(site.economy.prices[output_good] - site.economy.material_costs[output_good]) * site.economy.yields[labor]
|
||||
//* demand[output_good] / supply[output_good].max(0.001)
|
||||
});
|
||||
let labor_ratio_sum = labor_ratios.iter().map(|(_, r)| *r).sum::<f32>().max(0.01);
|
||||
productivity.iter().for_each(|(labor, _)| {
|
||||
let smooth = 0.8;
|
||||
site.economy.labors[labor] = smooth * site.economy.labors[labor]
|
||||
+ (1.0 - smooth)
|
||||
* (labor_ratios[labor].max(labor_ratio_sum / 1000.0) / labor_ratio_sum);
|
||||
});
|
||||
|
||||
// Production
|
||||
let stocks_before = site.economy.stocks.clone();
|
||||
let mut total_labor_values = MapVec::<_, f32>::default();
|
||||
let mut total_outputs = MapVec::<_, f32>::default();
|
||||
for (labor, orders) in orders.iter() {
|
||||
let scale = if let Some(labor) = labor {
|
||||
site.economy.labors[*labor]
|
||||
} else {
|
||||
1.0
|
||||
} * site.economy.pop;
|
||||
|
||||
// For each order, we try to find the minimum satisfaction rate - this limits
|
||||
// how much we can produce! For example, if we need 0.25 fish and
|
||||
// 0.75 oats to make 1 unit of food, but only 0.5 units of oats are
|
||||
// available then we only need to consume 2/3rds
|
||||
// of other ingredients and leave the rest in stock
|
||||
// In effect, this is the productivity
|
||||
let labor_productivity = orders
|
||||
.iter()
|
||||
.map(|(good, amount)| {
|
||||
// What quantity is this order requesting?
|
||||
let _quantity = *amount * scale;
|
||||
// What proportion of this order is the economy able to satisfy?
|
||||
(stocks_before[*good] / demand[*good]).min(1.0)
|
||||
})
|
||||
.min_by(|a, b| a.partial_cmp(b).unwrap())
|
||||
.unwrap_or_else(|| panic!("Industry {:?} requires at least one input order", labor));
|
||||
|
||||
let mut total_materials_cost = 0.0;
|
||||
for (good, amount) in orders {
|
||||
// What quantity is this order requesting?
|
||||
let quantity = *amount * scale;
|
||||
// What amount gets actually used in production?
|
||||
let used = quantity * labor_productivity;
|
||||
|
||||
// Material cost of each factor of production
|
||||
total_materials_cost += used * site.economy.labor_values[*good].unwrap_or(0.0);
|
||||
|
||||
// Deplete stocks accordingly
|
||||
site.economy.stocks[*good] = (site.economy.stocks[*good] - used).max(0.0);
|
||||
}
|
||||
|
||||
// Industries produce things
|
||||
if let Some(labor) = labor {
|
||||
let (stock, rate) = productivity[*labor];
|
||||
let workers = site.economy.labors[*labor] * site.economy.pop;
|
||||
let final_rate = rate;
|
||||
let yield_per_worker =
|
||||
labor_productivity * final_rate * (1.0 + workers / 100.0).min(3.0);
|
||||
site.economy.yields[*labor] = yield_per_worker;
|
||||
site.economy.productivity[*labor] = labor_productivity;
|
||||
let total_output = yield_per_worker * workers;
|
||||
site.economy.stocks[stock] += total_output;
|
||||
|
||||
// Materials cost per unit
|
||||
site.economy.material_costs[stock] = total_materials_cost / total_output.max(0.001);
|
||||
// Labor costs
|
||||
let wages = 1.0;
|
||||
let total_labor_cost = workers * wages;
|
||||
|
||||
total_labor_values[stock] += total_materials_cost + total_labor_cost;
|
||||
total_outputs[stock] += total_output;
|
||||
}
|
||||
}
|
||||
|
||||
// Update labour values per unit
|
||||
site.economy.labor_values = total_labor_values.map(|stock, tlv| {
|
||||
let total_output = total_outputs[stock];
|
||||
if total_output > 0.01 {
|
||||
Some(tlv / total_outputs[stock])
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
// Decay stocks
|
||||
site.economy
|
||||
.stocks
|
||||
.iter_mut()
|
||||
.for_each(|(c, v)| *v *= 1.0 - c.decay_rate());
|
||||
|
||||
// Decay stocks
|
||||
site.economy.replenish(index.time);
|
||||
|
||||
// Births/deaths
|
||||
const NATURAL_BIRTH_RATE: f32 = 0.05;
|
||||
const DEATH_RATE: f32 = 0.005;
|
||||
let birth_rate = if site.economy.surplus[Good::Food] > 0.0 {
|
||||
NATURAL_BIRTH_RATE
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
site.economy.pop += dt / YEAR * site.economy.pop * (birth_rate - DEATH_RATE);
|
||||
}
|
39
world/src/site/block_mask.rs
Normal file
39
world/src/site/block_mask.rs
Normal file
@ -0,0 +1,39 @@
|
||||
use common::{terrain::Block, vol::Vox};
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct BlockMask {
|
||||
block: Block,
|
||||
priority: i32,
|
||||
}
|
||||
|
||||
impl BlockMask {
|
||||
pub fn new(block: Block, priority: i32) -> Self { Self { block, priority } }
|
||||
|
||||
pub fn nothing() -> Self {
|
||||
Self {
|
||||
block: Block::empty(),
|
||||
priority: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_priority(mut self, priority: i32) -> Self {
|
||||
self.priority = priority;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn resolve_with(self, other: Self) -> Self {
|
||||
if self.priority >= other.priority {
|
||||
self
|
||||
} else {
|
||||
other
|
||||
}
|
||||
}
|
||||
|
||||
pub fn finish(self) -> Option<Block> {
|
||||
if self.priority > 0 {
|
||||
Some(self.block)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
416
world/src/site/castle/mod.rs
Normal file
416
world/src/site/castle/mod.rs
Normal file
@ -0,0 +1,416 @@
|
||||
use super::SpawnRules;
|
||||
use crate::{
|
||||
column::ColumnSample,
|
||||
sim::WorldSim,
|
||||
site::settlement::building::{
|
||||
archetype::keep::{Attr, Keep as KeepArchetype},
|
||||
Archetype, Ori,
|
||||
},
|
||||
};
|
||||
use common::{
|
||||
generation::ChunkSupplement,
|
||||
terrain::{Block, BlockKind},
|
||||
vol::{BaseVol, ReadVol, RectSizedVol, Vox, WriteVol},
|
||||
};
|
||||
use core::f32;
|
||||
use rand::prelude::*;
|
||||
use vek::*;
|
||||
|
||||
struct Keep {
|
||||
offset: Vec2<i32>,
|
||||
locus: i32,
|
||||
storeys: i32,
|
||||
is_tower: bool,
|
||||
alt: i32,
|
||||
}
|
||||
|
||||
struct Tower {
|
||||
offset: Vec2<i32>,
|
||||
alt: i32,
|
||||
}
|
||||
|
||||
pub struct Castle {
|
||||
origin: Vec2<i32>,
|
||||
//seed: u32,
|
||||
radius: i32,
|
||||
towers: Vec<Tower>,
|
||||
keeps: Vec<Keep>,
|
||||
rounded_towers: bool,
|
||||
ridged: bool,
|
||||
flags: bool,
|
||||
|
||||
evil: bool,
|
||||
}
|
||||
|
||||
pub struct GenCtx<'a, R: Rng> {
|
||||
sim: Option<&'a mut WorldSim>,
|
||||
rng: &'a mut R,
|
||||
}
|
||||
|
||||
impl Castle {
|
||||
#[allow(clippy::let_and_return)] // TODO: Pending review in #587
|
||||
pub fn generate(wpos: Vec2<i32>, sim: Option<&mut WorldSim>, rng: &mut impl Rng) -> Self {
|
||||
let ctx = GenCtx { sim, rng };
|
||||
|
||||
let boundary_towers = ctx.rng.gen_range(5, 10);
|
||||
let keep_count = ctx.rng.gen_range(1, 4);
|
||||
let boundary_noise = ctx.rng.gen_range(-2i32, 8).max(1) as f32;
|
||||
|
||||
let radius = 150;
|
||||
|
||||
let this = Self {
|
||||
origin: wpos,
|
||||
// alt: ctx
|
||||
// .sim
|
||||
// .as_ref()
|
||||
// .and_then(|sim| sim.get_alt_approx(wpos))
|
||||
// .unwrap_or(0.0) as i32
|
||||
// + 6,
|
||||
//seed: ctx.rng.gen(),
|
||||
radius,
|
||||
|
||||
towers: (0..boundary_towers)
|
||||
.map(|i| {
|
||||
let angle = (i as f32 / boundary_towers as f32) * f32::consts::PI * 2.0;
|
||||
let dir = Vec2::new(angle.cos(), angle.sin());
|
||||
let dist = radius as f32 + ((angle * boundary_noise).sin() - 1.0) * 40.0;
|
||||
|
||||
let mut offset = (dir * dist).map(|e| e as i32);
|
||||
// Try to move the tower around until it's not intersecting a path
|
||||
for i in (1..80).step_by(5) {
|
||||
if ctx
|
||||
.sim
|
||||
.as_ref()
|
||||
.and_then(|sim| sim.get_nearest_path(wpos + offset))
|
||||
.map(|(dist, _, _, _)| dist > 24.0)
|
||||
.unwrap_or(true)
|
||||
{
|
||||
break;
|
||||
}
|
||||
offset = (dir * dist)
|
||||
.map(|e| (e + ctx.rng.gen_range(-1.0, 1.0) * i as f32) as i32);
|
||||
}
|
||||
|
||||
Tower {
|
||||
offset,
|
||||
alt: ctx
|
||||
.sim
|
||||
.as_ref()
|
||||
.and_then(|sim| sim.get_alt_approx(wpos + offset))
|
||||
.unwrap_or(0.0) as i32
|
||||
+ 2,
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
rounded_towers: ctx.rng.gen(),
|
||||
ridged: ctx.rng.gen(),
|
||||
flags: ctx.rng.gen(),
|
||||
evil: ctx.rng.gen(),
|
||||
keeps: (0..keep_count)
|
||||
.map(|i| {
|
||||
let angle = (i as f32 / keep_count as f32) * f32::consts::PI * 2.0;
|
||||
let dir = Vec2::new(angle.cos(), angle.sin());
|
||||
let dist =
|
||||
(radius as f32 + ((angle * boundary_noise).sin() - 1.0) * 40.0) * 0.3;
|
||||
|
||||
let locus = ctx.rng.gen_range(20, 26);
|
||||
let offset = (dir * dist).map(|e| e as i32);
|
||||
let storeys = ctx.rng.gen_range(1, 8).clamped(3, 5);
|
||||
|
||||
Keep {
|
||||
offset,
|
||||
locus,
|
||||
storeys,
|
||||
is_tower: true,
|
||||
alt: ctx
|
||||
.sim
|
||||
.as_ref()
|
||||
.and_then(|sim| sim.get_alt_approx(wpos + offset))
|
||||
.unwrap_or(0.0) as i32
|
||||
+ 2,
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
};
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
pub fn contains_point(&self, wpos: Vec2<i32>) -> bool {
|
||||
let lpos = wpos - self.origin;
|
||||
for i in 0..self.towers.len() {
|
||||
let tower0 = &self.towers[i];
|
||||
let tower1 = &self.towers[(i + 1) % self.towers.len()];
|
||||
|
||||
if lpos.determine_side(Vec2::zero(), tower0.offset) > 0
|
||||
&& lpos.determine_side(Vec2::zero(), tower1.offset) <= 0
|
||||
&& lpos.determine_side(tower0.offset, tower1.offset) > 0
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub fn get_origin(&self) -> Vec2<i32> { self.origin }
|
||||
|
||||
pub fn radius(&self) -> f32 { 1200.0 }
|
||||
|
||||
#[allow(clippy::needless_update)] // TODO: Pending review in #587
|
||||
pub fn spawn_rules(&self, wpos: Vec2<i32>) -> SpawnRules {
|
||||
SpawnRules {
|
||||
trees: wpos.distance_squared(self.origin) > self.radius.pow(2),
|
||||
..SpawnRules::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply_to<'a>(
|
||||
&'a self,
|
||||
wpos2d: Vec2<i32>,
|
||||
mut get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>,
|
||||
vol: &mut (impl BaseVol<Vox = Block> + RectSizedVol + ReadVol + WriteVol),
|
||||
) {
|
||||
for y in 0..vol.size_xy().y as i32 {
|
||||
for x in 0..vol.size_xy().x as i32 {
|
||||
let offs = Vec2::new(x, y);
|
||||
|
||||
let wpos2d = wpos2d + offs;
|
||||
let rpos = wpos2d - self.origin;
|
||||
|
||||
if rpos.magnitude_squared() > (self.radius + 64).pow(2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let col_sample = if let Some(col) = get_column(offs) {
|
||||
col
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
|
||||
// Inner ground
|
||||
if self.contains_point(wpos2d) {
|
||||
let surface_z = col_sample.alt as i32;
|
||||
for z in -5..3 {
|
||||
let pos = Vec3::new(offs.x, offs.y, surface_z + z);
|
||||
|
||||
if z > 0 {
|
||||
if vol.get(pos).unwrap().kind() != BlockKind::Water {
|
||||
let _ = vol.set(pos, Block::empty());
|
||||
}
|
||||
} else {
|
||||
let _ = vol.set(
|
||||
pos,
|
||||
Block::new(
|
||||
BlockKind::Normal,
|
||||
col_sample.sub_surface_color.map(|e| (e * 255.0) as u8),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let (wall_dist, wall_pos, wall_alt, wall_ori, _towers) = (0..self.towers.len())
|
||||
.map(|i| {
|
||||
let tower0 = &self.towers[i];
|
||||
let tower1 = &self.towers[(i + 1) % self.towers.len()];
|
||||
|
||||
let wall = LineSegment2 {
|
||||
start: tower0.offset.map(|e| e as f32),
|
||||
end: tower1.offset.map(|e| e as f32),
|
||||
};
|
||||
|
||||
let projected = wall
|
||||
.projected_point(rpos.map(|e| e as f32))
|
||||
.map(|e| e.floor() as i32);
|
||||
|
||||
let tower0_dist = tower0
|
||||
.offset
|
||||
.map(|e| e as f32)
|
||||
.distance(projected.map(|e| e as f32));
|
||||
let tower1_dist = tower1
|
||||
.offset
|
||||
.map(|e| e as f32)
|
||||
.distance(projected.map(|e| e as f32));
|
||||
let tower_lerp = tower0_dist / (tower0_dist + tower1_dist);
|
||||
let wall_ori = if (tower0.offset.x - tower1.offset.x).abs()
|
||||
< (tower0.offset.y - tower1.offset.y).abs()
|
||||
{
|
||||
Ori::North
|
||||
} else {
|
||||
Ori::East
|
||||
};
|
||||
|
||||
(
|
||||
wall.distance_to_point(rpos.map(|e| e as f32)) as i32,
|
||||
projected,
|
||||
Lerp::lerp(tower0.alt as f32, tower1.alt as f32, tower_lerp) as i32,
|
||||
wall_ori,
|
||||
[tower0, tower1],
|
||||
)
|
||||
})
|
||||
.min_by_key(|x| x.0)
|
||||
.unwrap();
|
||||
let border_pos = (wall_pos - rpos).map(|e| e.abs());
|
||||
let wall_rpos = if wall_ori == Ori::East {
|
||||
rpos
|
||||
} else {
|
||||
rpos.yx()
|
||||
};
|
||||
let head_space = col_sample
|
||||
.path
|
||||
.map(|(dist, _, path, _)| path.head_space(dist))
|
||||
.unwrap_or(0);
|
||||
|
||||
let wall_sample = if let Some(col) = get_column(offs + wall_pos - rpos) {
|
||||
col
|
||||
} else {
|
||||
col_sample
|
||||
};
|
||||
|
||||
// Make sure particularly weird terrain doesn't give us underground walls
|
||||
let wall_alt = wall_alt + (wall_sample.alt as i32 - wall_alt - 10).max(0);
|
||||
|
||||
let keep_archetype = KeepArchetype {
|
||||
flag_color: if self.evil {
|
||||
Rgb::new(80, 10, 130)
|
||||
} else {
|
||||
Rgb::new(200, 80, 40)
|
||||
},
|
||||
stone_color: if self.evil {
|
||||
Rgb::new(65, 60, 55)
|
||||
} else {
|
||||
Rgb::new(100, 100, 110)
|
||||
},
|
||||
};
|
||||
|
||||
for z in -10..64 {
|
||||
let wpos = Vec3::new(wpos2d.x, wpos2d.y, col_sample.alt as i32 + z);
|
||||
|
||||
// Boundary wall
|
||||
let wall_z = wpos.z - wall_alt;
|
||||
if z < head_space {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut mask = keep_archetype.draw(
|
||||
Vec3::from(wall_rpos) + Vec3::unit_z() * wpos.z - wall_alt,
|
||||
wall_dist,
|
||||
border_pos,
|
||||
rpos - wall_pos,
|
||||
wall_z,
|
||||
wall_ori,
|
||||
4,
|
||||
0,
|
||||
&Attr {
|
||||
storeys: 2,
|
||||
is_tower: false,
|
||||
flag: self.flags,
|
||||
ridged: false,
|
||||
rounded: true,
|
||||
has_doors: false,
|
||||
},
|
||||
);
|
||||
|
||||
// Boundary towers
|
||||
for tower in &self.towers {
|
||||
let tower_wpos = Vec3::new(
|
||||
self.origin.x + tower.offset.x,
|
||||
self.origin.y + tower.offset.y,
|
||||
tower.alt,
|
||||
);
|
||||
let tower_locus = 10;
|
||||
|
||||
let border_pos = (tower_wpos - wpos).xy().map(|e| e.abs());
|
||||
mask = mask.resolve_with(keep_archetype.draw(
|
||||
if (tower_wpos.x - wpos.x).abs() < (tower_wpos.y - wpos.y).abs() {
|
||||
wpos - tower_wpos
|
||||
} else {
|
||||
Vec3::new(
|
||||
wpos.y - tower_wpos.y,
|
||||
wpos.x - tower_wpos.x,
|
||||
wpos.z - tower_wpos.z,
|
||||
)
|
||||
},
|
||||
border_pos.reduce_max() - tower_locus,
|
||||
Vec2::new(border_pos.reduce_min(), border_pos.reduce_max()),
|
||||
(wpos - tower_wpos).xy(),
|
||||
wpos.z - tower.alt,
|
||||
if border_pos.x > border_pos.y {
|
||||
Ori::East
|
||||
} else {
|
||||
Ori::North
|
||||
},
|
||||
tower_locus,
|
||||
0,
|
||||
&Attr {
|
||||
storeys: 3,
|
||||
is_tower: true,
|
||||
flag: self.flags,
|
||||
ridged: self.ridged,
|
||||
rounded: self.rounded_towers,
|
||||
has_doors: false,
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
// Keeps
|
||||
for keep in &self.keeps {
|
||||
let keep_wpos = Vec3::new(
|
||||
self.origin.x + keep.offset.x,
|
||||
self.origin.y + keep.offset.y,
|
||||
keep.alt,
|
||||
);
|
||||
|
||||
let border_pos = (keep_wpos - wpos).xy().map(|e| e.abs());
|
||||
mask = mask.resolve_with(keep_archetype.draw(
|
||||
if (keep_wpos.x - wpos.x).abs() < (keep_wpos.y - wpos.y).abs() {
|
||||
wpos - keep_wpos
|
||||
} else {
|
||||
Vec3::new(
|
||||
wpos.y - keep_wpos.y,
|
||||
wpos.x - keep_wpos.x,
|
||||
wpos.z - keep_wpos.z,
|
||||
)
|
||||
},
|
||||
border_pos.reduce_max() - keep.locus,
|
||||
Vec2::new(border_pos.reduce_min(), border_pos.reduce_max()),
|
||||
(wpos - keep_wpos).xy(),
|
||||
wpos.z - keep.alt,
|
||||
if border_pos.x > border_pos.y {
|
||||
Ori::East
|
||||
} else {
|
||||
Ori::North
|
||||
},
|
||||
keep.locus,
|
||||
0,
|
||||
&Attr {
|
||||
storeys: keep.storeys,
|
||||
is_tower: keep.is_tower,
|
||||
flag: self.flags,
|
||||
ridged: self.ridged,
|
||||
rounded: self.rounded_towers,
|
||||
has_doors: true,
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(block) = mask.finish() {
|
||||
let _ = vol.set(Vec3::new(offs.x, offs.y, wpos.z), block);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::or_fun_call)] // TODO: Pending review in #587
|
||||
pub fn apply_supplement<'a>(
|
||||
&'a self,
|
||||
_rng: &mut impl Rng,
|
||||
_wpos2d: Vec2<i32>,
|
||||
_get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>,
|
||||
_supplement: &mut ChunkSupplement,
|
||||
) {
|
||||
// TODO
|
||||
}
|
||||
}
|
@ -23,19 +23,6 @@ use rand::prelude::*;
|
||||
use std::sync::Arc;
|
||||
use vek::*;
|
||||
|
||||
impl WorldSim {
|
||||
#[allow(dead_code)]
|
||||
fn can_host_dungeon(&self, pos: Vec2<i32>) -> bool {
|
||||
self.get(pos)
|
||||
.map(|chunk| !chunk.near_cliffs && !chunk.river.is_river() && !chunk.river.is_lake())
|
||||
.unwrap_or(false)
|
||||
&& self
|
||||
.get_gradient_approx(pos)
|
||||
.map(|grad| grad > 0.25 && grad < 1.5)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Dungeon {
|
||||
origin: Vec2<i32>,
|
||||
alt: i32,
|
||||
@ -164,20 +151,6 @@ impl Dungeon {
|
||||
max: rpos + TerrainChunkSize::RECT_SIZE.map(|e| e as i32),
|
||||
};
|
||||
|
||||
if area.contains_point(Vec2::zero()) {
|
||||
let offs = Vec2::new(rng.gen_range(-1.0, 1.0), rng.gen_range(-1.0, 1.0))
|
||||
.try_normalized()
|
||||
.unwrap_or(Vec2::unit_y())
|
||||
* 12.0;
|
||||
supplement.add_entity(
|
||||
EntityInfo::at(
|
||||
Vec3::new(self.origin.x, self.origin.y, self.alt + 16).map(|e| e as f32)
|
||||
+ Vec3::from(offs),
|
||||
)
|
||||
.into_waypoint(),
|
||||
);
|
||||
}
|
||||
|
||||
let mut z = self.alt + ALT_OFFSET;
|
||||
for floor in &self.floors {
|
||||
z -= floor.total_depth();
|
||||
@ -228,6 +201,7 @@ pub struct Floor {
|
||||
hollow_depth: i32,
|
||||
#[allow(dead_code)]
|
||||
stair_tile: Vec2<i32>,
|
||||
final_level: bool,
|
||||
}
|
||||
|
||||
const FLOOR_SIZE: Vec2<i32> = Vec2::new(18, 18);
|
||||
@ -260,6 +234,7 @@ impl Floor {
|
||||
solid_depth: if level == 0 { 80 } else { 32 },
|
||||
hollow_depth: 30,
|
||||
stair_tile: new_stair_tile - tile_offset,
|
||||
final_level,
|
||||
};
|
||||
|
||||
const STAIR_ROOM_HEIGHT: i32 = 13;
|
||||
@ -305,7 +280,7 @@ impl Floor {
|
||||
|
||||
this.create_rooms(ctx, level, 7);
|
||||
// Create routes between all rooms
|
||||
let room_areas = this.rooms.iter().map(|r| r.area).collect::<Vec<_>>();
|
||||
let room_areas = this.rooms.values().map(|r| r.area).collect::<Vec<_>>();
|
||||
for a in room_areas.iter() {
|
||||
for b in room_areas.iter() {
|
||||
this.create_route(ctx, a.center(), b.center());
|
||||
@ -342,7 +317,7 @@ impl Floor {
|
||||
// Ensure no overlap
|
||||
if self
|
||||
.rooms
|
||||
.iter()
|
||||
.values()
|
||||
.any(|r| r.area.collides_with_rect(area_border))
|
||||
{
|
||||
return None;
|
||||
@ -423,6 +398,24 @@ impl Floor {
|
||||
origin: Vec3<i32>,
|
||||
supplement: &mut ChunkSupplement,
|
||||
) {
|
||||
let stair_rcenter =
|
||||
Vec3::from((self.stair_tile + self.tile_offset).map(|e| e * TILE_SIZE + TILE_SIZE / 2));
|
||||
|
||||
if area.contains_point(stair_rcenter.xy()) {
|
||||
let offs = Vec2::new(rng.gen_range(-1.0, 1.0), rng.gen_range(-1.0, 1.0))
|
||||
.try_normalized()
|
||||
.unwrap_or_else(Vec2::unit_y)
|
||||
* FLOOR_SIZE.x as f32
|
||||
/ 2.0
|
||||
- 8.0;
|
||||
if !self.final_level {
|
||||
supplement.add_entity(
|
||||
EntityInfo::at((origin + stair_rcenter).map(|e| e as f32) + Vec3::from(offs))
|
||||
.into_waypoint(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for x in area.min.x..area.max.x {
|
||||
for y in area.min.y..area.max.y {
|
||||
let tile_pos = Vec2::new(x, y).map(|e| e.div_euclid(TILE_SIZE)) - self.tile_offset;
|
||||
@ -478,7 +471,16 @@ impl Floor {
|
||||
if room.boss {
|
||||
let boss_spawn_tile = room.area.center();
|
||||
// Don't spawn the boss in a pillar
|
||||
let boss_spawn_tile = boss_spawn_tile + if tile_is_pillar { 1 } else { 0 };
|
||||
let boss_tile_is_pillar = room
|
||||
.pillars
|
||||
.map(|pillar_space| {
|
||||
boss_spawn_tile
|
||||
.map(|e| e.rem_euclid(pillar_space) == 0)
|
||||
.reduce_and()
|
||||
})
|
||||
.unwrap_or(false);
|
||||
let boss_spawn_tile =
|
||||
boss_spawn_tile + if boss_tile_is_pillar { 1 } else { 0 };
|
||||
|
||||
if tile_pos == boss_spawn_tile && tile_wcenter.xy() == wpos2d {
|
||||
let entity = EntityInfo::at(tile_wcenter.map(|e| e as f32))
|
||||
@ -635,10 +637,13 @@ impl Floor {
|
||||
empty
|
||||
};
|
||||
|
||||
let tunnel_height = if self.final_level { 16.0 } else { 8.0 };
|
||||
|
||||
move |z| match self.tiles.get(tile_pos) {
|
||||
Some(Tile::Solid) => BlockMask::nothing(),
|
||||
Some(Tile::Tunnel) => {
|
||||
if dist_to_wall >= wall_thickness && (z as f32) < 8.0 - 8.0 * tunnel_dist.powf(4.0)
|
||||
if dist_to_wall >= wall_thickness
|
||||
&& (z as f32) < tunnel_height * (1.0 - tunnel_dist.powf(4.0))
|
||||
{
|
||||
if z == 0 { floor_sprite } else { empty }
|
||||
} else {
|
||||
|
155
world/src/site/economy.rs
Normal file
155
world/src/site/economy.rs
Normal file
@ -0,0 +1,155 @@
|
||||
use crate::util::{DHashMap, MapVec};
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub enum Good {
|
||||
Wheat = 0,
|
||||
Flour = 1,
|
||||
Meat = 2,
|
||||
Fish = 3,
|
||||
Game = 4,
|
||||
Food = 5,
|
||||
Logs = 6,
|
||||
Wood = 7,
|
||||
Rock = 8,
|
||||
Stone = 9,
|
||||
}
|
||||
use Good::*;
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub enum Labor {
|
||||
Farmer = 0,
|
||||
Lumberjack = 1,
|
||||
Miner = 2,
|
||||
Fisher = 3,
|
||||
Hunter = 4,
|
||||
Cook = 5,
|
||||
}
|
||||
use Labor::*;
|
||||
|
||||
pub struct Economy {
|
||||
pub pop: f32,
|
||||
|
||||
pub stocks: MapVec<Good, f32>,
|
||||
pub surplus: MapVec<Good, f32>,
|
||||
pub marginal_surplus: MapVec<Good, f32>,
|
||||
pub values: MapVec<Good, Option<f32>>,
|
||||
|
||||
pub labor_values: MapVec<Good, Option<f32>>,
|
||||
pub material_costs: MapVec<Good, f32>,
|
||||
|
||||
pub labors: MapVec<Labor, f32>,
|
||||
pub yields: MapVec<Labor, f32>,
|
||||
pub productivity: MapVec<Labor, f32>,
|
||||
}
|
||||
|
||||
impl Default for Economy {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
pop: 32.0,
|
||||
|
||||
stocks: Default::default(),
|
||||
surplus: Default::default(),
|
||||
marginal_surplus: Default::default(),
|
||||
values: Default::default(),
|
||||
|
||||
labor_values: Default::default(),
|
||||
material_costs: Default::default(),
|
||||
|
||||
labors: Default::default(),
|
||||
yields: Default::default(),
|
||||
productivity: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Economy {
|
||||
pub fn get_orders(&self) -> DHashMap<Option<Labor>, Vec<(Good, f32)>> {
|
||||
vec![
|
||||
(None, vec![(Food, 0.5)]),
|
||||
(Some(Cook), vec![
|
||||
(Flour, 12.0),
|
||||
(Meat, 4.0),
|
||||
(Wood, 1.5),
|
||||
(Stone, 1.0),
|
||||
]),
|
||||
(Some(Lumberjack), vec![(Logs, 0.5)]),
|
||||
(Some(Miner), vec![(Rock, 0.5)]),
|
||||
(Some(Fisher), vec![(Fish, 4.0)]),
|
||||
(Some(Hunter), vec![(Game, 1.0)]),
|
||||
(Some(Farmer), vec![(Wheat, 2.0)]),
|
||||
]
|
||||
.into_iter()
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn get_productivity(&self) -> MapVec<Labor, (Good, f32)> {
|
||||
// Per labourer, per year
|
||||
MapVec::from_list(
|
||||
&[
|
||||
(Farmer, (Flour, 2.0)),
|
||||
(Lumberjack, (Wood, 0.5)),
|
||||
(Miner, (Stone, 0.5)),
|
||||
(Fisher, (Meat, 4.0)),
|
||||
(Hunter, (Meat, 1.0)),
|
||||
(Cook, (Food, 16.0)),
|
||||
],
|
||||
(Rock, 0.0),
|
||||
)
|
||||
.map(|l, (good, v)| (good, v * (1.0 + self.labors[l])))
|
||||
}
|
||||
|
||||
pub fn replenish(&mut self, time: f32) {
|
||||
//use rand::Rng;
|
||||
for (i, (g, v)) in [
|
||||
(Wheat, 50.0),
|
||||
(Logs, 20.0),
|
||||
(Rock, 120.0),
|
||||
(Game, 12.0),
|
||||
(Fish, 10.0),
|
||||
]
|
||||
.iter()
|
||||
.enumerate()
|
||||
{
|
||||
self.stocks[*g] = (*v
|
||||
* (1.25 + (((time * 0.0001 + i as f32).sin() + 1.0) % 1.0) * 0.5)
|
||||
- self.stocks[*g])
|
||||
* 0.075; //rand::thread_rng().gen_range(0.05, 0.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Good {
|
||||
fn default() -> Self {
|
||||
Good::Rock // Arbitrary
|
||||
}
|
||||
}
|
||||
|
||||
impl Good {
|
||||
pub fn list() -> &'static [Self] {
|
||||
static GOODS: [Good; 10] = [
|
||||
Wheat, Flour, Meat, Fish, Game, Food, Logs, Wood, Rock, Stone,
|
||||
];
|
||||
|
||||
&GOODS
|
||||
}
|
||||
|
||||
pub fn decay_rate(&self) -> f32 {
|
||||
match self {
|
||||
Food => 0.2,
|
||||
Wheat => 0.1,
|
||||
Meat => 0.25,
|
||||
Fish => 0.2,
|
||||
_ => 0.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Labor {
|
||||
pub fn list() -> &'static [Self] {
|
||||
static LABORS: [Labor; 6] = [Farmer, Lumberjack, Miner, Fisher, Hunter, Cook];
|
||||
|
||||
&LABORS
|
||||
}
|
||||
}
|
@ -1,57 +1,24 @@
|
||||
mod block_mask;
|
||||
mod castle;
|
||||
mod dungeon;
|
||||
pub mod economy;
|
||||
mod settlement;
|
||||
|
||||
// Reexports
|
||||
pub use self::{dungeon::Dungeon, settlement::Settlement};
|
||||
pub use self::{
|
||||
block_mask::BlockMask, castle::Castle, dungeon::Dungeon, economy::Economy,
|
||||
settlement::Settlement,
|
||||
};
|
||||
|
||||
use crate::column::ColumnSample;
|
||||
use common::{
|
||||
generation::ChunkSupplement,
|
||||
terrain::Block,
|
||||
vol::{BaseVol, ReadVol, RectSizedVol, Vox, WriteVol},
|
||||
vol::{BaseVol, ReadVol, RectSizedVol, WriteVol},
|
||||
};
|
||||
use rand::Rng;
|
||||
use std::{fmt, sync::Arc};
|
||||
use vek::*;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct BlockMask {
|
||||
block: Block,
|
||||
priority: i32,
|
||||
}
|
||||
|
||||
impl BlockMask {
|
||||
pub fn new(block: Block, priority: i32) -> Self { Self { block, priority } }
|
||||
|
||||
pub fn nothing() -> Self {
|
||||
Self {
|
||||
block: Block::empty(),
|
||||
priority: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_priority(mut self, priority: i32) -> Self {
|
||||
self.priority = priority;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn resolve_with(self, other: Self) -> Self {
|
||||
if self.priority >= other.priority {
|
||||
self
|
||||
} else {
|
||||
other
|
||||
}
|
||||
}
|
||||
|
||||
pub fn finish(self) -> Option<Block> {
|
||||
if self.priority > 0 {
|
||||
Some(self.block)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SpawnRules {
|
||||
pub trees: bool,
|
||||
}
|
||||
@ -60,31 +27,60 @@ impl Default for SpawnRules {
|
||||
fn default() -> Self { Self { trees: true } }
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum Site {
|
||||
Settlement(Arc<Settlement>),
|
||||
Dungeon(Arc<Dungeon>),
|
||||
pub struct Site {
|
||||
pub kind: SiteKind,
|
||||
pub economy: Economy,
|
||||
}
|
||||
|
||||
pub enum SiteKind {
|
||||
Settlement(Settlement),
|
||||
Dungeon(Dungeon),
|
||||
Castle(Castle),
|
||||
}
|
||||
|
||||
impl Site {
|
||||
pub fn settlement(s: Settlement) -> Self {
|
||||
Self {
|
||||
kind: SiteKind::Settlement(s),
|
||||
economy: Economy::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dungeon(d: Dungeon) -> Self {
|
||||
Self {
|
||||
kind: SiteKind::Dungeon(d),
|
||||
economy: Economy::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn castle(c: Castle) -> Self {
|
||||
Self {
|
||||
kind: SiteKind::Castle(c),
|
||||
economy: Economy::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn radius(&self) -> f32 {
|
||||
match self {
|
||||
Site::Settlement(settlement) => settlement.radius(),
|
||||
Site::Dungeon(dungeon) => dungeon.radius(),
|
||||
match &self.kind {
|
||||
SiteKind::Settlement(s) => s.radius(),
|
||||
SiteKind::Dungeon(d) => d.radius(),
|
||||
SiteKind::Castle(c) => c.radius(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_origin(&self) -> Vec2<i32> {
|
||||
match self {
|
||||
Site::Settlement(s) => s.get_origin(),
|
||||
Site::Dungeon(d) => d.get_origin(),
|
||||
match &self.kind {
|
||||
SiteKind::Settlement(s) => s.get_origin(),
|
||||
SiteKind::Dungeon(d) => d.get_origin(),
|
||||
SiteKind::Castle(c) => c.get_origin(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn spawn_rules(&self, wpos: Vec2<i32>) -> SpawnRules {
|
||||
match self {
|
||||
Site::Settlement(s) => s.spawn_rules(wpos),
|
||||
Site::Dungeon(d) => d.spawn_rules(wpos),
|
||||
match &self.kind {
|
||||
SiteKind::Settlement(s) => s.spawn_rules(wpos),
|
||||
SiteKind::Dungeon(d) => d.spawn_rules(wpos),
|
||||
SiteKind::Castle(c) => c.spawn_rules(wpos),
|
||||
}
|
||||
}
|
||||
|
||||
@ -94,9 +90,10 @@ impl Site {
|
||||
get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>,
|
||||
vol: &mut (impl BaseVol<Vox = Block> + RectSizedVol + ReadVol + WriteVol),
|
||||
) {
|
||||
match self {
|
||||
Site::Settlement(settlement) => settlement.apply_to(wpos2d, get_column, vol),
|
||||
Site::Dungeon(dungeon) => dungeon.apply_to(wpos2d, get_column, vol),
|
||||
match &self.kind {
|
||||
SiteKind::Settlement(s) => s.apply_to(wpos2d, get_column, vol),
|
||||
SiteKind::Dungeon(d) => d.apply_to(wpos2d, get_column, vol),
|
||||
SiteKind::Castle(c) => c.apply_to(wpos2d, get_column, vol),
|
||||
}
|
||||
}
|
||||
|
||||
@ -107,28 +104,10 @@ impl Site {
|
||||
get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>,
|
||||
supplement: &mut ChunkSupplement,
|
||||
) {
|
||||
match self {
|
||||
Site::Settlement(settlement) => {
|
||||
settlement.apply_supplement(rng, wpos2d, get_column, supplement)
|
||||
},
|
||||
Site::Dungeon(dungeon) => dungeon.apply_supplement(rng, wpos2d, get_column, supplement),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Settlement> for Site {
|
||||
fn from(settlement: Settlement) -> Self { Site::Settlement(Arc::new(settlement)) }
|
||||
}
|
||||
|
||||
impl From<Dungeon> for Site {
|
||||
fn from(dungeon: Dungeon) -> Self { Site::Dungeon(Arc::new(dungeon)) }
|
||||
}
|
||||
|
||||
impl fmt::Debug for Site {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Site::Settlement(_) => write!(f, "Settlement"),
|
||||
Site::Dungeon(_) => write!(f, "Dungeon"),
|
||||
match &self.kind {
|
||||
SiteKind::Settlement(s) => s.apply_supplement(rng, wpos2d, get_column, supplement),
|
||||
SiteKind::Dungeon(d) => d.apply_supplement(rng, wpos2d, get_column, supplement),
|
||||
SiteKind::Castle(c) => c.apply_supplement(rng, wpos2d, get_column, supplement),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,40 +12,81 @@ use common::{
|
||||
use rand::prelude::*;
|
||||
use vek::*;
|
||||
|
||||
const COLOR_THEMES: [Rgb<u8>; 11] = [
|
||||
Rgb::new(0x1D, 0x4D, 0x45),
|
||||
Rgb::new(0xB3, 0x7D, 0x60),
|
||||
Rgb::new(0xAC, 0x5D, 0x26),
|
||||
Rgb::new(0x32, 0x46, 0x6B),
|
||||
Rgb::new(0x2B, 0x19, 0x0F),
|
||||
Rgb::new(0x93, 0x78, 0x51),
|
||||
Rgb::new(0x92, 0x57, 0x24),
|
||||
Rgb::new(0x4A, 0x4E, 0x4E),
|
||||
Rgb::new(0x2F, 0x32, 0x47),
|
||||
Rgb::new(0x8F, 0x35, 0x43),
|
||||
Rgb::new(0x6D, 0x1E, 0x3A),
|
||||
pub struct ColorTheme {
|
||||
roof: Rgb<u8>,
|
||||
wall: Rgb<u8>,
|
||||
support: Rgb<u8>,
|
||||
}
|
||||
|
||||
const ROOF_COLORS: &[Rgb<u8>] = &[
|
||||
// Rgb::new(0x1D, 0x4D, 0x45),
|
||||
// Rgb::new(0xB3, 0x7D, 0x60),
|
||||
// Rgb::new(0xAC, 0x5D, 0x26),
|
||||
// Rgb::new(0x32, 0x46, 0x6B),
|
||||
// Rgb::new(0x2B, 0x19, 0x0F),
|
||||
// Rgb::new(0x93, 0x78, 0x51),
|
||||
// Rgb::new(0x92, 0x57, 0x24),
|
||||
// Rgb::new(0x4A, 0x4E, 0x4E),
|
||||
// Rgb::new(0x2F, 0x32, 0x47),
|
||||
// Rgb::new(0x8F, 0x35, 0x43),
|
||||
// Rgb::new(0x6D, 0x1E, 0x3A),
|
||||
// Rgb::new(0x6D, 0xA7, 0x80),
|
||||
// Rgb::new(0x4F, 0xA0, 0x95),
|
||||
// Rgb::new(0xE2, 0xB9, 0x99),
|
||||
// Rgb::new(0x7A, 0x30, 0x22),
|
||||
// Rgb::new(0x4A, 0x06, 0x08),
|
||||
// Rgb::new(0x8E, 0xB4, 0x57),
|
||||
Rgb::new(0x99, 0x5E, 0x54),
|
||||
Rgb::new(0x43, 0x63, 0x64),
|
||||
Rgb::new(0x76, 0x6D, 0x68),
|
||||
Rgb::new(0x7B, 0x41, 0x61),
|
||||
Rgb::new(0x52, 0x20, 0x20),
|
||||
Rgb::new(0x1A, 0x4A, 0x59),
|
||||
Rgb::new(0xCC, 0x76, 0x4E),
|
||||
];
|
||||
|
||||
const WALL_COLORS: &[Rgb<u8>] = &[
|
||||
Rgb::new(200, 180, 150),
|
||||
Rgb::new(0xB8, 0xB4, 0xA4),
|
||||
Rgb::new(0x76, 0x6D, 0x68),
|
||||
Rgb::new(0xF3, 0xC9, 0x8F),
|
||||
Rgb::new(0xD3, 0xB7, 0x99),
|
||||
Rgb::new(0xE1, 0xAB, 0x91),
|
||||
Rgb::new(0x82, 0x57, 0x4C),
|
||||
Rgb::new(0xB9, 0x96, 0x77),
|
||||
Rgb::new(0xAE, 0x8D, 0x9C),
|
||||
];
|
||||
|
||||
const SUPPORT_COLORS: &[Rgb<u8>] = &[
|
||||
Rgb::new(60, 45, 30),
|
||||
Rgb::new(0x65, 0x55, 0x56),
|
||||
Rgb::new(0x53, 0x33, 0x13),
|
||||
Rgb::new(0x58, 0x42, 0x33),
|
||||
];
|
||||
|
||||
pub struct House {
|
||||
roof_color: Rgb<u8>,
|
||||
noise: RandomField,
|
||||
roof_ribbing: bool,
|
||||
roof_ribbing_diagonal: bool,
|
||||
pub colors: ColorTheme,
|
||||
pub noise: RandomField,
|
||||
pub roof_ribbing: bool,
|
||||
pub roof_ribbing_diagonal: bool,
|
||||
}
|
||||
|
||||
enum Pillar {
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum Pillar {
|
||||
None,
|
||||
Chimney(i32),
|
||||
Tower(i32),
|
||||
}
|
||||
|
||||
enum RoofStyle {
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum RoofStyle {
|
||||
Hip,
|
||||
Gable,
|
||||
Rounded,
|
||||
}
|
||||
|
||||
enum StoreyFill {
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum StoreyFill {
|
||||
None,
|
||||
Upper,
|
||||
All,
|
||||
@ -69,16 +110,19 @@ impl StoreyFill {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Attr {
|
||||
central_supports: bool,
|
||||
storey_fill: StoreyFill,
|
||||
roof_style: RoofStyle,
|
||||
mansard: i32,
|
||||
pillar: Pillar,
|
||||
pub central_supports: bool,
|
||||
pub storey_fill: StoreyFill,
|
||||
pub roof_style: RoofStyle,
|
||||
pub mansard: i32,
|
||||
pub pillar: Pillar,
|
||||
pub levels: i32,
|
||||
pub window: BlockKind,
|
||||
}
|
||||
|
||||
impl Attr {
|
||||
fn generate<R: Rng>(rng: &mut R, locus: i32) -> Self {
|
||||
pub fn generate<R: Rng>(rng: &mut R, _locus: i32) -> Self {
|
||||
Self {
|
||||
central_supports: rng.gen(),
|
||||
storey_fill: match rng.gen_range(0, 2) {
|
||||
@ -93,9 +137,16 @@ impl Attr {
|
||||
},
|
||||
mansard: rng.gen_range(-7, 4).max(0),
|
||||
pillar: match rng.gen_range(0, 4) {
|
||||
0 => Pillar::Chimney(9 + locus + rng.gen_range(0, 4)),
|
||||
0 => Pillar::Chimney(rng.gen_range(2, 6)),
|
||||
_ => Pillar::None,
|
||||
},
|
||||
levels: rng.gen_range(1, 3),
|
||||
window: match rng.gen_range(0, 4) {
|
||||
0 => BlockKind::Window1,
|
||||
1 => BlockKind::Window2,
|
||||
2 => BlockKind::Window3,
|
||||
_ => BlockKind::Window4,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -107,6 +158,7 @@ impl Archetype for House {
|
||||
let len = rng.gen_range(-8, 24).clamped(0, 20);
|
||||
let locus = 6 + rng.gen_range(0, 5);
|
||||
let branches_per_side = 1 + len as usize / 20;
|
||||
let levels = rng.gen_range(1, 3);
|
||||
let skel = Skeleton {
|
||||
offset: -rng.gen_range(0, len + 7).clamped(0, len),
|
||||
ori: if rng.gen() { Ori::East } else { Ori::North },
|
||||
@ -116,10 +168,11 @@ impl Archetype for House {
|
||||
storey_fill: StoreyFill::All,
|
||||
mansard: 0,
|
||||
pillar: match rng.gen_range(0, 3) {
|
||||
0 => Pillar::Chimney(10 + locus + rng.gen_range(0, 4)),
|
||||
1 => Pillar::Tower(15 + locus + rng.gen_range(0, 4)),
|
||||
0 => Pillar::Chimney(rng.gen_range(2, 6)),
|
||||
1 => Pillar::Tower(5 + rng.gen_range(1, 5)),
|
||||
_ => Pillar::None,
|
||||
},
|
||||
levels,
|
||||
..Attr::generate(rng, locus)
|
||||
},
|
||||
locus,
|
||||
@ -134,7 +187,10 @@ impl Archetype for House {
|
||||
i as i32 * len / (branches_per_side - 1).max(1) as i32,
|
||||
Branch {
|
||||
len: rng.gen_range(8, 16) * flip,
|
||||
attr: Attr::generate(rng, locus),
|
||||
attr: Attr {
|
||||
levels: rng.gen_range(1, 4).min(levels),
|
||||
..Attr::generate(rng, locus)
|
||||
},
|
||||
locus: (6 + rng.gen_range(0, 3)).min(locus),
|
||||
border: 4,
|
||||
children: Vec::new(),
|
||||
@ -149,10 +205,11 @@ impl Archetype for House {
|
||||
};
|
||||
|
||||
let this = Self {
|
||||
roof_color: COLOR_THEMES
|
||||
.choose(rng)
|
||||
.unwrap()
|
||||
.map(|e| e.saturating_add(rng.gen_range(0, 20)) - 10),
|
||||
colors: ColorTheme {
|
||||
roof: *ROOF_COLORS.choose(rng).unwrap(),
|
||||
wall: *WALL_COLORS.choose(rng).unwrap(),
|
||||
support: *SUPPORT_COLORS.choose(rng).unwrap(),
|
||||
},
|
||||
noise: RandomField::new(rng.gen()),
|
||||
roof_ribbing: rng.gen(),
|
||||
roof_ribbing_diagonal: rng.gen(),
|
||||
@ -165,12 +222,15 @@ impl Archetype for House {
|
||||
#[allow(clippy::int_plus_one)] // TODO: Pending review in #587
|
||||
fn draw(
|
||||
&self,
|
||||
_pos: Vec3<i32>,
|
||||
dist: i32,
|
||||
bound_offset: Vec2<i32>,
|
||||
center_offset: Vec2<i32>,
|
||||
z: i32,
|
||||
ori: Ori,
|
||||
branch: &Branch<Self::Attr>,
|
||||
locus: i32,
|
||||
_len: i32,
|
||||
attr: &Self::Attr,
|
||||
) -> BlockMask {
|
||||
let profile = Vec2::new(bound_offset.x, z);
|
||||
|
||||
@ -185,7 +245,7 @@ impl Archetype for House {
|
||||
)
|
||||
};
|
||||
|
||||
let make_block = |r, g, b| {
|
||||
let make_block = |(r, g, b)| {
|
||||
let nz = self
|
||||
.noise
|
||||
.get(Vec3::new(center_offset.x, center_offset.y, z * 8));
|
||||
@ -205,32 +265,48 @@ impl Archetype for House {
|
||||
let foundation_layer = internal_layer + 1;
|
||||
let floor_layer = foundation_layer + 1;
|
||||
|
||||
let foundation = make_block(100, 100, 100).with_priority(foundation_layer);
|
||||
let log = make_block(60, 45, 30);
|
||||
let floor = make_block(100, 75, 50);
|
||||
let wall = make_block(200, 180, 150).with_priority(facade_layer);
|
||||
let roof = make_block(self.roof_color.r, self.roof_color.g, self.roof_color.b)
|
||||
.with_priority(facade_layer - 1);
|
||||
let foundation = make_block((100, 100, 100)).with_priority(foundation_layer);
|
||||
let log = make_block(self.colors.support.into_tuple());
|
||||
let floor = make_block((100, 75, 50));
|
||||
let wall = make_block(self.colors.wall.into_tuple()).with_priority(facade_layer);
|
||||
let roof = make_block(self.colors.roof.into_tuple()).with_priority(facade_layer - 1);
|
||||
let empty = BlockMask::nothing();
|
||||
let internal = BlockMask::new(Block::empty(), internal_layer);
|
||||
let end_window = BlockMask::new(
|
||||
Block::new(BlockKind::Window1, make_meta(ori.flip())),
|
||||
Block::new(attr.window, make_meta(ori.flip())),
|
||||
structural_layer,
|
||||
);
|
||||
let fire = BlockMask::new(Block::new(BlockKind::Ember, Rgb::white()), foundation_layer);
|
||||
|
||||
let ceil_height = 6;
|
||||
let lower_width = branch.locus - 1;
|
||||
let upper_width = branch.locus;
|
||||
let storey_height = 6;
|
||||
let storey = ((z - 1) / storey_height).min(attr.levels - 1);
|
||||
let floor_height = storey_height * storey;
|
||||
let ceil_height = storey_height * (storey + 1);
|
||||
let lower_width = locus - 1;
|
||||
let upper_width = locus;
|
||||
let width = if profile.y >= ceil_height {
|
||||
upper_width
|
||||
} else {
|
||||
lower_width
|
||||
};
|
||||
let foundation_height = 0 - (dist - width - 1).max(0);
|
||||
let roof_top = 8 + width;
|
||||
let roof_top = storey_height * attr.levels + 2 + width;
|
||||
|
||||
if let Pillar::Chimney(chimney_top) = branch.attr.pillar {
|
||||
let edge_ori = if bound_offset.x.abs() > bound_offset.y.abs() {
|
||||
if center_offset.x > 0 { 6 } else { 2 }
|
||||
} else if (center_offset.y > 0) ^ (ori == Ori::East) {
|
||||
0
|
||||
} else {
|
||||
4
|
||||
};
|
||||
let edge_ori = if ori == Ori::East {
|
||||
(edge_ori + 2) % 8
|
||||
} else {
|
||||
edge_ori
|
||||
};
|
||||
|
||||
if let Pillar::Chimney(chimney_height) = attr.pillar {
|
||||
let chimney_top = roof_top + chimney_height;
|
||||
// Chimney shaft
|
||||
if center_offset.map(|e| e.abs()).reduce_max() == 0
|
||||
&& profile.y >= foundation_height + 1
|
||||
@ -258,7 +334,7 @@ impl Archetype for House {
|
||||
|
||||
if profile.y <= foundation_height && dist < width + 3 {
|
||||
// Foundations
|
||||
if branch.attr.storey_fill.has_lower() {
|
||||
if attr.storey_fill.has_lower() {
|
||||
if dist == width - 1 {
|
||||
// Floor lining
|
||||
return log.with_priority(floor_layer);
|
||||
@ -281,7 +357,7 @@ impl Archetype for House {
|
||||
|profile: Vec2<i32>, width, dist, bound_offset: Vec2<i32>, roof_top, mansard| {
|
||||
// Roof
|
||||
|
||||
let (roof_profile, roof_dist) = match &branch.attr.roof_style {
|
||||
let (roof_profile, roof_dist) = match &attr.roof_style {
|
||||
RoofStyle::Hip => (Vec2::new(dist, profile.y), dist),
|
||||
RoofStyle::Gable => (profile, dist),
|
||||
RoofStyle::Rounded => {
|
||||
@ -320,13 +396,15 @@ impl Archetype for House {
|
||||
&& bound_offset.x > 0
|
||||
&& bound_offset.x < width
|
||||
&& profile.y < ceil_height
|
||||
&& branch.attr.storey_fill.has_lower()
|
||||
&& attr.storey_fill.has_lower()
|
||||
&& storey == 0
|
||||
{
|
||||
return Some(
|
||||
if (bound_offset.x == (width - 1) / 2
|
||||
|| bound_offset.x == (width - 1) / 2 + 1)
|
||||
&& profile.y <= foundation_height + 3
|
||||
{
|
||||
// Doors on first floor only
|
||||
if profile.y == foundation_height + 1 {
|
||||
BlockMask::new(
|
||||
Block::new(
|
||||
@ -351,9 +429,9 @@ impl Archetype for House {
|
||||
if bound_offset.x == bound_offset.y || profile.y == ceil_height {
|
||||
// Support beams
|
||||
return Some(log);
|
||||
} else if !branch.attr.storey_fill.has_lower() && profile.y < ceil_height {
|
||||
} else if !attr.storey_fill.has_lower() && profile.y < ceil_height {
|
||||
return Some(empty);
|
||||
} else if !branch.attr.storey_fill.has_upper() {
|
||||
} else if !attr.storey_fill.has_upper() {
|
||||
return Some(empty);
|
||||
} else {
|
||||
let (frame_bounds, frame_borders) = if profile.y >= ceil_height {
|
||||
@ -367,7 +445,7 @@ impl Archetype for House {
|
||||
} else {
|
||||
(
|
||||
Aabr {
|
||||
min: Vec2::new(2, foundation_height + 2),
|
||||
min: Vec2::new(2, floor_height + 2),
|
||||
max: Vec2::new(width - 2, ceil_height - 2),
|
||||
},
|
||||
Vec2::new(1, 0),
|
||||
@ -392,7 +470,7 @@ impl Archetype for House {
|
||||
}
|
||||
|
||||
// Wall
|
||||
return Some(if branch.attr.central_supports && profile.x == 0 {
|
||||
return Some(if attr.central_supports && profile.x == 0 {
|
||||
// Support beams
|
||||
log.with_priority(structural_layer)
|
||||
} else {
|
||||
@ -407,36 +485,88 @@ impl Archetype for House {
|
||||
if profile.x == 0 {
|
||||
// Rafters
|
||||
return Some(log);
|
||||
} else if branch.attr.storey_fill.has_upper() {
|
||||
} else if attr.storey_fill.has_upper() {
|
||||
// Ceiling
|
||||
return Some(floor);
|
||||
}
|
||||
} else if (!branch.attr.storey_fill.has_lower() && profile.y < ceil_height)
|
||||
|| (!branch.attr.storey_fill.has_upper() && profile.y >= ceil_height)
|
||||
} else if (!attr.storey_fill.has_lower() && profile.y < ceil_height)
|
||||
|| (!attr.storey_fill.has_upper() && profile.y >= ceil_height)
|
||||
{
|
||||
return Some(empty);
|
||||
// Furniture
|
||||
} else if dist == width - 1
|
||||
&& center_offset.sum() % 2 == 0
|
||||
&& profile.y == floor_height + 1
|
||||
&& self
|
||||
.noise
|
||||
.chance(Vec3::new(center_offset.x, center_offset.y, z), 0.2)
|
||||
{
|
||||
let furniture = match self.noise.get(Vec3::new(
|
||||
center_offset.x,
|
||||
center_offset.y,
|
||||
z + 100,
|
||||
)) % 11
|
||||
{
|
||||
0 => BlockKind::Planter,
|
||||
1 => BlockKind::ChairSingle,
|
||||
2 => BlockKind::ChairDouble,
|
||||
3 => BlockKind::CoatRack,
|
||||
4 => BlockKind::Crate,
|
||||
6 => BlockKind::DrawerMedium,
|
||||
7 => BlockKind::DrawerSmall,
|
||||
8 => BlockKind::TableSide,
|
||||
9 => BlockKind::WardrobeSingle,
|
||||
_ => BlockKind::Pot,
|
||||
};
|
||||
|
||||
return Some(BlockMask::new(
|
||||
Block::new(furniture, Rgb::new(edge_ori, 0, 0)),
|
||||
internal_layer,
|
||||
));
|
||||
} else {
|
||||
return Some(internal);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
// Wall ornaments
|
||||
if dist == width + 1
|
||||
&& center_offset.map(|e| e.abs()).reduce_min() == 0
|
||||
&& profile.y == floor_height + 3
|
||||
&& self
|
||||
.noise
|
||||
.chance(Vec3::new(center_offset.x, center_offset.y, z), 0.35)
|
||||
&& attr.storey_fill.has_lower()
|
||||
{
|
||||
let ornament =
|
||||
match self
|
||||
.noise
|
||||
.get(Vec3::new(center_offset.x, center_offset.y, z + 100))
|
||||
% 4
|
||||
{
|
||||
0 => BlockKind::HangingSign,
|
||||
1 | 2 | 3 => BlockKind::HangingBasket,
|
||||
_ => BlockKind::DungeonWallDecor,
|
||||
};
|
||||
|
||||
Some(BlockMask::new(
|
||||
Block::new(ornament, Rgb::new((edge_ori + 4) % 8, 0, 0)),
|
||||
internal_layer,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
let mut cblock = empty;
|
||||
|
||||
if let Some(block) = do_roof_wall(
|
||||
profile,
|
||||
width,
|
||||
dist,
|
||||
bound_offset,
|
||||
roof_top,
|
||||
branch.attr.mansard,
|
||||
) {
|
||||
if let Some(block) =
|
||||
do_roof_wall(profile, width, dist, bound_offset, roof_top, attr.mansard)
|
||||
{
|
||||
cblock = cblock.resolve_with(block);
|
||||
}
|
||||
|
||||
if let Pillar::Tower(tower_top) = branch.attr.pillar {
|
||||
if let Pillar::Tower(tower_height) = attr.pillar {
|
||||
let tower_top = roof_top + tower_height;
|
||||
let profile = Vec2::new(center_offset.x.abs(), profile.y);
|
||||
let dist = center_offset.map(|e| e.abs()).reduce_max();
|
||||
|
||||
@ -446,7 +576,7 @@ impl Archetype for House {
|
||||
dist,
|
||||
center_offset.map(|e| e.abs()),
|
||||
tower_top,
|
||||
branch.attr.mansard,
|
||||
attr.mansard,
|
||||
) {
|
||||
cblock = cblock.resolve_with(block);
|
||||
}
|
||||
|
@ -1,5 +1,8 @@
|
||||
use super::{super::skeleton::*, Archetype};
|
||||
use crate::site::BlockMask;
|
||||
use crate::{
|
||||
site::BlockMask,
|
||||
util::{RandomField, Sampler},
|
||||
};
|
||||
use common::{
|
||||
terrain::{Block, BlockKind},
|
||||
vol::Vox,
|
||||
@ -7,29 +10,56 @@ use common::{
|
||||
use rand::prelude::*;
|
||||
use vek::*;
|
||||
|
||||
pub struct Keep;
|
||||
pub struct Keep {
|
||||
pub flag_color: Rgb<u8>,
|
||||
pub stone_color: Rgb<u8>,
|
||||
}
|
||||
|
||||
pub struct Attr {
|
||||
pub storeys: i32,
|
||||
pub is_tower: bool,
|
||||
pub flag: bool,
|
||||
pub ridged: bool,
|
||||
pub rounded: bool,
|
||||
pub has_doors: bool,
|
||||
}
|
||||
|
||||
impl Archetype for Keep {
|
||||
type Attr = ();
|
||||
type Attr = Attr;
|
||||
|
||||
fn generate<R: Rng>(rng: &mut R) -> (Self, Skeleton<Self::Attr>) {
|
||||
let len = rng.gen_range(-8, 12).max(0);
|
||||
let len = rng.gen_range(-8, 24).max(0);
|
||||
let storeys = rng.gen_range(1, 3);
|
||||
let skel = Skeleton {
|
||||
offset: -rng.gen_range(0, len + 7).clamped(0, len),
|
||||
ori: if rng.gen() { Ori::East } else { Ori::North },
|
||||
root: Branch {
|
||||
len,
|
||||
attr: Self::Attr::default(),
|
||||
locus: 5 + rng.gen_range(0, 5),
|
||||
attr: Attr {
|
||||
storeys,
|
||||
is_tower: false,
|
||||
flag: false,
|
||||
ridged: false,
|
||||
rounded: true,
|
||||
has_doors: true,
|
||||
},
|
||||
locus: 10 + rng.gen_range(0, 5),
|
||||
border: 3,
|
||||
children: (0..rng.gen_range(0, 4))
|
||||
children: (0..1)
|
||||
.map(|_| {
|
||||
(
|
||||
rng.gen_range(-5, len + 5).clamped(0, len.max(1) - 1),
|
||||
Branch {
|
||||
len: rng.gen_range(5, 12) * if rng.gen() { 1 } else { -1 },
|
||||
attr: Self::Attr::default(),
|
||||
locus: 5 + rng.gen_range(0, 3),
|
||||
len: 0,
|
||||
attr: Attr {
|
||||
storeys: storeys + rng.gen_range(1, 3),
|
||||
is_tower: true,
|
||||
flag: true,
|
||||
ridged: false,
|
||||
rounded: true,
|
||||
has_doors: false,
|
||||
},
|
||||
locus: 6 + rng.gen_range(0, 3),
|
||||
border: 3,
|
||||
children: Vec::new(),
|
||||
},
|
||||
@ -39,44 +69,188 @@ impl Archetype for Keep {
|
||||
},
|
||||
};
|
||||
|
||||
(Self, skel)
|
||||
(
|
||||
Self {
|
||||
flag_color: Rgb::new(200, 80, 40),
|
||||
stone_color: Rgb::new(100, 100, 110),
|
||||
},
|
||||
skel,
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(clippy::if_same_then_else)] // TODO: Pending review in #587
|
||||
fn draw(
|
||||
&self,
|
||||
dist: i32,
|
||||
pos: Vec3<i32>,
|
||||
_dist: i32,
|
||||
bound_offset: Vec2<i32>,
|
||||
_center_offset: Vec2<i32>,
|
||||
center_offset: Vec2<i32>,
|
||||
z: i32,
|
||||
_ori: Ori,
|
||||
branch: &Branch<Self::Attr>,
|
||||
ori: Ori,
|
||||
locus: i32,
|
||||
_len: i32,
|
||||
attr: &Self::Attr,
|
||||
) -> BlockMask {
|
||||
let profile = Vec2::new(bound_offset.x, z);
|
||||
|
||||
let make_block =
|
||||
|r, g, b| BlockMask::new(Block::new(BlockKind::Normal, Rgb::new(r, g, b)), 2);
|
||||
let weak_layer = 1;
|
||||
let normal_layer = weak_layer + 1;
|
||||
let important_layer = normal_layer + 1;
|
||||
let internal_layer = important_layer + 1;
|
||||
|
||||
let foundation = make_block(100, 100, 100);
|
||||
let wall = make_block(75, 100, 125);
|
||||
let roof = make_block(150, 120, 50);
|
||||
let empty = BlockMask::new(Block::empty(), 2);
|
||||
let make_meta = |ori| {
|
||||
Rgb::new(
|
||||
match ori {
|
||||
Ori::East => 0,
|
||||
Ori::North => 2,
|
||||
},
|
||||
0,
|
||||
0,
|
||||
)
|
||||
};
|
||||
|
||||
let width = branch.locus;
|
||||
let rampart_width = 5 + branch.locus;
|
||||
let ceil_height = 16;
|
||||
let make_block = |r, g, b| {
|
||||
BlockMask::new(
|
||||
Block::new(BlockKind::Normal, Rgb::new(r, g, b)),
|
||||
normal_layer,
|
||||
)
|
||||
};
|
||||
|
||||
if profile.y <= 1 - (dist - width - 1).max(0) && dist < width + 3 {
|
||||
let brick_tex_pos = (pos + Vec3::new(pos.z, pos.z, 0)) / Vec3::new(2, 2, 1);
|
||||
let brick_tex = RandomField::new(0).get(brick_tex_pos) as u8 % 24;
|
||||
let foundation = make_block(80 + brick_tex, 80 + brick_tex, 80 + brick_tex);
|
||||
let wall = make_block(
|
||||
self.stone_color.r + brick_tex,
|
||||
self.stone_color.g + brick_tex,
|
||||
self.stone_color.b + brick_tex,
|
||||
);
|
||||
let window = BlockMask::new(
|
||||
Block::new(BlockKind::Window1, make_meta(ori.flip())),
|
||||
normal_layer,
|
||||
);
|
||||
let floor = make_block(
|
||||
80 + (pos.y.abs() % 2) as u8 * 15,
|
||||
60 + (pos.y.abs() % 2) as u8 * 15,
|
||||
10 + (pos.y.abs() % 2) as u8 * 15,
|
||||
)
|
||||
.with_priority(important_layer);
|
||||
let pole = make_block(90, 70, 50).with_priority(important_layer);
|
||||
let flag = make_block(self.flag_color.r, self.flag_color.g, self.flag_color.b)
|
||||
.with_priority(important_layer);
|
||||
let internal = BlockMask::new(Block::empty(), internal_layer);
|
||||
let empty = BlockMask::nothing();
|
||||
|
||||
let make_staircase = move |pos: Vec3<i32>, radius: f32, inner_radius: f32, stretch: f32| {
|
||||
let stone = BlockMask::new(Block::new(BlockKind::Normal, Rgb::new(150, 150, 175)), 5);
|
||||
|
||||
if (pos.xy().magnitude_squared() as f32) < inner_radius.powf(2.0) {
|
||||
stone
|
||||
} else if (pos.xy().magnitude_squared() as f32) < radius.powf(2.0) {
|
||||
if ((pos.x as f32).atan2(pos.y as f32) / (std::f32::consts::PI * 2.0) * stretch
|
||||
+ pos.z as f32)
|
||||
.rem_euclid(stretch)
|
||||
< 1.5
|
||||
{
|
||||
stone
|
||||
} else {
|
||||
internal
|
||||
}
|
||||
} else {
|
||||
BlockMask::nothing()
|
||||
}
|
||||
};
|
||||
|
||||
let ridge_x = (center_offset.map(|e| e.abs()).reduce_min() + 2) % 8;
|
||||
let width = locus
|
||||
+ if ridge_x < 4 && attr.ridged && !attr.rounded {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let rampart_width = 2 + width;
|
||||
let storey_height = 9;
|
||||
let roof_height = attr.storeys * storey_height;
|
||||
let storey_y = profile.y % storey_height;
|
||||
let door_height = 6;
|
||||
let rampart_height = roof_height + if ridge_x % 2 == 0 { 3 } else { 4 };
|
||||
let min_dist = if attr.rounded {
|
||||
bound_offset.map(|e| e.pow(2) as f32).sum().powf(0.5) as i32
|
||||
} else {
|
||||
bound_offset.map(|e| e.abs()).reduce_max()
|
||||
};
|
||||
|
||||
if profile.y <= 0 - (min_dist - width - 1).max(0) && min_dist < width + 3 {
|
||||
// Foundations
|
||||
foundation
|
||||
} else if profile.y == ceil_height && dist < rampart_width {
|
||||
roof
|
||||
} else if dist == rampart_width && profile.y >= ceil_height && profile.y < ceil_height + 4 {
|
||||
wall
|
||||
} else if dist == width && profile.y <= ceil_height {
|
||||
wall
|
||||
} else if (0..=roof_height).contains(&profile.y) && storey_y == 0 && min_dist <= width + 1 {
|
||||
if min_dist < width { floor } else { wall }
|
||||
} else if bound_offset.x.abs() < 3
|
||||
&& profile.y < door_height - bound_offset.x.abs()
|
||||
&& profile.y > 0
|
||||
&& min_dist >= width - 2
|
||||
&& min_dist <= width + 1
|
||||
&& attr.has_doors
|
||||
{
|
||||
internal
|
||||
} else if (min_dist == width || (!attr.is_tower && min_dist == width + 1))
|
||||
&& profile.y <= roof_height
|
||||
{
|
||||
if attr.is_tower
|
||||
&& (3..7).contains(&storey_y)
|
||||
&& bound_offset.x.abs() < width - 2
|
||||
&& (5..7).contains(&ridge_x)
|
||||
{
|
||||
window
|
||||
} else {
|
||||
wall
|
||||
}
|
||||
} else if profile.y >= roof_height {
|
||||
if profile.y > roof_height
|
||||
&& (min_dist < rampart_width - 1 || (attr.is_tower && min_dist < rampart_width))
|
||||
{
|
||||
if attr.is_tower
|
||||
&& attr.flag
|
||||
&& center_offset == Vec2::zero()
|
||||
&& profile.y < roof_height + 16
|
||||
{
|
||||
pole
|
||||
} else if attr.is_tower
|
||||
&& attr.flag
|
||||
&& center_offset.x == 0
|
||||
&& center_offset.y > 0
|
||||
&& center_offset.y < 8
|
||||
&& profile.y > roof_height + 8
|
||||
&& profile.y < roof_height + 14
|
||||
{
|
||||
flag
|
||||
} else {
|
||||
empty
|
||||
}
|
||||
} else if min_dist <= rampart_width {
|
||||
if profile.y < rampart_height {
|
||||
wall
|
||||
} else {
|
||||
empty
|
||||
}
|
||||
} else {
|
||||
empty
|
||||
}
|
||||
} else if profile.y < roof_height && min_dist < width {
|
||||
internal
|
||||
} else {
|
||||
empty
|
||||
}
|
||||
.resolve_with(
|
||||
if attr.is_tower && profile.y > 0 && profile.y <= roof_height {
|
||||
make_staircase(
|
||||
Vec3::new(center_offset.x, center_offset.y, pos.z),
|
||||
7.0f32.min(width as f32 - 1.0),
|
||||
0.5,
|
||||
9.0,
|
||||
)
|
||||
} else {
|
||||
BlockMask::nothing()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -12,13 +12,18 @@ pub trait Archetype {
|
||||
fn generate<R: Rng>(rng: &mut R) -> (Self, Skeleton<Self::Attr>)
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn draw(
|
||||
&self,
|
||||
pos: Vec3<i32>,
|
||||
dist: i32,
|
||||
bound_offset: Vec2<i32>,
|
||||
center_offset: Vec2<i32>,
|
||||
z: i32,
|
||||
ori: Ori,
|
||||
branch: &Branch<Self::Attr>,
|
||||
locus: i32,
|
||||
len: i32,
|
||||
attr: &Self::Attr,
|
||||
) -> BlockMask;
|
||||
}
|
||||
|
@ -1,16 +1,16 @@
|
||||
mod archetype;
|
||||
mod skeleton;
|
||||
pub mod archetype;
|
||||
pub mod skeleton;
|
||||
|
||||
// Reexports
|
||||
pub use self::archetype::Archetype;
|
||||
pub use self::{
|
||||
archetype::{house::House, keep::Keep, Archetype},
|
||||
skeleton::*,
|
||||
};
|
||||
|
||||
use self::skeleton::*;
|
||||
use common::terrain::Block;
|
||||
use rand::prelude::*;
|
||||
use vek::*;
|
||||
|
||||
pub type HouseBuilding = Building<archetype::house::House>;
|
||||
|
||||
pub struct Building<A: Archetype> {
|
||||
skel: Skeleton<A::Attr>,
|
||||
archetype: A,
|
||||
@ -42,7 +42,7 @@ impl<A: Archetype> Building<A> {
|
||||
let aabr = self.bounds_2d();
|
||||
Aabb {
|
||||
min: Vec3::from(aabr.min) + Vec3::unit_z() * (self.origin.z - 8),
|
||||
max: Vec3::from(aabr.max) + Vec3::unit_z() * (self.origin.z + 32),
|
||||
max: Vec3::from(aabr.max) + Vec3::unit_z() * (self.origin.z + 48),
|
||||
}
|
||||
}
|
||||
|
||||
@ -50,10 +50,19 @@ impl<A: Archetype> Building<A> {
|
||||
let rpos = pos - self.origin;
|
||||
self.skel
|
||||
.sample_closest(
|
||||
rpos.into(),
|
||||
|dist, bound_offset, center_offset, ori, branch| {
|
||||
self.archetype
|
||||
.draw(dist, bound_offset, center_offset, rpos.z, ori, branch)
|
||||
rpos,
|
||||
|pos, dist, bound_offset, center_offset, ori, branch| {
|
||||
self.archetype.draw(
|
||||
pos,
|
||||
dist,
|
||||
bound_offset,
|
||||
center_offset,
|
||||
rpos.z,
|
||||
ori,
|
||||
branch.locus,
|
||||
branch.len,
|
||||
&branch.attr,
|
||||
)
|
||||
},
|
||||
)
|
||||
.finish()
|
||||
|
@ -76,8 +76,8 @@ impl<T> Skeleton<T> {
|
||||
#[allow(clippy::or_fun_call)] // TODO: Pending review in #587
|
||||
pub fn sample_closest(
|
||||
&self,
|
||||
pos: Vec2<i32>,
|
||||
mut f: impl FnMut(i32, Vec2<i32>, Vec2<i32>, Ori, &Branch<T>) -> BlockMask,
|
||||
pos: Vec3<i32>,
|
||||
mut f: impl FnMut(Vec3<i32>, i32, Vec2<i32>, Vec2<i32>, Ori, &Branch<T>) -> BlockMask,
|
||||
) -> BlockMask {
|
||||
let mut min = None::<(_, BlockMask)>;
|
||||
self.for_each(|node, ori, branch, is_child, parent_locus| {
|
||||
@ -117,7 +117,7 @@ impl<T> Skeleton<T> {
|
||||
}
|
||||
|| true
|
||||
{
|
||||
let new_bm = f(dist, bound_offset, center_offset, ori, branch);
|
||||
let new_bm = f(pos, dist, bound_offset, center_offset, ori, branch);
|
||||
min = min
|
||||
.map(|(_, bm)| (dist_locus, bm.resolve_with(new_bm)))
|
||||
.or(Some((dist_locus, new_bm)));
|
||||
|
@ -1,6 +1,10 @@
|
||||
mod building;
|
||||
pub mod building;
|
||||
mod town;
|
||||
|
||||
use self::building::HouseBuilding;
|
||||
use self::{
|
||||
building::{Building, House, Keep},
|
||||
town::{District, Town},
|
||||
};
|
||||
use super::SpawnRules;
|
||||
use crate::{
|
||||
column::ColumnSample,
|
||||
@ -82,7 +86,8 @@ const AREA_SIZE: u32 = 32;
|
||||
fn to_tile(e: i32) -> i32 { ((e as f32).div_euclid(AREA_SIZE as f32)).floor() as i32 }
|
||||
|
||||
pub enum StructureKind {
|
||||
House(HouseBuilding),
|
||||
House(Building<House>),
|
||||
Keep(Building<Keep>),
|
||||
}
|
||||
|
||||
pub struct Structure {
|
||||
@ -93,6 +98,21 @@ impl Structure {
|
||||
pub fn bounds_2d(&self) -> Aabr<i32> {
|
||||
match &self.kind {
|
||||
StructureKind::House(house) => house.bounds_2d(),
|
||||
StructureKind::Keep(keep) => keep.bounds_2d(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bounds(&self) -> Aabb<i32> {
|
||||
match &self.kind {
|
||||
StructureKind::House(house) => house.bounds(),
|
||||
StructureKind::Keep(keep) => keep.bounds(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sample(&self, rpos: Vec3<i32>) -> Option<Block> {
|
||||
match &self.kind {
|
||||
StructureKind::House(house) => house.sample(rpos),
|
||||
StructureKind::Keep(keep) => keep.sample(rpos),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -107,10 +127,6 @@ pub struct Settlement {
|
||||
noise: RandomField,
|
||||
}
|
||||
|
||||
pub struct Town {
|
||||
base_tile: Vec2<i32>,
|
||||
}
|
||||
|
||||
pub struct Farm {
|
||||
#[allow(dead_code)]
|
||||
base_tile: Vec2<i32>,
|
||||
@ -216,7 +232,7 @@ impl Settlement {
|
||||
for _ in 0..PATH_COUNT {
|
||||
dir = (Vec2::new(rng.gen::<f32>() - 0.5, rng.gen::<f32>() - 0.5) * 2.0 - dir)
|
||||
.try_normalized()
|
||||
.unwrap_or(Vec2::zero());
|
||||
.unwrap_or_else(Vec2::zero);
|
||||
let origin = dir.map(|e| (e * 100.0) as i32);
|
||||
let origin = self
|
||||
.land
|
||||
@ -260,12 +276,28 @@ impl Settlement {
|
||||
Some(Plot::Dirt) => true,
|
||||
_ => false,
|
||||
}) {
|
||||
self.land
|
||||
.plot_at_mut(base_tile)
|
||||
.map(|plot| *plot = Plot::Town);
|
||||
// self.land
|
||||
// .plot_at_mut(base_tile)
|
||||
// .map(|plot| *plot = Plot::Town { district: None });
|
||||
|
||||
if i == 0 {
|
||||
self.town = Some(Town { base_tile });
|
||||
let town = Town::generate(self.origin, base_tile, ctx);
|
||||
|
||||
for (id, district) in town.districts().iter() {
|
||||
let district_plot =
|
||||
self.land.plots.insert(Plot::Town { district: Some(id) });
|
||||
|
||||
for x in district.aabr.min.x..district.aabr.max.x {
|
||||
for y in district.aabr.min.y..district.aabr.max.y {
|
||||
if !matches!(self.land.plot_at(Vec2::new(x, y)), Some(Plot::Hazard))
|
||||
{
|
||||
self.land.set(Vec2::new(x, y), district_plot);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.town = Some(town);
|
||||
origin = base_tile;
|
||||
}
|
||||
}
|
||||
@ -331,42 +363,56 @@ impl Settlement {
|
||||
.take(16usize.pow(2))
|
||||
{
|
||||
// This is a stupid way to decide how to place buildings
|
||||
for _ in 0..ctx.rng.gen_range(2, 5) {
|
||||
for i in 0..ctx.rng.gen_range(2, 5) {
|
||||
for _ in 0..25 {
|
||||
let house_pos = tile.map(|e| e * AREA_SIZE as i32 + AREA_SIZE as i32 / 2)
|
||||
+ Vec2::<i32>::zero().map(|_| {
|
||||
ctx.rng
|
||||
.gen_range(-(AREA_SIZE as i32) / 2, AREA_SIZE as i32 / 2)
|
||||
.gen_range(-(AREA_SIZE as i32) / 4, AREA_SIZE as i32 / 4)
|
||||
});
|
||||
|
||||
let tile_pos = house_pos.map(|e| e.div_euclid(AREA_SIZE as i32));
|
||||
if !matches!(self.land.plot_at(tile_pos), Some(Plot::Town))
|
||||
|| self
|
||||
.land
|
||||
.tile_at(tile_pos)
|
||||
.map(|t| t.contains(WayKind::Path))
|
||||
.unwrap_or(true)
|
||||
if self
|
||||
.land
|
||||
.tile_at(tile_pos)
|
||||
.map(|t| t.contains(WayKind::Path))
|
||||
.unwrap_or(true)
|
||||
|| ctx
|
||||
.sim
|
||||
.and_then(|sim| sim.get_nearest_path(self.origin + house_pos))
|
||||
.map(|(dist, _)| dist < 28.0)
|
||||
.map(|(dist, _, _, _)| dist < 28.0)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let structure = Structure {
|
||||
kind: StructureKind::House(HouseBuilding::generate(
|
||||
ctx.rng,
|
||||
Vec3::new(
|
||||
house_pos.x,
|
||||
house_pos.y,
|
||||
let alt = if let Some(Plot::Town { district }) = self.land.plot_at(tile_pos) {
|
||||
district
|
||||
.and_then(|d| self.town.as_ref().map(|t| t.districts().get(d)))
|
||||
.map(|d| d.alt)
|
||||
.filter(|_| false) // Temporary
|
||||
.unwrap_or_else(|| {
|
||||
ctx.sim
|
||||
.and_then(|sim| sim.get_alt_approx(self.origin + house_pos))
|
||||
.unwrap_or(0.0)
|
||||
.ceil() as i32,
|
||||
),
|
||||
)),
|
||||
.ceil() as i32
|
||||
})
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let structure = Structure {
|
||||
kind: if tile == town_center && i == 0 {
|
||||
StructureKind::Keep(Building::<Keep>::generate(
|
||||
ctx.rng,
|
||||
Vec3::new(house_pos.x, house_pos.y, alt),
|
||||
))
|
||||
} else {
|
||||
StructureKind::House(Building::<House>::generate(
|
||||
ctx.rng,
|
||||
Vec3::new(house_pos.x, house_pos.y, alt),
|
||||
))
|
||||
},
|
||||
};
|
||||
|
||||
let bounds = structure.bounds_2d();
|
||||
@ -501,12 +547,13 @@ impl Settlement {
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
let surface_z = col_sample.riverless_alt.floor() as i32;
|
||||
let land_surface_z = col_sample.riverless_alt.floor() as i32;
|
||||
let mut surface_z = land_surface_z;
|
||||
|
||||
// Sample settlement
|
||||
let sample = self.land.get_at_block(rpos);
|
||||
|
||||
let noisy_color = |col: Rgb<u8>, factor: u32| {
|
||||
let noisy_color = move |col: Rgb<u8>, factor: u32| {
|
||||
let nz = self.noise.get(Vec3::new(wpos2d.x, wpos2d.y, surface_z));
|
||||
col.map(|e| {
|
||||
(e as u32 + nz % (factor * 2))
|
||||
@ -515,46 +562,75 @@ impl Settlement {
|
||||
})
|
||||
};
|
||||
|
||||
// District alt
|
||||
if let Some(Plot::Town { district }) = sample.plot {
|
||||
if let Some(d) = district
|
||||
.and_then(|d| self.town.as_ref().map(|t| t.districts().get(d)))
|
||||
.filter(|_| false)
|
||||
// Temporary
|
||||
{
|
||||
let other = self
|
||||
.land
|
||||
.plot_at(sample.second_closest)
|
||||
.and_then(|p| match p {
|
||||
Plot::Town { district } => *district,
|
||||
_ => None,
|
||||
})
|
||||
.and_then(|d| {
|
||||
self.town.as_ref().map(|t| t.districts().get(d).alt as f32)
|
||||
})
|
||||
.filter(|_| false)
|
||||
.unwrap_or(surface_z as f32);
|
||||
surface_z = Lerp::lerp(
|
||||
(other + d.alt as f32) / 2.0,
|
||||
d.alt as f32,
|
||||
(1.25 * sample.edge_dist / (d.alt as f32 - other).abs()).min(1.0),
|
||||
) as i32;
|
||||
}
|
||||
}
|
||||
|
||||
// Paths
|
||||
if let Some((WayKind::Path, dist, nearest)) = sample.way {
|
||||
let inset = -1;
|
||||
// if let Some((WayKind::Path, dist, nearest)) = sample.way {
|
||||
// let inset = -1;
|
||||
|
||||
// Try to use the column at the centre of the path for sampling to make them
|
||||
// flatter
|
||||
let col = get_column(offs + (nearest.floor().map(|e| e as i32) - rpos))
|
||||
.unwrap_or(col_sample);
|
||||
let (bridge_offset, depth) = if let Some(water_dist) = col.water_dist {
|
||||
(
|
||||
((water_dist.max(0.0) * 0.2).min(f32::consts::PI).cos() + 1.0) * 5.0,
|
||||
((1.0 - ((water_dist + 2.0) * 0.3).min(0.0).cos().abs())
|
||||
* (col.riverless_alt + 5.0 - col.alt).max(0.0)
|
||||
* 1.75
|
||||
+ 3.0) as i32,
|
||||
)
|
||||
} else {
|
||||
(0.0, 3)
|
||||
};
|
||||
let surface_z = (col.riverless_alt + bridge_offset).floor() as i32;
|
||||
// // Try to use the column at the centre of the path for sampling to make
|
||||
// them // flatter
|
||||
// let col = get_column(offs + (nearest.floor().map(|e| e as i32) - rpos))
|
||||
// .unwrap_or(col_sample);
|
||||
// let (bridge_offset, depth) = if let Some(water_dist) = col.water_dist {
|
||||
// (
|
||||
// ((water_dist.max(0.0) * 0.2).min(f32::consts::PI).cos() + 1.0) *
|
||||
// 5.0, ((1.0 - ((water_dist + 2.0) *
|
||||
// 0.3).min(0.0).cos().abs())
|
||||
// * (col.riverless_alt + 5.0 - col.alt).max(0.0)
|
||||
// * 1.75
|
||||
// + 3.0) as i32,
|
||||
// )
|
||||
// } else {
|
||||
// (0.0, 3)
|
||||
// };
|
||||
// let surface_z = (col.riverless_alt + bridge_offset).floor() as i32;
|
||||
|
||||
for z in inset - depth..inset {
|
||||
let _ = vol.set(
|
||||
Vec3::new(offs.x, offs.y, surface_z + z),
|
||||
if bridge_offset >= 2.0 && dist >= 3.0 || z < inset - 1 {
|
||||
Block::new(BlockKind::Normal, noisy_color(Rgb::new(80, 80, 100), 8))
|
||||
} else {
|
||||
Block::new(BlockKind::Normal, noisy_color(Rgb::new(80, 50, 30), 8))
|
||||
},
|
||||
);
|
||||
}
|
||||
let head_space = (8 - (dist * 0.25).powf(6.0).round() as i32).max(1);
|
||||
for z in inset..inset + head_space {
|
||||
let pos = Vec3::new(offs.x, offs.y, surface_z + z);
|
||||
if vol.get(pos).unwrap().kind() != BlockKind::Water {
|
||||
let _ = vol.set(pos, Block::empty());
|
||||
}
|
||||
}
|
||||
// Ground colour
|
||||
} else {
|
||||
// for z in inset - depth..inset {
|
||||
// let _ = vol.set(
|
||||
// Vec3::new(offs.x, offs.y, surface_z + z),
|
||||
// if bridge_offset >= 2.0 && dist >= 3.0 || z < inset - 1 {
|
||||
// Block::new(BlockKind::Normal, noisy_color(Rgb::new(80, 80,
|
||||
// 100), 8)) } else {
|
||||
// Block::new(BlockKind::Normal, noisy_color(Rgb::new(80, 50,
|
||||
// 30), 8)) },
|
||||
// );
|
||||
// }
|
||||
// let head_space = (8 - (dist * 0.25).powf(6.0).round() as i32).max(1);
|
||||
// for z in inset..inset + head_space {
|
||||
// let pos = Vec3::new(offs.x, offs.y, surface_z + z);
|
||||
// if vol.get(pos).unwrap().kind() != BlockKind::Water {
|
||||
// let _ = vol.set(pos, Block::empty());
|
||||
// }
|
||||
// }
|
||||
// // Ground colour
|
||||
// } else
|
||||
{
|
||||
let mut surface_block = None;
|
||||
|
||||
let roll =
|
||||
@ -564,27 +640,29 @@ impl Settlement {
|
||||
Some(Plot::Dirt) => Some(Rgb::new(90, 70, 50)),
|
||||
Some(Plot::Grass) => Some(Rgb::new(100, 200, 0)),
|
||||
Some(Plot::Water) => Some(Rgb::new(100, 150, 250)),
|
||||
Some(Plot::Town) => {
|
||||
if let Some((_, path_nearest)) = col_sample.path {
|
||||
//Some(Plot::Town { district }) => None,
|
||||
Some(Plot::Town { .. }) => {
|
||||
if let Some((_, path_nearest, _, _)) = col_sample.path {
|
||||
let path_dir = (path_nearest - wpos2d.map(|e| e as f32))
|
||||
.rotated_z(f32::consts::PI / 2.0)
|
||||
.normalized();
|
||||
let is_lamp = if path_dir.x.abs() > path_dir.y.abs() {
|
||||
wpos2d.x as f32 % 20.0 / path_dir.dot(Vec2::unit_y()).abs()
|
||||
wpos2d.x as f32 % 30.0 / path_dir.dot(Vec2::unit_y()).abs()
|
||||
<= 1.0
|
||||
} else {
|
||||
wpos2d.y as f32 % 20.0 / path_dir.dot(Vec2::unit_x()).abs()
|
||||
(wpos2d.y as f32 + 10.0) % 30.0
|
||||
/ path_dir.dot(Vec2::unit_x()).abs()
|
||||
<= 1.0
|
||||
};
|
||||
if (col_sample.path.map(|(dist, _)| dist > 6.0 && dist < 7.0).unwrap_or(false) && is_lamp) //roll(0, 50) == 0)
|
||||
|| roll(0, 2000) == 0
|
||||
if (col_sample.path.map(|(dist, _, _, _)| dist > 6.0 && dist < 7.0).unwrap_or(false) && is_lamp) //roll(0, 50) == 0)
|
||||
|| (roll(0, 2000) == 0 && col_sample.path.map(|(dist, _, _, _)| dist > 20.0).unwrap_or(true))
|
||||
{
|
||||
surface_block =
|
||||
Some(Block::new(BlockKind::StreetLamp, Rgb::white()));
|
||||
}
|
||||
}
|
||||
|
||||
Some(Rgb::new(100, 90, 75).map2(Rgb::iota(), |e: u8, i: i32| {
|
||||
Some(Rgb::new(100, 95, 65).map2(Rgb::iota(), |e: u8, i: i32| {
|
||||
e.saturating_add(
|
||||
(self.noise.get(Vec3::new(wpos2d.x, wpos2d.y, i * 5)) % 1)
|
||||
as u8,
|
||||
@ -657,14 +735,27 @@ impl Settlement {
|
||||
};
|
||||
|
||||
if let Some(color) = color {
|
||||
if col_sample.water_dist.map(|dist| dist > 2.0).unwrap_or(true) {
|
||||
for z in -8..3 {
|
||||
let is_path = col_sample
|
||||
.path
|
||||
.map(|(dist, _, path, _)| dist < path.width)
|
||||
.unwrap_or(false);
|
||||
|
||||
if col_sample.water_dist.map(|dist| dist > 2.0).unwrap_or(true) && !is_path
|
||||
{
|
||||
let diff = (surface_z - land_surface_z).abs();
|
||||
|
||||
for z in -8 - diff..4 + diff {
|
||||
let pos = Vec3::new(offs.x, offs.y, surface_z + z);
|
||||
let block = vol.get(pos).ok().copied().unwrap_or_else(Block::empty);
|
||||
|
||||
if block.kind() == BlockKind::Air {
|
||||
break;
|
||||
}
|
||||
|
||||
if let (0, Some(block)) = (z, surface_block) {
|
||||
let _ = vol.set(pos, block);
|
||||
} else if z >= 0 {
|
||||
if vol.get(pos).unwrap().kind() != BlockKind::Water {
|
||||
if block.kind() != BlockKind::Water {
|
||||
let _ = vol.set(pos, Block::empty());
|
||||
}
|
||||
} else {
|
||||
@ -728,33 +819,27 @@ impl Settlement {
|
||||
continue;
|
||||
}
|
||||
|
||||
match &structure.kind {
|
||||
StructureKind::House(b) => {
|
||||
let bounds = b.bounds();
|
||||
let bounds = structure.bounds();
|
||||
|
||||
for x in bounds.min.x..bounds.max.x + 1 {
|
||||
for y in bounds.min.y..bounds.max.y + 1 {
|
||||
let col = if let Some(col) =
|
||||
get_column(self.origin + Vec2::new(x, y) - wpos2d)
|
||||
{
|
||||
col
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
for x in bounds.min.x..bounds.max.x + 1 {
|
||||
for y in bounds.min.y..bounds.max.y + 1 {
|
||||
let col = if let Some(col) = get_column(self.origin + Vec2::new(x, y) - wpos2d)
|
||||
{
|
||||
col
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
|
||||
for z in bounds.min.z.min(col.alt.floor() as i32 - 1)..bounds.max.z + 1
|
||||
{
|
||||
let rpos = Vec3::new(x, y, z);
|
||||
let wpos = Vec3::from(self.origin) + rpos;
|
||||
let coffs = wpos - Vec3::from(wpos2d);
|
||||
for z in bounds.min.z.min(col.alt.floor() as i32 - 1)..bounds.max.z + 1 {
|
||||
let rpos = Vec3::new(x, y, z);
|
||||
let wpos = Vec3::from(self.origin) + rpos;
|
||||
let coffs = wpos - Vec3::from(wpos2d);
|
||||
|
||||
if let Some(block) = b.sample(rpos) {
|
||||
let _ = vol.set(coffs, block);
|
||||
}
|
||||
}
|
||||
if let Some(block) = structure.sample(rpos) {
|
||||
let _ = vol.set(coffs, block);
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -785,7 +870,7 @@ impl Settlement {
|
||||
|
||||
let entity_wpos = Vec3::new(wpos2d.x as f32, wpos2d.y as f32, col_sample.alt + 3.0);
|
||||
|
||||
if matches!(sample.plot, Some(Plot::Town))
|
||||
if matches!(sample.plot, Some(Plot::Town { .. }))
|
||||
&& RandomField::new(self.seed).chance(Vec3::from(wpos2d), 1.0 / (50.0 * 50.0))
|
||||
{
|
||||
let is_human: bool;
|
||||
@ -875,7 +960,7 @@ impl Settlement {
|
||||
Some(Plot::Dirt) => return Some(Rgb::new(90, 70, 50)),
|
||||
Some(Plot::Grass) => return Some(Rgb::new(100, 200, 0)),
|
||||
Some(Plot::Water) => return Some(Rgb::new(100, 150, 250)),
|
||||
Some(Plot::Town) => {
|
||||
Some(Plot::Town { .. }) => {
|
||||
return Some(Rgb::new(150, 110, 60).map2(Rgb::iota(), |e: u8, i: i32| {
|
||||
e.saturating_add((self.noise.get(Vec3::new(pos.x, pos.y, i * 5)) % 16) as u8)
|
||||
.saturating_sub(8)
|
||||
@ -927,7 +1012,9 @@ pub enum Plot {
|
||||
Dirt,
|
||||
Grass,
|
||||
Water,
|
||||
Town,
|
||||
Town {
|
||||
district: Option<Id<District>>,
|
||||
},
|
||||
Field {
|
||||
farm: Id<Farm>,
|
||||
seed: u32,
|
||||
@ -987,6 +1074,8 @@ pub struct Sample<'a> {
|
||||
plot: Option<&'a Plot>,
|
||||
way: Option<(&'a WayKind, f32, Vec2<f32>)>,
|
||||
tower: Option<(&'a Tower, Vec2<i32>)>,
|
||||
edge_dist: f32,
|
||||
second_closest: Vec2<i32>,
|
||||
}
|
||||
|
||||
pub struct Land {
|
||||
@ -1021,6 +1110,15 @@ impl Land {
|
||||
.min_by_key(|(center, _)| center.distance_squared(pos))
|
||||
.unwrap()
|
||||
.0;
|
||||
let second_closest = neighbors
|
||||
.iter()
|
||||
.filter(|(center, _)| *center != closest)
|
||||
.min_by_key(|(center, _)| center.distance_squared(pos))
|
||||
.unwrap()
|
||||
.0;
|
||||
sample.second_closest = second_closest.map(to_tile);
|
||||
sample.edge_dist = (second_closest - pos).map(|e| e as f32).magnitude()
|
||||
- (closest - pos).map(|e| e as f32).magnitude();
|
||||
|
||||
let center_tile = self.tile_at(neighbors[4].0.map(to_tile));
|
||||
|
||||
@ -1064,6 +1162,7 @@ impl Land {
|
||||
self.tiles.get(&pos).map(|tile| self.plots.get(tile.plot))
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn plot_at_mut(&mut self, pos: Vec2<i32>) -> Option<&mut Plot> {
|
||||
self.tiles
|
||||
.get(&pos)
|
||||
|
82
world/src/site/settlement/town.rs
Normal file
82
world/src/site/settlement/town.rs
Normal file
@ -0,0 +1,82 @@
|
||||
use super::{GenCtx, AREA_SIZE};
|
||||
use common::store::Store;
|
||||
use rand::prelude::*;
|
||||
use vek::*;
|
||||
|
||||
pub struct Town {
|
||||
pub base_tile: Vec2<i32>,
|
||||
radius: i32,
|
||||
districts: Store<District>,
|
||||
}
|
||||
|
||||
impl Town {
|
||||
pub fn districts(&self) -> &Store<District> { &self.districts }
|
||||
|
||||
pub fn generate(origin: Vec2<i32>, base_tile: Vec2<i32>, ctx: &mut GenCtx<impl Rng>) -> Self {
|
||||
let mut this = Self {
|
||||
base_tile,
|
||||
radius: 4,
|
||||
districts: Store::default(),
|
||||
};
|
||||
|
||||
this.generate_districts(origin, ctx);
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
fn generate_districts(&mut self, origin: Vec2<i32>, ctx: &mut GenCtx<impl Rng>) {
|
||||
let base_aabr = Aabr {
|
||||
min: self.base_tile - self.radius,
|
||||
max: self.base_tile + self.radius,
|
||||
};
|
||||
|
||||
gen_plot(base_aabr, ctx).for_each(base_aabr, &mut |aabr| {
|
||||
if aabr.center().distance_squared(self.base_tile) < self.radius.pow(2) {
|
||||
self.districts.insert(District {
|
||||
seed: ctx.rng.gen(),
|
||||
aabr,
|
||||
alt: ctx
|
||||
.sim
|
||||
.and_then(|sim| {
|
||||
sim.get_alt_approx(
|
||||
origin + aabr.center() * AREA_SIZE as i32 + AREA_SIZE as i32 / 2,
|
||||
)
|
||||
})
|
||||
.unwrap_or(0.0) as i32,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub struct District {
|
||||
pub seed: u32,
|
||||
pub aabr: Aabr<i32>,
|
||||
pub alt: i32,
|
||||
}
|
||||
|
||||
enum Plot {
|
||||
District,
|
||||
Parent(Vec<(Aabr<i32>, Plot)>),
|
||||
}
|
||||
|
||||
impl Plot {
|
||||
fn for_each(&self, aabr: Aabr<i32>, f: &mut impl FnMut(Aabr<i32>)) {
|
||||
match self {
|
||||
Plot::District => f(aabr),
|
||||
Plot::Parent(children) => children.iter().for_each(|(aabr, p)| p.for_each(*aabr, f)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn gen_plot(aabr: Aabr<i32>, ctx: &mut GenCtx<impl Rng>) -> Plot {
|
||||
if aabr.size().product() <= 9 {
|
||||
Plot::District
|
||||
} else if aabr.size().w < aabr.size().h {
|
||||
let [a, b] = aabr.split_at_y(aabr.min.y + ctx.rng.gen_range(1, aabr.size().h));
|
||||
Plot::Parent(vec![(a, gen_plot(a, ctx)), (b, gen_plot(b, ctx))])
|
||||
} else {
|
||||
let [a, b] = aabr.split_at_x(aabr.min.x + ctx.rng.gen_range(1, aabr.size().w));
|
||||
Plot::Parent(vec![(a, gen_plot(a, ctx)), (b, gen_plot(b, ctx))])
|
||||
}
|
||||
}
|
79
world/src/util/map_vec.rs
Normal file
79
world/src/util/map_vec.rs
Normal file
@ -0,0 +1,79 @@
|
||||
use crate::util::DHashMap;
|
||||
use std::hash::Hash;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MapVec<K, T> {
|
||||
/// We use this hasher (FxHasher32) because
|
||||
/// (1) we don't care about DDOS attacks (ruling out SipHash);
|
||||
/// (2) we care about determinism across computers (ruling out AAHash);
|
||||
/// (3) we have 1-byte keys (for which FxHash is supposedly fastest).
|
||||
entries: DHashMap<K, T>,
|
||||
default: T,
|
||||
}
|
||||
|
||||
/// Need manual implementation of Default since K doesn't need that bound.
|
||||
impl<K, T: Default> Default for MapVec<K, T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
entries: Default::default(),
|
||||
default: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: Copy + Eq + Hash, T: Clone> MapVec<K, T> {
|
||||
pub fn from_list<'a>(i: impl IntoIterator<Item = &'a (K, T)>, default: T) -> Self
|
||||
where
|
||||
K: 'a,
|
||||
T: 'a,
|
||||
{
|
||||
Self {
|
||||
entries: i.into_iter().cloned().collect(),
|
||||
default,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_default(default: T) -> Self {
|
||||
Self {
|
||||
entries: DHashMap::default(),
|
||||
default,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_mut(&mut self, entry: K) -> &mut T {
|
||||
let default = &self.default;
|
||||
self.entries.entry(entry).or_insert_with(|| default.clone())
|
||||
}
|
||||
|
||||
pub fn get(&self, entry: K) -> &T { self.entries.get(&entry).unwrap_or(&self.default) }
|
||||
|
||||
#[allow(clippy::clone_on_copy)] // TODO: Pending review in #587
|
||||
pub fn map<U: Default>(self, mut f: impl FnMut(K, T) -> U) -> MapVec<K, U> {
|
||||
MapVec {
|
||||
entries: self
|
||||
.entries
|
||||
.into_iter()
|
||||
.map(|(s, v)| (s.clone(), f(s, v)))
|
||||
.collect(),
|
||||
default: U::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = (K, &T)> + '_ {
|
||||
self.entries.iter().map(|(s, v)| (*s, v))
|
||||
}
|
||||
|
||||
pub fn iter_mut(&mut self) -> impl Iterator<Item = (K, &mut T)> + '_ {
|
||||
self.entries.iter_mut().map(|(s, v)| (*s, v))
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: Copy + Eq + Hash, T: Clone> std::ops::Index<K> for MapVec<K, T> {
|
||||
type Output = T;
|
||||
|
||||
fn index(&self, entry: K) -> &Self::Output { self.get(entry) }
|
||||
}
|
||||
|
||||
impl<K: Copy + Eq + Hash, T: Clone> std::ops::IndexMut<K> for MapVec<K, T> {
|
||||
fn index_mut(&mut self, entry: K) -> &mut Self::Output { self.get_mut(entry) }
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
pub mod fast_noise;
|
||||
pub mod grid;
|
||||
pub mod map_vec;
|
||||
pub mod random;
|
||||
pub mod sampler;
|
||||
pub mod seed_expan;
|
||||
@ -11,6 +12,7 @@ pub mod unit_chooser;
|
||||
pub use self::{
|
||||
fast_noise::FastNoise,
|
||||
grid::Grid,
|
||||
map_vec::MapVec,
|
||||
random::{RandomField, RandomPerm},
|
||||
sampler::{Sampler, SamplerMut},
|
||||
small_cache::SmallCache,
|
||||
@ -18,8 +20,15 @@ pub use self::{
|
||||
unit_chooser::UnitChooser,
|
||||
};
|
||||
|
||||
use fxhash::FxHasher32;
|
||||
use hashbrown::{HashMap, HashSet};
|
||||
use std::hash::BuildHasherDefault;
|
||||
use vek::*;
|
||||
|
||||
// Deterministic HashMap and HashSet
|
||||
pub type DHashMap<K, V> = HashMap<K, V, BuildHasherDefault<FxHasher32>>;
|
||||
pub type DHashSet<T> = HashSet<T, BuildHasherDefault<FxHasher32>>;
|
||||
|
||||
pub fn attempt<T>(max_iters: usize, mut f: impl FnMut() -> Option<T>) -> Option<T> {
|
||||
(0..max_iters).find_map(|_| f())
|
||||
}
|
||||
|
@ -20,7 +20,22 @@ impl Sampler<'static> for RandomField {
|
||||
|
||||
fn get(&self, pos: Self::Index) -> Self::Sample {
|
||||
let pos = pos.map(|e| u32::from_le_bytes(e.to_le_bytes()));
|
||||
seed_expan::diffuse_mult(&[self.seed, pos.x, pos.y, pos.z])
|
||||
|
||||
let mut a = self.seed;
|
||||
a = (a ^ 61) ^ (a >> 16);
|
||||
a = a.wrapping_add(a << 3);
|
||||
a ^= pos.x;
|
||||
a ^= a >> 4;
|
||||
a = a.wrapping_mul(0x27d4eb2d);
|
||||
a ^= a >> 15;
|
||||
a ^= pos.y;
|
||||
a = (a ^ 61) ^ (a >> 16);
|
||||
a = a.wrapping_add(a << 3);
|
||||
a ^= a >> 4;
|
||||
a ^= pos.z;
|
||||
a = a.wrapping_mul(0x27d4eb2d);
|
||||
a ^= a >> 15;
|
||||
a
|
||||
}
|
||||
}
|
||||
|
||||
|
26
world/src/util/wgrid.rs
Normal file
26
world/src/util/wgrid.rs
Normal file
@ -0,0 +1,26 @@
|
||||
use super::Grid;
|
||||
use vek::*;
|
||||
|
||||
pub struct WGrid<T> {
|
||||
cell_size: u32,
|
||||
grid: Grid<T>,
|
||||
}
|
||||
|
||||
impl<T> WGrid<T> {
|
||||
pub fn new(radius: u32, cell_size: u32, default_cell: T) -> Self
|
||||
where T: Clone
|
||||
{
|
||||
Self {
|
||||
cell_size,
|
||||
grid: Grid::new(Vec2::broadcast(radius as i32 * 2 + 1), default_cell),
|
||||
}
|
||||
}
|
||||
|
||||
fn offset(&self) -> Vec2<i32> {
|
||||
self.grid.size() / 2
|
||||
}
|
||||
|
||||
pub fn get_local(&self, pos: Vec2<i32>) -> Option<&T> {
|
||||
self.grid.get(pos + self.offset())
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user