Skip to content

Instantly share code, notes, and snippets.

@Kattoor
Created February 3, 2025 14:50
Show Gist options
  • Save Kattoor/1e6290f3af39aa97e7c242c04585a39f to your computer and use it in GitHub Desktop.
Save Kattoor/1e6290f3af39aa97e7c242c04585a39f to your computer and use it in GitHub Desktop.
Arcanist deserializer
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;
}
}
class Kbuffer {
constructor(buffer) {
this.buffer = buffer;
this.offset = 0;
}
readBytes() {
const readLength = this.readInt32();
const buffer = this.buffer.subarray(this.offset, this.offset + readLength);
this.offset += readLength;
return buffer;
}
readUInt16() {
const uint16 = this.buffer.readUInt16LE(this.offset);
this.offset += 2;
return uint16;
}
readColor32NoAlpha() {
return {
a: 255, r: this.readByte(), g: this.readByte(), b: this.readByte()
};
}
readUInt64() {
const uint64 = this.buffer.readBigUint64LE(this.offset);
this.offset += 8;
return uint64;
}
readInt64() {
const int64 = this.buffer.readBigInt64LE(this.offset);
this.offset += 8;
return int64;
}
readMyLocation() {
return {x: this.readInt64(), y: this.readInt64()};
}
readByte() {
const byte = this.buffer.readUInt8(this.offset);
this.offset += 1;
return byte;
}
readInt16() {
const int16 = this.buffer.readInt16LE(this.offset);
this.offset += 2;
return int16;
}
readInt32() {
const int32 = this.buffer.readInt32LE(this.offset);
this.offset += 4;
return int32;
}
readBool() {
const bool = this.buffer.readUInt8(this.offset);
this.offset += 1;
return bool !== 0;
}
readString() {
const length = this.buffer.readUInt8(this.offset);
this.offset += 1;
const str = this.buffer.subarray(this.offset, this.offset + length).toString();
this.offset += length;
return str;
}
readFixed() {
const low = this.buffer.readUint32LE(this.offset);
this.offset += 4;
const high = this.buffer.readUint32LE(this.offset);
this.offset += 4;
return high * 2 ** 32 + low;
}
}
const kBufferHandler = {
get(target, prop) {
if (typeof prop === 'symbol' || isNaN(prop)) {
return target[prop];
}
const index = Number(prop);
return target.buffer[index];
}
};
export default function createKbuffer(buffer) {
const wrapper = new Kbuffer(buffer);
return new Proxy(wrapper, kBufferHandler);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment