Spaces:
Running
Running
; | |
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 | |