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.dervland.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; } renderEnt(ent, textArgs) { let { ctx, assetsIn } = this; if (ent.health <= 0) return; 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') return; Object.assign(textArgs, [`HP: ${ent.health}`, ent.pos.x, ent.pos.y - 64 / 2]); ctx.strokeText(...textArgs); ctx.fillText(...textArgs); ctx.textBaseline = "top"; Object.assign(textArgs, [`${ent.username || 'Guest'}`, ent.pos.x, ent.pos.y + 64 / 2]); ctx.strokeText(...textArgs); ctx.fillText(...textArgs); } 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 32px 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) { this.renderEnt(ent, textArgs); } ctx.restore(); ctx.textAlign = "start"; ctx.textBaseline = "top"; ctx.font = "bold 48px sans-serif"; ctx.fillStyle = "rgb(0,0,0)"; textArgs = [`XY: ${-Math.round(player.camera.x)}, ${-Math.round(player.camera.y)}`, 25, 25]; ctx.strokeText(...textArgs); ctx.fillText(...textArgs); textArgs = [`PT: ${Math.round(player.headCount)}`, 25, 75]; ctx.strokeText(...textArgs); ctx.fillText(...textArgs); textArgs = [`CH: ${Math.round(player.ticks * 100 / 300)}%`, 25, 125]; 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\n`, cs / 2, cs / 2); return; } 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 32px 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)); } entMapper(j, i, entList) { let that = this; let { player } = this; let you = player.you; 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]; } let { handleTick, bounce, handleInt } = type.prototype; Object.assign(x, { handleTick, bounce, handleInt }); 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; } recv({ data }) { let { player } = this; let you = player.you; let that = this; let entList = JSON.parse(data); entList = entList.map((j, i) => that.entMapper(j, i, entList)) 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]; } init() { let u = new URL(window.location); let tok = u.searchParams.get('token'); if (!tok) tok = window.localStorage.getItem('tok'); window.localStorage.setItem('tok', tok); window.history.replaceState(null, '', window.location.pathname); 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(); this.handleScores(); } async handleScores() { let jason = await (fetch('/leaderboard').then(x => x.text())) document.querySelector('.lb').textContent = jason; let sant = document.querySelector('.lb').innerHTML; sant = sant.replaceAll(/\[([^\[]+)\]/g, '[$1]'); document.querySelector('.lb').innerHTML = sant; } } var game = new Game(); game.init();