tp_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
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
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
-- Scratch driver: shots of the 3RD (third-person) rung -- the eye boomed -- off the back of the player's head, the player's own card drawn and turned -- to face it, the boom shortening against walls indoors, and the body -- turning to face where it walks. -- -- POKEPORT_DRIVER=mods/DramaticShapeVoxelMod/tests/tp_shots.lua \ -- SHOT_DIR=.scratchpad/tpshots lovec.exe . return function(game) local U = dofile("tests/drivers/util.lua") local Pipelines = require("src.render.Pipelines") local ROOT = (os.getenv("SHOT_DIR") or "shots/tp") local handle = game.mods.exports["DRAMATIC_SHAPE"] if not (handle and handle.lib) then print("[tp] DRAMATIC_SHAPE mod not loaded") return love.event.quit() end local V = handle.lib local FirstPerson = V.require("FirstPerson") local ThirdPerson = V.require("ThirdPerson") local ChunkMesher = V.require("ChunkMesher") local Voxel = V.require("VoxelState") local DayNight = V.require("DayNight") pcall(os.execute, 'mkdir -p "' .. ROOT .. '" 2>/dev/null') pcall(os.execute, 'mkdir "' .. ROOT:gsub("/", "\\") .. '" 2>nul') require("src.world.OverworldController").rollEncounter = function() return nil end local TileRenderer = require("src.render.TileRenderer") TileRenderer.tick = function() end TileRenderer.animFrame = function() return 0 end DayNight.setting:sync("day") local Zoom = require("src.render.Zoom") pcall(function() game.save.options.zoom = 0 Zoom.applyOptions(game.save.options) end) local function settle() for _ = 1, 900 do if ChunkMesher.pending() == 0 then break end U.wait(1) end for _ = 1, 300 do if FirstPerson.blend >= 1 and Voxel.ready and ChunkMesher.pending() == 0 then break end U.wait(1) end U.wait(40) end -- the nearest WALKABLE cell (fp_shots' helper): a guessed coordinate -- inside a building footprint buries the pivot in the geometry local function place(mapId, x, y) U.teleport(game, mapId, x, y, "down") local ow = game.stack:top() local map = ow and ow.map if not map or map:isWalkableCell(x, y) then return end for r = 1, 8 do for dy = -r, r do for dx = -r, r do if math.max(math.abs(dx), math.abs(dy)) == r then local cx, cy = x + dx, y + dy if map:inBounds(cx, cy) and map:isWalkableCell(cx, cy) then U.teleport(game, mapId, cx, cy, "down") print(("[tp] (%d,%d) not walkable; standing at (%d,%d)") :format(x, y, cx, cy)) return end end end end end end -- yaw is a world bearing: 0 south, pi/2 east, pi north, -pi/2 west local SCENES = { -- Pallet Town, mid-street: the player's own card seen from behind, in -- each compass direction plus a diagonal (the off-grid case, where a -- south-facing card would be edge-on to any orbit camera) { map = "PALLET_TOWN", x = 13, y = 14, yaw = math.pi, label = "pallet_north" }, { map = "PALLET_TOWN", x = 13, y = 14, yaw = 0, label = "pallet_south" }, { map = "PALLET_TOWN", x = 9, y = 7, yaw = math.pi / 2, label = "pallet_east" }, { map = "PALLET_TOWN", x = 9, y = 7, yaw = 3 * math.pi / 4, label = "pallet_diag" }, -- the shoreline: the boom over water, reflecting the player { map = "PALLET_TOWN", x = 9, y = 12, yaw = 0, label = "pallet_water" }, -- pitched down, the classic third-person framing; and up, where the -- boom has to shorten rather than bury the eye in the ground { map = "PALLET_TOWN", x = 13, y = 14, yaw = math.pi, pitch = math.rad(35), label = "pallet_down" }, { map = "PALLET_TOWN", x = 13, y = 14, yaw = math.pi, pitch = -math.rad(40), label = "pallet_skyward" }, -- Route 1: grass and ledges, with the character in frame for scale { map = "ROUTE_1", x = 10, y = 28, yaw = math.pi, label = "route1_north" }, -- interiors: the one place a 48px boom cannot fit, so the collision -- march is the whole shot { map = "VIRIDIAN_POKECENTER", x = 3, y = 5, yaw = math.pi, label = "center_north" }, { map = "REDS_HOUSE_1F", x = 3, y = 4, yaw = math.pi, label = "reds_house" }, } local shots = 0 for _, s in ipairs(SCENES) do place(s.map, s.x, s.y) Pipelines.setLevel("voxel", Voxel.TP_LEVEL) Pipelines.setLevel("tiltshift", 0) settle() FirstPerson.yaw = s.yaw FirstPerson.pitch = s.pitch or FirstPerson.PITCH_DEFAULT U.wait(20) print(("[tp] %-16s boom %.1f of %.1f"):format(s.label, ThirdPerson.len, ThirdPerson.BOOM)) if U.shot(game, ("%s/%s.png"):format(ROOT, s.label)) then shots = shots + 1 end end -- the slide from 1ST to 3RD: the eye walks out of the head rather than -- cutting, so the halfway frame is a real camera position place("PALLET_TOWN", 13, 14) Pipelines.setLevel("voxel", Voxel.FP_LEVEL) settle() if U.shot(game, ROOT .. "/from_1st.png") then shots = shots + 1 end Pipelines.setLevel("voxel", Voxel.TP_LEVEL) for _ = 1, 300 do if ThirdPerson.out >= 0.5 then break end U.wait(1) end if U.shot(game, ROOT .. "/boom_mid.png") then shots = shots + 1 end U.wait(60) if U.shot(game, ROOT .. "/boom_out.png") then shots = shots + 1 end -- ------- the free walk, with the body turning -- -- Hold forward with the head yawed off-grid: the player must GLIDE (off -- the 16px grid, which no grid step can do) and the body must end up -- facing its own travel rather than the lens. place("PALLET_TOWN", 13, 14) Pipelines.setLevel("voxel", Voxel.TP_LEVEL) settle() local ow = game.stack:top() local p = ow.player FirstPerson.yaw = 3 * math.pi / 4 -- northeast, deliberately off-grid FirstPerson.pitch = FirstPerson.PITCH_DEFAULT local x0, y0 = p.px, p.py U.hold(game, "up", 90) local moved = math.abs(p.px - x0) + math.abs(p.py - y0) print(("[tp] walk moved %.1f px; off-grid: %s; body faces %s (camera %s)") :format(moved, tostring(p.px % 16 ~= 0 or p.py % 16 ~= 0), tostring(p.facing), tostring(FirstPerson.compassFacing()))) if U.shot(game, ROOT .. "/walked.png") then shots = shots + 1 end -- strafing: the one case the body facing exists for. Hold RIGHT with the -- head due north and the character must walk east showing its flank. place("PALLET_TOWN", 13, 14) settle() FirstPerson.yaw = math.pi U.hold(game, "right", 40) print(("[tp] strafe: body faces %s, camera looks %s") :format(tostring(p.facing), tostring(FirstPerson.compassFacing()))) if U.shot(game, ROOT .. "/strafe.png") then shots = shots + 1 end -- ------- the spin, sampled -- -- Stand still and turn the camera fast, sampling the frame the player's -- own card actually draws every rendered frame. A standing body is -- pointed along the camera's own yaw, so it must show its back the whole -- way round; anything else is the sideways flick. Sampled through the -- live rig, so this measures the real thing rather than the arithmetic. place("PALLET_TOWN", 13, 14) Pipelines.setLevel("voxel", Voxel.TP_LEVEL) settle() ow = game.stack:top() p = ow.player local seen, frames = {}, 0 for _ = 1, 240 do FirstPerson.lookBy(math.rad(7), 0) -- ~420 deg/s, a hard flick U.wait(1) local f = FirstPerson.playerFacing(p.facing, p.px + 8, p.py + 8) seen[f] = (seen[f] or 0) + 1 frames = frames + 1 end local report = {} for f, n in pairs(seen) do report[#report + 1] = ("%s x%d"):format(f, n) end table.sort(report) print(("[tp] spin: %d frames, card frames seen: %s") :format(frames, table.concat(report, ", "))) print(("[tp] %d shots into %s"):format(shots, ROOT)) love.event.quit() end