diff --git a/src/core/game3/field_view.lua b/src/core/game3/field_view.lua index 8d5848c6..f644db0a 100644 --- a/src/core/game3/field_view.lua +++ b/src/core/game3/field_view.lua @@ -342,32 +342,58 @@ local function neighborActorDefs(mapId, def) return type(defs) == "table" and defs or nil end -local function collectNeighborActors(actors, baseIndex, hostMapId, hostDef) +local ghostActors = setmetatable({}, { __mode = "k" }) +local ACTOR_CULL_MARGIN = 64 + +local function entryInView(entry, x0, y0, x1, y1) + local L = entry.def and entry.def.midLayout + local w = (L and L.width) or ((tonumber(entry.def and entry.def.width) or 0) * 2) + local h = (L and L.height) or ((tonumber(entry.def and entry.def.height) or 0) * 2) + local ex, ey = entry.ox * CELL, entry.oy * CELL + return ex + w * CELL > x0 and ex < x1 and ey + h * CELL > y0 and ey < y1 +end + +local function collectNeighborActors(actors, baseIndex, hostMapId, hostDef, camX, camY) local Map = package.loaded["src.core.game3.map"] if not (Map and type(Map.world) == "table") then return baseIndex end local Ghosts = package.loaded["src.core.game3.ghosts"] local Objects = package.loaded["src.core.game3.objects"] + local vw = FieldView._viewW or Display.W + local vh = FieldView._viewH or Display.H + local cull = camX ~= nil and camY ~= nil + local x0, y0 = (camX or 0) - ACTOR_CULL_MARGIN, (camY or 0) - ACTOR_CULL_MARGIN + local x1, y1 = (camX or 0) + vw + ACTOR_CULL_MARGIN, (camY or 0) + vh + ACTOR_CULL_MARGIN for _, entry in ipairs(Map.world) do - if entry.id ~= hostMapId and entry.def ~= hostDef then + if entry.id ~= hostMapId and entry.def ~= hostDef + and (not cull or entryInView(entry, x0, y0, x1, y1)) then local live = Ghosts and Ghosts.forDraw and Ghosts.forDraw(entry.id) if live then for _, eo in ipairs(live) do - baseIndex = baseIndex + 1 - actors[#actors + 1] = { - kind = "npc", - i = baseIndex, - obj = eo.def, - eventObject = eo, - ghost = entry.id, - x = (eo.px or (eo.cellX or 0) * CELL) + entry.ox * CELL, - y = (eo.py or (eo.cellY or 0) * CELL) + entry.oy * CELL, - facing = eo.facing or "down", - walkPhase = Objects and Objects.walkPhase and Objects.walkPhase(eo) or 0, - stepFlip = eo.stepFlip and true or false, - sprite = eo.sprite or spriteNameForObj(eo.def or {}), - graphicsId = eo.graphicsId - or (eo.def and (eo.def.graphicsId or eo.def.graphics)), - } + local x = (eo.px or (eo.cellX or 0) * CELL) + entry.ox * CELL + local y = (eo.py or (eo.cellY or 0) * CELL) + entry.oy * CELL + if not cull or (x > x0 and x < x1 and y > y0 and y < y1) then + baseIndex = baseIndex + 1 + local a = ghostActors[eo] + if not a then + a = { kind = "npc", eventObject = eo } + ghostActors[eo] = a + end + a.i = baseIndex + a.obj = eo.def + a.ghost = entry.id + a.x = x + a.y = y + a.facing = eo.facing or "down" + a.walkPhase = Objects and Objects.walkPhase and Objects.walkPhase(eo) or 0 + a.stepFlip = eo.stepFlip and true or false + a.sprite = eo.sprite or spriteNameForObj(eo.def or {}) + a.graphicsId = eo.graphicsId + or (eo.def and (eo.def.graphicsId or eo.def.graphics)) + a.elevation = nil + a.priority = nil + a.subpriority = nil + actors[#actors + 1] = a + end end else local defs = neighborActorDefs(entry.id, entry.def) @@ -603,7 +629,7 @@ local function collectGame3Actors(game, mapDef, camX, camY, px, py, facing, walk actors[#actors + 1] = a end collectNeighborActors(actors, 10000, currentMapId(game), - resolveMapDef(game, currentMapId(game))) + resolveMapDef(game, currentMapId(game)), camX, camY) local Space = package.loaded["src.core.game3.scripting.space"] if Space and Space.resolveObjectGraphicsId then for _, a in ipairs(actors) do @@ -1357,6 +1383,7 @@ function FieldView.draw(game, canvasW, canvasH, opts) FieldView._billboard = opts.billboard and { vw = canvasW, vh = canvasH } or nil + FieldView._viewW, FieldView._viewH = canvasW, canvasH if not opts.actorsOnly then local Map = package.loaded["src.core.game3.map"] diff --git a/src/core/game3/map.lua b/src/core/game3/map.lua index d2bf29e0..8b60bd30 100644 --- a/src/core/game3/map.lua +++ b/src/core/game3/map.lua @@ -148,6 +148,14 @@ function Map.refreshWorld(game, reachW, reachH, rootId) for _, entry in ipairs(Map.world) do Map._loadedLayouts[entry.id] = true end + local NativeTileset = package.loaded["src.core.game3.tileset_native"] + if Map._warmPairs and NativeTileset and NativeTileset.get then + Map._warmPairs = nil + for _, entry in ipairs(Map.world) do + local pair = entry.def and (entry.def.pair or (entry.def.midLayout and entry.def.midLayout.pair)) + if pair and NativeTileset.ready(pair) then pcall(NativeTileset.get, pair) end + end + end Map._worldRoot = rootId Map._worldReachW = reachW Map._worldReachH = reachH @@ -443,6 +451,7 @@ function Map.load(mod, game, mapId, opts) Collision.clear() end + Map._warmPairs = not opts.seamless or nil -- pret GroundEffect_SpawnOnTallGrass when warping onto grass. if not opts.seamless then local onGrass = Collision.isGrass and Collision.isGrass(Player.cellX, Player.cellY) diff --git a/src/core/game3/tileset_anim.lua b/src/core/game3/tileset_anim.lua index b73b339e..269582fa 100644 --- a/src/core/game3/tileset_anim.lua +++ b/src/core/game3/tileset_anim.lua @@ -129,12 +129,29 @@ end local QUAD_XY = { [0] = { 0, 0 }, { 8, 0 }, { 0, 8 }, { 8, 8 } } -local function rse_paste(entry, bank, blob, mids, quads, frame, imageData, lut, over) +local function rse_quad_piece(bank, piece, key, q) + local cache = bank.quadPieces + if not cache then + cache = {} + bank.quadPieces = cache + end + local qk = key .. q + local sub = cache[qk] + if sub then return sub end + local o = QUAD_XY[q] + sub = love.image.newImageData(8, 8) + sub:paste(piece, 0, 0, o[1], o[2], 8, 8) + cache[qk] = sub + return sub +end + +local function rse_paste(entry, bank, blob, mids, quads, frame, imageData, lut, over, image) if not (blob and imageData and love and love.image) then return false end local ts = entry.atlas local cols = ts.cols or 16 local n = #mids local changed = false + local partial = image ~= nil and image.replacePixels ~= nil for k, mid in ipairs(mids) do local slot = ts.midToSlot[mid] if slot then @@ -142,13 +159,22 @@ 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 - for q = 0, 3 do - if math.floor(mask / 2 ^ q) % 2 == 1 then - local o = QUAD_XY[q] - imageData:paste(piece, ax + o[1], ay + o[2], o[1], o[2], 8, 8) + if mask == 15 then + imageData:paste(piece, ax, ay, 0, 0, 16, 16) + if partial then image:replacePixels(piece, 1, 1, ax, ay, false) end + else + for q = 0, 3 do + if math.floor(mask / 2 ^ q) % 2 == 1 then + local o = QUAD_XY[q] + imageData:paste(piece, ax + o[1], ay + o[2], o[1], o[2], 8, 8) + if partial then + local key = (over and "o" or "u") .. frame * 4096 + k + image:replacePixels(rse_quad_piece(bank, piece, key, q), 1, 1, ax + o[1], ay + o[2], false) + end + end end end - changed = true + changed = not partial end end end @@ -167,10 +193,10 @@ local function rse_apply(entry, bank, frame, dirty) end return end - if rse_paste(entry, bank, bank.under, row.mids or EMPTY, row.quads, frame, ts.imageData, entry.lut, false) then + if rse_paste(entry, bank, bank.under, row.mids or EMPTY, row.quads, frame, ts.imageData, entry.lut, false, ts.image) then dirty.under = true end - if rse_paste(entry, bank, bank.over, row.overMids or EMPTY, row.overQuads, frame, ts.overImageData, entry.lutOver, true) then + if rse_paste(entry, bank, bank.over, row.overMids or EMPTY, row.overQuads, frame, ts.overImageData, entry.lutOver, true, ts.overImage) then dirty.over = true end end