mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-15 07:41:21 +02:00
initial commit
This commit is contained in:
@@ -0,0 +1,396 @@
|
||||
-- The bag: lists inventory, uses items via ItemEffects.
|
||||
-- opts.battle = BattleState when opened mid-battle (balls throwable,
|
||||
-- using an item consumes the turn).
|
||||
|
||||
local ItemEffects = require("src.inventory.ItemEffects")
|
||||
local ListMenu = require("src.ui.ListMenu")
|
||||
local TextBox = require("src.render.TextBox")
|
||||
|
||||
local BagMenu = {}
|
||||
|
||||
local Bag = require("src.inventory.Bag")
|
||||
|
||||
-- acquisition order like wBagItems (Bag.order), not alphabetical
|
||||
local function buildItems(game)
|
||||
local items = {}
|
||||
for _, id in ipairs(Bag.order(game.save)) do
|
||||
local def = game.data.items[id]
|
||||
table.insert(items, {
|
||||
value = id,
|
||||
label = def and def.name or id,
|
||||
right = "x" .. game.save.inventory[id],
|
||||
})
|
||||
end
|
||||
return items
|
||||
end
|
||||
|
||||
local function consume(game, id)
|
||||
Bag.remove(game.save, id, 1)
|
||||
end
|
||||
|
||||
local function save_name(game)
|
||||
return game.save.player.name
|
||||
end
|
||||
|
||||
local function showMessages(game, msgs, onDone)
|
||||
if not msgs or #msgs == 0 then
|
||||
if onDone then onDone() end
|
||||
return
|
||||
end
|
||||
game.stack:push(TextBox.new(game, table.concat(msgs, "\f"), onDone))
|
||||
end
|
||||
|
||||
-- run the use-flow for an item on a chosen target
|
||||
local function useOn(game, battle, id, target, list, moveIndex)
|
||||
local result, payload, extra = ItemEffects.use(game.data, game.save, id, target,
|
||||
battle, moveIndex, game.overworld)
|
||||
|
||||
-- field POKé FLUTE: play the tune, then the no-effect text
|
||||
if result == "flute_field" then
|
||||
require("src.core.Sound").play(game.data, "Pokeflute")
|
||||
showMessages(game, payload)
|
||||
return
|
||||
end
|
||||
|
||||
-- field POKé FLUTE next to a not-yet-beaten Snorlax: "had effect" text,
|
||||
-- then the woke-up/battle sequence (data/scripts/story.lua snorlaxWake)
|
||||
if result == "flute_wake" then
|
||||
list:close()
|
||||
require("src.core.Sound").play(game.data, "Pokeflute")
|
||||
showMessages(game, payload, function()
|
||||
local ow = game.overworld
|
||||
local mod = ow and require("data.scripts.init").get(extra.mapId)
|
||||
if ow and mod and mod.snorlaxWake then
|
||||
ow.runner:run(mod.snorlaxWake.script, { npc = extra.npc })
|
||||
end
|
||||
end)
|
||||
return
|
||||
end
|
||||
|
||||
if result == "consumed_escape" then -- Poké Doll
|
||||
consume(game, id)
|
||||
list:close()
|
||||
showMessages(game, payload, function()
|
||||
battle.result = "run"
|
||||
battle.afterQueue = "finish"
|
||||
battle.phase = "messages"
|
||||
end)
|
||||
return
|
||||
end
|
||||
|
||||
if result == "bicycle" then
|
||||
list:close()
|
||||
local ow = game.overworld
|
||||
local Music = require("src.core.Music")
|
||||
-- IsBikeRidingAllowed (home/overworld.asm): the tilesets of
|
||||
-- bike_riding_tilesets.asm, plus Route 23 / Indigo Plateau by
|
||||
-- map id. Reads the extracted allowlist when present.
|
||||
local function bikeAllowed()
|
||||
if not ow then return false end
|
||||
local br = game.data.field.bikeRiding
|
||||
or { tilesets = { "OVERWORLD", "FOREST", "UNDERGROUND",
|
||||
"SHIP_PORT", "CAVERN" },
|
||||
maps = { "ROUTE_23", "INDIGO_PLATEAU" } }
|
||||
for _, m in ipairs(br.maps or {}) do
|
||||
if ow.map.id == m then return true end
|
||||
end
|
||||
for _, t in ipairs(br.tilesets or {}) do
|
||||
if ow.map.def.tileset == t then return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
if game.save.onBike then
|
||||
game.save.onBike = false
|
||||
Music.playMap(game.data, ow and ow.map.id, false)
|
||||
showMessages(game, { save_name(game) .. " got off\nthe BICYCLE." })
|
||||
elseif bikeAllowed() then
|
||||
game.save.onBike = true
|
||||
Music.playMap(game.data, ow.map.id, true)
|
||||
showMessages(game, { save_name(game) .. " got on\nthe BICYCLE!" })
|
||||
else
|
||||
showMessages(game, { "No cycling\nallowed here." })
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
if result == "fish" then
|
||||
list:close()
|
||||
local ow = game.overworld
|
||||
local p = ow and ow.player
|
||||
if ow and p then
|
||||
local fx, fy = p:facingCell()
|
||||
if ow.map:inBounds(fx, fy) and ow.map:isWaterCell(fx, fy) then
|
||||
ow:goFishing(id)
|
||||
return
|
||||
end
|
||||
end
|
||||
showMessages(game, { "No good! It's not\neven near water." })
|
||||
return
|
||||
end
|
||||
|
||||
if result == "ball" then
|
||||
if not battle then
|
||||
showMessages(game, { "OAK: " .. game.save.player.name .. "!\nThis isn't the\ntime to use that!" })
|
||||
return
|
||||
end
|
||||
consume(game, id)
|
||||
list:close()
|
||||
battle:throwBall(id)
|
||||
return
|
||||
end
|
||||
|
||||
if result == "learn" or result == "learnkept" then
|
||||
local moveId = payload
|
||||
local mdef = game.data.moves[moveId]
|
||||
local function teach()
|
||||
if #target.moves < 4 then
|
||||
table.insert(target.moves, { id = moveId, pp = mdef.pp })
|
||||
showMessages(game, { ("%s learned\n%s!"):format(target.nickname or
|
||||
game.data.pokemon[target.species].name, mdef.name) })
|
||||
if result == "learn" then consume(game, id) end
|
||||
else
|
||||
local MoveLearnMenu = require("src.ui.MoveLearnMenu")
|
||||
game.stack:push(MoveLearnMenu.new(game, target, moveId, function(learned)
|
||||
if learned and result == "learn" then consume(game, id) end
|
||||
end))
|
||||
end
|
||||
end
|
||||
list:close()
|
||||
teach()
|
||||
return
|
||||
end
|
||||
|
||||
-- the TOWN MAP screen (engine/menus/town_map.asm)
|
||||
if result == "townmap" then
|
||||
local ok, TownMap = pcall(require, "src.ui.TownMap")
|
||||
if ok then
|
||||
game.stack:push(TownMap.new(game))
|
||||
else
|
||||
showMessages(game, { "The TOWN MAP is\nunreadable here." })
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
-- ITEMFINDER (engine/items/itemfinder.asm): responds if the current
|
||||
-- map still has an unfound hidden item
|
||||
if result == "itemfinder" then
|
||||
local ow = game.overworld
|
||||
local t = game.data.text
|
||||
if ow and ow:hasHiddenItemLeft() then
|
||||
showMessages(game, { t._ItemfinderFoundItemText
|
||||
or "Yes! ITEMFINDER\nindicates there's\nan item nearby." })
|
||||
else
|
||||
showMessages(game, { t._ItemfinderFoundNothingText
|
||||
or "Nope! ITEMFINDER\nisn't responding." })
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
-- POKé FLUTE in battle: not consumed, but uses the turn
|
||||
if result == "flute" then
|
||||
list:close()
|
||||
require("src.core.Sound").play(game.data, "Pokeflute")
|
||||
showMessages(game, payload, function() battle:itemUsed({}) end)
|
||||
return
|
||||
end
|
||||
|
||||
if result == "escape_rope" then
|
||||
-- ItemUseEscapeRope: only inside the dungeon tilesets
|
||||
-- (escape_rope_tilesets.asm), never in Agatha's room, and it sets
|
||||
-- BIT_ESCAPE_WARP so special_warps.asm warps to wLastBlackoutMap
|
||||
-- -- the last Pokémon Center town, same as Dig/Teleport (NOT the
|
||||
-- spot you entered the dungeon from)
|
||||
local ESCAPE_ROPE_TILESETS = { FOREST = true, CEMETERY = true,
|
||||
CAVERN = true, FACILITY = true,
|
||||
INTERIOR = true }
|
||||
local ow = game.overworld
|
||||
if ow and ESCAPE_ROPE_TILESETS[ow.map.def.tileset]
|
||||
and ow.map.id ~= "AGATHAS_ROOM" then
|
||||
list:close()
|
||||
consume(game, id)
|
||||
require("src.core.Sound").play(game.data, "Teleport_Exit1")
|
||||
ow.player.surfing = false
|
||||
ow:warpToHealPoint()
|
||||
else
|
||||
showMessages(game, { "OAK: " .. game.save.player.name
|
||||
.. "!\nThis isn't the\ntime to use that!" })
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
if result == "consumed" then
|
||||
consume(game, id)
|
||||
if extra and extra.evolveTo then
|
||||
list:close()
|
||||
local Evolution = require("src.pokemon.Evolution")
|
||||
Evolution.evolve(game, target, extra.evolveTo)
|
||||
return
|
||||
end
|
||||
-- RARE CANDY: after the level text, the stat window, any level-up
|
||||
-- moves and a level evolution follow (item_effects.asm .useRareCandy
|
||||
-- runs PrintStatsBox, LearnMoveFromLevelUp and TryEvolvingMon)
|
||||
if extra and extra.leveledTo and target then
|
||||
list:close()
|
||||
showMessages(game, payload, function()
|
||||
local StatBox = require("src.battle.BattleState").StatBox
|
||||
game.stack:push(StatBox.new(game, target, function()
|
||||
local Experience = require("src.battle.Experience")
|
||||
local def = game.data.pokemon[target.species]
|
||||
local moves = Experience.movesLearnedAt(def, extra.leveledTo)
|
||||
local i = 0
|
||||
local function nextStep()
|
||||
i = i + 1
|
||||
local moveId = moves[i]
|
||||
if not moveId then
|
||||
local Evolution = require("src.pokemon.Evolution")
|
||||
local evoTo = Evolution.pendingLevelEvo(game.data, target)
|
||||
if evoTo then Evolution.evolve(game, target, evoTo) end
|
||||
return
|
||||
end
|
||||
for _, mv in ipairs(target.moves) do
|
||||
if mv.id == moveId then return nextStep() end
|
||||
end
|
||||
local mdef = game.data.moves[moveId]
|
||||
if #target.moves < 4 then
|
||||
table.insert(target.moves, { id = moveId, pp = mdef.pp })
|
||||
local name = target.nickname or def.name
|
||||
showMessages(game, { ("%s learned\n%s!"):format(name, mdef.name) },
|
||||
nextStep)
|
||||
else
|
||||
local MoveLearnMenu = require("src.ui.MoveLearnMenu")
|
||||
game.stack:push(MoveLearnMenu.new(game, target, moveId, nextStep))
|
||||
end
|
||||
end
|
||||
nextStep()
|
||||
end))
|
||||
end)
|
||||
return
|
||||
end
|
||||
-- refresh counts in the list
|
||||
for i, it in ipairs(list.items) do
|
||||
if it.value == id then
|
||||
local left = game.save.inventory[id]
|
||||
if left then it.right = "x" .. left else table.remove(list.items, i) end
|
||||
break
|
||||
end
|
||||
end
|
||||
list.index = math.min(list.index, math.max(1, #list.items))
|
||||
if battle then
|
||||
list:close()
|
||||
showMessages(game, payload, function() battle:itemUsed({}) end)
|
||||
else
|
||||
showMessages(game, payload)
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
showMessages(game, payload) -- failed
|
||||
end
|
||||
|
||||
local function useItem(game, battle, id, list)
|
||||
local def = game.data.items[id]
|
||||
if ItemEffects.needsTarget(id, def) and not ItemEffects.isBall(id) then
|
||||
-- pick a target from the party
|
||||
local PartyMenu = require("src.ui.PartyMenu")
|
||||
-- the ETHERs and PP UP open the move menu after picking a mon
|
||||
-- (ItemUsePPRestore / ItemUsePPUp); the ELIXERs hit every move
|
||||
local wantsMove = id == "ETHER" or id == "MAX_ETHER" or id == "PP_UP"
|
||||
game.stack:push(PartyMenu.new(game, {
|
||||
pickOnly = true,
|
||||
onSwitch = function(mon)
|
||||
if not wantsMove then
|
||||
useOn(game, battle, id, mon, list)
|
||||
return
|
||||
end
|
||||
local rows = {}
|
||||
for mi, mv in ipairs(mon.moves) do
|
||||
local mdef = game.data.moves[mv.id]
|
||||
table.insert(rows, {
|
||||
value = mi,
|
||||
label = mdef and mdef.name or mv.id,
|
||||
right = ("%d"):format(mv.pp),
|
||||
})
|
||||
end
|
||||
game.stack:push(ListMenu.new(game, "Which move?", rows, {
|
||||
onChoose = function(row, l)
|
||||
l:close()
|
||||
useOn(game, battle, id, mon, list, row.value)
|
||||
end,
|
||||
}))
|
||||
end,
|
||||
}))
|
||||
else
|
||||
useOn(game, battle, id, nil, list)
|
||||
end
|
||||
end
|
||||
|
||||
function BagMenu.new(game, opts)
|
||||
opts = opts or {}
|
||||
local battle = opts.battle
|
||||
local list
|
||||
list = ListMenu.new(game, "ITEMS", buildItems(game), {
|
||||
footer = ("¥%d"):format(game.save.money),
|
||||
-- SELECT reorders items like the original bag (swap_items.asm)
|
||||
onSelectKey = function(item, l)
|
||||
if not item then return end
|
||||
if not l.swapIndex then
|
||||
l.swapIndex = l.index
|
||||
return
|
||||
end
|
||||
local order = Bag.order(game.save)
|
||||
order[l.swapIndex], order[l.index] = order[l.index], order[l.swapIndex]
|
||||
l.swapIndex = nil
|
||||
require("src.core.Sound").play(game.data, "Swap")
|
||||
l.items = buildItems(game)
|
||||
end,
|
||||
onChoose = function(item)
|
||||
local id = item.value
|
||||
local def = game.data.items[id]
|
||||
if list.swapIndex then -- A also completes a pending swap
|
||||
local order = Bag.order(game.save)
|
||||
order[list.swapIndex], order[list.index] = order[list.index], order[list.swapIndex]
|
||||
list.swapIndex = nil
|
||||
require("src.core.Sound").play(game.data, "Swap")
|
||||
list.items = buildItems(game)
|
||||
return
|
||||
end
|
||||
if battle then -- no tossing mid-battle
|
||||
useItem(game, battle, id, list)
|
||||
return
|
||||
end
|
||||
-- USE / TOSS submenu (the original's item options)
|
||||
local Menu = require("src.ui.Menu")
|
||||
game.stack:push(Menu.new(game, {
|
||||
{ label = "USE", onSelect = function()
|
||||
useItem(game, battle, id, list)
|
||||
end },
|
||||
{ label = "TOSS", onSelect = function()
|
||||
-- KeyItemFlags + HMs decide tossability (not price:
|
||||
-- MOON STONE is price 0 but tossable)
|
||||
if not def or def.keyItem or id:find("^HM_") then
|
||||
showMessages(game, { "That's too impor-\ntant to toss!" })
|
||||
return
|
||||
end
|
||||
local QuantityBox = require("src.ui.QuantityBox")
|
||||
game.stack:push(QuantityBox.new(game, {
|
||||
max = game.save.inventory[id] or 1,
|
||||
onDone = function(qty)
|
||||
if not qty then return end
|
||||
local ChoiceBox = require("src.ui.ChoiceBox")
|
||||
game.stack:push(ChoiceBox.new(game, function(yes)
|
||||
if not yes then return end
|
||||
Bag.remove(game.save, id, qty)
|
||||
list.items = buildItems(game)
|
||||
list.index = math.min(list.index, math.max(1, #list.items))
|
||||
showMessages(game, { ("Threw away\n%s."):format(def and def.name or id) })
|
||||
end))
|
||||
end,
|
||||
}))
|
||||
end },
|
||||
}, { tx = 12, ty = 10, tw = 8, th = 6 }))
|
||||
end,
|
||||
})
|
||||
return list
|
||||
end
|
||||
|
||||
return BagMenu
|
||||
Reference in New Issue
Block a user