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
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -1031,6 +1031,7 @@ name = "open_tavern"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"base64",
|
||||
"futures-util",
|
||||
"parking_lot",
|
||||
"serde",
|
||||
|
|
|
@ -12,3 +12,4 @@ sqlx = { version = "0.8.2", features = ["runtime-tokio", "sqlite"] }
|
|||
tavern_macros = { version = "0.1.0", path = "tavern_macros" }
|
||||
parking_lot = "0.12.3"
|
||||
futures-util = "0.3.30"
|
||||
base64 = "0.22.1"
|
||||
|
|
BIN
assets/pf2r/tokens/louise.jpg
Normal file
BIN
assets/pf2r/tokens/louise.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 150 KiB |
|
@ -4,9 +4,11 @@
|
|||
<script src="./socket.js"></script>
|
||||
<script>
|
||||
// init - game view (map)
|
||||
const GRID_SIZE = 200; // Grid size in pixels
|
||||
var mapScale = 1.0;
|
||||
var mapOffsetX = 0.0;
|
||||
var mapOffsetY = 0.0;
|
||||
|
||||
function init() {
|
||||
let view = document.getElementById('game-view');
|
||||
view.onwheel = onGameViewScroll;
|
||||
|
@ -35,6 +37,11 @@
|
|||
}
|
||||
// focus on the username field for the sake of just pressing enter multiple times
|
||||
document.getElementById('login-username').focus();
|
||||
document.body.onclick = (e) => {
|
||||
document.getElementById('msg-context-menu').style.display = 'none';
|
||||
}
|
||||
// TODO: Remove when done dev-ing
|
||||
tavern.onmessage({ text: 'test', id: 1, source: 'rusty', character: 'bart' });
|
||||
}
|
||||
|
||||
tavern.onlogin = (s) => {
|
||||
|
@ -45,6 +52,7 @@
|
|||
game.style.display = 'flex';
|
||||
// get last 50 msgs (i think that is enough for now) when we get in
|
||||
tavern.get_last_msgs(50);
|
||||
tavern.get_tokens();
|
||||
}
|
||||
else {
|
||||
alert("Invalid username or password!");
|
||||
|
@ -57,13 +65,21 @@
|
|||
// #abusing_style_order_as_both_id_variable_and_forcing_chronological_order
|
||||
msg.style.order = m.id;
|
||||
msg.innerHTML = `
|
||||
<b style='align-self: center;'>${m.source}</b>
|
||||
<br>
|
||||
<p style='align-self: center; margin: 4px 0;'>
|
||||
<b>${m.character ?? ''}</b>
|
||||
(${m.source})
|
||||
</p>
|
||||
<hr style='width: 75%;' />
|
||||
<p style='margin: 4px 2px;'>
|
||||
${m.text}
|
||||
</p>
|
||||
`
|
||||
msg.oncontextmenu = () => {
|
||||
document.getElementById('msg-context-menu').style.display = 'flex';
|
||||
msg.oncontextmenu = (e) => {
|
||||
if(e.shiftKey) { return true; }
|
||||
let cm = document.getElementById('msg-context-menu');
|
||||
cm.style.display = 'flex';
|
||||
cm.style.top = `${e.pageY}px`;
|
||||
cm.style.left = `${e.pageX}px`;
|
||||
return false;
|
||||
}
|
||||
let history = document.getElementById('chat-history');
|
||||
|
@ -75,6 +91,26 @@
|
|||
history.appendChild(msg);
|
||||
msg.scrollIntoView();
|
||||
}
|
||||
tavern.onspawntoken = (t) => {
|
||||
console.log(t);
|
||||
let map = document.getElementById('map');
|
||||
let token = document.createElement('div');
|
||||
token.className = 'token';
|
||||
token.style.top = `${t.y * GRID_SIZE}px`;
|
||||
token.style.left = `${t.x * GRID_SIZE}px`;
|
||||
token.token_id = t.token_id;
|
||||
token.innerHTML = `
|
||||
<img src='data:image/jpg;base64,${t.img}'>
|
||||
`
|
||||
map.append(token);
|
||||
}
|
||||
tavern.onmovetoken = (m) => {
|
||||
let token = Array.from(document.getElementsByClassName('token')).filter(t => t.token_id == m.token_id)[0]
|
||||
if(token) {
|
||||
token.style.top = `${m.y * GRID_SIZE}px`;
|
||||
token.style.left = `${m.x * GRID_SIZE}px`;
|
||||
}
|
||||
}
|
||||
function onLoginClick() {
|
||||
let username = document.getElementById('login-username').value;
|
||||
let pass = document.getElementById('login-pass').value;
|
||||
|
@ -152,6 +188,36 @@
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
#msg-context-menu {
|
||||
display: none;
|
||||
position: absolute;
|
||||
z-index: 1000;
|
||||
padding: 4px;
|
||||
}
|
||||
#msg-context-menu ul {
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
list-style: none;
|
||||
}
|
||||
#msg-context-menu ul li {
|
||||
padding: 4px;
|
||||
|
||||
}
|
||||
#msg-context-menu ul li:hover {
|
||||
background: darkgray;
|
||||
cursor: pointer;
|
||||
}
|
||||
.token {
|
||||
position: absolute;
|
||||
transition:
|
||||
top 0.5s ease-in,
|
||||
left 0.5s ease-in;
|
||||
}
|
||||
.token img {
|
||||
cursor: grab;
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
}
|
||||
</style>
|
||||
|
||||
</head>
|
||||
|
@ -184,13 +250,12 @@
|
|||
</div>
|
||||
<div id="map" style="position:absolute;">
|
||||
<img src="https://rustystriker.dev/molly.jpg" height="200%" >
|
||||
<img src="https://rustystriker.dev/louise.jpg" height="10%" style="position: absolute; left:20px; top: 20px;" >
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="msg-context-menu" class="chat-message" style="display: none; position: absolute; z-index: 1000; top: 0px;">
|
||||
<div id="msg-context-menu" class="chat-message">
|
||||
<ul>
|
||||
<li onclick='document.getElementById("msg-context-menu").style.display="none"'>Edit</li>
|
||||
<li>Delete (TODO)</li>
|
||||
</ul>
|
||||
</div>
|
||||
</body>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
const tavern = {
|
||||
socket: socket = new WebSocket('ws:/' + window.location.host + '/ws'),
|
||||
msgs: [],
|
||||
connected: false,
|
||||
loggedIn: false,
|
||||
call: (f, ...args) => {
|
||||
|
@ -8,6 +9,27 @@ const tavern = {
|
|||
}
|
||||
}
|
||||
};
|
||||
tavern.add_msg_to_history = (m) => {
|
||||
let id = m.id - 1;
|
||||
if(id >= 0) {
|
||||
if(id < tavern.msgs.length) {
|
||||
if(tavern.msgs[id].id == id + 1) {
|
||||
tavern.msgs[id] = m;
|
||||
}
|
||||
else {
|
||||
for(let i = 0; i < tavern.msgs.length; i += 1) {
|
||||
if(tavern.msgs[i].id > id) {
|
||||
tavern.msgs.splice(i, 0, m);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
tavern.msgs.push(m)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tavern.socket.onopen = () => tavern.connected = true;
|
||||
tavern.socket.onmessage = (m) => {
|
||||
|
@ -18,13 +40,21 @@ tavern.socket.onmessage = (m) => {
|
|||
tavern.call(tavern.onlogin, tavern.socket.loggedIn);
|
||||
}
|
||||
if(m.message) {
|
||||
tavern.call(tavern.onmessage, m.message)
|
||||
tavern.add_msg_to_history(m.message);
|
||||
tavern.call(tavern.onmessage, m.message);
|
||||
}
|
||||
if(m.get_chat_history) {
|
||||
m.get_chat_history.forEach(msg => {
|
||||
tavern.add_msg_to_history(msg);
|
||||
tavern.call(tavern.onmessage, msg);
|
||||
});
|
||||
}
|
||||
if(m.spawn_token) {
|
||||
tavern.call(tavern.onspawntoken, m.spawn_token);
|
||||
}
|
||||
if(m.move_token) {
|
||||
tavern.call(tavern.onmovetoken, m.move_token);
|
||||
}
|
||||
}
|
||||
tavern.login = (username, password) => {
|
||||
if(!tavern.connected || tavern.loggedIn) { return false; }
|
||||
|
@ -32,7 +62,14 @@ tavern.login = (username, password) => {
|
|||
}
|
||||
tavern.simple_msg = (msg, token) => {
|
||||
if(!tavern.connected || tavern.loggedIn) { return false; }
|
||||
tavern.socket.send(JSON.stringify({ message: { text: msg, source: token ?? "" } }));
|
||||
tavern.socket.send(JSON.stringify({ message: { text: msg, character: token ?? "" } }));
|
||||
}
|
||||
tavern.edit_msg = (new_text, id) => {
|
||||
if(id <= tavern.msgs.length && id > 0) {
|
||||
let msg = tavern.msgs[id - 1];
|
||||
msg.text = new_text;
|
||||
tavern.socket.send(JSON.stringify({ message: msg }));
|
||||
}
|
||||
}
|
||||
tavern.get_chat_history = (from, amount) => {
|
||||
if(!tavern.connected || tavern.loggedIn) { return false; }
|
||||
|
@ -42,3 +79,11 @@ tavern.get_last_msgs = (amount) => {
|
|||
if(!tavern.connected || tavern.loggedIn) { return false; }
|
||||
tavern.socket.send(JSON.stringify({ get_last_messages: { amount: amount } }))
|
||||
}
|
||||
tavern.get_tokens = () => {
|
||||
if(!tavern.connected || tavern.loggedIn) { return false; }
|
||||
tavern.socket.send(JSON.stringify('get_tokens'));
|
||||
}
|
||||
tavern.move_token = (id, x, y) => {
|
||||
if(!tavern.connected || tavern.loggedIn) { return false; }
|
||||
tavern.socket.send(JSON.stringify({ move_token: { token_id: id, x: x, y: y } }));
|
||||
}
|
|
@ -15,7 +15,7 @@
|
|||
|
||||
[ ] impl different requests
|
||||
[ ] actual normal login
|
||||
[ ] allow sending of old info
|
||||
[x] allow sending of old info
|
||||
[x] chat history
|
||||
[ ] send texture (map/token/image)
|
||||
[ ] force show something
|
||||
|
|
|
@ -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 {
|
||||
|
|
94
src/lib.rs
94
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::Error => {}
|
||||
api::Request::Login(_) => {}
|
||||
api::Request::Message(mut msg) => {
|
||||
if msg.id == 0 || msg.id >= self.chat.len() {
|
||||
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…
Reference in a new issue