Bugs and stuff (#146)

* main menu scrollable when over 8 items

* buggies

* more buggies

* Lorelei, Bruno, and Agatha now push their AfterBattle text right after a win

* more and more bugs
This commit is contained in:
bryanthaboi
2026-07-24 09:26:43 -04:00
committed by GitHub
parent bfba1f7bb7
commit 819150f50f
22 changed files with 836 additions and 61 deletions
+36 -12
View File
@@ -2431,6 +2431,27 @@ function OverworldState:runVictoryHook()
if hooks and hooks.onVictory then hooks.onVictory(Game, self) end
end
-- pokered player sprite is fixed at screen ($40, $3c). TrainerEngage reads
-- the NPC's 8-bit SPRITESTATEDATA1 X/Y pixels and CalcDifference; engage
-- distance is stored as range<<4 (pixels). There is no tile LOS check for
-- interposed NPCs / walls -- but unsigned 8-bit Y makes a sprite exactly 4
-- tiles north of the player sit at Y=$fc, so |$3c-$fc|=$c0 and a range-4
-- DOWN trainer does not engage that tile (Route 9 Bug Catcher / issue #76).
local PLAYER_SCREEN_X, PLAYER_SCREEN_Y = 0x40, 0x3c
local function u8(n) return n % 256 end
local function calcDiff(a, b)
a, b = u8(a), u8(b)
return a >= b and a - b or b - a
end
local function trainerSightPixelDist(npc, player, horizontal)
if horizontal then
return calcDiff(PLAYER_SCREEN_X,
u8(PLAYER_SCREEN_X + (npc.cellX - player.cellX) * 16))
end
return calcDiff(PLAYER_SCREEN_Y,
u8(PLAYER_SCREEN_Y + (npc.cellY - player.cellY) * 16))
end
-- STAY trainers with a facing spot the player crossing their line of
-- sight (range from the extracted trainer headers), walk up and battle.
function OverworldState:checkTrainerSight()
@@ -2448,22 +2469,23 @@ function OverworldState:checkTrainerSight()
local range = header and header.range or 0
local vec = DIRVEC[npc.facing]
if range > 0 and vec then
local dist
local dist, horizontal
if vec[1] ~= 0 and npc.cellY == p.cellY then
dist = (p.cellX - npc.cellX) * vec[1]
horizontal = true
elseif vec[2] ~= 0 and npc.cellX == p.cellX then
dist = (p.cellY - npc.cellY) * vec[2]
horizontal = false
end
-- pokered's TrainerEngage / CheckSpriteCanSeePlayer compares screen
-- coordinates only (home/trainers.asm, engine/overworld/
-- trainer_sight.asm) -- there is no line-of-sight obstruction check.
-- An aligned trainer within range engages through interposed NPCs and
-- unwalkable tiles, and the scripted walk-up below (scriptMove) also
-- ignores collision, so the trainer simply walks/overlaps through
-- anything on the line -- exactly as OAM sprites overlap on hardware.
if dist and dist >= 1 and dist <= range then
self:startTrainerApproach(npc, dist)
return
-- Screen-pixel range (CheckSpriteCanSeePlayer), not cell count:
-- same facing-line rule as before, but the $fc Y quirk excludes the
-- 4-tiles-north tile that cell math would still count as in range.
if dist and dist >= 1 then
local pixelDist = trainerSightPixelDist(npc, p, horizontal)
if pixelDist > 0 and pixelDist <= range * 16 then
self:startTrainerApproach(npc, dist)
return
end
end
end
end
@@ -3492,7 +3514,9 @@ function OverworldState:billboard(fx, fy, vw, vh, colors, keyed, drawFn)
end
function OverworldState:drawWorld()
-- advance the water/flower tile animation (runs under dialogs too)
-- advance the water/flower tile animation (runs under dialogs too).
-- TileRenderer.tick uses wall-clock 60Hz steps so display refresh rate
-- does not speed or slow the cycle (issue #4).
require("src.render.TileRenderer").tick()
-- let the renderer know whether a spinner puzzle is currently sliding
-- the player, so it can flicker the arrow tiles between the blur and
+20 -4
View File
@@ -81,6 +81,14 @@ end
-- Advance one fixed step; returns true when a step just completed.
function Player:update()
-- land-frame walk pose lasts only through the draw after completion;
-- the next update (idle or a chained step) clears it
self.stepLanded = false
-- Ledge-hop arc is cosmetic but must track the fixed 60Hz logic step,
-- not love.draw's display refresh (issue #4: >59fps ended early).
if self.hopFrames and self.hopFrames > 0 then
self.hopFrames = self.hopFrames - 1
end
if self.turnTimer > 0 then
self.turnTimer = self.turnTimer - 1
end
@@ -109,6 +117,11 @@ function Player:update()
self.px, self.py = self.cellX * 16, self.cellY * 16
self.moving = false
self.stepFlip = not self.stepFlip
-- keep animClock's pose on this frame (issue #82): bike steps land
-- mid-cycle (animClock % 16 == 8), and walkPhase used to snap to
-- stand whenever moving cleared — a stand flash every tile on the
-- bike, and sometimes after dismount when the clock is desynced
self.stepLanded = true
return true
end
return false
@@ -119,7 +132,7 @@ function Player:facingCell()
end
function Player:walkPhase()
if not self.moving then return 0 end
if not self.moving and not self.stepLanded then return 0 end
-- walk frame during the middle of each 16-frame animation cycle
local p = (self.animClock or self.progress) % 16
return (p >= 4 and p < 12) and 1 or 0
@@ -129,10 +142,13 @@ local SPIN_ORDER = { "down", "left", "up", "right" }
function Player:draw(camX, camY)
local py = self.py
-- ledge hops arc (set for 2 cells by the ledge handler); surfing bobs
-- ledge hops arc (set for 2 cells by the ledge handler); surfing bobs.
-- hopFrames counts down in Player:update (fixed step), never here.
if self.hopFrames and self.hopFrames > 0 then
self.hopFrames = self.hopFrames - 1
local t = 1 - self.hopFrames / (self.hopTotal or 32)
local total = self.hopTotal or 32
-- update runs before draw, so remaining N means N steps already
-- consumed this hop → t matches the old draw-side post-decrement phase
local t = 1 - self.hopFrames / total
py = py - math.floor(10 * math.sin(t * math.pi) + 0.5)
-- the shadow stays on the ground under the jumper: one 8x8 tile
-- mirrored into a 2x2 block (normal/XFLIP/YFLIP/both) whose top-left