mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-19 12:15:31 +02:00
Merge branch 'dev' of https://github.com/bryanthaboi/gen1recomp into dev
This commit is contained in:
@@ -39,6 +39,21 @@ local function messageCopy(screen)
|
||||
return #lines > 0 and lines or nil
|
||||
end
|
||||
|
||||
local function itemCopies(game, screen, catchable)
|
||||
local out = {}
|
||||
for id, count in pairs((game.save and game.save.inventory) or {}) do
|
||||
local def = game.data.items and game.data.items[id]
|
||||
if count > 0 and def and def.pocket == "BALL" then
|
||||
out[#out + 1] = { id = id, name = def.name or id, count = count,
|
||||
ball = true, needsTarget = false,
|
||||
catchChance = catchable and screen.catchChance
|
||||
and screen:catchChance(id) or nil }
|
||||
end
|
||||
end
|
||||
table.sort(out, function(a, b) return a.name < b.name end)
|
||||
return out
|
||||
end
|
||||
|
||||
local function signature(game, screen, top)
|
||||
if not screen then return "none" end
|
||||
local battle = screen.battle or {}
|
||||
@@ -56,6 +71,9 @@ local function signature(game, screen, top)
|
||||
parts[#parts + 1] = tostring(mon.hp)
|
||||
parts[#parts + 1] = tostring(mon.status)
|
||||
end
|
||||
for _, item in ipairs(itemCopies(game, screen, false)) do
|
||||
parts[#parts + 1] = item.id .. "=" .. tostring(item.count)
|
||||
end
|
||||
return table.concat(parts, "|")
|
||||
end
|
||||
|
||||
@@ -111,9 +129,9 @@ function BattleAPI:snapshot()
|
||||
player = monCopy(game.data, battle.player, true),
|
||||
enemy = monCopy(game.data, battle.enemy, true),
|
||||
party = party, moves = moveCopies(game, battle),
|
||||
-- Gold's PACK is pocketed and target selection is screen-owned. Omit it
|
||||
-- until the engine can expose the same semantic item records as Gen 1.
|
||||
items = {} }
|
||||
-- Targeted medicine remains screen-owned, but balls are complete semantic
|
||||
-- records and can safely expose the same read-only preview as Gen 1.
|
||||
items = itemCopies(game, screen, battle.wild and not screen.tutorial) }
|
||||
end
|
||||
|
||||
local MENU_CHOICES = { fight = true, party = true, item = true, run = true }
|
||||
|
||||
@@ -305,6 +305,15 @@ function Catching.rate(opts)
|
||||
return math.min(255, rate), false
|
||||
end
|
||||
|
||||
-- Exact stock catch probability for read-only previews. A catch.rate hook
|
||||
-- may replace the roll entirely, so nil is safer than presenting a guess.
|
||||
function Catching.chance(opts)
|
||||
if Runtime.wantsHook("catch.rate") then return nil end
|
||||
local rate, guaranteed = Catching.rate(opts)
|
||||
if guaranteed or rate >= 255 then return 100 end
|
||||
return rate * 100 / 256
|
||||
end
|
||||
|
||||
-- The status half of the rate, off the merged `statuses` record the same way
|
||||
-- src/battle/Catching.lua reads record.catchBonus on Gen 1. Gold's records
|
||||
-- live on src/battle/gen2/Battle.lua (Battle.STATUSES) and carry BOTH numbers:
|
||||
|
||||
@@ -988,8 +988,18 @@ local function pickerHasKind(kind)
|
||||
return false
|
||||
end
|
||||
|
||||
local function findPendingRequiredImport()
|
||||
local function findPendingRequiredImport(self)
|
||||
local names = { "picked_required_import.bin", "picked_stadium.z64" }
|
||||
-- Builds released before required_import was added to the Android JNI bridge
|
||||
-- only understand the long-standing "rom" picker kind. While a required
|
||||
-- import request is in flight, it is safe to treat its staging name as a
|
||||
-- dependency file: the pending IDs below select the same validation/copy
|
||||
-- path as a current bridge. Never scan picked_rom.gb otherwise, since that
|
||||
-- remains reserved for an ordinary game-ROM import.
|
||||
if self and self.requiredImportLegacyRomPick
|
||||
and self.pickerPendingKind == "required_import" then
|
||||
names[#names + 1] = "picked_rom.gb"
|
||||
end
|
||||
for _, name in ipairs(names) do
|
||||
if love.filesystem.getInfo(name, "file") then return name end
|
||||
end
|
||||
@@ -1504,12 +1514,16 @@ function RomImporter:focus(f)
|
||||
local text = "Could not read the picked file. Reopen the picker and choose "
|
||||
.. "it with the Files (Documents) app, or copy it into: "
|
||||
.. love.filesystem.getSaveDirectory()
|
||||
local legacyRequiredPick = self.requiredImportLegacyRomPick
|
||||
and self.pickerPendingKind == "required_import"
|
||||
if pickError:find("picked_required_import", 1, true)
|
||||
or pickError:find("picked_stadium", 1, true) then
|
||||
or pickError:find("picked_stadium", 1, true)
|
||||
or (legacyRequiredPick and pickError:find("picked_rom", 1, true)) then
|
||||
self.modNotice = { ok = false, text = text }
|
||||
self.pickerPendingKind = nil
|
||||
self.pickerPendingModId = nil
|
||||
self.pickerPendingImportId = nil
|
||||
self.requiredImportLegacyRomPick = nil
|
||||
elseif pickError:find("picked_mod", 1, true) then
|
||||
if self.pickerPendingKind == "skin" then
|
||||
self.pickerPendingKind = nil
|
||||
@@ -1526,11 +1540,12 @@ function RomImporter:focus(f)
|
||||
end
|
||||
return
|
||||
end
|
||||
local requiredName = findPendingRequiredImport()
|
||||
local requiredName = findPendingRequiredImport(self)
|
||||
if requiredName then
|
||||
local modId, importId = self.pickerPendingModId, self.pickerPendingImportId
|
||||
self.pickerPendingKind = nil
|
||||
self.pickerPendingModId, self.pickerPendingImportId = nil, nil
|
||||
self.requiredImportLegacyRomPick = nil
|
||||
local imported = modId and importId
|
||||
and self:_importRequiredSource(modId, importId, requiredName)
|
||||
consumePick(self, requiredName, requiredName, imported)
|
||||
@@ -2069,7 +2084,17 @@ function RomImporter:chooseRequiredImport(modId, importId)
|
||||
return
|
||||
end
|
||||
if self.nativePicker then
|
||||
if self.mobileFileBridge and not pickerHasKind("required_import") then
|
||||
-- Android 13+ uses the Storage Access Framework for both paths. Some
|
||||
-- Android 15 installs carry the newer Lua launcher with an older native
|
||||
-- bridge, however, so they do not advertise required_import yet. Fall
|
||||
-- back to that bridge's known "rom" picker and quarantine its result by
|
||||
-- the pending required-import IDs. iOS has a different asynchronous
|
||||
-- bridge and deliberately keeps the explicit capability requirement.
|
||||
local legacyAndroidPicker = self.mobileFileBridge
|
||||
and love.system.getOS() == "Android"
|
||||
and not pickerHasKind("required_import")
|
||||
if self.mobileFileBridge and not pickerHasKind("required_import")
|
||||
and not legacyAndroidPicker then
|
||||
requiredImportNotice(self, modId, importId,
|
||||
"This app build cannot pick required mod files yet. Update the app and try again.")
|
||||
self.modNotice = nil
|
||||
@@ -2078,10 +2103,12 @@ function RomImporter:chooseRequiredImport(modId, importId)
|
||||
self.pickerPendingKind = "required_import"
|
||||
self.pickerPendingModId = modId
|
||||
self.pickerPendingImportId = importId
|
||||
if not pickFile("required_import") then
|
||||
self.requiredImportLegacyRomPick = legacyAndroidPicker or nil
|
||||
if not pickFile(legacyAndroidPicker and "rom" or "required_import") then
|
||||
self.pickerPendingKind = nil
|
||||
self.pickerPendingModId = nil
|
||||
self.pickerPendingImportId = nil
|
||||
self.requiredImportLegacyRomPick = nil
|
||||
requiredImportNotice(self, modId, importId, "Could not open the file picker.")
|
||||
self.modNotice = nil
|
||||
elseif self.android then
|
||||
|
||||
+33
-30
@@ -2945,6 +2945,37 @@ function BattleState:openDexEntry(species)
|
||||
})
|
||||
end
|
||||
|
||||
function BattleState:catchOptions(itemId)
|
||||
local data = self.game and self.game.data or {}
|
||||
local battle = self.battle
|
||||
local enemy = battle and battle.enemy
|
||||
if not enemy then return nil end
|
||||
local enemyDef = data.pokemon and data.pokemon[enemy.species]
|
||||
local dexEntry = data.gen2Pokedex and data.gen2Pokedex[enemy.species]
|
||||
local evolveItem
|
||||
for _, entry in ipairs((enemyDef and enemyDef.evolutions) or {}) do
|
||||
if entry.method == "EVOLVE_ITEM" then evolveItem = entry.item end
|
||||
end
|
||||
local player = battle.player
|
||||
return {
|
||||
battle = battle, mon = enemy, def = enemyDef,
|
||||
maxHp = enemy.maxHp or (enemy.stats and enemy.stats.hp), hp = enemy.hp,
|
||||
catchRate = enemyDef and enemyDef.catchRate or 45, ball = itemId,
|
||||
status = enemy.status, random = battle.random,
|
||||
weight = dexEntry and dexEntry.weight, level = enemy.level,
|
||||
playerLevel = player and player.level,
|
||||
fishing = battle.battleType == "fish", species = enemy.species,
|
||||
gender = enemy.gender, playerSpecies = player and player.species,
|
||||
playerGender = player and player.gender, evolveItem = evolveItem,
|
||||
}
|
||||
end
|
||||
|
||||
function BattleState:catchChance(itemId)
|
||||
if self.tutorial then return 100 end
|
||||
local opts = self:catchOptions(itemId)
|
||||
return opts and Catching.chance(opts) or nil
|
||||
end
|
||||
|
||||
-- Items in battle: balls try a catch, the stat items apply their stage, and
|
||||
-- everything with a ported party effect runs the same item_effects.asm routine
|
||||
-- the field pack runs. Anything else reports that it cannot be used, which is
|
||||
@@ -2974,7 +3005,6 @@ function BattleState:useItem(itemId)
|
||||
return
|
||||
end
|
||||
local enemy = self.battle.enemy
|
||||
local enemyDef = data.pokemon and data.pokemon[enemy.species]
|
||||
local caught, rate
|
||||
if self.tutorial then
|
||||
-- `ld a, [wBattleType] / cp BATTLETYPE_TUTORIAL /
|
||||
@@ -2988,35 +3018,8 @@ function BattleState:useItem(itemId)
|
||||
caught, rate = true, 255
|
||||
else
|
||||
-- The specialty-ball conditions (BallMultiplierFunctionTable): each one
|
||||
-- is something this screen already knows. Heavy Ball reads the dex
|
||||
-- weight, Moon Ball the species' stone row, Love Ball both genders,
|
||||
-- Level Ball the two levels, Lure Ball wBattleType.
|
||||
local dexEntry = data.gen2Pokedex and data.gen2Pokedex[enemy.species]
|
||||
local evolveItem
|
||||
for _, entry in ipairs((enemyDef and enemyDef.evolutions) or {}) do
|
||||
if entry.method == "EVOLVE_ITEM" then evolveItem = entry.item end
|
||||
end
|
||||
local player = self.battle.player
|
||||
caught, rate = Catching.attempt({
|
||||
battle = self.battle,
|
||||
mon = enemy,
|
||||
def = enemyDef,
|
||||
maxHp = enemy.maxHp or (enemy.stats and enemy.stats.hp),
|
||||
hp = enemy.hp,
|
||||
catchRate = enemyDef and enemyDef.catchRate or 45,
|
||||
ball = itemId,
|
||||
status = enemy.status,
|
||||
random = self.battle.random,
|
||||
weight = dexEntry and dexEntry.weight,
|
||||
level = enemy.level,
|
||||
playerLevel = player and player.level,
|
||||
fishing = self.battle.battleType == "fish",
|
||||
species = enemy.species,
|
||||
gender = enemy.gender,
|
||||
playerSpecies = player and player.species,
|
||||
playerGender = player and player.gender,
|
||||
evolveItem = evolveItem,
|
||||
})
|
||||
-- is also used by the read-only preview, so both paths stay exact.
|
||||
caught, rate = Catching.attempt(self:catchOptions(itemId))
|
||||
end
|
||||
-- wWildMon carries the answer through the animation, and
|
||||
-- wThrownBallWobbleCount is the counter GetPokeBallWobble bumps once per
|
||||
|
||||
Reference in New Issue
Block a user