mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-16 00:02:23 +02:00
536 lines
20 KiB
Lua
536 lines
20 KiB
Lua
-- Gen 2 move effects, as data plus the small amount of arithmetic each one
|
|
-- needs. Ported from engine/battle/effect_commands.asm; the battle engine
|
|
-- (src/battle/gen2/Battle.lua) owns the turn loop and calls in here for what a
|
|
-- move does beyond "roll damage, maybe inflict a status".
|
|
--
|
|
-- Everything is keyed by the move's *effect*, the same byte data/moves/moves.asm
|
|
-- stores, so a modded move that copies an effect inherits its behaviour -- and
|
|
-- an effect this table does not name still lands as an ordinary hit rather than
|
|
-- silently doing the wrong thing.
|
|
--
|
|
-- No love calls and no engine state: every function takes what it needs and
|
|
-- returns a value or a small table, which is what lets the tests drive them
|
|
-- directly.
|
|
|
|
local Effects = {}
|
|
|
|
-- --------------------------------------------------------------- stat stages
|
|
--
|
|
-- Stat changes come in four shapes and the effect name says which:
|
|
-- *_UP / *_UP_2 raise the user, by one stage or two
|
|
-- *_DOWN / *_DOWN_2 lower the target
|
|
-- *_UP_HIT raise the user after a damaging hit
|
|
-- *_DOWN_HIT lower the target after a damaging hit
|
|
-- StatUpMessage / StatDownMessage are the same either way, so the direction
|
|
-- and the target are the only things worth tabulating.
|
|
|
|
-- { stat, stages, target } where target is "self" or "foe".
|
|
Effects.STAT_CHANGES = {
|
|
EFFECT_ATTACK_UP = { "attack", 1, "self" },
|
|
EFFECT_DEFENSE_UP = { "defense", 1, "self" },
|
|
EFFECT_SP_ATK_UP = { "specialAttack", 1, "self" },
|
|
EFFECT_EVASION_UP = { "evasion", 1, "self" },
|
|
EFFECT_ATTACK_UP_2 = { "attack", 2, "self" },
|
|
EFFECT_DEFENSE_UP_2 = { "defense", 2, "self" },
|
|
EFFECT_SPEED_UP_2 = { "speed", 2, "self" },
|
|
EFFECT_SP_DEF_UP_2 = { "specialDefense", 2, "self" },
|
|
-- Defense Curl also arms Rollout, which Battle tracks separately.
|
|
EFFECT_DEFENSE_CURL = { "defense", 1, "self" },
|
|
|
|
EFFECT_ATTACK_DOWN = { "attack", -1, "foe" },
|
|
EFFECT_DEFENSE_DOWN = { "defense", -1, "foe" },
|
|
EFFECT_SPEED_DOWN = { "speed", -1, "foe" },
|
|
EFFECT_ACCURACY_DOWN = { "accuracy", -1, "foe" },
|
|
EFFECT_EVASION_DOWN = { "evasion", -1, "foe" },
|
|
EFFECT_ATTACK_DOWN_2 = { "attack", -2, "foe" },
|
|
EFFECT_DEFENSE_DOWN_2 = { "defense", -2, "foe" },
|
|
EFFECT_SPEED_DOWN_2 = { "speed", -2, "foe" },
|
|
}
|
|
|
|
-- The secondary versions, rolled against the move's effect chance after a hit.
|
|
Effects.STAT_CHANGES_ON_HIT = {
|
|
EFFECT_ATTACK_UP_HIT = { "attack", 1, "self" },
|
|
EFFECT_DEFENSE_UP_HIT = { "defense", 1, "self" },
|
|
EFFECT_ATTACK_DOWN_HIT = { "attack", -1, "foe" },
|
|
EFFECT_DEFENSE_DOWN_HIT = { "defense", -1, "foe" },
|
|
EFFECT_SPEED_DOWN_HIT = { "speed", -1, "foe" },
|
|
EFFECT_ACCURACY_DOWN_HIT = { "accuracy", -1, "foe" },
|
|
EFFECT_SP_DEF_DOWN_HIT = { "specialDefense", -1, "foe" },
|
|
}
|
|
|
|
-- Ancient Power raises every one of the user's stats at once.
|
|
Effects.ALL_UP_STATS = {
|
|
"attack", "defense", "speed", "specialAttack", "specialDefense",
|
|
}
|
|
|
|
Effects.STAT_NAMES = {
|
|
attack = "ATTACK", defense = "DEFENSE", speed = "SPEED",
|
|
specialAttack = "SPCL.ATK", specialDefense = "SPCL.DEF",
|
|
accuracy = "ACCURACY", evasion = "EVASION",
|
|
}
|
|
|
|
-- Stages clamp at ±6 (BattleCommand_StatUp's .CantRaise / .CantLower).
|
|
Effects.MAX_STAGE = 6
|
|
|
|
-- Applies a change and says what happened, so the caller can emit the cart's
|
|
-- own message: nil when the stage was already at the cap.
|
|
function Effects.applyStage(stages, stat, delta)
|
|
if not (stages and stat) then return nil end
|
|
local current = stages[stat] or 0
|
|
local wanted = current + delta
|
|
if wanted > Effects.MAX_STAGE then wanted = Effects.MAX_STAGE end
|
|
if wanted < -Effects.MAX_STAGE then wanted = -Effects.MAX_STAGE end
|
|
if wanted == current then return nil end
|
|
stages[stat] = wanted
|
|
return wanted - current
|
|
end
|
|
|
|
-- BattleCommand_StatUpMessage / StatDownMessage: one stage is "rose"/"fell",
|
|
-- two are "sharply rose" / "sharply fell".
|
|
function Effects.stageMessage(name, stat, applied)
|
|
local label = Effects.STAT_NAMES[stat] or stat
|
|
local sharply = math.abs(applied) >= 2 and "sharply " or ""
|
|
local verb = applied > 0 and "rose" or "fell"
|
|
return ("%s's %s %s%s!"):format(name, label, sharply, verb)
|
|
end
|
|
|
|
-- ------------------------------------------------------------------ hit count
|
|
--
|
|
-- BattleCommand_CheckHit's multi-hit roll: 2 and 3 hits are 3/8 each, 4 and 5
|
|
-- are 1/8 each, which is what the `and 3` on a 0-3 roll plus the two-step
|
|
-- fallthrough in .DetermineNumberOfHits produces.
|
|
function Effects.multiHitCount(random)
|
|
local function roll(n)
|
|
if random then return random(n) end
|
|
if love and love.math and love.math.random then
|
|
return love.math.random(n) - 1
|
|
end
|
|
return math.random(n) - 1
|
|
end
|
|
-- engine/battle/effect_commands.asm:5228
|
|
local first = roll(4)
|
|
if first < 2 then return first + 2 end
|
|
return roll(4) + 2
|
|
end
|
|
|
|
Effects.HIT_COUNTS = {
|
|
EFFECT_DOUBLE_HIT = 2,
|
|
EFFECT_POISON_MULTI_HIT = 2,
|
|
-- Triple Kick stops early if a hit misses; Battle rolls that per hit.
|
|
EFFECT_TRIPLE_KICK = 3,
|
|
}
|
|
|
|
function Effects.hitCount(effect, random)
|
|
if effect == "EFFECT_MULTI_HIT" then
|
|
return Effects.multiHitCount(random)
|
|
end
|
|
return Effects.HIT_COUNTS[effect] or 1
|
|
end
|
|
|
|
-- Triple Kick's power climbs 10/20/30 across its three kicks
|
|
-- (BattleCommand_TripleKick).
|
|
function Effects.tripleKickPower(base, hit)
|
|
return (base or 10) * hit
|
|
end
|
|
|
|
-- ----------------------------------------------------------- recoil and drain
|
|
|
|
-- BattleCommand_Recoil: a quarter of the damage dealt, minimum 1.
|
|
function Effects.recoilDamage(damageDealt)
|
|
return math.max(1, math.floor((damageDealt or 0) / 4))
|
|
end
|
|
|
|
-- BattleCommand_DrainTarget: half the damage dealt, minimum 1.
|
|
function Effects.drainAmount(damageDealt)
|
|
return math.max(1, math.floor((damageDealt or 0) / 2))
|
|
end
|
|
|
|
Effects.DRAIN = {
|
|
EFFECT_LEECH_HIT = true,
|
|
EFFECT_DREAM_EATER = true,
|
|
}
|
|
|
|
-- --------------------------------------------------------------- two-turn
|
|
--
|
|
-- The charge moves all share BattleCommand_Charge: turn one prints a line and
|
|
-- stores the move, turn two attacks. Fly and Dig also make the user
|
|
-- untargetable in between, which is the `semi-invulnerable` flag here.
|
|
Effects.CHARGE = {
|
|
EFFECT_RAZOR_WIND = { text = "%s made a whirlwind!" },
|
|
EFFECT_SOLARBEAM = { text = "%s took in sunlight!" },
|
|
EFFECT_SKULL_BASH = { text = "%s lowered its head!" },
|
|
EFFECT_SKY_ATTACK = { text = "%s is glowing!" },
|
|
EFFECT_FLY = { text = "%s flew up high!", vanish = true },
|
|
}
|
|
|
|
-- CheckHit's .FlyDigMoves (effect_commands.asm:1713-1746): a vanished target
|
|
-- is not a flat miss, four moves reach it in the air and three underground.
|
|
Effects.FLY_DIG_EXCEPTIONS = {
|
|
FLY = { GUST = true, WHIRLWIND = true, THUNDER = true, TWISTER = true },
|
|
DIG = { EARTHQUAKE = true, FISSURE = true, MAGNITUDE = true },
|
|
}
|
|
|
|
-- Keyed by the charge move the target is partway through, which is what the
|
|
-- port carries in place of SUBSTATUS_FLYING / SUBSTATUS_UNDERGROUND.
|
|
function Effects.hitsVanished(chargeMove, moveId)
|
|
local reaches = Effects.FLY_DIG_EXCEPTIONS[chargeMove]
|
|
return (reaches and reaches[moveId]) and true or false
|
|
end
|
|
|
|
-- --------------------------------------------------------------- fixed damage
|
|
|
|
-- BattleCommand_ConstantDamage (engine/battle/effect_commands.asm:3131-3205),
|
|
-- one command for the whole SuperFang / Psywave / StaticDamage list.
|
|
function Effects.fixedDamage(effect, attacker, defender, random, power)
|
|
if effect == "EFFECT_LEVEL_DAMAGE" then
|
|
return math.max(1, attacker.level or 1)
|
|
end
|
|
if effect == "EFFECT_SUPER_FANG" then
|
|
return math.max(1, math.floor((defender.hp or 1) / 2))
|
|
end
|
|
if effect == "EFFECT_PSYWAVE" then
|
|
-- .psywave rerolls until the byte is nonzero AND below level * 1.5, so the
|
|
-- top of the range is that ceiling minus one (effect_commands.asm:3163).
|
|
local ceiling = math.max(2, math.floor((attacker.level or 1) * 3 / 2))
|
|
return math.max(1, (random and random(ceiling - 1) or 0) + 1)
|
|
end
|
|
-- SONIC BOOM and DRAGON RAGE share EFFECT_STATIC_DAMAGE, whose arm reads
|
|
-- BATTLE_VARS_MOVE_POWER straight into the damage word: their stored power
|
|
-- (20 and 40) IS the damage, never a formula input
|
|
-- (effect_commands.asm:3157-3161).
|
|
if effect == "EFFECT_STATIC_DAMAGE" then
|
|
return math.max(1, math.floor(power or 0))
|
|
end
|
|
return nil
|
|
end
|
|
|
|
-- --------------------------------------------------------------- Substitute
|
|
|
|
-- BattleCommand_Substitute: a quarter of max HP, which is also what the user
|
|
-- pays. Refuses when the user has that much HP or less.
|
|
function Effects.substituteCost(maxHp)
|
|
return math.max(1, math.floor((maxHp or 1) / 4))
|
|
end
|
|
|
|
-- ------------------------------------------------------------ counter moves
|
|
|
|
-- Counter answers physical damage, Mirror Coat special, both at double and
|
|
-- both only when the foe hit the user this turn (BattleCommand_Counter).
|
|
Effects.COUNTER = {
|
|
EFFECT_COUNTER = "physical",
|
|
EFFECT_MIRROR_COAT = "special",
|
|
}
|
|
|
|
function Effects.counterDamage(taken)
|
|
return math.max(1, (taken or 0) * 2)
|
|
end
|
|
|
|
-- ------------------------------------------------------ rollout / fury cutter
|
|
|
|
-- Both double their power per consecutive use, Rollout for five turns and Fury
|
|
-- Cutter until it misses; the cart caps the doubling at 5 steps either way.
|
|
Effects.RAMPING = {
|
|
EFFECT_ROLLOUT = 5,
|
|
EFFECT_FURY_CUTTER = 5,
|
|
}
|
|
|
|
function Effects.rampedPower(base, count, curled)
|
|
local steps = math.min(math.max(count or 0, 0), 4)
|
|
local power = (base or 1) * 2 ^ steps
|
|
-- Defense Curl doubles Rollout again (BattleCommand_RolloutPower).
|
|
if curled then power = power * 2 end
|
|
return math.floor(power)
|
|
end
|
|
|
|
-- ----------------------------------------------------------------- magnitude
|
|
--
|
|
-- data/moves/magnitude_power.asm, one row per magnitude: { chance, power,
|
|
-- magnitude number }. The chance column is assembled through `percent`
|
|
-- (`* $ff / 100`, macros/data.asm:23), so `5 percent + 1` is 13 and
|
|
-- `100 percent` is 255 -- the thresholds below are those bytes, not the
|
|
-- percentages they were written as.
|
|
Effects.MAGNITUDE_POWER = {
|
|
{ 13, 10, 4 },
|
|
{ 38, 30, 5 },
|
|
{ 89, 50, 6 },
|
|
{ 166, 70, 7 },
|
|
{ 217, 90, 8 },
|
|
{ 242, 110, 9 },
|
|
{ 255, 150, 10 },
|
|
}
|
|
|
|
-- BattleCommand_GetMagnitude (engine/battle/move_effects/magnitude.asm): ONE
|
|
-- random byte walks the table and the first row whose threshold is not below
|
|
-- it wins (`ld a, [hli] / cp b / jr nc`). The row's power goes into d, which
|
|
-- is what damagecalc reads as the move's power -- data/moves/moves.asm stores
|
|
-- MAGNITUDE at power 1 precisely because this overwrites it. Returns the
|
|
-- power and the magnitude number the text prints.
|
|
--
|
|
-- `random` is BattleRandom (0..n-1). If none is supplied, roll via love.math
|
|
-- / math.random — never hard-code 0 (that always yields Magnitude 4).
|
|
function Effects.magnitudePower(random)
|
|
local roll
|
|
if type(random) == "function" then
|
|
roll = random(256) or 0
|
|
elseif love and love.math and love.math.random then
|
|
roll = love.math.random(256) - 1
|
|
else
|
|
roll = math.random(256) - 1
|
|
end
|
|
for _, row in ipairs(Effects.MAGNITUDE_POWER) do
|
|
if row[1] >= roll then return row[2], row[3] end
|
|
end
|
|
local last = Effects.MAGNITUDE_POWER[#Effects.MAGNITUDE_POWER]
|
|
return last[2], last[3]
|
|
end
|
|
|
|
-- ------------------------------------------------------------------- weather
|
|
--
|
|
-- BattleCommand_StartRain / StartSun / StartSandstorm all set wWeatherCount to
|
|
-- 5, which HandleWeather decrements at the end of every turn; the turn it
|
|
-- reaches zero the weather ends. data/battle/weather_modifiers.asm is the
|
|
-- whole of what weather does to damage.
|
|
|
|
Effects.WEATHER = {
|
|
EFFECT_RAIN_DANCE = "rain",
|
|
EFFECT_SUNNY_DAY = "sun",
|
|
EFFECT_SANDSTORM = "sandstorm",
|
|
}
|
|
|
|
Effects.WEATHER_TURNS = 5
|
|
|
|
Effects.WEATHER_START_TEXT = {
|
|
rain = "It started to rain!",
|
|
sun = "The sunlight got bright!",
|
|
sandstorm = "A sandstorm brewed!",
|
|
}
|
|
|
|
Effects.WEATHER_TURN_TEXT = {
|
|
rain = "Rain continues to fall.",
|
|
sun = "The sunlight is strong.",
|
|
sandstorm = "The sandstorm rages.",
|
|
}
|
|
|
|
Effects.WEATHER_END_TEXT = {
|
|
rain = "The rain stopped.",
|
|
sun = "The sunlight faded.",
|
|
sandstorm = "The sandstorm subsided.",
|
|
}
|
|
|
|
-- data/battle/weather_modifiers.asm pairs each weather with MORE_EFFECTIVE or
|
|
-- NOT_VERY_EFFECTIVE, and those are 15 and 05 in tenths
|
|
-- (constants/battle_constants.asm:22, :24) -- MORE_EFFECTIVE is x1.5, NOT the
|
|
-- type chart's x2, which is SUPER_EFFECTIVE (20). Gen 2's weather boost is a
|
|
-- half again, and only the type chart doubles.
|
|
Effects.WEATHER_TYPE_MODIFIERS = {
|
|
rain = { WATER = 1.5, FIRE = 0.5 },
|
|
sun = { FIRE = 1.5, WATER = 0.5 },
|
|
}
|
|
|
|
-- The one move whose EFFECT rather than type is modified: Solarbeam in rain.
|
|
Effects.WEATHER_MOVE_MODIFIERS = {
|
|
rain = { EFFECT_SOLARBEAM = 0.5 },
|
|
}
|
|
|
|
function Effects.weatherModifier(weather, moveType, effect)
|
|
if not weather then return 1 end
|
|
local byType = Effects.WEATHER_TYPE_MODIFIERS[weather]
|
|
if byType and byType[moveType] then return byType[moveType] end
|
|
local byMove = Effects.WEATHER_MOVE_MODIFIERS[weather]
|
|
if byMove and byMove[effect] then return byMove[effect] end
|
|
return 1
|
|
end
|
|
|
|
-- HandleWeather's .SandstormDamage: an eighth of max HP, and Rock, Ground and
|
|
-- Steel are immune. A mon underground (Dig) is skipped too.
|
|
Effects.SANDSTORM_IMMUNE = { ROCK = true, GROUND = true, STEEL = true }
|
|
|
|
function Effects.sandstormDamage(maxHp)
|
|
return math.max(1, math.floor((maxHp or 8) / 8))
|
|
end
|
|
|
|
function Effects.sandstormHits(types)
|
|
for _, type_ in ipairs(types or {}) do
|
|
if Effects.SANDSTORM_IMMUNE[type_] then return false end
|
|
end
|
|
return true
|
|
end
|
|
|
|
-- Morning Sun / Synthesis / Moonlight heal a HALF normally, and the weather
|
|
-- shifts that one step either way: x2 in sun, /2 in rain or sandstorm
|
|
-- (BattleCommand_Heal's .Weather block walks a multiplier index).
|
|
Effects.SUN_HEAL = {
|
|
EFFECT_MORNING_SUN = true,
|
|
EFFECT_SYNTHESIS = true,
|
|
EFFECT_MOONLIGHT = true,
|
|
}
|
|
|
|
function Effects.weatherHealFraction(weather)
|
|
if weather == "sun" then return 2 / 3 end
|
|
if weather == "rain" or weather == "sandstorm" then return 1 / 4 end
|
|
return 1 / 2
|
|
end
|
|
|
|
-- ---------------------------------------------------------------- Perish Song
|
|
--
|
|
-- BattleCommand_PerishSong sets the counter to 4 on BOTH sides; it ticks down
|
|
-- at the end of every turn and the mon faints when it reaches 0.
|
|
Effects.PERISH_TURNS = 4
|
|
|
|
-- -------------------------------------------------------------------- Encore
|
|
--
|
|
-- 3-6 turns (`and $3` plus three increments), and the target is locked into
|
|
-- the move it last used. Encore, Mirror Move and Struggle cannot be encored,
|
|
-- and neither can a move with no PP left.
|
|
Effects.ENCORE_BLOCKED = {
|
|
ENCORE = true, MIRROR_MOVE = true, STRUGGLE = true,
|
|
}
|
|
|
|
function Effects.encoreTurns(random)
|
|
return (random and random(4) or 0) + 3
|
|
end
|
|
|
|
-- ------------------------------------------------------------------- Disable
|
|
--
|
|
-- The count is a packed byte: the low nybble is the number of turns (1-8, the
|
|
-- `and 7` retried until nonzero, then incremented) and the high nybble is the
|
|
-- move slot plus one. Only the turn count matters to the port, but the shape
|
|
-- is what says a slot of 0 means "nothing disabled".
|
|
function Effects.disableTurns(random)
|
|
local roll = 0
|
|
for _ = 1, 8 do
|
|
roll = (random and random(8) or 1) % 8
|
|
if roll ~= 0 then break end
|
|
end
|
|
if roll == 0 then roll = 1 end
|
|
return roll + 1
|
|
end
|
|
|
|
-- --------------------------------------------------------- Protect and Endure
|
|
--
|
|
-- ProtectChance halves the success chance for every CONSECUTIVE use: the
|
|
-- threshold starts at $ff and is shifted right once per use, so use n
|
|
-- succeeds with probability (256 >> n) / 256. Once the shift reaches zero the
|
|
-- move always fails, which is five uses.
|
|
function Effects.protectChance(consecutive)
|
|
local threshold = 0xff
|
|
for _ = 1, (consecutive or 0) do
|
|
threshold = math.floor(threshold / 2)
|
|
if threshold == 0 then return 0 end
|
|
end
|
|
return threshold
|
|
end
|
|
|
|
-- The roll is a non-zero byte, decremented, and the move succeeds when it is
|
|
-- BELOW the threshold.
|
|
function Effects.protectSucceeds(consecutive, random)
|
|
local threshold = Effects.protectChance(consecutive)
|
|
if threshold == 0 then return false end
|
|
local roll = (random and random(255) or 0) + 1
|
|
return (roll - 1) < threshold
|
|
end
|
|
|
|
-- ---------------------------------------------------------------------- Bide
|
|
--
|
|
-- BattleCommand_UnleashEnergy stores for 2 or 3 turns (`and 1` plus two
|
|
-- increments) and BattleCommand_StoreEnergy pays back DOUBLE everything the
|
|
-- user took while storing, capped at the 16-bit maximum.
|
|
function Effects.bideTurns(random)
|
|
return (random and random(2) or 0) + 2
|
|
end
|
|
|
|
function Effects.bideDamage(stored)
|
|
return math.min(0xffff, (stored or 0) * 2)
|
|
end
|
|
|
|
-- ---------------------------------------------------------------------- Rage
|
|
--
|
|
-- SUBSTATUS_RAGE: while it is set, every hit the user takes raises its Attack
|
|
-- one stage. It is cleared by using any other move.
|
|
|
|
-- ---------------------------------------------------------------- Future Sight
|
|
--
|
|
-- Four turns: the damage is rolled NOW, stored, and lands when the counter
|
|
-- reaches one. The move itself does nothing on the turn it is used.
|
|
Effects.FUTURE_SIGHT_TURNS = 4
|
|
|
|
-- ---------------------------------------------------------------------- OHKO
|
|
--
|
|
-- BattleCommand_OHKO fails outright when the target is the higher level; when
|
|
-- it is not, the move's accuracy becomes acc + 2 * (level difference), capped
|
|
-- at 255, and a hit sets damage to $ffff.
|
|
function Effects.ohkoAccuracy(baseAccuracy, userLevel, targetLevel)
|
|
if (targetLevel or 1) > (userLevel or 1) then return nil end
|
|
local bonus = ((userLevel or 1) - (targetLevel or 1)) * 2
|
|
return math.min(255, (baseAccuracy or 0) + bonus)
|
|
end
|
|
|
|
-- ------------------------------------------------------------------- Beat Up
|
|
--
|
|
-- One hit per party member that is alive and free of any major status, each
|
|
-- swinging with that member's own base Attack and level against the target's
|
|
-- base Defense. The move fails outright when nobody qualifies.
|
|
function Effects.beatUpParty(party, activeIndex)
|
|
local hits = {}
|
|
for index, mon in ipairs(party or {}) do
|
|
local healthy = (mon.hp or 0) > 0
|
|
-- The ACTIVE mon is checked against its battle status rather than its
|
|
-- party record, which is the same thing here.
|
|
local clean = not mon.status or (index == activeIndex and not mon.status)
|
|
if healthy and clean then
|
|
hits[#hits + 1] = { index = index, mon = mon }
|
|
end
|
|
end
|
|
return hits
|
|
end
|
|
|
|
-- --------------------------------------------------------------- Baton Pass
|
|
--
|
|
-- ResetBatonPassStatus: what does NOT survive the switch. Everything else --
|
|
-- the stat stages, Substitute, Leech Seed, Perish Song, the confusion counter
|
|
-- -- goes with the incoming mon, which is the whole point of the move.
|
|
--
|
|
-- `preTransform` is deliberately NOT on this list even though `transformed` is:
|
|
-- it is the passer's own identity waiting to be put back, and the drops run
|
|
-- BEFORE Battle:clearVolatile, which is what puts it back and clears both keys
|
|
-- (Battle:untransform). Dropping it here would strand a passing DITTO as a
|
|
-- permanent copy instead.
|
|
Effects.BATON_PASS_DROPS = {
|
|
"nightmare", "disable", "disableTurns", "attract", "transformed",
|
|
"encore", "encoreTurns", "lastMove",
|
|
}
|
|
|
|
-- ------------------------------------------------------------------ Metronome
|
|
--
|
|
-- data/moves/metronome_exception_moves.asm: Metronome cannot pick these, and
|
|
-- it also never picks a move the user already knows.
|
|
Effects.METRONOME_EXCEPTIONS = {
|
|
METRONOME = true, STRUGGLE = true, SKETCH = true, MIMIC = true,
|
|
COUNTER = true, MIRROR_COAT = true, PROTECT = true, DETECT = true,
|
|
ENDURE = true, DESTINY_BOND = true, SLEEP_TALK = true, THIEF = true,
|
|
}
|
|
|
|
-- Picks a move id uniformly out of `moveOrder`, rerolling on an excepted move
|
|
-- or one the user already has -- the same reject loop .GetMove runs.
|
|
function Effects.metronomePick(moveOrder, known, random)
|
|
local count = #(moveOrder or {})
|
|
if count == 0 then return nil end
|
|
local owned = {}
|
|
for _, move in ipairs(known or {}) do owned[move.id or move] = true end
|
|
for _ = 1, 64 do
|
|
local pick = moveOrder[(random and random(count) or 0) + 1]
|
|
if pick and not Effects.METRONOME_EXCEPTIONS[pick] and not owned[pick] then
|
|
return pick
|
|
end
|
|
end
|
|
return nil
|
|
end
|
|
|
|
-- ---------------------------------------------------------------- Mirror Move
|
|
--
|
|
-- Copies the OPPONENT's last move, and fails when there is none or when the
|
|
-- user already knows it -- CheckUserMove returns "found", and Mirror Move
|
|
-- takes the .failed branch on a hit.
|
|
|
|
return Effects
|