mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-15 07:41:21 +02:00
174 lines
7.4 KiB
Lua
174 lines
7.4 KiB
Lua
-- Contextual field actions share one public contract in both generations
|
|
-- while each engine keeps ownership of its own field-item and move paths.
|
|
|
|
package.path = "./?.lua;./?/init.lua;" .. package.path
|
|
|
|
local T = require("tests.harness").suite("mod world field actions")
|
|
|
|
local facingWater = false
|
|
local redCut, redSurf = false, "no_water"
|
|
local redMoves = {}
|
|
local redWorld = {
|
|
isOverworld = true,
|
|
map = { id = "ROUTE_1", def = { tileset = "OVERWORLD" } },
|
|
player = { moving = false, inputLocked = false, surfing = false },
|
|
runner = { isRunning = function() return false end },
|
|
scriptMoves = {},
|
|
bikeAllowed = function() return true end,
|
|
facingIsShoreOrWater = function() return facingWater end,
|
|
useCutFieldMove = function() return redCut and "ok" or "nothing" end,
|
|
useSurfFieldMove = function() return redSurf end,
|
|
partyKnows = function(_, move) return redMoves[move] end,
|
|
useBicycle = function(self) self.bikeUsed = true return true end,
|
|
useFishingRod = function(self, rod) self.rodUsed = rod return true end,
|
|
}
|
|
local redGame = {
|
|
data = { field = { outsideTilesets = { "OVERWORLD" } },
|
|
items = { OLD_ROD = { name = "OLD ROD" } } },
|
|
save = { player = { name = "RED" }, party = {},
|
|
inventory = { BICYCLE = 1, OLD_ROD = 1 } },
|
|
stack = { states = { redWorld } },
|
|
overworld = redWorld,
|
|
}
|
|
function redGame.stack:top() return self.states[#self.states] end
|
|
|
|
local RedAPI = require("src.world.WorldAPI")
|
|
local red = RedAPI.new(redGame, "fixture")
|
|
local RedWorld = require("src.world.OverworldController")
|
|
T.check(type(RedWorld.useBicycle) == "function"
|
|
and type(RedWorld.useFishingRod) == "function"
|
|
and type(RedWorld.useFlashFieldMove) == "function"
|
|
and type(RedWorld.useStrengthFieldMove) == "function"
|
|
and type(RedWorld.stopSurfing) == "function",
|
|
"Red keeps field-action execution in its world")
|
|
local actions = red:availableFieldActions()
|
|
T.eq(actions[1].id, "bicycle", "Red lists an owned usable bicycle")
|
|
T.check(red:useFieldAction("bicycle"), "Red accepts the listed bicycle")
|
|
T.check(redWorld.bikeUsed, "Red delegates to its world-owned bicycle path")
|
|
|
|
facingWater = true
|
|
actions = red:availableFieldActions()
|
|
T.eq(actions[2].rods[1].id, "OLD_ROD", "Red lists owned rods at water")
|
|
T.check(red:useFieldAction("fish", { rod = "OLD_ROD" }),
|
|
"Red accepts a listed rod")
|
|
T.eq(redWorld.rodUsed, "OLD_ROD", "Red delegates to its fishing path")
|
|
local used = redWorld.rodUsed
|
|
local ok, err = red:useFieldAction("fish", { rod = "SUPER_ROD" })
|
|
T.check(not ok and err == "fishing rod unavailable",
|
|
"Red rejects an unowned rod")
|
|
T.eq(redWorld.rodUsed, used, "a rejected Red rod changes nothing")
|
|
|
|
redWorld.player.moving = true
|
|
T.eq(#red:availableFieldActions(), 0, "Red hides actions while moving")
|
|
ok, err = red:useFieldAction("bicycle")
|
|
T.check(not ok and err == "world is busy",
|
|
"Red refuses a stale action while busy")
|
|
|
|
redWorld.player.moving = false
|
|
facingWater, redCut, redSurf = false, true, "ok"
|
|
redWorld.dark = true
|
|
for _, move in ipairs({ "STRENGTH", "FLASH", "TELEPORT" }) do
|
|
redMoves[move] = { species = "MEW", moves = { { id = move } } }
|
|
end
|
|
redWorld.player.facingCell = function() return 4, 5 end
|
|
redWorld.tryCut = function(self) self.cutUsed = true return true end
|
|
redWorld.trySurf = function(self) self.surfUsed = true end
|
|
redWorld.useStrengthFieldMove = function(self) self.strengthUsed = true return true end
|
|
redWorld.useFlashFieldMove = function(self) self.flashUsed = true return true end
|
|
redWorld.beginTeleportOut = function(self) self.teleportUsed = true end
|
|
actions = red:availableFieldActions()
|
|
local byId = {}
|
|
for _, action in ipairs(actions) do byId[action.id] = action end
|
|
T.check(byId.cut and byId.surf and byId.strength and byId.flash
|
|
and byId.teleport, "Red lists field moves that can start now")
|
|
for _, id in ipairs({ "cut", "surf", "strength", "flash", "teleport" }) do
|
|
T.check(red:useFieldAction(id), "Red accepts listed " .. id)
|
|
end
|
|
T.check(redWorld.cutUsed and redWorld.surfUsed and redWorld.strengthUsed
|
|
and redWorld.flashUsed and redWorld.teleportUsed,
|
|
"Red delegates every move to its overworld path")
|
|
|
|
redSurf = "dismount"
|
|
redWorld.player.surfing = true
|
|
redWorld.stopSurfing = function(self) self.dismounted = true end
|
|
T.check(red:useFieldAction("surf") and redWorld.dismounted,
|
|
"Red delegates the contextual SURF dismount")
|
|
redWorld.player.surfing, redSurf = false, "ok"
|
|
|
|
redCut = false
|
|
ok, err = red:useFieldAction("cut")
|
|
T.check(not ok and err == "field action unavailable",
|
|
"Red revalidates a stale field move")
|
|
|
|
redWorld.map.id = "ROCK_TUNNEL_1F"
|
|
redWorld.map.def.tileset = "CAVERN"
|
|
redMoves.DIG = { species = "MEW", moves = { { id = "DIG" } } }
|
|
redWorld.beginTeleportOut = function(self) self.digUsed = true end
|
|
byId = {}
|
|
for _, action in ipairs(red:availableFieldActions()) do byId[action.id] = action end
|
|
T.check(byId.dig and not byId.teleport,
|
|
"Red distinguishes dungeon DIG from outdoor TELEPORT")
|
|
T.check(red:useFieldAction("dig") and redWorld.digUsed,
|
|
"Red delegates DIG to its escape path")
|
|
|
|
local goldWorld = {
|
|
map = { id = "ROUTE_29", def = { environment = "ROUTE" } },
|
|
player = {}, playerState = "normal",
|
|
acceptsMenuInput = function() return true end,
|
|
playerCollision = function() return 0x00 end,
|
|
alwaysOnBike = function() return false end,
|
|
useFieldItem = function(self, item) self.itemUsed = item return "used" end,
|
|
squirtbottleTreeScript = function() return { { op = "end" } } end,
|
|
useFieldMove = function(self, move)
|
|
self.moveUsed = move
|
|
return { ok = true }
|
|
end,
|
|
}
|
|
local goldGame = {
|
|
data = { items = { OLD_ROD = { name = "OLD ROD" },
|
|
SQUIRTBOTTLE = { name = "SQUIRTBOTTLE" } } },
|
|
save = { inventory = { BICYCLE = 1, OLD_ROD = 1, SQUIRTBOTTLE = 1 },
|
|
player = { badges = { FOG = true } },
|
|
party = { { moves = { { id = "SURF" }, { id = "SWEET_SCENT" },
|
|
{ id = "TELEPORT" } } } } },
|
|
world = goldWorld,
|
|
}
|
|
goldWorld.fieldContext = function(_, mon) return {
|
|
save = goldGame.save, party = goldGame.save.party, mon = mon,
|
|
facing = "right", facingColl = 0x29, playerColl = 0,
|
|
environment = "ROUTE", playerState = "normal", alwaysOnBike = false,
|
|
dark = false, canEscapeRope = false,
|
|
} end
|
|
|
|
local GoldAPI = require("src.world.gen2.WorldAPI")
|
|
local gold = GoldAPI.new(goldGame, "fixture")
|
|
actions = gold:availableFieldActions()
|
|
byId = {}
|
|
for _, action in ipairs(actions) do byId[action.id] = action end
|
|
T.eq(actions[1].id, "bicycle", "Gold shares the bicycle action id")
|
|
T.eq(actions[2].id, "fish", "Gold preserves the original action order")
|
|
T.check(byId.surf and byId.sweet_scent and byId.teleport,
|
|
"Gold lists field moves through its generic dispatcher")
|
|
T.eq(byId.fish.rods[1].id, "OLD_ROD", "Gold shares the rod shape")
|
|
T.check(byId.squirtbottle,
|
|
"Gold lists the SquirtBottle only at its matching tree")
|
|
T.check(gold:useFieldAction("sweet_scent"),
|
|
"Gold accepts a listed field move")
|
|
T.eq(goldWorld.moveUsed, "SWEET_SCENT",
|
|
"Gold delegates moves to its own field-move path")
|
|
T.check(gold:useFieldAction("fish", { rod = "OLD_ROD" }),
|
|
"Gold accepts the same fishing request")
|
|
T.eq(goldWorld.itemUsed, "OLD_ROD",
|
|
"Gold delegates to its own field-item path")
|
|
used = goldWorld.itemUsed
|
|
ok, err = gold:useFieldAction("fish", { rod = "SUPER_ROD" })
|
|
T.check(not ok and err == "fishing rod unavailable",
|
|
"Gold rejects an unowned rod")
|
|
T.eq(goldWorld.itemUsed, used, "a rejected Gold rod changes nothing")
|
|
T.check(gold:useFieldAction("squirtbottle"),
|
|
"Gold accepts the contextual SquirtBottle")
|
|
T.eq(goldWorld.itemUsed, "SQUIRTBOTTLE",
|
|
"Gold delegates the SquirtBottle to its field-item path")
|
|
|
|
T.finish()
|