diff --git a/readme.md b/readme.md index 2e7d41f..229d5c3 100644 --- a/readme.md +++ b/readme.md @@ -46,4 +46,25 @@ shit that needs to be done with characters handling: [ ] combat fog [ ] Characters in current map/scene [ ] initiative tracker -[ ] cards system (for a playstyle that relies less on the map) \ No newline at end of file +[ ] cards system (for a playstyle that relies less on the map) + + + +Scene: + Map: + Background image (the map itself) + Tokens: + Location + Image + Character + ... + ... + Images (popup images) (ability to list) + Characters (ability to list) + +Get a list of scenes + user picks a scene to view +get scene characters +get short for each character +get the map +IF map THEN get tokens \ No newline at end of file diff --git a/src/api/game_actions.rs b/src/api/game_actions.rs index af4bdb0..1ae98a2 100644 --- a/src/api/game_actions.rs +++ b/src/api/game_actions.rs @@ -26,6 +26,7 @@ pub struct SpawnToken { } impl std::fmt::Debug for SpawnToken { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + // I dont want it to print the `img` field because it VERY long :) f.debug_struct("SpawnToken") .field("token_id", &self.token_id) .field("x", &self.x) diff --git a/src/api/mod.rs b/src/api/mod.rs index d0fc053..583c974 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -29,7 +29,7 @@ pub enum Request { GetChatHistory { amount: usize, from: usize }, GetLastMessages { amount: usize, }, // Map requests - GetTokens, + GetTokens { scene: usize }, SpawnToken { map_id: usize, character: String, x: f32, y: f32, img_path: String }, MoveToken { token_id: usize, x: f32, y: f32 }, // Actions requests diff --git a/src/game/character_sheet.rs b/src/game/character_sheet.rs index a3da416..1e89eba 100644 --- a/src/game/character_sheet.rs +++ b/src/game/character_sheet.rs @@ -56,6 +56,8 @@ impl AccessLevel { /// |_______________________| /// ``` pub struct CharacterShort { + /// Character id to make sure we all know who is who + pub id: usize, /// Main title, usually the name pub title: String, /// little title under the main title, probably a title or class @@ -64,6 +66,12 @@ pub struct CharacterShort { pub health: Option, pub level: Option, } +impl CharacterShort { + pub fn with_id(mut self, id: usize) -> Self { + self.id = id; + self + } +} #[derive(Debug, Serialize, Deserialize)] pub enum EntryType { diff --git a/src/game/mod.rs b/src/game/mod.rs index 804d494..aead803 100644 --- a/src/game/mod.rs +++ b/src/game/mod.rs @@ -2,48 +2,66 @@ use std::{collections::HashMap, marker::PhantomData}; use character_sheet::{AccessLevel, Character, CharacterShort, EntryType}; +use scene::{Party, Scene, TokenInfo}; use serde::{Deserialize, Serialize}; pub mod character_sheet; pub mod chat_message; pub mod entry; +pub mod scene; pub trait GameImpl<'a, C: Character + Serialize + Deserialize<'a>, A: entry::GameEntry + Serialize + Deserialize<'a>> { /// Creates a new game fn new() -> Self; + + // Character management /// Creates a new character, returning the character id fn create_character(&mut self) -> usize; fn display_character(&self, character_id: usize, access: AccessLevel) -> Vec>; fn characters(&self) -> Vec; fn character_short(&self, character_id: usize) -> Option; - fn create_token(&mut self, map_id: usize, character: String, img_source: String, x: f32, y: f32) -> usize; - fn move_token(&mut self, map_id: usize, token_id: usize, x: f32, y: f32) -> bool; + // Scenes + /// the list of available scenes + fn scenes(&self) -> Vec; + /// Gets the map background (file path) + fn scene_characters(&self, scene: usize, character_id: usize) -> Option>; + fn scene_map(&self, scene_id: usize) -> Option; + fn create_token(&mut self, scene_id: usize, character: String, img_source: String, x: f32, y: f32) -> usize; + fn move_token(&mut self, scene_id: usize, token_id: usize, x: f32, y: f32) -> bool; fn token_info(&self, map_id: usize, token_id: usize) -> Option<&TokenInfo>; - fn available_tokens(&self) -> impl Iterator; + fn available_tokens(&self, scene: usize) -> Vec; } #[derive(Serialize, Deserialize)] pub struct Game + Serialize, A: entry::GameEntry + Serialize> { _a: PhantomData, - characters: Vec, - tokens: HashMap, + characters: Vec<(C, CharacterInfo)>, + scenes: HashMap, } impl<'a, C: Character + Serialize + Deserialize<'a>, A: entry::GameEntry + Serialize + Deserialize<'a>> GameImpl<'a, C, A> for Game { fn new() -> Self { let mut tokens = HashMap::new(); - tokens.insert(0, TokenInfo { character: "bart".to_string(), map_id: 0, img_source: "assets/pf2r/tokens/louise.jpg".to_string(), x: 2.0, y: 2.0 }); + tokens.insert(0, TokenInfo { character: "bart".to_string(), img_source: "assets/pf2r/tokens/louise.jpg".to_string(), x: 2.0, y: 2.0 }); + let mut scenes = HashMap::new(); + scenes.insert(0, Scene { map: None, characters: vec![(0, Party(true))] }); Self { _a: PhantomData, characters: Vec::new(), - tokens, + scenes, } } fn create_character(&mut self) -> usize { - self.characters.push(C::default()); + self.characters.push((C::default(), CharacterInfo::default())); self.characters.len() - 1 } - fn move_token(&mut self, _map_id: usize, token_id: usize, x: f32, y: f32) -> bool { - if let Some(ti) = self.tokens.get_mut(&token_id) { + fn move_token(&mut self, scene: usize, token_id: usize, x: f32, y: f32) -> bool { + let token = self.scenes + .get_mut(&scene) + .map(|s| s.map.as_mut()) + .flatten() + .map(|m| m.tokens.get_mut(&token_id)) + .flatten(); + if let Some(ti) = token { ti.x = x; ti.y = y; true @@ -52,8 +70,14 @@ impl<'a, C: Character + Serialize + Deserialize<'a>, A: entry::GameEntry + Se false } } - fn token_info(&self, _map_id: usize, token_id: usize) -> Option<&TokenInfo> { - if let Some(ti) = self.tokens.get(&token_id) { + fn token_info(&self, scene: usize, token_id: usize) -> Option<&TokenInfo> { + let token = self.scenes + .get(&scene) + .map(|s| s.map.as_ref()) + .flatten() + .map(|m| m.tokens.get(&token_id)) + .flatten(); + if let Some(ti) = token { Some(ti) } else { @@ -61,26 +85,43 @@ impl<'a, C: Character + Serialize + Deserialize<'a>, A: entry::GameEntry + Se } } - fn available_tokens(&self) -> impl Iterator { - self.tokens - .keys() - .into_iter() - .map(|k| *k) // this map feels stupid but keys() turns into a &usize iterator so :shrug: + fn available_tokens(&self, scene: usize) -> Vec { + self.scenes + .get(&scene) + .map(|s| s.map.as_ref()) + .flatten() + .map(|m| m.tokens + .keys() + .into_iter() + .map(|k| *k) + .collect() + ) // this map feels stupid but keys() turns into a &usize iterator so :shrug: + .unwrap_or(Vec::new()) } - fn create_token(&mut self, map_id: usize, character: String, img_source: String, x: f32, y: f32) -> usize { + fn create_token(&mut self, scene: usize, character: String, img_source: String, x: f32, y: f32) -> usize { let mut id = 0; - while self.tokens.contains_key(&id) { - id += 1; + let tokens = self.scenes + .get_mut(&scene) + .map(|s| s.map.as_mut()) + .flatten() + .map(|m| &mut m.tokens); + if let Some(tokens) = tokens { + while tokens.contains_key(&id) { + id += 1; + } + tokens.insert(id, TokenInfo { character, img_source, x, y }); + id + } + else { + 0 } - self.tokens.insert(id, TokenInfo { character, map_id, img_source, x, y }); - id } fn display_character(&self, character_id: usize, access: AccessLevel) -> Vec> { self.characters .get(character_id) - .map(|c| c.display(access)) + .map(|c| c.0.display(access)) .unwrap_or(Vec::new()) } @@ -93,21 +134,38 @@ impl<'a, C: Character + Serialize + Deserialize<'a>, A: entry::GameEntry + Se fn character_short(&self, character_id: usize) -> Option { self.characters .get(character_id) - .map(|c| c.short()) + .map(|c| c.0.short().with_id(character_id)) } + + fn scenes(&self) -> Vec { + self.scenes.keys().map(|k| *k).collect() + } + + fn scene_characters(&self, scene: usize, id: usize) -> Option> { + let party = self.characters.get(id).map(|c| c.1.party).unwrap_or_default(); + self.scenes + .get(&scene) + .map(|s| s.characters + .iter() + .filter(|c| party.can_see(c.1)) + .map(|c| self.characters.get(c.0).map(|e| e.0.short().with_id(c.0))) + .flatten() + .collect() + ) + } + + fn scene_map(&self, scene_id: usize) -> Option { + self.scenes + .get(&scene_id) + .map(|s| s.map.as_ref().map(|m| m.background.clone())) + .flatten() + } + } -#[derive(Serialize, Deserialize, Debug)] -pub struct TokenInfo { - /// Which character the token refers to - pub character: String, - /// Which map does the token exists in (allowing multiple tokens in multiple maps) - pub map_id: usize, - /// Token image source, as path relative to the data directory - pub img_source: String, - // x, y are floats to allow 'free movement' - /// X position, in grid slots units (integers are grid aligned) - pub x: f32, - /// Y position, in grid slots units (integers are grid aligned) - pub y: f32, + +#[derive(Serialize, Deserialize, Default, Debug, Clone)] +struct CharacterInfo { + pub owner: String, + pub party: Party, } \ No newline at end of file diff --git a/src/game/scene.rs b/src/game/scene.rs new file mode 100644 index 0000000..5b6553d --- /dev/null +++ b/src/game/scene.rs @@ -0,0 +1,40 @@ +use std::collections::HashMap; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug)] +pub struct Scene { + /// Map for the scene, None in case of theater of the mind kinda gameplay + pub map: Option, + /// List of character ids, and can the party see them (maybe change that to a different thing to allow maybe 2 parties? maybe a bit field) + pub characters: Vec<(usize, Party)>, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct Map { + /// Image source for the background of the map + pub background: String, + /// Tokens in the current map (should be of characters), maps from token_id to its info + pub tokens: HashMap, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct TokenInfo { + /// Which character the token refers to + pub character: String, + /// Token image source, as path relative to the data directory + pub img_source: String, + // x, y are floats to allow 'free movement' + /// X position, in grid slots units (integers are grid aligned) + pub x: f32, + /// Y position, in grid slots units (integers are grid aligned) + pub y: f32, +} + +// for now this is defined so i could easily modify this later if needed (and only change initializations) +#[derive(Serialize, Deserialize, Debug, Clone, Copy, Default)] +pub struct Party(pub bool); +impl Party { + pub fn can_see(&self, other: Party) -> bool { + !self.0 || other.0 + } +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 68a89cb..a9ecffa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -120,8 +120,8 @@ impl GameServer { self.chat.iter().skip(start).map(|m| m.1.clone()).collect(); _ = broadcast.send((Some(id), api::Response::GetChatHistory(history))); }, - api::Request::GetTokens => { - for token_id in self.game.available_tokens() { + api::Request::GetTokens { scene } => { + for token_id in self.game.available_tokens(scene) { if let Some(ti) = self.game.token_info(0, token_id) { let bits = std::fs::read(&ti.img_source).expect("FAILED READING TOKEN IMAGE"); let img = base64::Engine::encode(&base64::prelude::BASE64_STANDARD, &bits); diff --git a/src/pathfinder2r_impl/mod.rs b/src/pathfinder2r_impl/mod.rs index a05ebf4..5246a62 100644 --- a/src/pathfinder2r_impl/mod.rs +++ b/src/pathfinder2r_impl/mod.rs @@ -133,6 +133,7 @@ impl Character for Pathfinder2rCharacterSheet { fn short(&self) -> CharacterShort { CharacterShort { + id: 0, title: self.name.clone(), sub_title: Some("A Character!".to_string()), health: Some([ "Dying", "Hurt", "Harmed", "Unharmed" ][((self.health * 4) / self.max_health) as usize].to_string()),