mirror of
https://github.com/DramaticShape/DramaticShapeVoxelMod.git
synced 2026-08-12 13:50:50 +02:00
164 lines
6.3 KiB
Lua
164 lines
6.3 KiB
Lua
-- Driver: does a TRAINER knockout pay the whole party under LET'S GO FULL?
|
|
--
|
|
-- The catch payout is easy to see (one throw, one award). A knockout is
|
|
-- not: it goes through the engine's own faint -> awardExp path, which is
|
|
-- the one this mode replaces wholesale. So fight a real trainer with a
|
|
-- deliberately uneven party and read the deltas -- every member should
|
|
-- gain, and the low ones should gain multiples of the high one.
|
|
--
|
|
-- POKEPORT_DRIVER=mods/DramaticShapeVoxelMod/tests/letsgo_trainer_exp.lua \
|
|
-- SHOT_DIR=.scratchpad/letsgo "/c/Program Files/LOVE/lovec.exe" .
|
|
--
|
|
-- DS_LETSGO_MODE=off runs the same fight on the engine's own rules, which
|
|
-- is the control: there, only the Pokemon that fought should gain.
|
|
return function(game)
|
|
local U = dofile("tests/drivers/util.lua")
|
|
local MODE = os.getenv("DS_LETSGO_MODE") or "full"
|
|
local Pokemon = require("src.pokemon.Pokemon")
|
|
local BattleState = require("src.battle.BattleState")
|
|
|
|
local exports = game.mods and game.mods.exports
|
|
local lib = exports and exports.DRAMATIC_SHAPE and exports.DRAMATIC_SHAPE.lib
|
|
if not lib then
|
|
U.log("DRAMATIC_SHAPE is not loaded -- enable it and run again")
|
|
return
|
|
end
|
|
local LetsGo = lib.require("LetsGo")
|
|
LetsGo.setting:setValue(MODE == "off" and false or MODE, game)
|
|
U.log("LET'S GO mode: " .. tostring(LetsGo.mode()))
|
|
|
|
-- count the summary cards at their SOURCE rather than by catching them
|
|
-- on the stack: the driver taps fast enough to dismiss one between two
|
|
-- polls, and an absence there would look like a card that never came
|
|
local ExpPanel = lib.require("ExpPanel")
|
|
local innerNew, cards = ExpPanel.new, 0
|
|
ExpPanel.new = function(g, rows)
|
|
local panel = innerNew(g, rows)
|
|
cards = cards + 1
|
|
-- read at DRAW time in the real thing; here the loop that fills it
|
|
-- has already run, so peeking now is honest
|
|
local parts = {}
|
|
for _, r in ipairs(rows or {}) do
|
|
parts[#parts + 1] = ("%s+%d%s"):format(
|
|
(g.data.pokemon[r.mon.species] or {}).name or "?", r.gained or 0,
|
|
(r.to or 0) > (r.from or 0) and ("->L" .. r.to) or "")
|
|
end
|
|
U.log(("EXP CARD #%d: %s"):format(cards, table.concat(parts, " ")))
|
|
CARD_HOLD = 20 -- frames to stop tapping, so it can be shot
|
|
return panel
|
|
end
|
|
|
|
-- one strong fighter and two bystanders: only the fighter gains on the
|
|
-- engine's own rules, so the bystanders ARE the test
|
|
game.save.party = {
|
|
Pokemon.new(game.data, "CHARIZARD", 45),
|
|
Pokemon.new(game.data, "PIKACHU", 10),
|
|
Pokemon.new(game.data, "RATTATA", 5),
|
|
}
|
|
game.save.player.name = "RED"
|
|
|
|
-- The WEAKEST party in the game: one Pokemon, lowest level. Picked by
|
|
-- measuring rather than by name -- an alphabetical first pick lands on
|
|
-- OPP_AGATHA, whose Elite Four ghosts a level 45 Charizard does not
|
|
-- reliably clear, and a fight that never resolves reads exactly like a
|
|
-- payout that never happened.
|
|
local class, partyIx, best = nil, 1, nil
|
|
local ids = {}
|
|
for id in pairs(game.data.trainers) do
|
|
if type(id) == "string" and id:sub(1, 1) ~= "_" then ids[#ids + 1] = id end
|
|
end
|
|
table.sort(ids) -- stable across runs
|
|
for _, id in ipairs(ids) do
|
|
local rec = game.data.trainers[id]
|
|
for pi, party in ipairs((type(rec) == "table" and rec.parties) or {}) do
|
|
local n, top = 0, 0
|
|
for _, mon in ipairs(party) do
|
|
n = n + 1
|
|
top = math.max(top, tonumber(mon.level) or 0)
|
|
end
|
|
if n > 0 then
|
|
local score = n * 100 + top
|
|
if not best or score < best then
|
|
best, class, partyIx = score, id, pi
|
|
end
|
|
end
|
|
end
|
|
end
|
|
U.log(("fighting %s party %d (weakest of %d classes)")
|
|
:format(tostring(class), partyIx, #ids))
|
|
|
|
local before = {}
|
|
for i, m in ipairs(game.save.party) do
|
|
before[i] = { exp = m.exp, level = m.level }
|
|
end
|
|
|
|
U.teleport(game, "ROUTE_1", 5, 8, "down")
|
|
U.wait(60)
|
|
|
|
local battle = BattleState.newTrainer(game, class, partyIx)
|
|
battle.onFinish = function() end
|
|
game.overworld:pushBattle(battle)
|
|
U.wait(70)
|
|
|
|
-- through the intro to the menu, then FIGHT + first move, over and
|
|
-- over until the fight resolves. The enemy's HP is logged as it goes,
|
|
-- so a fight that stalls is visibly a stall rather than a silent zero.
|
|
local lastHP, shotPanel = nil, false
|
|
for i = 1, 900 do
|
|
-- the fight ending does NOT end the loop until the card has been
|
|
-- photographed: the last knockout resolves the battle in the same
|
|
-- breath that queues the card, so breaking on `result` alone leaves
|
|
-- every run with the card built and never seen
|
|
if battle.result and shotPanel then break end
|
|
-- hold the taps while the card is up, so it can be shot rather than
|
|
-- dismissed on the next frame
|
|
if (CARD_HOLD or 0) > 0 then
|
|
CARD_HOLD = CARD_HOLD - 1
|
|
if not shotPanel then
|
|
local top = game.stack and game.stack:top()
|
|
if top and rawget(top, "rows") then
|
|
shotPanel = true
|
|
U.shot(game, (os.getenv("SHOT_DIR") or ".scratchpad")
|
|
.. "/exp_panel.png")
|
|
U.log("shot the card")
|
|
end
|
|
end
|
|
U.wait(1)
|
|
elseif battle.phase == "menu" then
|
|
battle.menuIndex = 1 -- FIGHT
|
|
U.tap(game, "a")
|
|
elseif battle.phase == "moveSelect" then
|
|
battle.moveIndex = 1
|
|
U.tap(game, "a")
|
|
else
|
|
U.tap(game, "a")
|
|
end
|
|
local hp = battle.enemy and battle.enemy.mon and battle.enemy.mon.hp
|
|
if hp ~= lastHP then
|
|
lastHP = hp
|
|
U.log((" foe HP -> %s (phase %s, step %d)")
|
|
:format(tostring(hp), tostring(battle.phase), i))
|
|
end
|
|
-- photograph the summary card the moment it is on top, then let the
|
|
-- taps carry on and dismiss it
|
|
local top = game.stack and game.stack:top()
|
|
if top and not shotPanel and top ~= battle and rawget(top, "rows") then
|
|
shotPanel = true
|
|
U.shot(game, (os.getenv("SHOT_DIR") or ".scratchpad")
|
|
.. "/exp_panel.png")
|
|
U.log("shot the card")
|
|
end
|
|
U.wait(5)
|
|
end
|
|
U.log("battle result: " .. tostring(battle.result)
|
|
.. " phase=" .. tostring(battle.phase))
|
|
|
|
for i, m in ipairs(game.save.party) do
|
|
local was = before[i]
|
|
U.log(("EXP %-10s Lv%-3d -> Lv%-3d exp %d -> %d (+%d)")
|
|
:format(game.data.pokemon[m.species].name, was.level, m.level,
|
|
was.exp, m.exp, m.exp - was.exp))
|
|
end
|
|
U.log("done")
|
|
end
|