mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-09-26 13:33:27 -04:00
Port FireRed wild encounter grace period
Gen3 (FireRed) had no cooldown between wild battles, so every step on an encounter tile rolled at the area's full rate -- Route 1 (~21%) landed a battle on roughly one step in five. Gen2 already had this via World:wildCooldownStep(); game3 never did. Port pret/pokefirered wild_encounter.c: - GetMapBaseEncounterCooldown -> Encounters.mapBaseCooldown: steps of immunity derived from the area's own rate (rate >= 80 -> none, < 10 -> 8, else 8 - rate/10). - HandleWildEncounterCooldown -> Encounters.handleCooldown: a soft floor, not a hard gate -- once the minimum elapses a 5%/step leak lets a battle through anyway. Includes the White/Black Flute, Cleanse Tag, Stench and Illuminate modifiers in pret's application order (Cleanse Tag before the ability mod, which changes the result). - ResetEncounterRateModifiers -> Encounters.resetRateModifiers, wired to the two places pret resets: map load (Map.load, including seamless connection crossings) and battle start (BattleBridge.startWild). The latter is the bug-#1229 class -- scripted battles and fishing re-arm the grace period even though no step rolled. Measured on Route 1 over 4000 steps: 843 encounters before, 397 after; first encounter never lands before step 7. Not ported (makes encounters more likely, so it does not affect the reported symptom): the encounterRateBuff anti-frustration ramp and the Mach/Acro bike rate modifier. tests/engine/wild_encounter_cooldown.lua covers the cooldown golden table, the counter/leak mechanics, all five modifiers, both re-arm paths and the end-to-end step count. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -392,6 +392,13 @@ end
|
||||
function BattleBridge.startWild(mod, game, encounter, opts)
|
||||
opts = opts or {}
|
||||
opts.wild = true
|
||||
-- pret battle_setup.c resets the encounter cooldown when a battle starts, so
|
||||
-- the grace period re-arms after every wild battle -- including ones nothing
|
||||
-- stepped into (scripted battles, fishing).
|
||||
local okE, Encounters = pcall(require, "src.core.game3.encounters")
|
||||
if okE and Encounters and Encounters.resetRateModifiers then
|
||||
Encounters.resetRateModifiers()
|
||||
end
|
||||
return BattleBridge.start(mod, game, encounter, opts)
|
||||
end
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ local Encounters = {}
|
||||
Encounters._tables = {} -- mapId or "group:num" → { land = { rate, slots }, ... }
|
||||
Encounters._pendingWild = nil
|
||||
Encounters._prevGrass = false -- pret first-step-into-grass gate
|
||||
Encounters._stepsSinceLastEncounter = 0 -- pret sWildEncounterData.stepsSinceLastEncounter
|
||||
Encounters._logged = false
|
||||
Encounters._loaded = false
|
||||
|
||||
@@ -18,6 +19,21 @@ local LAND_WEIGHTS = { 20, 20, 10, 10, 10, 10, 5, 5, 4, 4, 1, 1 }
|
||||
local WATER_WEIGHTS = { 60, 30, 5, 4, 1 }
|
||||
local MAX_ENCOUNTER_RATE = 1600 -- pret wild_encounter.c (FireRed)
|
||||
|
||||
-- Ids the encounter-rate modifiers below key off (pret constants/abilities.h,
|
||||
-- constants/items.h, constants/flags.h).
|
||||
local ABILITY_STENCH = 1
|
||||
local ABILITY_ILLUMINATE = 35
|
||||
local ITEM_CLEANSE_TAG = 190
|
||||
local FLAG_SYS_WHITE_FLUTE_ACTIVE = 0x803
|
||||
local FLAG_SYS_BLACK_FLUTE_ACTIVE = 0x804
|
||||
|
||||
-- pret GetMapBaseEncounterCooldown returns 0xFF when the map has no encounter
|
||||
-- data for that tile type, which aborts the check instead of granting a grace
|
||||
-- period (the roll would fail anyway).
|
||||
local COOLDOWN_NONE = 0xFF
|
||||
local COOLDOWN_BASE_LEAK = 5 -- pret: encRate = 5 * 256
|
||||
local COOLDOWN_SCALE = 256 -- pret keeps minSteps/encRate scaled so the modifiers stay fractional
|
||||
|
||||
local function log(msg)
|
||||
print("[game3/encounters] " .. tostring(msg))
|
||||
end
|
||||
@@ -186,6 +202,130 @@ local function table_for(mapId)
|
||||
return Encounters._tables[tostring(mapId)]
|
||||
end
|
||||
|
||||
--- The area `terrain` rolls on, resolved the same way rollLand/rollWater do.
|
||||
--- The cooldown needs the rate before the roll happens, and must not consume
|
||||
--- RNG to get it.
|
||||
local function area_for(mapId, terrain)
|
||||
local t = table_for(mapId)
|
||||
if terrain == "water" then
|
||||
return normalize_area(t and t.water, 15)
|
||||
end
|
||||
return normalize_area(t and t.land) or normalize_area(t and t.grass)
|
||||
end
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Wild encounter grace period (pret wild_encounter.c).
|
||||
--
|
||||
-- FireRed is the only generation with a step cooldown between wild battles:
|
||||
-- HandleWildEncounterCooldown refuses the roll for a map-dependent number of
|
||||
-- steps after the last encounter, then lets a small percentage per step
|
||||
-- through so the wait is soft rather than a hard floor.
|
||||
-- ---------------------------------------------------------------------------
|
||||
|
||||
--- pret GetMapBaseEncounterCooldown: how many steps after a battle are immune,
|
||||
--- derived from the area's own encounter rate. Rates at 80+ get no grace period
|
||||
--- at all; below that the wait grows as the rate drops.
|
||||
function Encounters.mapBaseCooldown(terrain, rate)
|
||||
if terrain ~= "land" and terrain ~= "water" then return COOLDOWN_NONE end
|
||||
if rate == nil then return COOLDOWN_NONE end
|
||||
rate = tonumber(rate) or 0
|
||||
if rate >= 80 then return 0 end
|
||||
if rate < 10 then return 8 end
|
||||
return 8 - math.floor(rate / 10)
|
||||
end
|
||||
|
||||
--- pret GetLeadMonIndex: the lead party slot, eggs excluded.
|
||||
local function lead_mon()
|
||||
local ok, Runtime = pcall(require, "src.core.game3.runtime")
|
||||
local session = ok and Runtime and Runtime.getSession and Runtime.getSession()
|
||||
local party = session and session.party
|
||||
if type(party) ~= "table" then return nil end
|
||||
for i = 1, #party do
|
||||
local mon = party[i]
|
||||
if type(mon) == "table" and not mon.isEgg and not mon.egg then return mon end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
--- pret GetFluteEncounterRateModType: 1 = White Flute, 2 = Black Flute.
|
||||
local function flute_mod_type()
|
||||
local okS, Space = pcall(require, "src.core.game3.scripting.space")
|
||||
if not okS or not Space or not Space.store then return 0 end
|
||||
local okF, Flags = pcall(require, "src.core.game3.scripting.flags")
|
||||
if not okF or not Flags or not Flags.getFlag then return 0 end
|
||||
if Flags.getFlag(Space.store, nil, FLAG_SYS_WHITE_FLUTE_ACTIVE) then return 1 end
|
||||
if Flags.getFlag(Space.store, nil, FLAG_SYS_BLACK_FLUTE_ACTIVE) then return 2 end
|
||||
return 0
|
||||
end
|
||||
|
||||
--- pret IsLeadMonHoldingCleanseTag.
|
||||
local function lead_holds_cleanse_tag()
|
||||
local mon = lead_mon()
|
||||
if not mon then return false end
|
||||
return (tonumber(mon.item or mon.heldItem) or 0) == ITEM_CLEANSE_TAG
|
||||
end
|
||||
|
||||
--- pret GetAbilityEncounterRateModType: Stench 1 (rarer), Illuminate 2 (commoner).
|
||||
local function ability_mod_type()
|
||||
local mon = lead_mon()
|
||||
if not mon then return 0 end
|
||||
local ability = tonumber(mon.abilityId or mon.ability) or 0
|
||||
if ability == ABILITY_STENCH then return 1 end
|
||||
if ability == ABILITY_ILLUMINATE then return 2 end
|
||||
return 0
|
||||
end
|
||||
|
||||
--- The fully modified (minSteps, leak) pair pret computes inside
|
||||
--- HandleWildEncounterCooldown. nil means "no encounter data here".
|
||||
function Encounters.cooldownMinSteps(terrain, rate)
|
||||
local minSteps = Encounters.mapBaseCooldown(terrain, rate)
|
||||
if minSteps == COOLDOWN_NONE then return nil end
|
||||
|
||||
minSteps = minSteps * COOLDOWN_SCALE
|
||||
local leak = COOLDOWN_BASE_LEAK * COOLDOWN_SCALE
|
||||
local flute = flute_mod_type()
|
||||
if flute == 1 then
|
||||
minSteps = minSteps - math.floor(minSteps / 2)
|
||||
leak = leak + math.floor(leak / 2)
|
||||
elseif flute == 2 then
|
||||
minSteps = minSteps * 2
|
||||
leak = math.floor(leak / 2)
|
||||
end
|
||||
if lead_holds_cleanse_tag() then
|
||||
minSteps = minSteps + math.floor(minSteps / 3)
|
||||
leak = leak - math.floor(leak / 3)
|
||||
end
|
||||
local ability = ability_mod_type()
|
||||
if ability == 1 then
|
||||
minSteps = minSteps * 2
|
||||
leak = math.floor(leak / 2)
|
||||
elseif ability == 2 then
|
||||
minSteps = math.floor(minSteps / 2)
|
||||
leak = leak * 2
|
||||
end
|
||||
return math.floor(minSteps / COOLDOWN_SCALE), math.floor(leak / COOLDOWN_SCALE)
|
||||
end
|
||||
|
||||
--- pret HandleWildEncounterCooldown. TRUE means this step may roll for an
|
||||
--- encounter. Runs on every step onto an encounter tile -- including the steps
|
||||
--- the dice roll would have denied, which is what advances the counter.
|
||||
function Encounters.handleCooldown(terrain, rate)
|
||||
local minSteps, leak = Encounters.cooldownMinSteps(terrain, rate)
|
||||
if minSteps == nil then return false end
|
||||
|
||||
if Encounters._stepsSinceLastEncounter >= minSteps then return true end
|
||||
Encounters._stepsSinceLastEncounter = Encounters._stepsSinceLastEncounter + 1
|
||||
return (Rng.Random() % 100) < leak
|
||||
end
|
||||
|
||||
--- pret ResetEncounterRateModifiers, reached from RestartWildEncounterImmunitySteps
|
||||
--- on map load (overworld.c) and on battle start (battle_setup.c). Resetting when
|
||||
--- the battle starts is what re-arms the grace period, including for wild battles
|
||||
--- nothing stepped into (scripts, fishing).
|
||||
function Encounters.resetRateModifiers()
|
||||
Encounters._stepsSinceLastEncounter = 0
|
||||
end
|
||||
|
||||
local function roll_area(mapId, areaKey, weights, enterFromOther, fallbackRate)
|
||||
local t = table_for(mapId)
|
||||
local area = normalize_area(t and t[areaKey], fallbackRate)
|
||||
@@ -226,10 +366,18 @@ local function vanilla_step(mapId, terrain, opts)
|
||||
if enterFromOther == nil then
|
||||
enterFromOther = not Encounters._prevGrass
|
||||
end
|
||||
-- pret TryStandardWildEncounter consults the cooldown before the rate test.
|
||||
local area = area_for(mapId, terrain)
|
||||
if not Encounters.handleCooldown(terrain, area and area.rate) then return nil end
|
||||
local enc
|
||||
if terrain == "water" then
|
||||
return Encounters.rollWater(mapId, enterFromOther)
|
||||
enc = Encounters.rollWater(mapId, enterFromOther)
|
||||
else
|
||||
enc = Encounters.rollLand(mapId, nil, enterFromOther)
|
||||
end
|
||||
return Encounters.rollLand(mapId, nil, enterFromOther)
|
||||
-- pret sets stepsSinceLastEncounter = 0 once an encounter actually starts.
|
||||
if enc then Encounters.resetRateModifiers() end
|
||||
return enc
|
||||
end
|
||||
|
||||
local function mod_encounter(enc)
|
||||
@@ -277,7 +425,9 @@ function Encounters.onStep(mapId, terrain, opts)
|
||||
if enc and wantsSpecies then
|
||||
enc = ModRuntime.call("encounter.species", same_encounter, enc, ctx)
|
||||
end
|
||||
return engine_encounter(enc)
|
||||
enc = engine_encounter(enc)
|
||||
if enc then Encounters.resetRateModifiers() end
|
||||
return enc
|
||||
end
|
||||
|
||||
function Encounters.noteGrass(onGrass)
|
||||
|
||||
@@ -264,6 +264,15 @@ function Map.load(mod, game, mapId, opts)
|
||||
if not MapIds.isGame3Map(mapId) then
|
||||
return nil, "not a game3 map"
|
||||
end
|
||||
-- pret RestartWildEncounterImmunitySteps on LoadMap / LoadMapFromWarp: every
|
||||
-- map entry restarts the wild encounter grace period. Unconditional, so the
|
||||
-- seamless connection crossing between two routes resets it too.
|
||||
do
|
||||
local okE, Encounters = pcall(require, "src.core.game3.encounters")
|
||||
if okE and Encounters and Encounters.resetRateModifiers then
|
||||
Encounters.resetRateModifiers()
|
||||
end
|
||||
end
|
||||
local Ghosts = require("src.core.game3.ghosts")
|
||||
local fromMapId = Map._announced
|
||||
if Map.current and Map.current ~= mapId then
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
-- FireRed wild encounter grace period (pret wild_encounter.c).
|
||||
--
|
||||
-- FireRed is the only generation with a step cooldown between wild battles:
|
||||
-- HandleWildEncounterCooldown refuses the roll for a map-dependent number of
|
||||
-- steps after the last encounter, then lets 5%/step through so the wait is
|
||||
-- soft rather than a hard floor. Without it a Route 1 tile (rate 21) rolls
|
||||
-- 21% per step and the game reads as "a wild battle nearly every step".
|
||||
--
|
||||
-- Every expected value below is the literal pret constant, so a drift in the
|
||||
-- port shows up here rather than as a frequency change in-game.
|
||||
|
||||
package.path = "./?.lua;./?/init.lua;" .. package.path
|
||||
|
||||
local T = require("tests.harness")
|
||||
local check, eq = T.check, T.eq
|
||||
|
||||
local Rng = require("src.core.game3.rng")
|
||||
local Encounters = require("src.core.game3.encounters")
|
||||
|
||||
local LAND_SLOT = { { species = 16, minLevel = 3, maxLevel = 5 } }
|
||||
Encounters._tables = {
|
||||
ROUTE_1 = { land = { rate = 21, slots = LAND_SLOT } },
|
||||
RARE = { land = { rate = 5, slots = LAND_SLOT } },
|
||||
COMMON = { land = { rate = 80, slots = LAND_SLOT } },
|
||||
POND = { water = { rate = 21, slots = LAND_SLOT } },
|
||||
}
|
||||
Encounters._loaded = true
|
||||
|
||||
local function reset()
|
||||
Encounters.resetRateModifiers()
|
||||
end
|
||||
|
||||
-- ---------------------------------------------------------------- base table
|
||||
|
||||
-- pret GetMapBaseEncounterCooldown: 0xFF when there is no encounter data,
|
||||
-- 0 at rate >= 80, 8 at rate < 10, else 8 - rate/10.
|
||||
local GOLDEN_BASE = {
|
||||
[0] = 8, [5] = 8, [9] = 8, [10] = 7, [19] = 7, [21] = 6, [25] = 6,
|
||||
[40] = 4, [79] = 1, [80] = 0, [100] = 0,
|
||||
}
|
||||
for rate, want in pairs(GOLDEN_BASE) do
|
||||
eq(Encounters.mapBaseCooldown("land", rate), want, "mapBaseCooldown(land, " .. rate .. ")")
|
||||
eq(Encounters.mapBaseCooldown("water", rate), want, "mapBaseCooldown(water, " .. rate .. ")")
|
||||
end
|
||||
eq(Encounters.mapBaseCooldown("land", nil), 0xFF, "no landMonsInfo -> 0xFF")
|
||||
eq(Encounters.mapBaseCooldown("water", nil), 0xFF, "no waterMonsInfo -> 0xFF")
|
||||
eq(Encounters.mapBaseCooldown("none", 21), 0xFF, "TILE_ENCOUNTER_NONE -> 0xFF")
|
||||
|
||||
-- ---------------------------------------------------------------- counter
|
||||
|
||||
print("[test] cooldown counter")
|
||||
reset()
|
||||
eq(Encounters._stepsSinceLastEncounter, 0, "reset zeroes the counter")
|
||||
|
||||
-- Route 1 is a 6-step cooldown: the first six steps must be denied even with
|
||||
-- the rate test favouring an encounter.
|
||||
Rng.SeedRng(0xC0DE)
|
||||
local allowedEarly = 0
|
||||
for i = 1, 6 do
|
||||
if Encounters.handleCooldown("land", 21) then allowedEarly = allowedEarly + 1 end
|
||||
eq(Encounters._stepsSinceLastEncounter, i, "denied step " .. i .. " advances the counter")
|
||||
end
|
||||
check(allowedEarly == 0, "pinned seed: no leak across the 6-step cooldown")
|
||||
|
||||
-- Once the counter has reached minSteps the step is allowed, and the check
|
||||
-- stops touching the RNG or the counter (pret returns TRUE immediately).
|
||||
local before = Rng.getState().value
|
||||
check(Encounters.handleCooldown("land", 21), "counter at minSteps allows the roll")
|
||||
eq(Rng.getState().value, before, "allowed step consumes no RNG")
|
||||
eq(Encounters._stepsSinceLastEncounter, 6, "allowed step leaves the counter alone")
|
||||
|
||||
-- A map with no encounter data aborts before the counter moves at all.
|
||||
reset()
|
||||
check(not Encounters.handleCooldown("land", nil), "no encounter data denies the step")
|
||||
eq(Encounters._stepsSinceLastEncounter, 0, "no encounter data leaves the counter alone")
|
||||
|
||||
-- ---------------------------------------------------------------- leak
|
||||
|
||||
print("[test] 5%/step leak")
|
||||
Rng.SeedRng(0x1234)
|
||||
local leaked = 0
|
||||
local STEPS = 20000
|
||||
for _ = 1, STEPS do
|
||||
reset()
|
||||
if Encounters.handleCooldown("land", 21) then leaked = leaked + 1 end
|
||||
end
|
||||
local pct = leaked / STEPS * 100
|
||||
check(pct > 4.5 and pct < 5.5, ("leak is 5%%/step (%.2f%%)"):format(pct))
|
||||
|
||||
-- ---------------------------------------------------------------- modifiers
|
||||
|
||||
print("[test] rate modifiers (pret HandleWildEncounterCooldown)")
|
||||
local Space = require("src.core.game3.scripting.space")
|
||||
local Flags = require("src.core.game3.scripting.flags")
|
||||
local Runtime = require("src.core.game3.runtime")
|
||||
|
||||
local prevStore, prevSession = Space.store, Runtime.session
|
||||
Space.store = Flags.newStore()
|
||||
|
||||
local function mods()
|
||||
return Encounters.cooldownMinSteps("land", 21)
|
||||
end
|
||||
local function setMods(flute, party)
|
||||
Flags.setFlag(Space.store, nil, 0x803, flute == "white")
|
||||
Flags.setFlag(Space.store, nil, 0x804, flute == "black")
|
||||
Runtime.session = party and { party = party } or nil
|
||||
end
|
||||
|
||||
setMods(nil, nil)
|
||||
local baseSteps, baseLeak = mods()
|
||||
eq(baseSteps, 6, "base minSteps")
|
||||
eq(baseLeak, 5, "base leak")
|
||||
|
||||
setMods("white", nil)
|
||||
local ws, wl = mods()
|
||||
eq(ws, 3, "White Flute halves minSteps")
|
||||
eq(wl, 7, "White Flute raises the leak")
|
||||
|
||||
setMods("black", nil)
|
||||
local bs, bl = mods()
|
||||
eq(bs, 12, "Black Flute doubles minSteps")
|
||||
eq(bl, 2, "Black Flute halves the leak")
|
||||
|
||||
setMods(nil, { { species = 1, level = 5, item = 190 } })
|
||||
local cs, cl = mods()
|
||||
eq(cs, 8, "Cleanse Tag raises minSteps by a third")
|
||||
eq(cl, 3, "Cleanse Tag lowers the leak")
|
||||
|
||||
setMods(nil, { { species = 1, level = 5, abilityId = 1 } })
|
||||
local ss, sl = mods()
|
||||
eq(ss, 12, "Stench doubles minSteps")
|
||||
eq(sl, 2, "Stench halves the leak")
|
||||
|
||||
setMods(nil, { { species = 1, level = 5, abilityId = 35 } })
|
||||
local is, il = mods()
|
||||
eq(is, 3, "Illuminate halves minSteps")
|
||||
eq(il, 10, "Illuminate doubles the leak")
|
||||
|
||||
-- pret GetLeadMonIndex skips eggs, so an egg lead applies nothing.
|
||||
setMods(nil, { { species = 1, level = 5, isEgg = true, abilityId = 35 } })
|
||||
eq(mods(), 6, "egg lead applies no ability modifier")
|
||||
|
||||
Space.store, Runtime.session = prevStore, prevSession
|
||||
|
||||
-- ---------------------------------------------------------------- end to end
|
||||
|
||||
print("[test] step rolls (Route 1, rate 21)")
|
||||
-- Steady state in tall grass: enterFromOther is only set on the first step in,
|
||||
-- so every later step is a bare rate test. That is the 21%/step baseline.
|
||||
local function roll(steps, useCooldown)
|
||||
Rng.SeedRng(0xC0DE)
|
||||
Rng.SeedWildEncounterRng(0xBEEF)
|
||||
reset()
|
||||
local n, firstAt = 0, nil
|
||||
for i = 1, steps do
|
||||
local enc
|
||||
if useCooldown then
|
||||
enc = Encounters.onStep("ROUTE_1", "land", { enterFromOther = false })
|
||||
else
|
||||
enc = Encounters.rollLand("ROUTE_1", nil, false)
|
||||
end
|
||||
if enc then
|
||||
n = n + 1
|
||||
firstAt = firstAt or i
|
||||
reset()
|
||||
end
|
||||
end
|
||||
return n, firstAt
|
||||
end
|
||||
|
||||
local withCooldown = roll(4000, true)
|
||||
local withoutCooldown = roll(4000, false)
|
||||
eq(withCooldown, 397, "4000 steps with the cooldown -> 397 encounters")
|
||||
eq(withoutCooldown, 843, "4000 steps without it -> 843 encounters")
|
||||
check(withCooldown < withoutCooldown / 2,
|
||||
("cooldown more than halves the encounter count (%d vs %d)"):format(withCooldown, withoutCooldown))
|
||||
|
||||
-- The first encounter after a reset cannot land before the cooldown expires.
|
||||
Rng.SeedRng(0xC0DE)
|
||||
Rng.SeedWildEncounterRng(0xBEEF)
|
||||
reset()
|
||||
local pattern = {}
|
||||
for i = 1, 10 do
|
||||
pattern[i] = Encounters.onStep("ROUTE_1", "land", { enterFromOther = false }) and "E" or "-"
|
||||
end
|
||||
eq(table.concat(pattern), "------E---", "first encounter lands on step 7 of a 6-step cooldown")
|
||||
|
||||
-- A rate-80 area gets no grace period, exactly as pret.
|
||||
reset()
|
||||
check(Encounters.cooldownMinSteps("land", 80) == 0, "rate 80 has no cooldown")
|
||||
Rng.SeedRng(1)
|
||||
check(Encounters.handleCooldown("land", 80), "rate 80 allows the first step")
|
||||
|
||||
-- Water uses its own area's rate.
|
||||
reset()
|
||||
eq(Encounters.cooldownMinSteps("water", 21), 6, "water area cooldown")
|
||||
|
||||
-- ---------------------------------------------------------------- re-arm
|
||||
|
||||
print("[test] the cooldown re-arms when a battle starts")
|
||||
-- Regression for the Gen 2 bug #1229 class: a wild battle that no step rolled
|
||||
-- (script, fishing) still has to restart the immunity steps, or the next step
|
||||
-- in grass is a fresh encounter.
|
||||
local BattleBridge = require("src.core.game3.battle_bridge")
|
||||
Encounters._stepsSinceLastEncounter = 5
|
||||
pcall(BattleBridge.startWild, nil, nil, { species = 16, level = 3 }, {})
|
||||
eq(Encounters._stepsSinceLastEncounter, 0, "startWild restarts the immunity steps")
|
||||
|
||||
-- pret also restarts them on every map load (overworld.c LoadMap /
|
||||
-- LoadMapFromWarp), which is what makes walking out of a route and back in
|
||||
-- give a fresh grace period -- including the seamless connection crossing
|
||||
-- between two routes.
|
||||
print("[test] the cooldown re-arms on map load")
|
||||
local Map = require("src.core.game3.map")
|
||||
Encounters._stepsSinceLastEncounter = 5
|
||||
pcall(Map.load, nil, nil, "FR_ROUTE1", {})
|
||||
eq(Encounters._stepsSinceLastEncounter, 0, "map load restarts the immunity steps")
|
||||
|
||||
Encounters._stepsSinceLastEncounter = 5
|
||||
pcall(Map.load, nil, nil, "FR_ROUTE1", { seamless = true })
|
||||
eq(Encounters._stepsSinceLastEncounter, 0, "seamless connection load restarts them too")
|
||||
|
||||
Encounters._stepsSinceLastEncounter = 5
|
||||
pcall(Map.load, nil, nil, "PALLET_TOWN", {})
|
||||
eq(Encounters._stepsSinceLastEncounter, 5, "a non-game3 map id is not a load")
|
||||
|
||||
T.finish("wild_encounter_cooldown")
|
||||
Reference in New Issue
Block a user