battle_shots.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
-- Driver: screenshot an overworld battle against eight random trainers. -- -- The mode's whole claim is that a fight reads as happening on the map, so -- what has to be looked at is a spread of maps: open route, town, forest, -- cave, gym floor. Each one gets the same three beats -- the menu (both HUD -- panels up over whatever ground they landed on), a move animation, and the -- frame a few seconds later, which is where the parallax drift shows. -- -- The eight are drawn with a fixed seed, so a re-run after a tweak produces -- the same eight battles and the shots compare directly. -- -- SHOT_DIR=.scratchpad POKEPORT_DRIVER=mods/DramaticShapeVoxelMod/tests/battle_shots.lua love . -- -- SHOT_DIR must already exist: the capture writes with io.open, which does -- not create directories. return function(game) local U = dofile("tests/drivers/util.lua") local DIR = os.getenv("SHOT_DIR") or ".scratchpad" local Pokemon = require("src.pokemon.Pokemon") local BattleState = require("src.battle.BattleState") local SEED = tonumber(os.getenv("SHOT_SEED") or "") or 20260727 love.math.setRandomSeed(SEED) -- Somebody to fight with. Two very different back pics, so a pin that is -- wrong for one silhouette and right for another cannot hide. game.save.party = { Pokemon.new(game.data, "CHARIZARD", 45), Pokemon.new(game.data, "PIKACHU", 40), } game.save.player.name = "RED" -- Where to stage them: outdoor, wooded, urban, underground, indoor. Any id -- the merged dataset does not have is dropped rather than guessed at. local PLACES = { { "ROUTE_1", 5, 8 }, { "VIRIDIAN_FOREST", 12, 20 }, { "PALLET_TOWN", 5, 6 }, { "MT_MOON_1F", 12, 12 }, { "CERULEAN_CITY", 10, 12 }, { "ROUTE_25", 12, 5 }, { "PEWTER_GYM", 4, 8 }, { "ROUTE_4", 10, 5 }, { "VIRIDIAN_CITY", 20, 20 }, { "ROUTE_3", 10, 5 }, } local places = {} for _, p in ipairs(PLACES) do if game.data.maps[p[1]] then places[#places + 1] = p end end -- every trainer class the merged data carries, in a stable order so the -- seed picks the same ones every run local classes = {} for id, rec in pairs(game.data.trainers) do if type(id) == "string" and id:sub(1, 1) ~= "_" and type(rec) == "table" and rec.parties and rec.parties[1] then classes[#classes + 1] = id end end table.sort(classes) U.log(("%d trainer classes, %d places, seed %d") :format(#classes, #places, SEED)) local Battles = nil local exports = game.mods and game.mods.exports local lib = exports and exports.DRAMATIC_SHAPE and exports.DRAMATIC_SHAPE.lib if lib then Battles = lib.require("OverworldBattle") end if not Battles then U.log("DRAMATIC_SHAPE is not loaded -- enable it and run again") end local function label(place, class) local arena = Battles and Battles.arena() if not arena then return ("%s / %s -- NO ARENA (plain battle screen)") :format(place[1], class) end return ("%s / %s -- %s arena at cell %d,%d; enemy %d,%d player %d,%d") :format(place[1], class, arena.shape, arena.x, arena.y, arena.enemyCell[1], arena.enemyCell[2], arena.playerCell[1], arena.playerCell[2]) end for i = 1, 8 do -- the TRAINERS are the random part; the places are walked in order, so -- one run covers open route, town, forest, cave and gym floor rather -- than landing on the same meadow eight times local place = places[(i - 1) % #places + 1] local class = classes[love.math.random(#classes)] local rec = game.data.trainers[class] local partyIndex = love.math.random(#rec.parties) local tag = ("%02d_%s_%s"):format(i, place[1]:lower(), class:lower()) U.teleport(game, place[1], place[2], place[3], "down") -- let the neighbourhood's meshes land before the fight starts, so the -- first battle frame is not the flat fallback U.wait(90) local battle = BattleState.newTrainer(game, class, partyIndex) battle.onFinish = function() end game.overworld:pushBattle(battle) -- the wipe, then the send-out chatter U.wait(70) for _ = 1, 14 do U.tap(game, "a"); U.wait(8) end U.log(label(place, class)) U.shot(game, ("%s/%s_1_menu.png"):format(DIR, tag)) -- FIGHT -> first move -> the animation U.tap(game, "a") U.wait(12) U.tap(game, "a") U.wait(24) U.shot(game, ("%s/%s_2_anim.png"):format(DIR, tag)) -- and a while later, where the drift has moved the world under them U.wait(240) U.shot(game, ("%s/%s_3_drift.png"):format(DIR, tag)) -- out of the battle and on to the next: pop whatever is above the -- overworld rather than trying to play the fight to its end while game.stack:top() and game.stack:top() ~= game.overworld do game.stack:pop() end U.wait(10) end U.log("done -- " .. DIR) end