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 } => {