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:
parent
97475599a7
commit
be6dd7c0e4
13 changed files with 296 additions and 76 deletions
|
@ -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,
|
||||
|
||||
|
|
|
@ -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 }
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
98
src/lib.rs
98
src/lib.rs
|
@ -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));
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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!(),
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue