letsgo_trainer_exp.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
-- 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