Merge pull request #480 from bryanthaboi/dev

johnny boys fixes
This commit is contained in:
bryanthaboi
2026-07-30 13:55:19 -04:00
committed by GitHub
7 changed files with 201 additions and 3 deletions
+4
View File
@@ -183,6 +183,7 @@ M.BILLS_HOUSE = {
overworld = ow },
"BILLS_HOUSE", "BILLSHOUSE_BILL_POKEMON")
game.save.flags.EVENT_BILL_SAID_USE_CELL_SEPARATOR = true
require("src.world.PikachuFollower").onBillEnteredMachine(game, ow)
done()
end
if ow.player.facing == "down" then
@@ -273,6 +274,9 @@ M.BILLS_HOUSE = {
Commands.hide_object(ctx, "BILLS_HOUSE", "BILLSHOUSE_BILL1")
Commands.show_object(ctx, "BILLS_HOUSE", "BILLSHOUSE_BILL2")
end
if not flags.EVENT_MET_BILL_2 then
require("src.world.PikachuFollower").onBillsHouseEnter(game, ow)
end
end,
}
+1
View File
@@ -2093,6 +2093,7 @@ function OverworldState:billsHouseBillExits()
local Commands = require("src.script.Commands")
local ctx = { game = Game, save = Game.save, overworld = self }
Commands.show_object(ctx, "BILLS_HOUSE", "BILLSHOUSE_BILL1")
require("src.world.PikachuFollower").onBillExitedMachine(Game, self)
local function done()
Game.save.flags.EVENT_MET_BILL = true
Game.save.flags.EVENT_MET_BILL_2 = true
+66
View File
@@ -189,6 +189,9 @@ function PikachuFollower.current(ow)
end
function PikachuFollower.onMapEntered(game, ow, opts)
-- Bill's House owns a short scripted scene that deliberately keeps
-- Pikachu off the normal trailing loop. A new map instance ends it.
ow.pikachuBillsScene = nil
remove(ow)
if not shouldSpawn(game, ow) then return end
-- opts.keepPikachu is the follower a connection crossing kept alive:
@@ -393,6 +396,7 @@ end
-- (pikachu_follow.asm keeps it one walk step behind)
function PikachuFollower.update(game, ow)
if ow.pikaHop then return end -- the counter hop owns the follower (#417)
if ow.pikachuBillsScene then return end
local npc = findFollower(ow)
if not npc then
if shouldSpawn(game, ow) then PikachuFollower.onMapEntered(game, ow) end
@@ -741,6 +745,68 @@ function PikachuFollower.picLift(emote)
return 0
end
-- Bill's House has three map-scripted Yellow companion beats
-- (BillsHouseScript0/2/5): Pikachu walks over to investigate Bill, waits at
-- the cell separator, then reacts when Bill reappears. Keep it at the
-- machine until this map instance is discarded, just like the cartridge's
-- disabled following state.
local function billsHouseEmotion(game, ow, npc, bubble)
local Sprites = require("src.pokemon.Sprites")
ow.emote = {
npc = npc, frames = 50, bubble = bubbleIndex(game, bubble) or false,
pikaPic = Sprites.path(game.data, "PIKACHU", "front",
{ kind = "overworld" }),
}
end
local function movePikachu(ow, npc, steps, onDone)
npc.goalX, npc.goalY = nil, nil
idleReset(npc)
local function nextStep(i)
local step = steps[i]
if not step then
if onDone then onDone() end
return
end
ow:scriptMove(npc, step[1], step[2], function() nextStep(i + 1) end)
end
nextStep(1)
end
function PikachuFollower.onBillsHouseEnter(game, ow)
if not (GameVersion.isYellow() and ow.map and ow.map.id == "BILLS_HOUSE") then
return
end
if game.save.flags.EVENT_MET_BILL_2 then return end
local npc = findFollower(ow)
if not npc then return end
ow.pikachuBillsScene = true
movePikachu(ow, npc, { { "right", 3 }, { "up", 1 } }, function()
billsHouseEmotion(game, ow, npc, "QUESTION_BUBBLE")
end)
end
function PikachuFollower.onBillEnteredMachine(game, ow)
if not (GameVersion.isYellow() and ow.pikachuBillsScene) then return end
local npc = findFollower(ow)
if not npc then return end
local steps = ow.player.facing == "down"
and { { "up", 3 } }
or { { "up", 1 }, { "left", 1 }, { "up", 2 }, { "right", 1 } }
movePikachu(ow, npc, steps, function()
billsHouseEmotion(game, ow, npc, "QUESTION_BUBBLE")
end)
end
function PikachuFollower.onBillExitedMachine(game, ow)
if not (GameVersion.isYellow() and ow.pikachuBillsScene) then return end
local npc = findFollower(ow)
if not npc then return end
idleReset(npc)
npc.facing = "left"
billsHouseEmotion(game, ow, npc, "EXCLAMATION_BUBBLE")
end
-- ---------------------------------------------------------------------
-- PikachuWalksToNurseJoy (engine/pikachu/pikachu_emotions.asm, run by
-- engine/events/pokecenter.asm once the heal is accepted): the companion
+89
View File
@@ -0,0 +1,89 @@
-- Yellow starts Bill's House with Pikachu's confused reaction. The map
-- script owns that one-shot, while PikachuFollower owns the movement.
package.path = "./?.lua;./?/init.lua;" .. package.path
local S = require("tests.harness").suite("parity Yellow Bill's Pikachu")
local check = S.check
local entered = 0
local originalFollower = package.loaded["src.world.PikachuFollower"]
package.loaded["src.world.PikachuFollower"] = {
onBillsHouseEnter = function()
entered = entered + 1
end,
}
local story = dofile("data/scripts/story.lua")
local game = { save = { flags = {} } }
story.BILLS_HOUSE.onEnter(game, {})
check(entered == 1,
"entering Bill's House before meeting Bill starts Pikachu's reaction")
entered = 0
game.save.flags.EVENT_MET_BILL_2 = true
story.BILLS_HOUSE.onEnter(game, {})
check(entered == 0,
"Pikachu's Bill reaction does not replay after Bill is met")
package.loaded["src.world.PikachuFollower"] = originalFollower
local GameVersion = require("src.core.GameVersion")
local PikachuFollower = require("src.world.PikachuFollower")
GameVersion.set("yellow")
local npc = {
pikachuFollower = true, cellX = 3, cellY = 8, px = 48, py = 128,
facing = "up",
}
local moves = {}
local yellowGame = {
save = { flags = {} },
data = {
pokemon = { PIKACHU = { spriteFront = "pikachu.png" } },
field = { emotionBubbles = {
bubbles = {
{ name = "QUESTION_BUBBLE" }, { name = "EXCLAMATION_BUBBLE" },
},
} },
},
}
local ow = {
map = { id = "BILLS_HOUSE" }, npcs = { npc }, entities = { npc },
player = { cellX = 3, cellY = 7 },
scriptMove = function(_, entity, dir, tiles, onDone)
moves[#moves + 1] = { entity = entity, dir = dir, tiles = tiles,
onDone = onDone }
end,
}
PikachuFollower.onBillsHouseEnter(yellowGame, ow)
check(ow.pikachuBillsScene and #moves == 1
and moves[1].entity == npc and moves[1].dir == "right"
and moves[1].tiles == 3,
"Bill's House entry parks Pikachu and walks it to Bill")
moves[1].onDone()
check(#moves == 2 and moves[2].dir == "up" and moves[2].tiles == 1,
"Pikachu finishes its cartridge entry route beside Bill")
moves[2].onDone()
check(ow.emote and ow.emote.bubble == 1,
"Pikachu shows its confused reaction after reaching Bill")
ow.player.facing = "down"
PikachuFollower.onBillEnteredMachine(yellowGame, ow)
check(#moves == 3 and moves[3].dir == "up" and moves[3].tiles == 3,
"Pikachu walks to the cell separator after Bill enters it")
moves[3].onDone()
check(ow.emote and ow.emote.bubble == 1,
"Pikachu wonders at the cell separator")
npc.goalX, npc.goalY = 9, 9
PikachuFollower.update(yellowGame, ow)
check(npc.goalX == 9 and npc.goalY == 9,
"Pikachu stays parked in Bill's House during the scene")
PikachuFollower.onBillExitedMachine(yellowGame, ow)
check(ow.emote and ow.emote.bubble == 2 and npc.facing == "left",
"Pikachu reacts when Bill comes back out")
GameVersion.set("red")
S.finish()
+28
View File
@@ -431,6 +431,34 @@ do
for _, bak in ipairs(FsIo.globPrefix(tmpPath .. ".bak-")) do os.remove(bak) end
end
do
-- #476: on Android a high-DPI 1560x720 capture can leave the editor with
-- only a compact logical viewport. The Items picker must not hand a
-- negative list height to love.graphics.setScissor in that layout.
local tmpPath = os.tmpname() .. "-items-compact-save.lua"
local f = io.open(tmpPath, "wb")
f:write(SaveData.encode(SaveData.newGame()))
f:close()
local oldDimensions = love.graphics.getDimensions
local oldScissor = love.graphics.setScissor
love.graphics.getDimensions = function() return 520, 240 end
love.graphics.setScissor = function(_, _, width, height)
if width and (width < 0 or height < 0) then
error("Can't set scissor with negative width and/or height.")
end
end
App.load(tmpPath, { version = "red" })
App.getState().tab = "items"
local ok, err = pcall(App.draw)
check(ok, "the Items tab draws in a compact Android viewport: " .. tostring(err))
love.graphics.getDimensions = oldDimensions
love.graphics.setScissor = oldScissor
os.remove(tmpPath)
for _, bak in ipairs(FsIo.globPrefix(tmpPath .. ".bak-")) do os.remove(bak) end
end
do
-- Whole-editor smoke test: every tab has to survive a real headless draw,
-- which is what catches a layout that divides by a nil font metric or
+1
View File
@@ -3278,6 +3278,7 @@ runSuites(orderedGlob("tests/parity_*.lua", {
"tests/parity_static.lua", "tests/parity_trashcans.lua",
"tests/parity_hof.lua", "tests/parity_trade_gift.lua",
"tests/parity_yellow_trades.lua",
"tests/parity_yellow_bills_pikachu.lua",
"tests/parity_intro.lua", "tests/parity_tilt.lua",
"tests/parity_gbcfx.lua",
}))
+12 -3
View File
@@ -381,13 +381,22 @@ end
-- Clip drawing to a rect (list bodies). No-ops under the headless stub.
function Kit.pushClip(x, y, w, h)
if G and G.setScissor then
G.setScissor(math.floor(x), math.floor(y), math.ceil(w), math.ceil(h))
-- A compact mobile viewport can leave a panel with no room for a list.
-- LÖVE rejects negative scissor dimensions, so treat an exhausted clip
-- region as empty instead of passing invalid geometry through to it.
Kit._clipActive = G and G.setScissor ~= nil
if Kit._clipActive then
if w <= 0 or h <= 0 then
G.setScissor(0, 0, 0, 0)
else
G.setScissor(math.floor(x), math.floor(y), math.ceil(w), math.ceil(h))
end
end
end
function Kit.popClip()
if G and G.setScissor then G.setScissor() end
if Kit._clipActive and G and G.setScissor then G.setScissor() end
Kit._clipActive = false
end
return Kit