mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-12 08:21:02 +02:00
attacks should not animate if miss or fail
This commit is contained in:
@@ -1556,6 +1556,25 @@ function BattleState:animationsOn()
|
||||
return not o or o.animations ~= false
|
||||
end
|
||||
|
||||
-- Drop the announcement-time move-anim row. Gen 1 queues PlayMoveAnimation
|
||||
-- only after MoveHitTest / the effect lands (HandleIfPlayerMoveMissed skips
|
||||
-- it on a miss unless EXPLODE_EFFECT); we insert early for blink attachment
|
||||
-- and peel it back on miss/fail paths.
|
||||
function BattleState:cancelMoveAnim()
|
||||
local row = self.moveAnimRow
|
||||
if not row then return end
|
||||
self.moveAnimRow = nil
|
||||
for i, item in ipairs(self.queue) do
|
||||
if item == row then
|
||||
table.remove(self.queue, i)
|
||||
if self.nextInsert and i <= self.nextInsert then
|
||||
self.nextInsert = self.nextInsert - 1
|
||||
end
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- ------------------------------------------------------------------
|
||||
-- special-effect (SE_*) implementations. Palette effects are BGP
|
||||
-- shade maps ({[i] = shade color index i displays as}); on the SGB the
|
||||
@@ -2160,6 +2179,20 @@ end
|
||||
-- Decomposed into a staged pipeline over the merged move_effects record:
|
||||
-- announcement -> callsMove -> charge -> perform -> primary run -> the
|
||||
-- damaging pipeline (EffectRegistry.runDamaging).
|
||||
|
||||
-- Gen 1 status/stat primary effects call PlayCurrentMoveAnimation only
|
||||
-- after they land; these failure texts print with no animation.
|
||||
local function primaryEffectFailed(msgs)
|
||||
if not msgs or #msgs == 0 then return true end
|
||||
local m = msgs[1]
|
||||
if m == "But, it failed!" or m == "Nothing happened!" then return true end
|
||||
if m:find("didn't affect", 1, true) then return true end
|
||||
if m:find("is unaffected", 1, true) then return true end
|
||||
if m:find("protected by MIST", 1, true) then return true end
|
||||
if m:find("Already", 1, true) then return true end
|
||||
return false
|
||||
end
|
||||
|
||||
function BattleState:performMove(user, target, moveInst, isCalled)
|
||||
local move = self:moveDef(moveInst)
|
||||
if not move then
|
||||
@@ -2207,6 +2240,11 @@ function BattleState:performMove(user, target, moveInst, isCalled)
|
||||
-- already said its failure text
|
||||
if record and record.callsMove then
|
||||
local pick = record.callsMove(ctx)
|
||||
-- Mirror Move never plays its own anim (MetronomePickMove does;
|
||||
-- MirrorMoveCopyMove only reloads the copied move or prints fail)
|
||||
if move.id == "MIRROR_MOVE" or not pick then
|
||||
self:cancelMoveAnim()
|
||||
end
|
||||
if pick then
|
||||
self:performMove(user, target, { id = pick, pp = 1 }, true)
|
||||
end
|
||||
@@ -2249,10 +2287,19 @@ function BattleState:performMove(user, target, moveInst, isCalled)
|
||||
if record.accuracyChecked
|
||||
and (target.invulnerable
|
||||
or not self:accuracyRoll(move, user, target)) then
|
||||
-- SleepEffect/PoisonEffect/... call PlayCurrentMoveAnimation only
|
||||
-- after the effect lands; a miss skips it
|
||||
self:cancelMoveAnim()
|
||||
self:sayNext(("%s's\nattack missed!"):format(displayName(user)))
|
||||
return
|
||||
end
|
||||
for _, m in ipairs(record.run(ctx)) do
|
||||
local msgs = record.run(ctx)
|
||||
-- Gen 1 status/stat effects animate only when they take effect
|
||||
-- (AlreadyAsleep / NothingHappened / ButItFailed print with no anim)
|
||||
if primaryEffectFailed(msgs) then
|
||||
self:cancelMoveAnim()
|
||||
end
|
||||
for _, m in ipairs(msgs) do
|
||||
self:sayNext(m)
|
||||
end
|
||||
self:drainNext() -- REST/RECOVER/SOFTBOILED move the user's bar
|
||||
@@ -2260,6 +2307,7 @@ function BattleState:performMove(user, target, moveInst, isCalled)
|
||||
end
|
||||
if move.power == 0 and not (record and record.kind == "full") then
|
||||
MoveEffects.warnUnknown(move.effect)
|
||||
self:cancelMoveAnim()
|
||||
self:sayNext("But, it failed!")
|
||||
return
|
||||
end
|
||||
@@ -2299,6 +2347,7 @@ function BattleState:continueBide(user, target)
|
||||
local dmg = (user.bideDamage or 0) * 2
|
||||
user.bideTurns, user.bideDamage = nil, nil
|
||||
if dmg <= 0 then
|
||||
self:cancelMoveAnim()
|
||||
self:sayNext("But, it failed!")
|
||||
return
|
||||
end
|
||||
|
||||
@@ -89,6 +89,8 @@ function EffectRegistry.runDamaging(battle, ctx, record)
|
||||
-- Swift ignores semi-invulnerability (MoveHitTest returns hit for
|
||||
-- SWIFT_EFFECT before the INVULNERABLE check)
|
||||
if target.invulnerable and not neverMiss then
|
||||
-- Explosion/Selfdestruct still animate on a miss (HandleIfPlayerMoveMissed)
|
||||
if not (record and record.explode) then battle:cancelMoveAnim() end
|
||||
battle:sayNext(("%s's\nattack missed!"):format(displayName(user)))
|
||||
return
|
||||
end
|
||||
@@ -97,6 +99,7 @@ function EffectRegistry.runDamaging(battle, ctx, record)
|
||||
if record and record.gate then
|
||||
local ok, failMsg = record.gate(ctx)
|
||||
if not ok then
|
||||
battle:cancelMoveAnim()
|
||||
if failMsg then battle:sayNext(failMsg) end
|
||||
return
|
||||
end
|
||||
@@ -108,6 +111,8 @@ function EffectRegistry.runDamaging(battle, ctx, record)
|
||||
|
||||
if not neverMiss then
|
||||
if not battle:accuracyRoll(move, user, target) then
|
||||
-- Explosion/Selfdestruct still animate on a miss (HandleIfPlayerMoveMissed)
|
||||
if not (record and record.explode) then battle:cancelMoveAnim() end
|
||||
battle:sayNext(("%s's\nattack missed!"):format(displayName(user)))
|
||||
-- Jump Kick crash, Explode self-destruct
|
||||
if record and record.onMiss then record.onMiss(ctx, "accuracy") end
|
||||
@@ -134,6 +139,7 @@ function EffectRegistry.runDamaging(battle, ctx, record)
|
||||
end
|
||||
end
|
||||
if not counterable or (battle.lastDamage or 0) == 0 then
|
||||
battle:cancelMoveAnim()
|
||||
battle:sayNext(("%s's\nattack missed!"):format(displayName(user)))
|
||||
return
|
||||
end
|
||||
@@ -144,6 +150,7 @@ function EffectRegistry.runDamaging(battle, ctx, record)
|
||||
-- failed with that text already chosen
|
||||
local chosen, extra = record.chooseDamage(ctx)
|
||||
if not chosen then
|
||||
battle:cancelMoveAnim()
|
||||
if extra then battle:sayNext(extra) end
|
||||
return
|
||||
end
|
||||
@@ -154,12 +161,15 @@ function EffectRegistry.runDamaging(battle, ctx, record)
|
||||
end
|
||||
|
||||
if info.typeMult == 0 then
|
||||
-- type immunity zeros damage and sets wMoveMissed in Gen 1, so no anim
|
||||
if not (record and record.explode) then battle:cancelMoveAnim() end
|
||||
battle:sayNext(("It doesn't affect\n%s!"):format(displayName(target)))
|
||||
if record and record.onMiss then record.onMiss(ctx, "immune") end
|
||||
return
|
||||
end
|
||||
if info.missed then
|
||||
-- 0.25x floored the damage to zero: the original registers a miss
|
||||
if not (record and record.explode) then battle:cancelMoveAnim() end
|
||||
battle:sayNext(("%s's\nattack missed!"):format(displayName(user)))
|
||||
if record and record.onMiss then record.onMiss(ctx, "floored") end
|
||||
return
|
||||
|
||||
@@ -616,6 +616,7 @@ MoveEffects.full = {
|
||||
-- roll is below opponentLevel/4. Teleport's failure text is "But
|
||||
-- it failed!", Roar/Whirlwind's is DidntAffectText; in trainer
|
||||
-- battles Teleport fails and Roar/Whirlwind are "unaffected".
|
||||
-- Fail paths DelayFrames then print -- no PlayCurrentMoveAnimation.
|
||||
perform = function(ctx)
|
||||
local battle, user, target, move = ctx.battle, ctx.user, ctx.target, ctx.move
|
||||
if battle.kind == "wild" then
|
||||
@@ -635,13 +636,17 @@ MoveEffects.full = {
|
||||
battle.result = "run"
|
||||
battle.afterQueue = "finish"
|
||||
elseif move.id == "TELEPORT" then
|
||||
battle:cancelMoveAnim()
|
||||
ctx.say("But, it failed!")
|
||||
else
|
||||
battle:cancelMoveAnim()
|
||||
ctx.say(("It didn't affect\n%s!"):format(displayName(target)))
|
||||
end
|
||||
elseif move.id == "TELEPORT" then
|
||||
battle:cancelMoveAnim()
|
||||
ctx.say("But, it failed!")
|
||||
else
|
||||
battle:cancelMoveAnim()
|
||||
ctx.say(("%s\nis unaffected!"):format(displayName(target)))
|
||||
end
|
||||
end,
|
||||
|
||||
@@ -854,6 +854,11 @@ do
|
||||
ab.rng = mkseq({ 255 }) -- the 1/256 miss
|
||||
ab:performMove(ab.player, ab.enemy, { id = "THUNDER_WAVE", pp = 10 })
|
||||
eq(ab.enemy.mon.status, nil, "THUNDER WAVE misses on the 255 roll")
|
||||
local function hasAnim(b)
|
||||
for _, r in ipairs(b.queue) do if r.anim then return true end end
|
||||
return false
|
||||
end
|
||||
check(not hasAnim(ab), "a missed THUNDER WAVE plays no move animation")
|
||||
ab.rng = mkseq({ 254 })
|
||||
ab:performMove(ab.player, ab.enemy, { id = "THUNDER_WAVE", pp = 10 })
|
||||
eq(ab.enemy.mon.status, "PAR", "THUNDER WAVE lands on the 254 roll")
|
||||
@@ -864,6 +869,33 @@ do
|
||||
eq(sbst.player.stages.attack, 1, "SHARPEN skips the accuracy roll")
|
||||
end
|
||||
|
||||
-- HandleIfPlayerMoveMissed: skip PlayMoveAnimation on a miss
|
||||
-- (unless EXPLODE_EFFECT)
|
||||
do
|
||||
Game.save.party = { Pokemon.new(Data, "BULBASAUR", 20) }
|
||||
local function hasAnim(b)
|
||||
for _, r in ipairs(b.queue) do if r.anim then return true end end
|
||||
return false
|
||||
end
|
||||
local function sawMiss(b)
|
||||
for _, r in ipairs(b.queue) do
|
||||
if r.text and r.text:find("attack missed!", 1, true) then return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
local mb = BattleState.newWild(Game, "RATTATA", 5)
|
||||
mb.rng = function(a, b) return b end -- accuracy 255: miss
|
||||
mb:performMove(mb.player, mb.enemy, { id = "TACKLE", pp = 10 })
|
||||
check(sawMiss(mb), "TACKLE miss prints AttackMissedText")
|
||||
check(not hasAnim(mb), "a missed TACKLE plays no move animation")
|
||||
eq(mb.enemy.mon.hp, mb.enemy.mon.stats.hp, "a missed TACKLE deals no damage")
|
||||
|
||||
local hb = BattleState.newWild(Game, "RATTATA", 5)
|
||||
hb.rng = function(a, b) return a end -- hit
|
||||
hb:performMove(hb.player, hb.enemy, { id = "TACKLE", pp = 10 })
|
||||
check(hasAnim(hb), "a landing TACKLE still queues its move animation")
|
||||
end
|
||||
|
||||
-- #14: EXP.ALL second pass inherits the participant divisor and skips
|
||||
-- fainted mons
|
||||
do
|
||||
|
||||
Reference in New Issue
Block a user