"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var state_exports = {}; __export(state_exports, { State: () => State }); module.exports = __toCommonJS(state_exports); var import_battle = require("./battle"); var import_dex = require("./dex"); var import_field = require("./field"); var import_pokemon = require("./pokemon"); var import_prng = require("./prng"); var import_side = require("./side"); /** * Simulator State * Pokemon Showdown - http://pokemonshowdown.com/ * * Helper functions for serializing Battle instances to JSON and back. * * (You might also consider using input logs instead.) * * @license MIT */ const POSITIONS = "abcdefghijklmnopqrstuvwx"; const BATTLE = /* @__PURE__ */ new Set([ "dex", "gen", "ruleTable", "id", "log", "inherit", "format", "teamGenerator", "HIT_SUBSTITUTE", "NOT_FAIL", "FAIL", "SILENT_FAIL", "field", "sides", "prng", "hints", "deserialized", "queue", "actions" ]); const FIELD = /* @__PURE__ */ new Set(["id", "battle"]); const SIDE = /* @__PURE__ */ new Set(["battle", "team", "pokemon", "choice", "activeRequest"]); const POKEMON = /* @__PURE__ */ new Set([ "side", "battle", "set", "name", "fullname", "id", "happiness", "level", "pokeball", "baseMoveSlots" ]); const CHOICE = /* @__PURE__ */ new Set(["switchIns"]); const ACTIVE_MOVE = /* @__PURE__ */ new Set(["move"]); const State = new class { serializeBattle(battle) { const state = this.serialize(battle, BATTLE, battle); state.field = this.serializeField(battle.field); state.sides = new Array(battle.sides.length); for (const [i, side] of battle.sides.entries()) { state.sides[i] = this.serializeSide(side); } state.prng = battle.prng.getSeed(); state.hints = Array.from(battle.hints); state.log = battle.log; state.queue = this.serializeWithRefs(battle.queue.list, battle); state.formatid = battle.format.id; return state; } // Deserialization can only really be done on the root Battle object as // the leaf nodes like Side or Pokemon contain backreferences to Battle // but don't contain the information to fill it in because the cycles in // the graph have been serialized as references. Once deserialzized, the // Battle can then be restarted (and provided with a `send` function for // receiving updates). deserializeBattle(serialized) { const state = typeof serialized === "string" ? JSON.parse(serialized) : serialized; const options = { formatid: state.formatid, seed: state.prngSeed, rated: state.rated, debug: state.debugMode, // We need to tell the Battle that we're creating that it's been // deserialized so that it allows us to populate it correctly and // doesn't attempt to start playing out until we're ready. deserialized: true, strictChoices: state.strictChoices }; for (const side of state.sides) { const team = side.team.split(side.team.length > 9 ? "," : ""); options[side.id] = { name: side.name, avatar: side.avatar, team: team.map((p) => side.pokemon[Number(p) - 1].set) }; } const battle = new import_battle.Battle(options); for (const [i, s] of state.sides.entries()) { const side = battle.sides[i]; const ordered = new Array(side.pokemon.length); const team = s.team.split(s.team.length > 9 ? "," : ""); for (const [j, pos] of team.entries()) { ordered[Number(pos) - 1] = side.pokemon[j]; } side.pokemon = ordered; } this.deserialize(state, battle, BATTLE, battle); this.deserializeField(state.field, battle.field); let activeRequests = false; for (const [i, side] of state.sides.entries()) { this.deserializeSide(side, battle.sides[i]); activeRequests = activeRequests || side.activeRequest === void 0; } if (activeRequests) { const requests = battle.getRequests(battle.requestState); for (const [i, side] of state.sides.entries()) { battle.sides[i].activeRequest = side.activeRequest === null ? null : requests[i]; } } battle.prng = new import_prng.PRNG(state.prng); const queue = this.deserializeWithRefs(state.queue, battle); battle.queue.list = queue; battle.hints = new Set(state.hints); battle.log = state.log; return battle; } // Direct comparsions of serialized state will be flakey as the timestamp // protocol message |t:| can diverge between two different runs over the same state. // State must first be normalized before it is comparable. normalize(state) { state.log = this.normalizeLog(state.log); return state; } normalizeLog(log) { if (!log) return log; const normalized = (typeof log === "string" ? log.split("\n") : log).map((line) => line.startsWith(`|t:|`) ? `|t:|` : line); return typeof log === "string" ? normalized.join("\n") : normalized; } serializeField(field) { return this.serialize(field, FIELD, field.battle); } deserializeField(state, field) { this.deserialize(state, field, FIELD, field.battle); } serializeSide(side) { const state = this.serialize(side, SIDE, side.battle); state.pokemon = new Array(side.pokemon.length); const team = new Array(side.pokemon.length); for (const [i, pokemon] of side.pokemon.entries()) { state.pokemon[i] = this.serializePokemon(pokemon); team[side.team.indexOf(pokemon.set)] = i + 1; } state.team = team.join(team.length > 9 ? "," : ""); state.choice = this.serializeChoice(side.choice, side.battle); if (side.activeRequest === null) state.activeRequest = null; return state; } deserializeSide(state, side) { this.deserialize(state, side, SIDE, side.battle); for (const [i, pokemon] of state.pokemon.entries()) { this.deserializePokemon(pokemon, side.pokemon[i]); } this.deserializeChoice(state.choice, side.choice, side.battle); } serializePokemon(pokemon) { const state = this.serialize(pokemon, POKEMON, pokemon.battle); state.set = pokemon.set; if (pokemon.baseMoveSlots.length !== pokemon.moveSlots.length || !pokemon.baseMoveSlots.every((ms, i) => ms === pokemon.moveSlots[i])) { state.baseMoveSlots = this.serializeWithRefs(pokemon.baseMoveSlots, pokemon.battle); } return state; } deserializePokemon(state, pokemon) { this.deserialize(state, pokemon, POKEMON, pokemon.battle); pokemon.set = state.set; let baseMoveSlots; if (state.baseMoveSlots) { baseMoveSlots = this.deserializeWithRefs(state.baseMoveSlots, pokemon.battle); for (const [i, baseMoveSlot] of baseMoveSlots.entries()) { const moveSlot = pokemon.moveSlots[i]; if (moveSlot.id === baseMoveSlot.id && !moveSlot.virtual) { baseMoveSlots[i] = moveSlot; } } } else { baseMoveSlots = pokemon.moveSlots.slice(); } pokemon.baseMoveSlots = baseMoveSlots; if (state.showCure === void 0) pokemon.showCure = void 0; } serializeChoice(choice, battle) { const state = this.serialize(choice, CHOICE, battle); state.switchIns = Array.from(choice.switchIns); return state; } deserializeChoice(state, choice, battle) { this.deserialize(state, choice, CHOICE, battle); choice.switchIns = new Set(state.switchIns); } // Simply looking for a 'hit' field to determine if an object is an ActiveMove or not seems // pretty fragile, but its no different than what the simulator is doing. We go further and // also check if the object has an 'id', as that's what we will intrepret as the Move. isActiveMove(obj) { return obj.hasOwnProperty("hit") && (obj.hasOwnProperty("id") || obj.hasOwnProperty("move")); } // ActiveMove is somewhat problematic (#5415) as it sometimes extends a Move and adds on // some mutable fields. We'd like to avoid displaying all the readonly fields of Move // (which in theory should not be changed by the ActiveMove...), so we collapse them // into a 'move: [Move:...]' reference. If isActiveMove returns a false positive *and* // and object contains an 'id' field matching a Move *and* it contains fields with the // same name as said Move then we'll miss them during serialization and won't // deserialize properly. This is unlikely to be the case, and would probably indicate // a bug in the simulator if it ever happened, but if not, the isActiveMove check can // be extended. serializeActiveMove(move, battle) { const base = battle.dex.moves.get(move.id); const skip = /* @__PURE__ */ new Set([...ACTIVE_MOVE]); for (const [key, value] of Object.entries(base)) { if (typeof value === "object" || move[key] === value) skip.add(key); } const state = this.serialize(move, skip, battle); state.move = `[Move:${move.id}]`; return state; } deserializeActiveMove(state, battle) { const move = battle.dex.getActiveMove(this.fromRef(state.move, battle)); this.deserialize(state, move, ACTIVE_MOVE, battle); return move; } serializeWithRefs(obj, battle) { switch (typeof obj) { case "function": return void 0; case "undefined": case "boolean": case "number": case "string": return obj; case "object": if (obj === null) return null; if (Array.isArray(obj)) { const arr = new Array(obj.length); for (const [i, o2] of obj.entries()) { arr[i] = this.serializeWithRefs(o2, battle); } return arr; } if (this.isActiveMove(obj)) return this.serializeActiveMove(obj, battle); if (this.isReferable(obj)) return this.toRef(obj); if (obj.constructor !== Object) { throw new TypeError(`Unsupported type ${obj.constructor.name}: ${obj}`); } const o = {}; for (const [key, value] of Object.entries(obj)) { o[key] = this.serializeWithRefs(value, battle); } return o; default: throw new TypeError(`Unexpected typeof === '${typeof obj}': ${obj}`); } } deserializeWithRefs(obj, battle) { switch (typeof obj) { case "undefined": case "boolean": case "number": return obj; case "string": return this.fromRef(obj, battle) || obj; case "object": if (obj === null) return null; if (Array.isArray(obj)) { const arr = new Array(obj.length); for (const [i, o2] of obj.entries()) { arr[i] = this.deserializeWithRefs(o2, battle); } return arr; } if (this.isActiveMove(obj)) return this.deserializeActiveMove(obj, battle); const o = {}; for (const [key, value] of Object.entries(obj)) { o[key] = this.deserializeWithRefs(value, battle); } return o; case "function": default: throw new TypeError(`Unexpected typeof === '${typeof obj}': ${obj}`); } } isReferable(obj) { if (!this.REFERABLE) { this.REFERABLE = /* @__PURE__ */ new Set([ import_battle.Battle, import_field.Field, import_side.Side, import_pokemon.Pokemon, import_dex.Dex.Condition, import_dex.Dex.Ability, import_dex.Dex.Item, import_dex.Dex.Move, import_dex.Dex.Species ]); } return this.REFERABLE.has(obj.constructor); } toRef(obj) { const id = obj instanceof import_pokemon.Pokemon ? `${obj.side.id}${POSITIONS[obj.position]}` : `${obj.id}`; return `[${obj.constructor.name}${id ? ":" : ""}${id}]`; } fromRef(ref, battle) { if (!ref.startsWith("[") && !ref.endsWith("]")) return void 0; ref = ref.substring(1, ref.length - 1); if (ref === "Battle") return battle; if (ref === "Field") return battle.field; const [type, id] = ref.split(":"); switch (type) { case "Side": return battle.sides[Number(id[1]) - 1]; case "Pokemon": return battle.sides[Number(id[1]) - 1].pokemon[POSITIONS.indexOf(id[2])]; case "Ability": return battle.dex.abilities.get(id); case "Item": return battle.dex.items.get(id); case "Move": return battle.dex.moves.get(id); case "Condition": return battle.dex.conditions.get(id); case "Species": return battle.dex.species.get(id); default: return void 0; } } serialize(obj, skip, battle) { const state = {}; for (const [key, value] of Object.entries(obj)) { if (skip.has(key)) continue; const val = this.serializeWithRefs(value, battle); if (typeof val !== "undefined") state[key] = val; } return state; } deserialize(state, obj, skip, battle) { for (const [key, value] of Object.entries(state)) { if (skip.has(key)) continue; obj[key] = this.deserializeWithRefs(value, battle); } } }(); //# sourceMappingURL=state.js.map