diff --git a/public/client.js b/public/client.js index 861dd8d..2465cfb 100644 --- a/public/client.js +++ b/public/client.js @@ -6,6 +6,7 @@ const global = (0,eval)('this') const scene = new THREE.Scene() const raycaster = new THREE.Raycaster() const camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 0.1, 1000) +const clock = new THREE.Clock() const renderer = new THREE.WebGLRenderer() const backgroundColor = new THREE.Color().setHex(0x112233) scene.background = backgroundColor @@ -82,8 +83,10 @@ const mouse = {} var stats = new Stats() stats.showPanel(0) +var delta = 0 function render() { stats.begin() + delta = clock.getDelta() cameraMovement() Object.values(positionTweens).forEach((tween) => tween.update()) // TODO: clean up tweens renderer.render(scene, camera) @@ -94,11 +97,14 @@ function minimapRender() { minimapRenderer.render(scene, minimapCamera) } +const lockedCameraSpeedMultiplier = 3 var cameraLocked = true function followCamera() { const entity = entities[playerId] if (entity == null) { return } + const cameraSpeed = lockedCameraSpeedMultiplier * delta + const distanceX = Math.abs((entity.position.x + cameraOffsetX) - camera.position.x) const distanceY = Math.abs((entity.position.y + cameraOffsetY) - camera.position.y) @@ -128,13 +134,15 @@ function followCamera() { } } -const cameraSpeed = 0.03 +const cameraSpeedMultiplier = 10 function cameraMovement() { if (cameraLocked) { followCamera() return } + const cameraSpeed = cameraSpeedMultiplier * delta + if (keysDown.ArrowLeft) { camera.position.x -= cameraSpeed } else if (keysDown.ArrowRight) { camera.position.x += cameraSpeed } diff --git a/src/entity.js b/src/entity.js index a6dc525..2beca9e 100644 --- a/src/entity.js +++ b/src/entity.js @@ -329,13 +329,13 @@ export default class Entity { let customMultipliers = 0 if (this.hasBuff(Buff.exposed.id)) { const buff = this.getBuff(Buff.exposed.id) - if (buff.source == source.id) { + if (buff.source == source?.id) { customMultipliers += (buff.onHitMultiplier - 1) this.removeBuff(Buff.exposed.id) } } - const damageMultiplerBuffs = source.buffs.map((it) => it.getBuff).filter((it) => it != null && it.damageMultiplier != null) + const damageMultiplerBuffs = (source?.buffs ?? []).map((it) => it.getBuff).filter((it) => it != null && it.damageMultiplier != null) const buffPassiveDamageMultiplier = damageMultiplerBuffs.reduce((it) => it.damageMultiplier - 1, 0) const damageMultipler = 1 + buffPassiveDamageMultiplier + customMultipliers @@ -440,7 +440,7 @@ export default class Entity { } } - console.error(`Can't fix position ([${futurePosition.x}, ${futurePosition.y}]) of entity ID: ${this.id}`) + console.error({ error: 'position_unfixable', id: this.id, futurePosition }) } isColliding() { @@ -742,8 +742,7 @@ export default class Entity { } } - for (let failsafe = 0; failsafe <= (this.pathfindingObstacleLimit ?? 1000); failsafe++) { - if (failsafe >= 10) { console.error('Failsafe is reached!!!'); process.exit(0) } + for (let failsafe = 0; failsafe <= (this.pathfindingObstacleLimit ?? 10); failsafe++) { const obstaclesArray = Array.from(obstacles.values()) for (const obstacle of obstaclesArray) { diff --git a/src/game.js b/src/game.js index b8aa223..8c2c25a 100644 --- a/src/game.js +++ b/src/game.js @@ -35,7 +35,7 @@ export default class Game { action(id, options) { const entity = this.entities.find((it) => it.id == id) if (entity == null) { - console.error({ error: 'Invalid ID' }) + console.info({ info: 'action_invalid_id', id, options }) return } @@ -54,7 +54,7 @@ export default class Game { if (object instanceof Entity) { this.despawnEntity(object) } else if (object instanceof Terrain) { this.removeTerrain(object) } else if (object instanceof Projectile) { this.despawnProjectile(object) } - else { console.error({ error: { reason: 'Can\'t despawn object', object } }) } + else { console.error({ error: 'despawn_unknown_object', object }) } } despawnEntity(entity) { @@ -92,7 +92,7 @@ export default class Game { if (object instanceof Entity) { this.spawnEntity(object) } else if (object instanceof Terrain) { this.addTerrain(object) } else if (object instanceof Projectile) { this.spawnProjectile(object) } - else { console.error({ error: { reason: 'Can\'t spawn object', object } }) } + else { console.error({ error: 'spawn_unknown_object', object }) } } spawnEntity(entity) { @@ -109,7 +109,7 @@ export default class Game { if (this.#gameLoopIntervalId != null) { return } this.#startTimestamp = performance.now() + (this.currentTick * this.tickBudget) - console.info(`Started game ${this.id} with ${this.tickRate} tps. Starting on tick ${this.currentTick}.`) + console.info({ event: 'game_start', id: this.id, tickRate: this.tickRate, currentTick: this.currentTick }) this.#gameLoopIntervalId = setInterval(this.#gameLoopCall.bind(this), this.tickBudget / 5) } @@ -118,7 +118,7 @@ export default class Game { clearInterval(this.#gameLoopIntervalId) this.#gameLoopIntervalId = null - console.info(`Stopped game ${this.id}. Stopped on tick ${this.currentTick}.`) + console.info({ event: 'game_stop', id: this.id, currentTick: this.currentTick }) } subscription(websocket, id) { @@ -126,7 +126,10 @@ export default class Game { const game = this const entity = game.entities.find((it) => it.id == id) - if (entity == null) { return } + if (entity == null) { + websocket.close() + return + } const team = entity.team const state = game.visionByTeam(team) @@ -189,15 +192,14 @@ export default class Game { const before = performance.now() this.update() const after = performance.now() - const taken = (after - before) + const tickTaken = after - before const useAbsoluteBehind = true const absoluteBehind = Math.max(0, (start - this.#startTimestamp) - ((this.currentTick) * tickBudget)) this.#nextTickAt = start + tickBudget - (useAbsoluteBehind ? absoluteBehind : prevBehind) - if (after - before > tickBudget) { - const behindNotice = absoluteBehind > 0.1 ? `(Was already behind ${absoluteBehind.toFixed(1)} ms)` : `` - console.warn(`Can't keep up! A tick took ${taken.toFixed(1)} ms / ${tickBudget.toFixed(1)} ms. ${behindNotice}`) + if (tickTaken > tickBudget) { + console.warn({ warn: 'overload', tickTaken, tickBudget, absoluteBehind }) } } } diff --git a/src/index.js b/src/index.js index 5eeaf1f..1fcda57 100644 --- a/src/index.js +++ b/src/index.js @@ -9,7 +9,7 @@ try { os.setPriority(process.pid, os.constants.priority.PRIORITY_HIGHEST) } catch (error) { - console.warn('Could not adjust process priority on startup.') + console.warn({ warn: 'process_priority_unadjustable' }) } const app = new WebSocketExpress() @@ -30,16 +30,22 @@ app.ws('/ws', async (req, res) => { websocket.on('message', (rawData) => { const message = JSON.parse(rawData) - console.log(message) + console.info(message) if (message.action == 'join') { const id = message.id const connectionId = crypto.randomUUID() + if (!game.entities.some((it) => it.id == id)) { + console.info({ error: 'join_invalid_id', id, connectionId }) + return + } + + console.info({ event: 'connected', id, connectionId }) websocket.send(JSON.stringify(game.joinReport())) const subscription = game.subscription(websocket, id).bind(game) game.subscriptions.set(connectionId, subscription) websocket.on('close', () => { - console.log({ event: 'disconnected', id }) + console.info({ event: 'disconnected', id }) game.subscriptions.delete(connectionId) }) return @@ -50,7 +56,7 @@ app.ws('/ws', async (req, res) => { }) app.listen(port, () => { - console.info(`Server started! Visit http://localhost:${port}`) + console.info({ event: 'startup', visit: `http://localhost:${port}` }) Dungeon.scenario(game) }) diff --git a/src/level.js b/src/level.js index fc65fad..408ff2f 100644 --- a/src/level.js +++ b/src/level.js @@ -26,6 +26,7 @@ export class Dungeon { game.spawnEntity(new Entity(Template.player({ id: '2', spawnPosition: new Vector2(1500, 700), team, dead: true }))) game.spawnEntity(new Entity(Template.basilisk({ id: 'boss', spawnPosition: new Vector2(2200, 750), team: Team.neutral }))) + game.entities.find((it) => it.id == 'boss').damage(9999) game.start() } diff --git a/src/pathfind.js b/src/pathfind.js index 76b8fb5..7f6058c 100644 --- a/src/pathfind.js +++ b/src/pathfind.js @@ -182,15 +182,6 @@ export default class Pathfind { graphIndex += 5 } - // const niceGraph = [] - // for (let i = 0; i < graph.length / 5; i += 5) { - // niceGraph.push({ - // from: [graph[i], graph[i + 1]], - // to: [graph[i + 2], graph[i + 3]], - // distance: graph[i + 4], - // }) - // } - // console.log(niceGraph) return graph } diff --git a/src/template.js b/src/template.js index 7f454ba..b35d2b9 100644 --- a/src/template.js +++ b/src/template.js @@ -7,7 +7,7 @@ export default class Template { return { abilities: {}, height: 100, - logic: this.#basiliskLogic, + logic: this.#basiliskLogic(), radius: 180, speed: 230, visualRadius: 170, @@ -57,9 +57,27 @@ export default class Template { } static #basiliskLogic() { - const entity = this + let diedOnTick = null - return + return function builtBasiliskLogic() { + const entity = this + const despawnDelaySec = 5 + + const despawnDelay = entity.game?.secToTick(despawnDelaySec) ?? 1 + const timestamp = entity.game?.currentTick ?? 0 + + if (entity.dead) { + if (diedOnTick == null) { + diedOnTick = timestamp + } + else if (diedOnTick + despawnDelay < timestamp) { + entity.despawn() + } + } + else if (diedOnTick != null) { + diedOnTick = null + } + } } static #minionLogic(route = [], odd = false) {