mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-14 01:11:07 +02:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| eea0dec4a6 | |||
| 25d241f12c | |||
| 8a649e8d95 | |||
| 7b208043b9 | |||
| 26dfad0682 | |||
| d3010dc060 | |||
| 53e1a535dd |
@@ -331,6 +331,30 @@ function love.gamepadaxis(joystick, axis, value)
|
||||
Game:gamepadaxis(joystick, axis, value)
|
||||
end
|
||||
|
||||
function love.joystickpressed(joystick, button)
|
||||
if editorMode then return end
|
||||
if Importer then return Importer:joystickpressed(joystick, button) end
|
||||
Game:joystickpressed(joystick, button)
|
||||
end
|
||||
|
||||
function love.joystickreleased(joystick, button)
|
||||
if editorMode then return end
|
||||
if Importer then return Importer:joystickreleased(joystick, button) end
|
||||
Game:joystickreleased(joystick, button)
|
||||
end
|
||||
|
||||
function love.joystickaxis(joystick, axis, value)
|
||||
if editorMode then return end
|
||||
if Importer then return Importer:joystickaxis(joystick, axis, value) end
|
||||
Game:joystickaxis(joystick, axis, value)
|
||||
end
|
||||
|
||||
function love.joystickhat(joystick, hat, direction)
|
||||
if editorMode then return end
|
||||
if Importer then return Importer:joystickhat(joystick, hat, direction) end
|
||||
Game:joystickhat(joystick, hat, direction)
|
||||
end
|
||||
|
||||
function love.joystickremoved(joystick)
|
||||
if editorMode then return end
|
||||
if Importer then return end
|
||||
|
||||
@@ -38,6 +38,22 @@ local CONSTANT_DEFAULTS = {
|
||||
},
|
||||
}
|
||||
|
||||
-- data/events/trades.asm in Pokemon Yellow has its own TradeMons table.
|
||||
-- Old Yellow imports were built from the Red manifest, so correct that
|
||||
-- table after loading as well as in the fixed import manifest below.
|
||||
local YELLOW_TRADES = {
|
||||
{ give = "LICKITUNG", get = "DUGTRIO", dialogset = 1, nickname = "GURIO" },
|
||||
{ give = "CLEFAIRY", get = "MR_MIME", dialogset = 1, nickname = "MILES" },
|
||||
{ give = "BUTTERFREE", get = "BEEDRILL", dialogset = 3, nickname = "STINGER" },
|
||||
{ give = "KANGASKHAN", get = "MUK", dialogset = 1, nickname = "STICKY" },
|
||||
{ give = "MEW", get = "MEW", dialogset = 3, nickname = "BART" },
|
||||
{ give = "TANGELA", get = "PARASECT", dialogset = 1, nickname = "SPIKE" },
|
||||
{ give = "PIDGEOT", get = "PIDGEOT", dialogset = 2, nickname = "MARTY" },
|
||||
{ give = "GOLDUCK", get = "RHYDON", dialogset = 2, nickname = "BUFFY" },
|
||||
{ give = "GROWLITHE", get = "DEWGONG", dialogset = 3, nickname = "CEZANNE" },
|
||||
{ give = "CUBONE", get = "MACHOKE", dialogset = 3, nickname = "RICKY" },
|
||||
}
|
||||
|
||||
-- field.boot is the total-conversion override point for the new game; the
|
||||
-- values match what SaveData.newGame and the Oak speech used to inline.
|
||||
local BOOT_DEFAULTS = {
|
||||
@@ -58,6 +74,12 @@ local function copy(value)
|
||||
return out
|
||||
end
|
||||
|
||||
function Data:applyVersionedFieldData()
|
||||
if require("src.core.GameVersion").isYellow() then
|
||||
self.field.trades = copy(YELLOW_TRADES)
|
||||
end
|
||||
end
|
||||
|
||||
-- Fills only what the cache is missing, so an importer that learns to
|
||||
-- stamp one of these keys silently takes over from the engine.
|
||||
function Data:seedDefaults()
|
||||
@@ -77,6 +99,7 @@ function Data:seedDefaults()
|
||||
if constants.dexDigits == nil then
|
||||
constants.dexDigits = math.max(3, #tostring(constants.dexSize))
|
||||
end
|
||||
self:applyVersionedFieldData()
|
||||
local boot = self.field.boot
|
||||
if boot == nil then
|
||||
boot = {}
|
||||
|
||||
@@ -491,6 +491,25 @@ function Game:gamepadaxis(joystick, axis, value)
|
||||
Input:gamepadaxis(joystick, axis, value)
|
||||
end
|
||||
|
||||
function Game:joystickpressed(joystick, button)
|
||||
TouchControls:noteGamepad()
|
||||
Input:joystickpressed(joystick, button)
|
||||
end
|
||||
|
||||
function Game:joystickreleased(joystick, button)
|
||||
Input:joystickreleased(joystick, button)
|
||||
end
|
||||
|
||||
function Game:joystickaxis(joystick, axis, value)
|
||||
if math.abs(value) > 0.5 then TouchControls:noteGamepad() end
|
||||
Input:joystickaxis(joystick, axis, value)
|
||||
end
|
||||
|
||||
function Game:joystickhat(joystick, hat, direction)
|
||||
if direction ~= "c" then TouchControls:noteGamepad() end
|
||||
Input:joystickhat(joystick, hat, direction)
|
||||
end
|
||||
|
||||
-- Window focus/visibility flips: a release due while unfocused/hidden can
|
||||
-- be swallowed by the OS. Reset on both edges -- gaining focus with a
|
||||
-- physically held key won't re-fire keypressed, so trusting leftover
|
||||
|
||||
@@ -39,6 +39,20 @@ local DEFAULT_GAMEPAD_BINDINGS = {
|
||||
local STICK_ON = 0.5
|
||||
local STICK_OFF = 0.3
|
||||
|
||||
-- Generic SDL joysticks expose the left stick as the first two numbered
|
||||
-- axes and the D-pad as a hat. This is common on Linux handhelds whose
|
||||
-- controller has no game-controller database entry.
|
||||
local RAW_BUTTON_BINDINGS = {
|
||||
[1] = "a", [2] = "b",
|
||||
[7] = "select", [8] = "start", [9] = "select", [10] = "start",
|
||||
}
|
||||
|
||||
local HAT_DIRECTIONS = {
|
||||
u = { "up" }, d = { "down" }, l = { "left" }, r = { "right" },
|
||||
lu = { "left", "up" }, ru = { "right", "up" },
|
||||
ld = { "left", "down" }, rd = { "right", "down" },
|
||||
}
|
||||
|
||||
function Input:init()
|
||||
self:applyBindings(nil)
|
||||
self:reset()
|
||||
@@ -79,6 +93,7 @@ function Input:reset()
|
||||
self.sources = {}
|
||||
self.stickAxis = { x = 0, y = 0 }
|
||||
self.stickDir = nil
|
||||
self.hatDirs = {}
|
||||
end
|
||||
|
||||
-- Multiple physical sources (W + Up, d-pad + stick, etc.) can claim the
|
||||
@@ -179,6 +194,16 @@ function Input:gamepadreleased(joystick, button)
|
||||
end
|
||||
end
|
||||
|
||||
function Input:joystickpressed(joystick, button)
|
||||
local btn = RAW_BUTTON_BINDINGS[button]
|
||||
if btn then press(self, btn, "joy:" .. button) end
|
||||
end
|
||||
|
||||
function Input:joystickreleased(joystick, button)
|
||||
local btn = RAW_BUTTON_BINDINGS[button]
|
||||
if btn then release(self, btn, "joy:" .. button) end
|
||||
end
|
||||
|
||||
-- left stick treated as a continuous held direction, same 4-way rule as
|
||||
-- the touch swipe d-pad: whichever axis has the larger magnitude wins.
|
||||
function Input:gamepadaxis(joystick, axis, value)
|
||||
@@ -214,6 +239,26 @@ function Input:gamepadaxis(joystick, axis, value)
|
||||
end
|
||||
end
|
||||
|
||||
function Input:joystickaxis(joystick, axis, value)
|
||||
if axis == 1 then
|
||||
self:gamepadaxis(joystick, "leftx", value)
|
||||
elseif axis == 2 then
|
||||
self:gamepadaxis(joystick, "lefty", value)
|
||||
end
|
||||
end
|
||||
|
||||
function Input:joystickhat(joystick, hat, direction)
|
||||
local source = "hat:" .. hat
|
||||
for _, btn in ipairs(self.hatDirs[hat] or {}) do
|
||||
release(self, btn, source)
|
||||
end
|
||||
local dirs = HAT_DIRECTIONS[direction] or {}
|
||||
for _, btn in ipairs(dirs) do
|
||||
press(self, btn, source)
|
||||
end
|
||||
self.hatDirs[hat] = dirs
|
||||
end
|
||||
|
||||
function Input:isDown(btn)
|
||||
return self.state[btn] or false
|
||||
end
|
||||
|
||||
@@ -540,6 +540,7 @@ function RomImporter.new(onComplete, opts)
|
||||
_padCursorActive = false,
|
||||
_padAxis = { leftx = 0, lefty = 0, righty = 0 },
|
||||
_padDir = {},
|
||||
_rawHatDirs = {},
|
||||
_padInited = false,
|
||||
}, RomImporter)
|
||||
|
||||
@@ -1184,6 +1185,36 @@ function RomImporter:gamepadaxis(_, axis, value)
|
||||
end
|
||||
end
|
||||
|
||||
function RomImporter:joystickpressed(joystick, button)
|
||||
if button == 1 then self:gamepadpressed(joystick, "a") end
|
||||
end
|
||||
|
||||
function RomImporter:joystickreleased(joystick, button)
|
||||
if button == 1 then self:gamepadreleased(joystick, "a") end
|
||||
end
|
||||
|
||||
function RomImporter:joystickaxis(joystick, axis, value)
|
||||
if axis == 1 then
|
||||
self:gamepadaxis(joystick, "leftx", value)
|
||||
elseif axis == 2 then
|
||||
self:gamepadaxis(joystick, "lefty", value)
|
||||
end
|
||||
end
|
||||
|
||||
function RomImporter:joystickhat(_, hat, direction)
|
||||
for _, dir in ipairs(self._rawHatDirs[hat] or {}) do
|
||||
self._padDir[dir] = nil
|
||||
end
|
||||
local dirs = ({
|
||||
u = { "dpup" }, d = { "dpdown" }, l = { "dpleft" }, r = { "dpright" },
|
||||
lu = { "dpleft", "dpup" }, ru = { "dpright", "dpup" },
|
||||
ld = { "dpleft", "dpdown" }, rd = { "dpright", "dpdown" },
|
||||
})[direction] or {}
|
||||
for _, dir in ipairs(dirs) do self._padDir[dir] = true end
|
||||
self._rawHatDirs[hat] = dirs
|
||||
if #dirs > 0 then self:_activatePadCursor() end
|
||||
end
|
||||
|
||||
-- Player pressed Play on a game whose ROM is imported: hand off to boot.
|
||||
function RomImporter:play(version)
|
||||
if self.workState == "working" then return end
|
||||
|
||||
+3
-1
@@ -50,7 +50,9 @@ local CALLBACK_NAMES = {
|
||||
"keypressed", "keyreleased", "textinput",
|
||||
"mousepressed", "mousereleased", "mousemoved", "wheelmoved",
|
||||
"touchpressed", "touchmoved", "touchreleased",
|
||||
"gamepadpressed", "gamepadreleased", "gamepadaxis", "joystickremoved",
|
||||
"gamepadpressed", "gamepadreleased", "gamepadaxis",
|
||||
"joystickpressed", "joystickreleased", "joystickaxis", "joystickhat",
|
||||
"joystickremoved",
|
||||
"focus", "visible", "resize", "filedropped", "directorydropped",
|
||||
"errorhandler", "threaderror", "lowmemory",
|
||||
}
|
||||
|
||||
@@ -46,6 +46,51 @@ Input:step()
|
||||
check(Input:wasPressed("left"), "stick flick edges wasPressed")
|
||||
check(not Input:isDown("left"), "stick flick does not stick isDown")
|
||||
|
||||
-- Linux handhelds without an SDL game-controller mapping send raw joystick
|
||||
-- axes and D-pad hats instead of gamepad events.
|
||||
Input:reset()
|
||||
Input:joystickaxis(nil, 1, -0.9)
|
||||
Input:step()
|
||||
check(Input:isDown("left"), "raw joystick left axis holds left")
|
||||
Input:joystickaxis(nil, 1, 0)
|
||||
check(not Input:isDown("left"), "raw joystick axis release clears left")
|
||||
|
||||
Input:reset()
|
||||
Input:joystickhat(nil, 1, "u")
|
||||
Input:step()
|
||||
check(Input:isDown("up"), "raw joystick hat holds up")
|
||||
Input:joystickhat(nil, 1, "c")
|
||||
check(not Input:isDown("up"), "raw joystick hat release clears up")
|
||||
|
||||
Input:reset()
|
||||
Input:joystickpressed(nil, 1)
|
||||
Input:step()
|
||||
check(Input:isDown("a"), "raw joystick primary button presses A")
|
||||
|
||||
-- The launcher has a separate virtual cursor, so prove generic joystick
|
||||
-- events reach its left-stick and D-pad state too.
|
||||
local RomImporter = require("src.import.RomImporter")
|
||||
local importer = setmetatable({
|
||||
_padCursor = { x = 0, y = 0 }, _padCursorActive = false,
|
||||
_padAxis = { leftx = 0, lefty = 0, righty = 0 },
|
||||
_padDir = {}, _rawHatDirs = {}, _padInited = true,
|
||||
}, RomImporter)
|
||||
local clicked = false
|
||||
function importer:mousepressed(_, _, button)
|
||||
clicked = button == 1
|
||||
end
|
||||
importer:joystickaxis(nil, 1, -0.8)
|
||||
check(importer._padAxis.leftx == -0.8,
|
||||
"raw joystick left axis reaches the launcher cursor")
|
||||
importer:joystickhat(nil, 1, "r")
|
||||
check(importer._padDir.dpright,
|
||||
"raw joystick hat reaches the launcher cursor")
|
||||
importer:joystickhat(nil, 1, "c")
|
||||
check(not importer._padDir.dpright,
|
||||
"raw joystick hat release clears the launcher cursor")
|
||||
importer:joystickpressed(nil, 1)
|
||||
check(clicked, "raw joystick primary button clicks the launcher cursor")
|
||||
|
||||
-- Drivers that only inject pressQueue still get a one-step hold.
|
||||
Input:reset()
|
||||
table.insert(Input.pressQueue, "down")
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
-- Pokemon Yellow has a different TradeMons table from Red and Blue.
|
||||
-- The import manifest must agree, and Data:seedDefaults repairs Yellow
|
||||
-- caches made before that manifest carried the Yellow table (#453).
|
||||
|
||||
package.path = "./?.lua;./?/init.lua;" .. package.path
|
||||
if not _G.love then _G.love = require("tests.love_stub") end
|
||||
|
||||
local Data = require("src.core.Data")
|
||||
if not (Data.field and Data.field.trades) then Data:load() end
|
||||
local GameVersion = require("src.core.GameVersion")
|
||||
local S = require("tests.harness").suite("parity Yellow trades")
|
||||
local check, eq = S.check, S.eq
|
||||
|
||||
local oldVersion = GameVersion.get()
|
||||
local oldTrades = {}
|
||||
for i, trade in ipairs(Data.field.trades) do
|
||||
oldTrades[i] = {}
|
||||
for key, value in pairs(trade) do oldTrades[i][key] = value end
|
||||
end
|
||||
|
||||
GameVersion.set("yellow")
|
||||
Data:applyVersionedFieldData()
|
||||
|
||||
local expected = {
|
||||
{ "LICKITUNG", "DUGTRIO", "GURIO" },
|
||||
{ "CLEFAIRY", "MR_MIME", "MILES" },
|
||||
{ "BUTTERFREE", "BEEDRILL", "STINGER" },
|
||||
{ "KANGASKHAN", "MUK", "STICKY" },
|
||||
{ "MEW", "MEW", "BART" },
|
||||
{ "TANGELA", "PARASECT", "SPIKE" },
|
||||
{ "PIDGEOT", "PIDGEOT", "MARTY" },
|
||||
{ "GOLDUCK", "RHYDON", "BUFFY" },
|
||||
{ "GROWLITHE", "DEWGONG", "CEZANNE" },
|
||||
{ "CUBONE", "MACHOKE", "RICKY" },
|
||||
}
|
||||
|
||||
local manifestFile = assert(io.open("tools/rom_manifest_yellow.json", "r"))
|
||||
local manifest = manifestFile:read("*a")
|
||||
manifestFile:close()
|
||||
|
||||
eq(#Data.field.trades, #expected, "Yellow has ten in-game trade rows")
|
||||
for i, want in ipairs(expected) do
|
||||
local got = Data.field.trades[i]
|
||||
check(got.give == want[1] and got.get == want[2] and got.nickname == want[3],
|
||||
("Yellow trade %d is %s for %s (%s)"):format(i, want[1], want[2], want[3]))
|
||||
local row = ('"get": "%s",%%s+"give": "%s",%%s+"nickname": "%s"')
|
||||
:format(want[2], want[1], want[3])
|
||||
check(manifest:find(row) ~= nil,
|
||||
("Yellow manifest carries trade %d"):format(i))
|
||||
end
|
||||
|
||||
local ricky = Data.field.trades[10]
|
||||
check(ricky.give == "CUBONE" and ricky.get == "MACHOKE",
|
||||
"the Underground Path trade is Cubone for Machoke")
|
||||
|
||||
Data.field.trades = oldTrades
|
||||
GameVersion.set(oldVersion)
|
||||
|
||||
return S:finish()
|
||||
@@ -3277,6 +3277,7 @@ runSuites(orderedGlob("tests/parity_*.lua", {
|
||||
"tests/parity_flavor.lua", "tests/parity_trainer_sight.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_intro.lua", "tests/parity_tilt.lua",
|
||||
"tests/parity_gbcfx.lua",
|
||||
}))
|
||||
|
||||
@@ -382,6 +382,7 @@ def derive(red, pokeyellow, symbols_path):
|
||||
except SystemExit as exc:
|
||||
print(f"warning: parse_credits failed ({exc}); keeping Red credits")
|
||||
# TODO: hand-author a Yellow credits banner if pret layout drifts.
|
||||
yellow["field"]["trades"] = field.parse_trades(pokeyellow)
|
||||
finally:
|
||||
util.ASM_DEFINES = saved
|
||||
|
||||
|
||||
@@ -10552,63 +10552,63 @@
|
||||
"trades": [
|
||||
{
|
||||
"dialogset": 1,
|
||||
"get": "NIDORINA",
|
||||
"give": "NIDORINO",
|
||||
"nickname": "TERRY"
|
||||
"get": "DUGTRIO",
|
||||
"give": "LICKITUNG",
|
||||
"nickname": "GURIO"
|
||||
},
|
||||
{
|
||||
"dialogset": 1,
|
||||
"get": "MR_MIME",
|
||||
"give": "ABRA",
|
||||
"nickname": "MARCEL"
|
||||
"give": "CLEFAIRY",
|
||||
"nickname": "MILES"
|
||||
},
|
||||
{
|
||||
"dialogset": 3,
|
||||
"get": "BEEDRILL",
|
||||
"give": "BUTTERFREE",
|
||||
"nickname": "CHIKUCHIKU"
|
||||
"nickname": "STINGER"
|
||||
},
|
||||
{
|
||||
"dialogset": 1,
|
||||
"get": "SEEL",
|
||||
"give": "PONYTA",
|
||||
"nickname": "SAILOR"
|
||||
"get": "MUK",
|
||||
"give": "KANGASKHAN",
|
||||
"nickname": "STICKY"
|
||||
},
|
||||
{
|
||||
"dialogset": 3,
|
||||
"get": "FARFETCHD",
|
||||
"give": "SPEAROW",
|
||||
"nickname": "DUX"
|
||||
"get": "MEW",
|
||||
"give": "MEW",
|
||||
"nickname": "BART"
|
||||
},
|
||||
{
|
||||
"dialogset": 1,
|
||||
"get": "LICKITUNG",
|
||||
"give": "SLOWBRO",
|
||||
"nickname": "MARC"
|
||||
"get": "PARASECT",
|
||||
"give": "TANGELA",
|
||||
"nickname": "SPIKE"
|
||||
},
|
||||
{
|
||||
"dialogset": 2,
|
||||
"get": "JYNX",
|
||||
"give": "POLIWHIRL",
|
||||
"nickname": "LOLA"
|
||||
"get": "PIDGEOT",
|
||||
"give": "PIDGEOT",
|
||||
"nickname": "MARTY"
|
||||
},
|
||||
{
|
||||
"dialogset": 2,
|
||||
"get": "ELECTRODE",
|
||||
"give": "RAICHU",
|
||||
"nickname": "DORIS"
|
||||
"get": "RHYDON",
|
||||
"give": "GOLDUCK",
|
||||
"nickname": "BUFFY"
|
||||
},
|
||||
{
|
||||
"dialogset": 3,
|
||||
"get": "TANGELA",
|
||||
"give": "VENONAT",
|
||||
"nickname": "CRINKLES"
|
||||
"get": "DEWGONG",
|
||||
"give": "GROWLITHE",
|
||||
"nickname": "CEZANNE"
|
||||
},
|
||||
{
|
||||
"dialogset": 3,
|
||||
"get": "NIDORAN_F",
|
||||
"give": "NIDORAN_M",
|
||||
"nickname": "SPOT"
|
||||
"get": "MACHOKE",
|
||||
"give": "CUBONE",
|
||||
"nickname": "RICKY"
|
||||
}
|
||||
],
|
||||
"warpCarpets": {
|
||||
|
||||
Reference in New Issue
Block a user