diff --git a/Cargo.lock b/Cargo.lock index af538ce3..63248ec3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -335,6 +335,16 @@ dependencies = [ "objc2", ] +[[package]] +name = "bstr" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c48f0051a4b4c5e0b6d365cd04af53aeaa209e3cc15ec2cdb69e73cc87fbd0dc" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "bumpalo" version = "3.14.0" @@ -510,6 +520,7 @@ version = "0.1.0" dependencies = [ "bincode", "egui-inspect", + "fast-float", "geom", "inline_tweak", "log", @@ -1003,6 +1014,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "fast-float" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95765f67b4b18863968b4a1bd5bb576f732b29a4a28c7cd84c09fa3e2875f33c" + [[package]] name = "fdeflate" version = "0.3.3" @@ -1144,6 +1161,7 @@ dependencies = [ "flat_spatial", "fnv", "inline_tweak", + "mlua", "ordered-float", "serde", ] @@ -1702,6 +1720,15 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "luau0-src" +version = "0.7.11+luau606" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07ffc4945ee953a33cb2b331e00b19e11275fc105c8ac8a977c810597d790f08" +dependencies = [ + "cc", +] + [[package]] name = "mach2" version = "0.4.2" @@ -1818,6 +1845,32 @@ version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e53debba6bda7a793e5f99b8dacf19e626084f525f7829104ba9898f367d85ff" +[[package]] +name = "mlua" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "069264935e816c85884b99e88c8b408d6d92e40ae8760f726c983526a53546b5" +dependencies = [ + "bstr", + "libloading 0.8.1", + "mlua-sys", + "num-traits", + "once_cell", + "rustc-hash", +] + +[[package]] +name = "mlua-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4655631a02e3739d014951291ecfa08db49c4da3f7f8c6f3931ed236af5dd78e" +dependencies = [ + "cc", + "cfg-if", + "luau0-src", + "pkg-config", +] + [[package]] name = "naga" version = "0.14.2" @@ -2473,9 +2526,14 @@ dependencies = [ name = "prototypes" version = "0.1.0" dependencies = [ + "common", "egui-inspect", "geom", + "log", + "mlua", "serde", + "slotmapd", + "thiserror", ] [[package]] diff --git a/assets/companies.json b/assets/companies.json deleted file mode 100644 index e2d87616..00000000 --- a/assets/companies.json +++ /dev/null @@ -1,515 +0,0 @@ -[ - { - "name": "Bakery", - "bgen": { - "kind": "centered_door", - "vertical_factor": 1.0 - }, - "kind": "store", - "recipe": { - "consumption": [["flour", 1]], - "production": [["bread", 1]], - "complexity": 100, - "storage_multiplier": 5 - }, - "n_workers": 3, - "size": 10.0, - "asset_location": "bakery.glb", - "price": 1000 - }, - { - "name": "Flour Factory", - "bgen": { - "kind": "centered_door", - "vertical_factor": 0.6 - }, - "kind": "factory", - "n_trucks": 1, - "recipe": { - "consumption": [["cereal", 1]], - "production": [["flour", 10]], - "complexity": 200, - "storage_multiplier": 5 - }, - "n_workers": 10, - "size": 80.0, - "asset_location": "flour_factory.glb", - "price": 1000 - }, - { - "name": "Cereal Farm", - "bgen": {"kind": "farm"}, - "kind": "factory", - "n_trucks": 1, - "recipe": { - "consumption": [], - "production": [["cereal", 1]], - "complexity": 40, - "storage_multiplier": 5 - }, - "n_workers": 10, - "size": 120.0, - "asset_location": "assets/sprites/dirt.jpg", - "price": 200, - "zone": { - "floor": "assets/sprites/dirt.jpg", - "filler": "wheat_up.glb", - "price_per_area": 100, - "randomize_filler": true - } - }, - { - "name": "Solar Panels", - "bgen": { - "kind": "centered_door", - "vertical_factor": 1.0 - }, - "kind": "network", - "recipe": { - "consumption": [], - "production": [["electricity", 3]], - "complexity": 1, - "storage_multiplier": 5 - }, - "n_workers": 1, - "size": 120.0, - "asset_location": "assets/sprites/cement.jpg", - "price": 0, - "zone": { - "floor": "assets/sprites/cement.jpg", - "filler": "solarpanel.glb", - "price_per_area": 10 - } - }, - { - "name": "Coal power plant", - "bgen": { - "kind": "centered_door", - "vertical_factor": 1.0 - }, - "kind": "network", - "recipe": { - "consumption": [["coal", 1]], - "production": [["electricity", 2460]], - "complexity": 100, - "storage_multiplier": 5 - }, - "n_workers": 10, - "size": 165.0, - "asset_location": "coal_power_plant.glb", - "price": 1000 - }, - { - "name": "Supermarket", - "bgen": { - "kind": "centered_door", - "vertical_factor": 1.0 - }, - "kind": "store", - "recipe": { - "consumption": [ - ["meat", 1], - ["vegetable", 1], - ["cereal", 1] - ], - "production": [], - "complexity": 100, - "storage_multiplier": 5 - }, - "n_workers": 10, - "size": 80.0, - "asset_location": "assets/sprites/supermarket.png", - "price": 1000 - }, - { - "name": "Clothes store", - "bgen": { - "kind": "centered_door", - "vertical_factor": 1.0 - }, - "kind": "store", - "recipe": { - "consumption": [["cloth", 1]], - "production": [], - "complexity": 100, - "storage_multiplier": 5 - }, - "n_workers": 10, - "size": 10.0, - "asset_location": "assets/sprites/clothes_store.png", - "price": 1000 - }, - { - "name": "Cloth factory", - "bgen": { - "kind": "centered_door", - "vertical_factor": 1.0 - }, - "kind": "factory", - "n_trucks": 1, - "recipe": { - "consumption": [["polyester", 1], ["wool", 1]], - "production": [["cloth", 1]], - "complexity": 100, - "storage_multiplier": 5 - }, - "n_workers": 10, - "size": 80.0, - "asset_location": "assets/sprites/cloth_factory.png", - "price": 1000 - }, - { - "name": "Polyester refinery", - "bgen": { - "kind": "centered_door", - "vertical_factor": 1.0 - }, - "kind": "factory", - "n_trucks": 1, - "recipe": { - "consumption": [["oil", 1]], - "production": [["polyester", 1]], - "complexity": 100, - "storage_multiplier": 5 - }, - "n_workers": 5, - "size": 80.0, - "asset_location": "assets/sprites/polyester_refinery.png", - "price": 1000 - }, - { - "name": "Oil pump", - "bgen": { - "kind": "centered_door", - "vertical_factor": 1.0 - }, - "kind": "factory", - "n_trucks": 1, - "recipe": { - "consumption": [], - "production": [["oil", 1]], - "complexity": 100, - "storage_multiplier": 5 - }, - "n_workers": 5, - "size": 20.0, - "asset_location": "assets/sprites/oil_pump.png", - "price": 1000 - }, - { - "name": "Coal mine", - "bgen": { - "kind": "centered_door", - "vertical_factor": 1.0 - }, - "kind": "factory", - "n_trucks": 1, - "recipe": { - "consumption": [], - "production": [["coal", 1]], - "complexity": 100, - "storage_multiplier": 5 - }, - "n_workers": 5, - "size": 20.0, - "asset_location": "assets/sprites/oil_pump.png", - "price": 1000 - }, - { - "name": "Textile processing facility", - "bgen": { - "kind": "centered_door", - "vertical_factor": 1.0 - }, - "kind": "factory", - "n_trucks": 1, - "recipe": { - "consumption": [["wool", 1]], - "production": [["cloth", 1]], - "complexity": 100, - "storage_multiplier": 5 - }, - "n_workers": 10, - "size": 80.0, - "asset_location": "assets/sprites/textile_processing_facility.png", - "price": 1000 - }, - { - "name": "Wool farm", - "bgen": { - "kind": "centered_door", - "vertical_factor": 1.0 - }, - "kind": "factory", - "n_trucks": 1, - "recipe": { - "consumption": [], - "production": [["wool", 1]], - "complexity": 100, - "storage_multiplier": 5 - }, - "n_workers": 10, - "size": 80.0, - "asset_location": "assets/sprites/wool_farm.png", - "price": 1000 - }, - { - "name": "Florist", - "bgen": { - "kind": "centered_door", - "vertical_factor": 1.0 - }, - "kind": "store", - "recipe": { - "consumption": [["flower", 1]], - "production": [], - "complexity": 100, - "storage_multiplier": 5 - }, - "n_workers": 10, - "size": 10.0, - "asset_location": "assets/sprites/florist.png", - "price": 1000 - }, - { - "name": "Horticulturalist", - "bgen": { - "kind": "centered_door", - "vertical_factor": 1.0 - }, - "kind": "factory", - "n_trucks": 1, - "recipe": { - "consumption": [], - "production": [["flower", 1]], - "complexity": 100, - "storage_multiplier": 5 - }, - "n_workers": 5, - "size": 80.0, - "asset_location": "assets/sprites/horticulturalist.png", - "price": 1000 - }, - { - "name": "High tech store", - "bgen": { - "kind": "centered_door", - "vertical_factor": 1.0 - }, - "kind": "store", - "recipe": { - "consumption": [["high-tech-product", 1]], - "production": [], - "complexity": 100, - "storage_multiplier": 5 - }, - "n_workers": 10, - "size": 80.0, - "asset_location": "assets/sprites/hightech_store.png", - "price": 1000 - }, - { - "name": "High tech facility", - "bgen": { - "kind": "centered_door", - "vertical_factor": 1.0 - }, - "kind": "factory", - "n_trucks": 1, - "recipe": { - "consumption": [["gold", 1], ["metal", 1]], - "production": [["high-tech-product", 1]], - "complexity": 100, - "storage_multiplier": 5 - }, - "n_workers": 10, - "size": 80.0, - "asset_location": "assets/sprites/hightech_facility.png", - "price": 1000 - }, - { - "name": "Gold mine", - "bgen": { - "kind": "centered_door", - "vertical_factor": 1.0 - }, - "kind": "factory", - "n_trucks": 1, - "recipe": { - "consumption": [], - "production": [["gold", 1]], - "complexity": 100, - "storage_multiplier": 5 - }, - "n_workers": 10, - "size": 80.0, - "asset_location": "assets/sprites/rare_metal_mine.png", - "price": 1000 - }, - { - "name": "Furniture store", - "bgen": { - "kind": "centered_door", - "vertical_factor": 1.0 - }, - "kind": "store", - "recipe": { - "consumption": [["metal", 1], ["wood-plank", 1]], - "production": [["furniture", 1]], - "complexity": 100, - "storage_multiplier": 5 - }, - "n_workers": 10, - "size": 80.0, - "asset_location": "assets/sprites/furniture_store.png", - "price": 1000 - }, - { - "name": "Foundry", - "bgen": { - "kind": "centered_door", - "vertical_factor": 1.0 - }, - "kind": "factory", - "n_trucks": 1, - "recipe": { - "consumption": [["iron-ore", 1]], - "production": [["metal", 1]], - "complexity": 100, - "storage_multiplier": 5 - }, - "n_workers": 10, - "size": 80.0, - "asset_location": "assets/sprites/foundry.png", - "price": 1000 - }, - { - "name": "Iron mine", - "bgen": { - "kind": "centered_door", - "vertical_factor": 1.0 - }, - "kind": "factory", - "n_trucks": 1, - "recipe": { - "consumption": [], - "production": [["iron-ore", 1]], - "complexity": 100, - "storage_multiplier": 5 - }, - "n_workers": 10, - "size": 80.0, - "asset_location": "assets/sprites/iron_mine.png", - "price": 1000 - }, - { - "name": "Woodmill", - "bgen": { - "kind": "centered_door", - "vertical_factor": 1.0 - }, - "kind": "factory", - "n_trucks": 1, - "recipe": { - "consumption": [["tree-log", 1]], - "production": [["wood-plank", 1]], - "complexity": 100, - "storage_multiplier": 5 - }, - "n_workers": 10, - "size": 80.0, - "asset_location": "assets/sprites/woodmill.png", - "price": 1000 - }, - { - "name": "Lumber yard", - "bgen": {"kind": "farm"}, - "kind": "factory", - "n_trucks": 1, - "recipe": { - "consumption": [], - "production": [["tree-log", 1]], - "complexity": 100, - "storage_multiplier": 5 - }, - "n_workers": 10, - "size": 200.0, - "asset_location": "assets/sprites/lumber_yard.png", - "price": 1000 - }, - { - "name": "Meat facility", - "bgen": { - "kind": "centered_door", - "vertical_factor": 0.6 - }, - "kind": "factory", - "n_trucks": 1, - "recipe": { - "consumption": [["raw-meat", 1]], - "production": [["meat", 1]], - "complexity": 100, - "storage_multiplier": 5 - }, - "n_workers": 10, - "size": 80.0, - "asset_location": "assets/sprites/meat_facility.png", - "price": 1000 - }, - { - "name": "Slaughterhouse", - "bgen": { - "kind": "centered_door", - "vertical_factor": 1.0 - }, - "kind": "factory", - "n_trucks": 1, - "recipe": { - "consumption": [["carcass", 1]], - "production": [["raw-meat", 1]], - "complexity": 100, - "storage_multiplier": 5 - }, - "n_workers": 5, - "size": 50.0, - "asset_location": "assets/sprites/slaughterhouse.png", - "price": 1000 - }, - { - "name": "Animal Farm", - "bgen": {"kind": "farm"}, - "kind": "factory", - "n_trucks": 1, - "recipe": { - "consumption": [["cereal", 1]], - "production": [["carcass", 1]], - "complexity": 100, - "storage_multiplier": 5 - }, - "n_workers": 5, - "size": 80.0, - "asset_location": "assets/sprites/animal_farm.png", - "price": 1000 - }, - { - "name": "Vegetable Farm", - "bgen": {"kind": "farm"}, - "kind": "factory", - "n_trucks": 1, - "recipe": { - "consumption": [], - "production": [["vegetable", 2]], - "complexity": 2, - "storage_multiplier": 5 - }, - "n_workers": 10, - "size": 70.0, - "asset_location": "assets/sprites/vegetable_farm.png", - "price": 1000, - "zone": { - "floor": "assets/sprites/dirt.jpg", - "filler": "salad.glb", - "price_per_area": 100 - } - } -] \ No newline at end of file diff --git a/assets/items.json b/assets/items.json deleted file mode 100644 index 102b8158..00000000 --- a/assets/items.json +++ /dev/null @@ -1,91 +0,0 @@ -[ - { - "name": "job-opening", - "label": "Job opening", - "optout_exttrade": true - }, - { - "name": "cereal", - "label": "Cereal" - }, - { - "name": "flour", - "label": "Flour" - }, - { - "name": "bread", - "label": "Bread" - }, - { - "name": "vegetable", - "label": "Vegetable" - }, - { - "name": "carcass", - "label": "Carcass" - }, - { - "name": "raw-meat", - "label": "Raw meat" - }, - { - "name": "meat", - "label": "Meat" - }, - { - "name": "tree-log", - "label": "Tree Log" - }, - { - "name": "wood-plank", - "label": "Wood Plank" - }, - { - "name": "iron-ore", - "label": "Iron Ore" - }, - { - "name": "metal", - "label": "Metal" - }, - { - "name": "gold", - "label": "Gold" - }, - { - "name": "high-tech-product", - "label": "High Tech Product" - }, - { - "name": "furniture", - "label": "Furniture" - }, - { - "name": "flower", - "label": "Flower" - }, - { - "name": "wool", - "label": "Wool" - }, - { - "name": "cloth", - "label": "Cloth" - }, - { - "name": "oil", - "label": "Oil" - }, - { - "name": "coal", - "label": "Coal" - }, - { - "name": "electricity", - "label": "Electricity" - }, - { - "name": "polyester", - "label": "Polyester" - } -] \ No newline at end of file diff --git a/assets_gui/src/companies.rs b/assets_gui/src/companies.rs deleted file mode 100644 index 4088014b..00000000 --- a/assets_gui/src/companies.rs +++ /dev/null @@ -1,24 +0,0 @@ -use common::saveload::Encoder; -use prototypes::GoodsCompanyDescriptionJSON; -use std::io; - -pub struct Companies { - pub companies: Vec, - pub changed: bool, -} - -impl Companies { - pub fn new() -> io::Result { - let cjson = common::saveload::load_raw("assets/companies.json")?; - let companies = common::saveload::JSONPretty::decode(&cjson)?; - Ok(Self { - companies, - changed: false, - }) - } - - #[allow(dead_code)] - pub fn save(&self) { - common::saveload::JSONPretty::save(&self.companies, "companies"); - } -} diff --git a/assets_gui/src/main.rs b/assets_gui/src/main.rs index 07f7f495..f69dd656 100644 --- a/assets_gui/src/main.rs +++ b/assets_gui/src/main.rs @@ -6,12 +6,12 @@ use engine::{ SpriteBatchBuilder, }; use geom::{vec3, InfiniteFrustrum, LinearColor, Plane, Vec2, Vec3}; +use prototypes::{load_prototypes, try_prototype}; use std::path::{Path, PathBuf}; use crate::orbit_camera::OrbitCamera; use crate::yakui_gui::{Gui, Inspected, Shown}; -mod companies; mod lod; mod orbit_camera; mod yakui_gui; @@ -40,7 +40,7 @@ impl engine::framework::State for State { let mut gui = Gui::new(); - gui.inspected = Inspected::Company(1); + gui.inspected = Inspected::None; Self { camera, @@ -126,11 +126,11 @@ impl State { } } -fn create_shown(gfx: &mut GfxContext, state: &State, inspected: Inspected) -> Shown { +fn create_shown(gfx: &mut GfxContext, _state: &State, inspected: Inspected) -> Shown { match inspected { Inspected::None => Shown::None, Inspected::Company(i) => { - let comp = &state.gui.companies.companies[i]; + let comp = try_prototype(i).unwrap(); let p = Path::new(&comp.asset_location); match p.extension() { Some(x) if (x == "png" || x == "jpg") => { @@ -188,7 +188,9 @@ fn create_shown(gfx: &mut GfxContext, state: &State, inspected: Inspected) -> Sh } fn main() { - common::logger::MyLog::init(); - + engine::framework::init(); + unsafe { + load_prototypes("./").unwrap(); + } engine::framework::start::(); } diff --git a/assets_gui/src/yakui_gui.rs b/assets_gui/src/yakui_gui.rs index 68d5e671..7ab10c24 100644 --- a/assets_gui/src/yakui_gui.rs +++ b/assets_gui/src/yakui_gui.rs @@ -1,7 +1,7 @@ -use yakui::widgets::{List, Pad, StateResponse, TextBox}; +use yakui::widgets::{List, Pad, StateResponse}; use yakui::{ - align, center, colored_box_container, column, constrained, pad, row, use_state, Alignment, - Constraints, CrossAxisAlignment, MainAxisAlignment, MainAxisSize, Response, Vec2, + colored_box_container, column, constrained, row, use_state, Constraints, CrossAxisAlignment, + MainAxisAlignment, MainAxisSize, Response, Vec2, }; use engine::meshload::CPUMesh; @@ -9,22 +9,20 @@ use engine::wgpu::RenderPass; use engine::{set_cursor_icon, CursorIcon, Drawable, GfxContext, InstancedMesh, Mesh, SpriteBatch}; use geom::Matrix4; use goryak::{ - background, button_primary, center_width, checkbox_value, combo_box, divider, dragvalue, icon, - interact_box_radius, is_hovered, labelc, on_background, on_secondary, on_secondary_container, - on_surface, outline_variant, round_rect, scroll_vertical, secondary, secondary_container, - set_theme, stretch_width, surface, surface_variant, use_changed, CountGrid, Draggable, - MainAxisAlignItems, RoundRect, Theme, + background, button_primary, checkbox_value, divider, dragvalue, icon, interact_box_radius, + is_hovered, labelc, on_secondary_container, on_surface, outline_variant, round_rect, + scroll_vertical, secondary_container, set_theme, surface, surface_variant, use_changed, + CountGrid, RoundRect, Theme, }; -use prototypes::{BuildingGen, CompanyKind}; +use prototypes::{prototypes_iter, GoodsCompanyID, GoodsCompanyPrototype}; -use crate::companies::Companies; use crate::lod::LodGenerateParams; use crate::{GUIAction, State}; #[derive(PartialEq, Eq, Copy, Clone)] pub enum Inspected { None, - Company(usize), + Company(GoodsCompanyID), } #[derive(Clone)] @@ -36,7 +34,6 @@ pub enum Shown { } pub struct Gui { - pub companies: Companies, pub inspected: Inspected, pub shown: Shown, } @@ -44,7 +41,6 @@ pub struct Gui { impl Gui { pub fn new() -> Self { Self { - companies: Companies::new().expect("could not load companies.json"), inspected: Inspected::None, shown: Shown::None, } @@ -56,7 +52,7 @@ impl State { row(|| { self.explorer(); self.model_properties(); - self.properties(); + //self.properties(); }); } @@ -96,19 +92,15 @@ impl State { companies_open.modify(|x| !x); }, ); - if self.gui.companies.changed && button_primary("Save").clicked { - self.gui.companies.save(); - } if companies_open.get() { - for (i, comp) in self.gui.companies.companies.iter().enumerate() - { + for comp in prototypes_iter::() { Self::explore_item( 4, - Inspected::Company(i) == self.gui.inspected, + Inspected::Company(comp.id) == self.gui.inspected, comp.name.to_string(), None, || { - self.gui.inspected = Inspected::Company(i); + self.gui.inspected = Inspected::Company(comp.id); }, ); } @@ -267,12 +259,13 @@ impl State { }); } + /* fn properties(&mut self) { match self.gui.inspected { Inspected::None => {} Inspected::Company(i) => { properties_container(|| { - let comp = &mut self.gui.companies.companies[i]; + let comp = prototype(i).unwrap(); let label = |name: &str| { pad(Pad::all(3.0), || { @@ -399,10 +392,10 @@ impl State { }); } } - } + }*/ } -fn properties_container(children: impl FnOnce()) { +/*fn properties_container(children: impl FnOnce()) { let mut off = use_state(|| 350.0); resizebar_vert(&mut off, true); constrained( @@ -426,7 +419,7 @@ fn properties_container(children: impl FnOnce()) { }); }, ); -} +}*/ /// A horizontal resize bar. pub fn resizebar_vert(off: &mut Response>, scrollbar_on_left_side: bool) { @@ -465,7 +458,7 @@ pub fn resizebar_vert(off: &mut Response>, scrollbar_on_left_ }); } -fn text_inp(v: &mut String) { +/*fn text_inp(v: &mut String) { center(|| { let mut t = TextBox::new(v.clone()); t.fill = Some(secondary()); @@ -474,7 +467,7 @@ fn text_inp(v: &mut String) { *v = x; } }); -} +}*/ impl Drawable for Shown { fn draw<'a>(&'a self, gfx: &'a GfxContext, rp: &mut RenderPass<'a>) { diff --git a/base_mod/companies.lua b/base_mod/companies.lua new file mode 100644 index 00000000..bb37211e --- /dev/null +++ b/base_mod/companies.lua @@ -0,0 +1,567 @@ +data:extend { + { + type = "goods-company", + name = "bakery", + label = "Bakery", + bgen = { + kind = "centered_door", + vertical_factor = 1.0, + }, + kind = "store", + recipe = { + consumption = {{"flour", 1}}, + production = {{"bread", 1}}, + complexity = 100, + storage_multiplier = 5, + }, + n_workers = 3, + size = 10.0, + asset_location = "bakery.glb", + price = 1000, + }, + { + type = "goods-company", + name = "flour-factory", + label = "Flour Factory", + bgen = { + kind = "centered_door", + vertical_factor = 0.6, + }, + kind = "factory", + n_trucks = 1, + recipe = { + consumption = {{"cereal", 1}}, + production = {{"flour", 10}}, + complexity = 200, + storage_multiplier = 5, + }, + n_workers = 10, + size = 80.0, + asset_location = "flour_factory.glb", + price = 1000, + }, + { + type = "goods-company", + name = "cereal-farm", + label = "Cereal Farm", + bgen = "farm", + kind = "factory", + n_trucks = 1, + recipe = { + consumption = {}, + production = {{"cereal", 1}}, + complexity = 40, + storage_multiplier = 5, + }, + n_workers = 10, + size = 120.0, + asset_location = "assets/sprites/dirt.jpg", + price = 200, + zone = { + floor = "assets/sprites/dirt.jpg", + filler = "wheat_up.glb", + price_per_area = 100, + randomize_filler = true, + }, + }, + { + type = "goods-company", + name = "solar-panels", + label = "Solar Panels", + bgen = { + kind = "centered_door", + vertical_factor = 1.0, + }, + kind = "network", + recipe = { + consumption = {}, + production = {}, + complexity = 1, + storage_multiplier = 5, + power_generation = "1kW", + }, + n_workers = 1, + size = 120.0, + asset_location = "assets/sprites/cement.jpg", + price = 0, + zone = { + floor = "assets/sprites/cement.jpg", + filler = "solarpanel.glb", + price_per_area = 10, + }, + }, + { + type = "goods-company", + name = "coal-power-plant", + label = "Coal power plant", + bgen = { + kind = "centered_door", + vertical_factor = 1.0, + }, + kind = "network", + recipe = { + consumption = {{"coal", 1}}, + production = {}, + complexity = 100, + storage_multiplier = 5, + power_generation = "2.46MW", + }, + n_workers = 10, + size = 165.0, + asset_location = "coal_power_plant.glb", + price = 1000, + }, + { + type = "goods-company", + name = "supermarket", + label = "Supermarket", + bgen = { + kind = "centered_door", + vertical_factor = 1.0, + }, + kind = "store", + recipe = { + consumption = {{"meat", 1}, {"vegetable", 1}, {"cereal", 1}}, + production = {}, + complexity = 100, + storage_multiplier = 5, + }, + n_workers = 10, + size = 80.0, + asset_location = "assets/sprites/supermarket.png", + price = 1000, + }, + { + type = "goods-company", + name = "clothes-store", + label = "Clothes store", + bgen = { + kind = "centered_door", + vertical_factor = 1.0, + }, + kind = "store", + recipe = { + consumption = {{"cloth", 1}}, + production = {}, + complexity = 100, + storage_multiplier = 5, + }, + n_workers = 10, + size = 10.0, + asset_location = "assets/sprites/clothes_store.png", + price = 1000, + }, + { + type = "goods-company", + name = "cloth-factory", + label = "Cloth factory", + bgen = { + kind = "centered_door", + vertical_factor = 1.0, + }, + kind = "factory", + n_trucks = 1, + recipe = { + consumption = {{"polyester", 1}, {"wool", 1}}, + production = {{"cloth", 1}}, + complexity = 100, + storage_multiplier = 5, + }, + n_workers = 10, + size = 80.0, + asset_location = "assets/sprites/cloth_factory.png", + price = 1000, + }, + { + type = "goods-company", + name = "polyester-refinery", + label = "Polyester refinery", + bgen = { + kind = "centered_door", + vertical_factor = 1.0, + }, + kind = "factory", + n_trucks = 1, + recipe = { + consumption = {{"oil", 1}}, + production = {{"polyester", 1}}, + complexity = 100, + storage_multiplier = 5, + }, + n_workers = 5, + size = 80.0, + asset_location = "assets/sprites/polyester_refinery.png", + price = 1000, + }, + { + type = "goods-company", + name = "oil-pump", + label = "Oil pump", + bgen = { + kind = "centered_door", + vertical_factor = 1.0, + }, + kind = "factory", + n_trucks = 1, + recipe = { + consumption = {}, + production = {{"oil", 1}}, + complexity = 100, + storage_multiplier = 5, + }, + n_workers = 5, + size = 20.0, + asset_location = "assets/sprites/oil_pump.png", + price = 1000, + }, + { + type = "goods-company", + name = "coal-mine", + label = "Coal mine", + bgen = { + kind = "centered_door", + vertical_factor = 1.0, + }, + kind = "factory", + n_trucks = 1, + recipe = { + consumption = {}, + production = {{"coal", 1}}, + complexity = 100, + storage_multiplier = 5, + }, + n_workers = 5, + size = 20.0, + asset_location = "assets/sprites/oil_pump.png", + price = 1000, + }, + { + type = "goods-company", + name = "textile-processing-facility", + label = "Textile processing facility", + bgen = { + kind = "centered_door", + vertical_factor = 1.0, + }, + kind = "factory", + n_trucks = 1, + recipe = { + consumption = {{"wool", 1}}, + production = {{"cloth", 1}}, + complexity = 100, + storage_multiplier = 5, + }, + n_workers = 10, + size = 80.0, + asset_location = "assets/sprites/textile_processing_facility.png", + price = 1000, + }, + { + type = "goods-company", + name = "wool-farm", + label = "Wool farm", + bgen = { + kind = "centered_door", + vertical_factor = 1.0, + }, + kind = "factory", + n_trucks = 1, + recipe = { + consumption = {}, + production = {{"wool", 1}}, + complexity = 100, + storage_multiplier = 5, + }, + n_workers = 10, + size = 80.0, + asset_location = "assets/sprites/wool_farm.png", + price = 1000, + }, + { + type = "goods-company", + name = "florist", + label = "Florist", + bgen = { + kind = "centered_door", + vertical_factor = 1.0, + }, + kind = "store", + recipe = { + consumption = {{"flower", 1}}, + production = {}, + complexity = 100, + storage_multiplier = 5, + }, + n_workers = 10, + size = 10.0, + asset_location = "assets/sprites/florist.png", + price = 1000, + }, + { + type = "goods-company", + name = "horticulturalist", + label = "Horticulturalist", + bgen = { + kind = "centered_door", + vertical_factor = 1.0, + }, + kind = "factory", + n_trucks = 1, + recipe = { + consumption = {}, + production = {{"flower", 1}}, + complexity = 100, + storage_multiplier = 5, + }, + n_workers = 5, + size = 80.0, + asset_location = "assets/sprites/horticulturalist.png", + price = 1000, + }, + { + type = "goods-company", + name = "high-tech-store", + label = "High tech store", + bgen = { + kind = "centered_door", + vertical_factor = 1.0, + }, + kind = "store", + recipe = { + consumption = {{"high-tech-product", 1}}, + production = {}, + complexity = 100, + storage_multiplier = 5, + }, + n_workers = 10, + size = 80.0, + asset_location = "assets/sprites/hightech_store.png", + price = 1000, + }, + { + type = "goods-company", + name = "high-tech-facility", + label = "High tech facility", + bgen = { + kind = "centered_door", + vertical_factor = 1.0, + }, + kind = "factory", + n_trucks = 1, + recipe = { + consumption = {{"gold", 1}, {"metal", 1}}, + production = {{"high-tech-product", 1}}, + complexity = 100, + storage_multiplier = 5, + }, + n_workers = 10, + size = 80.0, + asset_location = "assets/sprites/hightech_facility.png", + price = 1000, + }, + { + type = "goods-company", + name = "gold-mine", + label = "Gold mine", + bgen = { + kind = "centered_door", + vertical_factor = 1.0, + }, + kind = "factory", + n_trucks = 1, + recipe = { + consumption = {}, + production = {{"gold", 1}}, + complexity = 100, + storage_multiplier = 5, + }, + n_workers = 10, + size = 80.0, + asset_location = "assets/sprites/rare_metal_mine.png", + price = 1000, + }, + { + type = "goods-company", + name = "furniture-store", + label = "Furniture store", + bgen = { + kind = "centered_door", + vertical_factor = 1.0, + }, + kind = "store", + recipe = { + consumption = {{"metal", 1}, {"wood-plank", 1}}, + production = {{"furniture", 1}}, + complexity = 100, + storage_multiplier = 5, + }, + n_workers = 10, + size = 80.0, + asset_location = "assets/sprites/furniture_store.png", + price = 1000, + }, + { + type = "goods-company", + name = "foundry", + label = "Foundry", + bgen = { + kind = "centered_door", + vertical_factor = 1.0, + }, + kind = "factory", + n_trucks = 1, + recipe = { + consumption = {{"iron-ore", 1}}, + production = {{"metal", 1}}, + complexity = 100, + storage_multiplier = 5, + }, + n_workers = 10, + size = 80.0, + asset_location = "assets/sprites/foundry.png", + price = 1000, + }, + { + type = "goods-company", + name = "iron-mine", + label = "Iron mine", + bgen = { + kind = "centered_door", + vertical_factor = 1.0, + }, + kind = "factory", + n_trucks = 1, + recipe = { + consumption = {}, + production = {{"iron-ore", 1}}, + complexity = 100, + storage_multiplier = 5, + }, + n_workers = 10, + size = 80.0, + asset_location = "assets/sprites/iron_mine.png", + price = 1000, + }, + { + type = "goods-company", + name = "woodmill", + label = "Woodmill", + bgen = { + kind = "centered_door", + vertical_factor = 1.0, + }, + kind = "factory", + n_trucks = 1, + recipe = { + consumption = {{"tree-log", 1}}, + production = {{"wood-plank", 1}}, + complexity = 100, + storage_multiplier = 5, + }, + n_workers = 10, + size = 80.0, + asset_location = "assets/sprites/woodmill.png", + price = 1000, + }, + { + type = "goods-company", + name = "lumber-yard", + label = "Lumber yard", + bgen = "farm", + kind = "factory", + n_trucks = 1, + recipe = { + consumption = {}, + production = {{"tree-log", 1}}, + complexity = 100, + storage_multiplier = 5, + }, + n_workers = 10, + size = 200.0, + asset_location = "assets/sprites/lumber_yard.png", + price = 1000, + }, + { + type = "goods-company", + name = "meat-facility", + label = "Meat facility", + bgen = { + kind = "centered_door", + vertical_factor = 0.6, + }, + kind = "factory", + n_trucks = 1, + recipe = { + consumption = {{"raw-meat", 1}}, + production = {{"meat", 1}}, + complexity = 100, + storage_multiplier = 5, + }, + n_workers = 10, + size = 80.0, + asset_location = "assets/sprites/meat_facility.png", + price = 1000, + }, + { + type = "goods-company", + name = "slaughterhouse", + label = "Slaughterhouse", + bgen = { + kind = "centered_door", + vertical_factor = 1.0, + }, + kind = "factory", + n_trucks = 1, + recipe = { + consumption = {{"carcass", 1}}, + production = {{"raw-meat", 1}}, + complexity = 100, + storage_multiplier = 5, + }, + n_workers = 5, + size = 50.0, + asset_location = "assets/sprites/slaughterhouse.png", + price = 1000, + }, + { + type = "goods-company", + name = "animal-farm", + label = "Animal Farm", + bgen = "farm", + kind = "factory", + n_trucks = 1, + recipe = { + consumption = {{"cereal", 1}}, + production = {{"carcass", 1}}, + complexity = 100, + storage_multiplier = 5, + }, + n_workers = 5, + size = 80.0, + asset_location = "assets/sprites/animal_farm.png", + price = 1000, + }, + { + type = "goods-company", + name = "vegetable-farm", + label = "Vegetable Farm", + bgen = "farm", + kind = "factory", + n_trucks = 1, + recipe = { + consumption = {}, + production = {{"vegetable", 2}}, + complexity = 2, + storage_multiplier = 5, + }, + n_workers = 10, + size = 70.0, + asset_location = "assets/sprites/vegetable_farm.png", + price = 1000, + zone = { + floor = "assets/sprites/dirt.jpg", + filler = "salad.glb", + price_per_area = 100, + }, + }, +} diff --git a/base_mod/data.lua b/base_mod/data.lua new file mode 100644 index 00000000..19d258f0 --- /dev/null +++ b/base_mod/data.lua @@ -0,0 +1,2 @@ +require("items") +require("companies") \ No newline at end of file diff --git a/base_mod/items.lua b/base_mod/items.lua new file mode 100644 index 00000000..5b1838d4 --- /dev/null +++ b/base_mod/items.lua @@ -0,0 +1,108 @@ +data:extend { + { + type = "item", + name = "job-opening", + label = "Job opening", + optout_exttrade = true, + }, + { + type = "item", + name = "cereal", + label = "Cereal", + }, + { + type = "item", + name = "flour", + label = "Flour", + }, + { + type = "item", + name = "bread", + label = "Bread", + }, + { + type = "item", + name = "vegetable", + label = "Vegetable", + }, + { + type = "item", + name = "carcass", + label = "Carcass", + }, + { + type = "item", + name = "raw-meat", + label = "Raw meat", + }, + { + type = "item", + name = "meat", + label = "Meat", + }, + { + type = "item", + name = "tree-log", + label = "Tree Log", + }, + { + type = "item", + name = "wood-plank", + label = "Wood Plank", + }, + { + type = "item", + name = "iron-ore", + label = "Iron Ore", + }, + { + type = "item", + name = "metal", + label = "Metal", + }, + { + type = "item", + name = "gold", + label = "Gold", + }, + { + type = "item", + name = "high-tech-product", + label = "High Tech Product", + }, + { + type = "item", + name = "furniture", + label = "Furniture", + }, + { + type = "item", + name = "flower", + label = "Flower", + }, + { + type = "item", + name = "wool", + label = "Wool", + }, + { + type = "item", + name = "cloth", + label = "Cloth", + }, + { + type = "item", + name = "oil", + label = "Oil", + }, + { + type = "item", + name = "coal", + label = "Coal", + }, + { + type = "item", + name = "polyester", + label = "Polyester", + }, +} diff --git a/common/Cargo.toml b/common/Cargo.toml index 4ef199e0..785f066a 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -16,4 +16,5 @@ miniz_oxide = "0.7" rustc-hash = "1.1.0" inline_tweak = {version = "1.0.8"} log-panics = { version = "2.0.0", features=["with-backtrace"] } -egui-inspect = { path = "../egui-inspect" } \ No newline at end of file +egui-inspect = { path = "../egui-inspect" } +fast-float = { version = "0.2.0", default-features = false } \ No newline at end of file diff --git a/common/src/lib.rs b/common/src/lib.rs index 98014af9..93ad3c1d 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -231,6 +231,26 @@ pub fn fastset_with_capacity(cap: usize) -> FastSet { FastSet::with_capacity_and_hasher(cap, Default::default()) } +pub type TransparentMap = std::collections::HashMap; + +pub fn transparentmap_with_capacity(cap: usize) -> TransparentMap { + TransparentMap::with_capacity_and_hasher(cap, Default::default()) +} + +pub fn transparentmap() -> TransparentMap { + TransparentMap::with_hasher(Default::default()) +} + +pub fn parse_f32(x: &str) -> fast_float::Result<(f32, &str)> { + fast_float::parse_partial::(x) + .map(|(v, l)| (v, if l == x.len() { "" } else { &x[l..] })) +} + +pub fn parse_f64(x: &str) -> fast_float::Result<(f64, &str)> { + fast_float::parse_partial::(x) + .map(|(v, l)| (v, if l == x.len() { "" } else { &x[l..] })) +} + #[derive(Default)] pub struct TransparentHasherU64(u64); diff --git a/common/src/saveload.rs b/common/src/saveload.rs index 29baeb46..f3b8d807 100644 --- a/common/src/saveload.rs +++ b/common/src/saveload.rs @@ -190,8 +190,6 @@ pub fn load_raw(p: impl AsRef) -> Result> { std::fs::read(p) } -pub fn load_string( - p: impl AsRef, -) -> std::result::Result> { - std::fs::read_to_string(p).map_err(|e| Box::new(e) as Box) +pub fn load_string(p: impl AsRef) -> Result { + std::fs::read_to_string(p) } diff --git a/common/src/timestep.rs b/common/src/timestep.rs index 9da0cc93..e41f9992 100644 --- a/common/src/timestep.rs +++ b/common/src/timestep.rs @@ -4,7 +4,7 @@ pub const UP_DT: Duration = Duration::from_millis(20); /// A timestep that can be used to update the game state. /// It will try to keep a constant update rate. -/// Based on https://gafferongames.com/post/fix_your_timestep/ +/// Based on pub struct Timestep { last_time: Instant, acc: Duration, diff --git a/engine/src/framework.rs b/engine/src/framework.rs index 329b013c..96a313c2 100644 --- a/engine/src/framework.rs +++ b/engine/src/framework.rs @@ -160,6 +160,18 @@ async fn run(el: EventLoop<()>, window: Window) { }).expect("Failed to run event loop"); } +pub fn init() { + #[cfg(target_arch = "wasm32")] + { + std::panic::set_hook(Box::new(console_error_panic_hook::hook)); + console_log::init().expect("Failed to initialize logger"); + } + #[cfg(not(target_arch = "wasm32"))] + { + common::logger::MyLog::init(); + } +} + pub fn start() { let el = EventLoop::new().expect("Failed to create event loop"); @@ -175,8 +187,6 @@ pub fn start() { .build(&el) .unwrap(); - std::panic::set_hook(Box::new(console_error_panic_hook::hook)); - console_log::init().expect("Failed to initialize logger"); use winit::platform::web::WindowExtWebSys; // On wasm, append the canvas to the document body web_sys::window() @@ -191,8 +201,6 @@ pub fn start() { } #[cfg(not(target_arch = "wasm32"))] { - common::logger::MyLog::init(); - let size = match el.primary_monitor() { Some(monitor) => monitor.size(), None => el.available_monitors().next().unwrap().size(), diff --git a/engine_demo/src/main.rs b/engine_demo/src/main.rs index ebb1ef13..29788675 100644 --- a/engine_demo/src/main.rs +++ b/engine_demo/src/main.rs @@ -285,7 +285,7 @@ impl State { } fn main() { - common::logger::MyLog::init(); + engine::framework::init(); engine::framework::start::(); } diff --git a/geom/Cargo.toml b/geom/Cargo.toml index 7e7cd9f1..32fd1abf 100644 --- a/geom/Cargo.toml +++ b/geom/Cargo.toml @@ -8,7 +8,8 @@ edition = "2021" [dependencies] ordered-float = { workspace = true } -serde = { version = "1.0", features = ["derive"] } +serde = { version = "1.0", features = ["derive"] } fnv = "1.0.3" -inline_tweak = "1.0.8" -flat_spatial = { workspace = true } \ No newline at end of file +inline_tweak = "1.0.8" +flat_spatial = { workspace = true } +mlua = "0.9.4" \ No newline at end of file diff --git a/geom/src/aabb3.rs b/geom/src/aabb3.rs index 01121b74..42769480 100644 --- a/geom/src/aabb3.rs +++ b/geom/src/aabb3.rs @@ -137,7 +137,7 @@ impl AABB3 { /// as ray is defined by O + tD, return the t values for the entering and exiting intersections /// Returns a 2-tuple of (t_near, t_far) - /// Adapted from https://gist.github.com/DomNomNom/46bb1ce47f68d255fd5d + /// Adapted from /// If the ray origin is inside the box, t_near will be zero #[inline] pub fn raycast(&self, ray: Ray3) -> Option<(f32, f32)> { diff --git a/geom/src/v2.rs b/geom/src/v2.rs index 7c7740be..6c056a34 100644 --- a/geom/src/v2.rs +++ b/geom/src/v2.rs @@ -1,4 +1,5 @@ use crate::{Circle, Intersect, Polygon, Radians, Shape, Vec3, Vec3d, AABB, OBB}; +use mlua::{FromLua, Value}; use serde::{Deserialize, Deserializer, Serialize}; use std::fmt::{Debug, Display, Formatter}; use std::hash::{Hash, Hasher}; @@ -1170,6 +1171,29 @@ impl From<[f64; 2]> for Vec2d { } } +impl<'a> FromLua<'a> for Vec2 { + fn from_lua(value: Value<'a>, _: &'a mlua::Lua) -> mlua::Result { + let t = match value { + Value::Vector(v) => return Ok(Self { x: v.x(), y: v.y() }), + Value::Table(t) => t, + _ => { + return Err(mlua::Error::FromLuaConversionError { + from: value.type_name(), + to: "Vec2", + message: Some("expected a table or vector".to_string()), + }) + } + }; + if let Ok(x) = t.get(1) { + return Ok(Self { x, y: t.get(2)? }); + } + + let x = t.get("x")?; + let y = t.get("y")?; + Ok(Vec2::new(x, y)) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/geom/src/v3.rs b/geom/src/v3.rs index 2dc90ed3..ac2a3e3e 100644 --- a/geom/src/v3.rs +++ b/geom/src/v3.rs @@ -1,4 +1,5 @@ use crate::{vec2, vec4, Shape3, Vec2, Vec4, AABB3}; +use mlua::{FromLua, Value}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::fmt::{Debug, Display, Formatter}; use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign}; @@ -636,3 +637,37 @@ impl Vec3 { m < f32::EPSILON } } + +impl<'a> FromLua<'a> for Vec3 { + fn from_lua(value: Value<'a>, _: &'a mlua::Lua) -> mlua::Result { + let t = match value { + Value::Vector(v) => { + return Ok(Self { + x: v.x(), + y: v.y(), + z: v.z(), + }) + } + Value::Table(t) => t, + _ => { + return Err(mlua::Error::FromLuaConversionError { + from: value.type_name(), + to: "Vec3", + message: Some("expected a table or vector".to_string()), + }) + } + }; + if let Ok(x) = t.get(1) { + return Ok(Self { + x, + y: t.get(2)?, + z: t.get(3)?, + }); + } + + let x = t.get("x")?; + let y = t.get("y")?; + let z = t.get("z")?; + Ok(Self { x, y, z }) + } +} diff --git a/native_app/src/gui/inspect/inspect_building.rs b/native_app/src/gui/inspect/inspect_building.rs index dd12c4a4..062a7dca 100644 --- a/native_app/src/gui/inspect/inspect_building.rs +++ b/native_app/src/gui/inspect/inspect_building.rs @@ -1,16 +1,16 @@ use crate::uiworld::UiWorld; use egui::{Context, Ui, Widget}; -use simulation::economy::{ItemRegistry, Market}; +use simulation::economy::Market; use simulation::world_command::WorldCommand; use simulation::{Simulation, SoulID}; use crate::gui::inspect::entity_link; use crate::gui::item_icon; use egui_inspect::{Inspect, InspectArgs, InspectVec2Rotation}; +use prototypes::{ItemID, Recipe}; use simulation::map::{Building, BuildingID, BuildingKind, Zone, MAX_ZONE_AREA}; use simulation::map_dynamic::BuildingInfos; use simulation::souls::freight_station::FreightTrainState; -use simulation::souls::goods_company::{GoodsCompanyRegistry, Recipe}; /// Inspect a specific building, showing useful information about it pub fn inspect_building(uiworld: &mut UiWorld, sim: &Simulation, ui: &Context, id: BuildingID) { @@ -18,11 +18,10 @@ pub fn inspect_building(uiworld: &mut UiWorld, sim: &Simulation, ui: &Context, i let Some(building) = map.buildings().get(id) else { return; }; - let gregistry = sim.read::(); let title: &str = match building.kind { BuildingKind::House => "House", - BuildingKind::GoodsCompany(id) => &gregistry.descriptions[id].name, + BuildingKind::GoodsCompany(id) => &id.prototype().name, BuildingKind::RailFreightStation => "Rail Freight Station", BuildingKind::TrainStation => "Train Station", BuildingKind::ExternalTrading => "External Trading", @@ -139,7 +138,6 @@ fn render_goodscompany(ui: &mut Ui, uiworld: &mut UiWorld, sim: &Simulation, b: let workers = &c.workers; let market = sim.read::(); - let itemregistry = sim.read::(); let max_workers = goods.max_workers; egui::ProgressBar::new(workers.0.len() as f32 / max_workers as f32) .text(format!("workers: {}/{}", workers.0.len(), max_workers)) @@ -160,7 +158,7 @@ fn render_goodscompany(ui: &mut Ui, uiworld: &mut UiWorld, sim: &Simulation, b: .ui(ui); } - render_recipe(ui, uiworld, sim, &goods.recipe); + render_recipe(ui, uiworld, &goods.kind.prototype().recipe); egui::ProgressBar::new(goods.progress) .show_percentage() @@ -170,7 +168,7 @@ fn render_goodscompany(ui: &mut Ui, uiworld: &mut UiWorld, sim: &Simulation, b: ui.add_space(10.0); ui.label("Storage"); - let jobopening = itemregistry.id("job-opening"); + let jobopening = ItemID::new("job-opening"); for (&id, m) in market.iter() { let Some(v) = m.capital(c_id.into()) else { continue; @@ -178,17 +176,12 @@ fn render_goodscompany(ui: &mut Ui, uiworld: &mut UiWorld, sim: &Simulation, b: if id == jobopening && v == 0 { continue; } - let Some(item) = itemregistry.get(id) else { - continue; - }; - item_icon(ui, uiworld, item, v); + item_icon(ui, uiworld, id, v); } } -fn render_recipe(ui: &mut Ui, uiworld: &UiWorld, sim: &Simulation, recipe: &Recipe) { - let registry = sim.read::(); - +fn render_recipe(ui: &mut Ui, uiworld: &UiWorld, recipe: &Recipe) { if recipe.consumption.is_empty() { ui.label("No Inputs"); } else { @@ -198,11 +191,8 @@ fn render_recipe(ui: &mut Ui, uiworld: &UiWorld, sim: &Simulation, recipe: &Reci "Inputs" }); ui.horizontal(|ui| { - for &(good, amount) in recipe.consumption.iter() { - let Some(item) = registry.get(good) else { - continue; - }; - item_icon(ui, uiworld, item, amount); + for item in recipe.consumption.iter() { + item_icon(ui, uiworld, item.id, item.amount); } }); } @@ -216,11 +206,8 @@ fn render_recipe(ui: &mut Ui, uiworld: &UiWorld, sim: &Simulation, recipe: &Reci "Outputs" }); ui.horizontal(|ui| { - for &(good, amount) in recipe.production.iter() { - let Some(item) = registry.get(good) else { - continue; - }; - item_icon(ui, uiworld, item, amount); + for item in recipe.production.iter() { + item_icon(ui, uiworld, item.id, item.amount); } }); } diff --git a/native_app/src/gui/inspect/inspect_debug.rs b/native_app/src/gui/inspect/inspect_debug.rs index 8050a85f..878b0ffc 100644 --- a/native_app/src/gui/inspect/inspect_debug.rs +++ b/native_app/src/gui/inspect/inspect_debug.rs @@ -2,7 +2,7 @@ use crate::gui::follow::FollowEntity; use crate::uiworld::UiWorld; use egui::Ui; use egui_inspect::{Inspect, InspectArgs}; -use simulation::economy::{ItemRegistry, Market}; +use simulation::economy::Market; use simulation::transportation::Location; use simulation::{ AnyEntity, CompanyEnt, FreightStationEnt, HumanEnt, Simulation, SoulID, TrainEnt, VehicleEnt, @@ -97,7 +97,6 @@ impl InspectRenderer { if let Ok(soul) = SoulID::try_from(entity) { let market = sim.read::(); - let registry = sim.read::(); let mut capitals = vec![]; let mut borders = vec![]; let mut sellorders = vec![]; @@ -116,7 +115,7 @@ impl InspectRenderer { egui::CollapsingHeader::new("Capital").show(ui, |ui| { ui.columns(2, |ui| { for (kind, cap) in capitals { - ui[0].label(®istry[*kind].label); + ui[0].label(&kind.prototype().label); ui[1].label(format!("{cap}")); } }); @@ -127,7 +126,7 @@ impl InspectRenderer { egui::CollapsingHeader::new("Buy orders").show(ui, |ui| { ui.columns(2, |ui| { for (kind, b) in borders { - ui[0].label(®istry[*kind].label); + ui[0].label(&kind.prototype().label); ui[1].label(format!("{b:#?}")); } }); @@ -138,7 +137,7 @@ impl InspectRenderer { egui::CollapsingHeader::new("Sell orders").show(ui, |ui| { ui.columns(2, |ui| { for (kind, b) in sellorders { - ui[0].label(®istry[*kind].label); + ui[0].label(&kind.prototype().label); ui[1].label(format!("{b:#?}")); } }); diff --git a/native_app/src/gui/inspect/inspect_human.rs b/native_app/src/gui/inspect/inspect_human.rs index 1a049c24..5006779a 100644 --- a/native_app/src/gui/inspect/inspect_human.rs +++ b/native_app/src/gui/inspect/inspect_human.rs @@ -1,6 +1,7 @@ use egui::{Context, Widget}; +use prototypes::ItemID; -use simulation::economy::{ItemRegistry, Market}; +use simulation::economy::Market; use simulation::map_dynamic::Destination; use simulation::souls::desire::WorkKind; use simulation::transportation::Location; @@ -96,11 +97,10 @@ pub fn inspect_human(uiworld: &mut UiWorld, sim: &Simulation, ui: &Context, id: }); let market = sim.read::(); - let itemregistry = sim.read::(); ui.add_space(10.0); - let jobopening = itemregistry.id("job-opening"); + let jobopening = ItemID::new("job-opening"); for (&item_id, m) in market.iter() { let Some(v) = m.capital(id.into()) else { continue; @@ -108,11 +108,8 @@ pub fn inspect_human(uiworld: &mut UiWorld, sim: &Simulation, ui: &Context, id: if item_id == jobopening { continue; } - let Some(item) = itemregistry.get(item_id) else { - continue; - }; - item_icon(ui, uiworld, item, v); + item_icon(ui, uiworld, item_id, v); } follow_button(uiworld, ui, id); diff --git a/native_app/src/gui/topgui.rs b/native_app/src/gui/topgui.rs index 9d3d50c7..055cbadc 100644 --- a/native_app/src/gui/topgui.rs +++ b/native_app/src/gui/topgui.rs @@ -18,13 +18,12 @@ use egui::{ }; use egui_inspect::{Inspect, InspectArgs}; use geom::{Polygon, Vec2}; -use prototypes::BuildingGen; +use prototypes::{prototypes_iter, BuildingGen, GoodsCompanyPrototype, ItemID}; use serde::{Deserialize, Serialize}; -use simulation::economy::{Government, Item, ItemRegistry, Money}; +use simulation::economy::{Government, Money}; use simulation::map::{ BuildingKind, LanePatternBuilder, LightPolicy, MapProject, TerraformKind, TurnPolicy, Zone, }; -use simulation::souls::goods_company::GoodsCompanyRegistry; use simulation::utils::time::{GameTime, SECONDS_PER_HOUR}; use simulation::world_command::WorldCommand; use simulation::Simulation; @@ -151,7 +150,7 @@ impl Gui { } } - pub fn toolbox(ui: &Context, uiworld: &mut UiWorld, sim: &Simulation) { + pub fn toolbox(ui: &Context, uiworld: &mut UiWorld, _sim: &Simulation) { profiling::scope!("topgui::toolbox"); #[derive(Copy, Clone)] pub enum Tab { @@ -546,10 +545,6 @@ impl Gui { } let building_select_w = 200.0; - let registry = sim.read::(); - let gbuildings = registry.descriptions.values().peekable(); - - let iregistry = sim.read::(); if matches!(*uiworld.read::(), Tab::Roadbuilding) { Window::new("Buildings") @@ -566,7 +561,7 @@ impl Gui { let mut picked_descr = None; ui.style_mut().spacing.interact_size = [building_select_w - 5.0, 35.0].into(); - for descr in gbuildings { + for descr in prototypes_iter::() { let cur_kind = cur_build.opt.as_ref().map(|x| &*x.asset).unwrap_or(""); let mut name = RichText::new(&descr.name); @@ -619,15 +614,15 @@ impl Gui { ui.add_space(10.0); if !descr.recipe.consumption.is_empty() { ui.label("consumption:"); - for (kind, n) in &descr.recipe.consumption { - item_icon(ui, uiworld, &iregistry[*kind], *n); + for item in &descr.recipe.consumption { + item_icon(ui, uiworld, item.id, item.amount); } ui.add_space(10.0); } if !descr.recipe.production.is_empty() { ui.label("production:"); - for (kind, n) in &descr.recipe.production { - item_icon(ui, uiworld, &iregistry[*kind], *n); + for item in &descr.recipe.production { + item_icon(ui, uiworld, item.id, item.amount); } ui.add_space(10.0); } @@ -814,7 +809,8 @@ impl Gui { } } -pub fn item_icon(ui: &mut Ui, uiworld: &UiWorld, item: &Item, multiplier: i32) -> Response { +pub fn item_icon(ui: &mut Ui, uiworld: &UiWorld, id: ItemID, multiplier: i32) -> Response { + let item = id.prototype(); ui.horizontal(move |ui| { if let Some(id) = uiworld .read::() diff --git a/native_app/src/gui/windows/economy.rs b/native_app/src/gui/windows/economy.rs index b8a3813d..5d2d7f23 100644 --- a/native_app/src/gui/windows/economy.rs +++ b/native_app/src/gui/windows/economy.rs @@ -4,10 +4,9 @@ use egui::{Align2, Color32, Ui}; use egui_plot::{Line, PlotPoints}; use geom::Color; use simulation::economy::{ - EcoStats, ItemHistories, ItemRegistry, Market, HISTORY_SIZE, LEVEL_FREQS, LEVEL_NAMES, + EcoStats, ItemHistories, Market, HISTORY_SIZE, LEVEL_FREQS, LEVEL_NAMES, }; use simulation::Simulation; -use slotmapd::Key; use std::cmp::Reverse; use std::collections::HashSet; @@ -40,7 +39,6 @@ pub fn economy(window: egui::Window<'_>, ui: &egui::Context, uiw: &mut UiWorld, }); let mut state = uiw.write::(); let ecostats = sim.read::(); - let registry = sim.read::(); window .anchor(Align2::CENTER_CENTER, [0.0, 0.0]) @@ -146,7 +144,7 @@ pub fn economy(window: egui::Window<'_>, ui: &egui::Context, uiw: &mut UiWorld, overallmax = maxval; } - let h = common::hash_u64(id.data().as_ffi()); + let h = id.hash(); let random_col = Color::new( 0.5 + 0.5 * common::rand::rand2(h as f32, 0.0), 0.5 + 0.5 * common::rand::rand2(h as f32, 1.0), @@ -170,8 +168,6 @@ pub fn economy(window: egui::Window<'_>, ui: &egui::Context, uiw: &mut UiWorld, first_zeros }); - let iname = ®istry[id].name; - ui.line( Line::new(PlotPoints::from_iter(heights)) .color(Color32::from_rgba_unmultiplied( @@ -180,7 +176,7 @@ pub fn economy(window: egui::Window<'_>, ui: &egui::Context, uiw: &mut UiWorld, (random_col.b * 255.0) as u8, (random_col.a * 255.0) as u8, )) - .name(iname), + .name(&id.prototype().name), ); } ui.line( @@ -230,9 +226,8 @@ pub fn economy(window: egui::Window<'_>, ui: &egui::Context, uiw: &mut UiWorld, histories.sort_by_key(|(_, sum)| Reverse(*sum)); for (id, sum) in histories { - let iname = ®istry[id].name; let mut enabled = filter.contains(&id); - if ui.checkbox(&mut enabled, iname).changed() { + if ui.checkbox(&mut enabled, &id.prototype().name).changed() { if enabled { filter.insert(id); } else { @@ -291,11 +286,10 @@ pub fn economy(window: egui::Window<'_>, ui: &egui::Context, uiw: &mut UiWorld, } fn render_market_prices(sim: &Simulation, ui: &mut Ui) { - let registry = sim.read::(); let market = sim.read::(); egui::Grid::new("marketprices").show(ui, |ui| { for (id, market) in market.iter() { - ui.label(®istry[*id].name); + ui.label(&id.prototype().name); ui.label(market.ext_value.to_string()); ui.end_row(); } diff --git a/native_app/src/main.rs b/native_app/src/main.rs index 7f7fb545..7af776cc 100644 --- a/native_app/src/main.rs +++ b/native_app/src/main.rs @@ -26,6 +26,8 @@ fn main() { profiling::tracy_client::Client::start(); profiling::register_thread!("Main Thread"); + engine::framework::init(); init::init(); + engine::framework::start::(); } diff --git a/native_app/src/rendering/map_rendering/map_mesh.rs b/native_app/src/rendering/map_rendering/map_mesh.rs index 62c129ed..ebd5c4f5 100644 --- a/native_app/src/rendering/map_rendering/map_mesh.rs +++ b/native_app/src/rendering/map_rendering/map_mesh.rs @@ -8,12 +8,12 @@ use engine::{ MeshVertex, MetallicRoughness, SpriteBatch, SpriteBatchBuilder, Tesselator, }; use geom::{minmax, vec2, vec3, Color, LinearColor, PolyLine3, Polygon, Radians, Vec2, Vec3}; +use prototypes::GoodsCompanyPrototype; use simulation::map::{ Building, BuildingKind, CanonicalPosition, Environment, Intersection, LaneKind, Lanes, LotKind, Map, MapSubscriber, ProjectFilter, ProjectKind, PylonPosition, Road, Roads, SubscriberChunkID, Turn, TurnKind, UpdateType, CROSSWALK_WIDTH, ROAD_Z_OFFSET, }; -use simulation::souls::goods_company::GoodsCompanyRegistry; use simulation::Simulation; use std::ops::{Mul, Neg}; use std::rc::Rc; @@ -55,7 +55,7 @@ impl MapMeshHandler { let mut buildmeshes = FastMap::default(); let mut zonemeshes = FastMap::default(); - for descr in sim.read::().descriptions.values() { + for descr in GoodsCompanyPrototype::iter() { let asset = &descr.asset_location; if !asset.ends_with(".png") && !asset.ends_with(".jpg") { continue; @@ -69,10 +69,7 @@ impl MapMeshHandler { ); } - for (asset, bkind) in sim - .read::() - .descriptions - .values() + for (asset, bkind) in GoodsCompanyPrototype::iter() .map(|descr| { ( descr.asset_location.as_ref(), @@ -98,7 +95,7 @@ impl MapMeshHandler { buildmeshes.insert(bkind, InstancedMeshBuilder::new(m)); } - for descr in sim.read::().descriptions.values() { + for descr in GoodsCompanyPrototype::iter() { let Some(ref z) = descr.zone else { continue }; let floor = &z.floor; let filler = &z.filler; diff --git a/prototypes/Cargo.toml b/prototypes/Cargo.toml index f980d4ac..e666bb2a 100644 --- a/prototypes/Cargo.toml +++ b/prototypes/Cargo.toml @@ -6,6 +6,11 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -serde = "1.0.195" -geom = { path = "../geom" } -egui-inspect = { path = "../egui-inspect" } \ No newline at end of file +common = { path = "../common" } +geom = { path = "../geom" } +egui-inspect = { path = "../egui-inspect" } +mlua = { version = "0.9.4", features = ["luau"] } +slotmapd = "1.0.10" +serde = "1.0.195" +thiserror = "1.0.56" +log = { version = "0.4.20", features = [] } \ No newline at end of file diff --git a/prototypes/src/company.rs b/prototypes/src/company.rs new file mode 100644 index 00000000..d67a54b9 --- /dev/null +++ b/prototypes/src/company.rs @@ -0,0 +1,302 @@ +use crate::{get_with_err, GoodsCompanyID, ItemID, Prototype, PrototypeBase}; +use egui_inspect::{debug_inspect_impl, Inspect}; +use geom::Vec2; +use mlua::{FromLua, Lua, Table, Value}; +use serde::{Deserialize, Serialize}; +use std::fmt::{Display, Formatter}; +use std::ops::Deref; +use std::str::FromStr; +use thiserror::Error; + +#[derive(Debug, Clone)] +/// Power in watts (J/s) +pub struct Power(pub i64); +debug_inspect_impl!(Power); + +#[derive(Debug, Error)] +pub enum PowerParseError { + #[error("Invalid unit: {0} (accepted: W, kW, MW, GW)")] + InvalidUnit(String), + #[error("Invalid number")] + InvalidNumber, + #[error("Power is too big")] + TooBig, +} + +impl FromStr for Power { + type Err = PowerParseError; + + /// Parse a power value from a string. The unit can be W, kW or MW. + fn from_str(s: &str) -> Result { + let s = s.trim(); + let (mut number, rest) = + common::parse_f64(s).map_err(|_| PowerParseError::InvalidNumber)?; + + let unit = rest.trim(); + + match unit { + "W" => {} + "kW" => number *= 1000.0, + "MW" => number *= 1000.0 * 1000.0, + _ => return Err(PowerParseError::InvalidUnit(unit.to_string())), + } + + if number > i64::MAX as f64 { + return Err(PowerParseError::TooBig); + } + + Ok(Self(number as i64)) + } +} + +impl Display for Power { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let (unit, div) = match self.0 { + 0..=999 => ("W", 1.0), + 1000..=999_999 => ("kW", 1000.0), + 1_000_000..=999_999_999 => ("MW", 1_000_000.0), + _ => ("GW", 1_000_000_000.0), + }; + + write!(f, "{:.2}{}", self.0 as f64 / div, unit) + } +} + +impl<'lua> FromLua<'lua> for Power { + fn from_lua(value: Value<'lua>, _: &'lua Lua) -> mlua::Result { + match value { + Value::Nil => Ok(Self(0)), + Value::Integer(i) => Ok(Self(i as i64)), + Value::Number(n) => { + if n > i64::MAX as f64 { + return Err(mlua::Error::external(PowerParseError::TooBig)); + } + Ok(Self(n as i64)) + } + Value::String(s) => { + let s = s.to_str()?; + Self::from_str(s).map_err(mlua::Error::external) + } + _ => { + return Err(mlua::Error::FromLuaConversionError { + from: value.type_name(), + to: "Power", + message: Some("expected nil, string or number".into()), + }) + } + } + } +} + +#[derive(Debug, Clone, Inspect)] +pub struct RecipeItem { + pub id: ItemID, + pub amount: i32, +} + +impl<'lua> FromLua<'lua> for RecipeItem { + fn from_lua(value: Value<'lua>, lua: &'lua Lua) -> mlua::Result { + let table: Table = FromLua::from_lua(value, lua)?; + + if let Ok(v) = table.get(1) { + let item_id = ItemID::from_lua(v, lua)?; + let amount = table.get(2)?; + return Ok(Self { + id: item_id, + amount, + }); + } + + let name = get_with_err::(&table, "id")?; + let item_id = ItemID::from(&name); + let amount = get_with_err(&table, "amount")?; + + Ok(Self { + id: item_id, + amount, + }) + } +} + +#[derive(Debug, Clone, Inspect)] +pub struct Recipe { + pub consumption: Vec, + pub production: Vec, + + pub power_usage: Power, + pub power_generation: Power, + + /// Time to execute the recipe when the facility is at full capacity, in seconds + pub complexity: i32, + + /// Quantity to store per production in terms of quantity produced. So if it takes 1ton of flour to make + /// 1 ton of bread. A storage multiplier of 3 means 3 tons of bread will be stored before stopping to + /// produce it. + pub storage_multiplier: i32, +} + +impl<'lua> FromLua<'lua> for Recipe { + fn from_lua(value: Value<'lua>, lua: &'lua Lua) -> mlua::Result { + let table: Table = FromLua::from_lua(value, lua)?; + Ok(Self { + consumption: get_with_err(&table, "consumption")?, + production: get_with_err(&table, "production")?, + power_usage: get_with_err(&table, "power_usage")?, + power_generation: get_with_err(&table, "power_generation")?, + complexity: get_with_err(&table, "complexity")?, + storage_multiplier: get_with_err(&table, "storage_multiplier")?, + }) + } +} + +#[derive(Debug)] +pub struct GoodsCompanyPrototype { + pub base: PrototypeBase, + pub id: GoodsCompanyID, + pub label: String, + pub bgen: BuildingGen, + pub kind: CompanyKind, + pub recipe: Recipe, + pub n_trucks: i32, + pub n_workers: i32, + pub size: f32, + pub asset_location: String, + pub price: i64, + pub zone: Option, +} + +impl Prototype for GoodsCompanyPrototype { + type ID = GoodsCompanyID; + const KIND: &'static str = "goods-company"; + + fn from_lua(table: &Table) -> mlua::Result { + Ok(Self { + base: PrototypeBase::from_lua(table)?, + id: GoodsCompanyID::from(&get_with_err::(table, "name")?), + label: get_with_err(table, "label")?, + bgen: get_with_err(table, "bgen")?, + kind: get_with_err(table, "kind")?, + recipe: get_with_err(table, "recipe")?, + n_trucks: table.get::<_, Option>("n_trucks")?.unwrap_or(0), + n_workers: get_with_err(table, "n_workers")?, + size: get_with_err(table, "size")?, + asset_location: get_with_err(table, "asset_location")?, + price: get_with_err(table, "price")?, + zone: get_with_err(table, "zone").ok(), + }) + } + + fn id(&self) -> Self::ID { + self.id + } +} + +impl Deref for GoodsCompanyPrototype { + type Target = PrototypeBase; + + fn deref(&self) -> &Self::Target { + &self.base + } +} + +#[derive(Copy, Clone, Serialize, Deserialize, Debug, PartialEq, Eq)] +pub enum CompanyKind { + // Buyers come to get their goods + Store, + // Buyers get their goods delivered to them + Factory, + // Buyers get their goods instantly delivered, useful for things like electricity/water/.. + Network, +} + +impl<'a> FromLua<'a> for CompanyKind { + fn from_lua(value: Value<'a>, lua: &'a Lua) -> mlua::Result { + let s: String = FromLua::from_lua(value, lua)?; + match &*s { + "store" => Ok(Self::Store), + "factory" => Ok(Self::Factory), + "network" => Ok(Self::Network), + _ => Err(mlua::Error::external(format!( + "Unknown company kind: {}", + s + ))), + } + } +} + +debug_inspect_impl!(CompanyKind); + +#[derive(Debug, Copy, Clone, Serialize, Deserialize)] +pub enum BuildingGen { + House, + Farm, + CenteredDoor { + vertical_factor: f32, // 1.0 means that the door is at the bottom, just on the street + }, + NoWalkway { + door_pos: Vec2, // door_pos is relative to the center of the building + }, +} + +debug_inspect_impl!(BuildingGen); + +impl<'a> FromLua<'a> for BuildingGen { + fn from_lua(value: Value<'a>, _: &'a Lua) -> mlua::Result { + let table = match value { + Value::String(s) => { + let s = s.to_str()?; + return match &*s { + "house" => Ok(Self::House), + "farm" => Ok(Self::Farm), + _ => Err(mlua::Error::external(format!( + "Unknown building gen kind: {}", + s + ))), + }; + } + Value::Table(t) => t, + _ => Err(mlua::Error::FromLuaConversionError { + from: value.type_name(), + to: "BuildingGen", + message: Some("expected string or table".into()), + })?, + }; + let kind = get_with_err::(&table, "kind")?; + match kind.as_str() { + "house" => Ok(Self::House), + "farm" => Ok(Self::Farm), + "centered_door" => Ok(Self::CenteredDoor { + vertical_factor: get_with_err(&table, "vertical_factor")?, + }), + "no_walkway" => Ok(Self::NoWalkway { + door_pos: get_with_err(&table, "door_pos")?, + }), + _ => Err(mlua::Error::external(format!( + "Unknown building gen kind: {}", + kind + ))), + } + } +} + +#[derive(Debug)] +pub struct ZoneDescription { + pub floor: String, + pub filler: String, + /// The price for each "production unit" + pub price_per_area: i64, + /// Whether the zone filler positions should be randomized + pub randomize_filler: bool, +} + +impl<'lua> FromLua<'lua> for ZoneDescription { + fn from_lua(value: Value<'lua>, lua: &'lua Lua) -> mlua::Result { + let table: Table = FromLua::from_lua(value, lua)?; + Ok(Self { + floor: get_with_err(&table, "floor")?, + filler: get_with_err(&table, "filler")?, + price_per_area: get_with_err(&table, "price_per_area").unwrap_or(100), + randomize_filler: get_with_err(&table, "randomize_filler").unwrap_or(false), + }) + } +} diff --git a/prototypes/src/item.rs b/prototypes/src/item.rs new file mode 100644 index 00000000..ed022757 --- /dev/null +++ b/prototypes/src/item.rs @@ -0,0 +1,39 @@ +use crate::{get_with_err, ItemID, Prototype, PrototypeBase}; +use mlua::Table; +use std::ops::Deref; + +/// Item is the runtime representation of an item, such as meat, wood, etc. +#[derive(Clone, Debug)] +pub struct ItemPrototype { + pub id: ItemID, + pub base: PrototypeBase, + pub label: String, + pub optout_exttrade: bool, +} + +impl Deref for ItemPrototype { + type Target = PrototypeBase; + + fn deref(&self) -> &Self::Target { + &self.base + } +} + +impl Prototype for ItemPrototype { + type ID = ItemID; + const KIND: &'static str = "item"; + + fn from_lua(table: &Table) -> mlua::Result { + let base = PrototypeBase::from_lua(table)?; + Ok(Self { + id: ItemID::new(&base.name), + base, + label: get_with_err(table, "label")?, + optout_exttrade: get_with_err(table, "optout_exttrade").unwrap_or(false), + }) + } + + fn id(&self) -> Self::ID { + ItemID::from(&self.name) + } +} diff --git a/prototypes/src/lib.rs b/prototypes/src/lib.rs index c74dc655..837b9111 100644 --- a/prototypes/src/lib.rs +++ b/prototypes/src/lib.rs @@ -1,74 +1,331 @@ -use egui_inspect::debug_inspect_impl; -use geom::Vec2; +use common::TransparentMap; +use egui_inspect::Inspect; +use mlua::{FromLua, Lua, Table}; use serde::{Deserialize, Serialize}; +use std::error::Error; +use std::fmt::{Debug, Display, Formatter}; +use std::hash::Hash; +use std::io; +use thiserror::Error; -#[derive(Serialize, Deserialize)] -pub struct RecipeDescription { - pub consumption: Vec<(String, i32)>, - pub production: Vec<(String, i32)>, - pub complexity: i32, - pub storage_multiplier: i32, -} - -#[derive(Copy, Clone, Serialize, Deserialize, Debug)] -#[serde(tag = "kind", rename_all = "snake_case")] -pub enum CompanyKind { - // Buyers come to get their goods - Store, - // Buyers get their goods delivered to them - Factory { n_trucks: u32 }, - // Buyers get their goods instantly delivered, useful for things like electricity/water/.. - Network, -} - -debug_inspect_impl!(CompanyKind); - -#[derive(Debug, Copy, Clone, Serialize, Deserialize)] -#[serde(tag = "kind", rename_all = "snake_case")] -pub enum BuildingGen { - House, - Farm, - CenteredDoor { - vertical_factor: f32, // 1.0 means that the door is at the bottom, just on the street - }, - NoWalkway { - door_pos: Vec2, // door_pos is relative to the center of the building - }, -} - -#[derive(Serialize, Deserialize)] -pub struct GoodsCompanyDescriptionJSON { +mod company; +mod item; +mod tests; +mod validation; + +use crate::validation::ValidationError; +pub use company::*; +pub use item::*; + +pub trait Prototype: 'static + Sized { + type ID: Copy + Clone + Eq + Ord + Hash + 'static; + const KIND: &'static str; + + fn from_lua(table: &Table) -> mlua::Result; + fn id(&self) -> Self::ID; +} + +pub trait ConcretePrototype: Prototype { + fn storage(prototypes: &Prototypes) -> &TransparentMap; + fn storage_mut(prototypes: &mut Prototypes) -> &mut TransparentMap; +} + +pub trait PrototypeID: Debug + Copy + Clone + Eq + Ord + Hash + 'static { + type Prototype: Prototype; +} + +#[derive(Debug, Clone, Inspect)] +pub struct PrototypeBase { pub name: String, - pub bgen: BuildingGen, - #[serde(flatten)] - pub kind: CompanyKind, - pub recipe: RecipeDescription, - pub n_workers: i32, - pub size: f32, - pub asset_location: String, - pub price: i64, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub zone: Option>, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct ZoneDescription { - pub floor: String, - pub filler: String, - /// The price for each "production unit" - pub price_per_area: i64, - /// Wether the zone filler positions should be randomized - #[serde(default)] - pub randomize_filler: bool, -} - -impl Default for ZoneDescription { - fn default() -> Self { - Self { - floor: "".to_string(), - filler: "".to_string(), - price_per_area: 100, - randomize_filler: false, +} + +impl Prototype for PrototypeBase { + type ID = (); + const KIND: &'static str = "base"; + + fn from_lua(table: &Table) -> mlua::Result { + Ok(Self { + name: table.get("name")?, + }) + } + + fn id(&self) -> Self::ID { + () + } +} + +static mut PROTOTYPES: Option<&'static Prototypes> = None; + +#[inline] +pub fn prototypes() -> &'static Prototypes { + #[cfg(debug_assertions)] + { + assert!(unsafe { PROTOTYPES.is_some() }); + } + + // Safety: Please just don't use prototypes before they were loaded... We can allow this footgun + unsafe { PROTOTYPES.unwrap_unchecked() } +} + +#[inline] +pub fn prototype(id: ID) -> &'static ::Prototype +where + ID::Prototype: ConcretePrototype, +{ + match ::Prototype::storage(prototypes()).get(&id) { + Some(v) => v, + None => panic!("no prototype for id {:?}", id), + } +} + +pub fn try_prototype(id: ID) -> Option<&'static ::Prototype> +where + ID::Prototype: ConcretePrototype, +{ + ::Prototype::storage(prototypes()).get(&id) +} + +pub fn prototypes_iter() -> impl Iterator { + T::storage(prototypes()).values() +} + +pub fn prototypes_iter_ids() -> impl Iterator { + T::storage(prototypes()).keys().copied() +} + +pub fn test_prototypes(lua: &str) { + let l = Lua::new(); + + unsafe { load_prototypes_str(l, lua).unwrap() }; +} + +#[derive(Error, Debug)] +pub enum PrototypeLoadError { + #[error("loading data.lua: {0}")] + LoadingDataLua(#[from] io::Error), + #[error("lua error: {0}")] + LuaError(#[from] mlua::Error), + #[error("lua error for {0} {1}: {2}")] + PrototypeLuaError(String, String, mlua::Error), + #[error("multiple errors: {0}")] + MultiError(MultiError), + #[error("validation errors: {0}")] + ValidationErrors(#[from] MultiError), +} + +#[derive(Debug)] +pub struct MultiError(Vec); + +impl Display for MultiError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + for e in &self.0 { + writeln!(f, "{}", e)?; + } + Ok(()) + } +} + +impl Error for MultiError {} + +macro_rules! prototype_id { + ($id:ident => $proto:ty) => { + #[derive( + Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, + )] + pub struct $id(pub(crate) u64); + + egui_inspect::debug_inspect_impl!($id); + + impl $id { + #[inline] + pub fn new(v: &str) -> $id { + Self(common::hash_u64(v)) + } + + #[inline] + pub fn prototype(self) -> &'static $proto { + crate::prototype(self) + } + + #[inline] + pub fn hash(&self) -> u64 { + self.0 + } } + + impl<'a> From<&'a str> for $id { + fn from(v: &'a str) -> Self { + Self(common::hash_u64(v)) + } + } + + impl<'a> From<&'a String> for $id { + fn from(v: &'a String) -> Self { + Self(common::hash_u64(&*v)) + } + } + + impl<'a> mlua::FromLua<'a> for $id { + fn from_lua(v: mlua::Value<'a>, _: &'a mlua::Lua) -> mlua::Result { + match v { + mlua::Value::String(s) => { + let Ok(v) = s.to_str() else { + return Err(mlua::Error::FromLuaConversionError { + from: "string", + to: stringify!($id), + message: Some("expected utf-8 string".into()), + }); + }; + Ok(Self(common::hash_u64(v))) + } + _ => Err(mlua::Error::FromLuaConversionError { + from: v.type_name(), + to: stringify!($id), + message: Some("expected string".into()), + }), + } + } + } + + impl crate::PrototypeID for $id { + type Prototype = $proto; + } + }; +} + +macro_rules! gen_prototypes { + ($($name:ident : $id:ident => $t:ty,)+) => { + $( + prototype_id!($id => $t); + )+ + + pub struct Prototypes { + $( + $name: TransparentMap<$id, $t>, + )+ + } + + impl Default for Prototypes { + fn default() -> Self { + Self { + $( + $name: Default::default(), + )+ + } + } + } + + $( + impl ConcretePrototype for $t { + fn storage(prototypes: &Prototypes) -> &TransparentMap { + &prototypes.$name + } + + fn storage_mut(prototypes: &mut Prototypes) -> &mut TransparentMap { + &mut prototypes.$name + } + } + + impl $t { + pub fn iter() -> impl Iterator { + crate::prototypes_iter::() + } + pub fn iter_ids() -> impl Iterator { + crate::prototypes_iter_ids::() + } + } + )+ + + fn print_prototype_stats() { + $( + log::info!("loaded {} {}", <$t>::storage(prototypes()).len(), <$t>::KIND); + )+ + } + + fn parse_prototype(table: Table, proto: &mut Prototypes) -> Result<(), PrototypeLoadError> { + let _type = table.get::<_, String>("type")?; + let _type_str = _type.as_str(); + match _type_str { + $( + <$t>::KIND => { + let p: $t = Prototype::from_lua(&table).map_err(|e| { + PrototypeLoadError::PrototypeLuaError(_type_str.to_string(), table.get::<_, String>("name").unwrap(), e) + })?; + if let Some(v) = proto.$name.insert((&p.name).into(), p) { + log::warn!("duplicate {} with name: {}", <$t>::KIND, v.name); + } + } + ),+ + _ => { + if let Ok(s) = table.get::<_, String>("type") { + log::warn!("unknown prototype: {}", s) + } + } + } + + Ok(()) + } + }; +} + +gen_prototypes!(companies: GoodsCompanyID => GoodsCompanyPrototype, + items: ItemID => ItemPrototype, +); + +/// Loads the prototypes from the data.lua file +/// # Safety +/// This function is not thread safe, and should only be called once at the start of the program. +pub unsafe fn load_prototypes(base: &str) -> Result<(), PrototypeLoadError> { + log::info!("loading prototypes from {}", base); + let l = Lua::new(); + + let base = base.to_string(); + + l.globals() + .get::<_, Table>("package")? + .set("path", base.clone() + "base_mod/?.lua")?; + + load_prototypes_str( + l, + &common::saveload::load_string(base + "base_mod/data.lua")?, + ) +} + +unsafe fn load_prototypes_str(l: Lua, main: &str) -> Result<(), PrototypeLoadError> { + l.load(include_str!("prototype_init.lua")).exec()?; + + l.load(main).exec()?; + + let mut p = Box::new(Prototypes::default()); + + let mut errors = Vec::new(); + + let data_table = l.globals().get::<_, Table>("data")?; + + let _ = data_table.for_each(|_: String, t: Table| { + let r = parse_prototype(t, &mut *p); + if let Err(e) = r { + errors.push(e); + } + Ok(()) + }); + + if !errors.is_empty() { + return Err(PrototypeLoadError::MultiError(MultiError(errors))); } + + validation::validate(&p)?; + + unsafe { + PROTOTYPES = Some(Box::leak(p)); + } + + print_prototype_stats(); + + Ok(()) +} + +fn get_with_err<'a, T: FromLua<'a>>(t: &Table<'a>, field: &'static str) -> mlua::Result { + t.get::<_, T>(field) + .map_err(|e| mlua::Error::external(format!("field {}: {}", field, e))) } diff --git a/prototypes/src/prototype_init.lua b/prototypes/src/prototype_init.lua new file mode 100644 index 00000000..72072c72 --- /dev/null +++ b/prototypes/src/prototype_init.lua @@ -0,0 +1,12 @@ +data = {} + +function data:extend (t) + for _, v in ipairs(t) do + rawset(self, rawlen(self)+1, v) + end +end + +setmetatable(data, { + __index = data, + __newindex = function (t, k, v) end, +}) \ No newline at end of file diff --git a/prototypes/src/tests.rs b/prototypes/src/tests.rs new file mode 100644 index 00000000..46ca3b77 --- /dev/null +++ b/prototypes/src/tests.rs @@ -0,0 +1,25 @@ +#![cfg(test)] + +use crate::{load_prototypes, try_prototype, GoodsCompanyID, ItemID}; + +#[test] +fn test_base() { + unsafe { + match load_prototypes("../") { + Ok(_) => {} + Err(e) => { + println!("failed to load prototypes: {}", e); + assert!(false); + } + } + + println!( + "{:?}", + try_prototype(ItemID::new("job-opening")) + .unwrap() + .optout_exttrade + ); + println!("{:?}", try_prototype(ItemID::new("cereal"))); + println!("{:#?}", try_prototype(GoodsCompanyID::new("bakery"))); + } +} diff --git a/prototypes/src/validation.rs b/prototypes/src/validation.rs new file mode 100644 index 00000000..e0fd9594 --- /dev/null +++ b/prototypes/src/validation.rs @@ -0,0 +1,68 @@ +use crate::{CompanyKind, MultiError, Prototypes}; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum ValidationError { + #[error("{0}: only factories can have trucks")] + WrongTrucks(String), + #[error("{0}: factories must have trucks")] + ZeroTrucks(String), + #[error("{0}.{1}: referenced prototype not found")] + ReferencedProtoNotFound(String, &'static str), + + #[error("{0}.{1}: {2}")] + InvalidField(String, &'static str, String), +} + +pub(crate) fn validate(proto: &Prototypes) -> Result<(), MultiError> { + let mut errors = vec![]; + + for comp in proto.companies.values() { + if comp.n_trucks > 0 && comp.kind != CompanyKind::Factory { + errors.push(ValidationError::WrongTrucks(comp.name.clone())); + } + + if comp.n_trucks == 0 && comp.kind == CompanyKind::Factory { + errors.push(ValidationError::ZeroTrucks(comp.name.clone())); + } + + for item in &comp.recipe.consumption { + if !proto.items.contains_key(&item.id) { + errors.push(ValidationError::ReferencedProtoNotFound( + comp.name.clone(), + "consumption", + )); + } + } + + for item in &comp.recipe.production { + if !proto.items.contains_key(&item.id) { + errors.push(ValidationError::ReferencedProtoNotFound( + comp.name.clone(), + "production", + )); + } + } + + if comp.recipe.power_usage.0 < 0 { + errors.push(ValidationError::InvalidField( + comp.name.clone(), + "power_usage", + "must not be negative".to_string(), + )); + } + + if comp.recipe.power_generation.0 < 0 { + errors.push(ValidationError::InvalidField( + comp.name.clone(), + "power_generation", + "must not be negative".to_string(), + )); + } + } + + if !errors.is_empty() { + return Err(MultiError(errors)); + } + Ok(()) +} diff --git a/simulation/src/economy/ecostats.rs b/simulation/src/economy/ecostats.rs index 269120b8..8f91d7d4 100644 --- a/simulation/src/economy/ecostats.rs +++ b/simulation/src/economy/ecostats.rs @@ -1,4 +1,5 @@ -use crate::economy::{ItemID, ItemRegistry, Money, Trade, TradeTarget}; +use crate::economy::{ItemID, Money, Trade, TradeTarget}; +use prototypes::{prototypes_iter, ItemPrototype}; use serde::{Deserialize, Serialize}; use serde_big_array::BigArray; use std::collections::BTreeMap; @@ -47,10 +48,9 @@ pub struct EcoStats { } impl ItemHistories { - pub fn new(registry: &ItemRegistry) -> Self { + pub fn new() -> Self { Self { - m: registry - .iter() + m: prototypes_iter::() .map(|item| (item.id, ItemHistory::default())) .collect(), cursors: [0; LEVEL_FREQS.len()], @@ -100,11 +100,11 @@ impl ItemHistories { } impl EcoStats { - pub fn new(registry: &ItemRegistry) -> Self { + pub fn new() -> Self { Self { - exports: ItemHistories::new(registry), - imports: ItemHistories::new(registry), - internal_trade: ItemHistories::new(registry), + exports: ItemHistories::new(), + imports: ItemHistories::new(), + internal_trade: ItemHistories::new(), } } diff --git a/simulation/src/economy/government.rs b/simulation/src/economy/government.rs index 1084e573..6ec4027e 100644 --- a/simulation/src/economy/government.rs +++ b/simulation/src/economy/government.rs @@ -1,7 +1,7 @@ use crate::economy::Money; use crate::map::{LanePattern, MapProject, MAX_ZONE_AREA}; use crate::world_command::WorldCommand; -use crate::{BuildingKind, GoodsCompanyRegistry, Simulation}; +use crate::{BuildingKind, Simulation}; use serde::{Deserialize, Serialize}; /// The government represents the player. @@ -32,13 +32,17 @@ impl Government { } => { let m = sim.map(); let Some(b) = m.buildings.get(*bid) else { + log::error!("Trying to update zone of non-existent building"); return Money::ZERO; }; let Some(gc) = b.kind.as_goods_company() else { + log::error!("Trying to update zone of non-goods-company building"); + return Money::ZERO; + }; + let Some(zonedescr) = gc.prototype().zone.as_ref() else { + log::error!("Trying to update zone of non-zoneable building"); return Money::ZERO; }; - let registry = sim.read::(); - let zonedescr = registry.descriptions[gc].zone.as_ref().unwrap(); let oldarea = b.zone.as_ref().map_or(0.0, |z| z.area); let newarea = z.area; @@ -55,7 +59,7 @@ impl Government { } WorldCommand::MapBuildSpecialBuilding { kind: x, .. } => match x { BuildingKind::GoodsCompany(x) => { - let descr = &sim.read::().descriptions[*x]; + let descr = x.prototype(); descr.price + descr .zone diff --git a/simulation/src/economy/item.rs b/simulation/src/economy/item.rs deleted file mode 100644 index 1fbc252c..00000000 --- a/simulation/src/economy/item.rs +++ /dev/null @@ -1,86 +0,0 @@ -use common::saveload::Encoder; -use common::FastMap; -use serde::{Deserialize, Serialize}; -use slotmapd::{new_key_type, SlotMap}; -use std::ops::Index; - -/// ItemDefinition is the definition of an item, as read from the items.json file. -#[derive(Serialize, Deserialize)] -struct ItemDefinition { - name: String, - label: String, - #[serde(default)] - optout_exttrade: bool, -} - -/// Item is the runtime representation of an item, such as meat, wood, etc. -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct Item { - pub id: ItemID, - pub name: String, - pub label: String, - pub optout_exttrade: bool, -} - -new_key_type! { - pub struct ItemID; -} - -debug_inspect_impl!(ItemID); - -#[derive(Default, Serialize, Deserialize)] -pub struct ItemRegistry { - items: SlotMap, - item_names: FastMap, -} - -impl Index for ItemRegistry { - type Output = Item; - fn index(&self, index: ItemID) -> &Self::Output { - &self.items[index] - } -} - -impl ItemRegistry { - pub fn id(&self, name: &str) -> ItemID { - self.item_names - .get(name) - .copied() - .unwrap_or_else(|| panic!("no item in registry named {name}")) - } - - pub fn try_id(&self, name: &str) -> Option { - self.item_names.get(name).copied() - } - - pub fn get(&self, id: ItemID) -> Option<&Item> { - self.items.get(id) - } - - pub fn iter(&self) -> impl Iterator + '_ { - self.items.values() - } - - pub fn load_item_definitions(&mut self, source: &str) { - let definitions: Vec = match common::saveload::JSON::decode(source.as_ref()) - { - Ok(x) => x, - Err(e) => { - log::error!("error loading item definitions: {}", e); - return; - } - }; - for definition in definitions { - let name = definition.name.clone(); - let id = self.items.insert_with_key(move |id| Item { - id, - name: definition.name, - label: definition.label, - optout_exttrade: definition.optout_exttrade, - }); - self.item_names.insert(name, id); - #[cfg(not(test))] - log::debug!("loaded {:?}", &self.items[id]); - } - } -} diff --git a/simulation/src/economy/market.rs b/simulation/src/economy/market.rs index a5a80c7a..93444e60 100644 --- a/simulation/src/economy/market.rs +++ b/simulation/src/economy/market.rs @@ -1,10 +1,10 @@ -use crate::economy::{Item, ItemID, ItemRegistry, Money, WORKER_CONSUMPTION_PER_SECOND}; +use crate::economy::{ItemID, Money, WORKER_CONSUMPTION_PER_SECOND}; use crate::map::BuildingID; use crate::map_dynamic::BuildingInfos; -use crate::souls::goods_company::GoodsCompanyID; -use crate::{BuildingKind, GoodsCompanyRegistry, Map, SoulID}; +use crate::{BuildingKind, Map, SoulID}; use geom::Vec2; use ordered_float::OrderedFloat; +use prototypes::{prototypes_iter, GoodsCompanyID, GoodsCompanyPrototype, ItemPrototype}; use serde::{Deserialize, Serialize}; use std::collections::btree_map::Entry; use std::collections::BTreeMap; @@ -121,11 +121,10 @@ pub fn find_trade_place( } impl Market { - pub fn new(registry: &ItemRegistry, companies: &GoodsCompanyRegistry) -> Self { - let prices = calculate_prices(registry, companies, 1.25); + pub fn new() -> Self { + let prices = calculate_prices(1.25); Self { - markets: registry - .iter() + markets: prototypes_iter::() .map(|v| (v.id, SingleMarket::new(prices[&v.id], v.optout_exttrade))) .collect(), all_trades: Default::default(), @@ -360,51 +359,38 @@ impl Market { } } -fn calculate_prices( - registry: &ItemRegistry, - companies: &GoodsCompanyRegistry, - price_multiplier: f32, -) -> BTreeMap { +fn calculate_prices(price_multiplier: f32) -> BTreeMap { let mut item_graph: BTreeMap> = BTreeMap::new(); - for (id, company) in companies.descriptions.iter() { - for (itemid, _) in &company.recipe.production { - item_graph.entry(*itemid).or_default().push(id); + for company in GoodsCompanyPrototype::iter() { + for item in &company.recipe.production { + item_graph.entry(item.id).or_default().push(company.id); } } let mut prices = BTreeMap::new(); fn calculate_price_inner( - registry: &ItemRegistry, - companies: &GoodsCompanyRegistry, item_graph: &BTreeMap>, - item: &Item, + id: ItemID, prices: &mut BTreeMap, price_multiplier: f32, ) { - if prices.contains_key(&item.id) { + if prices.contains_key(&id) { return; } let mut minprice = None; - for &comp in item_graph.get(&item.id).unwrap_or(&vec![]) { - let company = &companies.descriptions[comp]; + for &comp in item_graph.get(&id).unwrap_or(&vec![]) { + let company = &comp.prototype(); let mut price_consumption = Money::ZERO; - for &(itemid, qty) in &company.recipe.consumption { - calculate_price_inner( - registry, - companies, - item_graph, - ®istry[itemid], - prices, - price_multiplier, - ); - price_consumption += prices[&itemid] * qty as i64; + for recipe_item in &company.recipe.consumption { + calculate_price_inner(item_graph, recipe_item.id, prices, price_multiplier); + price_consumption += prices[&recipe_item.id] * recipe_item.amount as i64; } let qty = company .recipe .production .iter() - .find_map(|x| (x.0 == item.id).then_some(x.1)) + .find_map(|x| (x.id == id).then_some(x.amount)) .unwrap_or(0) as i64; let price_workers = company.recipe.complexity as i64 @@ -418,18 +404,11 @@ fn calculate_prices( minprice = minprice.map(|x: Money| x.min(newprice)).or(Some(newprice)); } - prices.insert(item.id, minprice.unwrap_or(Money::ZERO)); + prices.insert(id, minprice.unwrap_or(Money::ZERO)); } - for item in registry.iter() { - calculate_price_inner( - registry, - companies, - &item_graph, - item, - &mut prices, - price_multiplier, - ); + for item in ItemPrototype::iter() { + calculate_price_inner(&item_graph, item.id, &mut prices, price_multiplier); } prices @@ -438,12 +417,11 @@ fn calculate_prices( #[cfg(test)] mod tests { use super::Market; - use crate::economy::{ItemRegistry, WORKER_CONSUMPTION_PER_SECOND}; - use crate::souls::goods_company::{GoodsCompanyDescription, Recipe}; + use crate::economy::WORKER_CONSUMPTION_PER_SECOND; use crate::world::CompanyID; - use crate::{GoodsCompanyRegistry, SoulID}; - use common::descriptions::{BuildingGen, CompanyKind}; + use crate::SoulID; use geom::{vec2, Vec2}; + use prototypes::{test_prototypes, ItemID}; fn mk_ent(id: u64) -> CompanyID { CompanyID::from(slotmapd::KeyData::from_ffi(id)) @@ -455,26 +433,26 @@ mod tests { let seller_far = SoulID::GoodsCompany(mk_ent((1 << 32) | 2)); let buyer = SoulID::GoodsCompany(mk_ent((1 << 32) | 3)); - let mut registry = ItemRegistry::default(); - - registry.load_item_definitions( + test_prototypes( r#" - [{ - "name": "cereal", - "label": "Cereal" + data:extend { + { + type = "item", + name = "cereal", + label = "Cereal" }, { - "name": "wheat", - "label": "Wheat" - }] + type = "item", + name = "wheat", + label = "Wheat", + } + } "#, ); - let g = GoodsCompanyRegistry::default(); + let mut m = Market::new(); - let mut m = Market::new(®istry, &g); - - let cereal = registry.id("cereal"); + let cereal = ItemID::new("cereal"); m.produce(seller, cereal, 3); m.produce(seller_far, cereal, 3); @@ -494,67 +472,70 @@ mod tests { #[test] fn calculate_prices() { - let mut registry = ItemRegistry::default(); - - registry.load_item_definitions( + test_prototypes( r#" - [{ - "name": "cereal", - "label": "Cereal" + data:extend { + { + type = "item", + name = "cereal", + label = "Cereal" }, { - "name": "wheat", - "label": "Wheat" - }] + type = "item", + name = "wheat", + label = "Wheat", + } + } + + data:extend {{ + type = "goods-company", + name = "cereal-farm", + label = "Cereal farm", + kind = "factory", + bgen = "farm", + recipe = { + production = { + {"cereal", 3} + }, + consumption = {}, + complexity = 3, + storage_multiplier = 5, + }, + n_trucks = 1, + n_workers = 2, + size = 0.0, + asset_location = "", + price = 0, + }, + { + type = "goods-company", + name = "wheat-factory", + label = "Wheat factory", + kind = "factory", + bgen = "farm", + recipe = { + production = { + {"wheat", 2} + }, + consumption = { + {"cereal", 2} + }, + complexity = 10, + storage_multiplier = 5, + }, + n_trucks = 1, + n_workers = 5, + size = 0.0, + asset_location = "", + price = 0, + }} "#, ); - let cereal = registry.id("cereal"); - let wheat = registry.id("wheat"); - - let mut companies = GoodsCompanyRegistry::default(); - - companies - .descriptions - .insert_with_key(|id| GoodsCompanyDescription { - id, - name: "Cereal farm".to_string(), - bgen: BuildingGen::House, - kind: CompanyKind::Store, - recipe: Recipe { - production: vec![(cereal, 3)], - complexity: 3, - consumption: vec![], - storage_multiplier: 5, - }, - n_workers: 2, - size: 0.0, - asset_location: "".to_string(), - price: 0, - zone: None, - }); - - companies - .descriptions - .insert_with_key(|id| GoodsCompanyDescription { - id, - name: "Wheat factory".to_string(), - bgen: BuildingGen::House, - kind: CompanyKind::Store, - recipe: Recipe { - production: vec![(wheat, 2)], - complexity: 10, - consumption: vec![(cereal, 2)], - storage_multiplier: 5, - }, - n_workers: 5, - size: 0.0, - asset_location: "".to_string(), - price: 0, - zone: None, - }); - - let prices = super::calculate_prices(®istry, &companies, 1.0); + let cereal = ItemID::new("cereal"); + let wheat = ItemID::new("wheat"); + + let prices = super::calculate_prices(1.0); assert_eq!(prices.len(), 2); let price_cereal = 2 * WORKER_CONSUMPTION_PER_SECOND; diff --git a/simulation/src/economy/mod.rs b/simulation/src/economy/mod.rs index 39c14bf7..e14387f9 100644 --- a/simulation/src/economy/mod.rs +++ b/simulation/src/economy/mod.rs @@ -8,8 +8,8 @@ //! - The government, which is the entity representing the player //! use crate::utils::resources::Resources; +use crate::SoulID; use crate::World; -use crate::{GoodsCompanyRegistry, SoulID}; use egui_inspect::Inspect; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; @@ -19,15 +19,14 @@ use std::ops::{Add, AddAssign, Div, Mul, Neg, SubAssign}; mod ecostats; mod government; -mod item; mod market; use crate::utils::time::{Tick, TICKS_PER_SECOND}; use crate::world::HumanID; pub use ecostats::*; pub use government::*; -pub use item::*; pub use market::*; +use prototypes::ItemID; const WORKER_CONSUMPTION_PER_SECOND: Money = Money::new_cents(1); @@ -164,31 +163,10 @@ pub struct Bought(pub BTreeMap>); #[derive(Inspect, Debug, Default, Serialize, Deserialize)] pub struct Workers(pub Vec); -#[cfg(not(test))] -const ITEMS_PATH: &str = "assets/items.json"; -#[cfg(not(test))] -const COMPANIES_PATH: &str = "assets/companies.json"; - -#[cfg(test)] -const ITEMS_PATH: &str = "../assets/items.json"; -#[cfg(test)] -const COMPANIES_PATH: &str = "../assets/companies.json"; - pub fn init_market(_: &mut World, res: &mut Resources) { - res.write::() - .load_item_definitions(&common::saveload::load_string(ITEMS_PATH).unwrap()); - - res.write::().load( - &common::saveload::load_string(COMPANIES_PATH).unwrap(), - &res.read::(), - ); - - let market = Market::new( - &res.read::(), - &res.read::(), - ); + let market = Market::new(); res.insert(market); - let stats = EcoStats::new(&res.read::()); + let stats = EcoStats::new(); res.insert(stats); } @@ -197,7 +175,7 @@ pub fn market_update(world: &mut World, resources: &mut Resources) { let n_workers = world.humans.len(); let mut m = resources.write::(); - let job_opening = resources.read::().id("job-opening"); + let job_opening = ItemID::new("job-opening"); let mut gvt = resources.write::(); let tick = resources.read::().0; diff --git a/simulation/src/init.rs b/simulation/src/init.rs index 8679f888..c20dc7ef 100644 --- a/simulation/src/init.rs +++ b/simulation/src/init.rs @@ -1,4 +1,4 @@ -use crate::economy::{init_market, market_update, EcoStats, Government, ItemRegistry, Market}; +use crate::economy::{init_market, market_update, EcoStats, Government, Market}; use crate::map::Map; use crate::map_dynamic::{ dispatch_system, itinerary_update, routing_changed_system, routing_update_system, @@ -6,7 +6,7 @@ use crate::map_dynamic::{ }; use crate::multiplayer::MultiplayerState; use crate::souls::freight_station::freight_station_system; -use crate::souls::goods_company::{company_system, GoodsCompanyRegistry}; +use crate::souls::goods_company::company_system; use crate::souls::human::update_decision_system; use crate::transportation::pedestrian_decision_system; use crate::transportation::road::{vehicle_decision_system, vehicle_state_update_system}; @@ -28,6 +28,22 @@ use serde::de::DeserializeOwned; use serde::Serialize; pub fn init() { + // # Safety + // This function is called only once, before any other function in this crate. + unsafe { + #[cfg(not(test))] + let base = "./"; + #[cfg(test)] + let base = "../"; + + match prototypes::load_prototypes(base) { + Ok(_) => {} + Err(e) => { + panic!("Error loading prototypes: {}", e) + } + } + } + register_system("dispatch_system", dispatch_system); register_system("update_decision_system", update_decision_system); register_system("company_system", company_system); @@ -47,8 +63,6 @@ pub fn init() { register_system_sim("add_souls_to_empty_buildings", add_souls_to_empty_buildings); - register_resource_noserialize::(); - register_resource_noserialize::(); register_resource_noserialize::>(); register_resource_noserialize::>(); register_resource_noserialize::>(); diff --git a/simulation/src/lib.rs b/simulation/src/lib.rs index 6b904e58..5d185160 100644 --- a/simulation/src/lib.rs +++ b/simulation/src/lib.rs @@ -4,7 +4,6 @@ use crate::map::{BuildingKind, Map}; use crate::map_dynamic::{Itinerary, ItineraryLeader}; use crate::souls::add_souls_to_empty_buildings; -use crate::souls::goods_company::GoodsCompanyRegistry; use crate::utils::resources::{Ref, RefMut, Resources}; use crate::world_command::WorldCommand; use common::saveload::Encoder; @@ -493,10 +492,9 @@ const START_COMMANDS: &str = r#" ] }, "kind": "RailFreightStation", - "gen": { - "kind": "no_walkway", + "gen": {"NoWalkway": { "door_pos": 0 - }, + }}, "zone": null } } diff --git a/simulation/src/map/objects/building.rs b/simulation/src/map/objects/building.rs index 3862fa05..362ee6cd 100644 --- a/simulation/src/map/objects/building.rs +++ b/simulation/src/map/objects/building.rs @@ -1,9 +1,8 @@ use crate::map::procgen::{gen_exterior_farm, gen_exterior_house, ColoredMesh}; use crate::map::{Buildings, Environment, LanePattern, SpatialMap}; -use crate::souls::goods_company::GoodsCompanyID; use egui_inspect::debug_inspect_impl; use geom::{Color, Polygon, Vec2, Vec3, OBB}; -use prototypes::BuildingGen; +use prototypes::{BuildingGen, GoodsCompanyID}; use serde::{Deserialize, Serialize}; use slotmapd::new_key_type; diff --git a/simulation/src/souls/desire/buyfood.rs b/simulation/src/souls/desire/buyfood.rs index 123fa1ce..251bd025 100644 --- a/simulation/src/souls/desire/buyfood.rs +++ b/simulation/src/souls/desire/buyfood.rs @@ -1,4 +1,10 @@ -use crate::economy::{find_trade_place, Bought, ItemID, ItemRegistry, Market}; +use serde::{Deserialize, Serialize}; + +use egui_inspect::Inspect; +use geom::Transform; +use prototypes::ItemID; + +use crate::economy::{find_trade_place, Bought, Market}; use crate::map::BuildingID; use crate::map_dynamic::{BuildingInfos, Destination}; use crate::souls::human::HumanDecisionKind; @@ -6,9 +12,6 @@ use crate::transportation::Location; use crate::utils::time::{GameInstant, GameTime}; use crate::world::{HumanEnt, HumanID}; use crate::{Map, ParCommandBuffer, SoulID}; -use egui_inspect::Inspect; -use geom::Transform; -use serde::{Deserialize, Serialize}; #[derive(Clone, Serialize, Deserialize, Debug)] pub enum BuyFoodState { @@ -23,16 +26,14 @@ debug_inspect_impl!(BuyFoodState); pub struct BuyFood { pub last_ate: GameInstant, state: BuyFoodState, - bread: ItemID, pub last_score: f32, } impl BuyFood { - pub fn new(start: GameInstant, registry: &ItemRegistry) -> Self { + pub fn new(start: GameInstant) -> Self { BuyFood { last_ate: start, state: BuyFoodState::Empty, - bread: registry.id("bread"), last_score: 0.0, } } @@ -41,7 +42,7 @@ impl BuyFood { if matches!(self.state, BuyFoodState::WaitingForTrade) && bought .0 - .get(&self.bread) + .get(&ItemID::new("bread")) .map(Vec::is_empty) .unwrap_or(false) { @@ -70,15 +71,14 @@ impl BuyFood { match self.state { BuyFoodState::Empty => { let pos = trans.pos; - let bread = self.bread; cbuf.exec_on(id, move |market: &mut Market| { - market.buy(SoulID::Human(id), pos.xy(), bread, 1) + market.buy(SoulID::Human(id), pos.xy(), ItemID::new("bread"), 1) }); self.state = BuyFoodState::WaitingForTrade; Yield } BuyFoodState::WaitingForTrade => { - for trade in bought.0.entry(self.bread).or_default().drain(..) { + for trade in bought.0.entry(ItemID::new("bread")).or_default().drain(..) { if let Some(b) = find_trade_place(trade.seller, trans.pos.xy(), binfos, map) { self.state = BuyFoodState::BoughtAt(b); } diff --git a/simulation/src/souls/freight_station.rs b/simulation/src/souls/freight_station.rs index d1717335..c596f7a7 100644 --- a/simulation/src/souls/freight_station.rs +++ b/simulation/src/souls/freight_station.rs @@ -161,8 +161,8 @@ mod tests { use crate::souls::human::{spawn_human, HumanDecisionKind}; use crate::tests::TestCtx; use crate::{BuildingKind, SoulID, WorldCommand}; - use common::descriptions::BuildingGen; use geom::{vec2, vec3, OBB}; + use prototypes::BuildingGen; #[test] fn test_deliver_to_freight_station_incrs_station() { diff --git a/simulation/src/souls/goods_company.rs b/simulation/src/souls/goods_company.rs index 85e70520..930db578 100644 --- a/simulation/src/souls/goods_company.rs +++ b/simulation/src/souls/goods_company.rs @@ -1,5 +1,10 @@ -use super::desire::Work; -use crate::economy::{find_trade_place, ItemID, ItemRegistry, Market}; +use serde::{Deserialize, Serialize}; + +use egui_inspect::Inspect; +use geom::{Transform, Vec2}; +use prototypes::{CompanyKind, GoodsCompanyID, GoodsCompanyPrototype, ItemID, Recipe}; + +use crate::economy::{find_trade_place, Market}; use crate::map::{Building, BuildingID, Map, Zone, MAX_ZONE_AREA}; use crate::map_dynamic::BuildingInfos; use crate::souls::desire::WorkKind; @@ -8,147 +13,49 @@ use crate::utils::time::GameTime; use crate::world::{CompanyEnt, HumanEnt, HumanID, VehicleID}; use crate::{ParCommandBuffer, SoulID}; use crate::{Simulation, World}; -use common::saveload::Encoder; -use egui_inspect::Inspect; -use geom::{Transform, Vec2}; -use prototypes::{BuildingGen, CompanyKind, GoodsCompanyDescriptionJSON, ZoneDescription}; -use serde::{Deserialize, Serialize}; -use slotmapd::{new_key_type, SlotMap}; - -#[derive(Debug, Clone, Serialize, Deserialize, Inspect)] -pub struct Recipe { - pub consumption: Vec<(ItemID, i32)>, - pub production: Vec<(ItemID, i32)>, - - /// Time to execute the recipe when the facility is at full capacity, in seconds - pub complexity: i32, - - /// Quantity to store per production in terms of quantity produced. So if it takes 1ton of flour to make - /// 1 ton of bread. A storage multiplier of 3 means 3 tons of bread will be stored before stopping to - /// produce it. - pub storage_multiplier: i32, -} - -new_key_type! { - pub struct GoodsCompanyID; -} - -debug_inspect_impl!(GoodsCompanyID); - -#[derive(Debug)] -pub struct GoodsCompanyDescription { - pub id: GoodsCompanyID, - pub name: String, - pub bgen: BuildingGen, - pub kind: CompanyKind, - pub recipe: Recipe, - pub n_workers: i32, - pub size: f32, - pub asset_location: String, - pub price: i64, - pub zone: Option>, -} - -#[derive(Default)] -pub struct GoodsCompanyRegistry { - pub descriptions: SlotMap, -} -impl GoodsCompanyRegistry { - pub fn load(&mut self, source: &str, registry: &ItemRegistry) { - let descriptions: Vec = - match common::saveload::JSON::decode(source.as_ref()) { - Ok(x) => x, - Err(e) => { - log::error!("couldn't load goods company descriptions: {}", e); - return; - } - }; - - for descr in descriptions { - let recipe = Recipe { - consumption: descr - .recipe - .consumption - .into_iter() - .map(|(item, qty)| { - let item_id = registry.id(&item); - (item_id, qty) - }) - .collect(), - production: descr - .recipe - .production - .into_iter() - .map(|(item, qty)| { - let item_id = registry.id(&item); - (item_id, qty) - }) - .collect(), - complexity: descr.recipe.complexity, - storage_multiplier: descr.recipe.storage_multiplier, - }; - - #[allow(unused_variables)] - let id = self - .descriptions - .insert_with_key(move |id| GoodsCompanyDescription { - id, - name: descr.name, - bgen: descr.bgen, - kind: descr.kind, - recipe, - n_workers: descr.n_workers, - size: descr.size, - asset_location: descr.asset_location, - price: descr.price, - zone: descr.zone, - }); +use super::desire::Work; - #[cfg(not(test))] - log::debug!("loaded {:?}", &self.descriptions[id]); - } +pub fn recipe_init(recipe: &Recipe, soul: SoulID, near: Vec2, market: &mut Market) { + for item in &recipe.consumption { + market.buy_until(soul, near, item.id, item.amount as u32) } -} - -impl Recipe { - pub fn init(&self, soul: SoulID, near: Vec2, market: &mut Market) { - for &(kind, qty) in &self.consumption { - market.buy_until(soul, near, kind, qty as u32) - } - for &(kind, _) in &self.production { - market.register(soul, kind); - } + for item in &recipe.production { + market.register(soul, item.id); } +} - pub fn should_produce(&self, soul: SoulID, market: &Market) -> bool { - // Has enough resources - self.consumption +pub fn recipe_should_produce(recipe: &Recipe, soul: SoulID, market: &Market) -> bool { + // Has enough resources + recipe.consumption .iter() - .all(move |&(kind, qty)| market.capital(soul, kind) >= qty) + .all(move |item| market.capital(soul, item.id) >= item.amount) && // Has enough storage - self.production.iter().all(move |&(kind, qty)| { - market.capital(soul, kind) < qty * (self.storage_multiplier + 1) + recipe.production.iter().all(move |item| { + market.capital(soul, item.id) < item.amount * (recipe.storage_multiplier + 1) }) - } +} - pub fn act(&self, soul: SoulID, near: Vec2, market: &mut Market) { - for &(kind, qty) in &self.consumption { - market.produce(soul, kind, -qty); - market.buy_until(soul, near, kind, qty as u32); - } - for &(kind, qty) in &self.production { - market.produce(soul, kind, qty); - market.sell_all(soul, near, kind, (qty * self.storage_multiplier) as u32); - } +pub fn recipe_act(recipe: &Recipe, soul: SoulID, near: Vec2, market: &mut Market) { + for item in &recipe.consumption { + market.produce(soul, item.id, -item.amount); + market.buy_until(soul, near, item.id, item.amount as u32); + } + for item in &recipe.production { + market.produce(soul, item.id, item.amount); + market.sell_all( + soul, + near, + item.id, + (item.amount * recipe.storage_multiplier) as u32, + ); } } #[derive(Clone, Serialize, Deserialize, Inspect)] -pub struct GoodsCompany { - pub kind: CompanyKind, - pub recipe: Recipe, +pub struct GoodsCompanyState { + pub kind: GoodsCompanyID, pub building: BuildingID, pub max_workers: i32, /// In [0; 1] range, to show how much has been made until new product @@ -157,13 +64,17 @@ pub struct GoodsCompany { pub trucks: Vec, } -impl GoodsCompany { +impl GoodsCompanyState { pub fn productivity(&self, workers: usize, zone: Option<&Zone>) -> f32 { workers as f32 / self.max_workers as f32 * zone.map_or(1.0, |z| z.area / MAX_ZONE_AREA) } } -pub fn company_soul(sim: &mut Simulation, company: GoodsCompany) -> Option { +pub fn company_soul( + sim: &mut Simulation, + company: GoodsCompanyState, + proto: &GoodsCompanyPrototype, +) -> Option { let map = sim.map(); let b = map.buildings().get(company.building)?; let door_pos = b.door_pos; @@ -183,14 +94,14 @@ pub fn company_soul(sim: &mut Simulation, company: GoodsCompany) -> Option().id("job-opening"); + let job_opening = ItemID::new("job-opening"); { let m = &mut *sim.write::(); m.produce(soul, job_opening, company.max_workers); m.sell_all(soul, door_pos.xy(), job_opening, 0); - company.recipe.init(soul, door_pos.xy(), m); + recipe_init(&proto.recipe, soul, door_pos.xy(), m); } sim.write::() @@ -216,19 +127,22 @@ pub fn company_system(world: &mut World, res: &mut Resources) { return; }); - if c.comp.recipe.should_produce(soul, market) { + let proto = c.comp.kind.prototype(); + + if recipe_should_produce(&proto.recipe, soul, market) { c.comp.progress += c.comp.productivity(n_workers, b.zone.as_ref()) - / c.comp.recipe.complexity as f32 + / proto.recipe.complexity as f32 * delta; } if c.comp.progress >= 1.0 { c.comp.progress -= 1.0; - let recipe = c.comp.recipe.clone(); + let kind = c.comp.kind; let bpos = b.door_pos; cbuf.exec_on(me, move |market| { - recipe.act(soul, bpos.xy(), market); + let recipe = &kind.prototype().recipe; + recipe_act(recipe, soul, bpos.xy(), market); }); return; } @@ -299,8 +213,7 @@ pub fn company_system(world: &mut World, res: &mut Resources) { let mut kind = WorkKind::Worker; if let Some(truck) = c.comp.trucks.get(0) { - if matches!(c.comp.kind, CompanyKind::Factory { .. }) && c.comp.driver.is_none() - { + if proto.kind == CompanyKind::Factory && c.comp.driver.is_none() { kind = WorkKind::Driver { deliver_order: None, truck: *truck, diff --git a/simulation/src/souls/human.rs b/simulation/src/souls/human.rs index 34b563b1..8950f8ac 100644 --- a/simulation/src/souls/human.rs +++ b/simulation/src/souls/human.rs @@ -1,4 +1,4 @@ -use crate::economy::{Bought, ItemRegistry, Market}; +use crate::economy::{Bought, Market}; use crate::map::BuildingID; use crate::map_dynamic::{BuildingInfos, Destination, Itinerary, Router}; use crate::souls::desire::{BuyFood, Home, Work}; @@ -15,6 +15,7 @@ use crate::{BuildingKind, Map, ParCommandBuffer, Simulation, SoulID}; use egui_inspect::Inspect; use geom::Transform; use lazy_static::lazy_static; +use prototypes::ItemID; use serde::{Deserialize, Serialize}; #[derive(Inspect, Serialize, Deserialize, Default)] @@ -244,12 +245,8 @@ pub fn spawn_human(sim: &mut Simulation, house: BuildingID) -> Option { let hpos = sim.map().buildings().get(house)?.door_pos; let p = Pedestrian::new(&mut sim.write::()); - let registry = sim.read::(); let time = sim.read::().instant(); - let food = BuyFood::new(time, ®istry); - drop(registry); - let car = spawn_parked_vehicle(sim, VehicleKind::Car, housepos); let personal_info = Box::new(PersonalInfo::new(&mut sim.write::())); @@ -262,7 +259,7 @@ pub fn spawn_human(sim: &mut Simulation, house: BuildingID) -> Option { speed: Speed::default(), decision: HumanDecision::default(), home: Home::new(house), - food, + food: BuyFood::new(time), bought: Bought::default(), router: Router::new(car), collider: None, @@ -272,8 +269,7 @@ pub fn spawn_human(sim: &mut Simulation, house: BuildingID) -> Option { let soul = SoulID::Human(id); let mut m = sim.write::(); - let registry = sim.read::(); - m.buy(soul, housepos.xy(), registry.id("job-opening"), 1); + m.buy(soul, housepos.xy(), ItemID::new("job-opening"), 1); sim.write::().get_in(house, soul); sim.write::().set_owner(house, soul); diff --git a/simulation/src/souls/mod.rs b/simulation/src/souls/mod.rs index d74aa9d4..4be999fc 100644 --- a/simulation/src/souls/mod.rs +++ b/simulation/src/souls/mod.rs @@ -1,7 +1,7 @@ use crate::map::{BuildingID, BuildingKind}; use crate::map_dynamic::BuildingInfos; use crate::souls::freight_station::freight_station_soul; -use crate::souls::goods_company::{company_soul, GoodsCompany, GoodsCompanyRegistry}; +use crate::souls::goods_company::{company_soul, GoodsCompanyState}; use crate::souls::human::spawn_human; use crate::transportation::{spawn_parked_vehicle, VehicleKind}; use crate::Simulation; @@ -62,37 +62,29 @@ pub(crate) fn add_souls_to_empty_buildings(sim: &mut Simulation) { .filter_map(|(kind, v)| kind.as_goods_company().zip(Some(v))) .flat_map(|(bkind, v)| v.iter().map(move |x| (bkind, x))) { - let registry = sim.read::(); - let des = &unwrap_or!(registry.descriptions.get(bkind), continue); + let proto = bkind.prototype(); - let ckind = des.kind; - let mk_trucks = |sim: &mut Simulation| { - let mut trucks = vec![]; - if let CompanyKind::Factory { n_trucks } = ckind { - for _ in 0..n_trucks { - trucks.extend(spawn_parked_vehicle(sim, VehicleKind::Truck, pos)) - } - if trucks.is_empty() { - return None; - } + let ckind = proto.kind; + let mut trucks = vec![]; + if ckind == CompanyKind::Factory { + for _ in 0..proto.n_trucks { + trucks.extend(spawn_parked_vehicle(sim, VehicleKind::Truck, pos)) } - Some(trucks) - }; + if trucks.is_empty() { + continue; + } + } - let comp = GoodsCompany { - kind: des.kind, + let comp = GoodsCompanyState { + kind: proto.id, building: build_id, - recipe: des.recipe.clone(), - max_workers: des.n_workers, + max_workers: proto.n_workers, progress: 0.0, driver: None, - trucks: { - drop(registry); - unwrap_or!(mk_trucks(sim), continue) - }, + trucks, }; - company_soul(sim, comp); + company_soul(sim, comp, proto); n_souls_added += 1; } diff --git a/simulation/src/world.rs b/simulation/src/world.rs index d19060b2..756cbd5f 100644 --- a/simulation/src/world.rs +++ b/simulation/src/world.rs @@ -5,7 +5,7 @@ use crate::map_dynamic::{ }; use crate::souls::desire::{BuyFood, Home, Work}; use crate::souls::freight_station::FreightStation; -use crate::souls::goods_company::GoodsCompany; +use crate::souls::goods_company::GoodsCompanyState; use crate::souls::human::{HumanDecision, PersonalInfo}; use crate::transportation::train::{Locomotive, LocomotiveReservation, RailWagon}; use crate::transportation::{ @@ -166,7 +166,7 @@ impl SimDrop for FreightStationEnt { #[derive(Inspect, Serialize, Deserialize)] pub struct CompanyEnt { pub trans: Transform, - pub comp: GoodsCompany, + pub comp: GoodsCompanyState, pub workers: Workers, pub sold: Sold, pub bought: Bought,