diff --git a/public/client.js b/public/client.js index 029f18b..05b596a 100644 --- a/public/client.js +++ b/public/client.js @@ -249,6 +249,35 @@ function connectWebSocket() { terrain.position.set(t.position.x / 100, t.position.y / 100, 0) } + if (playerId != null) { + const player = state.entities.find((e) => e.id == playerId) + if (player != null) { + for (let abilityIndex = 0; abilityIndex < 4; abilityIndex++) { + if (player.abilities[abilityIndex] != null) { + const ability = player.abilities[abilityIndex] + const lastCast = player.cooldowns[ability.id] ?? -99999 + const cooldownDuration = (ability.cooldown * state.tickRate) ?? 0 + const remainingCooldown = (lastCast + cooldownDuration) - state.currentTick + let cssPercentage = '100%' + let text = '' + if (remainingCooldown > 0) { + const cooldownPercentage = 1 - (remainingCooldown / cooldownDuration) + cssPercentage = `${Math.round(100 * cooldownPercentage)}%` + if (remainingCooldown / state.tickRate <= 5) { + text = `${(Math.round(10 * remainingCooldown / state.tickRate) / 10).toFixed(1)}` + } + else { + text = `${Math.round(remainingCooldown / state.tickRate)}` + } + } + + document.getElementById(`ability-${abilityIndex}-cooldown`).style.clipPath = `polygon(0 ${cssPercentage}, 100% ${cssPercentage}, 100% 100%, 0 100%)` + document.getElementById(`ability-${abilityIndex}-cooldown-text`).innerHTML = text + } + } + } + } + document.getElementById('state').innerHTML = JSON.stringify(state, null, 2) } } @@ -291,9 +320,6 @@ window.addEventListener('load', () => { if (event.code == 'KeyE') { websocket.send(JSON.stringify({ action: 'cast', slot: 3, id: playerId, x, y })) } - if (event.code == 'KeyR') { - websocket.send(JSON.stringify({ action: 'cast', slot: 4, id: playerId, x, y })) - } if (event.code == 'KeyD') { websocket.send(JSON.stringify({ action: 'teleport', id: playerId, x, y })) diff --git a/public/index.html b/public/index.html index 2585e80..164ffad 100644 --- a/public/index.html +++ b/public/index.html @@ -56,6 +56,53 @@ border-bottom: none; border-right: none; } + + .hud { + position: fixed; + gap: 10px; + padding: 15px; + padding-bottom: 10px; + display: flex; + inset: auto 0 0 0; + width: fit-content; + margin: auto; + border: 5px solid gray; + background-color: black; + border-top-left-radius: 10px; + border-top-right-radius: 10px; + border-bottom: none; + } + + .ability { + position: relative; + flex: 1 0 0; + border: 1px solid white; + width: 75px; + height: 75px; + color: white; + } + + .cooldown { + position: absolute; + top: 0; + left: 0; + width: 73px; + height: 73px; + background-color: grey; + opacity: 0.4; + } + + .cooldown-text { + position: absolute; + top: 0; + left: 0; + width: 73px; + height: 73px; + line-height: 73px; + text-align: center; + color: white; + font-family: monospace; + } @@ -63,7 +110,28 @@

Connection:


     
-    
+    
+
+ A +
+
+
+
+ Q +
+
+
+
+ W +
+
+
+
+ E +
+
+
+
diff --git a/src/ability.js b/src/ability.js index 9ca8c1f..58bb0ad 100644 --- a/src/ability.js +++ b/src/ability.js @@ -4,12 +4,16 @@ export default class Ability { id = crypto.randomUUID() name = 'Ability' cooldown = 0 - effect = () => {} castTime = 0 + #effect = () => {} + + get effect() { return this.#effect } + set effect(value) { this.#effect = value } + constructor(options) { if (options.name != null) { this.name = options.name } - if (options.effect != null) { this.effect = options.effect } + if (options.effect != null) { this.#effect = options.effect } if (options.cooldown != null) { this.cooldown = options.cooldown } if (options.castTime != null) { this.castTime = options.castTime } } diff --git a/src/entity.js b/src/entity.js index 1330aeb..8af3650 100644 --- a/src/entity.js +++ b/src/entity.js @@ -3,7 +3,6 @@ import SAT from 'sat' import SATX from './satx.js' import Pathfind from './pathfind.js' import Ability from './ability.js' -import Effect from './effect.js' export default class Entity { id = crypto.randomUUID() @@ -16,7 +15,6 @@ export default class Entity { Ability.straightShot(800, 7, 3000), () => {}, () => {}, - () => {}, ] casting = null // TODO: teams diff --git a/src/game.js b/src/game.js index a823adf..c1c71f9 100644 --- a/src/game.js +++ b/src/game.js @@ -8,12 +8,14 @@ export default class Game { currentTick = 0 width = 2000 height = 2000 + running = false + nextTick = 0 #entities = [] #eventEmitter = new EventEmitter() #projectiles = [] #terrains = [] - #tickBudget = Math.floor(1000 / this.tickRate) + #tickBudget = 1000 / this.tickRate get entities() { return this.#entities } get eventEmitter() { return this.#eventEmitter } @@ -80,10 +82,23 @@ export default class Game { } } + gameLoop() { + const tickBudget = this.#tickBudget + + if (this.nextTick != null) { + const nextTick = this.nextTick + this.nextTick = null + + let start = performance.now() + while (start < nextTick) { start = performance.now() } + + this.update() + this.nextTick = start + tickBudget + } + } + start() { - const start = performance.now() - this.update() - setTimeout(() => this.start(), Math.max(0, this.#tickBudget - (performance.now() - start))) + setInterval(() => this.gameLoop(), 1) } update() { diff --git a/src/index.js b/src/index.js index 1dfcb63..83c927e 100644 --- a/src/index.js +++ b/src/index.js @@ -3,7 +3,6 @@ import { WebSocketExpress } from 'websocket-express' import Game from './game.js' import Entity from './entity.js' import Terrain from './terrain.js' -import SATX from './satx.js' import { Vector2 } from 'three' const app = new WebSocketExpress()