From 48c24ec8b011d081550dc78329cbe61de67b30e9 Mon Sep 17 00:00:00 2001 From: troido Date: Sun, 5 Apr 2020 20:04:33 +0200 Subject: items are now mostly replaced by itemids, with a mapping to the item in the encyclopedia --- content/encyclopediae/default_encyclopedia.json | 65 ++++---------------- src/assemblage.rs | 10 +-- src/components/inventory.rs | 20 ++++-- src/components/item.rs | 70 --------------------- src/components/mod.rs | 10 ++- src/componentwrapper.rs | 3 +- src/encyclopedia.rs | 82 ++++++++++++++++++++++--- src/item.rs | 74 ++++++++++++++++++++++ src/main.rs | 2 + src/parameter.rs | 2 - src/playerstate.rs | 55 ++++++++++------- src/room.rs | 2 +- src/systems/take.rs | 11 +++- src/systems/useitem.rs | 10 +-- src/systems/view.rs | 2 +- src/template.rs | 17 +++-- 16 files changed, 247 insertions(+), 188 deletions(-) delete mode 100644 src/components/item.rs create mode 100644 src/item.rs diff --git a/content/encyclopediae/default_encyclopedia.json b/content/encyclopediae/default_encyclopedia.json index a7196d8..1c48a6b 100644 --- a/content/encyclopediae/default_encyclopedia.json +++ b/content/encyclopediae/default_encyclopedia.json @@ -72,37 +72,6 @@ "sprite": "water", "height": 0.1 }, - "pebble": { - "components": [ - ["Item", { - "ent": ["self", null], - "name": ["string", "pebble"], - "action": ["action", ["none", null]] - }] - ], - "sprite": "pebble", - "height": 0.3 - }, - "stone": { - "item": ["build", ["builtwall", ["Floor"], ["Blocking"]]], - "sprite": "stone", - "height": 0.4 - }, - "player": { - "arguments": [["name", "string"]], - "components": [ - ["Visible", { - "sprite": ["string", "player"], - "height": ["float", 1.0], - "name": ["arg", "name"] - }], - ["Player", { - "name": ["arg", "name"] - }], - ["Inventory", {"capacity": ["int", 3]}], - ["Health", {"health": ["int", 9], "maxhealth": ["int", 10]}] - ] - }, "portal": { "arguments": [["destination", "string"], ["dest_pos", "string", ""]], "components": [ @@ -187,18 +156,12 @@ ["Loot", {"loot": ["list", [ ["list", [{"type": "radishseed"}, 0.92]], ["list", [{"type": "radishseed"}, 0.20]], - ["list", [{"type": "radishes"}, 0.8]], - ["list", [{"type": "radishes"}, 0.4]] + ["list", [{"type": "radish"}, 0.8]], + ["list", [{"type": "radish"}, 0.4]] ]]}] ], "flags": ["Occupied"] }, - "radishseed": { - "sprite": "seed", - "height": 0.2, - "name": "radishseed", - "item": ["build", ["plantedradishseed", ["Floor", "Soil"], ["Occupied", "Blocking"]]] - }, "plantedradishseed": { "arguments": [["target_time", "int", 0]], "sprite": "seed", @@ -233,20 +196,6 @@ }, "flags": ["Occupied"] }, - "radishes": { - "sprite": "food", - "height": 0.3, - "name": "radishes", - "item": ["eat", 3] - }, - "sword": { - "sprite": "sword", - "height": 0.5, - "item": ["equip", { - "slot": "hand", - "stats": {"strength": 50} - }] - }, "closeddoor": { "sprite": "closeddoor", "height": 2, @@ -279,5 +228,15 @@ ["Interactable", {"action": ["interaction", ["reply", "did you say '{}'?"]]}] ] } + }, + "items": { + "pebble": {}, + "stone": {"action": ["build", ["builtwall", ["Floor"], ["Blocking"]]]}, + "radishseed": {"action": ["build", ["plantedradishseed", ["Floor", "Soil"], ["Occupied", "Blocking"]]]}, + "radish": {"action": ["eat", 3]}, + "sword": {"action": ["equip", { + "slot": "hand", + "stats": {"strength": 50} + }]} } } diff --git a/src/assemblage.rs b/src/assemblage.rs index 6856151..aa3154f 100644 --- a/src/assemblage.rs +++ b/src/assemblage.rs @@ -98,15 +98,9 @@ impl Assemblage { }])); } // item component is common too - if let Some(action) = val.get("item") { + if let Some(item) = val.get("item") { components.push(json!(["Item", { - "ent": ["self", null], - "name": if let Some(n) = name { - json!(["string", n]) - } else { - json!(["name", Value::Null]) - }, - "action": ["action", action] + "item": ["string", item] }])); } // and so is flags diff --git a/src/components/inventory.rs b/src/components/inventory.rs index 81064d8..c3282e9 100644 --- a/src/components/inventory.rs +++ b/src/components/inventory.rs @@ -1,14 +1,22 @@ use std::collections::HashMap; use specs::{Component, FlaggedStorage, HashMapStorage}; -use super::{ +use crate::{ + ItemId, item::{Item, ItemAction}, - equipment::{Stat, Equippable}, + components::equipment::{Stat, Equippable}, }; +#[derive(Debug, Clone)] +pub struct InventoryEntry { + pub itemid: ItemId, + pub item: Item, + pub is_equipped: bool +} + #[derive(Debug, Clone, Default)] pub struct Inventory { - pub items: Vec<(Item, bool)>, + pub items: Vec, pub capacity: usize } impl Component for Inventory { @@ -19,9 +27,9 @@ impl Inventory { fn equipped(&self) -> Vec { let mut equippables = Vec::new(); - for (item, is_equipped) in self.items.iter() { - if *is_equipped { - if let ItemAction::Equip(equippable) = &item.action { + for entry in self.items.iter() { + if entry.is_equipped { + if let ItemAction::Equip(equippable) = &entry.item.action { equippables.push(equippable.clone()); } else { panic!("unequippable item equipped!"); diff --git a/src/components/item.rs b/src/components/item.rs deleted file mode 100644 index 0cb2589..0000000 --- a/src/components/item.rs +++ /dev/null @@ -1,70 +0,0 @@ - -use std::collections::HashSet; -use serde_json::{Value}; -use specs::{Component, DenseVecStorage}; -use crate::{ - Template, - components::Flag -}; - -use super::equipment::Equippable; - -#[derive(Component, Debug, Clone)] -pub struct Item { - pub ent: Template, - pub name: String, - pub action: ItemAction -} - - - - -#[derive(Debug, Clone, PartialEq)] -pub enum ItemAction { - Eat(i64), - Build(Template, HashSet, HashSet), - Equip(Equippable), - None -} - -use ItemAction::{Eat, Build, Equip, None}; - -impl ItemAction { - - 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.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} - }) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::hashmap; - use super::super::equipment::*; - use serde_json::json; - - #[test] - fn equip_from_json() { - assert_eq!( - ItemAction::from_json(&json!(["equip", {"slot": "hand", "stats": {"strength": 10}}])), - Some(ItemAction::Equip(Equippable {slot: Slot::Hand, stats: hashmap!(Stat::Strength => 10)})) - ); - assert_eq!( - ItemAction::from_json(&json!(["equip", {"slot": "hand", "stats": {"attack": 50}}])), - Option::None - ); - } -} - diff --git a/src/components/mod.rs b/src/components/mod.rs index 1a590b2..4065cc2 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -1,5 +1,4 @@ -pub mod item; pub mod messages; pub mod faction; pub mod interactable; @@ -9,7 +8,6 @@ pub mod serialise; pub mod flags; pub mod ear; -pub use item::Item; pub use messages::{ AttackMessage, AttackInbox, @@ -46,7 +44,8 @@ use crate::{ controls::Control, Template, playerstate::RoomPos, - Timestamp + Timestamp, + ItemId, }; #[derive(Component, Debug, Clone)] @@ -229,3 +228,8 @@ pub struct CreationTime { } +#[derive(Component, Debug, Clone)] +pub struct Item(pub ItemId); + + + diff --git a/src/componentwrapper.rs b/src/componentwrapper.rs index 360a546..d00b592 100644 --- a/src/componentwrapper.rs +++ b/src/componentwrapper.rs @@ -6,6 +6,7 @@ use rand::Rng; use crate::{ PlayerId, RoomId, + ItemId, Sprite, playerstate::RoomPos, components::{ @@ -143,7 +144,7 @@ components!( }; Movable (cooldown: Int); Player (name: String) {Player::new(PlayerId{name})}; - Item (ent: Template, name: String, action: Action); + Item (item: String) {Item(ItemId(item))}; Inventory () {panic!("inventory from parameters not implemented")}; Health (health: Int, maxhealth: Int); Serialise () {panic!("serialise from parameters not implemented")}; diff --git a/src/encyclopedia.rs b/src/encyclopedia.rs index 4e09f22..fe9a9d3 100644 --- a/src/encyclopedia.rs +++ b/src/encyclopedia.rs @@ -1,34 +1,98 @@ use std::collections::HashMap; -use serde_json::Value; +use serde_json::{Value, json}; use crate::{ assemblage::Assemblage, componentwrapper::PreEntity, Template, template::EntityType, Result, - aerr + aerr, + ItemId, + item::Item, + item::ItemAction }; #[derive(Default, Clone)] pub struct Encyclopedia { - items: HashMap + assemblages: HashMap, + items: HashMap } impl Encyclopedia { pub fn from_json(val: Value) -> Result { - let mut items = HashMap::new(); - for (k, v) in val.get("assemblages").ok_or(aerr!("no assemblages in encyclopedia json"))?.as_object().ok_or(aerr!("encyclopedia not a json object"))?.into_iter() { - items.insert(EntityType(k.clone()), Assemblage::from_json(v)?); - } - Ok(Encyclopedia{items}) + let mut assemblages = + val + .get("assemblages") + .ok_or(aerr!("no assemblages in encyclopedia json"))? + .as_object() + .ok_or(aerr!("encyclopedia assemblages not a json object"))? + .into_iter() + .map(|(k, v)| Ok((EntityType(k.clone()), Assemblage::from_json(v)?))) + .collect::>>()?; + let items = + val + .get("items") + .ok_or(aerr!("no items in encyclopedia json"))? + .as_object() + .ok_or(aerr!("encyclopedia items not a json object"))? + .into_iter() + .map(|(k, v)| { + let id = ItemId(k.clone()); + let sprite = + if let Some(sprite) = v.get("sprite") { + sprite.as_str().ok_or(aerr!("item sprite not a string: {:?}", v))? + } else { + k + }; + let name = + if let Some(name) = v.get("name") { + name.as_str().ok_or(aerr!("item name not a string: {:?}", v))?.to_string() + } else { + k.to_string() + }; + let item = Item { + name: name.clone(), + ent: + if let Some(ent) = v.get("entity") { + Template::from_json(ent)? + } else { + let enttyp = EntityType(k.clone()); + assemblages.insert(enttyp.clone(), Assemblage::from_json(&json!({ + "height": 0.3, + "sprite": sprite, + "name": name, + "item": k + }))?); + Template::from_entity_type(enttyp) + }, + action: + if let Some(action) = v.get("action") { + ItemAction::from_json(action).ok_or(aerr!("failed to parse ItemAction: {:?}", v))? + } else { + ItemAction::None + } + }; + Ok((id, item)) + }) + .collect::>>()?; + + Ok(Encyclopedia{ + assemblages, + items + }) } pub fn construct(&self, template: &Template) -> Result { - let assemblage = self.items.get(&template.name).ok_or(aerr!("unknown assemblage name: '{:?}' in {:?}", template.name, template))?; + let assemblage = self.assemblages + .get(&template.name) + .ok_or(aerr!("unknown assemblage name: '{:?}' in {:?}", template.name, template))?; assemblage.instantiate(template) } + pub fn get_item(&self, id: &ItemId) -> Option { + self.items.get(id).map(|item| item.clone()) + } } diff --git a/src/item.rs b/src/item.rs new file mode 100644 index 0000000..d5ffaa1 --- /dev/null +++ b/src/item.rs @@ -0,0 +1,74 @@ + + +use std::collections::HashSet; +use serde_json::{Value}; +use specs::{Component, DenseVecStorage}; +use crate::{ + Template, + components::{ + Flag, + equipment::Equippable + } +}; + + + +#[derive(Debug, Default, PartialEq, Eq, Clone, Hash)] +pub struct ItemId(pub String); + +#[derive(Debug, Clone)] +pub struct Item { + pub ent: Template, + pub name: String, + pub action: ItemAction +} + +#[derive(Debug, Clone, PartialEq)] +pub enum ItemAction { + Eat(i64), + Build(Template, HashSet, HashSet), + Equip(Equippable), + None +} + +use ItemAction::{Eat, Build, Equip, None}; + +impl ItemAction { + + 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.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} + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::hashmap; + use crate::components::equipment::*; + use serde_json::json; + + #[test] + fn equip_from_json() { + assert_eq!( + ItemAction::from_json(&json!(["equip", {"slot": "hand", "stats": {"strength": 10}}])), + Some(ItemAction::Equip(Equippable {slot: Slot::Hand, stats: hashmap!(Stat::Strength => 10)})) + ); + assert_eq!( + ItemAction::from_json(&json!(["equip", {"slot": "hand", "stats": {"attack": 50}}])), + Option::None + ); + } +} + diff --git a/src/main.rs b/src/main.rs index 2f42ac3..34819e3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -35,11 +35,13 @@ mod sprite; mod timestamp; mod purgatory; mod config; +mod item; use self::{ pos::Pos, playerid::PlayerId, roomid::RoomId, + item::ItemId, util::Result, sprite::Sprite, template::Template, diff --git a/src/parameter.rs b/src/parameter.rs index ed445b3..a7419ed 100644 --- a/src/parameter.rs +++ b/src/parameter.rs @@ -2,7 +2,6 @@ use serde_json::{Value, json}; use crate::{ Template, - components::item::ItemAction, components::interactable::Interactable, Pos }; @@ -69,7 +68,6 @@ 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)?) (panic!("item actions can't be serialized")); Interaction (Interactable) interaction, _v (Interactable::from_json(_v)?) (panic!("interactions can't be serialized")); Bool (bool) bool, v (v.as_bool()?) (json!(v)); List (Vec) list, _v diff --git a/src/playerstate.rs b/src/playerstate.rs index f05332b..852c04f 100644 --- a/src/playerstate.rs +++ b/src/playerstate.rs @@ -6,10 +6,12 @@ use crate::{ componentwrapper::{ComponentWrapper, PreEntity}, PlayerId, RoomId, + ItemId, components::{ Visible, Player, Inventory, + inventory::InventoryEntry, Health, Fighter, Healing, @@ -43,7 +45,7 @@ pub struct PlayerState { pub room: Option, pub pos: RoomPos, pub inventory_capacity: usize, - pub inventory: Vec<(Template, bool)>, + pub inventory: Vec<(ItemId, bool)>, pub health: i64, pub maximum_health: i64, pub equipment: HashMap> @@ -64,7 +66,7 @@ impl PlayerState { } } - pub fn create(id: PlayerId, room: RoomId, inventory: Vec<(Template, bool)>, inventory_capacity: usize, health: i64, maximum_health: i64, equipment: HashMap>) -> Self { + pub fn create(id: PlayerId, room: RoomId, inventory: Vec<(ItemId, bool)>, inventory_capacity: usize, health: i64, maximum_health: i64, equipment: HashMap>) -> Self { Self { id, room: Some(room), @@ -86,7 +88,7 @@ impl PlayerState { }, "inventory": { "capacity": self.inventory_capacity, - "items": self.inventory.iter().map(|(item, e)| (Template::to_json(item), *e)).collect::>() + "items": self.inventory.iter().map(|(item, e)| (json!(item.0), *e)).collect::>() }, "equipment": { "hand": null, @@ -99,17 +101,31 @@ impl PlayerState { pub fn from_json(val: &Value) -> Result { let inventory = val.get("inventory").ok_or(aerr!("player json does not have inventory"))?; - let mut items = vec![]; - for item in inventory.get("items").ok_or(aerr!("inventory does not have items"))?.as_array().ok_or(aerr!("inventory items not an array"))? { - if item.is_array() { - items.push(( - Template::from_json(item.get(0).ok_or(aerr!("invalid item"))?)?, - item.get(1).ok_or(aerr!("invalid item"))?.as_bool().ok_or(aerr!("invalid item"))? - )); - } else { - items.push((Template::from_json(item)?, false)); - } - } + let items = + inventory + .get("items") + .ok_or(aerr!("inventory does not have items"))? + .as_array() + .ok_or(aerr!("inventory items not an array"))? + .into_iter() + .map(|entry| { + let itemid = ItemId( + entry + .get(0) + .ok_or(aerr!("item does not have name"))? + .as_str() + .ok_or(aerr!("item name not a string"))? + .to_string() + ); + let is_equipped = + entry + .get(1) + .ok_or(aerr!("item does not have equipped flag"))? + .as_bool() + .ok_or(aerr!("item is_equipped not a bool"))?; + Ok((itemid, is_equipped)) + }) + .collect::>>()?; Ok(Self { id: PlayerId{name: val.get("name").ok_or(aerr!("player json does not have name"))?.as_str().ok_or(aerr!("player name not a string"))?.to_string()}, room: match val.get("roomname").ok_or(aerr!("player json does not have room name"))? { @@ -136,14 +152,9 @@ impl PlayerState { ComponentWrapper::Visible(Visible{sprite: Sprite{name: "player".to_string()}, height: 1.2, name: self.id.name.clone()}), ComponentWrapper::Player(Player::new(self.id.clone())), ComponentWrapper::Inventory(Inventory{ - items: self.inventory.iter().map( |(template, equippable)| { - let item_ent = encyclopedia.construct(template).unwrap(); - for component in item_ent { - if let ComponentWrapper::Item(item) = component { - return (item, *equippable); - } - } - panic!("Item in inventory does not have item component") + items: self.inventory.iter().map( |(itemid, is_equipped)| { + let item = encyclopedia.get_item(itemid).unwrap(); + InventoryEntry{itemid: itemid.clone(), item, is_equipped: *is_equipped} }).collect(), capacity: self.inventory_capacity }), diff --git a/src/room.rs b/src/room.rs index 2b4fb7e..46948cb 100644 --- a/src/room.rs +++ b/src/room.rs @@ -250,7 +250,7 @@ impl <'a, 'b>Room<'a, 'b> { Some(PlayerState::create( player.id.clone(), self.id.clone(), - inventory.items.iter().map(|(item, e)| (item.ent.clone(), *e)).collect(), + inventory.items.iter().map(|entry| (entry.itemid.clone(), entry.is_equipped)).collect(), inventory.capacity, health.health, health.maxhealth, diff --git a/src/systems/take.rs b/src/systems/take.rs index 9eb76e7..5a86269 100644 --- a/src/systems/take.rs +++ b/src/systems/take.rs @@ -14,6 +14,7 @@ use crate::components::{ Position, Removed, Inventory, + inventory::InventoryEntry, Item, Visible }; @@ -50,7 +51,11 @@ impl <'a> System<'a> for Take { } for ent in ents { if let Some(item) = items.get(ent) { - inventory.items.insert(0, (item.clone(), false)); + inventory.items.insert(0, InventoryEntry{ + itemid: item.0.clone(), + item: new.encyclopedia.get_item(&item.0).unwrap(), + is_equipped: false + }); if let Err(msg) = removed.insert(ent, Removed) { println!("{:?}", msg); } @@ -62,8 +67,8 @@ impl <'a> System<'a> for Take { if *rank >= inventory.items.len() { return } - let (item, _is_equipped) = inventory.items.remove(*rank); - let _ = new.create(position.pos, &item.ent); + let entry = inventory.items.remove(*rank); + let _ = new.create(position.pos, &entry.item.ent); } _ => {} } diff --git a/src/systems/useitem.rs b/src/systems/useitem.rs index 8763843..605f083 100644 --- a/src/systems/useitem.rs +++ b/src/systems/useitem.rs @@ -21,7 +21,7 @@ use crate::{ Flags }, resources::{NewEntities, Ground}, - components::item::ItemAction::{None, Build, Eat, Equip}, + item::ItemAction::{None, Build, Eat, Equip}, controls::Control, }; @@ -44,7 +44,7 @@ impl <'a> System<'a> for Use { match &controller.control { Control::Use(rank) => { if let Some(entry) = inventory.items.get_mut(*rank) { - match &entry.0.action { + match &entry.item.action { 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){ @@ -59,13 +59,13 @@ impl <'a> System<'a> for Use { Equip(equippable) => { let slot = equippable.slot; for otherentry in inventory.items.iter_mut() { - if let Equip(other) = &otherentry.0.action { + if let Equip(other) = &otherentry.item.action { if other.slot == slot { - otherentry.1 = false; + otherentry.is_equipped = false; } } } - inventory.items[*rank].1 = true; + inventory.items[*rank].is_equipped = true; } None => {} } diff --git a/src/systems/view.rs b/src/systems/view.rs index 4419203..f1641bd 100644 --- a/src/systems/view.rs +++ b/src/systems/view.rs @@ -59,7 +59,7 @@ impl <'a> System<'a> for View { updates.change = Some(changes.clone()); } if let Some(inventory) = inventories.get(ent){ - updates.inventory = Some(inventory.items.iter().map(|(item, _equipped)| item.name.clone()).collect()); + updates.inventory = Some(inventory.items.iter().map(|entry| entry.item.name.clone()).collect()); } if let Some(health) = healths.get(ent){ updates.health = Some((health.health, health.maxhealth)); diff --git a/src/template.rs b/src/template.rs index 496d08e..0f62754 100644 --- a/src/template.rs +++ b/src/template.rs @@ -31,15 +31,24 @@ impl Template { } } + pub fn empty(name: &str) -> Self { + Self::new(name, HashMap::new()) + } + + pub fn from_entity_type(typ: EntityType) -> Self { + Self { + name: typ, + args: Vec::new(), + kwargs: HashMap::new(), + save: true + } + } + pub fn unsaved(mut self) -> Self { self.save = false; self } - pub fn empty(name: &str) -> Self { - Self::new(name, HashMap::new()) - } - pub fn from_json(val: &Value) -> Result