import fs from 'fs'; import createKbuffer from "./kbuffer.js"; import pg from 'pg'; const {Pool} = pg; const pool = new Pool({ host: 'localhost', port: 5432, user: 'postgres', password: 'nope', database: 'postgres', }); const spellsEnum = { '-2': 'Player', // 0xFFFFFFFE '-1': 'None', // 0xFFFFFFFF '0': 'Arcane_Arrow', '1': 'Arcane_Bomb', '2': 'Arcane_Tower', '3': 'Arcane_Energiser', '4': 'Arcane_Gate', '5': 'Arcane_Portal', '6': 'Summon_Imps', '7': 'Imp_Destruction', '8': 'Arcane_Glyph', '9': 'Arcane_Sigil', '10': 'Arcane_Flash', // 0x0000000A '11': 'Summon_Dragon_Egg', // 0x0000000B '12': 'Fire_Ball', // 0x0000000C '13': 'Fire_Arrow', // 0x0000000D '14': 'Lava_Bomb', // 0x0000000E '15': 'Magma_Bomb', // 0x0000000F '16': 'Flame_Shield', // 0x00000010 '17': 'Flame_Wall', // 0x00000011 '18': 'Napalm', // 0x00000012 '19': 'Napalm_Bomb', // 0x00000013 '20': 'Rain_of_Fire', // 0x00000014 '21': 'Rain_of_Arrows', // 0x00000015 '22': 'Volcano', // 0x00000016 '23': 'Summon_Flame_Dragon', // 0x00000017 '24': 'Pebble_Shot', // 0x00000018 '25': 'Scatter_Rock', // 0x00000019 '26': 'Quake', // 0x0000001A '27': 'Disruption', // 0x0000001B '28': 'Mud_Ball', // 0x0000001C '29': 'Mega_Boulder', // 0x0000001D '30': 'Rock_Slab', // 0x0000001E '31': 'Fortress', // 0x0000001F '32': 'Summon_Dwarf', // 0x00000020 '33': 'Summon_Rock_Golem', // 0x00000021 '34': 'Meteor', // 0x00000022 '35': 'Fissure', // 0x00000023 '36': 'Thunder_Shock', // 0x00000024 '37': 'Chain_Lightning', // 0x00000025 '38': 'Wind_Shield', // 0x00000026 '39': 'Hurricane', // 0x00000027 '40': 'Shock_Bomb', // 0x00000028 '41': 'Storm_Shield', // 0x00000029 '42': 'Summon_Cyclops', // 0x0000002A '43': 'Conductor_Rod', // 0x0000002B '44': 'Summon_Storm_Spirit', // 0x0000002C '45': 'Flight', // 0x0000002D '46': 'Storm', // 0x0000002E '47': 'Summon_Storm_Dragon', // 0x0000002F '48': 'Ice_Ball', // 0x00000030 '49': 'Ice_Bomb', // 0x00000031 '50': 'Frost_Shards', // 0x00000032 '51': 'Frost_Arrow', // 0x00000033 '52': 'Snowball', // 0x00000034 '53': 'Blizzard', // 0x00000035 '54': 'Ice_Shield', // 0x00000036 '55': 'Ice_Castle', // 0x00000037 '56': 'Summon_Sylph', // 0x00000038 '57': 'Summon_Frost_Giant', // 0x00000039 '58': 'Comet', // 0x0000003A '59': 'Frost_Dragon', // 0x0000003B '60': 'Den_of_Darkness', // 0x0000003C '61': 'Rain_of_Chaos', // 0x0000003D '62': 'Drain_Bolt', // 0x0000003E '63': 'Death_Bomb', // 0x0000003F '64': 'Summon_Swarm', // 0x00000040 '65': 'Summon_Dark_Knight', // 0x00000041 '66': 'Raise_Dead', // 0x00000042 '67': 'Summon_Wraith', // 0x00000043 '68': 'Aura_of_Decay', // 0x00000044 '69': 'Dark_Defences', // 0x00000045 '70': 'Swallowing_Pit', // 0x00000046 '71': 'Lichdom', // 0x00000047 '72': 'Protection_Shield', // 0x00000048 '73': 'Sky_Ray', // 0x00000049 '74': 'Shining_Bolt', // 0x0000004A '75': 'Rising_Star', // 0x0000004B '76': 'Summon_Pegasus', // 0x0000004C '77': 'Summon_Paladin', // 0x0000004D '78': 'Forest_Seed', // 0x0000004E '79': 'Summon_Pixies', // 0x0000004F '80': 'Sphere_of_Healing', // 0x00000050 '81': 'Castle_of_Light', // 0x00000051 '82': 'Invulnerability_Shield', // 0x00000052 '83': 'Shining_Power', // 0x00000053 '84': 'Thorn_Ball', // 0x00000054 '85': 'Thorn_Bomb', // 0x00000055 '86': 'Vine_Whip', // 0x00000056 '87': 'Vine_Bridge', // 0x00000057 '88': 'Entangle', // 0x00000058 '89': 'Vine_Bomb', // 0x00000059 '90': 'Summon_Man_Trap', // 0x0000005A '91': 'Sanctuary', // 0x0000005B '92': 'Summon_Elves', // 0x0000005C '93': 'Flurry', // 0x0000005D '94': 'Nature_s_Wrath', // 0x0000005E '95': 'Vine_Bloom', // 0x0000005F '96': 'Water_Ball', // 0x00000060 '97': 'Maelstrom', // 0x00000061 '98': 'Summon_Water_Trolls', // 0x00000062 '99': 'Hydration', // 0x00000063 '100': 'Deluge', // 0x00000064 '101': 'English_Summer', // 0x00000065 '102': 'Brine_Bolt', // 0x00000066 '103': 'Brine_Bomb', // 0x00000067 '104': 'Summon_Brine_Goblin', // 0x00000068 '105': 'Rain_of_Clams', // 0x00000069 '106': 'Ocean_s_Fury', // 0x0000006A '107': 'Summon_Water_Lord', // 0x0000006B '108': 'Static_Ball', // 0x0000006C '109': 'Static_Shield', // 0x0000006D '110': 'Mechanical_Arrow', // 0x0000006E '111': 'Cog_Fall', // 0x0000006F '112': 'Recall_Device', // 0x00000070 '113': 'Calling_Bell', // 0x00000071 '114': 'Clock_Tower', // 0x00000072 '115': 'Cuckoo_Clock', // 0x00000073 '116': 'Blast_From_The_Past', // 0x00000074 '117': 'Clockwork_Bomb', // 0x00000075 '118': 'Steam_Dragon', // 0x00000076 '119': 'Redo', // 0x00000077 '120': 'Banish', // 0x00000078 '121': 'Self_Destruct', // 0x00000079 '122': 'Mudruption', // 0x0000007A '123': 'Mine', // 0x0000007B '124': 'Kablam', // 0x0000007C '125': 'Wallop', // 0x0000007D '126': 'Storm_Dragon_Breath', // 0x0000007E '127': 'Shock_Shield', // 0x0000007F '128': 'Spirit_Hurricane', // 0x00000080 '129': 'Snow', // 0x00000081 '130': 'Sylph_Arrow', // 0x00000082 '131': 'Charge', // 0x00000083 '132': 'Zombie_Breath', // 0x00000084 '133': 'Sunder', // 0x00000085 '134': 'Fairy_Ring', // 0x00000086 '135': 'Volley', // 0x00000087 '136': 'Dive', // 0x00000088 '137': 'Recall', // 0x00000089 '138': 'Monkey', // 0x0000008A '139': 'New_Years_Rocket', // 0x0000008B '140': 'Deserving_of_Coal', // 0x0000008C '141': 'Summon_Snowman', // 0x0000008D '142': 'Summon_Reindeer', // 0x0000008E '143': 'Presents', // 0x0000008F '144': 'Christmas_Tree', // 0x00000090 '145': 'Thanksgiving_Dinner', // 0x00000091 '146': 'Firecrackers', // 0x00000092 '147': 'Santas_Magic', // 0x00000093 '148': 'Jingle_Boom', // 0x00000094 '149': 'Coal', // 0x00000095 '150': 'Tree_House', // 0x00000096 '151': 'Firework_Show', // 0x00000097 '152': 'Arcane_Dragon', // 0x00000098 '153': 'Air_Surge', // 0x00000099 '154': 'Apparition', // 0x0000009A '155': 'Duplication', // 0x0000009B '156': 'Color_Spray', // 0x0000009C '157': 'Floating_Castle', // 0x0000009D '158': 'Glide', // 0x0000009E '159': 'Magical_Barrier', // 0x0000009F '160': 'Summon_Phantom', // 0x000000A0 '161': 'Vortex', // 0x000000A1 '162': 'Whisper_Arrow', // 0x000000A2 '163': 'Whisper_Bomb', // 0x000000A3 '164': 'Social_Distancing', // 0x000000A4 '165': 'Corrupt_Dragon', // 0x000000A5 '166': 'Frost_Leap', // 0x000000A6 '167': 'Hatch', // 0x000000A7 '168': 'Meteor_Shower', // 0x000000A8 '169': 'Time_Dilation', // 0x000000A9 '170': 'Summon_Kraken', // 0x000000AA '171': 'Summon_Ship', // 0x000000AB '172': 'Kraken_Attack', // 0x000000AC '173': 'From_the_Depths', // 0x000000AD '174': 'Fire_Cannon', // 0x000000AE '175': 'Entangle_Kraken', // 0x000000AF '176': 'Summon_Mountain_Goat', // 0x000000B0 '177': 'Fire_Wave', // 0x000000B1 '178': 'Gift_of_Giving', // 0x000000B2 '179': 'Forestation', // 0x000000B3 '180': 'Electrostatic_Charge', // 0x000000B4 '181': 'The_ol_swaparoo', // 0x000000B5 '182': 'Whistling_Winds', // 0x000000B6 '183': 'Blood_Lust', // 0x000000B7 '184': 'Curse_of_Loneliness', // 0x000000B8 '185': 'Blood_Mist', // 0x000000B9 '186': 'Barrage_of_Bones', // 0x000000BA '187': 'Flesh_Wound', // 0x000000BB '188': 'Blood_Bath', // 0x000000BC '189': 'Curse_of_Disabling', // 0x000000BD '190': 'Summon_Blood_Bank', // 0x000000BE '191': 'Summon_Gargoyle', // 0x000000BF '192': 'Blood_Craze', // 0x000000C0 '193': 'Summon_Vampire', // 0x000000C1 '194': 'Infection', // 0x000000C2 '195': 'Blood_Pact', // 0x000000C3 '196': 'Dark_Totem', // 0x000000C4 '197': 'Stone_Form', // 0x000000C5 '198': 'Resurrection', // 0x000000C6 '199': 'Arcane_Mist', // 0x000000C7 '200': 'Ent_Whip', // 0x000000C8 '201': 'Water_Drop', // 0x000000C9 '202': 'Blink', // 0x000000CA '203': 'HighHigh', // 0x000000CB '204': 'PowerOfLight', // 0x000000CC '205': 'Little_Devil', // 0x000000CD '206': 'Acid_Rain', // 0x000000CE '207': 'Arcane_Meteor', // 0x000000CF '208': 'Arcane_Meteor_Shard', // 0x000000D0 '209': 'Stepping_Stone', // 0x000000D1 '210': 'Summon_Tutorial_Target', // 0x000000D2 '211': 'Call_of_the_Void', // 0x000000D3 '212': 'Pocket_Sand', // 0x000000D4 '213': 'Summon_King_Monarch', // 0x000000D5 '214': 'Life_Dew', // 0x000000D6 '215': 'Breeze', // 0x000000D7 '216': 'Pinecone', // 0x000000D8 '217': 'Erosion', // 0x000000D9 '218': 'Acorn', // 0x000000DA '219': 'Morning_Sun', // 0x000000DB '220': 'Autumn_Leaves', // 0x000000DC '221': 'The_Four_Seasons', // 0x000000DD '222': 'Summon_Dryad', // 0x000000DE '223': 'Seasonal', // 0x000000DF '224': 'Flutter', // 0x000000E0 '225': 'Chomp', // 0x000000E1 '226': 'Sacrifice', // 0x000000E2 '227': 'Vampire_Bat', // 0x000000E3 '228': 'Melt', // 0x000000E4 '229': 'Snowbolt', // 0x000000E5 '230': 'Cogmobile', // 0x000000E6 '231': 'Cog', // 0x000000E7 '232': 'Smash', // 0x000000E8 '233': 'Blood_Siphon', // 0x000000E9 '234': 'Watchtower', // 0x000000EA '235': 'Summon_Beehive', // 0x000000EB '236': 'Miner_Market', // 0x000000EC '237': 'Rock_Blaster', // 0x000000ED '238': 'Shaft', // 0x000000EE '239': 'Miner_Map', // 0x000000EF '240': 'Summon_Bees', // 0x000000F0 '241': 'Summon_Snowman2', // 0x000000F1 '242': 'Snow_Globe2', // 0x000000F2 '243': 'Summon_Myth', // 0x000000F3 '244': 'Rusty_Bomb', // 0x000000F4 '245': 'Summon_Will_o_the_Wisp', // 0x000000F5 '246': 'Summon_Boar', // 0x000000F6 '247': 'Summon_Tiger', // 0x000000F7 '248': 'Bear_Form', // 0x000000F8 '249': 'Enchanted_Axes', // 0x000000F9 '250': 'Harmony', // 0x000000FA '251': 'Verdant_Javelin', // 0x000000FB '252': 'Faiere_Jump', // 0x000000FC '253': 'Bear_Claw', // 0x000000FD '254': 'Grove_Renewal', // 0x000000FE '255': 'Prickly_Barrier', // 0x000000FF '256': 'Healing_Spores', // 0x00000100 '257': 'Bite', // 0x00000101 '258': 'Rampage', // 0x00000102 '259': 'Spirit_Link', // 0x00000103 '260': 'Summon_Alpha_Wolf', // 0x00000104 '261': 'Star_Bolt', // 0x00000105 '262': 'Shooting_Stars', // 0x00000106 '263': 'Gravity_Pulse', // 0x00000107 '264': 'Dark_Matter_Bomb', // 0x00000108 '265': 'Wormhole', // 0x00000109 '266': 'Collision_Course', // 0x0000010A '267': 'Fusion', // 0x0000010B '268': 'MACAIR', // 0x0000010C '269': 'Abduction', // 0x0000010D '270': 'Cosmic_Horror', // 0x0000010E '271': 'Black_Hole', // 0x0000010F '272': 'Supernova', // 0x00000110 '273': 'Starfire', // 0x00000111 '274': 'Starfire_Shard', // 0x00000112 '275': 'Summon_Drone', // 0x00000113 '276': 'Drone_Strike', // 0x00000114 '277': 'Tentacle_Smash', // 0x00000115 '278': 'Star_Dust', // 0x00000116 '279': 'Star_Ball', // 0x00000117 '280': 'Swipe', // 0x00000118 '281': 'Brine_Burst', // 0x00000119 '282': 'Gravity_Well', // 0x0000011A '283': 'Butterfly_Jar', // 0x0000011B '284': 'Death_and_Decay', // 0x0000011C '285': 'Spirit_Walk', // 0x0000011D '286': 'Retribution', // 0x0000011E '287': 'Blood_Clot', // 0x0000011F '288': 'Old_Mud_Ball', // 0x00000120 '289': 'Large_Bite', // 0x00000121 '290': 'Pack_Mentality', // 0x00000122 '291': 'Pack_Leader', // 0x00000123 '292': 'Storm_Jolt', // 0x00000124 '293': 'Frost_Nova', // 0x00000125 '294': 'Summon_Titan', // 0x00000126 '295': 'Summon_Monarchs', // 0x00000127 '296': 'Pounce', // 0x00000128 '297': 'Stalk', // 0x00000129 '298': 'Rising_Lava', // 0x0000012A '299': 'Steam_Cannon', // 0x0000012B '300': 'Compete', // 0x0000012C '301': 'When_Pigs_Fly', // 0x0000012D '302': 'Dense_Fog', // 0x0000012E '303': 'Tomato', // 0x0000012F '304': 'Monolith', // 0x00000130 '305': 'Summon_Wyrm', // 0x00000131 '306': 'Sandbag', // 0x00000132 '307': 'Sandy_Shores', // 0x00000133 '308': 'Bucket_of_Sand', // 0x00000134 '309': 'Pyramid', // 0x00000135 '310': 'Sand_Trap', // 0x00000136 '311': 'Sand_Castle', // 0x00000137 '312': 'Burning_Sands', // 0x00000138 '313': 'Sand_Storm', // 0x00000139 '314': 'Summon_Sphinx', // 0x0000013A '315': 'Mind_Control', // 0x0000013B '316': 'Burrow', // 0x0000013C '317': 'Consume', // 0x0000013D '318': 'Spit', // 0x0000013E '319': 'Pyramid_Strike', // 0x0000013F '320': 'Mana', // 0x00000140 '321': 'Sands_of_Time', // 0x00000141 '322': 'Summon_Sand_Mite', // 0x00000142 '323': 'Entomb', // 0x00000143 '324': 'Tomato_Emoji', // 0x00000144 '325': 'Zombie_Dragon', // 0x00000145 }; const inviteEnum = { '-1': "Don't Mind", '1': 'Invite Only', '2': 'Clan', '4': 'Friends', '8': 'Similar Rating', '16': 'Open', '32768': 'BookClub' }; const timeEnum = { '-1': "Don't Mind", '32': 'Five', '256': 'One Hundred Twenty', '512': 'Ninety', '1024': 'Sixty', '2048': 'Forty Five', '4096': 'Thirty', '8192': 'Twenty', '16384': 'Ten', '262144': 'Seven', '1048576': 'Fifteen' }; const playersPerTeamEnum = { '-1': "Don't Mind", '1048576': 'Eight', '2097152': 'Half', '33554432': 'Two', '67108864': 'Three', '134217728': 'Four', '268435456': 'Five', '536870912': 'Six', '1073741824': 'Seven' }; const gameStyleEnum = { '-1': "Don't Mind", '1': 'Shuffle Players', '2': 'Elementals', '4': 'Custom HP', '8': 'Random Spells', '16': 'Original Spells Only', '128': 'No Movement', '256': 'Zero Shield', '2048': 'Bid', '4096': 'Standard', '8192': 'First Turn Teleport', '16384': 'Sandbox', '32768': 'Zombie Monkey', '65536': 'Watchtower', '131072': 'Arcane Monster', '262144': 'Wind' }; const mapEnum = { '-1': "Don't Mind", '16': 'Jungle', '32': 'Snowy Hills', '64': 'Ocean Floor', '512': 'Dark Fortress', '1024': 'Wasteland', '32768': 'Grassy Hills', '65536': 'Giants Mountain', '131072': 'Elven Isles', '262144': 'Goblin Caves', '524288': 'Murky Swamp', '1048576': 'Graveyard', '2097152': 'Sky Castles', '4194304': 'Mos LeHarmless', '8388608': 'Arcane Crystals', '16777216': 'Random', '33554432': 'Alien World', '67108864': 'Ghostly Halls', '134217728': 'Desert', '1073741824': 'Space Nexus' }; const playerEnum = { '-1': "Don't Mind", '33554432': 'Twenty Four', '67108864': 'Two', '134217728': 'Three', '268435456': 'Four', '536870912': 'Five', '1073741824': 'Six' }; const bookEnum = { '-1': 'Nothing', '0': 'Arcane', '1': 'Flame', '2': 'Stone', '3': 'Storm', '4': 'Frost', '5': 'Underdark', '6': 'OverLight', '7': 'Nature', '8': 'Seas', '9': 'Cogs', '10': 'Seasons', '11': 'Illusion', '12': 'Blood', '13': 'Druidism', '14': 'Cosmos', '15': 'Sands' }; const replays = Object.entries(JSON.parse(await fs.promises.readFile('./replays.json', 'utf-8'))).toSorted(([, replay1], [, replay2]) => new Date(replay1.timestamp) - new Date(replay2.timestamp)); let i = 0; for (const [id, {timestamp, content}] of replays/*.slice(foundReplayIndex)*/) { const matches = [...content.replaceAll('**', '').matchAll(/(?:\[#\d+] \[.*])?\[(.+?)] (-?\d+) \{([+\-\d]+)}/g)]; const records = matches.map(([_, name, rating, ratingChange]) => ({ name, preMatchRating: +rating, ratingChange: +ratingChange, postMatchRating: +rating + +ratingChange })); const buffer = await fs.promises.readFile(`./replays/${id}`); const replay = getReplay(createKbuffer(buffer)); if (replay === null) { continue; } const clientVersion = replay.version; const gameId = id; const players = replay.gameFacts.players.map((player) => player.account); const amountOfPlayers = players.length; const description = replay.gameFacts.gameSettings.description; const gameType = replay.gameFacts.gameSettings.gameType; const wasDraw = replay.winners.length === 0; if (replay.gameFacts.customArmageddon != undefined) { console.log(replay.gameFacts.customArmageddon); } if (i % 1000 === 0) { console.log(i + '/' + replays.length); } const {seed, gameSettings: {inviteMode, spectatorMode, timeMode, teamMode, multiTeamMode, playerMode, playersPerTeam, mapMode, elementalLevel, startHealth, armageddonTurn, customTime, customPlayerCount, mapWidth, mapHeight, numberOfPlayersPerTeam}} = replay.gameFacts; await pool.query( 'INSERT INTO game (id, version, amount_of_players, description, game_type, timestamp, was_draw, seed, invite_mode, spectator_mode, time_mode, team_mode, multi_team_mode, player_mode, players_per_team, map, elemental_level, start_health, armageddon_turn, custom_time, custom_player_count, map_width, map_height, number_of_players_per_team) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24)', [gameId, clientVersion, amountOfPlayers, description, gameType, timestamp, wasDraw, seed, inviteMode, spectatorMode, timeMode, teamMode, multiTeamMode, playerMode, playersPerTeam, mapMode, elementalLevel, startHealth, armageddonTurn, customTime, customPlayerCount, mapWidth, mapHeight, numberOfPlayersPerTeam] ); for (const enabledStyle of replay.gameFacts.gameSettings.gameStyles) { await pool.query( 'INSERT INTO game_style (game_id, enabled_style) VALUES ($1, $2)', [gameId, enabledStyle] ); } for (let {account: {name, oldName, rating1, rating2, rating3, discord}, accountSettings: {fullBook, spells}} of replay.gameFacts.players) { if (name === 'Lemon Caster' || name === 'xLemonKush') { discord = '991846146626555994'; // use 991846146626555994 } else if (name === 'xLemonTree') { discord = '339812968185200660'; } const userWon = replay.winners.includes(name); const indexOfPlayerInPlayersArray = replay.gameFacts.players.findIndex((player) => player.account.name === name); const rateGain = records[indexOfPlayerInPlayersArray].ratingChange; const dbRecordForDiscordId = (await pool.query('SELECT * FROM "user" WHERE id = $1', [discord]))?.rows?.[0]; const discordIdAlreadyInDatabase = dbRecordForDiscordId !== undefined; try { if (!discordIdAlreadyInDatabase) { const dbRecordForNameAndPreviousName = (await pool.query('SELECT * FROM "user" WHERE name = $1 AND previous_name = $2', [name, oldName]))?.rows?.[0]; const nameAndPreviousNameAlreadyInDatabase = dbRecordForNameAndPreviousName !== undefined; if (nameAndPreviousNameAlreadyInDatabase) { await pool.query('UPDATE "user" SET id = $1, lts_rating = $2, hts_rating = $3, pmo_rating = $4 WHERE name = $5', [discord, rating1, rating2, rating3, name]); } else { await pool.query( 'INSERT INTO "user" (id, name, previous_name, lts_rating, hts_rating, pmo_rating) VALUES ($1, $2, $3, $4, $5, $6)', [discord, name, oldName, rating1, rating2, rating3] ) } } else { await pool.query('UPDATE "user" SET name = $1, previous_name = $2, lts_rating = $3, hts_rating = $4, pmo_rating = $5 WHERE id = $6', [name, oldName, rating1, rating2, rating3, discord]); } } catch (e) { console.error(e); console.log(gameId); console.log(timestamp); console.log(name); console.log(oldName); console.log(discord); process.exit(1); } const postGameRate = {'low': rating1, 'high': rating2, 'party': rating3}[replay.gameFacts.gameSettings.gameType]; const wentFirst = replay.firstPlayerToDoAMove === name; await pool.query( 'INSERT INTO user_game (user_id, game_id, won, rate_gain, post_game_rate, went_first, book) VALUES ($1, $2, $3, $4, $5, $6, $7)', [discord, gameId, userWon, rateGain, postGameRate, wentFirst, bookEnum[fullBook - 1]] ); for (const spellIndex of spells) { await pool.query( 'INSERT INTO user_game_spell_in_book (user_id, game_id, spell) VALUES ($1, $2, $3)', [discord, gameId, spellsEnum[spellIndex]] ); } } for (const {player, spell, frame} of replay.timeline) { await pool.query( 'INSERT INTO user_game_spell_fired (user_id, game_id, spell, frame) VALUES ($1, $2, $3, $4)', [player, gameId, spell, frame] ); } i += 1; } function getReplay(fileKbuffer) { const gameType = ['low', 'high', 'party']; return deserializeReplay(); function deserializeReplay() { const replay = {}; replay.version = fileKbuffer.readString(); if (replay.version === 'v7.0') { return null; } fileKbuffer.readBool(); replay.server = fileKbuffer.readString(); replay.gameFacts = deserializeGameFacts(replay.version); replay.num = fileKbuffer.readInt32(); replay.timeline = []; replay.timelineList = []; replay.firstPlayerToDoAMove = null; for (let i = 0; i < replay.num; i++) { const timelineBytes = fileKbuffer.readBytes(); const kbuffer = createKbuffer(timelineBytes); //replay.timeline.push(kbuffer); const firstByte = kbuffer.readByte(); const playerIndex = timelineBytes.length > 1 && firstByte >= 175 ? kbuffer.readByte() : null; if (replay.firstPlayerToDoAMove === null && playerIndex !== null) { replay.firstPlayerToDoAMove = replay.gameFacts.players[playerIndex].account.name; } if (firstByte === 13) { const winners = []; const losers = []; for (let j = 0; j < replay.gameFacts.players.length; j++) { const isAlive = kbuffer.readBool(); if (isAlive) { winners.push(replay.gameFacts.players[j].account.name); } else { losers.push(replay.gameFacts.players[j].account.name); } } const msg = kbuffer.readString(); for (let j = 0; j < replay.gameFacts.players.length; j++) { const wandsGained = kbuffer.readInt32(); } replay.winners = winners; replay.losers = losers; } else if (firstByte === 198) { const turn = kbuffer.readInt32(); replay.timelineList.push({ turn: turn + 2, player: replay.gameFacts.players[playerIndex].account.name, frame: i }); } else if (firstByte === 220) { const gameId = kbuffer.readInt32(); const nextMoveId = kbuffer.readInt32(); const selectedCreatureIndex = kbuffer.readByte(); const selectedCreaturePlayerOffset = kbuffer.readByte(); const selectedSpell = kbuffer.readInt32(); /*const spellBonusDmg = kbuffer.readInt32(); const spellIsPresent = kbuffer.readBool(); const spellEndsTurn = kbuffer.readBool(); const selectedSpellIndex = kbuffer.readByte(); const rotZ = kbuffer.readFixed(); const zCreatureSelectedTransformScaleIsLargerThan0 = kbuffer.readBool(); const spellTarget = kbuffer.readMyLocation(); const meterFillAmount = kbuffer.readFixed(); const isExtendedShot = kbuffer.readBool(); const keyDown = kbuffer.readInt32(); const zCreatureSelectedPosition = kbuffer.readMyLocation(); const secTarget = kbuffer.readMyLocation();*/ replay.timeline.push({ description: 'spell', player: replay.gameFacts.players[playerIndex].account.discord, spell: spellsEnum[selectedSpell], frame: i }); } } return replay; } function deserializeGameFacts(clientVersion) { const gameFacts = {}; gameFacts.gameId = fileKbuffer.readInt32(); gameFacts.amountOfPlayers = fileKbuffer.readInt32(); gameFacts.realMap = fileKbuffer.readInt32(); gameFacts.port = fileKbuffer.readInt32(); gameFacts.seed = fileKbuffer.readInt32(); gameFacts.players = []; for (let i = 0; i < gameFacts.amountOfPlayers; i++) { gameFacts.players.push({ account: deserializeAccount(), accountSettings: deserializeAccountSettings() }); } gameFacts.gameSettings = deserializeGameSettings(clientVersion); gameFacts.status = fileKbuffer.readByte(); if (gameFacts.status !== 0) { gameFacts.gameSettings.mapMode = mapEnum[gameFacts.realMap]; } gameFacts.gameSettings.numberOfPlayersPerTeam = getActualNumberOfPlayersPerTeam(gameFacts); gameFacts.serverStartTime = fileKbuffer.readInt32(); if (clientVersion !== 'v7.1') { gameFacts.customQueue = fileKbuffer.readInt32(); } return gameFacts; } function getActualNumberOfPlayersPerTeam(gameFacts) { switch (gameFacts.gameSettings.playersPerTeam) { case 'Eight': return 8; case 'Two': return 2; case 'Three': return 3; case 'Four': return 4; case 'Five': return 5; case 'Six': return 6; case 'Seven': return 7; default: if (!gameFacts.gameSettings.multiTeamMode || gameFacts.status === 0 || gameFacts.players.length <= 2) { return Math.floor(Math.max(2, (1 + gameFacts.players.length) / 2)); } else { const firstPlayerName = gameFacts.players[0].account.name; return gameFacts.players.filter(({account: {name}}) => name === firstPlayerName).length; } } } function hasStyle(s, t) { return ((s & t) >>> 0) > 0; } function deserializeGameSettings(clientVersion) { const gameSettings = {}; gameSettings.description = fileKbuffer.readString(); const gameModes = fileKbuffer.readInt32(); gameSettings.inviteMode = inviteEnum[gameModes & 31]; gameSettings.spectatorMode = (gameModes & 64) > 0; gameSettings.ratedMode = (gameModes & 128) > 0; gameSettings.tournamentMode = (gameModes & 32768) > 0; gameSettings.timeMode = timeEnum[gameModes & 1343264]; gameSettings.teamMode = (gameModes & 16777216) > 0; gameSettings.multiTeamMode = (gameModes & 524288) > 0; gameSettings.playerMode = playerEnum[gameModes & 2113929216]; const gameModes2 = fileKbuffer.readInt32(); gameSettings.allowArcanePowers = (gameModes2 & 131072) !== 0; gameSettings.playersPerTeam = playersPerTeamEnum[gameModes2 & 2117074944]; gameSettings.style = gameModes2; //gameStyleEnum[gameModes2 & 258459]; gameSettings.gameStyles = Object.entries(gameStyleEnum).filter(([bitStuff]) => hasStyle(gameSettings.style, +bitStuff)).map(([_, name]) => name); gameSettings.isNonStandard = (gameModes2 & 254362) !== 0; const gameModes3 = fileKbuffer.readInt32(); gameSettings.armageddon = mapEnum[gameModes3 & 1342146160]; const gameModes4 = fileKbuffer.readInt32(); gameSettings.mapMode = mapEnum[gameModes4 & 1342146160]; gameSettings.elementalLevel = fileKbuffer.readByte(); gameSettings.startHealth = fileKbuffer.readUInt16(); gameSettings.armageddonTurn = fileKbuffer.readByte(); gameSettings.countdownTime = fileKbuffer.readInt16(); gameSettings.countdownDelay = fileKbuffer.readByte(); gameSettings.customTime = fileKbuffer.readUInt16(); gameSettings.customPlayerCount = fileKbuffer.readByte(); gameSettings.gameType = gameType[Math.min(2, Math.max(0, fileKbuffer.readInt32()))]; if (clientVersion !== 'v7.1') { gameSettings.altGeneration = fileKbuffer.readBool(); gameSettings.water = fileKbuffer.readInt32(); } gameSettings.version1 = fileKbuffer.readByte(); if (gameSettings.version1 > 0) { gameSettings.restrictions = deserializeRestrictions(); } else { gameSettings.restrictions = null; } gameSettings.mapSeed = fileKbuffer.readInt32(); gameSettings.fixedMapSeed = fileKbuffer.readBool(); gameSettings.mapWidth = fileKbuffer.readByte(); gameSettings.mapHeight = fileKbuffer.readByte(); gameSettings.customArmageddonCount = fileKbuffer.readInt32(); if (gameSettings.customArmageddonCount > 0 && gameSettings.customArmageddonCount <= 5) { gameSettings.customArmageddon = []; for (let i = 0; i < gameSettings.customArmageddonCount; i++) { gameSettings.customArmageddon.push(fileKbuffer.readInt32()); } } else { gameSettings.customArmageddon = null; } if (clientVersion !== 'v7.1') { gameSettings.num2 = fileKbuffer.readInt32(); if (gameSettings.num2 > 0 && gameSettings.num2 <= 250) { gameSettings.autoInclude = []; for (let i = 0; i < gameSettings.num2; i++) { gameSettings.autoInclude.push(fileKbuffer.readInt32()); } } else { gameSettings.autoInclude = null; } } return gameSettings; } function deserializeRestrictions() { const restrictions = {}; restrictions.availableSpells = []; for (let i = 0; i < 8; i++) { restrictions.availableSpells.push(fileKbuffer.readInt32()); } restrictions.elementals = fileKbuffer.readInt32(); return restrictions; } function deserializeAccount() { const account = {}; account.name = fileKbuffer.readString(); account.rating1 = fileKbuffer.readInt16(); account.rating2 = fileKbuffer.readInt16(); account.rating3 = fileKbuffer.readInt16(); account.accountType = fileKbuffer.readInt32(); account.experience = fileKbuffer.readByte(); account.discord = fileKbuffer.readUInt64(); account.displayedIcon = fileKbuffer.readInt32(); account.displayClanPrefix = fileKbuffer.readByte(); account.clan = fileKbuffer.readString(); account.clanRole = fileKbuffer.readByte(); account.location = fileKbuffer.readByte(); account.prestige = fileKbuffer.readByte(); account.oldName = fileKbuffer.readString(); account.server = fileKbuffer.readInt32(); account.activeItems = deserializeActiveItems(); return account; } function deserializeActiveItems() { const items = []; const num1 = fileKbuffer.readByte(); const num2 = fileKbuffer.readInt32(); for (let i = 0; i < num2; i++) { items.push({ which: fileKbuffer.readInt32(), index: fileKbuffer.readInt32() }); } return items; } function deserializeAccountSettings() { const accountSettings = {}; accountSettings.accountType = fileKbuffer.readByte(); accountSettings.indexHead = fileKbuffer.readByte(); accountSettings.indexBeard = fileKbuffer.readByte(); if (accountSettings.accountType > 3) { accountSettings.indexBeard2 = fileKbuffer.readByte(); accountSettings.indexBeard3 = fileKbuffer.readByte(); } else { accountSettings.indexBeard2 = 0; accountSettings.indexBeard3 = 0; } accountSettings.indexHat = fileKbuffer.readByte(); accountSettings.indexBody = fileKbuffer.readByte(); accountSettings.indexLeftHand = fileKbuffer.readByte(); accountSettings.indexRightHand = fileKbuffer.readByte(); accountSettings.spells = []; for (let i = 0; i < 16; i++) { accountSettings.spells[i] = fileKbuffer.readByte(); } if (accountSettings.accountType <= 1) { accountSettings.num2 = fileKbuffer.readByte(); accountSettings.num3 = fileKbuffer.readByte(); accountSettings.num4 = fileKbuffer.readByte(); accountSettings.num5 = fileKbuffer.readByte(); if (accountSettings.accountType > 0) { accountSettings.fullBook = fileKbuffer.readByte(); } } else { accountSettings.coloring = deserializeColoring(); accountSettings.fullBook = fileKbuffer.readByte(); accountSettings.extraInfo = accountSettings.accountType <= 2 ? 0 : fileKbuffer.readByte(); accountSettings.num2 = fileKbuffer.readByte(); if (accountSettings.num2 !== 0) { accountSettings.custom = accountSettings.num2; accountSettings.indexMouth = fileKbuffer.readByte(); accountSettings.indexLeftFoot = fileKbuffer.readByte(); accountSettings.indexRightFoot = fileKbuffer.readByte(); accountSettings.num3 = fileKbuffer.readByte(); accountSettings.customPieces = []; for (let i = 0; i < accountSettings.num3; i++) { if (i < 11) { accountSettings.customPieces[i] = fileKbuffer.readString(); } } } } return accountSettings; } function deserializeColoring() { const result = []; const num = fileKbuffer.readInt32(); if (num > 11) { return; } for (let i = 0; i < num; i++) { const red = fileKbuffer.readColor32NoAlpha(); const green = fileKbuffer.readColor32NoAlpha(); const blue = fileKbuffer.readColor32NoAlpha(); const gray = fileKbuffer.readColor32NoAlpha(); result.push({red, green, blue, gray}); } return result; } }