fp_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
-- Scratch driver: shots of the 1ST (first-person) rung -- the rig standing -- in the player's head, billboards yawing to face it, the sky meeting the -- horizon, the shadow box following the look, water seen from eye level, -- and an interior with its figures. -- -- POKEPORT_DRIVER=mods/DramaticShapeVoxelMod/tests/fp_shots.lua \ -- SHOT_DIR=.scratchpad/fpshots 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/fp") local handle = game.mods.exports["DRAMATIC_SHAPE"] if not (handle and handle.lib) then print("[fp] DRAMATIC_SHAPE mod not loaded") return love.event.quit() end local V = handle.lib local FirstPerson = V.require("FirstPerson") 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 -- Teleport to the nearest WALKABLE cell: a guessed coordinate inside a -- building footprint buries the eye in the geometry, which is what the -- first cut of every Pallet shot did. 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(("[fp] (%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: houses, the lab, NPCs -- the town seen -- from inside it, in each compass direction plus a diagonal { 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: water at eye level, which is where the battle pass -- says a low placed camera reads the reflection wrong -- the shot -- decides whether 1ST keeps it { map = "PALLET_TOWN", x = 9, y = 12, yaw = 0, label = "pallet_water" }, -- looking up: the sky's bands and the horizon line { map = "PALLET_TOWN", x = 13, y = 14, yaw = math.pi, pitch = -math.rad(25), label = "pallet_skyward" }, -- and down: the ground, the feet-level shadow { map = "PALLET_TOWN", x = 13, y = 14, yaw = math.pi, pitch = math.rad(45), label = "pallet_down" }, -- Route 1: grass rows and ledges from inside them { map = "ROUTE_1", x = 10, y = 28, yaw = math.pi, label = "route1_north" }, -- an interior: the Center's counter, machines and couch figures { map = "VIRIDIAN_POKECENTER", x = 3, y = 5, yaw = math.pi, label = "center_north" }, { map = "VIRIDIAN_POKECENTER", x = 6, y = 4, yaw = -math.pi / 2, label = "center_west" }, } local shots = 0 for _, s in ipairs(SCENES) do place(s.map, s.x, s.y) Pipelines.setLevel("voxel", Voxel.FP_LEVEL) Pipelines.setLevel("tiltshift", 0) settle() FirstPerson.yaw = s.yaw FirstPerson.pitch = s.pitch or FirstPerson.PITCH_DEFAULT U.wait(20) if U.shot(game, ("%s/%s.png"):format(ROOT, s.label)) then shots = shots + 1 end end -- one mid-blend shot: step the ladder onto 1ST from 75 and catch the -- dive halfway place("PALLET_TOWN", 13, 14) Pipelines.setLevel("voxel", 5) settle() Pipelines.setLevel("voxel", Voxel.FP_LEVEL) for _ = 1, 300 do if FirstPerson.blend >= 0.5 then break end U.wait(1) end if U.shot(game, ROOT .. "/blend_mid.png") then shots = shots + 1 end -- ------- the free walk, exercised -- -- Hold forward with the head yawed off-grid and confirm the player -- GLIDES: the position moves along the look direction, lands off the -- 16px grid (which no grid step can do), and the logical cell follows. place("PALLET_TOWN", 13, 14) Pipelines.setLevel("voxel", Voxel.FP_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, c0x, c0y = p.px, p.py, p.cellX, p.cellY U.hold(game, "up", 90) U.wait(5) local moved = math.abs(p.px - x0) + math.abs(p.py - y0) print(("[fp] walk: (%.1f,%.1f) cell(%d,%d) -> (%.1f,%.1f) cell(%d,%d)") :format(x0, y0, c0x, c0y, p.px, p.py, p.cellX, p.cellY)) print(("[fp] walk moved %.1f px; off-grid: %s; diagonal: %s") :format(moved, tostring(p.px % 16 ~= 0 or p.py % 16 ~= 0), tostring(math.abs(p.px - x0) > 8 and math.abs(p.py - y0) > 8))) if U.shot(game, ROOT .. "/walked.png") then shots = shots + 1 end print(("[fp] %d shots into %s"):format(shots, ROOT)) love.event.quit() end