mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Began work on basic economic simulation
This commit is contained in:
parent
348003fc1a
commit
46190aa634
@ -40,6 +40,8 @@ void main() {
|
||||
|
||||
f_pos_norm = v_pos_norm;
|
||||
|
||||
f_pos.z -= 0.2;
|
||||
|
||||
gl_Position =
|
||||
all_mat *
|
||||
vec4(f_pos, 1);
|
||||
|
@ -45,6 +45,8 @@ impl<T> Store<T> {
|
||||
|
||||
pub fn iter(&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 iter_ids(&self) -> impl Iterator<Item = (Id<T>, &T)> {
|
||||
self
|
||||
.items
|
||||
|
@ -18,14 +18,14 @@ fn main() {
|
||||
let map_file =
|
||||
// "map_1575990726223.bin";
|
||||
// "map_1575987666972.bin";
|
||||
"map_1576046079066.bin";
|
||||
let mut _map_file = PathBuf::from("./maps");
|
||||
_map_file.push(map_file);
|
||||
"map_1585335358316.bin";
|
||||
let mut map_path = PathBuf::from("./maps");
|
||||
map_path.push(map_file);
|
||||
|
||||
let world = World::generate(5284, WorldOpts {
|
||||
seed_elements: false,
|
||||
// world_file: sim::FileOpts::Load(_map_file),
|
||||
world_file: sim::FileOpts::Save,
|
||||
world_file: sim::FileOpts::Load(map_path),
|
||||
//world_file: sim::FileOpts::Save,
|
||||
..WorldOpts::default()
|
||||
});
|
||||
|
||||
@ -150,12 +150,16 @@ fn main() {
|
||||
}
|
||||
if win.get_mouse_down(minifb::MouseButton::Left) {
|
||||
if let Some((mx, my)) = win.get_mouse_pos(minifb::MouseMode::Clamp) {
|
||||
let pos = (Vec2::<f64>::from(focus) + (Vec2::new(mx as f64, my as f64) * scale))
|
||||
let chunk_pos = (Vec2::<f64>::from(focus) + (Vec2::new(mx as f64, my as f64) * scale))
|
||||
.map(|e| e as i32);
|
||||
println!(
|
||||
"Chunk position: {:?}",
|
||||
pos.map2(TerrainChunkSize::RECT_SIZE, |e, f| e * f as i32)
|
||||
);
|
||||
let block_pos = chunk_pos.map2(TerrainChunkSize::RECT_SIZE, |e, f| e * f as i32);
|
||||
println!("Block: ({}, {}), Chunk: ({}, {})", block_pos.x, block_pos.y, chunk_pos.x, chunk_pos.y);
|
||||
if let Some(chunk) = sampler.get(chunk_pos) {
|
||||
//println!("Chunk info: {:#?}", chunk);
|
||||
if let Some(place) = &chunk.place {
|
||||
println!("Place {} info: {:#?}", place.id(), world.civs().place(*place));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let is_camera = win.is_key_down(minifb::Key::C);
|
||||
|
@ -9,7 +9,7 @@ use common::{
|
||||
path::Path,
|
||||
astar::Astar,
|
||||
};
|
||||
use crate::sim::WorldSim;
|
||||
use crate::sim::{WorldSim, SimChunk};
|
||||
|
||||
const CARDINALS: [Vec2<i32>; 4] = [
|
||||
Vec2::new(1, 0),
|
||||
@ -39,8 +39,11 @@ const INITIAL_CIV_COUNT: usize = 20;
|
||||
pub struct Civs {
|
||||
civs: Store<Civ>,
|
||||
places: Store<Place>,
|
||||
|
||||
tracks: Store<Track>,
|
||||
track_map: HashMap<Id<Place>, HashMap<Id<Place>, Id<Track>>>,
|
||||
track_map: HashMap<Id<Site>, HashMap<Id<Site>, Id<Track>>>,
|
||||
|
||||
sites: Store<Site>,
|
||||
}
|
||||
|
||||
struct GenCtx<'a, R: Rng> {
|
||||
@ -55,13 +58,18 @@ impl Civs {
|
||||
let mut ctx = GenCtx { sim, rng: &mut rng };
|
||||
|
||||
for _ in 0..INITIAL_CIV_COUNT {
|
||||
if let Some(civ) = this.birth_civ(&mut ctx) {
|
||||
println!("Initial civilisation: {:#?}", this.civs.get(civ));
|
||||
} else {
|
||||
println!("Failed to find starting site");
|
||||
println!("Creating civilisation...");
|
||||
if let None = this.birth_civ(&mut ctx) {
|
||||
println!("Failed to find starting site for civilisation.");
|
||||
}
|
||||
}
|
||||
|
||||
// Tick
|
||||
const SIM_YEARS: usize = 100;
|
||||
for _ in 0..SIM_YEARS {
|
||||
this.tick(1.0);
|
||||
}
|
||||
|
||||
// Temporary!
|
||||
for track in this.tracks.iter() {
|
||||
for loc in track.path.iter() {
|
||||
@ -69,11 +77,28 @@ impl Civs {
|
||||
}
|
||||
}
|
||||
|
||||
this.display_info();
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
pub fn place(&self, id: Id<Place>) -> &Place { self.places.get(id) }
|
||||
|
||||
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);
|
||||
println!("{:?}", site);
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the direct track between two places
|
||||
fn track_between(&self, a: Id<Place>, b: Id<Place>) -> Option<Id<Track>> {
|
||||
fn track_between(&self, a: Id<Site>, b: Id<Site>) -> Option<Id<Track>> {
|
||||
self.track_map
|
||||
.get(&a)
|
||||
.and_then(|dests| dests.get(&b))
|
||||
@ -83,17 +108,19 @@ impl Civs {
|
||||
.copied()
|
||||
}
|
||||
|
||||
/// Return an iterator over a site's neighbors
|
||||
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()
|
||||
}
|
||||
|
||||
/// Find the cheapest route between two places
|
||||
fn route_between(&self, a: Id<Place>, b: Id<Place>) -> Option<(Path<Id<Place>>, f32)> {
|
||||
let heuristic = move |p: &Id<Place>| (self.places.get(*p).center.distance_squared(self.places.get(b).center) as f32).sqrt();
|
||||
let neighbors = |p: &Id<Place>| {
|
||||
let p = *p;
|
||||
let to = self.track_map.get(&p).map(|dests| dests.keys()).into_iter().flatten();
|
||||
let fro = self.track_map.iter().filter(move |(_, dests)| dests.contains_key(&p)).map(|(p, _)| p);
|
||||
to.chain(fro).filter(|p| **p != a).copied()
|
||||
};
|
||||
let transition = |a: &Id<Place>, b: &Id<Place>| self.tracks.get(self.track_between(*a, *b).unwrap()).cost;
|
||||
let satisfied = |p: &Id<Place>| *p == b;
|
||||
fn route_between(&self, a: Id<Site>, b: Id<Site>) -> Option<(Path<Id<Site>>, f32)> {
|
||||
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);
|
||||
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;
|
||||
let mut astar = Astar::new(100, a, heuristic);
|
||||
astar
|
||||
.poll(100, heuristic, neighbors, transition, satisfied)
|
||||
@ -102,14 +129,17 @@ impl Civs {
|
||||
}
|
||||
|
||||
fn birth_civ(&mut self, ctx: &mut GenCtx<impl Rng>) -> Option<Id<Civ>> {
|
||||
const CIV_BIRTHPLACE_AREA: Range<usize> = 64..256;
|
||||
let place = attempt(5, || {
|
||||
let site = attempt(5, || {
|
||||
let loc = find_site_loc(ctx, None)?;
|
||||
self.establish_place(ctx, loc, CIV_BIRTHPLACE_AREA)
|
||||
self.establish_site(ctx, loc, SiteKind::Settlement(Settlement {
|
||||
stocks: Stocks::default(),
|
||||
population: 24,
|
||||
}))
|
||||
})?;
|
||||
|
||||
let civ = self.civs.insert(Civ {
|
||||
homeland: place,
|
||||
capital: site,
|
||||
homeland: self.sites.get(site).place,
|
||||
});
|
||||
|
||||
Some(civ)
|
||||
@ -144,11 +174,37 @@ impl Civs {
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
fn establish_site(&mut self, ctx: &mut GenCtx<impl Rng>, loc: Vec2<i32>, kind: SiteKind) -> 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)?,
|
||||
};
|
||||
|
||||
let site = self.sites.insert(Site {
|
||||
kind,
|
||||
center: loc,
|
||||
place: place,
|
||||
});
|
||||
|
||||
// Find neighbors
|
||||
const MAX_NEIGHBOR_DISTANCE: f32 = 250.0;
|
||||
let mut nearby = self.places
|
||||
let mut nearby = self.sites
|
||||
.iter_ids()
|
||||
.map(|(id, p)| (id, (p.center.distance_squared(loc) as f32).sqrt()))
|
||||
.filter(|(p, dist)| *dist < MAX_NEIGHBOR_DISTANCE)
|
||||
@ -157,10 +213,10 @@ impl Civs {
|
||||
|
||||
for (nearby, _) in nearby.into_iter().take(ctx.rng.gen_range(3, 5)) {
|
||||
// Find a novel path
|
||||
if let Some((path, cost)) = find_path(ctx, loc, self.places.get(nearby).center) {
|
||||
if let Some((path, cost)) = find_path(ctx, loc, self.sites.get(nearby).center) {
|
||||
// Find a path using existing paths
|
||||
if self
|
||||
.route_between(place, nearby)
|
||||
.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()
|
||||
@ -170,21 +226,25 @@ impl Civs {
|
||||
path,
|
||||
});
|
||||
self.track_map
|
||||
.entry(place)
|
||||
.entry(site)
|
||||
.or_default()
|
||||
.insert(nearby, track);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Write place to map
|
||||
for cell in dead.union(&alive) {
|
||||
if let Some(chunk) = ctx.sim.get_mut(*cell) {
|
||||
chunk.place = Some(place);
|
||||
Some(site)
|
||||
}
|
||||
|
||||
pub fn tick(&mut self, years: f32) {
|
||||
for site in self.sites.iter_mut() {
|
||||
match &mut site.kind {
|
||||
SiteKind::Settlement(s) => {
|
||||
s.collect_stocks(years, &self.places.get(site.place).nat_res);
|
||||
s.consume_stocks(years);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Some(place)
|
||||
}
|
||||
}
|
||||
|
||||
@ -212,7 +272,7 @@ fn walk_in_dir(sim: &WorldSim, a: Vec2<i32>, dir: Vec2<i32>) -> Option<f32> {
|
||||
{
|
||||
let a_alt = sim.get(a)?.alt;
|
||||
let b_alt = sim.get(a + dir)?.alt;
|
||||
Some(0.5 + (b_alt - a_alt).max(-0.5).abs() / 5.0)
|
||||
Some((b_alt - a_alt).abs() / 2.5)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@ -236,7 +296,8 @@ fn site_in_dir(sim: &WorldSim, a: Vec2<i32>, dir: Vec2<i32>) -> bool {
|
||||
/// Return true if a position is suitable for site construction (TODO: criteria?)
|
||||
fn loc_suitable_for_site(sim: &WorldSim, loc: Vec2<i32>) -> bool {
|
||||
if let Some(chunk) = sim.get(loc) {
|
||||
!chunk.is_underwater() &&
|
||||
!chunk.river.is_ocean() &&
|
||||
!chunk.river.is_lake() &&
|
||||
sim.get_gradient_approx(loc).map(|grad| grad < 1.0).unwrap_or(false)
|
||||
} else {
|
||||
false
|
||||
@ -272,11 +333,39 @@ fn find_site_loc(ctx: &mut GenCtx<impl Rng>, near: Option<(Vec2<i32>, f32)>) ->
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Civ {
|
||||
capital: Id<Site>,
|
||||
homeland: Id<Place>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Place {
|
||||
center: Vec2<i32>,
|
||||
nat_res: NaturalResources,
|
||||
}
|
||||
|
||||
// Productive capacity per year
|
||||
#[derive(Default, Debug)]
|
||||
pub struct NaturalResources {
|
||||
wood: f32,
|
||||
stone: f32,
|
||||
river: f32,
|
||||
farmland: f32,
|
||||
}
|
||||
|
||||
impl NaturalResources {
|
||||
fn include_chunk(&mut self, ctx: &mut GenCtx<impl Rng>, loc: Vec2<i32>) {
|
||||
let chunk = if let Some(chunk) = ctx.sim.get(loc) { chunk } else { return };
|
||||
|
||||
self.wood += chunk.tree_density;
|
||||
self.stone += chunk.rockiness;
|
||||
self.river += if chunk.river.is_river() { 1.0 } else { 0.0 };
|
||||
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 };
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Track {
|
||||
@ -285,3 +374,66 @@ pub struct Track {
|
||||
cost: f32,
|
||||
path: Path<Vec2<i32>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Site {
|
||||
kind: SiteKind,
|
||||
center: Vec2<i32>,
|
||||
place: Id<Place>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum SiteKind {
|
||||
Settlement(Settlement),
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Settlement {
|
||||
stocks: Stocks,
|
||||
population: u32,
|
||||
}
|
||||
|
||||
impl Settlement {
|
||||
pub fn collect_stocks(&mut self, years: f32, nat_res: &NaturalResources) {
|
||||
// Per labourer, per year
|
||||
const LUMBER_RATE: f32 = 0.5;
|
||||
const MINE_RATE: f32 = 0.3;
|
||||
const FARM_RATE: f32 = 0.4;
|
||||
|
||||
// No more that 1.0 in total
|
||||
let lumberjacks = 0.2 * self.population as f32;
|
||||
let miners = 0.15 * self.population as f32;
|
||||
let farmers = 0.4 * self.population as f32;
|
||||
|
||||
self.stocks.logs += years * nat_res.wood.min(lumberjacks * LUMBER_RATE);
|
||||
self.stocks.rocks += years * nat_res.stone.min(miners * MINE_RATE);
|
||||
self.stocks.food += years * nat_res.farmland.min(farmers * FARM_RATE);
|
||||
}
|
||||
|
||||
pub fn consume_stocks(&mut self, years: f32) {
|
||||
const EAT_RATE: f32 = 0.15;
|
||||
// Food required to give birth
|
||||
const BIRTH_FOOD: f32 = 0.25;
|
||||
const MAX_ANNUAL_BABIES: f32 = 0.15;
|
||||
|
||||
let needed_food = self.population as f32 * EAT_RATE;
|
||||
let food_surplus = (self.stocks.food - needed_food).max(0.0);
|
||||
let food_deficit = -(self.stocks.food - needed_food).min(0.0);
|
||||
|
||||
self.stocks.food = (self.stocks.food - needed_food).max(0.0);
|
||||
|
||||
self.population -= (food_deficit * EAT_RATE).round() as u32;
|
||||
self.population += (food_surplus / BIRTH_FOOD).round().min(self.population as f32 * MAX_ANNUAL_BABIES) as u32;
|
||||
}
|
||||
|
||||
pub fn happiness(&self) -> f32 {
|
||||
self.stocks.food / self.population as f32
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Stocks {
|
||||
logs: f32,
|
||||
rocks: f32,
|
||||
food: f32,
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ use crate::{
|
||||
all::ForestKind,
|
||||
block::StructureMeta,
|
||||
sim::{
|
||||
local_cells, uniform_idx_as_vec2, vec2_as_uniform_idx, LocationInfo, RiverKind, SimChunk,
|
||||
local_cells, uniform_idx_as_vec2, vec2_as_uniform_idx, RiverKind, SimChunk,
|
||||
WorldSim,
|
||||
},
|
||||
util::{RandomPerm, Sampler, UnitChooser},
|
||||
@ -1097,7 +1097,6 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
temp,
|
||||
humidity,
|
||||
spawn_rate,
|
||||
location: sim_chunk.location.as_ref(),
|
||||
stone_col,
|
||||
|
||||
chunk: sim_chunk,
|
||||
@ -1129,7 +1128,6 @@ pub struct ColumnSample<'a> {
|
||||
pub temp: f32,
|
||||
pub humidity: f32,
|
||||
pub spawn_rate: f32,
|
||||
pub location: Option<&'a LocationInfo>,
|
||||
pub stone_col: Rgb<u8>,
|
||||
|
||||
pub chunk: &'a SimChunk,
|
||||
|
@ -47,6 +47,8 @@ impl World {
|
||||
|
||||
pub fn sim(&self) -> &sim::WorldSim { &self.sim }
|
||||
|
||||
pub fn civs(&self) -> &civ::Civs { &self.civs }
|
||||
|
||||
pub fn tick(&self, _dt: Duration) {
|
||||
// TODO
|
||||
}
|
||||
|
@ -1391,6 +1391,7 @@ impl WorldSim {
|
||||
// Place the locations onto the world
|
||||
let gen = StructureGen2d::new(self.seed, cell_size as u32, cell_size as u32 / 2);
|
||||
|
||||
/*
|
||||
self.chunks
|
||||
.par_iter_mut()
|
||||
.enumerate()
|
||||
@ -1447,6 +1448,7 @@ impl WorldSim {
|
||||
}
|
||||
}
|
||||
});
|
||||
*/
|
||||
|
||||
// Stage 2 - towns!
|
||||
let chunk_idx_center = |e: Vec2<i32>| {
|
||||
@ -1767,6 +1769,7 @@ impl WorldSim {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SimChunk {
|
||||
pub chaos: f32,
|
||||
pub alt: f32,
|
||||
@ -1782,7 +1785,6 @@ pub struct SimChunk {
|
||||
pub tree_density: f32,
|
||||
pub forest_kind: ForestKind,
|
||||
pub spawn_rate: f32,
|
||||
pub location: Option<LocationInfo>,
|
||||
pub river: RiverData,
|
||||
|
||||
pub sites: Vec<Site>,
|
||||
@ -1798,12 +1800,6 @@ pub struct RegionInfo {
|
||||
pub seed: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct LocationInfo {
|
||||
pub loc_idx: usize,
|
||||
pub near: Vec<RegionInfo>,
|
||||
}
|
||||
|
||||
impl SimChunk {
|
||||
fn generate(posi: usize, gen_ctx: &GenCtx, gen_cdf: &GenCdf) -> Self {
|
||||
let pos = uniform_idx_as_vec2(posi);
|
||||
@ -2023,7 +2019,6 @@ impl SimChunk {
|
||||
}
|
||||
},
|
||||
spawn_rate: 1.0,
|
||||
location: None,
|
||||
river,
|
||||
|
||||
sites: Vec::new(),
|
||||
@ -2037,11 +2032,16 @@ impl SimChunk {
|
||||
pub fn get_base_z(&self) -> f32 { self.alt - self.chaos * 50.0 - 16.0 }
|
||||
|
||||
pub fn get_name(&self, world: &WorldSim) -> Option<String> {
|
||||
// TODO
|
||||
None
|
||||
|
||||
/*
|
||||
if let Some(loc) = &self.location {
|
||||
Some(world.locations[loc.loc_idx].name().to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
pub fn get_biome(&self) -> BiomeKind {
|
||||
|
@ -11,7 +11,7 @@ use common::{
|
||||
terrain::Block,
|
||||
vol::{BaseVol, RectSizedVol, WriteVol},
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use std::{fmt, sync::Arc};
|
||||
use vek::*;
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -47,3 +47,11 @@ impl Site {
|
||||
impl From<Settlement> for Site {
|
||||
fn from(settlement: Settlement) -> Self { Site::Settlement(Arc::new(settlement)) }
|
||||
}
|
||||
|
||||
impl fmt::Debug for Site {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Site::Settlement(_) => write!(f, "Settlement"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user