map grid
This commit is contained in:
parent
2a7324b133
commit
bb7d3c48ea
6 changed files with 98 additions and 44 deletions
|
@ -1,12 +1,22 @@
|
||||||
// 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;
|
||||||
var draggedToken = { token: null, offX: 0, offY: 0 };
|
var draggedToken = { token: null, offX: 0, offY: 0 };
|
||||||
var draggedDiv = { div: 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;
|
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() {
|
function init() {
|
||||||
let view = document.getElementById('game-view');
|
let view = document.getElementById('game-view');
|
||||||
|
@ -40,34 +50,44 @@ function init() {
|
||||||
}
|
}
|
||||||
document.body.onmousemove = onMoveableDivDrag;
|
document.body.onmousemove = onMoveableDivDrag;
|
||||||
document.body.onmouseup = onMoveableDivMouseUp;
|
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
|
// 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 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('width', width);
|
||||||
svg.setAttribute('height', height);
|
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.innerHTML = '';
|
||||||
svg.setAttribute('stroke', 'black');
|
|
||||||
svg.setAttribute('stroke-width', '15');
|
|
||||||
svg.setAttribute('stroke-dash-array', lineDashTypes[gridDashType].join(' '))
|
|
||||||
|
|
||||||
let i = 0;
|
let i = 0;
|
||||||
while (i < Math.max(width, height)) {
|
while (i <= Math.max(width, height)) {
|
||||||
i += GRID_SIZE;
|
svg.innerHTML += `<line x1="${gridOffset[0] - gridSize}" x2="${width + gridOffset[0]}" y1="${i + gridOffset[1]}" y2="${i + gridOffset[1]}" vector-effect="non-scaling-stroke" />`;
|
||||||
svg.innerHTML += `<line x1="0" x2="${width}" y1="${i}" y2="${i}" />`;
|
svg.innerHTML += `<line x1="${i + gridOffset[0]}" x2="${i + gridOffset[0]}" y1="${gridOffset[1] - gridSize}" y2="${height + gridOffset[1]}" vector-effect="non-scaling-stroke" />`;
|
||||||
svg.innerHTML += `<line x1="${i}" x2="${i}" y1="0" y2="${height}" />`;
|
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) => {
|
tavern.onlogin = (s) => {
|
||||||
if (s) {
|
if (s) {
|
||||||
|
@ -136,11 +156,11 @@ tavern.onspawntoken = (t) => {
|
||||||
let map = document.getElementById('map');
|
let map = document.getElementById('map');
|
||||||
let token = document.createElement('div');
|
let token = document.createElement('div');
|
||||||
token.className = 'token token-transition';
|
token.className = 'token token-transition';
|
||||||
token.style.top = `${t.y * GRID_SIZE}px`;
|
token.style.top = `${t.y * gridSize + gridOffset[1]}px`;
|
||||||
token.style.left = `${t.x * GRID_SIZE}px`;
|
token.style.left = `${t.x * gridSize + gridOffset[0]}px`;
|
||||||
token.token_id = t.token_id;
|
token.token_id = t.token_id;
|
||||||
token.innerHTML = `
|
token.innerHTML = `
|
||||||
<img src='${t.img}' ondragstart='return false;'>
|
<img src='${t.img}' style="width: ${gridSize}px; height: ${gridSize}px;" ondragstart='return false;'>
|
||||||
`
|
`
|
||||||
token.onmousedown = (e) => {
|
token.onmousedown = (e) => {
|
||||||
token.classList.remove('token-transition');
|
token.classList.remove('token-transition');
|
||||||
|
@ -154,8 +174,8 @@ tavern.onspawntoken = (t) => {
|
||||||
tavern.onmovetoken = (m) => {
|
tavern.onmovetoken = (m) => {
|
||||||
let token = Array.from(document.getElementsByClassName('token')).filter(t => t.token_id == m.token_id)[0]
|
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.top = `${(m.y * gridSize) + gridOffset[1]}px`;
|
||||||
token.style.left = `${m.x * GRID_SIZE}px`;
|
token.style.left = `${(m.x * gridSize) + gridOffset[0]}px`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tavern.onshowscene = (show) => {
|
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));
|
Array.from(map.children).filter(c => c.classList.contains('token')).forEach(c => map.removeChild(c));
|
||||||
|
|
||||||
let background = document.getElementById('map-background');
|
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 ?? '';
|
background.src = show.background ?? '';
|
||||||
updateGridCanvas(background.width, background.height);
|
|
||||||
for (let token of show.tokens) {
|
for (let token of show.tokens) {
|
||||||
tavern.onspawntoken(token);
|
tavern.onspawntoken(token);
|
||||||
}
|
}
|
||||||
|
@ -205,8 +230,8 @@ function onGameMouseMove(event) {
|
||||||
function onGameMouseUp() {
|
function onGameMouseUp() {
|
||||||
if (draggedToken.token != null) {
|
if (draggedToken.token != null) {
|
||||||
let t = draggedToken.token;
|
let t = draggedToken.token;
|
||||||
let x = Math.floor(0.5 + parseInt(t.style.left) / GRID_SIZE);
|
let x = Math.floor(0.5 + (-gridOffset[0] + parseInt(t.style.left)) / gridSize);
|
||||||
let y = Math.floor(0.5 + parseInt(t.style.top) / GRID_SIZE);
|
let y = Math.floor(0.5 + (-gridOffset[1] + parseInt(t.style.top)) / gridSize);
|
||||||
let id = t.token_id;
|
let id = t.token_id;
|
||||||
t.classList.add('token-transition');
|
t.classList.add('token-transition');
|
||||||
t.children[0].style.cursor = '';
|
t.children[0].style.cursor = '';
|
||||||
|
|
|
@ -64,8 +64,12 @@
|
||||||
<button style="background-color: #ffffd6;" onclick="showHideDiv('initiative-tracker')"><b>i</b></button>
|
<button style="background-color: #ffffd6;" onclick="showHideDiv('initiative-tracker')"><b>i</b></button>
|
||||||
</div>
|
</div>
|
||||||
<div id="map" style="position:absolute; transform-origin: top left; user-select: none;">
|
<div id="map" style="position:absolute; transform-origin: top left; user-select: none;">
|
||||||
<img id="map-background" src="https://rustystriker.dev/molly.jpg">
|
<img id="map-background" src="https://rustystriker.dev/molly.jpg" onload="updateGrid()"
|
||||||
<svg id="map-grid" xmlns="http://www.w3.org/2000/svg" vector-effect="non-scaling-stroke" style="position: absolute; top: 0px; left: 0px"></svg>
|
onerror="alert('Failed loading map')">
|
||||||
|
<svg id="map-grid" xmlns="http://www.w3.org/2000/svg" style="position: absolute; top: 0px; left: 0px">
|
||||||
|
<g vector-effect="non-scaling-stroke" id="map-grid-horizontal" />
|
||||||
|
<g vector-effect="non-scaling-stroke" id="map-grid-vertical" />
|
||||||
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -75,11 +75,23 @@ pub enum Response {
|
||||||
Login(login::LoginResult),
|
Login(login::LoginResult),
|
||||||
Message(ChatMessage),
|
Message(ChatMessage),
|
||||||
GetChatHistory(Vec<ChatMessage>),
|
GetChatHistory(Vec<ChatMessage>),
|
||||||
ShowScene { scene: usize, tokens: Vec<SpawnToken>, background: Option<String> },
|
ShowScene {
|
||||||
MoveToken { token_id: usize, x: f32, y: f32 },
|
scene: usize,
|
||||||
|
tokens: Vec<SpawnToken>,
|
||||||
|
background: Option<String>,
|
||||||
|
grid_cell_size: Option<f32>,
|
||||||
|
grid_offset: Option<[f32; 2]>,
|
||||||
|
},
|
||||||
|
MoveToken {
|
||||||
|
token_id: usize,
|
||||||
|
x: f32,
|
||||||
|
y: f32,
|
||||||
|
},
|
||||||
SpawnToken(SpawnToken),
|
SpawnToken(SpawnToken),
|
||||||
CharacterCreated(usize),
|
CharacterCreated(usize),
|
||||||
Quit { id: String },
|
Quit {
|
||||||
|
id: String,
|
||||||
|
},
|
||||||
Shutdown,
|
Shutdown,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,8 @@ use character_sheet::{AccessLevel, Character, CharacterShort, EntryType};
|
||||||
use scene::{Party, Scene, TokenInfo};
|
use scene::{Party, Scene, TokenInfo};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::game::scene::Map;
|
||||||
|
|
||||||
pub mod character_sheet;
|
pub mod character_sheet;
|
||||||
pub mod chat_message;
|
pub mod chat_message;
|
||||||
pub mod entry;
|
pub mod entry;
|
||||||
|
@ -27,7 +29,7 @@ pub trait GameImpl<'a, C: Character<A> + Serialize + Deserialize<'a>, A: entry::
|
||||||
fn current_scene(&self) -> usize;
|
fn current_scene(&self) -> usize;
|
||||||
/// Gets the map background (file path)
|
/// Gets the map background (file path)
|
||||||
fn scene_characters(&self, scene: usize, character_id: usize) -> Option<Vec<CharacterShort>>;
|
fn scene_characters(&self, scene: usize, character_id: usize) -> Option<Vec<CharacterShort>>;
|
||||||
fn scene_map(&self, scene_id: usize) -> Option<String>;
|
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 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 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>;
|
fn token_info(&self, scene: usize, token_id: usize) -> Option<&TokenInfo>;
|
||||||
|
@ -70,6 +72,8 @@ impl<'a, C: Character<A> + Serialize + Deserialize<'a>, A: entry::GameEntry + Se
|
||||||
Scene {
|
Scene {
|
||||||
map: Some(scene::Map {
|
map: Some(scene::Map {
|
||||||
background: "assets/pf2r/maps/testmap.jpg".to_string(),
|
background: "assets/pf2r/maps/testmap.jpg".to_string(),
|
||||||
|
grid_cell_size: 150.0,
|
||||||
|
grid_offset: [80.0, 35.0],
|
||||||
tokens,
|
tokens,
|
||||||
}),
|
}),
|
||||||
characters: vec![(0, Party(true))],
|
characters: vec![(0, Party(true))],
|
||||||
|
@ -183,11 +187,8 @@ impl<'a, C: Character<A> + Serialize + Deserialize<'a>, A: entry::GameEntry + Se
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn scene_map(&self, scene_id: usize) -> Option<String> {
|
fn scene_map(&self, scene_id: usize) -> Option<&Map> {
|
||||||
self.scenes
|
self.scenes.get(&scene_id).map(|s| s.map.as_ref()).flatten()
|
||||||
.get(&scene_id)
|
|
||||||
.map(|s| s.map.as_ref().map(|m| m.background.clone()))
|
|
||||||
.flatten()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn current_scene(&self) -> usize {
|
fn current_scene(&self) -> usize {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use std::collections::HashMap;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
pub struct Scene {
|
pub struct Scene {
|
||||||
|
@ -13,6 +13,10 @@ pub struct Scene {
|
||||||
pub struct Map {
|
pub struct Map {
|
||||||
/// Image source for the background of the map
|
/// Image source for the background of the map
|
||||||
pub background: String,
|
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
|
/// Tokens in the current map (should be of characters), maps from token_id to its info
|
||||||
pub tokens: HashMap<usize, TokenInfo>,
|
pub tokens: HashMap<usize, TokenInfo>,
|
||||||
}
|
}
|
||||||
|
|
12
src/lib.rs
12
src/lib.rs
|
@ -171,12 +171,15 @@ impl GameServer {
|
||||||
img: info.img_source.clone(),
|
img: info.img_source.clone(),
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
let map = self.game.scene_map(scene);
|
||||||
_ = broadcast.send((
|
_ = broadcast.send((
|
||||||
Some(id.clone()),
|
Some(id.clone()),
|
||||||
api::Response::ShowScene {
|
api::Response::ShowScene {
|
||||||
scene: scene,
|
scene: scene,
|
||||||
tokens: scene_tokens,
|
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);
|
let token_id = self.game.create_token(map_id, character, img_path.clone(), x, y);
|
||||||
_ = broadcast.send((
|
_ = broadcast.send((
|
||||||
Some(id.clone()),
|
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 } => {
|
api::Request::MoveToken { token_id, x, y } => {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue