mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-12 08:21:02 +02:00
fix the mf grass
This commit is contained in:
+54
-15
@@ -475,6 +475,13 @@ function TileRenderer.new(map, data)
|
||||
-- match the atlas's static tiles instead of showing raw grayscale
|
||||
gbcCtx = { tilesetId = map.tileset.id, mapId = map.id, key = "#gbc:" .. map.id,
|
||||
groupColors = PaletteFX.worldGroupColors(data, map.tileset.id, map.id, nil) }
|
||||
-- ...and feeds the color-0-keyed single tiles the feet overdraw needs
|
||||
-- (see getKeyedTile): same source image and palette groups, so keep the
|
||||
-- context rather than re-deriving it per draw.
|
||||
gbcCtx.imagePath = map.tileset.image
|
||||
gbcCtx.perRow = map.tileset.tilesPerRow
|
||||
self.gbcCtx = gbcCtx
|
||||
self.gbcKeyed = {}
|
||||
end
|
||||
end
|
||||
-- a full-color atlas colors everything it paints, ring and border fill
|
||||
@@ -672,17 +679,48 @@ local function getColor0KeyShader()
|
||||
return color0KeyShader or nil
|
||||
end
|
||||
|
||||
-- draw a cell's bottom tile row without touching the shader (the caller
|
||||
-- owns it). drawCellBottom wraps this with the color-0 key; tilt mode's
|
||||
-- upright pass wraps it with a color-0-keyed palette shader instead
|
||||
-- (PaletteFX.keyedShader) so the feet patch is colorized like the ground.
|
||||
-- RED++ (COLORS=ADVANCED): the color-0 key, BAKED instead of tested for.
|
||||
local function getKeyedTile(self, tile)
|
||||
local ctx = self.gbcCtx
|
||||
local cached = self.gbcKeyed[tile]
|
||||
if cached ~= nil then return cached or nil end
|
||||
local img = false
|
||||
if ctx.groupColors and love.image and love.image.newImageData then
|
||||
local group = PaletteFX.worldGroupAt(ctx.tilesetId, ctx.mapId, tile)
|
||||
local colors = group and ctx.groupColors[group + 1]
|
||||
local src = Assets.imageData(ctx.imagePath)
|
||||
local ox = (tile % ctx.perRow) * 8
|
||||
local oy = math.floor(tile / ctx.perRow) * 8
|
||||
local out = love.image.newImageData(8, 8)
|
||||
for py = 0, 7 do
|
||||
for px = 0, 7 do
|
||||
local r, g, b, a = src:getPixel(ox + px, oy + py)
|
||||
-- read shade 0 off the RAW sheet, on recolorSample's own cutoff, so
|
||||
-- the keyed pixels are exactly the ones the shader path keys
|
||||
local shade0 = r > 0.83
|
||||
r, g, b, a = recolorSample(r, g, b, a, colors)
|
||||
out:setPixel(px, py, r, g, b, shade0 and 0 or a)
|
||||
end
|
||||
end
|
||||
img = love.graphics.newImage(out)
|
||||
end
|
||||
self.gbcKeyed[tile] = img
|
||||
return img or nil
|
||||
end
|
||||
|
||||
function TileRenderer:drawCellBottomRaw(cx, cy, camX, camY)
|
||||
local ty = cy * 2 + 1
|
||||
for i = 0, 1 do
|
||||
local tx = cx * 2 + i
|
||||
local quad = self.quads[self.map:tileAt(tx, ty)]
|
||||
if quad then
|
||||
love.graphics.draw(self.image, quad, tx * 8 - camX, ty * 8 - camY)
|
||||
local tile = self.map:tileAt(tx, ty)
|
||||
local keyed = tile and self.gbcCtx and getKeyedTile(self, tile)
|
||||
if keyed then
|
||||
love.graphics.draw(keyed, tx * 8 - camX, ty * 8 - camY)
|
||||
else
|
||||
local quad = self.quads[tile]
|
||||
if quad then
|
||||
love.graphics.draw(self.image, quad, tx * 8 - camX, ty * 8 - camY)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -690,7 +728,9 @@ end
|
||||
-- redraw a cell's bottom tile row (tall grass hides the lower half of
|
||||
-- sprites standing in it, like the GB sprite-priority trick)
|
||||
function TileRenderer:drawCellBottom(cx, cy, camX, camY)
|
||||
local shader = getColor0KeyShader()
|
||||
-- the RED++ path is pre-keyed; the white test would be a no-op there at
|
||||
-- best, and a false hit on some other group's near-white color 0 at worst
|
||||
local shader = not self.gbcCtx and getColor0KeyShader() or nil
|
||||
if shader then love.graphics.setShader(shader) end
|
||||
self:drawCellBottomRaw(cx, cy, camX, camY)
|
||||
if shader then love.graphics.setShader() end
|
||||
@@ -712,13 +752,6 @@ function TileRenderer:markCellBottomRedraw(cx, cy, camX, camY, colors)
|
||||
end
|
||||
end
|
||||
|
||||
-- Window cover for the static tile layer. Refill the reusable window batch
|
||||
-- (and the per-entry animated batches) only when the camera has scrolled past
|
||||
-- what they already cover; a small margin keeps small scrolls free. Cost
|
||||
-- scales with the view, never the map -- crossing a seam or warping in builds
|
||||
-- nothing. The beyond-body area (what the old 3-block ring drew) is painted
|
||||
-- by :drawBorderFill, whose world-aligned border-block tiling is identical
|
||||
-- there, so only body tiles are gathered here.
|
||||
local WINDOW_MARGIN = 8 -- tiles of slack kept around the view between refills
|
||||
|
||||
function TileRenderer:ensureWindow(camX, camY, vw, vh)
|
||||
@@ -873,6 +906,12 @@ end
|
||||
-- atlas -- is unique to this map (gbcAtlasCache is keyed by map id).
|
||||
function TileRenderer:release()
|
||||
self:releaseBatches()
|
||||
if self.gbcKeyed then
|
||||
-- baked per instance, shared with nobody (see getKeyedTile)
|
||||
for _, img in pairs(self.gbcKeyed) do safeRelease(img) end
|
||||
self.gbcKeyed = nil
|
||||
self.gbcCtx = nil
|
||||
end
|
||||
if self.gbcAtlas and self.image then
|
||||
local key = self.map.tileset.image .. "#gbc:" .. self.map.id
|
||||
if gbcAtlasCache[key] == self.image then gbcAtlasCache[key] = nil end
|
||||
|
||||
@@ -26,8 +26,20 @@
|
||||
-- * the ROUTE terrain palette still carries BOTH grass green (173,230,90) and
|
||||
-- light-blue (165,214,255), so the grass field keeps its green+blue dither.
|
||||
--
|
||||
-- Screenshots (SHOT_DIR): grass_bug150_sgb.png (the reported view) and
|
||||
-- grass_bug150_ogred.png (OG RED reference -- green character, red terrain).
|
||||
-- The SECOND half of #150 is the feet overdraw itself. Tall grass hides the
|
||||
-- lower half of whoever stands in it (the GB OBJ-priority trick: an OBJ shows
|
||||
-- through BG colour 0 and hides under colours 1-3), which the port reproduces
|
||||
-- by redrawing the cell's bottom tile row over the sprite with shade 0 keyed
|
||||
-- to alpha. Every mode but ADVANCED still has DMG white sitting in shade 0 at
|
||||
-- draw time, so a white test finds it; ADVANCED bakes the real per-tile GBC
|
||||
-- palette into the atlas first (TileRenderer.getGbcAtlas), which turns the
|
||||
-- grass tile's shade 0 into its palette's light green -- the white test then
|
||||
-- never fires and the patch paints an opaque block over the player. Hence the
|
||||
-- gap-pixel gate below, run in BOTH modes so the two can't drift apart again.
|
||||
--
|
||||
-- Screenshots (SHOT_DIR): grass_bug150_sgb.png (the reported view),
|
||||
-- grass_bug150_advanced.png (the ADVANCED view) and grass_bug150_ogred.png
|
||||
-- (OG RED reference -- green character, red terrain).
|
||||
--
|
||||
-- Run: POKEPORT_DRIVER=tests/drivers/grass_overlay_bug150_test.lua \
|
||||
-- POKEPORT_IDENTITY=bug150 POKEPORT_TOUCH=0 love .
|
||||
@@ -49,6 +61,31 @@ return function(game)
|
||||
return false
|
||||
end
|
||||
|
||||
-- How many of the 16x8 feet-overdraw pixels let the thing underneath show
|
||||
-- through, measured the way the screen does it: render the cell's bottom
|
||||
-- tile row over an opaque magenta field and count the magenta survivors.
|
||||
-- Mode-agnostic on purpose -- it asks what reached the framebuffer, not
|
||||
-- which of the two keying paths (shader or baked alpha) produced it.
|
||||
local function grassGapPixels(ow)
|
||||
local p = ow.player
|
||||
local canvas = love.graphics.newCanvas(16, 8)
|
||||
love.graphics.setCanvas(canvas)
|
||||
love.graphics.clear(1, 0, 1, 1)
|
||||
love.graphics.setColor(1, 1, 1, 1)
|
||||
-- camera placed so the cell's bottom tile row lands at the canvas origin
|
||||
ow.map.renderer:drawCellBottom(p.cellX, p.cellY, p.cellX * 16, p.cellY * 16 + 8)
|
||||
love.graphics.setCanvas()
|
||||
local id = canvas:newImageData()
|
||||
local n = 0
|
||||
for y = 0, 7 do
|
||||
for x = 0, 15 do
|
||||
local r, g, b = id:getPixel(x, y)
|
||||
if r > 0.9 and g < 0.1 and b > 0.9 then n = n + 1 end
|
||||
end
|
||||
end
|
||||
return n
|
||||
end
|
||||
|
||||
-- a party + starter flag so the overworld is fully usable
|
||||
game.save.flags.EVENT_GOT_STARTER = true
|
||||
local Pokemon = require("src.pokemon.Pokemon")
|
||||
@@ -70,6 +107,13 @@ return function(game)
|
||||
U.wait(40) -- let the grass/flower tile animation cycle
|
||||
U.shot(game, DIR .. "/grass_bug150_sgb.png")
|
||||
|
||||
-- the feet overdraw is see-through in SGB (the mode the reporter compared
|
||||
-- ADVANCED against), so this side of the gate holds before AND after
|
||||
local sgbGaps = grassGapPixels(ow)
|
||||
check(sgbGaps > 0,
|
||||
"SGB grass feet overdraw shows the sprite through its gaps ("
|
||||
.. sgbGaps .. "/128 px)")
|
||||
|
||||
-- === render-decision gate: fails before the fix, passes after =========
|
||||
check(PaletteFX.usesSpriteObp("gbc") == false,
|
||||
"SGB owns no object palette (it cannot colour an OBJ apart from the BG)")
|
||||
@@ -100,7 +144,47 @@ return function(game)
|
||||
check(hasColor(terrain, 165, 214, 255),
|
||||
"ROUTE terrain palette still contains light-blue (grass keeps its blue dither)")
|
||||
|
||||
-- === ADVANCED (RED++): the same overdraw, over a baked true-colour atlas ==
|
||||
-- setMode drops every cached Map/TileRenderer and reloads the visible one,
|
||||
-- so re-read the state's map before touching its renderer.
|
||||
game.save.options.colors = "redpp"
|
||||
PaletteFX.setMode("redpp")
|
||||
U.wait(20)
|
||||
ow = game.overworld
|
||||
check(ow.map.renderer.gbcAtlas ~= nil,
|
||||
"ADVANCED baked the per-tile GBC atlas for ROUTE_1")
|
||||
local advGaps = grassGapPixels(ow)
|
||||
check(advGaps > 0,
|
||||
"ADVANCED grass feet overdraw shows the sprite through its gaps ("
|
||||
.. advGaps .. "/128 px)")
|
||||
-- the gaps must be the SAME pixels the other modes key -- a baked-in green
|
||||
-- shade 0 is what #150 saw, so the count has to match SGB's exactly
|
||||
check(advGaps == sgbGaps,
|
||||
"ADVANCED keys the same shade-0 pixels SGB does (" .. advGaps
|
||||
.. " vs " .. sgbGaps .. ")")
|
||||
U.shot(game, DIR .. "/grass_bug150_advanced.png")
|
||||
|
||||
-- ROUTE_1 is the OVERWORLD tileset; the other two tilesets that own a grass
|
||||
-- tile (FOREST $20, PLATEAU $45) file it under the very same pack group 2,
|
||||
-- so all three baked the same green over shade 0 and all three broke
|
||||
-- together. One tileset passing proves nothing about the other two.
|
||||
for _, spot in ipairs({ { "VIRIDIAN_FOREST", 6, 6, "FOREST" },
|
||||
{ "ROUTE_23", 10, 44, "PLATEAU" } }) do
|
||||
local id, cx, cy, tsId = spot[1], spot[2], spot[3], spot[4]
|
||||
U.teleport(game, id, cx, cy, "down")
|
||||
U.wait(10)
|
||||
ow = game.overworld
|
||||
check(ow.map.tileset.id == tsId
|
||||
and ow.map:isGrassCell(ow.player.cellX, ow.player.cellY),
|
||||
id .. ": player stands on " .. tsId .. " tall grass")
|
||||
check(ow.map.renderer.gbcAtlas ~= nil, id .. ": ADVANCED baked its atlas")
|
||||
local gaps = grassGapPixels(ow)
|
||||
check(gaps > 0, id .. ": ADVANCED grass feet overdraw shows the sprite "
|
||||
.. "through its gaps (" .. gaps .. "/128 px)")
|
||||
end
|
||||
|
||||
-- OG RED reference for the human diff (green character over red terrain)
|
||||
U.teleport(game, "ROUTE_1", 10, 6, "down")
|
||||
game.save.options.colors = "ogred"
|
||||
PaletteFX.setMode("ogred")
|
||||
U.wait(20)
|
||||
|
||||
Reference in New Issue
Block a user