diff --git a/assets/web/app.js b/assets/web/app.js index 1d341b5..fd1755a 100644 --- a/assets/web/app.js +++ b/assets/web/app.js @@ -7,26 +7,26 @@ var draggedToken = { token: null, offX: 0, offY: 0 }; var draggedDiv = { div: null, offX: 0, offY: 0 }; function init() { - let view = document.getElementById('game-view'); + let view = document.getElementById('game-view'); view.onwheel = onGameViewScroll; view.onmousemove = onGameMouseMove; view.onmouseup = onGameMouseUp; view.oncontextmenu = () => false; // allow sending chat message using enter (and shift-enter for new line) document.getElementById('newmsg-content').onkeypress = (e) => { - if(e.key == "Enter" && !e.shiftKey) { + if (e.key == "Enter" && !e.shiftKey) { sendChatMessage(); return false; } } document.getElementById('login-username').onkeypress = (e) => { - if(e.key == 'Enter') { + if (e.key == 'Enter') { document.getElementById('login-pass').focus(); return false; } } document.getElementById('login-pass').onkeypress = (e) => { - if(e.key == 'Enter') { + if (e.key == 'Enter') { onLoginClick(); return false; } @@ -38,19 +38,17 @@ function init() { } document.body.onmousemove = onMoveableDivDrag; document.body.onmouseup = onMoveableDivMouseUp; - // TODO: Remove when done dev-ing - tavern.onmessage({ text: 'test', id: 1, source: 'rusty', character: 'bart' }); } tavern.onlogin = (s) => { - if(s) { + if (s) { let login = document.getElementById('login-screen'); let game = document.getElementById('game'); login.style.display = 'none'; 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(); + tavern.get_last_msgs(50); + tavern.get_current_scene(); } else { alert("Invalid username or password!"); @@ -72,15 +70,15 @@ tavern.onmessage = (m) => { ${m.text.replace('\n', '\n
\n')}

` - msg.oncontextmenu = (e) => { - if(e.shiftKey) { return true; } + 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; } - if(m.actions) { + if (m.actions) { let holder = document.createElement('div'); holder.style.display = 'flex'; holder.style.flexWrap = 'wrap'; @@ -98,7 +96,7 @@ tavern.onmessage = (m) => { let history = document.getElementById('chat-history'); // this is to force update everytime we get a duplicate msg to allow msg editing (yay) let exists = Array.from(history.children).filter(e => e.style.order == m.id)[0]; - if(exists) { + if (exists) { history.removeChild(exists); } history.appendChild(msg); @@ -126,15 +124,24 @@ tavern.onspawntoken = (t) => { } tavern.onmovetoken = (m) => { let token = Array.from(document.getElementsByClassName('token')).filter(t => t.token_id == m.token_id)[0] - if(token) { + if (token) { token.style.top = `${m.y * GRID_SIZE}px`; token.style.left = `${m.x * GRID_SIZE}px`; } } +tavern.onshowscene = (show) => { + let map = document.getElementById('map'); + // Remove existing tokens + Array.from(map.children).filter(c => c.classList.contains('token')).forEach(c => map.removeChild(c)); + + for (let token of show.tokens) { + tavern.onspawntoken(token); + } +} function onLoginClick() { let username = document.getElementById('login-username').value; let pass = document.getElementById('login-pass').value; - if(username == 'test') { + if (username == 'test') { // TODO: Remove this for when im done dev-ing with this file tavern.onlogin(true); } @@ -143,11 +150,11 @@ function onLoginClick() { function onGameViewScroll(event) { let map = document.getElementById('map'); mapScale += (event.wheelDelta / 1800.0); - if(mapScale < 0.1) { mapScale = 0.1; } + if (mapScale < 0.1) { mapScale = 0.1; } map.style.transform = `scale(${mapScale})`; } function onGameMouseMove(event) { - if(event.buttons == 2) { + if (event.buttons == 2) { // right click let map = document.getElementById('map'); let mult = event.ctrlKey ? 2.0 : 1.0; @@ -156,7 +163,7 @@ function onGameMouseMove(event) { map.style.left = `${mapOffsetX}px`; map.style.top = `${mapOffsetY}px`; } - else if(draggedToken.token != null && event.buttons == 1) { + else if (draggedToken.token != null && event.buttons == 1) { let top = (event.clientY - mapOffsetY) / mapScale - draggedToken.offY; let left = (event.clientX - mapOffsetX) / mapScale - draggedToken.offX; draggedToken.token.style.top = `${top}px`; @@ -164,7 +171,7 @@ function onGameMouseMove(event) { } } function onGameMouseUp() { - if(draggedToken.token != null) { + if (draggedToken.token != null) { let t = draggedToken.token; let x = Math.floor(0.5 + parseInt(t.style.left) / GRID_SIZE); let y = Math.floor(0.5 + parseInt(t.style.top) / GRID_SIZE); @@ -186,7 +193,7 @@ function openRollsPopup(action) { let holder = document.getElementById('dice-roll-holder'); holder.innerHTML = ''; // remove all holder children holder.action = action; - if(action.rolls != undefined && Array.isArray(action.rolls)) { + if (action.rolls != undefined && Array.isArray(action.rolls)) { for (const r of action.rolls) { // name (extra) (dice_amount)d(dice_type) + constant | enabled console.log(r); @@ -211,8 +218,8 @@ function rollPopup() { // get the holder and start rolling dice let rolls = []; let holder = document.getElementById('dice-roll-holder'); - for(const h of holder.children) { - if(h.roll && h.children[2].checked) { + for (const h of holder.children) { + if (h.roll && h.children[2].checked) { let roll = { name: h.roll.name, extra: h.roll.extra }; let msg = ''; let sum = 0; @@ -221,12 +228,12 @@ function rollPopup() { let roll = Math.floor(Math.random() * h.roll.dice_type) + 1; sum += roll; msg += `${roll}`; - if(i != h.roll.dice_amount - 1 || h.roll.constant != 0) { + if (i != h.roll.dice_amount - 1 || h.roll.constant != 0) { msg += ' + '; } } - if(h.roll.constant != 0) { - sum += h.roll.constant; + if (h.roll.constant != 0) { + sum += h.roll.constant; msg += `${h.roll.constant}`; } roll.result = sum; @@ -238,7 +245,7 @@ function rollPopup() { tavern.action_result(holder.action.name, 'Louise', [], rolls); } function onMoveableDivMouseDown(e, id) { - if(e.buttons == 1) { + if (e.buttons == 1) { let div = document.getElementById(id); let rect = div.getBoundingClientRect(); draggedDiv.div = div; @@ -248,7 +255,7 @@ function onMoveableDivMouseDown(e, id) { } } function onMoveableDivDrag(e) { - if(draggedDiv.div) { + if (draggedDiv.div) { draggedDiv.div.style.right = ''; draggedDiv.div.style.top = `${e.clientY - draggedDiv.offY}px`; draggedDiv.div.style.left = `${e.clientX - draggedDiv.offX}px`; diff --git a/assets/web/style.css b/assets/web/style.css index fbba9dc..8d391a1 100644 --- a/assets/web/style.css +++ b/assets/web/style.css @@ -1,33 +1,38 @@ -html, body { +html, +body { margin: 0; height: 100%; } -body{ + +body { background-color: rgb(32, 35, 35); color: white; } + #side-panel { - display: flex; + display: flex; flex-direction: row; justify-content: center; - resize: horizontal; - overflow: auto; - border: 0px solid black; - width: 20%; + resize: horizontal; + overflow: auto; + border: 0px solid black; + width: 20%; min-width: 10%; max-width: 50%; background-image: linear-gradient(135deg, #0f0f2f 0px, #0f0f2f 99%, rgb(188, 255, 185) 100%); } + #chat-history { - display: flex; - flex-direction: column; - width: 100%; - height: 90%; - resize: none; - overflow: auto; - margin-top: 10px; - background-color:#0f0f2f; + display: flex; + flex-direction: column; + width: 100%; + height: 90%; + resize: none; + overflow: auto; + margin-top: 10px; + background-color: #0f0f2f; } + .chat-message { background-color: #ffffd6; color: #000000; @@ -40,33 +45,40 @@ body{ display: flex; flex-direction: column; } + #msg-context-menu { - display: none; - position: absolute; - z-index: 1000; + 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; } + .token-transition { - transition: - top 0.5s ease-in, - left 0.5s ease-in; + transition: + top 0.1s ease-in, + left 0.1s ease-in; } + .token img { cursor: grab; width: 200px; diff --git a/assets/web/tavern.js b/assets/web/tavern.js index 8bf1beb..0b52947 100644 --- a/assets/web/tavern.js +++ b/assets/web/tavern.js @@ -1,24 +1,25 @@ -const tavern = { +const tavern = { socket: socket = new WebSocket('ws:/' + window.location.host + '/ws'), msgs: [], connected: false, loggedIn: false, + currentScene: 0, call: (f, ...args) => { - if(typeof(f) == "function") { + if (typeof (f) == "function") { f(...args); } } }; 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) { + 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) { + for (let i = 0; i < tavern.msgs.length; i += 1) { + if (tavern.msgs[i].id > id) { tavern.msgs.splice(i, 0, m); break; } @@ -35,59 +36,67 @@ tavern.socket.onopen = () => tavern.connected = true; tavern.socket.onmessage = (m) => { m = JSON.parse(m.data); console.log(m); - if(m.login) { + if (m.login) { tavern.socket.loggedIn = m.login.success; tavern.call(tavern.onlogin, tavern.socket.loggedIn); } - if(m.message) { + if (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 => { tavern.add_msg_to_history(msg); tavern.call(tavern.onmessage, msg); }); } - if(m.spawn_token) { + if (m.spawn_token) { tavern.call(tavern.onspawntoken, m.spawn_token); } - if(m.move_token) { + if (m.move_token) { tavern.call(tavern.onmovetoken, m.move_token); } + if (m.show_scene) { + tavern.currentScene = m.show_scene.scene; + tavern.call(tavern.onshowscene, m.show_scene); + } } tavern.login = (username, password) => { - if(!tavern.connected || tavern.loggedIn) { return false; } - tavern.socket.send(JSON.stringify({ login: { username, password }})); + if (!tavern.connected || tavern.loggedIn) { return false; } + tavern.socket.send(JSON.stringify({ login: { username, password } })); } 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, character: token ?? "" } })); } tavern.edit_msg = (new_text, id) => { - if(id <= tavern.msgs.length && id > 0) { + 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; } + if (!tavern.connected || tavern.loggedIn) { return false; } tavern.socket.send(JSON.stringify({ get_chat_history: { from: from, amount: amount } })) } 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.get_tokens = () => { - if(!tavern.connected || tavern.loggedIn) { return false; } - tavern.socket.send(JSON.stringify('get_tokens')); +tavern.get_tokens = (mapId) => { + if (!tavern.connected || tavern.loggedIn) { return false; } + tavern.socket.send(JSON.stringify({ get_tokens: { scene: mapId } })); } 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 } })); + if (!tavern.connected || tavern.loggedIn) { return false; } + tavern.socket.send(JSON.stringify({ move_token: { token_id: id, x: x, y: y } })); } tavern.action_result = (name, source, targets, results) => { - if(!tavern.connected || tavern.loggedIn) { return false; } - tavern.socket.send(JSON.stringify({ action_result: { name: name, source: source ?? '', targets: targets ?? [], results: results } })); + if (!tavern.connected || tavern.loggedIn) { return false; } + tavern.socket.send(JSON.stringify({ action_result: { name: name, source: source ?? '', targets: targets ?? [], results: results } })); +} +tavern.get_current_scene = () => { + if (!tavern.connected || tavern.loggedIn) { return; } + tavern.socket.send(JSON.stringify('get_current_scene')) } \ No newline at end of file diff --git a/src/api/game_actions.rs b/src/api/game_actions.rs index 1ae98a2..1fc2bf8 100644 --- a/src/api/game_actions.rs +++ b/src/api/game_actions.rs @@ -1,7 +1,5 @@ //! General game actions -use serde::{Serialize, Deserialize}; - - +use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize)] pub struct Ping { @@ -14,23 +12,23 @@ pub struct Ping { #[derive(Serialize, Deserialize)] pub struct ShowImage { /// Which texture to show - pub texture: String + pub texture: String, } #[derive(Serialize, Deserialize, Clone)] pub struct SpawnToken { - pub token_id: usize, - pub x: f32, - pub y: f32, - pub img: String + pub token_id: usize, + pub x: f32, + pub y: f32, + pub img: String, } impl std::fmt::Debug for SpawnToken { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - // I dont want it to print the `img` field because it VERY long :) + // I dont want it to print the `img` field because it is VERY long :) f.debug_struct("SpawnToken") .field("token_id", &self.token_id) .field("x", &self.x) .field("y", &self.y) .finish() } -} \ No newline at end of file +} diff --git a/src/api/mod.rs b/src/api/mod.rs index 583c974..e0e6a97 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,5 +1,5 @@ -pub mod login; pub mod game_actions; +pub mod login; pub mod map_actions; use game_actions::SpawnToken; @@ -12,28 +12,60 @@ use crate::game::{character_sheet::EntryType, chat_message::ChatMessage, entry:: pub enum Request { #[default] Error, - // Connection requests + // Connection requests Login(login::LoginRequest), Quit, Kick(String), Shutdown, // Character stuff CreateCharacter, - CharacterDisplay { id: usize }, - CharacterInputs { id: usize }, - CharacterGetField { id: usize, field: String }, - CharacterSetField { id: usize, field: String, val: EntryType }, - CharacterAssign { id: usize, user: String }, + CharacterDisplay { + id: usize, + }, + CharacterInputs { + id: usize, + }, + CharacterGetField { + id: usize, + field: String, + }, + CharacterSetField { + id: usize, + field: String, + val: EntryType, + }, + CharacterAssign { + id: usize, + user: String, + }, // Chat requests Message(ChatMessage), - GetChatHistory { amount: usize, from: usize }, - GetLastMessages { amount: usize, }, + GetChatHistory { + amount: usize, + from: usize, + }, + GetLastMessages { + amount: usize, + }, // Map requests - GetTokens { scene: usize }, - SpawnToken { map_id: usize, character: String, x: f32, y: f32, img_path: String }, - MoveToken { token_id: usize, x: f32, y: f32 }, + GetCurrentScene, + GetTokens { + scene: usize, + }, + SpawnToken { + map_id: usize, + character: String, + x: f32, + y: f32, + img_path: String, + }, + MoveToken { + token_id: usize, + x: f32, + y: f32, + }, // Actions requests - ActionResult(ActionResult) + ActionResult(ActionResult), } #[derive(Serialize, Clone, Debug)] @@ -43,12 +75,12 @@ pub enum Response { Login(login::LoginResult), Message(ChatMessage), GetChatHistory(Vec), + ShowScene { scene: usize, tokens: Vec }, MoveToken { token_id: usize, x: f32, y: f32 }, SpawnToken(SpawnToken), CharacterCreated(usize), Quit { id: String }, Shutdown, - } #[derive(Serialize, Debug, Clone)] @@ -56,4 +88,4 @@ pub enum Response { pub enum RequestError { InvalidRequest, AlreadyLoggedIn, -} \ No newline at end of file +} diff --git a/src/lib.rs b/src/lib.rs index 885d758..c5ddc1a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -42,7 +42,7 @@ impl GameServer { 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_roll(DiceRoll::new("Fire".to_string(), 4, 2).enabled(false)), ) .with_action(ActionDefinition::new("Attack +3".to_string()).with_roll(DiceRoll::new( "Base".to_string(), @@ -158,6 +158,33 @@ impl GameServer { } } } + api::Request::GetCurrentScene => { + let scene = self.game.current_scene(); + let mut scene_tokens = self + .game + .available_tokens(scene) + .iter() + .map(|id| self.game.token_info(0, *id).map(|info| (id, info))) + .flatten() + .map(|(id, info)| SpawnToken { + token_id: *id, + x: info.x, + y: info.y, + img: info.img_source.clone(), + }) + .collect::>(); + for spawn in scene_tokens.iter_mut() { + let bits = std::fs::read(&spawn.img).expect("FAILED READING TOKEN IMAGE"); + spawn.img = base64::Engine::encode(&base64::prelude::BASE64_STANDARD, &bits); + } + _ = broadcast.send(( + Some(id.clone()), + api::Response::ShowScene { + scene: scene, + tokens: scene_tokens, + }, + )); + } api::Request::SpawnToken { map_id, character, diff --git a/src/main.rs b/src/main.rs index ef01f2d..18d5794 100644 --- a/src/main.rs +++ b/src/main.rs @@ -111,9 +111,9 @@ async fn handle_socket( b = brecv.recv() => { println!("{} trying to log in: {:?}", &temp_id, &b); if let Ok((to_id, msg)) = b { - if let Response::Login(open_tavern::api::login::LoginResult { success, username }) = msg.clone() { + if let Response::Login(open_tavern::api::login::LoginResult { success, username }) = &msg { let to_id = to_id.map(|ti| ti == temp_id).unwrap_or(false); - if to_id && success && id.as_ref().map(|id| id == &username).unwrap_or(false) { + if to_id && *success && id.as_ref().map(|id| id == username).unwrap_or(false) { _ = socket.send(Message::Text(serde_json::to_string(&msg).unwrap_or_default().into())).await; break; }