mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-18 11:44:42 +02:00
feat(mods): expose read-only battle snapshots
This commit is contained in:
@@ -0,0 +1,200 @@
|
||||
-- Read-only Gen 1 battle state for companion UIs and accessibility mods.
|
||||
|
||||
local Damage = require("src.battle.Damage")
|
||||
local ItemEffects = require("src.inventory.ItemEffects")
|
||||
local TypeChart = require("src.battle.TypeChart")
|
||||
|
||||
local BattleAPI = {}
|
||||
BattleAPI.__index = BattleAPI
|
||||
|
||||
function BattleAPI.new(game)
|
||||
return setmetatable({ game = game, revision = 0, signature = nil }, BattleAPI)
|
||||
end
|
||||
|
||||
local function activeBattle(game)
|
||||
local states = game and game.stack and game.stack.states or {}
|
||||
for i = #states, 1, -1 do
|
||||
if states[i].isBattleState then return states[i], states[#states] end
|
||||
end
|
||||
end
|
||||
|
||||
local function monCopy(data, mon, active)
|
||||
if not mon then return nil end
|
||||
local def = data.pokemon[mon.species]
|
||||
return {
|
||||
species = mon.species,
|
||||
name = mon.nickname or (def and def.name) or mon.species,
|
||||
level = mon.level, hp = mon.hp,
|
||||
maxHp = mon.stats and mon.stats.hp or mon.hp,
|
||||
status = mon.status, active = active and true or false,
|
||||
}
|
||||
end
|
||||
|
||||
local function visibleMessage(battle, top)
|
||||
local source = top and top.isTextBox and top or top == battle and battle
|
||||
local lines = source and source.visibleText and source:visibleText()
|
||||
if not lines then return nil end
|
||||
local copy = {}
|
||||
for i, line in ipairs(lines) do copy[i] = tostring(line) end
|
||||
return copy
|
||||
end
|
||||
|
||||
local function signature(game, battle, top)
|
||||
if not battle then return "none" end
|
||||
local parts = { tostring(battle), tostring(top), battle.phase or "",
|
||||
tostring(battle.turnCount or 0), tostring(#(battle.queue or {})),
|
||||
tostring(battle.current), tostring(battle.msgWaiting),
|
||||
tostring(battle.msgPrompt), tostring(battle.menuIndex),
|
||||
tostring(battle.mimicIndex),
|
||||
tostring(battle.safari and battle.safari.balls),
|
||||
tostring(battle.ghost), tostring(battle.noCatch),
|
||||
table.concat(visibleMessage(battle, top) or {}, "\n") }
|
||||
for _, battler in ipairs({ battle.player, battle.enemy }) do
|
||||
local mon = battler and battler.mon
|
||||
parts[#parts + 1] = tostring(mon)
|
||||
parts[#parts + 1] = tostring(mon and mon.hp)
|
||||
parts[#parts + 1] = tostring(mon and mon.status)
|
||||
parts[#parts + 1] = table.concat(battler and battler.curTypes or {}, ",")
|
||||
end
|
||||
for _, mon in ipairs(game.save.party or {}) do
|
||||
parts[#parts + 1] = tostring(mon)
|
||||
parts[#parts + 1] = tostring(mon.hp)
|
||||
parts[#parts + 1] = tostring(mon.status)
|
||||
end
|
||||
local inventory = {}
|
||||
for id, count in pairs(game.save.inventory or {}) do
|
||||
if ItemEffects.isBall(id) or ItemEffects.isBattleMedicine(id) then
|
||||
inventory[#inventory + 1] = id .. "=" .. tostring(count)
|
||||
end
|
||||
end
|
||||
table.sort(inventory)
|
||||
for _, item in ipairs(inventory) do parts[#parts + 1] = item end
|
||||
return table.concat(parts, "|")
|
||||
end
|
||||
|
||||
function BattleAPI:_revision(battle, top)
|
||||
local nextSignature = signature(self.game, battle, top)
|
||||
if nextSignature ~= self.signature then
|
||||
self.signature = nextSignature
|
||||
self.revision = self.revision + 1
|
||||
end
|
||||
return self.revision
|
||||
end
|
||||
|
||||
local IMMUNITY_ONLY = { SPECIAL_DAMAGE_EFFECT = true,
|
||||
SUPER_FANG_EFFECT = true, OHKO_EFFECT = true }
|
||||
|
||||
local function movePreview(battle, move, def)
|
||||
local record = battle.effectRecord and battle:effectRecord(def.effect)
|
||||
local power, typeMult = def.power or 0
|
||||
if power > 0 and move.id ~= "COUNTER" then
|
||||
local raw = TypeChart.effectiveness(def.type, battle.enemy.curTypes or {})
|
||||
if IMMUNITY_ONLY[def.effect] then
|
||||
typeMult = raw == 0 and 0 or 10
|
||||
elseif not (record and record.chooseDamage) then
|
||||
typeMult = raw
|
||||
end
|
||||
end
|
||||
local hitChance
|
||||
if record and record.neverMiss then
|
||||
hitChance = 100
|
||||
elseif not (record and record.gate)
|
||||
and (power > 0 or (record and record.accuracyChecked)) then
|
||||
hitChance = battle.enemy.invulnerable and 0
|
||||
or Damage.accuracyChance(battle.ruleset, def,
|
||||
battle.player, battle.enemy)
|
||||
end
|
||||
local displayPower = power > 0 and move.id ~= "COUNTER"
|
||||
and not IMMUNITY_ONLY[def.effect]
|
||||
and not (record and record.chooseDamage) and power or nil
|
||||
return typeMult, hitChance, displayPower
|
||||
end
|
||||
|
||||
local function moveCopies(game, battle)
|
||||
local out = {}
|
||||
for slot, move in ipairs((battle.player and battle.player.curMoves) or {}) do
|
||||
local def = game.data.moves[move.id] or {}
|
||||
local mult, hitChance, displayPower = movePreview(battle, move, def)
|
||||
out[slot] = { slot = slot, id = move.id, name = def.name or move.id,
|
||||
pp = move.pp,
|
||||
maxPp = (def.pp or move.pp or 0)
|
||||
+ (move.ppUps or 0) * math.floor((def.pp or 0) / 5),
|
||||
type = def.type, power = def.power, accuracy = def.accuracy,
|
||||
displayPower = displayPower, hitChance = hitChance,
|
||||
effectiveness = mult,
|
||||
disabled = battle.player.disabledSlot == slot }
|
||||
end
|
||||
return out
|
||||
end
|
||||
|
||||
local function itemCopies(game, battle, catchable)
|
||||
local out = {}
|
||||
for id, count in pairs(game.save.inventory or {}) do
|
||||
if count > 0
|
||||
and (ItemEffects.isBall(id) or ItemEffects.isBattleMedicine(id)) then
|
||||
local def = game.data.items[id] or {}
|
||||
local ball = ItemEffects.isBall(id)
|
||||
out[#out + 1] = { id = id, name = def.name or id, count = count,
|
||||
ball = ball, needsTarget = not ball,
|
||||
catchChance = ball and catchable and battle.catchChance
|
||||
and battle:catchChance(id) or nil }
|
||||
end
|
||||
end
|
||||
table.sort(out, function(a, b) return a.name < b.name end)
|
||||
return out
|
||||
end
|
||||
|
||||
local function mimicCopies(game, battle)
|
||||
local out = {}
|
||||
for i, move in ipairs(battle.mimicMoves or {}) do
|
||||
local def = game.data.moves[move.id] or {}
|
||||
out[i] = { index = i, slot = move.slot, id = move.id,
|
||||
name = def.name or move.id }
|
||||
end
|
||||
return out
|
||||
end
|
||||
|
||||
function BattleAPI:snapshot()
|
||||
local game = self.game
|
||||
local battle, top = activeBattle(game)
|
||||
if not battle then return nil end
|
||||
local kind = battle:battleKind()
|
||||
local supported = kind ~= "oldman" and kind ~= "link"
|
||||
local catchable = kind == "wild" and not battle.ghost and not battle.noCatch
|
||||
local forcedParty = top and top.isPartyMenu and top.battle == battle
|
||||
and top.forceSwitch
|
||||
local canAdvance = supported and ((top == battle
|
||||
and battle.phase == "messages" and battle.current
|
||||
and (battle.msgWaiting or battle.msgPrompt))
|
||||
or (top and top.isTextBox and not top.choice
|
||||
and (top.waiting or top.done)))
|
||||
local prompt = "locked"
|
||||
if canAdvance then prompt = "advance"
|
||||
elseif supported and forcedParty then prompt = "party"
|
||||
elseif supported and top == battle and kind == "safari"
|
||||
and battle.phase == "menu" then prompt = "safari"
|
||||
elseif supported and top == battle and battle.phase == "mimicSelect" then
|
||||
prompt = "mimic"
|
||||
elseif supported and top == battle and battle.phase == "menu" then
|
||||
prompt = "menu"
|
||||
elseif supported and top == battle and battle.phase == "moveSelect" then
|
||||
prompt = "moves"
|
||||
end
|
||||
local party = {}
|
||||
for i, mon in ipairs(game.save.party or {}) do
|
||||
party[i] = monCopy(game.data, mon,
|
||||
battle.player and battle.player.mon == mon)
|
||||
party[i].slot = i
|
||||
end
|
||||
return { revision = self:_revision(battle, top), kind = kind,
|
||||
catchable = catchable, prompt = prompt,
|
||||
message = visibleMessage(battle, top), turn = battle.turnCount or 0,
|
||||
player = monCopy(game.data, battle.player and battle.player.mon, true),
|
||||
enemy = monCopy(game.data, battle.enemy and battle.enemy.mon, true),
|
||||
party = party, moves = moveCopies(game, battle),
|
||||
items = itemCopies(game, battle, catchable),
|
||||
safariBalls = battle.safari and battle.safari.balls or nil,
|
||||
mimicMoves = mimicCopies(game, battle), mimicIndex = battle.mimicIndex }
|
||||
end
|
||||
|
||||
return BattleAPI
|
||||
Reference in New Issue
Block a user