mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-20 20:50:21 +02:00
Merge pull request #965 from crusty/feat/worldapi-start-wild-battle
Add mod.world:startWildBattle
This commit is contained in:
@@ -8,6 +8,7 @@
|
|||||||
local Logger = require("src.core.Logger")
|
local Logger = require("src.core.Logger")
|
||||||
local Assets = require("src.render.Assets")
|
local Assets = require("src.render.Assets")
|
||||||
local MapLoader = require("src.world.MapLoader")
|
local MapLoader = require("src.world.MapLoader")
|
||||||
|
local Party = require("src.pokemon.Party")
|
||||||
local Runtime = require("src.mods.Runtime")
|
local Runtime = require("src.mods.Runtime")
|
||||||
|
|
||||||
local WorldAPI = {}
|
local WorldAPI = {}
|
||||||
@@ -265,6 +266,49 @@ function WorldAPI:queueScript(rows, extra)
|
|||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- The supported way to start a wild encounter. Hand-rolling this -- build a
|
||||||
|
-- BattleState, push it -- silently costs evolutions and blackout-on-loss
|
||||||
|
-- (both hang off onFinish -> afterBattle) plus the entry wipe and battle
|
||||||
|
-- theme (both owned by pushBattle). Nothing raises when they are missing.
|
||||||
|
function WorldAPI:startWildBattle(species, level)
|
||||||
|
local ow = self:overworld()
|
||||||
|
if not ow then return nil, NO_OVERWORLD end
|
||||||
|
if not self.game.data.pokemon[species] then
|
||||||
|
return nil, "unknown species: " .. tostring(species)
|
||||||
|
end
|
||||||
|
-- Pokemon.new writes the level through verbatim -- into level, the stat
|
||||||
|
-- calc and the exp curve -- so a fraction has to be refused here rather
|
||||||
|
-- than round somewhere downstream. The % test also catches NaN, which
|
||||||
|
-- passes both range comparisons.
|
||||||
|
level = tonumber(level)
|
||||||
|
if not level or level % 1 ~= 0 or level < 1 or level > 100 then
|
||||||
|
return nil, "level must be a whole number 1..100"
|
||||||
|
end
|
||||||
|
-- overworld() resolves the world from UNDER whatever sits on top of it,
|
||||||
|
-- so from a battle hook this would otherwise stack a second battle over
|
||||||
|
-- the live one -- and on a loss its afterBattle blacks out and warps
|
||||||
|
-- with the outer battle still on the stack.
|
||||||
|
local BattleTransition = require("src.render.BattleTransition")
|
||||||
|
for _, state in ipairs(self.game.stack and self.game.stack.states or {}) do
|
||||||
|
if state.awardExp or getmetatable(state) == BattleTransition then
|
||||||
|
return nil, "a battle is already running"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if ow.transitioning then return nil, "the world is mid-warp" end
|
||||||
|
-- BattleState.newWild marks the species SEEN before it reports an empty
|
||||||
|
-- party, so the party check comes first: a refused call must not leave a
|
||||||
|
-- Pokedex entry behind.
|
||||||
|
local save = self.game.save
|
||||||
|
if not (save and Party.firstHealthy(save.party or {})) then
|
||||||
|
return nil, "no healthy party"
|
||||||
|
end
|
||||||
|
local battle = require("src.battle.BattleState")
|
||||||
|
.newWild(self.game, species, level)
|
||||||
|
battle.onFinish = function(result) ow:afterBattle(result, battle) end
|
||||||
|
ow:pushBattle(battle)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
-- drop a map's cached instance so the next load re-reads its record; when
|
-- drop a map's cached instance so the next load re-reads its record; when
|
||||||
-- it is the active map the world reloads around the player in place
|
-- it is the active map the world reloads around the player in place
|
||||||
function WorldAPI:invalidateMap(mapId)
|
function WorldAPI:invalidateMap(mapId)
|
||||||
|
|||||||
@@ -916,6 +916,76 @@ do
|
|||||||
check(value == nil and err == "no overworld", "npc() off the world")
|
check(value == nil and err == "no overworld", "npc() off the world")
|
||||||
value, err = api:queueScript({})
|
value, err = api:queueScript({})
|
||||||
check(value == nil and err == "no overworld", "queueScript() off the world")
|
check(value == nil and err == "no overworld", "queueScript() off the world")
|
||||||
|
value, err = api:startWildBattle("PIDGEY", 5)
|
||||||
|
check(value == nil and err == "no overworld", "startWildBattle() off the world")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- startWildBattle: what regresses is the handoff, not the battle. A mod that
|
||||||
|
-- builds a BattleState and pushes it itself still fights and still levels; it
|
||||||
|
-- silently loses onFinish -> afterBattle (evolutions, blackout-on-loss) and
|
||||||
|
-- pushBattle (entry wipe, battle theme). Shipped mods have hit exactly this.
|
||||||
|
do
|
||||||
|
-- the real dataset, not fixture(): a battle reaches for type_chart, items,
|
||||||
|
-- battle_anims and more, and this block only reads
|
||||||
|
local data = Data
|
||||||
|
local state, game = liveWorld(data)
|
||||||
|
-- the handoff runs through these three, so each needs the live game
|
||||||
|
for _, fn in ipairs({ "pushBattle", "isDungeonTransitionMap", "afterBattle" }) do
|
||||||
|
check(bindGame(OW[fn], game), fn .. " binds Game")
|
||||||
|
end
|
||||||
|
state:setMap("PALLET_TOWN", 5, 6, "down", { via = "boot" })
|
||||||
|
|
||||||
|
local Pokemon = require("src.pokemon.Pokemon")
|
||||||
|
local api = WorldAPI.new(game, "tester")
|
||||||
|
|
||||||
|
local value, err = api:startWildBattle("NOT_A_MON", 5)
|
||||||
|
check(value == nil and err:find("unknown species", 1, true),
|
||||||
|
"an unknown species refuses and names it")
|
||||||
|
-- Pokemon.new writes the level through into the stat calc and the exp curve
|
||||||
|
-- verbatim, so a fraction has to be refused rather than rounded downstream
|
||||||
|
for _, lv in ipairs({ 0, 101, "nope", 5.5 }) do
|
||||||
|
check(api:startWildBattle("PIDGEY", lv) == nil,
|
||||||
|
"level " .. tostring(lv) .. " refuses")
|
||||||
|
end
|
||||||
|
|
||||||
|
local caterpie = Pokemon.new(data, "CATERPIE", 6)
|
||||||
|
game.save.party = { caterpie }
|
||||||
|
check(api:startWildBattle("PIDGEY", 25) == true, "a wild battle starts")
|
||||||
|
|
||||||
|
-- pushBattle pushes the entry transition, which pushes the battle from its
|
||||||
|
-- own callback; awardExp is the BattleState marker (screenId would not work,
|
||||||
|
-- only Screens.push stamps that and pushBattle pushes the battle directly)
|
||||||
|
check(game.stack:top() ~= nil and game.stack:top().awardExp == nil,
|
||||||
|
"the entry transition goes on first")
|
||||||
|
-- overworld() resolves the world from UNDER the battle, so a second call
|
||||||
|
-- while one is up has to refuse rather than stack another
|
||||||
|
check(api:startWildBattle("PIDGEY", 5) == nil,
|
||||||
|
"a battle already running refuses")
|
||||||
|
|
||||||
|
local battle
|
||||||
|
for _ = 1, 400 do
|
||||||
|
local t = game.stack:top()
|
||||||
|
if t and t.awardExp then battle = t break end
|
||||||
|
if t and t.update then t:update(1 / 60) else break end
|
||||||
|
end
|
||||||
|
check(battle ~= nil, "the transition hands off to the battle")
|
||||||
|
|
||||||
|
battle.participants = { [caterpie] = true }
|
||||||
|
battle:awardExp()
|
||||||
|
check(caterpie.level >= 7, "the mon levels past its evolution threshold")
|
||||||
|
check(battle.leveledUp and battle.leveledUp[caterpie],
|
||||||
|
"awardExp records the level-up for EvolveAfterBattle")
|
||||||
|
|
||||||
|
game.stack:pop()
|
||||||
|
battle.onFinish("win")
|
||||||
|
for _ = 1, 12 do
|
||||||
|
local t = game.stack:top()
|
||||||
|
if not t or t.screenId == "EvolutionState" then break end
|
||||||
|
game.stack:pop()
|
||||||
|
if t.onDone then t.onDone() end
|
||||||
|
end
|
||||||
|
check(game.stack:top() and game.stack:top().screenId == "EvolutionState",
|
||||||
|
"the win reaches the evolution screen")
|
||||||
end
|
end
|
||||||
|
|
||||||
do
|
do
|
||||||
|
|||||||
Reference in New Issue
Block a user