mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-10-26 14:55:36 -04:00
Merge pull request #2816 from bryanthaboi/dev
This commit is contained in:
@@ -11,8 +11,12 @@
|
||||
-- a lab loss (OaksLabRivalEndBattleScript), and Route 22's first battle
|
||||
-- upgrades FLAREON back to JOLTEON (Route22Rival1AfterBattleScript).
|
||||
|
||||
local Sound = require("src.core.Sound")
|
||||
|
||||
local OAK1 = 3
|
||||
local RIVAL = 1
|
||||
-- pokeyellow engine/overworld/movement.asm:871
|
||||
local FAST = { stepFrames = 16 }
|
||||
|
||||
-- engine/overworld/pathfinding.asm:36-70 (FindPathToPlayer): step whichever
|
||||
local function findPathRows(rows, objIndex, sx, sy, tx, ty)
|
||||
@@ -338,17 +342,20 @@ return {
|
||||
table.insert(rows, { "move_npc", RIVAL, side, 1 })
|
||||
table.insert(rows, { "move_npc", RIVAL, "down", 1 })
|
||||
table.insert(rows, { "face_player_dir", side })
|
||||
table.insert(rows, { "move_npc", RIVAL, "down", 1 })
|
||||
-- pokeyellow scripts/OaksLab.asm:431
|
||||
table.insert(rows, { "walk_npc", RIVAL, { "down" }, FAST })
|
||||
table.insert(rows, { "face_player_dir", "down" })
|
||||
table.insert(rows, { "move_npc", RIVAL, "down", 4 })
|
||||
table.insert(rows, { "walk_npc", RIVAL, { "down", "down", "down", "down" }, FAST })
|
||||
table.insert(rows, { "hide_object", "OAKS_LAB", "OAKSLAB_RIVAL" })
|
||||
table.insert(rows, { "play_music", "Music_OaksLab" })
|
||||
-- OaksLabPikachuEscapesPokeballScript: the follower reaches the map (#1009)
|
||||
table.insert(rows, { "face_player_dir", "up" })
|
||||
table.insert(rows, { "set_field", "pikachuInBall", false })
|
||||
-- pokeyellow scripts/OaksLab.asm:1094
|
||||
table.insert(rows, { "show_text", "_OaksLabPikachuDislikesPokeballsText1", false,
|
||||
{ preSound = function() return Sound.playPikaCry(game.data, 2) end } })
|
||||
-- pokeyellow scripts/OaksLab.asm:478
|
||||
table.insert(rows, { "spawn_pikachu_follower" })
|
||||
table.insert(rows, { "play_cry", "PIKACHU" })
|
||||
table.insert(rows, { "show_text", "_OaksLabPikachuDislikesPokeballsText1" })
|
||||
table.insert(rows, { "show_text", "_OaksLabPikachuDislikesPokeballsText2" })
|
||||
ow.runner:run(rows, { npc = rival })
|
||||
return true
|
||||
|
||||
@@ -484,6 +484,9 @@ function BattleBridge.start(mod, game, foe, opts)
|
||||
if okF and Fade and Fade.begin and not opts.headless and opts.fade ~= false then
|
||||
Fade.begin(Fade.MODE.FROM_BLACK, 1)
|
||||
end
|
||||
-- pokeemerald/src/field_player_avatar.c:883
|
||||
local FieldFx = package.loaded["src.core.game3.field_effects"]
|
||||
if FieldFx and FieldFx.resetSurfBob then FieldFx.resetSurfBob() end
|
||||
-- pokefirered/src/battle_setup.c:432
|
||||
local okS, Space = pcall(require, "src.core.game3.scripting.space")
|
||||
if okS and Space and Space.returnToField then
|
||||
|
||||
@@ -156,7 +156,7 @@ function Dive.useDive(slot, mon)
|
||||
Field.lock()
|
||||
t.state = 1
|
||||
-- pokeemerald/src/field_effect.c:1924
|
||||
lazyReq("src.core.game3.field_move_show_mon").start(t.mon, { pose = true }, function() t.state = 2 end)
|
||||
lazyReq("src.core.game3.field_move_show_mon").start(t.mon, { pose = false }, function() t.state = 2 end)
|
||||
return false
|
||||
elseif t.state == 1 then
|
||||
Field.lock()
|
||||
|
||||
@@ -234,6 +234,7 @@ function Field.update(_dt)
|
||||
if NativesEvents and NativesEvents.pollWalkaway then
|
||||
NativesEvents.pollWalkaway(Space and Space.vm, input)
|
||||
end
|
||||
lazyReq("src.core.game3.field_effects").stepSurfBob()
|
||||
Player.update(game, walkInput)
|
||||
lazyReq("src.world.game3.Follower").update(game)
|
||||
Field.updateWaterfall(game)
|
||||
|
||||
@@ -18,7 +18,8 @@ FieldEffects._sheets = {} -- [name] = { image = ..., quads = ..., fw = ..., fh =
|
||||
FieldEffects._fx = nil -- tall grass
|
||||
FieldEffects._anims = {} -- transient active field animations
|
||||
FieldEffects._ground = nil -- pokefirered/src/event_object_movement.c:8721
|
||||
FieldEffects._surfClock = 0
|
||||
FieldEffects._surfBob = nil
|
||||
FieldEffects._underwaterBob = nil
|
||||
FieldEffects._logged = false
|
||||
-- pokefirered/src/scrcmd.c:2051 — gFieldEffectArguments, written by
|
||||
-- setfieldeffectargument and read by the effect that dofieldeffect starts.
|
||||
@@ -404,7 +405,8 @@ function FieldEffects.install(cache)
|
||||
FieldEffects._fx = nil
|
||||
FieldEffects._anims = {}
|
||||
FieldEffects._ground = nil
|
||||
FieldEffects._surfClock = 0
|
||||
FieldEffects._surfBob = nil
|
||||
FieldEffects._underwaterBob = nil
|
||||
FieldEffects._logged = false
|
||||
local FieldView = modFieldView()
|
||||
if FieldView and FieldView.setCameraPanning then
|
||||
@@ -1643,6 +1645,62 @@ function FieldEffects.groundEffects()
|
||||
end
|
||||
end
|
||||
|
||||
-- pokeemerald/src/field_effect_helpers.c:1052 UpdateSurfBlobFieldEffect
|
||||
function FieldEffects.stepSurfBob()
|
||||
local SurfBob = lazyReq("src.core.game3.surf_bob")
|
||||
local P = package.loaded["src.core.game3.player"]
|
||||
if P and P.underwater then
|
||||
FieldEffects._surfBob = nil
|
||||
-- pokeemerald/src/field_player_avatar.c:893
|
||||
FieldEffects._underwaterBob = FieldEffects._underwaterBob or SurfBob.newUnderwater()
|
||||
SurfBob.tickUnderwater(FieldEffects._underwaterBob)
|
||||
return
|
||||
end
|
||||
FieldEffects._underwaterBob = nil
|
||||
if not (P and (P.surfing or P.surfHopping or P.dismounting)) then
|
||||
FieldEffects._surfBob = nil
|
||||
return
|
||||
end
|
||||
local b = FieldEffects._surfBob
|
||||
if not b then
|
||||
b = SurfBob.newBlob(rse() and "rse" or "frlg")
|
||||
FieldEffects._surfBob = b
|
||||
end
|
||||
SurfBob.syncAnim(b, P.facing or "down")
|
||||
local x, y = P.cellX, P.cellY
|
||||
if P.moving then x, y = P.targetX or x, P.targetY or y end
|
||||
local Collision = package.loaded["src.core.game3.collision"]
|
||||
SurfBob.syncPosition(b, x, y, Collision and Collision.elevationAt)
|
||||
local state = SurfBob.BOB_PLAYER_AND_MON
|
||||
if P.surfHopping then
|
||||
state = SurfBob.BOB_NONE
|
||||
elseif P.dismounting then
|
||||
-- pokeemerald/src/field_player_avatar.c:1671
|
||||
state = SurfBob.BOB_JUST_MON
|
||||
end
|
||||
SurfBob.tick(b, state)
|
||||
end
|
||||
|
||||
-- pokeemerald/src/field_player_avatar.c:883
|
||||
function FieldEffects.resetSurfBob()
|
||||
FieldEffects._surfBob = nil
|
||||
FieldEffects._underwaterBob = nil
|
||||
end
|
||||
|
||||
function FieldEffects.surfBlobY2()
|
||||
local b = FieldEffects._surfBob
|
||||
return b and b.y2 or 0
|
||||
end
|
||||
|
||||
function FieldEffects.surfPlayerY2()
|
||||
local P = package.loaded["src.core.game3.player"]
|
||||
if P and P.underwater then
|
||||
local u = FieldEffects._underwaterBob
|
||||
return u and u.y2 or 0
|
||||
end
|
||||
return lazyReq("src.core.game3.surf_bob").playerY2(FieldEffects._surfBob)
|
||||
end
|
||||
|
||||
-- ---------------------------------------------------------------- Step & Update
|
||||
function FieldEffects.step()
|
||||
FieldEffects.groundEffects()
|
||||
@@ -1679,9 +1737,6 @@ function FieldEffects.step()
|
||||
end
|
||||
end
|
||||
|
||||
-- Surfing blob clock (48 ticks per frame * 2 frames = 96 ticks per loop)
|
||||
FieldEffects._surfClock = (FieldEffects._surfClock + 1) % 96
|
||||
|
||||
-- Update active transient animations
|
||||
local active = {}
|
||||
for _, anim in ipairs(FieldEffects._anims) do
|
||||
@@ -1949,7 +2004,9 @@ function FieldEffects.drawBehind(camX, camY)
|
||||
local surfSheet = load_sheet("surf_blob", 32, 32, 6)
|
||||
if surfSheet then
|
||||
local facing = P.facing or "down"
|
||||
local step = math.floor(FieldEffects._surfClock / 48) % 2
|
||||
local b = FieldEffects._surfBob
|
||||
local step = b and lazyReq("src.core.game3.surf_bob").animCmdIndex(b) or 0
|
||||
local bob = b and b.y2 or 0
|
||||
local frameIdx = 0
|
||||
local flip = false
|
||||
|
||||
@@ -1975,15 +2032,13 @@ function FieldEffects.drawBehind(camX, camY)
|
||||
if q then
|
||||
local sx, sy
|
||||
if P.surfHopping then
|
||||
-- Player is hopping onto water: blob is in position on the destination tile
|
||||
sx = (P.targetX or P.cellX) * CELL - camX - 8
|
||||
sy = (P.targetY or P.cellY) * CELL - camY - 8
|
||||
sy = (P.targetY or P.cellY) * CELL - camY - 8 + bob
|
||||
elseif P.dismounting then
|
||||
-- Player is hopping off water to land: blob remains at origin tile
|
||||
sx = P.cellX * CELL - camX - 8
|
||||
sy = P.cellY * CELL - camY - 8
|
||||
sy = P.cellY * CELL - camY - 8 + bob
|
||||
else
|
||||
local bob = (not P.moving and not P.jumping) and ((step == 1) and -1 or 0) or 0
|
||||
-- pokeemerald/src/field_effect_helpers.c:1131
|
||||
sx = P.px - camX - 8
|
||||
sy = P.py - camY - 8 + bob
|
||||
end
|
||||
|
||||
@@ -498,6 +498,9 @@ function Map.load(mod, game, mapId, opts)
|
||||
-- pokeemerald/src/overworld.c:2170
|
||||
local CamObj = package.loaded["src.core.game3.camera_object"]
|
||||
if CamObj and CamObj.reset then CamObj.reset() end
|
||||
-- pokeemerald/src/field_player_avatar.c:883
|
||||
local FieldFx = package.loaded["src.core.game3.field_effects"]
|
||||
if FieldFx and FieldFx.resetSurfBob then FieldFx.resetSurfBob() end
|
||||
end
|
||||
-- pret RestartWildEncounterImmunitySteps on LoadMap / LoadMapFromWarp: every
|
||||
-- map entry restarts the wild encounter grace period. Unconditional, so the
|
||||
@@ -726,15 +729,15 @@ function Map.load(mod, game, mapId, opts)
|
||||
-- pokeemerald/src/overworld.c:801
|
||||
require("src.core.game3.rse.rematch").tryUpdateRandomTrainerRematchesForMap(session, mapId)
|
||||
end
|
||||
-- pokeemerald/src/overworld.c:802
|
||||
if session and require("src.core.game3.rtc").enabled(session) then
|
||||
require("src.core.game3.time_events").run(session)
|
||||
end
|
||||
if opts.keepScript and Space and Space.retarget then
|
||||
Space.retarget(mod or Runtime._mod, mapId, game, world)
|
||||
elseif Space and Space.activate then
|
||||
Space.activate(mod or Runtime._mod, mapId, game, world)
|
||||
end
|
||||
-- pokeemerald/src/overworld.c:802
|
||||
if session and require("src.core.game3.rtc").enabled(session) then
|
||||
require("src.core.game3.time_events").run(session)
|
||||
end
|
||||
|
||||
-- pokefirered/src/overworld.c:805
|
||||
local savedFlash = session and tonumber(session.flashLevel)
|
||||
|
||||
@@ -318,6 +318,12 @@ local function walkInPlaceFrames()
|
||||
return Player.walkInPlaceFast and RUN_FRAMES or WALK_FRAMES
|
||||
end
|
||||
|
||||
local function surfBobY()
|
||||
if not (Player.surfing or Player.underwater) then return nil end
|
||||
local FieldEffects = package.loaded["src.core.game3.field_effects"]
|
||||
return FieldEffects and FieldEffects.surfPlayerY2 and FieldEffects.surfPlayerY2() or 0
|
||||
end
|
||||
|
||||
-- pokefirered/src/field_effect.c:1215 FallWarpEffect_4
|
||||
local function warp_owns_sprite()
|
||||
local Warp = package.loaded["src.core.game3.warp"]
|
||||
@@ -398,7 +404,7 @@ local function beginStep(tx, ty, run, ledge)
|
||||
Player.targetY = ty
|
||||
Player.running = (not ledge) and run and true or false
|
||||
Player.jumping = ledge and true or false
|
||||
Player.spriteYOffset = 0
|
||||
Player.spriteYOffset = (not ledge) and surfBobY() or 0
|
||||
if Player.surfHopping or Player.dismounting then
|
||||
-- pokeemerald/src/event_object_movement.c:8495 DoJumpSpecialSpriteMovement
|
||||
Player.stepFrames = JUMP_FRAMES
|
||||
@@ -1205,10 +1211,9 @@ function Player.tick(game)
|
||||
end
|
||||
end
|
||||
if Player.action then return tickAction() end
|
||||
if Player.surfing and not Player.jumping then
|
||||
local okFx, FieldEffects = pcall(lazyReq, "src.core.game3.field_effects")
|
||||
local clock = (okFx and FieldEffects and FieldEffects._surfClock) or 0
|
||||
Player.spriteYOffset = (math.floor(clock / 48) % 2 == 1) and -1 or 0
|
||||
local bobY = (not Player.jumping) and surfBobY() or nil
|
||||
if bobY then
|
||||
Player.spriteYOffset = bobY
|
||||
elseif not Player.walkInPlace and not warp_owns_sprite() then
|
||||
Player.spriteYOffset = 0
|
||||
end
|
||||
@@ -1227,7 +1232,7 @@ function Player.tick(game)
|
||||
if Player.jumping then
|
||||
Player.spriteYOffset = Player.jumpSpriteY()
|
||||
else
|
||||
Player.spriteYOffset = 0
|
||||
Player.spriteYOffset = surfBobY() or 0
|
||||
end
|
||||
-- pokefirered/src/data/object_events/object_event_anims.h:556
|
||||
if Player.spinning then
|
||||
@@ -1237,6 +1242,8 @@ function Player.tick(game)
|
||||
end
|
||||
if Player.progress >= frames then
|
||||
finishStep(game)
|
||||
local bobY = (not Player.moving) and surfBobY() or nil
|
||||
if bobY then Player.spriteYOffset = bobY end
|
||||
return true
|
||||
end
|
||||
return false
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
-- pokeemerald/src/field_effect_helpers.c:1052 UpdateSurfBlobFieldEffect
|
||||
|
||||
local SurfBob = {}
|
||||
|
||||
-- pokeemerald/include/field_effect_helpers.h:7
|
||||
SurfBob.BOB_NONE = 0
|
||||
SurfBob.BOB_PLAYER_AND_MON = 1
|
||||
SurfBob.BOB_JUST_MON = 2
|
||||
|
||||
SurfBob.FAMILY = {
|
||||
-- pokeemerald/src/field_effect_helpers.c:1111
|
||||
rse = { velocity = -1, intervals = { 3, 7 }, flipMask = 15, animBump = false },
|
||||
-- pokefirered/src/field_effect_helpers.c:1049
|
||||
frlg = { velocity = 0, intervals = { 7, 15 }, flipMask = 31, animBump = true },
|
||||
}
|
||||
|
||||
-- pokeemerald/include/global.fieldmap.h:18
|
||||
local ELEVATION_DEFAULT = 3
|
||||
|
||||
-- pokefirered/src/data/field_effects/field_effect_objects.h:184
|
||||
SurfBob.FRLG_FRAME_DURATION = 48
|
||||
|
||||
local DIRS = { { 0, 1 }, { 0, -1 }, { -1, 0 }, { 1, 0 } }
|
||||
|
||||
-- pokeemerald/src/field_effect_helpers.c:999 FldEff_SurfBlob
|
||||
function SurfBob.newBlob(family)
|
||||
local p = SurfBob.FAMILY[family] or SurfBob.FAMILY.frlg
|
||||
return {
|
||||
p = p, velocity = p.velocity, timer = 0, y2 = 0, intervalIdx = 1,
|
||||
prevX = -1, prevY = -1, dir = nil, animClock = 0, state = SurfBob.BOB_NONE,
|
||||
}
|
||||
end
|
||||
|
||||
-- pokefirered/src/field_effect_helpers.c:1009 SynchroniseSurfAnim
|
||||
function SurfBob.syncAnim(b, dir)
|
||||
if b.dir ~= dir then
|
||||
b.dir = dir
|
||||
b.animClock = 0
|
||||
else
|
||||
b.animClock = b.animClock + 1
|
||||
end
|
||||
end
|
||||
|
||||
function SurfBob.animCmdIndex(b)
|
||||
return math.floor(b.animClock / SurfBob.FRLG_FRAME_DURATION) % 2
|
||||
end
|
||||
|
||||
-- pokeemerald/src/field_effect_helpers.c:1081 SynchronizeSurfPosition
|
||||
function SurfBob.syncPosition(b, x, y, elevAt)
|
||||
if b.y2 ~= 0 or (x == b.prevX and y == b.prevY) then return end
|
||||
b.intervalIdx = 1
|
||||
b.prevX, b.prevY = x, y
|
||||
if not elevAt then return end
|
||||
for _, d in ipairs(DIRS) do
|
||||
if elevAt(x + d[1], y + d[2]) == ELEVATION_DEFAULT then
|
||||
b.intervalIdx = 2
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- pokeemerald/src/field_effect_helpers.c:1107 UpdateBobbingEffect
|
||||
function SurfBob.tick(b, state)
|
||||
b.state = state
|
||||
if state == SurfBob.BOB_NONE then return end
|
||||
b.timer = (b.timer + 1) % 65536
|
||||
if b.timer % (b.p.intervals[b.intervalIdx] + 1) == 0 then
|
||||
b.y2 = b.y2 + b.velocity
|
||||
end
|
||||
if b.timer % (b.p.flipMask + 1) == 0 then
|
||||
b.velocity = -b.velocity
|
||||
end
|
||||
end
|
||||
|
||||
-- pokefirered/src/field_effect_helpers.c:1061
|
||||
function SurfBob.playerY2(b)
|
||||
if not b or b.state ~= SurfBob.BOB_PLAYER_AND_MON then return 0 end
|
||||
local y2 = b.y2
|
||||
if b.p.animBump and SurfBob.animCmdIndex(b) ~= 0 then y2 = y2 + 1 end
|
||||
return y2
|
||||
end
|
||||
|
||||
-- pokeemerald/src/field_effect_helpers.c:1150 StartUnderwaterSurfBlobBobbing
|
||||
function SurfBob.newUnderwater()
|
||||
return { bobY = 1, timer = 0, y2 = 0 }
|
||||
end
|
||||
|
||||
-- pokeemerald/src/field_effect_helpers.c:1164 SpriteCB_UnderwaterSurfBlob
|
||||
function SurfBob.tickUnderwater(u)
|
||||
local t = u.timer
|
||||
u.timer = (t + 1) % 65536
|
||||
if t % 4 == 0 then u.y2 = u.y2 + u.bobY end
|
||||
if u.timer % 16 == 0 then u.bobY = -u.bobY end
|
||||
end
|
||||
|
||||
return SurfBob
|
||||
@@ -57,13 +57,12 @@ local function storeOf(session, opts)
|
||||
local Runtime = package.loaded["src.core.game3.runtime"]
|
||||
local live = Runtime and Runtime.getSession and Runtime.getSession() or nil
|
||||
if Space and Space.store and (live == nil or live == session) then return Space.store end
|
||||
if type(session) == "table" then
|
||||
session.store = session.store or { flags = {}, vars = {} }
|
||||
if type(session) == "table" and type(session.store) == "table" then
|
||||
session.store.flags = session.store.flags or {}
|
||||
session.store.vars = session.store.vars or {}
|
||||
return session.store
|
||||
end
|
||||
return nil
|
||||
return { flags = {}, vars = {} }
|
||||
end
|
||||
|
||||
local function flags()
|
||||
|
||||
@@ -10,16 +10,20 @@ function P.generate(session, version)
|
||||
local Space = package.loaded["src.core.game3.scripting.space"]
|
||||
local Runtime = package.loaded["src.core.game3.runtime"]
|
||||
local current = Runtime and Runtime.getSession and Runtime.getSession()
|
||||
local live = s.store == nil and (current == nil or current == s) and Space and Space.getStore and Space.getStore() or nil
|
||||
local store, stats = s.store or live or s, s.gameStats or {}
|
||||
local Flags = require("src.core.game3.scripting.flags")
|
||||
local live = (current == nil or current == s) and Space and Space.getStore and Space.getStore() or nil
|
||||
local sources = {}
|
||||
for _, src in ipairs({live or false, s.store or false, s}) do
|
||||
if src and type(src.flags) == "table" then sources[#sources + 1] = src end
|
||||
end
|
||||
local store, stats = sources[1] or s, s.gameStats or {}
|
||||
local tower = s.battleTower or {}
|
||||
local function flag(name)
|
||||
local id, flags = C:require("flags", name), store.flags or {}
|
||||
local v = flags[id]
|
||||
if v == nil then v = flags[tostring(id)] end
|
||||
if v == nil then v = flags[string.format("0x%X", id)] end
|
||||
if v == nil then v = flags[name] end
|
||||
return v == true or v == 1
|
||||
local id = C:require("flags", name)
|
||||
for _, src in ipairs(sources) do
|
||||
if Flags.getFlag(src, nil, id) then return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
-- save_menu_util.c:118
|
||||
local _, caught = Policy.counts(s, Dex.nationalEnabled({version = version, dex = s.dex, flags = store.flags, vars = store.vars}))
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
local U = require("tests.drivers.util")
|
||||
local DIR = os.getenv("POKEPORT_SHOT_DIR") or "/tmp/em_surf_bob_dive_2813"
|
||||
|
||||
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") .. " em_surf_bob_dive_2813 failures=" .. failures)
|
||||
love.event.quit(failures == 0 and 0 or 1)
|
||||
end
|
||||
|
||||
return function(game)
|
||||
for _ = 1, 900 do
|
||||
if game.phase == "boot" and game.boot then break end
|
||||
U.wait(1)
|
||||
end
|
||||
if not check(game.boot ~= nil, "boot reached") then return finish() end
|
||||
game:_handleBootAction({ action = "new_game", name = "BRENDAN", gender = 0 })
|
||||
U.wait(60)
|
||||
|
||||
local Runtime = require("src.core.game3.runtime")
|
||||
local Map = require("src.core.game3.map")
|
||||
local Player = require("src.core.game3.player")
|
||||
local Warp = require("src.core.game3.warp")
|
||||
local Message = require("src.ui.game3.message")
|
||||
local Choice = require("src.ui.game3.choice")
|
||||
local Party = require("src.core.game3.party")
|
||||
local Space = require("src.core.game3.scripting.space")
|
||||
local Flags = require("src.core.game3.scripting.flags")
|
||||
local ShowMon = require("src.core.game3.field_move_show_mon")
|
||||
local FieldEffects = require("src.core.game3.field_effects")
|
||||
local C = require("src.core.game3.constants").of("emerald")
|
||||
local session = Runtime.getSession()
|
||||
if not check(session ~= nil, "field session exists") then return finish() end
|
||||
local IDS = Flags.forVersion("emerald").IDS
|
||||
|
||||
session.party = {}
|
||||
Party.giveMon(session, C.species.byName.SPECIES_WAILMER, 40, "WAILMER")
|
||||
local m = session.party[1]
|
||||
m.moves = { C.moves.byName.MOVE_DIVE, C.moves.byName.MOVE_SURF, C.moves.byName.MOVE_WATER_GUN, C.moves.byName.MOVE_REST }
|
||||
m.pp = { 10, 15, 25, 10 }
|
||||
Flags.setFlag(Space.store, nil, IDS.FLAG_BADGE05_GET, true)
|
||||
Flags.setFlag(Space.store, nil, IDS.FLAG_BADGE07_GET, true)
|
||||
|
||||
local function mapNow() local s = Runtime.getSession(); return s and s.map end
|
||||
|
||||
local function place(mapId, x, y, facing, surfing)
|
||||
Map.load(nil, game, mapId, { x = x, y = y, facing = facing })
|
||||
local s = Runtime.getSession()
|
||||
s.x, s.y, s.facing = x, y, facing
|
||||
Player.surfing = surfing == true
|
||||
require("src.core.game3.field").unlock()
|
||||
U.wait(20)
|
||||
end
|
||||
|
||||
local function sample(n, shots)
|
||||
local ys, blobs, lo, hi, seenAt = {}, {}, 99, -99, {}
|
||||
for i = 1, n do
|
||||
U.wait(1)
|
||||
local y = Player.spriteYOffset or 0
|
||||
ys[i], blobs[i] = y, FieldEffects.surfBlobY2()
|
||||
lo, hi = math.min(lo, y), math.max(hi, y)
|
||||
if shots and shots[y] and not seenAt[y] then
|
||||
seenAt[y] = true
|
||||
U.still(game, DIR .. "/" .. shots[y])
|
||||
end
|
||||
end
|
||||
return ys, blobs, lo, hi
|
||||
end
|
||||
|
||||
place("EM_ROUTE124", 10, 3, "down", true)
|
||||
U.wait(40)
|
||||
local ys, blobs, lo, hi = sample(64, { [0] = "2813_01_surf_bob_rest.png", [-4] = "2813_02_surf_bob_peak.png" })
|
||||
print("[driver] surf y2: " .. table.concat(ys, ","))
|
||||
check(lo == -4 and hi == 0, string.format("surfing bob spans 0..-4 (got %d..%d)", hi, lo))
|
||||
local synced = true
|
||||
for i = 1, #ys do if ys[i] ~= blobs[i] then synced = false end end
|
||||
check(synced, "player sprite and surf blob share y2 every frame")
|
||||
local runs, last, len = {}, nil, 0
|
||||
for i = 1, #ys do
|
||||
if ys[i] == last then len = len + 1 else if last then runs[#runs + 1] = len end last, len = ys[i], 1 end
|
||||
end
|
||||
local steady = #runs >= 4
|
||||
for i = 2, #runs do if runs[i] ~= 4 then steady = false end end
|
||||
check(steady, "bob steps one pixel every 4 frames")
|
||||
|
||||
local movingYs = {}
|
||||
for _ = 1, 40 do
|
||||
table.insert(game.input.pressQueue, "left")
|
||||
game.input.state.left = true
|
||||
U.wait(1)
|
||||
if Player.moving then movingYs[#movingYs + 1] = Player.spriteYOffset or 0 end
|
||||
end
|
||||
game.input.state.left = false
|
||||
local movedBob, smooth = false, true
|
||||
for i, y in ipairs(movingYs) do
|
||||
if y ~= 0 then movedBob = true end
|
||||
if i > 1 and math.abs(y - movingYs[i - 1]) > 1 then smooth = false end
|
||||
end
|
||||
check(#movingYs > 0 and movedBob, "bob keeps running while swimming (" .. table.concat(movingYs, ",") .. ")")
|
||||
check(smooth, "bob never snaps back to 0 between swim steps")
|
||||
for _ = 1, 40 do if not Player.moving then break end U.wait(1) end
|
||||
|
||||
place("EM_ROUTE124", 10, 3, "down", true)
|
||||
U.wait(10)
|
||||
U.tap(game, "a")
|
||||
local posed, cutin, shotCut = false, false, false
|
||||
for _ = 1, 1500 do
|
||||
if mapNow() == "EM_UNDERWATER_ROUTE124" and not Warp.isBusy() then break end
|
||||
if (Player.fieldMoveAnim or 0) > 0 then posed = true end
|
||||
if ShowMon.isActive() then
|
||||
if not cutin then U.still(game, DIR .. "/2813_03_dive_start_no_pose.png") end
|
||||
cutin = true
|
||||
if not shotCut and ShowMon._fx and ShowMon._fx.sprite and ShowMon._fx.sprite.state == "wait" then
|
||||
shotCut = true
|
||||
U.still(game, DIR .. "/2813_04_dive_cutin.png")
|
||||
end
|
||||
end
|
||||
if Choice.isOpen and Choice.isOpen() then
|
||||
U.tap(game, "a")
|
||||
elseif Message.isOpen and Message.isOpen() then
|
||||
U.tap(game, "a")
|
||||
U.wait(3)
|
||||
else
|
||||
U.wait(1)
|
||||
end
|
||||
end
|
||||
check(cutin, "Dive shows the field move mon")
|
||||
check(not posed, "Dive never puts the player in the field move pose")
|
||||
check(mapNow() == "EM_UNDERWATER_ROUTE124", "dive warps underwater (" .. tostring(mapNow()) .. ")")
|
||||
U.wait(30)
|
||||
check(Player.underwater == true, "avatar is underwater")
|
||||
|
||||
ys, _, lo, hi = sample(64, { [0] = "2813_05_underwater_bob_rest.png", [4] = "2813_06_underwater_bob_low.png" })
|
||||
print("[driver] underwater y2: " .. table.concat(ys, ","))
|
||||
check(lo == 0 and hi == 4, string.format("underwater bob spans 0..+4 (got %d..%d)", lo, hi))
|
||||
|
||||
finish()
|
||||
end
|
||||
@@ -0,0 +1,111 @@
|
||||
local U = require("tests.drivers.util")
|
||||
local DIR = os.getenv("POKEPORT_SHOT_DIR") or "/tmp/em_surf_bob_reload_fish_2813"
|
||||
|
||||
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") .. " em_surf_bob_reload_fish_2813 failures=" .. failures)
|
||||
love.event.quit(failures == 0 and 0 or 1)
|
||||
end
|
||||
|
||||
return function(game)
|
||||
for _ = 1, 900 do
|
||||
if game.phase == "boot" and game.boot then break end
|
||||
U.wait(1)
|
||||
end
|
||||
if not check(game.boot ~= nil, "boot reached") then return finish() end
|
||||
game:_handleBootAction({ action = "new_game", name = "BRENDAN", gender = 0 })
|
||||
U.wait(60)
|
||||
|
||||
local Runtime = require("src.core.game3.runtime")
|
||||
local Map = require("src.core.game3.map")
|
||||
local Player = require("src.core.game3.player")
|
||||
local Field = require("src.core.game3.field")
|
||||
local Party = require("src.core.game3.party")
|
||||
local Battle = require("src.core.game3.battle")
|
||||
local BattleBridge = require("src.core.game3.battle_bridge")
|
||||
local Message = require("src.ui.game3.message")
|
||||
local FieldEffects = require("src.core.game3.field_effects")
|
||||
local C = require("src.core.game3.constants").of("emerald")
|
||||
local session = Runtime.getSession()
|
||||
if not check(session ~= nil, "field session exists") then return finish() end
|
||||
|
||||
session.party = {}
|
||||
Party.giveMon(session, C.species.byName.SPECIES_WAILMER, 40, "WAILMER")
|
||||
|
||||
local function place(x, y, facing)
|
||||
Map.load(nil, game, "EM_ROUTE124", { x = x, y = y, facing = facing })
|
||||
local s = Runtime.getSession()
|
||||
s.x, s.y, s.facing = x, y, facing
|
||||
Player.surfing = true
|
||||
Field.unlock()
|
||||
end
|
||||
local function timer()
|
||||
local b = FieldEffects._surfBob
|
||||
return b and b.timer or -1
|
||||
end
|
||||
|
||||
place(10, 3, "down")
|
||||
U.wait(70)
|
||||
local before = timer()
|
||||
place(10, 3, "down")
|
||||
U.wait(10)
|
||||
local after = timer()
|
||||
print(string.format("[driver] blob timer before reload %d, 10 frames after %d", before, after))
|
||||
check(before > 60 and after >= 0 and after <= 11, "a warp load starts a fresh surf blob")
|
||||
|
||||
U.wait(50)
|
||||
before = timer()
|
||||
local ok, err = BattleBridge.startWild(Runtime._mod, game,
|
||||
{ species = C.species.byName.SPECIES_TENTACOOL, level = 5 }, {})
|
||||
check(ok == true, "wild battle started " .. tostring(err or ""))
|
||||
for _ = 1, 600 do
|
||||
if Battle.isActive() then break end
|
||||
U.wait(1)
|
||||
end
|
||||
U.wait(30)
|
||||
Battle.abort("run")
|
||||
local returned
|
||||
for i = 1, 900 do
|
||||
if not Battle.isActive() then returned = i break end
|
||||
U.wait(1)
|
||||
end
|
||||
U.wait(10)
|
||||
after = timer()
|
||||
print(string.format("[driver] blob timer before battle %d, 10 frames after return %d", before, after))
|
||||
check(returned ~= nil and Player.surfing, "back on the water after the battle")
|
||||
check(after >= 0 and after <= 11, "returning from battle starts a fresh surf blob")
|
||||
|
||||
for _ = 1, 60 do
|
||||
if not (Message.isOpen and Message.isOpen()) then break end
|
||||
U.tap(game, "a")
|
||||
U.wait(3)
|
||||
end
|
||||
place(10, 3, "down")
|
||||
U.wait(40)
|
||||
check(Field.startFishing(1), "fishing starts while surfing")
|
||||
local synced, offsets, blobYs, shot = true, {}, {}, false
|
||||
for _ = 1, 90 do
|
||||
U.wait(1)
|
||||
local _, _, fy = Field.fishingPose()
|
||||
local blob = FieldEffects.surfBlobY2()
|
||||
offsets[fy or 0] = true
|
||||
blobYs[blob] = true
|
||||
if (Player.spriteYOffset or 0) ~= blob then synced = false end
|
||||
if fy and fy ~= 0 and not shot then
|
||||
shot = true
|
||||
U.still(game, DIR .. "/2813_07_fishing_on_water.png")
|
||||
end
|
||||
end
|
||||
local nBlob = 0
|
||||
for _ in pairs(blobYs) do nBlob = nBlob + 1 end
|
||||
check(nBlob > 1, "surf blob keeps bobbing while the rod is out")
|
||||
check(offsets[8] or offsets[-8], "the rod pose offset fired during the cast")
|
||||
check(synced, "player y2 is the rod offset plus the blob y2 every frame")
|
||||
finish()
|
||||
end
|
||||
@@ -0,0 +1,78 @@
|
||||
local U = require("tests.drivers.util")
|
||||
|
||||
local MARKER = "drv2814_seeded"
|
||||
local failures = 0
|
||||
local function check(ok, label)
|
||||
print((ok and "PASS " or "FAIL ") .. "card2814 " .. label)
|
||||
if not ok then failures = failures + 1 end
|
||||
return ok
|
||||
end
|
||||
local function finish()
|
||||
print((failures == 0 and "PASS " or "FAIL ") .. "card2814 failures=" .. failures)
|
||||
love.event.quit(failures == 0 and 0 or 1)
|
||||
end
|
||||
|
||||
local function cardChecks(session, tag)
|
||||
local Space = require("src.core.game3.scripting.space")
|
||||
local Flags = require("src.core.game3.scripting.flags")
|
||||
local C = require("src.core.game3.constants").of(session.version)
|
||||
local dex, b1 = C:require("flags", "FLAG_SYS_POKEDEX_GET"), C:require("flags", "FLAG_BADGE01_GET")
|
||||
check(session.store == nil, tag .. "_no_fabricated_session_store")
|
||||
check(Flags.getFlag(Space.store, nil, dex) and Flags.getFlag(Space.store, nil, b1) and Flags.getFlag(Space.store, nil, b1 + 1),
|
||||
tag .. "_live_store_has_dex_and_badges")
|
||||
if session.version == "ruby" or session.version == "sapphire" then
|
||||
local card = require("src.ui.game3.rs.trainer_card_policy").generate(session)
|
||||
check(card.hasPokedex == true, tag .. "_card_hasPokedex")
|
||||
check(card.badges[1] == true and card.badges[2] == true and card.badges[3] == false, tag .. "_card_badges_1_2")
|
||||
end
|
||||
end
|
||||
|
||||
local function bootReady(game)
|
||||
for _ = 1, 600 do
|
||||
if game.phase == "boot" and game.boot then break end
|
||||
U.wait(1)
|
||||
end
|
||||
return check(game.phase == "boot" and game.boot ~= nil, "boot_ready")
|
||||
end
|
||||
|
||||
local function seed(game)
|
||||
game:_handleBootAction({ action = "new_game", name = "TAI" })
|
||||
U.wait(180)
|
||||
local session = require("src.core.game3.runtime").getSession()
|
||||
if not check(session ~= nil, "seed_field_session_ready") then return end
|
||||
local Space = require("src.core.game3.scripting.space")
|
||||
local Flags = require("src.core.game3.scripting.flags")
|
||||
local C = require("src.core.game3.constants").of(session.version)
|
||||
Flags.setFlag(Space.store, nil, C:require("flags", "FLAG_SYS_POKEDEX_GET"), true)
|
||||
Flags.setFlag(Space.store, nil, C:require("flags", "FLAG_SYS_CLOCK_SET"), true)
|
||||
for i = 0, 1 do Flags.setFlag(Space.store, nil, C:require("flags", "FLAG_BADGE01_GET") + i, true) end
|
||||
cardChecks(session, "seed")
|
||||
if check(game:saveGame() ~= false, "seed_save_written") then
|
||||
love.filesystem.write(MARKER, "1")
|
||||
print("card2814 seeded; run again to continue")
|
||||
end
|
||||
end
|
||||
|
||||
local function continueAndOpen(game)
|
||||
game:_handleBootAction({ action = "continue" })
|
||||
U.wait(180)
|
||||
local session = require("src.core.game3.runtime").getSession()
|
||||
if not check(session ~= nil, "continue_field_session_ready") then return end
|
||||
cardChecks(session, "continue")
|
||||
local Card = require("src.ui.game3.screens").get("trainer_card", session)
|
||||
Card.show({ session = session })
|
||||
U.wait(120)
|
||||
local shots = os.getenv("POKEPORT_SHOT_DIR")
|
||||
if shots then U.still(game, shots .. "/2814_card_front_" .. tostring(session.version) .. ".png") end
|
||||
love.filesystem.remove(MARKER)
|
||||
end
|
||||
|
||||
return function(game)
|
||||
if not bootReady(game) then return finish() end
|
||||
if love.filesystem.getInfo(MARKER) then
|
||||
continueAndOpen(game)
|
||||
else
|
||||
seed(game)
|
||||
end
|
||||
finish()
|
||||
end
|
||||
@@ -0,0 +1,145 @@
|
||||
-- pokeyellow scripts/OaksLab.asm:403-516
|
||||
return function(game)
|
||||
local U = dofile("tests/drivers/util.lua")
|
||||
local GameVersion = require("src.core.GameVersion")
|
||||
local Commands = require("src.script.Commands")
|
||||
local PF = require("src.world.PikachuFollower")
|
||||
local Pokemon = require("src.pokemon.Pokemon")
|
||||
local Sound = require("src.core.Sound")
|
||||
local TextBox = require("src.render.TextBox")
|
||||
|
||||
local SHOT_DIR = os.getenv("POKEPORT_SHOT_DIR") or "/tmp/shots"
|
||||
local LAB = "OAKS_LAB"
|
||||
local RIVAL = 1
|
||||
local failed = false
|
||||
|
||||
local function check(label, ok)
|
||||
U.log(ok and "PASS" or "FAIL", label)
|
||||
if not ok then failed = true end
|
||||
return ok
|
||||
end
|
||||
local function finish()
|
||||
love.event.quit(failed and 1 or 0)
|
||||
while true do coroutine.yield() end
|
||||
end
|
||||
local function ow() return game.overworld end
|
||||
local function ctx()
|
||||
return { game = game, save = game.save, overworld = game.overworld }
|
||||
end
|
||||
|
||||
if not check("running the Yellow cache", GameVersion.isYellow()) then finish() end
|
||||
|
||||
local boxText = setmetatable({}, { __mode = "k" })
|
||||
local newBox = TextBox.new
|
||||
TextBox.new = function(g, text, onDone, opts)
|
||||
local box = newBox(g, text, onDone, opts)
|
||||
boxText[box] = type(text) == "string" and text or ""
|
||||
return box
|
||||
end
|
||||
local pikaCries = {}
|
||||
local playPika = Sound.playPikaCry
|
||||
Sound.playPikaCry = function(data, n)
|
||||
pikaCries[#pikaCries + 1] = n
|
||||
return playPika(data, n)
|
||||
end
|
||||
local plainCries = {}
|
||||
local playCry = Sound.playCry
|
||||
Sound.playCry = function(data, species, ...)
|
||||
plainCries[#plainCries + 1] = species
|
||||
return playCry(data, species, ...)
|
||||
end
|
||||
|
||||
local flags = game.save.flags or {}
|
||||
game.save.flags = flags
|
||||
flags.EVENT_GOT_STARTER = true
|
||||
flags.EVENT_CHOSE_PIKACHU = true
|
||||
flags.EVENT_FOLLOWED_OAK_INTO_LAB = true
|
||||
flags.EVENT_FOLLOWED_OAK_INTO_LAB_2 = true
|
||||
flags.EVENT_BATTLED_RIVAL_IN_OAKS_LAB = nil
|
||||
flags.EVENT_OAK_ASKED_TO_CHOOSE_MON = true
|
||||
game.save.party = { Pokemon.new(game.data, "PIKACHU", 40) }
|
||||
game.save.party[1].moves = { { id = "THUNDERBOLT", pp = 15 } }
|
||||
game.save.onBike = false
|
||||
game.save.pikachuInBall = true
|
||||
|
||||
U.teleport(game, LAB, 5, 5, "down")
|
||||
U.wait(10)
|
||||
Commands.show_object(ctx(), LAB, "OAKSLAB_OAK1")
|
||||
Commands.show_object(ctx(), LAB, "OAKSLAB_RIVAL")
|
||||
U.wait(5)
|
||||
U.hold(game, "down", 20)
|
||||
game.input.state.down = false
|
||||
|
||||
local whatBox, rivalSteps, lastCell, lastStep = nil, {}, nil, nil
|
||||
local exiting = false
|
||||
for _ = 1, 6000 do
|
||||
local top = game.stack:top()
|
||||
local text = boxText[top]
|
||||
if text and text:find("What%?") and not text:find("look") then
|
||||
whatBox = top
|
||||
break
|
||||
end
|
||||
local o = game.overworld
|
||||
if top ~= o then
|
||||
if text and text:find("Smell") and not exiting then
|
||||
exiting = true
|
||||
plainCries, pikaCries = {}, {}
|
||||
end
|
||||
U.tap(game, "a")
|
||||
U.wait(2)
|
||||
else
|
||||
local rival = o:npcByIndex(RIVAL)
|
||||
if exiting and rival then
|
||||
local cell = rival.cellX .. "," .. rival.cellY
|
||||
if cell ~= lastCell then
|
||||
if lastCell then rivalSteps[#rivalSteps + 1] = lastStep end
|
||||
lastCell = cell
|
||||
end
|
||||
if rival.moving then lastStep = rival.stepFrames or 32 end
|
||||
end
|
||||
U.wait(1)
|
||||
end
|
||||
end
|
||||
|
||||
U.log("rival exit step lengths:", table.concat(rivalSteps, " "))
|
||||
check("rival walked side, down, then the $04 steps", #rivalSteps >= 6)
|
||||
local fastTail = #rivalSteps >= 6
|
||||
for i = 3, #rivalSteps do
|
||||
if not rivalSteps[i] or rivalSteps[i] > 16 then fastTail = false end
|
||||
end
|
||||
check("the last five rival steps run at double speed", fastTail)
|
||||
check("the first two rival steps are normal speed",
|
||||
rivalSteps[1] and rivalSteps[1] > 16 and rivalSteps[2] and rivalSteps[2] > 16)
|
||||
|
||||
if not check("the OAK: What? box came up", whatBox ~= nil) then finish() end
|
||||
check("no follower on the map while What? is typing", PF.current(ow()) == nil)
|
||||
|
||||
U.wait(240)
|
||||
check("What? is still on screen 240 frames later with no input",
|
||||
game.stack:top() == whatBox)
|
||||
check("still no follower before the box is closed", PF.current(ow()) == nil)
|
||||
check("PikachuCry2 played for the What? line",
|
||||
#pikaCries == 1 and pikaCries[1] == 2)
|
||||
local defaultPika = false
|
||||
for _, s in ipairs(plainCries) do
|
||||
if s == "PIKACHU" then defaultPika = true end
|
||||
end
|
||||
check("no default PIKACHU cry played in the escape scene", not defaultPika)
|
||||
U.still(game, SHOT_DIR .. "/2815_01_what_waits_no_pikachu.png")
|
||||
|
||||
U.tap(game, "a")
|
||||
local text2
|
||||
for _ = 1, 300 do
|
||||
local top = game.stack:top()
|
||||
if top ~= whatBox and boxText[top] and boxText[top]:find("look") then
|
||||
text2 = top
|
||||
break
|
||||
end
|
||||
U.wait(1)
|
||||
end
|
||||
check("Would you look at that! follows the A press", text2 ~= nil)
|
||||
check("the follower is on the map once What? closes", PF.current(ow()) ~= nil)
|
||||
U.wait(30)
|
||||
U.still(game, SHOT_DIR .. "/2815_02_look_at_that_pikachu_out.png")
|
||||
finish()
|
||||
end
|
||||
@@ -78,6 +78,11 @@ local function checkExit(rows, from, side, totalDowns, turnAfterDowns)
|
||||
while rows[j] and rows[j][1] ~= "hide_object" do
|
||||
local r = rows[j]
|
||||
if isMove(r, "down") then downs = downs + r[4] end
|
||||
if r[1] == "walk_npc" and r[2] == 1 then
|
||||
for _, d in ipairs(r[3]) do
|
||||
if d == "down" then downs = downs + 1 end
|
||||
end
|
||||
end
|
||||
if r[1] == "face_player_dir" and r[2] == side then sideTurnAt = downs end
|
||||
if r[1] == "face_player_dir" and r[2] == "down" then downTurnAt = downs end
|
||||
j = j + 1
|
||||
|
||||
@@ -51,7 +51,16 @@ for _, version in ipairs({ "ruby", "sapphire" }) do
|
||||
card = Policy.generate(stale)
|
||||
check(card.hasPokedex == true, version .. " live script store: pokedex row shown")
|
||||
check(owned(card) == "1,2,3,4,5,6,7,8", version .. " live script store: all badges (" .. owned(card) .. ")")
|
||||
|
||||
local withEmpty = { version = version, name = "MAY", flags = saved.flags, vars = saved.vars, store = { flags = {}, vars = {} } }
|
||||
card = Policy.generate(withEmpty)
|
||||
check(card.hasPokedex == true, version .. " empty session.store + live store: pokedex row shown")
|
||||
check(owned(card) == "1,2,3,4,5,6,7,8", version .. " empty session.store + live store: live badges (" .. owned(card) .. ")")
|
||||
package.loaded["src.core.game3.scripting.space"] = nil
|
||||
|
||||
card = Policy.generate(withEmpty)
|
||||
check(card.hasPokedex == true, version .. " empty session.store, no live store: session.flags pokedex")
|
||||
check(owned(card) == "1,4", version .. " empty session.store, no live store: session.flags badges 1,4 (" .. owned(card) .. ")")
|
||||
end
|
||||
|
||||
if failed > 0 then
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
package.path = "./?.lua;./?/init.lua;" .. package.path
|
||||
|
||||
local T = require("tests.harness")
|
||||
local check, eq = T.check, T.eq
|
||||
|
||||
local GameVersion = require("src.core.GameVersion")
|
||||
GameVersion.set("emerald")
|
||||
|
||||
local function seq(t)
|
||||
local out = {}
|
||||
for _, run in ipairs(t) do
|
||||
for _ = 1, run[2] do out[#out + 1] = run[1] end
|
||||
end
|
||||
return out
|
||||
end
|
||||
|
||||
local function same(got, want, label)
|
||||
local ok = #got >= #want
|
||||
for i = 1, #want do
|
||||
if got[i] ~= want[i] then ok = false break end
|
||||
end
|
||||
check(ok, label .. " (got " .. table.concat(got, ",", 1, math.min(#got, #want)) .. ")")
|
||||
end
|
||||
|
||||
do
|
||||
local Show = { calls = {} }
|
||||
local tasks = {}
|
||||
package.loaded["src.core.game3.field"] = {
|
||||
lock = function() end, unlock = function() end, holdInput = function() end,
|
||||
}
|
||||
package.loaded["src.core.game3.task"] = { spawn = function(fn) tasks[#tasks + 1] = fn end }
|
||||
package.loaded["src.core.game3.field_move_show_mon"] = {
|
||||
start = function(mon, opts, cb) Show.calls[#Show.calls + 1] = { mon = mon, opts = opts, cb = cb } end,
|
||||
}
|
||||
local Dive = require("src.core.game3.dive")
|
||||
Dive.useDive(0, { species = 320 })
|
||||
check(#tasks == 1, "FldEff_UseDive spawns its task")
|
||||
tasks[1]({ frames = 0 })
|
||||
eq(#Show.calls, 1, "Dive starts the show-mon cut-in")
|
||||
local opts = Show.calls[1] and Show.calls[1].opts or {}
|
||||
check(opts.pose ~= true, "Dive skips the player field-move pose (field_effect.c:1924)")
|
||||
Dive.reset()
|
||||
package.loaded["src.core.game3.field"] = nil
|
||||
package.loaded["src.core.game3.task"] = nil
|
||||
package.loaded["src.core.game3.field_move_show_mon"] = nil
|
||||
package.loaded["src.core.game3.dive"] = nil
|
||||
end
|
||||
|
||||
local FE = require("src.core.game3.field_effects")
|
||||
local P = require("src.core.game3.player")
|
||||
local function blobY2() return FE.surfBlobY2 and FE.surfBlobY2() end
|
||||
|
||||
local function resetPlayer()
|
||||
P.surfing, P.underwater, P.surfHopping, P.dismounting = false, false, false, false
|
||||
P.moving, P.jumping, P.action, P.walkInPlace = false, false, nil, false
|
||||
P.cellX, P.cellY, P.px, P.py, P.facing = 5, 5, 80, 80, "down"
|
||||
P.spriteYOffset = 0
|
||||
FE._surfBob, FE._underwaterBob = nil, nil
|
||||
end
|
||||
|
||||
local function run(n, each)
|
||||
local ys = {}
|
||||
for i = 1, n do
|
||||
if each then each(i) end
|
||||
;(FE.stepSurfBob or FE.step)()
|
||||
P.tick(nil)
|
||||
ys[i] = P.spriteYOffset
|
||||
end
|
||||
return ys
|
||||
end
|
||||
|
||||
local RSE_SURF = seq({ { 0, 3 }, { -1, 4 }, { -2, 4 }, { -3, 4 }, { -4, 4 }, { -3, 4 }, { -2, 4 }, { -1, 4 }, { 0, 4 }, { -1, 4 } })
|
||||
|
||||
FE._manifest = { family = "rse" }
|
||||
resetPlayer()
|
||||
P.surfing = true
|
||||
same(run(#RSE_SURF), RSE_SURF, "RSE surf: player bobs 0..-4 on 4-frame steps (field_effect_helpers.c:1107)")
|
||||
eq(blobY2(), P.spriteYOffset, "RSE surf: blob and player share y2")
|
||||
|
||||
resetPlayer()
|
||||
P.surfing = true
|
||||
P.moving, P.targetX, P.targetY, P.progress, P.stepFrames = true, 5, 6, 0, 1000
|
||||
local moving = run(16)
|
||||
eq(moving[16], -4, "RSE surf: bob keeps running while the player swims")
|
||||
P.moving = false
|
||||
|
||||
resetPlayer()
|
||||
P.surfHopping = true
|
||||
P.moving, P.jumping, P.targetX, P.targetY, P.progress, P.stepFrames = true, true, 5, 6, 0, 1000
|
||||
run(20)
|
||||
eq(blobY2(), 0, "RSE surf hop: blob holds still until the hop ends (field_effect.c:3068)")
|
||||
|
||||
local Collision = require("src.core.game3.collision")
|
||||
local realElev = Collision.elevationAt
|
||||
Collision.elevationAt = function(x, y) return (x == 5 and y == 4) and 3 or 1 end
|
||||
resetPlayer()
|
||||
P.surfing = true
|
||||
local nearLand = seq({ { 0, 7 }, { -1, 8 }, { -2, 8 }, { -1, 8 }, { 0, 8 } })
|
||||
same(run(#nearLand), nearLand, "RSE surf next to land bobs every 8th frame (field_effect_helpers.c:1097)")
|
||||
Collision.elevationAt = realElev
|
||||
|
||||
resetPlayer()
|
||||
P.underwater = true
|
||||
local UNDER = seq({ { 1, 4 }, { 2, 4 }, { 3, 4 }, { 4, 4 }, { 3, 4 }, { 2, 4 }, { 1, 4 }, { 0, 4 }, { 1, 4 } })
|
||||
same(run(#UNDER), UNDER, "underwater: player sprite bobs 0..+4 (field_effect_helpers.c:1164)")
|
||||
|
||||
FE._manifest = false
|
||||
resetPlayer()
|
||||
P.surfing = true
|
||||
local frlg = run(150)
|
||||
check(frlg[1] == 0 and frlg[48] == 0, "FRLG surf: player rests on blob frame 0")
|
||||
check(frlg[49] == 1 and frlg[96] == 1, "FRLG surf: player drops 1px on blob frame 1 (pokefirered field_effect_helpers.c:1069)")
|
||||
eq(frlg[97], 0, "FRLG surf: 96-frame blob cycle")
|
||||
eq(frlg[150], 1, "FRLG surf: second cycle on frame 1")
|
||||
eq(blobY2(), 0, "FRLG surf: blob never moves")
|
||||
P.facing = "left"
|
||||
eq(run(1)[1], 0, "FRLG surf: turning restarts the blob anim (pokefirered field_effect_helpers.c:1020)")
|
||||
|
||||
resetPlayer()
|
||||
FE._manifest = nil
|
||||
T.finish()
|
||||
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env luajit
|
||||
|
||||
package.path = "./?.lua;./?/init.lua;" .. package.path
|
||||
|
||||
local failed = 0
|
||||
local function check(cond, msg)
|
||||
if cond then
|
||||
print("[ok] " .. msg)
|
||||
else
|
||||
failed = failed + 1
|
||||
print("[FAIL] " .. msg)
|
||||
end
|
||||
end
|
||||
|
||||
package.loaded["src.core.game3.scripting.space"] = { store = nil, getStore = function() return nil end }
|
||||
local Flags = require("src.core.game3.scripting.flags")
|
||||
local TimeEvents = require("src.core.game3.time_events")
|
||||
|
||||
for _, version in ipairs({ "ruby", "sapphire", "emerald" }) do
|
||||
local session = { version = version, name = "MAY", flags = {}, vars = {} }
|
||||
TimeEvents.run(session)
|
||||
check(session.store == nil, version .. " run without live store leaves session.store nil")
|
||||
TimeEvents.init(session)
|
||||
check(session.store == nil, version .. " init without live store leaves session.store nil")
|
||||
|
||||
local live = Flags.newStore()
|
||||
package.loaded["src.core.game3.scripting.space"] = { store = live, getStore = function() return live end }
|
||||
local s2 = { version = version, name = "MAY", flags = {}, vars = {} }
|
||||
TimeEvents.run(s2)
|
||||
check(s2.store == nil, version .. " run with live store leaves session.store nil")
|
||||
package.loaded["src.core.game3.scripting.space"] = { store = nil, getStore = function() return nil end }
|
||||
|
||||
local own = { flags = {}, vars = {} }
|
||||
local s3 = { version = version, name = "MAY", flags = {}, vars = {}, store = own }
|
||||
TimeEvents.run(s3)
|
||||
check(s3.store == own, version .. " existing session.store is kept")
|
||||
end
|
||||
|
||||
if failed > 0 then
|
||||
print(string.format("%d failure(s)", failed))
|
||||
os.exit(1)
|
||||
end
|
||||
print("all ok")
|
||||
Reference in New Issue
Block a user