class Command { execute() { } toNetwork() { return null; } } class PunchCmd extends Command { execute(p) { p.punch(); } toNetwork(p) { return { Id: p.id, Action: ' ', ActionType: "keyDown" }; } } class JumpCmd extends Command { execute(p) { p.jump(); } toNetwork(p) { return { Id: p.id, Action: 'w', ActionType: 'keyDown' }; } } class CrouchPressCmd extends Command { execute(p) { p.crouching = true; } toNetwork(p) { return { Id: p.id, Action: 's', ActionType: 'keyDown' }; } } class CrouchUnpressCmd extends Command { execute(p) { p.crouching = false; } toNetwork(p) { return { Id: p.id, Action: 's', ActionType: 'keyUp' }; } } class MoveLeftPressCmd extends Command { execute(p) { p.keys.a = true; } toNetwork(p) { return { Id: p.id, Action: 'a', ActionType: 'keyDown' }; } } class MoveLeftUnpressCmd extends Command { execute(p) { p.keys.a = false; } toNetwork(p) { return { Id: p.id, Action: 'a', ActionType: 'keyUp' }; } } class MoveRightPressCmd extends Command { execute(p) { p.keys.d = true; } toNetwork(p) { return { Id: p.id, Action: 'd', ActionType: 'keyDown' }; } } class MoveRightUnpressCmd extends Command { execute(p) { p.keys.d = false; } toNetwork(p) { return { Id: p.id, Action: 'd', ActionType: 'keyUp' }; } } class TalkCmd extends Command { constructor(message) { super(); this.message = message; } execute(p) { p.talk(this.message); } toNetwork(p) { return { Id: p.id, Action: 't', ActionType: '', Message: this.message } } } class DisconnectedCmd extends Command { execute() { } toNetwork(p) { return { Id: p.id, Action: "disconnected", ActionType: "" }; } } export class InputHandler { constructor(game) { this.game = game; this.downMap = new Map([ [' ', () => new PunchCmd()], ['w', () => new JumpCmd()], ['s', () => new CrouchPressCmd()], ['a', () => new MoveLeftPressCmd()], ['d', () => new MoveRightPressCmd()], ]); this.upMap = new Map([ ['s', () => new CrouchUnpressCmd()], ['a', () => new MoveLeftUnpressCmd()], ['d', () => new MoveRightUnpressCmd()], ]); } handleKeyDown(e, player) { const key = e.code === 'Space' ? ' ' : (e.key || '').toLowerCase(); const k = this.downMap.get(key); if (!k) { return; } e.preventDefault(); e.stopPropagation(); this.dispatch(k(), player) } handleKeyUp(e, player) { const key = e.code === "Space" ? ' ' : e.key; const k = this.upMap.get(key); if (!k) { return; } e.preventDefault(); e.stopPropagation(); this.dispatch(k(), player) } dispatch(cmd, player) { if (!player) return; cmd.execute(player, this.game); if (player.isLocal) { const msg = cmd.toNetwork(player); if (msg) { this.game._send({ ...msg, PosX: Math.floor(player.x), PosY: Math.floor(player.y), Facing: player.facing, Ts: Date.now(), }); } } } handleNetworkMessage(data) { this.game._ensurePlayer(data.Id); const p = this.game.players?.[data.Id]; if (!p) { return; } if (typeof data.PosX === "number") { p._lastTx = p._tx ?? data.PosX; p._tx = data.PosX; } if (typeof data.PosY === "number") { p._lastTy = p._ty ?? data.PosY; p._ty = data.PosY; } if (typeof data.Facing === "number") { p._tFacing = data.Facing; } p._lastUpdateTime = Date.now(); const action = data.Action; const actionType = data.ActionType; const apply = (cmd) => cmd && cmd.execute(p, this.game); switch (action) { case 'disconnected': delete this.game.players[data.Id]; break; case 't': apply(new TalkCmd(data.Message ?? "")); break; case ' ': apply(new PunchCmd()); break; case 'w': apply(new JumpCmd()); break; case 's': apply(actionType === 'keyDown' ? new CrouchPressCmd() : new CrouchUnpressCmd()); break; case 'a': apply(actionType === 'keyDown' ? new MoveLeftPressCmd() : new MoveLeftUnpressCmd()); break; case 'd': apply(actionType === 'keyDown' ? new MoveRightPressCmd() : new MoveRightUnpressCmd()); break; } } releaseAll(player) { const cmds = []; if (player.keys?.a) cmds.push(new MoveLeftUnpressCmd()); if (player.keys?.d) cmds.push(new MoveRightUnpressCmd()); if (player.crouching) cmds.push(new CrouchUnpressCmd()); for (const c of cmds) this.dispatch(c, player); } }