it seems i didnt commit before taking a break for the semester

This commit is contained in:
Rusty Striker 2025-06-14 17:35:16 +03:00
parent 4a0673bde5
commit dbeda509fc
Signed by: RustyStriker
GPG key ID: 87E4D691632DFF15
8 changed files with 170 additions and 41 deletions

View file

@ -46,4 +46,25 @@ shit that needs to be done with characters handling:
[ ] combat fog [ ] combat fog
[ ] Characters in current map/scene [ ] Characters in current map/scene
[ ] initiative tracker [ ] initiative tracker
[ ] cards system (for a playstyle that relies less on the map) [ ] 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

View file

@ -26,6 +26,7 @@ pub struct SpawnToken {
} }
impl std::fmt::Debug for SpawnToken { impl std::fmt::Debug for SpawnToken {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 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") f.debug_struct("SpawnToken")
.field("token_id", &self.token_id) .field("token_id", &self.token_id)
.field("x", &self.x) .field("x", &self.x)

View file

@ -29,7 +29,7 @@ pub enum Request {
GetChatHistory { amount: usize, from: usize }, GetChatHistory { amount: usize, from: usize },
GetLastMessages { amount: usize, }, GetLastMessages { amount: usize, },
// Map requests // Map requests
GetTokens, GetTokens { scene: usize },
SpawnToken { map_id: usize, character: String, x: f32, y: f32, img_path: String }, SpawnToken { map_id: usize, character: String, x: f32, y: f32, img_path: String },
MoveToken { token_id: usize, x: f32, y: f32 }, MoveToken { token_id: usize, x: f32, y: f32 },
// Actions requests // Actions requests

View file

@ -56,6 +56,8 @@ impl AccessLevel {
/// |_______________________| /// |_______________________|
/// ``` /// ```
pub struct CharacterShort { pub struct CharacterShort {
/// Character id to make sure we all know who is who
pub id: usize,
/// Main title, usually the name /// Main title, usually the name
pub title: String, pub title: String,
/// little title under the main title, probably a title or class /// little title under the main title, probably a title or class
@ -64,6 +66,12 @@ pub struct CharacterShort {
pub health: Option<String>, pub health: Option<String>,
pub level: Option<i32>, pub level: Option<i32>,
} }
impl CharacterShort {
pub fn with_id(mut self, id: usize) -> Self {
self.id = id;
self
}
}
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub enum EntryType { pub enum EntryType {

View file

@ -2,48 +2,66 @@
use std::{collections::HashMap, marker::PhantomData}; use std::{collections::HashMap, marker::PhantomData};
use character_sheet::{AccessLevel, Character, CharacterShort, EntryType}; use character_sheet::{AccessLevel, Character, CharacterShort, EntryType};
use scene::{Party, Scene, TokenInfo};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
pub mod character_sheet; pub mod character_sheet;
pub mod chat_message; pub mod chat_message;
pub mod entry; pub mod entry;
pub mod scene;
pub trait GameImpl<'a, C: Character<A> + Serialize + Deserialize<'a>, A: entry::GameEntry + Serialize + Deserialize<'a>> { pub trait GameImpl<'a, C: Character<A> + Serialize + Deserialize<'a>, A: entry::GameEntry + Serialize + Deserialize<'a>> {
/// Creates a new game /// Creates a new game
fn new() -> Self; fn new() -> Self;
// Character management
/// Creates a new character, returning the character id /// Creates a new character, returning the character id
fn create_character(&mut self) -> usize; fn create_character(&mut self) -> usize;
fn display_character(&self, character_id: usize, access: AccessLevel) -> Vec<Option<(String, EntryType)>>; fn display_character(&self, character_id: usize, access: AccessLevel) -> Vec<Option<(String, EntryType)>>;
fn characters(&self) -> Vec<usize>; fn characters(&self) -> Vec<usize>;
fn character_short(&self, character_id: usize) -> Option<CharacterShort>; fn character_short(&self, character_id: usize) -> Option<CharacterShort>;
fn create_token(&mut self, map_id: usize, character: String, img_source: String, x: f32, y: f32) -> usize; // Scenes
fn move_token(&mut self, map_id: usize, token_id: usize, x: f32, y: f32) -> bool; /// the list of available scenes
fn scenes(&self) -> Vec<usize>;
/// Gets the map background (file path)
fn scene_characters(&self, scene: usize, character_id: usize) -> Option<Vec<CharacterShort>>;
fn scene_map(&self, scene_id: usize) -> Option<String>;
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 token_info(&self, map_id: usize, token_id: usize) -> Option<&TokenInfo>;
fn available_tokens(&self) -> impl Iterator<Item = usize>; fn available_tokens(&self, scene: usize) -> Vec<usize>;
} }
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct Game<C: Character<A> + Serialize, A: entry::GameEntry + Serialize> { pub struct Game<C: Character<A> + Serialize, A: entry::GameEntry + Serialize> {
_a: PhantomData<A>, _a: PhantomData<A>,
characters: Vec<C>, characters: Vec<(C, CharacterInfo)>,
tokens: HashMap<usize, TokenInfo>, scenes: HashMap<usize, Scene>,
} }
impl<'a, C: Character<A> + Serialize + Deserialize<'a>, A: entry::GameEntry + Serialize + Deserialize<'a>> GameImpl<'a, C, A> for Game<C, A> { impl<'a, C: Character<A> + Serialize + Deserialize<'a>, A: entry::GameEntry + Serialize + Deserialize<'a>> GameImpl<'a, C, A> for Game<C, A> {
fn new() -> Self { fn new() -> Self {
let mut tokens = HashMap::new(); 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 { Self {
_a: PhantomData, _a: PhantomData,
characters: Vec::new(), characters: Vec::new(),
tokens, scenes,
} }
} }
fn create_character(&mut self) -> usize { fn create_character(&mut self) -> usize {
self.characters.push(C::default()); self.characters.push((C::default(), CharacterInfo::default()));
self.characters.len() - 1 self.characters.len() - 1
} }
fn move_token(&mut self, _map_id: usize, token_id: usize, x: f32, y: f32) -> bool { fn move_token(&mut self, scene: usize, token_id: usize, x: f32, y: f32) -> bool {
if let Some(ti) = self.tokens.get_mut(&token_id) { 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.x = x;
ti.y = y; ti.y = y;
true true
@ -52,8 +70,14 @@ impl<'a, C: Character<A> + Serialize + Deserialize<'a>, A: entry::GameEntry + Se
false false
} }
} }
fn token_info(&self, _map_id: usize, token_id: usize) -> Option<&TokenInfo> { fn token_info(&self, scene: usize, token_id: usize) -> Option<&TokenInfo> {
if let Some(ti) = self.tokens.get(&token_id) { 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) Some(ti)
} }
else { else {
@ -61,26 +85,43 @@ impl<'a, C: Character<A> + Serialize + Deserialize<'a>, A: entry::GameEntry + Se
} }
} }
fn available_tokens(&self) -> impl Iterator<Item = usize> { fn available_tokens(&self, scene: usize) -> Vec<usize> {
self.tokens self.scenes
.keys() .get(&scene)
.into_iter() .map(|s| s.map.as_ref())
.map(|k| *k) // this map feels stupid but keys() turns into a &usize iterator so :shrug: .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; let mut id = 0;
while self.tokens.contains_key(&id) { let tokens = self.scenes
id += 1; .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<Option<(String, EntryType)>> { fn display_character(&self, character_id: usize, access: AccessLevel) -> Vec<Option<(String, EntryType)>> {
self.characters self.characters
.get(character_id) .get(character_id)
.map(|c| c.display(access)) .map(|c| c.0.display(access))
.unwrap_or(Vec::new()) .unwrap_or(Vec::new())
} }
@ -93,21 +134,38 @@ impl<'a, C: Character<A> + Serialize + Deserialize<'a>, A: entry::GameEntry + Se
fn character_short(&self, character_id: usize) -> Option<CharacterShort> { fn character_short(&self, character_id: usize) -> Option<CharacterShort> {
self.characters self.characters
.get(character_id) .get(character_id)
.map(|c| c.short()) .map(|c| c.0.short().with_id(character_id))
} }
fn scenes(&self) -> Vec<usize> {
self.scenes.keys().map(|k| *k).collect()
}
fn scene_characters(&self, scene: usize, id: usize) -> Option<Vec<CharacterShort>> {
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<String> {
self.scenes
.get(&scene_id)
.map(|s| s.map.as_ref().map(|m| m.background.clone()))
.flatten()
}
} }
#[derive(Serialize, Deserialize, Debug)]
pub struct TokenInfo { #[derive(Serialize, Deserialize, Default, Debug, Clone)]
/// Which character the token refers to struct CharacterInfo {
pub character: String, pub owner: String,
/// Which map does the token exists in (allowing multiple tokens in multiple maps) pub party: Party,
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,
} }

40
src/game/scene.rs Normal file
View file

@ -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<Map>,
/// 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<usize, TokenInfo>,
}
#[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
}
}

View file

@ -120,8 +120,8 @@ impl GameServer {
self.chat.iter().skip(start).map(|m| m.1.clone()).collect(); self.chat.iter().skip(start).map(|m| m.1.clone()).collect();
_ = broadcast.send((Some(id), api::Response::GetChatHistory(history))); _ = broadcast.send((Some(id), api::Response::GetChatHistory(history)));
}, },
api::Request::GetTokens => { api::Request::GetTokens { scene } => {
for token_id in self.game.available_tokens() { for token_id in self.game.available_tokens(scene) {
if let Some(ti) = self.game.token_info(0, token_id) { 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 bits = std::fs::read(&ti.img_source).expect("FAILED READING TOKEN IMAGE");
let img = base64::Engine::encode(&base64::prelude::BASE64_STANDARD, &bits); let img = base64::Engine::encode(&base64::prelude::BASE64_STANDARD, &bits);

View file

@ -133,6 +133,7 @@ impl Character<Entry> for Pathfinder2rCharacterSheet {
fn short(&self) -> CharacterShort { fn short(&self) -> CharacterShort {
CharacterShort { CharacterShort {
id: 0,
title: self.name.clone(), title: self.name.clone(),
sub_title: Some("A Character!".to_string()), sub_title: Some("A Character!".to_string()),
health: Some([ "Dying", "Hurt", "Harmed", "Unharmed" ][((self.health * 4) / self.max_health) as usize].to_string()), health: Some([ "Dying", "Hurt", "Harmed", "Unharmed" ][((self.health * 4) / self.max_health) as usize].to_string()),