support multiple scenes and passing between them

This commit is contained in:
Rusty Striker 2025-07-04 13:23:22 +03:00
parent 2d46cbb4d0
commit 838c89ac73
Signed by: RustyStriker
GPG key ID: 87E4D691632DFF15
8 changed files with 124 additions and 67 deletions

View file

@ -48,7 +48,10 @@ pub enum Request {
amount: usize,
},
// Map requests
GetCurrentScene,
GetScene {
id: usize,
},
GetSceneList,
GetTokens {
scene: usize,
},
@ -82,6 +85,9 @@ pub enum Response {
grid_cell_size: Option<f32>,
grid_offset: Option<[f32; 2]>,
},
SceneList {
scenes: Vec<(usize, String)>,
},
MoveToken {
token_id: usize,
x: f32,

View file

@ -5,8 +5,6 @@ use character_sheet::{AccessLevel, Character, CharacterShort, EntryType};
use scene::{Party, Scene, TokenInfo};
use serde::{Deserialize, Serialize};
use crate::game::scene::Map;
pub mod character_sheet;
pub mod chat_message;
pub mod entry;
@ -26,10 +24,7 @@ pub trait GameImpl<'a, C: Character<A> + Serialize + Deserialize<'a>, A: entry::
// Scenes
/// the list of available scenes
fn scenes(&self) -> Vec<usize>;
fn current_scene(&self) -> 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<&Map>;
fn get_scene(&self, id: usize) -> Option<&Scene>;
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, scene: usize, token_id: usize) -> Option<&TokenInfo>;
@ -41,7 +36,6 @@ pub struct Game<C: Character<A> + Serialize, A: entry::GameEntry + Serialize> {
_a: PhantomData<A>,
characters: Vec<(C, CharacterInfo)>,
scenes: HashMap<usize, Scene>,
current_scene: usize,
}
impl<'a, C: Character<A> + Serialize + Deserialize<'a>, A: entry::GameEntry + Serialize + Deserialize<'a>>
GameImpl<'a, C, A> for Game<C, A>
@ -70,6 +64,8 @@ impl<'a, C: Character<A> + Serialize + Deserialize<'a>, A: entry::GameEntry + Se
scenes.insert(
0,
Scene {
title: "Dungeon".to_string(),
visible_to_users: true,
map: Some(scene::Map {
background: "assets/pf2r/maps/testmap.jpg".to_string(),
grid_cell_size: 150.0,
@ -79,11 +75,24 @@ impl<'a, C: Character<A> + Serialize + Deserialize<'a>, A: entry::GameEntry + Se
characters: vec![(0, Party(true))],
},
);
scenes.insert(
1,
Scene {
title: "Basement".to_string(),
visible_to_users: false,
map: Some(scene::Map {
background: "assets/pf2r/maps/testmap.jpg".to_string(),
grid_cell_size: 300.0,
grid_offset: [80.0, 35.0],
tokens: HashMap::new(),
}),
characters: vec![(0, Party(true))],
},
);
Self {
_a: PhantomData,
characters: Vec::new(),
scenes,
current_scene: 0,
}
}
fn create_character(&mut self) -> usize {
@ -175,24 +184,8 @@ impl<'a, C: Character<A> + Serialize + Deserialize<'a>, A: entry::GameEntry + Se
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(c.0)))
.flatten()
.collect()
})
}
fn scene_map(&self, scene_id: usize) -> Option<&Map> {
self.scenes.get(&scene_id).map(|s| s.map.as_ref()).flatten()
}
fn current_scene(&self) -> usize {
self.current_scene
fn get_scene(&self, id: usize) -> Option<&Scene> {
self.scenes.get(&id)
}
}

View file

@ -3,6 +3,9 @@ use std::collections::HashMap;
#[derive(Serialize, Deserialize, Debug)]
pub struct Scene {
/// Scene title/name
pub title: String,
pub visible_to_users: bool,
/// 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)

View file

@ -156,32 +156,52 @@ impl GameServer {
}
}
}
api::Request::GetCurrentScene => {
let scene = self.game.current_scene();
let scene_tokens = self
api::Request::GetScene { id: scene_id } => {
if self
.game
.available_tokens(scene)
.get_scene(scene_id)
.map(|s| s.visible_to_users)
.unwrap_or(false) ||
*self.users.get(&id).unwrap_or(&false)
{
let scene_tokens = self
.game
.available_tokens(scene_id)
.iter()
.map(|&id| self.game.token_info(scene_id, id).map(|info| (id, info)))
.flatten()
.map(|(id, info)| SpawnToken {
token_id: id,
x: info.x,
y: info.y,
img: info.img_source.clone(),
})
.collect::<Vec<_>>();
let map = self.game.get_scene(scene_id).map(|s| s.map.as_ref()).flatten();
_ = broadcast.send((
Some(id.clone()),
api::Response::ShowScene {
scene: scene_id,
tokens: scene_tokens,
background: map.map(|m| m.background.clone()),
grid_cell_size: map.map(|m| m.grid_cell_size),
grid_offset: map.map(|m| m.grid_offset.clone()),
},
));
}
}
api::Request::GetSceneList => {
let admin = *self.users.get(&id).unwrap_or(&false);
let scenes = self
.game
.scenes()
.iter()
.map(|&id| self.game.token_info(scene, id).map(|info| (id, info)))
.map(|id| self.game.get_scene(*id).map(|s| (id, s)))
.flatten()
.map(|(id, info)| SpawnToken {
token_id: id,
x: info.x,
y: info.y,
img: info.img_source.clone(),
})
.filter(|(_, scene)| admin || scene.visible_to_users)
.map(|(id, s)| (*id, s.title.to_string()))
.collect::<Vec<_>>();
let map = self.game.scene_map(scene);
_ = broadcast.send((
Some(id.clone()),
api::Response::ShowScene {
scene: scene,
tokens: scene_tokens,
background: map.map(|m| m.background.clone()),
grid_cell_size: map.map(|m| m.grid_cell_size),
grid_offset: map.map(|m| m.grid_offset.clone()),
},
));
_ = broadcast.send((Some(id.clone()), api::Response::SceneList { scenes: scenes }))
}
api::Request::SpawnToken {
map_id,
@ -190,13 +210,10 @@ impl GameServer {
y,
img_path,
} => {
// TODO: Make sure the user is an admin
let token_id = self.game.create_token(map_id, character, img_path.clone(), x, y);
_ = broadcast.send((
if map_id == self.game.current_scene() {
None
} else {
Some(id.clone())
},
None, // TODO: add the option to spawn the token hidden
api::Response::SpawnToken(SpawnToken {
token_id,
x,
@ -250,11 +267,15 @@ impl GameServer {
_ = broadcast.send((Some(id), api::Response::Shutdown));
}
api::Request::Shutdown => break,
api::Request::CharacterDisplay { id } => todo!(),
api::Request::CharacterInputs { id } => todo!(),
api::Request::CharacterGetField { id, field } => todo!(),
api::Request::CharacterSetField { id, field, val } => todo!(),
api::Request::CharacterAssign { id, user } => todo!(),
api::Request::CharacterDisplay { id: _ } => {}
api::Request::CharacterInputs { id: _ } => todo!(),
api::Request::CharacterGetField { id: _, field: _ } => todo!(),
api::Request::CharacterSetField {
id: _,
field: _,
val: _,
} => todo!(),
api::Request::CharacterAssign { id: _, user: _ } => todo!(),
}
}
_ = broadcast.send((None, api::Response::Shutdown));