some tokens stuff (ye i started to work on the map, also i 'fixed' the context menu for msgs but still needs to be impl-ed)

This commit is contained in:
Rusty Striker 2024-10-08 19:55:48 +03:00
parent 97475599a7
commit be6dd7c0e4
Signed by: RustyStriker
GPG key ID: 87E4D691632DFF15
13 changed files with 296 additions and 76 deletions

View file

@ -15,7 +15,11 @@ pub enum Request {
Message(ChatMessage),
GetChatHistory { amount: usize, from: usize },
GetLastMessages { amount: usize, },
GetTokens,
SpawnToken { x: i32, y: i32, img_path: String },
MoveToken { token_id: usize, x: i32, y: i32 },
Quit,
Kick(String),
Shutdown
}
#[derive(Serialize, Clone)]
@ -25,6 +29,8 @@ pub enum Response {
Login(login::LoginResult),
Message(ChatMessage),
GetChatHistory(Vec<ChatMessage>),
MoveToken { token_id: usize, x: i32, y: i32 },
SpawnToken { token_id: usize, x: i32, y: i32, img: String },
Quit { id: String },
Shutdown,

View file

@ -1,24 +1,107 @@
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, Clone)]
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct ChatMessage {
/// message text, `{item}` can be used to refer to items and such, where item is of the path such as `items/sword` or `spells/fireball`
pub text: String,
/// Source user initiated the action/sent the message
#[serde(default = "default_source")]
pub source: String,
/// Character "sending" the message
pub character: Option<String>,
/// whisper item
pub whisper: Option<String>,
/// Rolls of the action, if not empty a roll should happen before
pub roll: Option<Vec<RollDialogOption>>,
/// Optional roll target
pub roll_target: Option<i32>,
/// Optional action buttons, for a chat message this will be empty
pub actions: Option<Vec<String>>,
/// Source/Caster/Whoever initiated the action/sent the message
pub source: String,
/// Targets of the action, for a chat message this will be empty
pub targets: Option<Vec<String>>,
/// message id, should be left emitted or 0 for new messages
#[serde(default = "default_id")]
pub id: usize,
}
fn default_source() -> String {
String::new()
}
impl ChatMessage {
/// Creates a new chat message with a given text and source
pub fn new(text: String) -> Self {
Self {
text, ..Default::default()
}
}
pub fn source(mut self, source: String) -> Self {
self.source = source;
self
}
/// sets the whisper value of the message
pub fn whisper(mut self, whisper: Option<String>) -> Self {
self.whisper = whisper;
self
}
/// sets the roll value for the message (chaining multiple will override each other)
pub fn roll(mut self, roll: Option<Vec<RollDialogOption>>) -> Self {
self.roll = roll;
self
}
/// adds a single roll to the message (chaining multiple will add multiple rolls)
pub fn with_roll(mut self, roll: RollDialogOption) -> Self {
if let Some(rs) = &mut self.roll {
rs.push(roll);
}
else {
self.roll = Some(vec![roll]);
}
self
}
pub fn roll_target(mut self, target: Option<i32>) -> Self {
self.roll_target = target;
self
}
/// sets the actions value (chaining multiple will override)
pub fn actions(mut self, actions: Option<Vec<String>>) -> Self {
self.actions = actions;
self
}
/// adds a single action to the message (chaining multiple will add multiple actions)
pub fn with_action(mut self, action: String) -> Self {
if let Some(acts) = &mut self.actions {
acts.push(action);
}
else {
self.actions = Some(vec![action]);
}
self
}
/// sets the targets value (chaining multiple will override)
pub fn targets(mut self, targets: Option<Vec<String>>) -> Self {
self.targets = targets;
self
}
/// adds a single target to the message (chaining multiple will add multiple targets)
pub fn with_target(mut self, target: String) -> Self {
if let Some(targets) = &mut self.targets {
targets.push(target);
}
else {
self.targets = Some(vec![target]);
}
self
}
/// Sets the message id
///
/// WARNING: duplicate message id will cause an overwrite of the original message (and an edit at the client)
pub fn id(mut self, id: usize) -> Self {
self.id = id;
self
}
}
fn default_id() -> usize { 0 }

View file

@ -20,7 +20,7 @@ pub trait GameEntry : Serialize + Sized {
/// Get all categories (such as weapons/consumables/spells)
fn categories() -> Vec<String>;
/// returns a chat message to show the entry (with description and all)
fn to_chat(&self, source: &str) -> ChatMessage;
fn to_chat(&self) -> ChatMessage;
}
pub struct ActionDefinition {

View file

@ -1,75 +1,109 @@
use game::{chat_message::ChatMessage, Game, GameImpl};
use tokio::sync::{broadcast, mpsc};
pub mod user;
pub mod table;
pub mod api;
pub mod game;
pub mod pathfinder2r_impl;
pub mod table;
pub mod user;
pub struct GameServer {
_game: Game<pathfinder2r_impl::Pathfinder2rCharacterSheet, pathfinder2r_impl::entry::Entry>,
tokens: Vec<(String, i32, i32)>,
chat: Vec<(String, ChatMessage)>,
}
impl GameServer {
pub fn new() -> Self {
Self {
_game: Game::new(),
tokens: vec![("assets/pf2r/tokens/louise.jpg".to_string(), 2, 2)],
chat: Vec::new(),
}
}
pub async fn server_loop(mut self, mut msgs: mpsc::Receiver<(String, api::Request)>, broadcast: broadcast::Sender<(Option<String>, api::Response)>) {
pub async fn server_loop(
mut self,
mut msgs: mpsc::Receiver<(String, api::Request)>,
broadcast: broadcast::Sender<(Option<String>, api::Response)>,
) {
while let Some(req) = msgs.recv().await {
// TODO: do stuff yo!
let (id, req) = req;
println!("Got message from {}: {:?}", &id, &req);
match req {
api::Request::Error => {},
api::Request::Login(_) => {},
api::Request::Message(mut msg) => {
if msg.id == 0 || msg.id >= self.chat.len() {
api::Request::Error => {}
api::Request::Login(_) => {}
api::Request::Message(mut msg) => {
if msg.id == 0 || msg.id > self.chat.len() {
msg.id = self.chat.len() + 1; // set the message id, 0 is invalid
}
// TODO: check if the editor is an admin as well
else if id == self.chat[msg.id].0 {
self.chat[msg.id] = (id.clone(), msg.clone());
}
else {
else if id == self.chat[msg.id - 1].0 {
self.chat[msg.id - 1] = (id.clone(), msg.clone());
} else {
// if its an edit message and editor is not the owner, skip
continue;
}
if msg.source.is_empty() {
msg.source = format!("({})", id.clone());
}
else {
msg.source = format!("{} ({})", msg.source, id.clone());
}
// Force the sender id to be the new id of the message, even if an id was provided
msg.source = id.clone();
self.chat.push((id.clone(), msg.clone()));
_ = broadcast.send((None, api::Response::Message(msg)));
},
api::Request::Quit => { _ = broadcast.send((None, api::Response::Quit { id }))},
api::Request::Shutdown => todo!(),
api::Request::GetChatHistory { mut amount, from: last_msg } => {
if amount == 0 { amount = self.chat.len(); }
let history: Vec<ChatMessage> = self.chat.iter()
if msg.whisper.is_some() {
_ = broadcast.send((Some(id.clone()), api::Response::Message(msg.clone())));
}
_ = broadcast.send((msg.whisper.clone(), api::Response::Message(msg)));
}
api::Request::GetChatHistory {
mut amount,
from: last_msg,
} => {
if amount == 0 {
amount = self.chat.len();
}
let history: Vec<ChatMessage> = self
.chat
.iter()
.skip(last_msg)
.take(amount)
.map(|m| m.1.clone())
.collect();
_ = broadcast.send((Some(id), api::Response::GetChatHistory(history)));
},
}
api::Request::GetLastMessages { mut amount } => {
if amount == 0 { amount = self.chat.len(); }
let start = if amount >= self.chat.len() { self.chat.len() } else { self.chat.len() - amount };
let history: Vec<ChatMessage> = self.chat.iter()
.skip(start)
.map(|m| m.1.clone())
.collect();
if amount == 0 {
amount = self.chat.len();
}
let start = if amount >= self.chat.len() {
self.chat.len()
} else {
self.chat.len() - amount
};
let history: Vec<ChatMessage> =
self.chat.iter().skip(start).map(|m| m.1.clone()).collect();
_ = broadcast.send((Some(id), api::Response::GetChatHistory(history)));
},
api::Request::GetTokens => {
for (i, (path, x, y)) in self.tokens.iter().enumerate() {
let bits = std::fs::read(path).expect("FAILED READING TOKEN IMAGE");
let img = base64::Engine::encode(&base64::prelude::BASE64_STANDARD, &bits);
_ = broadcast.send((Some(id.clone()), api::Response::SpawnToken { token_id: i, x: *x, y: *y, img }));
}
},
api::Request::SpawnToken { x, y, img_path } => {
let token_id = self.tokens.len();
self.tokens.push((img_path.clone(), x, y));
let bits = std::fs::read(img_path).expect("FAILED READING TOKEN IMAGE");
let img = base64::Engine::encode(&base64::prelude::BASE64_STANDARD, &bits);
_ = broadcast.send((Some(id.clone()), api::Response::SpawnToken { token_id, x, y, img }));
},
api::Request::MoveToken { token_id, x, y } => {
// TODO: add check to make sure the actor is authorized to move the token
_ = broadcast.send((None, api::Response::MoveToken { token_id, x, y }));
},
api::Request::Quit => _ = broadcast.send((None, api::Response::Quit { id })),
api::Request::Kick(id) => _ = broadcast.send((Some(id), api::Response::Shutdown)),
api::Request::Shutdown => break,
}
}
_ = broadcast.send((None, api::Response::Shutdown));

View file

@ -31,6 +31,7 @@ async fn socket_receiver(mut recv: SplitStream<ws::WebSocket>, msend: mpsc::Send
if let Ok(msg) = msg {
match msg {
Message::Text(t) => {
println!("Got message from {}: {}", &id, &t);
let req = serde_json::from_str::<Request>(&t).unwrap_or_default();
let erred = msend.send((id.clone(), req)).await.is_err();
if erred {
@ -54,7 +55,8 @@ async fn socket_sender(id: String, mut send: SplitSink<ws::WebSocket, ws::Messag
while let Ok((to_id, msg)) = brecv.recv().await {
if to_id.is_none() || to_id.map(|t| t == id).unwrap_or(false) {
let err = send.send(ws::Message::Text(serde_json::to_string(&msg).unwrap())).await.is_err();
if err {
if err || matches!(msg, Response::Shutdown) {
_ = send.close().await;
break;
}
}

View file

@ -35,16 +35,9 @@ impl GameEntry for Entry {
}
}
fn to_chat(&self, source: &str) -> ChatMessage {
ChatMessage {
text: format!("{} - it might be a {{weapon/short_bow}} it might not", self.display_name()),
roll: None,
roll_target: None,
actions: None,
source: String::from(source),
targets: None,
id: 0,
}
fn to_chat(&self) -> ChatMessage {
let text = format!("{} - it might be a {{weapon/short_bow}} it might not", self.display_name());
ChatMessage::new(text)
}
fn all(_filter: Option<&str>) -> Vec<String> {

View file

@ -88,25 +88,15 @@ impl Pathfinder2rCharacterSheet {
impl Character<Entry> for Pathfinder2rCharacterSheet {
fn use_action(&mut self, entry: &Entry, action: &ActionResult) -> ChatMessage {
match entry {
Entry::Weapon(_) => ChatMessage {
text: String::from("Attack"),
roll: Some(vec![RollDialogOption { name: String::from("pierce"), dice_type: 4, dice_amount: 1, constant: 0, extra: String::new(), enabled: true }]),
roll_target: Some(10),
actions: Some(vec!["damage".to_string(), "double".to_string()]),
source: self.name.clone(),
targets: None,
id: 0,
},
Entry::Weapon(_) => ChatMessage::new("Attack".to_string())
.with_roll(RollDialogOption { name: String::from("pierce"), dice_type: 4, dice_amount: 1, constant: 0, extra: String::new(), enabled: true })
.roll_target(Some(10))
.with_action("damage".to_string())
.with_action("double".to_string()),
Entry::Consumable(_consumable) => if action.name == "consume" {
ChatMessage {
text: "Heal".to_string(),
roll: Some(vec![RollDialogOption { name: "heal".to_string(), dice_type: 6, dice_amount: 1, constant: 0, extra: String::new(), enabled: true }]),
roll_target: None,
actions: Some(vec!["heal".to_string()]),
source: self.name.clone(),
targets: None,
id: 0,
}
ChatMessage::new("Heal".to_string())
.with_roll(RollDialogOption { name: "heal".to_string(), dice_type: 6, dice_amount: 1, constant: 0, extra: String::new(), enabled: true })
.with_action("heal".to_string())
} else { todo!() },
Entry::Spell(_spell) => todo!(),
}