mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-16 16:21:30 +02:00
Squadhing som bug (#130)
* squashy squash * squassshheee CLOSES #128 CLOSES #114 CLOSES #113 CLOSES #111 CLOSES #110 CLOSES #109 CLOSES #107 CLOSES #106 CLOSES #105 CLOSES #90 CLOSES #103 * brock guy fix CLOSES #39
This commit is contained in:
@@ -460,6 +460,13 @@ function Game:restoreSave(loaded, recovered)
|
||||
-- SaveData.load and skip on the format guard
|
||||
local activeMods = self.modStatus and self.modStatus.loaded
|
||||
SaveData.runMigrations(loaded, self.mods and self.mods.migrations, activeMods)
|
||||
-- Issue #103: 0.1.11 softlocks left CONTINUE in HALL_OF_FAME with
|
||||
-- lastOutdoor on Indigo. One-shot relocate + heal before validate.
|
||||
if SaveData.needsPostGameRescue(loaded) then
|
||||
SaveData.applyPostGameHome(loaded, self:bootConfig())
|
||||
local Pokemon = require("src.pokemon.Pokemon")
|
||||
for _, mon in ipairs(loaded.party or {}) do Pokemon.heal(mon) end
|
||||
end
|
||||
local modsDiff = SaveData.modsDiff(loaded, activeMods)
|
||||
local report = SaveData.validate(loaded, self.data)
|
||||
report.recovered = recovered
|
||||
|
||||
+6
-9
@@ -360,16 +360,13 @@ local function chipAwaitingFirstBuffer()
|
||||
and require("src.core.ChipAudio").awaitingFirstBuffer()
|
||||
end
|
||||
|
||||
-- is a playOnce jingle still sounding? (AnimateHealingMachine's
|
||||
-- .waitLoop2 holds the healing machine until MUSIC_PKMN_HEALED ends)
|
||||
-- is a playOnce jingle still in flight? (AnimateHealingMachine's
|
||||
-- .waitLoop2 / Mom heal / captain rub hold until MUSIC_PKMN_HEALED ends.)
|
||||
-- pendingRestore stays set from playOnce until Music.update restores the
|
||||
-- map theme, covering the threaded chip "empty QueueableSource" window
|
||||
-- where Source:isPlaying is briefly false before the first buffer lands.
|
||||
function Music.oneShotPlaying()
|
||||
if not state.pendingRestore then return false end
|
||||
-- threaded chip songs start silent for ~1 frame; that gap is not "over"
|
||||
if chipAwaitingFirstBuffer() then return true end
|
||||
local src = state.source
|
||||
if not src then return false end
|
||||
local ok, playing = pcall(src.isPlaying, src)
|
||||
return ok and playing or false
|
||||
return state.pendingRestore == true
|
||||
end
|
||||
|
||||
function Music.restoreMap(data)
|
||||
|
||||
@@ -813,6 +813,36 @@ function SaveData.defaultHeal(boot)
|
||||
return { map = map, x = boot.startX or 3, y = boot.startY or 6 }
|
||||
end
|
||||
|
||||
-- Post-credits home (issue #103). pokered left the player in HALL_OF_FAME
|
||||
-- after jp Init; this port places CONTINUE at the NewGameWarp bedroom and
|
||||
-- retargets LAST_MAP exits (Red's house mats) at the heal-point town so
|
||||
-- leaving the house does not dump the player back at Indigo Plateau.
|
||||
-- Marks postGameHomeOk so a later intentional HoF save is not relocated.
|
||||
function SaveData.applyPostGameHome(save, boot)
|
||||
boot = type(boot) == "table" and boot or {}
|
||||
local heal = SaveData.defaultHeal(boot)
|
||||
save.lastHeal = { map = heal.map, x = heal.x, y = heal.y }
|
||||
save.lastOutdoor = { id = heal.map, x = heal.x, y = heal.y }
|
||||
save.player = save.player or {}
|
||||
save.player.map = boot.startMap or "REDS_HOUSE_2F"
|
||||
save.player.x = boot.startX or 3
|
||||
save.player.y = boot.startY or 6
|
||||
save.player.facing = boot.startFacing or "down"
|
||||
save.postGameHomeOk = true
|
||||
return heal
|
||||
end
|
||||
|
||||
-- Softlocked 0.1.11 saves: still standing in HALL_OF_FAME after credits,
|
||||
-- with lastOutdoor on Indigo. One-shot rescue on CONTINUE.
|
||||
function SaveData.needsPostGameRescue(save)
|
||||
if not (save and save.player and save.player.map == "HALL_OF_FAME") then
|
||||
return false
|
||||
end
|
||||
if save.postGameHomeOk then return false end
|
||||
local hof = save.hallOfFame
|
||||
return type(hof) == "table" and #hof > 0
|
||||
end
|
||||
|
||||
function SaveData.newGame(boot)
|
||||
boot = type(boot) == "table" and boot or {}
|
||||
local map = boot.startMap or "REDS_HOUSE_2F"
|
||||
@@ -836,6 +866,10 @@ function SaveData.newGame(boot)
|
||||
},
|
||||
flags = {},
|
||||
inventory = {},
|
||||
-- Vanilla Gen1 seeds one Potion in the player's item PC
|
||||
-- (wBoxItems / players_pc.asm); existing saves keep whatever they
|
||||
-- already have — this only applies to New Game.
|
||||
pcItems = { POTION = 1 },
|
||||
party = {},
|
||||
box = {},
|
||||
money = boot.startMoney or 3000,
|
||||
|
||||
@@ -351,6 +351,18 @@ function RomImporter:setError(message, version)
|
||||
self.romData = nil
|
||||
end
|
||||
|
||||
-- draw() may leave the system hand cursor set while hovering a Play /
|
||||
-- Choose control. Once the importer is torn down that draw path stops
|
||||
-- running, so restore the arrow before handing off to boot (issue #114).
|
||||
local function resetPointerCursor(self)
|
||||
if self.android then return end
|
||||
if not (love.mouse.isCursorSupported and love.mouse.isCursorSupported()) then
|
||||
return
|
||||
end
|
||||
self.arrowCursor = self.arrowCursor or love.mouse.getSystemCursor("arrow")
|
||||
love.mouse.setCursor(self.arrowCursor)
|
||||
end
|
||||
|
||||
-- Verify + extract a ROM. The version is decided by the ROM's own SHA-1, so
|
||||
-- dropping a Red or Blue cart into either column always lands in the right one.
|
||||
function RomImporter:startData(data, displayName)
|
||||
@@ -428,6 +440,7 @@ function RomImporter:startData(data, displayName)
|
||||
-- Stay on the launcher; the player presses Play to boot the new game.
|
||||
return
|
||||
end
|
||||
resetPointerCursor(self)
|
||||
if self.onComplete then self.onComplete(version) end
|
||||
end)
|
||||
end
|
||||
@@ -507,6 +520,7 @@ end
|
||||
function RomImporter:play(version)
|
||||
if self.workState == "working" then return end
|
||||
if not self.ready[version] then return end
|
||||
resetPointerCursor(self)
|
||||
if self.onComplete then self.onComplete(version) end
|
||||
end
|
||||
|
||||
@@ -1045,8 +1059,7 @@ function RomImporter:draw()
|
||||
self.handCursor = self.handCursor or love.mouse.getSystemCursor("hand")
|
||||
love.mouse.setCursor(self.handCursor)
|
||||
else
|
||||
self.arrowCursor = self.arrowCursor or love.mouse.getSystemCursor("arrow")
|
||||
love.mouse.setCursor(self.arrowCursor)
|
||||
resetPointerCursor(self)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -872,11 +872,15 @@ R.palettes = {
|
||||
}
|
||||
|
||||
-- keyed by species id, unlike the vanilla byDex array: a species past the
|
||||
-- end of the dex gets an icon without punching a hole in the list
|
||||
-- end of the dex gets an icon without punching a hole in the list. The party
|
||||
-- menu (src/ui/PartyMenu.lua) reads this per-species entry before the vanilla
|
||||
-- dex-indexed default. The value is a built-in icon NAME -- one of BALL, BIRD,
|
||||
-- BUG, FAIRY, GRASS, HELIX, MON, QUADRUPED, SNAKE, WATER (uppercase) -- or a
|
||||
-- { image = <bundled file path>, frames? } table of your own art.
|
||||
R.icons = {
|
||||
semantics = "record", target = "icons.bySpecies",
|
||||
value = f.union{ f.str, f.rec{ image = f.path, frames = f.opt(f.int(1)) } },
|
||||
example = 'mod.content.icons:register("MISSINGNO", { image = "glitch.png" })',
|
||||
example = 'mod.content.icons:register("MODMON", "QUADRUPED") -- a built-in name, or { image = mod.assets:path("icon.png"), frames = 2 }',
|
||||
}
|
||||
|
||||
-- glyph codes are not bytes: the vanilla pages sit at $60/$80 but a
|
||||
|
||||
@@ -242,6 +242,20 @@ function PaletteFX.pack(data)
|
||||
return data and data.palettes or nil
|
||||
end
|
||||
|
||||
-- SuperPalettes that differ between Red and Blue (pokered data/sgb/
|
||||
-- sgb_palettes.asm IF DEF(_RED)/_BLUE). data/palettes_gbc.lua is the
|
||||
-- Red-derived pokered-gbc pack, so under RED++ a Blue playthrough must
|
||||
-- read these from the ROM-imported table or the title ribbon stays red
|
||||
-- and the Game Corner reels keep Red's pink (issue #128).
|
||||
local BLUE_VERSIONED = {
|
||||
LOGO1 = true, SLOTS2 = true, SLOTS3 = true, SLOTS4 = true,
|
||||
}
|
||||
|
||||
local function romNamedPal(data, name)
|
||||
local p = data and data.palettes
|
||||
return p and p.palettes and p.palettes[name]
|
||||
end
|
||||
|
||||
-- named palette from the active pack (nil on stale builds / missing name).
|
||||
-- RED++ falls back to the ROM pack for names the gbc table omits (rare).
|
||||
-- OG RED short-circuits EVERY name to the one global GBC boot-ROM BG palette
|
||||
@@ -251,11 +265,15 @@ end
|
||||
-- GBC_OBJ green), so this stays a BG-only hook.
|
||||
function PaletteFX.pal(data, name)
|
||||
if PaletteFX.mode == "ogred" then return PaletteFX.ogBg() end
|
||||
if GameVersion.isBlue() and BLUE_VERSIONED[name] then
|
||||
local fromRom = romNamedPal(data, name)
|
||||
if fromRom then return fromRom end
|
||||
end
|
||||
local p = PaletteFX.pack(data)
|
||||
local c = p and p.palettes[name]
|
||||
if c then return c end
|
||||
if PaletteFX.usesGbcPack() and data and data.palettes then
|
||||
return data.palettes.palettes[name]
|
||||
if PaletteFX.usesGbcPack() then
|
||||
return romNamedPal(data, name)
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
+85
-23
@@ -438,10 +438,18 @@ function Commands.play_sound(ctx, soundId)
|
||||
require("src.core.Sound").play(ctx.game.data, soundId)
|
||||
end
|
||||
|
||||
-- play_once <songId>: one-shot jingle (Music_PkmnHealed, etc.); the map
|
||||
-- theme resumes when it ends (Music.playOnce / pendingRestore)
|
||||
-- play_once <songId>: one-shot jingle (Music_PkmnHealed, etc.); blocks
|
||||
-- until it finishes so heal-rest scripts (Mom, captain text_asm) match
|
||||
-- Gen1's wait-on-channel loop. The map theme resumes when it ends
|
||||
-- (Music.playOnce / pendingRestore).
|
||||
function Commands.play_once(ctx, songId)
|
||||
require("src.core.Music").playOnce(ctx.game.data, songId)
|
||||
local Music = require("src.core.Music")
|
||||
if not Music.playOnce(ctx.game.data, songId) then return end
|
||||
local runner = ctx.runner
|
||||
runner.waitingCheck = function()
|
||||
return not Music.oneShotPlaying()
|
||||
end
|
||||
runner:yield()
|
||||
end
|
||||
|
||||
-- play_cry <species>: PlayCry (home/audio.asm). The text_asm bodies that
|
||||
@@ -515,12 +523,29 @@ function Commands.give_money(ctx, amount)
|
||||
ctx.save.money = math.max(0, ctx.save.money + amount)
|
||||
end
|
||||
|
||||
-- Point LAST_MAP exits at an outdoor door (pokered wLastMap). Keeps the
|
||||
-- live overworld memory in sync so a scripted home warp from the HoF PC
|
||||
-- does not leave Red's house mats aimed at Indigo Plateau (#103).
|
||||
function Commands.remember_outdoor(ctx, mapId, x, y)
|
||||
local outdoor = { id = mapId, x = x, y = y }
|
||||
ctx.save.lastOutdoor = outdoor
|
||||
if ctx.overworld and ctx.overworld.rememberOutdoor then
|
||||
ctx.overworld:rememberOutdoor(mapId, x, y)
|
||||
elseif ctx.overworld then
|
||||
ctx.overworld.lastOutdoor = outdoor
|
||||
end
|
||||
end
|
||||
|
||||
-- Hall of Fame: snapshot the winning party (SaveHallOfFameTeams inside
|
||||
-- AnimateHallOfFame), run the induction showcase and the end credits,
|
||||
-- autosave while THE END is up, then soft-reset to the title -- the whole
|
||||
-- predef HallOfFamePC + tail of HallOfFameResetEventsAndSaveScript
|
||||
-- (engine/movie/hall_of_fame.asm, engine/movie/credits.asm,
|
||||
-- scripts/HallOfFame.asm).
|
||||
--
|
||||
-- Departure from pokered: CONTINUE lands in the NewGameWarp bedroom with
|
||||
-- a healed party and lastOutdoor on Pallet Town, instead of remaining
|
||||
-- softlocked in HALL_OF_FAME with LAST_MAP still aimed at Indigo (#103).
|
||||
function Commands.record_hall_of_fame(ctx)
|
||||
ctx.save.hallOfFame = ctx.save.hallOfFame or {}
|
||||
local entry = {}
|
||||
@@ -536,17 +561,25 @@ function Commands.record_hall_of_fame(ctx)
|
||||
Screens.push(game, "Credits", function()
|
||||
runner:resume()
|
||||
end, function()
|
||||
-- THE END is on screen: HallOfFameResetEventsAndSaveScript sets
|
||||
-- wLastBlackoutMap := PALLET_TOWN and runs SaveGameData, so the
|
||||
-- save keeps the player standing in the HALL_OF_FAME room. (The
|
||||
-- E4 room-script/event resets that precede the save in pokered are
|
||||
-- the Indigo lobby's re-entry reset here, data/scripts/story6.lua.)
|
||||
-- pokered writes PALLET_TOWN here as a literal, not as "the spawn" --
|
||||
-- the vanilla spawn is REDS_HOUSE_2F. SaveData.defaultHeal carries
|
||||
-- that split (and lets a total conversion redirect it).
|
||||
-- THE END is on screen: heal, place the player at post-game home,
|
||||
-- retarget LAST_MAP, then SaveGameData. (E4 room-script/event
|
||||
-- resets that precede the save in pokered are the Indigo lobby's
|
||||
-- re-entry reset here, data/scripts/story6.lua.)
|
||||
local SaveData = require("src.core.SaveData")
|
||||
local Pokemon = require("src.pokemon.Pokemon")
|
||||
local boot = game.data.field and game.data.field.boot or {}
|
||||
ctx.save.lastHeal = require("src.core.SaveData").defaultHeal(boot)
|
||||
for _, mon in ipairs(ctx.save.party or {}) do
|
||||
Pokemon.heal(mon)
|
||||
end
|
||||
SaveData.applyPostGameHome(ctx.save, boot)
|
||||
if game.overworld then
|
||||
game.overworld.lastOutdoor = ctx.save.lastOutdoor
|
||||
end
|
||||
if game.writeSave then game:writeSave() end
|
||||
-- writeSave's captureSave re-stamps the live HALL_OF_FAME coords;
|
||||
-- re-apply home and persist so CONTINUE resumes in the bedroom.
|
||||
SaveData.applyPostGameHome(ctx.save, boot)
|
||||
SaveData.save(ctx.save)
|
||||
end)
|
||||
end)
|
||||
runner:yield()
|
||||
@@ -861,15 +894,21 @@ function Commands.push_screen(ctx, screenId, args)
|
||||
runner:yield()
|
||||
end
|
||||
|
||||
-- fade "out"|"in" [frames]: screen fade without warping (the Transition
|
||||
-- ramp startWarpTo uses, split in two). "out" pushes a black overlay
|
||||
-- that stays up; the held state keeps ticking the runner's frame-waits
|
||||
-- so a script can wait/replace_block under it. "in" ramps it away.
|
||||
-- fade "out"|"in" [frames|"white"|"black"] [frames|"white"|"black"]:
|
||||
-- screen fade without warping (the Transition ramp startWarpTo uses,
|
||||
-- split in two). "out" pushes an overlay that stays up; the held state
|
||||
-- keeps ticking the runner's frame-waits so a script can wait /
|
||||
-- heal_party / play_once under it. "in" ramps it away.
|
||||
-- Color defaults to black (warp-style); "white" matches GBFadeOutToWhite
|
||||
-- / GBFadeInFromWhite (Mom heal, Silph Co. nurse). White defaults to
|
||||
-- 24 frames (3 palettes x 8); black defaults to 12 (Transition).
|
||||
local FadeOverlay = {}
|
||||
FadeOverlay.__index = FadeOverlay
|
||||
|
||||
function FadeOverlay.new(game, ow)
|
||||
return setmetatable({ game = game, ow = ow, alpha = 0 }, FadeOverlay)
|
||||
function FadeOverlay.new(game, ow, color)
|
||||
return setmetatable({
|
||||
game = game, ow = ow, alpha = 0, color = color or "black",
|
||||
}, FadeOverlay)
|
||||
end
|
||||
|
||||
function FadeOverlay:update()
|
||||
@@ -895,28 +934,51 @@ function FadeOverlay:update()
|
||||
end
|
||||
|
||||
function FadeOverlay:draw()
|
||||
love.graphics.setColor(0, 0, 0, self.alpha)
|
||||
if self.color == "white" then
|
||||
love.graphics.setColor(1, 1, 1, self.alpha)
|
||||
else
|
||||
love.graphics.setColor(0, 0, 0, self.alpha)
|
||||
end
|
||||
love.graphics.rectangle("fill", 0, 0, 160, 144)
|
||||
love.graphics.setColor(1, 1, 1, 1)
|
||||
end
|
||||
|
||||
function Commands.fade(ctx, dir, frames)
|
||||
local function parseFadeArgs(a, b)
|
||||
local frames, color
|
||||
if type(a) == "string" then
|
||||
color = a
|
||||
if type(b) == "number" then frames = b end
|
||||
elseif type(a) == "number" then
|
||||
frames = a
|
||||
if type(b) == "string" then color = b end
|
||||
end
|
||||
color = color or "black"
|
||||
if not frames then
|
||||
frames = (color == "white") and 24 or 12
|
||||
end
|
||||
return frames, color
|
||||
end
|
||||
|
||||
function Commands.fade(ctx, dir, framesOrColor, colorOrFrames)
|
||||
local ow = ctx.overworld
|
||||
if not ow then return end
|
||||
local runner = ctx.runner
|
||||
frames = frames or 12 -- Transition's ramp length
|
||||
local frames, color = parseFadeArgs(framesOrColor, colorOrFrames)
|
||||
local overlay = ow.fadeOverlay
|
||||
if dir == "out" then
|
||||
if not overlay then
|
||||
overlay = FadeOverlay.new(ctx.game, ow)
|
||||
overlay = FadeOverlay.new(ctx.game, ow, color)
|
||||
ow.fadeOverlay = overlay
|
||||
ctx.game.stack:push(overlay)
|
||||
else
|
||||
overlay.color = color
|
||||
end
|
||||
overlay.ramp = { from = overlay.alpha, to = 1, frames = frames, t = 0,
|
||||
onDone = function() runner:resume() end }
|
||||
runner:yield()
|
||||
elseif dir == "in" then
|
||||
if not overlay then return end
|
||||
overlay.color = color or overlay.color
|
||||
overlay.ramp = { from = overlay.alpha, to = 0, frames = frames, t = 0,
|
||||
onDone = function() runner:resume() end }
|
||||
runner:yield()
|
||||
@@ -1006,7 +1068,7 @@ for _, verb in ipairs({ "show_text", "ask", "choice", "start_battle", "warp",
|
||||
"open_mart", "trade", "push_screen", "record_hall_of_fame",
|
||||
"old_man_demo", "static_battle", "rival_battle", "give_item", "wait",
|
||||
"wait_flag", "move_player", "move_npc", "move_npc_to", "walk_npc",
|
||||
"emote", "fade", "pan_camera" }) do
|
||||
"emote", "fade", "pan_camera", "play_once" }) do
|
||||
local meta = Commands.meta[verb] or {}
|
||||
Commands.meta[verb] = meta
|
||||
meta.blocking = true
|
||||
|
||||
+2
-1
@@ -217,7 +217,8 @@ function Credits:update(dt)
|
||||
self.timer = FADE_FRAMES
|
||||
elseif self.phase == "end_fade" then
|
||||
-- Credits returns to HallOfFameResetEventsAndSaveScript here: the
|
||||
-- save happens now, then 5 x 120 DelayFrames before the button wait
|
||||
-- save (plus post-game home relocate, #103) happens now, then
|
||||
-- 5 x 120 DelayFrames before the button wait
|
||||
if self.onTheEnd then self.onTheEnd() end
|
||||
self.phase = "end_hold"
|
||||
self.timer = 600
|
||||
|
||||
+23
-4
@@ -1,5 +1,11 @@
|
||||
-- Pokédex entry page: front sprite, kind, height/weight and the real
|
||||
-- dex description (data/pokemon/dex_entries.asm + dex_text.asm).
|
||||
--
|
||||
-- `species` may be a species id string, or a table
|
||||
-- `{ species = id, forceOwned = true }`. forceOwned mirrors pret's
|
||||
-- StarterDex (engine/events/starter_dex.asm), which temporarily sets the
|
||||
-- owned bit so Oak's lab ball previews show height/weight/description
|
||||
-- without permanently marking the mon owned.
|
||||
|
||||
local Font = require("src.render.Font")
|
||||
|
||||
@@ -16,8 +22,17 @@ function DexEntryMenu:sgbPalettes(game)
|
||||
P.zone(P.monPal(game.data, self.def and self.def.id), 1, 1, 8, 8) }
|
||||
end
|
||||
|
||||
function DexEntryMenu.new(game, species)
|
||||
local self = setmetatable({ game = game }, DexEntryMenu)
|
||||
local function resolveArgs(speciesOrOpts)
|
||||
if type(speciesOrOpts) == "table" then
|
||||
return speciesOrOpts.species or speciesOrOpts[1],
|
||||
speciesOrOpts.forceOwned and true or false
|
||||
end
|
||||
return speciesOrOpts, false
|
||||
end
|
||||
|
||||
function DexEntryMenu.new(game, speciesOrOpts)
|
||||
local species, forceOwned = resolveArgs(speciesOrOpts)
|
||||
local self = setmetatable({ game = game, forceOwned = forceOwned }, DexEntryMenu)
|
||||
self.def = game.data.pokemon[species]
|
||||
local ok, img = pcall(love.graphics.newImage, self.def.spriteFront)
|
||||
self.sprite = ok and img or nil
|
||||
@@ -42,12 +57,16 @@ function DexEntryMenu:draw()
|
||||
love.graphics.setColor(0, 0, 0, 1)
|
||||
Font.draw(def.name, 72, 8)
|
||||
local e = def.dexEntry or {}
|
||||
Font.draw((e.kind or "?") .. " POKéMON", 72, 20)
|
||||
-- English R/B prints only the kind string (hlcoord 9,4 PlaceString).
|
||||
-- PokeText ("#"/POKéMON) is an unreferenced JPN leftover in pokedex.asm;
|
||||
-- appending " POKéMON" here clipped longer kinds ("LIZARD POKé").
|
||||
Font.draw(e.kind or "?", 72, 20)
|
||||
-- same number width as the list (constants.dexDigits), so a dex past 999
|
||||
-- prints the extra digit everywhere at once
|
||||
local digits = (self.game.data.constants or {}).dexDigits or 3
|
||||
Font.draw(("No.%0" .. digits .. "d"):format(def.dex or 0), 72, 32)
|
||||
local owned = self.game.save.pokedex and self.game.save.pokedex.owned[def.id]
|
||||
local owned = self.forceOwned
|
||||
or (self.game.save.pokedex and self.game.save.pokedex.owned[def.id])
|
||||
-- height/weight print only once owned, like the description
|
||||
-- (pokedex.asm: "if the pokemon has not been owned, don't print the
|
||||
-- height, weight, or description")
|
||||
|
||||
+25
-5
@@ -69,10 +69,26 @@ local function drawIcon(game, mon, x, y, selected, counter)
|
||||
local icons = game.data.icons
|
||||
if not icons then return end
|
||||
local def = game.data.pokemon[mon.species]
|
||||
-- byDex is the vanilla lookup, but the icons registry can bring the table
|
||||
-- into existence on its own, so it may be the only key present
|
||||
local name = def and def.dex and icons.byDex and icons.byDex[def.dex]
|
||||
local path = name and icons.icons[name]
|
||||
-- Per-species override first: the icons registry folds into
|
||||
-- icons.bySpecies, and a pokemon record may carry its own `icon` field.
|
||||
-- Either is a built-in icon name (resolved through icons.icons) or a
|
||||
-- { image = <path>, frames? } table pointing at bundled art. Falling
|
||||
-- through to icons.byDex[def.dex] keeps the vanilla dex-indexed default;
|
||||
-- without the override a modded or dex-renumbered species could never
|
||||
-- change its menu icon.
|
||||
local entry = (icons.bySpecies and icons.bySpecies[mon.species])
|
||||
or (def and def.icon)
|
||||
local name, path
|
||||
if type(entry) == "string" then
|
||||
name = entry
|
||||
path = icons.icons and icons.icons[entry]
|
||||
elseif type(entry) == "table" then
|
||||
path = entry.image
|
||||
end
|
||||
if not path then
|
||||
name = def and def.dex and icons.byDex and icons.byDex[def.dex]
|
||||
path = name and icons.icons and icons.icons[name]
|
||||
end
|
||||
if not path then return end
|
||||
if iconImages[path] == nil then
|
||||
local ok, img = pcall(love.graphics.newImage, path)
|
||||
@@ -332,7 +348,11 @@ function PartyMenu:update(dt)
|
||||
local items = { { label = "STATS", action = "stats" },
|
||||
{ label = "SWITCH", action = "switch" } }
|
||||
local ow = self.game.overworld
|
||||
if not self.battle and ow and mon.hp > 0 then
|
||||
-- Field moves (HMs/TMs) are usable out of battle even when the mon
|
||||
-- is fainted -- Gen 1 does not require HP for Cut/Fly/Surf/etc.
|
||||
-- Battle still excludes this list via `not self.battle`. Softboiled
|
||||
-- can appear for a fainted user; its heal transfer then no-ops.
|
||||
if not self.battle and ow then
|
||||
for _, mv in ipairs(mon.moves) do
|
||||
if mv.id == "FLY" and ow.map.def.tileset == "OVERWORLD"
|
||||
and self.game.save.inventory.THUNDERBADGE then
|
||||
|
||||
+2
-1
@@ -1,6 +1,7 @@
|
||||
-- The player's item-storage PC (engine/menus/players_pc.asm):
|
||||
-- WITHDRAW ITEM / DEPOSIT ITEM / TOSS ITEM / LOG OFF over
|
||||
-- game.save.pcItems ({ ITEM_ID = count }, created lazily). Withdraw and
|
||||
-- game.save.pcItems ({ ITEM_ID = count }; New Game seeds { POTION = 1 },
|
||||
-- older saves may still be empty until first deposit). Withdraw and
|
||||
-- deposit ask "How many?" via the quantity selector (key items always
|
||||
-- move one); toss discards after a YES/NO confirm. Follows the
|
||||
-- BoxMenu/BagMenu list idioms.
|
||||
|
||||
@@ -1122,7 +1122,9 @@ end
|
||||
-- -------------------------------------------------------------------------
|
||||
|
||||
-- HM field moves are gated by badges like the original
|
||||
-- (constants.hmBadges; distinct from constants.hmMoves, the forget gate)
|
||||
-- (constants.hmBadges; distinct from constants.hmMoves, the forget gate).
|
||||
-- Gen 1 allows field use from fainted party members (party menu + name
|
||||
-- lookup for Cut/Surf messages); do not require mon.hp > 0 here.
|
||||
function OverworldState:partyKnows(moveId)
|
||||
local gate = (FieldDefaults.constant(Game.data, "hmBadges") or {})[moveId]
|
||||
local badge = gate and gate.badge
|
||||
@@ -1130,10 +1132,8 @@ function OverworldState:partyKnows(moveId)
|
||||
return nil
|
||||
end
|
||||
for _, mon in ipairs(Game.save.party) do
|
||||
if mon.hp > 0 then
|
||||
for _, mv in ipairs(mon.moves) do
|
||||
if mv.id == moveId then return mon end
|
||||
end
|
||||
for _, mv in ipairs(mon.moves) do
|
||||
if mv.id == moveId then return mon end
|
||||
end
|
||||
end
|
||||
return nil
|
||||
@@ -1887,7 +1887,7 @@ end
|
||||
-- to SurfingAttemptFailed (_NoSurfingHereText) if it isn't water. This is
|
||||
-- a side-effect-free check that reports which text/flow the caller should
|
||||
-- use; the actual mount happens in trySurf on "ok". Returns:
|
||||
-- "no_badge" -> SOULBADGE missing / no live SURF mon (_NewBadgeRequiredText)
|
||||
-- "no_badge" -> SOULBADGE missing / no SURF mon (_NewBadgeRequiredText)
|
||||
-- "forced_bike" -> on the Cycling Road (_CyclingIsFunText)
|
||||
-- "current" -> Seafoam B4F stairs before the boulders (_CurrentTooFastText)
|
||||
-- "dismount" -> already surfing, facing dry land; caller steps forward
|
||||
@@ -1930,7 +1930,7 @@ end
|
||||
-- (and .loop back to the submenu) if it isn't cuttable. Side-effect-free
|
||||
-- check mirroring useSurfFieldMove; tryCut does the actual cut on "ok".
|
||||
-- Returns:
|
||||
-- "no_badge" -> CASCADEBADGE missing / no live CUT mon (_NewBadgeRequiredText)
|
||||
-- "no_badge" -> CASCADEBADGE missing / no CUT mon (_NewBadgeRequiredText)
|
||||
-- "nothing" -> not facing a cuttable tree (_NothingToCutText)
|
||||
-- "ok" -> facing a cuttable tree; caller may call tryCut(fx, fy)
|
||||
function OverworldState:useCutFieldMove()
|
||||
@@ -2351,6 +2351,8 @@ end
|
||||
-- Badges/items awarded after specific battles (data/scripts/victories.lua).
|
||||
-- `deactivate` retires unfought gym/dojo trainers the way the originals'
|
||||
-- SetEvent / SetEventRange do after the leader victory.
|
||||
-- `hide` is { { mapId, objName }, ... } — HideObject on those toggles
|
||||
-- (e.g. Brock victory clears PEWTERCITY_YOUNGSTER / ROUTE22_RIVAL1).
|
||||
function OverworldState:checkVictoryRewards(trainerClass, partyIndex)
|
||||
local victories = require("data.scripts.victories")
|
||||
local reward = victories[trainerClass .. "#" .. tostring(partyIndex or 1)]
|
||||
@@ -2364,6 +2366,13 @@ function OverworldState:checkVictoryRewards(trainerClass, partyIndex)
|
||||
Game.save.flags[flag] = true
|
||||
end
|
||||
end
|
||||
if reward.hide then
|
||||
local Commands = require("src.script.Commands")
|
||||
local ctx = { game = Game, save = Game.save, overworld = self }
|
||||
for _, entry in ipairs(reward.hide) do
|
||||
Commands.hide_object(ctx, entry[1], entry[2])
|
||||
end
|
||||
end
|
||||
local lines = {}
|
||||
if reward.badge then
|
||||
Game.save.inventory[reward.badge] = 1
|
||||
|
||||
Reference in New Issue
Block a user