diff --git a/assets/web/app.js b/assets/web/app.js index ff46a02..3fb08eb 100644 --- a/assets/web/app.js +++ b/assets/web/app.js @@ -1,12 +1,22 @@ // init - game view (map) -const GRID_SIZE = 200; // Grid size in pixels var mapScale = 1.0; var mapOffsetX = 0.0; var mapOffsetY = 0.0; var draggedToken = { token: null, offX: 0, offY: 0 }; var draggedDiv = { div: null, offX: 0, offY: 0 }; -var gridDashType = 0; +var gridDashType = 1; +var gridLineWidth = 2; +var gridSize = 200; // Grid size in pixels +var gridOffset = [0, 0] +var gridColor = 'red'; var showGrid = true; +const LINE_DASH_TYPES = [ + [1.0, 0.0], + [0.1, 0.8, 0.1, 0.0], + [0, 0.1, 0.1, 0.6, 0.1, 0.1], + [0, 0.1, 0.3, 0.2, 0.3, 0.1], + [0.1, 0.8, 0.1], +]; function init() { let view = document.getElementById('game-view'); @@ -40,34 +50,44 @@ function init() { } document.body.onmousemove = onMoveableDivDrag; document.body.onmouseup = onMoveableDivMouseUp; - - let mapBackground = document.getElementById('map-background'); - updateGridCanvas(mapBackground.width, mapBackground.height); } -function updateGridCanvas(width, height) { +function updateGrid() { // Draw the grid on the grid thing + let mapBackground = document.getElementById('map-background'); + let width = mapBackground.width; + let height = mapBackground.height; let svg = document.getElementById('map-grid'); - let lineDashTypes = [ - [0.1, 0.8, 0.1, 0.0], - [0, 0.1, 0.1, 0.6, 0.1, 0.1], - [0, 0.1, 0.3, 0.2, 0.3, 0.1], - [0.1, 0.8, 0.1], - ]; + svg.setAttribute('width', width); svg.setAttribute('height', height); + svg.setAttribute('stroke', gridColor); + svg.setAttribute('stroke-width', gridLineWidth); + svg.setAttribute('stroke-dasharray', LINE_DASH_TYPES[gridDashType].map(v => v * gridSize).join(' ')) svg.innerHTML = ''; - svg.setAttribute('stroke', 'black'); - svg.setAttribute('stroke-width', '15'); - svg.setAttribute('stroke-dash-array', lineDashTypes[gridDashType].join(' ')) - + let i = 0; - while (i < Math.max(width, height)) { - i += GRID_SIZE; - svg.innerHTML += ``; - svg.innerHTML += ``; + while (i <= Math.max(width, height)) { + svg.innerHTML += ``; + svg.innerHTML += ``; + i += gridSize; } } +function updateGridDashType(type) { + gridDashType = Math.max(0, Math.min(LINE_DASH_TYPES.length - 1, type)); + let svg = document.getElementById('map-grid'); + svg.setAttribute('stroke-dasharray', LINE_DASH_TYPES[gridDashType].map(v => v * gridSize).join(' ')) +} +function updateGridColor(color) { + let svg = document.getElementById('map-grid'); + svg.setAttribute('stroke', color); + gridColor = color; +} +function updateGridWidth(width) { + let svg = document.getElementById('map-grid'); + gridLineWidth = width; + svg.setAttribute('stroke-width', gridLineWidth); +} tavern.onlogin = (s) => { if (s) { @@ -136,11 +156,11 @@ tavern.onspawntoken = (t) => { let map = document.getElementById('map'); let token = document.createElement('div'); token.className = 'token token-transition'; - token.style.top = `${t.y * GRID_SIZE}px`; - token.style.left = `${t.x * GRID_SIZE}px`; + token.style.top = `${t.y * gridSize + gridOffset[1]}px`; + token.style.left = `${t.x * gridSize + gridOffset[0]}px`; token.token_id = t.token_id; token.innerHTML = ` - + ` token.onmousedown = (e) => { token.classList.remove('token-transition'); @@ -154,8 +174,8 @@ tavern.onspawntoken = (t) => { 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`; + token.style.top = `${(m.y * gridSize) + gridOffset[1]}px`; + token.style.left = `${(m.x * gridSize) + gridOffset[0]}px`; } } tavern.onshowscene = (show) => { @@ -164,8 +184,13 @@ tavern.onshowscene = (show) => { Array.from(map.children).filter(c => c.classList.contains('token')).forEach(c => map.removeChild(c)); let background = document.getElementById('map-background'); + gridOffset = show.grid_offset; + Array.from(document.getElementsByClassName('token')).forEach(t => { + t.children[0].style.width = `${gridSize}px`; + t.children[0].style.height = `${gridSize}px`; + }); + gridSize = show.grid_cell_size; background.src = show.background ?? ''; - updateGridCanvas(background.width, background.height); for (let token of show.tokens) { tavern.onspawntoken(token); } @@ -205,8 +230,8 @@ function onGameMouseMove(event) { function onGameMouseUp() { 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); + let x = Math.floor(0.5 + (-gridOffset[0] + parseInt(t.style.left)) / gridSize); + let y = Math.floor(0.5 + (-gridOffset[1] + parseInt(t.style.top)) / gridSize); let id = t.token_id; t.classList.add('token-transition'); t.children[0].style.cursor = ''; diff --git a/assets/web/index.html b/assets/web/index.html index a809f5e..6854301 100644 --- a/assets/web/index.html +++ b/assets/web/index.html @@ -64,8 +64,12 @@
- - + + + + +
diff --git a/src/api/mod.rs b/src/api/mod.rs index 6874f8f..30d16d9 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -75,11 +75,23 @@ pub enum Response { Login(login::LoginResult), Message(ChatMessage), GetChatHistory(Vec), - ShowScene { scene: usize, tokens: Vec, background: Option }, - MoveToken { token_id: usize, x: f32, y: f32 }, + ShowScene { + scene: usize, + tokens: Vec, + background: Option, + grid_cell_size: Option, + grid_offset: Option<[f32; 2]>, + }, + MoveToken { + token_id: usize, + x: f32, + y: f32, + }, SpawnToken(SpawnToken), CharacterCreated(usize), - Quit { id: String }, + Quit { + id: String, + }, Shutdown, } diff --git a/src/game/mod.rs b/src/game/mod.rs index ff6f6d4..10ae5fe 100644 --- a/src/game/mod.rs +++ b/src/game/mod.rs @@ -5,6 +5,8 @@ use character_sheet::{AccessLevel, Character, CharacterShort, EntryType}; use scene::{Party, Scene, TokenInfo}; use serde::{Deserialize, Serialize}; +use crate::game::scene::Map; + pub mod character_sheet; pub mod chat_message; pub mod entry; @@ -27,7 +29,7 @@ pub trait GameImpl<'a, C: Character + Serialize + Deserialize<'a>, A: entry:: fn current_scene(&self) -> usize; /// Gets the map background (file path) fn scene_characters(&self, scene: usize, character_id: usize) -> Option>; - fn scene_map(&self, scene_id: usize) -> Option; + fn scene_map(&self, scene_id: usize) -> Option<&Map>; fn create_token(&mut self, scene_id: usize, character: String, img_source: String, x: f32, y: f32) -> usize; fn move_token(&mut self, scene_id: usize, token_id: usize, x: f32, y: f32) -> bool; fn token_info(&self, scene: usize, token_id: usize) -> Option<&TokenInfo>; @@ -70,6 +72,8 @@ impl<'a, C: Character + Serialize + Deserialize<'a>, A: entry::GameEntry + Se Scene { map: Some(scene::Map { background: "assets/pf2r/maps/testmap.jpg".to_string(), + grid_cell_size: 150.0, + grid_offset: [80.0, 35.0], tokens, }), characters: vec![(0, Party(true))], @@ -183,11 +187,8 @@ impl<'a, C: Character + Serialize + Deserialize<'a>, A: entry::GameEntry + Se }) } - fn scene_map(&self, scene_id: usize) -> Option { - self.scenes - .get(&scene_id) - .map(|s| s.map.as_ref().map(|m| m.background.clone())) - .flatten() + fn scene_map(&self, scene_id: usize) -> Option<&Map> { + self.scenes.get(&scene_id).map(|s| s.map.as_ref()).flatten() } fn current_scene(&self) -> usize { diff --git a/src/game/scene.rs b/src/game/scene.rs index 5b6553d..1d25874 100644 --- a/src/game/scene.rs +++ b/src/game/scene.rs @@ -1,5 +1,5 @@ -use std::collections::HashMap; use serde::{Deserialize, Serialize}; +use std::collections::HashMap; #[derive(Serialize, Deserialize, Debug)] pub struct Scene { @@ -13,6 +13,10 @@ pub struct Scene { pub struct Map { /// Image source for the background of the map pub background: String, + // Cell size of the map grid + pub grid_cell_size: f32, + // Grid offset from top left, [x, y] + pub grid_offset: [f32; 2], /// Tokens in the current map (should be of characters), maps from token_id to its info pub tokens: HashMap, } @@ -23,7 +27,7 @@ pub struct TokenInfo { pub character: String, /// Token image source, as path relative to the data directory pub img_source: String, - // x, y are floats to allow 'free movement' + // x, y are floats to allow 'free movement' /// X position, in grid slots units (integers are grid aligned) pub x: f32, /// Y position, in grid slots units (integers are grid aligned) @@ -37,4 +41,4 @@ impl Party { pub fn can_see(&self, other: Party) -> bool { !self.0 || other.0 } -} \ No newline at end of file +} diff --git a/src/lib.rs b/src/lib.rs index 7bd4ed5..27f0a3a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -171,12 +171,15 @@ impl GameServer { img: info.img_source.clone(), }) .collect::>(); + let map = self.game.scene_map(scene); _ = broadcast.send(( Some(id.clone()), api::Response::ShowScene { scene: scene, tokens: scene_tokens, - background: self.game.scene_map(scene), + background: map.map(|m| m.background.clone()), + grid_cell_size: map.map(|m| m.grid_cell_size), + grid_offset: map.map(|m| m.grid_offset.clone()), }, )); } @@ -190,7 +193,12 @@ impl GameServer { let token_id = self.game.create_token(map_id, character, img_path.clone(), x, y); _ = broadcast.send(( Some(id.clone()), - api::Response::SpawnToken(SpawnToken { token_id, x, y, img: img_path.clone() }), + api::Response::SpawnToken(SpawnToken { + token_id, + x, + y, + img: img_path.clone(), + }), )); } api::Request::MoveToken { token_id, x, y } => {