veloren/world/src/civ/mod.rs

1067 lines
36 KiB
Rust
Raw Normal View History

2020-04-23 15:16:08 +00:00
#![allow(dead_code)]
2020-03-28 18:16:19 +00:00
mod econ;
use self::{Occupation::*, Stock::*};
2020-04-17 23:29:01 +00:00
use crate::{
2020-04-23 15:16:08 +00:00
sim::WorldSim,
2020-04-17 23:29:01 +00:00
site::{Dungeon, Settlement, Site as WorldSite},
2020-04-20 16:30:39 +00:00
util::{attempt, seed_expan, CARDINALS, NEIGHBORS},
};
2020-03-27 13:16:02 +00:00
use common::{
astar::Astar,
2020-04-17 23:29:01 +00:00
path::Path,
2020-04-11 00:09:01 +00:00
spiral::Spiral2d,
2020-04-17 23:29:01 +00:00
store::{Id, Store},
terrain::TerrainChunkSize,
vol::RectVolSize,
2020-04-11 00:09:01 +00:00
};
use core::{
fmt,
hash::{BuildHasherDefault, Hash},
ops::Range,
};
use fxhash::{FxHasher32, FxHasher64};
2020-04-17 23:29:01 +00:00
use hashbrown::{HashMap, HashSet};
use rand::prelude::*;
use rand_chacha::ChaChaRng;
use vek::*;
2020-03-27 13:16:02 +00:00
const INITIAL_CIV_COUNT: usize = (crate::sim::WORLD_SIZE.x * crate::sim::WORLD_SIZE.y * 3) / 65536; //48 at default scale
2020-03-27 13:16:02 +00:00
#[derive(Default)]
pub struct Civs {
civs: Store<Civ>,
places: Store<Place>,
2020-03-27 18:52:28 +00:00
tracks: Store<Track>,
/// We use this hasher (FxHasher64) 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 8-byte keys (for which FxHash is fastest).
track_map: HashMap<
Id<Site>,
HashMap<Id<Site>, Id<Track>, BuildHasherDefault<FxHasher64>>,
BuildHasherDefault<FxHasher64>,
>,
sites: Store<Site>,
2020-03-27 13:16:02 +00:00
}
2020-05-15 18:26:09 +00:00
// Change this to get rid of particularly horrid seeds
2020-05-21 20:59:59 +00:00
const SEED_SKIP: u8 = 0;
2020-05-15 18:26:09 +00:00
2020-03-28 18:16:19 +00:00
pub struct GenCtx<'a, R: Rng> {
2020-03-27 13:16:02 +00:00
sim: &'a mut WorldSim,
rng: R,
}
impl<'a, R: Rng> GenCtx<'a, R> {
pub fn reseed(&mut self) -> GenCtx<'_, impl Rng> {
2020-05-15 18:26:09 +00:00
let mut entropy = self.rng.gen::<[u8; 32]>();
entropy[0] = entropy[0].wrapping_add(SEED_SKIP); // Skip bad seeds
GenCtx {
sim: self.sim,
2020-05-15 18:26:09 +00:00
rng: ChaChaRng::from_seed(entropy),
}
}
2020-03-27 13:16:02 +00:00
}
impl Civs {
pub fn generate(seed: u32, sim: &mut WorldSim) -> Self {
let mut this = Self::default();
let rng = ChaChaRng::from_seed(seed_expan::rng_state(seed));
let mut ctx = GenCtx { sim, rng };
2020-03-27 13:16:02 +00:00
for _ in 0..INITIAL_CIV_COUNT {
2020-04-17 15:46:08 +00:00
log::info!("Creating civilisation...");
2020-04-23 14:01:37 +00:00
if this.birth_civ(&mut ctx.reseed()).is_none() {
2020-04-17 15:46:08 +00:00
log::warn!("Failed to find starting site for civilisation.");
2020-03-27 13:16:02 +00:00
}
}
for _ in 0..INITIAL_CIV_COUNT * 3 {
2020-04-15 19:41:40 +00:00
attempt(5, || {
let loc = find_site_loc(&mut ctx, None)?;
this.establish_site(&mut ctx.reseed(), loc, |place| Site {
2020-04-15 19:41:40 +00:00
kind: SiteKind::Dungeon,
center: loc,
place,
population: 0.0,
stocks: Stocks::from_default(100.0),
surplus: Stocks::from_default(0.0),
values: Stocks::from_default(None),
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,
})
});
}
// Tick
2020-03-28 18:16:19 +00:00
const SIM_YEARS: usize = 1000;
for _ in 0..SIM_YEARS {
2020-03-28 18:16:19 +00:00
this.tick(&mut ctx, 1.0);
}
// Flatten ground around sites
2020-04-11 00:09:01 +00:00
for site in this.sites.iter() {
let radius = 48i32;
2020-04-28 18:09:45 +00:00
2020-04-11 00:09:01 +00:00
let wpos = site.center * Vec2::from(TerrainChunkSize::RECT_SIZE).map(|e: u32| e as i32);
2020-04-28 18:09:45 +00:00
let flatten_radius = match &site.kind {
SiteKind::Settlement => 10.0,
SiteKind::Dungeon => 2.0,
};
2020-04-28 18:31:41 +00:00
let (raise, raise_dist): (f32, i32) = match &site.kind {
SiteKind::Settlement => (10.0, 6),
_ => (0.0, 0),
};
// Flatten ground
if let Some(center_alt) = ctx.sim.get_alt_approx(wpos) {
for offs in Spiral2d::new().take(radius.pow(2) as usize) {
2020-04-17 23:29:01 +00:00
let center_alt = center_alt
2020-04-28 18:31:41 +00:00
+ if offs.magnitude_squared() <= raise_dist.pow(2) {
raise
2020-04-17 23:29:01 +00:00
} else {
0.0
}; // Raise the town centre up a little
let pos = site.center + offs;
2020-04-17 23:29:01 +00:00
let factor = (1.0
- (site.center - pos).map(|e| e as f32).magnitude() / flatten_radius)
* 1.15;
ctx.sim
.get_mut(pos)
// Don't disrupt chunks that are near water
.filter(|chunk| !chunk.river.near_water())
.map(|chunk| {
let diff = Lerp::lerp_precise(chunk.alt, center_alt, factor) - chunk.alt;
chunk.alt += diff;
chunk.basement += diff;
chunk.rockiness = 0.0;
2020-04-17 15:46:08 +00:00
chunk.warp_factor = 0.0;
});
}
}
}
// Place sites in world
for site in this.sites.iter() {
2020-04-20 16:30:39 +00:00
let wpos = site
.center
.map2(Vec2::from(TerrainChunkSize::RECT_SIZE), |e, sz: u32| {
e * sz as i32 + sz as i32 / 2
});
let mut rng = ctx.reseed().rng;
2020-04-15 19:41:40 +00:00
let world_site = match &site.kind {
SiteKind::Settlement => {
WorldSite::from(Settlement::generate(wpos, Some(ctx.sim), &mut rng))
},
SiteKind::Dungeon => {
WorldSite::from(Dungeon::generate(wpos, Some(ctx.sim), &mut rng))
},
2020-04-15 19:41:40 +00:00
};
2020-04-17 23:29:01 +00:00
let radius_chunks =
(world_site.radius() / TerrainChunkSize::RECT_SIZE.x as f32).ceil() as usize;
for pos in Spiral2d::new()
.map(|offs| site.center + offs)
.take((radius_chunks * 2).pow(2))
{
2020-04-11 00:09:01 +00:00
ctx.sim
.get_mut(pos)
2020-04-15 19:41:40 +00:00
.map(|chunk| chunk.sites.push(world_site.clone()));
2020-03-27 13:16:02 +00:00
}
2020-04-17 15:46:08 +00:00
log::info!("Placed site at {:?}", site.center);
2020-03-27 13:16:02 +00:00
}
2020-04-17 15:46:08 +00:00
//this.display_info();
2020-03-27 13:16:02 +00:00
this
}
pub fn place(&self, id: Id<Place>) -> &Place { self.places.get(id) }
2020-04-17 23:29:01 +00:00
pub fn sites(&self) -> impl Iterator<Item = &Site> + '_ { self.sites.iter() }
2020-03-28 18:16:19 +00:00
2020-04-23 15:16:08 +00:00
#[allow(dead_code)]
fn display_info(&self) {
for (id, civ) in self.civs.iter_ids() {
println!("# Civilisation {:?}", id);
println!("Name: {}", "<unnamed>");
println!("Homeland: {:#?}", self.places.get(civ.homeland));
}
for (id, site) in self.sites.iter_ids() {
println!("# Site {:?}", id);
2020-03-28 18:16:19 +00:00
println!("{:#?}", site);
}
}
2020-03-27 18:52:28 +00:00
/// Return the direct track between two places
fn track_between(&self, a: Id<Site>, b: Id<Site>) -> Option<Id<Track>> {
2020-03-27 18:52:28 +00:00
self.track_map
.get(&a)
.and_then(|dests| dests.get(&b))
2020-04-17 23:29:01 +00:00
.or_else(|| self.track_map.get(&b).and_then(|dests| dests.get(&a)))
2020-03-27 18:52:28 +00:00
.copied()
}
/// Return an iterator over a site's neighbors
2020-04-17 23:29:01 +00:00
fn neighbors(&self, site: Id<Site>) -> impl Iterator<Item = Id<Site>> + '_ {
let to = self
.track_map
.get(&site)
.map(|dests| dests.keys())
.into_iter()
.flatten();
let fro = self
.track_map
.iter()
.filter(move |(_, dests)| dests.contains_key(&site))
.map(|(p, _)| p);
to.chain(fro).filter(move |p| **p != site).copied()
}
2020-03-27 18:52:28 +00:00
/// Find the cheapest route between two places
fn route_between(&self, a: Id<Site>, b: Id<Site>) -> Option<(Path<Id<Site>>, f32)> {
2020-04-17 23:29:01 +00:00
let heuristic = move |p: &Id<Site>| {
(self
.sites
.get(*p)
.center
.distance_squared(self.sites.get(b).center) as f32)
.sqrt()
};
let neighbors = |p: &Id<Site>| self.neighbors(*p);
2020-04-17 23:29:01 +00:00
let transition =
|a: &Id<Site>, b: &Id<Site>| self.tracks.get(self.track_between(*a, *b).unwrap()).cost;
let satisfied = |p: &Id<Site>| *p == b;
// We use this hasher (FxHasher64) 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 8-byte keys (for which FxHash is fastest).
let mut astar = Astar::new(
100,
a,
heuristic,
BuildHasherDefault::<FxHasher64>::default(),
);
2020-03-27 18:52:28 +00:00
astar
.poll(100, heuristic, neighbors, transition, satisfied)
.into_path()
.and_then(|path| astar.get_cheapest_cost().map(|cost| (path, cost)))
}
2020-03-27 13:16:02 +00:00
fn birth_civ(&mut self, ctx: &mut GenCtx<impl Rng>) -> Option<Id<Civ>> {
let site = attempt(5, || {
2020-03-27 13:16:02 +00:00
let loc = find_site_loc(ctx, None)?;
2020-04-15 19:41:40 +00:00
self.establish_site(ctx, loc, |place| Site {
kind: SiteKind::Settlement,
center: loc,
place,
population: 24.0,
stocks: Stocks::from_default(100.0),
surplus: Stocks::from_default(0.0),
values: Stocks::from_default(None),
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,
})
2020-03-27 13:16:02 +00:00
})?;
let civ = self.civs.insert(Civ {
capital: site,
homeland: self.sites.get(site).place,
2020-03-27 13:16:02 +00:00
});
Some(civ)
}
2020-04-17 23:29:01 +00:00
fn establish_place(
&mut self,
ctx: &mut GenCtx<impl Rng>,
loc: Vec2<i32>,
area: Range<usize>,
) -> Option<Id<Place>> {
// We use this hasher (FxHasher64) 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 8-byte keys (for which FxHash is fastest).
let mut dead = HashSet::with_hasher(BuildHasherDefault::<FxHasher64>::default());
let mut alive = HashSet::with_hasher(BuildHasherDefault::<FxHasher64>::default());
2020-03-27 13:16:02 +00:00
alive.insert(loc);
// Fill the surrounding area
while let Some(cloc) = alive.iter().choose(&mut ctx.rng).copied() {
2020-03-27 13:16:02 +00:00
for dir in CARDINALS.iter() {
if site_in_dir(&ctx.sim, cloc, *dir) {
let rloc = cloc + *dir;
2020-04-17 23:29:01 +00:00
if !dead.contains(&rloc)
&& ctx
.sim
.get(rloc)
.map(|c| c.place.is_none())
.unwrap_or(false)
{
2020-03-27 13:16:02 +00:00
alive.insert(rloc);
}
}
}
alive.remove(&cloc);
dead.insert(cloc);
if dead.len() + alive.len() >= area.end {
break;
}
}
// Make sure the place is large enough
if dead.len() + alive.len() <= area.start {
return None;
}
2020-03-27 18:52:28 +00:00
let place = self.places.insert(Place {
center: loc,
nat_res: NaturalResources::default(),
});
// Write place to map
for cell in dead.union(&alive) {
if let Some(chunk) = ctx.sim.get_mut(*cell) {
chunk.place = Some(place);
self.places.get_mut(place).nat_res.include_chunk(ctx, *cell);
}
}
Some(place)
}
2020-04-17 23:29:01 +00:00
fn establish_site(
&mut self,
ctx: &mut GenCtx<impl Rng>,
loc: Vec2<i32>,
site_fn: impl FnOnce(Id<Place>) -> Site,
) -> Option<Id<Site>> {
const SITE_AREA: Range<usize> = 64..256;
let place = match ctx.sim.get(loc).and_then(|site| site.place) {
Some(place) => place,
None => self.establish_place(ctx, loc, SITE_AREA)?,
};
2020-04-15 19:41:40 +00:00
let site = self.sites.insert(site_fn(place));
2020-03-27 18:52:28 +00:00
2020-03-27 13:16:02 +00:00
// Find neighbors
2020-04-28 18:09:45 +00:00
const MAX_NEIGHBOR_DISTANCE: f32 = 500.0;
2020-04-17 23:29:01 +00:00
let mut nearby = self
.sites
2020-03-27 13:16:02 +00:00
.iter_ids()
.map(|(id, p)| (id, (p.center.distance_squared(loc) as f32).sqrt()))
2020-04-23 15:16:08 +00:00
.filter(|(_, dist)| *dist < MAX_NEIGHBOR_DISTANCE)
2020-03-27 13:16:02 +00:00
.collect::<Vec<_>>();
2020-03-27 18:52:28 +00:00
nearby.sort_by_key(|(_, dist)| *dist as i32);
if let SiteKind::Settlement = 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) {
// Find a path using existing paths
if self
.route_between(site, nearby)
// If the novel path isn't efficient compared to existing routes, don't use it
.filter(|(_, route_cost)| *route_cost < cost * 3.0)
.is_none()
{
// Write the track to the world as a path
for locs in path.nodes().windows(3) {
let to_prev_idx = NEIGHBORS
.iter()
.enumerate()
.find(|(_, dir)| **dir == locs[0] - locs[1])
.expect("Track locations must be neighbors")
.0;
let to_next_idx = NEIGHBORS
.iter()
.enumerate()
.find(|(_, dir)| **dir == locs[2] - locs[1])
.expect("Track locations must be neighbors")
.0;
2020-04-23 16:00:48 +00:00
ctx.sim.get_mut(locs[0]).unwrap().path.neighbors |=
1 << ((to_prev_idx as u8 + 4) % 8);
ctx.sim.get_mut(locs[2]).unwrap().path.neighbors |=
1 << ((to_next_idx as u8 + 4) % 8);
let mut chunk = ctx.sim.get_mut(locs[1]).unwrap();
2020-04-23 16:00:48 +00:00
chunk.path.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),
);
}
// Take note of the track
let track = self.tracks.insert(Track { cost, path });
self.track_map
.entry(site)
.or_default()
.insert(nearby, track);
}
2020-03-27 18:52:28 +00:00
}
}
2020-03-27 13:16:02 +00:00
}
Some(site)
}
2020-04-23 15:16:08 +00:00
fn tick(&mut self, _ctx: &mut GenCtx<impl Rng>, years: f32) {
2020-03-28 18:16:19 +00:00
for site in self.sites.iter_mut() {
site.simulate(years, &self.places.get(site.place).nat_res);
2020-03-28 18:16:19 +00:00
}
// Trade stocks
// let mut stocks = TRADE_STOCKS;
2020-04-17 23:29:01 +00:00
// 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 {
2020-04-17 23:29:01 +00:00
// 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, max_import) = {
// let site = self.sites.get(site);
// let budget = site.coin * 0.5;
2020-04-17 23:29:01 +00:00
// 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,
2020-04-17 23:29:01 +00:00
// -site.export_targets[stock].min(0.0), )
// };
// let (quantity, spent) = econ::buy_units(ctx, sell_orders
// .iter_mut()
2020-04-17 23:29:01 +00:00
// .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;
2020-04-17 23:29:01 +00:00
// 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.last_exports[stock] = order.q_sold;
//
2020-04-17 23:29:01 +00:00
// site.trade_states[stock].sell_belief.update_seller(order.q_sold /
// order.quantity); }
// }
// }
2020-03-27 13:16:02 +00:00
}
}
/// Attempt to find a path between two locations
2020-04-17 23:29:01 +00:00
fn find_path(
ctx: &mut GenCtx<impl Rng>,
a: Vec2<i32>,
b: Vec2<i32>,
) -> Option<(Path<Vec2<i32>>, f32)> {
2020-03-27 13:16:02 +00:00
let sim = &ctx.sim;
let heuristic = move |l: &Vec2<i32>| (l.distance_squared(b) as f32).sqrt();
let neighbors = |l: &Vec2<i32>| {
let l = *l;
2020-04-20 00:17:54 +00:00
NEIGHBORS
2020-04-17 23:29:01 +00:00
.iter()
.filter(move |dir| walk_in_dir(sim, l, **dir).is_some())
.map(move |dir| l + *dir)
2020-03-27 13:16:02 +00:00
};
2020-04-17 23:29:01 +00:00
let transition =
|a: &Vec2<i32>, b: &Vec2<i32>| 1.0 + walk_in_dir(sim, *a, *b - *a).unwrap_or(10000.0);
2020-03-27 13:16:02 +00:00
let satisfied = |l: &Vec2<i32>| *l == b;
// We use this hasher (FxHasher64) 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 8-byte keys (for which FxHash is fastest).
let mut astar = Astar::new(
20000,
a,
heuristic,
BuildHasherDefault::<FxHasher64>::default(),
);
2020-03-27 18:52:28 +00:00
astar
.poll(20000, heuristic, neighbors, transition, satisfied)
2020-03-27 13:16:02 +00:00
.into_path()
2020-03-27 18:52:28 +00:00
.and_then(|path| astar.get_cheapest_cost().map(|cost| (path, cost)))
2020-03-27 13:16:02 +00:00
}
2020-04-23 14:01:37 +00:00
/// Return Some if travel between a location and a chunk next to it is permitted
/// If permitted, the approximate relative const of traversal is given
// (TODO: by whom?)
2020-03-27 13:16:02 +00:00
fn walk_in_dir(sim: &WorldSim, a: Vec2<i32>, dir: Vec2<i32>) -> Option<f32> {
2020-04-17 23:29:01 +00:00
if loc_suitable_for_walking(sim, a) && loc_suitable_for_walking(sim, a + dir) {
let a_chunk = sim.get(a)?;
let b_chunk = sim.get(a + dir)?;
let hill_cost = ((b_chunk.alt - a_chunk.alt).abs() / 2.5).powf(2.0);
let water_cost = if b_chunk.river.near_water() {
50.0
2020-04-20 16:30:39 +00:00
} else {
0.0
};
let wild_cost = if b_chunk.path.is_path() {
0.0 // Traversing existing paths has no additional cost!
} else {
2.0
};
Some(1.0 + hill_cost + water_cost + wild_cost)
2020-03-27 13:16:02 +00:00
} else {
None
}
}
/// Return true if a position is suitable for walking on
fn loc_suitable_for_walking(sim: &WorldSim, loc: Vec2<i32>) -> bool {
if let Some(chunk) = sim.get(loc) {
!chunk.river.is_ocean() && !chunk.river.is_lake()
} else {
false
}
}
2020-04-17 23:29:01 +00:00
/// Return true if a site could be constructed between a location and a chunk
/// next to it is permitted (TODO: by whom?)
2020-03-27 13:16:02 +00:00
fn site_in_dir(sim: &WorldSim, a: Vec2<i32>, dir: Vec2<i32>) -> bool {
2020-04-17 23:29:01 +00:00
loc_suitable_for_site(sim, a) && loc_suitable_for_site(sim, a + dir)
2020-03-27 13:16:02 +00:00
}
2020-04-17 23:29:01 +00:00
/// Return true if a position is suitable for site construction (TODO:
/// criteria?)
2020-03-27 13:16:02 +00:00
fn loc_suitable_for_site(sim: &WorldSim, loc: Vec2<i32>) -> bool {
if let Some(chunk) = sim.get(loc) {
2020-04-17 23:29:01 +00:00
!chunk.river.is_ocean()
&& !chunk.river.is_lake()
&& sim
.get_gradient_approx(loc)
.map(|grad| grad < 1.0)
.unwrap_or(false)
2020-03-27 13:16:02 +00:00
} else {
false
}
}
/// Attempt to search for a location that's suitable for site construction
fn find_site_loc(ctx: &mut GenCtx<impl Rng>, near: Option<(Vec2<i32>, f32)>) -> Option<Vec2<i32>> {
const MAX_ATTEMPTS: usize = 100;
let mut loc = None;
for _ in 0..MAX_ATTEMPTS {
let test_loc = loc.unwrap_or_else(|| match near {
2020-04-17 23:29:01 +00:00
Some((origin, dist)) => {
origin
+ (Vec2::new(ctx.rng.gen_range(-1.0, 1.0), ctx.rng.gen_range(-1.0, 1.0))
.try_normalized()
.unwrap_or(Vec2::zero())
* ctx.rng.gen::<f32>()
* dist)
.map(|e| e as i32)
},
2020-03-27 13:16:02 +00:00
None => Vec2::new(
ctx.rng.gen_range(0, ctx.sim.get_size().x as i32),
ctx.rng.gen_range(0, ctx.sim.get_size().y as i32),
),
});
if loc_suitable_for_site(&ctx.sim, test_loc) {
return Some(test_loc);
}
2020-04-17 23:29:01 +00:00
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)
}),
)
});
2020-03-27 13:16:02 +00:00
}
None
}
#[derive(Debug)]
pub struct Civ {
capital: Id<Site>,
2020-03-27 13:16:02 +00:00
homeland: Id<Place>,
}
#[derive(Debug)]
2020-03-27 13:16:02 +00:00
pub struct Place {
center: Vec2<i32>,
nat_res: NaturalResources,
}
// Productive capacity per year
#[derive(Default, Debug)]
pub struct NaturalResources {
wood: f32,
rock: f32,
river: f32,
farmland: f32,
}
impl NaturalResources {
fn include_chunk(&mut self, ctx: &mut GenCtx<impl Rng>, loc: Vec2<i32>) {
2020-04-17 23:29:01 +00:00
let chunk = if let Some(chunk) = ctx.sim.get(loc) {
chunk
} else {
return;
};
self.wood += chunk.tree_density;
self.rock += chunk.rockiness;
self.river += if chunk.river.is_river() { 5.0 } else { 0.0 };
2020-04-17 23:29:01 +00:00
self.farmland += if chunk.humidity > 0.35
&& chunk.temp > -0.3
&& chunk.temp < 0.75
&& chunk.chaos < 0.5
&& ctx
.sim
.get_gradient_approx(loc)
.map(|grad| grad < 0.7)
.unwrap_or(false)
{
1.0
} else {
0.0
};
}
2020-03-27 13:16:02 +00:00
}
2020-03-27 18:52:28 +00:00
pub struct Track {
2020-04-17 23:29:01 +00:00
/// Cost of using this track relative to other paths. This cost is an
/// arbitrary unit and doesn't make sense unless compared to other track
/// costs.
2020-03-27 18:52:28 +00:00
cost: f32,
2020-03-27 13:16:02 +00:00
path: Path<Vec2<i32>>,
}
#[derive(Debug)]
pub struct Site {
pub kind: SiteKind,
pub center: Vec2<i32>,
2020-03-28 18:16:19 +00:00
pub place: Id<Place>,
population: f32,
// Total amount of each stock
2020-03-28 18:16:19 +00:00
stocks: Stocks<f32>,
2020-03-30 22:45:41 +00:00
// Surplus stock compared to demand orders
surplus: Stocks<f32>,
// For some goods, such a goods without any supply, it doesn't make sense to talk about value
values: Stocks<Option<f32>>,
// Proportion of individuals dedicated to an industry
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>,
2020-03-30 22:45:41 +00:00
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")?,
2020-04-15 19:41:40 +00:00
SiteKind::Dungeon => writeln!(f, "Dungeon")?,
}
writeln!(f, "- population: {}", self.population.floor() as u32)?;
2020-03-30 22:45:41 +00:00
writeln!(f, "- coin: {}", self.coin.floor() as u32)?;
writeln!(f, "Stocks")?;
for (stock, q) in self.stocks.iter() {
writeln!(f, "- {:?}: {}", stock, q.floor())?;
}
writeln!(f, "Values")?;
for stock in TRADE_STOCKS.iter() {
2020-04-17 23:29:01 +00:00
writeln!(
f,
"- {:?}: {}",
2020-04-17 23:29:01 +00:00
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(())
}
}
#[derive(Debug)]
pub enum SiteKind {
Settlement,
2020-04-15 19:41:40 +00:00
Dungeon,
}
2020-03-28 18:16:19 +00:00
impl Site {
pub fn simulate(&mut self, years: f32, nat_res: &NaturalResources) {
// Insert natural resources into the economy
if self.stocks[Fish] < nat_res.river {
self.stocks[Fish] = nat_res.river;
}
if self.stocks[Wheat] < nat_res.farmland {
self.stocks[Wheat] = nat_res.farmland;
}
if self.stocks[Logs] < nat_res.wood {
self.stocks[Logs] = nat_res.wood;
}
if self.stocks[Game] < nat_res.wood {
self.stocks[Game] = nat_res.wood;
}
if self.stocks[Rock] < nat_res.rock {
self.stocks[Rock] = nat_res.rock;
}
// 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).
let orders = vec![
(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)]),
(Some(Hunter), vec![(Game, 4.0)]),
(Some(Farmer), vec![(Wheat, 4.0)]),
]
2020-04-17 23:29:01 +00:00
.into_iter()
.collect::<HashMap<_, Vec<(Stock, f32)>, BuildHasherDefault<FxHasher32>>>();
// Per labourer, per year
let production = MapVec::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)),
],
(Rock, 0.0),
);
let mut demand = Stocks::from_default(0.0);
for (labor, orders) in &orders {
2020-04-17 23:29:01 +00:00
let scale = if let Some(labor) = labor {
self.labors[*labor]
} else {
1.0
} * self.population;
for (stock, amount) in orders {
demand[*stock] += *amount * scale;
}
}
2020-03-28 18:16:19 +00:00
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;
2020-04-17 23:29:01 +00:00
self.surplus = demand
.clone()
2020-04-23 15:16:08 +00:00
.map(|stock, _| supply[stock] + stocks[stock] - demand[stock] - last_exports[stock]);
// Update values according to the surplus of each stock
2020-03-30 22:45:41 +00:00
let values = &mut self.values;
self.surplus.iter().for_each(|(stock, surplus)| {
2020-03-30 23:03:56 +00:00
let val = 3.5f32.powf(1.0 - *surplus / demand[stock]);
2020-04-17 23:29:01 +00:00
values[stock] = if val > 0.001 && val < 1000.0 {
Some(val)
} else {
None
};
});
2020-03-28 18:16:19 +00:00
// Update export targets based on relative values
2020-04-17 23:29:01 +00:00
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;
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 };
2020-04-17 23:29:01 +00:00
export_targets[stock] = last_exports[stock] - rvalue * 0.1; // + (trade_states[stock].sell_belief.price - trade_states[stock].buy_belief.price) * 0.025;
});
2020-03-28 18:16:19 +00:00
let population = self.population;
// Redistribute workforce according to relative good values
let labor_ratios = production.clone().map(|labor, (output_stock, _)| {
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);
production.iter().for_each(|(labor, _)| {
let smooth = 0.8;
2020-04-17 23:29:01 +00:00
self.labors[labor] = smooth * self.labors[labor]
+ (1.0 - smooth)
* (labor_ratios[labor].max(labor_ratio_sum / 1000.0) / labor_ratio_sum);
});
2020-03-28 18:16:19 +00:00
// Production
let stocks_before = self.stocks.clone();
for (labor, orders) in orders.iter() {
2020-04-17 23:29:01 +00:00
let scale = if let Some(labor) = labor {
self.labors[*labor]
} else {
1.0
} * population;
// 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 productivity = orders
.iter()
.map(|(stock, amount)| {
// What quantity is this order requesting?
2020-04-23 15:16:08 +00:00
let _quantity = *amount * scale;
// What proportion of this order is the economy able to satisfy?
let satisfaction = (stocks_before[*stock] / demand[*stock]).min(1.0);
satisfaction
})
.min_by(|a, b| a.partial_cmp(b).unwrap())
2020-04-17 23:29:01 +00:00
.unwrap_or_else(|| {
panic!("Industry {:?} requires at least one input order", labor)
});
for (stock, amount) in orders {
// What quantity is this order requesting?
let quantity = *amount * scale;
// What amount gets actually used in production?
let used = quantity * productivity;
// Deplete stocks accordingly
2020-03-30 22:45:41 +00:00
self.stocks[*stock] = (self.stocks[*stock] - used).max(0.0);
2020-03-28 18:16:19 +00:00
}
// Industries produce things
if let Some(labor) = labor {
let (stock, rate) = production[*labor];
2020-03-30 22:45:41 +00:00
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;
2020-03-30 22:45:41 +00:00
self.stocks[stock] += yield_per_worker * workers.powf(1.1);
2020-03-28 18:16:19 +00:00
}
}
2020-03-30 22:45:41 +00:00
// Denature stocks
self.stocks.iter_mut().for_each(|(_, v)| *v *= 0.9);
// Births/deaths
const NATURAL_BIRTH_RATE: f32 = 0.15;
const DEATH_RATE: f32 = 0.05;
let birth_rate = if self.surplus[Food] > 0.0 {
2020-04-17 23:29:01 +00:00
NATURAL_BIRTH_RATE
} else {
0.0
};
self.population += years * self.population * (birth_rate - DEATH_RATE);
2020-03-28 18:16:19 +00:00
}
}
#[repr(u8)]
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
enum Occupation {
Farmer = 0,
Lumberjack = 1,
Miner = 2,
Fisher = 3,
Hunter = 4,
Cook = 5,
}
#[repr(u8)]
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum Stock {
Wheat = 0,
Flour = 1,
Meat = 2,
Fish = 3,
Game = 4,
Food = 5,
Logs = 6,
Wood = 7,
Rock = 8,
Stone = 9,
}
const TRADE_STOCKS: [Stock; 5] = [Flour, Meat, Food, Wood, Stone];
2020-03-28 18:16:19 +00:00
#[derive(Debug, Clone)]
struct TradeState {
buy_belief: econ::Belief,
sell_belief: econ::Belief,
}
2020-03-28 18:16:19 +00:00
impl Default for TradeState {
fn default() -> Self {
Self {
buy_belief: econ::Belief {
price: 1.0,
confidence: 0.25,
},
sell_belief: econ::Belief {
price: 1.0,
confidence: 0.25,
},
}
}
}
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,
2020-03-28 18:16:19 +00:00
}
/// 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
2020-04-17 23:29:01 +00:00
where
K: 'a,
T: 'a,
2020-03-28 18:16:19 +00:00
{
Self {
2020-03-29 20:46:19 +00:00
entries: i.into_iter().cloned().collect(),
default,
}
}
pub fn from_default(default: T) -> Self {
Self {
entries: HashMap::default(),
default,
2020-03-28 18:16:19 +00:00
}
}
2020-03-29 20:46:19 +00:00
pub fn get_mut(&mut self, entry: K) -> &mut T {
let default = &self.default;
2020-04-17 23:29:01 +00:00
self.entries.entry(entry).or_insert_with(|| default.clone())
2020-03-28 18:16:19 +00:00
}
2020-04-17 23:29:01 +00:00
pub fn get(&self, entry: K) -> &T { self.entries.get(&entry).unwrap_or(&self.default) }
2020-03-28 18:16:19 +00:00
2020-04-23 15:16:08 +00:00
pub fn map<U: Default>(self, mut f: impl FnMut(K, T) -> U) -> MapVec<K, U> {
MapVec {
2020-04-17 23:29:01 +00:00
entries: self
.entries
.into_iter()
.map(|(s, v)| (s.clone(), f(s, v)))
.collect(),
default: U::default(),
}
2020-03-28 18:16:19 +00:00
}
2020-04-17 23:29:01 +00:00
pub fn iter(&self) -> impl Iterator<Item = (K, &T)> + '_ {
2020-03-29 20:46:19 +00:00
self.entries.iter().map(|(s, v)| (*s, v))
2020-03-28 18:16:19 +00:00
}
2020-04-17 23:29:01 +00:00
pub fn iter_mut(&mut self) -> impl Iterator<Item = (K, &mut T)> + '_ {
2020-03-29 20:46:19 +00:00
self.entries.iter_mut().map(|(s, v)| (*s, v))
}
}
impl<K: Copy + Eq + Hash, T: Clone> std::ops::Index<K> for MapVec<K, T> {
2020-03-28 18:16:19 +00:00
type Output = T;
2020-04-17 23:29:01 +00:00
2020-03-29 20:46:19 +00:00
fn index(&self, entry: K) -> &Self::Output { self.get(entry) }
}
2020-03-28 18:16:19 +00:00
impl<K: Copy + Eq + Hash, T: Clone> std::ops::IndexMut<K> for MapVec<K, T> {
2020-03-29 20:46:19 +00:00
fn index_mut(&mut self, entry: K) -> &mut Self::Output { self.get_mut(entry) }
2020-03-28 18:16:19 +00:00
}