dice rolling (with a dialog and basic chat output)

This commit is contained in:
Rusty Striker 2024-10-11 12:57:45 +03:00
parent 45106498b4
commit 8b9b5db299
Signed by: RustyStriker
GPG key ID: 87E4D691632DFF15
7 changed files with 244 additions and 63 deletions

View file

@ -5,23 +5,28 @@ pub mod map_actions;
use game_actions::SpawnToken;
use serde::{Deserialize, Serialize};
use crate::game::chat_message::ChatMessage;
use crate::game::{chat_message::ChatMessage, entry::ActionResult};
#[derive(Serialize, Deserialize, Default, Debug)]
#[serde(rename_all = "snake_case")]
pub enum Request {
#[default]
Error,
// Connection requests
Login(login::LoginRequest),
Quit,
Kick(String),
Shutdown,
// Chat requests
Message(ChatMessage),
GetChatHistory { amount: usize, from: usize },
GetLastMessages { amount: usize, },
// Map requests
GetTokens,
SpawnToken { x: i32, y: i32, img_path: String },
MoveToken { token_id: usize, x: i32, y: i32 },
Quit,
Kick(String),
Shutdown
// Actions requests
ActionResult(ActionResult)
}
#[derive(Serialize, Clone, Debug)]
#[serde(rename_all = "snake_case")]

View file

@ -1,5 +1,7 @@
use serde::{Deserialize, Serialize};
use super::entry::ActionDefinition;
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct ChatMessage {
@ -12,12 +14,10 @@ pub struct ChatMessage {
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>,
/// If the originating action had a dice roll with a DC (or similar), an option to show if the action roll succeeded
pub success: Option<bool>,
/// Optional action buttons, for a chat message this will be empty
pub actions: Option<Vec<String>>,
pub actions: Option<Vec<ActionDefinition>>,
/// 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
@ -35,6 +35,10 @@ impl ChatMessage {
text, ..Default::default()
}
}
pub fn character(mut self, character: Option<String>) -> Self {
self.character = character;
self
}
pub fn source(mut self, source: String) -> Self {
self.source = source;
self
@ -45,31 +49,17 @@ impl ChatMessage {
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;
pub fn success(mut self, success: Option<bool>) -> Self {
self.success = success;
self
}
/// sets the actions value (chaining multiple will override)
pub fn actions(mut self, actions: Option<Vec<String>>) -> Self {
pub fn actions(mut self, actions: Option<Vec<ActionDefinition>>) -> 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 {
pub fn with_action(mut self, action: ActionDefinition) -> Self {
if let Some(acts) = &mut self.actions {
acts.push(action);
}
@ -103,20 +93,4 @@ impl ChatMessage {
}
fn default_id() -> usize { 0 }
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct RollDialogOption {
/// Field name
pub name: String,
/// dice size (aka d6, d12, d20)
pub dice_type: u16,
/// amount of dice (aka 1d6, 2d12, 10d20)
pub dice_amount: u16,
/// Constant amout to add (+7, +3, -1)
pub constant: i16,
/// Extra data, like damage type
pub extra: String,
/// should be enabled by default
pub enabled: bool
}
fn default_id() -> usize { 0 }

View file

@ -1,4 +1,4 @@
use serde::Serialize;
use serde::{Deserialize, Serialize};
use super::chat_message::ChatMessage;
@ -23,13 +23,93 @@ pub trait GameEntry : Serialize + Sized {
fn to_chat(&self) -> ChatMessage;
}
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct ActionDefinition {
pub name: String,
pub display_name: Option<String>,
pub source: Option<String>,
pub targets: i32,
pub rolls: Option<Vec<DiceRoll>>,
}
impl ActionDefinition {
pub fn new(name: String) -> Self {
Self {
name, ..Default::default()
}
}
pub fn display_name(mut self, name: Option<String>) -> Self {
self.display_name = name;
self
}
pub fn source(mut self, source: Option<String>) -> Self {
self.source = source;
self
}
pub fn targets(mut self, targets: i32) -> Self {
self.targets = targets;
self
}
pub fn rolls(mut self, rolls: Option<Vec<DiceRoll>>) -> Self {
self.rolls = rolls;
self
}
pub fn with_roll(mut self, roll: DiceRoll) -> Self {
if let Some(rolls) = &mut self.rolls {
rolls.push(roll);
}
else {
self.rolls = Some(vec![roll]);
}
self
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ActionResult {
pub name: String,
pub roll_result: i32,
pub roll_target: i32,
pub source: String,
pub targets: Vec<String>,
pub results: Vec<DiceRollResult>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct DiceRoll {
/// Field name
pub name: String,
/// dice size (aka d6, d12, d20)
pub dice_type: u16,
/// amount of dice (aka 1d6, 2d12, 10d20)
pub dice_amount: u16,
/// Constant amout to add (+7, +3, -1)
pub constant: i16,
/// should be enabled by default
pub enabled: bool,
/// Extra data, like damage type
pub extra: Option<String>,
}
impl DiceRoll {
pub fn new(name: String, dice_type: u16, dice_amount: u16) -> Self {
Self {
name, dice_type, dice_amount, enabled: true, ..Default::default()
}
}
pub fn constant(mut self, constant: i16) -> Self {
self.constant = constant;
self
}
pub fn enabled(mut self, enabled: bool) -> Self {
self.enabled = enabled;
self
}
pub fn extra(mut self, extra: Option<String>) -> Self {
self.extra = extra;
self
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct DiceRollResult {
pub name: String,
pub result: i32,
pub result_text: String,
pub extra: Option<String>,
}

View file

@ -1,5 +1,5 @@
use api::game_actions::SpawnToken;
use game::{chat_message::ChatMessage, Game, GameImpl};
use game::{chat_message::ChatMessage, entry::{ActionDefinition, DiceRoll}, Game, GameImpl};
use tokio::sync::{broadcast, mpsc};
pub mod api;
@ -18,7 +18,22 @@ impl GameServer {
Self {
_game: Game::new(),
tokens: vec![("assets/pf2r/tokens/louise.jpg".to_string(), 2, 2)],
chat: Vec::new(),
chat: vec![
(
"Server".to_string(),
ChatMessage::new("a weapon description".to_string())
.id(1)
.character(Some("Sword or something".to_string()))
.with_action(
ActionDefinition::new("weapon/attack".to_string())
.display_name(Some("Attack +7".to_string()))
.with_roll(DiceRoll::new("Pierce".to_string(), 12, 1).constant(1))
.with_roll(DiceRoll::new("Fire".to_string(), 4, 2))
)
.with_action(ActionDefinition::new("Attack +3".to_string()))
.with_action(ActionDefinition::new("Attack -1".to_string()))
)
],
}
}
@ -76,7 +91,7 @@ impl GameServer {
amount = self.chat.len();
}
let start = if amount >= self.chat.len() {
self.chat.len()
0
} else {
self.chat.len() - amount
};
@ -106,6 +121,20 @@ impl GameServer {
_ = broadcast.send((None, api::Response::MoveToken { token_id, x, y }));
}
},
api::Request::ActionResult(result) => {
let msg = ChatMessage::new(
result.results.iter()
// .map(|d| &d.result_text)
.fold(String::new(), |a,b| a + &format!("{}: {} = {}\n", &b.name, &b.result_text, b.result))
)
.character(Some(result.name))
.source(id.clone())
.targets(Some(result.targets))
.id(self.chat.len() + 1);
self.chat.push((id, msg.clone()));
_ = broadcast.send((None, api::Response::Message(msg)));
}
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,

View file

@ -1,4 +1,4 @@
use crate::game::{entry::{ActionDefinition, ActionResult, GameEntry}, character_sheet::*, chat_message::{ChatMessage, RollDialogOption}};
use crate::game::{character_sheet::*, chat_message::ChatMessage, entry::{ActionDefinition, ActionResult, DiceRoll, GameEntry}};
use serde::Serialize;
use tavern_macros::CharacterSheet;
@ -89,14 +89,20 @@ impl Character<Entry> for Pathfinder2rCharacterSheet {
fn use_action(&mut self, entry: &Entry, action: &ActionResult) -> ChatMessage {
match entry {
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()),
.with_action(
ActionDefinition::new("Attack".to_string())
.with_roll(DiceRoll::new("Piercing".to_string(), 12, 1))
)
.with_action(
ActionDefinition::new("Double".to_string())
.with_roll(DiceRoll::new("Piercing".to_string(), 12, 2))
),
Entry::Consumable(_consumable) => if action.name == "consume" {
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())
.with_action(
ActionDefinition::new("Heal".to_string())
.with_roll(DiceRoll::new("Heal".to_string(), 6, 0).constant(6))
)
} else { todo!() },
Entry::Spell(_spell) => todo!(),
}
@ -113,7 +119,7 @@ impl Character<Entry> for Pathfinder2rCharacterSheet {
Entry::Spell(_) => v = vec![("cast", 1)],
};
v.iter()
.map(|s| ActionDefinition { name: s.0.to_string(), targets: s.1 })
.map(|s| ActionDefinition { name: s.0.to_string(), targets: s.1, display_name: None, source: None, rolls: None })
.collect()
}
}