mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-17 19:24:01 +02:00
feat(mods): extend contextual field actions
This commit is contained in:
+3
-50
@@ -454,30 +454,7 @@ function PartyMenu:update(dt)
|
||||
refuseBadge(self)
|
||||
return
|
||||
end
|
||||
local TextBox = require("src.render.TextBox")
|
||||
local Transition = require("src.render.Transition")
|
||||
-- .flash prints on any map, but the light it records is this map's:
|
||||
-- home/overworld.asm re-arms wMapPalOffset on the next dark map, so
|
||||
-- a FLASH used in daylight must not carry into Rock Tunnel
|
||||
local wasDark = ow and ow.dark
|
||||
if wasDark then self.game.save.flashLit = true end
|
||||
self.game.stack:push(TextBox.new(self.game,
|
||||
self.game.data.text._FlashLightsAreaText
|
||||
or Strings("A blinding FLASH\nlights the area!"), function()
|
||||
self:close()
|
||||
-- setDark, not a bare field write: ADVANCED carries the darkness
|
||||
-- in a baked atlas, so lighting the cave drops every resident map
|
||||
-- and rebakes this one (#383). It runs HERE, before the blink,
|
||||
-- because start_sub_menus.asm .flash clears wMapPalOffset before
|
||||
-- PrintText and blinks last of all: the cave is already lit by the
|
||||
-- time GBPalWhiteOutWithDelay3 runs. Hanging the rebuild off the
|
||||
-- blink's completion instead left that rebuild's whole cost --
|
||||
-- seconds of per-pixel atlas baking on a phone -- on screen as a
|
||||
-- solid white frame with nothing under it, which reads as a
|
||||
-- lockup (#610).
|
||||
if wasDark then ow:setDark(false) end
|
||||
self.game.stack:push(Transition.whiteFlash(self.game))
|
||||
end))
|
||||
ow:useFlashFieldMove(function() self:close() end)
|
||||
return
|
||||
elseif action == "surf" then
|
||||
-- start_sub_menus.asm .surf: SOULBADGE-gated (useSurfFieldMove),
|
||||
@@ -505,12 +482,7 @@ function PartyMenu:update(dt)
|
||||
-- GBPalWhiteOutWithDelay3 blink, and the simulated pad press
|
||||
-- steps the player forward onto land (or across a connection
|
||||
-- strip when the shore is the next map's edge)
|
||||
self.game.stack:pop()
|
||||
ow.player.surfing = false
|
||||
require("src.core.Music").setSurfing(self.game.data, false)
|
||||
self.game.stack:push(Transition.whiteFlash(self.game, nil, function()
|
||||
ow:stepForwardOrCrossEdge(ow.player.facing)
|
||||
end))
|
||||
ow:stopSurfing(function() self.game.stack:pop() end)
|
||||
return
|
||||
end
|
||||
local TextBox = require("src.render.TextBox")
|
||||
@@ -571,26 +543,7 @@ function PartyMenu:update(dt)
|
||||
refuseBadge(self)
|
||||
return
|
||||
end
|
||||
local TextBox = require("src.render.TextBox")
|
||||
local Transition = require("src.render.Transition")
|
||||
local def = self.game.data.pokemon[mon.species]
|
||||
local name = mon.nickname or def.name
|
||||
ow.strengthActive = true
|
||||
local t1 = (self.game.data.text._UsedStrengthText
|
||||
or Strings("{RAM:wNameBuffer} used\nSTRENGTH.")):gsub("{RAM:wNameBuffer}", name)
|
||||
local t2 = (self.game.data.text._CanMoveBouldersText
|
||||
or Strings("{RAM:wNameBuffer} can\nmove boulders.")):gsub("{RAM:wNameBuffer}", name)
|
||||
-- like surf (#320, #385): both texts print with the party menu
|
||||
-- still on screen, and the blink IS the menu closing afterwards,
|
||||
-- not a flashbang on the empty map
|
||||
self.game.stack:push(TextBox.new(self.game, t1, function()
|
||||
self.game.stack:push(TextBox.new(self.game, t2, function()
|
||||
self:close()
|
||||
self.game.stack:push(Transition.whiteFlash(self.game))
|
||||
end))
|
||||
end, { auto = { sound = function()
|
||||
return require("src.core.Sound").playCry(self.game.data, mon.species)
|
||||
end } }))
|
||||
ow:useStrengthFieldMove(mon, function() self:close() end)
|
||||
return
|
||||
elseif action == "softboiled" then
|
||||
-- field SOFTBOILED (StartMenu_Pokemon .softboiled): transfer
|
||||
|
||||
@@ -784,6 +784,49 @@ function OverworldState:useBicycle()
|
||||
return true
|
||||
end
|
||||
|
||||
-- Field-move entry points keep presentation and state transitions in the
|
||||
-- overworld. The party menu and supported mod facade both call these, so a
|
||||
-- shortcut cannot drift from the game's own move flow.
|
||||
function OverworldState:useFlashFieldMove(onClose)
|
||||
-- A FLASH used in daylight must not carry into the next dark map. Lighting
|
||||
-- also happens before the blink: setDark may rebake an ADVANCED atlas, and
|
||||
-- putting that work in WhiteFlash:onDone caused the frozen white frame in
|
||||
-- #610.
|
||||
local wasDark = self.dark
|
||||
if wasDark then Game.save.flashLit = true end
|
||||
Game.stack:push(TextBox.new(Game,
|
||||
Game.data.text._FlashLightsAreaText
|
||||
or Strings("A blinding FLASH\nlights the area!"), function()
|
||||
if onClose then onClose() end
|
||||
if wasDark then self:setDark(false) end
|
||||
Game.stack:push(Transition.whiteFlash(Game))
|
||||
end))
|
||||
return true
|
||||
end
|
||||
|
||||
function OverworldState:useStrengthFieldMove(mon, onClose)
|
||||
mon = mon or self:partyKnows("STRENGTH")
|
||||
if not mon then return false end
|
||||
local def = Game.data.pokemon[mon.species]
|
||||
local name = mon.nickname or def.name
|
||||
self.strengthActive = true
|
||||
local first = (Game.data.text._UsedStrengthText
|
||||
or Strings("{RAM:wNameBuffer} used\nSTRENGTH."))
|
||||
:gsub("{RAM:wNameBuffer}", name)
|
||||
local second = (Game.data.text._CanMoveBouldersText
|
||||
or Strings("{RAM:wNameBuffer} can\nmove boulders."))
|
||||
:gsub("{RAM:wNameBuffer}", name)
|
||||
Game.stack:push(TextBox.new(Game, first, function()
|
||||
Game.stack:push(TextBox.new(Game, second, function()
|
||||
if onClose then onClose() end
|
||||
Game.stack:push(Transition.whiteFlash(Game))
|
||||
end))
|
||||
end, { auto = { sound = function()
|
||||
return require("src.core.Sound").playCry(Game.data, mon.species)
|
||||
end } }))
|
||||
return true
|
||||
end
|
||||
|
||||
-- The battle transition's dungeon wipe uses the explicit map lists in
|
||||
-- data/maps/dungeon_maps.asm (field.dungeonTransitionMaps): singles plus
|
||||
-- inclusive map-id ranges -- faithful to the original's omissions
|
||||
@@ -2500,6 +2543,15 @@ function OverworldState:trySurf(fx, fy, onClose)
|
||||
end))
|
||||
end
|
||||
|
||||
function OverworldState:stopSurfing(onClose)
|
||||
if onClose then onClose() end
|
||||
self.player.surfing = false
|
||||
require("src.core.Music").setSurfing(Game.data, false)
|
||||
Game.stack:push(Transition.whiteFlash(Game, nil, function()
|
||||
self:stepForwardOrCrossEdge(self.player.facing)
|
||||
end))
|
||||
end
|
||||
|
||||
function OverworldState:tryCut(fx, fy)
|
||||
-- UsedCut (engine/overworld/cut.asm) gates on the TILESET before
|
||||
-- anything else: only OVERWORLD (tree tile $3d) and GYM (plant tile
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
-- stays unsupported; anything a mod legitimately needs belongs here.
|
||||
|
||||
local Logger = require("src.core.Logger")
|
||||
local FieldDefaults = require("src.world.FieldDefaults")
|
||||
local Map = require("src.world.Map")
|
||||
local MapLoader = require("src.world.MapLoader")
|
||||
local MapOverview = require("src.world.MapOverview")
|
||||
local Party = require("src.pokemon.Party")
|
||||
@@ -15,6 +17,8 @@ local WorldAPI = {}
|
||||
WorldAPI.__index = WorldAPI
|
||||
|
||||
local NO_OVERWORLD = "no overworld"
|
||||
local DIG_TILESETS = { FOREST = true, CEMETERY = true, CAVERN = true,
|
||||
FACILITY = true, INTERIOR = true }
|
||||
local RODS = { "OLD_ROD", "GOOD_ROD", "SUPER_ROD" }
|
||||
|
||||
local function acceptsMenuInput(game, ow)
|
||||
@@ -115,6 +119,30 @@ function WorldAPI:availableFieldActions()
|
||||
out[#out + 1] = { id = "fish", label = "FISH", rods = rods }
|
||||
end
|
||||
end
|
||||
|
||||
if ow:useCutFieldMove() == "ok" then
|
||||
out[#out + 1] = { id = "cut", label = "CUT" }
|
||||
end
|
||||
local surf = ow:useSurfFieldMove()
|
||||
if surf == "ok" or surf == "dismount" then
|
||||
out[#out + 1] = { id = "surf",
|
||||
label = surf == "dismount" and "LEAVE WATER" or "SURF" }
|
||||
end
|
||||
|
||||
if not ow.strengthActive and ow:partyKnows("STRENGTH") then
|
||||
out[#out + 1] = { id = "strength", label = "STRENGTH" }
|
||||
end
|
||||
if ow.dark and ow:partyKnows("FLASH") then
|
||||
out[#out + 1] = { id = "flash", label = "FLASH" }
|
||||
end
|
||||
if DIG_TILESETS[ow.map.def.tileset] and ow.map.id ~= "AGATHAS_ROOM"
|
||||
and ow:partyKnows("DIG") then
|
||||
out[#out + 1] = { id = "dig", label = "DIG" }
|
||||
end
|
||||
if ow:partyKnows("TELEPORT") and Map.isOutside(ow.map.def,
|
||||
FieldDefaults.field(game.data, "outsideTilesets")) then
|
||||
out[#out + 1] = { id = "teleport", label = "TELEPORT" }
|
||||
end
|
||||
return out
|
||||
end
|
||||
|
||||
@@ -130,6 +158,19 @@ function WorldAPI:useFieldAction(id, opts)
|
||||
|
||||
if id == "bicycle" then
|
||||
if ow:useBicycle() then return true end
|
||||
elseif id == "cut" then
|
||||
local x, y = ow.player:facingCell()
|
||||
if ow:tryCut(x, y) then return true end
|
||||
elseif id == "surf" then
|
||||
local mode = ow:useSurfFieldMove()
|
||||
if mode == "dismount" then
|
||||
ow:stopSurfing()
|
||||
return true
|
||||
elseif mode == "ok" then
|
||||
local x, y = ow.player:facingCell()
|
||||
ow:trySurf(x, y)
|
||||
return true
|
||||
end
|
||||
elseif id == "fish" then
|
||||
local rod = opts and opts.rod
|
||||
if not rod and #found.rods == 1 then rod = found.rods[1].id end
|
||||
@@ -137,6 +178,13 @@ function WorldAPI:useFieldAction(id, opts)
|
||||
if choice.id == rod and ow:useFishingRod(rod) then return true end
|
||||
end
|
||||
return nil, "fishing rod unavailable"
|
||||
elseif id == "strength" then
|
||||
if ow:useStrengthFieldMove() then return true end
|
||||
elseif id == "flash" then
|
||||
if ow:useFlashFieldMove() then return true end
|
||||
elseif id == "dig" or id == "teleport" then
|
||||
ow:beginTeleportOut()
|
||||
return true
|
||||
end
|
||||
return nil, "field action unavailable"
|
||||
end
|
||||
|
||||
@@ -36,6 +36,18 @@ WorldAPI.__index = WorldAPI
|
||||
|
||||
local NO_OVERWORLD = "no overworld"
|
||||
local RODS = { "OLD_ROD", "GOOD_ROD", "SUPER_ROD" }
|
||||
local FIELD_ACTIONS = {
|
||||
{ id = "cut", move = "CUT" },
|
||||
{ id = "surf", move = "SURF" },
|
||||
{ id = "strength", move = "STRENGTH" },
|
||||
{ id = "flash", move = "FLASH" },
|
||||
{ id = "headbutt", move = "HEADBUTT" },
|
||||
{ id = "whirlpool", move = "WHIRLPOOL" },
|
||||
{ id = "waterfall", move = "WATERFALL" },
|
||||
{ id = "sweet_scent", move = "SWEET_SCENT" },
|
||||
{ id = "dig", move = "DIG" },
|
||||
{ id = "teleport", move = "TELEPORT" },
|
||||
}
|
||||
|
||||
function WorldAPI.new(game, modId)
|
||||
return setmetatable({ game = game, modId = modId }, WorldAPI)
|
||||
@@ -96,6 +108,26 @@ function WorldAPI:availableFieldActions()
|
||||
out[#out + 1] = { id = "fish", label = "FISH", rods = rods }
|
||||
end
|
||||
end
|
||||
|
||||
for _, row in ipairs(FIELD_ACTIONS) do
|
||||
if not (row.move == "STRENGTH" and world.strengthActive) then
|
||||
local mon = FieldMoves.partyMoveUser(context.party, row.move, context)
|
||||
if mon then
|
||||
context.mon = mon
|
||||
local result = FieldMoves.fromMenu(row.move, context)
|
||||
if result.ok then
|
||||
out[#out + 1] = { id = row.id,
|
||||
label = row.move:gsub("_", " ") }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if (inventory.SQUIRTBOTTLE or 0) > 0
|
||||
and world:squirtbottleTreeScript() then
|
||||
out[#out + 1] = { id = "squirtbottle",
|
||||
label = itemLabel(game, "SQUIRTBOTTLE") }
|
||||
end
|
||||
return out
|
||||
end
|
||||
|
||||
@@ -123,6 +155,18 @@ function WorldAPI:useFieldAction(id, opts)
|
||||
end
|
||||
end
|
||||
return nil, "fishing rod unavailable"
|
||||
elseif id == "squirtbottle" then
|
||||
local outcome = world:useFieldItem("SQUIRTBOTTLE")
|
||||
if outcome and outcome ~= "nowhere" then return true end
|
||||
end
|
||||
for _, row in ipairs(FIELD_ACTIONS) do
|
||||
if row.id == id then
|
||||
local context = world:fieldContext()
|
||||
local mon = FieldMoves.partyMoveUser(context.party, row.move, context)
|
||||
local result = mon and world:useFieldMove(row.move, mon)
|
||||
if result and result.ok then return true end
|
||||
return nil, "field action unavailable"
|
||||
end
|
||||
end
|
||||
return nil, "field action unavailable"
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user