mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-15 15:51:17 +02:00
498 lines
20 KiB
Lua
498 lines
20 KiB
Lua
-- Gen 2 pack items used on a party mon outside battle: the ITEMMENU_PARTY
|
|
-- half of engine/items/pack.asm UseItem, ported per item family from
|
|
-- engine/items/item_effects.asm.
|
|
--
|
|
-- Love-free on purpose, the same split src/core/gen2/Evolution.lua makes:
|
|
-- every routine here is table math over a party record, so the whole family
|
|
-- is assertable without a window. The screens (PackMenu -> PartyMenu ->
|
|
-- MoveDeleter) only choose the target and print what comes back.
|
|
--
|
|
-- Two contracts every entry point keeps, both from UseItem_SelectMon:
|
|
-- - an EGG refuses with CantUseOnEggMessage before any effect runs
|
|
-- (`cp EGG` is the routine's first test after the pick), and
|
|
-- - a refusal costs nothing: the item is only removed by the caller when
|
|
-- `used` comes back true, which is UseDisposableItem's own placement at
|
|
-- the tail of each success path.
|
|
|
|
local Happiness = require("src.core.gen2.Happiness")
|
|
local Mon = require("src.battle.gen2.Mon")
|
|
|
|
local ItemEffects = {}
|
|
|
|
-- data/items/heal_hp.asm HealingHPAmounts. MAX_STAT_VALUE (999) is the
|
|
-- table's own "everything" byte pair; nothing reaches it before the min()
|
|
-- against the missing HP.
|
|
ItemEffects.HEAL_HP = {
|
|
FRESH_WATER = 50, SODA_POP = 60, LEMONADE = 80,
|
|
HYPER_POTION = 200, SUPER_POTION = 50, POTION = 20,
|
|
MAX_POTION = 999, FULL_RESTORE = 999, MOOMOO_MILK = 100,
|
|
BERRY = 10, GOLD_BERRY = 30, ENERGYPOWDER = 50, ENERGY_ROOT = 200,
|
|
RAGECANDYBAR = 20, BERRY_JUICE = 20,
|
|
}
|
|
|
|
-- data/items/heal_status.asm StatusHealingActions, folded to the status class
|
|
-- each row's mask names. FULL_RESTORE is deliberately absent: its status
|
|
-- half only runs from FullRestoreEffect's full-HP arm, handled in useOnMon.
|
|
ItemEffects.HEAL_STATUS = {
|
|
ANTIDOTE = "psn", BURN_HEAL = "brn", ICE_HEAL = "frz",
|
|
AWAKENING = "slp", PARLYZ_HEAL = "par",
|
|
FULL_HEAL = "all", HEAL_POWDER = "all",
|
|
PSNCUREBERRY = "psn", PRZCUREBERRY = "par", BURNT_BERRY = "frz",
|
|
ICE_BERRY = "brn", MINT_BERRY = "slp", MIRACLEBERRY = "all",
|
|
}
|
|
|
|
-- RevivePokemon's one split: `cp REVIVE / jr z, .revive_half_hp` -- only the
|
|
-- plain REVIVE halves, MAX_REVIVE and REVIVAL_HERB both take ReviveFullHP.
|
|
ItemEffects.REVIVE = {
|
|
REVIVE = "half", MAX_REVIVE = "full", REVIVAL_HERB = "full",
|
|
}
|
|
|
|
-- RestorePP's per-item amounts: `ld c, 10` for the ETHER family, `ld c, 5`
|
|
-- under `cp MYSTERYBERRY`, and the `.restore_all` arm for the MAX pair.
|
|
-- `each` marks Elixer_RestorePPofAllMoves' loop over all four slots.
|
|
ItemEffects.RESTORE_PP = {
|
|
ETHER = { amount = 10 },
|
|
MAX_ETHER = { amount = "all" },
|
|
MYSTERYBERRY = { amount = 5 },
|
|
ELIXER = { amount = 10, each = true },
|
|
MAX_ELIXER = { amount = "all", each = true },
|
|
}
|
|
|
|
-- EnergypowderEnergyRootCommon / HealPowderEffect: the herb items charge
|
|
-- happiness for tasting bitter on top of their heal.
|
|
local BITTER = {
|
|
ENERGYPOWDER = "BITTERPOWDER", ENERGY_ROOT = "ENERGYROOT",
|
|
HEAL_POWDER = "BITTERPOWDER", REVIVAL_HERB = "REVIVALHERB",
|
|
}
|
|
|
|
-- _ItemWontHaveEffectText / _ItemCantUseOnEggText (data/text/common_3.asm).
|
|
ItemEffects.TEXT_NO_EFFECT = "It won't have any\neffect."
|
|
ItemEffects.TEXT_CANT_USE_ON_EGG = "That can't be used\non an EGG."
|
|
-- _PPRestoredText (data/text/common_3.asm).
|
|
ItemEffects.TEXT_PP_RESTORED = "PP was restored."
|
|
|
|
-- PrintPartyMenuActionText's .MenuActionTexts (engine/pokemon/party_menu.asm),
|
|
-- keyed by the class GetItemHealingAction resolves. Each is the two rows the
|
|
-- cart prints: the nickname line, then the fixed line.
|
|
local STATUS_TEXT = {
|
|
psn = "%s's\ncured of poison.",
|
|
par = "%s's\nrid of paralysis.",
|
|
brn = "%s's\nburn was healed.",
|
|
frz = "%s\nwas defrosted.",
|
|
slp = "%s\nwoke up.",
|
|
all = "%s's\nhealth returned.",
|
|
}
|
|
|
|
-- The port's party records spell status several ways (the battle writes the
|
|
-- long names, the party list reads both); fold them to the class letters the
|
|
-- heal tables use. FNT is an HP fact, not a status, and is not here.
|
|
--
|
|
-- Cross-file contract: every name src/battle/gen2/Battle.lua can write into
|
|
-- mon.status (STATUS_EFFECTS, SECONDARY_EFFECTS, HELD_STATUS_CURES and the
|
|
-- STATUS_TEXT lines beside them) must have a row here, or the cure for it
|
|
-- refuses everywhere in the pack. tests/gen2_battle_pack_test.lua walks the
|
|
-- battle's own tables against this one so the two cannot drift apart.
|
|
ItemEffects.STATUS_CLASS = {
|
|
psn = "psn", poison = "psn", toxic = "psn",
|
|
brn = "brn", burn = "brn",
|
|
frz = "frz", freeze = "frz",
|
|
par = "par", paralysis = "par", paralyze = "par",
|
|
slp = "slp", sleep = "slp",
|
|
}
|
|
local STATUS_CLASS = ItemEffects.STATUS_CLASS
|
|
|
|
local function monName(mon)
|
|
return (mon and (mon.nickname or mon.name or mon.species)) or "?"
|
|
end
|
|
|
|
local function maxHpOf(mon)
|
|
return mon.maxHp or (mon.stats and mon.stats.hp) or 0
|
|
end
|
|
|
|
local function fainted(mon)
|
|
return (mon.hp or 0) <= 0
|
|
end
|
|
|
|
-- HealStatus's field half: the status byte, the toxic counter and the turn
|
|
-- counter all clear together (wPlayerSubStatus5's SUBSTATUS_TOXIC rides the
|
|
-- same wipe on the cart; this port keeps that ramp on the mon record, so it
|
|
-- goes with the byte rather than with the battler).
|
|
local function clearStatus(mon)
|
|
mon.status = nil
|
|
mon.statusTurns = nil
|
|
mon.toxicCounter = nil
|
|
end
|
|
|
|
local function bitterHappiness(itemId, mon)
|
|
local event = BITTER[itemId]
|
|
if event then Happiness.change(mon, event) end
|
|
end
|
|
|
|
-- ItemRestoreHP: fainted and full-HP targets refuse before anything is spent,
|
|
-- then RestoreHealth adds the HealingHPAmounts row capped at max HP and
|
|
-- PARTYMENUTEXT_HEAL_HP prints the delta ("<name>\nrecovered NN HP!").
|
|
local function restoreHp(itemId, mon)
|
|
local amount = ItemEffects.HEAL_HP[itemId]
|
|
local maxHp = maxHpOf(mon)
|
|
if fainted(mon) or (mon.hp or 0) >= maxHp then
|
|
return { used = false, text = ItemEffects.TEXT_NO_EFFECT }
|
|
end
|
|
local healed = math.min(maxHp, (mon.hp or 0) + amount)
|
|
local gained = healed - (mon.hp or 0)
|
|
mon.hp = healed
|
|
-- FullRestoreEffect's .FullRestore clears the status alongside the refill.
|
|
if itemId == "FULL_RESTORE" then clearStatus(mon) end
|
|
bitterHappiness(itemId, mon)
|
|
return {
|
|
used = true,
|
|
text = ("%s\nrecovered %d HP!"):format(monName(mon), gained),
|
|
}
|
|
end
|
|
|
|
-- Which StatusHealingActions class cures this status. The fold table above
|
|
-- answers for every spelling the port writes; anything it does not know is a
|
|
-- status some mod registered, and its own `statuses` record says which cure
|
|
-- answers for it (src/battle/gen2/Battle.lua STATUSES, field `healClass`) --
|
|
-- read straight off the merged table so this module does not have to require
|
|
-- the battle engine to cure a burn.
|
|
function ItemEffects.healClassOf(status, data)
|
|
local key = tostring(status or ""):lower()
|
|
local class = STATUS_CLASS[key]
|
|
if class then return class end
|
|
local statuses = data and data.gen2Statuses
|
|
local record = statuses and (statuses[status] or statuses[key])
|
|
return record and record.healClass or nil
|
|
end
|
|
|
|
-- UseStatusHealer: the status byte must intersect the item's mask ($ff for
|
|
-- the HEAL_ALL family); a clean or fainted mon refuses. Field only -- the
|
|
-- confusion arm reads wPlayerSubStatus3, which does not exist out of battle.
|
|
local function healStatus(itemId, mon, class, data)
|
|
if fainted(mon) then
|
|
return { used = false, text = ItemEffects.TEXT_NO_EFFECT }
|
|
end
|
|
local have = ItemEffects.healClassOf(mon.status, data)
|
|
if not have or (class ~= "all" and have ~= class) then
|
|
return { used = false, text = ItemEffects.TEXT_NO_EFFECT }
|
|
end
|
|
clearStatus(mon)
|
|
bitterHappiness(itemId, mon)
|
|
local shape = STATUS_TEXT[class == "all" and "all" or have]
|
|
return { used = true, text = shape:format(monName(mon)) }
|
|
end
|
|
|
|
-- RevivePokemon: only a fainted mon accepts; REVIVE stands it up at half max
|
|
-- HP (ReviveHalfHP's `srl d / rr e`), the other two at full.
|
|
local function revive(itemId, mon)
|
|
if not fainted(mon) then
|
|
return { used = false, text = ItemEffects.TEXT_NO_EFFECT }
|
|
end
|
|
local maxHp = maxHpOf(mon)
|
|
mon.hp = (ItemEffects.REVIVE[itemId] == "half")
|
|
and math.max(1, math.floor(maxHp / 2)) or maxHp
|
|
clearStatus(mon)
|
|
bitterHappiness(itemId, mon)
|
|
return {
|
|
used = true,
|
|
text = ("%s\nis revitalized."):format(monName(mon)),
|
|
}
|
|
end
|
|
|
|
-- RareCandyEffect: MAX_LEVEL refuses; otherwise the level goes up one, the
|
|
-- experience is SET to CalcExpAtLevel's threshold, the stats recompute and
|
|
-- the CURRENT HP gains the max-HP delta -- no clamp and no faint check, so a
|
|
-- fainted mon stands up with the delta, exactly as the cart's arithmetic
|
|
-- leaves it. `learned` is LearnLevelMoves' slice: the EvosAttacks rows at
|
|
-- exactly the new level, for the caller to offer.
|
|
local function rareCandy(mon, data)
|
|
if (mon.level or 0) >= Mon.MAX_LEVEL then
|
|
return { used = false, text = ItemEffects.TEXT_NO_EFFECT }
|
|
end
|
|
local def = data and data.pokemon and data.pokemon[mon.species]
|
|
-- through Mon.growthFor, so a growth_rates record a mod registered is the
|
|
-- curve a Rare Candy uses too; wiring only some of the six readers would let
|
|
-- a mod curve drive battle EXP but not the candy
|
|
local growth = Mon.growthFor(data, def and def.growthRate)
|
|
local newLevel = (mon.level or 1) + 1
|
|
mon.level = newLevel
|
|
mon.experience = Mon.experienceForLevel(growth, newLevel)
|
|
local previousMax = maxHpOf(mon)
|
|
if def and def.baseStats then
|
|
mon.stats = Mon.stats(def.baseStats, mon.dvs, newLevel, mon.statExp)
|
|
mon.maxHp = mon.stats.hp
|
|
mon.hp = (mon.hp or previousMax) + (mon.maxHp - previousMax)
|
|
end
|
|
Happiness.change(mon, "GAINLEVEL")
|
|
local learned = {}
|
|
for _, entry in ipairs((def and def.levelMoves) or {}) do
|
|
if entry.level == newLevel then learned[#learned + 1] = entry.move end
|
|
end
|
|
return {
|
|
used = true,
|
|
level = newLevel,
|
|
learned = learned,
|
|
text = ("%s grew to\nlevel %d!"):format(monName(mon), newLevel),
|
|
}
|
|
end
|
|
|
|
-- --------------------------------------------------------- held attributes
|
|
--
|
|
-- The `held_items` registry (src/mods/Schemas.lua), which is the last two
|
|
-- columns of data/items/attributes.asm lifted out of the item record:
|
|
-- ItemAttributes' HELD_* effect byte and its parameter. Gen 1 has neither, so
|
|
-- this is one of the Gen 2-only registries -- gated under Gen 1, routed to
|
|
-- data.gen2HeldItems under Gen 2.
|
|
--
|
|
-- The table is a VIEW of data.items rather than a second source of truth: the
|
|
-- extractor writes both columns onto the item record and
|
|
-- src/battle/gen2/Battle.lua:itemDef reads them from there, so the registry
|
|
-- has to end up back on data.items or a mod's write would land in a table the
|
|
-- battle never opens. Hence the three routines below and the two calls in
|
|
-- src/core/Game2.lua:load that use them:
|
|
--
|
|
-- heldItemsFrom(items) builds the merge target, BEFORE mods:load, so the
|
|
-- registry's base is the vanilla row and a mod's
|
|
-- patch stacks on top of it (register collides, the
|
|
-- way it does against any other seeded id)
|
|
-- heldSnapshot(view) the same two bytes per id, kept aside
|
|
-- applyHeldItems(...) AFTER the merge: writes back only the ids whose
|
|
-- merged value differs from that snapshot
|
|
--
|
|
-- The diff is the whole reason this is not a blind write-back. A mod may just
|
|
-- as well reach an item's held columns through the shared `items` registry;
|
|
-- that merge has already landed on data.items by the time this runs, and
|
|
-- writing every row back would revert it to the vanilla view captured before
|
|
-- the merge. Only what the held_items merge actually changed is written, so
|
|
-- the two routes compose instead of racing.
|
|
function ItemEffects.heldItemsFrom(items)
|
|
local out = {}
|
|
for id, def in pairs(items or {}) do
|
|
if type(def) == "table" and def.heldEffect ~= nil then
|
|
out[id] = { heldEffect = def.heldEffect,
|
|
heldParameter = def.heldParameter or 0 }
|
|
end
|
|
end
|
|
return out
|
|
end
|
|
|
|
function ItemEffects.heldSnapshot(view)
|
|
local out = {}
|
|
for id, row in pairs(view or {}) do
|
|
if type(row) == "table" then
|
|
out[id] = { heldEffect = row.heldEffect, heldParameter = row.heldParameter }
|
|
end
|
|
end
|
|
return out
|
|
end
|
|
|
|
-- the merged `held_items` record for an item, the item's own columns when no
|
|
-- loader ran
|
|
function ItemEffects.heldItemFor(itemId, data)
|
|
if itemId == nil then return nil end
|
|
local merged = data and data.gen2HeldItems
|
|
local row = merged and merged[itemId]
|
|
if row then return row end
|
|
local def = data and data.items and data.items[itemId]
|
|
if type(def) ~= "table" or def.heldEffect == nil then return nil end
|
|
return { heldEffect = def.heldEffect, heldParameter = def.heldParameter or 0 }
|
|
end
|
|
|
|
-- Returns the number of item records the merge changed. Zero on a mod-free
|
|
-- boot, which is the parity claim: the seeded row IS the item's own two bytes,
|
|
-- so nothing differs and nothing is written.
|
|
function ItemEffects.applyHeldItems(data, snapshot)
|
|
local items = data and data.items
|
|
local merged = data and data.gen2HeldItems
|
|
if not (items and merged) then return 0 end
|
|
local applied = 0
|
|
for id, row in pairs(merged) do
|
|
if type(row) == "table" then
|
|
local was = (snapshot or {})[id]
|
|
if not was or was.heldEffect ~= row.heldEffect
|
|
or was.heldParameter ~= row.heldParameter then
|
|
local def = items[id]
|
|
if type(def) == "table" then
|
|
def.heldEffect = row.heldEffect
|
|
def.heldParameter = row.heldParameter or 0
|
|
applied = applied + 1
|
|
end
|
|
end
|
|
end
|
|
end
|
|
-- a tombstoned id (mod.content.held_items:remove) leaves the merged table
|
|
-- without the row, which is the cart's own "holds nothing"
|
|
for id, was in pairs(snapshot or {}) do
|
|
if merged[id] == nil and type(items[id]) == "table" and was.heldEffect then
|
|
items[id].heldEffect = nil
|
|
items[id].heldParameter = nil
|
|
applied = applied + 1
|
|
end
|
|
end
|
|
return applied
|
|
end
|
|
|
|
-- the merged `item_effects` record for an item id, the module's own when no
|
|
-- loader ran; `data` is optional so the callers that only know an item id
|
|
-- (src/core/Game2.lua asks partyAction before it has picked a mon) keep
|
|
-- their signature and read the module records
|
|
function ItemEffects.recordFor(itemId, data)
|
|
if itemId == nil then return nil end
|
|
local merged = data and data.gen2ItemEffects
|
|
return (merged and merged[itemId]) or ItemEffects.RECORDS[itemId]
|
|
end
|
|
|
|
-- Which family a PACK item runs on a party mon, or nil for anything whose
|
|
-- ITEMMENU_PARTY behaviour is not ported (vitamins, PP UP, evolution stones).
|
|
-- FULL_RESTORE classifies as "heal"; its full-HP status arm lives inside the
|
|
-- record's own `use` the way FullRestoreEffect keeps both halves in one
|
|
-- routine.
|
|
function ItemEffects.partyAction(itemId, data)
|
|
local record = ItemEffects.recordFor(itemId, data)
|
|
return record and record.action or nil
|
|
end
|
|
|
|
-- The one-call families (everything but PP, which needs a move pick first).
|
|
-- Returns { used, text, learned?, level? }.
|
|
function ItemEffects.useOnMon(itemId, mon, data)
|
|
if not mon then return { used = false, text = ItemEffects.TEXT_NO_EFFECT } end
|
|
if mon.isEgg then
|
|
return { used = false, text = ItemEffects.TEXT_CANT_USE_ON_EGG }
|
|
end
|
|
local record = ItemEffects.recordFor(itemId, data)
|
|
-- The PP family has its own entry point; reaching it here is the same
|
|
-- "nothing happens" the unported items get.
|
|
if not record or not record.use or record.action == "pp" then
|
|
return { used = false, text = ItemEffects.TEXT_NO_EFFECT }
|
|
end
|
|
return record.use({ item = itemId, mon = mon, data = data })
|
|
end
|
|
|
|
-- RestorePP over one move entry: a slot already at max refuses (`cp b /
|
|
-- jr nc, .dont_restore`), "all" fills it, a number adds capped at max.
|
|
local function restoreMove(move, amount)
|
|
if type(move) ~= "table" or not move.id then return false end
|
|
local maxPp = move.maxPp or move.pp or 0
|
|
if (move.pp or 0) >= maxPp then return false end
|
|
if amount == "all" then
|
|
move.pp = maxPp
|
|
else
|
|
move.pp = math.min(maxPp, (move.pp or 0) + amount)
|
|
end
|
|
return true
|
|
end
|
|
|
|
-- RestorePPEffect's two shapes: the ETHER family lands on one chosen slot,
|
|
-- the ELIXER family (Elixer_RestorePPofAllMoves) walks every slot and counts
|
|
-- -- one restored move is enough for the item to be spent.
|
|
function ItemEffects.usePpItem(itemId, mon, slot, data)
|
|
if not mon then return { used = false, text = ItemEffects.TEXT_NO_EFFECT } end
|
|
if mon.isEgg then
|
|
return { used = false, text = ItemEffects.TEXT_CANT_USE_ON_EGG }
|
|
end
|
|
local record = ItemEffects.recordFor(itemId, data)
|
|
if not record or not record.use or record.action ~= "pp" then
|
|
return { used = false, text = ItemEffects.TEXT_NO_EFFECT }
|
|
end
|
|
return record.use({ item = itemId, mon = mon, data = data, slot = slot })
|
|
end
|
|
|
|
-- ------------------------------------------------------------- the registry
|
|
--
|
|
-- The four tables above as records, in the shape src/mods/Schemas.lua's
|
|
-- `item_effects` registry validates. Same registry NAME the Gen 1 catalog
|
|
-- carries, and the same two Gen 1 fields where Gen 2 has a meaning for them:
|
|
-- `use` (required) and `field`, which is true for every row here because this
|
|
-- module is the ITEMMENU_PARTY half of pack.asm and nothing else.
|
|
--
|
|
-- `use` is fn(ctx) -> { used, text, learned?, level? }, where ctx carries
|
|
-- { item, mon, data, slot }. Gen 1 has no call site for its own item_effects
|
|
-- records yet, so this is the first shape either generation gives them; it is
|
|
-- the one Gold's two entry points already hand around.
|
|
--
|
|
-- Two fields Gen 2 adds rather than renaming anything: `action`, the family
|
|
-- src/core/Game2.lua and src/ui/gen2/BattleState.lua branch on before they
|
|
-- know a target (it is what partyAction answers), and `needsTarget`, which
|
|
-- the same screens read as "pick a party mon first".
|
|
ItemEffects.RECORDS = {}
|
|
|
|
local function record(itemId, action, use)
|
|
ItemEffects.RECORDS[itemId] = {
|
|
use = use, action = action, field = true, needsTarget = true,
|
|
}
|
|
end
|
|
|
|
-- Built in reverse precedence order, so an id that appeared in two of the
|
|
-- source tables would resolve the way partyAction's if-chain used to: heal
|
|
-- first, then status, revive, candy and pp. Nothing overlaps today; the order
|
|
-- is what keeps that true if something ever does.
|
|
for itemId, row in pairs(ItemEffects.RESTORE_PP) do
|
|
record(itemId, "pp", function(ctx)
|
|
-- RestorePPEffect's two shapes: the ETHER family lands on the chosen slot,
|
|
-- the ELIXER family (Elixer_RestorePPofAllMoves) walks every slot and
|
|
-- counts -- one restored move is enough for the item to be spent.
|
|
local moves = ctx.mon.moves or {}
|
|
local any = false
|
|
if row.each then
|
|
for _, move in ipairs(moves) do
|
|
if restoreMove(move, row.amount) then any = true end
|
|
end
|
|
else
|
|
any = restoreMove(moves[ctx.slot], row.amount)
|
|
end
|
|
if not any then
|
|
return { used = false, text = ItemEffects.TEXT_NO_EFFECT }
|
|
end
|
|
return { used = true, text = ItemEffects.TEXT_PP_RESTORED }
|
|
end)
|
|
end
|
|
|
|
record("RARE_CANDY", "candy", function(ctx)
|
|
return rareCandy(ctx.mon, ctx.data)
|
|
end)
|
|
|
|
for _, itemId in ipairs({ "SUN_STONE", "MOON_STONE", "FIRE_STONE",
|
|
"THUNDERSTONE", "WATER_STONE", "LEAF_STONE" }) do
|
|
record(itemId, "stone", function(ctx)
|
|
if ctx.mon.item == "EVERSTONE" then
|
|
return { used = false, text = ItemEffects.TEXT_NO_EFFECT }
|
|
end
|
|
local Evolution = require("src.core.gen2.Evolution")
|
|
local entry = Evolution.checkMon(ctx.data, ctx.mon,
|
|
{ force = true, item = ctx.item })
|
|
if not entry then return { used = false, text = ItemEffects.TEXT_NO_EFFECT } end
|
|
return { used = true, evolution = entry }
|
|
end)
|
|
end
|
|
|
|
for itemId in pairs(ItemEffects.REVIVE) do
|
|
record(itemId, "revive", function(ctx) return revive(ctx.item, ctx.mon) end)
|
|
end
|
|
|
|
for itemId, class in pairs(ItemEffects.HEAL_STATUS) do
|
|
record(itemId, "status", function(ctx)
|
|
return healStatus(ctx.item, ctx.mon, class, ctx.data)
|
|
end)
|
|
end
|
|
|
|
for itemId in pairs(ItemEffects.HEAL_HP) do
|
|
record(itemId, "heal", function(ctx)
|
|
-- FullRestoreEffect: a full-HP target falls through to FullyHealStatus
|
|
-- rather than refusing, so a paralyzed mon at full health is still cured.
|
|
if ctx.item == "FULL_RESTORE" and not fainted(ctx.mon)
|
|
and (ctx.mon.hp or 0) >= maxHpOf(ctx.mon) then
|
|
return healStatus(ctx.item, ctx.mon, "all", ctx.data)
|
|
end
|
|
return restoreHp(ctx.item, ctx.mon)
|
|
end)
|
|
end
|
|
|
|
-- vanilla registrations, engine-owned (Schemas.ENGINE), so a mod's register of
|
|
-- one of these ids collides the way it does on Red and has to say override
|
|
function ItemEffects.registerInto(registry, _, owner)
|
|
for id, entry in pairs(ItemEffects.RECORDS) do
|
|
registry:register(id, entry, owner)
|
|
end
|
|
end
|
|
|
|
return ItemEffects
|