import Player from "./player.js"; import GameBasic from "./game_basic.js"; import NPC from "./npc.js"; import Shooter from "./shooter.js"; const cs = 1024; const assets = [ 'assets/player.svg', 'assets/head.svg', 'assets/map.svg', 'assets/npc.svg', 'assets/shooter.svg' ]; const legalTypes = { Player, NPC, Shooter } const bodies = { 'Player': 0, 'NPC': 3, 'Shooter': 4 }; const emojis = [ 'Troll', 'Exit', 'Elec Piano Loop', 'No', 'Yes' ] const origin = (window.location.href.indexOf('localhost') != -1) ? window.location.href : 'https://ub.xuyezo.net/' class Game extends GameBasic { constructor() { super(); let assetsIn = {}; for (let asset in assets) { assetsIn[asset] = new Image(); assetsIn[asset].src = assets[asset]; } let canvas = document.querySelector("#canvas"); canvas.width = canvas.height = cs; this.canvas = canvas; this.ctx = canvas.getContext("2d"); this.assetsIn = assetsIn; } render() { let { ctx, assetsIn, entities, player, width, height } = this; ctx.clearRect(0, 0, cs, cs); ctx.strokeStyle = "rgb(255,255,255)"; ctx.lineWidth = "8"; ctx.textAlign = "center"; ctx.textBaseline = "bottom"; ctx.lineCap = "round"; ctx.lineJoin = "round"; ctx.font = "bold 16px sans-serif"; ctx.save(); ctx.translate(player.camera.x + cs / 2, player.camera.y + cs / 2); ctx.drawImage(assetsIn[2], -width / 2, -height / 2, width, height); let textArgs = []; for (let ent of entities) { if (ent.health <= 0) continue; if (ent.type == 'Player') { ctx.save(); ctx.translate(ent.pos.x, ent.pos.y); ctx.rotate(ent.rot); ctx.drawImage(assetsIn[1], -64 / 2, -128, 64, 128); ctx.restore(); if (ent.playing) { ctx.strokeStyle = 'white'; ctx.lineWidth = "20"; ctx.beginPath(); ctx.arc(ent.pos.x, ent.pos.y, 32, 0, 2 * Math.PI); ctx.stroke(); } ctx.fillStyle = ent.you.split('-')[0]; ctx.beginPath(); ctx.arc(ent.pos.x, ent.pos.y, 32, 0, 2 * Math.PI); ctx.fill(); } let a = assetsIn[bodies[ent.type]] ctx.drawImage(a, ent.pos.x - 64 / 2, ent.pos.y - 64 / 2, 64, 64); if (ent.type != 'Player') continue; textArgs = [`HP: ${ent.health} PT: ${ent.headCount}`, ent.pos.x, ent.pos.y - 64 / 2]; ctx.strokeText(...textArgs); ctx.fillText(...textArgs); ctx.textBaseline = "top"; textArgs = [`${ent.username || 'Guest'}`, ent.pos.x, ent.pos.y + 64 / 2]; ctx.strokeText(...textArgs); ctx.fillText(...textArgs); } ctx.restore(); ctx.textAlign = "start"; ctx.textBaseline = "top"; ctx.font = "bold 32px sans-serif"; textArgs = [`XY: ${-Math.round(player.camera.x)}, ${-Math.round(player.camera.y)}`,25,25]; ctx.strokeText(...textArgs); ctx.fillText(...textArgs); if (player.health <= 0 || player.isMenu) { this.doMenu(); } } doMenu() { let { ctx, player } = this; ctx.fillStyle = 'rgba(0,0,0,0.5)'; ctx.fillRect(0, 0, cs, cs); ctx.fillStyle = 'rgb(255,255,255)'; ctx.textAlign = "center"; ctx.textBaseline = "middle"; ctx.font = "bold 48px sans-serif"; if (player.health <= 0) ctx.fillText('You died! Click to respawn', cs / 2, cs / 2); if (player.isMenu) { let r = Math.floor(Math.abs(player.rot / 1.2) % emojis.length); ctx.fillText(`Click to react ${emojis[r]}`, cs / 2, cs / 2) ctx.font = "bold 16px sans-serif"; ctx.fillText(`Wait for emojis ${emojis.join(', ')}`, cs / 2, cs / 2 + 50) } } click() { let { player } = this; if (player.health <= 0) { let that = this; this.ws.close(); this.ws = new WebSocket(origin); this.ws.addEventListener('open', () => this.opener(that)) } else if (player.isMenu) { player.r = Math.floor(Math.abs(player.rot / 1.2) % emojis.length); player.isMenu = false; this.sync(); } else { player.bump(); this.sync(); } } sync(first = false) { let { player } = this; if (first) { player.ref = new URL(window.location).searchParams.get('ref') || 'nobody'; } else { player.ref = false; } let p = player.legalProps.map(prop => player[prop]); p.splice(0, 0, 'SYNC'); this.ws.send(JSON.stringify(p)); } recv({ data }) { let { player } = this; let you = player.you; let that = this; let entList = JSON.parse(data); entList = entList.map((j, i) => { if (i == entList.length - 1) { that.width = j[0]; that.height = j[1]; return undefined; } if (!j) return undefined; let y = j; //(typeof j == 'string') ? JSON.parse(j) : j; let type = (Object.keys(legalTypes).indexOf(y[0]) == -1) ? Player : legalTypes[y[0]]; let props = new type().serverProps; let x = {}; for (let i in props) { x[props[i]] = y[i]; } x.handleTick = type.prototype.handleTick; x.bounce = type.prototype.bounce; if (x.r != 1 && type == Player) { let a = new Audio(`sfx/${emojis[x.r]}.wav`); a.addEventListener('ended', () => { if (x.you == you) { that.player.playing = false; that.sync(); } a.remove(); }) if (you != x.you) { x.r = 1; } a.play(); } return x; }) entList = entList.filter(x => x); let matchingPlayer = entList.filter(x => x && x.you == you) if (matchingPlayer.length == 0) { matchingPlayer = entList.filter(x => x && x.isYou); } this.player = Object.assign(this.player || new Player(false, false), matchingPlayer[0]); if (this.player.r != 1) { this.player.playing = true; this.player.r = 1; that.sync(); } this.entities = entList; if (this.entities.length == 0) this.entities = [this.player]; } async init() { let tok = new URL(window.location).searchParams.get('token'); super.init(); let that = this; this.ws = new WebSocket(origin); this.opener = function (that) { that.sync(true); if (tok) { that.ws.send(JSON.stringify(["AUTH", tok])); } setInterval(function () { that.sync() }, 1000 / 5); that.ws.addEventListener('message', function (e) { that.recv(e) }); }; this.ws.addEventListener('open', () => this.opener(that)) setInterval(function () { that.render() }, 1000 / 60); game.canvas.onclick = () => that.click(); let jason = await (fetch('/leaderboard').then(x => x.json())) let scores = {}; for (let e of jason) { scores[e.ip] = scores[e.ip] || 0; scores[e.ip] += e.ko ** 2; } for (let score in scores) { let s = scores[score]; scores[score] = Math.round(Math.sqrt(s)); } scores = Object.entries(scores).sort((a, b) => b[1] - a[1]); document.querySelector('.lb').textContent = scores.map(x => `${x[0]}: ${x[1]}`).join('\n'); } } var game = new Game(); game.init();