mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-10-07 17:31:35 -04:00
further performance udpdates
This commit is contained in:
@@ -15,6 +15,7 @@ local MapIds = require("src.core.game3.map_ids")
|
||||
local Profile = require("src.core.game3.profile")
|
||||
local Runtime = require("src.core.game3.runtime")
|
||||
local Audio = require("src.core.game3.audio")
|
||||
local Warm = require("src.core.game3.warm")
|
||||
local Options = require("src.core.game3.options")
|
||||
local Dataset = require("src.core.game3.dataset")
|
||||
local Display = require("src.core.game3.display")
|
||||
@@ -117,6 +118,10 @@ function Game3:_enterField(session, reason, opts)
|
||||
-- pokefirered/src/fieldmap.c:100
|
||||
Runtime.start(nil, self, session, { reason = reason or "new_game" })
|
||||
Map._nextEnterVia = nil
|
||||
pcall(function() require("src.core.game3.prewarm").session(session) end)
|
||||
local Syms = package.loaded["src.import.gba.syms"]
|
||||
if Syms and Syms.reset then Syms.reset() end
|
||||
collectgarbage("collect")
|
||||
if fieldCallback then require(fieldCallback).execute() end
|
||||
if reason == "continue" then
|
||||
if lazyReq("src.core.game3.profile").family(session) == "rse" then
|
||||
@@ -755,6 +760,8 @@ function Game3:update(dt)
|
||||
local okP, errP = pcall(function() lazyReq("src.render.Pipelines").update(dt) end)
|
||||
if not okP then s9log("pipelines", errP) end
|
||||
pcall(function() lazyReq("src.core.DiscordPresence").update(dt) end)
|
||||
local okW, errW = pcall(Warm.step)
|
||||
if not okW then s9log("warm", errW) end
|
||||
Game3.stepGC()
|
||||
end
|
||||
|
||||
|
||||
+2
-2
@@ -23,8 +23,8 @@ local function applyVolume(src)
|
||||
fading = state.fade ~= nil,
|
||||
optionScale = volumeScale,
|
||||
}
|
||||
local ok, Game = pcall(require, "src.core.Game")
|
||||
if ok and Game then
|
||||
local Game = package.loaded["src.core.Game"]
|
||||
if type(Game) == "table" then
|
||||
local ow = Game.overworld
|
||||
if ow and ow.player then
|
||||
ctx.x, ctx.y = ow.player.cellX, ow.player.cellY
|
||||
|
||||
+120
-48
@@ -9,6 +9,7 @@ local Mix = require("src.core.game3.m4a_mix")
|
||||
local Player = require("src.core.game3.m4a_player")
|
||||
local SE = require("src.core.game3.se_ids")
|
||||
local Song = require("src.core.game3.song_ids")
|
||||
local Warm = require("src.core.game3.warm")
|
||||
local ffiOk, ffi = pcall(require, "ffi")
|
||||
|
||||
local Audio = {}
|
||||
@@ -817,6 +818,7 @@ Audio.SE_LOOP_MAX_SEC = 2.5
|
||||
Audio.SE_ONESHOT_MAX_SEC = 30
|
||||
|
||||
function Audio._seRawClear()
|
||||
Audio._cryCache, Audio._cryCacheN = nil, 0
|
||||
Audio._seRaw = {}
|
||||
Audio._seRawFrames = 0
|
||||
Audio._seRawTick = 0
|
||||
@@ -919,6 +921,55 @@ function Audio._seSourceFor(id, rawL, rawR, master, pan, mono, loop)
|
||||
return src
|
||||
end
|
||||
|
||||
local function bake_se(id, opts, memoable)
|
||||
local slot = { voices = {} }
|
||||
-- SE must run the M4A sequencer (SE_SELECT is CGB pulse, not voice0 PCM).
|
||||
local ok = Player.start(Audio._pack, Audio._cache, slot, id, { forceSeq = true })
|
||||
if not ok then
|
||||
warn_once("se:" .. tostring(id), "SE " .. tostring(id) .. " missing")
|
||||
return nil
|
||||
end
|
||||
|
||||
local loop = opts.loop
|
||||
if loop == nil then
|
||||
-- SE_LOW_HEALTH and any track with GOTO before FINE are hardware loops.
|
||||
loop = (id == SE.SE_LOW_HEALTH) or Audio._songHasGoto(slot)
|
||||
end
|
||||
|
||||
local loopBody = loop and opts.loop == nil and id ~= SE.SE_LOW_HEALTH
|
||||
-- pokefirered/src/battle_anim_special.c:1200
|
||||
local cut = ((loop and not loopBody) or id == SE.SE_EXP)
|
||||
local maxSec = opts.maxSec
|
||||
or (cut and Audio.SE_LOOP_MAX_SEC or Audio.SE_ONESHOT_MAX_SEC)
|
||||
local rawL, rawR, loopStart = Player.bakeSlot(slot, {
|
||||
raw = true,
|
||||
maxSec = maxSec,
|
||||
stopOnGoto = loop and true or false,
|
||||
loopBody = loopBody,
|
||||
})
|
||||
if not cut and opts.maxSec == nil and type(rawL) == "table"
|
||||
and #rawL >= math.floor(Mix.SAMPLE_RATE * maxSec) then
|
||||
warn_once("selen:" .. tostring(id),
|
||||
"SE " .. tostring(id) .. " hit the " .. tostring(maxSec) .. "s bake ceiling")
|
||||
end
|
||||
if memoable then Audio._seRawPut(id, loop and true or false, rawL, rawR, loopStart) end
|
||||
return loop and true or false, rawL, rawR, loopStart
|
||||
end
|
||||
|
||||
function Audio.prewarmSe(id, priority)
|
||||
id = SE.resolve(id)
|
||||
if id == nil or not Audio.isReady() then return false end
|
||||
if Audio._seRaw and Audio._seRaw[id] then return false end
|
||||
local info = Audio.songInfo(id)
|
||||
if info and info.kind == "fanfare" then return false end
|
||||
return Warm.add("se:" .. tostring(id), function()
|
||||
if Audio._seRaw and Audio._seRaw[id] then return end
|
||||
local loop, rawL, rawR = bake_se(id, {}, true)
|
||||
if loop == nil or loop then return end
|
||||
Audio._seSourceFor(id, rawL, rawR, Audio._sfxVolume or 1, Audio.normalizePan(nil), Audio._mono, false)
|
||||
end, priority)
|
||||
end
|
||||
|
||||
function Audio.playSe(id, opts)
|
||||
opts = opts or {}
|
||||
id = SE.resolve(id)
|
||||
@@ -938,42 +989,14 @@ function Audio.playSe(id, opts)
|
||||
Audio._stopSePlayer(mplay)
|
||||
|
||||
local memoable = opts.loop == nil and opts.maxSec == nil
|
||||
if memoable then Warm.flush("se:" .. tostring(id)) end
|
||||
local hit = memoable and Audio._seRawGet(id) or nil
|
||||
local loop, rawL, rawR, loopStart
|
||||
if hit then
|
||||
loop, rawL, rawR, loopStart = hit.loop, hit.rawL, hit.rawR, hit.loopStart
|
||||
else
|
||||
local slot = { voices = {} }
|
||||
-- SE must run the M4A sequencer (SE_SELECT is CGB pulse, not voice0 PCM).
|
||||
local ok = Player.start(Audio._pack, Audio._cache, slot, id, { forceSeq = true })
|
||||
if not ok then
|
||||
warn_once("se:" .. tostring(id), "SE " .. tostring(id) .. " missing")
|
||||
return false
|
||||
end
|
||||
|
||||
loop = opts.loop
|
||||
if loop == nil then
|
||||
-- SE_LOW_HEALTH and any track with GOTO before FINE are hardware loops.
|
||||
loop = (id == SE.SE_LOW_HEALTH) or Audio._songHasGoto(slot)
|
||||
end
|
||||
|
||||
local loopBody = loop and opts.loop == nil and id ~= SE.SE_LOW_HEALTH
|
||||
-- pokefirered/src/battle_anim_special.c:1200
|
||||
local cut = ((loop and not loopBody) or id == SE.SE_EXP)
|
||||
local maxSec = opts.maxSec
|
||||
or (cut and Audio.SE_LOOP_MAX_SEC or Audio.SE_ONESHOT_MAX_SEC)
|
||||
rawL, rawR, loopStart = Player.bakeSlot(slot, {
|
||||
raw = true,
|
||||
maxSec = maxSec,
|
||||
stopOnGoto = loop and true or false,
|
||||
loopBody = loopBody,
|
||||
})
|
||||
if not cut and opts.maxSec == nil and type(rawL) == "table"
|
||||
and #rawL >= math.floor(Mix.SAMPLE_RATE * maxSec) then
|
||||
warn_once("selen:" .. tostring(id),
|
||||
"SE " .. tostring(id) .. " hit the " .. tostring(maxSec) .. "s bake ceiling")
|
||||
end
|
||||
if memoable then Audio._seRawPut(id, loop and true or false, rawL, rawR, loopStart) end
|
||||
loop, rawL, rawR, loopStart = bake_se(id, opts, memoable)
|
||||
if loop == nil then return false end
|
||||
end
|
||||
|
||||
local pan = Audio.normalizePan(opts.pan)
|
||||
@@ -1045,6 +1068,7 @@ function Audio._buildSeSoundData(L, R, master, pan, mono)
|
||||
master = master or 1
|
||||
local gainL, gainR = Audio._seGains(pan)
|
||||
local gl, gr = master * gainL, master * gainR
|
||||
local warm = package.loaded["src.core.game3.warm"]
|
||||
local ptr
|
||||
if ffiOk and ffi and sd.getFFIPointer then
|
||||
local okP, p = pcall(sd.getFFIPointer, sd)
|
||||
@@ -1068,6 +1092,7 @@ function Audio._buildSeSoundData(L, R, master, pan, mono)
|
||||
sd:setSample(i - 1, 1, l)
|
||||
sd:setSample(i - 1, 2, r)
|
||||
end
|
||||
if warm and i % 4096 == 0 then warm.yield() end
|
||||
end
|
||||
return sd
|
||||
end
|
||||
@@ -1338,6 +1363,67 @@ function Audio.waitFanfare(cb)
|
||||
if not Audio._fanfareActive and cb then cb() end
|
||||
end
|
||||
|
||||
Audio.CRY_CACHE_MAX = 24
|
||||
|
||||
local function cry_inputs(mode, pan, volume)
|
||||
mode = tonumber(mode) or 0
|
||||
local cfg = Audio.config()
|
||||
-- pokeruby/src/sound.c:364
|
||||
if cfg.cryModeMax and (mode < 0 or mode > cfg.cryModeMax) then mode = 0 end
|
||||
local params = Sample.cryParams(mode, volume or cfg.cryDefaultVolume, cfg.cryModeOverrides)
|
||||
return params, pan and pan ~= 0 and Audio.normalizePan(pan) or nil
|
||||
end
|
||||
|
||||
local function cry_meta(slot)
|
||||
local cry = slot and slot.info
|
||||
if not (cry and cry.cryIndex ~= nil) then return nil end
|
||||
local c = Audio._pack.index.cries[cry.cryIndex]
|
||||
return c and Audio._pack.samples[c.sampleId], cry.cryIndex
|
||||
end
|
||||
|
||||
local function cry_render(slot, params, pan, mode, volume)
|
||||
local meta, cryIndex = cry_meta(slot)
|
||||
if not meta then return nil end
|
||||
local key = table.concat({ tostring(cryIndex), tostring(mode), tostring(volume), tostring(pan),
|
||||
Audio._mono and "m" or "s" }, ":")
|
||||
Warm.flush("cry:" .. key)
|
||||
local cache = Audio._cryCache or {}
|
||||
Audio._cryCache = cache
|
||||
local hit = cache[key]
|
||||
if hit then return hit.sd, hit.info end
|
||||
local pcm = Sample.loadPcm(Audio._pack.samplesBin, meta)
|
||||
if not pcm then return nil end
|
||||
local sd, info = Sample.renderCry(pcm, Mix.waveRate(meta.freq), params, {
|
||||
outRate = Mix.SAMPLE_RATE,
|
||||
pan = pan,
|
||||
mono = Audio._mono,
|
||||
})
|
||||
if sd then
|
||||
Audio._cryCacheN = (Audio._cryCacheN or 0) + 1
|
||||
if Audio._cryCacheN > Audio.CRY_CACHE_MAX then
|
||||
Audio._cryCache, Audio._cryCacheN = {}, 1
|
||||
cache = Audio._cryCache
|
||||
end
|
||||
cache[key] = { sd = sd, info = info }
|
||||
end
|
||||
return sd, info
|
||||
end
|
||||
|
||||
function Audio.prewarmCry(species, mode, pan, priority)
|
||||
species = tonumber(species) or species
|
||||
if species == nil or not Audio.isReady() then return false end
|
||||
local params, npan = cry_inputs(mode, pan, nil)
|
||||
local slot = Player.startCry(Audio._pack, species, { pitch = 1.0 })
|
||||
local _, cryIndex = cry_meta(slot)
|
||||
if cryIndex == nil then return false end
|
||||
local key = table.concat({ tostring(cryIndex), tostring(tonumber(mode) or 0), "nil", tostring(npan),
|
||||
Audio._mono and "m" or "s" }, ":")
|
||||
if Audio._cryCache and Audio._cryCache[key] then return false end
|
||||
return Warm.add("cry:" .. key, function()
|
||||
cry_render(slot, params, npan, tonumber(mode) or 0, nil)
|
||||
end, priority)
|
||||
end
|
||||
|
||||
-- pokefirered/src/sound.c:333
|
||||
function Audio.playCry(species, mode, pan)
|
||||
species = tonumber(species) or species
|
||||
@@ -1350,10 +1436,7 @@ function Audio.playCry(species, mode, pan)
|
||||
noDuck = o.noDuck == true
|
||||
end
|
||||
mode = tonumber(mode) or 0
|
||||
local cfg = Audio.config()
|
||||
-- pokeruby/src/sound.c:364
|
||||
if cfg.cryModeMax and (mode < 0 or mode > cfg.cryModeMax) then mode = 0 end
|
||||
local params = Sample.cryParams(mode, volume or cfg.cryDefaultVolume, cfg.cryModeOverrides)
|
||||
local params, npan = cry_inputs(mode, pan, volume)
|
||||
local doubles = params.mode == 1 or noDuck
|
||||
Audio._cryParams = params
|
||||
log(string.format("playCry species=%s mode=%d", tostring(species), params.mode))
|
||||
@@ -1367,25 +1450,14 @@ function Audio.playCry(species, mode, pan)
|
||||
return false
|
||||
end
|
||||
Audio._crySlot = slot
|
||||
local cry = slot.info
|
||||
local meta = nil
|
||||
if cry and cry.cryIndex ~= nil then
|
||||
local c = Audio._pack.index.cries[cry.cryIndex]
|
||||
if c then meta = Audio._pack.samples[c.sampleId] end
|
||||
end
|
||||
local pcm = meta and Sample.loadPcm(Audio._pack.samplesBin, meta)
|
||||
if pcm then
|
||||
if cry_meta(slot) then
|
||||
Audio._crySource = nil
|
||||
if not doubles then
|
||||
Audio._duck = 85 / 256
|
||||
Audio._duckHold = 2
|
||||
apply_bgm_gain()
|
||||
end
|
||||
local sd, info = Sample.renderCry(pcm, Mix.waveRate(meta.freq), params, {
|
||||
outRate = Mix.SAMPLE_RATE,
|
||||
pan = pan and pan ~= 0 and Audio.normalizePan(pan) or nil,
|
||||
mono = Audio._mono,
|
||||
})
|
||||
local sd, info = cry_render(slot, params, npan, mode, volume)
|
||||
if info then
|
||||
Audio._cryUntil = (Audio._cryClock or 0) + info.frames
|
||||
end
|
||||
|
||||
@@ -81,6 +81,21 @@ local function frlgSheetKey(env, id)
|
||||
return primary
|
||||
end
|
||||
|
||||
function BattleBg.resolveOpts(opts)
|
||||
local terrain = opts.terrain
|
||||
-- pokefirered/src/battle_main.c:689
|
||||
if terrain == nil and (opts.mapBehavior ~= nil or opts.mapType ~= nil) then
|
||||
terrain = BattleBg.resolveFromBehavior(opts.mapBehavior, opts.mapKind, opts.mapType)
|
||||
end
|
||||
if terrain == nil and opts.mapKind then
|
||||
terrain = BattleBg.resolveFromMapKind(opts.mapKind)
|
||||
end
|
||||
if terrain == nil then
|
||||
terrain = BattleBg.TERRAIN.BUILDING
|
||||
end
|
||||
return terrain
|
||||
end
|
||||
|
||||
function BattleBg.sheetKey(id)
|
||||
id = tonumber(id) or BattleBg._terrainId
|
||||
local env = BattleBg.env()
|
||||
|
||||
@@ -679,17 +679,7 @@ function Battle.start(opts)
|
||||
end
|
||||
|
||||
local BattleBg = require("src.core.game3.battle.bg")
|
||||
local terrain = opts.terrain
|
||||
-- pokefirered/src/battle_main.c:689
|
||||
if terrain == nil and (opts.mapBehavior ~= nil or opts.mapType ~= nil) then
|
||||
terrain = BattleBg.resolveFromBehavior(opts.mapBehavior, opts.mapKind, opts.mapType)
|
||||
end
|
||||
if terrain == nil and opts.mapKind then
|
||||
terrain = BattleBg.resolveFromMapKind(opts.mapKind)
|
||||
end
|
||||
if terrain == nil then
|
||||
terrain = BattleBg.TERRAIN.BUILDING
|
||||
end
|
||||
local terrain = BattleBg.resolveOpts(opts)
|
||||
st.terrain = terrain
|
||||
-- pokefirered/src/battle_bg.c:714
|
||||
BattleBg.setTerrain(BattleBg.resolveOverride(terrain, {
|
||||
|
||||
@@ -48,8 +48,13 @@ function Moves.loadRomPack(cache)
|
||||
Moves._romLoaded = true
|
||||
local root = (Extract.CACHE_ROOT or "data/generated/gba") .. "/pokemon/battle_moves.lua"
|
||||
cache = cache or require("src.core.game3.dataset").cache()
|
||||
local src = assert(cache:read(root), "pokemon/battle_moves.lua is not in the cache")
|
||||
local pack = assert(load(src, "@" .. root, "t", {}))()
|
||||
local chunk = Moves._romChunk
|
||||
if not (chunk and Moves._romChunkCache == cache) then
|
||||
local src = assert(cache:read(root), "pokemon/battle_moves.lua is not in the cache")
|
||||
chunk = assert(load(src, "@" .. root, "t", {}))
|
||||
Moves._romChunk, Moves._romChunkCache = chunk, cache
|
||||
end
|
||||
local pack = chunk()
|
||||
Moves._rom = assert(pack and pack.moves, "pokemon/battle_moves.lua has no moves")
|
||||
Moves._numByName = nil
|
||||
build_names()
|
||||
|
||||
@@ -243,7 +243,7 @@ function Ui.reset(opts)
|
||||
Ui._oldManSubstate = nil
|
||||
if Message and Message.isHeld and Message.isHeld() then Message.close() end
|
||||
if not Ui._headless then
|
||||
local okC, errC = pcall(BattleChrome.install, nil)
|
||||
local okC, errC = pcall(BattleChrome.ensureInstalled)
|
||||
if not okC and not chromeInstallWarned then
|
||||
chromeInstallWarned = true
|
||||
print("[game3/battle.ui] BattleChrome.install failed: " .. tostring(errC))
|
||||
|
||||
@@ -146,8 +146,8 @@ function BattleBridge.installWhiteoutIntercept(mod, game)
|
||||
end
|
||||
BattleBridge._whiteoutHook = onWhiteout
|
||||
|
||||
local ok, World = pcall(require, "src.world.gen2.World")
|
||||
if ok and World then
|
||||
local World = package.loaded["src.world.gen2.World"]
|
||||
if type(World) == "table" then
|
||||
if type(World.whiteOut) == "function" and not World._game3WhiteOut then
|
||||
local prev = World.whiteOut
|
||||
World.whiteOut = function(self, ...)
|
||||
@@ -398,6 +398,9 @@ function BattleBridge.start(mod, game, foe, opts)
|
||||
if not session then return nil, "no session" end
|
||||
|
||||
BattleBridge.installWhiteoutIntercept(mod, game)
|
||||
if not opts.headless then
|
||||
pcall(function() require("src.core.game3.prewarm").battle(session, foe) end)
|
||||
end
|
||||
if opts.wild and require("src.core.game3.profile").family(session) == "rse" then
|
||||
-- pokeemerald/src/battle_setup.c:417
|
||||
require("src.core.game3.rse.init").call("tv", "incrementDailyWildBattles", "IncrementDailyWildBattles", nil)
|
||||
@@ -659,6 +662,9 @@ function BattleBridge.start(mod, game, foe, opts)
|
||||
local StayMessage = package.loaded["src.ui.game3.message"]
|
||||
if StayMessage and StayMessage.closeStay then StayMessage.closeStay() end
|
||||
end
|
||||
if not opts.headless then
|
||||
pcall(function() require("src.core.game3.prewarm").battleStart(startOpts) end)
|
||||
end
|
||||
|
||||
local function doStart()
|
||||
local ok, err = Battle.start(startOpts)
|
||||
|
||||
+34
-13
@@ -376,11 +376,29 @@ function Dataset.mountExtractRoots()
|
||||
end
|
||||
|
||||
local manifestLayouts = {}
|
||||
local lazyLayouts, LazyDef
|
||||
|
||||
function Dataset.invalidateManifestCache()
|
||||
manifestLayouts = {}
|
||||
end
|
||||
|
||||
lazyLayouts = setmetatable({}, { __mode = "k" })
|
||||
|
||||
LazyDef = {
|
||||
__index = function(def, k)
|
||||
if k ~= "midLayout" then return nil end
|
||||
local src = lazyLayouts[def]
|
||||
if not src then return nil end
|
||||
lazyLayouts[def] = nil
|
||||
local blob = src.cache:read(src.rel)
|
||||
local decoded = blob and require("src.import.gba.native_pack").decodeMidLayout(blob)
|
||||
if not decoded then return nil end
|
||||
local layout = require("src.core.game3.layout_native").fromDecoded(decoded, src.mapId, src.pair, blob)
|
||||
rawset(def, "midLayout", layout)
|
||||
return layout
|
||||
end,
|
||||
}
|
||||
|
||||
--- Bind LayoutNative handles onto map defs (FieldView needs midLayout).
|
||||
function Dataset.attachMidLayouts(maps, cache)
|
||||
if type(maps) ~= "table" then return 0 end
|
||||
@@ -397,25 +415,28 @@ function Dataset.attachMidLayouts(maps, cache)
|
||||
end
|
||||
local attached = 0
|
||||
for mapId, def in pairs(maps) do
|
||||
if def and not def.midLayout then
|
||||
if def and not rawget(def, "midLayout") and not lazyLayouts[def] then
|
||||
local info = layouts[mapId]
|
||||
local rel = nativeRoot .. "/"
|
||||
.. ((info and info.file) or ("layouts/" .. mapId .. ".mid"))
|
||||
local blob = cache:read(rel)
|
||||
if blob then
|
||||
local decoded = NativePack.decodeMidLayout(blob)
|
||||
if decoded then
|
||||
local pair = (info and info.pair) or def.pair
|
||||
def.midLayout = LayoutNative.fromDecoded(decoded, mapId, pair, blob)
|
||||
local tw = decoded.trueWidth or decoded.width
|
||||
local th = decoded.trueHeight or decoded.height
|
||||
if tw and tw > 0 then def.width = tw end
|
||||
if th and th > 0 then def.height = th end
|
||||
if pair then def.pair = pair end
|
||||
attached = attached + 1
|
||||
if blob and #blob >= 16 and blob:sub(1, 4) == NativePack.MAGIC_MID then
|
||||
local pair = (info and info.pair) or def.pair
|
||||
local tw = blob:byte(11) + blob:byte(12) * 256
|
||||
local th = blob:byte(13) + blob:byte(14) * 256
|
||||
if tw > 0 then def.width = tw end
|
||||
if th > 0 then def.height = th end
|
||||
if pair then def.pair = pair end
|
||||
if getmetatable(def) == nil then
|
||||
lazyLayouts[def] = { cache = cache, rel = rel, mapId = mapId, pair = pair }
|
||||
setmetatable(def, LazyDef)
|
||||
else
|
||||
local decoded = NativePack.decodeMidLayout(blob)
|
||||
if decoded then def.midLayout = LayoutNative.fromDecoded(decoded, mapId, pair, blob) end
|
||||
end
|
||||
attached = attached + 1
|
||||
end
|
||||
elseif def and def.midLayout then
|
||||
elseif def then
|
||||
attached = attached + 1
|
||||
end
|
||||
end
|
||||
|
||||
@@ -146,7 +146,7 @@ local function drawFrlgReflections(camX, camY)
|
||||
for j = 1, #live do
|
||||
local obj = live[j]
|
||||
if not obj.hideReflection and obj.graphicsId and not obj.virtualId then
|
||||
local spr = Ow.getDraw(obj.graphicsId)
|
||||
local spr = (Ow.peekDraw or Ow.getDraw)(obj.graphicsId)
|
||||
if spr then
|
||||
objectPoseOpts.frame = obj.customFrame
|
||||
local frame, flip = Ow.pose(spr, obj.facing, Objects.walkPhase(obj), obj.stepFlip,
|
||||
|
||||
@@ -682,7 +682,7 @@ local ghostPose = {}
|
||||
|
||||
local function drawReflection(obj, gid, frame, hflip, x2, y2, camX, camY, ox, oy)
|
||||
local Ow = package.loaded["src.core.game3.ow_sprites"]
|
||||
local spr = Ow and Ow.getDraw and Ow.getDraw(gid)
|
||||
local spr = Ow and (Ow.peekDraw or Ow.getDraw)(gid)
|
||||
if not (spr and spr.quads and spr.quads[frame]) then return false end
|
||||
ox, oy = ox or 0, oy or 0
|
||||
local cx = (obj.moving and obj.targetX or obj.cellX) + ox
|
||||
@@ -871,7 +871,7 @@ local function drawReflections(camX, camY)
|
||||
for j = 1, #live do
|
||||
local eo = live[j]
|
||||
if not eo.hideReflection and eo.graphicsId and not eo.virtualId then
|
||||
local spr = Ow.getDraw(eo.graphicsId)
|
||||
local spr = (Ow.peekDraw or Ow.getDraw)(eo.graphicsId)
|
||||
if spr then
|
||||
ghostPose.frame = eo.customFrame
|
||||
local frame, flip = Ow.pose(spr, eo.facing, O.walkPhase(eo), eo.stepFlip, ghostPose)
|
||||
|
||||
@@ -47,7 +47,8 @@ end
|
||||
local function not_the_time(session)
|
||||
if require("src.core.game3.profile").family(session) == "rse" then
|
||||
-- pokeemerald/src/item_use.c:158 DisplayDadsAdviceCannotUseItemMessage
|
||||
return (RomText.box("gText_DadsAdvice", { playerName = player_name(session) }))
|
||||
-- pokeruby/src/item_use.c:146
|
||||
return (RomText.box(is_rs() and "gOtherText_DadsAdvice" or "gText_DadsAdvice", { playerName = player_name(session) }))
|
||||
end
|
||||
return (RomText.box("gText_OakForbidsUseOfItemHere", { playerName = player_name(session) }))
|
||||
end
|
||||
|
||||
@@ -590,6 +590,8 @@ function Player.bakeSlot(slot, opts)
|
||||
else
|
||||
idle = 0
|
||||
end
|
||||
local warm = package.loaded["src.core.game3.warm"]
|
||||
if warm then warm.yield() end
|
||||
end
|
||||
if #L == 0 then
|
||||
L[1] = 0
|
||||
|
||||
@@ -181,6 +181,7 @@ function Sample.renderCryMix(pcm, sampleRate, params, opts)
|
||||
local gain = (tonumber(params.volume) or Sample.CRY_VOLUME) / 127
|
||||
local reverse = params.reverse and true or false
|
||||
local out = {}
|
||||
local warm = package.loaded["src.core.game3.warm"]
|
||||
for i = 0, nOut - 1 do
|
||||
local g = env.gain(math.floor(i * frameRate / outRate)) * gain
|
||||
local acc = 0
|
||||
@@ -196,6 +197,7 @@ function Sample.renderCryMix(pcm, sampleRate, params, opts)
|
||||
if acc > 1 then acc = 1 elseif acc < -1 then acc = -1 end
|
||||
end
|
||||
out[i + 1] = acc
|
||||
if warm and i % 2048 == 2047 then warm.yield() end
|
||||
end
|
||||
local v1 = voices[1]
|
||||
local sampleFrames = v1.samples * frameRate / outRate
|
||||
@@ -223,6 +225,7 @@ function Sample.renderCry(pcm, sampleRate, params, opts)
|
||||
gainL = (127 - pan) / 191
|
||||
gainR = (128 + pan) / 191
|
||||
end
|
||||
local warm = package.loaded["src.core.game3.warm"]
|
||||
for i = 0, #out - 1 do
|
||||
local v = out[i + 1]
|
||||
if channels == 2 then
|
||||
@@ -231,6 +234,7 @@ function Sample.renderCry(pcm, sampleRate, params, opts)
|
||||
else
|
||||
sd:setSample(i, v * (gainL + gainR) * 0.5)
|
||||
end
|
||||
if warm and i % 2048 == 2047 then warm.yield() end
|
||||
end
|
||||
return sd, info
|
||||
end
|
||||
|
||||
@@ -405,6 +405,15 @@ function OwSprites.getDraw(graphicsId)
|
||||
return OwSprites.get(graphicsId)
|
||||
end
|
||||
|
||||
function OwSprites.peekDraw(graphicsId)
|
||||
graphicsId = tonumber(graphicsId)
|
||||
if graphicsId == nil then return nil end
|
||||
local ov = OwSprites._overrides and OwSprites._overrides[graphicsId]
|
||||
if ov or OwSprites._loaded[graphicsId] or not OwSprites._stream then return OwSprites.getDraw(graphicsId) end
|
||||
OwSprites.prefetch(graphicsId, 0)
|
||||
return nil
|
||||
end
|
||||
|
||||
local function paletteRgb(colors)
|
||||
if type(colors) ~= "table" then return nil end
|
||||
local out = {}
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
local Warm = require("src.core.game3.warm")
|
||||
local Prewarm = {}
|
||||
|
||||
Prewarm.FIELD_SE = { "SE_SELECT", "SE_WIN_OPEN", "SE_WALL_HIT", "SE_EXIT", "SE_RS_DOOR", "SE_LEDGE",
|
||||
"SE_SLIDING_DOOR", "SE_PIN" }
|
||||
|
||||
Prewarm.BATTLE_SE = { "SE_BALL_OPEN", "SE_BALL_THROW", "SE_FAINT", "SE_NOT_EFFECTIVE", "SE_EFFECTIVE",
|
||||
"SE_SUPER_EFFECTIVE", "SE_EXP" }
|
||||
|
||||
Prewarm.MODULES = {
|
||||
"src.core.game3.party", "src.core.game3.trainer_sight", "src.core.game3.warp",
|
||||
"src.core.game3.battle_downgrade", "src.core.game3.battle_bridge", "src.core.game3.battle_transition",
|
||||
"src.core.game3.battle.ai", "src.core.game3.battle.ai_cmds", "src.core.game3.battle.ai_vm",
|
||||
"src.core.game3.battle.ai_items", "src.core.game3.battle.link_guard", "src.ui.game3.stat_growth",
|
||||
"src.ui.game3.map_name_popup",
|
||||
}
|
||||
|
||||
Prewarm.FAMILY_MODULES = {
|
||||
rse = { "src.core.game3.audio_policy_rse", "src.ui.game3.rse.mapsec", "src.ui.game3.rse.scene_kit",
|
||||
"src.core.game3.battle_transition_ids_rse", "src.core.game3.battle_transition_rse_a",
|
||||
"src.core.game3.battle_transition_rse_b", "src.core.game3.battle_transition_rse_frontier" },
|
||||
frlg = { "src.core.game3.battle_transition_ids_frlg" },
|
||||
}
|
||||
|
||||
Prewarm.FONT_FACES = { "normal", "small", "short", "narrow", "small_narrow" }
|
||||
|
||||
Prewarm.CONSTANTS = { "weather", "region_map_sections", "trainer_classes", "moves", "abilities" }
|
||||
|
||||
local function se_list(names, priority)
|
||||
local Audio = package.loaded["src.core.game3.audio"]
|
||||
if not Audio then return end
|
||||
for _, name in ipairs(names) do Audio.prewarmSe(name, priority) end
|
||||
end
|
||||
|
||||
function Prewarm.session(session)
|
||||
local Profile = require("src.core.game3.profile")
|
||||
local family = Profile.family(session)
|
||||
Warm.require(Prewarm.MODULES, 6)
|
||||
Warm.require(Prewarm.FAMILY_MODULES[family] or {}, 6)
|
||||
local okG, game = pcall(function() return require("src.core.GameVersion").get() end)
|
||||
if okG and game then
|
||||
for _, kind in ipairs(Prewarm.CONSTANTS) do
|
||||
Warm.add("const:" .. tostring(game) .. ":" .. kind, function()
|
||||
local C = require("src.core.game3.constants").of(game)
|
||||
local _ = C[kind]
|
||||
end, 7)
|
||||
end
|
||||
end
|
||||
for _, face in ipairs(Prewarm.FONT_FACES) do
|
||||
Warm.add("font:" .. face, function()
|
||||
local Font = require("src.ui.game3.frlg_font")
|
||||
if Font.face then pcall(Font.face, { font = face }) end
|
||||
end, 5)
|
||||
end
|
||||
Warm.add("data:pic_coords", function() require("src.core.game3.battle.pic_coords").active() end, 7)
|
||||
Warm.add("data:mon_anim", function() require("src.core.game3.mon_anim_data").get() end, 7)
|
||||
Warm.add("data:moves", function() require("src.core.game3.battle.moves").loadRomPack() end, 7)
|
||||
se_list(Prewarm.FIELD_SE, 9)
|
||||
end
|
||||
|
||||
local function species_of(mon)
|
||||
if type(mon) ~= "table" then return nil end
|
||||
return tonumber(mon.species or mon.speciesId)
|
||||
end
|
||||
|
||||
local function lead_of(party)
|
||||
for _, mon in ipairs(type(party) == "table" and party or {}) do
|
||||
if type(mon) == "table" and not mon.isEgg and not mon.egg and (tonumber(mon.hp) or 1) > 0 then return mon end
|
||||
end
|
||||
end
|
||||
|
||||
function Prewarm.battle(session, foe)
|
||||
local Audio = package.loaded["src.core.game3.audio"]
|
||||
if not Audio then return end
|
||||
local lead = lead_of(session and session.party)
|
||||
local IntroSeq = package.loaded["src.core.game3.battle.intro_seq"]
|
||||
local mode = lead and IntroSeq and IntroSeq.releaseCryMode and IntroSeq.releaseCryMode(lead) or 0
|
||||
if species_of(lead) then Audio.prewarmCry(species_of(lead), mode, -25, 1) end
|
||||
local first = foe and ((type(foe.party) == "table" and foe.party[1]) or foe)
|
||||
if species_of(first) then Audio.prewarmCry(species_of(first), 0, 25, 1) end
|
||||
se_list(Prewarm.BATTLE_SE, 2)
|
||||
end
|
||||
|
||||
function Prewarm.battleStart(opts)
|
||||
if type(opts) ~= "table" then return end
|
||||
Warm.add("battle:chrome", function()
|
||||
require("src.ui.game3.battle_chrome").ensureInstalled()
|
||||
local BattleBg = require("src.core.game3.battle.bg")
|
||||
require("src.ui.game3.battle_chrome").terrain(BattleBg.sheetKey(BattleBg.resolveOpts(opts)))
|
||||
end, 1)
|
||||
Warm.add("battle:back", function()
|
||||
require("src.core.game3.trainer_pic").back(tonumber(opts.playerGender) or 0)
|
||||
end, 1)
|
||||
end
|
||||
|
||||
return Prewarm
|
||||
@@ -466,7 +466,8 @@ function Runtime.install(mod)
|
||||
Runtime._mod = mod
|
||||
log("Runtime.install — display ownership + START intercept + game.ready")
|
||||
|
||||
local ok, World = pcall(lazyReq, "src.world.gen2.World")
|
||||
local World = package.loaded["src.world.gen2.World"]
|
||||
local ok = type(World) == "table"
|
||||
if ok and World and World.step and not World._game3RuntimeStep then
|
||||
local prev = World.step
|
||||
World.step = function(self, ...)
|
||||
@@ -507,7 +508,8 @@ function Runtime.install(mod)
|
||||
end
|
||||
|
||||
-- Own the frame: replace Gen2 drawScene presentation while active.
|
||||
local ok2, Game2 = pcall(lazyReq, "src.core.Game2")
|
||||
local Game2 = package.loaded["src.core.Game2"]
|
||||
local ok2 = type(Game2) == "table"
|
||||
if ok2 and Game2 and Game2.drawScene and not Game2._game3Display then
|
||||
local prevDraw = Game2.drawScene
|
||||
Game2.drawScene = function(self, w, h)
|
||||
|
||||
@@ -744,7 +744,8 @@ function Space.install(mod)
|
||||
end
|
||||
end
|
||||
-- Direct Gen2 World:setMap (ferry/warps often skip the facade).
|
||||
local ok, World = pcall(lazyReq, "src.world.gen2.World")
|
||||
local World = package.loaded["src.world.gen2.World"]
|
||||
local ok = type(World) == "table"
|
||||
if ok and World and World.setMap and not World._game3SetMap then
|
||||
local prevW = World.setMap
|
||||
World.setMap = function(self, mapId, ...)
|
||||
@@ -796,7 +797,8 @@ function Space.install(mod)
|
||||
|
||||
-- Gen2 World:busy must see game3 scripts or frozeNpcs clears mid-dialog.
|
||||
do
|
||||
local ok, World = pcall(lazyReq, "src.world.gen2.World")
|
||||
local World = package.loaded["src.world.gen2.World"]
|
||||
local ok = type(World) == "table"
|
||||
if ok and World and World.busy and not World._game3Busy then
|
||||
local prevBusy = World.busy
|
||||
World.busy = function(self)
|
||||
|
||||
@@ -18,6 +18,12 @@ TilesetAnim._enabled = true
|
||||
|
||||
local MID_RGBA = 16 * 16 * 4 -- 1024
|
||||
|
||||
local nativeMod
|
||||
local function NativeTileset()
|
||||
nativeMod = nativeMod or require("src.core.game3.tileset_native")
|
||||
return nativeMod
|
||||
end
|
||||
|
||||
local function log(msg)
|
||||
print("[game3/anim] " .. tostring(msg))
|
||||
end
|
||||
@@ -167,6 +173,7 @@ local function rse_paste(entry, bank, blob, mids, quads, frame, imageData, lut,
|
||||
if piece then
|
||||
local ax, ay = (slot % cols) * 16, math.floor(slot / cols) * 16
|
||||
local mask = quads and quads[k] or 15
|
||||
NativeTileset().markDirty(ts, over, slot)
|
||||
if mask == 15 then
|
||||
imageData:paste(piece, ax, ay, 0, 0, 16, 16)
|
||||
else
|
||||
@@ -234,12 +241,6 @@ end
|
||||
|
||||
local rseDirty = {}
|
||||
|
||||
local nativeMod
|
||||
local function NativeTileset()
|
||||
nativeMod = nativeMod or require("src.core.game3.tileset_native")
|
||||
return nativeMod
|
||||
end
|
||||
|
||||
-- pokeemerald/src/tileset_anims.c:586
|
||||
function TilesetAnim.stepRse()
|
||||
local st = TilesetAnim._rse
|
||||
@@ -295,6 +296,49 @@ local function load_bank(cache, pair, kind, info)
|
||||
}
|
||||
end
|
||||
|
||||
local get_frame_piece
|
||||
|
||||
local function prewarm(pair, entry)
|
||||
local Warm = require("src.core.game3.warm")
|
||||
Warm.add("anim:" .. pair, function()
|
||||
if TilesetAnim._pairs[pair] ~= entry then return end
|
||||
NativeTileset().ensureAlt(entry.atlas)
|
||||
if entry.rse then
|
||||
for _, bank in ipairs(entry.banks) do
|
||||
local row = bank and bank.row
|
||||
if row and row.kind == "palette" then
|
||||
if row.paletteSlot then NativeTileset().prepareSlot(entry.atlas, tonumber(row.paletteSlot) or 0) end
|
||||
elseif row then
|
||||
local frames = tonumber(row.frames) or 0
|
||||
for frame = 0, frames - 1 do
|
||||
local n, on = #(row.mids or EMPTY), #(row.overMids or EMPTY)
|
||||
for k = 1, n do
|
||||
if bank.under then rse_piece(entry, bank, bank.under, entry.lut, frame, k, n, false) end
|
||||
if k % 4 == 0 then Warm.yield() end
|
||||
end
|
||||
for k = 1, on do
|
||||
if bank.over then rse_piece(entry, bank, bank.over, entry.lutOver, frame, k, on, true) end
|
||||
if k % 4 == 0 then Warm.yield() end
|
||||
end
|
||||
Warm.yield()
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
for _, bank in pairs(entry.banks) do
|
||||
local nMids = #bank.mids
|
||||
for frame = 0, bank.frames - 1 do
|
||||
for mi = 1, nMids do
|
||||
get_frame_piece(bank, frame, mi, (frame * nMids + (mi - 1)) * MID_RGBA)
|
||||
if mi % 8 == 0 then Warm.yield() end
|
||||
end
|
||||
Warm.yield()
|
||||
end
|
||||
end
|
||||
end
|
||||
end, 3)
|
||||
end
|
||||
|
||||
-- pokefirered/src/tileset_anims.c:223
|
||||
function TilesetAnim.bindPair(pair, atlas, prepared)
|
||||
if not Versions.NATIVE_RENDER or Versions.TILESET_ANIM == false then
|
||||
@@ -314,6 +358,7 @@ function TilesetAnim.bindPair(pair, atlas, prepared)
|
||||
entry = rse_entry(cache, pair, atlas, man)
|
||||
TilesetAnim._pairs[pair] = entry
|
||||
if not entry then return false end
|
||||
prewarm(pair, entry)
|
||||
if not TilesetAnim._rse then TilesetAnim.enterMap(pair, false) end
|
||||
TilesetAnim._visible[pair] = true
|
||||
return true
|
||||
@@ -324,6 +369,7 @@ function TilesetAnim.bindPair(pair, atlas, prepared)
|
||||
flower = load_bank(cache, pair, "flower", man.flower),
|
||||
} }
|
||||
TilesetAnim._pairs[pair] = entry
|
||||
prewarm(pair, entry)
|
||||
end
|
||||
TilesetAnim._visible[pair] = true
|
||||
if entry.rse then return true end
|
||||
@@ -356,7 +402,7 @@ function TilesetAnim.setVisiblePairs(visible)
|
||||
end
|
||||
end
|
||||
|
||||
local function get_frame_piece(bank, frame, mi, srcOff)
|
||||
function get_frame_piece(bank, frame, mi, srcOff)
|
||||
if not (love and love.image and love.image.newImageData) then return nil end
|
||||
local pieces = bank.pieces
|
||||
if not pieces then
|
||||
@@ -395,6 +441,7 @@ function TilesetAnim._applyKind(entry, kind, frame, deferUpload)
|
||||
local ax = (slot % cols) * 16
|
||||
local ay = math.floor(slot / cols) * 16
|
||||
local piece = get_frame_piece(bank, frame, mi, srcOff)
|
||||
NativeTileset().markDirty(ts, false, slot)
|
||||
local pasted = false
|
||||
if piece and ts.imageData.paste then
|
||||
ts.imageData:paste(piece, ax, ay)
|
||||
|
||||
@@ -166,11 +166,16 @@ local function scan_slot(blob, cols, slot, skipZero)
|
||||
local base = 13 + midCount * 2
|
||||
local lo, hi = slot * 16, slot * 16 + 15
|
||||
local n = 0
|
||||
local cells = {}
|
||||
out.cells = cells
|
||||
local warm = package.loaded["src.core.game3.warm"]
|
||||
for i = 0, midCount * 256 - 1 do
|
||||
local b = blob:byte(base + i)
|
||||
if warm and i % 16384 == 16383 then warm.yield() end
|
||||
if b and b >= lo and b <= hi and not (skipZero and b == 0) then
|
||||
local mid = math.floor(i / 256)
|
||||
local within = i % 256
|
||||
cells[mid] = true
|
||||
n = n + 1
|
||||
out[n] = {
|
||||
(mid % cols) * 16 + within % 16,
|
||||
@@ -200,43 +205,98 @@ local function retarget(old, new)
|
||||
end
|
||||
end
|
||||
|
||||
local function flip(ts, imgKey, dataKey, altKey)
|
||||
local scratch
|
||||
local function cellData(data, x, y)
|
||||
if not scratch then scratch = love.image.newImageData(16, 16) end
|
||||
scratch:paste(data, 0, 0, x, y, 16, 16)
|
||||
return scratch
|
||||
end
|
||||
|
||||
local function merge(pend, slots, full)
|
||||
if full or not slots then
|
||||
pend.full, pend.slots = true, nil
|
||||
elseif not pend.full then
|
||||
pend.slots = pend.slots or {}
|
||||
for slot in pairs(slots) do pend.slots[slot] = true end
|
||||
end
|
||||
end
|
||||
|
||||
local function apply(ts, image, data, pend)
|
||||
if pend.full then
|
||||
image:replacePixels(data)
|
||||
elseif pend.slots then
|
||||
local cols = ts.cols or 16
|
||||
for slot in pairs(pend.slots) do
|
||||
local x, y = (slot % cols) * 16, math.floor(slot / cols) * 16
|
||||
image:replacePixels(cellData(data, x, y), 1, 1, x, y)
|
||||
end
|
||||
end
|
||||
pend.full, pend.slots = false, nil
|
||||
end
|
||||
|
||||
local function flip(ts, imgKey, dataKey, altKey, slots)
|
||||
local img, data = ts[imgKey], ts[dataKey]
|
||||
if not (img and data and img.replacePixels) then return end
|
||||
ts._pend = ts._pend or setmetatable({}, { __mode = "k" })
|
||||
local pend = ts._pend
|
||||
local alt = ts[altKey]
|
||||
local full = not slots or not next(slots)
|
||||
if alt and alt.replacePixels then
|
||||
alt:replacePixels(data)
|
||||
pend[alt] = pend[alt] or {}
|
||||
merge(pend[alt], slots, full)
|
||||
apply(ts, alt, data, pend[alt])
|
||||
elseif love and love.graphics and love.graphics.newImage then
|
||||
alt = love.graphics.newImage(data)
|
||||
if alt.setFilter then alt:setFilter("nearest", "nearest") end
|
||||
pend[alt] = {}
|
||||
else
|
||||
img:replacePixels(data)
|
||||
return
|
||||
end
|
||||
pend[img] = pend[img] or {}
|
||||
merge(pend[img], slots, full)
|
||||
ts[imgKey], ts[altKey] = alt, img
|
||||
retarget(img, alt)
|
||||
end
|
||||
|
||||
function NativeTileset.flush(ts, under, over)
|
||||
if under then flip(ts, "image", "imageData", "imageAlt") end
|
||||
if over then flip(ts, "overImage", "overImageData", "overImageAlt") end
|
||||
function NativeTileset.markDirty(ts, over, slot)
|
||||
local key = over and "_dirtyOver" or "_dirtyUnder"
|
||||
local set = ts[key]
|
||||
if not set then set = {}; ts[key] = set end
|
||||
set[slot] = true
|
||||
end
|
||||
|
||||
local function paint(imageData, list, colors)
|
||||
function NativeTileset.flush(ts, under, over)
|
||||
local du, dov = ts._dirtyUnder, ts._dirtyOver
|
||||
ts._dirtyUnder, ts._dirtyOver = nil, nil
|
||||
if under then flip(ts, "image", "imageData", "imageAlt", du) end
|
||||
if over then flip(ts, "overImage", "overImageData", "overImageAlt", dov) end
|
||||
end
|
||||
|
||||
function NativeTileset.ensureAlt(ts)
|
||||
for _, k in ipairs({ { "image", "imageData", "imageAlt" }, { "overImage", "overImageData", "overImageAlt" } }) do
|
||||
local img, data = ts[k[1]], ts[k[2]]
|
||||
if img and data and not ts[k[3]] and love and love.graphics and love.graphics.newImage then
|
||||
local alt = love.graphics.newImage(data)
|
||||
if alt.setFilter then alt:setFilter("nearest", "nearest") end
|
||||
ts[k[3]] = alt
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function paint(ts, over, imageData, list, colors)
|
||||
if not (imageData and #list > 0) then return false end
|
||||
for i = 1, #list do
|
||||
local p = list[i]
|
||||
local c = colors[p[3]]
|
||||
imageData:setPixel(p[1], p[2], c[1] / 255, c[2] / 255, c[3] / 255, 1)
|
||||
end
|
||||
for slot in pairs(list.cells or {}) do NativeTileset.markDirty(ts, over, slot) end
|
||||
return true
|
||||
end
|
||||
|
||||
-- pokefirered/src/palette.c:88
|
||||
function NativeTileset.setSlotPalette(pairOrTs, slot, bgr16)
|
||||
local ts = type(pairOrTs) == "table" and pairOrTs or NativeTileset.get(pairOrTs)
|
||||
if not (ts and ts.imageData and type(bgr16) == "table") then return false end
|
||||
slot = tonumber(slot) or 0
|
||||
function NativeTileset.prepareSlot(ts, slot)
|
||||
local pix = ts.slotPix[slot]
|
||||
if not pix then
|
||||
pix = {
|
||||
@@ -245,10 +305,19 @@ function NativeTileset.setSlotPalette(pairOrTs, slot, bgr16)
|
||||
}
|
||||
ts.slotPix[slot] = pix
|
||||
end
|
||||
return pix
|
||||
end
|
||||
|
||||
function NativeTileset.setSlotPalette(pairOrTs, slot, bgr16)
|
||||
local ts = type(pairOrTs) == "table" and pairOrTs or NativeTileset.get(pairOrTs)
|
||||
if not (ts and ts.imageData and type(bgr16) == "table") then return false end
|
||||
slot = tonumber(slot) or 0
|
||||
local pix = NativeTileset.prepareSlot(ts, slot)
|
||||
local src = {}
|
||||
for c = 0, 15 do src[c] = bgr16[c + 1] or 0 end
|
||||
local colors = NativePack.palsToRgb8({ [0] = src })[0]
|
||||
NativeTileset.flush(ts, paint(ts.imageData, pix.under, colors), paint(ts.overImageData, pix.over, colors))
|
||||
NativeTileset.flush(ts, paint(ts, false, ts.imageData, pix.under, colors),
|
||||
paint(ts, true, ts.overImageData, pix.over, colors))
|
||||
ts.patchedSlots = ts.patchedSlots or {}
|
||||
ts.patchedSlots[slot] = true
|
||||
return true
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
local Warm = {}
|
||||
Warm.BUDGET = 0.002
|
||||
|
||||
local jobs, order = {}, {}
|
||||
local clock = function() return love and love.timer and love.timer.getTime() or os.clock() end
|
||||
|
||||
function Warm.add(key, fn, priority)
|
||||
if jobs[key] then
|
||||
if priority and priority < jobs[key].priority then jobs[key].priority = priority end
|
||||
return false
|
||||
end
|
||||
local job = { key = key, fn = fn, priority = priority or 5 }
|
||||
jobs[key] = job
|
||||
order[#order + 1] = job
|
||||
return true
|
||||
end
|
||||
|
||||
function Warm.pending(key) return jobs[key] ~= nil end
|
||||
|
||||
local function finish(job)
|
||||
jobs[job.key] = nil
|
||||
for i = #order, 1, -1 do
|
||||
if order[i] == job then table.remove(order, i) break end
|
||||
end
|
||||
end
|
||||
|
||||
local function resume(job)
|
||||
if not job.co then job.co = coroutine.create(job.fn) end
|
||||
local ok, err = coroutine.resume(job.co)
|
||||
if not ok then
|
||||
print("[game3/warm] " .. tostring(job.key) .. ": " .. tostring(err))
|
||||
finish(job)
|
||||
elseif coroutine.status(job.co) == "dead" then
|
||||
finish(job)
|
||||
end
|
||||
end
|
||||
|
||||
function Warm.flush(key)
|
||||
local job = jobs[key]
|
||||
if not job or (job.co and job.co == coroutine.running()) then return false end
|
||||
job.flushing = true
|
||||
while jobs[key] == job do resume(job) end
|
||||
return true
|
||||
end
|
||||
|
||||
function Warm.step(budget)
|
||||
if #order == 0 then return end
|
||||
local deadline = clock() + (budget or Warm.BUDGET)
|
||||
repeat
|
||||
local best
|
||||
for _, job in ipairs(order) do
|
||||
if not best or job.priority < best.priority then best = job end
|
||||
end
|
||||
if not best then return end
|
||||
resume(best)
|
||||
until clock() >= deadline
|
||||
end
|
||||
|
||||
function Warm.require(names, priority)
|
||||
for _, name in ipairs(names) do
|
||||
if not package.loaded[name] then
|
||||
Warm.add("mod:" .. name, function() pcall(require, name) end, priority or 8)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Warm.yield()
|
||||
local co, main = coroutine.running()
|
||||
if co and not main then
|
||||
for _, job in pairs(jobs) do
|
||||
if job.co == co then
|
||||
if not job.flushing then coroutine.yield() end
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Warm.reset()
|
||||
jobs, order = {}, {}
|
||||
end
|
||||
|
||||
return Warm
|
||||
@@ -959,12 +959,13 @@ function PaletteFX.shadeMap()
|
||||
end
|
||||
|
||||
local function invalidateColorCaches()
|
||||
pcall(function() require("src.battle.BattleState").invalidate() end)
|
||||
pcall(function() require("src.render.SpriteRenderer").invalidate() end)
|
||||
local loaded = package.loaded
|
||||
pcall(function() if loaded["src.battle.BattleState"] then loaded["src.battle.BattleState"].invalidate() end end)
|
||||
pcall(function() if loaded["src.render.SpriteRenderer"] then loaded["src.render.SpriteRenderer"].invalidate() end end)
|
||||
pcall(function()
|
||||
require("src.world.MapLoader").invalidateAll()
|
||||
local Game = require("src.core.Game")
|
||||
if Game.overworld and Game.overworld.map and Game.overworld.reloadMap then
|
||||
if loaded["src.world.MapLoader"] then loaded["src.world.MapLoader"].invalidateAll() end
|
||||
local Game = loaded["src.core.Game"]
|
||||
if type(Game) == "table" and Game.overworld and Game.overworld.map and Game.overworld.reloadMap then
|
||||
Game.overworld:reloadMap(Game.overworld.map.id, "colors")
|
||||
end
|
||||
end)
|
||||
@@ -995,8 +996,8 @@ function PaletteFX.setCustomRamp(ramp)
|
||||
end
|
||||
|
||||
function PaletteFX.pickerActive()
|
||||
local ok, Game = pcall(require, "src.core.Game")
|
||||
local states = ok and Game.stack and Game.stack.states
|
||||
local Game = package.loaded["src.core.Game"]
|
||||
local states = type(Game) == "table" and Game.stack and Game.stack.states or nil
|
||||
local top = states and states[#states]
|
||||
return top ~= nil and top.screenId == "PaletteScreen"
|
||||
end
|
||||
|
||||
@@ -970,8 +970,8 @@ function Renderer:endFrame(zones, worldZones)
|
||||
local extendedBlackBand = false
|
||||
local bandR, bandG, bandB = 1, 1, 1
|
||||
if not self.worldActive then
|
||||
local ok, Game = pcall(require, "src.core.Game")
|
||||
local stack = ok and Game and Game.stack
|
||||
local Game = package.loaded["src.core.Game"]
|
||||
local stack = type(Game) == "table" and Game.stack
|
||||
local base = stack and stack.visibleBase and stack:visibleBase()
|
||||
local state = base and stack.states and stack.states[base]
|
||||
local ownState = self.surroundState
|
||||
@@ -1214,8 +1214,8 @@ function Renderer:endFrame(zones, worldZones)
|
||||
-- field, so never cover it with the native back-sprite fallback.
|
||||
if self.extendedWorldBand and not self.worldOverride
|
||||
and not FaithfulRes.scaleCap() then
|
||||
local ok, Game = pcall(require, "src.core.Game")
|
||||
love.graphics.setColor(PaletteFX.paperShade(ok and Game and Game.data))
|
||||
local Game = package.loaded["src.core.Game"]
|
||||
love.graphics.setColor(PaletteFX.paperShade(type(Game) == "table" and Game.data or nil))
|
||||
love.graphics.rectangle("fill", uox, vuy, uvpw, vuh)
|
||||
love.graphics.setColor(1, 1, 1, 1)
|
||||
end
|
||||
|
||||
@@ -161,8 +161,18 @@ local TERRAIN_MT = {
|
||||
__index = function(_, key) return BattleChrome.terrain(key) end,
|
||||
}
|
||||
|
||||
local function install_key(cache)
|
||||
return tostring(cache) .. "|" .. battle_root()
|
||||
end
|
||||
|
||||
function BattleChrome.ensureInstalled()
|
||||
if BattleChrome._installKey == install_key(resolve_cache(nil)) and BattleChrome._manifest then return end
|
||||
BattleChrome.install(nil)
|
||||
end
|
||||
|
||||
function BattleChrome.install(cache)
|
||||
BattleChrome._cache = resolve_cache(cache)
|
||||
BattleChrome._installKey = install_key(BattleChrome._cache)
|
||||
BattleChrome._manifest = nil
|
||||
BattleChrome._textbox = nil
|
||||
BattleChrome._playerBox = nil
|
||||
|
||||
@@ -19,6 +19,21 @@ local WIN = {
|
||||
yesno = Window.template(9, 7, 5, 4),
|
||||
}
|
||||
ItemStorage.WIN = WIN
|
||||
-- pokeruby/src/player_pc.c:973
|
||||
local WIN_RS = {
|
||||
list = WIN.list,
|
||||
message = WIN.message,
|
||||
title = Window.template(1, 1, 10, 2),
|
||||
-- pokeruby/src/player_pc.c:606
|
||||
quantity = Window.template(7, 9, 6, 2),
|
||||
-- pokeruby/src/player_pc.c:703
|
||||
yesno = Window.template(8, 7, 5, 4),
|
||||
}
|
||||
ItemStorage.WIN_RS = WIN_RS
|
||||
|
||||
local function is_rs()
|
||||
return require("src.ui.game3.rs.player_pc_policy").matches(ItemStorage._session)
|
||||
end
|
||||
-- pokeemerald/src/player_pc.c:283
|
||||
local ITEM_X, UP_TEXT_Y = 8, 9
|
||||
-- pokeemerald/src/player_pc.c:699
|
||||
@@ -53,10 +68,12 @@ local function pos()
|
||||
end
|
||||
|
||||
local function narrow()
|
||||
if is_rs() then return { font = "normal" } end
|
||||
return { font = "narrow" }
|
||||
end
|
||||
|
||||
local function row_height()
|
||||
if is_rs() then return 16 end
|
||||
local face = FrlgFont.face and FrlgFont.face(narrow())
|
||||
return face and face.height or 16
|
||||
end
|
||||
@@ -384,7 +401,44 @@ local function frame(tpl)
|
||||
Window.fill(tpl, 1, 1, 1, 1)
|
||||
end
|
||||
|
||||
-- pokeruby/src/player_pc.c:862
|
||||
local function draw_list_rs()
|
||||
local tpl = WIN_RS.list
|
||||
frame(tpl)
|
||||
local ox = tpl.left * 8
|
||||
local list = items()
|
||||
local shown = page_items()
|
||||
local font = narrow()
|
||||
local ItemsData = require("src.core.game3.items_data")
|
||||
for i = 0, shown - 1 do
|
||||
local idx = ItemStorage.scroll + i
|
||||
local y = (i * 2 + 2) * 8
|
||||
local e = list[idx + 1]
|
||||
if not e then
|
||||
FrlgFont.draw(RomText.plain("gText_Cancel2"), ox, y,
|
||||
{ font = "normal", colors = { fg = FrlgFont.STDPAL[1], bg = { 0, 0, 0, 0 }, shadow = FrlgFont.STDPAL[8] } })
|
||||
break
|
||||
end
|
||||
local colors = { fg = FrlgFont.STDPAL[ItemStorage.swapFrom == idx and 2 or 1], bg = { 0, 0, 0, 0 },
|
||||
shadow = FrlgFont.STDPAL[8] }
|
||||
FrlgFont.draw(item_name(e.id), ox, y, { font = "normal", colors = colors })
|
||||
local pocket = ItemsData.pocketOf(e.id)
|
||||
-- pokeruby/src/player_pc.c:815
|
||||
if pocket ~= "KEY_ITEMS" and not ItemsData.isHm(e.id) then
|
||||
local qx = 26 * 8
|
||||
FrlgFont.draw("×", qx, y, { font = "normal", colors = colors })
|
||||
local digits = tostring(tonumber(e.qty) or 0)
|
||||
local dx = qx + FrlgFont.measure("×", font) + (3 - #digits) * 6
|
||||
for c = 1, #digits do
|
||||
FrlgFont.draw(digits:sub(c, c), dx + (c - 1) * 6, y, { font = "normal", colors = colors })
|
||||
end
|
||||
end
|
||||
end
|
||||
require("src.ui.game3.rs.menu_cursor").draw(ox, 16 + ItemStorage.row * 16, 13 * 8)
|
||||
end
|
||||
|
||||
local function draw_list()
|
||||
if is_rs() then return draw_list_rs() end
|
||||
local tpl = WIN.list
|
||||
frame(tpl)
|
||||
local ox, oy = tpl.left * 8, tpl.top * 8
|
||||
@@ -427,14 +481,20 @@ end
|
||||
|
||||
function ItemStorage.draw()
|
||||
if not ItemStorage.open then return end
|
||||
local title = WIN.title
|
||||
local rs = is_rs()
|
||||
local W = rs and WIN_RS or WIN
|
||||
local title = W.title
|
||||
frame(title)
|
||||
local t = RomText.plain(ItemStorage._toss and "gText_TossItem" or "gText_WithdrawItem")
|
||||
-- pokeemerald/src/player_pc.c:960
|
||||
Window.printPx(t, title.left * 8 + math.floor((104 - FrlgFont.measure(t)) / 2), title.top * 8 + 1)
|
||||
if rs then
|
||||
Window.printPx(t, title.left * 8, title.top * 8)
|
||||
else
|
||||
-- pokeemerald/src/player_pc.c:960
|
||||
Window.printPx(t, title.left * 8 + math.floor((104 - FrlgFont.measure(t)) / 2), title.top * 8 + 1)
|
||||
end
|
||||
draw_list()
|
||||
draw_icon()
|
||||
local m = WIN.message
|
||||
if not rs then draw_icon() end
|
||||
local m = W.message
|
||||
frame(m)
|
||||
local pitch = FrlgFont.linePitch()
|
||||
local y = m.top * 8 + 1
|
||||
@@ -443,13 +503,13 @@ function ItemStorage.draw()
|
||||
y = y + pitch
|
||||
end
|
||||
if ItemStorage.state == "quantity" then
|
||||
local q = WIN.quantity
|
||||
local q = W.quantity
|
||||
frame(q)
|
||||
local s = RomText.plain("gText_xVar1", { stringVars = { string.format("%03d", ItemStorage.quantity) } })
|
||||
-- pokeemerald/src/player_pc.c:1361
|
||||
Window.printPx(s, q.left * 8 + math.floor((48 - FrlgFont.measure(s)) / 2), q.top * 8 + 1)
|
||||
elseif ItemStorage.state == "yesno" then
|
||||
local yn = WIN.yesno
|
||||
local yn = W.yesno
|
||||
frame(yn)
|
||||
local ph = Window.optionHeight()
|
||||
Window.printPx(RomText.plain("gText_Yes"), yn.left * 8 + 8, yn.top * 8 + 1)
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
local U = require("tests.drivers.util")
|
||||
|
||||
return function(game)
|
||||
local fails = 0
|
||||
local function result(ok, label)
|
||||
print((ok and "PASS " or "FAIL ") .. label)
|
||||
if not ok then fails = fails + 1 end
|
||||
end
|
||||
local function finish()
|
||||
print(string.format("[driver] em_sendout_profile: %d failure(s)", fails))
|
||||
love.event.quit(fails == 0 and 0 or 1)
|
||||
U.wait(10)
|
||||
end
|
||||
|
||||
for _ = 1, 900 do
|
||||
if game.phase == "boot" and game.boot then break end
|
||||
U.wait(1)
|
||||
end
|
||||
game:_handleBootAction({ action = "new_game", name = "NICK", gender = 0 })
|
||||
U.wait(240)
|
||||
|
||||
local Runtime = require("src.core.game3.runtime")
|
||||
local Party = require("src.core.game3.party")
|
||||
local BattleBridge = require("src.core.game3.battle_bridge")
|
||||
local Battle = require("src.core.game3.battle")
|
||||
local Ui = require("src.core.game3.battle.ui")
|
||||
local Map = require("src.core.game3.map")
|
||||
local C = require("src.core.game3.constants").of("emerald")
|
||||
local session = Runtime.getSession()
|
||||
if not session then result(false, "field session"); return finish() end
|
||||
|
||||
local ok = pcall(function() Map.load(nil, game, "EM_ROUTE101", { x = 7, y = 13, facing = "down" }) end)
|
||||
result(ok, "Route 101 loads")
|
||||
U.wait(120)
|
||||
session.party = {}
|
||||
-- pokeemerald/src/battle_setup.c:923
|
||||
Party.giveMon(session, C.species.byName.SPECIES_TREECKO, 5, "TREECKO")
|
||||
print("SENDOUT_MARK battle_start")
|
||||
local started = BattleBridge.startFirstBattle(Runtime._mod, game, { done = function() end })
|
||||
result(started == true, "first battle started")
|
||||
if not started then return finish() end
|
||||
|
||||
local lastTap, f = 0, 0
|
||||
for _ = 1, 4000 do
|
||||
if Battle.isActive() and Battle._phase == "command" and Ui._mode == "menu" then break end
|
||||
f = f + 1
|
||||
if Ui.dialogPending and Ui.dialogPending() and f - lastTap >= 14 then
|
||||
lastTap = f
|
||||
U.tap(game, "a")
|
||||
else
|
||||
U.wait(1)
|
||||
end
|
||||
end
|
||||
result(Battle._phase == "command" and Ui._mode == "menu", "reached the action menu after send-out")
|
||||
U.wait(30)
|
||||
finish()
|
||||
end
|
||||
@@ -0,0 +1,74 @@
|
||||
local U = require("tests.drivers.util")
|
||||
|
||||
local BIG = {}
|
||||
local function census()
|
||||
collectgarbage("collect")
|
||||
print(string.format("HEAP_AFTER_COLLECT_KB %.0f", collectgarbage("count")))
|
||||
local seen, rows = {}, {}
|
||||
local function size(t, depth)
|
||||
if seen[t] then return 0, 0 end
|
||||
seen[t] = true
|
||||
local slots, bytes = 0, 0
|
||||
for k, v in next, t do
|
||||
slots = slots + 1
|
||||
if type(v) == "string" then
|
||||
bytes = bytes + #v
|
||||
if #v > 262144 then BIG[#BIG + 1] = { #v, tostring(k) } end
|
||||
end
|
||||
if type(k) == "string" then bytes = bytes + #k end
|
||||
end
|
||||
return slots, bytes
|
||||
end
|
||||
local function walk(t, path, depth)
|
||||
if type(t) ~= "table" or seen[t] or depth > 12 then return 0 end
|
||||
local slots, bytes = size(t, depth)
|
||||
local total = slots * 40 + bytes
|
||||
for k, v in next, t do
|
||||
if type(v) == "table" then
|
||||
total = total + walk(v, path .. "." .. tostring(k), depth + 1)
|
||||
elseif type(v) == "function" then
|
||||
for i = 1, 60 do
|
||||
local n, uv = debug.getupvalue(v, i)
|
||||
if not n then break end
|
||||
if type(uv) == "table" then total = total + walk(uv, path .. "." .. tostring(k) .. "^" .. n, depth + 1) end
|
||||
end
|
||||
end
|
||||
end
|
||||
rows[#rows + 1] = { path, total, slots }
|
||||
return total
|
||||
end
|
||||
for name, mod in pairs(package.loaded) do
|
||||
if type(mod) == "table" then walk(mod, name, 0) end
|
||||
if type(mod) == "function" then
|
||||
for i = 1, 60 do
|
||||
local n, uv = debug.getupvalue(mod, i)
|
||||
if not n then break end
|
||||
if type(uv) == "table" then walk(uv, name .. "^" .. n, 1) end
|
||||
end
|
||||
end
|
||||
end
|
||||
walk(debug.getregistry(), "REG", 0)
|
||||
table.sort(rows, function(a, b) return a[2] > b[2] end)
|
||||
local bigTotal = 0
|
||||
table.sort(BIG, function(a, b) return a[1] > b[1] end)
|
||||
for i, b in ipairs(BIG) do bigTotal = bigTotal + b[1]; if i <= 25 then print(string.format("BIGSTR %8.0fKB key=%s", b[1] / 1024, b[2])) end end
|
||||
print(string.format("BIGSTR_TOTAL %.0fKB n=%d", bigTotal / 1024, #BIG))
|
||||
print(string.format("HEAP_TOTAL_KB %.0f", collectgarbage("count")))
|
||||
local shown = 0
|
||||
for _, r in ipairs(rows) do
|
||||
if shown >= 60 then break end
|
||||
print(string.format("HEAP %8.0fKB slots=%-8d %s", r[2] / 1024, r[3], r[1]))
|
||||
shown = shown + 1
|
||||
end
|
||||
end
|
||||
|
||||
return function(game)
|
||||
for _ = 1, 900 do
|
||||
if game.phase == "boot" and game.boot then break end
|
||||
U.wait(1)
|
||||
end
|
||||
game:_handleBootAction({ action = "new_game", name = "BRENDAN", gender = 0 })
|
||||
U.wait(300)
|
||||
census()
|
||||
love.event.quit(0)
|
||||
end
|
||||
@@ -0,0 +1,100 @@
|
||||
local INNER = assert(os.getenv("HITCH_INNER"), "HITCH_INNER=tests/drivers/<driver>.lua")
|
||||
local LIMIT_MS = tonumber(os.getenv("HITCH_MS") or "8") or 8
|
||||
local DEPTH = tonumber(os.getenv("HITCH_DEPTH") or "10") or 10
|
||||
if os.getenv("HITCH_JIT") ~= "1" and jit then jit.off(); jit.flush() end
|
||||
|
||||
local okProf, profile = pcall(require, "jit.profile")
|
||||
if os.getenv("HITCH_STACKS") == "0" then okProf = false end
|
||||
local frame, stacks, vm, samples = 0, {}, {}, 0
|
||||
local hitches, all, known = {}, {}, {}
|
||||
local frameStart, stuckShown
|
||||
local clock = love.timer.getTime
|
||||
|
||||
local function top(map, n)
|
||||
local rows = {}
|
||||
for k, v in pairs(map) do rows[#rows + 1] = { k, v } end
|
||||
table.sort(rows, function(a, b) return a[2] > b[2] end)
|
||||
local out = {}
|
||||
for i = 1, math.min(n, #rows) do out[i] = rows[i] end
|
||||
return out
|
||||
end
|
||||
|
||||
if okProf then
|
||||
profile.start("i1", function(th, n, state)
|
||||
samples = samples + n
|
||||
vm[state] = (vm[state] or 0) + n
|
||||
local s = profile.dumpstack(th, "pl < ", DEPTH)
|
||||
stacks[s] = (stacks[s] or 0) + n
|
||||
if frameStart and clock() - frameStart > 5 and not stuckShown then
|
||||
stuckShown = true
|
||||
print("HITCH_STUCK " .. profile.dumpstack(th, "pl\n", 40))
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
local function reset() stacks, vm, samples = {}, {}, 0 end
|
||||
|
||||
local baseUpdate, baseDraw = love.update, love.draw
|
||||
local tu, td, kb0
|
||||
love.update = function(dt)
|
||||
frame = frame + 1
|
||||
reset()
|
||||
kb0 = collectgarbage("count")
|
||||
local t = clock()
|
||||
frameStart = t
|
||||
baseUpdate(dt)
|
||||
tu = (clock() - t) * 1000
|
||||
end
|
||||
love.draw = function()
|
||||
local t = clock()
|
||||
baseDraw()
|
||||
td = (clock() - t) * 1000
|
||||
local total = (tu or 0) + td
|
||||
all[#all + 1] = total
|
||||
if os.getenv("HITCH_STATUS") and frame % 60 == 0 then
|
||||
local B = package.loaded["src.core.game3.battle"]
|
||||
local T = package.loaded["src.core.game3.battle_transition"]
|
||||
print(string.format("HITCH_STATUS frame=%d battle=%s phase=%s transition=%s",
|
||||
frame, tostring(B and B.isActive and B.isActive()), tostring(B and B._phase),
|
||||
tostring(T and T._transitionId)))
|
||||
end
|
||||
for name in pairs(package.loaded) do
|
||||
if not known[name] then
|
||||
known[name] = true
|
||||
if frame > 1 then print(string.format("LAZY_REQUIRE frame=%d %s", frame, name)) end
|
||||
end
|
||||
end
|
||||
if total >= LIMIT_MS then
|
||||
local Map = package.loaded["src.core.game3.map"]
|
||||
hitches[#hitches + 1] = total
|
||||
print(string.format("HITCH frame=%d total=%.1fms update=%.1f draw=%.1f gcKB=%+.0f heapKB=%.0f map=%s samples=%d vm[N=%d I=%d C=%d G=%d J=%d]",
|
||||
frame, total, tu or 0, td, collectgarbage("count") - (kb0 or 0), collectgarbage("count"),
|
||||
tostring(Map and Map.current), samples, vm.N or 0, vm.I or 0, vm.C or 0, vm.G or 0, vm.J or 0))
|
||||
for _, row in ipairs(top(stacks, 6)) do
|
||||
print(string.format(" %3d %s", row[2], row[1]))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local quit = love.event.quit
|
||||
love.event.quit = function(...)
|
||||
table.sort(all)
|
||||
local n = #all
|
||||
local function p(q) return all[math.max(1, math.ceil(n * q))] or 0 end
|
||||
print(string.format("HITCH_SUMMARY frames=%d hitches=%d p50=%.2f p95=%.2f p99=%.2f max=%.2f jit=%s",
|
||||
n, #hitches, p(.5), p(.95), p(.99), all[n] or 0, tostring(jit and jit.status())))
|
||||
local foreign = {}
|
||||
for name in pairs(package.loaded) do
|
||||
if name:find("^src%.[%w_]+%.gen2%.") or name:find("^src%.[%w_]+%.gen2$")
|
||||
or name == "src.core.Game" or name == "src.core.Game2" or name == "src.world.OverworldController"
|
||||
or name == "src.battle.BattleState" then
|
||||
foreign[#foreign + 1] = name
|
||||
end
|
||||
end
|
||||
table.sort(foreign)
|
||||
print(string.format("FOREIGN_MODULES n=%d %s", #foreign, table.concat(foreign, " ")))
|
||||
if okProf then profile.stop() end
|
||||
return quit(...)
|
||||
end
|
||||
|
||||
return assert(loadfile(INNER))()
|
||||
@@ -0,0 +1,64 @@
|
||||
local U = require("tests.drivers.util")
|
||||
local DIR = os.getenv("POKEPORT_SHOT_DIR") or "/tmp/rs_bike_pc_items"
|
||||
|
||||
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") .. " rs_bike_pc_items failures=" .. failures)
|
||||
love.event.quit(failures == 0 and 0 or 1)
|
||||
U.wait(10)
|
||||
end
|
||||
|
||||
return function(game)
|
||||
for _ = 1, 900 do
|
||||
if game.phase == "boot" and game.boot then break end
|
||||
U.wait(1)
|
||||
end
|
||||
game:_handleBootAction({ action = "new_game", name = "BRENDAN", gender = 0 })
|
||||
U.wait(240)
|
||||
local Runtime = require("src.core.game3.runtime")
|
||||
local session = Runtime.getSession()
|
||||
if not check(session ~= nil, "session") then return finish() end
|
||||
local C = require("src.core.game3.constants").active(session)
|
||||
local ItemUse = require("src.core.game3.item_use")
|
||||
local Bag = require("src.core.game3.bag")
|
||||
|
||||
local bike = C:id("items", "ITEM_MACH_BIKE")
|
||||
Bag.add(session.bag, bike, 1)
|
||||
local ok, res, a, b, c = pcall(ItemUse.useField, session, session.bag, bike)
|
||||
check(ok, "bike use indoors does not error (" .. tostring(ok and "" or res) .. ")")
|
||||
check(ok and res == false and type(b) == "string" and b:find("DAD") ~= nil,
|
||||
"bike indoors gives DAD's advice: " .. tostring(b))
|
||||
|
||||
local Storage = require("src.core.game3.storage")
|
||||
local list = Storage.ensure(session).items
|
||||
for i = #list, 1, -1 do list[i] = nil end
|
||||
list[1] = { id = C:id("items", "ITEM_POTION"), qty = 5 }
|
||||
list[2] = { id = C:id("items", "ITEM_SUPER_POTION"), qty = 12 }
|
||||
list[3] = { id = C:id("items", "ITEM_HM01_CUT"), qty = 1 }
|
||||
list[4] = { id = C:id("items", "ITEM_ACRO_BIKE"), qty = 1 }
|
||||
local ItemStorage = require("src.ui.game3.rse.item_storage")
|
||||
ItemStorage.show({ session = session })
|
||||
U.wait(10)
|
||||
check(U.still(game, DIR .. "/01_withdraw.png"), "withdraw shot")
|
||||
U.tap(game, "down")
|
||||
U.wait(6)
|
||||
U.tap(game, "select")
|
||||
U.wait(6)
|
||||
check(U.still(game, DIR .. "/02_swap.png"), "swap shot")
|
||||
U.tap(game, "b")
|
||||
U.wait(6)
|
||||
U.tap(game, "a")
|
||||
U.wait(6)
|
||||
check(U.still(game, DIR .. "/03_quantity.png"), "quantity shot")
|
||||
U.tap(game, "b")
|
||||
U.wait(4)
|
||||
ItemStorage.close()
|
||||
U.wait(4)
|
||||
finish()
|
||||
end
|
||||
Reference in New Issue
Block a user