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)
|
||||
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 += `<line x1="0" x2="${width}" y1="${i}" y2="${i}" />`;
|
||||
svg.innerHTML += `<line x1="${i}" x2="${i}" y1="0" y2="${height}" />`;
|
||||
while (i <= Math.max(width, height)) {
|
||||
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="${i + gridOffset[0]}" x2="${i + gridOffset[0]}" y1="${gridOffset[1] - gridSize}" y2="${height + gridOffset[1]}" vector-effect="non-scaling-stroke" />`;
|
||||
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 = `
|
||||
<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.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 = '';
|
||||
|
|
|
@ -64,8 +64,12 @@
|
|||
<button style="background-color: #ffffd6;" onclick="showHideDiv('initiative-tracker')"><b>i</b></button>
|
||||
</div>
|
||||
<div id="map" style="position:absolute; transform-origin: top left; user-select: none;">
|
||||
<img id="map-background" src="https://rustystriker.dev/molly.jpg">
|
||||
<svg id="map-grid" xmlns="http://www.w3.org/2000/svg" vector-effect="non-scaling-stroke" style="position: absolute; top: 0px; left: 0px"></svg>
|
||||
<img id="map-background" src="https://rustystriker.dev/molly.jpg" onload="updateGrid()"
|
||||
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>
|
||||
|
|
|
@ -75,11 +75,23 @@ pub enum Response {
|
|||
Login(login::LoginResult),
|
||||
Message(ChatMessage),
|
||||
GetChatHistory(Vec<ChatMessage>),
|
||||
ShowScene { scene: usize, tokens: Vec<SpawnToken>, background: Option<String> },
|
||||
MoveToken { token_id: usize, x: f32, y: f32 },
|
||||
ShowScene {
|
||||
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),
|
||||
CharacterCreated(usize),
|
||||
Quit { id: String },
|
||||
Quit {
|
||||
id: String,
|
||||
},
|
||||
Shutdown,
|
||||
}
|
||||
|
||||
|
|
|
@ -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<A> + 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<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 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<A> + 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<A> + Serialize + Deserialize<'a>, A: entry::GameEntry + Se
|
|||
})
|
||||
}
|
||||
|
||||
fn scene_map(&self, scene_id: usize) -> Option<String> {
|
||||
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 {
|
||||
|
|
|
@ -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<usize, TokenInfo>,
|
||||
}
|
||||
|
|
12
src/lib.rs
12
src/lib.rs
|
@ -171,12 +171,15 @@ impl GameServer {
|
|||
img: info.img_source.clone(),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
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 } => {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue