mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-20 04:31:09 +02:00
also fixes feet layering of gold with grass. Unifies gen1/gen2 grass layering into a single system
This commit is contained in:
@@ -5303,16 +5303,25 @@ function OverworldState:drawWorld()
|
||||
if not ((self.flyAnim or self.flyArrive or self.playerHidden)
|
||||
and e == self.player) then
|
||||
e:draw(cam.x, cam.y)
|
||||
-- tall grass overdraws the sprite's feet (GB sprite priority);
|
||||
-- the overdraw is BG tiles, so it rides the shake offset too
|
||||
love.graphics.setColor(1, 1, 1, 1)
|
||||
if self.map:isGrassCell(e.cellX, e.cellY) then
|
||||
self.map.renderer:drawCellBottom(e.cellX, e.cellY, cam.x, bgY)
|
||||
if grassColors then
|
||||
self.map.renderer:markCellBottomRedraw(e.cellX, e.cellY,
|
||||
cam.x, bgY, grassColors)
|
||||
end
|
||||
end
|
||||
if e.targetX and self.map:isGrassCell(e.targetX, e.targetY) then
|
||||
self.map.renderer:drawCellBottom(e.targetX, e.targetY, cam.x, bgY)
|
||||
if grassColors then
|
||||
self.map.renderer:markCellBottomRedraw(e.targetX, e.targetY,
|
||||
cam.x, bgY, grassColors)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
-- tall grass overdraws every visible grass cell's feet row after all
|
||||
-- sprites (GB sprite-priority parity). One pass regardless of how many
|
||||
-- entities are on screen -- see TileRenderer:drawGrassOverdraw.
|
||||
love.graphics.setColor(1, 1, 1, 1)
|
||||
self.map.renderer:drawGrassOverdraw(cam.x, bgY)
|
||||
if grassColors then
|
||||
self.map.renderer:markGrassOverdrawRedraw(cam.x, bgY, grassColors)
|
||||
end
|
||||
fxHeal()
|
||||
fxDust()
|
||||
fxCutTree()
|
||||
@@ -5348,18 +5357,6 @@ function OverworldState:drawWorld()
|
||||
items[#items + 1] = { y = e.py + 16, kind = "entity", e = e }
|
||||
end
|
||||
end
|
||||
-- Inject grass-cell overdraw items into the same depth-sorted queue so
|
||||
-- they occlude entities at lower y correctly (back-to-front by cell foot).
|
||||
-- Each cell's foot y = cy*16 + 16 in world pixels (bottom of its two rows).
|
||||
local grassCells = self.map.renderer.grassCells
|
||||
if grassCells then
|
||||
for _, c in ipairs(grassCells) do
|
||||
local cx, cy = c[1], c[2]
|
||||
-- world-pixel foot of the grass cell's bottom tile row
|
||||
local cellFootY = (cy * 2 + 2) * 8 -- == cy*16+16
|
||||
items[#items + 1] = { y = cellFootY, kind = "grass", cx = cx, cy = cy }
|
||||
end
|
||||
end
|
||||
table.sort(items, function(a, b) return a.y < b.y end)
|
||||
|
||||
for _, it in ipairs(items) do
|
||||
@@ -5371,20 +5368,6 @@ function OverworldState:drawWorld()
|
||||
local fy = g.npc.py - cam.y + g.oy + 16
|
||||
self:billboard(fx, fy, vw, vh, zoneColorsAt(zones, fx, fy), false,
|
||||
function() g.npc:draw(cam.x - g.ox, cam.y - g.oy) end)
|
||||
elseif it.kind == "grass" then
|
||||
-- tall-grass bottom-row overdraw: billboarded at the cell's foot so
|
||||
-- it depth-sorts correctly against any entity in the same y column.
|
||||
-- bgY keeps the elevator-shake offset; drawCellBottomRaw lets the
|
||||
-- billboard own the shader (color-0 keying baked into the keyed image
|
||||
-- on GBC, or applied by drawCellBottom's shader on DMG/SGB).
|
||||
local cx, cy = it.cx, it.cy
|
||||
local fx = cx * 16 - cam.x + 8 -- horizontal centre of the cell
|
||||
local fy = (cy * 2 + 2) * 8 - cam.y -- foot of the bottom tile row
|
||||
local colors = zoneColorsAt(zones, fx, fy)
|
||||
self:billboard(fx, fy, vw, vh, colors, true, function()
|
||||
love.graphics.setColor(1, 1, 1, 1)
|
||||
self.map.renderer:drawCellBottomRaw(cx, cy, cam.x, bgY)
|
||||
end)
|
||||
else
|
||||
local e = it.e
|
||||
local fx = e.px - cam.x + 8
|
||||
@@ -5392,6 +5375,22 @@ function OverworldState:drawWorld()
|
||||
local colors = zoneColorsAt(zones, fx, fy)
|
||||
self:billboard(fx, fy, vw, vh, colors, false,
|
||||
function() e:draw(cam.x, cam.y) end)
|
||||
-- tall-grass feet overdraw glued to the sprite: same anchor + depth
|
||||
-- so it keeps hiding the feet, color-0-keyed palette so its white
|
||||
-- gaps still show the sprite through (drawCellBottomRaw lets the
|
||||
-- billboard own the shader; bgY keeps the elevator-shake offset).
|
||||
if self.map:isGrassCell(e.cellX, e.cellY) then
|
||||
self:billboard(fx, fy, vw, vh, colors, true, function()
|
||||
love.graphics.setColor(1, 1, 1, 1)
|
||||
self.map.renderer:drawCellBottomRaw(e.cellX, e.cellY, cam.x, bgY)
|
||||
end)
|
||||
end
|
||||
if e.targetX and self.map:isGrassCell(e.targetX, e.targetY) then
|
||||
self:billboard(fx, fy, vw, vh, colors, true, function()
|
||||
love.graphics.setColor(1, 1, 1, 1)
|
||||
self.map.renderer:drawCellBottomRaw(e.targetX, e.targetY, cam.x, bgY)
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
+55
-33
@@ -313,9 +313,8 @@ local TROPHY_BOXES = {
|
||||
}
|
||||
|
||||
-- Script_FishCastRod ends on `pause 40`, and Script_GotABite pauses another 40
|
||||
-- over the bobbing rod before the text lands. ShakeHeadbuttTree counts down
|
||||
-- wFrameCounter from 32. All three are frames at 60 Hz, which is the same
|
||||
-- clock World:step runs on.
|
||||
-- over the bobbing rod before the text lands.
|
||||
-- All are frames at 60 Hz, which is the same clock World:step runs on.
|
||||
local FISH_CAST_FRAMES = 40
|
||||
local FISH_BITE_FRAMES = 40
|
||||
local HEADBUTT_SHAKE_FRAMES = 32
|
||||
@@ -346,10 +345,12 @@ local function sameEncounter(enc) return enc end
|
||||
-- (engine/events/fish.asm Fish) byte for byte.
|
||||
local FISH_ROD_KEY = { OLD_ROD = "old", GOOD_ROD = "good", SUPER_ROD = "super" }
|
||||
|
||||
local function fishVanilla(rod, _mapId, candidates)
|
||||
local function fishVanilla(rod, _mapId, candidates, ctx)
|
||||
if not candidates then return nil end
|
||||
return Encounter.fish({ fishGroups = { hooked = candidates } }, "hooked",
|
||||
FISH_ROD_KEY[rod] or rod or "old", nil)
|
||||
local tod = ctx and (ctx.tod or ctx.daytime)
|
||||
return Encounter.fish({ fishGroups = { hooked = candidates },
|
||||
timeFishGroups = ctx and ctx.encounters and ctx.encounters.timeFishGroups },
|
||||
"hooked", FISH_ROD_KEY[rod] or rod or "old", tod, nil)
|
||||
end
|
||||
|
||||
local function speciesByIndex(pokemon, index)
|
||||
@@ -4291,6 +4292,7 @@ function World:rollFishing(rod)
|
||||
return "nibble"
|
||||
end
|
||||
local roll
|
||||
local tod = self.tod or "DAY"
|
||||
if Runtime.wantsHook("encounter.fishing") then
|
||||
-- Gen 1's three arguments, in Gen 1's order: the rod, the map, and the
|
||||
-- candidate list the chain may inspect or replace before the roll. Gold's
|
||||
@@ -4304,10 +4306,10 @@ function World:rollFishing(rod)
|
||||
roll = Runtime.call("encounter.fishing", fishVanilla, rod, map.id,
|
||||
groups and groups[group],
|
||||
{ fishGroup = group, swarm = swarm, encounters = self.encounters,
|
||||
maps = self.maps, data = game.data })
|
||||
maps = self.maps, data = game.data, tod = tod, daytime = tod })
|
||||
else
|
||||
roll = Encounter.fishSlot(self.encounters, map.id, rod, nil, self.maps,
|
||||
swarm)
|
||||
swarm, tod)
|
||||
end
|
||||
if not roll or not roll.species then return "nibble" end
|
||||
local wild = Mon.new(game.data, roll.species, roll.level)
|
||||
@@ -4770,19 +4772,36 @@ function World:runQueuedScript()
|
||||
end
|
||||
|
||||
-- Script_FishCastRod, then Script_NotEvenANibble or Script_GotABite. Held as
|
||||
-- a frame counter rather than a movement byte stream because the three
|
||||
-- commands involved -- fish_cast_rod ($52), fish_got_bite ($51) and show_emote
|
||||
-- ($54) -- are object ACTION changes, not steps, and Movement.decodeByte has
|
||||
-- nothing to say about them.
|
||||
-- an exact frame counter matching 60 Hz engine ticks.
|
||||
function World:beginFishing(outcome, wild)
|
||||
self.fishing = {
|
||||
phase = "cast", timer = FISH_CAST_FRAMES, outcome = outcome, wild = wild,
|
||||
local p = self.player
|
||||
local d = Map.DELTA[p and p.facing or "down"] or Map.DELTA.down
|
||||
local targetCellX = p and (p.cellX + d[1]) or 0
|
||||
local targetCellY = p and (p.cellY + d[2]) or 0
|
||||
local bobber = {
|
||||
cellX = targetCellX,
|
||||
cellY = targetCellY,
|
||||
px = targetCellX * 16,
|
||||
py = targetCellY * 16,
|
||||
}
|
||||
self.fishing = {
|
||||
phase = "cast",
|
||||
timer = FISH_CAST_FRAMES,
|
||||
outcome = outcome,
|
||||
wild = wild,
|
||||
bobber = bobber,
|
||||
facing = p and p.facing or "down",
|
||||
}
|
||||
if self.player then
|
||||
self.player.fishing = true
|
||||
self.player.fishingState = self.fishing
|
||||
end
|
||||
end
|
||||
|
||||
function World:updateFishing()
|
||||
local st = self.fishing
|
||||
if not st then return end
|
||||
if self.player then self.player.fishingState = st end
|
||||
-- A text box owns the frame while it is up; the script only moves on when
|
||||
-- its own callback fires.
|
||||
if self.textbox or self.choicebox then return end
|
||||
@@ -4791,7 +4810,7 @@ function World:updateFishing()
|
||||
-- StepFunction_GotBite (engine/overworld/map_objects.asm:1430) is one byte
|
||||
-- of animation: OBJECT_SPRITE_Y_OFFSET flipped between 0 and 1 once a
|
||||
-- frame for the length of the bite, which is the rod jerking in the
|
||||
-- player's hands. The cast holds still, so only the bite bobs.
|
||||
-- player's hands.
|
||||
if self.player then
|
||||
self.player.spriteYOffset =
|
||||
(st.phase == "bite" and st.timer % 2 == 1) and 1 or 0
|
||||
@@ -4801,34 +4820,33 @@ function World:updateFishing()
|
||||
if self.player then self.player.spriteYOffset = 0 end
|
||||
if st.phase == "cast" then
|
||||
if st.outcome == "battle" then
|
||||
-- Script_GotABite: four fish_got_bite bobs with the EMOTE_SHOCK bubble
|
||||
-- over the player, then `pause 40` before the rod comes back.
|
||||
st.phase = "bite"
|
||||
st.timer = FISH_BITE_FRAMES
|
||||
self:showEmote(EMOTE_SHOCK, 0, FISH_BITE_FRAMES)
|
||||
return
|
||||
end
|
||||
-- Script_NotEvenANibble (queued by $1 .FishNoBite) and
|
||||
-- Script_NotEvenANibble2 (by $4 .FishNoFish) differ only in the
|
||||
-- wFishingResult they record; both write RodNothingText and fall through
|
||||
-- to the same PutTheRodAway.
|
||||
st.phase = "done"
|
||||
self:showText(Strings(TEXT_ROD_NOTHING), function() self.fishing = nil end)
|
||||
self:showText(Strings(TEXT_ROD_NOTHING), function()
|
||||
self.fishing = nil
|
||||
if self.player then
|
||||
self.player.fishing = nil
|
||||
self.player.fishingState = nil
|
||||
end
|
||||
end)
|
||||
return
|
||||
end
|
||||
if st.phase == "bite" then
|
||||
st.phase = "done"
|
||||
self:showText(Strings(TEXT_ROD_BITE), function()
|
||||
local wild = st.wild
|
||||
-- PutTheRodAway and closetext come before startbattle, and the state has
|
||||
-- to be gone before the battle is pushed or World:busy would still be
|
||||
-- holding the world when it returns.
|
||||
self.fishing = nil
|
||||
-- FishFunction's `.goodtofish` writes BATTLETYPE_FISH into wBattleType
|
||||
-- alongside the species and level it hooked (engine/events/overworld.asm),
|
||||
-- which is the one condition LureBallMultiplier reads for its x3.
|
||||
if self.player then
|
||||
self.player.fishing = nil
|
||||
self.player.fishingState = nil
|
||||
end
|
||||
if wild then self:startBattle({ wild = wild, battleType = "fish" }) end
|
||||
end)
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
@@ -7819,7 +7837,10 @@ function World:drawGrassOver(entity, ox, oy, s)
|
||||
local tilePalettes = tileset.tilePalettes
|
||||
local tilesPerRow = tileset.tilesPerRow or 16
|
||||
local aw, ah = atlas:getDimensions()
|
||||
local rx, ry = entity.px, entity.py + 4
|
||||
-- Only draw the bottom 8px tile row of the cell (ty = py + 8) over the feet,
|
||||
-- matching Gen 1's drawCellBottomRaw. Starting at py + 4 sampled the top
|
||||
-- tile row and drew grass tufts over the face and torso.
|
||||
local rx, ry = entity.px, entity.py + 8
|
||||
self.grassQuad = self.grassQuad or G.newQuad(0, 0, 8, 8, aw, ah)
|
||||
local quad = self.grassQuad
|
||||
G.setColor(1, 1, 1, 1)
|
||||
@@ -9794,13 +9815,14 @@ function World:drawPeople(s, billboard)
|
||||
else
|
||||
entry.npc:draw(ox, oy, s)
|
||||
end
|
||||
-- ShakeGrass rustle only. The cart also ORs OAM_PRIO onto the lower
|
||||
-- 16x8 (drawGrassOver / IN_GRASS) so the BG tuft covers the feet, but
|
||||
-- stacking that plain grass tile on top of the character with the
|
||||
-- rustle reads as a double overlay here -- keep the walk-through anim.
|
||||
-- ShakeGrass rustle only while moving; drawGrassOver when standing/in grass
|
||||
-- so the BG tuft covers the feet.
|
||||
-- Only the current map's own entities: a ghost's cells belong to a
|
||||
-- neighbour's block list.
|
||||
if entry.ox == 0 and entry.oy == 0 then
|
||||
if entity.inGrass and not (entity.grassShake and entity.moving) then
|
||||
self:drawGrassOver(entity, ox, oy, s)
|
||||
end
|
||||
self:drawGrassShake(entity, ox, oy, s)
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user