From 074d6ad45d7afae4b03f07a92f6e173e4083a1c8 Mon Sep 17 00:00:00 2001 From: xangelo Date: Thu, 7 Sep 2023 13:56:29 -0400 Subject: [PATCH 1/7] feat: cleanup chat commands It's now easy to add chat commands independently without touching the main server code. You get - raw string without the `/server` prefix - Socket.io Server - Socket.io Socket - Calling Player Adn you are free to do whatever you need. All existing commands have been moved to this format. --- src/server/api.ts | 8 +-- src/server/chat-commands.ts | 64 ++++++-------------- src/server/chat-commands/base.ts | 14 +++++ src/server/chat-commands/index.ts | 14 +++++ src/server/chat-commands/refresh-cities.ts | 16 +++++ src/server/chat-commands/refresh-monsters.ts | 16 +++++ src/server/chat-commands/refresh-shops.ts | 18 ++++++ src/server/chat-commands/say.ts | 14 +++++ src/server/chat-commands/set-level.ts | 42 +++++++++++++ src/server/player.ts | 26 ++++++++ src/shared/player.ts | 2 +- 11 files changed, 182 insertions(+), 52 deletions(-) create mode 100644 src/server/chat-commands/base.ts create mode 100644 src/server/chat-commands/index.ts create mode 100644 src/server/chat-commands/refresh-cities.ts create mode 100644 src/server/chat-commands/refresh-monsters.ts create mode 100644 src/server/chat-commands/refresh-shops.ts create mode 100644 src/server/chat-commands/say.ts create mode 100644 src/server/chat-commands/set-level.ts diff --git a/src/server/api.ts b/src/server/api.ts index 0aa2689..4a6a9dd 100644 --- a/src/server/api.ts +++ b/src/server/api.ts @@ -163,14 +163,12 @@ app.post('/chat', authEndpoint, async (req: Request, res: Response) => { } if(msg.startsWith('/server') && req.player.permissions.includes('admin')) { + const sender = io.sockets.sockets.get(cache.get(`socket:${req.player.id}`)); try { - const output = await handleChatCommands(msg, req.player, io); - if(output) { - io.to(cache.get(`socket:${req.player.id}`)).emit('chat', renderChatMessage(output)); - } + await handleChatCommands(msg, req.player, io, sender); } catch(e) { - io.to(cache.get(`socket:${req.player.id}`)).emit('chat', renderChatMessage(broadcastMessage('server', e.message))); + sender.emit('chat', renderChatMessage(broadcastMessage('server', e.message))); } } else if(msg === '/online') { diff --git a/src/server/chat-commands.ts b/src/server/chat-commands.ts index 5e43627..527e77e 100644 --- a/src/server/chat-commands.ts +++ b/src/server/chat-commands.ts @@ -1,52 +1,24 @@ -import { Server } from 'socket.io'; -import { maxHp, maxVigor, Player } from '../shared/player'; -import { createMonsters } from '../../seeds/monsters'; -import { createAllCitiesAndLocations } from '../../seeds/cities'; -import { createShopItems, createShopEquipment } from '../../seeds/shop_items'; -import { broadcastMessage, Message } from '../shared/message'; -import { updatePlayer } from './player'; +import { Server, Socket } from 'socket.io'; +import { Player } from '../shared/player'; +import { broadcastMessage } from '../shared/message'; +import { renderChatMessage } from './views/chat'; +import { Commands } from './chat-commands/'; -export async function handleChatCommands(msg: string, player: Player, io: Server): Promise { - let message: Message; - if(msg === '/server refresh-monsters') { - await createMonsters(); - message = broadcastMessage('server', 'Monster refresh!'); - } - else if(msg === '/server refresh-cities') { - await createAllCitiesAndLocations(); - message = broadcastMessage('server', 'Cities, Locations, and Paths refreshed!'); - } - else if(msg === '/server refresh-shops') { - await createShopItems(); - await createShopEquipment(); - message = broadcastMessage('server', 'Refresh shop items'); - } - else if(msg.startsWith('/server set-level')) { - const level = parseInt(msg.split(' ').pop()); - if(level < 1) { - message = broadcastMessage('server', 'Needs to be at least level 1'); - } - else { - message = broadcastMessage('server', `Set player level: ${level}`); - player.level = level; - player.strength = 4; - player.constitution = 4; - player.dexterity = 4; - player.intelligence = 4; - player.hp = maxHp(player.constitution, player.level); - player.vigor = maxVigor(player.constitution, player.level); - player.stat_points = level-1; +export async function handleChatCommands(msg: string, player: Player, io: Server, sender: Socket): Promise { + const rawCommand = msg.split('/server ')[1]; - await updatePlayer(player); + let matched = false; + Commands.forEach(async command => { + if(command.regex.test(rawCommand)) { + matched = true; + console.log(`${player.username} running command: [${rawCommand}]`); + await command.handler(rawCommand, sender, player, io); } - } - else { - const str = msg.split('/server ')[1]; - if(str) { - message = broadcastMessage('server', str); - } - } + }); - return message; + if(!matched) { + const message = broadcastMessage('server', `Invalid command: [${rawCommand}]`); + sender.emit('chat', renderChatMessage(message)); + } } diff --git a/src/server/chat-commands/base.ts b/src/server/chat-commands/base.ts new file mode 100644 index 0000000..8241067 --- /dev/null +++ b/src/server/chat-commands/base.ts @@ -0,0 +1,14 @@ +import type { Player } from '../../shared/player'; +import type { Server, Socket } from 'socket.io'; + +type ChatCommandHandler = (command: string, sender: Socket, player: Player, io: Server) => Promise; + +export class ChatCommand { + constructor( + public name: string, + public regex: RegExp, + public handler: ChatCommandHandler + ) { + + } +} diff --git a/src/server/chat-commands/index.ts b/src/server/chat-commands/index.ts new file mode 100644 index 0000000..59e2d79 --- /dev/null +++ b/src/server/chat-commands/index.ts @@ -0,0 +1,14 @@ +import { ChatCommand } from "./base"; +import { refreshMonsters } from "./refresh-monsters"; +import { refreshCities } from './refresh-cities'; +import { refreshShops } from './refresh-shops'; +import { setLevel } from './set-level'; +import { say } from './say'; + +export const Commands = new Set(); + +Commands.add(refreshMonsters); +Commands.add(refreshCities); +Commands.add(refreshShops); +Commands.add(setLevel); +Commands.add(say); diff --git a/src/server/chat-commands/refresh-cities.ts b/src/server/chat-commands/refresh-cities.ts new file mode 100644 index 0000000..25c94cb --- /dev/null +++ b/src/server/chat-commands/refresh-cities.ts @@ -0,0 +1,16 @@ +import { ChatCommand } from './base'; +import { Socket } from 'socket.io'; +import { createAllCitiesAndLocations } from '../../../seeds/cities'; +import { broadcastMessage } from '../../shared/message'; +import { renderChatMessage } from './../views/chat'; +import { Player } from '../../shared/player'; + +async function handler(rawCommand: string, sender: Socket, player: Player) { + if(player.permissions.includes('admin')) { + await createAllCitiesAndLocations(); + const message = broadcastMessage('server', 'Cities, Locations, and Paths refreshed!'); + sender.emit('chat', renderChatMessage(message)); + } +} + +export const refreshCities = new ChatCommand('refresh-cities', new RegExp(/^refresh-cities/), handler); diff --git a/src/server/chat-commands/refresh-monsters.ts b/src/server/chat-commands/refresh-monsters.ts new file mode 100644 index 0000000..e0c0624 --- /dev/null +++ b/src/server/chat-commands/refresh-monsters.ts @@ -0,0 +1,16 @@ +import { ChatCommand } from './base'; +import { Socket } from 'socket.io'; +import { createMonsters } from '../../../seeds/monsters'; +import { broadcastMessage } from '../../shared/message'; +import { renderChatMessage } from './../views/chat'; +import { Player } from '../../shared/player'; + +async function handler(rawCommand: string, sender: Socket, player: Player) { + if(player.permissions.includes('admin')) { + await createMonsters(); + const message = broadcastMessage('server', 'Monsters refreshed!'); + sender.emit('chat', renderChatMessage(message)); + } +} + +export const refreshMonsters = new ChatCommand('refresh-monsters', new RegExp(/^refresh-monsters$/), handler); diff --git a/src/server/chat-commands/refresh-shops.ts b/src/server/chat-commands/refresh-shops.ts new file mode 100644 index 0000000..2f90c3b --- /dev/null +++ b/src/server/chat-commands/refresh-shops.ts @@ -0,0 +1,18 @@ +import { ChatCommand } from './base'; +import { Socket } from 'socket.io'; +import { createShopItems, createShopEquipment } from '../../../seeds/shop_items'; +import { broadcastMessage } from '../../shared/message'; +import { renderChatMessage } from './../views/chat'; +import { Player } from '../../shared/player'; + +async function handler(rawCommand: string, sender: Socket, player: Player) { + if(player.permissions.includes('admin')) { + await createShopItems(); + await createShopEquipment(); + const message = broadcastMessage('server', 'Shop items refreshed!'); + sender.emit('chat', renderChatMessage(message)); + + } +} + +export const refreshShops = new ChatCommand('refresh-shops', new RegExp(/^refresh-shops/), handler); diff --git a/src/server/chat-commands/say.ts b/src/server/chat-commands/say.ts new file mode 100644 index 0000000..1c1104e --- /dev/null +++ b/src/server/chat-commands/say.ts @@ -0,0 +1,14 @@ +import { ChatCommand } from './base'; +import { Socket } from 'socket.io'; +import { broadcastMessage, Message } from '../../shared/message'; +import { renderChatMessage } from './../views/chat'; +import { Player } from '../../shared/player'; + +async function handler(rawCommand: string, sender: Socket, player: Player) { + if(player.permissions.includes('moderator') || player.permissions.includes('admin')) { + let message: Message = broadcastMessage('server', rawCommand.split('say ')[1].trim()); + sender.emit('chat', renderChatMessage(message)); + } +} + +export const say = new ChatCommand('say', new RegExp(/^say (.*)+/), handler); diff --git a/src/server/chat-commands/set-level.ts b/src/server/chat-commands/set-level.ts new file mode 100644 index 0000000..640a194 --- /dev/null +++ b/src/server/chat-commands/set-level.ts @@ -0,0 +1,42 @@ +import { ChatCommand } from './base'; +import { Socket } from 'socket.io'; +import { broadcastMessage, Message } from '../../shared/message'; +import { renderChatMessage } from './../views/chat'; +import { updatePlayer } from '../player'; +import { maxHp, maxVigor, Player } from '../../shared/player'; + +async function handler(rawCommand: string, sender: Socket, player: Player) { + if(player.permissions.includes('admin') || player.permissions.includes('tester')) { + let message: Message; + // command in set-level username level + const pieces = rawCommand.split(' '); + if(pieces.length !== 2) { + message = broadcastMessage('server', 'format: /set-level level'); + } + else { + const level = parseInt(pieces.pop() || '0'); + if(level < 1) { + message = broadcastMessage('server', 'format: /set-level [level >= 1]'); + } + else { + message = broadcastMessage('server', `Set to level ${level}. Please reload.`); + + player.level = level; + player.strength = 4; + player.constitution = 4; + player.dexterity = 4; + player.intelligence = 4; + player.hp = maxHp(player.constitution, player.level); + player.vigor = maxVigor(player.constitution, player.level); + player.stat_points = level-1; + + await updatePlayer(player); + } + } + + sender.emit('chat', renderChatMessage(message)); + + } +} + +export const setLevel = new ChatCommand('set-level', new RegExp(/^set-level \d+$/), handler); diff --git a/src/server/player.ts b/src/server/player.ts index a9d077c..b957969 100644 --- a/src/server/player.ts +++ b/src/server/player.ts @@ -31,6 +31,32 @@ export async function loadPlayer(authToken: string): Promise { return res; } +export async function findPlayerByUsername(username: string): Promise { + const res = await db.first() + .select( + 'players.*', + 'profession_levels.level', + 'profession_levels.exp', + db.raw(`coalesce(pp.permissions, '[]'::json) as permissions`) + ) + .from('players') + .join('profession_levels', function() { + this.on(function() { + this.on('profession_levels.player_id', '=', 'players.id') + this.andOn('profession_levels.profession', '=', 'players.profession') + }) + }) + .leftJoin( + db.raw(`(select json_agg(pp.permission) as permissions, pp.player_id from player_permissions pp group by pp.player_id) pp`), + 'pp.player_id','=', 'players.id' + ) + .where({ + 'players.username': username + }); + + return res; +} + export async function createPlayer(): Promise { const raw: Partial = { username: `Player${Date.now().toString().substr(-7)}`, diff --git a/src/shared/player.ts b/src/shared/player.ts index 93d75f3..564ed1d 100644 --- a/src/shared/player.ts +++ b/src/shared/player.ts @@ -3,7 +3,7 @@ import { Stat } from './stats'; import { SkillDefinition, Skill } from './skills'; import { EquippedItemDetails } from './equipped'; -export type Permission = 'admin' | 'moderator'; +export type Permission = 'admin' | 'moderator' | 'tester'; export type Player = { id: string, -- 2.25.1 From 2ec43dfb71243db8c51cca67f240c3148bc9c13a Mon Sep 17 00:00:00 2001 From: xangelo Date: Tue, 12 Sep 2023 15:24:37 -0400 Subject: [PATCH 2/7] feat!: dungeon traversal Dungeons are built in twine and exported as Twison. It's then placed into the "/data/dungeons" folder and imported. This correctly parses all the configuration data and generates a dungeon that you get to walk around. A single room is designated as the "end" room and it will result in the dungeon rewards being presented to the user and all tracking removed. Eventually this tracking will migrate over to influx or clickhouse to allow for longer term queries. --- data/dungeons/storage_cellar.json | 224 ++++++++++++++++++++ migrations/20230908160725_dungeons.ts | 44 ++++ package-lock.json | 32 +++ package.json | 2 + public/assets/bundle.js | 2 +- public/assets/css/game.css | 4 + seeds/dungeons.ts | 176 +++++++++++++++ src/client/htmx.ts | 6 + src/server/admin.ts | 6 + src/server/api.ts | 26 ++- src/server/chat-commands/give-permission.ts | 31 +++ src/server/chat-commands/index.ts | 2 + src/server/dungeon.ts | 151 +++++++++++++ src/server/locations/dungeon.ts | 181 ++++++++++++++++ src/server/map.ts | 8 +- src/server/views/dungeons/room.ts | 71 +++++++ src/server/views/fight.ts | 13 +- src/server/views/map.ts | 8 +- src/shared/constants.ts | 2 + src/shared/dungeon.ts | 73 +++++++ src/shared/map.ts | 2 +- src/shared/monsters.ts | 2 +- src/shared/player.ts | 8 +- 23 files changed, 1060 insertions(+), 14 deletions(-) create mode 100644 data/dungeons/storage_cellar.json create mode 100644 migrations/20230908160725_dungeons.ts create mode 100644 seeds/dungeons.ts create mode 100644 src/server/admin.ts create mode 100644 src/server/chat-commands/give-permission.ts create mode 100644 src/server/dungeon.ts create mode 100644 src/server/locations/dungeon.ts create mode 100644 src/server/views/dungeons/room.ts create mode 100644 src/shared/dungeon.ts diff --git a/data/dungeons/storage_cellar.json b/data/dungeons/storage_cellar.json new file mode 100644 index 0000000..ece9763 --- /dev/null +++ b/data/dungeons/storage_cellar.json @@ -0,0 +1,224 @@ +{ + "passages": [ + { + "text": "You stand at the bottom of the stairwell leading outside the cellar. You can make out a faint square of light above indicating the hatch that doesn't sit quite as flush as it once did.\n\n\n[[Head toward the torch->w1]]\n[[Forward->n1]]\n[[Head away from the torch->e1]]\n{{visit}}\n{{1}}\nYou climb down old wooden stairs into the dark cellar. The only light seems to be from the hatch you just climbed through.\n\n\"I'll close this up, otherwise one of those darned rats might get out\" Varun says. \n\nYou hear him grunt as he lifts the hatch and drops it into place. \n\nIt takes a minute for your eyes to adjust but soon you notice the torches on the wall.. it's not as dark down here as you imagined...\n{{/1}}\n{{/visit}}", + "links": [ + { + "name": "Head toward the torch", + "link": "w1", + "pid": "3" + }, + { + "name": "Forward", + "link": "n1", + "pid": "2" + }, + { + "name": "Head away from the torch", + "link": "e1", + "pid": "6" + } + ], + "props": { + "visit": { + "1": "You climb down old wooden stairs into the dark cellar. The only light seems to be from the hatch you just climbed through.\"I'll close this up, otherwise one of those darned rats might get out\" Varun says. You hear him grunt as he lifts the hatch and drops it into place. It takes a minute for your eyes to adjust but soon you notice the torches on the wall.. it's not as dark down here as you imagined..." + } + }, + "name": "start", + "pid": "1", + "position": { + "x": "800", + "y": "600" + } + }, + { + "text": " A few feet in front of the hatch the ground is fairly compacted... covered in footsteps upon footsteps. This must be the route Varun's helpers take to retrieve the grain. \n\n\n[[Step to the Left->w2n2]]\n[[Back toward Hatch->start]]\n[[Keep going forward->n2]]\n[[Head right->e2n2]]", + "links": [ + { + "name": "Step to the Left", + "link": "w2n2", + "pid": "4" + }, + { + "name": "Back toward Hatch", + "link": "start", + "pid": "1" + }, + { + "name": "Keep going forward", + "link": "n2", + "pid": "5" + }, + { + "name": "Head right", + "link": "e2n2", + "pid": "7" + } + ], + "name": "n1", + "pid": "2", + "position": { + "x": "800", + "y": "400" + } + }, + { + "text": "You step toward the torch. The light isn't bright enough to light up much... but you can see that the walls look a bit damp. Doesn't seem like ideal food storage conditions...\n\n\n\n[[Step forward along the wall->w2n2]]\n[[Back toward the hatch->start]]", + "links": [ + { + "name": "Step forward along the wall", + "link": "w2n2", + "pid": "4" + }, + { + "name": "Back toward the hatch", + "link": "start", + "pid": "1" + } + ], + "name": "w1", + "pid": "3", + "position": { + "x": "600", + "y": "600" + } + }, + { + "text": "You're not that far from the torch but already it feels like this area is getting no light. Your foot snags against something.\n\nInspecting closer you see the torn rags of some kind of sack...\n\nIt looks like it fell off the wooden crates in front of you\n\n[[Back toward the torch->w1]]\n[[Step to the right->n1]]\n{{fight}}\n{{monster_id}}2{{/monster_id}}\n{{one_time}}true{{/one_time}}\n{{/fight}}", + "links": [ + { + "name": "Back toward the torch", + "link": "w1", + "pid": "3" + }, + { + "name": "Step to the right", + "link": "n1", + "pid": "2" + } + ], + "props": { + "fight": { + "monster_id": "2", + "one_time": "true" + } + }, + "name": "w2n2", + "pid": "4", + "position": { + "x": "600", + "y": "400" + } + }, + { + "text": "You can see that a path has been cleared between the wooden crates. The crates have dug into the floor as they were pushed around. \n\nThe slain rat lies on the floor. \n\n[[Head forward->n3]]\n[[Head back->n1]]\n{{fight}}\n{{monster_id}}2{{/monster_id}}\n{{one_time}}true{{/one_time}}\n{{/fight}}", + "links": [ + { + "name": "Head forward", + "link": "n3", + "pid": "8" + }, + { + "name": "Head back", + "link": "n1", + "pid": "2" + } + ], + "props": { + "fight": { + "monster_id": "2", + "one_time": "true" + } + }, + "name": "n2", + "pid": "5", + "position": { + "x": "800", + "y": "200" + } + }, + { + "text": "As you step away from the torch, you're aware of how cold the cellar actually is. It's not that big, but the light barely reaches into this corner.\n\n\n[[Investigate the noise->e2n2]]\n[[Head back to the hatch->start]]\n{{visit}}\n\t{{1}}You hear some skittering up ahead{{/1}}\n{{/visit}}", + "links": [ + { + "name": "Investigate the noise", + "link": "e2n2", + "pid": "7" + }, + { + "name": "Head back to the hatch", + "link": "start", + "pid": "1" + } + ], + "props": { + "visit": { + "1": "You hear some skittering up ahead" + } + }, + "name": "e1", + "pid": "6", + "position": { + "x": "1000", + "y": "600" + } + }, + { + "text": "You see some bags in the corner.. and also the corpse of that Rat you dealt wtih.\n\nIn front of you you can see stacks of wooden crates, often topped by a burlap sack of some kind.\n\n\n[[Head back the way you came->e1]]\n[[Head toward the hatch->n1]]\n{{fight}}\n{{monster_id}}2{{/monster_id}}\n{{one_time}}true{{/one_time}}\n{{/fight}}\n{{visit}}\n{{1}}\nThat rat was way too big to be a regular rat... \n{{/1}}\n{{/visit}}", + "links": [ + { + "name": "Head back the way you came", + "link": "e1", + "pid": "6" + }, + { + "name": "Head toward the hatch", + "link": "n1", + "pid": "2" + } + ], + "props": { + "fight": { + "monster_id": "2", + "one_time": "true" + }, + "visit": { + "1": "That rat was way too big to be a regular rat... " + } + }, + "name": "e2n2", + "pid": "7", + "position": { + "x": "1000", + "y": "400" + } + }, + { + "text": "You reach the wall at the other end of the cellar.. you don't hear anything further.. maybe it's been cleared out?\n\n\n\n{{end}}true{{/end}}\n{{rewards}}\n\t{{base}}\n \t{{exp}}20{{/exp}}\n \t{{gold}}60{{/gold}}\n {{/base}}\n\t{{per_kill_bonus}}\n \t{{exp}}10{{/exp}}\n {{gold}}10{{/gold}}\n {{/per_kill_bonus}}\n{{/rewards}}", + "props": { + "end": "true", + "rewards": { + "base": { + "exp": "20", + "gold": "60" + }, + "per_kill_bonus": { + "exp": "10", + "gold": "10" + } + } + }, + "name": "n3", + "pid": "8", + "position": { + "x": "800", + "y": "0" + } + } + ], + "name": "Storage Cellar", + "startnode": "1", + "creator": "Twine", + "creator-version": "2.7.1", + "ifid": "0F34ADD6-2DC1-4A8C-9464-008386845215" +} diff --git a/migrations/20230908160725_dungeons.ts b/migrations/20230908160725_dungeons.ts new file mode 100644 index 0000000..73da240 --- /dev/null +++ b/migrations/20230908160725_dungeons.ts @@ -0,0 +1,44 @@ +import { Knex } from "knex"; + + +export async function up(knex: Knex): Promise { + return knex.schema.createTable('dungeons', function(table) { + table.string('id').primary().defaultTo(knex.raw('uuid_generate_v4()')); + table.string('name'); + table.uuid('starting_room'); + }).createTable('dungeon_rooms', function(table) { + table.uuid('id').defaultTo(knex.raw('uuid_generate_v4()')).primary(); + table.uuid('dungeon_id'); + table.text('description'); + table.json('exits').defaultTo(knex.raw("'[]'::json")); + table.json('settings').defaultTo('{}'); + }).createTable('dungeon_things', function(table) { + table.uuid('room_id'); + table.uuid('id').defaultTo(knex.raw('uuid_generate_v4()')).primary(); + table.string('name'); + table.json('properties').defaultTo({}) + }).createTable('dungeon_players', function(table) { + table.uuid('player_id').primary(); + table.uuid('dungeon_id'); + table.uuid('current_room_id'); + table.uuid('target_room_id'); // only used when movement is interrupted by a fight + }).createTable('dungeon_state', function(table) { + table.uuid('id').primary().defaultTo(knex.raw('uuid_generate_v4()')); + table.uuid('player_id'); + table.uuid('dungeon_id'); + table.string('event_name'); + table.json('event_props'); + table.timestamp('create_date').defaultTo(knex.raw('NOW()')) + table.index(['player_id', 'dungeon_id']); + table.index(['player_id', 'dungeon_id', 'event_name']); + }) +} + + +export async function down(knex: Knex): Promise { + return knex.schema.dropTable('dungeon_rooms') + .dropTable('dungeon_things') + .dropTable('dungeon_players') + .dropTable('dungeon_state') + .dropTable('dungeons'); +} diff --git a/package-lock.json b/package-lock.json index 312fb12..f73a4df 100644 --- a/package-lock.json +++ b/package-lock.json @@ -39,9 +39,11 @@ "@types/jest": "^29.5.3", "@types/jquery": "^3.5.16", "@types/lodash": "^4.14.195", + "@types/marked": "^5.0.1", "husky": "^8.0.0", "jest": "^29.6.2", "jquery": "^3.7.0", + "marked": "^9.0.0", "nodemon": "^2.0.20", "standard-version": "^9.5.0", "ts-jest": "^29.1.1", @@ -4320,6 +4322,12 @@ "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==" }, + "node_modules/@types/marked": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@types/marked/-/marked-5.0.1.tgz", + "integrity": "sha512-Y3pAUzHKh605fN6fvASsz5FDSWbZcs/65Q6xYRmnIP9ZIYz27T4IOmXfH9gWJV1dpi7f1e7z7nBGUTx/a0ptpA==", + "dev": true + }, "node_modules/@types/memcached": { "version": "2.2.7", "resolved": "https://registry.npmjs.org/@types/memcached/-/memcached-2.2.7.tgz", @@ -9025,6 +9033,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/marked": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-9.0.0.tgz", + "integrity": "sha512-37yoTpjU+TSXb9OBYY5n78z/CqXh76KiQj9xsKxEdztzU9fRLmbWO5YqKxgCVGKlNdexppnbKTkwB3RipVri8w==", + "dev": true, + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 16" + } + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -15110,6 +15130,12 @@ "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==" }, + "@types/marked": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@types/marked/-/marked-5.0.1.tgz", + "integrity": "sha512-Y3pAUzHKh605fN6fvASsz5FDSWbZcs/65Q6xYRmnIP9ZIYz27T4IOmXfH9gWJV1dpi7f1e7z7nBGUTx/a0ptpA==", + "dev": true + }, "@types/memcached": { "version": "2.2.7", "resolved": "https://registry.npmjs.org/@types/memcached/-/memcached-2.2.7.tgz", @@ -18696,6 +18722,12 @@ "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", "dev": true }, + "marked": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-9.0.0.tgz", + "integrity": "sha512-37yoTpjU+TSXb9OBYY5n78z/CqXh76KiQj9xsKxEdztzU9fRLmbWO5YqKxgCVGKlNdexppnbKTkwB3RipVri8w==", + "dev": true + }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", diff --git a/package.json b/package.json index 3c242fc..7487106 100644 --- a/package.json +++ b/package.json @@ -29,9 +29,11 @@ "@types/jest": "^29.5.3", "@types/jquery": "^3.5.16", "@types/lodash": "^4.14.195", + "@types/marked": "^5.0.1", "husky": "^8.0.0", "jest": "^29.6.2", "jquery": "^3.7.0", + "marked": "^9.0.0", "nodemon": "^2.0.20", "standard-version": "^9.5.0", "ts-jest": "^29.1.1", diff --git a/public/assets/bundle.js b/public/assets/bundle.js index 696b7fa..f2ca3ad 100644 --- a/public/assets/bundle.js +++ b/public/assets/bundle.js @@ -1 +1 @@ -(()=>{var e={802:(e,t,s)=>{t.formatArgs=function(t){if(t[0]=(this.useColors?"%c":"")+this.namespace+(this.useColors?" %c":" ")+t[0]+(this.useColors?"%c ":" ")+"+"+e.exports.humanize(this.diff),!this.useColors)return;const s="color: "+this.color;t.splice(1,0,s,"color: inherit");let n=0,r=0;t[0].replace(/%[a-zA-Z%]/g,(e=>{"%%"!==e&&(n++,"%c"===e&&(r=n))})),t.splice(r,0,s)},t.save=function(e){try{e?t.storage.setItem("debug",e):t.storage.removeItem("debug")}catch(e){}},t.load=function(){let e;try{e=t.storage.getItem("debug")}catch(e){}return!e&&"undefined"!=typeof process&&"env"in process&&(e=process.env.DEBUG),e},t.useColors=function(){return!("undefined"==typeof window||!window.process||"renderer"!==window.process.type&&!window.process.__nwjs)||("undefined"==typeof navigator||!navigator.userAgent||!navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/))&&("undefined"!=typeof document&&document.documentElement&&document.documentElement.style&&document.documentElement.style.WebkitAppearance||"undefined"!=typeof window&&window.console&&(window.console.firebug||window.console.exception&&window.console.table)||"undefined"!=typeof navigator&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/)&&parseInt(RegExp.$1,10)>=31||"undefined"!=typeof navigator&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/))},t.storage=function(){try{return localStorage}catch(e){}}(),t.destroy=(()=>{let e=!1;return()=>{e||(e=!0,console.warn("Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`."))}})(),t.colors=["#0000CC","#0000FF","#0033CC","#0033FF","#0066CC","#0066FF","#0099CC","#0099FF","#00CC00","#00CC33","#00CC66","#00CC99","#00CCCC","#00CCFF","#3300CC","#3300FF","#3333CC","#3333FF","#3366CC","#3366FF","#3399CC","#3399FF","#33CC00","#33CC33","#33CC66","#33CC99","#33CCCC","#33CCFF","#6600CC","#6600FF","#6633CC","#6633FF","#66CC00","#66CC33","#9900CC","#9900FF","#9933CC","#9933FF","#99CC00","#99CC33","#CC0000","#CC0033","#CC0066","#CC0099","#CC00CC","#CC00FF","#CC3300","#CC3333","#CC3366","#CC3399","#CC33CC","#CC33FF","#CC6600","#CC6633","#CC9900","#CC9933","#CCCC00","#CCCC33","#FF0000","#FF0033","#FF0066","#FF0099","#FF00CC","#FF00FF","#FF3300","#FF3333","#FF3366","#FF3399","#FF33CC","#FF33FF","#FF6600","#FF6633","#FF9900","#FF9933","#FFCC00","#FFCC33"],t.log=console.debug||console.log||(()=>{}),e.exports=s(804)(t);const{formatters:n}=e.exports;n.j=function(e){try{return JSON.stringify(e)}catch(e){return"[UnexpectedJSONParseError]: "+e.message}}},804:(e,t,s)=>{e.exports=function(e){function t(e){let s,r,o,i=null;function a(...e){if(!a.enabled)return;const n=a,r=Number(new Date),o=r-(s||r);n.diff=o,n.prev=s,n.curr=r,s=r,e[0]=t.coerce(e[0]),"string"!=typeof e[0]&&e.unshift("%O");let i=0;e[0]=e[0].replace(/%([a-zA-Z%])/g,((s,r)=>{if("%%"===s)return"%";i++;const o=t.formatters[r];if("function"==typeof o){const t=e[i];s=o.call(n,t),e.splice(i,1),i--}return s})),t.formatArgs.call(n,e),(n.log||t.log).apply(n,e)}return a.namespace=e,a.useColors=t.useColors(),a.color=t.selectColor(e),a.extend=n,a.destroy=t.destroy,Object.defineProperty(a,"enabled",{enumerable:!0,configurable:!1,get:()=>null!==i?i:(r!==t.namespaces&&(r=t.namespaces,o=t.enabled(e)),o),set:e=>{i=e}}),"function"==typeof t.init&&t.init(a),a}function n(e,s){const n=t(this.namespace+(void 0===s?":":s)+e);return n.log=this.log,n}function r(e){return e.toString().substring(2,e.toString().length-2).replace(/\.\*\?$/,"*")}return t.debug=t,t.default=t,t.coerce=function(e){return e instanceof Error?e.stack||e.message:e},t.disable=function(){const e=[...t.names.map(r),...t.skips.map(r).map((e=>"-"+e))].join(",");return t.enable(""),e},t.enable=function(e){let s;t.save(e),t.namespaces=e,t.names=[],t.skips=[];const n=("string"==typeof e?e:"").split(/[\s,]+/),r=n.length;for(s=0;s{t[s]=e[s]})),t.names=[],t.skips=[],t.formatters={},t.selectColor=function(e){let s=0;for(let t=0;t{var t=1e3,s=60*t,n=60*s,r=24*n;function o(e,t,s,n){var r=t>=1.5*s;return Math.round(e/s)+" "+n+(r?"s":"")}e.exports=function(e,i){i=i||{};var a,c,u=typeof e;if("string"===u&&e.length>0)return function(e){if(!((e=String(e)).length>100)){var o=/^(-?(?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)?$/i.exec(e);if(o){var i=parseFloat(o[1]);switch((o[2]||"ms").toLowerCase()){case"years":case"year":case"yrs":case"yr":case"y":return 315576e5*i;case"weeks":case"week":case"w":return 6048e5*i;case"days":case"day":case"d":return i*r;case"hours":case"hour":case"hrs":case"hr":case"h":return i*n;case"minutes":case"minute":case"mins":case"min":case"m":return i*s;case"seconds":case"second":case"secs":case"sec":case"s":return i*t;case"milliseconds":case"millisecond":case"msecs":case"msec":case"ms":return i;default:return}}}}(e);if("number"===u&&isFinite(e))return i.long?(a=e,(c=Math.abs(a))>=r?o(a,c,r,"day"):c>=n?o(a,c,n,"hour"):c>=s?o(a,c,s,"minute"):c>=t?o(a,c,t,"second"):a+" ms"):function(e){var o=Math.abs(e);return o>=r?Math.round(e/r)+"d":o>=n?Math.round(e/n)+"h":o>=s?Math.round(e/s)+"m":o>=t?Math.round(e/t)+"s":e+"ms"}(e);throw new Error("val is not a non-empty string or a valid number. val="+JSON.stringify(e))}},669:(e,t,s)=>{t.formatArgs=function(t){if(t[0]=(this.useColors?"%c":"")+this.namespace+(this.useColors?" %c":" ")+t[0]+(this.useColors?"%c ":" ")+"+"+e.exports.humanize(this.diff),!this.useColors)return;const s="color: "+this.color;t.splice(1,0,s,"color: inherit");let n=0,r=0;t[0].replace(/%[a-zA-Z%]/g,(e=>{"%%"!==e&&(n++,"%c"===e&&(r=n))})),t.splice(r,0,s)},t.save=function(e){try{e?t.storage.setItem("debug",e):t.storage.removeItem("debug")}catch(e){}},t.load=function(){let e;try{e=t.storage.getItem("debug")}catch(e){}return!e&&"undefined"!=typeof process&&"env"in process&&(e=process.env.DEBUG),e},t.useColors=function(){return!("undefined"==typeof window||!window.process||"renderer"!==window.process.type&&!window.process.__nwjs)||("undefined"==typeof navigator||!navigator.userAgent||!navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/))&&("undefined"!=typeof document&&document.documentElement&&document.documentElement.style&&document.documentElement.style.WebkitAppearance||"undefined"!=typeof window&&window.console&&(window.console.firebug||window.console.exception&&window.console.table)||"undefined"!=typeof navigator&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/)&&parseInt(RegExp.$1,10)>=31||"undefined"!=typeof navigator&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/))},t.storage=function(){try{return localStorage}catch(e){}}(),t.destroy=(()=>{let e=!1;return()=>{e||(e=!0,console.warn("Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`."))}})(),t.colors=["#0000CC","#0000FF","#0033CC","#0033FF","#0066CC","#0066FF","#0099CC","#0099FF","#00CC00","#00CC33","#00CC66","#00CC99","#00CCCC","#00CCFF","#3300CC","#3300FF","#3333CC","#3333FF","#3366CC","#3366FF","#3399CC","#3399FF","#33CC00","#33CC33","#33CC66","#33CC99","#33CCCC","#33CCFF","#6600CC","#6600FF","#6633CC","#6633FF","#66CC00","#66CC33","#9900CC","#9900FF","#9933CC","#9933FF","#99CC00","#99CC33","#CC0000","#CC0033","#CC0066","#CC0099","#CC00CC","#CC00FF","#CC3300","#CC3333","#CC3366","#CC3399","#CC33CC","#CC33FF","#CC6600","#CC6633","#CC9900","#CC9933","#CCCC00","#CCCC33","#FF0000","#FF0033","#FF0066","#FF0099","#FF00CC","#FF00FF","#FF3300","#FF3333","#FF3366","#FF3399","#FF33CC","#FF33FF","#FF6600","#FF6633","#FF9900","#FF9933","#FFCC00","#FFCC33"],t.log=console.debug||console.log||(()=>{}),e.exports=s(231)(t);const{formatters:n}=e.exports;n.j=function(e){try{return JSON.stringify(e)}catch(e){return"[UnexpectedJSONParseError]: "+e.message}}},231:(e,t,s)=>{e.exports=function(e){function t(e){let s,r,o,i=null;function a(...e){if(!a.enabled)return;const n=a,r=Number(new Date),o=r-(s||r);n.diff=o,n.prev=s,n.curr=r,s=r,e[0]=t.coerce(e[0]),"string"!=typeof e[0]&&e.unshift("%O");let i=0;e[0]=e[0].replace(/%([a-zA-Z%])/g,((s,r)=>{if("%%"===s)return"%";i++;const o=t.formatters[r];if("function"==typeof o){const t=e[i];s=o.call(n,t),e.splice(i,1),i--}return s})),t.formatArgs.call(n,e),(n.log||t.log).apply(n,e)}return a.namespace=e,a.useColors=t.useColors(),a.color=t.selectColor(e),a.extend=n,a.destroy=t.destroy,Object.defineProperty(a,"enabled",{enumerable:!0,configurable:!1,get:()=>null!==i?i:(r!==t.namespaces&&(r=t.namespaces,o=t.enabled(e)),o),set:e=>{i=e}}),"function"==typeof t.init&&t.init(a),a}function n(e,s){const n=t(this.namespace+(void 0===s?":":s)+e);return n.log=this.log,n}function r(e){return e.toString().substring(2,e.toString().length-2).replace(/\.\*\?$/,"*")}return t.debug=t,t.default=t,t.coerce=function(e){return e instanceof Error?e.stack||e.message:e},t.disable=function(){const e=[...t.names.map(r),...t.skips.map(r).map((e=>"-"+e))].join(",");return t.enable(""),e},t.enable=function(e){let s;t.save(e),t.namespaces=e,t.names=[],t.skips=[];const n=("string"==typeof e?e:"").split(/[\s,]+/),r=n.length;for(s=0;s{t[s]=e[s]})),t.names=[],t.skips=[],t.formatters={},t.selectColor=function(e){let s=0;for(let t=0;t{var t=1e3,s=60*t,n=60*s,r=24*n;function o(e,t,s,n){var r=t>=1.5*s;return Math.round(e/s)+" "+n+(r?"s":"")}e.exports=function(e,i){i=i||{};var a,c,u=typeof e;if("string"===u&&e.length>0)return function(e){if(!((e=String(e)).length>100)){var o=/^(-?(?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)?$/i.exec(e);if(o){var i=parseFloat(o[1]);switch((o[2]||"ms").toLowerCase()){case"years":case"year":case"yrs":case"yr":case"y":return 315576e5*i;case"weeks":case"week":case"w":return 6048e5*i;case"days":case"day":case"d":return i*r;case"hours":case"hour":case"hrs":case"hr":case"h":return i*n;case"minutes":case"minute":case"mins":case"min":case"m":return i*s;case"seconds":case"second":case"secs":case"sec":case"s":return i*t;case"milliseconds":case"millisecond":case"msecs":case"msec":case"ms":return i;default:return}}}}(e);if("number"===u&&isFinite(e))return i.long?(a=e,(c=Math.abs(a))>=r?o(a,c,r,"day"):c>=n?o(a,c,n,"hour"):c>=s?o(a,c,s,"minute"):c>=t?o(a,c,t,"second"):a+" ms"):function(e){var o=Math.abs(e);return o>=r?Math.round(e/r)+"d":o>=n?Math.round(e/n)+"h":o>=s?Math.round(e/s)+"m":o>=t?Math.round(e/t)+"s":e+"ms"}(e);throw new Error("val is not a non-empty string or a valid number. val="+JSON.stringify(e))}},618:(e,t,s)=>{t.formatArgs=function(t){if(t[0]=(this.useColors?"%c":"")+this.namespace+(this.useColors?" %c":" ")+t[0]+(this.useColors?"%c ":" ")+"+"+e.exports.humanize(this.diff),!this.useColors)return;const s="color: "+this.color;t.splice(1,0,s,"color: inherit");let n=0,r=0;t[0].replace(/%[a-zA-Z%]/g,(e=>{"%%"!==e&&(n++,"%c"===e&&(r=n))})),t.splice(r,0,s)},t.save=function(e){try{e?t.storage.setItem("debug",e):t.storage.removeItem("debug")}catch(e){}},t.load=function(){let e;try{e=t.storage.getItem("debug")}catch(e){}return!e&&"undefined"!=typeof process&&"env"in process&&(e=process.env.DEBUG),e},t.useColors=function(){return!("undefined"==typeof window||!window.process||"renderer"!==window.process.type&&!window.process.__nwjs)||("undefined"==typeof navigator||!navigator.userAgent||!navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/))&&("undefined"!=typeof document&&document.documentElement&&document.documentElement.style&&document.documentElement.style.WebkitAppearance||"undefined"!=typeof window&&window.console&&(window.console.firebug||window.console.exception&&window.console.table)||"undefined"!=typeof navigator&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/)&&parseInt(RegExp.$1,10)>=31||"undefined"!=typeof navigator&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/))},t.storage=function(){try{return localStorage}catch(e){}}(),t.destroy=(()=>{let e=!1;return()=>{e||(e=!0,console.warn("Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`."))}})(),t.colors=["#0000CC","#0000FF","#0033CC","#0033FF","#0066CC","#0066FF","#0099CC","#0099FF","#00CC00","#00CC33","#00CC66","#00CC99","#00CCCC","#00CCFF","#3300CC","#3300FF","#3333CC","#3333FF","#3366CC","#3366FF","#3399CC","#3399FF","#33CC00","#33CC33","#33CC66","#33CC99","#33CCCC","#33CCFF","#6600CC","#6600FF","#6633CC","#6633FF","#66CC00","#66CC33","#9900CC","#9900FF","#9933CC","#9933FF","#99CC00","#99CC33","#CC0000","#CC0033","#CC0066","#CC0099","#CC00CC","#CC00FF","#CC3300","#CC3333","#CC3366","#CC3399","#CC33CC","#CC33FF","#CC6600","#CC6633","#CC9900","#CC9933","#CCCC00","#CCCC33","#FF0000","#FF0033","#FF0066","#FF0099","#FF00CC","#FF00FF","#FF3300","#FF3333","#FF3366","#FF3399","#FF33CC","#FF33FF","#FF6600","#FF6633","#FF9900","#FF9933","#FFCC00","#FFCC33"],t.log=console.debug||console.log||(()=>{}),e.exports=s(224)(t);const{formatters:n}=e.exports;n.j=function(e){try{return JSON.stringify(e)}catch(e){return"[UnexpectedJSONParseError]: "+e.message}}},224:(e,t,s)=>{e.exports=function(e){function t(e){let s,r,o,i=null;function a(...e){if(!a.enabled)return;const n=a,r=Number(new Date),o=r-(s||r);n.diff=o,n.prev=s,n.curr=r,s=r,e[0]=t.coerce(e[0]),"string"!=typeof e[0]&&e.unshift("%O");let i=0;e[0]=e[0].replace(/%([a-zA-Z%])/g,((s,r)=>{if("%%"===s)return"%";i++;const o=t.formatters[r];if("function"==typeof o){const t=e[i];s=o.call(n,t),e.splice(i,1),i--}return s})),t.formatArgs.call(n,e),(n.log||t.log).apply(n,e)}return a.namespace=e,a.useColors=t.useColors(),a.color=t.selectColor(e),a.extend=n,a.destroy=t.destroy,Object.defineProperty(a,"enabled",{enumerable:!0,configurable:!1,get:()=>null!==i?i:(r!==t.namespaces&&(r=t.namespaces,o=t.enabled(e)),o),set:e=>{i=e}}),"function"==typeof t.init&&t.init(a),a}function n(e,s){const n=t(this.namespace+(void 0===s?":":s)+e);return n.log=this.log,n}function r(e){return e.toString().substring(2,e.toString().length-2).replace(/\.\*\?$/,"*")}return t.debug=t,t.default=t,t.coerce=function(e){return e instanceof Error?e.stack||e.message:e},t.disable=function(){const e=[...t.names.map(r),...t.skips.map(r).map((e=>"-"+e))].join(",");return t.enable(""),e},t.enable=function(e){let s;t.save(e),t.namespaces=e,t.names=[],t.skips=[];const n=("string"==typeof e?e:"").split(/[\s,]+/),r=n.length;for(s=0;s{t[s]=e[s]})),t.names=[],t.skips=[],t.formatters={},t.selectColor=function(e){let s=0;for(let t=0;t{var t=1e3,s=60*t,n=60*s,r=24*n;function o(e,t,s,n){var r=t>=1.5*s;return Math.round(e/s)+" "+n+(r?"s":"")}e.exports=function(e,i){i=i||{};var a,c,u=typeof e;if("string"===u&&e.length>0)return function(e){if(!((e=String(e)).length>100)){var o=/^(-?(?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)?$/i.exec(e);if(o){var i=parseFloat(o[1]);switch((o[2]||"ms").toLowerCase()){case"years":case"year":case"yrs":case"yr":case"y":return 315576e5*i;case"weeks":case"week":case"w":return 6048e5*i;case"days":case"day":case"d":return i*r;case"hours":case"hour":case"hrs":case"hr":case"h":return i*n;case"minutes":case"minute":case"mins":case"min":case"m":return i*s;case"seconds":case"second":case"secs":case"sec":case"s":return i*t;case"milliseconds":case"millisecond":case"msecs":case"msec":case"ms":return i;default:return}}}}(e);if("number"===u&&isFinite(e))return i.long?(a=e,(c=Math.abs(a))>=r?o(a,c,r,"day"):c>=n?o(a,c,n,"hour"):c>=s?o(a,c,s,"minute"):c>=t?o(a,c,t,"second"):a+" ms"):function(e){var o=Math.abs(e);return o>=r?Math.round(e/r)+"d":o>=n?Math.round(e/n)+"h":o>=s?Math.round(e/s)+"m":o>=t?Math.round(e/t)+"s":e+"ms"}(e);throw new Error("val is not a non-empty string or a valid number. val="+JSON.stringify(e))}},628:function(e,t,s){"use strict";var n=this&&this.__createBinding||(Object.create?function(e,t,s,n){void 0===n&&(n=s);var r=Object.getOwnPropertyDescriptor(t,s);r&&!("get"in r?!t.__esModule:r.writable||r.configurable)||(r={enumerable:!0,get:function(){return t[s]}}),Object.defineProperty(e,n,r)}:function(e,t,s,n){void 0===n&&(n=s),e[n]=t[s]}),r=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),o=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var s in e)"default"!==s&&Object.prototype.hasOwnProperty.call(e,s)&&n(t,e,s);return r(t,e),t};Object.defineProperty(t,"__esModule",{value:!0}),t.authToken=void 0;const i=s(46),a=s(182),c=o(s(454));function u(e,t=document){return t.querySelector(e)}function h(e,t=document){return Array.from(t.querySelectorAll(e))}function l(){return localStorage.getItem("authToken")||""}function d(){const e=p.gradientName(),t=u("body");let s;t.classList.value=e,t.classList.add(p.getTimePeriod()),s=p.get24Hour()>=5&&p.get24Hour()<9?"morning":p.get24Hour()>=9&&p.get24Hour()<17?"afternoon":p.get24Hour()>=17&&p.get24Hour()<20?"evening":"night",u("#time-of-day").innerHTML=` ${p.getHour()}${p.getAmPm()}`}t.authToken=l;const p=new a.TimeManager,f=(0,i.io)({extraHeaders:{"x-authtoken":l()}});d(),setInterval(d,6e4),f.on("connect",(()=>{console.log(`Connected: ${f.id}`)})),f.on("authToken",(e=>{console.log(`recv auth token ${e}`),localStorage.getItem("authToken")!==e&&(localStorage.setItem("authToken",e),window.location.reload())})),f.on("status",(e=>u("#server-stats").innerHTML=e)),f.on("chat",(function(e){u("#chat-messages").insertAdjacentHTML("beforeend",e),u("#chat-messages").scrollTop=u("#chat-messages").scrollHeight})),f.on("ready",(function(){console.log("Server connection verified"),h("nav a")[3].click()})),h("nav a").forEach((e=>{e.addEventListener("click",(e=>{const t=e.target;h("a",t.closest("nav")).forEach((e=>{e.classList.remove("active")})),t.classList.add("active");const s=u(t.getAttribute("hx-target").toString());Array.from(s.parentElement.children).forEach((e=>{e.classList.remove("active")})),s.classList.add("active")}))})),u("body").addEventListener("click",(e=>{var t;const s=e.target;if(s.parentElement.classList.contains("filter")){Array.from(s.parentElement.children).forEach((e=>e.classList.remove("active"))),s.classList.add("active");const e=s.getAttribute("data-filter"),t=u(`.filter-result[data-filter="${e}"]`,s.closest(".filter-container"));t&&Array.from(t.parentElement.children).forEach((t=>{t.getAttribute("data-filter")===e?(t.classList.remove("hidden"),t.classList.add("active")):(t.classList.add("hidden"),t.classList.remove("active"))}))}"dialog"===s.getAttribute("formmethod")&&"cancel"===s.getAttribute("value")&&(null===(t=s.closest("dialog"))||void 0===t||t.close())}));const g=new MutationObserver(((e,t)=>{const s={modal:!1,alert:!1};e.forEach((e=>{switch(e.target.id){case"modal-wrapper":s.modal=!0;break;case"alerts":s.alert=!0}})),s.modal&&u("#modal-wrapper").children.length&&h("#modal-wrapper dialog").forEach((e=>{e.open||e.showModal()})),s.alert&&u("#alerts").children.length&&h("#alerts .alert").forEach((e=>{if(!e.getAttribute("data-dismiss-at")){const t=Date.now()+c.ALERT_DISPLAY_LENGTH;e.setAttribute("data-dismiss-at",t.toString()),setTimeout((()=>{e.remove()}),c.ALERT_DISPLAY_LENGTH)}}))}));g.observe(u("#modal-wrapper"),{childList:!0}),g.observe(u("#alerts"),{childList:!0}),document.body.addEventListener("htmx:configRequest",(function(e){e.detail.headers["x-authtoken"]=l()})),document.body.addEventListener("htmx:load",(function(e){h(".disabled[data-block]").forEach((e=>{const t=parseInt(e.getAttribute("data-block")||"0");if(t>Date.now()){const s=t-Date.now();setTimeout((()=>{e.removeAttribute("disabled")}),s)}else e.removeAttribute("disabled")}))})),document.body.addEventListener("htmx:beforeSwap",(function(e){"chat-form"===e.target.id?u("#message").value="":"logout"===e.detail.serverResponse&&(localStorage.removeItem("authToken"),window.location.reload())}))},454:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.ALERT_DISPLAY_LENGTH=t.STEP_DELAY=t.FIGHT_ATTACK_DELAY=void 0,t.FIGHT_ATTACK_DELAY=1500,t.STEP_DELAY=2e3,t.ALERT_DISPLAY_LENGTH=3e3},182:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.TimeManager=void 0,t.TimeManager=class{constructor(e=120){this.dayLength=e,this.scaleFactor=30,this.dayLengthAsMS=60*e*1e3}dayScaleFactor(){return this.dayLength/30}getTimePeriod(){return this.isMorning()?"morning":this.isAfternoon()?"afternoon":this.isEvening()?"evening":this.isNight()?"night":void 0}getAmPm(){return this.get24Hour()<12?"am":"pm"}get24Hour(){const e=new Date,t=(e.getMinutes()+e.getHours()%2*(this.dayLength/2))/this.dayLength;return Math.floor(24*t)}getHour(){const e=this.get24Hour(),t=e>12?e-12:e;return 0===t?12:t}gradientName(){const e=Math.floor(this.get24Hour()/24*this.scaleFactor);return`sky-gradient-${e<10?"0":""}${e}`}isNight(){const e=this.get24Hour();return e>=0&&e<5||e>=21&&e<24}isMorning(){const e=this.get24Hour();return e>=5&&e<12}isAfternoon(){const e=this.get24Hour();return e>=12&&e<18}isEvening(){const e=this.get24Hour();return e>=18&&e<21}}},419:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.hasCORS=void 0;let s=!1;try{s="undefined"!=typeof XMLHttpRequest&&"withCredentials"in new XMLHttpRequest}catch(e){}t.hasCORS=s},754:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.decode=t.encode=void 0,t.encode=function(e){let t="";for(let s in e)e.hasOwnProperty(s)&&(t.length&&(t+="&"),t+=encodeURIComponent(s)+"="+encodeURIComponent(e[s]));return t},t.decode=function(e){let t={},s=e.split("&");for(let e=0,n=s.length;e{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.parse=void 0;const s=/^(?:(?![^:@\/?#]+:[^:@\/]*@)(http|https|ws|wss):\/\/)?((?:(([^:@\/?#]*)(?::([^:@\/?#]*))?)?@)?((?:[a-f0-9]{0,4}:){2,7}[a-f0-9]{0,4}|[^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/,n=["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"];t.parse=function(e){const t=e,r=e.indexOf("["),o=e.indexOf("]");-1!=r&&-1!=o&&(e=e.substring(0,r)+e.substring(r,o).replace(/:/g,";")+e.substring(o,e.length));let i=s.exec(e||""),a={},c=14;for(;c--;)a[n[c]]=i[c]||"";return-1!=r&&-1!=o&&(a.source=t,a.host=a.host.substring(1,a.host.length-1).replace(/;/g,":"),a.authority=a.authority.replace("[","").replace("]","").replace(/;/g,":"),a.ipv6uri=!0),a.pathNames=function(e,t){const s=t.replace(/\/{2,9}/g,"/").split("/");return"/"!=t.slice(0,1)&&0!==t.length||s.splice(0,1),"/"==t.slice(-1)&&s.splice(s.length-1,1),s}(0,a.path),a.queryKey=function(e,t){const s={};return t.replace(/(?:^|&)([^&=]*)=?([^&]*)/g,(function(e,t,n){t&&(s[t]=n)})),s}(0,a.query),a}},726:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.yeast=t.decode=t.encode=void 0;const s="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_".split(""),n={};let r,o=0,i=0;function a(e){let t="";do{t=s[e%64]+t,e=Math.floor(e/64)}while(e>0);return t}for(t.encode=a,t.decode=function(e){let t=0;for(i=0;i{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.globalThisShim=void 0,t.globalThisShim="undefined"!=typeof self?self:"undefined"!=typeof window?window:Function("return this")()},679:(e,t,s)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.nextTick=t.parse=t.installTimerFunctions=t.transports=t.Transport=t.protocol=t.Socket=void 0;const n=s(481);Object.defineProperty(t,"Socket",{enumerable:!0,get:function(){return n.Socket}}),t.protocol=n.Socket.protocol;var r=s(870);Object.defineProperty(t,"Transport",{enumerable:!0,get:function(){return r.Transport}});var o=s(385);Object.defineProperty(t,"transports",{enumerable:!0,get:function(){return o.transports}});var i=s(622);Object.defineProperty(t,"installTimerFunctions",{enumerable:!0,get:function(){return i.installTimerFunctions}});var a=s(222);Object.defineProperty(t,"parse",{enumerable:!0,get:function(){return a.parse}});var c=s(552);Object.defineProperty(t,"nextTick",{enumerable:!0,get:function(){return c.nextTick}})},481:function(e,t,s){"use strict";var n=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.Socket=void 0;const r=s(385),o=s(622),i=s(754),a=s(222),c=n(s(802)),u=s(260),h=s(373),l=(0,c.default)("engine.io-client:socket");class d extends u.Emitter{constructor(e,t={}){super(),this.writeBuffer=[],e&&"object"==typeof e&&(t=e,e=null),e?(e=(0,a.parse)(e),t.hostname=e.host,t.secure="https"===e.protocol||"wss"===e.protocol,t.port=e.port,e.query&&(t.query=e.query)):t.host&&(t.hostname=(0,a.parse)(t.host).host),(0,o.installTimerFunctions)(this,t),this.secure=null!=t.secure?t.secure:"undefined"!=typeof location&&"https:"===location.protocol,t.hostname&&!t.port&&(t.port=this.secure?"443":"80"),this.hostname=t.hostname||("undefined"!=typeof location?location.hostname:"localhost"),this.port=t.port||("undefined"!=typeof location&&location.port?location.port:this.secure?"443":"80"),this.transports=t.transports||["polling","websocket"],this.writeBuffer=[],this.prevBufferLen=0,this.opts=Object.assign({path:"/engine.io",agent:!1,withCredentials:!1,upgrade:!0,timestampParam:"t",rememberUpgrade:!1,addTrailingSlash:!0,rejectUnauthorized:!0,perMessageDeflate:{threshold:1024},transportOptions:{},closeOnBeforeunload:!0},t),this.opts.path=this.opts.path.replace(/\/$/,"")+(this.opts.addTrailingSlash?"/":""),"string"==typeof this.opts.query&&(this.opts.query=(0,i.decode)(this.opts.query)),this.id=null,this.upgrades=null,this.pingInterval=null,this.pingTimeout=null,this.pingTimeoutTimer=null,"function"==typeof addEventListener&&(this.opts.closeOnBeforeunload&&(this.beforeunloadEventListener=()=>{this.transport&&(this.transport.removeAllListeners(),this.transport.close())},addEventListener("beforeunload",this.beforeunloadEventListener,!1)),"localhost"!==this.hostname&&(this.offlineEventListener=()=>{this.onClose("transport close",{description:"network connection lost"})},addEventListener("offline",this.offlineEventListener,!1))),this.open()}createTransport(e){l('creating transport "%s"',e);const t=Object.assign({},this.opts.query);t.EIO=h.protocol,t.transport=e,this.id&&(t.sid=this.id);const s=Object.assign({},this.opts.transportOptions[e],this.opts,{query:t,socket:this,hostname:this.hostname,secure:this.secure,port:this.port});return l("options: %j",s),new r.transports[e](s)}open(){let e;if(this.opts.rememberUpgrade&&d.priorWebsocketSuccess&&-1!==this.transports.indexOf("websocket"))e="websocket";else{if(0===this.transports.length)return void this.setTimeoutFn((()=>{this.emitReserved("error","No transports available")}),0);e=this.transports[0]}this.readyState="opening";try{e=this.createTransport(e)}catch(e){return l("error while creating transport: %s",e),this.transports.shift(),void this.open()}e.open(),this.setTransport(e)}setTransport(e){l("setting transport %s",e.name),this.transport&&(l("clearing existing transport %s",this.transport.name),this.transport.removeAllListeners()),this.transport=e,e.on("drain",this.onDrain.bind(this)).on("packet",this.onPacket.bind(this)).on("error",this.onError.bind(this)).on("close",(e=>this.onClose("transport close",e)))}probe(e){l('probing transport "%s"',e);let t=this.createTransport(e),s=!1;d.priorWebsocketSuccess=!1;const n=()=>{s||(l('probe transport "%s" opened',e),t.send([{type:"ping",data:"probe"}]),t.once("packet",(n=>{if(!s)if("pong"===n.type&&"probe"===n.data){if(l('probe transport "%s" pong',e),this.upgrading=!0,this.emitReserved("upgrading",t),!t)return;d.priorWebsocketSuccess="websocket"===t.name,l('pausing current transport "%s"',this.transport.name),this.transport.pause((()=>{s||"closed"!==this.readyState&&(l("changing transport and sending upgrade packet"),u(),this.setTransport(t),t.send([{type:"upgrade"}]),this.emitReserved("upgrade",t),t=null,this.upgrading=!1,this.flush())}))}else{l('probe transport "%s" failed',e);const s=new Error("probe error");s.transport=t.name,this.emitReserved("upgradeError",s)}})))};function r(){s||(s=!0,u(),t.close(),t=null)}const o=s=>{const n=new Error("probe error: "+s);n.transport=t.name,r(),l('probe transport "%s" failed because of error: %s',e,s),this.emitReserved("upgradeError",n)};function i(){o("transport closed")}function a(){o("socket closed")}function c(e){t&&e.name!==t.name&&(l('"%s" works - aborting "%s"',e.name,t.name),r())}const u=()=>{t.removeListener("open",n),t.removeListener("error",o),t.removeListener("close",i),this.off("close",a),this.off("upgrading",c)};t.once("open",n),t.once("error",o),t.once("close",i),this.once("close",a),this.once("upgrading",c),t.open()}onOpen(){if(l("socket open"),this.readyState="open",d.priorWebsocketSuccess="websocket"===this.transport.name,this.emitReserved("open"),this.flush(),"open"===this.readyState&&this.opts.upgrade){l("starting upgrade probes");let e=0;const t=this.upgrades.length;for(;e{this.onClose("ping timeout")}),this.pingInterval+this.pingTimeout),this.opts.autoUnref&&this.pingTimeoutTimer.unref()}onDrain(){this.writeBuffer.splice(0,this.prevBufferLen),this.prevBufferLen=0,0===this.writeBuffer.length?this.emitReserved("drain"):this.flush()}flush(){if("closed"!==this.readyState&&this.transport.writable&&!this.upgrading&&this.writeBuffer.length){const e=this.getWritablePackets();l("flushing %d packets in socket",e.length),this.transport.send(e),this.prevBufferLen=e.length,this.emitReserved("flush")}}getWritablePackets(){if(!(this.maxPayload&&"polling"===this.transport.name&&this.writeBuffer.length>1))return this.writeBuffer;let e=1;for(let t=0;t0&&e>this.maxPayload)return l("only send %d out of %d packets",t,this.writeBuffer.length),this.writeBuffer.slice(0,t);e+=2}return l("payload size is %d (max: %d)",e,this.maxPayload),this.writeBuffer}write(e,t,s){return this.sendPacket("message",e,t,s),this}send(e,t,s){return this.sendPacket("message",e,t,s),this}sendPacket(e,t,s,n){if("function"==typeof t&&(n=t,t=void 0),"function"==typeof s&&(n=s,s=null),"closing"===this.readyState||"closed"===this.readyState)return;(s=s||{}).compress=!1!==s.compress;const r={type:e,data:t,options:s};this.emitReserved("packetCreate",r),this.writeBuffer.push(r),n&&this.once("flush",n),this.flush()}close(){const e=()=>{this.onClose("forced close"),l("socket closing - telling transport to close"),this.transport.close()},t=()=>{this.off("upgrade",t),this.off("upgradeError",t),e()},s=()=>{this.once("upgrade",t),this.once("upgradeError",t)};return"opening"!==this.readyState&&"open"!==this.readyState||(this.readyState="closing",this.writeBuffer.length?this.once("drain",(()=>{this.upgrading?s():e()})):this.upgrading?s():e()),this}onError(e){l("socket error %j",e),d.priorWebsocketSuccess=!1,this.emitReserved("error",e),this.onClose("transport error",e)}onClose(e,t){"opening"!==this.readyState&&"open"!==this.readyState&&"closing"!==this.readyState||(l('socket close with reason: "%s"',e),this.clearTimeoutFn(this.pingTimeoutTimer),this.transport.removeAllListeners("close"),this.transport.close(),this.transport.removeAllListeners(),"function"==typeof removeEventListener&&(removeEventListener("beforeunload",this.beforeunloadEventListener,!1),removeEventListener("offline",this.offlineEventListener,!1)),this.readyState="closed",this.id=null,this.emitReserved("close",e,t),this.writeBuffer=[],this.prevBufferLen=0)}filterUpgrades(e){const t=[];let s=0;const n=e.length;for(;s{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.transports=void 0;const n=s(484),r=s(308);t.transports={websocket:r.WS,polling:n.Polling}},484:function(e,t,s){"use strict";var n=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.Request=t.Polling=void 0;const r=s(870),o=n(s(802)),i=s(726),a=s(754),c=s(373),u=s(666),h=s(260),l=s(622),d=s(242),p=(0,o.default)("engine.io-client:polling");function f(){}const g=null!=new u.XHR({xdomain:!1}).responseType;class m extends r.Transport{constructor(e){if(super(e),this.polling=!1,"undefined"!=typeof location){const t="https:"===location.protocol;let s=location.port;s||(s=t?"443":"80"),this.xd="undefined"!=typeof location&&e.hostname!==location.hostname||s!==e.port,this.xs=e.secure!==t}const t=e&&e.forceBase64;this.supportsBinary=g&&!t}get name(){return"polling"}doOpen(){this.poll()}pause(e){this.readyState="pausing";const t=()=>{p("paused"),this.readyState="paused",e()};if(this.polling||!this.writable){let e=0;this.polling&&(p("we are currently polling - waiting to pause"),e++,this.once("pollComplete",(function(){p("pre-pause polling complete"),--e||t()}))),this.writable||(p("we are currently writing - waiting to pause"),e++,this.once("drain",(function(){p("pre-pause writing complete"),--e||t()})))}else t()}poll(){p("polling"),this.polling=!0,this.doPoll(),this.emitReserved("poll")}onData(e){p("polling got data %s",e),(0,c.decodePayload)(e,this.socket.binaryType).forEach((e=>{if("opening"===this.readyState&&"open"===e.type&&this.onOpen(),"close"===e.type)return this.onClose({description:"transport closed by the server"}),!1;this.onPacket(e)})),"closed"!==this.readyState&&(this.polling=!1,this.emitReserved("pollComplete"),"open"===this.readyState?this.poll():p('ignoring poll - transport state "%s"',this.readyState))}doClose(){const e=()=>{p("writing close packet"),this.write([{type:"close"}])};"open"===this.readyState?(p("transport open - closing"),e()):(p("transport not open - deferring close"),this.once("open",e))}write(e){this.writable=!1,(0,c.encodePayload)(e,(e=>{this.doWrite(e,(()=>{this.writable=!0,this.emitReserved("drain")}))}))}uri(){let e=this.query||{};const t=this.opts.secure?"https":"http";let s="";!1!==this.opts.timestampRequests&&(e[this.opts.timestampParam]=(0,i.yeast)()),this.supportsBinary||e.sid||(e.b64=1),this.opts.port&&("https"===t&&443!==Number(this.opts.port)||"http"===t&&80!==Number(this.opts.port))&&(s=":"+this.opts.port);const n=(0,a.encode)(e);return t+"://"+(-1!==this.opts.hostname.indexOf(":")?"["+this.opts.hostname+"]":this.opts.hostname)+s+this.opts.path+(n.length?"?"+n:"")}request(e={}){return Object.assign(e,{xd:this.xd,xs:this.xs},this.opts),new y(this.uri(),e)}doWrite(e,t){const s=this.request({method:"POST",data:e});s.on("success",t),s.on("error",((e,t)=>{this.onError("xhr post error",e,t)}))}doPoll(){p("xhr poll");const e=this.request();e.on("data",this.onData.bind(this)),e.on("error",((e,t)=>{this.onError("xhr poll error",e,t)})),this.pollXhr=e}}t.Polling=m;class y extends h.Emitter{constructor(e,t){super(),(0,l.installTimerFunctions)(this,t),this.opts=t,this.method=t.method||"GET",this.uri=e,this.async=!1!==t.async,this.data=void 0!==t.data?t.data:null,this.create()}create(){const e=(0,l.pick)(this.opts,"agent","pfx","key","passphrase","cert","ca","ciphers","rejectUnauthorized","autoUnref");e.xdomain=!!this.opts.xd,e.xscheme=!!this.opts.xs;const t=this.xhr=new u.XHR(e);try{p("xhr open %s: %s",this.method,this.uri),t.open(this.method,this.uri,this.async);try{if(this.opts.extraHeaders){t.setDisableHeaderCheck&&t.setDisableHeaderCheck(!0);for(let e in this.opts.extraHeaders)this.opts.extraHeaders.hasOwnProperty(e)&&t.setRequestHeader(e,this.opts.extraHeaders[e])}}catch(e){}if("POST"===this.method)try{t.setRequestHeader("Content-type","text/plain;charset=UTF-8")}catch(e){}try{t.setRequestHeader("Accept","*/*")}catch(e){}"withCredentials"in t&&(t.withCredentials=this.opts.withCredentials),this.opts.requestTimeout&&(t.timeout=this.opts.requestTimeout),t.onreadystatechange=()=>{4===t.readyState&&(200===t.status||1223===t.status?this.onLoad():this.setTimeoutFn((()=>{this.onError("number"==typeof t.status?t.status:0)}),0))},p("xhr data %s",this.data),t.send(this.data)}catch(e){return void this.setTimeoutFn((()=>{this.onError(e)}),0)}"undefined"!=typeof document&&(this.index=y.requestsCount++,y.requests[this.index]=this)}onError(e){this.emitReserved("error",e,this.xhr),this.cleanup(!0)}cleanup(e){if(void 0!==this.xhr&&null!==this.xhr){if(this.xhr.onreadystatechange=f,e)try{this.xhr.abort()}catch(e){}"undefined"!=typeof document&&delete y.requests[this.index],this.xhr=null}}onLoad(){const e=this.xhr.responseText;null!==e&&(this.emitReserved("data",e),this.emitReserved("success"),this.cleanup())}abort(){this.cleanup()}}if(t.Request=y,y.requestsCount=0,y.requests={},"undefined"!=typeof document)if("function"==typeof attachEvent)attachEvent("onunload",C);else if("function"==typeof addEventListener){const e="onpagehide"in d.globalThisShim?"pagehide":"unload";addEventListener(e,C,!1)}function C(){for(let e in y.requests)y.requests.hasOwnProperty(e)&&y.requests[e].abort()}},552:(e,t,s)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.defaultBinaryType=t.usingBrowserWebSocket=t.WebSocket=t.nextTick=void 0;const n=s(242);t.nextTick="function"==typeof Promise&&"function"==typeof Promise.resolve?e=>Promise.resolve().then(e):(e,t)=>t(e,0),t.WebSocket=n.globalThisShim.WebSocket||n.globalThisShim.MozWebSocket,t.usingBrowserWebSocket=!0,t.defaultBinaryType="arraybuffer"},308:function(e,t,s){"use strict";var n=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.WS=void 0;const r=s(870),o=s(754),i=s(726),a=s(622),c=s(552),u=n(s(802)),h=s(373),l=(0,u.default)("engine.io-client:websocket"),d="undefined"!=typeof navigator&&"string"==typeof navigator.product&&"reactnative"===navigator.product.toLowerCase();class p extends r.Transport{constructor(e){super(e),this.supportsBinary=!e.forceBase64}get name(){return"websocket"}doOpen(){if(!this.check())return;const e=this.uri(),t=this.opts.protocols,s=d?{}:(0,a.pick)(this.opts,"agent","perMessageDeflate","pfx","key","passphrase","cert","ca","ciphers","rejectUnauthorized","localAddress","protocolVersion","origin","maxPayload","family","checkServerIdentity");this.opts.extraHeaders&&(s.headers=this.opts.extraHeaders);try{this.ws=c.usingBrowserWebSocket&&!d?t?new c.WebSocket(e,t):new c.WebSocket(e):new c.WebSocket(e,t,s)}catch(e){return this.emitReserved("error",e)}this.ws.binaryType=this.socket.binaryType||c.defaultBinaryType,this.addEventListeners()}addEventListeners(){this.ws.onopen=()=>{this.opts.autoUnref&&this.ws._socket.unref(),this.onOpen()},this.ws.onclose=e=>this.onClose({description:"websocket connection closed",context:e}),this.ws.onmessage=e=>this.onData(e.data),this.ws.onerror=e=>this.onError("websocket error",e)}write(e){this.writable=!1;for(let t=0;t{const t={};!c.usingBrowserWebSocket&&(s.options&&(t.compress=s.options.compress),this.opts.perMessageDeflate)&&("string"==typeof e?Buffer.byteLength(e):e.length){this.writable=!0,this.emitReserved("drain")}),this.setTimeoutFn)}))}}doClose(){void 0!==this.ws&&(this.ws.close(),this.ws=null)}uri(){let e=this.query||{};const t=this.opts.secure?"wss":"ws";let s="";this.opts.port&&("wss"===t&&443!==Number(this.opts.port)||"ws"===t&&80!==Number(this.opts.port))&&(s=":"+this.opts.port),this.opts.timestampRequests&&(e[this.opts.timestampParam]=(0,i.yeast)()),this.supportsBinary||(e.b64=1);const n=(0,o.encode)(e);return t+"://"+(-1!==this.opts.hostname.indexOf(":")?"["+this.opts.hostname+"]":this.opts.hostname)+s+this.opts.path+(n.length?"?"+n:"")}check(){return!!c.WebSocket}}t.WS=p},666:(e,t,s)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.XHR=void 0;const n=s(419),r=s(242);t.XHR=function(e){const t=e.xdomain;try{if("undefined"!=typeof XMLHttpRequest&&(!t||n.hasCORS))return new XMLHttpRequest}catch(e){}if(!t)try{return new(r.globalThisShim[["Active"].concat("Object").join("X")])("Microsoft.XMLHTTP")}catch(e){}}},622:(e,t,s)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.byteLength=t.installTimerFunctions=t.pick=void 0;const n=s(242);t.pick=function(e,...t){return t.reduce(((t,s)=>(e.hasOwnProperty(s)&&(t[s]=e[s]),t)),{})};const r=n.globalThisShim.setTimeout,o=n.globalThisShim.clearTimeout;t.installTimerFunctions=function(e,t){t.useNativeTimers?(e.setTimeoutFn=r.bind(n.globalThisShim),e.clearTimeoutFn=o.bind(n.globalThisShim)):(e.setTimeoutFn=n.globalThisShim.setTimeout.bind(n.globalThisShim),e.clearTimeoutFn=n.globalThisShim.clearTimeout.bind(n.globalThisShim))},t.byteLength=function(e){return"string"==typeof e?function(e){let t=0,s=0;for(let n=0,r=e.length;n=57344?s+=3:(n++,s+=4);return s}(e):Math.ceil(1.33*(e.byteLength||e.size))}},87:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.ERROR_PACKET=t.PACKET_TYPES_REVERSE=t.PACKET_TYPES=void 0;const s=Object.create(null);t.PACKET_TYPES=s,s.open="0",s.close="1",s.ping="2",s.pong="3",s.message="4",s.upgrade="5",s.noop="6";const n=Object.create(null);t.PACKET_TYPES_REVERSE=n,Object.keys(s).forEach((e=>{n[s[e]]=e})),t.ERROR_PACKET={type:"error",data:"parser error"}},469:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.decode=t.encode=void 0;const s="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",n="undefined"==typeof Uint8Array?[]:new Uint8Array(256);for(let e=0;e<64;e++)n[s.charCodeAt(e)]=e;t.encode=e=>{let t,n=new Uint8Array(e),r=n.length,o="";for(t=0;t>2],o+=s[(3&n[t])<<4|n[t+1]>>4],o+=s[(15&n[t+1])<<2|n[t+2]>>6],o+=s[63&n[t+2]];return r%3==2?o=o.substring(0,o.length-1)+"=":r%3==1&&(o=o.substring(0,o.length-2)+"=="),o},t.decode=e=>{let t,s,r,o,i,a=.75*e.length,c=e.length,u=0;"="===e[e.length-1]&&(a--,"="===e[e.length-2]&&a--);const h=new ArrayBuffer(a),l=new Uint8Array(h);for(t=0;t>4,l[u++]=(15&r)<<4|o>>2,l[u++]=(3&o)<<6|63&i;return h}},572:(e,t,s)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const n=s(87),r=s(469),o="function"==typeof ArrayBuffer,i=(e,t)=>{if(o){const s=(0,r.decode)(e);return a(s,t)}return{base64:!0,data:e}},a=(e,t)=>"blob"===t&&e instanceof ArrayBuffer?new Blob([e]):e;t.default=(e,t)=>{if("string"!=typeof e)return{type:"message",data:a(e,t)};const s=e.charAt(0);return"b"===s?{type:"message",data:i(e.substring(1),t)}:n.PACKET_TYPES_REVERSE[s]?e.length>1?{type:n.PACKET_TYPES_REVERSE[s],data:e.substring(1)}:{type:n.PACKET_TYPES_REVERSE[s]}:n.ERROR_PACKET}},908:(e,t,s)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const n=s(87),r="function"==typeof Blob||"undefined"!=typeof Blob&&"[object BlobConstructor]"===Object.prototype.toString.call(Blob),o="function"==typeof ArrayBuffer,i=(e,t)=>{const s=new FileReader;return s.onload=function(){const e=s.result.split(",")[1];t("b"+(e||""))},s.readAsDataURL(e)};t.default=({type:e,data:t},s,a)=>{return r&&t instanceof Blob?s?a(t):i(t,a):o&&(t instanceof ArrayBuffer||(c=t,"function"==typeof ArrayBuffer.isView?ArrayBuffer.isView(c):c&&c.buffer instanceof ArrayBuffer))?s?a(t):i(new Blob([t]),a):a(n.PACKET_TYPES[e]+(t||""));var c}},373:(e,t,s)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.decodePayload=t.decodePacket=t.encodePayload=t.encodePacket=t.protocol=void 0;const n=s(908);t.encodePacket=n.default;const r=s(572);t.decodePacket=r.default;const o=String.fromCharCode(30);t.encodePayload=(e,t)=>{const s=e.length,r=new Array(s);let i=0;e.forEach(((e,a)=>{(0,n.default)(e,!1,(e=>{r[a]=e,++i===s&&t(r.join(o))}))}))},t.decodePayload=(e,t)=>{const s=e.split(o),n=[];for(let e=0;e{"use strict";function s(e){e=e||{},this.ms=e.min||100,this.max=e.max||1e4,this.factor=e.factor||2,this.jitter=e.jitter>0&&e.jitter<=1?e.jitter:0,this.attempts=0}Object.defineProperty(t,"__esModule",{value:!0}),t.Backoff=void 0,t.Backoff=s,s.prototype.duration=function(){var e=this.ms*Math.pow(this.factor,this.attempts++);if(this.jitter){var t=Math.random(),s=Math.floor(t*this.jitter*e);e=0==(1&Math.floor(10*t))?e-s:e+s}return 0|Math.min(e,this.max)},s.prototype.reset=function(){this.attempts=0},s.prototype.setMin=function(e){this.ms=e},s.prototype.setMax=function(e){this.max=e},s.prototype.setJitter=function(e){this.jitter=e}},46:function(e,t,s){"use strict";var n=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.default=t.connect=t.io=t.Socket=t.Manager=t.protocol=void 0;const r=s(84),o=s(168);Object.defineProperty(t,"Manager",{enumerable:!0,get:function(){return o.Manager}});const i=s(312);Object.defineProperty(t,"Socket",{enumerable:!0,get:function(){return i.Socket}});const a=n(s(669)).default("socket.io-client"),c={};function u(e,t){"object"==typeof e&&(t=e,e=void 0),t=t||{};const s=r.url(e,t.path||"/socket.io"),n=s.source,i=s.id,u=s.path,h=c[i]&&u in c[i].nsps;let l;return t.forceNew||t["force new connection"]||!1===t.multiplex||h?(a("ignoring socket cache for %s",n),l=new o.Manager(n,t)):(c[i]||(a("new io instance for %s",n),c[i]=new o.Manager(n,t)),l=c[i]),s.query&&!t.query&&(t.query=s.queryKey),l.socket(s.path,t)}t.io=u,t.connect=u,t.default=u,Object.assign(u,{Manager:o.Manager,Socket:i.Socket,io:u,connect:u});var h=s(514);Object.defineProperty(t,"protocol",{enumerable:!0,get:function(){return h.protocol}}),e.exports=u},168:function(e,t,s){"use strict";var n=this&&this.__createBinding||(Object.create?function(e,t,s,n){void 0===n&&(n=s),Object.defineProperty(e,n,{enumerable:!0,get:function(){return t[s]}})}:function(e,t,s,n){void 0===n&&(n=s),e[n]=t[s]}),r=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),o=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var s in e)"default"!==s&&Object.prototype.hasOwnProperty.call(e,s)&&n(t,e,s);return r(t,e),t},i=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.Manager=void 0;const a=s(679),c=s(312),u=o(s(514)),h=s(149),l=s(159),d=s(260),p=i(s(669)).default("socket.io-client:manager");class f extends d.Emitter{constructor(e,t){var s;super(),this.nsps={},this.subs=[],e&&"object"==typeof e&&(t=e,e=void 0),(t=t||{}).path=t.path||"/socket.io",this.opts=t,a.installTimerFunctions(this,t),this.reconnection(!1!==t.reconnection),this.reconnectionAttempts(t.reconnectionAttempts||1/0),this.reconnectionDelay(t.reconnectionDelay||1e3),this.reconnectionDelayMax(t.reconnectionDelayMax||5e3),this.randomizationFactor(null!==(s=t.randomizationFactor)&&void 0!==s?s:.5),this.backoff=new l.Backoff({min:this.reconnectionDelay(),max:this.reconnectionDelayMax(),jitter:this.randomizationFactor()}),this.timeout(null==t.timeout?2e4:t.timeout),this._readyState="closed",this.uri=e;const n=t.parser||u;this.encoder=new n.Encoder,this.decoder=new n.Decoder,this._autoConnect=!1!==t.autoConnect,this._autoConnect&&this.open()}reconnection(e){return arguments.length?(this._reconnection=!!e,this):this._reconnection}reconnectionAttempts(e){return void 0===e?this._reconnectionAttempts:(this._reconnectionAttempts=e,this)}reconnectionDelay(e){var t;return void 0===e?this._reconnectionDelay:(this._reconnectionDelay=e,null===(t=this.backoff)||void 0===t||t.setMin(e),this)}randomizationFactor(e){var t;return void 0===e?this._randomizationFactor:(this._randomizationFactor=e,null===(t=this.backoff)||void 0===t||t.setJitter(e),this)}reconnectionDelayMax(e){var t;return void 0===e?this._reconnectionDelayMax:(this._reconnectionDelayMax=e,null===(t=this.backoff)||void 0===t||t.setMax(e),this)}timeout(e){return arguments.length?(this._timeout=e,this):this._timeout}maybeReconnectOnOpen(){!this._reconnecting&&this._reconnection&&0===this.backoff.attempts&&this.reconnect()}open(e){if(p("readyState %s",this._readyState),~this._readyState.indexOf("open"))return this;p("opening %s",this.uri),this.engine=new a.Socket(this.uri,this.opts);const t=this.engine,s=this;this._readyState="opening",this.skipReconnect=!1;const n=h.on(t,"open",(function(){s.onopen(),e&&e()})),r=h.on(t,"error",(t=>{p("error"),s.cleanup(),s._readyState="closed",this.emitReserved("error",t),e?e(t):s.maybeReconnectOnOpen()}));if(!1!==this._timeout){const e=this._timeout;p("connect attempt will timeout after %d",e),0===e&&n();const s=this.setTimeoutFn((()=>{p("connect attempt timed out after %d",e),n(),t.close(),t.emit("error",new Error("timeout"))}),e);this.opts.autoUnref&&s.unref(),this.subs.push((function(){clearTimeout(s)}))}return this.subs.push(n),this.subs.push(r),this}connect(e){return this.open(e)}onopen(){p("open"),this.cleanup(),this._readyState="open",this.emitReserved("open");const e=this.engine;this.subs.push(h.on(e,"ping",this.onping.bind(this)),h.on(e,"data",this.ondata.bind(this)),h.on(e,"error",this.onerror.bind(this)),h.on(e,"close",this.onclose.bind(this)),h.on(this.decoder,"decoded",this.ondecoded.bind(this)))}onping(){this.emitReserved("ping")}ondata(e){try{this.decoder.add(e)}catch(e){this.onclose("parse error",e)}}ondecoded(e){a.nextTick((()=>{this.emitReserved("packet",e)}),this.setTimeoutFn)}onerror(e){p("error",e),this.emitReserved("error",e)}socket(e,t){let s=this.nsps[e];return s?this._autoConnect&&!s.active&&s.connect():(s=new c.Socket(this,e,t),this.nsps[e]=s),s}_destroy(e){const t=Object.keys(this.nsps);for(const e of t)if(this.nsps[e].active)return void p("socket %s is still active, skipping close",e);this._close()}_packet(e){p("writing packet %j",e);const t=this.encoder.encode(e);for(let s=0;se())),this.subs.length=0,this.decoder.destroy()}_close(){p("disconnect"),this.skipReconnect=!0,this._reconnecting=!1,this.onclose("forced close"),this.engine&&this.engine.close()}disconnect(){return this._close()}onclose(e,t){p("closed due to %s",e),this.cleanup(),this.backoff.reset(),this._readyState="closed",this.emitReserved("close",e,t),this._reconnection&&!this.skipReconnect&&this.reconnect()}reconnect(){if(this._reconnecting||this.skipReconnect)return this;const e=this;if(this.backoff.attempts>=this._reconnectionAttempts)p("reconnect failed"),this.backoff.reset(),this.emitReserved("reconnect_failed"),this._reconnecting=!1;else{const t=this.backoff.duration();p("will wait %dms before reconnect attempt",t),this._reconnecting=!0;const s=this.setTimeoutFn((()=>{e.skipReconnect||(p("attempting reconnect"),this.emitReserved("reconnect_attempt",e.backoff.attempts),e.skipReconnect||e.open((t=>{t?(p("reconnect attempt error"),e._reconnecting=!1,e.reconnect(),this.emitReserved("reconnect_error",t)):(p("reconnect success"),e.onreconnect())})))}),t);this.opts.autoUnref&&s.unref(),this.subs.push((function(){clearTimeout(s)}))}}onreconnect(){const e=this.backoff.attempts;this._reconnecting=!1,this.backoff.reset(),this.emitReserved("reconnect",e)}}t.Manager=f},149:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.on=void 0,t.on=function(e,t,s){return e.on(t,s),function(){e.off(t,s)}}},312:function(e,t,s){"use strict";var n=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.Socket=void 0;const r=s(514),o=s(149),i=s(260),a=n(s(669)).default("socket.io-client:socket"),c=Object.freeze({connect:1,connect_error:1,disconnect:1,disconnecting:1,newListener:1,removeListener:1});class u extends i.Emitter{constructor(e,t,s){super(),this.connected=!1,this.recovered=!1,this.receiveBuffer=[],this.sendBuffer=[],this._queue=[],this._queueSeq=0,this.ids=0,this.acks={},this.flags={},this.io=e,this.nsp=t,s&&s.auth&&(this.auth=s.auth),this._opts=Object.assign({},s),this.io._autoConnect&&this.open()}get disconnected(){return!this.connected}subEvents(){if(this.subs)return;const e=this.io;this.subs=[o.on(e,"open",this.onopen.bind(this)),o.on(e,"packet",this.onpacket.bind(this)),o.on(e,"error",this.onerror.bind(this)),o.on(e,"close",this.onclose.bind(this))]}get active(){return!!this.subs}connect(){return this.connected||(this.subEvents(),this.io._reconnecting||this.io.open(),"open"===this.io._readyState&&this.onopen()),this}open(){return this.connect()}send(...e){return e.unshift("message"),this.emit.apply(this,e),this}emit(e,...t){if(c.hasOwnProperty(e))throw new Error('"'+e.toString()+'" is a reserved event name');if(t.unshift(e),this._opts.retries&&!this.flags.fromQueue&&!this.flags.volatile)return this._addToQueue(t),this;const s={type:r.PacketType.EVENT,data:t,options:{}};if(s.options.compress=!1!==this.flags.compress,"function"==typeof t[t.length-1]){const e=this.ids++;a("emitting packet with ack id %d",e);const n=t.pop();this._registerAckCallback(e,n),s.id=e}const n=this.io.engine&&this.io.engine.transport&&this.io.engine.transport.writable;return!this.flags.volatile||n&&this.connected?this.connected?(this.notifyOutgoingListeners(s),this.packet(s)):this.sendBuffer.push(s):a("discard packet as the transport is not currently writable"),this.flags={},this}_registerAckCallback(e,t){var s;const n=null!==(s=this.flags.timeout)&&void 0!==s?s:this._opts.ackTimeout;if(void 0===n)return void(this.acks[e]=t);const r=this.io.setTimeoutFn((()=>{delete this.acks[e];for(let t=0;t{this.io.clearTimeoutFn(r),t.apply(this,[null,...e])}}emitWithAck(e,...t){const s=void 0!==this.flags.timeout||void 0!==this._opts.ackTimeout;return new Promise(((n,r)=>{t.push(((e,t)=>s?e?r(e):n(t):n(e))),this.emit(e,...t)}))}_addToQueue(e){let t;"function"==typeof e[e.length-1]&&(t=e.pop());const s={id:this._queueSeq++,tryCount:0,pending:!1,args:e,flags:Object.assign({fromQueue:!0},this.flags)};e.push(((e,...n)=>{if(s===this._queue[0])return null!==e?s.tryCount>this._opts.retries&&(a("packet [%d] is discarded after %d tries",s.id,s.tryCount),this._queue.shift(),t&&t(e)):(a("packet [%d] was successfully sent",s.id),this._queue.shift(),t&&t(null,...n)),s.pending=!1,this._drainQueue()})),this._queue.push(s),this._drainQueue()}_drainQueue(e=!1){if(a("draining queue"),!this.connected||0===this._queue.length)return;const t=this._queue[0];!t.pending||e?(t.pending=!0,t.tryCount++,a("sending packet [%d] (try n°%d)",t.id,t.tryCount),this.flags=t.flags,this.emit.apply(this,t.args)):a("packet [%d] has already been sent and is waiting for an ack",t.id)}packet(e){e.nsp=this.nsp,this.io._packet(e)}onopen(){a("transport is open - connecting"),"function"==typeof this.auth?this.auth((e=>{this._sendConnectPacket(e)})):this._sendConnectPacket(this.auth)}_sendConnectPacket(e){this.packet({type:r.PacketType.CONNECT,data:this._pid?Object.assign({pid:this._pid,offset:this._lastOffset},e):e})}onerror(e){this.connected||this.emitReserved("connect_error",e)}onclose(e,t){a("close (%s)",e),this.connected=!1,delete this.id,this.emitReserved("disconnect",e,t)}onpacket(e){if(e.nsp===this.nsp)switch(e.type){case r.PacketType.CONNECT:e.data&&e.data.sid?this.onconnect(e.data.sid,e.data.pid):this.emitReserved("connect_error",new Error("It seems you are trying to reach a Socket.IO server in v2.x with a v3.x client, but they are not compatible (more information here: https://socket.io/docs/v3/migrating-from-2-x-to-3-0/)"));break;case r.PacketType.EVENT:case r.PacketType.BINARY_EVENT:this.onevent(e);break;case r.PacketType.ACK:case r.PacketType.BINARY_ACK:this.onack(e);break;case r.PacketType.DISCONNECT:this.ondisconnect();break;case r.PacketType.CONNECT_ERROR:this.destroy();const t=new Error(e.data.message);t.data=e.data.data,this.emitReserved("connect_error",t)}}onevent(e){const t=e.data||[];a("emitting event %j",t),null!=e.id&&(a("attaching ack callback to event"),t.push(this.ack(e.id))),this.connected?this.emitEvent(t):this.receiveBuffer.push(Object.freeze(t))}emitEvent(e){if(this._anyListeners&&this._anyListeners.length){const t=this._anyListeners.slice();for(const s of t)s.apply(this,e)}super.emit.apply(this,e),this._pid&&e.length&&"string"==typeof e[e.length-1]&&(this._lastOffset=e[e.length-1])}ack(e){const t=this;let s=!1;return function(...n){s||(s=!0,a("sending ack %j",n),t.packet({type:r.PacketType.ACK,id:e,data:n}))}}onack(e){const t=this.acks[e.id];"function"==typeof t?(a("calling ack %s with %j",e.id,e.data),t.apply(this,e.data),delete this.acks[e.id]):a("bad ack %s",e.id)}onconnect(e,t){a("socket connected with id %s",e),this.id=e,this.recovered=t&&this._pid===t,this._pid=t,this.connected=!0,this.emitBuffered(),this.emitReserved("connect"),this._drainQueue(!0)}emitBuffered(){this.receiveBuffer.forEach((e=>this.emitEvent(e))),this.receiveBuffer=[],this.sendBuffer.forEach((e=>{this.notifyOutgoingListeners(e),this.packet(e)})),this.sendBuffer=[]}ondisconnect(){a("server disconnect (%s)",this.nsp),this.destroy(),this.onclose("io server disconnect")}destroy(){this.subs&&(this.subs.forEach((e=>e())),this.subs=void 0),this.io._destroy(this)}disconnect(){return this.connected&&(a("performing disconnect (%s)",this.nsp),this.packet({type:r.PacketType.DISCONNECT})),this.destroy(),this.connected&&this.onclose("io client disconnect"),this}close(){return this.disconnect()}compress(e){return this.flags.compress=e,this}get volatile(){return this.flags.volatile=!0,this}timeout(e){return this.flags.timeout=e,this}onAny(e){return this._anyListeners=this._anyListeners||[],this._anyListeners.push(e),this}prependAny(e){return this._anyListeners=this._anyListeners||[],this._anyListeners.unshift(e),this}offAny(e){if(!this._anyListeners)return this;if(e){const t=this._anyListeners;for(let s=0;s{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.reconstructPacket=t.deconstructPacket=void 0;const n=s(665);function r(e,t){if(!e)return e;if((0,n.isBinary)(e)){const s={_placeholder:!0,num:t.length};return t.push(e),s}if(Array.isArray(e)){const s=new Array(e.length);for(let n=0;n=0&&e.num{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.Decoder=t.Encoder=t.PacketType=t.protocol=void 0;const n=s(260),r=s(880),o=s(665),i=(0,s(618).default)("socket.io-parser");var a;t.protocol=5,function(e){e[e.CONNECT=0]="CONNECT",e[e.DISCONNECT=1]="DISCONNECT",e[e.EVENT=2]="EVENT",e[e.ACK=3]="ACK",e[e.CONNECT_ERROR=4]="CONNECT_ERROR",e[e.BINARY_EVENT=5]="BINARY_EVENT",e[e.BINARY_ACK=6]="BINARY_ACK"}(a=t.PacketType||(t.PacketType={})),t.Encoder=class{constructor(e){this.replacer=e}encode(e){return i("encoding packet %j",e),e.type!==a.EVENT&&e.type!==a.ACK||!(0,o.hasBinary)(e)?[this.encodeAsString(e)]:this.encodeAsBinary({type:e.type===a.EVENT?a.BINARY_EVENT:a.BINARY_ACK,nsp:e.nsp,data:e.data,id:e.id})}encodeAsString(e){let t=""+e.type;return e.type!==a.BINARY_EVENT&&e.type!==a.BINARY_ACK||(t+=e.attachments+"-"),e.nsp&&"/"!==e.nsp&&(t+=e.nsp+","),null!=e.id&&(t+=e.id),null!=e.data&&(t+=JSON.stringify(e.data,this.replacer)),i("encoded %j as %s",e,t),t}encodeAsBinary(e){const t=(0,r.deconstructPacket)(e),s=this.encodeAsString(t.packet),n=t.buffers;return n.unshift(s),n}};class c extends n.Emitter{constructor(e){super(),this.reviver=e}add(e){let t;if("string"==typeof e){if(this.reconstructor)throw new Error("got plaintext data when reconstructing a packet");t=this.decodeString(e);const s=t.type===a.BINARY_EVENT;s||t.type===a.BINARY_ACK?(t.type=s?a.EVENT:a.ACK,this.reconstructor=new u(t),0===t.attachments&&super.emitReserved("decoded",t)):super.emitReserved("decoded",t)}else{if(!(0,o.isBinary)(e)&&!e.base64)throw new Error("Unknown type: "+e);if(!this.reconstructor)throw new Error("got binary data when not reconstructing a packet");t=this.reconstructor.takeBinaryData(e),t&&(this.reconstructor=null,super.emitReserved("decoded",t))}}decodeString(e){let t=0;const s={type:Number(e.charAt(0))};if(void 0===a[s.type])throw new Error("unknown packet type "+s.type);if(s.type===a.BINARY_EVENT||s.type===a.BINARY_ACK){const n=t+1;for(;"-"!==e.charAt(++t)&&t!=e.length;);const r=e.substring(n,t);if(r!=Number(r)||"-"!==e.charAt(t))throw new Error("Illegal attachments");s.attachments=Number(r)}if("/"===e.charAt(t+1)){const n=t+1;for(;++t&&","!==e.charAt(t)&&t!==e.length;);s.nsp=e.substring(n,t)}else s.nsp="/";const n=e.charAt(t+1);if(""!==n&&Number(n)==n){const n=t+1;for(;++t;){const s=e.charAt(t);if(null==s||Number(s)!=s){--t;break}if(t===e.length)break}s.id=Number(e.substring(n,t+1))}if(e.charAt(++t)){const n=this.tryParse(e.substr(t));if(!c.isPayloadValid(s.type,n))throw new Error("invalid payload");s.data=n}return i("decoded %s as %j",e,s),s}tryParse(e){try{return JSON.parse(e,this.reviver)}catch(e){return!1}}static isPayloadValid(e,t){switch(e){case a.CONNECT:return"object"==typeof t;case a.DISCONNECT:return void 0===t;case a.CONNECT_ERROR:return"string"==typeof t||"object"==typeof t;case a.EVENT:case a.BINARY_EVENT:return Array.isArray(t)&&("string"==typeof t[0]||"number"==typeof t[0]);case a.ACK:case a.BINARY_ACK:return Array.isArray(t)}}destroy(){this.reconstructor&&(this.reconstructor.finishedReconstruction(),this.reconstructor=null)}}t.Decoder=c;class u{constructor(e){this.packet=e,this.buffers=[],this.reconPack=e}takeBinaryData(e){if(this.buffers.push(e),this.buffers.length===this.reconPack.attachments){const e=(0,r.reconstructPacket)(this.reconPack,this.buffers);return this.finishedReconstruction(),e}return null}finishedReconstruction(){this.reconPack=null,this.buffers=[]}}},665:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.hasBinary=t.isBinary=void 0;const s="function"==typeof ArrayBuffer,n=Object.prototype.toString,r="function"==typeof Blob||"undefined"!=typeof Blob&&"[object BlobConstructor]"===n.call(Blob),o="function"==typeof File||"undefined"!=typeof File&&"[object FileConstructor]"===n.call(File);function i(e){return s&&(e instanceof ArrayBuffer||(e=>"function"==typeof ArrayBuffer.isView?ArrayBuffer.isView(e):e.buffer instanceof ArrayBuffer)(e))||r&&e instanceof Blob||o&&e instanceof File}t.isBinary=i,t.hasBinary=function e(t,s){if(!t||"object"!=typeof t)return!1;if(Array.isArray(t)){for(let s=0,n=t.length;s{"use strict";function n(e){if(e)return function(e){for(var t in n.prototype)e[t]=n.prototype[t];return e}(e)}s.r(t),s.d(t,{Emitter:()=>n}),n.prototype.on=n.prototype.addEventListener=function(e,t){return this._callbacks=this._callbacks||{},(this._callbacks["$"+e]=this._callbacks["$"+e]||[]).push(t),this},n.prototype.once=function(e,t){function s(){this.off(e,s),t.apply(this,arguments)}return s.fn=t,this.on(e,s),this},n.prototype.off=n.prototype.removeListener=n.prototype.removeAllListeners=n.prototype.removeEventListener=function(e,t){if(this._callbacks=this._callbacks||{},0==arguments.length)return this._callbacks={},this;var s,n=this._callbacks["$"+e];if(!n)return this;if(1==arguments.length)return delete this._callbacks["$"+e],this;for(var r=0;r{for(var n in t)s.o(t,n)&&!s.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},s.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),s.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},s(628)})(); \ No newline at end of file +(()=>{var e={802:(e,t,s)=>{t.formatArgs=function(t){if(t[0]=(this.useColors?"%c":"")+this.namespace+(this.useColors?" %c":" ")+t[0]+(this.useColors?"%c ":" ")+"+"+e.exports.humanize(this.diff),!this.useColors)return;const s="color: "+this.color;t.splice(1,0,s,"color: inherit");let n=0,r=0;t[0].replace(/%[a-zA-Z%]/g,(e=>{"%%"!==e&&(n++,"%c"===e&&(r=n))})),t.splice(r,0,s)},t.save=function(e){try{e?t.storage.setItem("debug",e):t.storage.removeItem("debug")}catch(e){}},t.load=function(){let e;try{e=t.storage.getItem("debug")}catch(e){}return!e&&"undefined"!=typeof process&&"env"in process&&(e=process.env.DEBUG),e},t.useColors=function(){return!("undefined"==typeof window||!window.process||"renderer"!==window.process.type&&!window.process.__nwjs)||("undefined"==typeof navigator||!navigator.userAgent||!navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/))&&("undefined"!=typeof document&&document.documentElement&&document.documentElement.style&&document.documentElement.style.WebkitAppearance||"undefined"!=typeof window&&window.console&&(window.console.firebug||window.console.exception&&window.console.table)||"undefined"!=typeof navigator&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/)&&parseInt(RegExp.$1,10)>=31||"undefined"!=typeof navigator&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/))},t.storage=function(){try{return localStorage}catch(e){}}(),t.destroy=(()=>{let e=!1;return()=>{e||(e=!0,console.warn("Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`."))}})(),t.colors=["#0000CC","#0000FF","#0033CC","#0033FF","#0066CC","#0066FF","#0099CC","#0099FF","#00CC00","#00CC33","#00CC66","#00CC99","#00CCCC","#00CCFF","#3300CC","#3300FF","#3333CC","#3333FF","#3366CC","#3366FF","#3399CC","#3399FF","#33CC00","#33CC33","#33CC66","#33CC99","#33CCCC","#33CCFF","#6600CC","#6600FF","#6633CC","#6633FF","#66CC00","#66CC33","#9900CC","#9900FF","#9933CC","#9933FF","#99CC00","#99CC33","#CC0000","#CC0033","#CC0066","#CC0099","#CC00CC","#CC00FF","#CC3300","#CC3333","#CC3366","#CC3399","#CC33CC","#CC33FF","#CC6600","#CC6633","#CC9900","#CC9933","#CCCC00","#CCCC33","#FF0000","#FF0033","#FF0066","#FF0099","#FF00CC","#FF00FF","#FF3300","#FF3333","#FF3366","#FF3399","#FF33CC","#FF33FF","#FF6600","#FF6633","#FF9900","#FF9933","#FFCC00","#FFCC33"],t.log=console.debug||console.log||(()=>{}),e.exports=s(804)(t);const{formatters:n}=e.exports;n.j=function(e){try{return JSON.stringify(e)}catch(e){return"[UnexpectedJSONParseError]: "+e.message}}},804:(e,t,s)=>{e.exports=function(e){function t(e){let s,r,o,i=null;function a(...e){if(!a.enabled)return;const n=a,r=Number(new Date),o=r-(s||r);n.diff=o,n.prev=s,n.curr=r,s=r,e[0]=t.coerce(e[0]),"string"!=typeof e[0]&&e.unshift("%O");let i=0;e[0]=e[0].replace(/%([a-zA-Z%])/g,((s,r)=>{if("%%"===s)return"%";i++;const o=t.formatters[r];if("function"==typeof o){const t=e[i];s=o.call(n,t),e.splice(i,1),i--}return s})),t.formatArgs.call(n,e),(n.log||t.log).apply(n,e)}return a.namespace=e,a.useColors=t.useColors(),a.color=t.selectColor(e),a.extend=n,a.destroy=t.destroy,Object.defineProperty(a,"enabled",{enumerable:!0,configurable:!1,get:()=>null!==i?i:(r!==t.namespaces&&(r=t.namespaces,o=t.enabled(e)),o),set:e=>{i=e}}),"function"==typeof t.init&&t.init(a),a}function n(e,s){const n=t(this.namespace+(void 0===s?":":s)+e);return n.log=this.log,n}function r(e){return e.toString().substring(2,e.toString().length-2).replace(/\.\*\?$/,"*")}return t.debug=t,t.default=t,t.coerce=function(e){return e instanceof Error?e.stack||e.message:e},t.disable=function(){const e=[...t.names.map(r),...t.skips.map(r).map((e=>"-"+e))].join(",");return t.enable(""),e},t.enable=function(e){let s;t.save(e),t.namespaces=e,t.names=[],t.skips=[];const n=("string"==typeof e?e:"").split(/[\s,]+/),r=n.length;for(s=0;s{t[s]=e[s]})),t.names=[],t.skips=[],t.formatters={},t.selectColor=function(e){let s=0;for(let t=0;t{var t=1e3,s=60*t,n=60*s,r=24*n;function o(e,t,s,n){var r=t>=1.5*s;return Math.round(e/s)+" "+n+(r?"s":"")}e.exports=function(e,i){i=i||{};var a,c,u=typeof e;if("string"===u&&e.length>0)return function(e){if(!((e=String(e)).length>100)){var o=/^(-?(?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)?$/i.exec(e);if(o){var i=parseFloat(o[1]);switch((o[2]||"ms").toLowerCase()){case"years":case"year":case"yrs":case"yr":case"y":return 315576e5*i;case"weeks":case"week":case"w":return 6048e5*i;case"days":case"day":case"d":return i*r;case"hours":case"hour":case"hrs":case"hr":case"h":return i*n;case"minutes":case"minute":case"mins":case"min":case"m":return i*s;case"seconds":case"second":case"secs":case"sec":case"s":return i*t;case"milliseconds":case"millisecond":case"msecs":case"msec":case"ms":return i;default:return}}}}(e);if("number"===u&&isFinite(e))return i.long?(a=e,(c=Math.abs(a))>=r?o(a,c,r,"day"):c>=n?o(a,c,n,"hour"):c>=s?o(a,c,s,"minute"):c>=t?o(a,c,t,"second"):a+" ms"):function(e){var o=Math.abs(e);return o>=r?Math.round(e/r)+"d":o>=n?Math.round(e/n)+"h":o>=s?Math.round(e/s)+"m":o>=t?Math.round(e/t)+"s":e+"ms"}(e);throw new Error("val is not a non-empty string or a valid number. val="+JSON.stringify(e))}},669:(e,t,s)=>{t.formatArgs=function(t){if(t[0]=(this.useColors?"%c":"")+this.namespace+(this.useColors?" %c":" ")+t[0]+(this.useColors?"%c ":" ")+"+"+e.exports.humanize(this.diff),!this.useColors)return;const s="color: "+this.color;t.splice(1,0,s,"color: inherit");let n=0,r=0;t[0].replace(/%[a-zA-Z%]/g,(e=>{"%%"!==e&&(n++,"%c"===e&&(r=n))})),t.splice(r,0,s)},t.save=function(e){try{e?t.storage.setItem("debug",e):t.storage.removeItem("debug")}catch(e){}},t.load=function(){let e;try{e=t.storage.getItem("debug")}catch(e){}return!e&&"undefined"!=typeof process&&"env"in process&&(e=process.env.DEBUG),e},t.useColors=function(){return!("undefined"==typeof window||!window.process||"renderer"!==window.process.type&&!window.process.__nwjs)||("undefined"==typeof navigator||!navigator.userAgent||!navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/))&&("undefined"!=typeof document&&document.documentElement&&document.documentElement.style&&document.documentElement.style.WebkitAppearance||"undefined"!=typeof window&&window.console&&(window.console.firebug||window.console.exception&&window.console.table)||"undefined"!=typeof navigator&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/)&&parseInt(RegExp.$1,10)>=31||"undefined"!=typeof navigator&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/))},t.storage=function(){try{return localStorage}catch(e){}}(),t.destroy=(()=>{let e=!1;return()=>{e||(e=!0,console.warn("Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`."))}})(),t.colors=["#0000CC","#0000FF","#0033CC","#0033FF","#0066CC","#0066FF","#0099CC","#0099FF","#00CC00","#00CC33","#00CC66","#00CC99","#00CCCC","#00CCFF","#3300CC","#3300FF","#3333CC","#3333FF","#3366CC","#3366FF","#3399CC","#3399FF","#33CC00","#33CC33","#33CC66","#33CC99","#33CCCC","#33CCFF","#6600CC","#6600FF","#6633CC","#6633FF","#66CC00","#66CC33","#9900CC","#9900FF","#9933CC","#9933FF","#99CC00","#99CC33","#CC0000","#CC0033","#CC0066","#CC0099","#CC00CC","#CC00FF","#CC3300","#CC3333","#CC3366","#CC3399","#CC33CC","#CC33FF","#CC6600","#CC6633","#CC9900","#CC9933","#CCCC00","#CCCC33","#FF0000","#FF0033","#FF0066","#FF0099","#FF00CC","#FF00FF","#FF3300","#FF3333","#FF3366","#FF3399","#FF33CC","#FF33FF","#FF6600","#FF6633","#FF9900","#FF9933","#FFCC00","#FFCC33"],t.log=console.debug||console.log||(()=>{}),e.exports=s(231)(t);const{formatters:n}=e.exports;n.j=function(e){try{return JSON.stringify(e)}catch(e){return"[UnexpectedJSONParseError]: "+e.message}}},231:(e,t,s)=>{e.exports=function(e){function t(e){let s,r,o,i=null;function a(...e){if(!a.enabled)return;const n=a,r=Number(new Date),o=r-(s||r);n.diff=o,n.prev=s,n.curr=r,s=r,e[0]=t.coerce(e[0]),"string"!=typeof e[0]&&e.unshift("%O");let i=0;e[0]=e[0].replace(/%([a-zA-Z%])/g,((s,r)=>{if("%%"===s)return"%";i++;const o=t.formatters[r];if("function"==typeof o){const t=e[i];s=o.call(n,t),e.splice(i,1),i--}return s})),t.formatArgs.call(n,e),(n.log||t.log).apply(n,e)}return a.namespace=e,a.useColors=t.useColors(),a.color=t.selectColor(e),a.extend=n,a.destroy=t.destroy,Object.defineProperty(a,"enabled",{enumerable:!0,configurable:!1,get:()=>null!==i?i:(r!==t.namespaces&&(r=t.namespaces,o=t.enabled(e)),o),set:e=>{i=e}}),"function"==typeof t.init&&t.init(a),a}function n(e,s){const n=t(this.namespace+(void 0===s?":":s)+e);return n.log=this.log,n}function r(e){return e.toString().substring(2,e.toString().length-2).replace(/\.\*\?$/,"*")}return t.debug=t,t.default=t,t.coerce=function(e){return e instanceof Error?e.stack||e.message:e},t.disable=function(){const e=[...t.names.map(r),...t.skips.map(r).map((e=>"-"+e))].join(",");return t.enable(""),e},t.enable=function(e){let s;t.save(e),t.namespaces=e,t.names=[],t.skips=[];const n=("string"==typeof e?e:"").split(/[\s,]+/),r=n.length;for(s=0;s{t[s]=e[s]})),t.names=[],t.skips=[],t.formatters={},t.selectColor=function(e){let s=0;for(let t=0;t{var t=1e3,s=60*t,n=60*s,r=24*n;function o(e,t,s,n){var r=t>=1.5*s;return Math.round(e/s)+" "+n+(r?"s":"")}e.exports=function(e,i){i=i||{};var a,c,u=typeof e;if("string"===u&&e.length>0)return function(e){if(!((e=String(e)).length>100)){var o=/^(-?(?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)?$/i.exec(e);if(o){var i=parseFloat(o[1]);switch((o[2]||"ms").toLowerCase()){case"years":case"year":case"yrs":case"yr":case"y":return 315576e5*i;case"weeks":case"week":case"w":return 6048e5*i;case"days":case"day":case"d":return i*r;case"hours":case"hour":case"hrs":case"hr":case"h":return i*n;case"minutes":case"minute":case"mins":case"min":case"m":return i*s;case"seconds":case"second":case"secs":case"sec":case"s":return i*t;case"milliseconds":case"millisecond":case"msecs":case"msec":case"ms":return i;default:return}}}}(e);if("number"===u&&isFinite(e))return i.long?(a=e,(c=Math.abs(a))>=r?o(a,c,r,"day"):c>=n?o(a,c,n,"hour"):c>=s?o(a,c,s,"minute"):c>=t?o(a,c,t,"second"):a+" ms"):function(e){var o=Math.abs(e);return o>=r?Math.round(e/r)+"d":o>=n?Math.round(e/n)+"h":o>=s?Math.round(e/s)+"m":o>=t?Math.round(e/t)+"s":e+"ms"}(e);throw new Error("val is not a non-empty string or a valid number. val="+JSON.stringify(e))}},618:(e,t,s)=>{t.formatArgs=function(t){if(t[0]=(this.useColors?"%c":"")+this.namespace+(this.useColors?" %c":" ")+t[0]+(this.useColors?"%c ":" ")+"+"+e.exports.humanize(this.diff),!this.useColors)return;const s="color: "+this.color;t.splice(1,0,s,"color: inherit");let n=0,r=0;t[0].replace(/%[a-zA-Z%]/g,(e=>{"%%"!==e&&(n++,"%c"===e&&(r=n))})),t.splice(r,0,s)},t.save=function(e){try{e?t.storage.setItem("debug",e):t.storage.removeItem("debug")}catch(e){}},t.load=function(){let e;try{e=t.storage.getItem("debug")}catch(e){}return!e&&"undefined"!=typeof process&&"env"in process&&(e=process.env.DEBUG),e},t.useColors=function(){return!("undefined"==typeof window||!window.process||"renderer"!==window.process.type&&!window.process.__nwjs)||("undefined"==typeof navigator||!navigator.userAgent||!navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/))&&("undefined"!=typeof document&&document.documentElement&&document.documentElement.style&&document.documentElement.style.WebkitAppearance||"undefined"!=typeof window&&window.console&&(window.console.firebug||window.console.exception&&window.console.table)||"undefined"!=typeof navigator&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/)&&parseInt(RegExp.$1,10)>=31||"undefined"!=typeof navigator&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/))},t.storage=function(){try{return localStorage}catch(e){}}(),t.destroy=(()=>{let e=!1;return()=>{e||(e=!0,console.warn("Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`."))}})(),t.colors=["#0000CC","#0000FF","#0033CC","#0033FF","#0066CC","#0066FF","#0099CC","#0099FF","#00CC00","#00CC33","#00CC66","#00CC99","#00CCCC","#00CCFF","#3300CC","#3300FF","#3333CC","#3333FF","#3366CC","#3366FF","#3399CC","#3399FF","#33CC00","#33CC33","#33CC66","#33CC99","#33CCCC","#33CCFF","#6600CC","#6600FF","#6633CC","#6633FF","#66CC00","#66CC33","#9900CC","#9900FF","#9933CC","#9933FF","#99CC00","#99CC33","#CC0000","#CC0033","#CC0066","#CC0099","#CC00CC","#CC00FF","#CC3300","#CC3333","#CC3366","#CC3399","#CC33CC","#CC33FF","#CC6600","#CC6633","#CC9900","#CC9933","#CCCC00","#CCCC33","#FF0000","#FF0033","#FF0066","#FF0099","#FF00CC","#FF00FF","#FF3300","#FF3333","#FF3366","#FF3399","#FF33CC","#FF33FF","#FF6600","#FF6633","#FF9900","#FF9933","#FFCC00","#FFCC33"],t.log=console.debug||console.log||(()=>{}),e.exports=s(224)(t);const{formatters:n}=e.exports;n.j=function(e){try{return JSON.stringify(e)}catch(e){return"[UnexpectedJSONParseError]: "+e.message}}},224:(e,t,s)=>{e.exports=function(e){function t(e){let s,r,o,i=null;function a(...e){if(!a.enabled)return;const n=a,r=Number(new Date),o=r-(s||r);n.diff=o,n.prev=s,n.curr=r,s=r,e[0]=t.coerce(e[0]),"string"!=typeof e[0]&&e.unshift("%O");let i=0;e[0]=e[0].replace(/%([a-zA-Z%])/g,((s,r)=>{if("%%"===s)return"%";i++;const o=t.formatters[r];if("function"==typeof o){const t=e[i];s=o.call(n,t),e.splice(i,1),i--}return s})),t.formatArgs.call(n,e),(n.log||t.log).apply(n,e)}return a.namespace=e,a.useColors=t.useColors(),a.color=t.selectColor(e),a.extend=n,a.destroy=t.destroy,Object.defineProperty(a,"enabled",{enumerable:!0,configurable:!1,get:()=>null!==i?i:(r!==t.namespaces&&(r=t.namespaces,o=t.enabled(e)),o),set:e=>{i=e}}),"function"==typeof t.init&&t.init(a),a}function n(e,s){const n=t(this.namespace+(void 0===s?":":s)+e);return n.log=this.log,n}function r(e){return e.toString().substring(2,e.toString().length-2).replace(/\.\*\?$/,"*")}return t.debug=t,t.default=t,t.coerce=function(e){return e instanceof Error?e.stack||e.message:e},t.disable=function(){const e=[...t.names.map(r),...t.skips.map(r).map((e=>"-"+e))].join(",");return t.enable(""),e},t.enable=function(e){let s;t.save(e),t.namespaces=e,t.names=[],t.skips=[];const n=("string"==typeof e?e:"").split(/[\s,]+/),r=n.length;for(s=0;s{t[s]=e[s]})),t.names=[],t.skips=[],t.formatters={},t.selectColor=function(e){let s=0;for(let t=0;t{var t=1e3,s=60*t,n=60*s,r=24*n;function o(e,t,s,n){var r=t>=1.5*s;return Math.round(e/s)+" "+n+(r?"s":"")}e.exports=function(e,i){i=i||{};var a,c,u=typeof e;if("string"===u&&e.length>0)return function(e){if(!((e=String(e)).length>100)){var o=/^(-?(?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)?$/i.exec(e);if(o){var i=parseFloat(o[1]);switch((o[2]||"ms").toLowerCase()){case"years":case"year":case"yrs":case"yr":case"y":return 315576e5*i;case"weeks":case"week":case"w":return 6048e5*i;case"days":case"day":case"d":return i*r;case"hours":case"hour":case"hrs":case"hr":case"h":return i*n;case"minutes":case"minute":case"mins":case"min":case"m":return i*s;case"seconds":case"second":case"secs":case"sec":case"s":return i*t;case"milliseconds":case"millisecond":case"msecs":case"msec":case"ms":return i;default:return}}}}(e);if("number"===u&&isFinite(e))return i.long?(a=e,(c=Math.abs(a))>=r?o(a,c,r,"day"):c>=n?o(a,c,n,"hour"):c>=s?o(a,c,s,"minute"):c>=t?o(a,c,t,"second"):a+" ms"):function(e){var o=Math.abs(e);return o>=r?Math.round(e/r)+"d":o>=n?Math.round(e/n)+"h":o>=s?Math.round(e/s)+"m":o>=t?Math.round(e/t)+"s":e+"ms"}(e);throw new Error("val is not a non-empty string or a valid number. val="+JSON.stringify(e))}},628:function(e,t,s){"use strict";var n=this&&this.__createBinding||(Object.create?function(e,t,s,n){void 0===n&&(n=s);var r=Object.getOwnPropertyDescriptor(t,s);r&&!("get"in r?!t.__esModule:r.writable||r.configurable)||(r={enumerable:!0,get:function(){return t[s]}}),Object.defineProperty(e,n,r)}:function(e,t,s,n){void 0===n&&(n=s),e[n]=t[s]}),r=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),o=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var s in e)"default"!==s&&Object.prototype.hasOwnProperty.call(e,s)&&n(t,e,s);return r(t,e),t};Object.defineProperty(t,"__esModule",{value:!0}),t.authToken=void 0;const i=s(46),a=s(182),c=o(s(454));function u(e,t=document){return t.querySelector(e)}function h(e,t=document){return Array.from(t.querySelectorAll(e))}function l(){return localStorage.getItem("authToken")||""}function d(){const e=p.gradientName(),t=u("body");let s;t.classList.value=e,t.classList.add(p.getTimePeriod()),s=p.get24Hour()>=5&&p.get24Hour()<9?"morning":p.get24Hour()>=9&&p.get24Hour()<17?"afternoon":p.get24Hour()>=17&&p.get24Hour()<20?"evening":"night",u("#time-of-day").innerHTML=` ${p.getHour()}${p.getAmPm()}`}t.authToken=l;const p=new a.TimeManager,f=(0,i.io)({extraHeaders:{"x-authtoken":l()}});d(),setInterval(d,6e4),f.on("connect",(()=>{console.log(`Connected: ${f.id}`)})),f.on("authToken",(e=>{console.log(`recv auth token ${e}`),localStorage.getItem("authToken")!==e&&(localStorage.setItem("authToken",e),window.location.reload())})),f.on("status",(e=>u("#server-stats").innerHTML=e)),f.on("chat",(function(e){u("#chat-messages").insertAdjacentHTML("beforeend",e),u("#chat-messages").scrollTop=u("#chat-messages").scrollHeight})),f.on("ready",(function(){console.log("Server connection verified"),h("nav a")[3].click()})),h("nav a").forEach((e=>{e.addEventListener("click",(e=>{const t=e.target;h("a",t.closest("nav")).forEach((e=>{e.classList.remove("active")})),t.classList.add("active");const s=u(t.getAttribute("hx-target").toString());Array.from(s.parentElement.children).forEach((e=>{e.classList.remove("active")})),s.classList.add("active")}))})),u("body").addEventListener("click",(e=>{var t;const s=e.target;if(s.parentElement.classList.contains("filter")){Array.from(s.parentElement.children).forEach((e=>e.classList.remove("active"))),s.classList.add("active");const e=s.getAttribute("data-filter"),t=u(`.filter-result[data-filter="${e}"]`,s.closest(".filter-container"));t&&Array.from(t.parentElement.children).forEach((t=>{t.getAttribute("data-filter")===e?(t.classList.remove("hidden"),t.classList.add("active")):(t.classList.add("hidden"),t.classList.remove("active"))}))}if("dialog"===s.getAttribute("formmethod")&&"cancel"===s.getAttribute("value")){null===(t=s.closest("dialog"))||void 0===t||t.close();const e=s.getAttribute("nav-trigger");if(e){const[t,s]=e.split("|");h(t)[s].click()}}}));const g=new MutationObserver(((e,t)=>{const s={modal:!1,alert:!1};e.forEach((e=>{switch(e.target.id){case"modal-wrapper":s.modal=!0;break;case"alerts":s.alert=!0}})),s.modal&&u("#modal-wrapper").children.length&&h("#modal-wrapper dialog").forEach((e=>{e.open||e.showModal()})),s.alert&&u("#alerts").children.length&&h("#alerts .alert").forEach((e=>{if(!e.getAttribute("data-dismiss-at")){const t=Date.now()+c.ALERT_DISPLAY_LENGTH;e.setAttribute("data-dismiss-at",t.toString()),setTimeout((()=>{e.remove()}),c.ALERT_DISPLAY_LENGTH)}}))}));g.observe(u("#modal-wrapper"),{childList:!0}),g.observe(u("#alerts"),{childList:!0}),document.body.addEventListener("htmx:configRequest",(function(e){e.detail.headers["x-authtoken"]=l()})),document.body.addEventListener("htmx:load",(function(e){h(".disabled[data-block]").forEach((e=>{const t=parseInt(e.getAttribute("data-block")||"0");if(t>Date.now()){const s=t-Date.now();setTimeout((()=>{e.removeAttribute("disabled")}),s)}else e.removeAttribute("disabled")}))})),document.body.addEventListener("htmx:beforeSwap",(function(e){"chat-form"===e.target.id?u("#message").value="":"logout"===e.detail.serverResponse&&(localStorage.removeItem("authToken"),window.location.reload())}))},454:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.DUNGEON_TRAVEL_BLOCK=t.CHANCE_TO_FIGHT_SPECIAL=t.ALERT_DISPLAY_LENGTH=t.STEP_DELAY=t.FIGHT_ATTACK_DELAY=void 0,t.FIGHT_ATTACK_DELAY=1500,t.STEP_DELAY=2e3,t.ALERT_DISPLAY_LENGTH=3e3,t.CHANCE_TO_FIGHT_SPECIAL=10,t.DUNGEON_TRAVEL_BLOCK=3e3},182:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.TimeManager=void 0,t.TimeManager=class{constructor(e=120){this.dayLength=e,this.scaleFactor=30,this.dayLengthAsMS=60*e*1e3}dayScaleFactor(){return this.dayLength/30}getTimePeriod(){return this.isMorning()?"morning":this.isAfternoon()?"afternoon":this.isEvening()?"evening":this.isNight()?"night":void 0}getAmPm(){return this.get24Hour()<12?"am":"pm"}get24Hour(){const e=new Date,t=(e.getMinutes()+e.getHours()%2*(this.dayLength/2))/this.dayLength;return Math.floor(24*t)}getHour(){const e=this.get24Hour(),t=e>12?e-12:e;return 0===t?12:t}gradientName(){const e=Math.floor(this.get24Hour()/24*this.scaleFactor);return`sky-gradient-${e<10?"0":""}${e}`}isNight(){const e=this.get24Hour();return e>=0&&e<5||e>=21&&e<24}isMorning(){const e=this.get24Hour();return e>=5&&e<12}isAfternoon(){const e=this.get24Hour();return e>=12&&e<18}isEvening(){const e=this.get24Hour();return e>=18&&e<21}}},419:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.hasCORS=void 0;let s=!1;try{s="undefined"!=typeof XMLHttpRequest&&"withCredentials"in new XMLHttpRequest}catch(e){}t.hasCORS=s},754:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.decode=t.encode=void 0,t.encode=function(e){let t="";for(let s in e)e.hasOwnProperty(s)&&(t.length&&(t+="&"),t+=encodeURIComponent(s)+"="+encodeURIComponent(e[s]));return t},t.decode=function(e){let t={},s=e.split("&");for(let e=0,n=s.length;e{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.parse=void 0;const s=/^(?:(?![^:@\/?#]+:[^:@\/]*@)(http|https|ws|wss):\/\/)?((?:(([^:@\/?#]*)(?::([^:@\/?#]*))?)?@)?((?:[a-f0-9]{0,4}:){2,7}[a-f0-9]{0,4}|[^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/,n=["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"];t.parse=function(e){const t=e,r=e.indexOf("["),o=e.indexOf("]");-1!=r&&-1!=o&&(e=e.substring(0,r)+e.substring(r,o).replace(/:/g,";")+e.substring(o,e.length));let i=s.exec(e||""),a={},c=14;for(;c--;)a[n[c]]=i[c]||"";return-1!=r&&-1!=o&&(a.source=t,a.host=a.host.substring(1,a.host.length-1).replace(/;/g,":"),a.authority=a.authority.replace("[","").replace("]","").replace(/;/g,":"),a.ipv6uri=!0),a.pathNames=function(e,t){const s=t.replace(/\/{2,9}/g,"/").split("/");return"/"!=t.slice(0,1)&&0!==t.length||s.splice(0,1),"/"==t.slice(-1)&&s.splice(s.length-1,1),s}(0,a.path),a.queryKey=function(e,t){const s={};return t.replace(/(?:^|&)([^&=]*)=?([^&]*)/g,(function(e,t,n){t&&(s[t]=n)})),s}(0,a.query),a}},726:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.yeast=t.decode=t.encode=void 0;const s="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_".split(""),n={};let r,o=0,i=0;function a(e){let t="";do{t=s[e%64]+t,e=Math.floor(e/64)}while(e>0);return t}for(t.encode=a,t.decode=function(e){let t=0;for(i=0;i{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.globalThisShim=void 0,t.globalThisShim="undefined"!=typeof self?self:"undefined"!=typeof window?window:Function("return this")()},679:(e,t,s)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.nextTick=t.parse=t.installTimerFunctions=t.transports=t.Transport=t.protocol=t.Socket=void 0;const n=s(481);Object.defineProperty(t,"Socket",{enumerable:!0,get:function(){return n.Socket}}),t.protocol=n.Socket.protocol;var r=s(870);Object.defineProperty(t,"Transport",{enumerable:!0,get:function(){return r.Transport}});var o=s(385);Object.defineProperty(t,"transports",{enumerable:!0,get:function(){return o.transports}});var i=s(622);Object.defineProperty(t,"installTimerFunctions",{enumerable:!0,get:function(){return i.installTimerFunctions}});var a=s(222);Object.defineProperty(t,"parse",{enumerable:!0,get:function(){return a.parse}});var c=s(552);Object.defineProperty(t,"nextTick",{enumerable:!0,get:function(){return c.nextTick}})},481:function(e,t,s){"use strict";var n=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.Socket=void 0;const r=s(385),o=s(622),i=s(754),a=s(222),c=n(s(802)),u=s(260),h=s(373),l=(0,c.default)("engine.io-client:socket");class d extends u.Emitter{constructor(e,t={}){super(),this.writeBuffer=[],e&&"object"==typeof e&&(t=e,e=null),e?(e=(0,a.parse)(e),t.hostname=e.host,t.secure="https"===e.protocol||"wss"===e.protocol,t.port=e.port,e.query&&(t.query=e.query)):t.host&&(t.hostname=(0,a.parse)(t.host).host),(0,o.installTimerFunctions)(this,t),this.secure=null!=t.secure?t.secure:"undefined"!=typeof location&&"https:"===location.protocol,t.hostname&&!t.port&&(t.port=this.secure?"443":"80"),this.hostname=t.hostname||("undefined"!=typeof location?location.hostname:"localhost"),this.port=t.port||("undefined"!=typeof location&&location.port?location.port:this.secure?"443":"80"),this.transports=t.transports||["polling","websocket"],this.writeBuffer=[],this.prevBufferLen=0,this.opts=Object.assign({path:"/engine.io",agent:!1,withCredentials:!1,upgrade:!0,timestampParam:"t",rememberUpgrade:!1,addTrailingSlash:!0,rejectUnauthorized:!0,perMessageDeflate:{threshold:1024},transportOptions:{},closeOnBeforeunload:!0},t),this.opts.path=this.opts.path.replace(/\/$/,"")+(this.opts.addTrailingSlash?"/":""),"string"==typeof this.opts.query&&(this.opts.query=(0,i.decode)(this.opts.query)),this.id=null,this.upgrades=null,this.pingInterval=null,this.pingTimeout=null,this.pingTimeoutTimer=null,"function"==typeof addEventListener&&(this.opts.closeOnBeforeunload&&(this.beforeunloadEventListener=()=>{this.transport&&(this.transport.removeAllListeners(),this.transport.close())},addEventListener("beforeunload",this.beforeunloadEventListener,!1)),"localhost"!==this.hostname&&(this.offlineEventListener=()=>{this.onClose("transport close",{description:"network connection lost"})},addEventListener("offline",this.offlineEventListener,!1))),this.open()}createTransport(e){l('creating transport "%s"',e);const t=Object.assign({},this.opts.query);t.EIO=h.protocol,t.transport=e,this.id&&(t.sid=this.id);const s=Object.assign({},this.opts.transportOptions[e],this.opts,{query:t,socket:this,hostname:this.hostname,secure:this.secure,port:this.port});return l("options: %j",s),new r.transports[e](s)}open(){let e;if(this.opts.rememberUpgrade&&d.priorWebsocketSuccess&&-1!==this.transports.indexOf("websocket"))e="websocket";else{if(0===this.transports.length)return void this.setTimeoutFn((()=>{this.emitReserved("error","No transports available")}),0);e=this.transports[0]}this.readyState="opening";try{e=this.createTransport(e)}catch(e){return l("error while creating transport: %s",e),this.transports.shift(),void this.open()}e.open(),this.setTransport(e)}setTransport(e){l("setting transport %s",e.name),this.transport&&(l("clearing existing transport %s",this.transport.name),this.transport.removeAllListeners()),this.transport=e,e.on("drain",this.onDrain.bind(this)).on("packet",this.onPacket.bind(this)).on("error",this.onError.bind(this)).on("close",(e=>this.onClose("transport close",e)))}probe(e){l('probing transport "%s"',e);let t=this.createTransport(e),s=!1;d.priorWebsocketSuccess=!1;const n=()=>{s||(l('probe transport "%s" opened',e),t.send([{type:"ping",data:"probe"}]),t.once("packet",(n=>{if(!s)if("pong"===n.type&&"probe"===n.data){if(l('probe transport "%s" pong',e),this.upgrading=!0,this.emitReserved("upgrading",t),!t)return;d.priorWebsocketSuccess="websocket"===t.name,l('pausing current transport "%s"',this.transport.name),this.transport.pause((()=>{s||"closed"!==this.readyState&&(l("changing transport and sending upgrade packet"),u(),this.setTransport(t),t.send([{type:"upgrade"}]),this.emitReserved("upgrade",t),t=null,this.upgrading=!1,this.flush())}))}else{l('probe transport "%s" failed',e);const s=new Error("probe error");s.transport=t.name,this.emitReserved("upgradeError",s)}})))};function r(){s||(s=!0,u(),t.close(),t=null)}const o=s=>{const n=new Error("probe error: "+s);n.transport=t.name,r(),l('probe transport "%s" failed because of error: %s',e,s),this.emitReserved("upgradeError",n)};function i(){o("transport closed")}function a(){o("socket closed")}function c(e){t&&e.name!==t.name&&(l('"%s" works - aborting "%s"',e.name,t.name),r())}const u=()=>{t.removeListener("open",n),t.removeListener("error",o),t.removeListener("close",i),this.off("close",a),this.off("upgrading",c)};t.once("open",n),t.once("error",o),t.once("close",i),this.once("close",a),this.once("upgrading",c),t.open()}onOpen(){if(l("socket open"),this.readyState="open",d.priorWebsocketSuccess="websocket"===this.transport.name,this.emitReserved("open"),this.flush(),"open"===this.readyState&&this.opts.upgrade){l("starting upgrade probes");let e=0;const t=this.upgrades.length;for(;e{this.onClose("ping timeout")}),this.pingInterval+this.pingTimeout),this.opts.autoUnref&&this.pingTimeoutTimer.unref()}onDrain(){this.writeBuffer.splice(0,this.prevBufferLen),this.prevBufferLen=0,0===this.writeBuffer.length?this.emitReserved("drain"):this.flush()}flush(){if("closed"!==this.readyState&&this.transport.writable&&!this.upgrading&&this.writeBuffer.length){const e=this.getWritablePackets();l("flushing %d packets in socket",e.length),this.transport.send(e),this.prevBufferLen=e.length,this.emitReserved("flush")}}getWritablePackets(){if(!(this.maxPayload&&"polling"===this.transport.name&&this.writeBuffer.length>1))return this.writeBuffer;let e=1;for(let t=0;t0&&e>this.maxPayload)return l("only send %d out of %d packets",t,this.writeBuffer.length),this.writeBuffer.slice(0,t);e+=2}return l("payload size is %d (max: %d)",e,this.maxPayload),this.writeBuffer}write(e,t,s){return this.sendPacket("message",e,t,s),this}send(e,t,s){return this.sendPacket("message",e,t,s),this}sendPacket(e,t,s,n){if("function"==typeof t&&(n=t,t=void 0),"function"==typeof s&&(n=s,s=null),"closing"===this.readyState||"closed"===this.readyState)return;(s=s||{}).compress=!1!==s.compress;const r={type:e,data:t,options:s};this.emitReserved("packetCreate",r),this.writeBuffer.push(r),n&&this.once("flush",n),this.flush()}close(){const e=()=>{this.onClose("forced close"),l("socket closing - telling transport to close"),this.transport.close()},t=()=>{this.off("upgrade",t),this.off("upgradeError",t),e()},s=()=>{this.once("upgrade",t),this.once("upgradeError",t)};return"opening"!==this.readyState&&"open"!==this.readyState||(this.readyState="closing",this.writeBuffer.length?this.once("drain",(()=>{this.upgrading?s():e()})):this.upgrading?s():e()),this}onError(e){l("socket error %j",e),d.priorWebsocketSuccess=!1,this.emitReserved("error",e),this.onClose("transport error",e)}onClose(e,t){"opening"!==this.readyState&&"open"!==this.readyState&&"closing"!==this.readyState||(l('socket close with reason: "%s"',e),this.clearTimeoutFn(this.pingTimeoutTimer),this.transport.removeAllListeners("close"),this.transport.close(),this.transport.removeAllListeners(),"function"==typeof removeEventListener&&(removeEventListener("beforeunload",this.beforeunloadEventListener,!1),removeEventListener("offline",this.offlineEventListener,!1)),this.readyState="closed",this.id=null,this.emitReserved("close",e,t),this.writeBuffer=[],this.prevBufferLen=0)}filterUpgrades(e){const t=[];let s=0;const n=e.length;for(;s{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.transports=void 0;const n=s(484),r=s(308);t.transports={websocket:r.WS,polling:n.Polling}},484:function(e,t,s){"use strict";var n=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.Request=t.Polling=void 0;const r=s(870),o=n(s(802)),i=s(726),a=s(754),c=s(373),u=s(666),h=s(260),l=s(622),d=s(242),p=(0,o.default)("engine.io-client:polling");function f(){}const g=null!=new u.XHR({xdomain:!1}).responseType;class m extends r.Transport{constructor(e){if(super(e),this.polling=!1,"undefined"!=typeof location){const t="https:"===location.protocol;let s=location.port;s||(s=t?"443":"80"),this.xd="undefined"!=typeof location&&e.hostname!==location.hostname||s!==e.port,this.xs=e.secure!==t}const t=e&&e.forceBase64;this.supportsBinary=g&&!t}get name(){return"polling"}doOpen(){this.poll()}pause(e){this.readyState="pausing";const t=()=>{p("paused"),this.readyState="paused",e()};if(this.polling||!this.writable){let e=0;this.polling&&(p("we are currently polling - waiting to pause"),e++,this.once("pollComplete",(function(){p("pre-pause polling complete"),--e||t()}))),this.writable||(p("we are currently writing - waiting to pause"),e++,this.once("drain",(function(){p("pre-pause writing complete"),--e||t()})))}else t()}poll(){p("polling"),this.polling=!0,this.doPoll(),this.emitReserved("poll")}onData(e){p("polling got data %s",e),(0,c.decodePayload)(e,this.socket.binaryType).forEach((e=>{if("opening"===this.readyState&&"open"===e.type&&this.onOpen(),"close"===e.type)return this.onClose({description:"transport closed by the server"}),!1;this.onPacket(e)})),"closed"!==this.readyState&&(this.polling=!1,this.emitReserved("pollComplete"),"open"===this.readyState?this.poll():p('ignoring poll - transport state "%s"',this.readyState))}doClose(){const e=()=>{p("writing close packet"),this.write([{type:"close"}])};"open"===this.readyState?(p("transport open - closing"),e()):(p("transport not open - deferring close"),this.once("open",e))}write(e){this.writable=!1,(0,c.encodePayload)(e,(e=>{this.doWrite(e,(()=>{this.writable=!0,this.emitReserved("drain")}))}))}uri(){let e=this.query||{};const t=this.opts.secure?"https":"http";let s="";!1!==this.opts.timestampRequests&&(e[this.opts.timestampParam]=(0,i.yeast)()),this.supportsBinary||e.sid||(e.b64=1),this.opts.port&&("https"===t&&443!==Number(this.opts.port)||"http"===t&&80!==Number(this.opts.port))&&(s=":"+this.opts.port);const n=(0,a.encode)(e);return t+"://"+(-1!==this.opts.hostname.indexOf(":")?"["+this.opts.hostname+"]":this.opts.hostname)+s+this.opts.path+(n.length?"?"+n:"")}request(e={}){return Object.assign(e,{xd:this.xd,xs:this.xs},this.opts),new y(this.uri(),e)}doWrite(e,t){const s=this.request({method:"POST",data:e});s.on("success",t),s.on("error",((e,t)=>{this.onError("xhr post error",e,t)}))}doPoll(){p("xhr poll");const e=this.request();e.on("data",this.onData.bind(this)),e.on("error",((e,t)=>{this.onError("xhr poll error",e,t)})),this.pollXhr=e}}t.Polling=m;class y extends h.Emitter{constructor(e,t){super(),(0,l.installTimerFunctions)(this,t),this.opts=t,this.method=t.method||"GET",this.uri=e,this.async=!1!==t.async,this.data=void 0!==t.data?t.data:null,this.create()}create(){const e=(0,l.pick)(this.opts,"agent","pfx","key","passphrase","cert","ca","ciphers","rejectUnauthorized","autoUnref");e.xdomain=!!this.opts.xd,e.xscheme=!!this.opts.xs;const t=this.xhr=new u.XHR(e);try{p("xhr open %s: %s",this.method,this.uri),t.open(this.method,this.uri,this.async);try{if(this.opts.extraHeaders){t.setDisableHeaderCheck&&t.setDisableHeaderCheck(!0);for(let e in this.opts.extraHeaders)this.opts.extraHeaders.hasOwnProperty(e)&&t.setRequestHeader(e,this.opts.extraHeaders[e])}}catch(e){}if("POST"===this.method)try{t.setRequestHeader("Content-type","text/plain;charset=UTF-8")}catch(e){}try{t.setRequestHeader("Accept","*/*")}catch(e){}"withCredentials"in t&&(t.withCredentials=this.opts.withCredentials),this.opts.requestTimeout&&(t.timeout=this.opts.requestTimeout),t.onreadystatechange=()=>{4===t.readyState&&(200===t.status||1223===t.status?this.onLoad():this.setTimeoutFn((()=>{this.onError("number"==typeof t.status?t.status:0)}),0))},p("xhr data %s",this.data),t.send(this.data)}catch(e){return void this.setTimeoutFn((()=>{this.onError(e)}),0)}"undefined"!=typeof document&&(this.index=y.requestsCount++,y.requests[this.index]=this)}onError(e){this.emitReserved("error",e,this.xhr),this.cleanup(!0)}cleanup(e){if(void 0!==this.xhr&&null!==this.xhr){if(this.xhr.onreadystatechange=f,e)try{this.xhr.abort()}catch(e){}"undefined"!=typeof document&&delete y.requests[this.index],this.xhr=null}}onLoad(){const e=this.xhr.responseText;null!==e&&(this.emitReserved("data",e),this.emitReserved("success"),this.cleanup())}abort(){this.cleanup()}}if(t.Request=y,y.requestsCount=0,y.requests={},"undefined"!=typeof document)if("function"==typeof attachEvent)attachEvent("onunload",C);else if("function"==typeof addEventListener){const e="onpagehide"in d.globalThisShim?"pagehide":"unload";addEventListener(e,C,!1)}function C(){for(let e in y.requests)y.requests.hasOwnProperty(e)&&y.requests[e].abort()}},552:(e,t,s)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.defaultBinaryType=t.usingBrowserWebSocket=t.WebSocket=t.nextTick=void 0;const n=s(242);t.nextTick="function"==typeof Promise&&"function"==typeof Promise.resolve?e=>Promise.resolve().then(e):(e,t)=>t(e,0),t.WebSocket=n.globalThisShim.WebSocket||n.globalThisShim.MozWebSocket,t.usingBrowserWebSocket=!0,t.defaultBinaryType="arraybuffer"},308:function(e,t,s){"use strict";var n=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.WS=void 0;const r=s(870),o=s(754),i=s(726),a=s(622),c=s(552),u=n(s(802)),h=s(373),l=(0,u.default)("engine.io-client:websocket"),d="undefined"!=typeof navigator&&"string"==typeof navigator.product&&"reactnative"===navigator.product.toLowerCase();class p extends r.Transport{constructor(e){super(e),this.supportsBinary=!e.forceBase64}get name(){return"websocket"}doOpen(){if(!this.check())return;const e=this.uri(),t=this.opts.protocols,s=d?{}:(0,a.pick)(this.opts,"agent","perMessageDeflate","pfx","key","passphrase","cert","ca","ciphers","rejectUnauthorized","localAddress","protocolVersion","origin","maxPayload","family","checkServerIdentity");this.opts.extraHeaders&&(s.headers=this.opts.extraHeaders);try{this.ws=c.usingBrowserWebSocket&&!d?t?new c.WebSocket(e,t):new c.WebSocket(e):new c.WebSocket(e,t,s)}catch(e){return this.emitReserved("error",e)}this.ws.binaryType=this.socket.binaryType||c.defaultBinaryType,this.addEventListeners()}addEventListeners(){this.ws.onopen=()=>{this.opts.autoUnref&&this.ws._socket.unref(),this.onOpen()},this.ws.onclose=e=>this.onClose({description:"websocket connection closed",context:e}),this.ws.onmessage=e=>this.onData(e.data),this.ws.onerror=e=>this.onError("websocket error",e)}write(e){this.writable=!1;for(let t=0;t{const t={};!c.usingBrowserWebSocket&&(s.options&&(t.compress=s.options.compress),this.opts.perMessageDeflate)&&("string"==typeof e?Buffer.byteLength(e):e.length){this.writable=!0,this.emitReserved("drain")}),this.setTimeoutFn)}))}}doClose(){void 0!==this.ws&&(this.ws.close(),this.ws=null)}uri(){let e=this.query||{};const t=this.opts.secure?"wss":"ws";let s="";this.opts.port&&("wss"===t&&443!==Number(this.opts.port)||"ws"===t&&80!==Number(this.opts.port))&&(s=":"+this.opts.port),this.opts.timestampRequests&&(e[this.opts.timestampParam]=(0,i.yeast)()),this.supportsBinary||(e.b64=1);const n=(0,o.encode)(e);return t+"://"+(-1!==this.opts.hostname.indexOf(":")?"["+this.opts.hostname+"]":this.opts.hostname)+s+this.opts.path+(n.length?"?"+n:"")}check(){return!!c.WebSocket}}t.WS=p},666:(e,t,s)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.XHR=void 0;const n=s(419),r=s(242);t.XHR=function(e){const t=e.xdomain;try{if("undefined"!=typeof XMLHttpRequest&&(!t||n.hasCORS))return new XMLHttpRequest}catch(e){}if(!t)try{return new(r.globalThisShim[["Active"].concat("Object").join("X")])("Microsoft.XMLHTTP")}catch(e){}}},622:(e,t,s)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.byteLength=t.installTimerFunctions=t.pick=void 0;const n=s(242);t.pick=function(e,...t){return t.reduce(((t,s)=>(e.hasOwnProperty(s)&&(t[s]=e[s]),t)),{})};const r=n.globalThisShim.setTimeout,o=n.globalThisShim.clearTimeout;t.installTimerFunctions=function(e,t){t.useNativeTimers?(e.setTimeoutFn=r.bind(n.globalThisShim),e.clearTimeoutFn=o.bind(n.globalThisShim)):(e.setTimeoutFn=n.globalThisShim.setTimeout.bind(n.globalThisShim),e.clearTimeoutFn=n.globalThisShim.clearTimeout.bind(n.globalThisShim))},t.byteLength=function(e){return"string"==typeof e?function(e){let t=0,s=0;for(let n=0,r=e.length;n=57344?s+=3:(n++,s+=4);return s}(e):Math.ceil(1.33*(e.byteLength||e.size))}},87:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.ERROR_PACKET=t.PACKET_TYPES_REVERSE=t.PACKET_TYPES=void 0;const s=Object.create(null);t.PACKET_TYPES=s,s.open="0",s.close="1",s.ping="2",s.pong="3",s.message="4",s.upgrade="5",s.noop="6";const n=Object.create(null);t.PACKET_TYPES_REVERSE=n,Object.keys(s).forEach((e=>{n[s[e]]=e})),t.ERROR_PACKET={type:"error",data:"parser error"}},469:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.decode=t.encode=void 0;const s="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",n="undefined"==typeof Uint8Array?[]:new Uint8Array(256);for(let e=0;e<64;e++)n[s.charCodeAt(e)]=e;t.encode=e=>{let t,n=new Uint8Array(e),r=n.length,o="";for(t=0;t>2],o+=s[(3&n[t])<<4|n[t+1]>>4],o+=s[(15&n[t+1])<<2|n[t+2]>>6],o+=s[63&n[t+2]];return r%3==2?o=o.substring(0,o.length-1)+"=":r%3==1&&(o=o.substring(0,o.length-2)+"=="),o},t.decode=e=>{let t,s,r,o,i,a=.75*e.length,c=e.length,u=0;"="===e[e.length-1]&&(a--,"="===e[e.length-2]&&a--);const h=new ArrayBuffer(a),l=new Uint8Array(h);for(t=0;t>4,l[u++]=(15&r)<<4|o>>2,l[u++]=(3&o)<<6|63&i;return h}},572:(e,t,s)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const n=s(87),r=s(469),o="function"==typeof ArrayBuffer,i=(e,t)=>{if(o){const s=(0,r.decode)(e);return a(s,t)}return{base64:!0,data:e}},a=(e,t)=>"blob"===t&&e instanceof ArrayBuffer?new Blob([e]):e;t.default=(e,t)=>{if("string"!=typeof e)return{type:"message",data:a(e,t)};const s=e.charAt(0);return"b"===s?{type:"message",data:i(e.substring(1),t)}:n.PACKET_TYPES_REVERSE[s]?e.length>1?{type:n.PACKET_TYPES_REVERSE[s],data:e.substring(1)}:{type:n.PACKET_TYPES_REVERSE[s]}:n.ERROR_PACKET}},908:(e,t,s)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const n=s(87),r="function"==typeof Blob||"undefined"!=typeof Blob&&"[object BlobConstructor]"===Object.prototype.toString.call(Blob),o="function"==typeof ArrayBuffer,i=(e,t)=>{const s=new FileReader;return s.onload=function(){const e=s.result.split(",")[1];t("b"+(e||""))},s.readAsDataURL(e)};t.default=({type:e,data:t},s,a)=>{return r&&t instanceof Blob?s?a(t):i(t,a):o&&(t instanceof ArrayBuffer||(c=t,"function"==typeof ArrayBuffer.isView?ArrayBuffer.isView(c):c&&c.buffer instanceof ArrayBuffer))?s?a(t):i(new Blob([t]),a):a(n.PACKET_TYPES[e]+(t||""));var c}},373:(e,t,s)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.decodePayload=t.decodePacket=t.encodePayload=t.encodePacket=t.protocol=void 0;const n=s(908);t.encodePacket=n.default;const r=s(572);t.decodePacket=r.default;const o=String.fromCharCode(30);t.encodePayload=(e,t)=>{const s=e.length,r=new Array(s);let i=0;e.forEach(((e,a)=>{(0,n.default)(e,!1,(e=>{r[a]=e,++i===s&&t(r.join(o))}))}))},t.decodePayload=(e,t)=>{const s=e.split(o),n=[];for(let e=0;e{"use strict";function s(e){e=e||{},this.ms=e.min||100,this.max=e.max||1e4,this.factor=e.factor||2,this.jitter=e.jitter>0&&e.jitter<=1?e.jitter:0,this.attempts=0}Object.defineProperty(t,"__esModule",{value:!0}),t.Backoff=void 0,t.Backoff=s,s.prototype.duration=function(){var e=this.ms*Math.pow(this.factor,this.attempts++);if(this.jitter){var t=Math.random(),s=Math.floor(t*this.jitter*e);e=0==(1&Math.floor(10*t))?e-s:e+s}return 0|Math.min(e,this.max)},s.prototype.reset=function(){this.attempts=0},s.prototype.setMin=function(e){this.ms=e},s.prototype.setMax=function(e){this.max=e},s.prototype.setJitter=function(e){this.jitter=e}},46:function(e,t,s){"use strict";var n=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.default=t.connect=t.io=t.Socket=t.Manager=t.protocol=void 0;const r=s(84),o=s(168);Object.defineProperty(t,"Manager",{enumerable:!0,get:function(){return o.Manager}});const i=s(312);Object.defineProperty(t,"Socket",{enumerable:!0,get:function(){return i.Socket}});const a=n(s(669)).default("socket.io-client"),c={};function u(e,t){"object"==typeof e&&(t=e,e=void 0),t=t||{};const s=r.url(e,t.path||"/socket.io"),n=s.source,i=s.id,u=s.path,h=c[i]&&u in c[i].nsps;let l;return t.forceNew||t["force new connection"]||!1===t.multiplex||h?(a("ignoring socket cache for %s",n),l=new o.Manager(n,t)):(c[i]||(a("new io instance for %s",n),c[i]=new o.Manager(n,t)),l=c[i]),s.query&&!t.query&&(t.query=s.queryKey),l.socket(s.path,t)}t.io=u,t.connect=u,t.default=u,Object.assign(u,{Manager:o.Manager,Socket:i.Socket,io:u,connect:u});var h=s(514);Object.defineProperty(t,"protocol",{enumerable:!0,get:function(){return h.protocol}}),e.exports=u},168:function(e,t,s){"use strict";var n=this&&this.__createBinding||(Object.create?function(e,t,s,n){void 0===n&&(n=s),Object.defineProperty(e,n,{enumerable:!0,get:function(){return t[s]}})}:function(e,t,s,n){void 0===n&&(n=s),e[n]=t[s]}),r=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),o=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var s in e)"default"!==s&&Object.prototype.hasOwnProperty.call(e,s)&&n(t,e,s);return r(t,e),t},i=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.Manager=void 0;const a=s(679),c=s(312),u=o(s(514)),h=s(149),l=s(159),d=s(260),p=i(s(669)).default("socket.io-client:manager");class f extends d.Emitter{constructor(e,t){var s;super(),this.nsps={},this.subs=[],e&&"object"==typeof e&&(t=e,e=void 0),(t=t||{}).path=t.path||"/socket.io",this.opts=t,a.installTimerFunctions(this,t),this.reconnection(!1!==t.reconnection),this.reconnectionAttempts(t.reconnectionAttempts||1/0),this.reconnectionDelay(t.reconnectionDelay||1e3),this.reconnectionDelayMax(t.reconnectionDelayMax||5e3),this.randomizationFactor(null!==(s=t.randomizationFactor)&&void 0!==s?s:.5),this.backoff=new l.Backoff({min:this.reconnectionDelay(),max:this.reconnectionDelayMax(),jitter:this.randomizationFactor()}),this.timeout(null==t.timeout?2e4:t.timeout),this._readyState="closed",this.uri=e;const n=t.parser||u;this.encoder=new n.Encoder,this.decoder=new n.Decoder,this._autoConnect=!1!==t.autoConnect,this._autoConnect&&this.open()}reconnection(e){return arguments.length?(this._reconnection=!!e,this):this._reconnection}reconnectionAttempts(e){return void 0===e?this._reconnectionAttempts:(this._reconnectionAttempts=e,this)}reconnectionDelay(e){var t;return void 0===e?this._reconnectionDelay:(this._reconnectionDelay=e,null===(t=this.backoff)||void 0===t||t.setMin(e),this)}randomizationFactor(e){var t;return void 0===e?this._randomizationFactor:(this._randomizationFactor=e,null===(t=this.backoff)||void 0===t||t.setJitter(e),this)}reconnectionDelayMax(e){var t;return void 0===e?this._reconnectionDelayMax:(this._reconnectionDelayMax=e,null===(t=this.backoff)||void 0===t||t.setMax(e),this)}timeout(e){return arguments.length?(this._timeout=e,this):this._timeout}maybeReconnectOnOpen(){!this._reconnecting&&this._reconnection&&0===this.backoff.attempts&&this.reconnect()}open(e){if(p("readyState %s",this._readyState),~this._readyState.indexOf("open"))return this;p("opening %s",this.uri),this.engine=new a.Socket(this.uri,this.opts);const t=this.engine,s=this;this._readyState="opening",this.skipReconnect=!1;const n=h.on(t,"open",(function(){s.onopen(),e&&e()})),r=h.on(t,"error",(t=>{p("error"),s.cleanup(),s._readyState="closed",this.emitReserved("error",t),e?e(t):s.maybeReconnectOnOpen()}));if(!1!==this._timeout){const e=this._timeout;p("connect attempt will timeout after %d",e),0===e&&n();const s=this.setTimeoutFn((()=>{p("connect attempt timed out after %d",e),n(),t.close(),t.emit("error",new Error("timeout"))}),e);this.opts.autoUnref&&s.unref(),this.subs.push((function(){clearTimeout(s)}))}return this.subs.push(n),this.subs.push(r),this}connect(e){return this.open(e)}onopen(){p("open"),this.cleanup(),this._readyState="open",this.emitReserved("open");const e=this.engine;this.subs.push(h.on(e,"ping",this.onping.bind(this)),h.on(e,"data",this.ondata.bind(this)),h.on(e,"error",this.onerror.bind(this)),h.on(e,"close",this.onclose.bind(this)),h.on(this.decoder,"decoded",this.ondecoded.bind(this)))}onping(){this.emitReserved("ping")}ondata(e){try{this.decoder.add(e)}catch(e){this.onclose("parse error",e)}}ondecoded(e){a.nextTick((()=>{this.emitReserved("packet",e)}),this.setTimeoutFn)}onerror(e){p("error",e),this.emitReserved("error",e)}socket(e,t){let s=this.nsps[e];return s?this._autoConnect&&!s.active&&s.connect():(s=new c.Socket(this,e,t),this.nsps[e]=s),s}_destroy(e){const t=Object.keys(this.nsps);for(const e of t)if(this.nsps[e].active)return void p("socket %s is still active, skipping close",e);this._close()}_packet(e){p("writing packet %j",e);const t=this.encoder.encode(e);for(let s=0;se())),this.subs.length=0,this.decoder.destroy()}_close(){p("disconnect"),this.skipReconnect=!0,this._reconnecting=!1,this.onclose("forced close"),this.engine&&this.engine.close()}disconnect(){return this._close()}onclose(e,t){p("closed due to %s",e),this.cleanup(),this.backoff.reset(),this._readyState="closed",this.emitReserved("close",e,t),this._reconnection&&!this.skipReconnect&&this.reconnect()}reconnect(){if(this._reconnecting||this.skipReconnect)return this;const e=this;if(this.backoff.attempts>=this._reconnectionAttempts)p("reconnect failed"),this.backoff.reset(),this.emitReserved("reconnect_failed"),this._reconnecting=!1;else{const t=this.backoff.duration();p("will wait %dms before reconnect attempt",t),this._reconnecting=!0;const s=this.setTimeoutFn((()=>{e.skipReconnect||(p("attempting reconnect"),this.emitReserved("reconnect_attempt",e.backoff.attempts),e.skipReconnect||e.open((t=>{t?(p("reconnect attempt error"),e._reconnecting=!1,e.reconnect(),this.emitReserved("reconnect_error",t)):(p("reconnect success"),e.onreconnect())})))}),t);this.opts.autoUnref&&s.unref(),this.subs.push((function(){clearTimeout(s)}))}}onreconnect(){const e=this.backoff.attempts;this._reconnecting=!1,this.backoff.reset(),this.emitReserved("reconnect",e)}}t.Manager=f},149:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.on=void 0,t.on=function(e,t,s){return e.on(t,s),function(){e.off(t,s)}}},312:function(e,t,s){"use strict";var n=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.Socket=void 0;const r=s(514),o=s(149),i=s(260),a=n(s(669)).default("socket.io-client:socket"),c=Object.freeze({connect:1,connect_error:1,disconnect:1,disconnecting:1,newListener:1,removeListener:1});class u extends i.Emitter{constructor(e,t,s){super(),this.connected=!1,this.recovered=!1,this.receiveBuffer=[],this.sendBuffer=[],this._queue=[],this._queueSeq=0,this.ids=0,this.acks={},this.flags={},this.io=e,this.nsp=t,s&&s.auth&&(this.auth=s.auth),this._opts=Object.assign({},s),this.io._autoConnect&&this.open()}get disconnected(){return!this.connected}subEvents(){if(this.subs)return;const e=this.io;this.subs=[o.on(e,"open",this.onopen.bind(this)),o.on(e,"packet",this.onpacket.bind(this)),o.on(e,"error",this.onerror.bind(this)),o.on(e,"close",this.onclose.bind(this))]}get active(){return!!this.subs}connect(){return this.connected||(this.subEvents(),this.io._reconnecting||this.io.open(),"open"===this.io._readyState&&this.onopen()),this}open(){return this.connect()}send(...e){return e.unshift("message"),this.emit.apply(this,e),this}emit(e,...t){if(c.hasOwnProperty(e))throw new Error('"'+e.toString()+'" is a reserved event name');if(t.unshift(e),this._opts.retries&&!this.flags.fromQueue&&!this.flags.volatile)return this._addToQueue(t),this;const s={type:r.PacketType.EVENT,data:t,options:{}};if(s.options.compress=!1!==this.flags.compress,"function"==typeof t[t.length-1]){const e=this.ids++;a("emitting packet with ack id %d",e);const n=t.pop();this._registerAckCallback(e,n),s.id=e}const n=this.io.engine&&this.io.engine.transport&&this.io.engine.transport.writable;return!this.flags.volatile||n&&this.connected?this.connected?(this.notifyOutgoingListeners(s),this.packet(s)):this.sendBuffer.push(s):a("discard packet as the transport is not currently writable"),this.flags={},this}_registerAckCallback(e,t){var s;const n=null!==(s=this.flags.timeout)&&void 0!==s?s:this._opts.ackTimeout;if(void 0===n)return void(this.acks[e]=t);const r=this.io.setTimeoutFn((()=>{delete this.acks[e];for(let t=0;t{this.io.clearTimeoutFn(r),t.apply(this,[null,...e])}}emitWithAck(e,...t){const s=void 0!==this.flags.timeout||void 0!==this._opts.ackTimeout;return new Promise(((n,r)=>{t.push(((e,t)=>s?e?r(e):n(t):n(e))),this.emit(e,...t)}))}_addToQueue(e){let t;"function"==typeof e[e.length-1]&&(t=e.pop());const s={id:this._queueSeq++,tryCount:0,pending:!1,args:e,flags:Object.assign({fromQueue:!0},this.flags)};e.push(((e,...n)=>{if(s===this._queue[0])return null!==e?s.tryCount>this._opts.retries&&(a("packet [%d] is discarded after %d tries",s.id,s.tryCount),this._queue.shift(),t&&t(e)):(a("packet [%d] was successfully sent",s.id),this._queue.shift(),t&&t(null,...n)),s.pending=!1,this._drainQueue()})),this._queue.push(s),this._drainQueue()}_drainQueue(e=!1){if(a("draining queue"),!this.connected||0===this._queue.length)return;const t=this._queue[0];!t.pending||e?(t.pending=!0,t.tryCount++,a("sending packet [%d] (try n°%d)",t.id,t.tryCount),this.flags=t.flags,this.emit.apply(this,t.args)):a("packet [%d] has already been sent and is waiting for an ack",t.id)}packet(e){e.nsp=this.nsp,this.io._packet(e)}onopen(){a("transport is open - connecting"),"function"==typeof this.auth?this.auth((e=>{this._sendConnectPacket(e)})):this._sendConnectPacket(this.auth)}_sendConnectPacket(e){this.packet({type:r.PacketType.CONNECT,data:this._pid?Object.assign({pid:this._pid,offset:this._lastOffset},e):e})}onerror(e){this.connected||this.emitReserved("connect_error",e)}onclose(e,t){a("close (%s)",e),this.connected=!1,delete this.id,this.emitReserved("disconnect",e,t)}onpacket(e){if(e.nsp===this.nsp)switch(e.type){case r.PacketType.CONNECT:e.data&&e.data.sid?this.onconnect(e.data.sid,e.data.pid):this.emitReserved("connect_error",new Error("It seems you are trying to reach a Socket.IO server in v2.x with a v3.x client, but they are not compatible (more information here: https://socket.io/docs/v3/migrating-from-2-x-to-3-0/)"));break;case r.PacketType.EVENT:case r.PacketType.BINARY_EVENT:this.onevent(e);break;case r.PacketType.ACK:case r.PacketType.BINARY_ACK:this.onack(e);break;case r.PacketType.DISCONNECT:this.ondisconnect();break;case r.PacketType.CONNECT_ERROR:this.destroy();const t=new Error(e.data.message);t.data=e.data.data,this.emitReserved("connect_error",t)}}onevent(e){const t=e.data||[];a("emitting event %j",t),null!=e.id&&(a("attaching ack callback to event"),t.push(this.ack(e.id))),this.connected?this.emitEvent(t):this.receiveBuffer.push(Object.freeze(t))}emitEvent(e){if(this._anyListeners&&this._anyListeners.length){const t=this._anyListeners.slice();for(const s of t)s.apply(this,e)}super.emit.apply(this,e),this._pid&&e.length&&"string"==typeof e[e.length-1]&&(this._lastOffset=e[e.length-1])}ack(e){const t=this;let s=!1;return function(...n){s||(s=!0,a("sending ack %j",n),t.packet({type:r.PacketType.ACK,id:e,data:n}))}}onack(e){const t=this.acks[e.id];"function"==typeof t?(a("calling ack %s with %j",e.id,e.data),t.apply(this,e.data),delete this.acks[e.id]):a("bad ack %s",e.id)}onconnect(e,t){a("socket connected with id %s",e),this.id=e,this.recovered=t&&this._pid===t,this._pid=t,this.connected=!0,this.emitBuffered(),this.emitReserved("connect"),this._drainQueue(!0)}emitBuffered(){this.receiveBuffer.forEach((e=>this.emitEvent(e))),this.receiveBuffer=[],this.sendBuffer.forEach((e=>{this.notifyOutgoingListeners(e),this.packet(e)})),this.sendBuffer=[]}ondisconnect(){a("server disconnect (%s)",this.nsp),this.destroy(),this.onclose("io server disconnect")}destroy(){this.subs&&(this.subs.forEach((e=>e())),this.subs=void 0),this.io._destroy(this)}disconnect(){return this.connected&&(a("performing disconnect (%s)",this.nsp),this.packet({type:r.PacketType.DISCONNECT})),this.destroy(),this.connected&&this.onclose("io client disconnect"),this}close(){return this.disconnect()}compress(e){return this.flags.compress=e,this}get volatile(){return this.flags.volatile=!0,this}timeout(e){return this.flags.timeout=e,this}onAny(e){return this._anyListeners=this._anyListeners||[],this._anyListeners.push(e),this}prependAny(e){return this._anyListeners=this._anyListeners||[],this._anyListeners.unshift(e),this}offAny(e){if(!this._anyListeners)return this;if(e){const t=this._anyListeners;for(let s=0;s{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.reconstructPacket=t.deconstructPacket=void 0;const n=s(665);function r(e,t){if(!e)return e;if((0,n.isBinary)(e)){const s={_placeholder:!0,num:t.length};return t.push(e),s}if(Array.isArray(e)){const s=new Array(e.length);for(let n=0;n=0&&e.num{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.Decoder=t.Encoder=t.PacketType=t.protocol=void 0;const n=s(260),r=s(880),o=s(665),i=(0,s(618).default)("socket.io-parser");var a;t.protocol=5,function(e){e[e.CONNECT=0]="CONNECT",e[e.DISCONNECT=1]="DISCONNECT",e[e.EVENT=2]="EVENT",e[e.ACK=3]="ACK",e[e.CONNECT_ERROR=4]="CONNECT_ERROR",e[e.BINARY_EVENT=5]="BINARY_EVENT",e[e.BINARY_ACK=6]="BINARY_ACK"}(a=t.PacketType||(t.PacketType={})),t.Encoder=class{constructor(e){this.replacer=e}encode(e){return i("encoding packet %j",e),e.type!==a.EVENT&&e.type!==a.ACK||!(0,o.hasBinary)(e)?[this.encodeAsString(e)]:this.encodeAsBinary({type:e.type===a.EVENT?a.BINARY_EVENT:a.BINARY_ACK,nsp:e.nsp,data:e.data,id:e.id})}encodeAsString(e){let t=""+e.type;return e.type!==a.BINARY_EVENT&&e.type!==a.BINARY_ACK||(t+=e.attachments+"-"),e.nsp&&"/"!==e.nsp&&(t+=e.nsp+","),null!=e.id&&(t+=e.id),null!=e.data&&(t+=JSON.stringify(e.data,this.replacer)),i("encoded %j as %s",e,t),t}encodeAsBinary(e){const t=(0,r.deconstructPacket)(e),s=this.encodeAsString(t.packet),n=t.buffers;return n.unshift(s),n}};class c extends n.Emitter{constructor(e){super(),this.reviver=e}add(e){let t;if("string"==typeof e){if(this.reconstructor)throw new Error("got plaintext data when reconstructing a packet");t=this.decodeString(e);const s=t.type===a.BINARY_EVENT;s||t.type===a.BINARY_ACK?(t.type=s?a.EVENT:a.ACK,this.reconstructor=new u(t),0===t.attachments&&super.emitReserved("decoded",t)):super.emitReserved("decoded",t)}else{if(!(0,o.isBinary)(e)&&!e.base64)throw new Error("Unknown type: "+e);if(!this.reconstructor)throw new Error("got binary data when not reconstructing a packet");t=this.reconstructor.takeBinaryData(e),t&&(this.reconstructor=null,super.emitReserved("decoded",t))}}decodeString(e){let t=0;const s={type:Number(e.charAt(0))};if(void 0===a[s.type])throw new Error("unknown packet type "+s.type);if(s.type===a.BINARY_EVENT||s.type===a.BINARY_ACK){const n=t+1;for(;"-"!==e.charAt(++t)&&t!=e.length;);const r=e.substring(n,t);if(r!=Number(r)||"-"!==e.charAt(t))throw new Error("Illegal attachments");s.attachments=Number(r)}if("/"===e.charAt(t+1)){const n=t+1;for(;++t&&","!==e.charAt(t)&&t!==e.length;);s.nsp=e.substring(n,t)}else s.nsp="/";const n=e.charAt(t+1);if(""!==n&&Number(n)==n){const n=t+1;for(;++t;){const s=e.charAt(t);if(null==s||Number(s)!=s){--t;break}if(t===e.length)break}s.id=Number(e.substring(n,t+1))}if(e.charAt(++t)){const n=this.tryParse(e.substr(t));if(!c.isPayloadValid(s.type,n))throw new Error("invalid payload");s.data=n}return i("decoded %s as %j",e,s),s}tryParse(e){try{return JSON.parse(e,this.reviver)}catch(e){return!1}}static isPayloadValid(e,t){switch(e){case a.CONNECT:return"object"==typeof t;case a.DISCONNECT:return void 0===t;case a.CONNECT_ERROR:return"string"==typeof t||"object"==typeof t;case a.EVENT:case a.BINARY_EVENT:return Array.isArray(t)&&("string"==typeof t[0]||"number"==typeof t[0]);case a.ACK:case a.BINARY_ACK:return Array.isArray(t)}}destroy(){this.reconstructor&&(this.reconstructor.finishedReconstruction(),this.reconstructor=null)}}t.Decoder=c;class u{constructor(e){this.packet=e,this.buffers=[],this.reconPack=e}takeBinaryData(e){if(this.buffers.push(e),this.buffers.length===this.reconPack.attachments){const e=(0,r.reconstructPacket)(this.reconPack,this.buffers);return this.finishedReconstruction(),e}return null}finishedReconstruction(){this.reconPack=null,this.buffers=[]}}},665:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.hasBinary=t.isBinary=void 0;const s="function"==typeof ArrayBuffer,n=Object.prototype.toString,r="function"==typeof Blob||"undefined"!=typeof Blob&&"[object BlobConstructor]"===n.call(Blob),o="function"==typeof File||"undefined"!=typeof File&&"[object FileConstructor]"===n.call(File);function i(e){return s&&(e instanceof ArrayBuffer||(e=>"function"==typeof ArrayBuffer.isView?ArrayBuffer.isView(e):e.buffer instanceof ArrayBuffer)(e))||r&&e instanceof Blob||o&&e instanceof File}t.isBinary=i,t.hasBinary=function e(t,s){if(!t||"object"!=typeof t)return!1;if(Array.isArray(t)){for(let s=0,n=t.length;s{"use strict";function n(e){if(e)return function(e){for(var t in n.prototype)e[t]=n.prototype[t];return e}(e)}s.r(t),s.d(t,{Emitter:()=>n}),n.prototype.on=n.prototype.addEventListener=function(e,t){return this._callbacks=this._callbacks||{},(this._callbacks["$"+e]=this._callbacks["$"+e]||[]).push(t),this},n.prototype.once=function(e,t){function s(){this.off(e,s),t.apply(this,arguments)}return s.fn=t,this.on(e,s),this},n.prototype.off=n.prototype.removeListener=n.prototype.removeAllListeners=n.prototype.removeEventListener=function(e,t){if(this._callbacks=this._callbacks||{},0==arguments.length)return this._callbacks={},this;var s,n=this._callbacks["$"+e];if(!n)return this;if(1==arguments.length)return delete this._callbacks["$"+e],this;for(var r=0;r{for(var n in t)s.o(t,n)&&!s.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},s.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),s.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},s(628)})(); \ No newline at end of file diff --git a/public/assets/css/game.css b/public/assets/css/game.css index 898b78a..f910410 100644 --- a/public/assets/css/game.css +++ b/public/assets/css/game.css @@ -785,3 +785,7 @@ footer { overflow: auto; } } + +.dungeon-room-description { + padding: 1rem; +} diff --git a/seeds/dungeons.ts b/seeds/dungeons.ts new file mode 100644 index 0000000..eb210d1 --- /dev/null +++ b/seeds/dungeons.ts @@ -0,0 +1,176 @@ +import { config as dotenv } from 'dotenv'; +import { db } from "../src/server/lib/db"; +import { join } from 'path'; +import { Dungeon, DungeonRoom, RoomExit } from '../src/shared/dungeon'; +import * as fs from 'fs'; +import * as marked from 'marked'; +import { map, max } from 'lodash'; + +dotenv(); + +type TwisonLink = { + name: string; + ink: string; + pid: string; +} + +type TwisonPassage = { + text: string; + name: string; + pid: string; + props?: { + fight?: { + monster_id: string; + one_time: string; + }, + visit?: Record, + end?: string, + rewards?: { + base: { + exp?: string, + gold?: string + }, + per_kill_bonus?: { + exp?: string, + gold?: string + } + } + }, + position: { + x: string; + y: string; + }; + links:TwisonLink[] +} + +type TwisonDungeon = { + passages: TwisonPassage[]; + name: string; + startnode: string; + creator: string; + 'creator-version': string; + ifid: string; +} + +async function getFileData(filename: string): Promise { + const data = fs.readFileSync(join(__dirname, '..', 'data', 'dungeons', filename), 'utf8'); + + console.log(data); + + return JSON.parse(data) as TwisonDungeon; +} + +export async function main(filename: string, done?: any) { + await db('dungeon_players').delete(); + + const raw = await getFileData(filename); + + // attempt to create base dungeon! + const dungeon: Dungeon = (await db('dungeons').insert({ + id: raw.ifid.toLowerCase(), + name: raw.name + }).onConflict('id').merge().returning('*')).pop(); + + // clear the rooms for the created dungeon + await db('dungeon_rooms').where({dungeon_id: dungeon.id}).delete(); + + // create the rooms first! + const roomsToCreate = raw.passages.map(passage => { + let data: Omit = { + dungeon_id: dungeon.id, + description: marked.parse(passage.text.split('')[0]), + exits: [], + settings: {} + }; + + if(passage.props) { + if(passage.props.fight) { + data.settings.fight = { + monster_id: parseInt(passage.props.fight.monster_id), + one_time: passage.props.fight.one_time.toLowerCase() === 'true' + } + } + if(passage.props.visit) { + data.settings.visit = map(passage.props.visit, (str: string, index: string) => { + return { + visit_number: parseInt(index), + description: marked.parse(str) + } + }); + } + if(passage.props.end) { + data.settings.end = true; + } + if(passage.props.rewards) { + data.settings.rewards = { + base: { + exp: max([parseInt(passage.props.rewards.base.exp || '0'), 0]), + gold: max([parseInt(passage.props.rewards.base.gold || '0'), 0]), + } + }; + + if(passage.props.rewards.per_kill_bonus) { + data.settings.rewards.per_kill_bonus = { + exp: max([parseInt(passage.props.rewards.per_kill_bonus.exp || '0'), 0]), + gold: max([parseInt(passage.props.rewards.per_kill_bonus.gold || '0'), 0]), + } + } + } + } + + return data; + }); + + const createdRooms: DungeonRoom[] = await db('dungeon_rooms').insert(roomsToCreate,).returning('*'); + + console.log(createdRooms); + + // set the starting room! + await db('dungeons').update({starting_room: createdRooms[pidToIndex(raw.startnode)].id.toLowerCase()}).where({ + id: dungeon.id + }); + + const exits = raw.passages.map(passage => { + const roomIndex = pidToIndex(passage.pid); + let exits: RoomExit[] = []; + + // ending nodes don't have any links + if(passage.links) { + exits = passage.links.map(link => { + const pid = pidToIndex(link.pid); + return { + name: link.name, + target_room_id: createdRooms[pid].id + } + }); + } + + return { + id: createdRooms[roomIndex].id, + exits + }; + }); + + let i = 0; + exits.map(async exit => { + await db('dungeon_rooms').update({exits: JSON.stringify(exit.exits)}).where({ + id: exit.id + }); + console.log(`${++i}/${raw.passages.length} rooms created`); + + if(i === raw.passages.length) { + done(); + } + }); + + console.log(JSON.stringify(exits, null, 2)); +} + +function pidToIndex(str: string): number { + return parseInt(str)-1; +} + +main('storage_cellar.json', () => { + console.log('Dungeons created!'); + process.exit(0); +}); diff --git a/src/client/htmx.ts b/src/client/htmx.ts index 2dc5608..5b14ef3 100644 --- a/src/client/htmx.ts +++ b/src/client/htmx.ts @@ -119,6 +119,12 @@ $('body').addEventListener('click', e => { if(target.getAttribute('formmethod') === 'dialog' && target.getAttribute('value') === 'cancel') { target.closest('dialog')?.close(); + // sometimes dialog buttons have a "nav direct" + const attr = target.getAttribute('nav-trigger'); + if(attr) { + const [selector, click_num] = attr.split('|'); + $$(selector)[click_num].click(); + } } }); diff --git a/src/server/admin.ts b/src/server/admin.ts new file mode 100644 index 0000000..e45466d --- /dev/null +++ b/src/server/admin.ts @@ -0,0 +1,6 @@ +import { Permission } from "../shared/player"; +import { db } from './lib/db'; + +export async function givePlayerPermission(player_id: string, permission: Permission) { + return db('player_permissions').insert({player_id, permission}); +} diff --git a/src/server/api.ts b/src/server/api.ts index 4a6a9dd..da7f014 100644 --- a/src/server/api.ts +++ b/src/server/api.ts @@ -12,7 +12,7 @@ import { Server, Socket } from 'socket.io'; import * as CONSTANT from '../shared/constants'; import { logger } from './lib/logger'; import { loadPlayer, createPlayer, updatePlayer, movePlayer } from './player'; -import { random, sample } from 'lodash'; +import { random, round, sample } from 'lodash'; import {broadcastMessage, Message} from '../shared/message'; import {maxHp, maxVigor, Player} from '../shared/player'; import {createFight, getMonsterList, getMonsterLocation, getRandomMonster, loadMonster, loadMonsterFromFight, loadMonsterWithFaction} from './monster'; @@ -32,6 +32,7 @@ import { fightRound, blockPlayerInFight } from './fight'; import { router as healerRouter } from './locations/healer'; import { router as professionRouter } from './locations/recruiter'; import { router as repairRouter } from './locations/repair'; +import { router as dungeonRouter } from './locations/dungeon'; import * as Alert from './views/alert'; import { renderPlayerBar } from './views/player-bar' @@ -49,6 +50,8 @@ import { renderChatMessage } from './views/chat'; import { Item, PlayerItem, ShopItem } from 'shared/items'; import { equip, unequip } from './equipment'; import { HealthPotionSmall } from '../shared/items/health_potion'; +import { completeDungeonFight, getRoomVists, loadRoom, updatePlayerDungeonState } from './dungeon'; +import { renderDungeon, renderDungeonRoom } from './views/dungeons/room'; dotenv(); @@ -146,6 +149,7 @@ io.on('connection', async socket => { app.use(healerRouter); app.use(professionRouter); app.use(repairRouter); +app.use(dungeonRouter); app.get('/chat/history', authEndpoint, async (req: Request, res: Response) => { @@ -591,6 +595,22 @@ app.post('/fight/turn', authEndpoint, async (req: Request, res: Response) => { }); + + if(fightData.roundData.winner !== 'in-progress') { + delete cache[fightBlockKey]; + } + + if(monster.fight_trigger === 'dungeon-forced' && fightData.roundData.winner === 'player') { + // ok the player was in a dungeon, lets make sure + // that they complete whatever dungeon room they are in + const dungeonState = await completeDungeonFight(req.player.id, monster); + const room = await loadRoom(dungeonState.current_room_id); + const visits = await getRoomVists(req.player.id, room.dungeon_id); + + res.send(renderDungeonRoom(room, visits)); + return; + } + let html = renderFight( monster, renderRoundDetails(fightData.roundData), @@ -598,10 +618,6 @@ app.post('/fight/turn', authEndpoint, async (req: Request, res: Response) => { cache[fightBlockKey] ); - if(fightData.roundData.winner !== 'in-progress') { - delete cache[fightBlockKey]; - } - if(fightData.monsters.length && monster.fight_trigger === 'explore') { html += renderMonsterSelector(fightData.monsters, monster.ref_id); } diff --git a/src/server/chat-commands/give-permission.ts b/src/server/chat-commands/give-permission.ts new file mode 100644 index 0000000..2586f8c --- /dev/null +++ b/src/server/chat-commands/give-permission.ts @@ -0,0 +1,31 @@ +import { Socket } from 'socket.io'; +import { Permission, PermissionGuard, Player } from '../../shared/player'; +import { ChatCommand } from './base'; +import { givePlayerPermission } from '../admin'; +import { findPlayerByUsername } from '../player'; +import { broadcastMessage, Message } from '../../shared/message'; +import { renderChatMessage } from './../views/chat'; + +async function handler(rawCommand: string, sender: Socket, player: Player) { + if(player.permissions.includes('admin')) { + const pieces = rawCommand.split(' '); + const username = pieces[1]; + const permission = pieces[2]; + + if(PermissionGuard(permission)) { + const recipient = await findPlayerByUsername(username); + if(player) { + await givePlayerPermission(recipient.id, permission as Permission); + + const message: Message = broadcastMessage('server', `Granted ${permission} to ${recipient.username}`); + sender.emit('chat', renderChatMessage(message)); + } + else { + const message: Message = broadcastMessage('server', `Cant find user [${username}]`); + sender.emit('chat', renderChatMessage(message)); + } + } + } +} + +export const setPermission = new ChatCommand('give-permission', new RegExp(/^give-permission (.*)+/), handler); diff --git a/src/server/chat-commands/index.ts b/src/server/chat-commands/index.ts index 59e2d79..2ea13bc 100644 --- a/src/server/chat-commands/index.ts +++ b/src/server/chat-commands/index.ts @@ -4,6 +4,7 @@ import { refreshCities } from './refresh-cities'; import { refreshShops } from './refresh-shops'; import { setLevel } from './set-level'; import { say } from './say'; +import { setPermission } from './give-permission'; export const Commands = new Set(); @@ -12,3 +13,4 @@ Commands.add(refreshCities); Commands.add(refreshShops); Commands.add(setLevel); Commands.add(say); +Commands.add(setPermission); diff --git a/src/server/dungeon.ts b/src/server/dungeon.ts new file mode 100644 index 0000000..b2d3830 --- /dev/null +++ b/src/server/dungeon.ts @@ -0,0 +1,151 @@ +import { Fight } from "shared/monsters"; +import { Dungeon, DungeonRoom, DungeonThing, DungeonPlayer, DungeonState, DungeonStateSummaryVists, DungeonStateSummaryFights } from "../shared/dungeon"; +import { db } from './lib/db'; + +export async function getActiveDungeon(player_id: string): Promise { + return db.select('*').from('dungeon_players').where({ + player_id + }).first(); +} + +export async function loadDungeon(dungeon_id: string): Promise { + return db.select('*').from('dungeons').where({id: dungeon_id}).first(); +} + +export async function getDungeonState(player_id: string, dungeon_id: string) { + return db.select('*').from('dungeon_state').where({ + player_id, + dungeon_id + }); +} + +export async function addNewState(player_id: string, dungeon_id: string, event_name: string, props: Record = {}) { + return db('dungeon_state').insert({ + player_id, + dungeon_id, + event_name, + event_props: props + }); +} +// in a dungeon fight, we dont care what room the player is in, +// but we care what room they are going to +export async function completeDungeonFight(player_id: string, monster: Fight): Promise { + const activeDungeon = await getActiveDungeon(player_id); + + await addNewState(player_id, activeDungeon.dungeon_id, 'FIGHT_COMPLETE', { + monster_id: monster.ref_id, + target_room: activeDungeon.target_room_id, + source_room: activeDungeon.current_room_id + }); + + await movePlayerToRoomInDungeon(player_id, activeDungeon.dungeon_id, activeDungeon.target_room_id); + + return getActiveDungeon(player_id); +} + +export async function movePlayerToRoomInDungeon(player_id: string, dungeon_id: string, room_id: string) { + await updatePlayerDungeonState(player_id, dungeon_id, { + current_room_id: room_id, + target_room_id: room_id + }); + + await addNewState(player_id, dungeon_id, 'ROOM_VISIT', { + room_id + }); +} + +export async function getRoomVists(player_id: string, dungeon_id: string): Promise { + + const res = await db.raw(`select +count(id) as visits, +event_props->>'room_id' as room_id +from dungeon_state +group by event_props->>'room_id',player_id,dungeon_id,event_name +having player_id = ? and dungeon_id = ? and event_name = ? +`, [ + player_id, + dungeon_id, + 'ROOM_VISIT' + ]); + + let data = {}; + res.rows.forEach((row: {room_id: string, visits: string}) => { + data[row.room_id] = parseInt(row.visits); + }); + + return data; +} + +export async function getUniqueFights(player_id: string, dungeon_id: string): Promise { + // fights always happen between movement source->target. So when we record + // the state of a fight, we record the source/target room props. + // however, when we are checking if a fight occurrent in a room.. we really + // only care about the TARGET room. + const res = await db.raw(`select +count(id) as fights, +event_props->>'monster_id' as monster_id, +event_props->>'target_room' as room_id +from dungeon_state +group by event_props->>'monster_id',event_props->>'target_room', +player_id,dungeon_id,event_name +having player_id = ? and dungeon_id = ? and event_name = ? +`, [ + player_id, + dungeon_id, + 'FIGHT_COMPLETE' + ]); + + let data = {}; + res.rows.forEach((row: {fights: string, monster_id: string, room_id: string}) => { + if(!data[row.room_id]) { + data[row.room_id] = {}; + } + + if(!data[row.room_id][row.monster_id]) { + data[row.room_id][row.monster_id] = row.fights; + } + }); + + return data; +} + +export async function putPlayerInDungeon(player_id: string, dungeon_id: string): Promise { + const res: {rows: DungeonPlayer[] }= await db.raw(` + insert into dungeon_players +(player_id, dungeon_id, current_room_id) +values +(?, ?, (select starting_room from dungeons where id = ?)) returning *`, [player_id, dungeon_id, dungeon_id]) + + const data = res.rows.pop(); + + await addNewState(player_id, dungeon_id, 'ROOM_VISIT', { + room_id: data.current_room_id + }); + + return data; +} + + +export async function updatePlayerDungeonState(player_id: string, dungeon_id: string, fields: any) { + const res = await db('dungeon_players').where({ + player_id, + dungeon_id + }).update(fields).returning('*'); + + return res.pop(); +} + +export async function loadRoom(room_id: string): Promise { + return db.select('*') + .from('dungeon_rooms') + .where({id: room_id}) + .first(); +} + +export async function completeDungeon(player_id: string) { + const dungeonPlayer = await getActiveDungeon(player_id); + //const state = await getDungeonState(player_id, dungeonPlayer.dungeon_id); + + await db('dungeon_players').where({player_id}).delete(); + await db('dungeon_state').where({player_id, dungeon_id: dungeonPlayer.dungeon_id}).delete(); +} diff --git a/src/server/locations/dungeon.ts b/src/server/locations/dungeon.ts new file mode 100644 index 0000000..5b2cc59 --- /dev/null +++ b/src/server/locations/dungeon.ts @@ -0,0 +1,181 @@ +import { Router } from "express"; +import { authEndpoint } from '../auth'; +import { logger } from "../lib/logger"; +import { getDungeon, getService } from '../map'; +import { completeDungeon, getActiveDungeon, getRoomVists, getUniqueFights, loadDungeon, loadRoom, movePlayerToRoomInDungeon, putPlayerInDungeon, updatePlayerDungeonState } from '../dungeon'; +import { Dungeon, DungeonRewards } from "../../shared/dungeon"; +import { dungeonRewards, renderDungeon } from '../views/dungeons/room'; +import * as Alert from '../views/alert'; +import { createFight, loadMonster } from "../monster"; +import { renderFightPreRoundDungeon } from "../views/fight"; +import { has, max, each } from 'lodash'; +import { expToLevel, maxHp, maxVigor } from "../../shared/player"; +import { updatePlayer } from "../player"; + +export const router = Router(); + +router.get('/city/dungeon/:dungeon_id/:location_id', authEndpoint, async (req, res) => { + let dungeon: Dungeon; + let activeDungeon = await getActiveDungeon(req.player.id); + // because of how we treat locations + dungeons, the "event_name" of a location + // is actually the uuid of the dungeon. How fancy + // in this case service.event_name === dungeon.id + const service = await getService(parseInt(req.params.location_id)); + + if(service.type !== 'DUNGEON') { + logger.log(`Attempting to enter a non-dungeon`); + res.sendStatus(400); + return; + } + + if(service.city_id !== req.player.city_id) { + logger.log(`Player is not in the same place as the dungeon: [${req.params.location_id}]`); + res.sendStatus(400); + return; + } + + if(!activeDungeon) { + // for a dungeon the "event_name" serves as a mapping between the + // airtable integer id that is used for the location and the ifid (interactive fiction id) + // that is generated by twine + activeDungeon = await putPlayerInDungeon(req.player.id, service.event_name); + } + + const room = await loadRoom(activeDungeon.current_room_id); + const visits = await getRoomVists(req.player.id, service.event_name); + + res.send(renderDungeon(service.city_name, service.name, room, visits)); +}); + +router.post('/city/dungeon/step/:target_room_id', authEndpoint, async (req, res) => { + const activeDungeon = await getActiveDungeon(req.player.id); + if(!activeDungeon) { + logger.log(`Not in a dungeon`); + res.sendStatus(400); + return; + } + + const dungeon = await loadDungeon(activeDungeon.dungeon_id); + const service = await getDungeon(dungeon.id); + + const targetRoomId = req.params.target_room_id.toLowerCase().trim(); + const currentRoom = await loadRoom(activeDungeon.current_room_id); + + const targetExit = currentRoom.exits.filter(exit => { + return exit.target_room_id === targetRoomId + }); + + if(!targetExit.length) { + logger.log(`Invalid exit: [${targetRoomId}]`); + res.sendStatus(400); + return; + } + + const nextRoom = await loadRoom(targetRoomId); + + if(!nextRoom) { + logger.error(`Dang.. no valid room`, targetRoomId, currentRoom); + res.send(Alert.ErrorAlert(`${req.params.direction} is not a valid direction`)).status(400); + return; + } + + + // if the room contiains a fight and + // its a one time fight the user hasn't finished + // OR + // its not a one time fight + // render the fight screen + if(nextRoom.settings.fight) { + const fights = await getUniqueFights(req.player.id, nextRoom.dungeon_id); + if( + ( + nextRoom.settings.fight.one_time && + ( + has(fights, [nextRoom.id, nextRoom.settings.fight.monster_id]) + ? + fights[nextRoom.id][nextRoom.settings.fight.monster_id] + : + 0 + ) === 0 + ) + || + !nextRoom.settings.fight.one_time + ){ + const monster = await loadMonster(nextRoom.settings.fight.monster_id); + const fight = await createFight(req.player.id, monster, 'dungeon-forced'); + + // ensure that we know what room the player was attempting to go + // to + await updatePlayerDungeonState(req.player.id, currentRoom.dungeon_id, { + current_room_id: currentRoom.id, + target_room_id: nextRoom.id + }); + + // ok render the fight view instead! + res.send(renderFightPreRoundDungeon(service.city_name, service.name, fight)); + return; + } + } + + await movePlayerToRoomInDungeon(req.player.id, nextRoom.dungeon_id, nextRoom.id); + const visits = await getRoomVists(req.player.id, service.event_name); + + res.send(renderDungeon(service.city_name, service.name, nextRoom, visits)); +}); + +router.post('/city/dungeon/:dungeon_id/complete', authEndpoint, async (req, res) => { + const activeDungeon = await getActiveDungeon(req.player.id); + if(!activeDungeon) { + logger.log(`Not in a dungeon`); + res.sendStatus(400); + return; + } + + const dungeon = await loadDungeon(activeDungeon.dungeon_id); + const currentRoom = await loadRoom(activeDungeon.current_room_id); + + if(!currentRoom.settings.end) { + logger.log(`Not the end of the dungeon: [${currentRoom.id}]`); + res.sendStatus(400); + return; + } + + const stats = await getUniqueFights(req.player.id, dungeon.id); + + const rewards: DungeonRewards = { + exp: max([currentRoom.settings.rewards?.base.exp, 0]), + gold: max([currentRoom.settings.rewards?.base.gold, 0]) + }; + + if(currentRoom.settings.rewards.per_kill_bonus) { + each(stats, (room) => { + each(room, (count) => { + if(currentRoom.settings.rewards.per_kill_bonus.exp) { + rewards.exp += (count * currentRoom.settings.rewards.per_kill_bonus.exp) + } + if(currentRoom.settings.rewards.per_kill_bonus.gold) { + rewards.gold += (count * currentRoom.settings.rewards.per_kill_bonus.gold) + } + }); + }); + } + + // delete the tracking for this dungeon-run + await completeDungeon(req.player.id); + + // give the user these rewards! + req.player.gold += rewards.gold; + req.player.exp += rewards.exp; + + while(req.player.exp >= expToLevel(req.player.level + 1)) { + req.player.exp -= expToLevel(req.player.level + 1); + req.player.level++; + req.player.stat_points += req.player.profession === 'Wanderer' ? 1 : 2; + req.player.hp = maxHp(req.player.constitution, req.player.level); + req.player.vigor = maxVigor(req.player.constitution, req.player.level); + } + + await updatePlayer(req.player); + + res.send(dungeonRewards(dungeon, rewards)); +}); diff --git a/src/server/map.ts b/src/server/map.ts index df70a38..8a49db3 100644 --- a/src/server/map.ts +++ b/src/server/map.ts @@ -19,6 +19,13 @@ export async function getService(location_id: number): Promise }).first(); } +export async function getDungeon(dungeon_id: string): Promise { + return db.select(['locations.*', 'cities.name as city_name']). + from('locations').join('cities', 'locations.city_id', '=', 'cities.id').where({ + 'locations.event_name': dungeon_id + }).first(); +} + export async function getAllPaths(city_id: number): Promise { const res = await db.raw(` select @@ -82,7 +89,6 @@ export async function clearTravelPlan(player_id: string): Promise { export async function completeTravel(player_id: string): Promise { const rows = await db('travel').where({player_id}).delete().returning('*'); if(rows.length !== 1) { - console.log(rows); throw new Error('Unexpected response when moving'); } diff --git a/src/server/views/dungeons/room.ts b/src/server/views/dungeons/room.ts new file mode 100644 index 0000000..5f371e8 --- /dev/null +++ b/src/server/views/dungeons/room.ts @@ -0,0 +1,71 @@ +import { DUNGEON_TRAVEL_BLOCK } from '../../../shared/constants'; +import { Dungeon, DungeonPlayer, DungeonRewards, DungeonRoom, DungeonState, DungeonStateSummaryVists } from '../../../shared/dungeon'; +import { Button, ButtonWithBlock } from '../components/button'; +import { Details, Title } from '../components/city'; + +function renderMovementButtons(room: DungeonRoom): string { + let html: string[] = []; + const now = Date.now(); + + room.exits.forEach(exit => { + html.push(ButtonWithBlock({ + id: `target-${exit.target_room_id}`, + 'hx-post': `/city/dungeon/step/${exit.target_room_id}`, + 'hx-target': '#explore' + }, exit.name, now + DUNGEON_TRAVEL_BLOCK)); + }); + + if(room.settings?.end) { + html.push(Button({ + id: `target-exit`, + 'hx-post': `/city/dungeon/${room.dungeon_id}/complete`, + 'hx-target': '#modal-wrapper' + }, 'Complete Dungeon')); + } + + return html.join("\n"); +} + +export function renderDungeonRoom(room: DungeonRoom, state: DungeonStateSummaryVists): string { + // first visit for this room? + let desc = room.description; + room.settings?.visit?.forEach(visit => { + // This ssets the stage for repeat events. + // eventually the description might change if you visit + // a spot MROE than 5 or 6 times + if(state[room.id]) { + if(visit?.visit_number === state[room.id]) { + desc = visit.description; + } + } + }); + + let html = `
${desc}
${renderMovementButtons(room)}
`; + + return html; + +} + +export function renderDungeon(city: string, dungeon_name: string, room: DungeonRoom, state: DungeonStateSummaryVists): string { + return` + ${Title(city)} + ${Details(dungeon_name, renderDungeonRoom(room, state))} +`; +} + +export function dungeonRewards(dungeon: Dungeon, rewards: DungeonRewards): string { + return ` + +
+

Congratulations on completing the ${dungeon.name} Dungeon!

+ Rewards:
+ Exp: ${rewards.exp.toLocaleString()}
+ Gold: ${rewards.gold.toLocaleString()}
+
+
+ +
+
+ `; + +} diff --git a/src/server/views/fight.ts b/src/server/views/fight.ts index c66cdbd..46d962b 100644 --- a/src/server/views/fight.ts +++ b/src/server/views/fight.ts @@ -1,7 +1,9 @@ +import { Dungeon } from "shared/dungeon"; import { FightRound } from "shared/fight"; import { LocationWithCity } from "shared/map"; import { Fight, MonsterForFight } from "../../shared/monsters"; import { Button, ButtonWithBlock } from "./components/button"; +import { Details, Title } from "./components/city"; export function renderRoundDetails(roundData: FightRound): string { let html: string[] = roundData.roundDetails.map(d => `
${d}
`); @@ -91,14 +93,21 @@ export function renderFight(monster: Fight, results: string = '', displayFightAc `: ''} - +
${results}
-`; +`; return html; } +export function renderFightPreRoundDungeon(city: string, dungeon_name: string, monster: Fight): string { + return` + ${Title(city)} + ${Details(dungeon_name, renderFight(monster, '', true))} +`; +} + export function renderFightPreRound(monster: Fight, displayFightActions: boolean = true, location: LocationWithCity, closestTown: number) { let html = `
diff --git a/src/server/views/map.ts b/src/server/views/map.ts index e432ebd..0b6f2c2 100644 --- a/src/server/views/map.ts +++ b/src/server/views/map.ts @@ -9,7 +9,8 @@ export async function renderMap(data: { city: City, locations: Location[], paths const servicesParsed: Record = { 'SERVICES': [], 'STORES': [], - 'EXPLORE': [] + 'EXPLORE': [], + 'DUNGEON': [] }; data.locations.forEach(l => { @@ -28,7 +29,10 @@ export async function renderMap(data: { city: City, locations: Location[], paths html += `

Stores

${servicesParsed.STORES.join("
")}
` } if(servicesParsed.EXPLORE.length) { - html += `

Explore

${servicesParsed.EXPLORE.join("
")}
` + html += ` +

Explore

${servicesParsed.EXPLORE.join("
")} + ${servicesParsed.DUNGEON.length ? `
${servicesParsed.DUNGEON.join("
")}` : ''} +
`; } html += `
diff --git a/src/shared/constants.ts b/src/shared/constants.ts index 4cda940..42e884c 100644 --- a/src/shared/constants.ts +++ b/src/shared/constants.ts @@ -3,3 +3,5 @@ export const STEP_DELAY = 2000; export const ALERT_DISPLAY_LENGTH = 3000; // this is displayed as a percentage out of 100 export const CHANCE_TO_FIGHT_SPECIAL = 10; + +export const DUNGEON_TRAVEL_BLOCK = 3000; diff --git a/src/shared/dungeon.ts b/src/shared/dungeon.ts new file mode 100644 index 0000000..e345523 --- /dev/null +++ b/src/shared/dungeon.ts @@ -0,0 +1,73 @@ +export type Dungeon = { + id: string; + name: string; + starting_room: string; +} + +export type RoomExit = { + name: string; + target_room_id: string +} + +export type RoomSettings = { + visit?: { + visit_number: number; + description?: string; + }[], + fight?: { + monster_id: number; + one_time: boolean; + }, + end?: boolean; + rewards?: { + base: { + exp?: number, + gold?: number + }, + per_kill_bonus?: { + exp?: number, + gold?: number + } + } +} + +export type DungeonRoom = { + id: string; + dungeon_id: string; + description: string; + exits: RoomExit[]; + settings: RoomSettings; +} + +export type DungeonThing = { + room_id: string; + id: number; + name: string; + type: string; + properties: Record; +} + +export type DungeonState = { + player_id: string; + dungeon_id: string; + event_name: 'ROOM_VISIT' | 'FIGHT_COMPLETE'; + event_props: any; + create_date: number; +} + +export type DungeonPlayer = { + player_id: string; + dungeon_id: string; + current_room_id: string; + target_room_id: string; +} + +// +export type DungeonStateSummaryVists = Record; +// > +export type DungeonStateSummaryFights = Record>; + +export type DungeonRewards = { + exp: number; + gold: number; +} diff --git a/src/shared/map.ts b/src/shared/map.ts index c05ae8f..b4267db 100644 --- a/src/shared/map.ts +++ b/src/shared/map.ts @@ -5,7 +5,7 @@ export type City = { name: string; } -export type LocationType = 'SERVICES' | 'STORES' | 'EXPLORE'; +export type LocationType = 'SERVICES' | 'STORES' | 'EXPLORE' | 'DUNGEON'; export type Location = { id: number; diff --git a/src/shared/monsters.ts b/src/shared/monsters.ts index c7f8700..854eee5 100644 --- a/src/shared/monsters.ts +++ b/src/shared/monsters.ts @@ -25,7 +25,7 @@ export type MonsterForList = { level: number; } -export type FightTrigger = 'explore' | 'travel'; +export type FightTrigger = 'explore' | 'travel' | 'dungeon-forced'; export type Fight = Omit & { id: string; diff --git a/src/shared/player.ts b/src/shared/player.ts index 564ed1d..339396d 100644 --- a/src/shared/player.ts +++ b/src/shared/player.ts @@ -3,7 +3,13 @@ import { Stat } from './stats'; import { SkillDefinition, Skill } from './skills'; import { EquippedItemDetails } from './equipped'; -export type Permission = 'admin' | 'moderator' | 'tester'; +export function PermissionGuard(request: any): boolean { + const perms: Permission[] = ['admin', 'moderator', 'tester', 'archaeologist']; + + return perms.includes(request); +} + +export type Permission = 'admin' | 'moderator' | 'tester' | 'archaeologist'; export type Player = { id: string, -- 2.25.1 From dfa62a7e55a7d2cc991b4d9bce179ab5f8afe836 Mon Sep 17 00:00:00 2001 From: xangelo Date: Thu, 14 Sep 2023 12:11:49 -0400 Subject: [PATCH 3/7] fix: auto-enter dungeon if you are in it --- src/server/api.ts | 75 ++++++++++++--------- src/server/dungeon.ts | 14 +++- src/server/views/components/explore-pane.ts | 5 ++ src/server/views/dungeons/room.ts | 1 - 4 files changed, 61 insertions(+), 34 deletions(-) create mode 100644 src/server/views/components/explore-pane.ts diff --git a/src/server/api.ts b/src/server/api.ts index da7f014..b1ff042 100644 --- a/src/server/api.ts +++ b/src/server/api.ts @@ -21,7 +21,7 @@ import { getItemFromPlayer, getItemFromShop, getPlayersItems, getShopItems, give import {FightTrigger, Monster, MonsterForFight} from '../shared/monsters'; import {getShopEquipment, listShopItems } from './shopEquipment'; import {EquipmentSlot} from '../shared/inventory'; -import { clearTravelPlan, completeTravel, getAllPaths, getAllServices, getCityDetails, getService, getTravelPlan, stepForward, travel } from './map'; +import { clearTravelPlan, completeTravel, getAllPaths, getAllServices, getCityDetails, getService, getTravelPlan, stepForward, travel, getDungeon } from './map'; import { signup, login, authEndpoint } from './auth'; import {db} from './lib/db'; import { getPlayerSkills} from './skills'; @@ -35,6 +35,7 @@ import { router as repairRouter } from './locations/repair'; import { router as dungeonRouter } from './locations/dungeon'; import * as Alert from './views/alert'; +import { ExplorePane } from './views/components/explore-pane'; import { renderPlayerBar } from './views/player-bar' import { renderEquipmentDetails, renderStore } from './views/stores'; import { renderMap } from './views/map'; @@ -50,7 +51,7 @@ import { renderChatMessage } from './views/chat'; import { Item, PlayerItem, ShopItem } from 'shared/items'; import { equip, unequip } from './equipment'; import { HealthPotionSmall } from '../shared/items/health_potion'; -import { completeDungeonFight, getRoomVists, loadRoom, updatePlayerDungeonState } from './dungeon'; +import { completeDungeonFight, getActiveDungeon, getRoomVists, loadRoom, blockPlayerInDungeon } from './dungeon'; import { renderDungeon, renderDungeonRoom } from './views/dungeons/room'; dotenv(); @@ -237,7 +238,7 @@ app.get('/player/inventory', authEndpoint, async (req: Request, res: Response) = res.send(renderInventoryPage(inventory, items)); }); -app.post('/player/equip/:item_id/:slot', authEndpoint, blockPlayerInFight, async (req: Request, res: Response) => { +app.post('/player/equip/:item_id/:slot', authEndpoint, blockPlayerInFight, blockPlayerInDungeon, async (req: Request, res: Response) => { const inventoryItem = await getInventoryItem(req.player.id, req.params.item_id); const equippedItems = await getEquippedItems(req.player.id); const requestedSlot = req.params.slot; @@ -289,7 +290,7 @@ app.post('/player/equip/:item_id/:slot', authEndpoint, blockPlayerInFight, async res.send(renderInventoryPage(inventory, items, inventoryItem.type) + renderPlayerBar(req.player)); }); -app.post('/player/unequip/:item_id', authEndpoint, blockPlayerInFight, async (req: Request, res: Response) => { +app.post('/player/unequip/:item_id', authEndpoint, blockPlayerInFight, blockPlayerInDungeon, async (req: Request, res: Response) => { const [item, ] = await Promise.all([ getInventoryItem(req.player.id, req.params.item_id), unequip(req.player.id, req.params.item_id) @@ -316,40 +317,50 @@ app.get('/player/explore', authEndpoint, async (req: Request, res: Response) => const location = await getMonsterLocation(fight.ref_id); res.send(renderPlayerBar(req.player) + renderFightPreRound(fight, true, location, closestTown)); + return; } - else { - if(travelPlan) { - // traveling! - const chanceToSeeMonster = random(0, 100); - const things: any[] = []; - if(chanceToSeeMonster <= 30) { - const monster = await getRandomMonster([closestTown]); - things.push(monster); - } - // STEP_DELAY - const nextAction = cache[`step:${req.player.id}`] || 0; + const dungeon = await getActiveDungeon(req.player.id); + if(dungeon) { + const service = await getDungeon(dungeon.dungeon_id); + const room = await loadRoom(dungeon.current_room_id); + const visits = await getRoomVists(req.player.id, service.event_name); - res.send(renderPlayerBar(req.player) + renderTravel({ - things, - nextAction, - closestTown: closestTown, - walkingText: '', - travelPlan - })); - } - else { - // display the city info! - const [city, locations, paths] = await Promise.all([ - getCityDetails(req.player.city_id), - getAllServices(req.player.city_id), - getAllPaths(req.player.city_id) - ]); - - res.send(renderPlayerBar(req.player) + await renderMap({city, locations, paths}, closestTown)); + res.send(ExplorePane(service.city_id, renderDungeon(service.city_name, service.name, room, visits))); + return; + } + + // are you in a dungeon? + if(travelPlan) { + // traveling! + const chanceToSeeMonster = random(0, 100); + const things: any[] = []; + if(chanceToSeeMonster <= 30) { + const monster = await getRandomMonster([closestTown]); + things.push(monster); } + // STEP_DELAY + const nextAction = cache[`step:${req.player.id}`] || 0; + + res.send(renderPlayerBar(req.player) + renderTravel({ + things, + nextAction, + closestTown: closestTown, + walkingText: '', + travelPlan + })); + return; } + + // display the default explore view + const [city, locations, paths] = await Promise.all([ + getCityDetails(req.player.city_id), + getAllServices(req.player.city_id), + getAllPaths(req.player.city_id) + ]); + + res.send(renderPlayerBar(req.player) + await renderMap({city, locations, paths}, closestTown)); }); // used to purchase equipment from a particular shop diff --git a/src/server/dungeon.ts b/src/server/dungeon.ts index b2d3830..a58168d 100644 --- a/src/server/dungeon.ts +++ b/src/server/dungeon.ts @@ -1,6 +1,18 @@ import { Fight } from "shared/monsters"; -import { Dungeon, DungeonRoom, DungeonThing, DungeonPlayer, DungeonState, DungeonStateSummaryVists, DungeonStateSummaryFights } from "../shared/dungeon"; +import { Dungeon, DungeonRoom, DungeonPlayer, DungeonState, DungeonStateSummaryVists, DungeonStateSummaryFights } from "../shared/dungeon"; import { db } from './lib/db'; +import { Request, Response } from 'express'; +import { ErrorAlert } from "./views/alert"; + +export async function blockPlayerInDungeon(req: Request, res: Response, next: any) { + const state = await getActiveDungeon(req.player.id); + if(!state) { + next(); + } + else { + res.send(ErrorAlert('You are currently exploring a dungeon')); + } +} export async function getActiveDungeon(player_id: string): Promise { return db.select('*').from('dungeon_players').where({ diff --git a/src/server/views/components/explore-pane.ts b/src/server/views/components/explore-pane.ts new file mode 100644 index 0000000..581677b --- /dev/null +++ b/src/server/views/components/explore-pane.ts @@ -0,0 +1,5 @@ +export function ExplorePane(townId: number, contents: string): string { + return `
+ ${contents} +
`; +} diff --git a/src/server/views/dungeons/room.ts b/src/server/views/dungeons/room.ts index 5f371e8..cde75e5 100644 --- a/src/server/views/dungeons/room.ts +++ b/src/server/views/dungeons/room.ts @@ -67,5 +67,4 @@ export function dungeonRewards(dungeon: Dungeon, rewards: DungeonRewards): strin
`; - } -- 2.25.1 From 43f0bc31d6a0c2974891840f7ba10efa77c2e520 Mon Sep 17 00:00:00 2001 From: xangelo Date: Thu, 28 Sep 2023 15:04:20 -0400 Subject: [PATCH 4/7] feat: psql based event system you can now track arbitrary events that get flushed to postgres so that you can track things. To start we're tracking dungeon completions so that we can give users 20% rewards after 5 daily completions. --- migrations/20230915162829_events.ts | 20 +++++++ package.json | 2 +- src/server/api.ts | 35 ++++++++++--- src/server/dungeon.ts | 17 +++++- src/server/events.ts | 81 +++++++++++++++++++++++++++++ src/server/fight.ts | 5 ++ src/server/lib/clickhouse.ts | 3 ++ src/server/locations/dungeon.ts | 15 ++++-- src/server/views/dungeons/room.ts | 3 +- src/shared/constants.ts | 3 ++ src/shared/event.ts | 23 ++++++++ 11 files changed, 192 insertions(+), 15 deletions(-) create mode 100644 migrations/20230915162829_events.ts create mode 100644 src/server/events.ts create mode 100644 src/server/lib/clickhouse.ts create mode 100644 src/shared/event.ts diff --git a/migrations/20230915162829_events.ts b/migrations/20230915162829_events.ts new file mode 100644 index 0000000..b34437b --- /dev/null +++ b/migrations/20230915162829_events.ts @@ -0,0 +1,20 @@ +import { Knex } from "knex"; + + +export async function up(knex: Knex): Promise { + return knex.schema.createTable('events', function(table) { + table.uuid('id').primary().defaultTo(knex.raw('uuid_generate_v4()')); + table.string('event_name'); + table.uuid('player_id'); + table.integer('value').defaultTo(1); + table.string('app_version'); + table.json('props').defaultTo('{}'); + table.timestamp('created').defaultTo(knex.raw('NOW()')) + }); +} + + +export async function down(knex: Knex): Promise { + return knex.schema.dropTable('events'); +} + diff --git a/package.json b/package.json index 7487106..6b395b4 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "seed": "npx ts-node ./node_modules/knex/bin/cli.js seed:run", "seed:prod": "NODE_ENV=production npm run seed", "dev:client": "npx webpack -w", - "dev:server": "npx nodemon src/server/api.ts", + "dev": "npx nodemon src/server/api.ts", "prepare": "husky install", "release": "npx standard-version && npm run copy-changelog", "copy-changelog": "cp ./CHANGELOG.md ~/repos/xangelo.ca/static/", diff --git a/src/server/api.ts b/src/server/api.ts index b1ff042..9d1b641 100644 --- a/src/server/api.ts +++ b/src/server/api.ts @@ -12,13 +12,13 @@ import { Server, Socket } from 'socket.io'; import * as CONSTANT from '../shared/constants'; import { logger } from './lib/logger'; import { loadPlayer, createPlayer, updatePlayer, movePlayer } from './player'; -import { random, round, sample } from 'lodash'; +import { random, sample } from 'lodash'; import {broadcastMessage, Message} from '../shared/message'; import {maxHp, maxVigor, Player} from '../shared/player'; import {createFight, getMonsterList, getMonsterLocation, getRandomMonster, loadMonster, loadMonsterFromFight, loadMonsterWithFaction} from './monster'; import {addInventoryItem, getEquippedItems, getInventory, getInventoryItem} from './inventory'; import { getItemFromPlayer, getItemFromShop, getPlayersItems, getShopItems, givePlayerItem, updateItemCount } from './items'; -import {FightTrigger, Monster, MonsterForFight} from '../shared/monsters'; +import {FightTrigger, Monster} from '../shared/monsters'; import {getShopEquipment, listShopItems } from './shopEquipment'; import {EquipmentSlot} from '../shared/inventory'; import { clearTravelPlan, completeTravel, getAllPaths, getAllServices, getCityDetails, getService, getTravelPlan, stepForward, travel, getDungeon } from './map'; @@ -53,11 +53,13 @@ import { equip, unequip } from './equipment'; import { HealthPotionSmall } from '../shared/items/health_potion'; import { completeDungeonFight, getActiveDungeon, getRoomVists, loadRoom, blockPlayerInDungeon } from './dungeon'; import { renderDungeon, renderDungeonRoom } from './views/dungeons/room'; +import { flushBuffer, addEvent } from './events'; dotenv(); otel.s(); +flushBuffer(); const app = express(); const server = http.createServer(app); @@ -144,6 +146,8 @@ io.on('connection', async socket => { // this is a special event to let the client know it can start // requesting data socket.emit('ready'); + + addEvent('LOGIN', player.id); }); @@ -313,7 +317,7 @@ app.get('/player/explore', authEndpoint, async (req: Request, res: Response) => closestTown = (travelPlan.current_position / travelPlan.total_distance) > 0.5 ? travelPlan.destination_id : travelPlan.source_id; } - if(fight) { + if(fight && req.player.hp > 0) { const location = await getMonsterLocation(fight.ref_id); res.send(renderPlayerBar(req.player) + renderFightPreRound(fight, true, location, closestTown)); @@ -321,7 +325,7 @@ app.get('/player/explore', authEndpoint, async (req: Request, res: Response) => } const dungeon = await getActiveDungeon(req.player.id); - if(dungeon) { + if(dungeon && req.player.hp > 0) { const service = await getDungeon(dungeon.dungeon_id); const room = await loadRoom(dungeon.current_room_id); const visits = await getRoomVists(req.player.id, service.event_name); @@ -330,8 +334,7 @@ app.get('/player/explore', authEndpoint, async (req: Request, res: Response) => return; } - // are you in a dungeon? - if(travelPlan) { + if(travelPlan && req.player.hp > 0) { // traveling! const chanceToSeeMonster = random(0, 100); const things: any[] = []; @@ -606,11 +609,29 @@ app.post('/fight/turn', authEndpoint, async (req: Request, res: Response) => { }); - if(fightData.roundData.winner !== 'in-progress') { delete cache[fightBlockKey]; } + if(fightData.roundData.winner === 'player') { + //@TODO: Add equipped weapons + addEvent('MONSTER_KILLED', req.player.id, { + monster_id: monster.ref_id, + monster_name: monster.name, + monster_level: monster.level, + fight_trigger: monster.fight_trigger, + }); + } + else if(fightData.roundData.winner === 'monster') { + addEvent('PLAYER_KILLED', req.player.id, { + monster_id: monster.ref_id, + monster_name: monster.name, + monster_level: monster.level, + fight_trigger: monster.fight_trigger + }); + } + + if(monster.fight_trigger === 'dungeon-forced' && fightData.roundData.winner === 'player') { // ok the player was in a dungeon, lets make sure // that they complete whatever dungeon room they are in diff --git a/src/server/dungeon.ts b/src/server/dungeon.ts index a58168d..33558a9 100644 --- a/src/server/dungeon.ts +++ b/src/server/dungeon.ts @@ -3,6 +3,7 @@ import { Dungeon, DungeonRoom, DungeonPlayer, DungeonState, DungeonStateSummaryV import { db } from './lib/db'; import { Request, Response } from 'express'; import { ErrorAlert } from "./views/alert"; +import { addEvent } from './events'; export async function blockPlayerInDungeon(req: Request, res: Response, next: any) { const state = await getActiveDungeon(req.player.id); @@ -156,8 +157,22 @@ export async function loadRoom(room_id: string): Promise { + startTime = Math.min(startTime, s.create_date); + endTime = Math.max(endTime, s.create_date); + }); + + addEvent('DUNGEON_COMPLETE', player_id, { + dungeon_id: dungeonPlayer.dungeon_id, + start: startTime, + end: endTime, + duration: endTime - startTime + }); } diff --git a/src/server/events.ts b/src/server/events.ts new file mode 100644 index 0000000..f5a630d --- /dev/null +++ b/src/server/events.ts @@ -0,0 +1,81 @@ +import { db } from './lib/db'; +import { version } from '../../package.json'; +import { CreatedEvent, Event, EventName } from '../shared/event'; +import { isEqual } from 'lodash'; +import { logger } from './lib/logger'; +import { EVENT_FLUSH_INTERVAL, EVENT_SECOND_BUCKET } from '../shared/constants'; +import { clickhouse } from './lib/clickhouse'; + +const eventBuffer: CreatedEvent[] = []; +const maxToAdd = 10; + +export async function flushBuffer() { + const events = eventBuffer.splice(0, maxToAdd); + if(events.length) { + await addEvents(events); + logger.log(`Flushed ${events.length} events`); + } + else { + logger.log('No events to flush'); + } + setTimeout(flushBuffer, EVENT_FLUSH_INTERVAL); +} + +function bucketTime(date: Date): Date { + const d = new Date(); + d.setFullYear(date.getFullYear()); + d.setMonth(date.getMonth()); + d.setDate(date.getDate()); + d.setHours(date.getHours()); + d.setMinutes(date.getMinutes()); + d.setMilliseconds(0); + + const s = date.getSeconds(); + + // round down to closest 5 second interval + d.setSeconds(s - (s%EVENT_SECOND_BUCKET)); + return d; +} + + +export async function addEvent(event_name: EventName, player_id: string, props?: any, created?: Date) { + eventBuffer.push({ + event_name, + player_id, + app_version: version, + props: props, + created + }); +} + +export async function addEvents(events: CreatedEvent[]) { + return db('events').insert(events); +} + +export async function getEventHistory(player_id: string, event_name: EventName) { + return db.select('*').from>('events').where({ + player_id, + event_name + }); +} + +export async function getEventHistoryToday(player_id: string, event_name: EventName) { + return db.select('*').from>('events').where({ + player_id, + event_name + }).andWhere('created', '>=', db.raw('current_date::timestamp')); + +} + +export async function hasNeverHappenedBefeore(name: EventName, player_id: string, props?: any): Promise { + return hasHappendXTimes(0, name, player_id, props); +} + +export async function hasHappendXTimes(times: number, event_name: EventName, player_id: string, props?: any): Promise { + const res: Event[] = await getEventHistory(player_id, event_name); + + if(props) { + return res.filter(row => isEqual(row.props, props)).length === times; + } + return res.length === times; +} diff --git a/src/server/fight.ts b/src/server/fight.ts index 011cc5c..83a8132 100644 --- a/src/server/fight.ts +++ b/src/server/fight.ts @@ -11,6 +11,7 @@ import { getPlayerSkillsAsObject, updatePlayerSkills } from './skills'; import { SkillID, Skills } from '../shared/skills'; import { Request, Response } from 'express'; import * as Alert from './views/alert'; +import { addEvent } from './events'; export async function blockPlayerInFight(req: Request, res: Response, next: any) { const fight = await loadMonsterFromFight(req.player.id); @@ -173,6 +174,10 @@ export async function fightRound(player: Player, monster: Fight, data: {action: if(player.exp >= expToLevel(player.level + 1)) { player.exp -= expToLevel(player.level + 1) player.level++; + addEvent('LEVEL_UP', player.id, { + from_level: player.level-1, + to_level: player.level + }); roundData.rewards.levelIncrease = true; let statPointsGained = 1; diff --git a/src/server/lib/clickhouse.ts b/src/server/lib/clickhouse.ts new file mode 100644 index 0000000..831f46d --- /dev/null +++ b/src/server/lib/clickhouse.ts @@ -0,0 +1,3 @@ +import { createClient } from '@clickhouse/client'; + +export const clickhouse = createClient(); diff --git a/src/server/locations/dungeon.ts b/src/server/locations/dungeon.ts index 5b2cc59..26f7460 100644 --- a/src/server/locations/dungeon.ts +++ b/src/server/locations/dungeon.ts @@ -11,6 +11,7 @@ import { renderFightPreRoundDungeon } from "../views/fight"; import { has, max, each } from 'lodash'; import { expToLevel, maxHp, maxVigor } from "../../shared/player"; import { updatePlayer } from "../player"; +import { getEventHistoryToday } from "../events"; export const router = Router(); @@ -160,12 +161,14 @@ router.post('/city/dungeon/:dungeon_id/complete', authEndpoint, async (req, res) }); } - // delete the tracking for this dungeon-run - await completeDungeon(req.player.id); + + // if this is not the first completion, lets give them diminishing returns + const completionsToday = await getEventHistoryToday(req.player.id, 'DUNGEON_COMPLETE'); + let factor = completionsToday.length <= 5 ? 1 : 0.2; // give the user these rewards! - req.player.gold += rewards.gold; - req.player.exp += rewards.exp; + req.player.gold += Math.ceil(rewards.gold * factor); + req.player.exp += Math.ceil(rewards.exp * factor); while(req.player.exp >= expToLevel(req.player.level + 1)) { req.player.exp -= expToLevel(req.player.level + 1); @@ -175,7 +178,9 @@ router.post('/city/dungeon/:dungeon_id/complete', authEndpoint, async (req, res) req.player.vigor = maxVigor(req.player.constitution, req.player.level); } + // delete the tracking for this dungeon-run + await completeDungeon(req.player.id); await updatePlayer(req.player); - res.send(dungeonRewards(dungeon, rewards)); + res.send(dungeonRewards(dungeon, rewards, completionsToday.length)); }); diff --git a/src/server/views/dungeons/room.ts b/src/server/views/dungeons/room.ts index cde75e5..a4e3552 100644 --- a/src/server/views/dungeons/room.ts +++ b/src/server/views/dungeons/room.ts @@ -53,11 +53,12 @@ export function renderDungeon(city: string, dungeon_name: string, room: DungeonR `; } -export function dungeonRewards(dungeon: Dungeon, rewards: DungeonRewards): string { +export function dungeonRewards(dungeon: Dungeon, rewards: DungeonRewards, completions: number): string { return `

Congratulations on completing the ${dungeon.name} Dungeon!

+

${completions <= 5 ? `${completions}/5 Runs` : `You've exhausted your runs!`}

Rewards:
Exp: ${rewards.exp.toLocaleString()}
Gold: ${rewards.gold.toLocaleString()}
diff --git a/src/shared/constants.ts b/src/shared/constants.ts index 42e884c..7804b56 100644 --- a/src/shared/constants.ts +++ b/src/shared/constants.ts @@ -5,3 +5,6 @@ export const ALERT_DISPLAY_LENGTH = 3000; export const CHANCE_TO_FIGHT_SPECIAL = 10; export const DUNGEON_TRAVEL_BLOCK = 3000; + +export const EVENT_FLUSH_INTERVAL = 10000; +export const EVENT_SECOND_BUCKET = 3; diff --git a/src/shared/event.ts b/src/shared/event.ts new file mode 100644 index 0000000..bb2bec2 --- /dev/null +++ b/src/shared/event.ts @@ -0,0 +1,23 @@ +type UUID = string; + +export type EventName = 'DUNGEON_COMPLETE' +| 'MONSTER_KILLED' +| 'PLAYER_KILLED' +| 'LEVEL_UP' +| 'LOGIN' +; + +export type Event = { + id: UUID; + event_name: EventName; + player_id: UUID; + app_version: string; + value: number; + props: T + created: Date; +} + +export type CreatedEvent = Omit, 'id'|'created'|'value'> & { + created?: Date; + value?: number; +} -- 2.25.1 From 2be01609a627d3db6fc39ff63f46b70016abbc43 Mon Sep 17 00:00:00 2001 From: xangelo Date: Fri, 29 Sep 2023 10:31:19 -0400 Subject: [PATCH 5/7] feat: min level for all locations All locations default to a min level of 1, so they're always visible. but some things (stores, dungeons) can have a higher level where they will not be visible until the player meets the requirement. --- seeds/cities.ts | 3 ++- src/server/api.ts | 6 +++--- src/server/fight.ts | 7 +++++++ src/server/map.ts | 3 ++- 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/seeds/cities.ts b/seeds/cities.ts index 2e1bfba..866907f 100644 --- a/seeds/cities.ts +++ b/seeds/cities.ts @@ -73,7 +73,8 @@ export async function createLocations(): Promise { type: r.fields.Type, city_id: r.fields.city_id[0], display_order: r.fields["Display Order"], - event_name: r.fields['event_name'] + event_name: r.fields['event_name'], + min_level: Math.max(parseInt(r.fields['Min Level'].toString()), 1) } })).onConflict('id').merge(); diff --git a/src/server/api.ts b/src/server/api.ts index 9d1b641..c476f77 100644 --- a/src/server/api.ts +++ b/src/server/api.ts @@ -359,7 +359,7 @@ app.get('/player/explore', authEndpoint, async (req: Request, res: Response) => // display the default explore view const [city, locations, paths] = await Promise.all([ getCityDetails(req.player.city_id), - getAllServices(req.player.city_id), + getAllServices(req.player.city_id, req.player.level), getAllPaths(req.player.city_id) ]); @@ -725,7 +725,7 @@ app.post('/travel/step', authEndpoint, async (req: Request, res: Response) => { const [city, locations, paths] = await Promise.all([ getCityDetails(travel.destination_id), - getAllServices(travel.destination_id), + getAllServices(travel.destination_id, req.player.level), getAllPaths(travel.destination_id) ]); @@ -782,7 +782,7 @@ app.post('/travel/return-to-source', authEndpoint, async (req: Request, res: Res else { const [city, locations, paths] = await Promise.all([ getCityDetails(req.player.city_id), - getAllServices(req.player.city_id), + getAllServices(req.player.city_id, req.player.level), getAllPaths(req.player.city_id) ]); diff --git a/src/server/fight.ts b/src/server/fight.ts index 83a8132..4515c30 100644 --- a/src/server/fight.ts +++ b/src/server/fight.ts @@ -163,6 +163,13 @@ export async function fightRound(player: Player, monster: Fight, data: {action: roundData.monster.hp = 0; roundData.winner = 'player'; + addEvent('MONSTER_KILLED', player.id, { + monster_id: roundData.monster.ref_id, + monster_name: roundData.monster.name, + level: roundData.monster.level, + fightTrigger: roundData.monster.fight_trigger + }); + const expGained = exponentialExp(monster.exp, monster.level, player.level); roundData.rewards.exp = expGained; diff --git a/src/server/map.ts b/src/server/map.ts index 8a49db3..45b0084 100644 --- a/src/server/map.ts +++ b/src/server/map.ts @@ -4,10 +4,11 @@ import type { Travel, TravelWithNames } from '../shared/travel'; import { db } from './lib/db'; import { random } from 'lodash'; -export async function getAllServices(city_id: number): Promise { +export async function getAllServices(city_id: number, min_level: number): Promise { return db.select('*') .from('locations') .where({city_id}) + .andWhere('min_level', '<=', min_level) .orderBy('type') .orderBy('display_order'); } -- 2.25.1 From 552c7444a7b178ba000dcd34a03cca80ca56b57e Mon Sep 17 00:00:00 2001 From: xangelo Date: Fri, 29 Sep 2023 12:12:24 -0400 Subject: [PATCH 6/7] fix: remove log of 0 events being flushed --- src/server/events.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/server/events.ts b/src/server/events.ts index f5a630d..df0b819 100644 --- a/src/server/events.ts +++ b/src/server/events.ts @@ -15,9 +15,6 @@ export async function flushBuffer() { await addEvents(events); logger.log(`Flushed ${events.length} events`); } - else { - logger.log('No events to flush'); - } setTimeout(flushBuffer, EVENT_FLUSH_INTERVAL); } -- 2.25.1 From 6d8c4310bd2bac9c15c138d1dfc10bdb2ea72ad9 Mon Sep 17 00:00:00 2001 From: xangelo Date: Fri, 29 Sep 2023 12:12:39 -0400 Subject: [PATCH 7/7] chore(release): 0.4.0 --- CHANGELOG.md | 20 ++++++++++++++++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c9a78dc..5c6ff07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,26 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [0.4.0](https://git.xangelo.ca/?p=risinglegends.git;a=commitdiff;h=v0.4.0;hp=v0.3.6;ds=sidebyside) (2023-09-29) + + +### ⚠ BREAKING CHANGES + +* dungeon traversal + +### Features + +* cleanup chat commands 074d6ad +* dungeon traversal 2ec43df +* min level for all locations 2be0160 +* psql based event system 43f0bc3 + + +### Bug Fixes + +* auto-enter dungeon if you are in it dfa62a7 +* remove log of 0 events being flushed 552c744 + ### [0.3.6](https://git.xangelo.ca/?p=risinglegends.git;a=commitdiff;h=v0.3.6;hp=v0.3.5;ds=sidebyside) (2023-09-06) diff --git a/package-lock.json b/package-lock.json index f73a4df..5125160 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "rising-legends", - "version": "0.3.6", + "version": "0.4.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "rising-legends", - "version": "0.3.6", + "version": "0.4.0", "dependencies": { "@honeycombio/opentelemetry-node": "^0.4.0", "@opentelemetry/auto-instrumentations-node": "^0.37.0", diff --git a/package.json b/package.json index 6b395b4..7c8a2eb 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "rising-legends", "private": true, - "version": "0.3.6", + "version": "0.4.0", "scripts": { "up": "npx prisma migrate dev --name \"init\"", "start": "pm2 start dist/server/api.js", -- 2.25.1