mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-13 00:40:59 +02:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cecb24bb0e | |||
| b99b497893 | |||
| e16a45d6ba | |||
| 0e29046c31 | |||
| 5cbada493a | |||
| 4c79db3d5d | |||
| c7e46b6563 |
@@ -78,6 +78,14 @@ function love.conf(t)
|
||||
-- player to copy their ROM there instead of needing a native file
|
||||
-- picker (LOVE 11.5 on Android has none -- see src/import/RomImporter.lua).
|
||||
t.externalstorage = osName == "Android"
|
||||
-- LOVE 11.x exposes the phone's accelerometer as a 3-axis joystick by
|
||||
-- default. Gravity keeps one axis pinned near +/-1.0 whenever the phone
|
||||
-- is held upright, so it streams past-deadzone joystickaxis events that
|
||||
-- both hid the touch overlay every instant (it reads as "a controller is
|
||||
-- in use") and, via the generic-joystick axis mapping (#459), walked the
|
||||
-- player around by tilt. Nothing in the game reads the accelerometer,
|
||||
-- so drop the device entirely (#468).
|
||||
t.accelerometerjoystick = false
|
||||
else
|
||||
t.window.resizable = true
|
||||
end
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
|
||||
@@ -491,21 +491,36 @@ function Game:gamepadaxis(joystick, axis, value)
|
||||
Input:gamepadaxis(joystick, axis, value)
|
||||
end
|
||||
|
||||
-- conf.lua turns the mobile accelerometer-joystick off (#468), but guard the
|
||||
-- generic joystick path anyway: any sensor-style device that still reaches us
|
||||
-- has gravity pinning an axis past the deadzone, which would hide the touch
|
||||
-- overlay every instant and steer the player by tilt through the axis-1/2
|
||||
-- mapping (#459). Real controllers arrive as SDL gamepads or named sticks,
|
||||
-- never as "* Accelerometer".
|
||||
local function isAccelerometer(joystick)
|
||||
local name = joystick and joystick.getName and joystick:getName()
|
||||
return name ~= nil and name:lower():find("accelerometer", 1, true) ~= nil
|
||||
end
|
||||
|
||||
function Game:joystickpressed(joystick, button)
|
||||
if isAccelerometer(joystick) then return end
|
||||
TouchControls:noteGamepad()
|
||||
Input:joystickpressed(joystick, button)
|
||||
end
|
||||
|
||||
function Game:joystickreleased(joystick, button)
|
||||
if isAccelerometer(joystick) then return end
|
||||
Input:joystickreleased(joystick, button)
|
||||
end
|
||||
|
||||
function Game:joystickaxis(joystick, axis, value)
|
||||
if isAccelerometer(joystick) then return end
|
||||
if math.abs(value) > 0.5 then TouchControls:noteGamepad() end
|
||||
Input:joystickaxis(joystick, axis, value)
|
||||
end
|
||||
|
||||
function Game:joystickhat(joystick, hat, direction)
|
||||
if isAccelerometer(joystick) then return end
|
||||
if direction ~= "c" then TouchControls:noteGamepad() end
|
||||
Input:joystickhat(joystick, hat, direction)
|
||||
end
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
}))
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user