mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-19 04:06:10 +02:00
CLOSES #1211, CLOSES #1228, CLOSES #1229, CLOSES #1232, CLOSES #1251, CLOSES #1265, CLOSES #1267, CLOSES #1276, CLOSES #1279, CLOSES #1282, CLOSES #1293, CLOSES #1296, CLOSES #1303, CLOSES #1329, CLOSES #1338, CLOSES #1341, CLOSES #1343, CLOSES #1344, CLOSES #1368, CLOSES #1385, CLOSES #1388, CLOSES #1389, CLOSES #1391
This commit is contained in:
+20
-10
@@ -583,7 +583,8 @@ end
|
||||
local function stampOT(save, mon)
|
||||
save.player.id = save.player.id or math.random(0, 65535)
|
||||
mon.ot = mon.ot or save.player.name
|
||||
mon.otId = mon.otId or save.player.id
|
||||
-- engine/battle/experience.asm:69
|
||||
if not mon.traded then mon.otId = mon.otId or save.player.id end
|
||||
end
|
||||
BattleState.stampOT = stampOT
|
||||
|
||||
@@ -5923,10 +5924,16 @@ function BattleState:drawTextArea()
|
||||
Font.drawCode(Font.BORDER.h, 32, 96)
|
||||
Font.drawCode(Font.BORDER.br, 80, 96)
|
||||
love.graphics.setColor(0, 0, 0, 1)
|
||||
for i, mv in ipairs(self.player.curMoves) do
|
||||
-- unknown ids (mod-injected moves) print raw instead of crashing
|
||||
local def = self.data.moves[mv.id]
|
||||
Font.draw(def and def.name or tostring(mv.id), 48, 96 + i * 8)
|
||||
-- engine/battle/misc.asm:37
|
||||
for i = 1, 4 do
|
||||
local mv = self.player.curMoves[i]
|
||||
if mv then
|
||||
-- unknown ids (mod-injected moves) print raw instead of crashing
|
||||
local def = self.data.moves[mv.id]
|
||||
Font.draw(def and def.name or tostring(mv.id), 48, 96 + i * 8)
|
||||
else
|
||||
Font.draw("-", 48, 96 + i * 8)
|
||||
end
|
||||
end
|
||||
-- Swap cursor: SelectMenuItem parks the hollow arrow on the marked row
|
||||
-- (core.asm:2600-2607), then HandleMenuInput's PlaceMenuCursor writes the
|
||||
@@ -5953,13 +5960,16 @@ function BattleState:drawTextArea()
|
||||
end
|
||||
end
|
||||
elseif self.phase == "mimicSelect" then
|
||||
-- Mimic's copy menu (MoveSelectionMenu .mimicmenu, core.asm:
|
||||
-- 2506-2517): the enemy's move list in a 16x6 box at (0,7), names
|
||||
-- single-spaced from (2,8), cursor at column 1
|
||||
-- Mimic's copy menu (MoveSelectionMenu .mimicmenu, core.asm:2506-2517):
|
||||
-- 16x6 box at (0,7), names from (2,8), cursor at column 1
|
||||
Font.drawBox(0, 7, 16, 6)
|
||||
love.graphics.setColor(0, 0, 0, 1)
|
||||
for i, m in ipairs(self.mimicMoves) do
|
||||
Font.draw(self.data.moves[m.id].name, 16, (7 + i) * 8)
|
||||
-- engine/battle/misc.asm:37
|
||||
for i = 1, 4 do
|
||||
local m = self.mimicMoves[i]
|
||||
local def = m and self.data.moves[m.id]
|
||||
Font.draw(m and (def and def.name or tostring(m.id)) or "-",
|
||||
16, (7 + i) * 8)
|
||||
end
|
||||
Font.drawCode(0xED, 8, (7 + self.mimicIndex) * 8)
|
||||
Font.draw(Strings("WHICH TECHNIQUE?"), 8, 112)
|
||||
|
||||
@@ -232,15 +232,15 @@ function EffectRegistry.runDamaging(battle, ctx, record)
|
||||
hitSfx = { sound = "Damage", pitch = 0x20 }
|
||||
end
|
||||
-- GetPlayerAnimationType / GetEnemyAnimationType (engine/battle/core.asm
|
||||
-- :3159 / :5555): wAnimationType is 4 (blink the enemy pic) or 1 (shake
|
||||
-- the screen vertically) for a damaging move with no added effect, and
|
||||
-- 5 / 2 (a horizontal shake) as soon as the move HAS one -- which is why
|
||||
-- Bubblebeam and Confusion shake instead of blinking (#354)
|
||||
-- :3159 / :5555): 4 blinks the enemy pic, 1 shakes vertically, 5 / 2 once
|
||||
-- the move has an added effect (#354)
|
||||
local added = move.effect ~= nil and move.effect ~= "NO_ADDITIONAL_EFFECT"
|
||||
-- PlayApplyingAttackAnimation runs on both arms of the wOptions check
|
||||
-- (engine/battle/animations.asm:424-437), so the blink is not gated (#1384)
|
||||
local hitFx = { sfx = hitSfx,
|
||||
animType = user.isPlayer and (added and 5 or 4)
|
||||
or (added and 2 or 1),
|
||||
blink = battle:animationsOn() and target or nil }
|
||||
blink = target }
|
||||
|
||||
local totalDealt = 0
|
||||
local landed, brokeSub = 0, false
|
||||
|
||||
@@ -192,14 +192,8 @@ function Runner:loadGfx(names)
|
||||
end
|
||||
end
|
||||
|
||||
-- BattleAnimCmd_BattlerGFX_1Row / _2Row. The battlers' pic tiles are
|
||||
-- APPENDED after whatever the script already loaded rather than replacing it,
|
||||
-- and they always land on the same two fixed tile ids.
|
||||
--
|
||||
-- (pokegold's jumptable has these two labels the other way round from the
|
||||
-- macro names -- $d9 dispatches to BattleAnimCmd_BattlerGFX_1Row while
|
||||
-- anim_battlergfx_2row is $d9 -- so the names below follow the MACRO, which
|
||||
-- is what a script actually writes.)
|
||||
-- engine/battle_anims/anim_commands.asm:755. The jumptable crosses the macro
|
||||
-- names: $d9 (anim_battlergfx_2row) dispatches to _1Row (#1401)
|
||||
function Runner:loadBattlerGfx(rows)
|
||||
local tiles = rows == 2 and BATTLER_TILES.twoRow or BATTLER_TILES.oneRow
|
||||
local slot = 1
|
||||
@@ -210,11 +204,11 @@ function Runner:loadBattlerGfx(rows)
|
||||
self.tileDict[slot] = { gfx = "BATTLE_ANIM_GFX_PLAYERHEAD", tile = tiles.player }
|
||||
self.tileDict[slot + 1] = { gfx = "BATTLE_ANIM_GFX_ENEMYFEET", tile = tiles.enemy }
|
||||
self.loaded[#self.loaded + 1] =
|
||||
{ gfx = "BATTLE_ANIM_GFX_PLAYERHEAD", tile = tiles.player, tiles = rows * 6,
|
||||
battler = "player", rows = rows }
|
||||
self.loaded[#self.loaded + 1] =
|
||||
{ gfx = "BATTLE_ANIM_GFX_ENEMYFEET", tile = tiles.enemy, tiles = rows * 7,
|
||||
{ gfx = "BATTLE_ANIM_GFX_PLAYERHEAD", tile = tiles.player, tiles = rows * 7,
|
||||
battler = "enemy", rows = rows }
|
||||
self.loaded[#self.loaded + 1] =
|
||||
{ gfx = "BATTLE_ANIM_GFX_ENEMYFEET", tile = tiles.enemy, tiles = rows * 6,
|
||||
battler = "player", rows = rows }
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------
|
||||
|
||||
+80
-31
@@ -80,6 +80,7 @@ Battle.SECONDARY_EFFECTS = {
|
||||
EFFECT_PARALYZE_HIT = "paralyze",
|
||||
EFFECT_SLEEP_HIT = "sleep",
|
||||
EFFECT_CONFUSE_HIT = "confuse",
|
||||
EFFECT_SACRED_FIRE = "burn", -- data/moves/effects.asm:1696
|
||||
}
|
||||
|
||||
local function rand(random, n)
|
||||
@@ -1320,6 +1321,18 @@ function Battle:markMissed()
|
||||
if self.moveEvent then self.moveEvent.missed = true end
|
||||
end
|
||||
|
||||
-- engine/battle/effect_commands.asm:3615
|
||||
Battle.AI_FAIL_STATUSES = {
|
||||
sleep = true, poison = true, toxic = true, paralyze = true,
|
||||
}
|
||||
|
||||
-- engine/battle/effect_commands.asm:3615
|
||||
function Battle:aiRandomFail(attacker, defender)
|
||||
if self:sideOf(attacker) ~= "enemy" then return false end
|
||||
if self:volatile(defender).lockOn then return false end
|
||||
return rand(self.random, 256) < 64
|
||||
end
|
||||
|
||||
-- One attack, start to finish.
|
||||
function Battle:useMove(attacker, defender, moveId)
|
||||
local move = self:findMove(attacker, moveId)
|
||||
@@ -1478,10 +1491,10 @@ function Battle:useMove(attacker, defender, moveId)
|
||||
if charge and not charging then
|
||||
state.chargeMove = moveId
|
||||
state.vanished = charge.vanish or nil
|
||||
-- DIG and FLY are the same effect in Gen 2 (both EFFECT_FLY), so the table
|
||||
-- keyed by effect cannot tell them apart and DIG announced itself with
|
||||
-- "flew up high!". BattleCommand_Fly picks the line off the MOVE, not the
|
||||
-- effect: `cp DIG` and then the burrow text.
|
||||
-- engine/battle/effect_commands.asm:5458
|
||||
if self.moveEvent then self.moveEvent.animParam = 1 end
|
||||
-- BattleCommand_Charge picks the line off the MOVE, not the shared
|
||||
-- EFFECT_FLY (`cp DIG`, effect_commands.asm:5464).
|
||||
local text = charge.text
|
||||
if moveId == "DIG" then text = "%s dug a hole!" end
|
||||
self:emit({ kind = "message", text = text:format(name) })
|
||||
@@ -1811,20 +1824,20 @@ function Battle:useMove(attacker, defender, moveId)
|
||||
-- Defense Curl arms Rollout as well as raising Defense.
|
||||
if def.effect == "EFFECT_DEFENSE_CURL" then state.curled = true end
|
||||
|
||||
-- Stat changes: the primary ones always land, the *_HIT ones roll the
|
||||
-- move's effect chance after a hit that connected.
|
||||
--
|
||||
-- A refused primary change is a failure the cart detects BEFORE its anim
|
||||
-- command: RaiseStat's `.cant_raise_stat` and StatDown's `.CantLower` /
|
||||
-- `.Mist` all write wAttackMissed (effect_commands.asm:4191, :4380-4400),
|
||||
-- and `statupanim` / `statdownanim` read it (:2022) from a slot AFTER
|
||||
-- `attackup` / `attackdown` in the effect list (data/moves/effects.asm,
|
||||
-- AttackUp). The *_HIT twins must NOT be marked: their `attackdown` runs
|
||||
-- after `moveanim` (AttackDownHit), so the animation has already played.
|
||||
-- A refused primary change writes wAttackMissed (effect_commands.asm:4191,
|
||||
-- :4380-4400); the *_HIT twins animate first and must stay unmarked.
|
||||
local change = Effects.STAT_CHANGES[def.effect]
|
||||
if change then
|
||||
local target = change[3] == "self" and attacker or defender
|
||||
if not self:changeStageAgainstMist(attacker, target, change[1], change[2])
|
||||
-- CheckMist first (effect_commands.asm:4290), then .ComputerMiss (:4318)
|
||||
local misted = target ~= attacker and (change[2] or 0) < 0
|
||||
and self:volatile(target).mist
|
||||
if not misted and change[3] == "foe"
|
||||
and def.effect ~= "EFFECT_ACCURACY_DOWN_HIT"
|
||||
and self:aiRandomFail(attacker, target) then
|
||||
self:markMissed()
|
||||
self:emit({ kind = "message", text = "But it failed!" })
|
||||
elseif not self:changeStageAgainstMist(attacker, target, change[1], change[2])
|
||||
then
|
||||
self:markMissed()
|
||||
end
|
||||
@@ -1853,19 +1866,21 @@ function Battle:useMove(attacker, defender, moveId)
|
||||
local record = Battle.moveEffectRecordFor(self.data, def.effect)
|
||||
local status = record and record.kind == "primary" and record.status or nil
|
||||
if status and (def.power or 0) == 0 then
|
||||
-- Every status command's already-statused / immune arm ends on
|
||||
-- AnimateFailedMove (BattleCommand_Poison's `.failed`,
|
||||
-- effect_commands.asm:3748-3750, and :6656-6659): LowerSub, MoveDelay,
|
||||
-- RaiseSub and no LoadMoveAnim. AnimateCurrentMove only runs on the
|
||||
-- success path (:3752), and the effect scripts carry no moveanim of their
|
||||
-- own (data/moves/effects.asm, Toxic / DoPoison). The SECONDARY_EFFECTS
|
||||
-- branch below is the opposite case: that move already hit and already
|
||||
-- animated, so a refused secondary must leave the event unmarked.
|
||||
if not self:applyStatus(defender, status, attacker) then self:markMissed() end
|
||||
-- A refused primary status is a failed move (effect_commands.asm:3748,
|
||||
-- :6656); a refused secondary already animated and stays unmarked (:3752).
|
||||
if Battle.AI_FAIL_STATUSES[status]
|
||||
and self:aiRandomFail(attacker, defender) then
|
||||
self:markMissed()
|
||||
self:emit({ kind = "message", text = "But it failed!" })
|
||||
elseif not self:applyStatus(defender, status, attacker) then
|
||||
self:markMissed()
|
||||
end
|
||||
else
|
||||
local secondary = record and record.kind == "secondary"
|
||||
and record.status or nil
|
||||
if secondary and (defender.hp or 0) > 0 then
|
||||
-- engine/battle/effect_commands.asm:6325
|
||||
if secondary and (defender.hp or 0) > 0
|
||||
and not self:safeguarded(defender) then
|
||||
local chance = def.effectChance or 0
|
||||
if chance > 0 and rand(self.random, 100) < chance then
|
||||
self:applyStatus(defender, secondary, attacker)
|
||||
@@ -2425,6 +2440,15 @@ Battle.MOVE_EFFECTS.EFFECT_REFLECT = function(self, attacker)
|
||||
text = self:monName(attacker) .. "'s DEFENSE rose!" })
|
||||
end
|
||||
|
||||
-- engine/battle/move_effects/safeguard.asm:1
|
||||
Battle.MOVE_EFFECTS.EFFECT_SAFEGUARD = function(self, attacker)
|
||||
local side = self.screens[self:sideOf(attacker)]
|
||||
if (side.safeguard or 0) > 0 then return fail(self) end
|
||||
side.safeguard = Battle.SCREEN_TURNS
|
||||
self:emit({ kind = "message",
|
||||
text = self:monName(attacker) .. "'s covered by a veil!" })
|
||||
end
|
||||
|
||||
-- BattleCommand_Curse (engine/battle/move_effects/curse.asm): two moves in
|
||||
-- one body. A non-Ghost user trades a stage of Speed for one each of Attack
|
||||
-- and Defense, refused only when BOTH raises are already capped; a Ghost
|
||||
@@ -2910,6 +2934,11 @@ function Battle.statusPenaltyFor(data, mon, stat, value)
|
||||
return math.max(1, math.floor(value / math.max(1, penalty.div or 1)))
|
||||
end
|
||||
|
||||
-- engine/battle/effect_commands.asm:6325
|
||||
function Battle:safeguarded(mon)
|
||||
return (self.screens[self:sideOf(mon)].safeguard or 0) > 0
|
||||
end
|
||||
|
||||
-- `source` is the battler that inflicted it, carried only so
|
||||
-- battle.status_inflicted can name it the way Gen 1's does.
|
||||
function Battle:applyStatus(mon, status, source)
|
||||
@@ -2917,7 +2946,14 @@ function Battle:applyStatus(mon, status, source)
|
||||
-- Confusion is SUBSTATUS_CONFUSED on the cart, not a status byte: it lives
|
||||
-- in the volatile beside the major status, so a confused mon can still be
|
||||
-- burned and a switch shakes the confusion off.
|
||||
if status == "confuse" then return self:applyConfusion(mon) end
|
||||
if status == "confuse" then return self:applyConfusion(mon, nil, source) end
|
||||
-- engine/battle/effect_commands.asm:6338
|
||||
if source and self:sideOf(source) ~= self:sideOf(mon)
|
||||
and self:safeguarded(mon) then
|
||||
self:emit({ kind = "message",
|
||||
text = self:monName(mon) .. " is protected by SAFEGUARD!" })
|
||||
return false
|
||||
end
|
||||
-- One major status at a time.
|
||||
if mon.status then
|
||||
self:emit({ kind = "message",
|
||||
@@ -2953,8 +2989,15 @@ end
|
||||
-- as 256 turns. HELD_PREVENT_CONFUSE on the target blocks it outright.
|
||||
Battle.BERSERK_GENE_CONFUSE_TURNS = 256
|
||||
|
||||
function Battle:applyConfusion(mon, turns)
|
||||
function Battle:applyConfusion(mon, turns, source)
|
||||
if (mon.hp or 0) <= 0 then return false end
|
||||
-- engine/battle/effect_commands.asm:6338
|
||||
if source and self:sideOf(source) ~= self:sideOf(mon)
|
||||
and self:safeguarded(mon) then
|
||||
self:emit({ kind = "message",
|
||||
text = self:monName(mon) .. " is protected by SAFEGUARD!" })
|
||||
return false
|
||||
end
|
||||
local state = self:volatile(mon)
|
||||
if (state.substitute or 0) > 0 then return false end
|
||||
local held = self:heldEffect(mon, "confuse")
|
||||
@@ -4434,14 +4477,20 @@ Battle.SCREEN_FALL_TEXT = {
|
||||
function Battle:tickScreens()
|
||||
for _, side in ipairs({ "player", "enemy" }) do
|
||||
local screens = self.screens[side]
|
||||
for _, field in ipairs({ "lightScreen", "reflect" }) do
|
||||
for _, field in ipairs({ "lightScreen", "reflect", "safeguard" }) do
|
||||
if (screens[field] or 0) > 0 then
|
||||
screens[field] = screens[field] - 1
|
||||
if screens[field] <= 0 then
|
||||
screens[field] = nil
|
||||
self:emit({ kind = "message",
|
||||
text = Battle.SCREEN_SIDE_LABEL[side]
|
||||
.. Battle.SCREEN_FALL_TEXT[field] })
|
||||
if field == "safeguard" then
|
||||
-- engine/battle/core.asm:1527
|
||||
self:emit({ kind = "message",
|
||||
text = self:monName(self[side]) .. "'s SAFEGUARD faded!" })
|
||||
else
|
||||
self:emit({ kind = "message",
|
||||
text = Battle.SCREEN_SIDE_LABEL[side]
|
||||
.. Battle.SCREEN_FALL_TEXT[field] })
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user