mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-09-27 14:02:01 -04:00
930 lines
34 KiB
Lua
930 lines
34 KiB
Lua
-- FRLG field item-use handlers for game3 bag.
|
|
|
|
local ItemsData = require("src.core.game3.items_data")
|
|
local Bag = require("src.core.game3.bag")
|
|
local Pokemon = require("src.core.game3.pokemon")
|
|
local ModRuntime = require("src.mods.Runtime")
|
|
local Strings = require("src.core.Strings")
|
|
local Capabilities = require("src.core.game3.capabilities")
|
|
|
|
local ItemUse = {}
|
|
|
|
-- pokefirered/src/data/pokemon/item_effects.h:80
|
|
local HERB_HEAL = { [30] = 50, [31] = 200 }
|
|
local ITEM_REVIVAL_HERB = 33
|
|
|
|
-- pokefirered/src/data/pokemon/item_effects.h:81
|
|
local BITTER_MEDICINE_FRIENDSHIP = {
|
|
[30] = { -5, -5, -10 },
|
|
[31] = { -10, -10, -15 },
|
|
[32] = { -5, -5, -10 },
|
|
[33] = { -15, -15, -20 },
|
|
}
|
|
ItemUse.BITTER_MEDICINE_FRIENDSHIP = BITTER_MEDICINE_FRIENDSHIP
|
|
ItemUse.ITEM_REVIVAL_HERB = ITEM_REVIVAL_HERB
|
|
|
|
local function heal_amount(id)
|
|
local n = ItemsData.HEAL_AMOUNT[id]
|
|
if n then return n end
|
|
local num = ItemsData.toNumericId(id) or tonumber(id)
|
|
if num and HERB_HEAL[num] then return HERB_HEAL[num] end
|
|
if num and ItemsData.HEAL_AMOUNT[num] then return ItemsData.HEAL_AMOUNT[num] end
|
|
-- Pack holdEffectParam: Potion=20, Super=50, Hyper=200, Full Restore=255→full
|
|
local info = ItemsData.info(id)
|
|
local param = info and tonumber(info.holdEffectParam)
|
|
if param and param > 0 then
|
|
if param >= 255 then return 9999 end
|
|
return param
|
|
end
|
|
return nil
|
|
end
|
|
|
|
local function mon_status(mon)
|
|
if not mon then return nil, 0 end
|
|
local st = mon.status
|
|
local sleep = tonumber(mon.sleep) or 0
|
|
if type(st) == "string" and st ~= "" and st ~= "0" then return st, sleep end
|
|
local n = tonumber(st) or 0
|
|
if n ~= 0 then return n, sleep end
|
|
if sleep > 0 then return "SLP", sleep end
|
|
return nil, 0
|
|
end
|
|
|
|
--- Apply heal to one party slot. Returns ok, restoredAmount
|
|
function ItemUse.healMon(session, mon, id)
|
|
if not mon then return false, 0 end
|
|
local kind = ItemsData.medicineKind(id)
|
|
local maxHp = tonumber(mon.maxHp) or tonumber(mon.maxhp) or 0
|
|
local hp = tonumber(mon.hp) or 0
|
|
if maxHp <= 0 then return false, 0 end
|
|
|
|
if kind == "full_restore" then
|
|
if hp <= 0 then return false, 0 end
|
|
local changed = false
|
|
local restored = 0
|
|
if hp < maxHp then
|
|
restored = maxHp - hp
|
|
mon.hp = maxHp
|
|
changed = true
|
|
end
|
|
local st = mon_status(mon)
|
|
if st then
|
|
mon.status = nil
|
|
mon.sleep = 0
|
|
changed = true
|
|
end
|
|
return changed, restored
|
|
end
|
|
|
|
local amt = heal_amount(id)
|
|
if not amt then return false, 0 end
|
|
if hp <= 0 then return false, 0 end
|
|
if hp >= maxHp then return false, 0 end
|
|
local newHp
|
|
if amt >= 9999 then
|
|
newHp = maxHp
|
|
else
|
|
newHp = math.min(maxHp, hp + amt)
|
|
end
|
|
local restored = newHp - hp
|
|
mon.hp = newHp
|
|
return true, restored
|
|
end
|
|
|
|
function ItemUse.clearStatus(mon, id)
|
|
if not mon then return false, nil end
|
|
local st, sleep = mon_status(mon)
|
|
if not st and sleep <= 0 then return false, nil end
|
|
local num = ItemsData.toNumericId(id) or tonumber(id)
|
|
local cured = "status"
|
|
-- Specific cures when known; FULL HEAL / powder clear all.
|
|
if num == 14 then -- ANTIDOTE
|
|
if st ~= "PSN" and st ~= "TOX" and st ~= 1 and st ~= 2 then return false, nil end
|
|
cured = "poison"
|
|
elseif num == 15 then -- BURN
|
|
if st ~= "BRN" and st ~= 3 then return false, nil end
|
|
cured = "burn"
|
|
elseif num == 16 then -- ICE / FREEZE
|
|
if st ~= "FRZ" and st ~= 4 then return false, nil end
|
|
cured = "freeze"
|
|
elseif num == 17 then -- AWAKENING
|
|
if st ~= "SLP" and sleep <= 0 and st ~= 5 then return false, nil end
|
|
cured = "sleep"
|
|
elseif num == 18 then -- PARLYZ
|
|
if st ~= "PAR" and st ~= 6 then return false, nil end
|
|
cured = "paralysis"
|
|
end
|
|
mon.status = nil
|
|
mon.sleep = 0
|
|
return true, cured
|
|
end
|
|
|
|
function ItemUse.revive(mon, max)
|
|
if not mon then return false, 0 end
|
|
local maxHp = tonumber(mon.maxHp) or tonumber(mon.maxhp) or 0
|
|
local hp = tonumber(mon.hp) or 0
|
|
if hp > 0 or maxHp <= 0 then return false, 0 end
|
|
if max then
|
|
mon.hp = maxHp
|
|
else
|
|
mon.hp = math.max(1, math.floor(maxHp / 2))
|
|
end
|
|
mon.status = nil
|
|
mon.sleep = 0
|
|
return true, mon.hp
|
|
end
|
|
|
|
function ItemUse.reviveAll(party)
|
|
local any = false
|
|
for _, mon in ipairs(party or {}) do
|
|
if ItemUse.revive(mon, true) then any = true end
|
|
end
|
|
return any
|
|
end
|
|
|
|
--- Give item to party mon as held item. Returns ok, reason, messageText.
|
|
function ItemUse.giveToMon(session, bag, id, partySlot)
|
|
local party = session and session.party
|
|
local mon = party and party[partySlot]
|
|
if not mon then return false, "noparty", Strings("There's no POKéMON!") end
|
|
local pocket = ItemsData.pocketOf(id)
|
|
if pocket == "KEY_ITEMS" or pocket == "TM_CASE" then
|
|
return false, "cant_hold", Strings("This item can't be held.")
|
|
end
|
|
if not Bag.has(bag, id, 1) then
|
|
return false, "none", Strings("You don't have that item.")
|
|
end
|
|
local prev = mon.item or mon.heldItem
|
|
if prev and prev ~= 0 and prev ~= "" and prev ~= "NONE" then
|
|
-- Swap: return previous to bag if possible
|
|
if not Bag.canAdd(bag, prev, 1) then
|
|
return false, "bag_full", Strings("The BAG is full.")
|
|
end
|
|
end
|
|
Bag.remove(bag, id, 1)
|
|
if prev and prev ~= 0 and prev ~= "" and prev ~= "NONE" then
|
|
Bag.add(bag, prev, 1)
|
|
end
|
|
mon.item = ItemsData.toNumericId(id) or id
|
|
mon.heldItem = mon.item
|
|
local monName = Pokemon.displayMonName(mon)
|
|
local text
|
|
if prev and prev ~= 0 and prev ~= "" and prev ~= "NONE" then
|
|
text = Strings("Took the %s and\ngave the %s to\n%s.", ItemsData.displayName(prev), ItemsData.displayName(id), monName)
|
|
else
|
|
text = Strings("%s was given\nto %s.", ItemsData.displayName(id), monName)
|
|
end
|
|
local Q=require("src.core.game3.quest_log_recorder")
|
|
if prev and prev~=0 and prev~="" and prev~="NONE" then
|
|
Q.event(session,"SwappedHeldItemsOnMon",{monName,ItemsData.displayName(prev),ItemsData.displayName(id)})
|
|
else Q.event(session,"GaveMonHeldItem",{monName,ItemsData.displayName(id)}) end
|
|
return true, "give", text
|
|
end
|
|
|
|
--- Take held item from party mon. Returns ok, reason, messageText.
|
|
function ItemUse.takeFromMon(session, bag, partySlot)
|
|
local party = session and session.party
|
|
local mon = party and party[partySlot]
|
|
if not mon then return false, "noparty", Strings("There's no POKéMON!") end
|
|
local held = mon.item or mon.heldItem
|
|
local monName = Pokemon.displayMonName(mon)
|
|
if not held or held == 0 or held == "" or held == "NONE" then
|
|
return false, "none", Strings("%s isn't\nholding anything.", monName)
|
|
end
|
|
if not Bag.canAdd(bag, held, 1) then
|
|
return false, "bag_full", Strings("The BAG is full. The\nitem could not be removed.")
|
|
end
|
|
mon.item = nil
|
|
mon.heldItem = nil
|
|
Bag.add(bag, held, 1)
|
|
local text = Strings("Took the %s from\n%s and put it in the BAG.", ItemsData.displayName(held), monName)
|
|
require("src.core.game3.quest_log_recorder").event(session,"TookHeldItemFromMon",
|
|
{monName,ItemsData.displayName(held)})
|
|
return true, "take", text
|
|
end
|
|
|
|
-- pokefirered/include/global.fieldmap.h:191 gMapHeader
|
|
local function current_map_def(session)
|
|
local mapId = session and session.map
|
|
if type(mapId) ~= "string" then return nil end
|
|
local Runtime = package.loaded["src.core.game3.runtime"]
|
|
local game = Runtime and Runtime._game
|
|
local data = game and game.data and game.data.maps
|
|
local def = data and data[mapId]
|
|
if def then return def end
|
|
local Map = package.loaded["src.core.game3.map"]
|
|
local cur = Map and Map.currentDef and Map.currentDef()
|
|
if type(cur) == "table" and cur.id == mapId then return cur end
|
|
return nil
|
|
end
|
|
|
|
-- pokefirered/src/overworld.c:948 Overworld_IsBikingAllowed
|
|
local function map_header_flag(session, key)
|
|
local def = current_map_def(session)
|
|
if def == nil or def[key] == nil then return nil end
|
|
return (tonumber(def[key]) or 0) ~= 0
|
|
end
|
|
|
|
local function is_outdoor(session)
|
|
local mapId = session and session.map
|
|
if type(mapId) ~= "string" then return false end
|
|
local def = current_map_def(session)
|
|
local pair = def and (def.pair or (def.midLayout and def.midLayout.pair))
|
|
if type(pair) == "string" and pair:find("outdoor", 1, true) then
|
|
return true
|
|
end
|
|
-- Heuristic when layout pair missing
|
|
if mapId:find("HOUSE", 1, true) or mapId:find("CENTER", 1, true)
|
|
or mapId:find("GYM", 1, true) or mapId:find("MART", 1, true)
|
|
or mapId:find("LAB", 1, true) or mapId:find("CAVE", 1, true)
|
|
or mapId:find("TUNNEL", 1, true) or mapId:find("TOWER", 1, true)
|
|
or mapId:find("MANSION", 1, true) then
|
|
return false
|
|
end
|
|
if mapId:find("ROUTE", 1, true) or mapId:find("TOWN", 1, true)
|
|
or mapId:find("CITY", 1, true) or mapId:find("ISLAND", 1, true) then
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
-- pokefirered/src/item_use.c:614 CanUseEscapeRopeOnCurrMap
|
|
local function can_escape(session)
|
|
if not session then return false end
|
|
return map_header_flag(session, "allowEscaping") == true
|
|
end
|
|
|
|
function ItemUse.useEscapeRope(session, bag, id)
|
|
if not can_escape(session) then
|
|
return false, "escape", Strings("OAK: This isn't the\ntime to use that!")
|
|
end
|
|
local Runtime = package.loaded["src.core.game3.runtime"]
|
|
local mod = Runtime and Runtime._mod
|
|
local game = Runtime and Runtime._game
|
|
local Field = package.loaded["src.core.game3.field"]
|
|
Bag.remove(bag, id, 1)
|
|
local t = Strings("%s used\nESCAPE ROPE.", tostring(session.name or "RED"))
|
|
local hx = session.healX or 8
|
|
local hy = session.healY or 5
|
|
local mapId = session.healMap or "FR_PLAYERS_HOUSE_1F"
|
|
-- pokefirered/src/field_effect.c:2126 SetWarpDestinationToEscapeWarp
|
|
local esc = session.escapeWarp
|
|
if type(esc) == "table" and type(esc.map) == "string" then
|
|
mapId = esc.map
|
|
hx = tonumber(esc.x) or hx
|
|
hy = tonumber(esc.y) or hy
|
|
end
|
|
local Warp = require("src.core.game3.warp")
|
|
-- pokefirered/src/item_use.c:642 Task_UseDigEscapeRopeOnField
|
|
local function leave()
|
|
if mod and game then
|
|
Warp.request(mod, game, mapId, hx, hy, "down", { fade = true })
|
|
elseif Field and Field.respawnAtHeal then
|
|
local Map = require("src.core.game3.map")
|
|
Map.load(mod, game, mapId, { x = hx, y = hy, facing = "down" })
|
|
end
|
|
end
|
|
-- pokefirered/src/item_use.c:634 ItemUseOnFieldCB_EscapeRope
|
|
local function onField()
|
|
local okM, Message = pcall(require, "src.ui.game3.message")
|
|
if okM and Message and Message.show then
|
|
-- pokefirered/src/new_menu_helpers.c:641 DisplayItemMessageOnField
|
|
Message.show(t, { done = leave })
|
|
else
|
|
leave()
|
|
end
|
|
end
|
|
-- pokefirered/src/item_use.c:159 SetUpItemUseOnFieldCallback
|
|
if not ItemUse.setUpOnFieldCallback(onField) then onField() end
|
|
return true, "escape", t
|
|
end
|
|
|
|
-- pokefirered/src/item_use.c:253 FieldUseFunc_Bike
|
|
function ItemUse.useBike(session)
|
|
-- pokefirered/src/overworld.c:948 Overworld_IsBikingAllowed
|
|
local biking = map_header_flag(session, "bikingAllowed")
|
|
if biking == nil then biking = is_outdoor(session) end
|
|
if not biking then
|
|
local t = Strings("OAK: This isn't the\ntime to use that!")
|
|
return false, "bike", t
|
|
end
|
|
local Player = require("src.core.game3.player")
|
|
-- pokefirered/src/item_use.c:276 ItemUseOnFieldCB_Bicycle
|
|
if not Player.biking then
|
|
pcall(function()
|
|
local Audio = require("src.core.game3.audio")
|
|
local SE = require("src.core.game3.se_ids")
|
|
if Audio and Audio.playSe then Audio.playSe(SE.SE_BIKE_BELL) end
|
|
end)
|
|
end
|
|
Player.biking = not Player.biking
|
|
local t
|
|
if Player.biking then
|
|
t = Strings("%s got on the\nBICYCLE.", tostring(session.name or "RED"))
|
|
else
|
|
t = Strings("%s got off the\nBICYCLE.", tostring(session.name or "RED"))
|
|
end
|
|
return true, "bike", t
|
|
end
|
|
|
|
--- Check TM pre-flight compatibility and known moves matching retail FRLG.
|
|
-- Returns status ("knows" | "incompatible" | "ok"), messageText, moveId, moveName
|
|
function ItemUse.checkTmPreflight(mon, tmId)
|
|
if not mon then return "none", Strings("There's no POKéMON!"), nil, nil end
|
|
local moveId = Pokemon.moveFromTmItem(tmId)
|
|
local monName = Pokemon.displayMonName(mon)
|
|
local moveName = Pokemon.moveName(moveId) or "MOVE"
|
|
if not moveId then
|
|
return "invalid", Strings("This isn't the time to use\nthat!"), nil, nil
|
|
end
|
|
if Pokemon.knowsMove(mon, moveId) then
|
|
return "knows", Strings("%s already knows\n%s.", monName, moveName), moveId, moveName
|
|
end
|
|
local species = tonumber(mon.species or mon.speciesId)
|
|
if not Pokemon.canLearnTmItem(species, tmId) then
|
|
return "incompatible", Strings("%s can't learn\n%s.", monName, moveName), moveId, moveName
|
|
end
|
|
local prompt = Strings("Booted up a TM.\nIt contained %s.\nTeach %s to %s?", moveName, moveName, monName)
|
|
return "ok", prompt, moveId, moveName
|
|
end
|
|
|
|
--- Teach TM/HM. partySlot required. Consumes TM (not HM).
|
|
function ItemUse.useTm(session, bag, id, partySlot)
|
|
local party = session and session.party
|
|
local mon = party and party[partySlot]
|
|
if not mon then
|
|
return false, "noparty", Strings("There's no POKéMON!")
|
|
end
|
|
local status, preflightMsg, moveId, moveName = ItemUse.checkTmPreflight(mon, id)
|
|
if status ~= "ok" then
|
|
return false, status, preflightMsg
|
|
end
|
|
local monName = Pokemon.displayMonName(mon)
|
|
|
|
local isHm = ItemsData.isHm(id)
|
|
local consumed = false
|
|
|
|
local function finish_consume(learned)
|
|
if learned then
|
|
-- pokefirered/src/party_menu.c:4287
|
|
Pokemon.adjustFriendship(mon, Pokemon.FRIENDSHIP_EVENT_LEARN_TMHM,
|
|
{ mapSec = Pokemon.currentMapSec(session) })
|
|
require("src.core.game3.quest_log_recorder").event(session,
|
|
isHm and "MonLearnedMoveFromHM" or "MonLearnedMoveFromTM",{monName,moveName})
|
|
end
|
|
if learned and not isHm and not consumed then
|
|
Bag.remove(bag, id, 1)
|
|
consumed = true
|
|
end
|
|
end
|
|
|
|
if Pokemon.moveSlotCount(mon) < 4 then
|
|
local ok = Pokemon.teachMove(mon, moveId)
|
|
if ok then
|
|
finish_consume(true)
|
|
return true, "tm", Strings("%s learned\n%s!", monName, moveName)
|
|
end
|
|
end
|
|
|
|
local LearnMove = require("src.core.game3.battle.learn_move")
|
|
LearnMove.begin({
|
|
mon = mon,
|
|
moveId = moveId,
|
|
displayName = monName,
|
|
headless = true,
|
|
onDone = function(learned)
|
|
finish_consume(learned)
|
|
end,
|
|
})
|
|
if Pokemon.moveSlotCount(mon) >= 4 then
|
|
return false, "full", Strings("%s can't learn\nmore than four moves.", monName)
|
|
end
|
|
return true, "tm", Strings("%s learned\n%s!", monName, moveName)
|
|
end
|
|
|
|
--- Check if using this item requires selecting a party Pokémon target.
|
|
function ItemUse.needsPartyTarget(id)
|
|
if not id then return false end
|
|
local info = ItemsData.info(id)
|
|
if not info then return false end
|
|
local use = ItemsData.fieldUseKind(id)
|
|
if use == "heal" or use == "status" or use == "revive" or use == "tm"
|
|
or use == "pp" or use == "level" or use == "evo" or use == "vitamin" then
|
|
return true
|
|
end
|
|
if info.pocket == "TM_CASE" then return true end
|
|
return false
|
|
end
|
|
|
|
-- pokefirered/src/party_menu.c:5018
|
|
function ItemUse.levelUpEvent(mon, level)
|
|
if not ModRuntime.wants("pokemon.level_up") then return end
|
|
local G3 = require("src.mods.Gen3Compat")
|
|
local learnable, learnableIds = {}, {}
|
|
for _, mv in ipairs(Pokemon.movesLearnedAt(tonumber(mon.species or mon.speciesId), level)) do
|
|
learnable[#learnable + 1] = G3.moveName(mv)
|
|
learnableIds[#learnableIds + 1] = mv
|
|
end
|
|
ModRuntime.emit("pokemon.level_up", {
|
|
mon = mon, level = level, prevLevel = level - 1,
|
|
learnable = learnable, learnableIds = learnableIds, via = "item",
|
|
})
|
|
end
|
|
|
|
function ItemUse.useRareCandy(session, mon)
|
|
if not mon then return false, "none", Strings("There's no POKéMON!") end
|
|
local lvl = tonumber(mon.level) or 1
|
|
local hp = tonumber(mon.hp) or 0
|
|
if lvl >= 100 or hp <= 0 then
|
|
return false, "no_effect", Strings("It won't have any effect.")
|
|
end
|
|
local oldMax = tonumber(mon.maxHp) or tonumber(mon.maxhp) or 1
|
|
local oldHp = hp
|
|
mon.level = lvl + 1
|
|
Pokemon.applyStats(mon)
|
|
local newMax = tonumber(mon.maxHp) or tonumber(mon.maxhp) or oldMax
|
|
mon.hp = math.min(newMax, oldHp + math.max(0, newMax - oldMax))
|
|
-- pokefirered/src/data/pokemon/item_effects.h:200 sItemEffect_RareCandy
|
|
Pokemon.itemFriendship(mon, Pokemon.VITAMIN_FRIENDSHIP_CHANGE,
|
|
{ mapSec = Pokemon.currentMapSec(session) })
|
|
ItemUse.levelUpEvent(mon, mon.level)
|
|
local t = Strings("%s grew to\nLv. %d!", Pokemon.displayMonName(mon), mon.level)
|
|
return true, "level", t
|
|
end
|
|
|
|
-- pokefirered/src/data/pokemon/item_effects.h:168
|
|
local VITAMIN_STAT = {
|
|
[63] = "hp", [64] = "atk", [65] = "def", [66] = "spe", [67] = "spa", [70] = "spd",
|
|
}
|
|
local VITAMIN_ADD_EV = 10
|
|
|
|
local function vitamin_stat_name(key)
|
|
-- pokefirered/src/strings.c:248
|
|
if key == "hp" then return Strings("HP") end
|
|
if key == "atk" then return Strings("ATTACK") end
|
|
if key == "def" then return Strings("DEFENSE") end
|
|
if key == "spe" then return Strings("SPEED") end
|
|
if key == "spa" then return Strings("SP. ATK") end
|
|
return Strings("SP. DEF")
|
|
end
|
|
|
|
function ItemUse.useVitamin(session, mon, itemId)
|
|
if not mon then return false, "none", Strings("There's no POKéMON!") end
|
|
local num = ItemsData.toNumericId(itemId) or tonumber(itemId)
|
|
local key = VITAMIN_STAT[num]
|
|
if not key then
|
|
return false, "no_effect", Strings("It won't have any effect.")
|
|
end
|
|
-- pokefirered/src/party_menu.c:4405 NotUsingHPEVItemOnShedinja
|
|
if key == "hp" and (tonumber(mon.species or mon.speciesId) or 0) == 303 then
|
|
return false, "no_effect", Strings("It won't have any effect.")
|
|
end
|
|
local gained = Pokemon.raiseEvFromItem(mon, key, VITAMIN_ADD_EV)
|
|
if not gained or gained <= 0 then
|
|
return false, "no_effect", Strings("It won't have any effect.")
|
|
end
|
|
Pokemon.itemFriendship(mon, Pokemon.VITAMIN_FRIENDSHIP_CHANGE,
|
|
{ mapSec = Pokemon.currentMapSec(session) })
|
|
-- pokefirered/src/strings.c:298 gText_PkmnBaseVar2StatIncreased
|
|
local t = Strings("%s's base %s\nstat was raised.",
|
|
Pokemon.displayMonName(mon), vitamin_stat_name(key))
|
|
return true, "vitamin", t
|
|
end
|
|
|
|
function ItemUse.useEvolutionStone(session, mon, itemId, bag)
|
|
local Evolution = require("src.core.game3.evolution")
|
|
local target = Evolution.itemTarget and Evolution.itemTarget(mon, itemId, session)
|
|
if not target then
|
|
return false, "no_evo", Strings("It won't have any effect.")
|
|
end
|
|
local oldName = Pokemon.displayMonName(mon)
|
|
local newName = Pokemon.name(target) or "POKéMON"
|
|
|
|
local okEv, EvolutionScene = pcall(require, "src.ui.game3.evolution_scene")
|
|
if okEv and EvolutionScene and EvolutionScene.start and love and love.graphics then
|
|
local Audio = require("src.core.game3.audio")
|
|
EvolutionScene.start(mon, target, {
|
|
canStop = false,
|
|
session = session,
|
|
bag = bag,
|
|
savedSong = Audio._mapSong,
|
|
via = "item",
|
|
})
|
|
return true, "evo", Strings("Evolving...")
|
|
else
|
|
Evolution.apply(mon, target, session, bag, "item")
|
|
local t = Strings("%s evolved into\n%s!", oldName, newName)
|
|
return true, "evo", t
|
|
end
|
|
end
|
|
|
|
-- pokefirered/include/constants/items.h:273
|
|
local ROD_ITEMS = { [262] = true, [263] = true, [264] = true }
|
|
local ITEM_BLACK_FLUTE = 42
|
|
local ITEM_WHITE_FLUTE = 43
|
|
local ITEM_POKE_FLUTE = 350
|
|
-- pokefirered/include/constants/items.h:432 ITEM_BICYCLE
|
|
local ITEM_BICYCLE = 360
|
|
-- pokefirered/include/constants/items.h:438 ITEM_TEACHY_TV
|
|
local ITEM_TEACHY_TV = 366
|
|
-- pokefirered/include/constants/items.h:435 ITEM_FAME_CHECKER
|
|
local ITEM_FAME_CHECKER = 363
|
|
local ITEM_AWAKENING = 17
|
|
-- pokefirered/include/constants/flags.h:1330
|
|
local FLAG_SYS_WHITE_FLUTE_ACTIVE = 0x803
|
|
local FLAG_SYS_BLACK_FLUTE_ACTIVE = 0x804
|
|
-- pokefirered/include/constants/songs.h:114
|
|
local SE_GLASS_FLUTE = 110
|
|
-- pokefirered/include/constants/songs.h:346 MUS_POKE_FLUTE
|
|
local MUS_POKE_FLUTE = 338
|
|
|
|
local function sys_flag(session, flagId, value)
|
|
local Space = package.loaded["src.core.game3.scripting.space"]
|
|
local Flags = require("src.core.game3.scripting.flags")
|
|
if Space and Space.store then
|
|
Flags.setFlag(Space.store, Space.vm and Space.vm.ctx or nil, flagId, value)
|
|
end
|
|
if session then
|
|
session.flags = session.flags or {}
|
|
session.flags[flagId] = value or nil
|
|
end
|
|
end
|
|
|
|
local function play_se(id)
|
|
pcall(function()
|
|
local Audio = require("src.core.game3.audio")
|
|
if Audio and Audio.playSe then Audio.playSe(id) end
|
|
end)
|
|
end
|
|
|
|
local function not_the_time(session)
|
|
-- pokefirered/src/strings.c:188 gText_OakForbidsUseOfItemHere
|
|
return Strings("OAK: %s!\nThis isn't the time to use that!",
|
|
tostring((session and session.name) or "RED"))
|
|
end
|
|
|
|
-- pokefirered/src/item_use.c:159 SetUpItemUseOnFieldCallback
|
|
function ItemUse.exitMenusToField()
|
|
local closed = false
|
|
local BagMenu = package.loaded["src.ui.game3.bag_menu"]
|
|
if BagMenu and BagMenu.isOpen and BagMenu.isOpen() and BagMenu.close then
|
|
BagMenu.close()
|
|
closed = true
|
|
end
|
|
local StartMenu = package.loaded["src.ui.game3.start_menu"]
|
|
if StartMenu and StartMenu.isOpen and StartMenu.isOpen() then
|
|
StartMenu.open = false
|
|
StartMenu._onClose = nil
|
|
local okS, Stack = pcall(require, "src.ui.game3.stack")
|
|
if okS and Stack and Stack.pop then Stack.pop("start") end
|
|
closed = true
|
|
end
|
|
if closed then
|
|
local okF, Fade = pcall(require, "src.ui.game3.fade")
|
|
if okF and Fade and Fade.begin and Fade.MODE then
|
|
Fade.begin(Fade.MODE.FROM_BLACK, 1)
|
|
end
|
|
end
|
|
return closed
|
|
end
|
|
|
|
-- pokefirered/src/item_use.c:159 SetUpItemUseOnFieldCallback
|
|
function ItemUse.setUpOnFieldCallback(cb)
|
|
local BagMenu = package.loaded["src.ui.game3.bag_menu"]
|
|
if not (BagMenu and BagMenu.isOpen and BagMenu.isOpen()) then return false end
|
|
ItemUse._onFieldCB = cb
|
|
return true
|
|
end
|
|
|
|
-- pokefirered/src/item_use.c:176 Task_WaitFadeIn_CallItemUseOnFieldCB
|
|
function ItemUse.runOnFieldCallback()
|
|
local cb = ItemUse._onFieldCB
|
|
ItemUse._onFieldCB = nil
|
|
if cb then cb() end
|
|
return cb ~= nil
|
|
end
|
|
|
|
-- pokefirered/src/item_use.c:182 DisplayItemMessageInCurrentContext
|
|
function ItemUse.showFieldMessage(text, onDone)
|
|
if not text then return false end
|
|
-- pokefirered/src/item_menu.c:1018 DisplayItemMessageInBag
|
|
local BagMenu = package.loaded["src.ui.game3.bag_menu"]
|
|
if BagMenu and BagMenu.isOpen and BagMenu.isOpen() and BagMenu.showMessage then
|
|
BagMenu.showMessage(text, onDone)
|
|
return true
|
|
end
|
|
-- pokefirered/src/new_menu_helpers.c:641 DisplayItemMessageOnField
|
|
local okM, Message = pcall(require, "src.ui.game3.message")
|
|
if not (okM and Message and Message.show) then return false end
|
|
Message.show(text, { done = onDone })
|
|
return true
|
|
end
|
|
|
|
-- pokefirered/src/item_use.c:296 CanFish
|
|
function ItemUse.canFish()
|
|
local P = package.loaded["src.core.game3.player"] or require("src.core.game3.player")
|
|
local Collision = require("src.core.game3.collision")
|
|
local FieldMoves = require("src.core.game3.field_moves")
|
|
local DELTA = { up = { 0, -1 }, down = { 0, 1 }, left = { -1, 0 }, right = { 1, 0 } }
|
|
local d = DELTA[P.facing or "down"] or DELTA.down
|
|
local fx, fy = P.cellX + d[1], P.cellY + d[2]
|
|
local beh = Collision.behavior and Collision.behavior(fx, fy)
|
|
if FieldMoves.isWaterfallBehavior(beh) then return false end
|
|
if not P.surfing then
|
|
-- pokefirered/src/field_player_avatar.c:1209 IsPlayerFacingSurfableFishableWater
|
|
return (Collision.isWater and Collision.isWater(fx, fy)) == true
|
|
end
|
|
return (Collision.isSurfable and Collision.isSurfable(beh)) == true
|
|
and (tonumber(Collision.cell(fx, fy)) or 0xff) == 0
|
|
end
|
|
|
|
-- pokefirered/src/item_use.c:286 FieldUseFunc_Rod
|
|
function ItemUse.useRod(session, id)
|
|
if not ItemUse.canFish() then
|
|
-- pokefirered/src/item_use.c:294 PrintNotTheTimeToUseThat
|
|
local text = not_the_time(session)
|
|
return false, "rod", text
|
|
end
|
|
local info = ItemsData.info(id)
|
|
local Field = require("src.core.game3.field")
|
|
-- pokefirered/src/item_use.c:326 ItemUseOnFieldCB_Rod
|
|
Field.startFishing(tonumber(info and info.secondaryId) or 0)
|
|
return true, "rod", nil
|
|
end
|
|
|
|
-- pokefirered/src/item_use.c:359 FieldUseFunc_PokeFlute
|
|
function ItemUse.usePokeFlute(session)
|
|
local woke = false
|
|
for _, mon in ipairs((session and session.party) or {}) do
|
|
local isEgg = mon.isEgg or (type(mon.egg) == "boolean" and mon.egg)
|
|
if not isEgg and ItemUse.clearStatus(mon, ITEM_AWAKENING) then woke = true end
|
|
end
|
|
if not woke then
|
|
-- pokefirered/src/strings.c:204 gText_PlayedPokeFluteCatchy
|
|
local text = Strings("Played the POKé FLUTE.\\pNow, that's a catchy tune!")
|
|
return true, "flute", text
|
|
end
|
|
pcall(function()
|
|
local Audio = require("src.core.game3.audio")
|
|
if Audio and Audio.playFanfare then Audio.playFanfare(MUS_POKE_FLUTE) end
|
|
end)
|
|
-- pokefirered/src/strings.c:205 gText_PlayedPokeFlute, :206 gText_PokeFluteAwakenedMon
|
|
local text =
|
|
Strings("Played the POKé FLUTE.\\pThe POKé FLUTE awakened sleeping\nPOKéMON.")
|
|
return true, "flute", text
|
|
end
|
|
|
|
-- pokefirered/src/item_use.c:582 FieldUseFunc_BlackWhiteFlute
|
|
function ItemUse.useBlackWhiteFlute(session, num)
|
|
local name = ItemsData.displayName(num)
|
|
local player = tostring((session and session.name) or "RED")
|
|
play_se(SE_GLASS_FLUTE)
|
|
local text
|
|
if num == ITEM_WHITE_FLUTE then
|
|
sys_flag(session, FLAG_SYS_WHITE_FLUTE_ACTIVE, true)
|
|
sys_flag(session, FLAG_SYS_BLACK_FLUTE_ACTIVE, false)
|
|
-- pokefirered/src/strings.c:199 gText_UsedVar2WildLured
|
|
text = Strings("%s used the\n%s.\\pWild POKéMON will be lured.", player, name)
|
|
else
|
|
sys_flag(session, FLAG_SYS_BLACK_FLUTE_ACTIVE, true)
|
|
sys_flag(session, FLAG_SYS_WHITE_FLUTE_ACTIVE, false)
|
|
-- pokefirered/src/strings.c:200 gText_UsedVar2WildRepelled
|
|
text = Strings("%s used the\n%s.\\pWild POKéMON will be repelled.", player, name)
|
|
end
|
|
return true, "flute", text
|
|
end
|
|
|
|
--- Try field use. partySlot optional for heal/status/revive/tm/give.
|
|
-- Returns ok, reason, messageText
|
|
local function useField(session, bag, id, partySlot)
|
|
local info = ItemsData.info(id)
|
|
if not info then return false, "unknown", Strings("Unknown item.") end
|
|
local use = ItemsData.fieldUseKind(id)
|
|
|
|
if use == "battle" then
|
|
return false, "battle", Strings("This can't be used outside\nof battle.")
|
|
end
|
|
|
|
if use == "map" then
|
|
local RegionMap = require("src.ui.game3.region_map")
|
|
RegionMap.show({ session = session })
|
|
return true, "map", Strings("Used TOWN MAP.")
|
|
end
|
|
|
|
if use == "bike" then
|
|
return ItemUse.useBike(session)
|
|
end
|
|
|
|
-- pokefirered/src/item_use.c:337 FieldUseFunc_CoinCase
|
|
if use == "coin_case" then
|
|
local coins = Bag.Coins and Bag.Coins.get and Bag.Coins.get(session) or 0
|
|
-- pokefirered/src/strings.c:193 gText_CoinCase
|
|
return true, "coin_case", Strings("Your COINS:\n%d", coins)
|
|
end
|
|
|
|
-- pokefirered/src/item_use.c:348 FieldUseFunc_PowderJar
|
|
if use == "powder_jar" then
|
|
-- pokefirered/src/berry_powder.c:90 GetBerryPowder
|
|
local powder = math.floor(tonumber(session and session.berryPowder) or 0)
|
|
if powder < 0 then powder = 0 end
|
|
-- pokefirered/src/strings.c:202 gText_PowderQty
|
|
return true, "powder_jar", Strings("POWDER QTY: %d", powder)
|
|
end
|
|
|
|
if use == "escape" then
|
|
return ItemUse.useEscapeRope(session, bag, id)
|
|
end
|
|
|
|
if use == "repel" then
|
|
local steps = ItemsData.REPEL_STEPS[id]
|
|
or ItemsData.REPEL_STEPS[ItemsData.toNumericId(id) or -1]
|
|
or 100
|
|
session.repelSteps = steps
|
|
Bag.remove(bag, id, 1)
|
|
local t = Strings("The repelling effect wore\non for a while.")
|
|
return true, "repel", t
|
|
end
|
|
|
|
if use == "vs_seeker" or id == ItemsData.ITEM_VS_SEEKER or id == "VS_SEEKER"
|
|
or ItemsData.toNumericId(id) == ItemsData.ITEM_VS_SEEKER then
|
|
if not Capabilities.gate(session, "vs_seeker") then
|
|
return false, "vs_seeker", nil
|
|
end
|
|
local VsSeeker = require("src.core.game3.vs_seeker")
|
|
if not VsSeeker.canUseHere(session) then
|
|
return false, "vs_seeker", VsSeeker.notTimeText(session)
|
|
end
|
|
return true, "vs_seeker", nil
|
|
end
|
|
|
|
if use == "itemfinder" or id == ItemsData.ITEM_ITEMFINDER or id == "ITEMFINDER"
|
|
or ItemsData.toNumericId(id) == ItemsData.ITEM_ITEMFINDER then
|
|
local Field = require("src.core.game3.field")
|
|
local ok, kind, text = Field.useItemfinder(session)
|
|
return ok, kind or "itemfinder", text
|
|
end
|
|
|
|
if id == ItemsData.ITEM_TM_CASE or id == "TM_CASE"
|
|
or ItemsData.toNumericId(id) == ItemsData.ITEM_TM_CASE then
|
|
local TmCase = require("src.ui.game3.tm_case")
|
|
TmCase.show(session, bag)
|
|
return true, "tm_case", Strings("Opened TM CASE.")
|
|
end
|
|
|
|
if id == ItemsData.ITEM_BERRY_POUCH or id == "BERRY_POUCH"
|
|
or ItemsData.toNumericId(id) == ItemsData.ITEM_BERRY_POUCH then
|
|
local BerryPouch = require("src.ui.game3.berry_pouch")
|
|
BerryPouch.show(session, bag)
|
|
return true, "berry_pouch", Strings("Opened BERRY POUCH.")
|
|
end
|
|
|
|
-- pokefirered/src/item_use.c:518 FieldUseFunc_TeachyTv
|
|
if id == ITEM_TEACHY_TV or id == "TEACHY_TV"
|
|
or ItemsData.toNumericId(id) == ITEM_TEACHY_TV then
|
|
if not Capabilities.gate(session, "teachy_tv") then
|
|
return false, "teachy_tv", nil
|
|
end
|
|
local TeachyTv = require("src.core.game3.teachy_tv")
|
|
TeachyTv.show(session, bag)
|
|
return true, "teachy_tv", nil
|
|
end
|
|
|
|
-- pokefirered/src/item_use.c:680 FieldUseFunc_FameChecker
|
|
if id == ITEM_FAME_CHECKER or id == "FAME_CHECKER"
|
|
or ItemsData.toNumericId(id) == ITEM_FAME_CHECKER then
|
|
if not Capabilities.gate(session, "fame_checker") then
|
|
return false, "fame_checker", nil
|
|
end
|
|
local FameCheckerUi = require("src.ui.game3.fame_checker")
|
|
-- pokefirered/src/item_use.c:696 UseFameCheckerFromBag
|
|
local okBag, BagMenu = pcall(require, "src.ui.game3.bag_menu")
|
|
local fromBag = okBag and BagMenu and BagMenu.open and true or false
|
|
FameCheckerUi.show(session, { fromBag = fromBag })
|
|
return true, "fame_checker", nil
|
|
end
|
|
|
|
do
|
|
local num = ItemsData.toNumericId(id) or tonumber(id)
|
|
if ROD_ITEMS[num] then
|
|
return ItemUse.useRod(session, id)
|
|
end
|
|
if num == ITEM_POKE_FLUTE then
|
|
return ItemUse.usePokeFlute(session)
|
|
end
|
|
if num == ITEM_WHITE_FLUTE or num == ITEM_BLACK_FLUTE then
|
|
return ItemUse.useBlackWhiteFlute(session, num)
|
|
end
|
|
-- pokefirered/src/item_use.c:253 FieldUseFunc_Bike
|
|
if num == ITEM_BICYCLE then
|
|
return ItemUse.useBike(session)
|
|
end
|
|
end
|
|
|
|
if use == "key" or use == "rod" or use == "berry" or use == "mail"
|
|
or use == "flute" or use == "none" then
|
|
local t = Strings("OAK: This isn't the\ntime to use that!")
|
|
return false, use, t
|
|
end
|
|
|
|
if use == "heal" or use == "status" or use == "revive" or use == "tm"
|
|
or use == "pp" or use == "level" or use == "evo" or use == "vitamin" then
|
|
local party = session and session.party
|
|
if not party or #party < 1 then
|
|
return false, "noparty", Strings("There is no POKéMON.")
|
|
end
|
|
if not partySlot then
|
|
return false, "need_slot", Strings("Select a POKéMON.")
|
|
end
|
|
local mon = party[partySlot]
|
|
if not mon then
|
|
return false, "noparty", Strings("There is no POKéMON.")
|
|
end
|
|
local ok = false
|
|
local text = nil
|
|
local _
|
|
local num = ItemsData.toNumericId(id) or tonumber(id)
|
|
local monName = Pokemon.displayMonName(mon)
|
|
|
|
if use == "tm" then
|
|
return ItemUse.useTm(session, bag, id, partySlot)
|
|
elseif use == "evo" then
|
|
ok, _, text = ItemUse.useEvolutionStone(session, mon, id, bag)
|
|
elseif use == "level" then
|
|
ok, _, text = ItemUse.useRareCandy(session, mon)
|
|
-- pokefirered/src/pokemon.c:4258
|
|
elseif use == "revive" or num == ITEM_REVIVAL_HERB then
|
|
if num == 45 then -- Sacred Ash
|
|
ok = ItemUse.reviveAll(party)
|
|
text = Strings("All POKéMON's HP was\nfully restored!")
|
|
else
|
|
local max = num == 25 or num == ITEM_REVIVAL_HERB or tostring(id) == "MAX_REVIVE"
|
|
ok = ItemUse.revive(mon, max)
|
|
text = Strings("%s's HP was\nrestored!", monName)
|
|
end
|
|
elseif use == "status" then
|
|
local stOk, cured = ItemUse.clearStatus(mon, id)
|
|
ok = stOk
|
|
if cured == "poison" then
|
|
text = Strings("%s was\ncured of poison.", monName)
|
|
elseif cured == "paralysis" then
|
|
text = Strings("%s was\ncured of paralysis.", monName)
|
|
elseif cured == "burn" then
|
|
text = Strings("%s's burn\nwas healed.", monName)
|
|
elseif cured == "freeze" then
|
|
text = Strings("%s was\ndefrosted.", monName)
|
|
elseif cured == "sleep" then
|
|
text = Strings("%s woke up.", monName)
|
|
else
|
|
text = Strings("%s recovered\nfrom illness!", monName)
|
|
end
|
|
elseif use == "pp" then
|
|
return false, "pp", Strings("It won't have any effect.")
|
|
elseif use == "vitamin" then
|
|
ok, _, text = ItemUse.useVitamin(session, mon, id)
|
|
else
|
|
local healOk, restored = ItemUse.healMon(session, mon, id)
|
|
ok = healOk
|
|
if restored and restored > 0 then
|
|
text = Strings("%s's HP was\nrestored by %d points.", monName, restored)
|
|
else
|
|
text = Strings("%s's HP was\nrestored!", monName)
|
|
end
|
|
end
|
|
|
|
if ok then
|
|
-- pokefirered/src/pokemon.c:4481
|
|
if BITTER_MEDICINE_FRIENDSHIP[num] then
|
|
Pokemon.itemFriendship(mon, BITTER_MEDICINE_FRIENDSHIP[num],
|
|
{ mapSec = Pokemon.currentMapSec(session) })
|
|
end
|
|
Bag.remove(bag, id, 1)
|
|
return true, use, text or Strings("It restored health!")
|
|
end
|
|
return false, "noeffect", text or Strings("It won't have any effect.")
|
|
end
|
|
|
|
return false, "none", Strings("OAK: This isn't the\ntime to use that!")
|
|
end
|
|
|
|
function ItemUse.useField(session,bag,id,partySlot)
|
|
local ok,kind,text
|
|
if ModRuntime.wantsHook("item.use") then
|
|
local Runtime=package.loaded["src.core.game3.runtime"]
|
|
ok,kind,text=ModRuntime.call("item.use",function(_,_,hid,hslot)
|
|
return useField(session,bag,hid,hslot)
|
|
end,Runtime and Runtime._game,nil,id,partySlot,bag)
|
|
else
|
|
ok,kind,text=useField(session,bag,id,partySlot)
|
|
end
|
|
if ok and kind~="tm" and kind~="tm_case" and kind~="berry_pouch" and kind~="vs_seeker" then
|
|
local Items=require("src.core.game3.items")
|
|
local Pokemon=require("src.core.game3.pokemon")
|
|
local mon=partySlot and session and session.party and session.party[partySlot]
|
|
require("src.core.game3.quest_log_recorder").event(session,
|
|
mon and "UsedItemOnMonAtThisLocation" or "UsedTheItem",
|
|
{Items.displayName(id),mon and Pokemon.displayMonName(mon)})
|
|
end
|
|
return ok,kind,text
|
|
end
|
|
return ItemUse
|