mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-16 08:11:35 +02:00
G2 support
This commit is contained in:
@@ -0,0 +1,483 @@
|
||||
-- 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 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
|
||||
Reference in New Issue
Block a user