diff --git a/assets/voxygen/shaders/fluid-vert.glsl b/assets/voxygen/shaders/fluid-vert.glsl index 412b46bd80..3386ebba06 100644 --- a/assets/voxygen/shaders/fluid-vert.glsl +++ b/assets/voxygen/shaders/fluid-vert.glsl @@ -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); diff --git a/common/src/store.rs b/common/src/store.rs index cb0916691c..6d6320bdbc 100644 --- a/common/src/store.rs +++ b/common/src/store.rs @@ -45,6 +45,8 @@ impl Store { pub fn iter(&self) -> impl Iterator { self.items.iter() } + pub fn iter_mut(&mut self) -> impl Iterator { self.items.iter_mut() } + pub fn iter_ids(&self) -> impl Iterator, &T)> { self .items diff --git a/world/examples/water.rs b/world/examples/water.rs index 4f6696b152..74695b5fb9 100644 --- a/world/examples/water.rs +++ b/world/examples/water.rs @@ -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::::from(focus) + (Vec2::new(mx as f64, my as f64) * scale)) + let chunk_pos = (Vec2::::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); diff --git a/world/src/civ/mod.rs b/world/src/civ/mod.rs index 7b1183f9b2..fe7778d8d9 100644 --- a/world/src/civ/mod.rs +++ b/world/src/civ/mod.rs @@ -9,7 +9,7 @@ use common::{ path::Path, astar::Astar, }; -use crate::sim::WorldSim; +use crate::sim::{WorldSim, SimChunk}; const CARDINALS: [Vec2; 4] = [ Vec2::new(1, 0), @@ -39,8 +39,11 @@ const INITIAL_CIV_COUNT: usize = 20; pub struct Civs { civs: Store, places: Store, + tracks: Store, - track_map: HashMap, HashMap, Id>>, + track_map: HashMap, HashMap, Id>>, + + sites: Store, } 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 { self.places.get(id) } + + fn display_info(&self) { + for (id, civ) in self.civs.iter_ids() { + println!("# Civilisation {:?}", id); + println!("Name: {}", ""); + 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, b: Id) -> Option> { + fn track_between(&self, a: Id, b: Id) -> Option> { 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) -> impl Iterator> + '_ { + 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, b: Id) -> Option<(Path>, f32)> { - let heuristic = move |p: &Id| (self.places.get(*p).center.distance_squared(self.places.get(b).center) as f32).sqrt(); - let neighbors = |p: &Id| { - 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, b: &Id| self.tracks.get(self.track_between(*a, *b).unwrap()).cost; - let satisfied = |p: &Id| *p == b; + fn route_between(&self, a: Id, b: Id) -> Option<(Path>, f32)> { + let heuristic = move |p: &Id| (self.sites.get(*p).center.distance_squared(self.sites.get(b).center) as f32).sqrt(); + let neighbors = |p: &Id| self.neighbors(*p); + let transition = |a: &Id, b: &Id| self.tracks.get(self.track_between(*a, *b).unwrap()).cost; + let satisfied = |p: &Id| *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) -> Option> { - const CIV_BIRTHPLACE_AREA: Range = 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, loc: Vec2, kind: SiteKind) -> Option> { + const SITE_AREA: Range = 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, dir: Vec2) -> Option { { 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, dir: Vec2) -> bool { /// Return true if a position is suitable for site construction (TODO: criteria?) fn loc_suitable_for_site(sim: &WorldSim, loc: Vec2) -> 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, near: Option<(Vec2, f32)>) -> #[derive(Debug)] pub struct Civ { + capital: Id, homeland: Id, } +#[derive(Debug)] pub struct Place { center: Vec2, + 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, loc: Vec2) { + 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>, } + +#[derive(Debug)] +pub struct Site { + kind: SiteKind, + center: Vec2, + place: Id, +} + +#[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, +} diff --git a/world/src/column/mod.rs b/world/src/column/mod.rs index d822099bd2..3e9e74a702 100644 --- a/world/src/column/mod.rs +++ b/world/src/column/mod.rs @@ -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, pub chunk: &'a SimChunk, diff --git a/world/src/lib.rs b/world/src/lib.rs index 98ec73eb10..5f82b0fa90 100644 --- a/world/src/lib.rs +++ b/world/src/lib.rs @@ -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 } diff --git a/world/src/sim/mod.rs b/world/src/sim/mod.rs index 22afd5ab35..ea7b8c5039 100644 --- a/world/src/sim/mod.rs +++ b/world/src/sim/mod.rs @@ -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| { @@ -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, pub river: RiverData, pub sites: Vec, @@ -1798,12 +1800,6 @@ pub struct RegionInfo { pub seed: u32, } -#[derive(Clone)] -pub struct LocationInfo { - pub loc_idx: usize, - pub near: Vec, -} - 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 { + // 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 { diff --git a/world/src/site/mod.rs b/world/src/site/mod.rs index 6245c40e07..1fe239851b 100644 --- a/world/src/site/mod.rs +++ b/world/src/site/mod.rs @@ -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 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"), + } + } +}