const LOCAL_GRID_TYPE = "GRID_DASH_TYPE"; const LOCAL_GRID_COLOR = "GRID_COLOR"; const LOCAL_GRID_WIDTH = "GRID_WIDTH"; // init - game view (map) 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 = 1; var gridLineWidth = 0; var gridColor = 'black'; var gridSize = 200; // Grid size in pixels var gridOffset = [0, 0]; 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'); view.onwheel = onGameViewScroll; view.onmousemove = onGameMouseMove; view.onmouseup = onGameMouseUp; view.oncontextmenu = () => false; // allow sending chat message using enter (and shift-enter for new line) document.getElementById('newmsg-content').onkeydown = (e) => { if (e.key == "Enter" && !e.shiftKey) { sendChatMessage(); return false; } } document.getElementById('login-username').onkeydown = (e) => { if (e.key == 'Enter') { document.getElementById('login-pass').focus(); return false; } } document.getElementById('login-pass').onkeydown = (e) => { if (e.key == 'Enter') { onLoginClick(); return false; } } // focus on the username field for the sake of just pressing enter multiple times document.getElementById('login-username').focus(); document.body.onclick = (e) => { document.getElementById('msg-context-menu').style.display = 'none'; } document.body.onmousemove = onMoveableDivDrag; document.body.onmouseup = onMoveableDivMouseUp; // Load user grid settings from local storage document.getElementById('settings-grid-color').value = gridColor = localStorage.getItem(LOCAL_GRID_COLOR) ?? 'black'; let savedGridWidth = parseInt(localStorage.getItem(LOCAL_GRID_WIDTH)); let savedGridType = parseInt(localStorage.getItem(LOCAL_GRID_TYPE)); document.getElementById('settings-grid-width').value = gridLineWidth = isNaN(savedGridWidth) ? 2 : savedGridWidth; document.getElementById('settings-grid-type').selectedIndex = gridDashType = isNaN(savedGridType) ? 0 : savedGridType; } 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'); if (gridLineWidth <= 0) { gridLineWidth = 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 = ''; let i = 0; 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(' ')) localStorage.setItem(LOCAL_GRID_TYPE, gridDashType); } function updateGridColor(color) { let svg = document.getElementById('map-grid'); svg.setAttribute('stroke', color); gridColor = color; localStorage.setItem(LOCAL_GRID_COLOR, color); } function updateGridWidth(width) { let svg = document.getElementById('map-grid'); gridLineWidth = width; localStorage.setItem(LOCAL_GRID_WIDTH, width); svg.setAttribute('stroke-width', gridLineWidth); } tavern.onlogin = (s) => { if (s) { let login = document.getElementById('login-screen'); let game = document.getElementById('game'); login.style.display = 'none'; game.style.display = 'flex'; // get last 50 msgs (i think that is enough for now) when we get in tavern.get_last_msgs(50); tavern.get_current_scene(); } else { alert("Invalid username or password!"); } } tavern.onmessage = (m) => { console.log(m); let msg = document.createElement('div'); msg.className = 'chat-message'; // #abusing_style_order_as_both_id_variable_and_forcing_chronological_order msg.style.order = m.id; msg.innerHTML = `

${m.character ?? ''} (${m.source})


${m.text.replace('\n', '\n
\n')}

` msg.oncontextmenu = (e) => { if (e.shiftKey) { return true; } let cm = document.getElementById('msg-context-menu'); cm.style.display = 'flex'; cm.style.top = `${e.pageY}px`; cm.style.left = `${e.pageX}px`; return false; } if (m.actions) { let holder = document.createElement('div'); holder.style.display = 'flex'; holder.style.flexWrap = 'wrap'; msg.appendChild(holder); for (const act of m.actions) { let button = document.createElement('button'); button.innerText = act.display_name ?? act.name; button.action = act; button.onclick = () => console.log(button.action); button.style.margin = '2px'; button.onclick = () => openRollsPopup(act); holder.appendChild(button); } } let history = document.getElementById('chat-history'); // this is to force update everytime we get a duplicate msg to allow msg editing (yay) let exists = Array.from(history.children).filter(e => e.style.order == m.id)[0]; if (exists) { history.removeChild(exists); } history.appendChild(msg); msg.scrollIntoView(); } tavern.onspawntoken = (t) => { console.log(t); let map = document.getElementById('map'); let token = document.createElement('div'); token.className = 'token token-transition'; 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'); draggedToken.token = token; draggedToken.offX = ((e.clientX - mapOffsetX) / mapScale) - parseInt(token.style.left); draggedToken.offY = ((e.clientY - mapOffsetY) / mapScale) - parseInt(token.style.top); token.children[0].style.cursor = 'grabbing'; } map.appendChild(token); } 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 * gridSize) + gridOffset[1]}px`; token.style.left = `${(m.x * gridSize) + gridOffset[0]}px`; } } tavern.onshowscene = (show) => { let map = document.getElementById('map'); // Remove existing tokens 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 ?? ''; for (let token of show.tokens) { tavern.onspawntoken(token); } } function onLoginClick() { let username = document.getElementById('login-username').value; let pass = document.getElementById('login-pass').value; if (username == 'test') { // TODO: Remove this for when im done dev-ing with this file tavern.onlogin(true); } tavern.login(username, pass); } function onGameViewScroll(event) { let map = document.getElementById('map'); mapScale += (event.wheelDelta / 1800.0); if (mapScale < 0.1) { mapScale = 0.1; } map.style.transform = `scale(${mapScale})`; } function onGameMouseMove(event) { if (event.buttons == 2) { // right click let map = document.getElementById('map'); let mult = event.ctrlKey ? 2.0 : 1.0; mapOffsetX += event.movementX * mult; mapOffsetY += event.movementY * mult; map.style.left = `${mapOffsetX}px`; map.style.top = `${mapOffsetY}px`; } else if (draggedToken.token != null && event.buttons == 1) { let top = (event.clientY - mapOffsetY) / mapScale - draggedToken.offY; let left = (event.clientX - mapOffsetX) / mapScale - draggedToken.offX; draggedToken.token.style.top = `${top}px`; draggedToken.token.style.left = `${left}px`; } } function onGameMouseUp() { if (draggedToken.token != null) { let t = draggedToken.token; 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 = ''; tavern.move_token(id, x, y); draggedToken.token = null; } } function sendChatMessage() { let tb = document.getElementById('newmsg-content'); // get the msg and reset the textarea let text = tb.value; tb.value = ''; tavern.simple_msg(text); } function openRollsPopup(action) { let holder = document.getElementById('dice-roll-holder'); holder.innerHTML = ''; // remove all holder children holder.action = action; if (action.rolls != undefined && Array.isArray(action.rolls)) { let i = 0; for (const r of action.rolls) { // name (extra) (dice_amount)d(dice_type) + constant | enabled console.log(r); let row = document.createElement('div'); row.className = 'dice-roll-row'; row.roll = r; row.innerHTML = `

${r.name} ${r.extra != null ? '(' + r.extra + ')' : ''}

`; holder.appendChild(row); i += 1; } } document.getElementById('dice-roll-title').innerText = action.display_name ?? action.name; document.getElementById('dice-roll-popup').style.display = 'flex'; } function rollPopup() { // TODO: Maybe let the server roll the dice? // first - hide the popup document.getElementById('dice-roll-popup').style.display = 'none'; // get the holder and start rolling dice let rolls = []; let holder = document.getElementById('dice-roll-holder'); for (const h of holder.children) { if (h.roll && h.children[2].checked) { let roll = { name: h.roll.name, extra: h.roll.extra }; let msg = ''; let sum = 0; for (let i = 0; i < h.roll.dice_amount; i++) { // Math.random gives a value in [0, 1), so we need to add 1 at the end let roll = Math.floor(Math.random() * h.roll.dice_type) + 1; sum += roll; msg += `${roll}`; if (i != h.roll.dice_amount - 1 || h.roll.constant != 0) { msg += ' + '; } } if (h.roll.constant != 0) { sum += h.roll.constant; msg += `${h.roll.constant}`; } roll.result = sum; roll.result_text = msg; rolls.push(roll); } } console.log(rolls); tavern.action_result(holder.action.name, 'Louise', [], rolls); } function onMoveableDivMouseDown(e, id) { if (e.buttons == 1) { let div = document.getElementById(id); let rect = div.getBoundingClientRect(); draggedDiv.div = div; draggedDiv.offX = e.clientX - rect.x; draggedDiv.offY = e.clientY - rect.y; } } function onMoveableDivDrag(e) { if (draggedDiv.div) { draggedDiv.div.style.right = ''; draggedDiv.div.style.top = `${e.clientY - draggedDiv.offY}px`; draggedDiv.div.style.left = `${e.clientX - draggedDiv.offX}px`; } } function onMoveableDivMouseUp(e, id) { draggedDiv.div = null; } function showHideDiv(id) { let div = document.getElementById(id); div.style.display = div.style.display == 'none' ? 'flex' : 'none'; }