Files
gen1recomp/tests/engine/game3_em_fc_field_test.lua
T
bryanthaboi 690f0cd9a1 TOUCHDOWN
2026-09-27 16:07:31 -04:00

214 lines
10 KiB
Lua

package.path = "./?.lua;./?/init.lua;" .. package.path
local T = require("tests.harness")
local check, eq = T.check, T.eq
local GameVersion = require("src.core.GameVersion")
local before = GameVersion.get()
GameVersion.set("emerald")
local FxRse = require("src.core.game3.field_effects_rse")
local a = FxRse.anim({ { "frame", 0, 2 }, { "loop", 0 }, { "frame", 1, 1 }, { "loop", 2 }, { "frame", 2, 1 }, { "end" } })
local seq = { a.frame }
for _ = 1, 8 do
FxRse.animTick(a)
seq[#seq + 1] = a.frame
end
eq(table.concat(seq, ""), "001112222", "AnimCmd loop(0)/loop(2) plays the loop body three times")
check(a.ended, "anim ends on the end command")
local j = FxRse.anim({ { "frame", 0, 1 }, { "frame", 1, 1 }, { "jump", 0 } })
FxRse.animTick(j)
FxRse.animTick(j)
eq(j.frame, 0, "jump command restarts the anim")
check(not j.ended, "jump anims never end")
local sk = FxRse.anim({ { "frame", 0, 4 }, { "frame", 1, 4 }, { "frame", 2, 4 }, { "end" } }, 2)
eq(sk.frame, 2, "SeekSpriteAnim(2) starts at the third command")
eq(FxRse.tireTrackAnim("down", "down"), 1, "tire tracks south to south")
eq(FxRse.tireTrackAnim("down", "left"), 7, "tire tracks south to west curve")
eq(FxRse.tireTrackAnim("left", "up"), 8, "tire tracks west to north curve")
eq(FxRse.tireTrackAnim("right", "right"), 4, "tire tracks east to east")
local MB = require("src.core.game3.mb")
check(FxRse.B.reflective(MB.id("POND_WATER")), "pond water is reflective")
check(FxRse.B.reflective(MB.id("ICE")), "ice is reflective")
check(not FxRse.B.reflective(MB.id("NORMAL")), "normal ground does not reflect")
eq(FxRse.B.bridgeType(MB.id("BRIDGE_OVER_POND_MED_EDGE_1")), 2, "pond bridge med edge is bridge type 2")
eq(FxRse.B.bridgeType(MB.id("BRIDGE_OVER_POND_HIGH")), 3, "pond bridge high is bridge type 3")
eq(FxRse.B.bridgeType(MB.id("NORMAL")), 0, "no bridge")
check(FxRse.B.ripples(MB.id("SOOTOPOLIS_DEEP_WATER")), "Sootopolis deep water ripples")
check(FxRse.B.seaweed(MB.id("SEAWEED_NO_SURFACING")), "seaweed (no surfacing) is seaweed")
FxRse._fc = {
palTagNone = 0x11FF,
slotTags = { [0] = 0x1100, [1] = 0x1101, [2] = 0x1103, [6] = 0x1107, [10] = 0x1110, [11] = 0x1111 },
reflectionMap = { [0] = 1, [1] = 1, [2] = 6, [3] = 7, [4] = 8, [5] = 9, [10] = 11 },
playerReflections = { [0x1100] = 0x1101, [0x1110] = 0x1111 },
specialReflections = {},
gfx = {
[0] = { paletteTag = 0x1100, reflectionTag = 0x1102, slot = 0 },
[89] = { paletteTag = 0x1110, reflectionTag = 0x1102, slot = 0 },
[7] = { paletteTag = 0x1103, reflectionTag = 0x11FF, slot = 2 },
[8] = { paletteTag = 0x1103, reflectionTag = 0x1107, slot = 2 },
},
}
eq(FxRse.reflectionPaletteTag(0, 0), 0x1101, "Brendan reflects with BrendanReflection")
eq(FxRse.reflectionPaletteTag(89, 0), 0x1111, "May reflects with MayReflection")
eq(FxRse.reflectionPaletteTag(0, 2), 0x1102, "high bridge loads the bridge reflection palette")
eq(FxRse.reflectionPaletteTag(7, 0), 0x1107, "NPC with no reflection tag keeps the slot default")
eq(FxRse.reflectionPaletteTag(8, 0), 0x1107, "NPC slot 2 reflects in slot 6")
FxRse._fc = nil
local Objects = require("src.core.game3.objects")
eq(Objects.copyDirection("up", "up", "left"), "left", "COPY_PLAYER copies the move when entered facing north")
eq(Objects.copyDirection("down", "up", "left"), "right", "COPY_PLAYER_OPPOSITE mirrors")
eq(Objects.copyDirection("up", "down", "left"), "right", "player entered facing south flips the copy")
eq(Objects.copyDirection("left", "up", "up"), "left", "counterclockwise copy")
eq(Objects.copyDirection("right", "up", "up"), "right", "clockwise copy")
local fx, fy = 0, 0
for i = 1, #Objects.FIG8_X do
fx, fy = fx + Objects.FIG8_X[i], fy + Objects.FIG8_Y[i]
end
eq(#Objects.FIG8_X, 72, "figure 8 table length")
eq(Objects.JUMP_Y.high[6], -12, "high jump peak")
eq(Objects.JUMP_Y.normal[7], -10, "normal jump peak")
local eo = { cellX = 3, cellY = 4, px = 48, py = 64, targetX = 3, targetY = 4, facing = "down" }
Objects.startJumpArc(eo, 0, {})
eq(eo.stepFrames, 16, "jump in place lasts 16 frames")
check(eo.jumpArc and eo.jumpArc.shadow, "jumping objects draw a shadow")
Objects.startJumpArc(eo, 2, {})
eq(eo.stepFrames, 32, "jump 2 lasts 32 frames")
eq(eo.jumpArc.shift, 1, "jump 2 indexes the arc every other frame")
local RG = require("src.core.game3.rotating_gate")
local vars = {}
RG.setStore(function(id) return vars[id] or 0 end, function(id, v) vars[id] = v end)
RG._p = { map = nil, gates = { { x = 5, y = 5, shape = 0, orientation = 0 }, { x = 9, y = 9, shape = 4, orientation = 1 } }, anims = {} }
RG.setOrientation(0, 0)
RG.setOrientation(1, 3)
eq(RG.getOrientation(0), 0, "gate 0 orientation low byte")
eq(RG.getOrientation(1), 3, "gate 1 orientation high byte")
local free = function() return false end
local wall = function() return true end
check(RG.hasArm(0, 0, 0), "L1 gate has a short north arm at 0 degrees")
check(RG.hasArm(0, 1, 0), "L1 gate has a short east arm at 0 degrees")
check(not RG.hasArm(0, 2, 0), "L1 gate has no south arm")
check(RG.canRotate(0, RG.ROTATE_CLOCKWISE, free), "a gate with room rotates")
check(not RG.canRotate(0, RG.ROTATE_CLOCKWISE, wall), "a wall blocks the swing")
RG.rotate(0, RG.ROTATE_ANTICLOCKWISE)
eq(RG.getOrientation(0), 3, "anticlockwise from 0 wraps to 270")
RG.rotate(0, RG.ROTATE_CLOCKWISE)
eq(RG.getOrientation(0), 0, "clockwise from 270 wraps to 0")
RG._p = nil
RG.setStore(nil, nil)
local RTP = require("src.core.game3.rotating_tile_puzzle")
eq(RTP.rotation(1, 0), "ccw", "arrow tiles rotate objects counterclockwise")
eq(RTP.rotation(0, 3), "ccw", "wrap from right arrow to up arrow is counterclockwise")
eq(RTP.rotation(1, 1), nil, "no rotation on the same tile")
local BerryTrees = require("src.core.game3.rse.berry_trees")
BerryTrees.berries = function()
return { [0] = { name = "CHERI", stageDuration = 3, maxYield = 3, minYield = 2 } }
end
local s = {}
local r = function() return 0 end
BerryTrees.plant(1, 1, BerryTrees.STAGE_PLANTED, true, s, r)
local tree = BerryTrees.peek(s, 1)
eq(tree.minutesUntilNextStage, 180, "stage duration is hours times 60")
check(BerryTrees.water(1, s), "planted tree can be watered")
BerryTrees.timeUpdate(s, 180, r)
eq(tree.stage, BerryTrees.STAGE_SPROUTED, "180 minutes grows one stage")
BerryTrees.timeUpdate(s, 540, r)
eq(tree.stage, BerryTrees.STAGE_BERRIES, "three more stages reach berries")
eq(tree.minutesUntilNextStage, 720, "berries stay four stage lengths")
eq(tree.berryYield, BerryTrees.yieldInternal(3, 2, 1, r), "yield uses the watered stage count")
BerryTrees.timeUpdate(s, 720, r)
eq(tree.stage, BerryTrees.STAGE_SPROUTED, "berries fall and the tree regrows from sprout")
eq(tree.regrowthCount, 1, "regrowth counted")
BerryTrees.timeUpdate(s, 180 * 71, r)
eq(BerryTrees.peek(s, 1).berry, 0, "a very long absence clears the tree")
BerryTrees.plant(2, 1, BerryTrees.STAGE_BERRIES, false, s, r)
BerryTrees.timeUpdate(s, 5000, r)
eq(BerryTrees.peek(s, 2).stage, BerryTrees.STAGE_BERRIES, "unseen trees do not grow")
BerryTrees.berries = nil
package.loaded["src.core.game3.rse.berry_trees"] = nil
local SpecialScene = require("src.core.game3.special_scene_rse")
local rows = SpecialScene.orbSpans(120, 80, 10)
eq(rows[80] and rows[80][1], 110, "orb window left edge at the center row")
eq(rows[80] and rows[80][2], 130, "orb window right edge at the center row")
check(rows[70] ~= nil and rows[91] == nil, "orb window spans the radius")
local ready, frames = false, 0
SpecialScene.startOrb(1, function() ready = true end)
eq(SpecialScene._orb.blue and SpecialScene._orb.cx, 136, "VAR_RESULT 1 is the blue orb centred at x 136")
while not ready and frames < 1000 do
SpecialScene.step()
frames = frames + 1
end
eq(frames, 320, "the orb window grows one pixel every two frames to radius 160")
local faded = false
SpecialScene.fadeOutOrb(function() faded = true end)
frames = 0
while not faded and frames < 1000 do
SpecialScene.step()
frames = frames + 1
end
check(faded and SpecialScene._orb == nil, "FadeOutOrbEffect blends the orb away and ends the effect")
eq(frames, 2 + 8 * 23, "orb fade-out takes 23 blend steps of 8 frames")
local Task = require("src.core.game3.task")
Task.clear()
FxRse._fc = { mirageTower = { crumblePositions = { { 0, 10, 65 }, { 17, 3, 50 } }, invisibleMetatiles = {} } }
local MirageTower = require("src.core.game3.mirage_tower")
local crumbled = false
MirageTower.ceilingCrumble(function() crumbled = true end)
eq(#MirageTower._crumbles, 4, "each crumble position drops a large and a small piece")
local n = 0
while not crumbled and n < 2000 do
Task.update(1 / 60)
n = n + 1
end
check(crumbled and MirageTower._crumbles == nil, "ceiling crumble ends once the pieces land and the shake stops")
FxRse._fc = nil
Task.clear()
local FieldEffects = require("src.core.game3.field_effects")
FieldEffects._manifest = { family = "rse", _byName = {} }
local Constants = require("src.core.game3.constants")
local C = Constants.of("emerald")
local used = {
"FLDEFF_NPCFLY_OUT", "FLDEFF_USE_STRENGTH", "FLDEFF_USE_SECRET_POWER_TREE", "FLDEFF_USE_SECRET_POWER_SHRUB",
"FLDEFF_USE_SECRET_POWER_CAVE", "FLDEFF_USE_ROCK_SMASH", "FLDEFF_USE_DIVE", "FLDEFF_USE_CUT_ON_TREE",
"FLDEFF_PCTURN_ON", "FLDEFF_USE_WATERFALL", "FLDEFF_USE_SURF", "FLDEFF_SAND_PILLAR", "FLDEFF_POKECENTER_HEAL",
"FLDEFF_HALL_OF_FAME_RECORD", "FLDEFF_DESTROY_DEOXYS_ROCK", "FLDEFF_SPARKLE",
}
for _, name in ipairs(used) do
check(C:id("field_effects", name) ~= nil, name .. " exists in Emerald")
check(FieldEffects.handlerFor(name) ~= nil, name .. " has a handler on Emerald")
end
FieldEffects._manifest = nil
local NB = require("src.core.game3.scripting.natives_berry")
local NP = require("src.core.game3.scripting.natives_puzzles_rse")
for _, mod in ipairs({ NB, NP }) do
for name in pairs(mod.BY_NAME) do
check(C:special(name) ~= nil, name .. " is an Emerald special")
end
end
GameVersion.set("firered")
FieldEffects._manifest = false
check(FieldEffects.rse() == nil, "FireRed never loads the RSE field effects")
check(not Objects.isRse(), "FireRed objects are not RSE")
local FieldView = require("src.core.game3.field_view")
eq(FieldView.maxFlashLevel(), 4, "FireRed keeps four flash levels")
eq(FieldView.radiusForLevel(2), 56, "FireRed flash level 2 radius")
FieldEffects._manifest = nil
GameVersion.set(before)
T.finish()