mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Start of layered settlement generation, better settlement terraingen
This commit is contained in:
parent
9daf20e87e
commit
966f96c588
@ -1,6 +1,6 @@
|
||||
use rand::thread_rng;
|
||||
use vek::*;
|
||||
use veloren_world::generator::settlement::Settlement;
|
||||
use veloren_world::site::Settlement;
|
||||
|
||||
const W: usize = 640;
|
||||
const H: usize = 480;
|
||||
|
@ -39,7 +39,7 @@ fn attempt<T>(max_iters: usize, mut f: impl FnMut() -> Option<T>) -> Option<T> {
|
||||
(0..max_iters).find_map(|_| f())
|
||||
}
|
||||
|
||||
const INITIAL_CIV_COUNT: usize = 16;
|
||||
const INITIAL_CIV_COUNT: usize = 32;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Civs {
|
||||
@ -216,7 +216,10 @@ impl Civs {
|
||||
|
||||
labors: MapVec::from_default(0.01),
|
||||
yields: MapVec::from_default(1.0),
|
||||
productivity: MapVec::from_default(1.0),
|
||||
|
||||
last_exports: Stocks::from_default(0.0),
|
||||
export_targets: Stocks::from_default(0.0),
|
||||
trade_states: Stocks::default(),
|
||||
coin: 1000.0,
|
||||
});
|
||||
@ -262,67 +265,64 @@ impl Civs {
|
||||
}
|
||||
|
||||
// Trade stocks
|
||||
let mut stocks = TRADE_STOCKS;
|
||||
stocks.shuffle(ctx.rng); // Give each stock a chance to be traded first
|
||||
for stock in stocks.iter().copied() {
|
||||
let mut sell_orders = self.sites
|
||||
.iter_ids()
|
||||
.map(|(id, site)| (id, {
|
||||
let total_value = site.values.iter().map(|(_, v)| (*v).unwrap_or(0.0)).sum::<f32>();
|
||||
let quantity = if let Some(val) = site.values[stock] {
|
||||
((total_value / val) * site.surplus[stock].max(0.0)).min(site.stocks[stock])
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
econ::SellOrder {
|
||||
quantity,
|
||||
price: site.trade_states[stock].sell_belief.choose_price(ctx) * 1.25, // Trade cost
|
||||
q_sold: 0.0,
|
||||
}
|
||||
}))
|
||||
.filter(|(_, order)| order.quantity > 0.0)
|
||||
.collect::<Vec<_>>();
|
||||
// let mut stocks = TRADE_STOCKS;
|
||||
// stocks.shuffle(ctx.rng); // Give each stock a chance to be traded first
|
||||
// for stock in stocks.iter().copied() {
|
||||
// let mut sell_orders = self.sites
|
||||
// .iter_ids()
|
||||
// .map(|(id, site)| (id, {
|
||||
// econ::SellOrder {
|
||||
// quantity: site.export_targets[stock].max(0.0).min(site.stocks[stock]),
|
||||
// price: site.trade_states[stock].sell_belief.choose_price(ctx) * 1.25, // Trade cost
|
||||
// q_sold: 0.0,
|
||||
// }
|
||||
// }))
|
||||
// .filter(|(_, order)| order.quantity > 0.0)
|
||||
// .collect::<Vec<_>>();
|
||||
|
||||
let mut sites = self.sites
|
||||
.ids()
|
||||
.collect::<Vec<_>>();
|
||||
sites.shuffle(ctx.rng); // Give all sites a chance to buy first
|
||||
for site in sites {
|
||||
let (max_spend, max_price) = {
|
||||
let site = self.sites.get(site);
|
||||
let budget = site.coin * 0.5;
|
||||
let total_value = site.values.iter().map(|(_, v)| (*v).unwrap_or(0.0)).sum::<f32>();
|
||||
(
|
||||
(site.values[stock].unwrap_or(0.1) / total_value * budget).min(budget),
|
||||
site.trade_states[stock].buy_belief.price,
|
||||
)
|
||||
};
|
||||
let (quantity, spent) = econ::buy_units(ctx, sell_orders
|
||||
.iter_mut()
|
||||
.filter(|(id, _)| site != *id && self.track_between(site, *id).is_some())
|
||||
.map(|(_, order)| order),
|
||||
1000000.0, // Max quantity TODO
|
||||
1000000.0, // Max price TODO
|
||||
max_spend,
|
||||
);
|
||||
let mut site = self.sites.get_mut(site);
|
||||
site.coin -= spent;
|
||||
if quantity > 0.0 {
|
||||
site.stocks[stock] += quantity;
|
||||
site.trade_states[stock].buy_belief.update_buyer(years, spent / quantity);
|
||||
println!("Belief: {:?}", site.trade_states[stock].buy_belief);
|
||||
}
|
||||
}
|
||||
// let mut sites = self.sites
|
||||
// .ids()
|
||||
// .collect::<Vec<_>>();
|
||||
// sites.shuffle(ctx.rng); // Give all sites a chance to buy first
|
||||
// for site in sites {
|
||||
// let (max_spend, max_price, max_import) = {
|
||||
// let site = self.sites.get(site);
|
||||
// let budget = site.coin * 0.5;
|
||||
// let total_value = site.values.iter().map(|(_, v)| (*v).unwrap_or(0.0)).sum::<f32>();
|
||||
// (
|
||||
// 100000.0,//(site.values[stock].unwrap_or(0.1) / total_value * budget).min(budget),
|
||||
// site.trade_states[stock].buy_belief.price,
|
||||
// -site.export_targets[stock].min(0.0),
|
||||
// )
|
||||
// };
|
||||
// let (quantity, spent) = econ::buy_units(ctx, sell_orders
|
||||
// .iter_mut()
|
||||
// .filter(|(id, _)| site != *id && self.track_between(site, *id).is_some())
|
||||
// .map(|(_, order)| order),
|
||||
// max_import,
|
||||
// 1000000.0, // Max price TODO
|
||||
// max_spend,
|
||||
// );
|
||||
// let mut site = self.sites.get_mut(site);
|
||||
// site.coin -= spent;
|
||||
// if quantity > 0.0 {
|
||||
// site.stocks[stock] += quantity;
|
||||
// site.last_exports[stock] = -quantity;
|
||||
// site.trade_states[stock].buy_belief.update_buyer(years, spent / quantity);
|
||||
// println!("Belief: {:?}", site.trade_states[stock].buy_belief);
|
||||
// }
|
||||
// }
|
||||
|
||||
for (site, order) in sell_orders {
|
||||
let mut site = self.sites.get_mut(site);
|
||||
site.coin += order.q_sold * order.price;
|
||||
if order.q_sold > 0.0 {
|
||||
site.stocks[stock] -= order.q_sold;
|
||||
site.trade_states[stock].sell_belief.update_seller(order.q_sold / order.quantity);
|
||||
}
|
||||
}
|
||||
}
|
||||
// for (site, order) in sell_orders {
|
||||
// let mut site = self.sites.get_mut(site);
|
||||
// site.coin += order.q_sold * order.price;
|
||||
// if order.q_sold > 0.0 {
|
||||
// site.stocks[stock] -= order.q_sold;
|
||||
// site.last_exports[stock] = order.q_sold;
|
||||
// site.trade_states[stock].sell_belief.update_seller(order.q_sold / order.quantity);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
@ -472,7 +472,10 @@ pub struct Site {
|
||||
labors: MapVec<Occupation, f32>,
|
||||
// Per worker, per year, of their output good
|
||||
yields: MapVec<Occupation, f32>,
|
||||
productivity: MapVec<Occupation, f32>,
|
||||
|
||||
last_exports: Stocks<f32>,
|
||||
export_targets: Stocks<f32>,
|
||||
trade_states: Stocks<TradeState>,
|
||||
coin: f32,
|
||||
}
|
||||
@ -488,14 +491,18 @@ impl fmt::Display for Site {
|
||||
for (stock, q) in self.stocks.iter() {
|
||||
writeln!(f, "- {}: {}", stock, q.floor())?;
|
||||
}
|
||||
writeln!(f, "Prices")?;
|
||||
for (stock, v) in self.values.iter() {
|
||||
writeln!(f, "- {}: {}", stock, v.map(|x| x.to_string()).unwrap_or_else(|| "N/A".to_string()))?;
|
||||
writeln!(f, "Values")?;
|
||||
for stock in TRADE_STOCKS.iter() {
|
||||
writeln!(f, "- {}: {}", stock, self.values[*stock].map(|x| x.to_string()).unwrap_or_else(|| "N/A".to_string()))?;
|
||||
}
|
||||
writeln!(f, "Laborers")?;
|
||||
for (labor, n) in self.labors.iter() {
|
||||
writeln!(f, "- {}: {}", labor, (*n * self.population).floor() as u32)?;
|
||||
}
|
||||
writeln!(f, "Export targets")?;
|
||||
for (stock, n) in self.export_targets.iter() {
|
||||
writeln!(f, "- {}: {}", stock, n)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -526,8 +533,8 @@ impl Site {
|
||||
}
|
||||
|
||||
let orders = vec![
|
||||
(None, vec![(FOOD, 0.25)]),
|
||||
(Some(COOK), vec![(FLOUR, 6.5), (MEAT, 1.5)]),
|
||||
(None, vec![(FOOD, 0.5)]),
|
||||
(Some(COOK), vec![(FLOUR, 16.0), (MEAT, 4.0), (WOOD, 3.0)]),
|
||||
(Some(LUMBERJACK), vec![(LOGS, 4.5)]),
|
||||
(Some(MINER), vec![(ROCK, 7.5)]),
|
||||
(Some(FISHER), vec![(FISH, 4.0)]),
|
||||
@ -537,20 +544,33 @@ impl Site {
|
||||
.into_iter()
|
||||
.collect::<HashMap<_, Vec<(Stock, f32)>>>();
|
||||
|
||||
// Per labourer, per year
|
||||
let production = Stocks::from_list(&[
|
||||
(FARMER, (FLOUR, 2.0)),
|
||||
(LUMBERJACK, (WOOD, 1.5)),
|
||||
(MINER, (STONE, 0.6)),
|
||||
(FISHER, (MEAT, 3.0)),
|
||||
(HUNTER, (MEAT, 0.25)),
|
||||
(COOK, (FOOD, 20.0)),
|
||||
]);
|
||||
|
||||
let mut demand = Stocks::from_default(0.0);
|
||||
for (labor, orders) in &orders {
|
||||
let scale = if let Some(labor) = labor { self.labors[*labor] } else { 1.0 } * self.population;
|
||||
for (stock, amount) in orders {
|
||||
debug_assert!(!amount.is_nan(), "{:?}, {}", labor, stock);
|
||||
debug_assert!(!scale.is_nan(), "{:?}, {}, {}", labor, stock, self.population);
|
||||
demand[*stock] += *amount * scale;
|
||||
}
|
||||
}
|
||||
|
||||
let mut supply = Stocks::from_default(0.0);
|
||||
for (labor, (output_stock, _)) in production.iter() {
|
||||
supply[*output_stock] += self.yields[labor] * self.labors[labor] * self.population;
|
||||
}
|
||||
|
||||
let last_exports = &self.last_exports;
|
||||
let stocks = &self.stocks;
|
||||
self.surplus = demand.clone().map(|stock, tgt| {
|
||||
debug_assert!(!self.stocks[stock].is_nan());
|
||||
debug_assert!(!demand[stock].is_nan());
|
||||
self.stocks[stock] - demand[stock]
|
||||
supply[stock] + stocks[stock] - demand[stock] - last_exports[stock]
|
||||
});
|
||||
|
||||
// Update values according to the surplus of each stock
|
||||
@ -560,30 +580,28 @@ impl Site {
|
||||
values[stock] = if val > 0.001 && val < 1000.0 { Some(val) } else { None };
|
||||
});
|
||||
|
||||
// Per labourer, per year
|
||||
let production = Stocks::from_list(&[
|
||||
(FARMER, (FLOUR, 2.0)),
|
||||
(LUMBERJACK, (WOOD, 1.5)),
|
||||
(MINER, (STONE, 0.6)),
|
||||
(FISHER, (MEAT, 3.0)),
|
||||
(HUNTER, (MEAT, 0.5)),
|
||||
(COOK, (FOOD, 8.0)),
|
||||
]);
|
||||
// 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 self.export_targets;
|
||||
let last_exports = &self.last_exports;
|
||||
let trade_states = &self.trade_states;
|
||||
self.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 population = self.population;
|
||||
|
||||
// Redistribute workforce according to relative good values
|
||||
let labor_ratios = production.clone().map(|labor, (output_stock, _)| {
|
||||
debug_assert!(self.values[output_stock].unwrap_or(0.0) < 1000000.0, "{:?}", self.values[output_stock]);
|
||||
debug_assert!(self.yields[labor] < 1000000.0, "{}", self.yields[labor]);
|
||||
self.values[output_stock].unwrap_or(0.0) * self.yields[labor]
|
||||
self.productivity[labor] * demand[output_stock] / supply[output_stock].max(0.001)
|
||||
});
|
||||
let labor_ratio_sum = labor_ratios.iter().map(|(_, r)| *r).sum::<f32>().max(0.01);
|
||||
assert!(labor_ratio_sum > 0.0);
|
||||
production.iter().for_each(|(labor, _)| {
|
||||
debug_assert!(!labor_ratios[labor].is_nan() && !labor_ratios[labor].is_infinite(), "{:?}, {}", labor, labor_ratios[labor]);
|
||||
debug_assert!(!labor_ratio_sum.is_nan() && !labor_ratio_sum.is_infinite(), "{:?}, {}", labor, labor_ratio_sum);
|
||||
let smooth = 0.5;
|
||||
let smooth = 0.8;
|
||||
self.labors[labor] = smooth * self.labors[labor] + (1.0 - smooth) * (labor_ratios[labor].max(labor_ratio_sum / 1000.0) / labor_ratio_sum);
|
||||
});
|
||||
|
||||
@ -596,7 +614,8 @@ impl Site {
|
||||
// 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
|
||||
let min_satisfaction = orders
|
||||
// In effect, this is the productivity
|
||||
let productivity = orders
|
||||
.iter()
|
||||
.map(|(stock, amount)| {
|
||||
// What quantity is this order requesting?
|
||||
@ -612,7 +631,7 @@ impl Site {
|
||||
// What quantity is this order requesting?
|
||||
let quantity = *amount * scale;
|
||||
// What amount gets actually used in production?
|
||||
let used = quantity * min_satisfaction;
|
||||
let used = quantity * productivity;
|
||||
|
||||
// Deplete stocks accordingly
|
||||
self.stocks[*stock] = (self.stocks[*stock] - used).max(0.0);
|
||||
@ -621,9 +640,11 @@ impl Site {
|
||||
// Industries produce things
|
||||
if let Some(labor) = labor {
|
||||
let (stock, rate) = production[*labor];
|
||||
let yield_per_worker = min_satisfaction * rate;
|
||||
self.yields[*labor] = yield_per_worker;
|
||||
let workers = self.labors[*labor] * population;
|
||||
let final_rate = rate;
|
||||
let yield_per_worker = productivity * final_rate;
|
||||
self.yields[*labor] = yield_per_worker;
|
||||
self.productivity[*labor] = productivity;
|
||||
self.stocks[stock] += yield_per_worker * workers.powf(1.1);
|
||||
}
|
||||
}
|
||||
@ -634,8 +655,6 @@ impl Site {
|
||||
// Births/deaths
|
||||
const NATURAL_BIRTH_RATE: f32 = 0.15;
|
||||
const DEATH_RATE: f32 = 0.05;
|
||||
debug_assert!(!self.surplus[FOOD].is_nan());
|
||||
debug_assert!(!self.surplus[FOOD].is_infinite());
|
||||
let birth_rate = if self.surplus[FOOD] > 0.0 { NATURAL_BIRTH_RATE } else { 0.0 };
|
||||
self.population += years * self.population * (birth_rate - DEATH_RATE);
|
||||
}
|
||||
@ -752,7 +771,3 @@ impl<K: Copy + Eq + Hash, T: Default + Clone> std::ops::IndexMut<K> for MapVec<K
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -999,13 +999,6 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
humidity.sub(CONFIG.jungle_hum).mul(1.0),
|
||||
);
|
||||
|
||||
let ground = sim_chunk.sites.iter().fold(ground, |ground, site| {
|
||||
site.get_surface(wpos)
|
||||
.and_then(|block| block.get_color())
|
||||
.map(|col| col.map(|e| e as f32 / 255.0))
|
||||
.unwrap_or(ground)
|
||||
});
|
||||
|
||||
// Snow covering
|
||||
let snow_cover = temp
|
||||
.sub(CONFIG.snow_temp)
|
||||
@ -1082,7 +1075,12 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
),
|
||||
sub_surface_color,
|
||||
// No growing directly on bedrock.
|
||||
tree_density: Lerp::lerp(0.0, tree_density, alt.sub(2.0).sub(basement).mul(0.5)),
|
||||
// And, no growing on sites that don't want them TODO: More precise than this when we apply trees as a post-processing layer
|
||||
tree_density: if sim_chunk.sites.iter().all(|site| site.spawn_rules(wpos).trees) {
|
||||
Lerp::lerp(0.0, tree_density, alt.sub(2.0).sub(basement).mul(0.5))
|
||||
} else {
|
||||
0.0
|
||||
},
|
||||
forest_kind: sim_chunk.forest_kind,
|
||||
close_structures: self.gen_close_structures(wpos),
|
||||
cave_xy,
|
||||
|
@ -14,6 +14,10 @@ use common::{
|
||||
use std::{fmt, sync::Arc};
|
||||
use vek::*;
|
||||
|
||||
pub struct SpawnRules {
|
||||
pub trees: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum Site {
|
||||
Settlement(Arc<Settlement>),
|
||||
@ -26,9 +30,9 @@ impl Site {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_surface(&self, wpos: Vec2<i32>) -> Option<Block> {
|
||||
pub fn spawn_rules(&self, wpos: Vec2<i32>) -> SpawnRules {
|
||||
match self {
|
||||
Site::Settlement(settlement) => settlement.get_surface(wpos),
|
||||
Site::Settlement(s) => s.spawn_rules(wpos)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,12 +3,13 @@ use crate::{
|
||||
sim::{SimChunk, WorldSim},
|
||||
util::{Grid, RandomField, Sampler, StructureGen2d},
|
||||
};
|
||||
use super::SpawnRules;
|
||||
use common::{
|
||||
astar::Astar,
|
||||
path::Path,
|
||||
spiral::Spiral2d,
|
||||
terrain::{Block, BlockKind},
|
||||
vol::{BaseVol, RectSizedVol, WriteVol},
|
||||
vol::{BaseVol, RectSizedVol, WriteVol, Vox},
|
||||
store::{Id, Store},
|
||||
};
|
||||
use hashbrown::{HashMap, HashSet};
|
||||
@ -357,6 +358,12 @@ impl Settlement {
|
||||
|
||||
pub fn radius(&self) -> f32 { 1200.0 }
|
||||
|
||||
pub fn spawn_rules(&self, wpos: Vec2<i32>) -> SpawnRules {
|
||||
SpawnRules {
|
||||
trees: self.land.get_at_block(wpos - self.origin).plot.is_none(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply_to<'a>(
|
||||
&'a self,
|
||||
wpos2d: Vec2<i32>,
|
||||
@ -369,52 +376,81 @@ impl Settlement {
|
||||
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;
|
||||
|
||||
// Sample terrain
|
||||
let col_sample = if let Some(col_sample) = get_column(offs) {
|
||||
col_sample
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
let surface_z = col_sample.alt.floor() as i32;
|
||||
|
||||
let wpos2d = wpos2d + offs;
|
||||
// Sample settlement
|
||||
let sample = self.land.get_at_block(rpos);
|
||||
|
||||
match self.land.get_at_block(wpos2d - self.origin) {
|
||||
Sample::Way(WayKind::Wall, dist) => {
|
||||
let color = Lerp::lerp(
|
||||
Rgb::new(130i32, 100, 0),
|
||||
Rgb::new(90, 70, 50),
|
||||
(rand_field.get(wpos2d.into()) % 256) as f32 / 256.0,
|
||||
)
|
||||
.map(|e| (e % 256) as u8);
|
||||
for z in 0..12 {
|
||||
if dist / WayKind::Wall.width()
|
||||
< ((1.0 - z as f32 / 12.0) * 2.0).min(1.0)
|
||||
{
|
||||
vol.set(
|
||||
Vec3::new(offs.x, offs.y, col_sample.alt.floor() as i32 + z),
|
||||
Block::new(BlockKind::Normal, color),
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
Sample::Tower(Tower::Wall, _pos) => {
|
||||
for z in 0..16 {
|
||||
// Ground color
|
||||
if let Some(color) = self.get_color(rpos) {
|
||||
for z in -3..3 {
|
||||
vol.set(
|
||||
Vec3::new(offs.x, offs.y, surface_z + z),
|
||||
if z >= 0 { Block::empty() } else { Block::new(BlockKind::Normal, color) },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Walls
|
||||
if let Some((WayKind::Wall, dist)) = sample.way {
|
||||
let color = Lerp::lerp(
|
||||
Rgb::new(130i32, 100, 0),
|
||||
Rgb::new(90, 70, 50),
|
||||
(rand_field.get(wpos2d.into()) % 256) as f32 / 256.0,
|
||||
)
|
||||
.map(|e| (e % 256) as u8);
|
||||
for z in 0..12 {
|
||||
if dist / WayKind::Wall.width()
|
||||
< ((1.0 - z as f32 / 12.0) * 2.0).min(1.0)
|
||||
{
|
||||
vol.set(
|
||||
Vec3::new(offs.x, offs.y, col_sample.alt.floor() as i32 + z),
|
||||
Block::new(BlockKind::Normal, Rgb::new(50, 50, 50)),
|
||||
Vec3::new(offs.x, offs.y, surface_z + z),
|
||||
Block::new(BlockKind::Normal, color),
|
||||
);
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
||||
// Towers
|
||||
if let Some((Tower::Wall, _pos)) = sample.tower {
|
||||
for z in 0..16 {
|
||||
vol.set(
|
||||
Vec3::new(offs.x, offs.y, surface_z + z),
|
||||
Block::new(BlockKind::Normal, Rgb::new(50, 50, 50)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Paths
|
||||
if let Some((WayKind::Path, dist)) = sample.way {
|
||||
let inset = -1;
|
||||
for z in -3..inset {
|
||||
vol.set(
|
||||
Vec3::new(offs.x, offs.y, surface_z + z),
|
||||
Block::new(BlockKind::Normal, Rgb::new(90, 70, 50)),
|
||||
);
|
||||
}
|
||||
let head_space = (6 - (dist * 0.4).powf(6.0).round() as i32).max(1);
|
||||
for z in inset..inset + head_space {
|
||||
vol.set(
|
||||
Vec3::new(offs.x, offs.y, surface_z + z),
|
||||
Block::empty(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_surface(&self, wpos: Vec2<i32>) -> Option<Block> {
|
||||
self.get_color(wpos - self.origin)
|
||||
.map(|col| Block::new(BlockKind::Normal, col))
|
||||
}
|
||||
|
||||
pub fn get_color(&self, pos: Vec2<i32>) -> Option<Rgb<u8>> {
|
||||
if let Some(structure) = self
|
||||
.structures
|
||||
@ -426,24 +462,30 @@ impl Settlement {
|
||||
});
|
||||
}
|
||||
|
||||
Some(match self.land.get_at_block(pos) {
|
||||
Sample::Wilderness => return None,
|
||||
Sample::Plot(Plot::Hazard) => return None,
|
||||
Sample::Way(WayKind::Path, _) => Rgb::new(90, 70, 50),
|
||||
Sample::Way(WayKind::Hedge, _) => Rgb::new(0, 150, 0),
|
||||
Sample::Way(WayKind::Wall, _) => Rgb::new(60, 60, 60),
|
||||
Sample::Tower(Tower::Wall, _) => Rgb::new(50, 50, 50),
|
||||
Sample::Plot(Plot::Dirt) => Rgb::new(90, 70, 50),
|
||||
Sample::Plot(Plot::Grass) => Rgb::new(100, 200, 0),
|
||||
Sample::Plot(Plot::Water) => Rgb::new(100, 150, 250),
|
||||
Sample::Plot(Plot::Town) => {
|
||||
if pos.map(|e| e.rem_euclid(4) < 2).reduce(|x, y| x ^ y) {
|
||||
Rgb::new(200, 130, 120)
|
||||
} else {
|
||||
Rgb::new(160, 150, 120)
|
||||
}
|
||||
},
|
||||
Sample::Plot(Plot::Field { seed, .. }) => {
|
||||
let sample = self.land.get_at_block(pos);
|
||||
|
||||
match sample.tower {
|
||||
Some((Tower::Wall, _)) => return Some(Rgb::new(50, 50, 50)),
|
||||
_ => {},
|
||||
}
|
||||
|
||||
match sample.way {
|
||||
Some((WayKind::Path, _)) => return Some(Rgb::new(90, 70, 50)),
|
||||
Some((WayKind::Hedge, _)) => return Some(Rgb::new(0, 150, 0)),
|
||||
Some((WayKind::Wall, _)) => return Some(Rgb::new(60, 60, 60)),
|
||||
_ => {},
|
||||
}
|
||||
|
||||
match sample.plot {
|
||||
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) => return Some(if pos.map(|e| e.rem_euclid(4) < 2).reduce(|x, y| x ^ y) {
|
||||
Rgb::new(200, 130, 120)
|
||||
} else {
|
||||
Rgb::new(160, 150, 120)
|
||||
}),
|
||||
Some(Plot::Field { seed, .. }) => {
|
||||
let furrow_dirs = [
|
||||
Vec2::new(1, 0),
|
||||
Vec2::new(0, 1),
|
||||
@ -452,7 +494,7 @@ impl Settlement {
|
||||
];
|
||||
let furrow_dir = furrow_dirs[*seed as usize % furrow_dirs.len()];
|
||||
let furrow = (pos * furrow_dir).sum().rem_euclid(6) < 3;
|
||||
Rgb::new(
|
||||
return Some(Rgb::new(
|
||||
if furrow {
|
||||
100
|
||||
} else {
|
||||
@ -460,9 +502,12 @@ impl Settlement {
|
||||
},
|
||||
64 + seed.to_le_bytes()[1] % 128,
|
||||
16 + seed.to_le_bytes()[2] % 32,
|
||||
)
|
||||
));
|
||||
},
|
||||
})
|
||||
_ => {},
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@ -523,11 +568,11 @@ impl Tile {
|
||||
pub fn contains(&self, kind: WayKind) -> bool { self.ways.iter().any(|way| way == &Some(kind)) }
|
||||
}
|
||||
|
||||
pub enum Sample<'a> {
|
||||
Wilderness,
|
||||
Plot(&'a Plot),
|
||||
Way(&'a WayKind, f32),
|
||||
Tower(&'a Tower, Vec2<i32>),
|
||||
#[derive(Default)]
|
||||
pub struct Sample<'a> {
|
||||
plot: Option<&'a Plot>,
|
||||
way: Option<(&'a WayKind, f32)>,
|
||||
tower: Option<(&'a Tower, Vec2<i32>)>,
|
||||
}
|
||||
|
||||
pub struct Land {
|
||||
@ -546,6 +591,8 @@ impl Land {
|
||||
}
|
||||
|
||||
pub fn get_at_block(&self, pos: Vec2<i32>) -> Sample {
|
||||
let mut sample = Sample::default();
|
||||
|
||||
let neighbors = self.sampler_warp.get(pos);
|
||||
let closest = neighbors
|
||||
.iter()
|
||||
@ -557,7 +604,7 @@ impl Land {
|
||||
|
||||
if let Some(tower) = center_tile.and_then(|tile| tile.tower.as_ref()) {
|
||||
if (neighbors[4].0.distance_squared(pos) as f32) < tower.radius().powf(2.0) {
|
||||
return Sample::Tower(tower, neighbors[4].0);
|
||||
sample.tower = Some((tower, neighbors[4].0));
|
||||
}
|
||||
}
|
||||
|
||||
@ -570,15 +617,14 @@ impl Land {
|
||||
if let Some(way) = center_tile.and_then(|tile| tile.ways[i].as_ref()) {
|
||||
let dist = dist_to_line(line, pos.map(|e| e as f32));
|
||||
if dist < way.width() {
|
||||
return Sample::Way(way, dist);
|
||||
sample.way = Some((way, dist));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let plot = self.plot_at(closest.map(to_tile));
|
||||
sample.plot = self.plot_at(closest.map(to_tile));
|
||||
|
||||
plot.map(|plot| Sample::Plot(plot))
|
||||
.unwrap_or(Sample::Wilderness)
|
||||
sample
|
||||
}
|
||||
|
||||
pub fn tile_at(&self, pos: Vec2<i32>) -> Option<&Tile> { self.tiles.get(&pos) }
|
||||
|
Loading…
Reference in New Issue
Block a user