@slippi/slippi-js
Version:
Official Project Slippi Javascript SDK
1,450 lines (1,432 loc) • 165 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var get = require('lodash/get');
var isEqual = require('lodash/isEqual');
var keyBy = require('lodash/keyBy');
var last = require('lodash/last');
var set = require('lodash/set');
var size = require('lodash/size');
var events = require('events');
var filter = require('lodash/filter');
var groupBy = require('lodash/groupBy');
var orderBy = require('lodash/orderBy');
var first = require('lodash/first');
var flatten = require('lodash/flatten');
var mapValues = require('lodash/mapValues');
var zip = require('lodash/zip');
var dateFns = require('date-fns');
var fs = require('fs');
var forEach = require('lodash/forEach');
var stream = require('stream');
var ubjson = require('@shelacek/ubjson');
var net = require('net');
var inject = require('reconnect-core');
var iconv = require('iconv-lite');
var map = require('lodash/map');
var path = require('path');
var semver = require('semver');
// eslint-disable-next-line
function getDeathDirection(actionStateId) {
if (actionStateId > 0xa) {
return null;
}
switch (actionStateId) {
case 0:
return "down";
case 1:
return "left";
case 2:
return "right";
default:
return "up";
}
}
var animationUtils = {
__proto__: null,
getDeathDirection: getDeathDirection
};
var characters = {
"0": {
name: "Captain Falcon",
shortName: "Falcon",
colors: [
"Black",
"Red",
"White",
"Green",
"Blue"
]
},
"1": {
name: "Donkey Kong",
shortName: "DK",
colors: [
"Black",
"Red",
"Blue",
"Green"
]
},
"2": {
name: "Fox",
colors: [
"Red",
"Blue",
"Green"
]
},
"3": {
name: "Mr. Game & Watch",
shortName: "G&W",
colors: [
"Red",
"Blue",
"Green"
]
},
"4": {
name: "Kirby",
colors: [
"Yellow",
"Blue",
"Red",
"Green",
"White"
]
},
"5": {
name: "Bowser",
colors: [
"Red",
"Blue",
"Black"
]
},
"6": {
name: "Link",
colors: [
"Red",
"Blue",
"Black",
"White"
]
},
"7": {
name: "Luigi",
colors: [
"White",
"Blue",
"Red"
]
},
"8": {
name: "Mario",
colors: [
"Yellow",
"Black",
"Blue",
"Green"
]
},
"9": {
name: "Marth",
colors: [
"Red",
"Green",
"Black",
"White"
]
},
"10": {
name: "Mewtwo",
colors: [
"Red",
"Blue",
"Green"
]
},
"11": {
name: "Ness",
colors: [
"Yellow",
"Blue",
"Green"
]
},
"12": {
name: "Peach",
colors: [
"Daisy",
"White",
"Blue",
"Green"
]
},
"13": {
name: "Pikachu",
colors: [
"Red",
"Party Hat",
"Cowboy Hat"
]
},
"14": {
name: "Ice Climbers",
shortName: "ICs",
colors: [
"Green",
"Orange",
"Red"
]
},
"15": {
name: "Jigglypuff",
shortName: "Puff",
colors: [
"Red",
"Blue",
"Headband",
"Crown"
]
},
"16": {
name: "Samus",
colors: [
"Pink",
"Black",
"Green",
"Purple"
]
},
"17": {
name: "Yoshi",
colors: [
"Red",
"Blue",
"Yellow",
"Pink",
"Cyan"
]
},
"18": {
name: "Zelda",
colors: [
"Red",
"Blue",
"Green",
"White"
]
},
"19": {
name: "Sheik",
colors: [
"Red",
"Blue",
"Green",
"White"
]
},
"20": {
name: "Falco",
colors: [
"Red",
"Blue",
"Green"
]
},
"21": {
name: "Young Link",
shortName: "YLink",
colors: [
"Red",
"Blue",
"White",
"Black"
]
},
"22": {
name: "Dr. Mario",
shortName: "Doc",
colors: [
"Red",
"Blue",
"Green",
"Black"
]
},
"23": {
name: "Roy",
colors: [
"Red",
"Blue",
"Green",
"Yellow"
]
},
"24": {
name: "Pichu",
colors: [
"Red",
"Blue",
"Green"
]
},
"25": {
name: "Ganondorf",
shortName: "Ganon",
colors: [
"Red",
"Blue",
"Green",
"Purple"
]
},
"26": {
name: "Master Hand"
},
"27": {
name: "Wireframe (Male)"
},
"28": {
name: "Wireframe (Female)"
},
"29": {
name: "Gigabowser"
},
"30": {
name: "Crazy Hand"
},
"31": {
name: "Sandbag"
},
"32": {
name: "Popo"
}
};
const DEFAULT_COLOR = "Default";
const UnknownCharacter = {
id: -1,
name: "Unknown Character",
shortName: "Unknown",
colors: [DEFAULT_COLOR]
};
function generateCharacterInfo(id, info) {
var _info$shortName, _info$colors;
if (!info) {
return UnknownCharacter;
}
return {
id,
name: info.name,
shortName: (_info$shortName = info.shortName) != null ? _info$shortName : info.name,
colors: [DEFAULT_COLOR, ...((_info$colors = info.colors) != null ? _info$colors : [])]
};
}
function getAllCharacters() {
return Object.entries(characters).map(([id, data]) => generateCharacterInfo(parseInt(id, 10), data)).sort((a, b) => a.id - b.id);
}
function getCharacterInfo(externalCharacterId) {
const data = characters[externalCharacterId.toString()];
return generateCharacterInfo(externalCharacterId, data);
}
function getCharacterShortName(externalCharacterId) {
const character = getCharacterInfo(externalCharacterId);
return character.shortName;
}
function getCharacterName(externalCharacterId) {
const character = getCharacterInfo(externalCharacterId);
return character.name;
}
// Return a human-readable color from a characterCode.
function getCharacterColorName(externalCharacterId, characterColor) {
const character = getCharacterInfo(externalCharacterId);
const color = character.colors[characterColor];
if (color) {
return color;
}
return DEFAULT_COLOR;
}
var characterUtils = {
__proto__: null,
UnknownCharacter: UnknownCharacter,
getAllCharacters: getAllCharacters,
getCharacterColorName: getCharacterColorName,
getCharacterInfo: getCharacterInfo,
getCharacterName: getCharacterName,
getCharacterShortName: getCharacterShortName
};
var moveNames = {
"1": {
name: "Miscellaneous",
shortName: "misc"
},
"2": {
name: "Jab",
shortName: "jab"
},
"3": {
name: "Jab",
shortName: "jab"
},
"4": {
name: "Jab",
shortName: "jab"
},
"5": {
name: "Rapid Jabs",
shortName: "rapid-jabs"
},
"6": {
name: "Dash Attack",
shortName: "dash"
},
"7": {
name: "Forward Tilt",
shortName: "ftilt"
},
"8": {
name: "Up Tilt",
shortName: "utilt"
},
"9": {
name: "Down Tilt",
shortName: "dtilt"
},
"10": {
name: "Forward Smash",
shortName: "fsmash"
},
"11": {
name: "Up Smash",
shortName: "usmash"
},
"12": {
name: "Down Smash",
shortName: "dsmash"
},
"13": {
name: "Neutral Air",
shortName: "nair"
},
"14": {
name: "Forward Air",
shortName: "fair"
},
"15": {
name: "Back Air",
shortName: "bair"
},
"16": {
name: "Up Air",
shortName: "uair"
},
"17": {
name: "Down Air",
shortName: "dair"
},
"18": {
name: "Neutral B",
shortName: "neutral-b"
},
"19": {
name: "Side B",
shortName: "side-b"
},
"20": {
name: "Up B",
shortName: "up-b"
},
"21": {
name: "Down B",
shortName: "down-b"
},
"50": {
name: "Getup Attack",
shortName: "getup"
},
"51": {
name: "Getup Attack (Slow)",
shortName: "getup-slow"
},
"52": {
name: "Grab Pummel",
shortName: "pummel"
},
"53": {
name: "Forward Throw",
shortName: "fthrow"
},
"54": {
name: "Back Throw",
shortName: "bthrow"
},
"55": {
name: "Up Throw",
shortName: "uthrow"
},
"56": {
name: "Down Throw",
shortName: "dthrow"
},
"61": {
name: "Edge Attack (Slow)",
shortName: "edge-slow"
},
"62": {
name: "Edge Attack",
shortName: "edge"
}
};
const UnknownMove = {
id: -1,
name: "Unknown Move",
shortName: "unknown"
};
function getMoveInfo(moveId) {
const moveName = moveNames[moveId.toString()];
if (!moveName) {
return UnknownMove;
}
return {
id: moveId,
name: moveName.name,
shortName: moveName.shortName
};
}
function getMoveShortName(moveId) {
const move = getMoveInfo(moveId);
return move.shortName;
}
function getMoveName(moveId) {
const move = getMoveInfo(moveId);
return move.name;
}
var moveUtils = {
__proto__: null,
UnknownMove: UnknownMove,
getMoveInfo: getMoveInfo,
getMoveName: getMoveName,
getMoveShortName: getMoveShortName
};
var stageNames = {
"2": "Fountain of Dreams",
"3": "Pokémon Stadium",
"4": "Princess Peach's Castle",
"5": "Kongo Jungle",
"6": "Brinstar",
"7": "Corneria",
"8": "Yoshi's Story",
"9": "Onett",
"10": "Mute City",
"11": "Rainbow Cruise",
"12": "Jungle Japes",
"13": "Great Bay",
"14": "Hyrule Temple",
"15": "Brinstar Depths",
"16": "Yoshi's Island",
"17": "Green Greens",
"18": "Fourside",
"19": "Mushroom Kingdom I",
"20": "Mushroom Kingdom II",
"22": "Venom",
"23": "Poké Floats",
"24": "Big Blue",
"25": "Icicle Mountain",
"26": "Icetop",
"27": "Flat Zone",
"28": "Dream Land N64",
"29": "Yoshi's Island N64",
"30": "Kongo Jungle N64",
"31": "Battlefield",
"32": "Final Destination",
"33": "Target Test (Mario)",
"34": "Target Test (Captain Falcon)",
"35": "Target Test (Young Link)",
"36": "Target Test (Donkey Kong)",
"37": "Target Test (Dr. Mario)",
"38": "Target Test (Falco)",
"39": "Target Test (Fox)",
"40": "Target Test (Ice Climbers)",
"41": "Target Test (Kirby)",
"42": "Target Test (Bowser)",
"43": "Target Test (Link)",
"44": "Target Test (Luigi)",
"45": "Target Test (Marth)",
"46": "Target Test (Mewtwo)",
"47": "Target Test (Ness)",
"48": "Target Test (Peach)",
"49": "Target Test (Pichu)",
"50": "Target Test (Pikachu)",
"51": "Target Test (Jigglypuff)",
"52": "Target Test (Samus)",
"53": "Target Test (Sheik)",
"54": "Target Test (Yoshi)",
"55": "Target Test (Zelda)",
"56": "Target Test (Mr. Game & Watch)",
"57": "Target Test (Roy)",
"58": "Target Test (Ganondorf)",
"84": "Home-Run Contest"
};
const UnknownStage = {
id: -1,
name: "Unknown Stage"
};
function getStageInfo(stageId) {
const stageName = stageNames[stageId.toString()];
if (!stageName) {
return UnknownStage;
}
return {
id: stageId,
name: stageName
};
}
function getStageName(stageId) {
const stage = getStageInfo(stageId);
return stage.name;
}
var stageUtils = {
__proto__: null,
UnknownStage: UnknownStage,
getStageInfo: getStageInfo,
getStageName: getStageName
};
exports.Character = void 0;
(function (Character) {
Character[Character["CAPTAIN_FALCON"] = 0] = "CAPTAIN_FALCON";
Character[Character["DONKEY_KONG"] = 1] = "DONKEY_KONG";
Character[Character["FOX"] = 2] = "FOX";
Character[Character["GAME_AND_WATCH"] = 3] = "GAME_AND_WATCH";
Character[Character["KIRBY"] = 4] = "KIRBY";
Character[Character["BOWSER"] = 5] = "BOWSER";
Character[Character["LINK"] = 6] = "LINK";
Character[Character["LUIGI"] = 7] = "LUIGI";
Character[Character["MARIO"] = 8] = "MARIO";
Character[Character["MARTH"] = 9] = "MARTH";
Character[Character["MEWTWO"] = 10] = "MEWTWO";
Character[Character["NESS"] = 11] = "NESS";
Character[Character["PEACH"] = 12] = "PEACH";
Character[Character["PIKACHU"] = 13] = "PIKACHU";
Character[Character["ICE_CLIMBERS"] = 14] = "ICE_CLIMBERS";
Character[Character["JIGGLYPUFF"] = 15] = "JIGGLYPUFF";
Character[Character["SAMUS"] = 16] = "SAMUS";
Character[Character["YOSHI"] = 17] = "YOSHI";
Character[Character["ZELDA"] = 18] = "ZELDA";
Character[Character["SHEIK"] = 19] = "SHEIK";
Character[Character["FALCO"] = 20] = "FALCO";
Character[Character["YOUNG_LINK"] = 21] = "YOUNG_LINK";
Character[Character["DR_MARIO"] = 22] = "DR_MARIO";
Character[Character["ROY"] = 23] = "ROY";
Character[Character["PICHU"] = 24] = "PICHU";
Character[Character["GANONDORF"] = 25] = "GANONDORF";
Character[Character["MASTER_HAND"] = 26] = "MASTER_HAND";
Character[Character["WIREFRAME_MALE"] = 27] = "WIREFRAME_MALE";
Character[Character["WIREFRAME_FEMALE"] = 28] = "WIREFRAME_FEMALE";
Character[Character["GIGA_BOWSER"] = 29] = "GIGA_BOWSER";
Character[Character["CRAZY_HAND"] = 30] = "CRAZY_HAND";
Character[Character["SANDBAG"] = 31] = "SANDBAG";
Character[Character["POPO"] = 32] = "POPO";
})(exports.Character || (exports.Character = {}));
exports.Stage = void 0;
(function (Stage) {
Stage[Stage["FOUNTAIN_OF_DREAMS"] = 2] = "FOUNTAIN_OF_DREAMS";
Stage[Stage["POKEMON_STADIUM"] = 3] = "POKEMON_STADIUM";
Stage[Stage["PEACHS_CASTLE"] = 4] = "PEACHS_CASTLE";
Stage[Stage["KONGO_JUNGLE"] = 5] = "KONGO_JUNGLE";
Stage[Stage["BRINSTAR"] = 6] = "BRINSTAR";
Stage[Stage["CORNERIA"] = 7] = "CORNERIA";
Stage[Stage["YOSHIS_STORY"] = 8] = "YOSHIS_STORY";
Stage[Stage["ONETT"] = 9] = "ONETT";
Stage[Stage["MUTE_CITY"] = 10] = "MUTE_CITY";
Stage[Stage["RAINBOW_CRUISE"] = 11] = "RAINBOW_CRUISE";
Stage[Stage["JUNGLE_JAPES"] = 12] = "JUNGLE_JAPES";
Stage[Stage["GREAT_BAY"] = 13] = "GREAT_BAY";
Stage[Stage["HYRULE_TEMPLE"] = 14] = "HYRULE_TEMPLE";
Stage[Stage["BRINSTAR_DEPTHS"] = 15] = "BRINSTAR_DEPTHS";
Stage[Stage["YOSHIS_ISLAND"] = 16] = "YOSHIS_ISLAND";
Stage[Stage["GREEN_GREENS"] = 17] = "GREEN_GREENS";
Stage[Stage["FOURSIDE"] = 18] = "FOURSIDE";
Stage[Stage["MUSHROOM_KINGDOM"] = 19] = "MUSHROOM_KINGDOM";
Stage[Stage["MUSHROOM_KINGDOM_2"] = 20] = "MUSHROOM_KINGDOM_2";
Stage[Stage["VENOM"] = 22] = "VENOM";
Stage[Stage["POKE_FLOATS"] = 23] = "POKE_FLOATS";
Stage[Stage["BIG_BLUE"] = 24] = "BIG_BLUE";
Stage[Stage["ICICLE_MOUNTAIN"] = 25] = "ICICLE_MOUNTAIN";
Stage[Stage["ICETOP"] = 26] = "ICETOP";
Stage[Stage["FLAT_ZONE"] = 27] = "FLAT_ZONE";
Stage[Stage["DREAMLAND"] = 28] = "DREAMLAND";
Stage[Stage["YOSHIS_ISLAND_N64"] = 29] = "YOSHIS_ISLAND_N64";
Stage[Stage["KONGO_JUNGLE_N64"] = 30] = "KONGO_JUNGLE_N64";
Stage[Stage["BATTLEFIELD"] = 31] = "BATTLEFIELD";
Stage[Stage["FINAL_DESTINATION"] = 32] = "FINAL_DESTINATION";
Stage[Stage["TARGET_TEST_MARIO"] = 33] = "TARGET_TEST_MARIO";
Stage[Stage["TARGET_TEST_CAPTAIN_FALCON"] = 34] = "TARGET_TEST_CAPTAIN_FALCON";
Stage[Stage["TARGET_TEST_YOUNG_LINK"] = 35] = "TARGET_TEST_YOUNG_LINK";
Stage[Stage["TARGET_TEST_DONKEY_KONG"] = 36] = "TARGET_TEST_DONKEY_KONG";
Stage[Stage["TARGET_TEST_DR_MARIO"] = 37] = "TARGET_TEST_DR_MARIO";
Stage[Stage["TARGET_TEST_FALCO"] = 38] = "TARGET_TEST_FALCO";
Stage[Stage["TARGET_TEST_FOX"] = 39] = "TARGET_TEST_FOX";
Stage[Stage["TARGET_TEST_ICE_CLIMBERS"] = 40] = "TARGET_TEST_ICE_CLIMBERS";
Stage[Stage["TARGET_TEST_KIRBY"] = 41] = "TARGET_TEST_KIRBY";
Stage[Stage["TARGET_TEST_BOWSER"] = 42] = "TARGET_TEST_BOWSER";
Stage[Stage["TARGET_TEST_LINK"] = 43] = "TARGET_TEST_LINK";
Stage[Stage["TARGET_TEST_LUIGI"] = 44] = "TARGET_TEST_LUIGI";
Stage[Stage["TARGET_TEST_MARTH"] = 45] = "TARGET_TEST_MARTH";
Stage[Stage["TARGET_TEST_MEWTWO"] = 46] = "TARGET_TEST_MEWTWO";
Stage[Stage["TARGET_TEST_NESS"] = 47] = "TARGET_TEST_NESS";
Stage[Stage["TARGET_TEST_PEACH"] = 48] = "TARGET_TEST_PEACH";
Stage[Stage["TARGET_TEST_PICHU"] = 49] = "TARGET_TEST_PICHU";
Stage[Stage["TARGET_TEST_PIKACHU"] = 50] = "TARGET_TEST_PIKACHU";
Stage[Stage["TARGET_TEST_JIGGLYPUFF"] = 51] = "TARGET_TEST_JIGGLYPUFF";
Stage[Stage["TARGET_TEST_SAMUS"] = 52] = "TARGET_TEST_SAMUS";
Stage[Stage["TARGET_TEST_SHEIK"] = 53] = "TARGET_TEST_SHEIK";
Stage[Stage["TARGET_TEST_YOSHI"] = 54] = "TARGET_TEST_YOSHI";
Stage[Stage["TARGET_TEST_ZELDA"] = 55] = "TARGET_TEST_ZELDA";
Stage[Stage["TARGET_TEST_GAME_AND_WATCH"] = 56] = "TARGET_TEST_GAME_AND_WATCH";
Stage[Stage["TARGET_TEST_ROY"] = 57] = "TARGET_TEST_ROY";
Stage[Stage["TARGET_TEST_GANONDORF"] = 58] = "TARGET_TEST_GANONDORF";
Stage[Stage["RACE_TO_THE_FINISH"] = 82] = "RACE_TO_THE_FINISH";
Stage[Stage["GRAB_THE_TROPHIES"] = 83] = "GRAB_THE_TROPHIES";
Stage[Stage["HOME_RUN_CONTEST"] = 84] = "HOME_RUN_CONTEST";
Stage[Stage["ALL_STAR_LOBBY"] = 85] = "ALL_STAR_LOBBY";
Stage[Stage["EVENT_ONE"] = 202] = "EVENT_ONE";
Stage[Stage["EVENT_EIGHTEEN"] = 203] = "EVENT_EIGHTEEN";
Stage[Stage["EVENT_THREE"] = 204] = "EVENT_THREE";
Stage[Stage["EVENT_FOUR"] = 205] = "EVENT_FOUR";
Stage[Stage["EVENT_FIVE"] = 206] = "EVENT_FIVE";
Stage[Stage["EVENT_SIX"] = 207] = "EVENT_SIX";
Stage[Stage["EVENT_SEVEN"] = 208] = "EVENT_SEVEN";
Stage[Stage["EVENT_EIGHT"] = 209] = "EVENT_EIGHT";
Stage[Stage["EVENT_NINE"] = 210] = "EVENT_NINE";
Stage[Stage["EVENT_TEN_PART_ONE"] = 211] = "EVENT_TEN_PART_ONE";
Stage[Stage["EVENT_ELEVEN"] = 212] = "EVENT_ELEVEN";
Stage[Stage["EVENT_TWELVE"] = 213] = "EVENT_TWELVE";
Stage[Stage["EVENT_THIRTEEN"] = 214] = "EVENT_THIRTEEN";
Stage[Stage["EVENT_FOURTEEN"] = 215] = "EVENT_FOURTEEN";
Stage[Stage["EVENT_THIRTY_SEVEN"] = 216] = "EVENT_THIRTY_SEVEN";
Stage[Stage["EVENT_SIXTEEN"] = 217] = "EVENT_SIXTEEN";
Stage[Stage["EVENT_SEVENTEEN"] = 218] = "EVENT_SEVENTEEN";
Stage[Stage["EVENT_TWO"] = 219] = "EVENT_TWO";
Stage[Stage["EVENT_NINETEEN"] = 220] = "EVENT_NINETEEN";
Stage[Stage["EVENT_TWENTY_PART_ONE"] = 221] = "EVENT_TWENTY_PART_ONE";
Stage[Stage["EVENT_TWENTY_ONE"] = 222] = "EVENT_TWENTY_ONE";
Stage[Stage["EVENT_TWENTY_TWO"] = 223] = "EVENT_TWENTY_TWO";
Stage[Stage["EVENT_TWENTY_SEVEN"] = 224] = "EVENT_TWENTY_SEVEN";
Stage[Stage["EVENT_TWENTY_FOUR"] = 225] = "EVENT_TWENTY_FOUR";
Stage[Stage["EVENT_TWENTY_FIVE"] = 226] = "EVENT_TWENTY_FIVE";
Stage[Stage["EVENT_TWENTY_SIX"] = 227] = "EVENT_TWENTY_SIX";
Stage[Stage["EVENT_TWENTY_THREE"] = 228] = "EVENT_TWENTY_THREE";
Stage[Stage["EVENT_TWENTY_EIGHT"] = 229] = "EVENT_TWENTY_EIGHT";
Stage[Stage["EVENT_TWENTY_NINE"] = 230] = "EVENT_TWENTY_NINE";
Stage[Stage["EVENT_THIRTY_PART_ONE"] = 231] = "EVENT_THIRTY_PART_ONE";
Stage[Stage["EVENT_THIRTY_ONE"] = 232] = "EVENT_THIRTY_ONE";
Stage[Stage["EVENT_THIRTY_TWO"] = 233] = "EVENT_THIRTY_TWO";
Stage[Stage["EVENT_THIRTY_THREE"] = 234] = "EVENT_THIRTY_THREE";
Stage[Stage["EVENT_THIRTY_FOUR"] = 235] = "EVENT_THIRTY_FOUR";
Stage[Stage["EVENT_FORTY_EIGHT"] = 236] = "EVENT_FORTY_EIGHT";
Stage[Stage["EVENT_THIRTY_SIX_PART_ONE"] = 237] = "EVENT_THIRTY_SIX_PART_ONE";
Stage[Stage["EVENT_FIFTEEN"] = 238] = "EVENT_FIFTEEN";
Stage[Stage["EVENT_THIRTY_EIGHT"] = 239] = "EVENT_THIRTY_EIGHT";
Stage[Stage["EVENT_THIRTY_NINE"] = 240] = "EVENT_THIRTY_NINE";
Stage[Stage["EVENT_FORTY_PART_ONE"] = 241] = "EVENT_FORTY_PART_ONE";
Stage[Stage["EVENT_FORTY_ONE"] = 242] = "EVENT_FORTY_ONE";
Stage[Stage["EVENT_FORTY_TWO"] = 243] = "EVENT_FORTY_TWO";
Stage[Stage["EVENT_FORTY_THREE"] = 244] = "EVENT_FORTY_THREE";
Stage[Stage["EVENT_FORTY_FOUR"] = 245] = "EVENT_FORTY_FOUR";
Stage[Stage["EVENT_FORTY_FIVE"] = 246] = "EVENT_FORTY_FIVE";
Stage[Stage["EVENT_FORTY_SIX"] = 247] = "EVENT_FORTY_SIX";
Stage[Stage["EVENT_FORTY_SEVEN"] = 248] = "EVENT_FORTY_SEVEN";
Stage[Stage["EVENT_THIRTY_FIVE"] = 249] = "EVENT_THIRTY_FIVE";
Stage[Stage["EVENT_FORTY_NINE_PART_ONE"] = 250] = "EVENT_FORTY_NINE_PART_ONE";
Stage[Stage["EVENT_FIFTY"] = 251] = "EVENT_FIFTY";
Stage[Stage["EVENT_FIFTY_ONE"] = 252] = "EVENT_FIFTY_ONE";
Stage[Stage["EVENT_TEN_PART_TWO"] = 253] = "EVENT_TEN_PART_TWO";
Stage[Stage["EVENT_TEN_PART_THREE"] = 254] = "EVENT_TEN_PART_THREE";
Stage[Stage["EVENT_TEN_PART_FOUR"] = 255] = "EVENT_TEN_PART_FOUR";
Stage[Stage["EVENT_TEN_PART_FIVE"] = 256] = "EVENT_TEN_PART_FIVE";
Stage[Stage["EVENT_TWENTY_PART_TWO"] = 257] = "EVENT_TWENTY_PART_TWO";
Stage[Stage["EVENT_TWENTY_PART_THREE"] = 258] = "EVENT_TWENTY_PART_THREE";
Stage[Stage["EVENT_TWENTY_PART_FOUR"] = 259] = "EVENT_TWENTY_PART_FOUR";
Stage[Stage["EVENT_TWENTY_PART_FIVE"] = 260] = "EVENT_TWENTY_PART_FIVE";
Stage[Stage["EVENT_THIRTY_PART_TWO"] = 261] = "EVENT_THIRTY_PART_TWO";
Stage[Stage["EVENT_THIRTY_PART_THREE"] = 262] = "EVENT_THIRTY_PART_THREE";
Stage[Stage["EVENT_THIRTY_PART_FOUR"] = 263] = "EVENT_THIRTY_PART_FOUR";
Stage[Stage["EVENT_FORTY_PART_TWO"] = 264] = "EVENT_FORTY_PART_TWO";
Stage[Stage["EVENT_FORTY_PART_THREE"] = 265] = "EVENT_FORTY_PART_THREE";
Stage[Stage["EVENT_FORTY_PART_FOUR"] = 266] = "EVENT_FORTY_PART_FOUR";
Stage[Stage["EVENT_FORTY_PART_FIVE"] = 267] = "EVENT_FORTY_PART_FIVE";
Stage[Stage["EVENT_FORTY_NINE_PART_TWO"] = 268] = "EVENT_FORTY_NINE_PART_TWO";
Stage[Stage["EVENT_FORTY_NINE_PART_THREE"] = 269] = "EVENT_FORTY_NINE_PART_THREE";
Stage[Stage["EVENT_FORTY_NINE_PART_FOUR"] = 270] = "EVENT_FORTY_NINE_PART_FOUR";
Stage[Stage["EVENT_FORTY_NINE_PART_FIVE"] = 271] = "EVENT_FORTY_NINE_PART_FIVE";
Stage[Stage["EVENT_FORTY_NINE_PART_SIX"] = 272] = "EVENT_FORTY_NINE_PART_SIX";
Stage[Stage["EVENT_THIRTY_SIX_PART_TWO"] = 273] = "EVENT_THIRTY_SIX_PART_TWO";
Stage[Stage["MULTI_MAN_MELEE"] = 285] = "MULTI_MAN_MELEE";
})(exports.Stage || (exports.Stage = {}));
exports.State = void 0;
(function (State) {
// Animation ID ranges
State[State["DAMAGE_START"] = 75] = "DAMAGE_START";
State[State["DAMAGE_END"] = 91] = "DAMAGE_END";
State[State["CAPTURE_START"] = 223] = "CAPTURE_START";
State[State["CAPTURE_END"] = 232] = "CAPTURE_END";
State[State["GUARD_START"] = 178] = "GUARD_START";
State[State["GUARD_END"] = 182] = "GUARD_END";
State[State["GROUNDED_CONTROL_START"] = 14] = "GROUNDED_CONTROL_START";
State[State["GROUNDED_CONTROL_END"] = 24] = "GROUNDED_CONTROL_END";
State[State["SQUAT_START"] = 39] = "SQUAT_START";
State[State["SQUAT_END"] = 41] = "SQUAT_END";
State[State["DOWN_START"] = 183] = "DOWN_START";
State[State["DOWN_END"] = 198] = "DOWN_END";
State[State["TECH_START"] = 199] = "TECH_START";
State[State["TECH_END"] = 204] = "TECH_END";
State[State["DYING_START"] = 0] = "DYING_START";
State[State["DYING_END"] = 10] = "DYING_END";
State[State["CONTROLLED_JUMP_START"] = 24] = "CONTROLLED_JUMP_START";
State[State["CONTROLLED_JUMP_END"] = 34] = "CONTROLLED_JUMP_END";
State[State["GROUND_ATTACK_START"] = 44] = "GROUND_ATTACK_START";
State[State["GROUND_ATTACK_END"] = 64] = "GROUND_ATTACK_END";
State[State["AERIAL_ATTACK_START"] = 65] = "AERIAL_ATTACK_START";
State[State["AERIAL_ATTACK_END"] = 74] = "AERIAL_ATTACK_END";
State[State["ATTACK_FTILT_START"] = 51] = "ATTACK_FTILT_START";
State[State["ATTACK_FTILT_END"] = 55] = "ATTACK_FTILT_END";
State[State["ATTACK_FSMASH_START"] = 58] = "ATTACK_FSMASH_START";
State[State["ATTACK_FSMASH_END"] = 62] = "ATTACK_FSMASH_END";
// Animation ID specific
State[State["ROLL_FORWARD"] = 233] = "ROLL_FORWARD";
State[State["ROLL_BACKWARD"] = 234] = "ROLL_BACKWARD";
State[State["SPOT_DODGE"] = 235] = "SPOT_DODGE";
State[State["AIR_DODGE"] = 236] = "AIR_DODGE";
State[State["ACTION_WAIT"] = 14] = "ACTION_WAIT";
State[State["ACTION_DASH"] = 20] = "ACTION_DASH";
State[State["ACTION_KNEE_BEND"] = 24] = "ACTION_KNEE_BEND";
State[State["GUARD_ON"] = 178] = "GUARD_ON";
State[State["TECH_MISS_UP"] = 183] = "TECH_MISS_UP";
State[State["JAB_RESET_UP"] = 185] = "JAB_RESET_UP";
State[State["TECH_MISS_DOWN"] = 191] = "TECH_MISS_DOWN";
State[State["JAB_RESET_DOWN"] = 193] = "JAB_RESET_DOWN";
State[State["NEUTRAL_TECH"] = 199] = "NEUTRAL_TECH";
State[State["FORWARD_TECH"] = 200] = "FORWARD_TECH";
State[State["BACKWARD_TECH"] = 201] = "BACKWARD_TECH";
State[State["WALL_TECH"] = 202] = "WALL_TECH";
State[State["MISSED_WALL_TECH"] = 247] = "MISSED_WALL_TECH";
State[State["DASH"] = 20] = "DASH";
State[State["TURN"] = 18] = "TURN";
State[State["LANDING_FALL_SPECIAL"] = 43] = "LANDING_FALL_SPECIAL";
State[State["JUMP_FORWARD"] = 25] = "JUMP_FORWARD";
State[State["JUMP_BACKWARD"] = 26] = "JUMP_BACKWARD";
State[State["FALL_FORWARD"] = 30] = "FALL_FORWARD";
State[State["FALL_BACKWARD"] = 31] = "FALL_BACKWARD";
State[State["GRAB"] = 212] = "GRAB";
State[State["DASH_GRAB"] = 214] = "DASH_GRAB";
State[State["GRAB_WAIT"] = 216] = "GRAB_WAIT";
State[State["PUMMEL"] = 217] = "PUMMEL";
State[State["CLIFF_CATCH"] = 252] = "CLIFF_CATCH";
State[State["THROW_UP"] = 221] = "THROW_UP";
State[State["THROW_FORWARD"] = 219] = "THROW_FORWARD";
State[State["THROW_DOWN"] = 222] = "THROW_DOWN";
State[State["THROW_BACK"] = 220] = "THROW_BACK";
State[State["DAMAGE_FALL"] = 38] = "DAMAGE_FALL";
State[State["ATTACK_JAB1"] = 44] = "ATTACK_JAB1";
State[State["ATTACK_JAB2"] = 45] = "ATTACK_JAB2";
State[State["ATTACK_JAB3"] = 46] = "ATTACK_JAB3";
State[State["ATTACK_JABM"] = 47] = "ATTACK_JABM";
State[State["ATTACK_DASH"] = 50] = "ATTACK_DASH";
State[State["ATTACK_UTILT"] = 56] = "ATTACK_UTILT";
State[State["ATTACK_DTILT"] = 57] = "ATTACK_DTILT";
State[State["ATTACK_USMASH"] = 63] = "ATTACK_USMASH";
State[State["ATTACK_DSMASH"] = 64] = "ATTACK_DSMASH";
State[State["AERIAL_NAIR"] = 65] = "AERIAL_NAIR";
State[State["AERIAL_FAIR"] = 66] = "AERIAL_FAIR";
State[State["AERIAL_BAIR"] = 67] = "AERIAL_BAIR";
State[State["AERIAL_UAIR"] = 68] = "AERIAL_UAIR";
State[State["AERIAL_DAIR"] = 69] = "AERIAL_DAIR";
// Weird GnW IDs
State[State["GNW_JAB1"] = 341] = "GNW_JAB1";
State[State["GNW_JABM"] = 342] = "GNW_JABM";
State[State["GNW_DTILT"] = 345] = "GNW_DTILT";
State[State["GNW_FSMASH"] = 346] = "GNW_FSMASH";
State[State["GNW_NAIR"] = 347] = "GNW_NAIR";
State[State["GNW_BAIR"] = 348] = "GNW_BAIR";
State[State["GNW_UAIR"] = 349] = "GNW_UAIR";
// Peach FSMASH ID
// FSMASH1 = Golf Club, FSMASH2 = Frying Pan, FSMASH3 = Tennis Racket
State[State["PEACH_FSMASH1"] = 349] = "PEACH_FSMASH1";
State[State["PEACH_FSMASH2"] = 350] = "PEACH_FSMASH2";
State[State["PEACH_FSMASH3"] = 351] = "PEACH_FSMASH3";
// Command Grabs
State[State["BARREL_WAIT"] = 293] = "BARREL_WAIT";
State[State["COMMAND_GRAB_RANGE1_START"] = 266] = "COMMAND_GRAB_RANGE1_START";
State[State["COMMAND_GRAB_RANGE1_END"] = 304] = "COMMAND_GRAB_RANGE1_END";
State[State["COMMAND_GRAB_RANGE2_START"] = 327] = "COMMAND_GRAB_RANGE2_START";
State[State["COMMAND_GRAB_RANGE2_END"] = 338] = "COMMAND_GRAB_RANGE2_END";
})(exports.State || (exports.State = {}));
const Timers = {
PUNISH_RESET_FRAMES: 45,
RECOVERY_RESET_FRAMES: 45,
COMBO_STRING_RESET_FRAMES: 45
};
function getSinglesPlayerPermutationsFromSettings(settings) {
if (!settings || settings.players.length !== 2) {
// Only return opponent indices for singles
return [];
}
return [{
playerIndex: settings.players[0].playerIndex,
opponentIndex: settings.players[1].playerIndex
}, {
playerIndex: settings.players[1].playerIndex,
opponentIndex: settings.players[0].playerIndex
}];
}
function didLoseStock(frame, prevFrame) {
if (!frame || !prevFrame) {
return false;
}
return prevFrame.stocksRemaining - frame.stocksRemaining > 0;
}
function isInControl(state) {
const ground = state >= exports.State.GROUNDED_CONTROL_START && state <= exports.State.GROUNDED_CONTROL_END;
const squat = state >= exports.State.SQUAT_START && state <= exports.State.SQUAT_END;
const groundAttack = state > exports.State.GROUND_ATTACK_START && state <= exports.State.GROUND_ATTACK_END;
const isGrab = state === exports.State.GRAB;
// TODO: Add grounded b moves?
return ground || squat || groundAttack || isGrab;
}
function isTeching(state) {
return state >= exports.State.TECH_START && state <= exports.State.TECH_END;
}
function isDown(state) {
return state >= exports.State.DOWN_START && state <= exports.State.DOWN_END;
}
function isDamaged(state) {
return state >= exports.State.DAMAGE_START && state <= exports.State.DAMAGE_END || state === exports.State.DAMAGE_FALL || state === exports.State.JAB_RESET_UP || state === exports.State.JAB_RESET_DOWN;
}
function isGrabbed(state) {
return state >= exports.State.CAPTURE_START && state <= exports.State.CAPTURE_END;
}
// TODO: Find better implementation of 3 seperate ranges
function isCommandGrabbed(state) {
return (state >= exports.State.COMMAND_GRAB_RANGE1_START && state <= exports.State.COMMAND_GRAB_RANGE1_END || state >= exports.State.COMMAND_GRAB_RANGE2_START && state <= exports.State.COMMAND_GRAB_RANGE2_END) && state !== exports.State.BARREL_WAIT;
}
function isDead(state) {
return state >= exports.State.DYING_START && state <= exports.State.DYING_END;
}
function calcDamageTaken(frame, prevFrame) {
var _frame$percent, _prevFrame$percent;
const percent = (_frame$percent = frame.percent) != null ? _frame$percent : 0;
const prevPercent = (_prevFrame$percent = prevFrame.percent) != null ? _prevFrame$percent : 0;
return percent - prevPercent;
}
// Frame pattern that indicates a dash dance turn was executed
const dashDanceAnimations = [exports.State.DASH, exports.State.TURN, exports.State.DASH];
class ActionsComputer {
constructor() {
this.playerPermutations = new Array();
this.state = new Map();
}
setup(settings) {
this.state = new Map();
this.playerPermutations = getSinglesPlayerPermutationsFromSettings(settings);
this.playerPermutations.forEach(indices => {
const playerCounts = {
playerIndex: indices.playerIndex,
wavedashCount: 0,
wavelandCount: 0,
airDodgeCount: 0,
dashDanceCount: 0,
spotDodgeCount: 0,
ledgegrabCount: 0,
rollCount: 0,
lCancelCount: {
success: 0,
fail: 0
},
attackCount: {
jab1: 0,
jab2: 0,
jab3: 0,
jabm: 0,
dash: 0,
ftilt: 0,
utilt: 0,
dtilt: 0,
fsmash: 0,
usmash: 0,
dsmash: 0,
nair: 0,
fair: 0,
bair: 0,
uair: 0,
dair: 0
},
grabCount: {
success: 0,
fail: 0
},
throwCount: {
up: 0,
forward: 0,
back: 0,
down: 0
},
groundTechCount: {
// tech away/in are in reference to the opponents position and not the stage
away: 0,
in: 0,
neutral: 0,
fail: 0
},
wallTechCount: {
success: 0,
fail: 0
}
};
const playerState = {
playerCounts: playerCounts,
animations: [],
actionFrameCounters: []
};
this.state.set(indices, playerState);
});
}
processFrame(frame) {
this.playerPermutations.forEach(indices => {
const state = this.state.get(indices);
if (state) {
handleActionCompute(state, indices, frame);
}
});
}
fetch() {
return Array.from(this.state.values()).map(val => val.playerCounts);
}
}
function isMissGroundTech(animation) {
return animation === exports.State.TECH_MISS_DOWN || animation === exports.State.TECH_MISS_UP;
}
function isRolling(animation) {
return animation === exports.State.ROLL_BACKWARD || animation === exports.State.ROLL_FORWARD;
}
function isGrabAction(animation) {
// Includes Grab pull, wait, pummel, and throws
return animation > exports.State.GRAB && animation <= exports.State.THROW_DOWN && animation !== exports.State.DASH_GRAB;
}
function isGrabbing(animation) {
return animation === exports.State.GRAB || animation === exports.State.DASH_GRAB;
}
function isAerialAttack(animation) {
return animation >= exports.State.AERIAL_ATTACK_START && animation <= exports.State.AERIAL_ATTACK_END;
}
function isForwardTilt(animation) {
return animation >= exports.State.ATTACK_FTILT_START && animation <= exports.State.ATTACK_FTILT_END;
}
function isForwardSmash(animation) {
return animation >= exports.State.ATTACK_FSMASH_START && animation <= exports.State.ATTACK_FSMASH_END;
}
function handleActionCompute(state, indices, frame) {
const playerFrame = frame.players[indices.playerIndex].post;
const opponentFrame = frame.players[indices.opponentIndex].post;
const incrementCount = (field, condition) => {
if (!condition) {
return;
}
const current = get(state.playerCounts, field, 0);
set(state.playerCounts, field, current + 1);
};
// Manage animation state
const currentAnimation = playerFrame.actionStateId;
state.animations.push(currentAnimation);
const currentFrameCounter = playerFrame.actionStateCounter;
state.actionFrameCounters.push(currentFrameCounter);
// Grab last 3 frames
const last3Frames = state.animations.slice(-3);
const prevAnimation = last3Frames[last3Frames.length - 2];
const prevFrameCounter = state.actionFrameCounters[state.actionFrameCounters.length - 2];
// New action if new animation or frame counter goes back down (repeated action)
const isNewAction = currentAnimation !== prevAnimation || prevFrameCounter > currentFrameCounter;
if (!isNewAction) {
return;
}
// Increment counts based on conditions
const didDashDance = isEqual(last3Frames, dashDanceAnimations);
incrementCount("dashDanceCount", didDashDance);
incrementCount("rollCount", isRolling(currentAnimation));
incrementCount("spotDodgeCount", currentAnimation === exports.State.SPOT_DODGE);
incrementCount("airDodgeCount", currentAnimation === exports.State.AIR_DODGE);
incrementCount("ledgegrabCount", currentAnimation === exports.State.CLIFF_CATCH);
// Grabs
incrementCount("grabCount.success", isGrabbing(prevAnimation) && isGrabAction(currentAnimation));
incrementCount("grabCount.fail", isGrabbing(prevAnimation) && !isGrabAction(currentAnimation));
if (currentAnimation === exports.State.DASH_GRAB && prevAnimation === exports.State.ATTACK_DASH) {
state.playerCounts.attackCount.dash -= 1; // subtract from dash attack if boost grab
}
// Basic attacks
incrementCount("attackCount.jab1", currentAnimation === exports.State.ATTACK_JAB1);
incrementCount("attackCount.jab2", currentAnimation === exports.State.ATTACK_JAB2);
incrementCount("attackCount.jab3", currentAnimation === exports.State.ATTACK_JAB3);
incrementCount("attackCount.jabm", currentAnimation === exports.State.ATTACK_JABM);
incrementCount("attackCount.dash", currentAnimation === exports.State.ATTACK_DASH);
incrementCount("attackCount.ftilt", isForwardTilt(currentAnimation));
incrementCount("attackCount.utilt", currentAnimation === exports.State.ATTACK_UTILT);
incrementCount("attackCount.dtilt", currentAnimation === exports.State.ATTACK_DTILT);
incrementCount("attackCount.fsmash", isForwardSmash(currentAnimation));
incrementCount("attackCount.usmash", currentAnimation === exports.State.ATTACK_USMASH);
incrementCount("attackCount.dsmash", currentAnimation === exports.State.ATTACK_DSMASH);
incrementCount("attackCount.nair", currentAnimation === exports.State.AERIAL_NAIR);
incrementCount("attackCount.fair", currentAnimation === exports.State.AERIAL_FAIR);
incrementCount("attackCount.bair", currentAnimation === exports.State.AERIAL_BAIR);
incrementCount("attackCount.uair", currentAnimation === exports.State.AERIAL_UAIR);
incrementCount("attackCount.dair", currentAnimation === exports.State.AERIAL_DAIR);
// GnW is weird and has unique IDs for some moves
if (playerFrame.internalCharacterId === 0x18) {
incrementCount("attackCount.jab1", currentAnimation === exports.State.GNW_JAB1);
incrementCount("attackCount.jabm", currentAnimation === exports.State.GNW_JABM);
incrementCount("attackCount.dtilt", currentAnimation === exports.State.GNW_DTILT);
incrementCount("attackCount.fsmash", currentAnimation === exports.State.GNW_FSMASH);
incrementCount("attackCount.nair", currentAnimation === exports.State.GNW_NAIR);
incrementCount("attackCount.bair", currentAnimation === exports.State.GNW_BAIR);
incrementCount("attackCount.uair", currentAnimation === exports.State.GNW_UAIR);
}
// Peach is also weird and has a unique ID for her fsmash
// FSMASH1 = Golf Club, FSMASH2 = Frying Pan, FSMASH3 = Tennis Racket
if (playerFrame.internalCharacterId === 0x09) {
incrementCount("attackCount.fsmash", currentAnimation === exports.State.PEACH_FSMASH1);
incrementCount("attackCount.fsmash", currentAnimation === exports.State.PEACH_FSMASH2);
incrementCount("attackCount.fsmash", currentAnimation === exports.State.PEACH_FSMASH3);
}
// Throws
incrementCount("throwCount.up", currentAnimation === exports.State.THROW_UP);
incrementCount("throwCount.forward", currentAnimation === exports.State.THROW_FORWARD);
incrementCount("throwCount.down", currentAnimation === exports.State.THROW_DOWN);
incrementCount("throwCount.back", currentAnimation === exports.State.THROW_BACK);
// Techs
const opponentDir = playerFrame.positionX > opponentFrame.positionX ? -1 : 1;
const facingOpponent = playerFrame.facingDirection === opponentDir;
incrementCount("groundTechCount.fail", isMissGroundTech(currentAnimation));
incrementCount("groundTechCount.in", currentAnimation === exports.State.FORWARD_TECH && facingOpponent);
incrementCount("groundTechCount.in", currentAnimation === exports.State.BACKWARD_TECH && !facingOpponent);
incrementCount("groundTechCount.neutral", currentAnimation === exports.State.NEUTRAL_TECH);
incrementCount("groundTechCount.away", currentAnimation === exports.State.BACKWARD_TECH && facingOpponent);
incrementCount("groundTechCount.away", currentAnimation === exports.State.FORWARD_TECH && !facingOpponent);
incrementCount("wallTechCount.success", currentAnimation === exports.State.WALL_TECH);
incrementCount("wallTechCount.fail", currentAnimation === exports.State.MISSED_WALL_TECH);
if (isAerialAttack(currentAnimation)) {
incrementCount("lCancelCount.success", playerFrame.lCancelStatus === 1);
incrementCount("lCancelCount.fail", playerFrame.lCancelStatus === 2);
}
// Handles wavedash detection (and waveland)
handleActionWavedash(state.playerCounts, state.animations);
}
function handleActionWavedash(counts, animations) {
const currentAnimation = last(animations);
const prevAnimation = animations[animations.length - 2];
const isSpecialLanding = currentAnimation === exports.State.LANDING_FALL_SPECIAL;
const isAcceptablePrevious = isWavedashInitiationAnimation(prevAnimation);
const isPossibleWavedash = isSpecialLanding && isAcceptablePrevious;
if (!isPossibleWavedash) {
return;
}
// Here we special landed, it might be a wavedash, let's check
// We grab the last 8 frames here because that should be enough time to execute a
// wavedash. This number could be tweaked if we find false negatives
const recentFrames = animations.slice(-8);
const recentAnimations = keyBy(recentFrames, animation => animation);
if (size(recentAnimations) === 2 && recentAnimations[exports.State.AIR_DODGE]) {
// If the only other animation is air dodge, this might be really late to the point
// where it was actually an air dodge. Air dodge animation is really long
return;
}
if (recentAnimations[exports.State.AIR_DODGE]) {
// If one of the recent animations was an air dodge, let's remove that from the
// air dodge counter, we don't want to count air dodges used to wavedash/land
counts.airDodgeCount -= 1;
}
if (recentAnimations[exports.State.ACTION_KNEE_BEND]) {
// If a jump was started recently, we will consider this a wavedash
counts.wavedashCount += 1;
} else {
// If there was no jump recently, this is a waveland
counts.wavelandCount += 1;
}
}
function isWavedashInitiationAnimation(animation) {
if (animation === exports.State.AIR_DODGE) {
return true;
}
const isAboveMin = animation >= exports.State.CONTROLLED_JUMP_START;
const isBelowMax = animation <= exports.State.CONTROLLED_JUMP_END;
return isAboveMin && isBelowMax;
}
var ComboEvent;
(function (ComboEvent) {
ComboEvent["COMBO_START"] = "COMBO_START";
ComboEvent["COMBO_EXTEND"] = "COMBO_EXTEND";
ComboEvent["COMBO_END"] = "COMBO_END";
})(ComboEvent || (ComboEvent = {}));
class ComboComputer extends events.EventEmitter {
constructor(...args) {
super(...args);
this.playerPermutations = new Array();
this.state = new Map();
this.combos = new Array();
this.settings = null;
}
setup(settings) {
// Reset the state
this.settings = settings;
this.state = new Map();
this.combos = [];
this.playerPermutations = getSinglesPlayerPermutationsFromSettings(settings);
this.playerPermutations.forEach(indices => {
const playerState = {
combo: null,
move: null,
resetCounter: 0,
lastHitAnimation: null,
event: null
};
this.state.set(indices, playerState);
});
}
processFrame(frame, allFrames) {
this.playerPermutations.forEach(indices => {
const state = this.state.get(indices);
if (state) {
handleComboCompute(allFrames, state, indices, frame, this.combos);
// Emit an event for the new combo
if (state.event !== null) {
this.emit(state.event, {
combo: last(this.combos),
settings: this.settings
});
state.event = null;
}
}
});
}
fetch() {
return this.combos;
}
}
function handleComboCompute(frames, state, indices, frame, combos) {
const currentFrameNumber = frame.frame;
const playerFrame = frame.players[indices.playerIndex].post;
const opponentFrame = frame.players[indices.opponentIndex].post;
const prevFrameNumber = currentFrameNumber - 1;
let prevPlayerFrame = null;
let prevOpponentFrame = null;
if (frames[prevFrameNumber]) {
prevPlayerFrame = frames[prevFrameNumber].players[indices.playerIndex].post;
prevOpponentFrame = frames[prevFrameNumber].players[indices.opponentIndex].post;
}
const oppActionStateId = opponentFrame.actionStateId;
const opntIsDamaged = isDamaged(oppActionStateId);
const opntIsGrabbed = isGrabbed(oppActionStateId);
const opntIsCommandGrabbed = isCommandGrabbed(oppActionStateId);
const opntDamageTaken = prevOpponentFrame ? calcDamageTaken(opponentFrame, prevOpponentFrame) : 0;
// Keep track of whether actionState changes after a hit. Used to compute move count
// When purely using action state there was a bug where if you did two of the same
// move really fast (such as ganon's jab), it would count as one move. Added
// the actionStateCounter at this point which counts the number of frames since
// an animation started. Should be more robust, for old files it should always be
// null and null < null = false
const actionChangedSinceHit = playerFrame.actionStateId !== state.lastHitAnimation;
const actionCounter = playerFrame.actionStateCounter;
const prevActionCounter = prevPlayerFrame ? prevPlayerFrame.actionStateCounter : 0;
const actionFrameCounterReset = actionCounter < prevActionCounter;
if (actionChangedSinceHit || actionFrameCounterReset) {
state.lastHitAnimation = null;
}
// If opponent took damage and was put in some kind of stun this frame, either
// start a combo or count the moves for the existing combo
if (opntIsDamaged || opntIsGrabbed || opntIsCommandGrabbed) {
let comboStarted = false;
if (!state.combo) {
var _prevOpponentFrame$pe, _opponentFrame$percen;
state.combo = {
playerIndex: indices.opponentIndex,
startFrame: currentFrameNumber,
endFrame: null,
startPercent: prevOpponentFrame ? (_prevOpponentFrame$pe = prevOpponentFrame.percent) != null ? _prevOpponentFrame$pe : 0 : 0,
currentPercent: (_opponentFrame$percen = opponentFrame.percent) != null ? _opponentFrame$percen : 0,
endPercent: null,
moves: [],
didKill: false,
lastHitBy: indices.playerIndex
};
combos.push(state.combo);
// Track whether this is a new combo or not
comboStarted = true;
}
if (opntDamageTaken) {
// If animation of last hit has been cleared that means this is a new move. This
// prevents counting multiple hits from the same move such as fox's drill
if (state.lastHitAnimation === null) {
state.move = {
playerIndex: indices.playerIndex,
frame: currentFrameNumber,
moveId: playerFrame.lastAttackLanded,
hitCount: 0,
damage: 0
};
state.combo.moves.push(state.move);
// Make sure we don't overwrite the START event
if (!comboStarted) {
state.event = ComboEvent.COMBO_EXTEND;
}
}
if (state.move) {
state.move.hitCount += 1;
state.move.damage += opntDamageTaken;
}
// Store previous frame animation to consider the case of a trade, the previous
// frame should always be the move that actually connected... I hope
state.lastHitAnimation = prevPlayerFrame ? prevPlayerFrame.actionStateId : null;
}
if (comboStarted) {
state.event = ComboEvent.COMBO_START;
}
}
if (!state.combo) {
// The rest of the function handles combo termination logic, so if we don't
// have a combo started, there is no need to continue
return;
}
const opntIsTeching = isTeching(oppActionStateId);
const opntIsDowned = isDown(oppActionStateId);
const opntDidLoseStock = prevOpponentFrame && didLoseStock(opponentFrame, prevOpponentFrame);
const opntIsDying = isDead(oppActionStateId);
// Update percent if opponent didn't lose stock
if (!opntDidLoseStock) {
var _opponentFrame$percen2;
state.combo.currentPercent = (_opponentFrame$percen2 = opponentFrame.percent) != null ? _opponentFrame$percen2 : 0;
}
if (opntIsDamaged || opntIsGrabbed || opntIsCommandGrabbed || opntIsTeching || opntIsDowned || opntIsDying) {
// If opponent got grabbed or damaged, reset the reset counter
state.resetCounter = 0;
} else {
state.resetCounter += 1;
}
let shouldTerminate = false;
// Termination condition 1 - player kills opponent
if (opntDidLoseStock) {
state.combo.didKill = true;
shouldTerminate = true;
}
// Termination condition 2 - combo resets on time
if (state.resetCounter > Timers.COMBO_STRING_RESET_FRAMES) {
shouldTerminate = true;
}
// If combo should terminate, mark the end states and add it to list
if (shouldTerminate) {
var _prevOpponentFrame$pe2;
state.combo.endFrame = playerFrame.frame;
state.combo.endPercent = prevOpponentFrame ? (_prevOpponentFrame$pe2 = prevOpponentFrame.percent) != null ? _prevOpponentFrame$pe2 : 0 : 0;
state.event = ComboEvent.COMBO_END;
state.combo = null;
state.move = null;
}
}
class ConversionComputer extends events.EventEmitter {
constructor() {
super();
this.playerPermutations = new Array();
this.conversions = new Array();
this.state = new Map();
this.metadata = void 0;
this.settings = null;
this.metadata = {
lastEndFrameByOppIdx: {}
};
}
setup(settings) {
// Reset the state
this.playerPermutations = getSinglesPlayerPermutationsFromSettings(settings);
this.conversions = [];
this.state = new Map();
this.metadata = {
lastEndFrameByOppIdx: {}
};
this.settings = settings;
this.playerPermutations.forEach(indices => {
const playerState = {
conversion: null,
move: null,
resetCounter: 0,
lastHitAnimation: null
};
this.state.set(indices, playerState);
});
}
processFrame(frame, allFrames) {
this.playerPermutations.forEach(indices => {
const state = this.state.get(indices);
if (state) {
const terminated = handleConversionCompute(allFrames, state, indices, frame, this.conversions);
if (terminated) {
this.emit("CONVERSION", {
combo: last(this.conversions),
settings: this.settings
});
}
}
});
}
fetch() {
this._populateConversionTypes();
return this.conversions;
}
_populateConversionTypes() {
// Post-processing step: set the openingTypes
const conversionsToHandle = filter(this.conversions, conversion => {
return conversion.openingType === "unknown";
});
// Group new conversions by startTime and sort
const groupedConversions = groupBy(conversionsToHandle, "startFrame");
const sortedConversions = orderBy(groupedConversions, conversions => get(conversions, [0, "startFrame"]));
// Set the opening types on the conversions we need to handle
sortedConversions.forEach(conversions => {
const isTrade = conversions.length >= 2;
conversions.forEach(conversion => {
// Set end frame for this conversion
this.metadata.lastEndFrameByOppIdx[conversion.playerIndex] = conversion.endFrame;
if (isTrade) {
// If trade, just short-circuit
conversion.openingType = "trade";
return;
}
// If not trade, check the opponent endFrame
const lastMove = last(conversion.moves);
const oppEndFrame = this.metadata.lastEndFrameByOppIdx[lastMove ? lastMove.playerIndex : conversion.playerIndex];
const isCounterAttack = oppEndFrame && oppEndFrame > conversion.startFrame;
conversion.openingType = isCounterAttack ? "counter-attack" : "neutral-win";
});
});
}
}
function handleConversionCompute(frames, state, indices, frame, conversions) {
const currentFrameNumber = frame.frame;
const playerFrame = frame.players[indices.playerIndex].post;
const opponentFrame = frame.players[indices.opponentIndex].post;
const prevFrameNumber = currentFrameNumber - 1;
let prevPlayerFrame = null;
let prevOpponentFrame = null;
if (frames[prevFrameNumber]) {
prevPlayerFrame = frames[prevFrameNumber].players[indices.playerIndex].post;
prevOpponentFrame = frames[prevFrameNumber].players[indices.opponentIndex].post;
}
const oppActionStateId = opponentFrame.actionStateId;
const opntIsDamaged = isDamaged(oppActionStateId);
const opntIsGrabbed = isGrabbed(oppActionStateId);
const opntIsCommandGrabbed = isCommandGrabbed(oppActionStateId);
const opntDamageTaken = prevOpponentFrame ? calcDamageTaken(opponentFrame, prevOpponentFrame) : 0;
// Keep track of whether actionState changes after a hit. Used to compute move count
// When purely using action state there was a bug where if you did two of the same
// move really fast (such as ganon's jab), it would count as one move. Added
// the actionStateCounter at this point which counts the number of frames since
// an animation started. Should be more robust, for old files it should always be
// null and null < null = false
const actionChangedSinceHit = playerFrame.actionStateId !== state.lastHitAnimation;
const actionCounter = playerFrame.actionStateCounter;
const prevActionCounter = prevPlayerFrame ? prevPlayerFrame.actionStateCou