summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortroido <troido@protonmail.com>2020-09-25 08:54:20 +0200
committertroido <troido@protonmail.com>2020-09-25 08:54:20 +0200
commit09306cb76c6e1eabb4082a985a0a0fa335bda5c1 (patch)
treee43abd096374a8e79186b519d80372112ab0ca74
parent9eb3a9da97e53cee14e585e027badb3783b8e25e (diff)
proper serialisation for playerstate; strum for old-style enums
-rw-r--r--src/components/equipment.rs52
-rw-r--r--src/components/interactable.rs5
-rw-r--r--src/components/messages.rs18
-rw-r--r--src/componentwrapper.rs6
-rw-r--r--src/item.rs2
-rw-r--r--src/persistence.rs5
-rw-r--r--src/playerstate.rs164
-rw-r--r--src/room.rs2
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
))
}