From f1a688ce5f6414277a08af7042c3439839c97aeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BD=D1=82=D0=BE=D0=BD=20=D0=A2=D1=80=D0=BE=D1=88?= =?UTF-8?q?=D0=B8=D0=BD?= Date: Sat, 1 Aug 2026 00:25:12 +0300 Subject: [PATCH] multiplayer - choose any room when joining --- index.html | 2 +- server/server.js | 13 +++ src/ai.js | 4 +- src/gfx.js | 6 +- src/input.js | 2 +- src/main.js | 253 ++++++++++++++++++++++++++++++++------------ src/net.js | 44 +++++++- src/net/protocol.js | 2 + src/panel.js | 4 +- src/projectiles.js | 14 +-- src/sky.js | 8 +- src/sound.js | 2 +- src/terrain.js | 6 +- src/utils.js | 2 +- src/weapons.js | 10 +- 15 files changed, 275 insertions(+), 97 deletions(-) diff --git a/index.html b/index.html index 9ccb036..baf5fdb 100644 --- a/index.html +++ b/index.html @@ -17,6 +17,6 @@ - + diff --git a/server/server.js b/server/server.js index 77974dc..e4556de 100644 --- a/server/server.js +++ b/server/server.js @@ -1,6 +1,7 @@ import {WebSocketServer} from 'ws'; import {randomUUID} from 'node:crypto'; import {MSG} from '../src/net/protocol.js'; +import {MAX_PLAYERS} from '../src/constants.js'; const PORT = process.env.PORT || 8090; const ROOM_CODE_ALPHABET = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'; @@ -79,6 +80,10 @@ wss.on('connection', (ws, req) => { const id = randomUUID(); room = rooms.get(code); + if (room && room.clients.size >= MAX_PLAYERS) { + send(ws, {type: MSG.ERROR, message: 'room full'}); + return; + } if (!room) { room = {code, host: null, clients: new Map()}; rooms.set(code, room); @@ -100,6 +105,14 @@ wss.on('connection', (ws, req) => { return; } + if (msg.type === MSG.LIST_ROOMS) { + const list = [...rooms.values()] + .filter(r => r.clients.size < MAX_PLAYERS) + .map(r => ({room: r.code, players: r.clients.size, host: r.host ? r.host.name : ''})); + send(ws, {type: MSG.ROOMS, rooms: list}); + return; + } + if (msg.type === MSG.LEAVE) { ws.close(); return; diff --git a/src/ai.js b/src/ai.js index 3a935f8..0e858f3 100644 --- a/src/ai.js +++ b/src/ai.js @@ -1,5 +1,5 @@ -import {PLAYER_MAX_ENERGY, PLAYER_INITIAL_POWER, PLAYER_ENERGY_POWER_MULTIPLIER} from './constants.js?v=22'; -import {randomInt} from './math.js?v=22'; +import {PLAYER_MAX_ENERGY, PLAYER_INITIAL_POWER, PLAYER_ENERGY_POWER_MULTIPLIER} from './constants.js?v=24'; +import {randomInt} from './math.js?v=24'; export const AI_TYPES = { diff --git a/src/gfx.js b/src/gfx.js index 99654ba..2c980ce 100644 --- a/src/gfx.js +++ b/src/gfx.js @@ -1,6 +1,6 @@ -import {splitWords} from './utils.js?v=22'; -import {parable} from './math.js?v=22'; -import {FONT_HEIGHT, FONT_WIDTH} from './constants.js?v=22'; +import {splitWords} from './utils.js?v=24'; +import {parable} from './math.js?v=24'; +import {FONT_HEIGHT, FONT_WIDTH} from './constants.js?v=24'; export function createCanvas(width, height, readable) { const canvas = document.createElement('canvas'); diff --git a/src/input.js b/src/input.js index e441f98..28d7c5e 100644 --- a/src/input.js +++ b/src/input.js @@ -1,4 +1,4 @@ -import {DEFAULT_KEYPRESS_DELAY, Z} from './constants.js?v=22'; +import {DEFAULT_KEYPRESS_DELAY, Z} from './constants.js?v=24'; const input = {}; let lastKeypressTime = 0; diff --git a/src/main.js b/src/main.js index 1bdc5c6..c15a815 100644 --- a/src/main.js +++ b/src/main.js @@ -1,17 +1,17 @@ -import {AI_TYPES} from './ai.js?v=22'; -import {DEATH_SPECS, EXPLOSION_SHAKE_REDUCTION_FACTOR, H, MAX_EXPLOSION_SHAKE_FACTOR, MAX_WIND, PARTICLE_AMOUNT, PARTICLE_FADE_AMOUNT, PARTICLE_MAX_POWER_FACTOR, PARTICLE_MIN_LIFETIME, PARTICLE_MIN_POWER_FACTOR, PARTICLE_POWER_REDUCTION_FACTOR, PARTICLE_TIME_FACTOR, PARTICLE_WIND_REDUCTION_FACTOR, PLAYER_ANGLE_FAST_INCREMENT, PLAYER_ANGLE_INCREMENT, PLAYER_ANGLE_TICK_SOUND_INTERVAL, PLAYER_COLORS, PLAYER_ENERGY_POWER_MULTIPLIER, PLAYER_EXPLOSION_PARTICLE_POWER, PLAYER_FALL_DAMAGE_FACTOR, PLAYER_FALL_DAMAGE_HEIGHT, PLAYER_INITIAL_POWER, PLAYER_MAX_ENERGY, PLAYER_POWER_FAST_INCREMENT, PLAYER_POWER_INCREMENT, PLAYER_POWER_TICK_SOUND_INTERVAL, PLAYER_STARTING_TOOLS, PLAYER_STARTING_WEAPONS, PLAYER_TANK_BOUNDING_RADIUS, PLAYER_TANK_Y_FOOTPRINT, SHIELD_TYPES, TRAJECTORY_FADE_SPEED, TRAJECTORY_FLOAT_SPEED, W, WEAPON_TYPES, Z, STARTING_SCORE, SCORE_PER_KILL, SCORE_FOR_WIN, MARKET_ITEMS, NAPALM_SPAWN_RATE, FIRE_DURATION, FIRE_DAMAGE} from './constants.js?v=22'; -import {createCanvas, drawLine, drawRect, drawSemiCircle, drawText, loop, plot, strokeCircle} from './gfx.js?v=22'; -import {afterKeyDelay, key, initClickCanvas, popClick, getPointer, clearKeys} from './input.js?v=22'; -import {clamp, deg2rad, distance, parable, random, randomInt, vec, wrap} from './math.js?v=22'; -import {PROJECTILE_TYPES} from './projectiles.js?v=22'; -import {generateSky} from './sky.js?v=22'; -import {playTickSound} from './sound.js?v=22'; -import {clipTerrain, closestLand, collapseTerrain, generateTerrain, isTerrain, landHeight, startCollapseTerrain, collapseTerrainStep} from './terrain.js?v=22'; -import {sample, shuffle, newPlayerId} from './utils.js?v=22'; -import {EXPLOSION_TYPES} from './weapons.js?v=22'; -import {drawPanelBg, drawPanelTitle, drawPanelTitleFancy, drawPanelText, drawPanelDivider, drawPanelMenu, getPanelBounds, drawButton, checkHit, PANEL_X, PANEL_WIDTH} from './panel.js?v=22'; -import {MSG, CMD, HOST_MSG} from './net/protocol.js?v=22'; -import {netBroadcast, netConnect, netDisconnect, netIsConnected, netIsHost, netMakeRoomCode, netMyId, netOnMessage, netOnStatus, netRoom, netSendCommand} from './net.js?v=22'; +import {AI_TYPES} from './ai.js?v=24'; +import {DEATH_SPECS, EXPLOSION_SHAKE_REDUCTION_FACTOR, H, MAX_EXPLOSION_SHAKE_FACTOR, MAX_WIND, PARTICLE_AMOUNT, PARTICLE_FADE_AMOUNT, PARTICLE_MAX_POWER_FACTOR, PARTICLE_MIN_LIFETIME, PARTICLE_MIN_POWER_FACTOR, PARTICLE_POWER_REDUCTION_FACTOR, PARTICLE_TIME_FACTOR, PARTICLE_WIND_REDUCTION_FACTOR, PLAYER_ANGLE_FAST_INCREMENT, PLAYER_ANGLE_INCREMENT, PLAYER_ANGLE_TICK_SOUND_INTERVAL, PLAYER_COLORS, PLAYER_ENERGY_POWER_MULTIPLIER, PLAYER_EXPLOSION_PARTICLE_POWER, PLAYER_FALL_DAMAGE_FACTOR, PLAYER_FALL_DAMAGE_HEIGHT, PLAYER_INITIAL_POWER, PLAYER_MAX_ENERGY, PLAYER_POWER_FAST_INCREMENT, PLAYER_POWER_INCREMENT, PLAYER_POWER_TICK_SOUND_INTERVAL, PLAYER_STARTING_TOOLS, PLAYER_STARTING_WEAPONS, PLAYER_TANK_BOUNDING_RADIUS, PLAYER_TANK_Y_FOOTPRINT, SHIELD_TYPES, TRAJECTORY_FADE_SPEED, TRAJECTORY_FLOAT_SPEED, W, WEAPON_TYPES, Z, STARTING_SCORE, SCORE_PER_KILL, SCORE_FOR_WIN, MARKET_ITEMS, NAPALM_SPAWN_RATE, FIRE_DURATION, FIRE_DAMAGE, MAX_PLAYERS} from './constants.js?v=24'; +import {createCanvas, drawLine, drawRect, drawSemiCircle, drawText, loop, plot, strokeCircle} from './gfx.js?v=24'; +import {afterKeyDelay, key, initClickCanvas, popClick, getPointer, clearKeys} from './input.js?v=24'; +import {clamp, deg2rad, distance, parable, random, randomInt, vec, wrap} from './math.js?v=24'; +import {PROJECTILE_TYPES} from './projectiles.js?v=24'; +import {generateSky} from './sky.js?v=24'; +import {playTickSound} from './sound.js?v=24'; +import {clipTerrain, closestLand, collapseTerrain, generateTerrain, isTerrain, landHeight, startCollapseTerrain, collapseTerrainStep} from './terrain.js?v=24'; +import {sample, shuffle, newPlayerId} from './utils.js?v=24'; +import {EXPLOSION_TYPES} from './weapons.js?v=24'; +import {drawPanelBg, drawPanelTitle, drawPanelTitleFancy, drawPanelText, drawPanelDivider, drawPanelMenu, getPanelBounds, drawButton, checkHit, PANEL_X, PANEL_WIDTH} from './panel.js?v=24'; +import {MSG, CMD, HOST_MSG} from './net/protocol.js?v=24'; +import {netBroadcast, netConnect, netDisconnect, netIsConnected, netIsHost, netListRooms, netMakeRoomCode, netMyId, netOnMessage, netOnStatus, netRoom, netSendCommand} from './net.js?v=24'; let state = 'start-game'; @@ -852,22 +852,9 @@ function updateNetMenu() { const s = netMenuState; if (!s) return; if (s.phase === 'connecting') return; - if (key('Escape')) { - if (afterKeyDelay()) { - state = 'start-menu'; - netMenuState = null; - netError = null; - } - return; - } - const activate = (i) => { - if (i === 2) { - state = 'start-menu'; - netMenuState = null; - netError = null; - return; - } - const hostBtn = i === 0; + + const joinRoom = (code) => { + const fromBrowse = s.phase === 'browse'; let name = s.name; if (!name) { name = prompt('Your name:') || ''; @@ -875,10 +862,6 @@ function updateNetMenu() { if (!name) return; s.name = name.slice(0, 20); } - const code = hostBtn - ? (new URLSearchParams(location.search).get('room') || netMakeRoomCode()) - : (prompt('Room code:') || '').toUpperCase().trim(); - clearKeys(); if (!code) return; s.phase = 'connecting'; netError = null; @@ -900,42 +883,148 @@ function updateNetMenu() { menuState = null; state = 'net-lobby'; } - }).catch(() => { - s.phase = 'setup'; - netError = 'connection failed'; + }).catch((e) => { + netError = e.message || 'connection failed'; + if (fromBrowse) { + s.phase = 'browse'; + s.lastRefresh = 0; + } else { + s.phase = 'setup'; + } }); }; - if (key('ArrowUp')) { - if (afterKeyDelay()) { - s.selected = (s.selected - 1 + 3) % 3; - playTickSound(); + const refreshRooms = () => { + if (s.loading) return; + s.loading = true; + s.lastRefresh = Date.now(); + netListRooms().then((rooms) => { + s.rooms = rooms || []; + s.loading = false; + s.error = null; + }).catch((e) => { + s.loading = false; + s.error = e.message; + }); + }; + + const goBack = () => { + state = 'start-menu'; + netMenuState = null; + netError = null; + }; + + const enterBrowse = () => { + s.phase = 'browse'; + s.rooms = []; + s.selected = 0; + s.scrollOffset = 0; + s.loading = false; + s.error = null; + s.lastRefresh = 0; + refreshRooms(); + }; + + if (s.phase === 'setup') { + if (key('Escape')) { + if (afterKeyDelay()) goBack(); + return; } - } else if (key('ArrowDown')) { - if (afterKeyDelay()) { - s.selected = (s.selected + 1) % 3; - playTickSound(); + if (key('ArrowUp')) { + if (afterKeyDelay()) { + s.selected = (s.selected - 1 + 3) % 3; + playTickSound(); + } + } else if (key('ArrowDown')) { + if (afterKeyDelay()) { + s.selected = (s.selected + 1) % 3; + playTickSound(); + } + } else if (key('Enter')) { + if (afterKeyDelay()) { + if (s.selected === 0) joinRoom(new URLSearchParams(location.search).get('room') || netMakeRoomCode()); + else if (s.selected === 1) enterBrowse(); + else goBack(); + } } - } else if (key('Enter')) { - if (afterKeyDelay()) { - activate(s.selected); + const click = popClick(); + if (click) { + const buttons = [[PANEL_X+100, 140], [PANEL_X+100, 170], [PANEL_X+100, 200]]; + for (let i = 0; i < 3; i++) { + if (checkHit(click.x, click.y, buttons[i][0], buttons[i][1], 200, 22)) { + s.selected = i; + playTickSound(); + if (i === 0) joinRoom(new URLSearchParams(location.search).get('room') || netMakeRoomCode()); + else if (i === 1) enterBrowse(); + else goBack(); + return; + } + } } + return; } - const click = popClick(); - if (!click) return; - const buttons = [ - [PANEL_X+100, 140], - [PANEL_X+100, 170], - [PANEL_X+100, 200], - ]; - for (let i = 0; i < buttons.length; i++) { - if (checkHit(click.x, click.y, buttons[i][0], buttons[i][1], 200, 22)) { - s.selected = i; - playTickSound(); - activate(i); + if (s.phase === 'browse') { + if (key('Escape')) { + if (afterKeyDelay()) { + s.phase = 'setup'; + s.selected = 1; + } return; } + if (!s.loading && (!s.lastRefresh || Date.now() - s.lastRefresh >= 2000)) refreshRooms(); + const rooms = s.rooms || []; + const itemCount = rooms.length + 2; + if (key('ArrowUp')) { + if (afterKeyDelay()) { + s.selected = (s.selected - 1 + itemCount) % itemCount; + playTickSound(); + } + } else if (key('ArrowDown')) { + if (afterKeyDelay()) { + s.selected = (s.selected + 1) % itemCount; + playTickSound(); + } + } else if (key('Enter')) { + if (afterKeyDelay() && !s.loading) { + if (s.selected < rooms.length) joinRoom(rooms[s.selected].room); + else if (s.selected === rooms.length) refreshRooms(); + else { + s.phase = 'setup'; + s.selected = 1; + } + } + } + if (s.selected < rooms.length) { + if (s.selected < s.scrollOffset) s.scrollOffset = s.selected; + if (s.selected > s.scrollOffset + 7) s.scrollOffset = s.selected - 7; + } + s.scrollOffset = Math.max(0, Math.min(s.scrollOffset, Math.max(0, rooms.length - 8))); + const click = popClick(); + if (click && !s.loading) { + for (let i = 0; i < Math.min(8, rooms.length); i++) { + const idx = s.scrollOffset + i; + if (idx >= rooms.length) break; + if (checkHit(click.x, click.y, PANEL_X+30, 115 + i*16, PANEL_WIDTH-60, 16)) { + s.selected = idx; + playTickSound(); + joinRoom(rooms[idx].room); + return; + } + } + if (checkHit(click.x, click.y, PANEL_X+40, 300, 90, 22)) { + s.selected = rooms.length; + playTickSound(); + refreshRooms(); + return; + } + if (checkHit(click.x, click.y, PANEL_X+150, 300, 90, 22)) { + s.selected = rooms.length + 1; + playTickSound(); + s.phase = 'setup'; + s.selected = 1; + } + } } } @@ -1201,31 +1290,31 @@ function update() { player.a = wrap(0, a -incr, 180); if (isPrecise || isFast || a % PLAYER_ANGLE_TICK_SOUND_INTERVAL === 0) playTickSound(); - } else if (key('ArrowRight')) { + } else if (isMyTurn && key('ArrowRight')) { if (isPrecise && !afterKeyDelay()) return; let incr = isFast ? PLAYER_ANGLE_FAST_INCREMENT : PLAYER_ANGLE_INCREMENT; player.a = wrap(0, a +incr, 180); if (isPrecise || isFast || a % PLAYER_ANGLE_TICK_SOUND_INTERVAL === 0) playTickSound(); - } else if (key('ArrowUp')) { + } else if (isMyTurn && key('ArrowUp')) { if (isPrecise && !afterKeyDelay()) return; let incr = isFast ? PLAYER_POWER_FAST_INCREMENT : PLAYER_POWER_INCREMENT; player.p = clamp(0, p +incr, maxPower); if (p < maxPower && (isPrecise || isFast || p % PLAYER_POWER_TICK_SOUND_INTERVAL === 0)) playTickSound(); - } else if (key('ArrowDown')) { + } else if (isMyTurn && key('ArrowDown')) { if (isPrecise && !afterKeyDelay()) return; let incr = isFast ? PLAYER_POWER_FAST_INCREMENT : PLAYER_POWER_INCREMENT; player.p = clamp(0, p -incr, maxPower); if (p > 0 && (isPrecise || isFast || p % PLAYER_POWER_TICK_SOUND_INTERVAL === 0)) playTickSound(); - } else if (key('Tab')) { + } else if (isMyTurn && key('Tab')) { if (!afterKeyDelay()) return; const dir = isReverse ? -1 : 1; player.currentWeapon = wrap(0, player.currentWeapon+dir, player.weapons.length-1); playTickSound(); - } else if (key(' ')) { + } else if (isMyTurn && key(' ')) { if (!afterKeyDelay()) return; shoot = {a, p}; @@ -1918,6 +2007,7 @@ function drawGameControls() { if (state !== 'aim') return; const player = players[currentPlayer]; if (player.ai) return; + if (networkMode && player.id !== netPlayerId) return; const maxPower = player.energy * PLAYER_ENERGY_POWER_MULTIPLIER; @@ -2135,9 +2225,14 @@ function drawParticles() { function drawNetMenuPanel() { drawPanelBg(framebuffer); + const s = netMenuState; + if (s && s.phase === 'browse') { + drawPanelTitle(framebuffer, 'JOIN GAME', '#48f'); + drawRoomsList(); + return; + } drawPanelTitle(framebuffer, 'MULTIPLAYER', '#48f'); - const s = netMenuState; drawPanelText(framebuffer, 'Play against other players', PANEL_X+40, 100, '#aaa'); if (!s || s.phase === 'connecting') { @@ -2154,6 +2249,32 @@ function drawNetMenuPanel() { drawPanelText(framebuffer, `Relay: ${netIsConnected() ? 'connected' : 'offline'}`, PANEL_X+40, 270, '#aaa'); } +function drawRoomsList() { + const s = netMenuState; + if (!s) return; + const rooms = s.rooms || []; + if (s.loading && !rooms.length) { + drawPanelText(framebuffer, 'SEARCHING...', PANEL_X+40, 130, 'yellow'); + } + if (s.error) { + drawPanelText(framebuffer, s.error, PANEL_X+40, 130, '#f66'); + } + if (!rooms.length && !s.loading && !s.error) { + drawPanelText(framebuffer, 'No open games', PANEL_X+40, 130, '#aaa'); + } + for (let i = 0; i < Math.min(8, rooms.length); i++) { + const idx = s.scrollOffset + i; + if (idx >= rooms.length) break; + const r = rooms[idx]; + const isSel = s.selected === idx; + const y = 115 + i*16; + drawPanelText(framebuffer, `${r.room} ${r.host}`, PANEL_X+40, y, isSel ? 'yellow' : 'white'); + drawPanelText(framebuffer, `${r.players}/${MAX_PLAYERS}`, PANEL_X+PANEL_WIDTH-40, y, isSel ? 'yellow' : '#aaa', 'right'); + } + drawButton(framebuffer, PANEL_X+40, 300, 90, 22, 'REFRESH', s.selected === rooms.length); + drawButton(framebuffer, PANEL_X+150, 300, 90, 22, 'BACK', s.selected === rooms.length + 1); +} + function drawNetLobbyPanel() { drawPanelBg(framebuffer); drawPanelTitle(framebuffer, 'LOBBY', '#48f'); diff --git a/src/net.js b/src/net.js index a019e2c..b79aaef 100644 --- a/src/net.js +++ b/src/net.js @@ -1,4 +1,4 @@ -import {MSG, HOST_MSG} from './net/protocol.js?v=22'; +import {MSG, HOST_MSG} from './net/protocol.js?v=24'; let socket = null; let myId = null; @@ -44,6 +44,42 @@ export function netMakeRoomCode() { return code; } +export function netListRooms() { + return new Promise((resolve, reject) => { + let done = false; + let ws; + const finish = (err, rooms) => { + if (done) return; + done = true; + clearTimeout(timer); + if (ws) { try { ws.close(); } catch {} } + if (err) reject(new Error(err)); + else resolve(rooms); + }; + const timer = setTimeout(() => finish('timeout'), 5000); + try { + ws = new WebSocket(relayUrl()); + } catch (e) { + finish('connection failed'); + return; + } + ws.onopen = () => { + ws.send(JSON.stringify({type: MSG.LIST_ROOMS})); + }; + ws.onmessage = (event) => { + let msg; + try { + msg = JSON.parse(event.data); + } catch { + return; + } + if (msg.type === MSG.ROOMS) finish(null, msg.rooms); + else if (msg.type === MSG.ERROR) finish(msg.message || 'error'); + }; + ws.onerror = () => finish('connection failed'); + }); +} + export function netOnMessage(fn) { handlers.push(fn); } @@ -92,6 +128,12 @@ export function netConnect(name, code) { resolve({id: msg.id, host: msg.host, room: msg.room}); return; } + if (msg.type === MSG.ERROR && !joined) { + setStatus('disconnected'); + try { socket.close(); } catch {} + reject(new Error(msg.message || 'error')); + return; + } emit(msg); }; diff --git a/src/net/protocol.js b/src/net/protocol.js index 0886e66..b806002 100644 --- a/src/net/protocol.js +++ b/src/net/protocol.js @@ -5,9 +5,11 @@ export const MSG = { // client -> relay JOIN: 'join', // {room, name} LEAVE: 'leave', // {} + LIST_ROOMS: 'list-rooms', // {} fetch open rooms before joining // relay -> client JOINED: 'joined', // {id, host, room} ROSTER: 'roster', // {players: [{id, name}]} (host only) + ROOMS: 'rooms', // {rooms: [{room, players, host}]} HOST_LEFT: 'host-left', // {} ERROR: 'error', // {message} // client -> relay -> host (non-host clients only) diff --git a/src/panel.js b/src/panel.js index 2dce76a..8a80001 100644 --- a/src/panel.js +++ b/src/panel.js @@ -1,5 +1,5 @@ -import {PANEL_WIDTH, PANEL_HEIGHT, PANEL_FONT_SIZE, PANEL_TITLE_FONT_SIZE, PANEL_BG_COLOR, PANEL_BORDER_COLOR, W, H, FONT_WIDTH} from './constants.js?v=22'; -import {drawRect, drawLine} from './gfx.js?v=22'; +import {PANEL_WIDTH, PANEL_HEIGHT, PANEL_FONT_SIZE, PANEL_TITLE_FONT_SIZE, PANEL_BG_COLOR, PANEL_BORDER_COLOR, W, H, FONT_WIDTH} from './constants.js?v=24'; +import {drawRect, drawLine} from './gfx.js?v=24'; export const PANEL_X = (W - PANEL_WIDTH) / 2; export {PANEL_WIDTH}; diff --git a/src/projectiles.js b/src/projectiles.js index 440481e..8adff19 100644 --- a/src/projectiles.js +++ b/src/projectiles.js @@ -1,10 +1,10 @@ -import {H, PROJECTILE_ITERATIONS_PER_FRAME, PROJECTILE_ITERATION_PROGRESS, PROJECTILE_MAX_SOUND_FREQUENCY, PROJECTILE_MIN_SOUND_FREQUENCY, PROJECTILE_POWER_REDUCTION_FACTOR, PROJECTILE_WIND_REDUCTION_FACTOR, WEAPON_TYPES, PLAYER_TANK_Y_FOOTPRINT} from './constants.js?v=22'; -import {checkLineWith, drawLineVirtual} from './gfx.js?v=22'; -import {createParticles, isTank, isTankShield, spawnNapalm, tracerMode} from './main.js?v=22'; -import {deg2rad, parable, rad2deg, wrap} from './math.js?v=22'; -import {audio, createOsc} from './sound.js?v=22'; -import {isTerrain, landHeight} from './terrain.js?v=22'; -import {EXPLOSION_TYPES} from './weapons.js?v=22'; +import {H, PROJECTILE_ITERATIONS_PER_FRAME, PROJECTILE_ITERATION_PROGRESS, PROJECTILE_MAX_SOUND_FREQUENCY, PROJECTILE_MIN_SOUND_FREQUENCY, PROJECTILE_POWER_REDUCTION_FACTOR, PROJECTILE_WIND_REDUCTION_FACTOR, WEAPON_TYPES, PLAYER_TANK_Y_FOOTPRINT} from './constants.js?v=24'; +import {checkLineWith, drawLineVirtual} from './gfx.js?v=24'; +import {createParticles, isTank, isTankShield, spawnNapalm, tracerMode} from './main.js?v=24'; +import {deg2rad, parable, rad2deg, wrap} from './math.js?v=24'; +import {audio, createOsc} from './sound.js?v=24'; +import {isTerrain, landHeight} from './terrain.js?v=24'; +import {EXPLOSION_TYPES} from './weapons.js?v=24'; export const PROJECTILE_TYPES = { diff --git a/src/sky.js b/src/sky.js index 2425d5f..e7bc4af 100644 --- a/src/sky.js +++ b/src/sky.js @@ -1,7 +1,7 @@ -import {SKY_COLORS} from './constants.js?v=22'; -import {drawRect} from './gfx.js?v=22'; -import {gradient} from './math.js?v=22'; -import {sample} from './utils.js?v=22'; +import {SKY_COLORS} from './constants.js?v=24'; +import {drawRect} from './gfx.js?v=24'; +import {gradient} from './math.js?v=24'; +import {sample} from './utils.js?v=24'; export function generateSky(ctx) { const {width, height} = ctx.canvas; diff --git a/src/sound.js b/src/sound.js index 354e863..550a78f 100644 --- a/src/sound.js +++ b/src/sound.js @@ -1,4 +1,4 @@ -import {SOUND_MUSIC_VOLUME, SOUND_SFX_VOLUME} from './constants.js?v=22'; +import {SOUND_MUSIC_VOLUME, SOUND_SFX_VOLUME} from './constants.js?v=24'; // @ts-ignore export const audio = new (window.AudioContext || window.webkitAudioContext)(); diff --git a/src/terrain.js b/src/terrain.js index 448383c..f7de81c 100644 --- a/src/terrain.js +++ b/src/terrain.js @@ -1,6 +1,6 @@ -import {clipCanvas, drawCircle, drawRect} from './gfx.js?v=22'; -import {clamp, coords2index, random, randomInt} from './math.js?v=22'; -import {sample} from './utils.js?v=22'; +import {clipCanvas, drawCircle, drawRect} from './gfx.js?v=24'; +import {clamp, coords2index, random, randomInt} from './math.js?v=24'; +import {sample} from './utils.js?v=24'; let cachedImageData; diff --git a/src/utils.js b/src/utils.js index 67ea367..5e4479e 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,4 +1,4 @@ -import {randomInt} from './math.js?v=22'; +import {randomInt} from './math.js?v=24'; export function sample(arr) { return arr[randomInt(0, arr.length-1)]; diff --git a/src/weapons.js b/src/weapons.js index c869225..607fa77 100644 --- a/src/weapons.js +++ b/src/weapons.js @@ -1,8 +1,8 @@ -import {PLAYER_TANK_BOUNDING_RADIUS, PLAYER_TANK_Y_FOOTPRINT, SHIELD_TYPES} from './constants.js?v=22'; -import {drawCircle, plot} from './gfx.js?v=22'; -import {clamp, cycle, distance, randomInt} from './math.js?v=22'; -import {audio, createOsc} from './sound.js?v=22'; -import {clipTerrain} from './terrain.js?v=22'; +import {PLAYER_TANK_BOUNDING_RADIUS, PLAYER_TANK_Y_FOOTPRINT, SHIELD_TYPES} from './constants.js?v=24'; +import {drawCircle, plot} from './gfx.js?v=24'; +import {clamp, cycle, distance, randomInt} from './math.js?v=24'; +import {audio, createOsc} from './sound.js?v=24'; +import {clipTerrain} from './terrain.js?v=24'; export function drawExplosion(ctx, x, y, r) { const step = Math.max(2, Math.floor(r / 6));