mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-10-25 14:38:42 -04:00
CLOSES #2759, CLOSES #2761, CLOSES #2762, CLOSES #2763, CLOSES #2764, CLOSES #2765, CLOSES #2766, CLOSES #2767
CLOSES #2759, CLOSES #2761, CLOSES #2762, CLOSES #2763, CLOSES #2764, CLOSES #2765, CLOSES #2766, CLOSES #2767
This commit is contained in:
@@ -1641,6 +1641,11 @@ function love.quit()
|
||||
require("src.import.LauncherWindow").observe(0)
|
||||
require("src.import.LauncherWindow").flush()
|
||||
end
|
||||
local osName = love.system and love.system.getOS and love.system.getOS()
|
||||
if osName == "NX" then
|
||||
endProcessOnce()
|
||||
return
|
||||
end
|
||||
if editorMode and EditorApp.quit then
|
||||
-- true blocks the quit (unsaved-changes prompt). A quit that proceeds
|
||||
-- must fall through to the worker shutdowns below instead of returning:
|
||||
@@ -1667,7 +1672,6 @@ function love.quit()
|
||||
-- docs/modding.md's core.quit_to_launcher entry) may veto returning to
|
||||
-- this Lua launcher via that hook. Vanilla behavior (used when no mod
|
||||
-- claims the hook) is exactly the condition below.
|
||||
local osName = love.system and love.system.getOS and love.system.getOS()
|
||||
local mobile = (osName == "Android" or osName == "iOS")
|
||||
local wouldReturnToLauncher = PlatformHooks.quitToLauncher(function()
|
||||
return Game and not Importer and not quitToLauncher and not scripted
|
||||
|
||||
+8
-1
@@ -1043,7 +1043,14 @@ function Game3:saveGame()
|
||||
end)
|
||||
if FieldModules.enabled("questLog", self.session) then QuestRecorder.save(self) end
|
||||
local P = package.loaded["src.core.game3.player"]
|
||||
if P and P.facing and not P.moving and P.cellX == self.session.x and P.cellY == self.session.y then self.session.facing = P.facing end
|
||||
if P and P.facing and not P.moving and P.cellX == self.session.x and P.cellY == self.session.y then
|
||||
self.session.facing = P.facing
|
||||
self.session.surfing = P.surfing == true
|
||||
self.session.underwater = P.underwater == true
|
||||
self.session.biking = P.biking == true
|
||||
self.session.bikeType = P.bikeType
|
||||
self.session.elevation = P.elevation
|
||||
end
|
||||
local save = Schema.toSaveTable(self.session)
|
||||
local rules = Schema.rulesFor(self.session.version)
|
||||
if rules.saveLocation then
|
||||
|
||||
@@ -63,6 +63,7 @@ Ui._pendingYesNo = nil
|
||||
Ui._session = nil
|
||||
Ui._active = 0
|
||||
Ui._actionCursor = {}
|
||||
Ui._actionCursorBattler = {}
|
||||
Ui._moveCursor = {}
|
||||
Ui._moveCursorMon = {}
|
||||
Ui._target = nil
|
||||
@@ -228,6 +229,7 @@ function Ui.reset(opts)
|
||||
Ui._pendingYesNo = nil
|
||||
Ui._active = 0
|
||||
Ui._actionCursor = {}
|
||||
Ui._actionCursorBattler = {}
|
||||
Ui._moveCursor = {}
|
||||
Ui._moveCursorMon = {}
|
||||
Ui._swap = nil
|
||||
@@ -715,12 +717,14 @@ function Ui.openMenu(battlerId, opts)
|
||||
Ui._target = nil
|
||||
Ui._active = tonumber(battlerId) or 0
|
||||
Ui._partnerAction = opts and opts.partnerAction or nil
|
||||
if is_double() then
|
||||
-- pokefirered/src/battle_controller_player.c:2421
|
||||
Ui._menuIndex = Ui._actionCursor[Ui._active] or 1
|
||||
else
|
||||
Ui._menuIndex = 1
|
||||
local battler = Ui._st and State.battler(Ui._st, Ui._active)
|
||||
-- pokefirered/src/battle_controller_player.c:2099
|
||||
if Ui._actionCursorBattler[Ui._active] ~= battler then
|
||||
Ui._actionCursorBattler[Ui._active] = battler
|
||||
Ui._actionCursor[Ui._active] = 1
|
||||
end
|
||||
-- pokefirered/src/battle_controller_player.c:2421
|
||||
Ui._menuIndex = Ui._actionCursor[Ui._active] or 1
|
||||
Ui._pendingCommand = nil
|
||||
Ui._wally = nil
|
||||
local Wally = require("src.core.game3.battle.tutorial_wally")
|
||||
@@ -1641,11 +1645,13 @@ function Ui.handleInput(input)
|
||||
local idx, moved = grid_nav(Ui._menuIndex, input, 4)
|
||||
if moved then
|
||||
Ui._menuIndex = idx
|
||||
Ui._actionCursor[Ui._active] = idx
|
||||
play_select()
|
||||
return true
|
||||
end
|
||||
if input:wasPressed("a") then
|
||||
play_select()
|
||||
Ui._actionCursor[Ui._active] = Ui._menuIndex
|
||||
if Ui._st and Ui._st.safari then
|
||||
-- pokefirered/src/battle_controller_safari.c:162
|
||||
local act = Commands.playerAction(Ui._st, Ui._menuIndex, nil)
|
||||
|
||||
@@ -851,8 +851,6 @@ function Collision.tryConnection(game, fromX, fromY, dir, run)
|
||||
-- Park one cell before landing and keep the step running so the seam does
|
||||
-- not hitch (same world pixels the neighbor strip already showed).
|
||||
local CELL = 16
|
||||
local WALK_FRAMES = 16
|
||||
local RUN_FRAMES = 8
|
||||
Player.cellX, Player.cellY = lx - d[1], ly - d[2]
|
||||
Player.px, Player.py = Player.cellX * CELL, Player.cellY * CELL
|
||||
Player.facing = dir
|
||||
@@ -865,7 +863,7 @@ function Collision.tryConnection(game, fromX, fromY, dir, run)
|
||||
Player.dismounting = Player.surfing and not landingWater or false
|
||||
if Player.dismounting then lazyReq("src.core.game3.audio").stopSurfMusic() end
|
||||
Player.spriteYOffset = 0
|
||||
Player.stepFrames = run and RUN_FRAMES or WALK_FRAMES
|
||||
Player.stepFrames, Player.running = Player.ordinaryStepFrames(run)
|
||||
Player.syncSavePosition(g)
|
||||
|
||||
if Collision.isGrass and Collision.isGrass(lx, ly) then
|
||||
|
||||
@@ -253,7 +253,10 @@ local function drawFieldPlane(game, vw, vh, Renderer)
|
||||
if Tilt.active() and not transitioning and Renderer and Renderer.beginUprightPass then
|
||||
FieldView.draw(game, vw, vh, { skipActors = true, exchangeCanvas = exchange })
|
||||
Renderer:beginUprightPass()
|
||||
FieldView.draw(game, vw, vh, { actorsOnly = true, billboard = true })
|
||||
FieldView.draw(game, vw, vh, {
|
||||
actorsOnly = true, billboard = true,
|
||||
endUnderActors = function(drawOverhead) Renderer:occludeUprightActors(drawOverhead) end,
|
||||
})
|
||||
Renderer:endUprightPass()
|
||||
else
|
||||
FieldView.draw(game, vw, vh, { exchangeCanvas = exchange })
|
||||
|
||||
@@ -1417,9 +1417,20 @@ function Field.executeFieldMove(payload)
|
||||
Flags.setFlag(Space.store, nil, payload.flag, true)
|
||||
end
|
||||
end
|
||||
FieldEffects.startFlash(function()
|
||||
Field.locked = false
|
||||
end)
|
||||
local profile = lazyReq("src.core.game3.profile").forSession(Field._session)
|
||||
local script = profile.field and profile.field.flashScript
|
||||
if script then
|
||||
local Space = lazyReq("src.core.game3.scripting.space")
|
||||
local key = assert(Space.scriptKey(script), "ROM Flash script is not in the script cache: " .. script)
|
||||
assert(Space.vm, "Flash requires the field script VM")
|
||||
-- pokeruby/src/script.c:230
|
||||
Space.vm.ctx.fieldControlsLocked = true
|
||||
assert(Space.startScript(key), "ROM Flash script could not start: " .. script)
|
||||
else
|
||||
FieldEffects.startFlash(function()
|
||||
Field.locked = false
|
||||
end)
|
||||
end
|
||||
end)
|
||||
elseif act == "dig" or act == "braille_rs_dig" then
|
||||
Field.locked = true
|
||||
|
||||
@@ -992,7 +992,8 @@ function FxRse.collectActors(actors)
|
||||
local eo = O._byId[lid]
|
||||
if eo and eo.visible and not eo.hidden then
|
||||
local d = eo.disguise
|
||||
if d and not d.done then
|
||||
-- pokeemerald/src/field_effect_helpers.c:1362
|
||||
if d and not d.done and not eo.invisible then
|
||||
local sheet = FE().loadSheet(d.sheet)
|
||||
local q = sheet and d.a and sheet.quads[d.a.frame or 0]
|
||||
if q then
|
||||
@@ -1004,6 +1005,8 @@ function FxRse.collectActors(actors)
|
||||
love.graphics.draw(sheet.image, q, math.floor(x) - camX, math.floor(y) - camY)
|
||||
love.graphics.setColor(1, 1, 1, 1)
|
||||
end)
|
||||
-- pokeemerald/src/field_effect_helpers.c:1365
|
||||
actors[#actors].disguiseObject = eo
|
||||
end
|
||||
end
|
||||
local bt = eo.berryTree
|
||||
|
||||
@@ -513,14 +513,18 @@ local function sortActors(a, b)
|
||||
return a.subpriority > b.subpriority
|
||||
end
|
||||
|
||||
local drawOrderObjects = {}
|
||||
|
||||
-- event_object_movement.c:7739-7754, scrcmd.c:1130
|
||||
local function applyDrawOrder(actors, underActors, overActors, camY)
|
||||
underActors = underActors or {}
|
||||
overActors = overActors or {}
|
||||
for i = #underActors, 1, -1 do underActors[i] = nil end
|
||||
for i = #overActors, 1, -1 do overActors[i] = nil end
|
||||
for obj in pairs(drawOrderObjects) do drawOrderObjects[obj] = nil end
|
||||
for _, a in ipairs(actors) do
|
||||
local obj = a.eventObject
|
||||
if obj then drawOrderObjects[obj] = a end
|
||||
if (obj and obj.fixedPriority) or a.fixedPriority then
|
||||
a.fixedPriority = true
|
||||
if obj and obj.fixedClass == nil then obj.fixedClass = a.priority or actorPriority(a) end
|
||||
@@ -542,6 +546,11 @@ local function applyDrawOrder(actors, underActors, overActors, camY)
|
||||
underActors[#underActors + 1] = a
|
||||
end
|
||||
end
|
||||
-- pokeemerald/src/field_effect_helpers.c:1365
|
||||
for _, a in ipairs(actors) do
|
||||
local linked = a.disguiseObject and drawOrderObjects[a.disguiseObject]
|
||||
if linked then a.subpriority = (linked.subpriority - 1) % 256 end
|
||||
end
|
||||
table.sort(underActors, sortActors)
|
||||
table.sort(overActors, sortActors)
|
||||
return underActors, overActors
|
||||
@@ -1787,6 +1796,7 @@ function FieldView.draw(game, canvasW, canvasH, opts)
|
||||
for _, a in ipairs(underActors) do
|
||||
drawSingleActor(game, mapDef, a, camX, camY)
|
||||
end
|
||||
if usedNative and opts.endUnderActors then opts.endUnderActors(drawNativeOverTiles) end
|
||||
end
|
||||
|
||||
-- pret BG1: metatile top layer covers normal OW sprites (roofs, desk counters, trees).
|
||||
|
||||
+50
-4
@@ -453,9 +453,40 @@ function Map.ensureMidLayout(game, mapId, def)
|
||||
return def.midLayout
|
||||
end
|
||||
|
||||
--- Load a Sevii map under game3 ownership (pret enter order).
|
||||
-- 1) Bind game3 player + collision + EventObjects
|
||||
-- 2) Objects.loadMap then Space.runEnterScripts (ON_TRANSITION → ON_FRAME)
|
||||
-- pokeemerald/src/overworld.c:911
|
||||
-- pokefirered/src/overworld.c:878
|
||||
function Map.applyInitialAvatar(session, def, saved, previous)
|
||||
if not session then return end
|
||||
local Player = require("src.core.game3.player")
|
||||
if saved and (saved.surfing ~= nil or saved.underwater ~= nil) then
|
||||
Player.restoreAvatar(saved)
|
||||
return
|
||||
end
|
||||
local Flags = require("src.core.game3.scripting.flags")
|
||||
local version = session.version or require("src.core.game3.profile").active().id
|
||||
local cruiseId = Flags.forVersion(version).IDS.FLAG_SYS_CRUISE_MODE
|
||||
local cruise = cruiseId and Flags.getFlag(session, nil, cruiseId)
|
||||
local mapType = def and tonumber(def.mapType)
|
||||
local isRse = session and require("src.core.game3.profile").family(session) == "rse"
|
||||
local Collision = require("src.core.game3.collision")
|
||||
local surfable = Collision.isSurfable(Collision.behavior(Player.cellX, Player.cellY))
|
||||
local mapId = session and session.map or ""
|
||||
local seafoam = not isRse and (mapId == "FR_SEAFOAM_ISLANDS_B3F" or mapId == "FR_SEAFOAM_ISLANDS_B4F")
|
||||
Player.surfing, Player.underwater = false, false
|
||||
if cruise and mapType ~= 8 then
|
||||
Player.biking = false
|
||||
if mapType == 6 then Player.facing = "right" end
|
||||
elseif mapType == 5 then
|
||||
Player.underwater, Player.biking = true, false
|
||||
elseif surfable and not seafoam then
|
||||
Player.surfing, Player.biking = true, false
|
||||
end
|
||||
if previous and ((previous.underwater and Player.surfing)
|
||||
or (previous.surfing and Player.underwater)) then
|
||||
Player.facing = previous.facing
|
||||
end
|
||||
end
|
||||
|
||||
function Map.load(mod, game, mapId, opts)
|
||||
opts = opts or {}
|
||||
if not MapIds.isGame3Map(mapId) then
|
||||
@@ -557,6 +588,13 @@ function Map.load(mod, game, mapId, opts)
|
||||
local session = (Runtime.getSession and Runtime.getSession()) or (game and game.session)
|
||||
local save = game and game.save
|
||||
local Player = require("src.core.game3.player")
|
||||
local previousAvatar = { surfing = Player.surfing, underwater = Player.underwater, facing = Player.facing }
|
||||
local savedAvatar = session and session._savedAvatar
|
||||
if (opts.enterVia or Map._nextEnterVia) ~= "continue" or not savedAvatar
|
||||
or savedAvatar.map ~= mapId or savedAvatar.x ~= x or savedAvatar.y ~= y
|
||||
or (session and session._continueWarpDeferred) then
|
||||
savedAvatar = nil
|
||||
end
|
||||
local onCyclingRoad = Player.isOnCyclingRoad and Player.isOnCyclingRoad(session, x, y, def)
|
||||
local wasBiking = (Player.biking == true)
|
||||
if opts.initialLoad and not wasBiking then
|
||||
@@ -613,7 +651,6 @@ function Map.load(mod, game, mapId, opts)
|
||||
end
|
||||
-- pokefirered/src/overworld.c:2145 SetPlayerAvatarTransitionFlags
|
||||
Player.biking = keepBike
|
||||
Player.syncSavePosition(game)
|
||||
|
||||
local okFv, FieldView = pcall(require, "src.core.game3.field_view")
|
||||
if okFv and FieldView then FieldView._nativeDirty = true end
|
||||
@@ -646,6 +683,15 @@ function Map.load(mod, game, mapId, opts)
|
||||
Collision.clear()
|
||||
end
|
||||
|
||||
if not opts.seamless then
|
||||
Map.applyInitialAvatar(session, def, savedAvatar, previousAvatar)
|
||||
if session then
|
||||
session._savedAvatar = nil
|
||||
session.facing = Player.facing
|
||||
end
|
||||
end
|
||||
Player.syncSavePosition(game)
|
||||
|
||||
Map._warmPairs = not opts.seamless or nil
|
||||
-- pret GroundEffect_SpawnOnTallGrass when warping onto grass.
|
||||
if not opts.seamless then
|
||||
|
||||
@@ -1173,11 +1173,11 @@ local function tickMotion(eo, game, ctx)
|
||||
end
|
||||
|
||||
--- Scripted one-cell step (no collision — FRLG applymovement forces).
|
||||
function Objects.scriptStep(eo, dir, run, slow, fast)
|
||||
function Objects.scriptStep(eo, dir, run, slow, fast, frames)
|
||||
if not eo then return false end
|
||||
local P = Player()
|
||||
if eo == P then
|
||||
return P.scriptStep and P.scriptStep(dir, run, slow, fast)
|
||||
return P.scriptStep and P.scriptStep(dir, run, slow, fast, frames)
|
||||
end
|
||||
if eo.moving then return false end
|
||||
local d = DELTA[dir]
|
||||
@@ -1188,6 +1188,7 @@ function Objects.scriptStep(eo, dir, run, slow, fast)
|
||||
-- pokefirered/src/event_object_movement.c:5333 StartRunningAnim
|
||||
if run then eo.stepFrames = slow and RUN_SLOW_FRAMES or RUN_FRAMES end
|
||||
if fast then eo.stepFrames = RUN_FRAMES end
|
||||
if frames then eo.stepFrames = frames end
|
||||
eo.frozen = true
|
||||
eo.scriptBusy = true
|
||||
return true
|
||||
@@ -1307,9 +1308,9 @@ local function advanceTrack(lid, tr, game)
|
||||
if type(act) == "table" then
|
||||
if act.kind == "step" then
|
||||
if eo == Player() then
|
||||
if Player().scriptStep then Player().scriptStep(act.dir, act.run, act.slow, act.fast) end
|
||||
if Player().scriptStep then Player().scriptStep(act.dir, act.run, act.slow, act.fast, act.frames) end
|
||||
elseif eo then
|
||||
Objects.scriptStep(eo, act.dir, act.run, act.slow, act.fast)
|
||||
Objects.scriptStep(eo, act.dir, act.run, act.slow, act.fast, act.frames)
|
||||
end
|
||||
elseif act.kind == "jump" then
|
||||
if eo == Player() then
|
||||
@@ -1355,6 +1356,7 @@ local function advanceTrack(lid, tr, game)
|
||||
Objects.revealTrainer(eo)
|
||||
elseif act.kind == "turn" then
|
||||
Objects.scriptFace(eo, act.dir)
|
||||
if act.frames then tr.sleep = act.frames end
|
||||
elseif act.kind == "face_player" then
|
||||
-- pokefirered/src/event_object_movement.c:6772 MovementAction_FacePlayer_Step0
|
||||
if eo and eo ~= Player() then
|
||||
|
||||
@@ -192,6 +192,7 @@ function Player.reset(x, y, facing)
|
||||
-- whiteout out of the water must not leave surfing set -- Collision.canEnter
|
||||
-- reads Player.surfing and would treat water as walkable on land.
|
||||
Player.surfing = false
|
||||
Player.underwater = false
|
||||
Player.surfHopping = false
|
||||
Player.dismounting = false
|
||||
Player.prevCellX = Player.cellX
|
||||
@@ -230,6 +231,18 @@ function Player.syncFromSession(session)
|
||||
if session.bikeType ~= nil then
|
||||
Player.bikeType = session.bikeType
|
||||
end
|
||||
Player.restoreAvatar(session)
|
||||
end
|
||||
|
||||
-- pokeemerald/src/field_player_avatar.c:1373
|
||||
function Player.restoreAvatar(state)
|
||||
if not state then return end
|
||||
Player.underwater = state.underwater == true
|
||||
Player.surfing = not Player.underwater and state.surfing == true
|
||||
Player.biking = not Player.underwater and not Player.surfing and state.biking == true
|
||||
Player.bikeType = state.bikeType
|
||||
if state.elevation ~= nil then Player.elevation = tonumber(state.elevation) or 3 end
|
||||
Player.surfHopping, Player.dismounting = false, false
|
||||
end
|
||||
|
||||
function Player.syncFromHost(game)
|
||||
@@ -249,17 +262,27 @@ end
|
||||
--- Write avatar coords into save.position (ferry / host save). No host entity mirror.
|
||||
function Player.syncSavePosition(game)
|
||||
local save = game and game.save
|
||||
if not (save and save.position) then return end
|
||||
save.position.x = Player.cellX
|
||||
save.position.y = Player.cellY
|
||||
save.position.facing = Player.facing
|
||||
save.position.biking = (Player.biking == true)
|
||||
save.biking = (Player.biking == true)
|
||||
if save then
|
||||
if save.position then
|
||||
save.position.x = Player.cellX
|
||||
save.position.y = Player.cellY
|
||||
save.position.facing = Player.facing
|
||||
save.position.biking = Player.biking == true
|
||||
end
|
||||
save.biking = Player.biking == true
|
||||
save.surfing = Player.surfing == true
|
||||
save.underwater = Player.underwater == true
|
||||
save.elevation = Player.elevation
|
||||
end
|
||||
local session = package.loaded["src.core.game3.runtime"]
|
||||
session = session and session.getSession and session.getSession()
|
||||
if session then
|
||||
session.biking = (Player.biking == true)
|
||||
if session.map then
|
||||
session.surfing = Player.surfing == true
|
||||
session.underwater = Player.underwater == true
|
||||
session.bikeType = Player.bikeType
|
||||
session.elevation = Player.elevation
|
||||
if session.map and save and save.position then
|
||||
save.position.map = session.map
|
||||
end
|
||||
end
|
||||
@@ -629,9 +652,21 @@ function Player.tryMove(dir, game, run)
|
||||
end
|
||||
|
||||
beginStep(tx, ty, run, false)
|
||||
if not Player.jumping and not Player.biking then
|
||||
Player.stepFrames, Player.running = Player.ordinaryStepFrames(run)
|
||||
end
|
||||
return "step"
|
||||
end
|
||||
|
||||
-- pokeemerald/src/field_player_avatar.c:651
|
||||
function Player.ordinaryStepFrames(run)
|
||||
if Player.surfHopping or Player.dismounting then return WALK_FRAMES, false end
|
||||
if Player.surfing and not Player.underwater and not Player.biking and not Player.action then
|
||||
return RUN_FRAMES, false
|
||||
end
|
||||
return run and RUN_FRAMES or WALK_FRAMES, run == true
|
||||
end
|
||||
|
||||
-- pokefirered/src/metatile_behavior.c:668 MetatileBehavior_IsCyclingRoadPullDownTile
|
||||
local function isCyclingRoadPullDown(beh)
|
||||
if Collision.isCyclingRoadPullDown then
|
||||
@@ -777,7 +812,7 @@ function Player.forcedStep(dir, frames, opts)
|
||||
end
|
||||
|
||||
--- Forced script step (applymovement localId 0xFF) — skips collision.
|
||||
function Player.scriptStep(dir, run, slow, fast)
|
||||
function Player.scriptStep(dir, run, slow, fast, frames)
|
||||
if Player.moving then return false end
|
||||
local d = DELTA[dir or Player.facing]
|
||||
if not d then return false end
|
||||
@@ -804,6 +839,7 @@ function Player.scriptStep(dir, run, slow, fast)
|
||||
-- pokefirered/src/event_object_movement.c:9029 UpdateRunSlowAnim
|
||||
if run and slow then Player.stepFrames = RUN_SLOW_FRAMES end
|
||||
if fast then Player.stepFrames = RUN_FRAMES end
|
||||
if frames then Player.stepFrames = frames end
|
||||
return true
|
||||
end
|
||||
|
||||
@@ -942,7 +978,6 @@ local function finishStep(game)
|
||||
Player.jumpType = nil
|
||||
Player.spriteYOffset = 0
|
||||
Player.updateElevation(Player.cellX, Player.cellY)
|
||||
Player.syncSavePosition(game)
|
||||
|
||||
-- Surf landing / dismount state transitions
|
||||
local wasSurfing = Player.surfing or Player.dismounting
|
||||
@@ -969,6 +1004,7 @@ local function finishStep(game)
|
||||
end)
|
||||
end
|
||||
end
|
||||
Player.syncSavePosition(game)
|
||||
|
||||
if ModRuntime.wants("world.stepped") then
|
||||
local Map = package.loaded["src.core.game3.map"]
|
||||
|
||||
@@ -53,6 +53,8 @@ return {
|
||||
invalidGfx = "OBJ_EVENT_GFX_NINJA_BOY",
|
||||
-- pokeemerald/data/scripts/field_move_scripts.inc:60
|
||||
fieldMoveScripts = true,
|
||||
-- pokeemerald/data/scripts/flash.inc:1
|
||||
flashScript = "EventScript_UseFlash",
|
||||
-- pokeemerald/include/constants/event_objects.h:89
|
||||
fieldMoveGfx = {
|
||||
CUT_TREE = "OBJ_EVENT_GFX_CUTTABLE_TREE",
|
||||
|
||||
@@ -49,6 +49,8 @@ return {
|
||||
-- pokeruby/src/event_object_movement.c:1767
|
||||
invalidGfx = "OBJ_EVENT_GFX_LITTLE_BOY_1",
|
||||
fieldMoveScripts = true,
|
||||
-- pokeruby/src/fldeff_flash.c:111
|
||||
flashScript = "gUnknown_081B694A",
|
||||
fieldMoveGfx = {
|
||||
CUT_TREE = "OBJ_EVENT_GFX_CUTTABLE_TREE",
|
||||
ROCK_SMASH_ROCK = "OBJ_EVENT_GFX_BREAKABLE_ROCK",
|
||||
|
||||
@@ -41,6 +41,8 @@ TextAliases.gText_NothingToCut = "OtherText_NothingToCut"
|
||||
TextAliases.gText_CantSurfHere = "OtherText_CantSurf"
|
||||
TextAliases.gText_AlreadySurfing = "OtherText_AlreadySurfing"
|
||||
TextAliases.gText_CantUseHere = "OtherText_CantUseThatHere"
|
||||
-- pokeruby/src/party_menu.c:250
|
||||
TextAliases.gText_InUseAlready_PM = "OtherText_CantUseThatHere"
|
||||
TextAliases.gText_NotEnoughHp = "OtherText_NotEnoughHP"
|
||||
TextAliases.gText_CantUseUntilNewBadge = "gOtherText_CantBeUsedBadge"
|
||||
-- pokeruby/data/field_move_scripts.inc:10
|
||||
|
||||
@@ -219,10 +219,21 @@ function Runtime.start(mod, game, session, opts)
|
||||
end
|
||||
end
|
||||
Player.biking = keepBike
|
||||
if session then session.biking = keepBike end
|
||||
local Collision = lazyReq("src.core.game3.collision")
|
||||
if def then Collision.bindMap(game, session.map, def) end
|
||||
local savedAvatar = session._savedAvatar
|
||||
if savedAvatar and (savedAvatar.map ~= session.map or savedAvatar.x ~= session.x
|
||||
or savedAvatar.y ~= session.y or session._continueWarpDeferred) then savedAvatar = nil end
|
||||
local adoptedAvatar = (session.surfing ~= nil or session.underwater ~= nil) and {
|
||||
surfing = session.surfing, underwater = session.underwater,
|
||||
biking = keepBike, bikeType = session.bikeType, elevation = session.elevation,
|
||||
} or nil
|
||||
Map.applyInitialAvatar(session, def, savedAvatar or adoptedAvatar)
|
||||
session._savedAvatar = nil
|
||||
if session then session.biking = Player.biking end
|
||||
if game and game.save then
|
||||
game.save.biking = keepBike
|
||||
if game.save.position then game.save.position.biking = keepBike end
|
||||
game.save.biking = Player.biking
|
||||
if game.save.position then game.save.position.biking = Player.biking end
|
||||
end
|
||||
Player.syncSavePosition(game)
|
||||
-- Space.onMapEnter already ran (or will run) from afterMap — don't double.
|
||||
@@ -233,6 +244,8 @@ function Runtime.start(mod, game, session, opts)
|
||||
x = session.x,
|
||||
y = session.y,
|
||||
facing = session.facing,
|
||||
initialLoad = true,
|
||||
enterVia = opts.reason == "continue" and "continue" or nil,
|
||||
depth1Connections = true,
|
||||
})
|
||||
log("warped via game3 map loader → " .. tostring(session.map))
|
||||
|
||||
@@ -337,10 +337,10 @@ function Schema.toSaveTable(session)
|
||||
x = session.x,
|
||||
y = session.y,
|
||||
facing = session.facing,
|
||||
biking = (package.loaded["src.core.game3.player"] and package.loaded["src.core.game3.player"].biking ~= nil)
|
||||
and (package.loaded["src.core.game3.player"].biking == true)
|
||||
or (session and session.biking == true)
|
||||
or false,
|
||||
biking = session.biking == true,
|
||||
surfing = session.surfing,
|
||||
underwater = session.underwater,
|
||||
elevation = session.elevation,
|
||||
bikeType = session.bikeType,
|
||||
healMap = session.healMap,
|
||||
healX = session.healX,
|
||||
@@ -460,6 +460,9 @@ function Schema.fromSaveTable(save)
|
||||
y = save.y or MapIds.newGameStart(version).y,
|
||||
facing = save.facing or "down",
|
||||
biking = save.biking == true,
|
||||
surfing = save.surfing,
|
||||
underwater = save.underwater,
|
||||
elevation = tonumber(save.elevation),
|
||||
bikeType = save.bikeType,
|
||||
healMap = save.healMap,
|
||||
healX = save.healX,
|
||||
@@ -515,7 +518,16 @@ function Schema.fromSaveTable(save)
|
||||
require("src.core.game3.save_sections").restore(save, session, version)
|
||||
require("src.core.game3.save_mon").each(session, require("src.core.game3.save_mon").normalize)
|
||||
rules.resetStateOnContinue(session)
|
||||
session._savedAvatar = {
|
||||
map = session.map, x = session.x, y = session.y,
|
||||
surfing = session.surfing, underwater = session.underwater,
|
||||
biking = session.biking, bikeType = session.bikeType, elevation = session.elevation,
|
||||
}
|
||||
local warpFlags = session.specialSaveWarpFlags
|
||||
rules.useContinueGameWarp(session)
|
||||
if session.specialSaveWarpFlags ~= warpFlags or session._continueWarpDeferred then
|
||||
session._savedAvatar = nil
|
||||
end
|
||||
Schema.ensureMonBalls(session)
|
||||
local legacySecret = save.secretId == nil and session.secretId == nil
|
||||
Schema.repairOwnMons(session)
|
||||
|
||||
@@ -89,6 +89,7 @@ Movement.decodeRse = rseAction
|
||||
|
||||
function Movement.decodeAction(b)
|
||||
b = tonumber(b) or 0
|
||||
local rseFamily = require("src.core.game3.profile").family(nil) == "rse"
|
||||
if b == Movement.STEP_END or b == 0xFF then
|
||||
return { kind = "end" }
|
||||
end
|
||||
@@ -104,10 +105,10 @@ function Movement.decodeAction(b)
|
||||
if b >= 0x14 and b <= 0x17 then
|
||||
return { kind = "jump", dir = DIR[b - 0x14], distance = 2 }
|
||||
end
|
||||
-- Delay 1 / 2 / 4 / 8 / 16 frames (scaled up for host step rate).
|
||||
-- pokeemerald/src/event_object_movement.c:5609
|
||||
if b >= 0x18 and b <= 0x1C then
|
||||
local frames = ({ [0x18] = 2, [0x19] = 4, [0x1A] = 8, [0x1B] = 16, [0x1C] = 32 })[b]
|
||||
return { kind = "sleep", frames = frames or 8 }
|
||||
local frames = 2 ^ (b - 0x18)
|
||||
return { kind = "sleep", frames = rseFamily and frames or frames * 2 }
|
||||
end
|
||||
-- Walk fast / in-place / faster walks → step or turn-in-place.
|
||||
if b >= 0x1D and b <= 0x20 then
|
||||
@@ -116,13 +117,20 @@ function Movement.decodeAction(b)
|
||||
return { kind = "step", dir = DIR[b - 0x1D], fast = fast }
|
||||
end
|
||||
if b >= 0x21 and b <= 0x30 then
|
||||
return { kind = "turn", dir = DIR[(b - 0x21) % 4] }
|
||||
-- pokeemerald/src/event_object_movement.c:5811
|
||||
local frames = rseFamily and b >= 0x2D and 4 or nil
|
||||
return { kind = "turn", dir = DIR[(b - 0x21) % 4], frames = frames }
|
||||
end
|
||||
-- pokeemerald/src/event_object_movement.c:5827
|
||||
if rseFamily and b >= 0x31 and b <= 0x34 then
|
||||
return { kind = "step", dir = DIR[b - 0x31], frames = 6 }
|
||||
end
|
||||
if b >= 0x35 and b <= 0x38 then
|
||||
return { kind = "step", dir = DIR[b - 0x35] }
|
||||
end
|
||||
if b >= 0x39 and b <= 0x3C then
|
||||
return { kind = "step", dir = DIR[b - 0x39] }
|
||||
-- pokeemerald/src/event_object_movement.c:5956
|
||||
return { kind = "step", dir = DIR[b - 0x39], frames = rseFamily and 2 or nil }
|
||||
end
|
||||
-- pokefirered/src/event_object_movement.c:5333 StartRunningAnim (MOVE_SPEED_FAST_1)
|
||||
if b >= 0x3D and b <= 0x40 then
|
||||
|
||||
@@ -122,8 +122,10 @@ function Renderer:releaseCanvases()
|
||||
releaseCanvas(self.battleHUDCanvas); self.battleHUDCanvas = nil
|
||||
releaseCanvas(self.worldCanvas); self.worldCanvas = nil
|
||||
releaseCanvas(self.uprightCanvas); self.uprightCanvas = nil
|
||||
releaseCanvas(self.tiltOverheadCanvas); self.tiltOverheadCanvas = nil
|
||||
self.worldActive = false
|
||||
self.uprightActive = false
|
||||
self.uprightOccluded = false
|
||||
self.worldOverride = nil
|
||||
end
|
||||
|
||||
@@ -347,6 +349,7 @@ function Renderer:beginFrame(transparent)
|
||||
self.uiOpaque = not transparent
|
||||
self.worldActive = false
|
||||
self.uprightActive = false
|
||||
self.uprightOccluded = false
|
||||
self.worldOverride = nil
|
||||
-- warp-fade overlay from Transition (issue #121); cleared each frame so
|
||||
-- a popped transition cannot leave a sticky black veil
|
||||
@@ -590,6 +593,51 @@ function Renderer:tiltMesh()
|
||||
return self._tiltMesh or nil
|
||||
end
|
||||
|
||||
function Renderer:occludeUprightActors(drawOverhead)
|
||||
local mesh = self:tiltMesh()
|
||||
if not mesh then return end
|
||||
if self._tiltOcclusionShader == nil then
|
||||
local source, replaced = TILT_SHADER:gsub("return Texel%(tex, tc / vScale%) %* color;", [[
|
||||
float a = Texel(tex, tc / vScale).a;
|
||||
if (a <= 0.0) discard;
|
||||
return vec4(1.0 - a);
|
||||
]])
|
||||
assert(replaced == 1)
|
||||
self._tiltOcclusionShader = love.graphics.newShader(source)
|
||||
end
|
||||
local shader = self._tiltOcclusionShader
|
||||
local vw, vh = self.worldCanvas:getWidth(), self.worldCanvas:getHeight()
|
||||
local mask = self.tiltOverheadCanvas
|
||||
if not mask or mask:getWidth() ~= vw or mask:getHeight() ~= vh then
|
||||
if mask and mask.release then mask:release() end
|
||||
mask = PixelCanvas.new(vw, vh, "linear")
|
||||
self.tiltOverheadCanvas = mask
|
||||
end
|
||||
love.graphics.push("all")
|
||||
love.graphics.origin()
|
||||
love.graphics.setCanvas(mask)
|
||||
love.graphics.setShader()
|
||||
love.graphics.setStencilTest()
|
||||
love.graphics.setScissor()
|
||||
love.graphics.setBlendMode("alpha", "alphamultiply")
|
||||
love.graphics.clear(0, 0, 0, 0)
|
||||
love.graphics.setColor(1, 1, 1, 1)
|
||||
drawOverhead()
|
||||
love.graphics.pop()
|
||||
|
||||
love.graphics.push("all")
|
||||
love.graphics.setShader(shader)
|
||||
love.graphics.setStencilTest()
|
||||
love.graphics.setScissor()
|
||||
love.graphics.setBlendMode("multiply", "premultiplied")
|
||||
love.graphics.setColor(1, 1, 1, 1)
|
||||
mesh:setTexture(mask)
|
||||
mesh:setVertices(Tilt.meshCorners(vw, vh))
|
||||
love.graphics.draw(mesh)
|
||||
love.graphics.pop()
|
||||
self.uprightOccluded = true
|
||||
end
|
||||
|
||||
-- Draw the world pass through the tilt projection. Two steps: (1) a
|
||||
-- canvas-to-canvas palette pre-pass that bakes the SGB world zones into a
|
||||
-- colorized ground canvas in flat space (a perspective transform breaks
|
||||
@@ -1157,7 +1205,13 @@ function Renderer:endFrame(zones, worldZones)
|
||||
local M = self.UPRIGHT_MARGIN
|
||||
love.graphics.setColor(1, 1, 1, 1)
|
||||
love.graphics.setScissor(vux, vuy, vuw, vuh)
|
||||
local blend, alpha
|
||||
if self.uprightOccluded then
|
||||
blend, alpha = love.graphics.getBlendMode()
|
||||
love.graphics.setBlendMode("alpha", "premultiplied")
|
||||
end
|
||||
love.graphics.draw(self.uprightCanvas, wox - M * sx, woy - M * sy, 0, sx, sy)
|
||||
if self.uprightOccluded then love.graphics.setBlendMode(blend, alpha) end
|
||||
love.graphics.setScissor()
|
||||
end
|
||||
-- Screen-space warp fade (Transition) over the full world composite so
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
local U = require("tests.drivers.util")
|
||||
return function(game)
|
||||
local failures = 0
|
||||
local function check(ok, label)
|
||||
print((ok and "PASS " or "FAIL ") .. label)
|
||||
if not ok then failures = failures + 1 end
|
||||
return ok
|
||||
end
|
||||
local function finish()
|
||||
print("RESULT 2764 failures=" .. failures)
|
||||
love.event.quit(failures == 0 and 0 or 1)
|
||||
end
|
||||
for _ = 1, 600 do
|
||||
if game.phase == "boot" and game.boot then break end
|
||||
U.wait(1)
|
||||
end
|
||||
if not check(game.phase == "boot", "2764 boot ready") then return finish() end
|
||||
game:_handleBootAction({ action = "new_game", name = "TIMING" })
|
||||
U.wait(180)
|
||||
local Runtime = require("src.core.game3.runtime")
|
||||
local Map = require("src.core.game3.map")
|
||||
local Player = require("src.core.game3.player")
|
||||
local Objects = require("src.core.game3.objects")
|
||||
local Space = require("src.core.game3.scripting.space")
|
||||
local Field = require("src.core.game3.field")
|
||||
local Message = require("src.ui.game3.message")
|
||||
local shots = os.getenv("POKEPORT_SHOT_DIR")
|
||||
require("src.core.game3.encounters").onStep = function() return nil end
|
||||
require("src.core.game3.trainer_sight").check = function() return false end
|
||||
local function enter(mapId, x, y)
|
||||
if Space.vm and Space.vm:isRunning() then Space.vm:halt(false) end
|
||||
Message.reset()
|
||||
Map.load(Runtime._mod, game, mapId, { x = x, y = y, facing = "left" })
|
||||
U.wait(90)
|
||||
if Space.vm and Space.vm:isRunning() then Space.vm:halt(false) end
|
||||
Message.reset()
|
||||
Player.reset(x, y, "left")
|
||||
Field.locked = true
|
||||
end
|
||||
local function shot(name)
|
||||
return shots and U.still(game, shots .. "/" .. name)
|
||||
end
|
||||
enter("EM_ROUTE119_WEATHER_INSTITUTE_2F", 5, 6)
|
||||
local grunt, scientist = assert(Objects.find(7)), assert(Objects.find(5))
|
||||
scientist.cellX, scientist.cellY, scientist.px, scientist.py = 1, 6, 16, 96
|
||||
check(Space.vm:start("g3:0826ffc8"), "2764 cached Shelly continuation starts")
|
||||
local shove, contact, dialogue = false, false, false
|
||||
for _ = 1, 1800 do
|
||||
grunt, scientist = assert(Objects.find(7)), assert(Objects.find(5))
|
||||
if not shove and Player.cellY == 5 and grunt.moving then
|
||||
shove = check(true, "2764 Weather north shove precedes grunt completion")
|
||||
check(shot("2764_01_weather_north_shove.png"), "2764 Weather shove frame captured")
|
||||
end
|
||||
if not contact and grunt.cellX == 5 then
|
||||
contact = true
|
||||
check(Player.cellY == 5, "2764 Weather player vacates grunt destination")
|
||||
end
|
||||
if scientist.cellX == 4 and not scientist.moving and Message.isOpen() then
|
||||
dialogue = true
|
||||
check(Player.cellX == 5 and Player.cellY == 6 and scientist.cellY == 6,
|
||||
"2764 Weather dialogue player5,6 scientist4,6")
|
||||
if Message.isTyping() then Message.skipReveal() end
|
||||
check(shot("2764_02_weather_dialogue_positions.png"), "2764 Weather dialogue frame captured")
|
||||
break
|
||||
end
|
||||
if Message.isTyping() then Message.skipReveal() end
|
||||
if Message.isWaiting() then U.tap(game, "a") end
|
||||
U.wait(1)
|
||||
end
|
||||
check(shove and contact and dialogue, "2764 Weather cached continuation reaches dialogue")
|
||||
enter("EM_SLATEPORT_CITY_OCEANIC_MUSEUM_2F", 12, 6)
|
||||
Objects.addObject(2)
|
||||
Objects.addObject(4)
|
||||
local archie, other = assert(Objects.find(2)), assert(Objects.find(4))
|
||||
other.cellX, other.cellY, other.px, other.py = 10, 6, 160, 96
|
||||
archie.hidden, other.hidden = false, false
|
||||
local tail
|
||||
for _, rows in pairs(Space.bundle.scripts) do
|
||||
if type(rows) == "table" then
|
||||
for i, row in ipairs(rows) do
|
||||
if row.op == "applymovement" and row[1] == 2 and row[2] == "g3:0820bcd8" then
|
||||
tail = {}
|
||||
for j = i, #rows do tail[#tail + 1] = rows[j] end
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
if tail then break end
|
||||
end
|
||||
if not check(tail ~= nil, "2764 Museum cached Archie arrival found") then return finish() end
|
||||
Space.vm.scripts["2764_museum_tail"] = tail
|
||||
check(Space.vm:start("2764_museum_tail"), "2764 Museum cached continuation starts")
|
||||
local vacates, arrived = false, false
|
||||
for _ = 1, 600 do
|
||||
if not vacates and archie.moving and other.moving and other.py > 96 then
|
||||
vacates = check(true, "2764 Museum grunt vacates during Archie approach")
|
||||
check(shot("2764_03_museum_grunt_shove.png"), "2764 Museum shove frame captured")
|
||||
end
|
||||
if archie.cellX == 10 and archie.cellY == 6 and not archie.moving and Message.isOpen() then
|
||||
arrived = true
|
||||
check(other.cellX == 10 and other.cellY == 7, "2764 Museum grunt destination10,7")
|
||||
check(Player.cellX == 12 and Player.cellY == 6, "2764 Museum player remains12,6")
|
||||
if Message.isTyping() then Message.skipReveal() end
|
||||
check(shot("2764_04_museum_dialogue_positions.png"), "2764 Museum dialogue frame captured")
|
||||
break
|
||||
end
|
||||
U.wait(1)
|
||||
end
|
||||
check(vacates and arrived, "2764 Museum cached continuation reaches dialogue")
|
||||
finish()
|
||||
end
|
||||
@@ -0,0 +1,132 @@
|
||||
local U = require("tests.drivers.util")
|
||||
local DIR = os.getenv("POKEPORT_SHOT_DIR")
|
||||
|
||||
return function(game)
|
||||
local failures = 0
|
||||
local function check(ok, label)
|
||||
print((ok and "PASS " or "FAIL ") .. label)
|
||||
if not ok then failures = failures + 1 end
|
||||
return ok
|
||||
end
|
||||
local function finish()
|
||||
print("RESULT native_surf_reload failures=" .. failures)
|
||||
love.event.quit(failures == 0 and 0 or 1)
|
||||
end
|
||||
local phase = os.getenv("BSA2759_PHASE") or "setup"
|
||||
local identity = os.getenv("POKEPORT_IDENTITY")
|
||||
if not check(identity and identity ~= "pokemon-love2d", "dedicated identity") then return finish() end
|
||||
for _ = 1, 900 do
|
||||
if game.phase == "boot" and game.boot then break end
|
||||
U.wait(1)
|
||||
end
|
||||
if not check(game.phase == "boot", "boot reached") then return finish() end
|
||||
local SaveData = require("src.core.SaveData")
|
||||
local Runtime = require("src.core.game3.runtime")
|
||||
local Player = require("src.core.game3.player")
|
||||
local Collision = require("src.core.game3.collision")
|
||||
local Field = require("src.core.game3.field")
|
||||
local Map = require("src.core.game3.map")
|
||||
local Space = require("src.core.game3.scripting.space")
|
||||
local Sprites = require("src.core.game3.ow_sprites")
|
||||
local version = game.version or require("src.core.GameVersion").get()
|
||||
local rse = require("src.core.game3.profile").family(version) == "rse"
|
||||
local function stopScript()
|
||||
if Space.vm and Space.vm.stop then Space.vm:stop() end
|
||||
Field.unlock()
|
||||
end
|
||||
local function trace(label)
|
||||
print(string.format("STATE %s map=%s x=%s y=%s water=%s surfing=%s gfx=%s avatar=%s",
|
||||
label, tostring(Map.current), tostring(Player.cellX), tostring(Player.cellY),
|
||||
tostring(Collision.isWater(Player.cellX, Player.cellY)), tostring(Player.surfing),
|
||||
tostring(Sprites.playerGraphicsId(game, Player)), tostring(Sprites.avatarState(Player))))
|
||||
end
|
||||
if phase == "setup" then
|
||||
game:_handleBootAction({ action = "new_game", name = "RELOAD", gender = 0 })
|
||||
U.wait(240)
|
||||
local mapId
|
||||
for key in pairs(game.data.maps) do
|
||||
if key:find(rse and "SEAFLOOR_CAVERN_ENTRANCE" or "BERRY_FOREST", 1, true) then mapId = key break end
|
||||
end
|
||||
if not check(mapId ~= nil, "avatar water map present in ROM cache") then return finish() end
|
||||
require("src.core.game3.encounters").onStep = function() return nil end
|
||||
require("src.core.game3.trainer_sight").check = function() return false end
|
||||
Map.load(Runtime._mod, game, mapId, { x = 0, y = 0, facing = "down" })
|
||||
U.wait(90)
|
||||
stopScript()
|
||||
local layout = game.data.maps[mapId].midLayout
|
||||
local shore, water
|
||||
local dirs = { { "right", 1, 0 }, { "left", -1, 0 }, { "down", 0, 1 }, { "up", 0, -1 } }
|
||||
for y = 1, layout.height - 2 do
|
||||
for x = 1, layout.width - 2 do
|
||||
if Collision.isWater(x, y) then
|
||||
for _, dir in ipairs(dirs) do
|
||||
local sx, sy = x - dir[2], y - dir[3]
|
||||
if Collision.isWalkable(sx, sy) and not Collision.isWater(sx, sy) then
|
||||
shore, water = { x = sx, y = sy, facing = dir[1] }, { x = x, y = y }
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
if shore then break end
|
||||
end
|
||||
if shore then break end
|
||||
end
|
||||
if not check(shore ~= nil, "water with adjacent walkable shore") then return finish() end
|
||||
if rse then
|
||||
Map.load(Runtime._mod, game, mapId, { x = water.x, y = water.y, facing = shore.facing })
|
||||
U.wait(90)
|
||||
stopScript()
|
||||
check(Player.surfing and not Player.underwater, "RSE ordinary cave water arrival restores Surf")
|
||||
check(U.still(game, DIR .. "/2761_01_cave_water_entry.png"), "cave-entry shot")
|
||||
local nextDir
|
||||
for _, dir in ipairs(dirs) do
|
||||
if Collision.isWater(water.x + dir[2], water.y + dir[3]) then nextDir = dir[1] break end
|
||||
end
|
||||
if not check(nextDir ~= nil, "cave water has next Surf cell") then return finish() end
|
||||
Player.facing, Player.turnArmed = nextDir, false
|
||||
check(Player.tryMove(nextDir, game, false) == "step", "RSE cave arrival moves without another Surf prompt")
|
||||
Player._onStepDone = function() end
|
||||
U.wait(40)
|
||||
check(Player.surfing, "RSE cave next step retains Surf")
|
||||
Map.load(Runtime._mod, game, mapId, shore)
|
||||
U.wait(90)
|
||||
stopScript()
|
||||
check(not Player.surfing and not Player.underwater, "RSE ordinary cave land arrival is on foot")
|
||||
end
|
||||
Map.load(Runtime._mod, game, mapId, shore)
|
||||
U.wait(90)
|
||||
stopScript()
|
||||
if not check(Player.startSurfing(game), "real player Surf hop starts") then return finish() end
|
||||
U.wait(90)
|
||||
trace("before_save")
|
||||
if not check(Player.surfing and Player.cellX == water.x and Player.cellY == water.y,
|
||||
"finished Surf hop is on selected water") then return finish() end
|
||||
check(U.still(game, DIR .. "/2759_01_before_save_surf.png"), "before-save shot")
|
||||
if not check(game:saveGame() == true, "Game3 native save succeeds") then return finish() end
|
||||
local saved = SaveData.load()
|
||||
check(saved and saved.engine == "game3" and saved.map == mapId
|
||||
and saved.x == water.x and saved.y == water.y, "native disk save retains exact water location")
|
||||
print("SAVE surfing=" .. tostring(saved and saved.surfing) .. " biking=" .. tostring(saved and saved.biking))
|
||||
check(saved and saved.surfing == true and saved.underwater == false, "native disk save captures completed Surf mode")
|
||||
check(love.filesystem.write("2759_witness.lua", SaveData.encode({ map = mapId,
|
||||
x = water.x, y = water.y, gfx = Sprites.playerGraphicsId(game, Player) })), "restart witness saved")
|
||||
else
|
||||
local witness = SaveData.decode(love.filesystem.read("2759_witness.lua"))
|
||||
if not check(witness ~= nil, "previous process supplied witness") then return finish() end
|
||||
game:_handleBootAction({ action = "continue" })
|
||||
for _ = 1, 4000 do
|
||||
if game.phase == "field" then break end
|
||||
U.wait(1)
|
||||
end
|
||||
if not check(game.phase == "field", "fresh process Continue reaches field") then return finish() end
|
||||
U.wait(240)
|
||||
trace("after_continue")
|
||||
check(Map.current == witness.map and Player.cellX == witness.x and Player.cellY == witness.y,
|
||||
"native Continue restores saved location")
|
||||
check(Collision.isWater(Player.cellX, Player.cellY), "saved location remains water")
|
||||
check(U.still(game, DIR .. "/2759_02_after_continue_surf.png"), "after-Continue shot")
|
||||
check(Player.surfing, "native Continue restores Surf flag")
|
||||
check(Sprites.playerGraphicsId(game, Player) == witness.gfx, "native Continue restores Surf graphics")
|
||||
end
|
||||
finish()
|
||||
end
|
||||
@@ -0,0 +1,171 @@
|
||||
local U = require("tests.drivers.util")
|
||||
local DIR = os.getenv("POKEPORT_SHOT_DIR") or "/tmp/cursor2766"
|
||||
return function(game)
|
||||
local failures = 0
|
||||
local function check(ok, label)
|
||||
print((ok and "PASS " or "FAIL ") .. "cursor2766_native " .. label)
|
||||
if not ok then failures = failures + 1 end
|
||||
return ok
|
||||
end
|
||||
for _ = 1, 600 do
|
||||
if game.phase == "boot" and game.boot then break end
|
||||
U.wait(1)
|
||||
end
|
||||
game:_handleBootAction({ action = "new_game", name = "RED" })
|
||||
U.wait(180)
|
||||
local Runtime = require("src.core.game3.runtime")
|
||||
local Party = require("src.core.game3.party")
|
||||
local Bag = require("src.core.game3.bag")
|
||||
local Items = require("src.core.game3.items_data")
|
||||
local Bridge = require("src.core.game3.battle_bridge")
|
||||
local Battle = require("src.core.game3.battle")
|
||||
local Ui = require("src.core.game3.battle.ui")
|
||||
local BagMenu = require("src.ui.game3.bag_menu")
|
||||
local PartyMenu = require("src.ui.game3.party_menu")
|
||||
local Anim = require("src.core.game3.battle.anim")
|
||||
local AnimSeq = require("src.core.game3.battle.anim_seq")
|
||||
local Intro = require("src.core.game3.battle.intro_seq")
|
||||
local session = Runtime.getSession()
|
||||
session.party = {}
|
||||
Party.giveMon(session, 1, 20)
|
||||
Party.giveMon(session, 4, 20)
|
||||
Party.giveMon(session, 7, 20)
|
||||
for _, mon in ipairs(session.party) do mon.moves, mon.pp = { 150 }, { 40 } end
|
||||
session.party[1].hp = math.max(1, session.party[1].hp - 15)
|
||||
session.bag = Bag.new()
|
||||
Bag.add(session.bag, 13, 3)
|
||||
Bag.add(session.bag, 4, 3)
|
||||
local tick, lastTap = 0, 0
|
||||
local function busy()
|
||||
local step = AnimSeq._steps and AnimSeq._steps[AnimSeq._i]
|
||||
return (Anim.vm() and Anim.vm():busy()) or (step and step.kind ~= "msg") or Intro._waitingGen
|
||||
end
|
||||
local function untilState(predicate, limit)
|
||||
for _ = 1, limit or 5000 do
|
||||
tick = tick + 1
|
||||
if predicate() then return true end
|
||||
if not busy() and Ui.dialogPending() and tick - lastTap >= 14 then
|
||||
lastTap = tick
|
||||
U.tap(game, "a")
|
||||
else
|
||||
U.wait(1)
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
local function menu()
|
||||
return Battle.isActive() and Battle._phase == "command" and Ui._mode == "menu"
|
||||
end
|
||||
local function waitFor(predicate)
|
||||
for _ = 1, 600 do
|
||||
if predicate() then return true end
|
||||
U.wait(1)
|
||||
end
|
||||
return false
|
||||
end
|
||||
local function itemRow(item)
|
||||
for pocket = 1, #Items.BAG_POCKET_ORDER do
|
||||
BagMenu.pocketIdx = pocket
|
||||
for row, entry in ipairs(BagMenu.list(BagMenu.currentPocket())) do
|
||||
if Items.toNumericId(entry.id) == item then
|
||||
BagMenu.cursor, BagMenu.scroll = row, 0
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
check(Bridge.startWild(Runtime._mod, game, { species = 150, level = 2 }, { fade = false }),
|
||||
"wild_battle_started")
|
||||
if not check(untilState(menu), "initial_command_menu") then love.event.quit(1) return end
|
||||
local st = Battle.getState()
|
||||
st.enemy.mon.moves, st.enemy.mon.pp = { 150 }, { 40 }
|
||||
local old, turn = st.player, st.turn
|
||||
U.tap(game, "right")
|
||||
U.tap(game, "a")
|
||||
if not check(waitFor(function() return BagMenu.open and BagMenu.mode == "list" and not BagMenu._open end),
|
||||
"Potion_Bag_open") then love.event.quit(1) return end
|
||||
assert(itemRow(13))
|
||||
U.tap(game, "a")
|
||||
U.wait(10)
|
||||
U.tap(game, "a")
|
||||
if not check(waitFor(function() return PartyMenu.open and PartyMenu.mode == "use" end),
|
||||
"Potion_Party_target_open") then love.event.quit(1) return end
|
||||
PartyMenu.cursor = 1
|
||||
U.wait(20)
|
||||
U.tap(game, "a")
|
||||
for _ = 1, 1200 do
|
||||
if not PartyMenu.open then break end
|
||||
U.tap(game, "a")
|
||||
U.wait(3)
|
||||
end
|
||||
if not check(untilState(function() return menu() and st.turn > turn end),
|
||||
"Potion_next_turn") then love.event.quit(1) return end
|
||||
check(st.player == old and Bag.get(session.bag, 13) == 2 and Ui._menuIndex == 2,
|
||||
"Potion_next_turn_Bag_selected")
|
||||
check(U.still(game, DIR .. "/2766_potion_next_turn_Bag_selected.png"), "Potion_Bag_shot")
|
||||
U.tap(game, "a")
|
||||
check(waitFor(function() return BagMenu.open and BagMenu.mode == "list" and not BagMenu._open end),
|
||||
"Potion_next_A_reopens_Bag")
|
||||
U.tap(game, "b")
|
||||
check(waitFor(menu) and Ui._menuIndex == 2, "Bag_cancel_retains_Bag")
|
||||
U.tap(game, "a")
|
||||
if not check(waitFor(function() return BagMenu.open and BagMenu.mode == "list" and not BagMenu._open end),
|
||||
"Ball_Bag_open") then love.event.quit(1) return end
|
||||
assert(itemRow(4))
|
||||
turn = st.turn
|
||||
local Catching = require("src.core.game3.battle.catching")
|
||||
local tryCatch = Catching.tryCatch
|
||||
Catching.tryCatch = function() return false, 0 end
|
||||
U.tap(game, "a")
|
||||
U.wait(10)
|
||||
U.tap(game, "a")
|
||||
local returned = untilState(function() return menu() and st.turn > turn end)
|
||||
Catching.tryCatch = tryCatch
|
||||
if not check(returned, "failed_Ball_next_turn") then love.event.quit(1) return end
|
||||
check(Bag.get(session.bag, 4) == 2 and Ui._menuIndex == 2, "failed_Ball_next_turn_Bag_selected")
|
||||
check(U.still(game, DIR .. "/2766_failed_ball_next_turn_Bag_selected.png"), "failed_Ball_Bag_shot")
|
||||
U.tap(game, "a")
|
||||
check(waitFor(function() return BagMenu.open and BagMenu.mode == "list" and not BagMenu._open end),
|
||||
"failed_Ball_next_A_reopens_Bag")
|
||||
U.tap(game, "b")
|
||||
if not waitFor(menu) then love.event.quit(1) return end
|
||||
U.tap(game, "left")
|
||||
U.tap(game, "down")
|
||||
U.tap(game, "a")
|
||||
if not check(waitFor(function() return PartyMenu.open end), "switch_Party_open") then
|
||||
love.event.quit(1) return
|
||||
end
|
||||
old, turn = st.player, st.turn
|
||||
PartyMenu.cursor = 2
|
||||
U.wait(60)
|
||||
U.tap(game, "a")
|
||||
U.wait(10)
|
||||
U.tap(game, "a")
|
||||
check(untilState(function() return menu() and st.player ~= old and st.turn > turn end),
|
||||
"real_switch_next_turn")
|
||||
check(Ui._menuIndex == 1 and st.player.partyIndex == 2, "real_switch_resets_Fight")
|
||||
check(U.still(game, DIR .. "/2766_switched_Pokemon_Fight_selected.png"), "switch_Fight_shot")
|
||||
local State = require("src.core.game3.battle.state")
|
||||
local Engine = require("src.core.game3.battle.engine")
|
||||
st.double, st.absent = true, {}
|
||||
st.battlers = { [0] = st.player, [1] = st.enemy,
|
||||
[2] = State.makeBattler(st.playerParty[1], "player", { partyIndex = 1, id = 2 }),
|
||||
[3] = State.makeBattler(st.enemy.mon, "enemy", { partyIndex = 1, id = 3 }) }
|
||||
local function direct(key)
|
||||
Ui.handleInput({ wasPressed = function(_, k) return k == key end })
|
||||
end
|
||||
Ui.openMenu(0)
|
||||
direct("right")
|
||||
Ui.openMenu(2)
|
||||
direct("down")
|
||||
Ui.openMenu(0)
|
||||
check(Ui._menuIndex == 2, "double_slot0_Bag_independent")
|
||||
assert(Engine.performSwitch(st, Battle._adapter, 2, 3, { reason = "switch" }))
|
||||
Ui.openMenu(2)
|
||||
check(Ui._menuIndex == 1, "real_double_slot2_switch_Fight")
|
||||
Ui.openMenu(0)
|
||||
check(Ui._menuIndex == 2, "double_partner_switch_preserves_Bag")
|
||||
print((failures == 0 and "PASS " or "FAIL ") .. "cursor2766_native_complete")
|
||||
love.event.quit(failures == 0 and 0 or 1)
|
||||
end
|
||||
@@ -0,0 +1,84 @@
|
||||
local U = require("tests.drivers.util")
|
||||
local F = require("tests.drivers.em_fc_util").new("game3_disguise_order_2763")
|
||||
|
||||
return function(game)
|
||||
if not F.boot(game) then return F.finish() end
|
||||
local Runtime = require("src.core.game3.runtime")
|
||||
local session = Runtime.getSession()
|
||||
local version = session.version
|
||||
local prefix = ({ emerald = "EM", ruby = "RU", sapphire = "SA" })[version]
|
||||
if not F.check(prefix ~= nil, "disguise2763 RSE version") then return F.finish() end
|
||||
local Objects = require("src.core.game3.objects")
|
||||
local Fx = require("src.core.game3.field_effects_rse")
|
||||
local View = require("src.core.game3.field_view")
|
||||
local Player = require("src.core.game3.player")
|
||||
local Flags = require("src.core.game3.scripting.flags")
|
||||
local Space = require("src.core.game3.scripting.space")
|
||||
local Sight = require("src.core.game3.trainer_sight")
|
||||
local Field = require("src.core.game3.field")
|
||||
|
||||
local function ordered(eo, tag)
|
||||
local actors, ninja = {}, nil
|
||||
for _, obj in ipairs(Objects.forDraw()) do
|
||||
local actor = { kind = "npc", i = obj.localId, eventObject = obj,
|
||||
x = obj.px, y = obj.py, sortY = obj.py, elevation = obj.elevation }
|
||||
actors[#actors + 1] = actor
|
||||
if obj == eo then ninja = actor end
|
||||
end
|
||||
Fx.collectActors(actors)
|
||||
local under = View.applyDrawOrder(actors, {}, {}, Player.py - 72)
|
||||
local ni, mi, mask
|
||||
for i, actor in ipairs(under) do
|
||||
if actor == ninja then ni = i end
|
||||
if actor.disguiseObject == eo then mi, mask = i, actor end
|
||||
end
|
||||
return F.check(ni and mi and ni < mi and mask.subpriority == (ninja.subpriority - 1) % 256,
|
||||
"disguise2763 " .. version .. " " .. tag .. " ninja_before_mask")
|
||||
end
|
||||
|
||||
local function spot(route, lid, kind)
|
||||
local tag = kind .. "_" .. route
|
||||
local mapId = prefix .. "_" .. route
|
||||
if not F.check(F.goTo(game, mapId, 5, 5, "down"), tag .. " map_loaded") then return end
|
||||
for _, id in ipairs(Objects._order) do
|
||||
local trainerId = Sight.getTrainerId(Objects._byId[id])
|
||||
if trainerId then Flags.setFlag(Space.store, nil, Flags.trainerFlagId(trainerId), true) end
|
||||
end
|
||||
local eo = Objects.find(lid)
|
||||
if not F.check(eo and eo.disguise and eo.disguise.sheet == kind,
|
||||
"disguise2763 " .. version .. " " .. tag .. " cached_disguise") then return end
|
||||
F.goTo(game, mapId, eo.cellX + 3, eo.cellY + 1, "left", { wait = 20 })
|
||||
eo = Objects.find(lid)
|
||||
Field.lock()
|
||||
F.check(eo.disguise and eo.disguise.a and eo.disguise.a.frame == 0 and not eo.invisible,
|
||||
"disguise2763 " .. version .. " " .. tag .. " idle_frame")
|
||||
ordered(eo, tag .. "_hidden")
|
||||
F.check(F.shot(game, "2763_" .. version .. "_" .. tag .. "_01_hidden.png", true),
|
||||
"disguise2763 " .. version .. " " .. tag .. " hidden_capture")
|
||||
Objects.revealTrainer(eo)
|
||||
U.wait(10)
|
||||
F.check(eo.disguise and eo.disguise.revealing and not eo.disguise.done
|
||||
and eo.disguise.a.frame > 0 and not eo.invisible,
|
||||
"disguise2763 " .. version .. " " .. tag .. " midtear_frame")
|
||||
ordered(eo, tag .. "_midtear")
|
||||
F.check(F.shot(game, "2763_" .. version .. "_" .. tag .. "_02_midtear.png", true),
|
||||
"disguise2763 " .. version .. " " .. tag .. " midtear_capture")
|
||||
U.wait(40)
|
||||
local actors = {}
|
||||
Fx.collectActors(actors)
|
||||
local mask
|
||||
for _, actor in ipairs(actors) do if actor.disguiseObject == eo then mask = actor end end
|
||||
local drawn = false
|
||||
for _, obj in ipairs(Objects.forDraw()) do if obj == eo then drawn = true end end
|
||||
F.check(mask == nil and drawn and not eo.invisible,
|
||||
"disguise2763 " .. version .. " " .. tag .. " revealed_ninja_only")
|
||||
F.check(F.shot(game, "2763_" .. version .. "_" .. tag .. "_03_revealed.png", true),
|
||||
"disguise2763 " .. version .. " " .. tag .. " revealed_capture")
|
||||
Field.unlock()
|
||||
F.haltVm()
|
||||
end
|
||||
|
||||
spot("ROUTE119", 12, "tree_disguise")
|
||||
spot("ROUTE120", 34, "mountain_disguise")
|
||||
F.finish()
|
||||
end
|
||||
@@ -83,7 +83,11 @@ return function(game)
|
||||
U.wait(1)
|
||||
end
|
||||
check(game.boot ~= nil, "boot reached")
|
||||
local Runtime = require("src.mods.Runtime")
|
||||
local safeMode = Runtime.safeMode
|
||||
Runtime.safeMode = false
|
||||
try("title", function() titleOptions(game) end)
|
||||
Runtime.safeMode = safeMode
|
||||
try("new_game", function() game:_handleBootAction({ action = "new_game", name = "RED" }) end)
|
||||
for _ = 1, 600 do
|
||||
if game.phase == "field" then break end
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
local U = require("tests.drivers.util")
|
||||
local failures = 0
|
||||
local function check(ok, label)
|
||||
print((ok and "PASS " or "FAIL ") .. "flash2762 " .. label)
|
||||
if not ok then failures = failures + 1 end
|
||||
return ok
|
||||
end
|
||||
local function finish()
|
||||
print((failures == 0 and "PASS " or "FAIL ") .. "flash2762_native failures=" .. failures)
|
||||
love.event.quit(failures == 0 and 0 or 1)
|
||||
end
|
||||
return function(game)
|
||||
for _ = 1, 600 do
|
||||
if game.phase == "boot" and game.boot then break end
|
||||
U.wait(1)
|
||||
end
|
||||
if not check(game.phase == "boot" and game.boot ~= nil, "boot_ready") then return finish() end
|
||||
game:_handleBootAction({ action = "new_game", name = "FLASH" })
|
||||
U.wait(180)
|
||||
local Runtime = require("src.core.game3.runtime")
|
||||
local session = Runtime.getSession()
|
||||
if not check(session ~= nil, "field_session_ready") then return finish() end
|
||||
local version = session.version
|
||||
local Profile = require("src.core.game3.profile").of(version)
|
||||
local rse = Profile.family == "rse"
|
||||
local Map = require("src.core.game3.map")
|
||||
local Field = require("src.core.game3.field")
|
||||
local View = require("src.core.game3.field_view")
|
||||
local Space = require("src.core.game3.scripting.space")
|
||||
local Flags = require("src.core.game3.scripting.flags")
|
||||
local Moves = require("src.core.game3.field_moves")
|
||||
local ShowMon = require("src.core.game3.field_move_show_mon")
|
||||
local Party = require("src.ui.game3.party_menu")
|
||||
local Pokemon = require("src.core.game3.pokemon")
|
||||
local Text = require("src.core.game3.rom_text")
|
||||
require("src.core.game3.encounters").onStep = function() return nil end
|
||||
require("src.core.game3.trainer_sight").check = function() return false end
|
||||
local species = assert(Pokemon.speciesFromName("PIKACHU"))
|
||||
local move = Moves.MOVES.FLASH
|
||||
local mon = { species = species, nickname = "FLASH", level = 20, moves = { move }, pp = { 20 }, personality = 0 }
|
||||
Pokemon.applyStats(mon, session)
|
||||
mon.hp = mon.maxHp
|
||||
session.party = { mon }
|
||||
Flags.setFlag(Space.store, nil, Moves.badgeFlag("FLASH"), true)
|
||||
local maps = game.data.maps
|
||||
local function mapNamed(suffix)
|
||||
for id in pairs(maps) do
|
||||
if id:sub(-#suffix) == suffix then return id end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
local first = assert(mapNamed(rse and "GRANITE_CAVE_B1F" or "ROCK_TUNNEL_1F"))
|
||||
local second = assert(mapNamed(rse and "GRANITE_CAVE_B2F" or "ROCK_TUNNEL_B1F"))
|
||||
local outdoors = assert(mapNamed(rse and "DEWFORD_TOWN" or "PALLET_TOWN"))
|
||||
local shots = os.getenv("POKEPORT_SHOT_DIR")
|
||||
local function load(id)
|
||||
local def = maps[id]
|
||||
local warp = def.warps and def.warps[1]
|
||||
local x, y = warp and warp.x or 8, warp and warp.y or 8
|
||||
Map.load(Runtime._mod, game, id, { x = x, y = y, facing = "down" })
|
||||
U.wait(90)
|
||||
return check(Map.current == id, "loaded_" .. id)
|
||||
end
|
||||
local function selectFlash()
|
||||
Party.show(session.party, nil, { session = session })
|
||||
U.wait(40)
|
||||
U.tap(game, "a")
|
||||
U.wait(3)
|
||||
local index
|
||||
for i, name in ipairs(Party.ACTIONS) do if name == "FLASH" then index = i end end
|
||||
if not check(index ~= nil and Party.mode == "action", "known_flash_action_available") then return false end
|
||||
Party.actionCursor = index
|
||||
U.tap(game, "a")
|
||||
return true
|
||||
end
|
||||
if not load(first) then return finish() end
|
||||
check(tonumber(Map.currentDef().cave) == 1 and not Flags.getFlag(Space.store, nil, Moves.SYS_FLAGS.FLASH_ACTIVE),
|
||||
"initial_dark_cave_without_flash")
|
||||
if not selectFlash() then return finish() end
|
||||
check(ShowMon.isActive() and Field.locked, "show_mon_then_animation_lock")
|
||||
for _ = 1, 900 do
|
||||
if not ShowMon.isActive() and not Field.locked and not Space.vm:isRunning() then break end
|
||||
U.wait(1)
|
||||
end
|
||||
check(not Field.locked and not ShowMon.isActive() and not Space.vm:isRunning(), "animation_end_unlocks_field")
|
||||
check(View.getFlashLevel() == (rse and 1 or 0), "first_use_cart_level")
|
||||
check(View.flashRadius() == (rse and 72 or nil), "first_use_cart_radius")
|
||||
if shots then check(U.still(game, shots .. "/2762_01_first_flash.png"), "first_flash_frame_captured") end
|
||||
if not load(second) then return finish() end
|
||||
check(Flags.getFlag(Space.store, nil, Moves.SYS_FLAGS.FLASH_ACTIVE)
|
||||
and View.getFlashLevel() == (rse and 1 or 0), "floor_change_preserves_flash")
|
||||
if not selectFlash() then return finish() end
|
||||
local key = (version == "ruby" or version == "sapphire") and "OtherText_CantUseThatHere" or "gText_InUseAlready_PM"
|
||||
check(Party.mode == "message" and Party._messageText == Text.ascii(key), "repeated_flash_exact_cached_prompt")
|
||||
check(Flags.getFlag(Space.store, nil, Moves.SYS_FLAGS.FLASH_ACTIVE)
|
||||
and View.getFlashLevel() == (rse and 1 or 0), "rejection_keeps_flash_state")
|
||||
if shots then check(U.still(game, shots .. "/2762_02_repeated_flash_prompt.png"), "repeat_prompt_frame_captured") end
|
||||
Party.close()
|
||||
if not load(outdoors) then return finish() end
|
||||
check(not Flags.getFlag(Space.store, nil, Moves.SYS_FLAGS.FLASH_ACTIVE), "outdoors_clears_flash")
|
||||
if not load(first) then return finish() end
|
||||
if not selectFlash() then return finish() end
|
||||
for _ = 1, 900 do
|
||||
if not ShowMon.isActive() and not Field.locked and not Space.vm:isRunning() then break end
|
||||
U.wait(1)
|
||||
end
|
||||
check(Flags.getFlag(Space.store, nil, Moves.SYS_FLAGS.FLASH_ACTIVE) and not Field.locked
|
||||
and View.getFlashLevel() == (rse and 1 or 0), "cave_return_flash_usable")
|
||||
finish()
|
||||
end
|
||||
@@ -0,0 +1,78 @@
|
||||
local U=require('tests.drivers.util')
|
||||
local failures=0
|
||||
local function check(ok,label)
|
||||
print((ok and 'PASS ' or 'FAIL ')..label)
|
||||
if not ok then failures=failures+1 end
|
||||
return ok
|
||||
end
|
||||
local function finish()
|
||||
print((failures==0 and 'PASS ' or 'FAIL ')..'2767 native Surf timing failures='..failures)
|
||||
love.event.quit(failures==0 and 0 or 1)
|
||||
end
|
||||
return function(game)
|
||||
for _=1,600 do
|
||||
if game.phase=='boot' and game.boot then break end
|
||||
U.wait(1)
|
||||
end
|
||||
if not check(game.phase=='boot' and game.boot~=nil,'2767 boot ready') then return finish() end
|
||||
game:_handleBootAction({action='new_game',name='SURFER'})
|
||||
U.wait(180)
|
||||
local Runtime=require('src.core.game3.runtime')
|
||||
local Map=require('src.core.game3.map')
|
||||
local Player=require('src.core.game3.player')
|
||||
local Collision=require('src.core.game3.collision')
|
||||
local Space=require('src.core.game3.scripting.space')
|
||||
local Flags=require('src.core.game3.scripting.flags')
|
||||
local Field=require('src.core.game3.field')
|
||||
require('src.core.game3.encounters').onStep=function() return nil end
|
||||
require('src.core.game3.trainer_sight').check=function() return false end
|
||||
local session=Runtime.getSession()
|
||||
if not check(session~=nil,'2767 field session ready') then return finish() end
|
||||
local loader=game.mods
|
||||
print('MODS 2767 safeMode='..tostring(loader and loader.safeMode))
|
||||
for _,id in ipairs(loader and loader.order or {}) do
|
||||
print('MODS 2767 ordered='..tostring(type(id)=='table' and id.manifest and id.manifest.id or id))
|
||||
end
|
||||
local mapId='EM_ROUTE131'
|
||||
Map.load(Runtime._mod,game,mapId,{x=17,y=2,facing='right'})
|
||||
U.wait(90)
|
||||
local shoeId=Flags.forVersion('emerald').IDS.FLAG_SYS_B_DASH
|
||||
if not check(Collision.isWater(17,2) and Collision.isWater(18,2),'2767 real Route131 water fixture') then return finish() end
|
||||
local function measure(label,b,shoes)
|
||||
if Space.vm and Space.vm:isRunning() then Space.vm:halt(false) end
|
||||
Field.locked=false
|
||||
Player.reset(17,2,'right')
|
||||
Player.surfing,Player.currentElevation,Player.elevation=true,1,1
|
||||
Flags.setFlag(Space.store,nil,shoeId,shoes)
|
||||
local input={isDown=function(_,key) return key=='right' or key=='b' and b end}
|
||||
Player.update(game,input)
|
||||
if not check(Player.moving,label..' step starts') then return end
|
||||
local selected=Player.stepFrames
|
||||
Player._onStepDone=function() end
|
||||
local count=0
|
||||
while Player.moving and count<24 do
|
||||
U.wait(1)
|
||||
count=count+1
|
||||
end
|
||||
print('MEASURE 2767 '..label..' selected='..selected..' duration='..count..' surf='..tostring(Player.surfing))
|
||||
check(count==8 and selected==8 and Player.cellX==18 and Player.surfing,label..' completes in 8 frames')
|
||||
end
|
||||
measure('no_B_no_shoes',false,false)
|
||||
measure('B_no_shoes',true,false)
|
||||
measure('no_B_shoes',false,true)
|
||||
measure('B_shoes',true,true)
|
||||
local shots=os.getenv('POKEPORT_SHOT_DIR')
|
||||
if shots then check(U.still(game,shots..'/2767_01_surfing.png'),'2767 Surf frame captured') end
|
||||
Map.load(Runtime._mod,game,mapId,{x=59,y=2,facing='right'})
|
||||
U.wait(90)
|
||||
Player.surfing,Player.currentElevation,Player.elevation=true,1,1
|
||||
Player._onStepDone=function() end
|
||||
local status=Player.tryMove('right',game,false)
|
||||
local selected=Player.stepFrames
|
||||
local count=0
|
||||
while Player.moving and count<24 do U.wait(1);count=count+1 end
|
||||
print('MEASURE 2767 water_seam result='..tostring(status)..' duration='..count..' selected='..selected..' map='..tostring(Map.current))
|
||||
check(status=='connection' and Map.current=='EM_ROUTE130' and count==8 and selected==8 and Player.surfing,'2767 water seam completes in 8 frames')
|
||||
if shots then check(U.still(game,shots..'/2767_02_water_seam.png'),'2767 seam frame captured') end
|
||||
finish()
|
||||
end
|
||||
@@ -0,0 +1,239 @@
|
||||
local U = require("tests.drivers.util")
|
||||
local F = require("tests.drivers.em_fc_util").new("game3_tilt_roof_2765")
|
||||
|
||||
local function up(fn, wanted, replacement)
|
||||
for i = 1, 100 do
|
||||
local name, value = debug.getupvalue(fn, i)
|
||||
if name == wanted then
|
||||
if replacement then debug.setupvalue(fn, i, replacement) end
|
||||
return value
|
||||
end
|
||||
end
|
||||
error("missing " .. wanted)
|
||||
end
|
||||
|
||||
return function(game)
|
||||
if not F.boot(game) then return F.finish() end
|
||||
local View = require("src.core.game3.field_view")
|
||||
local Display = require("src.core.game3.display")
|
||||
local Renderer = require("src.render.Renderer")
|
||||
local Tilt = require("src.render.Tilt")
|
||||
local Player = require("src.core.game3.player")
|
||||
local Runtime = require("src.core.game3.runtime")
|
||||
local session = Runtime.getSession()
|
||||
local version = session.version
|
||||
local map = version == "emerald" and "EM_OLDALE_TOWN" or "FR_PALLET_TOWN"
|
||||
local x, y = 6, version == "emerald" and 13 or 3
|
||||
if not F.check(F.goTo(game, map, x, y, "down"), "tilt2765 cached_roof_map") then return F.finish() end
|
||||
require("src.core.game3.field").lock()
|
||||
local plane = up(up(Display.present, "presentPlanes"), "drawFieldPlane")
|
||||
local overhead = up(View.draw, "drawNativeOverTiles")
|
||||
local function capturePlane(g)
|
||||
love.graphics.push("all")
|
||||
love.graphics.origin()
|
||||
love.graphics.setShader()
|
||||
love.graphics.setBlendMode("alpha", "alphamultiply")
|
||||
love.graphics.setColor(1, 1, 1, 1)
|
||||
Renderer:beginFrame(true)
|
||||
Renderer:beginWorldPass()
|
||||
local vw, vh = Renderer:worldViewSize()
|
||||
plane(g, vw, vh, Renderer)
|
||||
Renderer:endWorldPass()
|
||||
love.graphics.pop()
|
||||
return Renderer.worldCanvas:newImageData(),
|
||||
Renderer.uprightActive and Renderer.uprightCanvas:newImageData() or nil
|
||||
end
|
||||
local function projectedAlpha()
|
||||
local vw, vh = Renderer.worldCanvas:getDimensions()
|
||||
local source = love.graphics.newCanvas(vw, vh, { dpiscale = 1 })
|
||||
source:setFilter("linear", "linear")
|
||||
local m = Renderer.UPRIGHT_MARGIN
|
||||
local target = love.graphics.newCanvas(vw + 2 * m, vh + 2 * m, { dpiscale = 1 })
|
||||
love.graphics.push("all")
|
||||
love.graphics.origin()
|
||||
love.graphics.setCanvas(source)
|
||||
love.graphics.clear(0, 0, 0, 0)
|
||||
love.graphics.setShader()
|
||||
love.graphics.setStencilTest()
|
||||
love.graphics.setScissor()
|
||||
love.graphics.setBlendMode("alpha", "alphamultiply")
|
||||
overhead()
|
||||
love.graphics.setCanvas(target)
|
||||
love.graphics.clear(0, 0, 0, 0)
|
||||
love.graphics.setBlendMode("replace", "premultiplied")
|
||||
love.graphics.setShader(Renderer:tiltShader())
|
||||
local mesh = Renderer:tiltMesh()
|
||||
mesh:setTexture(source)
|
||||
mesh:setVertices(Tilt.meshCorners(vw, vh))
|
||||
love.graphics.translate(m, m)
|
||||
love.graphics.draw(mesh)
|
||||
love.graphics.pop()
|
||||
local data = target:newImageData()
|
||||
source:release()
|
||||
target:release()
|
||||
return data
|
||||
end
|
||||
local maskMethod = Renderer.occludeUprightActors
|
||||
Tilt.applyOptions({ tilt = 0 })
|
||||
local off = capturePlane(game)
|
||||
Renderer.occludeUprightActors = function() end
|
||||
local referenceOff = capturePlane(game)
|
||||
Renderer.occludeUprightActors = maskMethod
|
||||
F.check(off:getString() == referenceOff:getString(), "tilt2765 " .. version .. " OFF_byte_identical")
|
||||
F.check(F.shot(game, "2765_" .. version .. "_00_off.png", true), "tilt2765 OFF_capture")
|
||||
local priority = Player.oamPriority
|
||||
for level = 1, 3 do
|
||||
Tilt.applyOptions({ tilt = level })
|
||||
Player.oamPriority = 0
|
||||
local _, elevated = capturePlane(game)
|
||||
Player.oamPriority = 2
|
||||
local _, normal = capturePlane(game)
|
||||
local alpha = projectedAlpha()
|
||||
local hidden = 0
|
||||
if normal and elevated then
|
||||
local w, h = normal:getDimensions()
|
||||
for py = math.floor(h / 2) - 30, math.floor(h / 2) + 20 do
|
||||
for px = math.floor(w / 2) - 16, math.floor(w / 2) + 16 do
|
||||
local _, _, _, ea = elevated:getPixel(px, py)
|
||||
local _, _, _, na = normal:getPixel(px, py)
|
||||
local _, _, _, ma = alpha:getPixel(px, py)
|
||||
if ea > 0.99 and ma > 0.99 and na < 0.01 then hidden = hidden + 1 end
|
||||
end
|
||||
end
|
||||
end
|
||||
F.check(hidden > 0, "tilt2765 " .. version .. " " .. Tilt.levelLabel(level) .. " real_roof_normal_hidden_elevated_visible")
|
||||
F.check(F.shot(game, "2765_" .. version .. "_" .. Tilt.levelLabel(level) .. "_normal.png", true), "tilt2765 normal_capture")
|
||||
Player.oamPriority = 0
|
||||
F.check(F.shot(game, "2765_" .. version .. "_" .. Tilt.levelLabel(level) .. "_elevated.png", true), "tilt2765 elevated_capture")
|
||||
end
|
||||
Player.oamPriority = priority
|
||||
|
||||
local weather = require("src.core.game3.field_weather_rse")
|
||||
if version == "emerald" then
|
||||
require("src.core.game3.weather").resume()
|
||||
local exchanges, writes = 0, 0
|
||||
local exchange, write = Renderer.exchangeWorldCanvas, weather.writeActorMask
|
||||
Renderer.exchangeWorldCanvas = function(self, current, replacement)
|
||||
local changed = exchange(self, current, replacement)
|
||||
if changed then exchanges = exchanges + 1 end
|
||||
return changed
|
||||
end
|
||||
weather.writeActorMask = function(code, draw)
|
||||
local canvas, shader = love.graphics.getCanvas(), love.graphics.getShader()
|
||||
write(code, draw)
|
||||
F.check(love.graphics.getCanvas() == canvas and love.graphics.getShader() == shader,
|
||||
"tilt2765 weather_actor_mask_canvas_shader_restored")
|
||||
writes = writes + 1
|
||||
end
|
||||
for _, id in ipairs({ weather.WEATHER.SHADE, weather.WEATHER.RAIN, weather.WEATHER.FOG_HORIZONTAL }) do
|
||||
weather.setCurrentAndNextWeatherNoDelay(id)
|
||||
weather.readyForInit()
|
||||
for _ = 1, 180 do weather.update() end
|
||||
capturePlane(game)
|
||||
F.check(Renderer.uprightActive and Renderer.uprightOccluded, "tilt2765 weather_keeps_tilt_mask")
|
||||
end
|
||||
F.check(exchanges > 0 and writes > 0,
|
||||
"tilt2765 real_weather_canvas_exchange_and_actor_mask exchanges=" .. exchanges .. " writes=" .. writes)
|
||||
Renderer.exchangeWorldCanvas, weather.writeActorMask = exchange, write
|
||||
weather.setCurrentAndNextWeatherNoDelay(weather.WEATHER.NONE)
|
||||
end
|
||||
|
||||
local vw, vh = 96, 80
|
||||
local oldViewSize = Renderer.worldViewSize
|
||||
Renderer.worldViewSize = function() return vw, vh end
|
||||
local saved = {}
|
||||
local function replace(name, fn) saved[name] = up(View.draw, name, fn) end
|
||||
replace("currentMapId", function() return "fixture" end)
|
||||
replace("playerPixels", function() return 40, 32, "down", 0, false, nil, 0, 0 end)
|
||||
for _, name in ipairs({ "modSeagallop", "modShopMenu", "modFieldEffects", "modDoors", "modHeal", "modSSAnne", "modFieldWeather" }) do
|
||||
replace(name, function() return nil end)
|
||||
end
|
||||
local function batch(w, h, ox, oy, pixel)
|
||||
local data = love.image.newImageData(w, h)
|
||||
for py = 0, h - 1 do for px = 0, w - 1 do data:setPixel(px, py, pixel(px, py)) end end
|
||||
local image = love.graphics.newImage(data)
|
||||
image:setFilter("nearest", "nearest")
|
||||
local b = love.graphics.newSpriteBatch(image, 1)
|
||||
b:add(ox, oy)
|
||||
return b
|
||||
end
|
||||
local main = batch(32, 32, 24, 20, function(px, py)
|
||||
return 1, 0, 0, (px >= 12 and px < 20 and py >= 12 and py < 20) and 0 or 1
|
||||
end)
|
||||
local neighbor = batch(16, 32, 64, 20, function(_, py) return 1, 1, 0, py < 16 and 1 or 0.5 end)
|
||||
replace("drawNativeTiles", function()
|
||||
View._nativeOverPair, View._nativeOverBatches = "main", { main = main, neighbor = neighbor }
|
||||
View._nativeOverOx, View._nativeOverOy, View._voidFrom = 0, 0, nil
|
||||
love.graphics.setColor(0, 0, 1, 1)
|
||||
love.graphics.rectangle("fill", 0, 0, vw, vh)
|
||||
return true
|
||||
end)
|
||||
local elevatedOn = false
|
||||
replace("collectGame3Actors", function()
|
||||
local actors = { { kind = "mod", elevation = 3, x = 40, y = 24, draw = function()
|
||||
love.graphics.setColor(0, 1, 0, 1)
|
||||
love.graphics.rectangle("fill", 0, 0, vw, vh)
|
||||
end } }
|
||||
if elevatedOn then actors[#actors + 1] = { kind = "mod", elevation = 13, x = 40, y = 24, draw = function()
|
||||
love.graphics.setColor(0, 1, 1, 1)
|
||||
love.graphics.rectangle("fill", 64, 20, 16, 32)
|
||||
end } end
|
||||
return View.applyDrawOrder(actors)
|
||||
end)
|
||||
local fixture = { data = { maps = { fixture = { width = 32, blocks = {} } } } }
|
||||
View._flashMapId, View._flashRadius = "fixture", nil
|
||||
for level = 1, 3 do
|
||||
Tilt.applyOptions({ tilt = level })
|
||||
elevatedOn = false
|
||||
local _, normal = capturePlane(fixture)
|
||||
local alpha = projectedAlpha()
|
||||
local errors, opaque, holes, partial = 0, 0, 0, 0
|
||||
local m = Renderer.UPRIGHT_MARGIN
|
||||
for py = 1, vh - 2 do
|
||||
for px = 1, vw - 2 do
|
||||
local _, green, _, a = normal:getPixel(px + m, py + m)
|
||||
local _, _, _, mask = alpha:getPixel(px + m, py + m)
|
||||
if math.abs(a - (1 - mask)) > 0.01 or math.abs(green - (1 - mask)) > 0.01 then errors = errors + 1 end
|
||||
if mask > 0.99 then opaque = opaque + 1 elseif mask < 0.01 then holes = holes + 1 else partial = partial + 1 end
|
||||
end
|
||||
end
|
||||
F.check(errors == 0 and opaque > 0 and holes > 0 and partial > 0,
|
||||
"tilt2765 GPU_" .. Tilt.levelLabel(level) .. " opaque_holes_fractional_connected_pair errors=" .. errors)
|
||||
elevatedOn = true
|
||||
local _, elevated = capturePlane(fixture)
|
||||
local _, g, b, a = elevated:getPixel(70 + m, 30 + m)
|
||||
F.check(g > 0.99 and b > 0.99 and a > 0.99, "tilt2765 GPU_elevated_above_mask")
|
||||
end
|
||||
local current = love.graphics.newCanvas(160, 120, { dpiscale = 1 })
|
||||
local shader = love.graphics.newShader("vec4 effect(vec4 c, Image t, vec2 uv, vec2 p) { return Texel(t,uv)*c; }")
|
||||
love.graphics.push("all")
|
||||
love.graphics.setCanvas(current)
|
||||
love.graphics.setShader(shader)
|
||||
love.graphics.setBlendMode("add", "premultiplied")
|
||||
love.graphics.setColor(0.2, 0.3, 0.4, 0.5)
|
||||
love.graphics.setScissor(4, 5, 6, 7)
|
||||
love.graphics.translate(3, 9)
|
||||
local before = { love.graphics.transformPoint(0, 0) }
|
||||
before[3], before[4] = love.graphics.transformPoint(1, 0)
|
||||
before[5], before[6] = love.graphics.transformPoint(0, 1)
|
||||
Renderer:occludeUprightActors(overhead)
|
||||
local r, g, b, a = love.graphics.getColor()
|
||||
local blend, mode = love.graphics.getBlendMode()
|
||||
local sx, sy, sw, sh = love.graphics.getScissor()
|
||||
local after = { love.graphics.transformPoint(0, 0) }
|
||||
after[3], after[4] = love.graphics.transformPoint(1, 0)
|
||||
after[5], after[6] = love.graphics.transformPoint(0, 1)
|
||||
local sameTransform = true
|
||||
for i, value in ipairs(before) do sameTransform = sameTransform and value == after[i] end
|
||||
F.check(love.graphics.getCanvas() == current and love.graphics.getShader() == shader
|
||||
and blend == "add" and mode == "premultiplied" and math.abs(r - 0.2) < 0.001
|
||||
and math.abs(g - 0.3) < 0.001 and math.abs(b - 0.4) < 0.001 and a == 0.5
|
||||
and sx == 4 and sy == 5 and sw == 6 and sh == 7 and sameTransform,
|
||||
"tilt2765 GPU_canvas_shader_blend_color_scissor_transform_restored")
|
||||
love.graphics.pop()
|
||||
for name, fn in pairs(saved) do up(View.draw, name, fn) end
|
||||
Renderer.worldViewSize = oldViewSize
|
||||
View.invalidate()
|
||||
Tilt.applyOptions({ tilt = 0 })
|
||||
F.finish()
|
||||
end
|
||||
@@ -0,0 +1,208 @@
|
||||
package.path = "./?.lua;./?/init.lua;" .. package.path
|
||||
love = love or require("tests.love_stub")
|
||||
local T = require("tests.harness")
|
||||
local check, eq = T.check, T.eq
|
||||
local GameVersion = require("src.core.GameVersion")
|
||||
local Schema = require("src.core.game3.save_schema_firered")
|
||||
local SaveData = require("src.core.SaveData")
|
||||
local Collision = require("src.core.game3.collision")
|
||||
local MB = require("src.core.game3.mb")
|
||||
local Interaction = require("src.core.game3.scripting.interaction_scripts")
|
||||
require("src.core.game3.save_sections").of("emerald")
|
||||
require("src.core.game3.save_sections").of("ruby")
|
||||
local s
|
||||
local function stub(name, value) package.loaded["src.core.game3." .. name] = value end
|
||||
stub("runtime", { isActive = function() return true end, getSession = function() return s end })
|
||||
stub("scripting.space", { bundle = {}, ensureBundle = function() end, activate = function() end,
|
||||
runEnterScripts = function() end, runOnWarpIntoMap = function() end })
|
||||
stub("ghosts", { capture = function() end, adopt = function() end,
|
||||
visibleIds = function() return {} end, openFadeWindow = function() end })
|
||||
stub("field", { lock = function() end, unlock = function() end, clearMetatiles = function() end })
|
||||
stub("audio", { setSavedSong = function() end, canOverrideMapMusic = function() return false end,
|
||||
stopSurfMusic = function() end, bikeMusic = function() end })
|
||||
stub("encounters", { resetRateModifiers = function() end })
|
||||
stub("field_effects", { leaveTallGrass = function() end, tallGrassAt = function() end })
|
||||
stub("field_view", { setCameraPanning = function() end })
|
||||
stub("field_modules", { enabled = function() return false end })
|
||||
stub("rse.init", { call = function() end })
|
||||
stub("rs.rematch", { enabled = function() return false end })
|
||||
stub("rse.rematch", { tryUpdateRandomTrainerRematchesForMap = function() end })
|
||||
stub("time_events", { run = function() end })
|
||||
stub("objects", { loadMap = function() end, restoreSnapshot = function() end, beginFadeIn = function() end })
|
||||
local Player = require("src.core.game3.player")
|
||||
local Map = require("src.core.game3.map")
|
||||
local Sprites = require("src.core.game3.ow_sprites")
|
||||
local Flags = require("src.core.game3.scripting.flags")
|
||||
local game = { save = { position = {} }, data = { maps = {} } }
|
||||
local mid, def
|
||||
local behavior, elevation, coll
|
||||
local function terrain(beh, elev, collision)
|
||||
behavior, elevation, coll = beh, elev, collision
|
||||
Interaction.behaviors[def.pair] = { [0] = beh }
|
||||
end
|
||||
local function setup(version)
|
||||
GameVersion.set(version)
|
||||
s = { version = version, flags = {}, vars = {}, party = {}, name = "SURFER" }
|
||||
mid = require("src.core.game3.profile").of(version).map.enginePrefix .. "AVATAR_TEST"
|
||||
s.map, s.x, s.y, s.facing = mid, 0, 0, "right"
|
||||
def = { width = 2, height = 2, mapType = 4, objects = {}, connections = {}, bikingAllowed = 1, pair = "avatar_test" }
|
||||
local layout = { width = 2, height = 2, pair = def.pair }
|
||||
function layout:collAt() return coll end
|
||||
function layout:elevAt() return elevation end
|
||||
function layout:midAt() return 0 end
|
||||
function layout:collArray() return { coll, coll, coll, coll } end
|
||||
def.midLayout = layout
|
||||
game.data.maps, game.session = { [mid] = def }, s
|
||||
terrain(MB.OCEAN_WATER, 1, 0x29)
|
||||
Player.reset(0, 0, "right")
|
||||
end
|
||||
local function load(opts)
|
||||
opts = opts or {}
|
||||
opts.x, opts.y, opts.facing = 0, 0, opts.facing or "right"
|
||||
check(Map.load(nil, game, mid, opts) ~= nil, "real Map.load completes")
|
||||
end
|
||||
local function roundtrip(surfing, underwater, elev)
|
||||
s.surfing, s.underwater, s.biking, s.elevation = surfing, underwater, false, elev
|
||||
local disk = SaveData.decode(SaveData.encode(Schema.toSaveTable(s)))
|
||||
s = Schema.fromSaveTable(disk)
|
||||
game.session = s
|
||||
Player.syncFromSession(s)
|
||||
load({ enterVia = "continue", initialLoad = true })
|
||||
end
|
||||
for _, version in ipairs({ "firered", "leafgreen", "emerald", "ruby", "sapphire" }) do
|
||||
for gender = 0, 1 do
|
||||
setup(version)
|
||||
s.gender = gender
|
||||
roundtrip(true, false, 1)
|
||||
check(Player.surfing and not Player.underwater and not Player.biking,
|
||||
version .. " native_codec_final_reset_restores_surf gender=" .. gender)
|
||||
eq(Sprites.avatarState(Player), "SURFING", version .. " saved Surf graphics")
|
||||
check(not Player.surfHopping and not Player.dismounting, version .. " Continue has no Surf animation")
|
||||
roundtrip(false, false, 4)
|
||||
check(not Player.surfing and not Player.underwater, version .. " explicit_on_foot_water_continue")
|
||||
eq(Player.elevation, 4, version .. " saved upper elevation")
|
||||
end
|
||||
setup(version)
|
||||
s.surfing, s.underwater = nil, nil
|
||||
s = Schema.fromSaveTable(SaveData.decode(SaveData.encode(Schema.toSaveTable(s))))
|
||||
eq(s.surfing, nil, version .. " legacy absence retained")
|
||||
Player.syncFromSession(s)
|
||||
load({ enterVia = "continue", initialLoad = true })
|
||||
check(Player.surfing, version .. " legacy_surfable_continue")
|
||||
Player.surfing, Player.underwater = true, false
|
||||
load({ seamless = true })
|
||||
check(Player.surfing, version .. " seamless preserves Surf")
|
||||
terrain(MB.NORMAL, 3, 0)
|
||||
load()
|
||||
check(not Player.surfing and not Player.underwater, version .. " ordinary_land_clears_avatar")
|
||||
terrain(MB.OCEAN_WATER, 1, 0x29)
|
||||
load()
|
||||
check(Player.surfing, version .. " ordinary_surfable_entry")
|
||||
local cruise = Flags.forVersion(version).IDS.FLAG_SYS_CRUISE_MODE
|
||||
Flags.setFlag(s, nil, cruise, true)
|
||||
def.mapType = 6
|
||||
load()
|
||||
check(not Player.surfing and not Player.underwater, version .. " cruise wins over water")
|
||||
eq(Player.facing, "right", version .. " cruise direction")
|
||||
Flags.setFlag(s, nil, cruise, false)
|
||||
if version == "firered" or version == "leafgreen" then
|
||||
mid = "FR_SEAFOAM_ISLANDS_B3F"
|
||||
s.map = mid
|
||||
game.data.maps[mid] = def
|
||||
load()
|
||||
check(not Player.surfing, version .. " seafoam_ordinary_on_foot")
|
||||
roundtrip(true, false, 1)
|
||||
check(Player.surfing, version .. " seafoam_saved_surf_continue")
|
||||
else
|
||||
def.mapType = 5
|
||||
load()
|
||||
check(Player.underwater and not Player.surfing, version .. " underwater_entry")
|
||||
roundtrip(false, true, 1)
|
||||
check(Player.underwater and not Player.surfing, version .. " native_underwater_continue")
|
||||
def.mapType = 4
|
||||
load()
|
||||
check(Player.surfing and not Player.underwater, version .. " dive_surface_entry")
|
||||
terrain(MB.BRIDGE_OVER_OCEAN, 4, 0)
|
||||
s.surfing, s.underwater, s.elevation = nil, nil, nil
|
||||
s = Schema.fromSaveTable(Schema.toSaveTable(s))
|
||||
Player.syncFromSession(s)
|
||||
load({ enterVia = "continue" })
|
||||
check(not Player.surfing, version .. " legacy_upper_bridge_on_foot")
|
||||
end
|
||||
setup(version)
|
||||
s.surfing, s.underwater = true, false
|
||||
s.specialSaveWarpFlags = 1
|
||||
s.continueGameWarp = { map = mid, x = 0, y = 0 }
|
||||
s = Schema.fromSaveTable(Schema.toSaveTable(s))
|
||||
terrain(MB.NORMAL, 3, 0)
|
||||
Player.syncFromSession(s)
|
||||
load({ enterVia = "continue" })
|
||||
check(not Player.surfing, version .. " special_continue_relocation_clears_saved_surf")
|
||||
end
|
||||
Player.underwater = true
|
||||
Player.reset(0, 0, "right")
|
||||
check(not Player.underwater, "reset clears stale underwater")
|
||||
local ModRuntime = require("src.mods.Runtime")
|
||||
local wants, emit = ModRuntime.wants, ModRuntime.emit
|
||||
local expected, label, observed, nativeShape
|
||||
local function synchronized()
|
||||
eq(s.surfing, expected.surfing, label .. " session Surf")
|
||||
eq(game.save.surfing, expected.surfing, label .. " host Surf")
|
||||
eq(s.biking, expected.biking, label .. " session bike")
|
||||
eq(game.save.biking, expected.biking, label .. " host bike")
|
||||
if nativeShape then
|
||||
eq(game.save.position, nil, label .. " native save stays flat")
|
||||
else
|
||||
eq(game.save.position.biking, expected.biking, label .. " host position bike")
|
||||
end
|
||||
eq(s.underwater, false, label .. " session underwater")
|
||||
eq(game.save.elevation, Player.elevation, label .. " host elevation")
|
||||
if not nativeShape then eq(game.save.position.x, Player.cellX, label .. " host x") end
|
||||
eq(s.x, Player.cellX, label .. " session x")
|
||||
end
|
||||
ModRuntime.wants = function(name) return name == "world.stepped" end
|
||||
ModRuntime.emit = function(name)
|
||||
eq(name, "world.stepped", label .. " observer event")
|
||||
observed = observed + 1
|
||||
synchronized()
|
||||
end
|
||||
local function completed(surfing, biking, name)
|
||||
expected, label, observed = { surfing = surfing, biking = biking }, name, 0
|
||||
local callback = 0
|
||||
Player._onStepDone = function()
|
||||
callback = callback + 1
|
||||
synchronized()
|
||||
end
|
||||
local frames = Player.stepFrames
|
||||
for _ = 1, frames do Player.tick(game) end
|
||||
check(not Player.moving and Player.surfing == surfing and Player.biking == biking, label .. " live completed mode")
|
||||
eq(observed, 1, label .. " observer runs once")
|
||||
eq(callback, 1, label .. " callback runs once")
|
||||
synchronized()
|
||||
end
|
||||
for _, version in ipairs({ "firered", "leafgreen", "emerald", "ruby", "sapphire" }) do
|
||||
local road = Flags.forVersion(version).IDS.FLAG_SYS_ON_CYCLING_ROAD
|
||||
for _, native in ipairs({ false, true }) do
|
||||
nativeShape = native
|
||||
for _, cycling in ipairs(road and { false, true } or { false }) do
|
||||
setup(version)
|
||||
terrain(MB.NORMAL, 3, 0)
|
||||
load()
|
||||
game.save = native and Schema.toSaveTable(s) or { position = {} }
|
||||
terrain(MB.OCEAN_WATER, 1, 0x29)
|
||||
Collision.bindMap(game, mid, def)
|
||||
check(Player.startSurfing(game), version .. " mount starts")
|
||||
local suffix = " native=" .. tostring(native) .. " cycling=" .. tostring(cycling)
|
||||
completed(true, false, version .. " completed_mount_sync" .. suffix)
|
||||
if native then game.save = Schema.toSaveTable(s) end
|
||||
terrain(MB.NORMAL, 3, 0)
|
||||
Collision.bindMap(game, mid, def)
|
||||
if road then Flags.setFlag(s, nil, road, cycling) end
|
||||
check(Player.forcedStep("left"), version .. " dismount starts")
|
||||
check(Player.dismounting, version .. " dismount transition selected")
|
||||
completed(false, cycling, version .. (cycling and " cycling_remount_sync" or " completed_dismount_sync") .. suffix)
|
||||
end
|
||||
end
|
||||
end
|
||||
ModRuntime.wants, ModRuntime.emit = wants, emit
|
||||
T.finish("game3_avatar_restore_2759_2761")
|
||||
@@ -0,0 +1,56 @@
|
||||
package.path = "./?.lua;./?/init.lua;" .. package.path
|
||||
require("tests.fixture_data.game3_map_sections").install()
|
||||
local Ui = require("src.core.game3.battle.ui")
|
||||
local function press(key)
|
||||
Ui.handleInput({ wasPressed = function(_, k) return k == key end })
|
||||
end
|
||||
local function check(ok, label)
|
||||
assert(ok, "FAIL cursor2766 " .. label)
|
||||
print("PASS cursor2766 " .. label)
|
||||
end
|
||||
local st = { player = { mon = {} } }
|
||||
Ui.reset({ headless = true })
|
||||
Ui.bindState(st)
|
||||
Ui.openMenu()
|
||||
check(Ui._menuIndex == 1, "initial_Fight")
|
||||
press("right")
|
||||
Ui.openMenu()
|
||||
check(Ui._menuIndex == 2, "single_navigation_reopen_Bag")
|
||||
press("down")
|
||||
Ui.openMenu()
|
||||
check(Ui._menuIndex == 4, "single_navigation_reopen_Run")
|
||||
st.player = { mon = {} }
|
||||
Ui.openMenu()
|
||||
check(Ui._menuIndex == 1, "single_new_battler_Fight")
|
||||
st.double = true
|
||||
st.battlers = { [0] = st.player, [2] = { mon = {} } }
|
||||
Ui.openMenu(0)
|
||||
press("right")
|
||||
Ui.openMenu(2)
|
||||
press("down")
|
||||
Ui.openMenu(0)
|
||||
check(Ui._menuIndex == 2, "double_slot0_Bag")
|
||||
Ui.openMenu(2)
|
||||
check(Ui._menuIndex == 3, "double_slot2_Pokemon")
|
||||
st.battlers[0] = { mon = {} }
|
||||
Ui.openMenu(0)
|
||||
check(Ui._menuIndex == 1, "double_slot0_canonical_replacement_Fight")
|
||||
Ui.openMenu(2)
|
||||
check(Ui._menuIndex == 3, "double_partner_cursor_unchanged")
|
||||
st.battlers[2] = { mon = {} }
|
||||
Ui.openMenu(2)
|
||||
check(Ui._menuIndex == 1, "double_slot2_replacement_Fight")
|
||||
st.double, st.battlers, st.safari = false, nil, true
|
||||
Ui.openMenu()
|
||||
press("right")
|
||||
Ui.openMenu()
|
||||
check(Ui._menuIndex == 2, "Safari_cursor_persists")
|
||||
st.safari, st.oldManTutorial = false, true
|
||||
Ui.openMenu()
|
||||
local command = Ui.takeCommand()
|
||||
check(Ui._mode == "none" and command and command.kind == "bag", "OldMan_scripted_Bag")
|
||||
Ui.reset({ headless = true })
|
||||
check(next(Ui._actionCursor) == nil and next(Ui._actionCursorBattler) == nil,
|
||||
"new_battle_clears_owners_and_cursors")
|
||||
print("PASS cursor2766_ROM_free")
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
package.path = "./?.lua;./?/init.lua;" .. package.path
|
||||
love = love or require("tests.love_stub")
|
||||
local T = require("tests.harness")
|
||||
local GameVersion = require("src.core.GameVersion")
|
||||
local Movement = require("src.core.game3.scripting.movement")
|
||||
local Player = require("src.core.game3.player")
|
||||
local Objects = require("src.core.game3.objects")
|
||||
local Collision = require("src.core.game3.collision")
|
||||
local Runtime = require("src.core.game3.runtime")
|
||||
local Field = require("src.core.game3.field")
|
||||
local game = { data = {}, session = { version = "emerald", flags = {}, vars = {}, party = {} } }
|
||||
Runtime.session = game.session
|
||||
Field.locked = true
|
||||
local dirs = { "down", "up", "left", "right" }
|
||||
local delta = { down = { 0, 1 }, up = { 0, -1 }, left = { -1, 0 }, right = { 1, 0 } }
|
||||
local function tick()
|
||||
Player.tick(game)
|
||||
Objects.update(game)
|
||||
end
|
||||
local function fixture()
|
||||
Player.reset(5, 5, "down")
|
||||
local eo = { localId = 1, cellX = 5, cellY = 5, px = 80, py = 80,
|
||||
facing = "down", animClock = 0, progress = 0, frozen = true, scriptBusy = true }
|
||||
Objects._byId, Objects._order, Objects._tracks = { [1] = eo }, { 1 }, {}
|
||||
return eo
|
||||
end
|
||||
for _, version in ipairs({ "emerald", "ruby", "sapphire" }) do
|
||||
GameVersion.set(version)
|
||||
game.session.version = version
|
||||
for i = 0, 4 do
|
||||
local frames = 2 ^ i
|
||||
fixture()
|
||||
Objects.applyMovement(255, { 0x18 + i, 0xFE })
|
||||
for n = 1, frames - 1 do
|
||||
tick()
|
||||
T.check(not Objects.pollMovement(255), version .. " delay remains active " .. n)
|
||||
end
|
||||
tick()
|
||||
T.check(Objects.pollMovement(255), version .. " delay ends at " .. frames)
|
||||
end
|
||||
for i, dir in ipairs(dirs) do
|
||||
for _, spec in ipairs({ { 0x31, 6 }, { 0x39, 2 } }) do
|
||||
for _, lid in ipairs({ 1, 255 }) do
|
||||
local eo = fixture()
|
||||
local actor = lid == 255 and Player or eo
|
||||
Objects.applyMovement(lid, { spec[1] + i - 1, 0xFE })
|
||||
T.eq(actor.stepFrames, spec[2], version .. " " .. dir .. " selected duration")
|
||||
for n = 1, spec[2] do
|
||||
tick()
|
||||
T.eq(actor.moving, n < spec[2], version .. " " .. dir .. " moving tick " .. n)
|
||||
end
|
||||
T.eq(actor.cellX, 5 + delta[dir][1], version .. " " .. dir .. " landing x")
|
||||
T.eq(actor.cellY, 5 + delta[dir][2], version .. " " .. dir .. " landing y")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
for _, version in ipairs({ "firered", "leafgreen" }) do
|
||||
GameVersion.set(version)
|
||||
game.session.version = version
|
||||
T.eq(Movement.decodeAction(0x1C).frames, 32, version .. " delay behavior retained")
|
||||
T.eq(Movement.decodeAction(0x32).kind, "nop", version .. " current behavior retained")
|
||||
T.eq(Movement.decodeAction(0x39).frames, nil, version .. " slide behavior retained")
|
||||
end
|
||||
GameVersion.set("emerald")
|
||||
game.session.version = "emerald"
|
||||
require("src.import.gba.versions").select("emerald")
|
||||
local Cache = require("tests.game3_cache")
|
||||
local root = Cache.mount("scripts/movements.lua", { native = true })
|
||||
if not root then
|
||||
print("[skip] 2764 extracted Emerald scenes: " .. tostring(Cache.reason))
|
||||
T.finish("game3_cutscene_push_2764")
|
||||
end
|
||||
local Dataset = require("src.core.game3.dataset")
|
||||
Dataset.hydrate(game)
|
||||
local bundle = require("src.import.gba.extract_scripts").loadBundle(Cache.cache(), root, { allowIncomplete = true })
|
||||
local moves = bundle.movements
|
||||
local function enter(id)
|
||||
game.session.map = id
|
||||
local def = assert(game.data.maps[id])
|
||||
Collision.bindMap(game, id, def)
|
||||
Objects.loadMap(game, id, def)
|
||||
end
|
||||
local function stepFor(n)
|
||||
for _ = 1, n do tick() end
|
||||
end
|
||||
enter("EM_ROUTE119_WEATHER_INSTITUTE_2F")
|
||||
Player.reset(5, 6, "left")
|
||||
local grunt = assert(Objects.find(7))
|
||||
grunt.hidden = false
|
||||
Objects.applyMovement(7, assert(moves["g3:08270170"]))
|
||||
Objects.applyMovement(255, assert(moves["g3:0827017c"]))
|
||||
stepFor(80)
|
||||
T.eq(Player.py, 96, "Weather player stays until shove")
|
||||
stepFor(6)
|
||||
T.eq(Player.cellY, 5, "Weather six-frame north shove complete")
|
||||
T.check(grunt.moving, "Weather shove precedes grunt completion")
|
||||
stepFor(2)
|
||||
T.eq(grunt.cellX, 5, "Weather grunt reaches destination")
|
||||
T.eq(Player.cellY, 5, "Weather player vacated grunt destination")
|
||||
stepFor(4)
|
||||
Objects.applyMovement(255, assert(moves["g3:08270184"]))
|
||||
local scientist = assert(Objects.find(5))
|
||||
scientist.cellX, scientist.cellY, scientist.px, scientist.py = 1, 6, 16, 96
|
||||
scientist.hidden = false
|
||||
Objects.applyMovement(5, assert(moves["g3:08270187"]))
|
||||
stepFor(2)
|
||||
T.eq(Player.cellY, 6, "Weather return slide lasts two frames")
|
||||
stepFor(48)
|
||||
T.eq(Player.cellX, 5, "Weather dialogue player x")
|
||||
T.eq(Player.cellY, 6, "Weather dialogue player y")
|
||||
T.eq(scientist.cellX, 4, "Weather dialogue scientist x")
|
||||
T.eq(scientist.cellY, 6, "Weather dialogue scientist y")
|
||||
enter("EM_SLATEPORT_CITY_OCEANIC_MUSEUM_2F")
|
||||
Player.reset(12, 6, "left")
|
||||
local archie, other = assert(Objects.find(2)), assert(Objects.find(4))
|
||||
other.cellX, other.cellY, other.px, other.py = 10, 6, 160, 96
|
||||
archie.hidden, other.hidden = false, false
|
||||
Objects.applyMovement(2, assert(moves["g3:0820bcd8"]))
|
||||
Objects.applyMovement(4, assert(moves["g3:0820bcfe"]))
|
||||
stepFor(140)
|
||||
T.check(archie.moving and other.moving and other.py > 96, "Museum grunt vacates during Archie approach")
|
||||
stepFor(4)
|
||||
T.eq(archie.cellX, 10, "Museum Archie destination x")
|
||||
T.eq(archie.cellY, 6, "Museum Archie destination y")
|
||||
T.eq(other.cellY, 7, "Museum grunt vacates destination on time")
|
||||
T.eq(Player.cellY, 6, "Museum player was not shoved")
|
||||
T.finish("game3_cutscene_push_2764")
|
||||
@@ -0,0 +1,147 @@
|
||||
package.path = "./?.lua;./?/init.lua;" .. package.path
|
||||
love = require("tests.love_stub")
|
||||
local GameVersion = require("src.core.GameVersion")
|
||||
local Versions = require("src.import.gba.versions")
|
||||
local Objects = require("src.core.game3.objects")
|
||||
local Prepare = require("src.core.game3.object_prepare")
|
||||
local sheets, sprites = {}, {}
|
||||
package.loaded["src.core.game3.ow_sprites"] = { get = function(id) return sprites[id] end }
|
||||
package.loaded["src.core.game3.field_effects"] = {
|
||||
loadSheet = function(name) return sheets[name] end,
|
||||
manifestObject = function(name) return sheets[name] end,
|
||||
}
|
||||
local Fx = require("src.core.game3.field_effects_rse")
|
||||
local View = require("src.core.game3.field_view")
|
||||
|
||||
local function check(ok, label)
|
||||
assert(ok, "FAIL disguise2763 " .. label)
|
||||
end
|
||||
|
||||
local function maskFor(eo)
|
||||
local actors = {}
|
||||
Fx.collectActors(actors)
|
||||
for _, actor in ipairs(actors) do
|
||||
if actor.disguiseObject == eo then return actor end
|
||||
end
|
||||
end
|
||||
|
||||
local function exercise(def, version, label)
|
||||
local eo = Prepare.instance(def, {
|
||||
version = version, rse = true, graphicsId = def.graphicsId, elevation = def.elevation,
|
||||
})
|
||||
eo.disguise.a = Fx.anim(Fx.animCmds(eo.disguise.sheet, 1))
|
||||
Objects._order, Objects._byId = { eo.localId }, { [eo.localId] = eo }
|
||||
Objects._bounds = nil
|
||||
check(#Objects.forDraw() == 1, label .. " ninja_drawn")
|
||||
for _, camY in ipairs({ 0, eo.py - 112, eo.py - 23, eo.py + 17 }) do
|
||||
for _, fixed in ipairs({ false, 0, 1, 83, 255 }) do
|
||||
eo.fixedPriority, eo.subpriority = fixed ~= false, fixed ~= false and fixed or nil
|
||||
eo.fixedClass = nil
|
||||
local ninja = { kind = "npc", i = eo.localId, eventObject = eo,
|
||||
x = eo.px, y = eo.py, sortY = eo.py, elevation = eo.elevation }
|
||||
local back = { kind = "npc", i = 201, y = eo.py - 32, elevation = 3 }
|
||||
local front = { kind = "npc", i = 202, y = eo.py + 32, elevation = 3 }
|
||||
local actors = { front, ninja, back }
|
||||
Fx.collectActors(actors)
|
||||
check(#actors == 4, label .. " mask_collected")
|
||||
local mask = actors[4]
|
||||
check(mask.x == eo.px and mask.y == eo.py - 16, label .. " bitmap_anchor")
|
||||
check(mask.eventObject == nil, label .. " independent_sprite")
|
||||
actors = { mask, front, ninja, back }
|
||||
local under, over = View.applyDrawOrder(actors, {}, {}, camY)
|
||||
check(#under == 4 and #over == 0, label .. " mask_priority_class")
|
||||
check(mask.subpriority == (ninja.subpriority - 1) % 256, label .. " linked_subpriority")
|
||||
check(mask.priority == 2 and not mask.fixedPriority, label .. " mask_fixed_state")
|
||||
check(eo.fixedClass == (fixed ~= false and 2 or nil), label .. " linked_fixed_class")
|
||||
local indices = {}
|
||||
for i, actor in ipairs(under) do indices[actor] = i end
|
||||
check(indices[back] < indices[front], label .. " unrelated_objects_order")
|
||||
if fixed == 0 then
|
||||
check(mask.subpriority == 255 and indices[mask] < indices[ninja], label .. " unsigned_wrap")
|
||||
else
|
||||
check(indices[ninja] < indices[mask], label .. " ninja_before_mask")
|
||||
end
|
||||
check(mask.x == eo.px and mask.y == eo.py - 16, label .. " sorting_preserves_position")
|
||||
end
|
||||
end
|
||||
eo.fixedPriority, eo.subpriority, eo.fixedClass = false, nil, nil
|
||||
eo.invisible = true
|
||||
check(maskFor(eo) == nil and #Objects.forDraw() == 0, label .. " linked_invisible")
|
||||
eo.invisible, eo.hidden = false, true
|
||||
check(maskFor(eo) == nil, label .. " linked_hidden")
|
||||
eo.hidden, eo.visible = false, false
|
||||
check(maskFor(eo) == nil, label .. " linked_not_visible")
|
||||
eo.visible = true
|
||||
Objects.revealTrainer(eo)
|
||||
Fx.stepObjects()
|
||||
check(eo.disguise.revealing and maskFor(eo) ~= nil and #Objects.forDraw() == 1,
|
||||
label .. " ninja_drawn_during_tear")
|
||||
for _ = 1, 100 do Fx.stepObjects() end
|
||||
check(eo.disguise.done and maskFor(eo) == nil and #Objects.forDraw() == 1,
|
||||
label .. " reveal_removes_only_mask")
|
||||
print("PASS disguise2763 " .. label .. " linked_order_visibility_reveal")
|
||||
end
|
||||
|
||||
local version = arg and arg[1]
|
||||
if version then
|
||||
GameVersion.set(version)
|
||||
Versions.select(version)
|
||||
local Cache = require("tests.game3_cache")
|
||||
local root = Cache.mount()
|
||||
if not root then
|
||||
print("[skip] disguise2763 cache integration: " .. tostring(Cache.reason))
|
||||
os.exit(0)
|
||||
end
|
||||
local cache = Cache.cache()
|
||||
local function data(rel)
|
||||
return assert(loadstring(assert(cache:read(root .. "/" .. rel)), "@" .. rel))()
|
||||
end
|
||||
sprites = data("ow/manifest.lua").sprites
|
||||
for _, row in ipairs(data("field_effects/objects.lua").objects) do
|
||||
if row.name == "tree_disguise" or row.name == "mountain_disguise" then
|
||||
row.image, row.quads = {}, {}
|
||||
for frame = 0, row.frames - 1 do row.quads[frame] = {} end
|
||||
sheets[row.name] = row
|
||||
end
|
||||
end
|
||||
local ninja = assert(cache:read(root .. "/ow/5.rgba"))
|
||||
for name in pairs(sheets) do
|
||||
local mask = assert(cache:read(root .. "/field_effects/" .. name .. ".rgba"))
|
||||
local opaque, covered = 0, 0
|
||||
for pixel = 0, 255 do
|
||||
if ninja:byte(pixel * 4 + 4) > 0 then
|
||||
opaque = opaque + 1
|
||||
if mask:byte((pixel + 256) * 4 + 4) > 0 then covered = covered + 1 end
|
||||
end
|
||||
end
|
||||
check(opaque == 124 and covered == opaque, version .. " " .. name .. " opaque_occlusion")
|
||||
print("PASS disguise2763 " .. version .. " " .. name .. " opaque_occlusion_124_of_124")
|
||||
end
|
||||
local count = 0
|
||||
for mapId, events in pairs(data("scripts/events.lua")) do
|
||||
for _, def in ipairs(events.objects or {}) do
|
||||
if def.movementType == 0x39 or def.movementType == 0x3A then
|
||||
exercise(def, version, version .. " " .. mapId .. " " .. def.localId)
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
check(count == (version == "emerald" and 7 or 5), version .. " all_cached_disguises")
|
||||
print("PASS disguise2763 " .. version .. " cached_disguises=" .. count)
|
||||
else
|
||||
sprites[5] = { width = 16, height = 16 }
|
||||
for _, name in ipairs({ "tree_disguise", "mountain_disguise" }) do
|
||||
sheets[name] = { fw = 16, fh = 32, image = {}, quads = { [0] = {}, [1] = {} },
|
||||
anims = { { { "frame", 0, 2 }, { "end" } },
|
||||
{ { "frame", 0, 2 }, { "frame", 1, 2 }, { "end" } } } }
|
||||
end
|
||||
for _, selected in ipairs({ "emerald", "ruby", "sapphire" }) do
|
||||
GameVersion.set(selected)
|
||||
Versions.select(selected)
|
||||
for _, movement in ipairs({ 0x39, 0x3A }) do
|
||||
exercise({ localId = 1, x = 12, y = 14, elevation = 3, graphicsId = 5,
|
||||
movementType = movement }, selected, selected .. " movement=" .. movement)
|
||||
end
|
||||
end
|
||||
end
|
||||
print("PASS game3_disguise_order_2763")
|
||||
@@ -0,0 +1,98 @@
|
||||
package.path = "./?.lua;./?/init.lua;" .. package.path
|
||||
love = require("tests.love_stub")
|
||||
local T = require("tests.harness")
|
||||
local GameVersion = require("src.core.GameVersion")
|
||||
local Profile = require("src.core.game3.profile")
|
||||
local Flags = require("src.core.game3.scripting.flags")
|
||||
local Space = require("src.core.game3.scripting.space")
|
||||
local Vm = require("src.core.game3.scripting.vm")
|
||||
local TextIR = require("src.core.game3.scripting.text_ir")
|
||||
local session, finishShowMon, sound
|
||||
package.loaded["src.core.game3.runtime"] = { getSession = function() return session end }
|
||||
package.loaded["src.core.game3.field_move_show_mon"] = {
|
||||
start = function(_, _, fn) finishShowMon = fn end,
|
||||
}
|
||||
package.loaded["src.core.game3.audio"] = { playSe = function(id) sound = id end }
|
||||
local level, radius = 4, nil
|
||||
local View = {
|
||||
getFlashLevel = function() return level end,
|
||||
radiusForLevel = function(v) return 120 - v * 20 end,
|
||||
setFlashRadius = function(v) radius = v end,
|
||||
setFlashLevel = function(v) level, radius = v, nil end,
|
||||
}
|
||||
package.loaded["src.core.game3.field_view"] = View
|
||||
local Moves = require("src.core.game3.field_moves")
|
||||
local Field = require("src.core.game3.field")
|
||||
local Fx = require("src.core.game3.field_effects")
|
||||
local Text = require("src.core.game3.rom_text")
|
||||
local function rejection(ctx, label)
|
||||
local ok, res = pcall(Moves.flashFromMenu, ctx)
|
||||
T.check(ok and res.ok == false and res.text == "CACHED REJECTION", label)
|
||||
end
|
||||
for _, version in ipairs({ "ruby", "sapphire", "emerald", "firered", "leafgreen" }) do
|
||||
GameVersion.set(version)
|
||||
session = { version = version, flags = {}, vars = {} }
|
||||
Field._session = session
|
||||
Field._locks = {}
|
||||
Space.store = Flags.newStore({ flags = session.flags })
|
||||
session.flags = Space.store.flags
|
||||
local rs = version == "ruby" or version == "sapphire"
|
||||
local label = rs and "gUnknown_081B694A" or "EventScript_UseFlash"
|
||||
local key = "fixture_flash"
|
||||
Space.bundle = { text = {
|
||||
[rs and "OtherText_CantUseThatHere" or "gText_InUseAlready_PM"] = TextIR.fromAscii("CACHED REJECTION"),
|
||||
}, labels = { [label] = key }, scripts = {
|
||||
[key] = { { op = "animateflash", 1 }, { op = "setflashlevel", 1 }, { op = "end" } },
|
||||
} }
|
||||
Space.vm = Vm.new({ scripts = Space.bundle.scripts, store = Space.store })
|
||||
Flags.setFlag(Space.store, nil, Moves.badgeFlag("FLASH"), true)
|
||||
local ctx = { session = session, store = Space.store, isCave = true }
|
||||
local payload = Moves.flashFromMenu(ctx)
|
||||
T.check(payload.ok and payload.action == "flash", version .. " first_use_available")
|
||||
level, radius, sound = 4, nil, nil
|
||||
Fx._anims = {}
|
||||
Field.executeFieldMove(payload)
|
||||
T.check(Field.locked and finishShowMon ~= nil and #Fx._anims == 0,
|
||||
version .. " show_mon_precedes_flash")
|
||||
T.check(not Flags.getFlag(Space.store, nil, payload.flag), version .. " flag_waits_for_show_mon")
|
||||
finishShowMon()
|
||||
T.eq(sound, payload.se, version .. " flash_sound_preserved")
|
||||
T.check(Flags.getFlag(Space.store, nil, payload.flag) and Field.locked,
|
||||
version .. " active_flag_and_animation_lock")
|
||||
Fx.step()
|
||||
Space.vm:tick()
|
||||
T.check(Field.locked and radius ~= nil, version .. " lock_waits_for_radius_animation")
|
||||
for _ = 1, 600 do Fx.step(); Space.vm:tick() end
|
||||
local rse = Profile.of(version).family == "rse"
|
||||
T.eq(level, rse and 1 or 0, version .. " first_use_cart_level")
|
||||
T.check(not Field.locked and not Space.vm:isRunning(), version .. " animation_end_unlocks_field")
|
||||
rejection(ctx, version .. " repeated_flash_cached_prompt")
|
||||
local game = { session = session, data = { maps = {
|
||||
floor = { mapType = 4, cave = 1 }, lit = { mapType = 4, cave = 0 },
|
||||
outdoors = { mapType = 1, cave = 0 },
|
||||
} } }
|
||||
Field.clearTempFieldEventData(game, "floor")
|
||||
rejection(ctx, version .. " dark_floor_retains_flash")
|
||||
Field.clearTempFieldEventData(game, "lit")
|
||||
rejection(ctx, version .. " lit_underground_retains_flash")
|
||||
Field.clearTempFieldEventData(game, "outdoors")
|
||||
T.check(not Flags.getFlag(Space.store, nil, payload.flag) and Moves.flashFromMenu(ctx).ok,
|
||||
version .. " outdoors_enables_next_cave_flash")
|
||||
if rs then
|
||||
T.raises(function() Text.ir("uncached_prompt") end, "is not in the script cache",
|
||||
version .. " missing_rom_text_stays_strict")
|
||||
Space.bundle.scripts[key] = { { op = "animateflash", 2 }, { op = "setflashlevel", 2 }, { op = "end" } }
|
||||
level, radius = 4, nil
|
||||
Fx._anims = {}
|
||||
Field.executeFieldMove(payload)
|
||||
finishShowMon()
|
||||
for _ = 1, 600 do Fx.step(); Space.vm:tick() end
|
||||
T.eq(level, 2, version .. " extracted_script_controls_level")
|
||||
Space.bundle.labels[label] = nil
|
||||
Field.executeFieldMove(payload)
|
||||
T.raises(finishShowMon, "ROM Flash script is not in the script cache",
|
||||
version .. " missing_rom_script_stays_strict")
|
||||
Field._locks = {}
|
||||
end
|
||||
end
|
||||
T.finish("game3_rs_flash_2762")
|
||||
@@ -0,0 +1,85 @@
|
||||
package.path = "./?.lua;./?/init.lua;" .. package.path
|
||||
love = love or require("tests.love_stub")
|
||||
local T = require("tests.harness")
|
||||
local check, eq = T.check, T.eq
|
||||
local GameVersion = require("src.core.GameVersion")
|
||||
local MB = require("src.core.game3.mb")
|
||||
local Collision = require("src.core.game3.collision")
|
||||
local Interaction = require("src.core.game3.scripting.interaction_scripts")
|
||||
local session = { version = "emerald", map = "EM_SURF_TIMING", flags = {}, vars = {} }
|
||||
package.loaded["src.core.game3.runtime"] = { getSession = function() return session end }
|
||||
package.loaded["src.core.game3.audio"] = { stopSurfMusic = function() end }
|
||||
package.loaded["src.core.game3.bike"] = { rse = function() return nil end }
|
||||
package.loaded["src.core.game3.field_effects"] = { leaveTallGrass = function() end, tallGrassAt = function() end }
|
||||
package.loaded["src.core.game3.field"] = { locked = false }
|
||||
package.loaded["src.core.game3.objects"] = { hasMap = function() return false end }
|
||||
local Player = require("src.core.game3.player")
|
||||
local Flags = require("src.core.game3.scripting.flags")
|
||||
local game = { save = { position = {} } }
|
||||
local function setup(version, surfing, underwater, shore)
|
||||
GameVersion.set(version)
|
||||
session.version = version
|
||||
local layout = { width = 4, height = 1, pair = "surf_timing" }
|
||||
function layout:collAt(x) return surfing and not (shore and x == 1) and 0x29 or 0 end
|
||||
function layout:elevAt(x) return surfing and not (shore and x == 1) and 1 or 3 end
|
||||
function layout:midAt(x) return x end
|
||||
function layout:collArray() return { self:collAt(0), self:collAt(1), self:collAt(2), self:collAt(3) } end
|
||||
Interaction.behaviors[layout.pair] = { [0] = MB.OCEAN_WATER, [1] = shore and MB.NORMAL or MB.OCEAN_WATER,
|
||||
[2] = MB.OCEAN_WATER, [3] = MB.OCEAN_WATER }
|
||||
Collision.bindMap(game, session.map, { midLayout = layout, pair = layout.pair, warps = {} })
|
||||
Player.reset(0, 0, "right")
|
||||
Player.surfing, Player.underwater = surfing, underwater
|
||||
Player.elevation, Player.currentElevation = surfing and 1 or 3, surfing and 1 or 3
|
||||
end
|
||||
local function measure(frames, label)
|
||||
local start = Player.px
|
||||
Player._onStepDone = function() end
|
||||
eq(Player.stepFrames, frames, label .. " selected duration")
|
||||
for frame = 1, frames do
|
||||
Player.tick(game)
|
||||
eq(Player.px - start, math.floor(frame * 16 / frames), label .. " pixel " .. frame)
|
||||
eq(Player.moving, frame < frames, label .. " completes exactly " .. frame)
|
||||
end
|
||||
eq(Player.cellX, 1, label .. " landing")
|
||||
end
|
||||
for _, version in ipairs({ "firered", "leafgreen", "emerald", "ruby", "sapphire" }) do
|
||||
for _, b in ipairs({ false, true }) do
|
||||
for _, shoes in ipairs({ false, true }) do
|
||||
setup(version, true, false)
|
||||
local id = Flags.forVersion(version).IDS.FLAG_SYS_B_DASH
|
||||
Flags.setFlag(session, nil, id, shoes)
|
||||
local input = { isDown = function(_, key) return key == "right" or key == "b" and b end }
|
||||
Player.update(game, input)
|
||||
check(Player.moving, version .. " Surf input starts")
|
||||
measure(8, version .. " surf_B=" .. tostring(b) .. "_shoes=" .. tostring(shoes))
|
||||
check(Player.surfing and not Player.running, version .. " fast Surf keeps walking animation")
|
||||
end
|
||||
end
|
||||
setup(version, true, false)
|
||||
eq(Player.tryMove("right", game, false), "step", version .. " direct Surf step")
|
||||
measure(8, version .. " direct_surf_eight_frames")
|
||||
setup(version, false, false)
|
||||
eq(Player.tryMove("right", game, false), "step", version .. " foot walk")
|
||||
measure(16, version .. " foot_walk")
|
||||
setup(version, false, false)
|
||||
eq(Player.tryMove("right", game, true), "step", version .. " foot run")
|
||||
measure(8, version .. " foot_run")
|
||||
setup(version, true, false)
|
||||
check(Player.scriptStep("right", false), version .. " scripted Surf walk starts")
|
||||
measure(16, version .. " scripted_surf_walk")
|
||||
setup(version, true, true)
|
||||
eq(Player.tryMove("right", game, false), "step", version .. " underwater step")
|
||||
measure(16, version .. " underwater_walk")
|
||||
setup(version, true, false, true)
|
||||
eq(Player.tryMove("right", game, true), "step", version .. " Surf dismount")
|
||||
eq(Player.stepFrames, 16, version .. " dismount hop duration")
|
||||
check(Player.dismounting and Player.jumping, version .. " dismount semantics")
|
||||
setup(version, true, false)
|
||||
Player.surfHopping = true
|
||||
eq(Player.tryMove("right", game, false), "step", version .. " Surf mount")
|
||||
eq(Player.stepFrames, 16, version .. " mount hop duration")
|
||||
setup(version, true, false)
|
||||
check(Player.forcedStep("right", 4), version .. " explicit forced Surf step")
|
||||
eq(Player.stepFrames, 4, version .. " forced duration remains explicit")
|
||||
end
|
||||
T.finish("game3_surf_speed_2767")
|
||||
@@ -0,0 +1,145 @@
|
||||
package.path = "./?.lua;./?/init.lua;" .. package.path
|
||||
love = require("tests.love_stub")
|
||||
local T = require("tests.harness")
|
||||
local noop = function() end
|
||||
for _, name in ipairs({ "seagallop_ui", "shop_menu", "field_effects", "doors", "pokecenter_heal", "ss_anne", "field_weather", "ow_sprites" }) do
|
||||
package.loaded["src.core.game3." .. name] = {}
|
||||
end
|
||||
package.loaded["src.core.game3.map"] = { refreshWorld = noop }
|
||||
package.loaded["src.core.game3.bg"] = { hasVisible = function() return false end }
|
||||
package.loaded["src.core.game3.oam"] = {
|
||||
resetFrame = noop, setLayer = noop, animateSprites = noop, buildOamBuffer = noop, flush = noop,
|
||||
}
|
||||
local F = require("src.core.game3.field_view")
|
||||
local D = require("src.core.game3.display")
|
||||
local R = require("src.render.Renderer")
|
||||
local Tilt = require("src.render.Tilt")
|
||||
local function up(fn, wanted, replacement)
|
||||
for i = 1, 100 do
|
||||
local name, value = debug.getupvalue(fn, i)
|
||||
if name == wanted then
|
||||
if replacement then debug.setupvalue(fn, i, replacement) end
|
||||
return value
|
||||
end
|
||||
end
|
||||
error("missing " .. wanted)
|
||||
end
|
||||
local plane = up(up(D.present, "presentPlanes"), "drawFieldPlane")
|
||||
local records = {}
|
||||
local function record(label) records[#records + 1] = label end
|
||||
local roof, neighbor = {}, {}
|
||||
local mesh = { setTexture = function(self, texture) self.texture = texture end,
|
||||
setVertices = function(self, vertices) self.vertices = vertices end }
|
||||
love.graphics.newMesh = function() return mesh end
|
||||
love.graphics.newShader = function() return {} end
|
||||
love.graphics.setStencilTest = noop
|
||||
love.graphics.draw = function(what)
|
||||
if what == roof or what == neighbor then
|
||||
record(love.graphics.getCanvas() == R.worldCanvas and "ground-roof" or "mask-roof")
|
||||
elseif what == mesh then
|
||||
T.eq(love.graphics.getCanvas(), R.uprightCanvas, "mask projects onto upright actors")
|
||||
T.eq(love.graphics.getBlendMode(), "multiply", "mask removes actor coverage")
|
||||
local shader = love.graphics.getShader()
|
||||
T.check(shader ~= nil, "projected mask uses a shader")
|
||||
record("occlude-normal")
|
||||
end
|
||||
end
|
||||
up(F.draw, "drawNativeTiles", function()
|
||||
F._nativeOverPair, F._nativeOverBatches = "main", { main = roof, neighbor = neighbor }
|
||||
record("ground")
|
||||
return true
|
||||
end)
|
||||
up(F.draw, "collectGame3Actors", function()
|
||||
return F.applyDrawOrder({
|
||||
{ kind = "mod", elevation = 3, x = 80, y = 192, draw = function() record("normal") end },
|
||||
{ kind = "mod", elevation = 13, x = 80, y = 192, draw = function() record("elevated") end },
|
||||
})
|
||||
end)
|
||||
local game = { mapId = "sample", data = { maps = { sample = { width = 32, blocks = {} } } },
|
||||
world = { map = { id = "sample" }, player = { px = 80, py = 192 } } }
|
||||
F._flashMapId = "sample"
|
||||
local function index(label)
|
||||
for i, item in ipairs(records) do if item == label then return i end end
|
||||
end
|
||||
local function render(level, transitioning)
|
||||
records = {}
|
||||
Tilt.applyOptions({ tilt = level })
|
||||
package.loaded["src.core.game3.battle_transition"] = { isActive = function() return transitioning end }
|
||||
R:init()
|
||||
R:beginFrame(true)
|
||||
R:beginWorldPass()
|
||||
plane(game, 240, 160, R)
|
||||
T.eq(love.graphics.getCanvas(), R.worldCanvas, "field plane restores the current world canvas")
|
||||
if level > 0 and not transitioning then
|
||||
T.check(index("occlude-normal") ~= nil, "tilt2765 normal actors receive roof occlusion")
|
||||
local masked = index("occlude-normal")
|
||||
T.check(masked and index("normal") < masked and masked < index("elevated"),
|
||||
"tilt2765 elevated actors draw after roof occlusion")
|
||||
local maskRoofs = 0
|
||||
for _, item in ipairs(records) do if item == "mask-roof" then maskRoofs = maskRoofs + 1 end end
|
||||
T.eq(maskRoofs, 2, "tilt2765 includes the connected-pair overhead batch")
|
||||
local vw, vh = R.worldCanvas:getWidth(), R.worldCanvas:getHeight()
|
||||
local expected = Tilt.meshCorners(vw, vh)
|
||||
if mesh.vertices then
|
||||
for i, vertex in ipairs(expected) do
|
||||
for j, value in ipairs(vertex) do T.eq(mesh.vertices[i][j], value, "mask shares ground projection") end
|
||||
end
|
||||
end
|
||||
T.eq(love.graphics.getShader(), nil, "mask restores shader")
|
||||
T.eq(love.graphics.getBlendMode(), "alpha", "mask restores blend mode")
|
||||
else
|
||||
T.eq(index("occlude-normal"), nil, "tilt2765 OFF and transitions skip the mask")
|
||||
T.check(index("normal") < index("ground-roof") and index("ground-roof") < index("elevated"),
|
||||
"flat field keeps cart layer ordering")
|
||||
T.eq(R.uprightActive, false, "flat field does not enter upright pass")
|
||||
end
|
||||
R:endWorldPass()
|
||||
end
|
||||
for level = 0, 1 do
|
||||
Tilt.applyOptions({ tilt = level })
|
||||
R:init()
|
||||
R:beginFrame(true)
|
||||
R:beginWorldPass()
|
||||
if level > 0 then
|
||||
R:beginUprightPass()
|
||||
R:endUprightPass()
|
||||
end
|
||||
R:endWorldPass()
|
||||
local draw, getBlend = love.graphics.draw, love.graphics.getBlendMode
|
||||
local reads = 0
|
||||
love.graphics.draw = noop
|
||||
love.graphics.getBlendMode = function()
|
||||
reads = reads + 1
|
||||
return getBlend()
|
||||
end
|
||||
R:endFrame(nil, nil)
|
||||
love.graphics.draw, love.graphics.getBlendMode = draw, getBlend
|
||||
T.eq(reads, 0, "tilt2765 unmasked composite skips blend-state reads at level " .. level)
|
||||
end
|
||||
render(0, false)
|
||||
for level = 1, 3 do render(level, false) end
|
||||
render(1, true)
|
||||
render(1, false)
|
||||
local priorMask = R.tiltOverheadCanvas
|
||||
T.check(priorMask ~= nil, "tilt2765 lifecycle starts with an allocated mask")
|
||||
R:init()
|
||||
T.check(priorMask and priorMask.released, "tilt2765 init releases the overhead mask")
|
||||
T.eq(R.tiltOverheadCanvas, nil, "tilt2765 init drops the overhead mask")
|
||||
T.eq(R.uprightOccluded, false, "tilt2765 init clears premultiplied upright compositing")
|
||||
render(1, false)
|
||||
local releasedMask = R.tiltOverheadCanvas
|
||||
R:releaseCanvases()
|
||||
T.check(releasedMask and releasedMask.released, "tilt2765 release frees the overhead mask")
|
||||
T.eq(R.tiltOverheadCanvas, nil, "tilt2765 release drops the overhead mask")
|
||||
T.eq(R.uprightOccluded, false, "tilt2765 release clears premultiplied upright compositing")
|
||||
T.check(pcall(R.releaseCanvases, R), "tilt2765 repeated release is safe")
|
||||
R._tiltOcclusionShader = nil
|
||||
love.graphics.newShader = function() error("unsupported mask shader") end
|
||||
local current = love.graphics.getCanvas()
|
||||
local ok = pcall(R.occludeUprightActors, R, noop)
|
||||
T.check(not ok, "tilt2765 shader compilation failure reaches the display error boundary")
|
||||
T.eq(love.graphics.getCanvas(), current, "failed shader creation does not switch canvas")
|
||||
R._tiltMesh = false
|
||||
T.check(pcall(R.occludeUprightActors, R, noop), "tilt2765 unavailable perspective mesh uses the existing renderer route")
|
||||
Tilt.reset()
|
||||
T.finish("game3_tilt_roof_2765")
|
||||
@@ -0,0 +1,123 @@
|
||||
package.path = "./?.lua;./?/init.lua;" .. package.path
|
||||
|
||||
local T = require("tests.harness")
|
||||
local check, eq = T.check, T.eq
|
||||
|
||||
local function read(path)
|
||||
local file = assert(io.open(path, "rb"))
|
||||
local body = file:read("*a")
|
||||
file:close()
|
||||
return body
|
||||
end
|
||||
|
||||
local main = read("main.lua")
|
||||
local hostSource = read("src/core/HostShell.lua")
|
||||
local quitSource = assert(main:match("\nfunction love%.quit%(%).-\nend\n"))
|
||||
local returnSource = assert(main:match("local function returnToLauncher%([^)]*%).-\nend\n"))
|
||||
local endSource = assert(main:match("local function endProcessOnce%(%).-\nend\n"))
|
||||
local dispatchSource = assert(main:match('( if name == "quit" then.-)\n if WAKE'))
|
||||
|
||||
local function scenario(osName, state, options)
|
||||
options = options or {}
|
||||
local stats = { events = {}, joins = 0, presence = 0, markers = 0,
|
||||
sessions = 0, editor = 0, platform = 0, splash = 0, theme = 0,
|
||||
observe = 0, flush = 0 }
|
||||
local env = setmetatable({ processEnded = false, quitToLauncher = false,
|
||||
launchedIntoGame = options.shortcut or false, editorMode = state == "editor",
|
||||
RELAUNCH_MARKER = "marker" }, { __index = _G })
|
||||
env.os = setmetatable({ getenv = function() return nil end }, { __index = os })
|
||||
env.Game = state == "game" and {} or nil
|
||||
env.Importer = state ~= "game" and { _themeVideo = {
|
||||
release = function() stats.theme = stats.theme + 1 end } } or nil
|
||||
env.launcherSplash = { release = function() stats.splash = stats.splash + 1 end }
|
||||
env.EditorApp = { quit = function() stats.editor = stats.editor + 1 return true end }
|
||||
env.PlatformHooks = { quitToLauncher = function(fn)
|
||||
stats.platform = stats.platform + 1
|
||||
if options.platformVeto then return true end
|
||||
return fn()
|
||||
end }
|
||||
env.love = {
|
||||
system = { getOS = function() return osName end },
|
||||
event = { quit = function(value) stats.events[#stats.events + 1] = value or 0 end },
|
||||
filesystem = { write = function() stats.markers = stats.markers + 1 end },
|
||||
}
|
||||
env.SessionLifecycle = {
|
||||
endProcess = function() stats.joins = stats.joins + 1 end,
|
||||
endGameSession = function() stats.sessions = stats.sessions + 1 end,
|
||||
}
|
||||
local hostChunk = assert(loadstring(hostSource, "@src/core/HostShell.lua"))
|
||||
setfenv(hostChunk, env)
|
||||
local host = hostChunk()
|
||||
env.require = function(name)
|
||||
if name == "src.core.HostShell" then return host end
|
||||
if name == "src.core.SessionLifecycle" then return env.SessionLifecycle end
|
||||
if name == "src.core.DiscordPresence" then
|
||||
return { shutdown = function() stats.presence = stats.presence + 1 end }
|
||||
end
|
||||
if name == "src.core.SaveSerializer" then return { encode = function() return "{}" end } end
|
||||
if name == "src.import.LauncherWindow" then
|
||||
return { observe = function() stats.observe = stats.observe + 1 end,
|
||||
flush = function() stats.flush = stats.flush + 1 end }
|
||||
end
|
||||
error("unexpected module " .. name)
|
||||
end
|
||||
local chunk = assert(loadstring(endSource .. "\n" .. returnSource .. "\n"
|
||||
.. quitSource .. '\nreturn { quit = love.quit, explicitReturn = returnToLauncher, dispatch = function(a)\n'
|
||||
.. 'local name = "quit"\n' .. dispatchSource .. '\nreturn "veto"\nend }', "@main.lua:quit-policy"))
|
||||
setfenv(chunk, env)
|
||||
return chunk(), stats, env
|
||||
end
|
||||
|
||||
for _, state in ipairs({ "game", "launcher", "editor" }) do
|
||||
local callbacks, stats, env = scenario("NX", state, { platformVeto = true })
|
||||
eq(callbacks.dispatch(), 0, "NX " .. state .. " accepts system quit")
|
||||
eq(stats.joins, 1, "NX " .. state .. " tears down workers once")
|
||||
eq(stats.presence, 1, "NX " .. state .. " tears down presence once")
|
||||
eq(#stats.events, 0, "NX " .. state .. " queues no restart")
|
||||
eq(stats.markers, 0, "NX " .. state .. " writes no launcher handoff")
|
||||
eq(stats.platform, 0, "NX " .. state .. " bypasses platform veto")
|
||||
eq(stats.editor, 0, "NX " .. state .. " bypasses editor veto")
|
||||
eq(stats.splash, 1, "NX " .. state .. " releases splash")
|
||||
if state ~= "game" then
|
||||
eq(stats.theme, 1, "NX " .. state .. " releases launcher theme")
|
||||
eq(stats.observe, 1, "NX " .. state .. " observes launcher window")
|
||||
eq(stats.flush, 1, "NX " .. state .. " flushes launcher window")
|
||||
eq(env.Importer._themeVideo, nil, "NX " .. state .. " drops theme reference")
|
||||
end
|
||||
eq(callbacks.dispatch(), 0, "NX " .. state .. " accepts repeated quit")
|
||||
eq(stats.joins, 1, "NX " .. state .. " repeated quit keeps teardown idempotent")
|
||||
end
|
||||
|
||||
local desktop, desktopStats = scenario("OS X", "game")
|
||||
eq(desktop.dispatch(), "veto", "desktop game close returns to launcher")
|
||||
eq(desktopStats.events[1], "restart", "desktop game close requests restart")
|
||||
eq(desktopStats.markers, 1, "desktop game close writes launcher handoff")
|
||||
eq(desktopStats.joins, 1, "desktop game close joins before restart")
|
||||
eq(desktop.dispatch("restart"), "restart", "desktop queued restart is accepted")
|
||||
eq(desktopStats.joins, 1, "desktop queued restart avoids duplicate joins")
|
||||
|
||||
local editor, editorStats = scenario("OS X", "editor")
|
||||
eq(editor.dispatch(), "veto", "desktop editor preserves unsaved-change veto")
|
||||
eq(editorStats.joins, 0, "desktop editor veto leaves workers running")
|
||||
|
||||
local shortcut, shortcutStats = scenario("OS X", "game", { shortcut = true })
|
||||
eq(shortcut.dispatch(), 0, "desktop shortcut exits without returning to launcher")
|
||||
eq(#shortcutStats.events, 0, "desktop shortcut queues no restart")
|
||||
|
||||
local restart, restartStats = scenario("NX", "game")
|
||||
eq(restart.dispatch("restart"), "restart", "NX explicit restart keeps requested native action")
|
||||
eq(restartStats.joins, 1, "NX explicit restart joins workers once")
|
||||
eq(#restartStats.events, 0, "NX explicit restart adds no event")
|
||||
eq(restartStats.markers, 0, "NX explicit restart adds no launcher handoff")
|
||||
|
||||
local inGame, inGameStats = scenario("NX", "game")
|
||||
inGame.explicitReturn()
|
||||
eq(inGameStats.events[1], "restart", "NX in-game EXIT GAME retains explicit restart")
|
||||
eq(inGameStats.markers, 1, "NX in-game EXIT GAME retains launcher handoff")
|
||||
eq(inGameStats.sessions, 1, "NX in-game EXIT GAME ends game session")
|
||||
eq(inGameStats.joins, 1, "NX in-game EXIT GAME joins workers once")
|
||||
eq(inGame.dispatch("restart"), "restart", "NX in-game queued restart retains action")
|
||||
eq(inGameStats.joins, 1, "NX in-game queued restart avoids duplicate joins")
|
||||
check(inGameStats.events[2] == nil, "NX in-game queued restart adds no event")
|
||||
|
||||
T.finish("nx_quit_policy_2720")
|
||||
@@ -217,7 +217,13 @@ check(source("src/net/Fetch.lua"):find("registerProcessShutdown(Fetch.shutdown)"
|
||||
|
||||
local returnBody = mainSrc:match("local function returnToLauncher%(opts%)(.-)\nend\n") or ""
|
||||
check(quitHook:find("returnToLauncher()", 1, true) ~= nil,
|
||||
"love.quit routes a game window close to returnToLauncher on every platform")
|
||||
"love.quit retains the desktop game window returnToLauncher route")
|
||||
local nxAt = quitHook:find('if osName == "NX" then', 1, true)
|
||||
local editorAt = quitHook:find("if editorMode and EditorApp.quit then", 1, true)
|
||||
local platformAt = quitHook:find("PlatformHooks.quitToLauncher", 1, true)
|
||||
check(nxAt ~= nil and editorAt ~= nil and platformAt ~= nil
|
||||
and nxAt < editorAt and nxAt < platformAt,
|
||||
"NX application exit precedes editor and platform quit vetoes")
|
||||
check(quitHook:find("inProcessReturn", 1, true) == nil,
|
||||
"no platform keeps the old in-process launcher swap")
|
||||
local joinAt = returnBody:find("endProcessOnce()", 1, true)
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
package.path = "./?.lua;./?/init.lua;" .. package.path
|
||||
local version = os.getenv("POKEPORT_VERSION") or "firered"
|
||||
require("src.core.GameVersion").set(version)
|
||||
require("src.import.gba.versions").select(version)
|
||||
local Cache = require("tests.game3_cache")
|
||||
if not Cache.mount() then
|
||||
print("[skip] cursor2766 integration: " .. tostring(Cache.reason))
|
||||
os.exit(0)
|
||||
end
|
||||
if version == "ruby" or version == "sapphire" then
|
||||
local Kit = require("src.ui.game3.rse.scene_kit")
|
||||
local cache = require("src.core.game3.dataset").cache()
|
||||
Kit.loadLua = function(path)
|
||||
return assert(loadstring(assert(cache:read(path))))()
|
||||
end
|
||||
require("src.ui.game3.frlg_font").measure = function(text) return #tostring(text) * 6 end
|
||||
end
|
||||
local Bag = require("src.core.game3.bag")
|
||||
local ItemsData = require("src.core.game3.items_data")
|
||||
local Pokemon = require("src.core.game3.pokemon")
|
||||
ItemsData.install(nil)
|
||||
Pokemon.install(nil)
|
||||
local Runtime = { session = nil }
|
||||
function Runtime.getSession() return Runtime.session end
|
||||
package.loaded["src.core.game3.runtime"] = Runtime
|
||||
local Battle = require("src.core.game3.battle")
|
||||
local Ui = require("src.core.game3.battle.ui")
|
||||
local State = require("src.core.game3.battle.state")
|
||||
local Engine = require("src.core.game3.battle.engine")
|
||||
local BagMenu = require("src.ui.game3.bag_menu")
|
||||
local PartyMenu = require("src.ui.game3.party_menu")
|
||||
local function input(key)
|
||||
return { wasPressed = function(_, k) return k == key end }
|
||||
end
|
||||
local function press(key) Ui.handleInput(input(key)) end
|
||||
local function check(ok, label)
|
||||
assert(ok, "FAIL cursor2766 " .. version .. " " .. label)
|
||||
print("PASS cursor2766 " .. version .. " " .. label)
|
||||
end
|
||||
local function mon(species, hp)
|
||||
return { species = species, level = 20, hp = hp or 1000, maxHp = 1000,
|
||||
moves = { 150 }, pp = { 40 } }
|
||||
end
|
||||
local function setup()
|
||||
Battle.abort()
|
||||
BagMenu.close()
|
||||
PartyMenu.close()
|
||||
local session = { version = version, name = "PLAYER", bag = Bag.new(),
|
||||
dex = { seen = {}, owned = {} }, party = { mon(1, 5), mon(4), mon(7) } }
|
||||
Bag.add(session.bag, 13, 3)
|
||||
Bag.add(session.bag, 4, 3)
|
||||
Runtime.session = session
|
||||
assert(Battle.start({ session = session, headless = true, autoFight = false,
|
||||
playerParty = session.party, foe = mon(150), wild = true }))
|
||||
for _ = 1, 50 do
|
||||
Battle.update(1 / 60, nil)
|
||||
if Ui._mode == "menu" then break end
|
||||
end
|
||||
assert(Ui._mode == "menu")
|
||||
return session, Battle.getState()
|
||||
end
|
||||
local function openBag()
|
||||
press("right")
|
||||
assert(Ui._menuIndex == 2)
|
||||
press("a")
|
||||
BagMenu.settle()
|
||||
assert(BagMenu.open)
|
||||
end
|
||||
local function choose(item)
|
||||
for pocket = 1, #ItemsData.BAG_POCKET_ORDER do
|
||||
BagMenu.pocketIdx = pocket
|
||||
for row, entry in ipairs(BagMenu.list(BagMenu.currentPocket())) do
|
||||
if ItemsData.toNumericId(entry.id) == item then
|
||||
BagMenu.cursor, BagMenu.scroll, BagMenu.mode = row, 0, "list"
|
||||
BagMenu.handleInput(input("a"))
|
||||
BagMenu.actionCursor = 1
|
||||
BagMenu.handleInput(input("a"))
|
||||
BagMenu.settle()
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
error("missing item " .. item)
|
||||
end
|
||||
local function nextTurn(st)
|
||||
local turn = st.turn
|
||||
Battle._headless = false
|
||||
for _ = 1, 2000 do
|
||||
Battle.update(1 / 60, { input = input(nil) })
|
||||
if Battle._phase == "command" and Ui._mode == "menu" and st.turn > turn then
|
||||
Battle._headless = true
|
||||
return
|
||||
end
|
||||
assert(not st.over, "battle ended")
|
||||
end
|
||||
error("next turn unavailable " .. tostring(Battle._phase))
|
||||
end
|
||||
local session, st = setup()
|
||||
openBag()
|
||||
BagMenu.handleInput(input("b"))
|
||||
BagMenu.settle()
|
||||
check(Ui._mode == "menu" and Ui._menuIndex == 2, "Bag_cancel_retains_Bag")
|
||||
session, st = setup()
|
||||
openBag()
|
||||
choose(13)
|
||||
assert(PartyMenu.open)
|
||||
PartyMenu.cursor = 1
|
||||
for _ = 1, 100 do
|
||||
if not PartyMenu.open then break end
|
||||
PartyMenu.update(1 / 60)
|
||||
PartyMenu.handleInput(input("a"))
|
||||
end
|
||||
BagMenu.settle()
|
||||
assert(Ui._pendingCommand and Ui._pendingCommand.usedInMenu)
|
||||
local old = st.player
|
||||
nextTurn(st)
|
||||
check(Bag.get(session.bag, 13) == 2 and st.player == old and Ui._menuIndex == 2,
|
||||
"Potion_real_callback_next_turn_Bag")
|
||||
press("a")
|
||||
BagMenu.settle()
|
||||
check(BagMenu.open and Ui._mode == "bag", "Potion_next_A_reopens_Bag")
|
||||
BagMenu.close()
|
||||
session, st = setup()
|
||||
openBag()
|
||||
choose(4)
|
||||
assert(Ui._pendingCommand and Ui._pendingCommand.itemId == 4)
|
||||
local Catching = require("src.core.game3.battle.catching")
|
||||
local tryCatch = Catching.tryCatch
|
||||
Catching.tryCatch = function() return false, 0 end
|
||||
nextTurn(st)
|
||||
Catching.tryCatch = tryCatch
|
||||
check(Bag.get(session.bag, 4) == 2 and Ui._menuIndex == 2,
|
||||
"failed_Ball_real_callback_next_turn_Bag")
|
||||
press("a")
|
||||
BagMenu.settle()
|
||||
check(BagMenu.open and Ui._mode == "bag", "failed_Ball_next_A_reopens_Bag")
|
||||
BagMenu.close()
|
||||
press("left")
|
||||
press("down")
|
||||
press("a")
|
||||
assert(PartyMenu.open)
|
||||
for _ = 1, 60 do PartyMenu.update(1 / 60) end
|
||||
PartyMenu.handleInput(input("b"))
|
||||
check(Ui._mode == "menu" and Ui._menuIndex == 3, "Pokemon_cancel_retains_Pokemon")
|
||||
old = st.player
|
||||
assert(Engine.performSwitch(st, Battle._adapter, 0, 2, { reason = "switch" }))
|
||||
Ui.openMenu()
|
||||
check(st.player ~= old and Ui._menuIndex == 1, "real_single_switch_resets_Fight")
|
||||
session, st = setup()
|
||||
st.double = true
|
||||
st.absent = {}
|
||||
st.battlers = { [0] = st.player, [1] = st.enemy,
|
||||
[2] = State.makeBattler(st.playerParty[2], "player", { partyIndex = 2, id = 2 }),
|
||||
[3] = State.makeBattler(st.enemy.mon, "enemy", { partyIndex = 1, id = 3 }) }
|
||||
Ui.openMenu(0)
|
||||
openBag()
|
||||
choose(4)
|
||||
local command = Ui.takeCommand()
|
||||
check(command and command.battler == 0, "double_Bag_callback_owns_slot0")
|
||||
Ui.openMenu(2, { partnerAction = command })
|
||||
press("down")
|
||||
Ui.openMenu(0)
|
||||
check(Ui._menuIndex == 2, "double_slot0_retains_Bag")
|
||||
assert(Engine.performSwitch(st, Battle._adapter, 2, 3, { reason = "switch" }))
|
||||
Ui.openMenu(2)
|
||||
check(Ui._menuIndex == 1, "real_double_slot2_switch_resets_Fight")
|
||||
Ui.openMenu(0)
|
||||
check(Ui._menuIndex == 2, "double_slot2_switch_preserves_slot0_Bag")
|
||||
assert(Engine.performSwitch(st, Battle._adapter, 0, 2, { reason = "switch" }))
|
||||
Ui.openMenu(0)
|
||||
check(Ui._menuIndex == 1, "real_double_slot0_switch_resets_Fight")
|
||||
Ui.reset({ headless = true })
|
||||
check(next(Ui._actionCursor) == nil and next(Ui._actionCursorBattler) == nil,
|
||||
"new_battle_clears_cursor_ownership")
|
||||
Battle.abort()
|
||||
print("PASS cursor2766_cache_integration " .. version)
|
||||
@@ -26,6 +26,22 @@ package.loaded["src.core.game3.field_effects"] = {
|
||||
startRockSmash = function(_, _, _, done) calls.rock = calls.rock + 1; done() end,
|
||||
startFlash = function(done) calls.flash = calls.flash + 1; done() end,
|
||||
}
|
||||
local scriptVm = { ctx = {} }
|
||||
package.loaded["src.core.game3.scripting.space"] = {
|
||||
vm = scriptVm,
|
||||
scriptKey = function(name)
|
||||
T.eq(name, "EventScript_UseFlash", "ordinary Emerald Flash resolves its profile ROM label")
|
||||
return "fixture_flash"
|
||||
end,
|
||||
startScript = function(key)
|
||||
T.eq(key, "fixture_flash", "ordinary Emerald Flash dispatches the resolved cached script")
|
||||
T.check(scriptVm.ctx.fieldControlsLocked, "ordinary Emerald Flash gives the VM the field lock")
|
||||
calls.flash, calls.flashScript = calls.flash + 1, key
|
||||
scriptVm.ctx.fieldControlsLocked = nil
|
||||
package.loaded["src.core.game3.field"].unlock()
|
||||
return true
|
||||
end,
|
||||
}
|
||||
|
||||
local GameVersion = require("src.core.GameVersion")
|
||||
GameVersion.set("emerald")
|
||||
@@ -82,6 +98,8 @@ local function testCase(case)
|
||||
T.eq(calls.puzzle, shouldOpen and 1 or 0, case.name .. " calls the door effect exactly when valid")
|
||||
T.eq(calls.rock, not case.puzzle and not steel and 1 or 0, case.name .. " preserves normal Rock Smash")
|
||||
T.eq(calls.flash, not case.puzzle and steel and 1 or 0, case.name .. " preserves normal Flash")
|
||||
T.eq(calls.flashScript, not case.puzzle and steel and session.version == "emerald" and "fixture_flash" or nil,
|
||||
case.name .. " dispatches the Emerald cached Flash script only for ordinary Emerald Flash")
|
||||
T.eq(Field._locks.default, nil, case.name .. " releases only the move lock")
|
||||
T.eq(Field.locked, case.namedLock == true, case.name .. " preserves named lock ownership")
|
||||
if shouldOpen then
|
||||
|
||||
@@ -223,8 +223,9 @@ local rc, curFrames = walkAndSettle("right")
|
||||
eq(rc, "step", "the surfer paddles one cell east into the current")
|
||||
eq(Player.cellX, 16, "the eastward current runs them to the corner")
|
||||
eq(Player.cellY, 1, "the northward current then lifts them up the column")
|
||||
-- pokefirered/src/field_player_avatar.c:509
|
||||
-- pokefirered/src/event_object_movement.c:8925 sStepTimes
|
||||
eq(curFrames, 16 + 10 * 6, "one paddle plus ten current pushes")
|
||||
eq(curFrames, 8 + 10 * 6, "one paddle plus ten current pushes")
|
||||
eq(ForcedMovement.forced, false, "leaving the current clears the forced flag")
|
||||
|
||||
print("[test] 8. a forced step rolls no encounter and burns no step counter")
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
package.path = "./?.lua;./?/init.lua;" .. package.path
|
||||
love = require("tests.love_stub")
|
||||
local version = os.getenv("POKEPORT_VERSION") or "sapphire"
|
||||
require("src.core.GameVersion").set(version)
|
||||
require("src.import.gba.versions").select(version)
|
||||
local Cache = require("tests.game3_cache")
|
||||
local root = Cache.mount()
|
||||
if not root then
|
||||
print("[skip] flash2762 cache integration: " .. tostring(Cache.reason))
|
||||
os.exit(0)
|
||||
end
|
||||
local Dataset = require("src.core.game3.dataset")
|
||||
local Profile = require("src.core.game3.profile").of(version)
|
||||
local Space = require("src.core.game3.scripting.space")
|
||||
local Extract = require("src.import.gba.extract_scripts")
|
||||
Space.bundle = Extract.loadBundle(Dataset.cache(), root, { allowIncomplete = true })
|
||||
if Profile.field and Profile.field.flashScript then
|
||||
assert(Space.installLabels(Space.bundle, Dataset.cache(), root))
|
||||
end
|
||||
local session = { version = version, flags = {}, vars = {} }
|
||||
package.loaded["src.core.game3.runtime"] = { getSession = function() return session end }
|
||||
local showDone
|
||||
package.loaded["src.core.game3.field_move_show_mon"] = { start = function(_, _, fn) showDone = fn end }
|
||||
local Flags = require("src.core.game3.scripting.flags")
|
||||
local Moves = require("src.core.game3.field_moves")
|
||||
local Field = require("src.core.game3.field")
|
||||
local View = require("src.core.game3.field_view")
|
||||
local Fx = require("src.core.game3.field_effects")
|
||||
local Text = require("src.core.game3.rom_text")
|
||||
Space.store = Flags.newStore({ flags = session.flags })
|
||||
session.flags = Space.store.flags
|
||||
Space.vm = require("src.core.game3.scripting.vm").new({ scripts = Space.bundle.scripts, store = Space.store })
|
||||
Field._session = session
|
||||
Fx._cache = Dataset.cache()
|
||||
local function check(ok, label)
|
||||
assert(ok, "FAIL flash2762 " .. version .. " " .. label)
|
||||
print("PASS flash2762 " .. version .. " " .. label)
|
||||
end
|
||||
Flags.setFlag(Space.store, nil, Moves.badgeFlag("FLASH"), true)
|
||||
local ctx = { session = session, store = Space.store, isCave = true }
|
||||
local payload = Moves.flashFromMenu(ctx)
|
||||
check(payload.ok, "first_use_available")
|
||||
View.setFlashLevel(4)
|
||||
Fx._anims = {}
|
||||
Field.executeFieldMove(payload)
|
||||
showDone()
|
||||
check(Field.locked and Flags.getFlag(Space.store, nil, payload.flag), "animation_locked_and_active")
|
||||
for _ = 1, 600 do Fx.step(); Space.vm:tick() end
|
||||
local rse = Profile.family == "rse"
|
||||
check(View.getFlashLevel() == (rse and 1 or 0), "first_use_cart_level")
|
||||
check(View.flashRadius() == (rse and 72 or nil), "first_use_cart_radius")
|
||||
check(not Field.locked and not Space.vm:isRunning(), "animation_end_unlocks_field")
|
||||
local key = (version == "ruby" or version == "sapphire") and "OtherText_CantUseThatHere" or "gText_InUseAlready_PM"
|
||||
local expected = Text.ascii(key)
|
||||
local res = Moves.flashFromMenu(ctx)
|
||||
check(not res.ok and res.text == expected, "repeated_flash_exact_cached_prompt")
|
||||
local game = { session = session, data = { maps = Dataset.buildMaps() } }
|
||||
local floors = 0
|
||||
for mapId, def in pairs(game.data.maps) do
|
||||
if tonumber(def.cave) == 1 then
|
||||
Field.clearTempFieldEventData(game, mapId)
|
||||
View.setDefaultFlashLevel(game, mapId)
|
||||
check(Flags.getFlag(Space.store, nil, payload.flag) and View.getFlashLevel() == (rse and 1 or 0),
|
||||
"dark_floor_persists_" .. mapId)
|
||||
floors = floors + 1
|
||||
if floors == 2 then break end
|
||||
end
|
||||
end
|
||||
check(floors == 2, "two_actual_cached_dark_floors")
|
||||
print("PASS flash2762_cache_integration " .. version)
|
||||
@@ -113,6 +113,10 @@ tests/engine/fly_map_arrows_bug1892.lua
|
||||
tests/engine/formulas.lua
|
||||
tests/engine/frame_cap_display.lua
|
||||
tests/engine/frame_cap_legacy_loop_2106.lua
|
||||
tests/engine/game3_avatar_restore_2759_2761.lua
|
||||
tests/engine/game3_battle_action_cursor_2766.lua
|
||||
tests/engine/game3_cutscene_push_2764.lua
|
||||
tests/engine/game3_disguise_order_2763.lua
|
||||
tests/engine/game3_em_bike_doors_2637.lua
|
||||
tests/engine/game3_em_catch_prompt_2637.lua
|
||||
tests/engine/game3_frontier_obedience_2638.lua
|
||||
@@ -120,11 +124,14 @@ tests/engine/game3_post_dex_background_2637.lua
|
||||
tests/engine/game3_rs_anim_names_test.lua
|
||||
tests/engine/game3_rs_bag_cursor_width_2709.lua
|
||||
tests/engine/game3_rs_bag_row_layout_2727.lua
|
||||
tests/engine/game3_rs_flash_2762.lua
|
||||
tests/engine/game3_rs_item_use_text_2699_test.lua
|
||||
tests/engine/game3_rs_starter_choose_scale_2703.lua
|
||||
tests/engine/game3_rs_summary_memo_location_2707.lua
|
||||
tests/engine/game3_speed_hotkey_test.lua
|
||||
tests/engine/game3_surf_speed_2767.lua
|
||||
tests/engine/game3_tent_save_reload_2638.lua
|
||||
tests/engine/game3_tilt_roof_2765.lua
|
||||
tests/engine/game3_wailmer_pail_2697_test.lua
|
||||
tests/engine/game_display_chord_test.lua
|
||||
tests/engine/game_speed_categories_test.lua
|
||||
@@ -512,6 +519,7 @@ tests/engine/npc_stay_turns_bug2117.lua
|
||||
tests/engine/npc_walk_cadence_bug1303.lua
|
||||
tests/engine/nurse_bow_bug995.lua
|
||||
tests/engine/nx_display_test.lua
|
||||
tests/engine/nx_quit_policy_2720.lua
|
||||
tests/engine/nx_yellow_boot_test.lua
|
||||
tests/engine/oak_intro_cry_bug2481.lua
|
||||
tests/engine/oak_shrink_timing_2685.lua
|
||||
@@ -754,6 +762,7 @@ tests/engine/yellow_oak_back_pikapic.lua
|
||||
tests/engine/yellow_viridian_old_man_bug2122.lua
|
||||
tests/engine/zero_damage_miss_ruleset_bug2153.lua
|
||||
tests/game3_back_pic_anims_2702_test.lua
|
||||
tests/game3_battle_action_cursor_2766_test.lua
|
||||
tests/game3_battle_anims_phase1_test.lua
|
||||
tests/game3_battle_rendering_test.lua
|
||||
tests/game3_battle_shiny_test.lua
|
||||
@@ -810,6 +819,7 @@ tests/game3_quest_log_integration_test.lua
|
||||
tests/game3_reflection_neighbor_test.lua
|
||||
tests/game3_renewable_hidden_items_test.lua
|
||||
tests/game3_resident_caches_2695_test.lua
|
||||
tests/game3_rs_flash_2762_test.lua
|
||||
tests/game3_rs_naming_kb_crop_2701_test.lua
|
||||
tests/game3_rs_pokedex_palette_2710_test.lua
|
||||
tests/game3_runtime_camera_object_test.lua
|
||||
|
||||
Reference in New Issue
Block a user