diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/components/equipment.rs | 52 | ||||
| -rw-r--r-- | src/components/interactable.rs | 5 | ||||
| -rw-r--r-- | src/components/messages.rs | 18 | ||||
| -rw-r--r-- | src/componentwrapper.rs | 6 | ||||
| -rw-r--r-- | src/item.rs | 2 | ||||
| -rw-r--r-- | src/persistence.rs | 5 | ||||
| -rw-r--r-- | src/playerstate.rs | 164 | ||||
| -rw-r--r-- | src/room.rs | 2 |
8 files changed, 101 insertions, 153 deletions
diff --git a/src/components/equipment.rs b/src/components/equipment.rs index cbd482c..929a035 100644 --- a/src/components/equipment.rs +++ b/src/components/equipment.rs @@ -5,50 +5,31 @@ use specs::{ Component, HashMapStorage }; +use strum_macros::{EnumString, Display}; use crate::{ Sprite }; -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] -#[serde(rename_all = "lowercase")] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, EnumString, Display)] +#[serde(rename_all = "lowercase")] +#[strum(serialize_all = "snake_case")] pub enum Slot { Hand, Body, Back } -impl Slot { - pub fn from_str(txt: &str) -> Option<Self> { - match txt { - "hand" => Some(Self::Hand), - "body" => Some(Self::Body), - "back" => Some(Self::Back), - _ => None - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] -#[serde(rename_all = "lowercase")] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, EnumString, Display)] +#[serde(rename_all = "lowercase")] +#[strum(serialize_all = "snake_case")] pub enum Stat { Strength, Defence, Mining } -impl Stat { - pub fn from_str(txt: &str) -> Option<Self> { - match txt { - "strength" => Some(Self::Strength), - "defence" => Some(Self::Defence), - "mining" => Some(Self::Mining), - _ => None - } - } -} - #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct Equippable { @@ -70,23 +51,24 @@ mod tests { use super::*; use crate::hashmap; use serde_json::json; + use std::str::FromStr; #[test] fn slots() { - assert_eq!(Slot::from_str("hand"), Some(Slot::Hand)); - assert_eq!(Slot::from_str("body"), Some(Slot::Body)); - assert_eq!(Slot::from_str("hands"), None); - assert_eq!(Slot::from_str("head"), None); + assert_eq!(Slot::from_str("hand"), Ok(Slot::Hand)); + assert_eq!(Slot::from_str("body"), Ok(Slot::Body)); + assert!(Slot::from_str("hands").is_err()); + assert!(Slot::from_str("head").is_err()); } #[test] fn stats() { - assert_eq!(Stat::from_str("strength"), Some(Stat::Strength)); - assert_eq!(Stat::from_str("defence"), Some(Stat::Defence)); - assert_eq!(Stat::from_str("hand"), None); - assert_eq!(Stat::from_str("body"), None); - assert_eq!(Stat::from_str("attack"), None); + assert_eq!(Stat::from_str("strength"), Ok(Stat::Strength)); + assert_eq!(Stat::from_str("defence"), Ok(Stat::Defence)); + assert!(Stat::from_str("hand").is_err()); + assert!(Stat::from_str("body").is_err()); + assert!(Stat::from_str("attack").is_err()); } #[test] diff --git a/src/components/interactable.rs b/src/components/interactable.rs index 651437c..8141416 100644 --- a/src/components/interactable.rs +++ b/src/components/interactable.rs @@ -1,5 +1,6 @@ use std::collections::HashMap; +use std::str::FromStr; use specs::{ Component, HashMapStorage, @@ -30,9 +31,9 @@ impl Interactable { pub fn parse_from_parameter(typ: &str, arg: &Parameter) -> Option<Self> { Some(match (typ, arg) { - ("trigger", Parameter::String(s)) => Trigger(Trigger::from_str(s)?), + ("trigger", Parameter::String(s)) => Trigger(Trigger::from_str(s).ok()?), ("visit", Parameter::String(s)) => Visit(RoomId(s.clone())), - ("mine", Parameter::String(s)) => Mine(Stat::from_str(s)?), + ("mine", Parameter::String(s)) => Mine(Stat::from_str(s).ok()?), ("say", Parameter::String(s)) => Say(s.clone()), ("reply", Parameter::String(s)) => Reply(s.clone()), ("exchange", p) => { diff --git a/src/components/messages.rs b/src/components/messages.rs index 5e27cc6..f815ab5 100644 --- a/src/components/messages.rs +++ b/src/components/messages.rs @@ -1,6 +1,7 @@ use std::collections::HashMap; use std::any::Any; +use strum_macros::{EnumString, Display}; use specs::{ Component, DenseVecStorage, @@ -84,7 +85,8 @@ pub type AttackInbox = Inbox<AttackMessage>; -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, EnumString, Display)] +#[strum(serialize_all="snake_case")] pub enum Trigger { // basic triggers Loot, @@ -96,20 +98,6 @@ pub enum Trigger { Change // Remove + Build } -impl Trigger { - pub fn from_str(txt: &str) -> Option<Self> { - Some(match txt { - "loot" => Self::Loot, - "remove" => Self::Remove, - "build" => Self::Build, - "spawn" => Self::Spawn, - "die" => Self::Die, - "change" => Self::Change, - _ => {return None} - }) - } -} - impl Message for Trigger {} pub type TriggerBox = Inbox<Trigger>; diff --git a/src/componentwrapper.rs b/src/componentwrapper.rs index 96fcba2..320ae1b 100644 --- a/src/componentwrapper.rs +++ b/src/componentwrapper.rs @@ -202,7 +202,7 @@ components!(all: }) ) Timer { - trigger: Trigger::from_str(&trigger).ok_or(aerr!("invalid trigger name {}", trigger))?, + trigger: Trigger::from_str(&trigger).map_err(|_|aerr!("invalid trigger name {}", trigger))?, delay, spread, target_time: if target_time == -1 { None } else { Some(Timestamp(target_time)) } @@ -224,7 +224,7 @@ components!(all: Dedup (id: String, priority: i64); Minable (trigger: String, total: i64) { Minable { - trigger: Trigger::from_str(&trigger).ok_or(aerr!("invalid trigger name {}", trigger))?, + trigger: Trigger::from_str(&trigger).map_err(|_|aerr!("invalid trigger name {}", trigger))?, progress: 0, total } @@ -233,7 +233,7 @@ components!(all: LootHolder () {panic!("LootHolder from parameters not implemented")}; OnSpawn (trigger: String) { OnSpawn { - trigger: Trigger::from_str(&trigger).ok_or(aerr!("invalid trigger name {}", trigger))? + trigger: Trigger::from_str(&trigger).map_err(|_|aerr!("invalid trigger name {}", trigger))? } }; Substitute (into: Template); diff --git a/src/item.rs b/src/item.rs index 2e14606..37ee4f2 100644 --- a/src/item.rs +++ b/src/item.rs @@ -15,7 +15,7 @@ use crate::{ -#[derive(Debug, Default, PartialEq, Eq, Clone, Hash, Deserialize)] +#[derive(Debug, Default, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)] pub struct ItemId(pub String); impl FromStr for ItemId { diff --git a/src/persistence.rs b/src/persistence.rs index 932840a..446d93a 100644 --- a/src/persistence.rs +++ b/src/persistence.rs @@ -97,8 +97,7 @@ impl PersistentStorage for FileStorage { LoaderError::InvalidResource(Box::new(err)) } })?; - let json: Value = inv!(serde_json::from_str(&text))?; - let state = inv!(PlayerState::from_json(&json))?; + let state = inv!(serde_json::from_str(&text))?; Ok(state) } @@ -139,7 +138,7 @@ impl PersistentStorage for FileStorage { fs::create_dir_all(&path)?; let fname = id.to_string() + ".save.json"; path.push(fname); - let text = state.to_json().to_string(); + let text = serde_json::to_string(&state).unwrap(); write_file_safe(path, text)?; Ok(()) } diff --git a/src/playerstate.rs b/src/playerstate.rs index f159567..658aeb2 100644 --- a/src/playerstate.rs +++ b/src/playerstate.rs @@ -1,5 +1,6 @@ -use serde_json::{Value, json}; +use std::collections::HashMap; +use serde::{Serialize, Deserialize, Serializer, Deserializer}; use crate::{ componentwrapper::{ComponentWrapper, PreEntity}, PlayerId, @@ -26,10 +27,13 @@ use crate::{ Sprite, Encyclopedia, Pos, - PResult, - perr }; +#[allow(non_upper_case_globals)] +const maximum_health: i64 = 50; +#[allow(non_upper_case_globals)] +const inventory_capacity: usize = 20; + #[derive(Debug, Clone)] #[allow(dead_code)] pub enum RoomPos { @@ -43,10 +47,8 @@ pub struct PlayerState { pub id: PlayerId, pub room: Option<RoomId>, pub pos: RoomPos, - pub inventory_capacity: usize, pub inventory: Vec<(ItemId, bool)>, - pub health: i64, - pub maximum_health: i64 + pub health: i64 } impl PlayerState { @@ -57,105 +59,24 @@ impl PlayerState { room: None, pos: RoomPos::Unknown, inventory: Vec::new(), - inventory_capacity: 10, - health: 25, - maximum_health: 50 + health: maximum_health/2, } } - pub fn create(id: PlayerId, room: RoomId, inventory: Vec<(ItemId, bool)>, inventory_capacity: usize, health: i64, maximum_health: i64) -> Self { + pub fn create(id: PlayerId, room: RoomId, inventory: Vec<(ItemId, bool)>, health: i64) -> Self { Self { id, room: Some(room), pos: RoomPos::Unknown, inventory, health, - inventory_capacity, - maximum_health } } - - pub fn to_json(&self) -> Value { - json!({ - "name": self.id, - "roomname": match &self.room { - Some(id) => json!(id.to_string()), - None => json!(null) - }, - "inventory": { - "items": self.inventory.iter().map(|(item, e)| (json!(item.0), *e)).collect::<Vec<(Value, bool)>>() - }, - "health": self.health - }) - } - - pub fn from_json(val: &Value) -> PResult<Self> { - let inventory = val.get("inventory").ok_or(perr!("player json does not have inventory"))?; - let mut items = - inventory - .get("items") - .ok_or(perr!("inventory does not have items"))? - .as_array() - .ok_or(perr!("inventory items not an array"))? - .iter() - .map(|entry| { - if entry.is_array() { - let itemid = ItemId( - entry - .get(0) - .ok_or(perr!("item does not have name"))? - .as_str() - .ok_or(perr!("item name not a string"))? - .to_string() - ); - let is_equipped = - entry - .get(1) - .ok_or(perr!("item does not have equipped flag"))? - .as_bool() - .ok_or(perr!("item is_equipped not a bool"))?; - Ok((itemid, is_equipped)) - } else if entry.is_string() { - Ok((ItemId(entry.as_str().unwrap().to_string()), false)) - } else { - Err(perr!("item entry must be a string or array, not {:?}", entry)) - } - }) - .collect::<PResult<Vec<(ItemId, bool)>>>()?; - if let Some(equipment) = val.get("equipment") { - for (slot, item) in equipment.as_object().ok_or(perr!("equipment not a json object: {:?}", equipment))?.iter() { - if item.is_null(){ - continue - } - let itemid = ItemId( - item - .as_str() - .ok_or(perr!("equipment item not a string: {:?}", item))? - .to_string() - ); - // validate the slot, but don't do anything with it - Slot::from_str(slot).ok_or(perr!("invalid slot: {:?}", slot))?; - items.push((itemid, true)) - } - } - Ok(Self { - id: PlayerId(val.get("name").ok_or(perr!("player json does not have name"))?.as_str().ok_or(perr!("player name not a string"))?.to_string()), - room: match val.get("roomname").ok_or(perr!("player json does not have room name"))? { - Value::String(name) => Some(RoomId(name.clone())), - _ => None - }, - pos: RoomPos::Unknown, - inventory: items, - health: val.get("health").ok_or(perr!("player json does not have health"))?.as_i64().ok_or(perr!("player health not a number"))?, - inventory_capacity: 12, - maximum_health: 50, - }) - } pub fn respawn(&mut self) { self.room = None; self.pos = RoomPos::Unknown; - self.health = self.maximum_health / 2; + self.health = maximum_health / 2; } pub fn construct(&self, encyclopedia: &Encyclopedia) -> Result<PreEntity> { @@ -167,9 +88,9 @@ impl PlayerState { let item = encyclopedia.get_item(&itemid).ok_or(aerr!("failed to load item '{:?} in inventory of player {:?}", itemid, self))?; Ok(InventoryEntry{itemid: itemid.clone(), item, is_equipped: *is_equipped}) }).collect::<Result<Vec<InventoryEntry>>>()?, - capacity: self.inventory_capacity + capacity: inventory_capacity }), - ComponentWrapper::Health(Health{health: self.health, maxhealth: self.maximum_health}), + ComponentWrapper::Health(Health{health: self.health, maxhealth: maximum_health}), ComponentWrapper::Fighter(Fighter{attack: AttackType::Attack(5), cooldown: 8, range: 1}), ComponentWrapper::Healing(Healing{delay: 50, health: 1, next_heal: None}), ComponentWrapper::Movable(Movable{cooldown: 2}), @@ -180,3 +101,62 @@ impl PlayerState { ]) } } + +impl Serialize for PlayerState { + fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> + where S: Serializer { + PlayerStateSave::New{name: self.id.clone(), roomname: self.room.clone(), inventory: NewInventorySave{items: self.inventory.clone()}, health: self.health}.serialize(serializer) + } +} +impl<'de> Deserialize<'de> for PlayerState { + fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error> + where D: Deserializer<'de> { + Ok(match PlayerStateSave::deserialize(deserializer)? { + PlayerStateSave::New{name, roomname, inventory, health} => PlayerState{id: name, room: roomname, inventory: inventory.items, health, pos: RoomPos::Unknown}, + PlayerStateSave::Old{name, roomname, inventory, equipment, health} => { + PlayerState{ + id: name, + room: roomname, + inventory: { + let mut inv = Vec::new(); + for item in inventory.items { + inv.push((item, false)); + } + for (_slot, item) in equipment.into_iter() { + inv.push((item, true)); + } + inv + }, + health, + pos: RoomPos::Unknown + } + } + }) + } +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +struct OldInventorySave { + pub items: Vec<ItemId> +} +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +struct NewInventorySave { + pub items: Vec<(ItemId, bool)> +} +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(untagged)] +enum PlayerStateSave { + New { + name: PlayerId, + roomname: Option<RoomId>, + inventory: NewInventorySave, + health: i64 + }, + Old { + name: PlayerId, + roomname: Option<RoomId>, + inventory: OldInventorySave, + equipment: HashMap<Slot, ItemId>, + health: i64 + } +} diff --git a/src/room.rs b/src/room.rs index 56fe2e6..6100ce1 100644 --- a/src/room.rs +++ b/src/room.rs @@ -255,9 +255,7 @@ impl <'a, 'b>Room<'a, 'b> { player.id.clone(), self.id.clone(), inventory.items.iter().map(|entry| (entry.itemid.clone(), entry.is_equipped)).collect(), - inventory.capacity, health.health, - health.maxhealth )) } |
