const WebSocket = require('ws'); const pokemon = require('pokemon') const pokeTypes = require('./poketypes.json') const wss = new WebSocket.Server({ port: 5000 }) function flatten(obj) { if(obj !== null && obj !== undefined) { let ret = {}; for (let i in obj) { ret[i] = obj[i]; } return ret; } else { return obj } } function badObjectCopy(values, object) { if(values !== undefined && object !== undefined && values !== null && object !== null) { for(let i in values) { if(typeof values[i] === "object") { badObjectCopy(values[i], object[i]) } else { object[i] = values[i] } } } } function flattenObject(obj) { return Object.fromEntries(Object.entries(obj).map(([key, val]) => [key, flatten(val)])) } wss.on('connection', ws => { try { const timeStarted = new Date(); const Pokemon = {} const types = { grass: {type: 'grass' , __proto__: Pokemon}, fire: {type: 'fire' , __proto__: Pokemon}, water: {type: 'water' , __proto__: Pokemon}, poison: {type: 'poison' , __proto__: Pokemon}, flying: {type: 'flying' , __proto__: Pokemon}, bug: {type: 'bug' , __proto__: Pokemon}, normal: {type: 'normal' , __proto__: Pokemon}, electric: {type: 'electric', __proto__: Pokemon}, ground: {type: 'ground' , __proto__: Pokemon}, fairy: {type: 'fairy' , __proto__: Pokemon}, fighting: {type: 'fighting', __proto__: Pokemon}, psychic: {type: 'psychic' , __proto__: Pokemon}, rock: {type: 'rock' , __proto__: Pokemon}, steel: {type: 'steel' , __proto__: Pokemon}, ice: {type: 'ice' , __proto__: Pokemon}, ghost: {type: 'ghost' , __proto__: Pokemon}, dragon: {type: 'dragon' , __proto__: Pokemon} } const pokedex = Object.fromEntries(pokemon.all().slice(0, 151).map(poke => { const type = types[pokeTypes[poke]] return [poke, {__proto__: type}] })) const send = async(msg, data) => await ws.send(JSON.stringify({msg, data})) const checkIfAllCaptured = async () => { if(Object.values(pokedex).every(poke => poke["captured"])) { const diff = new Date() - timeStarted; if(diff < 60000) { send("pokedexCompleted", { msg: "I can't believe you beat my near impossible 60 second speedrun! It's impossible! I am the best trainer to have ever lived! Urgh... I guess I don't have a choice but to give you this item...\n\n*You obtained the key IGCTF{MissingNo}!*\n\nHave fun with it... I guess.\n\n-Gary" }) } else { send("pokedexCompleted", { msg: "Hah, you did good, but not nearly as good as I did. You still got a long way to beat my 1 minute run, loser.\n\n-Gary" }) } ws.close() } } let messagesReceived = 0; let timeOfLastMessage = 0; let poisonMessageSent = false; ws.on('message', evt => { messagesReceived++; const currentTime = new Date() console.log(currentTime - timeOfLastMessage) if(currentTime - timeOfLastMessage < 500) { send("error", {msg: "You are sending messages too fast to the backend. Please slow down."}) timeOfLastMessage = currentTime return } else { timeOfLastMessage = currentTime } const {msg, data} = JSON.parse(evt) switch (msg) { case "initializePokedex": send('pokedexInitialized', flattenObject(pokedex)); break; case "updatePokedex": { const {pokemon, values} = data; if(pokemon !== '__proto__') { if(pokeTypes[pokemon] === 'poison') { badObjectCopy(values, pokedex[pokemon].__proto__); if(!poisonMessageSent) { send("error", {msg: "Dear Pokémon trainer,\n\n There seems to be a bug when adding a Poison type Pokémon: it automatically registers all Poison type Pokémon in the Pokédex as captured! Somehow the poison seems to affect the shared prototype...\n\nYours truly,\nProfessor Oak"}) poisonMessageSent = true } } else { badObjectCopy(values, pokedex[pokemon]); } } else { send("error", {msg: "I read somewhere that letting the client change the __proto__ property isn't such a good idea, so I'll just add this check here. I sure hope that I didn't forget to check this anywhere else... \n\n -Professor Oak"}) } send('pokedexUpdated', flattenObject(pokedex)) checkIfAllCaptured() break; } } }); } catch(e) { console.error(e) } })