From 4f9932074a8f0390d5cb6072b4e419c7ab08ffed Mon Sep 17 00:00:00 2001 From: troido Date: Thu, 2 Apr 2020 14:13:18 +0200 Subject: added flags component, and conditions for building --- content/encyclopediae/default_encyclopedia.json | 26 ++++++++++---------- src/assemblage.rs | 2 +- src/components/flags.rs | 32 +++++++++++++++++++++++++ src/components/item.rs | 22 ++++++++--------- src/components/mod.rs | 13 ++++------ src/componentwrapper.rs | 32 ++++++++++++++++--------- src/parameter.rs | 7 +++++- src/resources/ground.rs | 14 ++++++++++- src/room.rs | 2 +- src/systems/moving.rs | 23 +++++------------- src/systems/useitem.rs | 23 +++++++++++------- src/world.rs | 2 +- todo.md | 1 + 13 files changed, 126 insertions(+), 73 deletions(-) create mode 100644 src/components/flags.rs diff --git a/content/encyclopediae/default_encyclopedia.json b/content/encyclopediae/default_encyclopedia.json index 28d834d..46bb64e 100644 --- a/content/encyclopediae/default_encyclopedia.json +++ b/content/encyclopediae/default_encyclopedia.json @@ -1,22 +1,22 @@ { "assemblages": { "wall": { - "components": ["Blocking"], + "components": [["Flags", {"flags": ["strings", ["Blocking"]]}]], "sprite": "wall", "height": 2 }, "rock": { - "components": ["Blocking"], + "components": [["Flags", {"flags": ["strings", ["Blocking"]]}]], "sprite": "rock", "height": 10 }, "tree": { - "components": ["Blocking"], + "components": [["Flags", {"flags": ["strings", ["Blocking"]]}]], "sprite": "tree", "height": 3 }, "fence": { - "components": ["Blocking"], + "components": [["Flags", {"flags": ["strings", ["Blocking"]]}]], "sprite": "fence", "height": 1 }, @@ -35,7 +35,7 @@ "height": ["float", 0.1], "name": ["string", "grass"] }], - "Floor" + ["Flags", {"flags": ["strings", ["Floor", "Soil"]]}] ] }, "greengrass": { @@ -49,22 +49,22 @@ "height": ["float", 0.1], "name": ["string", "grass"] }], - "Floor" + ["Flags", {"flags": ["strings", ["Floor", "Soil"]]}] ] }, "ground": { - "components": ["Floor"], + "components": [["Flags", {"flags": ["strings", ["Floor", "Soil"]]}]], "sprite": "ground", "height": 0.1 }, "floor": { - "components": ["Floor"], + "components": [["Flags", {"flags": ["strings", ["Floor"]]}]], "sprite": "floor", "height": 0.1 }, "bridge": { "components": [ - "Floor" + ["Flags", {"flags": ["strings", ["Floor"]]}] ], "sprite": "bridge", "height": 0.1 @@ -86,7 +86,7 @@ "height": 0.3 }, "stone": { - "item": ["build", "builtwall"], + "item": ["build", ["builtwall", ["Floor"], ["Blocking"]]], "sprite": "stone", "height": 0.4 }, @@ -109,13 +109,13 @@ "arguments": [["destination", "string"], ["dest_pos", "string", ""]], "components": [ ["RoomExit", {"destination": ["arg", "destination"], "dest_pos": ["arg", "dest_pos"]}], - "Floor" + ["Flags", {"flags": ["strings", ["Floor"]]}] ] }, "builtwall": { "arguments": [["health", "int", 100]], "components": [ - "Blocking", + ["Flags", {"flags": ["strings", ["Blocking"]]}], ["Health", {"health": ["arg", "health"], "maxhealth": ["int", 100]}], "Mortal" ], @@ -193,7 +193,7 @@ "sprite": "seed", "height": 0.2, "name": "radishseed", - "item": ["build", "plantedradishseed"] + "item": ["build", ["plantedradishseed", ["Floor", "Soil"], ["Occupied"]]] }, "plantedradishseed": { "arguments": [["target_time", "int", 0]], diff --git a/src/assemblage.rs b/src/assemblage.rs index 55c52f4..ffb1420 100644 --- a/src/assemblage.rs +++ b/src/assemblage.rs @@ -188,7 +188,7 @@ impl Assemblage { for (name, param) in compparams { compargs.insert(name.as_str(), param.evaluate(&arguments, template).ok_or(aerr!("argument not found"))?); } - components.push(ComponentWrapper::load_component(*comptype, compargs).ok_or(aerr!("failed to load component"))?); + components.push(ComponentWrapper::load_component(*comptype, compargs)?); } if template.save && self.save { components.push(ComponentWrapper::Serialise(Serialise{template: template.clone(), extract: self.extract.clone() })); diff --git a/src/components/flags.rs b/src/components/flags.rs new file mode 100644 index 0000000..d985bf2 --- /dev/null +++ b/src/components/flags.rs @@ -0,0 +1,32 @@ + +use std::collections::HashSet; +use specs::{ + Component, + VecStorage, +}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum Flag { + Blocking, + Floor, + Occupied, + Soil +} + +use Flag::*; +impl Flag { + pub fn from_str(s: &str) -> Option { + Some(match s { + "Blocking" => Blocking, + "Floor" => Floor, + "Occupied" => Occupied, + "Soil" => Soil, + _ => None? + }) + } +} + + +#[derive(Component, Debug, Clone, PartialEq, Eq)] +#[storage(VecStorage)] +pub struct Flags(pub HashSet); diff --git a/src/components/item.rs b/src/components/item.rs index 3b931bb..9793061 100644 --- a/src/components/item.rs +++ b/src/components/item.rs @@ -1,6 +1,10 @@ +use std::collections::HashSet; use specs::{Component, DenseVecStorage}; -use crate::{Template}; +use crate::{ + Template, + components::Flag +}; use super::equipment::Equippable; @@ -18,7 +22,7 @@ use serde_json::{json, Value}; #[derive(Debug, Clone, PartialEq)] pub enum ItemAction { Eat(i64), - Build(Template), + Build(Template, HashSet, HashSet), Equip(Equippable), None } @@ -26,21 +30,17 @@ pub enum ItemAction { use ItemAction::{Eat, Build, Equip, None}; impl ItemAction { - pub fn to_json(&self) -> Value { - match self { - Eat(health) => json!(["eat", health]), - Build(template) => json!(["build", template.to_json()]), - Equip(equippable) => json!(["equip", equippable.to_json()]), - None => json!(["none", null]) - } - } pub fn from_json(val: &Value) -> Option { let typ = val.get(0)?; let arg = val.get(1)?; Some(match typ.as_str()? { "eat" => Eat(arg.as_i64()?), - "build" => Build(Template::from_json(arg).ok()?), + "build" => Build( + Template::from_json(arg.get(0)?).ok()?, + arg.get(1)?.as_array()?.into_iter().map(|v| Flag::from_str(v.as_str()?)).collect::>>()?, + arg.get(2)?.as_array()?.into_iter().map(|v| Flag::from_str(v.as_str()?)).collect::>>()? + ), "none" => None, "equip" => Equip(Equippable::from_json(arg)?), _ => {return Option::None} diff --git a/src/components/mod.rs b/src/components/mod.rs index 071b5e0..efdf8f7 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -6,6 +6,7 @@ pub mod interactable; pub mod equipment; pub mod inventory; pub mod serialise; +pub mod flags; pub use item::Item; pub use messages::{ @@ -18,6 +19,10 @@ pub use interactable::Interactable; pub use equipment::Equipment; pub use inventory::Inventory; pub use serialise::Serialise; +pub use flags::{ + Flag, + Flags +}; use specs::{ DenseVecStorage, @@ -74,14 +79,6 @@ pub struct Movable { pub cooldown: i64 } -#[derive(Default, Component, Debug, Clone)] -#[storage(NullStorage)] -pub struct Blocking; - -#[derive(Default, Component, Debug, Clone)] -#[storage(NullStorage)] -pub struct Floor; - #[derive(Default, Component, Debug, Clone)] #[storage(NullStorage)] pub struct New; diff --git a/src/componentwrapper.rs b/src/componentwrapper.rs index 42eccff..c4b70a5 100644 --- a/src/componentwrapper.rs +++ b/src/componentwrapper.rs @@ -1,5 +1,5 @@ -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use specs::Builder; use rand::Rng; @@ -10,10 +10,13 @@ use crate::{ playerstate::RoomPos, components::{ AttackType, - Clan + Clan, + Flag }, parameter::{Parameter, ParameterType}, - Timestamp + Timestamp, + Result, + aerr }; @@ -33,16 +36,17 @@ macro_rules! components { )* } } - pub fn load_component(comptype: ComponentType, mut parameters: HashMap<&str, Parameter>) -> Option { + pub fn load_component(comptype: ComponentType, mut parameters: HashMap<&str, Parameter>) -> Result { #[allow(unused_imports, unreachable_code)] match comptype { $( - ComponentType::$comp => Some(Self::$comp({ + ComponentType::$comp => Ok(Self::$comp({ use crate::components::$comp; $( - let $paramname = match parameters.remove(stringify!($paramname))? { + let $paramname = match parameters.remove(stringify!($paramname)) + .ok_or(aerr!(&format!("required parameter '{}'not found", stringify!($paramname))))? { Parameter::$paramtype(p) => p, - _ => {return None} + x => Err(aerr!(&format!("parameter type mismatch for parameter {}: {} {:?}", stringify!($paramname), stringify!($paramtype), x)))? }; )* $creation @@ -137,8 +141,6 @@ components!( } }; Movable (cooldown: Int); - Blocking; - Floor; Player (name: String) {Player::new(PlayerId{name})}; Item (ent: Template, name: String, action: Action); Inventory () {panic!("inventory from parameters not implemented")}; @@ -178,8 +180,8 @@ components!( }; Clan (name: String); Home (home: Pos); - Faction (faction: String) {Faction::from_str(faction.as_str())?}; - Interactable (action: String) {Interactable::from_str(action.as_str())?}; + Faction (faction: String) {Faction::from_str(faction.as_str()).ok_or(aerr!("invalid faction name"))?}; + Interactable (action: String) {Interactable::from_str(action.as_str()).ok_or(aerr!("invalid interactable name"))?}; Loot (loot: LootList); Grow ( into: Template (Grow.into.clone()), @@ -200,6 +202,14 @@ components!( }; Equipment () {panic!("equipment from parameters not implemented")}; CreationTime (time: Int) {CreationTime{time: Timestamp(time)}}; + Flags (flags: Strings) { + Flags( + flags + .iter() + .map(|f| Flag::from_str(f)) + .collect::>>().ok_or(aerr!("invalid flag name"))? + ) + }; ); diff --git a/src/parameter.rs b/src/parameter.rs index 829c5cf..2ac9be1 100644 --- a/src/parameter.rs +++ b/src/parameter.rs @@ -68,13 +68,18 @@ parameters!( Pos (Pos) pos, v (Pos::from_json(v)?) (json!(v)); Float (f64) float, v (v.as_f64()?) (json!(v)); Template (Template) template, v (Template::from_json(v).ok()?) (v.to_json()); - Action (ItemAction) action, v (ItemAction::from_json(v)?) (v.to_json()); + Action (ItemAction) action, v (ItemAction::from_json(v)?) (panic!("item actions can't be serialized")); Bool (bool) bool, v (v.as_bool()?) (json!(v)); LootList (Vec<(Template, f64)>) lootlist, v (v.as_array()?.iter().map(|item| Some((Template::from_json(item.get(0)?).ok()?, item.get(1)?.as_f64()?)) ).collect::>>()?) ({json!(v.iter().map(|(t, c)| (t.to_json(), *c)).collect::>())}); + Strings (Vec) strings, v + (v.as_array()?.iter().map(|item| + Some(item.as_str()?.to_string()) + ).collect::>>()?) + ({json!(v)}); ); diff --git a/src/resources/ground.rs b/src/resources/ground.rs index a5f4c00..8194a44 100644 --- a/src/resources/ground.rs +++ b/src/resources/ground.rs @@ -8,7 +8,7 @@ use specs::{ }; use crate::{ - components::{Visible, Removed}, + components::{Visible, Removed, Flags, Flag}, Pos }; @@ -28,6 +28,15 @@ impl Ground { .collect() } + pub fn all_components_on<'a, C: Component>(&self, pos: Pos, component_type: &'a ReadStorage) -> Vec<&'a C> { + self.cells + .get(&pos) + .unwrap_or(&HashSet::new()) + .iter() + .filter_map(|e| component_type.get(*e)) + .collect() + } + pub fn by_height(&self, pos: &Pos, visibles: &ReadStorage, ignore: &Entity) -> Vec { let mut entities: Vec = self.cells .get(&pos).unwrap_or(&HashSet::new()) @@ -41,4 +50,7 @@ impl Ground { entities } + pub fn flags_on<'a>(&self, pos: Pos, flags: &'a ReadStorage) -> HashSet { + self.all_components_on::(pos, flags).into_iter().fold(HashSet::new(), |a, b| &a | &b.0) + } } diff --git a/src/room.rs b/src/room.rs index 805f94e..6c92e8c 100644 --- a/src/room.rs +++ b/src/room.rs @@ -122,7 +122,7 @@ impl <'a, 'b>Room<'a, 'b> { world.insert(NewEntities::new(encyclopedia)); register_insert!( world, - (Position, Visible, Controller, Movable, Blocking, Floor, New, Removed, Moved, Player, Inventory, Health, Serialise, RoomExit, Entered, Dead, Trap, Fighter, Healing, Volatile, ControlCooldown, Autofight, MonsterAI, Home, Mortal, AttackInbox, Item, Spawner, Clan, Faction, Interactable, Loot, Grow, Equipment, CreationTime), + (Position, Visible, Controller, Movable, /*Blocking, Floor,*/ New, Removed, Moved, Player, Inventory, Health, Serialise, RoomExit, Entered, Dead, Trap, Fighter, Healing, Volatile, ControlCooldown, Autofight, MonsterAI, Home, Mortal, AttackInbox, Item, Spawner, Clan, Faction, Interactable, Loot, Grow, Equipment, CreationTime, Flags), (Ground, Input, Output, Size, Spawn, Players, Emigration, Time) ); diff --git a/src/systems/moving.rs b/src/systems/moving.rs index 5232076..6ecf040 100644 --- a/src/systems/moving.rs +++ b/src/systems/moving.rs @@ -15,9 +15,9 @@ use crate::{ Pos, components::{ Controller, - Blocking, Position, - Floor, + Flags, + Flag, Moved, Entered, Movable, @@ -40,34 +40,23 @@ impl <'a> System<'a> for Move { ReadStorage<'a, Controller>, WriteStorage<'a, Position>, Read<'a, Size>, - ReadStorage<'a, Blocking>, + ReadStorage<'a, Flags>, Write<'a, Ground>, - ReadStorage<'a, Floor>, WriteStorage<'a, Moved>, WriteStorage<'a, Entered>, ReadStorage<'a, Movable>, WriteStorage<'a, ControlCooldown> ); - fn run(&mut self, (entities, controllers, mut positions, size, blocking, mut ground, floor, mut moved, mut entered, movables, mut cooldowns): Self::SystemData) { + fn run(&mut self, (entities, controllers, mut positions, size, flags, mut ground, mut moved, mut entered, movables, mut cooldowns): Self::SystemData) { moved.clear(); entered.clear(); for (ent, controller, mut pos, movable) in (&entities, &controllers, &mut positions.restrict_mut(), &movables).join(){ match &controller.control { Control::Move(direction) => { let newpos = (pos.get_unchecked().pos + direction.to_position()).clamp(Pos::new(0, 0), Pos::new(size.width - 1, size.height - 1)); - let mut blocked = false; - let mut on_floor = false; - for ent in ground.cells.get(&newpos).unwrap_or(&HashSet::new()) { - if blocking.get(*ent).is_some(){ - blocked = true; - break; - } - if floor.get(*ent).is_some(){ - on_floor = true; - } - } - if !blocked && on_floor { + let ground_flags = ground.flags_on(newpos, &flags); + if !ground_flags.contains(&Flag::Blocking) && ground_flags.contains(&Flag::Floor) { let mut pos_mut = pos.get_mut_unchecked(); moved.insert(ent, Moved{from: pos_mut.pos}).expect("can't insert Moved"); ground.cells.get_mut(&pos_mut.pos).unwrap().remove(&ent); diff --git a/src/systems/useitem.rs b/src/systems/useitem.rs index de9ed29..d96f6ef 100644 --- a/src/systems/useitem.rs +++ b/src/systems/useitem.rs @@ -6,7 +6,8 @@ use specs::{ WriteStorage, System, Join, - Write + Write, + Read }; use crate::{ @@ -16,9 +17,10 @@ use crate::{ Inventory, AttackInbox, AttackMessage, - AttackType + AttackType, + Flags }, - resources::{NewEntities}, + resources::{NewEntities, Ground}, components::item::ItemAction::{None, Build, Eat, Equip}, controls::Control, }; @@ -32,18 +34,23 @@ impl <'a> System<'a> for Use { ReadStorage<'a, Position>, WriteStorage<'a, Inventory>, Write<'a, NewEntities>, - WriteStorage<'a, AttackInbox> + WriteStorage<'a, AttackInbox>, + Read<'a, Ground>, + ReadStorage<'a, Flags> ); - fn run(&mut self, (entities, controllers, positions, mut inventories, mut new, mut attacked): Self::SystemData) { + fn run(&mut self, (entities, controllers, positions, mut inventories, mut new, mut attacked, ground, flags): Self::SystemData) { for (ent, controller, position, inventory) in (&entities, &controllers, &positions, &mut inventories).join(){ match &controller.control { Control::Use(rank) => { if let Some(entry) = inventory.items.get_mut(*rank) { match &entry.0.action { - Build(template) => { - new.create(position.pos, template.clone()).unwrap(); - inventory.items.remove(*rank); + Build(template, required_flags, blocking_flags) => { + let ground_flags = ground.flags_on(position.pos, &flags); + if required_flags.is_subset(&ground_flags) && blocking_flags.is_disjoint(&ground_flags){ + new.create(position.pos, template.clone()).unwrap(); + inventory.items.remove(*rank); + } } Eat(health_diff) => { AttackInbox::add_message(&mut attacked, ent, AttackMessage{typ: AttackType::Heal(*health_diff), attacker: Option::None}); diff --git a/src/world.rs b/src/world.rs index 50a3a4b..cdcbff1 100644 --- a/src/world.rs +++ b/src/world.rs @@ -180,7 +180,7 @@ impl <'a, 'b>World<'a, 'b> { } else { let age = *self.room_age.get(&roomid).unwrap_or(&0) + 1; self.room_age.insert(roomid.clone(), age); - if age > 2 { + if age > 10 { to_remove.push(roomid.clone()); } } diff --git a/todo.md b/todo.md index ec698be..ee36f49 100644 --- a/todo.md +++ b/todo.md @@ -2,6 +2,7 @@ # TODO - make readme +- more tests - timer resource? - log world events to player - draw new entities -- cgit