mirror of
https://github.com/DramaticShape/DramaticShapeVoxelMod.git
synced 2026-08-12 11:20:50 +02:00
dcf078c2e0
Flowers stand as 1px animated cutouts on their own pulled mesh (own-cell flowers hide behind the walker, the cell south still overdraws feet). Cuttable bushes, gym statues (single-block plinth) and Lt. Surge's trash cans stand as 5-voxel prop cutouts; prop_ground names the tile painted beneath. Block edits refresh meshes in place instead of blinking to 2D.
151 lines
5.9 KiB
Lua
151 lines
5.9 KiB
Lua
-- Driver: does the water/flower tile animation reach voxel mode?
|
|
--
|
|
-- Shoots a burst of frames spaced about one animation period apart, in
|
|
-- voxel mode and (for reference) flat. The evidence is the difference
|
|
-- between consecutive shots: water tile $14 rolls sideways a pixel a step
|
|
-- and flower tile $03 cycles three frames, so the burst should NOT be
|
|
-- eight copies of the same picture.
|
|
--
|
|
-- POKEPORT_DRIVER=mods/DRAMATIC_SHAPE/tests/voxel_anim_probe.lua lovec .
|
|
--
|
|
-- knobs (env):
|
|
-- ANIM_MAP map id (default PALLET_TOWN)
|
|
-- ANIM_SPOT "x,y[,facing]" (default 5,6,down)
|
|
-- ANIM_SHOTS frames in the burst (default 6)
|
|
-- ANIM_LEVEL voxel rung, 0 = flat (default 3)
|
|
-- SHOT_DIR output directory, must exist (default "shots")
|
|
return function(game)
|
|
local U = dofile("tests/drivers/util.lua")
|
|
local Pipelines = require("src.render.Pipelines")
|
|
local TileRenderer = require("src.render.TileRenderer")
|
|
|
|
local SPEED = math.max(1,
|
|
math.floor(tonumber(os.getenv("POKEPORT_SPEED")) or 1))
|
|
local function wait(n) U.wait(n * SPEED) end
|
|
|
|
local DIR = os.getenv("SHOT_DIR") or "shots"
|
|
local mapId = os.getenv("ANIM_MAP") or "PALLET_TOWN"
|
|
local shots = math.floor(tonumber(os.getenv("ANIM_SHOTS")) or 6)
|
|
local level = math.floor(tonumber(os.getenv("ANIM_LEVEL")) or 3)
|
|
local sx, sy, facing = (os.getenv("ANIM_SPOT") or "5,6,down")
|
|
:match("^%s*(%d+)%s*,%s*(%d+)%s*,?%s*(%a*)")
|
|
facing = (facing ~= "" and facing) or "down"
|
|
|
|
local tileset = game.data.maps[mapId] and game.data.maps[mapId].tileset
|
|
local specs = tileset and game.data.tilesets
|
|
and TileRenderer.defaultAnimatedTiles(game.data.tilesets[tileset])
|
|
print(("[anim] %s tileset=%s animated entries=%d")
|
|
:format(mapId, tostring(tileset), specs and #specs or 0))
|
|
for _, s in ipairs(specs or {}) do
|
|
print(("[anim] tile $%02X kind=%s period=%s")
|
|
:format(s.tile or -1, tostring(s.kind), tostring(s.period)))
|
|
end
|
|
|
|
local Zoom = require("src.render.Zoom")
|
|
Zoom.reset()
|
|
Pipelines.setLevel("tiltshift", 0)
|
|
U.teleport(game, mapId, tonumber(sx), tonumber(sy), facing)
|
|
wait(20)
|
|
|
|
Pipelines.setLevel("voxel", level)
|
|
wait(30) -- outlast the camera tween
|
|
|
|
-- the atlas the terrain mesh actually samples, dumped per step: if these
|
|
-- differ but the frames do not, the animated tiles are not reaching the
|
|
-- geometry (baked into prop prisms, say) rather than not animating
|
|
local V = game.mods.exports["DRAMATIC_SHAPE"]
|
|
V = V and V.lib
|
|
local TerrainAtlas = V and V.require("TerrainAtlas")
|
|
local PaletteFX = require("src.render.PaletteFX")
|
|
local function dumpAtlas(tag)
|
|
if not TerrainAtlas then return end
|
|
local ow = game.overworld
|
|
local map = ow and ow.map
|
|
if not map then return end
|
|
local colors = nil
|
|
if not PaletteFX.usesGbcPack() and ow.paletteFor then
|
|
colors = ow:paletteFor(map)
|
|
end
|
|
local img = TerrainAtlas.forMap(map, colors)
|
|
if not img then return end
|
|
print(("[anim] atlas %s: animated copy=%s")
|
|
:format(tag, tostring(img ~= map.renderer.image)))
|
|
local ok = pcall(function()
|
|
local w, h = img:getDimensions()
|
|
local c = love.graphics.newCanvas(w, h)
|
|
love.graphics.setCanvas(c)
|
|
love.graphics.clear(0, 0, 0, 0)
|
|
love.graphics.setColor(1, 1, 1, 1)
|
|
love.graphics.draw(img)
|
|
love.graphics.setCanvas()
|
|
c:newImageData():encode("png", "atlas_" .. tag .. ".png")
|
|
end)
|
|
if not ok then print("[anim] atlas dump failed") end
|
|
end
|
|
|
|
-- where the animated tiles ended up in the geometry: a flat top quad
|
|
-- samples its tile through uvRect and picks the atlas patch up, but a
|
|
-- cell Structures turned into a prop prism has its pixels baked as
|
|
-- point-sampled voxels and would not
|
|
if V and specs then
|
|
local Structures = V.require("Structures")
|
|
local ow = game.overworld
|
|
local S = ow and ow.map and Structures.forMap(ow.map)
|
|
if S then
|
|
local def = ow.map.def
|
|
for _, spec in ipairs(specs) do
|
|
if not spec.tile then goto continue end -- "toggle" specs claim
|
|
-- tile LISTS, not one slot
|
|
local flat, prop, run, first = 0, 0, 0, nil
|
|
for ty = 0, def.height * 4 - 1 do
|
|
for tx = 0, def.width * 4 - 1 do
|
|
local k = (ty + 64) * 4096 + (tx + 64)
|
|
if S.tileAt[k] == spec.tile then
|
|
if S.skip[k] then prop = prop + 1
|
|
elseif S.runs[k] then run = run + 1
|
|
else
|
|
flat = flat + 1
|
|
first = first or { tx, ty }
|
|
end
|
|
end
|
|
end
|
|
end
|
|
print(("[anim] tile $%02X on this map: %d flat, %d in a volume run,"
|
|
.. " %d inside a prop%s"):format(spec.tile, flat, run, prop,
|
|
first and (" -- first at cell " .. math.floor(first[1] / 2)
|
|
.. "," .. math.floor(first[2] / 2)) or ""))
|
|
::continue::
|
|
end
|
|
end
|
|
end
|
|
|
|
-- one animation period is 20 logic frames at 60Hz, and TileRenderer.tick
|
|
-- runs on wall-clock steps, so ~22 rendered frames lands the next step.
|
|
-- animFrame is an OPTIONAL engine seam this build may not export; the
|
|
-- mod's own clock (TerrainAtlas._animFrame) reads the same counter by
|
|
-- whatever route exists, so ask it first.
|
|
local function clock()
|
|
if TerrainAtlas and TerrainAtlas._animFrame then
|
|
local ok, f = pcall(TerrainAtlas._animFrame)
|
|
if ok and type(f) == "number" then return f end
|
|
end
|
|
if TileRenderer.animFrame then
|
|
local ok, f = pcall(TileRenderer.animFrame)
|
|
if ok and type(f) == "number" then return f end
|
|
end
|
|
return -1
|
|
end
|
|
for i = 1, shots do
|
|
game.capturePath = ("%s/anim_%s_L%d_%02d.png"):format(DIR, mapId, level, i)
|
|
wait(3)
|
|
print(("[anim] shot %d at animFrame %d step %d"):format(
|
|
i, clock(), math.floor(clock() / 20) % 8))
|
|
dumpAtlas(tostring(i))
|
|
wait(22)
|
|
end
|
|
|
|
Pipelines.setLevel("voxel", 0)
|
|
wait(5)
|
|
print("[anim] done")
|
|
end
|