mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-12 08:21:02 +02:00
227 lines
9.3 KiB
Lua
227 lines
9.3 KiB
Lua
-- The fishing half of a swarm (engine/events/fish.asm).
|
|
--
|
|
-- luajit tests/gen2_fishing_swarm_test.lua (ROM-free; the cache facts SKIP
|
|
-- without a gold cache)
|
|
--
|
|
-- Fish calls GetFishGroupIndex before it indexes FishGroups, and that routine
|
|
-- is the ONLY reader of wFishingSwarmFlag: a map whose MAP_FISHGROUP is
|
|
-- FISHGROUP_QWILFISH rolls FISHGROUP_QWILFISH_SWARM instead while the flag says
|
|
-- FISHSWARM_QWILFISH, and the same for Remoraid. Nothing about the map header
|
|
-- changes, which is why the phone call that starts a swarm is one setval and
|
|
-- one special.
|
|
--
|
|
-- The port wired swarms into the grass and water tables (_SwarmWildmonCheck,
|
|
-- src/core/gen2/Roamers.lua Swarm.tables) and left fishing reading the header
|
|
-- alone, so `special ActivateFishingSwarm` had no reader at all: the Route 32
|
|
-- Qwilfish swarm and the Route 44 Remoraid swarm never reached a rod.
|
|
--
|
|
-- Everything below goes through World:rollFishing, which is what World:useRod
|
|
-- and World:tryFishing both call, rather than through Encounter directly.
|
|
package.path = "./?.lua;./?/init.lua;" .. package.path
|
|
|
|
local S = require("tests.harness").suite("gen2 fishing swarm")
|
|
local check, eq = S.check, S.eq
|
|
|
|
love = require("tests.love_stub")
|
|
|
|
local World = require("src.world.gen2.World")
|
|
local Encounter = require("src.battle.gen2.Encounter")
|
|
local Roamers = require("src.core.gen2.Roamers")
|
|
local Permissions = require("src.world.gen2.Permissions")
|
|
|
|
local COLL_FLOOR, COLL_WATER = 0x00, 0x29
|
|
local MAP_W, MAP_H = 10, 10
|
|
|
|
-- constants/script_constants.asm.
|
|
eq(Encounter.FISHSWARM_NONE, 0, "FISHSWARM_NONE is 0")
|
|
eq(Encounter.FISHSWARM_QWILFISH, 1, "FISHSWARM_QWILFISH is 1")
|
|
eq(Encounter.FISHSWARM_REMORAID, 2, "FISHSWARM_REMORAID is 2")
|
|
eq(Roamers.Swarm.FISH_QWILFISH, Encounter.FISHSWARM_QWILFISH,
|
|
"and the save-side names agree with them")
|
|
eq(Roamers.Swarm.FISH_REMORAID, Encounter.FISHSWARM_REMORAID, "for both")
|
|
|
|
-- One always-hit row per list, so the ROLL is never what these assert: whatever
|
|
-- comes back names the group it was rolled from.
|
|
local function only(species)
|
|
local list = { { chance = 256, species = species, level = 10 } }
|
|
return { old = list, good = list, super = list }
|
|
end
|
|
|
|
local ENCOUNTERS = {
|
|
fishGroups = {
|
|
FISHGROUP_POND = only("MAGIKARP"),
|
|
FISHGROUP_QWILFISH = only("MAGIKARP"),
|
|
FISHGROUP_QWILFISH_SWARM = only("QWILFISH"),
|
|
FISHGROUP_QWILFISH_NO_SWARM = only("MAGIKARP"),
|
|
FISHGROUP_REMORAID = only("GOLDEEN"),
|
|
FISHGROUP_REMORAID_SWARM = only("REMORAID"),
|
|
},
|
|
}
|
|
|
|
local function mon(name)
|
|
return {
|
|
name = name, types = { "WATER", "WATER" },
|
|
baseStats = { hp = 20, attack = 10, defense = 55, speed = 80,
|
|
specialAttack = 15, specialDefense = 20 },
|
|
levelMoves = { { level = 1, move = "SPLASH" } },
|
|
}
|
|
end
|
|
|
|
local DATA = {
|
|
items = {},
|
|
moves = { SPLASH = { name = "SPLASH", pp = 40 } },
|
|
pokemon = {
|
|
MAGIKARP = mon("MAGIKARP"), QWILFISH = mon("QWILFISH"),
|
|
GOLDEEN = mon("GOLDEEN"), REMORAID = mon("REMORAID"),
|
|
},
|
|
}
|
|
|
|
-- A world with one water tile at (5,4) and the player facing it from (5,5),
|
|
-- which is the only geometry .TryFish reads.
|
|
local function fishWorld(mapId, fishGroup)
|
|
local cells = { [4 * 100 + 5] = COLL_WATER }
|
|
local map
|
|
map = {
|
|
id = mapId,
|
|
width = MAP_W, height = MAP_H,
|
|
def = { bgEvents = {}, objects = {}, width = MAP_W, height = MAP_H,
|
|
environment = "ROUTE", fishGroup = fishGroup },
|
|
cellCollision = function(_, x, y) return cells[y * 100 + x] or COLL_FLOOR end,
|
|
inBounds = function(_, x, y)
|
|
return x >= 0 and y >= 0 and x < MAP_W * 2 and y < MAP_H * 2
|
|
end,
|
|
isWalkable = function(_, x, y)
|
|
return Permissions.isWalkable(map:cellCollision(x, y))
|
|
end,
|
|
warpAt = function() return nil end,
|
|
}
|
|
local game = {
|
|
data = DATA,
|
|
save = { player = { name = "GOLD" }, party = {}, inventory = {} },
|
|
}
|
|
local world = World.new(game)
|
|
game.world = world
|
|
world.map = map
|
|
world.maps = { [mapId] = map.def }
|
|
world.encounters = ENCOUNTERS
|
|
world.player = {
|
|
cellX = 5, cellY = 5, px = 80, py = 80, facing = "up", moving = false,
|
|
turnArmed = true, update = function() return false end,
|
|
setSprite = function() end,
|
|
}
|
|
world.pollTimeOfDay = function() end
|
|
world.vm = { running = function() return false end, update = function() end }
|
|
return world, game
|
|
end
|
|
|
|
local function hooked(world, rod)
|
|
local outcome, wild = world:rollFishing(rod or "OLD_ROD")
|
|
return outcome, wild and wild.species
|
|
end
|
|
|
|
-- ---- ROUTE_32, the Qwilfish swarm -----------------------------------------
|
|
do
|
|
local world, game = fishWorld("ROUTE_32", "FISHGROUP_QWILFISH")
|
|
local outcome, species = hooked(world)
|
|
eq(outcome, "battle", "the rod hooks something off the map's own group")
|
|
eq(species, "MAGIKARP", "which is FISHGROUP_QWILFISH with no swarm running")
|
|
|
|
-- `special ActivateFishingSwarm` with wScriptVar = FISHSWARM_QWILFISH: the
|
|
-- save write the port already had, and the ONLY thing that changes here.
|
|
Roamers.Swarm.setFishing(game.save, Roamers.Swarm.FISH_QWILFISH)
|
|
outcome, species = hooked(world)
|
|
eq(outcome, "battle", "the rod still hooks something during the swarm")
|
|
eq(species, "QWILFISH", "and it is FISHGROUP_QWILFISH_SWARM's list")
|
|
for _, rod in ipairs({ "OLD_ROD", "GOOD_ROD", "SUPER_ROD" }) do
|
|
local _, got = hooked(world, rod)
|
|
eq(got, "QWILFISH", "the swap is per GROUP, so it covers the " .. rod)
|
|
end
|
|
|
|
-- The Remoraid swarm is a different flag value and must not touch this map.
|
|
Roamers.Swarm.setFishing(game.save, Roamers.Swarm.FISH_REMORAID)
|
|
local _, other = hooked(world)
|
|
eq(other, "MAGIKARP", "a Remoraid swarm leaves Route 32 alone")
|
|
|
|
-- CheckSwarmFlag is what ends it: the daily reset drops DAILYFLAGS1_SWARM and
|
|
-- the next poll clears wFishingSwarmFlag with it.
|
|
Roamers.Swarm.setFishing(game.save, Roamers.Swarm.FISH_QWILFISH)
|
|
game.save.dailyFlags.swarm = false
|
|
eq(Roamers.Swarm.check(game.save), 1, "CheckSwarmFlag answers 1 once it is down")
|
|
local _, ended = hooked(world)
|
|
eq(ended, "MAGIKARP", "and the rod is back on the map's own group")
|
|
end
|
|
|
|
-- ---- ROUTE_44, the Remoraid swarm -----------------------------------------
|
|
do
|
|
local world, game = fishWorld("ROUTE_44", "FISHGROUP_REMORAID")
|
|
local _, species = hooked(world)
|
|
eq(species, "GOLDEEN", "Route 44 fishes FISHGROUP_REMORAID by default")
|
|
Roamers.Swarm.setFishing(game.save, Roamers.Swarm.FISH_REMORAID)
|
|
local _, swarmed = hooked(world)
|
|
eq(swarmed, "REMORAID", "and FISHGROUP_REMORAID_SWARM while the swarm runs")
|
|
Roamers.Swarm.setFishing(game.save, Roamers.Swarm.FISH_QWILFISH)
|
|
local _, wrong = hooked(world)
|
|
eq(wrong, "GOLDEEN", "a Qwilfish swarm leaves Route 44 alone")
|
|
end
|
|
|
|
-- ---- the groups the substitution must NOT touch ---------------------------
|
|
do
|
|
-- FISHGROUP_QWILFISH_NO_SWARM is a map header value of its own, not a state
|
|
-- of FISHGROUP_QWILFISH: GetFishGroupIndex compares against FISHGROUP_QWILFISH
|
|
-- and FISHGROUP_REMORAID and nothing else.
|
|
local world, game = fishWorld("ROUTE_33", "FISHGROUP_QWILFISH_NO_SWARM")
|
|
Roamers.Swarm.setFishing(game.save, Roamers.Swarm.FISH_QWILFISH)
|
|
local _, species = hooked(world)
|
|
eq(species, "MAGIKARP",
|
|
"FISHGROUP_QWILFISH_NO_SWARM never becomes a swarm group")
|
|
|
|
local pond, pondGame = fishWorld("ROUTE_34", "FISHGROUP_POND")
|
|
Roamers.Swarm.setFishing(pondGame.save, Roamers.Swarm.FISH_QWILFISH)
|
|
local _, caught = hooked(pond)
|
|
eq(caught, "MAGIKARP", "and an ordinary pond is untouched by either swarm")
|
|
end
|
|
|
|
-- ---- a cache with no swarm rows -------------------------------------------
|
|
do
|
|
local world, game = fishWorld("ROUTE_32", "FISHGROUP_QWILFISH")
|
|
world.encounters = { fishGroups = { FISHGROUP_QWILFISH = only("MAGIKARP") } }
|
|
Roamers.Swarm.setFishing(game.save, Roamers.Swarm.FISH_QWILFISH)
|
|
local outcome, species = hooked(world)
|
|
eq(outcome, "battle", "an older cache still bites")
|
|
eq(species, "MAGIKARP", "off the map's own group rather than off nothing")
|
|
end
|
|
|
|
-- ---- the group names in the real cache ------------------------------------
|
|
do
|
|
local cache = os.getenv("GOLD_CACHE")
|
|
if not cache then
|
|
local home = os.getenv("HOME") or ""
|
|
cache = home .. "/Library/Application Support/LOVE/gold-dev/gold"
|
|
end
|
|
local encChunk = loadfile(cache .. "/data/generated/encounters.lua")
|
|
local mapChunk = loadfile(cache .. "/data/generated/maps.lua")
|
|
if not (encChunk and mapChunk) then
|
|
check(true, "no gold cache: fish group names (SKIP)")
|
|
else
|
|
local groups = (encChunk() or {}).fishGroups or {}
|
|
local maps = mapChunk()
|
|
for _, name in ipairs({ "FISHGROUP_QWILFISH", "FISHGROUP_QWILFISH_SWARM",
|
|
"FISHGROUP_REMORAID", "FISHGROUP_REMORAID_SWARM" }) do
|
|
check(groups[name] ~= nil, "the cache carries " .. name)
|
|
end
|
|
eq((maps.ROUTE_32 or {}).fishGroup, "FISHGROUP_QWILFISH",
|
|
"ROUTE_32's header names the Qwilfish group")
|
|
eq((maps.ROUTE_44 or {}).fishGroup, "FISHGROUP_REMORAID",
|
|
"and ROUTE_44's the Remoraid one")
|
|
-- The substitution is resolved against the cache's own group table.
|
|
eq(Encounter.fishGroupFor({ fishGroups = groups }, "FISHGROUP_QWILFISH",
|
|
Encounter.FISHSWARM_QWILFISH), "FISHGROUP_QWILFISH_SWARM",
|
|
"so the real Route 32 header swaps")
|
|
eq(Encounter.fishGroupFor({ fishGroups = groups }, "FISHGROUP_REMORAID",
|
|
Encounter.FISHSWARM_REMORAID), "FISHGROUP_REMORAID_SWARM",
|
|
"and so does the real Route 44 one")
|
|
end
|
|
end
|
|
|
|
S.finish()
|