This commit is contained in:
Rusty Striker 2025-06-22 20:49:54 +03:00
parent 2a7324b133
commit bb7d3c48ea
Signed by: RustyStriker
GPG key ID: 87E4D691632DFF15
6 changed files with 98 additions and 44 deletions

View file

@ -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 = '';

View file

@ -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>

View file

@ -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,
}

View file

@ -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 {

View file

@ -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>,
}

View file

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