mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-09-28 23:00:21 -04:00
59c6629457
- Fix held item (berries, herbs, pinch stat items) activation and permanent consumption on battler and party data during battles. - Enforce mandatory bicycle riding on Cycling Road (Route 16-18) when dismounting Surf, entering map, loading saves, and prevent bike dismount attempts. - Fix warp and door exit arrivals (e.g. Safari Zone gate) to execute warpExitArrival post-fade step so the player is placed on the correct forward tile and does not bypass triggers. - Optimize field rendering pipeline by caching animated tileset pieces, pooling native metatile cell allocations, and hoisting dynamic module lookups to fast accessors.
483 lines
25 KiB
Lua
483 lines
25 KiB
Lua
#!/usr/bin/env luajit
|
|
|
|
package.path = "./?.lua;./?/init.lua;" .. package.path
|
|
local GameCache = require("tests.game3_cache")
|
|
if not GameCache.bundle() then
|
|
print("[skip] game3 battle items + abilities: " .. tostring(GameCache.reason))
|
|
os.exit(0)
|
|
end
|
|
require("tests.fixture_data.game3_items").install()
|
|
|
|
local Moves = require("src.core.game3.battle.moves")
|
|
|
|
-- pokefirered/src/data/battle_moves.h:1
|
|
local ROM = {
|
|
[1] = { effect = 0, power = 40, type = 0, accuracy = 100, pp = 35, secondaryChance = 0, target = 0, priority = 0, flags = 51 },
|
|
[10] = { effect = 0, power = 40, type = 0, accuracy = 100, pp = 35, secondaryChance = 0, target = 0, priority = 0, flags = 51 },
|
|
[12] = { effect = 38, power = 1, type = 0, accuracy = 30, pp = 5, secondaryChance = 0, target = 0, priority = 0, flags = 19 },
|
|
[24] = { effect = 44, power = 30, type = 1, accuracy = 100, pp = 30, secondaryChance = 0, target = 0, priority = 0, flags = 51 },
|
|
[33] = { effect = 0, power = 35, type = 0, accuracy = 95, pp = 35, secondaryChance = 0, target = 0, priority = 0, flags = 51 },
|
|
[45] = { effect = 18, power = 0, type = 0, accuracy = 100, pp = 40, secondaryChance = 0, target = 8, priority = 0, flags = 22 },
|
|
[52] = { effect = 4, power = 40, type = 10, accuracy = 100, pp = 25, secondaryChance = 10, target = 0, priority = 0, flags = 18 },
|
|
[53] = { effect = 4, power = 95, type = 10, accuracy = 100, pp = 15, secondaryChance = 10, target = 0, priority = 0, flags = 18 },
|
|
[55] = { effect = 0, power = 40, type = 11, accuracy = 100, pp = 25, secondaryChance = 0, target = 0, priority = 0, flags = 50 },
|
|
[85] = { effect = 6, power = 95, type = 13, accuracy = 100, pp = 15, secondaryChance = 10, target = 0, priority = 0, flags = 18 },
|
|
[86] = { effect = 67, power = 0, type = 13, accuracy = 100, pp = 20, secondaryChance = 0, target = 0, priority = 0, flags = 22 },
|
|
[92] = { effect = 33, power = 0, type = 3, accuracy = 85, pp = 10, secondaryChance = 100, target = 0, priority = 0, flags = 22 },
|
|
[150] = { effect = 85, power = 0, type = 0, accuracy = 0, pp = 40, secondaryChance = 0, target = 16, priority = 0, flags = 0 },
|
|
[163] = { effect = 43, power = 70, type = 0, accuracy = 100, pp = 20, secondaryChance = 0, target = 0, priority = 0, flags = 51 },
|
|
[240] = { effect = 136, power = 0, type = 11, accuracy = 0, pp = 5, secondaryChance = 0, target = 16, priority = 0, flags = 0 },
|
|
}
|
|
for id, row in pairs(ROM) do row.numId = id end
|
|
Moves._romLoaded = true
|
|
Moves._rom = ROM
|
|
|
|
local State = require("src.core.game3.battle.state")
|
|
local Engine = require("src.core.game3.battle.engine")
|
|
local Adapter = require("src.core.game3.battle.adapter")
|
|
local Damage = require("src.core.game3.battle.damage")
|
|
local Types = require("src.core.game3.battle.types")
|
|
local HeldItems = require("src.core.game3.battle.held_items")
|
|
local T = Types.ID
|
|
|
|
local failed, passed = 0, 0
|
|
local function check(cond, msg)
|
|
if cond then
|
|
passed = passed + 1
|
|
print("[ok] " .. msg)
|
|
else
|
|
failed = failed + 1
|
|
print("[FAIL] " .. msg)
|
|
end
|
|
end
|
|
|
|
local function mon(o)
|
|
return {
|
|
species = o.species or 1, level = o.level or 50, hp = o.hp or 100, maxHp = o.maxHp or 100,
|
|
attack = o.attack or 50, defense = o.defense or 50, spAtk = o.spAtk or 50, spDef = o.spDef or 50,
|
|
speed = o.speed or 50, ability = o.ability or 0, nickname = o.nickname,
|
|
moves = o.moves or { 33 }, pp = o.pp or { 20, 20, 20, 20 }, maxPp = o.maxPp, status = o.status,
|
|
item = o.item, heldItem = o.item, gender = o.gender, personality = o.personality or 0,
|
|
otId = o.otId, otName = o.otName,
|
|
}
|
|
end
|
|
|
|
local function mkRng(map)
|
|
map = map or {}
|
|
local cursor = {}
|
|
return function(lo, hi)
|
|
local key = tostring(lo) .. "," .. tostring(hi)
|
|
local v = map[key]
|
|
if type(v) == "table" then
|
|
local i = (cursor[key] or 0) + 1
|
|
cursor[key] = i
|
|
v = v[math.min(i, #v)]
|
|
end
|
|
if v ~= nil then return v end
|
|
if lo == 1 and hi == 100 then return 1 end
|
|
return hi
|
|
end
|
|
end
|
|
|
|
local function battle(p, e, extra)
|
|
extra = extra or {}
|
|
p.nickname = p.nickname or "ALPHA"
|
|
e.nickname = e.nickname or "BRAVO"
|
|
local party = { mon(p) }
|
|
for _, x in ipairs(extra.bench or {}) do party[#party + 1] = mon(x) end
|
|
local foeParty = { mon(e) }
|
|
for _, x in ipairs(extra.foeBench or {}) do foeParty[#foeParty + 1] = mon(x) end
|
|
local st = State.new({ wild = extra.wild ~= false, playerParty = party, foeParty = foeParty })
|
|
st.player.type1, st.player.type2 = p.type1 or T.NORMAL, p.type2
|
|
st.enemy.type1, st.enemy.type2 = e.type1 or T.NORMAL, e.type2
|
|
st.player.species = p.species or st.player.species
|
|
st.enemy.species = e.species or st.enemy.species
|
|
st.rng = mkRng(extra.rng)
|
|
st.badges = extra.badges
|
|
st.playerTrainerId = extra.playerTrainerId
|
|
st.playerName = extra.playerName
|
|
local ad = Adapter.new(st)
|
|
return st, ad
|
|
end
|
|
|
|
local function use(st, ad, user, move, slot)
|
|
local target = (user == st.player) and st.enemy or st.player
|
|
local out = {}
|
|
Engine.resolveMove(user, target, move, slot or 1, ad, st, out)
|
|
return table.concat(out, " || "), out
|
|
end
|
|
|
|
local function eot(st, ad)
|
|
local events = Engine.collectResidualEvents(st, ad)
|
|
local msgs, evs = {}, {}
|
|
for _, ev in ipairs(events) do
|
|
for _, m in ipairs(ev.msgs or {}) do msgs[#msgs + 1] = m end
|
|
for _, e in ipairs(ev.events or {}) do evs[#evs + 1] = e end
|
|
end
|
|
return table.concat(msgs, " || "), evs
|
|
end
|
|
|
|
local function has(s, needle) return s:find(needle, 1, true) ~= nil end
|
|
|
|
local function hasAnim(evs, name, side)
|
|
for _, e in ipairs(evs or {}) do
|
|
if e.kind == "anim" and e.name == name and (side == nil or e.target == side) then return true end
|
|
end
|
|
return false
|
|
end
|
|
|
|
local function hpEvent(evs, side)
|
|
for _, e in ipairs(evs or {}) do
|
|
if e.kind == "hp" and e.side == side then return e end
|
|
end
|
|
return nil
|
|
end
|
|
|
|
print("=== LEFTOVERS ===")
|
|
do
|
|
local st, ad = battle({ item = 200, hp = 50, maxHp = 160 }, {})
|
|
local txt, evs = eot(st, ad)
|
|
check(ad:hp(st.player) == 60, "Leftovers heals maxHP/16 at end of turn")
|
|
check(has(txt, "ALPHA's LEFTOVERS\nrestored its HP a little!") or has(txt, "restored its HP a little!"), "Leftovers message")
|
|
check(hasAnim(evs, "HELD_ITEM_EFFECT", "player") and hpEvent(evs, "player") ~= nil, "Leftovers plays HELD_ITEM_EFFECT and emits hp event")
|
|
check(st.player.item == 200, "Leftovers is not consumed")
|
|
end
|
|
|
|
print("=== ORAN / SITRUS (consumed, persisted, recyclable) ===")
|
|
do
|
|
local st, ad = battle({ item = 139, hp = 50, maxHp = 100 }, {})
|
|
eot(st, ad)
|
|
check(ad:hp(st.player) == 60, "Oran Berry restores 10 HP at <= 1/2")
|
|
check(st.player.item == 0 and st.playerParty[1].item == nil, "consumed berry is removed from the party mon")
|
|
check(st.playerSide.expUsedHeldItem == 139, "used held item recorded for Recycle")
|
|
|
|
local st2, ad2 = battle({ item = 142, hp = 51, maxHp = 100 }, {})
|
|
eot(st2, ad2)
|
|
check(ad2:hp(st2.player) == 51 and st2.player.item == 142, "Sitrus waits until HP <= 1/2")
|
|
|
|
local st3, ad3 = battle({ item = 139, hp = 60, maxHp = 100 }, { moves = { 33 } })
|
|
use(st3, ad3, st3.enemy, 33)
|
|
check(st3.player.item == 0 and st3.playerParty[1].item == nil, "HP berries trigger and are consumed mid-turn when HP drops")
|
|
check(ad3:hp(st3.player) > (60 - 35), "Oran Berry healed after taking damage")
|
|
end
|
|
|
|
print("=== CHERI / LUM / PERSIM at move end ===")
|
|
do
|
|
local st, ad = battle({ item = 133 }, { moves = { 86 } })
|
|
local txt = use(st, ad, st.enemy, 86)
|
|
check(ad:status(st.player) == nil and st.player.item == 0, "Cheri Berry cures Thunder Wave paralysis right away")
|
|
check(has(txt, "ALPHA's CHERI BERRY\ncured paralysis!"), "Cheri message")
|
|
|
|
local st2, ad2 = battle({ item = 141, status = "PAR" }, {})
|
|
st2.player.confusionTurns = 3
|
|
local txt2 = eot(st2, ad2)
|
|
check(has(txt2, "LUM BERRY\nnormalized its status!") and ad2:status(st2.player) == nil
|
|
and (st2.player.confusionTurns or 0) == 0, "Lum Berry normalizes multiple problems at end of turn")
|
|
|
|
local st3, ad3 = battle({ item = 140 }, { moves = { 150 } })
|
|
st3.player.confusionTurns = 3
|
|
local txt3 = use(st3, ad3, st3.enemy, 150)
|
|
check(has(txt3, "snapped it out of confusion!") and (st3.player.confusionTurns or 0) == 0, "Persim Berry at move end")
|
|
end
|
|
|
|
print("=== CONFUSION BERRIES (flavor dislike) ===")
|
|
do
|
|
local st, ad = battle({ item = 143, hp = 40, maxHp = 80, personality = 15 }, {}, { rng = { ["0,3"] = 1 } })
|
|
local txt = eot(st, ad)
|
|
check(ad:hp(st.player) == 50, "Figy restores maxHP/8")
|
|
check(has(txt, "For ALPHA,\nFIGY BERRY was too spicy!") and (st.player.confusionTurns or 0) == 3,
|
|
"Modest nature dislikes spicy and gets confused")
|
|
local st2, ad2 = battle({ item = 143, hp = 40, maxHp = 80, personality = 3 }, {})
|
|
local txt2 = eot(st2, ad2)
|
|
check(not has(txt2, "too spicy") and (st2.player.confusionTurns or 0) == 0, "Adamant nature likes spicy")
|
|
end
|
|
|
|
print("=== PINCH STAT BERRIES ===")
|
|
do
|
|
local st, ad = battle({ item = 168, hp = 25, maxHp = 100 }, {})
|
|
local txt, evs = eot(st, ad)
|
|
check(st.player.stages.attack == 1 and st.player.item == 0, "Liechi raises Attack at 1/4 HP")
|
|
check(has(txt, "Using LIECHI BERRY, the ATTACK\nof ALPHA rose!"), "stat berry message")
|
|
check(hasAnim(evs, "HELD_ITEM_EFFECT") and hasAnim(evs, "STATS_CHANGE"), "stat berry anims")
|
|
|
|
local st2, ad2 = battle({ item = 174, hp = 20, maxHp = 100 }, {}, { rng = { ["0,4"] = 2 } })
|
|
local txt2 = eot(st2, ad2)
|
|
check(st2.player.stages.speed == 2 and has(txt2, "sharply rose!"), "Starf sharply raises a random stat")
|
|
|
|
local st3, ad3 = battle({ item = 173, hp = 20, maxHp = 100 }, {})
|
|
local txt3 = eot(st3, ad3)
|
|
check(st3.player.focusEnergy and has(txt3, "used\nLANSAT BERRY to hustle!"), "Lansat sets focus energy")
|
|
end
|
|
|
|
print("=== LEPPA ===")
|
|
do
|
|
local st, ad = battle({ item = 138, moves = { 33, 1 }, pp = { 0, 5 }, maxPp = { 35, 35 } }, {})
|
|
local txt = eot(st, ad)
|
|
check(st.player.mon.pp[1] == 10 and st.playerParty[1].pp[1] == 10, "Leppa restores 10 PP to the empty move")
|
|
check(has(txt, "restored TACKLE's PP!") or has(txt, "'s PP!"), "Leppa message")
|
|
end
|
|
|
|
print("=== WHITE HERB / MENTAL HERB ===")
|
|
do
|
|
local st, ad = battle({ item = 180 }, { moves = { 45 } })
|
|
local txt = use(st, ad, st.enemy, 45)
|
|
check(st.player.stages.attack == 0 and st.player.item == 0 and has(txt, "WHITE HERB\nrestored its status!"),
|
|
"White Herb restores lowered stats at move end")
|
|
local st2, ad2 = battle({ item = 185 }, { moves = { 150 } })
|
|
st2.player.expInfatuated = true
|
|
st2.player.expInfatuatedWith = st2.enemy
|
|
local txt2 = use(st2, ad2, st2.enemy, 150)
|
|
check(not st2.player.expInfatuated and has(txt2, "MENTAL HERB\ncured its love problem!"), "Mental Herb cures infatuation")
|
|
end
|
|
|
|
print("=== DAMAGE ITEMS ===")
|
|
do
|
|
local function base(p, e, move, extra)
|
|
local st, ad = battle(p, e, extra)
|
|
return Damage.base(st.player, st.enemy, ROM[move], { adapter = ad, power = ROM[move].power, moveType = ROM[move].type })
|
|
end
|
|
local plain = base({}, {}, 33)
|
|
check(base({ item = 186 }, {}, 33) > plain, "Choice Band boosts physical attack")
|
|
check(base({ item = 215 }, {}, 52) > base({}, {}, 52), "Charcoal boosts Fire moves")
|
|
check(base({ item = 215 }, {}, 33) == plain, "Charcoal does not boost Normal moves")
|
|
check(base({ item = 202, species = 25 }, {}, 85) > base({ species = 25 }, {}, 85), "Light Ball doubles Pikachu SpAtk")
|
|
check(base({ item = 224, species = 104 }, {}, 33) > base({ species = 104 }, {}, 33), "Thick Club doubles Cubone Attack")
|
|
check(base({}, { item = 223, species = 132 }, 33) < base({}, { species = 132 }, 33), "Metal Powder doubles Ditto Defense")
|
|
check(base({ item = 192, species = 373 }, {}, 55) > base({ species = 373 }, {}, 55), "DeepSeaTooth for Clamperl")
|
|
check(base({ item = 191, species = 407 }, {}, 55) > base({ species = 407 }, {}, 55), "Soul Dew for Latias")
|
|
check(base({}, {}, 33, { badges = { true } }) > plain, "Boulder Badge boosts player Attack")
|
|
check(base({ ability = "PLUS" }, { ability = "MINUS" }, 55) > base({ ability = "PLUS" }, {}, 55), "Plus with Minus on field")
|
|
end
|
|
|
|
print("=== CHOICE BAND lock ===")
|
|
do
|
|
local st, ad = battle({ item = 186, moves = { 33, 1 } }, { hp = 999, maxHp = 999 })
|
|
use(st, ad, st.player, 33, 1)
|
|
check(st.player.choicedMove == 33, "Choice Band records the locked move on the battler")
|
|
local acts = Engine.planTurnFromActions(st, ad, { kind = "move", move = 1, slot = 2 }, { kind = "move", move = 33, slot = 1 })
|
|
local pAct
|
|
for _, a in ipairs(acts) do if a.user == st.player then pAct = a end end
|
|
check(pAct and pAct.move == 33 and pAct.slot == 1, "planTurn enforces the Choice Band move")
|
|
local bad = Engine.moveLimitations(st.player, ad)
|
|
check(bad[2] and not bad[1], "moveLimitations greys out non-choiced moves")
|
|
end
|
|
|
|
print("=== QUICK CLAW / MACHO BRACE ===")
|
|
do
|
|
local st, ad = battle({ item = 183, speed = 10 }, { speed = 100 }, { rng = { ["0,65535"] = 100 } })
|
|
local acts = Engine.planTurnFromActions(st, ad, { kind = "move", move = 33, slot = 1 }, { kind = "move", move = 33, slot = 1 })
|
|
check(acts[1].user == st.player, "Quick Claw activates when randomTurnNumber < 0xFFFF*20/100")
|
|
local st2, ad2 = battle({ item = 183, speed = 10 }, { speed = 100 }, { rng = { ["0,65535"] = 20000 } })
|
|
local acts2 = Engine.planTurnFromActions(st2, ad2, { kind = "move", move = 33, slot = 1 }, { kind = "move", move = 33, slot = 1 })
|
|
check(acts2[1].user == st2.enemy, "Quick Claw does not activate on a high roll")
|
|
local st3, ad3 = battle({ item = 181, speed = 100 }, { speed = 60 })
|
|
check(Engine.speedOf(st3.player, st3, ad3) == 50, "Macho Brace halves speed")
|
|
local st4, ad4 = battle({ speed = 100 }, {}, { badges = { false, false, true } })
|
|
check(Engine.speedOf(st4.player, st4, ad4) == 110, "Thunder Badge speed boost")
|
|
end
|
|
|
|
print("=== BRIGHT POWDER / LAX INCENSE ===")
|
|
do
|
|
local st, ad = battle({ moves = { 1 } }, { item = 179, hp = 999, maxHp = 999 }, { rng = { ["1,100"] = 91 } })
|
|
local txt = use(st, ad, st.player, 1)
|
|
check(has(txt, "attack missed"), "Bright Powder: 100 acc -> 90")
|
|
local st2, ad2 = battle({ moves = { 1 } }, { item = 221, hp = 999, maxHp = 999 }, { rng = { ["1,100"] = 91 } })
|
|
local txt2 = use(st2, ad2, st2.player, 1)
|
|
check(not has(txt2, "attack missed"), "Lax Incense uses param 5 (95)")
|
|
end
|
|
|
|
print("=== FOCUS BAND ===")
|
|
do
|
|
local st, ad = battle({ moves = { 163 }, attack = 300 }, { item = 196, hp = 5, maxHp = 100 }, { rng = { ["0,99"] = 0 } })
|
|
local txt, out = use(st, ad, st.player, 163)
|
|
check(ad:hp(st.enemy) == 1, "Focus Band leaves 1 HP")
|
|
check(has(txt, "BRAVO hung on\nusing its FOCUS BAND!"), "Focus Band message")
|
|
check(hasAnim(out.events, "FOCUS_BAND", "enemy"), "FOCUS_BAND anim")
|
|
local st2, ad2 = battle({ moves = { 12 }, level = 60 }, { item = 196, level = 50 }, { rng = { ["0,99"] = 0, ["1,100"] = 1 } })
|
|
use(st2, ad2, st2.player, 12)
|
|
check(ad2:hp(st2.enemy) == 1, "Focus Band survives an OHKO move")
|
|
end
|
|
|
|
print("=== KING'S ROCK / SHELL BELL ===")
|
|
do
|
|
local st, ad = battle({ item = 187, moves = { 33 } }, { hp = 999, maxHp = 999 }, { rng = { ["0,99"] = 0 } })
|
|
use(st, ad, st.player, 33)
|
|
check(st.enemy.flinched == true, "King's Rock flinches on a contact move")
|
|
local st2, ad2 = battle({ item = 219, moves = { 33 }, hp = 50, maxHp = 100 }, { hp = 999, maxHp = 999 })
|
|
local _, out = use(st2, ad2, st2.player, 33)
|
|
local dealt = 999 - ad2:hp(st2.enemy)
|
|
check(ad2:hp(st2.player) == 50 + math.max(1, math.floor(dealt / 8)), "Shell Bell heals 1/8 damage dealt")
|
|
check(hasAnim(out.events, "HELD_ITEM_EFFECT", "player"), "Shell Bell anim")
|
|
end
|
|
|
|
print("=== FLEE / SMOKE BALL / RUN AWAY / TRAPPING ===")
|
|
do
|
|
local st, ad = battle({ item = 194, speed = 1 }, { speed = 200 })
|
|
local ok, how = Engine.tryFlee(st, ad)
|
|
check(ok and how == "item", "Smoke Ball always escapes")
|
|
check(hasAnim(ad:events(), "SMOKEBALL_ESCAPE"), "SMOKEBALL_ESCAPE anim")
|
|
local st2, ad2 = battle({ ability = "RUN_AWAY", speed = 1 }, { speed = 200 })
|
|
local ok2, how2 = Engine.tryFlee(st2, ad2)
|
|
check(ok2 and how2 == "ability", "Run Away always escapes")
|
|
local st3, ad3 = battle({ speed = 10 }, { speed = 100 }, { rng = { ["0,255"] = 11 } })
|
|
check(Engine.tryFlee(st3, ad3) == true, "flee: 10*128/100 = 12 > 11")
|
|
local st4, ad4 = battle({ speed = 10 }, { speed = 100 }, { rng = { ["0,255"] = 12 } })
|
|
check(Engine.tryFlee(st4, ad4) == false and st4.fleeAttempts == 1, "flee fails and counts attempts")
|
|
local st5, ad5 = battle({}, { ability = "SHADOW_TAG" })
|
|
local can, msg = Engine.canRun(st5, ad5)
|
|
check(not can and has(msg, "prevents\nescape with SHADOW TAG!"), "Shadow Tag prevents escape")
|
|
local st6, ad6 = battle({ type1 = T.FLYING }, { ability = "ARENA_TRAP" })
|
|
check(Engine.canRun(st6, ad6) == true, "Arena Trap does not trap Flying types")
|
|
end
|
|
|
|
print("=== INTIMIDATE / DRIZZLE / TRACE at battle start ===")
|
|
do
|
|
local st, ad = battle({ ability = "INTIMIDATE" }, {})
|
|
local out = {}
|
|
ad._say = function(t) out[#out + 1] = t end
|
|
Engine.battleStartEffects(st, ad)
|
|
local txt = table.concat(out, " || ")
|
|
check(st.enemy.stages.attack == -1 and has(txt, "ALPHA's INTIMIDATE\ncuts Wild BRAVO's ATTACK!"), "Intimidate lowers foe Attack")
|
|
check(hasAnim(ad:events(), "STATS_CHANGE", "enemy"), "Intimidate plays STATS_CHANGE on the foe")
|
|
|
|
local st2, ad2 = battle({ ability = "INTIMIDATE" }, { ability = "HYPER_CUTTER" })
|
|
local o2 = {}
|
|
ad2._say = function(t) o2[#o2 + 1] = t end
|
|
Engine.battleStartEffects(st2, ad2)
|
|
check(st2.enemy.stages.attack == 0 and has(table.concat(o2), "Wild BRAVO's HYPER CUTTER\nprevented ALPHA's\\lINTIMIDATE from working!"),
|
|
"Hyper Cutter blocks Intimidate")
|
|
|
|
local st3, ad3 = battle({}, { ability = "DRIZZLE" })
|
|
local o3 = {}
|
|
ad3._say = function(t) o3[#o3 + 1] = t end
|
|
Engine.battleStartEffects(st3, ad3)
|
|
check(st3.weather == "RAIN" and (st3.weatherTurns or 0) == 0, "Drizzle sets permanent rain")
|
|
check(has(table.concat(o3), "BRAVO's DRIZZLE\nmade it rain!") and hasAnim(ad3:events(), "RAIN_CONTINUES"), "Drizzle message + anim")
|
|
|
|
local st4, ad4 = battle({ ability = "TRACE" }, { ability = "STATIC" })
|
|
local o4 = {}
|
|
ad4._say = function(t) o4[#o4 + 1] = t end
|
|
Engine.battleStartEffects(st4, ad4)
|
|
check(ad4:abilityOf(st4.player) == "STATIC" and has(table.concat(o4), "ALPHA TRACED\nWild BRAVO's STATIC!"), "Trace copies the foe ability")
|
|
end
|
|
|
|
print("=== CONTACT ABILITIES ===")
|
|
do
|
|
local st, ad = battle({ moves = { 33 } }, { ability = "STATIC", hp = 999, maxHp = 999 }, { rng = { ["0,2"] = 0 } })
|
|
local txt = use(st, ad, st.player, 33)
|
|
check(ad:status(st.player) == "PAR" and has(txt, "BRAVO's STATIC\nparalyzed ALPHA!"), "Static paralyzes on contact")
|
|
local st2, ad2 = battle({ moves = { 55 } }, { ability = "STATIC", hp = 999, maxHp = 999 }, { rng = { ["0,2"] = 0 } })
|
|
use(st2, ad2, st2.player, 55)
|
|
check(ad2:status(st2.player) == nil, "Static ignores non-contact moves")
|
|
local st3, ad3 = battle({ moves = { 33 }, maxHp = 160, hp = 160 }, { ability = "ROUGH_SKIN", hp = 999, maxHp = 999 })
|
|
local txt3 = use(st3, ad3, st3.player, 33)
|
|
check(ad3:hp(st3.player) == 150 and has(txt3, "BRAVO's ROUGH SKIN\nhurt ALPHA!"), "Rough Skin deals 1/16")
|
|
local st4, ad4 = battle({ moves = { 33 } }, { ability = "EFFECT_SPORE", hp = 999, maxHp = 999 },
|
|
{ rng = { ["0,9"] = 0, ["0,3"] = 1 } })
|
|
local txt4 = use(st4, ad4, st4.player, 33)
|
|
check(ad4:status(st4.player) == "SLP" and has(txt4, "EFFECT SPORE\nmade ALPHA sleep!"), "Effect Spore sleep")
|
|
local st5, ad5 = battle({ moves = { 33 }, ability = "LIMBER" }, { ability = "STATIC", hp = 999, maxHp = 999 }, { rng = { ["0,2"] = 0 } })
|
|
local txt5 = use(st5, ad5, st5.player, 33)
|
|
check(ad5:status(st5.player) == nil and not has(txt5, "prevents"), "Limber silently blocks Static")
|
|
local st6, ad6 = battle({ moves = { 33 }, gender = "M" }, { ability = "CUTE_CHARM", gender = "F", hp = 999, maxHp = 999 }, { rng = { ["0,2"] = 0 } })
|
|
local txt6 = use(st6, ad6, st6.player, 33)
|
|
check(st6.player.expInfatuated and has(txt6, "BRAVO's CUTE CHARM\ninfatuated ALPHA!"), "Cute Charm infatuates")
|
|
local st7, ad7 = battle({ moves = { 52 } }, { ability = "COLOR_CHANGE", hp = 999, maxHp = 999 })
|
|
local txt7 = use(st7, ad7, st7.player, 52)
|
|
check(st7.enemy.type1 == T.FIRE and has(txt7, "COLOR CHANGE\nmade it the FIRE type!"), "Color Change")
|
|
end
|
|
|
|
print("=== ABSORB ABILITIES / SOUNDPROOF ===")
|
|
do
|
|
local st, ad = battle({ moves = { 85 } }, { ability = "VOLT_ABSORB", hp = 50, maxHp = 100 })
|
|
local txt = use(st, ad, st.player, 85)
|
|
check(ad:hp(st.enemy) == 75 and has(txt, "BRAVO restored HP\nusing its VOLT ABSORB!"), "Volt Absorb heals 1/4")
|
|
local st2, ad2 = battle({ moves = { 55 } }, { ability = "WATER_ABSORB" })
|
|
local txt2 = use(st2, ad2, st2.player, 55)
|
|
check(ad2:hp(st2.enemy) == 100 and has(txt2, "BRAVO's WATER ABSORB\nmade ") and has(txt2, " useless!"), "Water Absorb at full HP")
|
|
local st3, ad3 = battle({ moves = { 52 } }, { ability = "FLASH_FIRE" })
|
|
local txt3 = use(st3, ad3, st3.player, 52)
|
|
check(st3.enemy.expFlashFire and has(txt3, "FLASH FIRE\nraised its FIRE power!"), "Flash Fire activates")
|
|
local st4, ad4 = battle({ moves = { 45 } }, { ability = "SOUNDPROOF" })
|
|
local txt4 = use(st4, ad4, st4.player, 45)
|
|
check(st4.enemy.stages.attack == 0 and has(txt4, "BRAVO's SOUNDPROOF\nblocks GROWL!"), "Soundproof blocks Growl")
|
|
end
|
|
|
|
print("=== SYNCHRONIZE / IMMUNITY CURE ===")
|
|
do
|
|
local st, ad = battle({ moves = { 86 } }, { ability = "SYNCHRONIZE" })
|
|
local txt = use(st, ad, st.player, 86)
|
|
check(ad:status(st.enemy) == "PAR" and ad:status(st.player) == "PAR", "Synchronize passes paralysis back")
|
|
check(has(txt, "BRAVO's SYNCHRONIZE\nparalyzed ALPHA!"), "Synchronize message")
|
|
local st2, ad2 = battle({ moves = { 92 } }, { ability = "SYNCHRONIZE" }, { rng = { ["1,100"] = 1 } })
|
|
use(st2, ad2, st2.player, 92)
|
|
check(ad2:status(st2.player) == "PSN", "Synchronize turns Toxic into regular poison")
|
|
local st3, ad3 = battle({ status = "PSN", ability = "IMMUNITY" }, { moves = { 150 } })
|
|
local txt3 = use(st3, ad3, st3.enemy, 150)
|
|
check(ad3:status(st3.player) == nil and has(txt3, "ALPHA's IMMUNITY\ncured its poison problem!"), "Immunity cures poison at move end")
|
|
end
|
|
|
|
print("=== END-OF-TURN ABILITIES ===")
|
|
do
|
|
local st, ad = battle({ ability = "SPEED_BOOST" }, {})
|
|
st.player.isFirstTurn = 1
|
|
local txt = eot(st, ad)
|
|
check(st.player.stages.speed == 1 and has(txt, "ALPHA's SPEED BOOST\nraised its SPEED!"), "Speed Boost")
|
|
local st2, ad2 = battle({ ability = "SPEED_BOOST" }, {})
|
|
eot(st2, ad2)
|
|
check(st2.player.stages.speed == 0, "Speed Boost skips the switch-in turn")
|
|
local st3, ad3 = battle({ ability = "RAIN_DISH", hp = 50, maxHp = 160 }, {})
|
|
st3.weather, st3.weatherTurns = "RAIN", 5
|
|
eot(st3, ad3)
|
|
check(ad3:hp(st3.player) == 60, "Rain Dish heals 1/16 in rain")
|
|
local st4, ad4 = battle({ ability = "SHED_SKIN", status = "BRN" }, {}, { rng = { ["0,2"] = 0 } })
|
|
local txt4 = eot(st4, ad4)
|
|
check(ad4:status(st4.player) == nil and has(txt4, "SHED SKIN\ncured its burn problem!"), "Shed Skin")
|
|
end
|
|
|
|
print("=== TRUANT ===")
|
|
do
|
|
local st, ad = battle({ ability = "TRUANT", moves = { 33 } }, { hp = 999, maxHp = 999 })
|
|
use(st, ad, st.player, 33)
|
|
eot(st, ad)
|
|
local txt = use(st, ad, st.player, 33)
|
|
check(has(txt, "ALPHA is\nloafing around!"), "Truant loafs every other turn")
|
|
eot(st, ad)
|
|
local txt2 = use(st, ad, st.player, 33)
|
|
check(not has(txt2, "loafing"), "Truant acts again the next turn")
|
|
end
|
|
|
|
print("=== NATURAL CURE / FORECAST ===")
|
|
do
|
|
local st, ad = battle({ ability = "NATURAL_CURE", status = "PSN" }, {})
|
|
Engine.switchOutEffects(st, ad, st.player)
|
|
check(ad:status(st.player) == nil and st.playerParty[1].status == nil, "Natural Cure cures on switch-out")
|
|
local st2, ad2 = battle({}, { ability = "FORECAST", species = 385, moves = { 240 } })
|
|
local txt2 = use(st2, ad2, st2.enemy, 240)
|
|
check(st2.enemy.type1 == T.WATER and has(txt2, "BRAVO transformed!"), "Forecast turns Castform Water in rain")
|
|
check(hasAnim(ad2:events(), "CASTFORM_CHANGE", "enemy"), "CASTFORM_CHANGE anim")
|
|
end
|
|
|
|
print("=== DISOBEDIENCE ===")
|
|
do
|
|
local st, ad = battle({ moves = { 33 }, level = 60, otId = 999, otName = "ASH" }, { hp = 999, maxHp = 999 },
|
|
{ playerTrainerId = 1, playerName = "RED", rng = { ["0,255"] = { 255, 255, 0, 255 }, ["0,3"] = 1 } })
|
|
local txt = use(st, ad, st.player, 33)
|
|
check(has(txt, "ALPHA began to nap!") and ad:status(st.player) == "SLP", "traded mon over the cap naps")
|
|
local st2, ad2 = battle({ moves = { 33 }, level = 60, otId = 1, otName = "RED" }, { hp = 999, maxHp = 999 },
|
|
{ playerTrainerId = 1, playerName = "RED", rng = { ["0,255"] = 255 } })
|
|
local txt2 = use(st2, ad2, st2.player, 33)
|
|
check(has(txt2, "ALPHA used"), "own mon always obeys")
|
|
local st3, ad3 = battle({ moves = { 33 }, level = 60, otId = 999 }, { hp = 999, maxHp = 999 },
|
|
{ playerTrainerId = 1, playerName = "RED", badges = { true, true, true, true, true, true, true, true } })
|
|
local txt3 = use(st3, ad3, st3.player, 33)
|
|
check(has(txt3, "ALPHA used"), "Earth Badge makes traded mons obey")
|
|
end
|
|
|
|
print(string.format("\n%d passed, %d failed", passed, failed))
|
|
if failed > 0 then
|
|
print("[FAIL] game3 battle items + abilities")
|
|
os.exit(1)
|
|
end
|
|
print("[ok] game3 battle items + abilities")
|