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"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"axum",
|
"axum",
|
||||||
|
"base64",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"serde",
|
"serde",
|
||||||
|
|
|
@ -12,3 +12,4 @@ sqlx = { version = "0.8.2", features = ["runtime-tokio", "sqlite"] }
|
||||||
tavern_macros = { version = "0.1.0", path = "tavern_macros" }
|
tavern_macros = { version = "0.1.0", path = "tavern_macros" }
|
||||||
parking_lot = "0.12.3"
|
parking_lot = "0.12.3"
|
||||||
futures-util = "0.3.30"
|
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 src="./socket.js"></script>
|
||||||
<script>
|
<script>
|
||||||
// init - game view (map)
|
// init - game view (map)
|
||||||
|
const GRID_SIZE = 200; // Grid size in pixels
|
||||||
var mapScale = 1.0;
|
var mapScale = 1.0;
|
||||||
var mapOffsetX = 0.0;
|
var mapOffsetX = 0.0;
|
||||||
var mapOffsetY = 0.0;
|
var mapOffsetY = 0.0;
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
let view = document.getElementById('game-view');
|
let view = document.getElementById('game-view');
|
||||||
view.onwheel = onGameViewScroll;
|
view.onwheel = onGameViewScroll;
|
||||||
|
@ -35,6 +37,11 @@
|
||||||
}
|
}
|
||||||
// focus on the username field for the sake of just pressing enter multiple times
|
// focus on the username field for the sake of just pressing enter multiple times
|
||||||
document.getElementById('login-username').focus();
|
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) => {
|
tavern.onlogin = (s) => {
|
||||||
|
@ -45,6 +52,7 @@
|
||||||
game.style.display = 'flex';
|
game.style.display = 'flex';
|
||||||
// get last 50 msgs (i think that is enough for now) when we get in
|
// get last 50 msgs (i think that is enough for now) when we get in
|
||||||
tavern.get_last_msgs(50);
|
tavern.get_last_msgs(50);
|
||||||
|
tavern.get_tokens();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
alert("Invalid username or password!");
|
alert("Invalid username or password!");
|
||||||
|
@ -57,13 +65,21 @@
|
||||||
// #abusing_style_order_as_both_id_variable_and_forcing_chronological_order
|
// #abusing_style_order_as_both_id_variable_and_forcing_chronological_order
|
||||||
msg.style.order = m.id;
|
msg.style.order = m.id;
|
||||||
msg.innerHTML = `
|
msg.innerHTML = `
|
||||||
<b style='align-self: center;'>${m.source}</b>
|
<p style='align-self: center; margin: 4px 0;'>
|
||||||
<br>
|
<b>${m.character ?? ''}</b>
|
||||||
|
(${m.source})
|
||||||
|
</p>
|
||||||
<hr style='width: 75%;' />
|
<hr style='width: 75%;' />
|
||||||
${m.text}
|
<p style='margin: 4px 2px;'>
|
||||||
|
${m.text}
|
||||||
|
</p>
|
||||||
`
|
`
|
||||||
msg.oncontextmenu = () => {
|
msg.oncontextmenu = (e) => {
|
||||||
document.getElementById('msg-context-menu').style.display = 'flex';
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
let history = document.getElementById('chat-history');
|
let history = document.getElementById('chat-history');
|
||||||
|
@ -75,6 +91,26 @@
|
||||||
history.appendChild(msg);
|
history.appendChild(msg);
|
||||||
msg.scrollIntoView();
|
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() {
|
function onLoginClick() {
|
||||||
let username = document.getElementById('login-username').value;
|
let username = document.getElementById('login-username').value;
|
||||||
let pass = document.getElementById('login-pass').value;
|
let pass = document.getElementById('login-pass').value;
|
||||||
|
@ -152,6 +188,36 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
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>
|
</style>
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
|
@ -184,13 +250,12 @@
|
||||||
</div>
|
</div>
|
||||||
<div id="map" style="position:absolute;">
|
<div id="map" style="position:absolute;">
|
||||||
<img src="https://rustystriker.dev/molly.jpg" height="200%" >
|
<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>
|
||||||
</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>
|
<ul>
|
||||||
<li onclick='document.getElementById("msg-context-menu").style.display="none"'>Edit</li>
|
<li>Delete (TODO)</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
const tavern = {
|
const tavern = {
|
||||||
socket: socket = new WebSocket('ws:/' + window.location.host + '/ws'),
|
socket: socket = new WebSocket('ws:/' + window.location.host + '/ws'),
|
||||||
|
msgs: [],
|
||||||
connected: false,
|
connected: false,
|
||||||
loggedIn: false,
|
loggedIn: false,
|
||||||
call: (f, ...args) => {
|
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.onopen = () => tavern.connected = true;
|
||||||
tavern.socket.onmessage = (m) => {
|
tavern.socket.onmessage = (m) => {
|
||||||
|
@ -18,13 +40,21 @@ tavern.socket.onmessage = (m) => {
|
||||||
tavern.call(tavern.onlogin, tavern.socket.loggedIn);
|
tavern.call(tavern.onlogin, tavern.socket.loggedIn);
|
||||||
}
|
}
|
||||||
if(m.message) {
|
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) {
|
if(m.get_chat_history) {
|
||||||
m.get_chat_history.forEach(msg => {
|
m.get_chat_history.forEach(msg => {
|
||||||
|
tavern.add_msg_to_history(msg);
|
||||||
tavern.call(tavern.onmessage, 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) => {
|
tavern.login = (username, password) => {
|
||||||
if(!tavern.connected || tavern.loggedIn) { return false; }
|
if(!tavern.connected || tavern.loggedIn) { return false; }
|
||||||
|
@ -32,7 +62,14 @@ tavern.login = (username, password) => {
|
||||||
}
|
}
|
||||||
tavern.simple_msg = (msg, token) => {
|
tavern.simple_msg = (msg, token) => {
|
||||||
if(!tavern.connected || tavern.loggedIn) { return false; }
|
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) => {
|
tavern.get_chat_history = (from, amount) => {
|
||||||
if(!tavern.connected || tavern.loggedIn) { return false; }
|
if(!tavern.connected || tavern.loggedIn) { return false; }
|
||||||
|
@ -42,3 +79,11 @@ tavern.get_last_msgs = (amount) => {
|
||||||
if(!tavern.connected || tavern.loggedIn) { return false; }
|
if(!tavern.connected || tavern.loggedIn) { return false; }
|
||||||
tavern.socket.send(JSON.stringify({ get_last_messages: { amount: amount } }))
|
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
|
[ ] impl different requests
|
||||||
[ ] actual normal login
|
[ ] actual normal login
|
||||||
[ ] allow sending of old info
|
[x] allow sending of old info
|
||||||
[x] chat history
|
[x] chat history
|
||||||
[ ] send texture (map/token/image)
|
[ ] send texture (map/token/image)
|
||||||
[ ] force show something
|
[ ] force show something
|
||||||
|
|
|
@ -15,7 +15,11 @@ pub enum Request {
|
||||||
Message(ChatMessage),
|
Message(ChatMessage),
|
||||||
GetChatHistory { amount: usize, from: usize },
|
GetChatHistory { amount: usize, from: usize },
|
||||||
GetLastMessages { amount: usize, },
|
GetLastMessages { amount: usize, },
|
||||||
|
GetTokens,
|
||||||
|
SpawnToken { x: i32, y: i32, img_path: String },
|
||||||
|
MoveToken { token_id: usize, x: i32, y: i32 },
|
||||||
Quit,
|
Quit,
|
||||||
|
Kick(String),
|
||||||
Shutdown
|
Shutdown
|
||||||
}
|
}
|
||||||
#[derive(Serialize, Clone)]
|
#[derive(Serialize, Clone)]
|
||||||
|
@ -25,6 +29,8 @@ pub enum Response {
|
||||||
Login(login::LoginResult),
|
Login(login::LoginResult),
|
||||||
Message(ChatMessage),
|
Message(ChatMessage),
|
||||||
GetChatHistory(Vec<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 },
|
Quit { id: String },
|
||||||
Shutdown,
|
Shutdown,
|
||||||
|
|
||||||
|
|
|
@ -1,24 +1,107 @@
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
|
||||||
pub struct ChatMessage {
|
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`
|
/// 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,
|
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
|
/// Rolls of the action, if not empty a roll should happen before
|
||||||
pub roll: Option<Vec<RollDialogOption>>,
|
pub roll: Option<Vec<RollDialogOption>>,
|
||||||
/// Optional roll target
|
/// Optional roll target
|
||||||
pub roll_target: Option<i32>,
|
pub roll_target: Option<i32>,
|
||||||
/// Optional action buttons, for a chat message this will be empty
|
/// Optional action buttons, for a chat message this will be empty
|
||||||
pub actions: Option<Vec<String>>,
|
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
|
/// Targets of the action, for a chat message this will be empty
|
||||||
pub targets: Option<Vec<String>>,
|
pub targets: Option<Vec<String>>,
|
||||||
/// message id, should be left emitted or 0 for new messages
|
/// message id, should be left emitted or 0 for new messages
|
||||||
#[serde(default = "default_id")]
|
#[serde(default = "default_id")]
|
||||||
pub id: usize,
|
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 }
|
fn default_id() -> usize { 0 }
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ pub trait GameEntry : Serialize + Sized {
|
||||||
/// Get all categories (such as weapons/consumables/spells)
|
/// Get all categories (such as weapons/consumables/spells)
|
||||||
fn categories() -> Vec<String>;
|
fn categories() -> Vec<String>;
|
||||||
/// returns a chat message to show the entry (with description and all)
|
/// 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 {
|
pub struct ActionDefinition {
|
||||||
|
|
94
src/lib.rs
94
src/lib.rs
|
@ -1,75 +1,109 @@
|
||||||
use game::{chat_message::ChatMessage, Game, GameImpl};
|
use game::{chat_message::ChatMessage, Game, GameImpl};
|
||||||
use tokio::sync::{broadcast, mpsc};
|
use tokio::sync::{broadcast, mpsc};
|
||||||
|
|
||||||
pub mod user;
|
|
||||||
pub mod table;
|
|
||||||
pub mod api;
|
pub mod api;
|
||||||
pub mod game;
|
pub mod game;
|
||||||
pub mod pathfinder2r_impl;
|
pub mod pathfinder2r_impl;
|
||||||
|
pub mod table;
|
||||||
|
pub mod user;
|
||||||
|
|
||||||
pub struct GameServer {
|
pub struct GameServer {
|
||||||
_game: Game<pathfinder2r_impl::Pathfinder2rCharacterSheet, pathfinder2r_impl::entry::Entry>,
|
_game: Game<pathfinder2r_impl::Pathfinder2rCharacterSheet, pathfinder2r_impl::entry::Entry>,
|
||||||
|
tokens: Vec<(String, i32, i32)>,
|
||||||
chat: Vec<(String, ChatMessage)>,
|
chat: Vec<(String, ChatMessage)>,
|
||||||
}
|
}
|
||||||
impl GameServer {
|
impl GameServer {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
_game: Game::new(),
|
_game: Game::new(),
|
||||||
|
tokens: vec![("assets/pf2r/tokens/louise.jpg".to_string(), 2, 2)],
|
||||||
chat: Vec::new(),
|
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 {
|
while let Some(req) = msgs.recv().await {
|
||||||
// TODO: do stuff yo!
|
// TODO: do stuff yo!
|
||||||
let (id, req) = req;
|
let (id, req) = req;
|
||||||
println!("Got message from {}: {:?}", &id, &req);
|
println!("Got message from {}: {:?}", &id, &req);
|
||||||
|
|
||||||
match req {
|
match req {
|
||||||
api::Request::Error => {},
|
api::Request::Error => {}
|
||||||
api::Request::Login(_) => {},
|
api::Request::Login(_) => {}
|
||||||
api::Request::Message(mut msg) => {
|
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
|
msg.id = self.chat.len() + 1; // set the message id, 0 is invalid
|
||||||
}
|
}
|
||||||
// TODO: check if the editor is an admin as well
|
// TODO: check if the editor is an admin as well
|
||||||
else if id == self.chat[msg.id].0 {
|
else if id == self.chat[msg.id - 1].0 {
|
||||||
self.chat[msg.id] = (id.clone(), msg.clone());
|
self.chat[msg.id - 1] = (id.clone(), msg.clone());
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
// if its an edit message and editor is not the owner, skip
|
// if its an edit message and editor is not the owner, skip
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if msg.source.is_empty() {
|
// Force the sender id to be the new id of the message, even if an id was provided
|
||||||
msg.source = format!("({})", id.clone());
|
msg.source = id.clone();
|
||||||
}
|
|
||||||
else {
|
|
||||||
msg.source = format!("{} ({})", msg.source, id.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
self.chat.push((id.clone(), msg.clone()));
|
self.chat.push((id.clone(), msg.clone()));
|
||||||
_ = broadcast.send((None, api::Response::Message(msg)));
|
if msg.whisper.is_some() {
|
||||||
},
|
_ = broadcast.send((Some(id.clone()), api::Response::Message(msg.clone())));
|
||||||
api::Request::Quit => { _ = broadcast.send((None, api::Response::Quit { id }))},
|
}
|
||||||
api::Request::Shutdown => todo!(),
|
_ = broadcast.send((msg.whisper.clone(), api::Response::Message(msg)));
|
||||||
api::Request::GetChatHistory { mut amount, from: last_msg } => {
|
}
|
||||||
if amount == 0 { amount = self.chat.len(); }
|
api::Request::GetChatHistory {
|
||||||
let history: Vec<ChatMessage> = self.chat.iter()
|
mut amount,
|
||||||
|
from: last_msg,
|
||||||
|
} => {
|
||||||
|
if amount == 0 {
|
||||||
|
amount = self.chat.len();
|
||||||
|
}
|
||||||
|
let history: Vec<ChatMessage> = self
|
||||||
|
.chat
|
||||||
|
.iter()
|
||||||
.skip(last_msg)
|
.skip(last_msg)
|
||||||
.take(amount)
|
.take(amount)
|
||||||
.map(|m| m.1.clone())
|
.map(|m| m.1.clone())
|
||||||
.collect();
|
.collect();
|
||||||
_ = broadcast.send((Some(id), api::Response::GetChatHistory(history)));
|
_ = broadcast.send((Some(id), api::Response::GetChatHistory(history)));
|
||||||
},
|
}
|
||||||
api::Request::GetLastMessages { mut amount } => {
|
api::Request::GetLastMessages { mut amount } => {
|
||||||
if amount == 0 { amount = self.chat.len(); }
|
if amount == 0 {
|
||||||
let start = if amount >= self.chat.len() { self.chat.len() } else { self.chat.len() - amount };
|
amount = self.chat.len();
|
||||||
let history: Vec<ChatMessage> = self.chat.iter()
|
}
|
||||||
.skip(start)
|
let start = if amount >= self.chat.len() {
|
||||||
.map(|m| m.1.clone())
|
self.chat.len()
|
||||||
.collect();
|
} 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)));
|
_ = 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));
|
_ = 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 {
|
if let Ok(msg) = msg {
|
||||||
match msg {
|
match msg {
|
||||||
Message::Text(t) => {
|
Message::Text(t) => {
|
||||||
|
println!("Got message from {}: {}", &id, &t);
|
||||||
let req = serde_json::from_str::<Request>(&t).unwrap_or_default();
|
let req = serde_json::from_str::<Request>(&t).unwrap_or_default();
|
||||||
let erred = msend.send((id.clone(), req)).await.is_err();
|
let erred = msend.send((id.clone(), req)).await.is_err();
|
||||||
if erred {
|
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 {
|
while let Ok((to_id, msg)) = brecv.recv().await {
|
||||||
if to_id.is_none() || to_id.map(|t| t == id).unwrap_or(false) {
|
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();
|
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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,16 +35,9 @@ impl GameEntry for Entry {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_chat(&self, source: &str) -> ChatMessage {
|
fn to_chat(&self) -> ChatMessage {
|
||||||
ChatMessage {
|
let text = format!("{} - it might be a {{weapon/short_bow}} it might not", self.display_name());
|
||||||
text: format!("{} - it might be a {{weapon/short_bow}} it might not", self.display_name()),
|
ChatMessage::new(text)
|
||||||
roll: None,
|
|
||||||
roll_target: None,
|
|
||||||
actions: None,
|
|
||||||
source: String::from(source),
|
|
||||||
targets: None,
|
|
||||||
id: 0,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn all(_filter: Option<&str>) -> Vec<String> {
|
fn all(_filter: Option<&str>) -> Vec<String> {
|
||||||
|
|
|
@ -88,25 +88,15 @@ impl Pathfinder2rCharacterSheet {
|
||||||
impl Character<Entry> for Pathfinder2rCharacterSheet {
|
impl Character<Entry> for Pathfinder2rCharacterSheet {
|
||||||
fn use_action(&mut self, entry: &Entry, action: &ActionResult) -> ChatMessage {
|
fn use_action(&mut self, entry: &Entry, action: &ActionResult) -> ChatMessage {
|
||||||
match entry {
|
match entry {
|
||||||
Entry::Weapon(_) => ChatMessage {
|
Entry::Weapon(_) => ChatMessage::new("Attack".to_string())
|
||||||
text: String::from("Attack"),
|
.with_roll(RollDialogOption { name: String::from("pierce"), dice_type: 4, dice_amount: 1, constant: 0, extra: String::new(), enabled: true })
|
||||||
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))
|
||||||
roll_target: Some(10),
|
.with_action("damage".to_string())
|
||||||
actions: Some(vec!["damage".to_string(), "double".to_string()]),
|
.with_action("double".to_string()),
|
||||||
source: self.name.clone(),
|
|
||||||
targets: None,
|
|
||||||
id: 0,
|
|
||||||
},
|
|
||||||
Entry::Consumable(_consumable) => if action.name == "consume" {
|
Entry::Consumable(_consumable) => if action.name == "consume" {
|
||||||
ChatMessage {
|
ChatMessage::new("Heal".to_string())
|
||||||
text: "Heal".to_string(),
|
.with_roll(RollDialogOption { name: "heal".to_string(), dice_type: 6, dice_amount: 1, constant: 0, extra: String::new(), enabled: true })
|
||||||
roll: Some(vec![RollDialogOption { name: "heal".to_string(), dice_type: 6, dice_amount: 1, constant: 0, extra: String::new(), enabled: true }]),
|
.with_action("heal".to_string())
|
||||||
roll_target: None,
|
|
||||||
actions: Some(vec!["heal".to_string()]),
|
|
||||||
source: self.name.clone(),
|
|
||||||
targets: None,
|
|
||||||
id: 0,
|
|
||||||
}
|
|
||||||
} else { todo!() },
|
} else { todo!() },
|
||||||
Entry::Spell(_spell) => todo!(),
|
Entry::Spell(_spell) => todo!(),
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue