mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-12 08:21:02 +02:00
8e48bb4e32
Yellow's Pallet Town intro (professor catches the wild Pikachu, then walks the player to the lab) played the map's default Pallet Town theme the whole time instead of the dedicated escort cue. Per pokeyellow's actual scripts: PlayDefaultMusicFadeOutCurrent (run on every battle exit) legitimately restores Pallet Town's theme after the Pikachu demo battle for the Whew.../Come with me lines; the escort cue (MUSIC_MUSEUM_GUY, the same "led by an NPC" theme Pewter's museum guide uses) only starts in PalletMovementScript_OakMoveLeft, the first function of the escort script -- but only in pokeyellow's copy. pokered's copy of that same shared Red/Yellow function only sets BIT_NO_MAP_MUSIC and leaves whatever was already playing (MUSIC_MEET_PROF_OAK) running uninterrupted into the lab. Start Music_MuseumGuy at the top of escortToLab, gated on Yellow so Red/Blue keeps its unchanged behavior, and fix the Oak-escort warp's keepMusic comment to say which song rides the warp in each version.
1088 lines
44 KiB
Lua
1088 lines
44 KiB
Lua
-- More hand-ported events: the Pallet Town intro, the thirsty Saffron
|
|
-- gate guards, the Bike Voucher chain, fossils and the day-care.
|
|
-- Registered via data/scripts/init.lua; Red/Blue cite pokered, Yellow
|
|
-- cites pokeyellow (Pallet stop row, Oak spawn, walk RLE, and the
|
|
-- wild-Pikachu beat before the lab escort).
|
|
|
|
local GameVersion = require("src.core.GameVersion")
|
|
|
|
local M = {}
|
|
|
|
-- -------------------------------------------------------------------
|
|
-- Pallet Town intro (scripts/PalletTown.asm PalletTownOakHeyWaitScript):
|
|
-- stepping toward the grass without a starter makes Oak stop you and
|
|
-- take you to his lab (the walk cutscene is compressed into the warp).
|
|
-- -------------------------------------------------------------------
|
|
|
|
-- Pure escort data, exposed on the map entry as `escort` for
|
|
-- tests/parity_intro.lua.
|
|
local escort = {}
|
|
|
|
-- FindPathToPlayer (engine/overworld/pathfinding.asm): each step
|
|
-- reduces whichever axis has more distance left; ties step X first
|
|
-- (`ld a, e / cp d / jr c, .yDistanceGreater`, e = X left, d = Y left).
|
|
function escort.findPath(fromX, fromY, toX, toY)
|
|
local xdist, ydist = math.abs(toX - fromX), math.abs(toY - fromY)
|
|
local xdir = toX > fromX and "right" or "left"
|
|
local ydir = toY > fromY and "down" or "up"
|
|
local xprog, yprog = 0, 0
|
|
local path = {}
|
|
while xprog < xdist or yprog < ydist do
|
|
if xdist - xprog >= ydist - yprog and xprog < xdist then
|
|
xprog = xprog + 1
|
|
path[#path + 1] = xdir
|
|
else
|
|
yprog = yprog + 1
|
|
path[#path + 1] = ydir
|
|
end
|
|
end
|
|
return path
|
|
end
|
|
|
|
-- Red: Oak object (8,5) -> one tile below the player at y=1.
|
|
-- Yellow: Oak object (10,4); stop fires at y=0 (pokeyellow PalletTown).
|
|
function escort.oakApproach(playerX)
|
|
if GameVersion.isYellow() then
|
|
return escort.findPath(10, 4, playerX, 1)
|
|
end
|
|
return escort.findPath(8, 5, playerX, 2)
|
|
end
|
|
|
|
-- RLEList_ProfOakWalkToLab (engine/overworld/auto_movement.asm).
|
|
-- Yellow differs: first DOWN is x6 (Oak starts one tile farther north).
|
|
local function buildOakSteps()
|
|
if GameVersion.isYellow() then
|
|
return {
|
|
"down", "down", "down", "down", "down", "down",
|
|
"left",
|
|
"down", "down", "down", "down", "down",
|
|
"right", "right", "right",
|
|
"up",
|
|
}
|
|
end
|
|
return {
|
|
"down", "down", "down", "down", "down",
|
|
"left",
|
|
"down", "down", "down", "down", "down",
|
|
"right", "right", "right",
|
|
"up",
|
|
}
|
|
end
|
|
|
|
escort.oakSteps = buildOakSteps()
|
|
|
|
-- Player stays one step behind Oak (simplified reverse-RLE port).
|
|
escort.playerSteps = { "down" }
|
|
for _, d in ipairs(escort.oakSteps) do
|
|
escort.playerSteps[#escort.playerSteps + 1] = d
|
|
end
|
|
|
|
local function npcNamed(ow, name)
|
|
for _, n in ipairs(ow.npcs or {}) do
|
|
if n.def and n.def.name == name then return n end
|
|
end
|
|
return nil
|
|
end
|
|
|
|
M.PALLET_TOWN = {
|
|
talk = require("data.scripts.pallet_town").talk,
|
|
escort = escort,
|
|
-- Red: stop at y==1 from (8,5). Yellow: stop at y==0 from (10,4),
|
|
-- then a wild Pikachu battle before the lab escort (pokeyellow
|
|
-- PalletTownPikachuBattleScript).
|
|
onStep = function(game, ow, x, y)
|
|
local yellow = GameVersion.isYellow()
|
|
local stopY = yellow and 0 or 1
|
|
if y ~= stopY or game.save.flags.EVENT_FOLLOWED_OAK_INTO_LAB
|
|
or game.save.flags.EVENT_GOT_STARTER then
|
|
return false
|
|
end
|
|
local TextBox = require("src.render.TextBox")
|
|
local Commands = require("src.script.Commands")
|
|
local Music = require("src.core.Music")
|
|
local BattleState = require("src.battle.BattleState")
|
|
local t = game.data.text
|
|
local ctx = { save = game.save, game = game, overworld = ow }
|
|
|
|
-- Red turns the player down; Yellow faces up at the north exit.
|
|
ow.player.facing = yellow and "up" or "down"
|
|
Music.play(game.data, "Music_MeetProfOak")
|
|
|
|
local function hold(frames, npc, cb)
|
|
ow.emote = { frames = frames, npc = npc, onDone = cb }
|
|
end
|
|
|
|
local function walkList(entity, steps, done)
|
|
local i = 0
|
|
local function nextStep()
|
|
i = i + 1
|
|
if not entity or not steps[i] then
|
|
if done then done() end
|
|
return
|
|
end
|
|
ow:scriptMove(entity, steps[i], 1, nextStep)
|
|
end
|
|
nextStep()
|
|
end
|
|
|
|
-- OaksLabOakChooseMonSpeechScript. Yellow's OakChooseMon text is
|
|
-- the single-ball speech, not Red's "there are 3 POKéMON".
|
|
local function chooseMonSpeech()
|
|
local function say(key, fb, next)
|
|
game.stack:push(TextBox.new(game, t[key] or fb, next))
|
|
end
|
|
say("_OaksLabRivalFedUpWithWaitingText",
|
|
"{RIVAL}: Gramps!\nI'm fed up with\nwaiting!", function()
|
|
hold(3, nil, function()
|
|
say("_OaksLabOakChooseMonText",
|
|
yellow
|
|
and "OAK: Look, {PLAYER}! Do\nyou see that ball\non the table?"
|
|
or "OAK: Here, {PLAYER}!\fThere are 3\nPOKéMON here!\fYou can have one!\nChoose!", function()
|
|
hold(3, nil, function()
|
|
say("_OaksLabRivalWhatAboutMeText",
|
|
"{RIVAL}: Hey!\nGramps! What\nabout me?", function()
|
|
hold(3, nil, function()
|
|
say("_OaksLabOakBePatientText",
|
|
"OAK: Be patient!\n{RIVAL}, you can\nhave one too!", function()
|
|
game.save.flags.EVENT_OAK_ASKED_TO_CHOOSE_MON = true
|
|
end)
|
|
end)
|
|
end)
|
|
end)
|
|
end)
|
|
end)
|
|
end)
|
|
end
|
|
|
|
-- Door Oak is OAKSLAB_OAK2: Red index 8, Yellow index 6 (one ball).
|
|
local function labWalkIn()
|
|
local oak2 = npcNamed(ow, "OAKSLAB_OAK2") or ow:npcByIndex(yellow and 6 or 8)
|
|
local function swapOaks()
|
|
Commands.hide_object(ctx, "OAKS_LAB", "OAKSLAB_OAK2")
|
|
Commands.show_object(ctx, "OAKS_LAB", "OAKSLAB_OAK1")
|
|
hold(3, nil, function()
|
|
Commands.face_object(ctx, 1, "down")
|
|
ow:scriptMove(ow.player, "up", 8, function()
|
|
game.save.flags.EVENT_FOLLOWED_OAK_INTO_LAB = true
|
|
game.save.flags.EVENT_FOLLOWED_OAK_INTO_LAB_2 = true
|
|
Commands.face_object(ctx, 1, "up")
|
|
Music.playMap(game.data, "OAKS_LAB")
|
|
chooseMonSpeech()
|
|
end)
|
|
end)
|
|
end
|
|
if oak2 then
|
|
ow:scriptMove(oak2, "up", 3, swapOaks)
|
|
else
|
|
swapOaks()
|
|
end
|
|
end
|
|
|
|
local function enterLab()
|
|
Commands.hide_object(ctx, "PALLET_TOWN", "PALLETTOWN_OAK")
|
|
Commands.show_object(ctx, "OAKS_LAB", "OAKSLAB_OAK2")
|
|
ow.doorWarp = true
|
|
ow:startWarpTo("OAKS_LAB", 5, 11, "up", labWalkIn,
|
|
{ keepMusic = true })
|
|
end
|
|
|
|
local function walkToLab(oak)
|
|
local i = 0
|
|
local function tick()
|
|
i = i + 1
|
|
local playerStep = escort.playerSteps[i]
|
|
if not playerStep then
|
|
enterLab()
|
|
return
|
|
end
|
|
if oak and escort.oakSteps[i] then
|
|
ow:scriptMove(oak, escort.oakSteps[i], 1)
|
|
elseif oak then
|
|
ow:marchInPlace(oak)
|
|
end
|
|
ow:scriptMove(ow.player, playerStep, 1, tick)
|
|
end
|
|
tick()
|
|
end
|
|
|
|
local function escortToLab(oak)
|
|
-- PalletMovementScript_OakMoveLeft (engine/overworld/auto_movement
|
|
-- .asm) is shared by Red and Yellow, but only Yellow's copy starts
|
|
-- MUSIC_MUSEUM_GUY there (the instant the movement script is
|
|
-- armed, before Oak or the player takes a single step); pokered's
|
|
-- copy only sets BIT_NO_MAP_MUSIC and leaves whatever was already
|
|
-- playing (MUSIC_MEET_PROF_OAK, started when Oak first appears)
|
|
-- running uninterrupted all the way into the lab. Until Yellow's
|
|
-- switch fires (including the Whew.../Come with me lines right
|
|
-- after the Pikachu battle) the map's default Pallet Town theme
|
|
-- plays, restored by the battle's own exit path.
|
|
if yellow then
|
|
Music.play(game.data, "Music_MuseumGuy")
|
|
end
|
|
local numSteps = x - 10
|
|
if oak and numSteps > 0 then
|
|
ow:scriptMove(oak, "left", numSteps, function()
|
|
ow:scriptMove(ow.player, "left", numSteps, function()
|
|
walkToLab(oak)
|
|
end)
|
|
end)
|
|
else
|
|
walkToLab(oak)
|
|
end
|
|
end
|
|
|
|
-- After Oak reaches the player: Red goes straight to "It's unsafe!".
|
|
-- Yellow (PalletTownOakGreetsPlayerScript..AfterPikachuBattleScript):
|
|
-- ThatWasClose -> Oak faces the grass patch -> BATTLE_TYPE_PIKACHU
|
|
-- (the old-man-style simulated battle where PROF.OAK throws the ball
|
|
-- and always catches the lv5 Pikachu) -> Whew -> ComeWithMe.
|
|
local function afterOakArrives(oak)
|
|
if not yellow then
|
|
game.stack:push(TextBox.new(game,
|
|
t._PalletTownOakItsUnsafeText
|
|
or "OAK: It's unsafe!\nWild POKéMON\nlive in tall grass!",
|
|
function() escortToLab(oak) end))
|
|
return
|
|
end
|
|
local function comeWithMe()
|
|
game.stack:push(TextBox.new(game,
|
|
t._PalletTownOakComeWithMe
|
|
or "OAK: Here, come with\nme!",
|
|
function() escortToLab(oak) end))
|
|
end
|
|
local function afterPikaBattle()
|
|
if oak then oak.facing = "up" end
|
|
game.stack:push(TextBox.new(game,
|
|
t._PalletTownOakWhewText or "OAK: Whew...",
|
|
comeWithMe))
|
|
end
|
|
game.stack:push(TextBox.new(game,
|
|
t._PalletTownOakThatWasCloseText
|
|
or "OAK: That was\nclose!\fWild POKéMON live\nin tall grass!",
|
|
function()
|
|
-- Oak turns toward the horizontally adjacent grass (left exit
|
|
-- looks right, right exit looks left -- the
|
|
-- EVENT_PLAYER_AT_RIGHT_EXIT_TO_PALLET_TOWN branch)
|
|
if oak then oak.facing = x == 10 and "right" or "left" end
|
|
local battle = BattleState.newWild(game, "PIKACHU", 5)
|
|
battle:makeOldManDemo("PROF.OAK")
|
|
battle.onFinish = function()
|
|
afterPikaBattle()
|
|
end
|
|
game.stack:push(battle)
|
|
end))
|
|
end
|
|
|
|
local function oakAppearsAndWalks()
|
|
Commands.show_object(ctx, "PALLET_TOWN", "PALLETTOWN_OAK")
|
|
local oak = npcNamed(ow, "PALLETTOWN_OAK") or ow:npcByIndex(1)
|
|
if oak then oak.facing = "up" end
|
|
hold(6, nil, function()
|
|
walkList(oak, escort.oakApproach(x), function()
|
|
afterOakArrives(oak)
|
|
end)
|
|
end)
|
|
end
|
|
|
|
game.stack:push(TextBox.new(game,
|
|
t._PalletTownOakHeyWaitDontGoOutText or "OAK: Hey! Wait!\nDon't go out!",
|
|
nil, { auto = { delay = 10, overlap = 10, onOverlap = function()
|
|
-- .HeyWaitDontGoOutText turns the player to face down (toward
|
|
-- the approaching Oak) before the exclamation bubble
|
|
ow.player.facing = "down"
|
|
ow.emote = { npc = ow.player, frames = 50, onDone = oakAppearsAndWalks }
|
|
end } }))
|
|
return true
|
|
end,
|
|
}
|
|
|
|
-- -------------------------------------------------------------------
|
|
-- Saffron gate guards (scripts/Route5Gate.asm etc.): crossing the gate
|
|
-- without having given them a drink gets you turned back; a drink from
|
|
-- the bag (bought at Celadon's vending machines... or any mart that
|
|
-- stocks them) opens all four gates.
|
|
-- -------------------------------------------------------------------
|
|
|
|
local DRINKS = { "FRESH_WATER", "SODA_POP", "LEMONADE" }
|
|
|
|
-- Hand over the first drink in the bag, if any. Mirrors RemoveGuardDrink
|
|
-- (engine/items/inventory.asm), which walks the same three item ids and
|
|
-- removes ONE, and the caller's BIT_GAVE_SAFFRON_GUARDS_DRINK.
|
|
local function takeGuardDrink(game)
|
|
for _, drink in ipairs(DRINKS) do
|
|
if (game.save.inventory[drink] or 0) > 0 then
|
|
game.save.inventory[drink] = game.save.inventory[drink] - 1
|
|
if game.save.inventory[drink] == 0 then
|
|
game.save.inventory[drink] = nil
|
|
end
|
|
game.save.flags.EVENT_GAVE_GUARDS_DRINK = true
|
|
return true
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
local function saffronGate(guardText, triggers, horizontal)
|
|
return {
|
|
talk = {
|
|
[guardText] = function(game, ow, npc, done)
|
|
local TextBox = require("src.render.TextBox")
|
|
local t = game.data.text
|
|
if game.save.flags.EVENT_GAVE_GUARDS_DRINK then
|
|
game.stack:push(TextBox.new(game,
|
|
t._SaffronGateGuardThanksForTheDrinkText or "Gee, that was\ntasty!", done))
|
|
return
|
|
end
|
|
if takeGuardDrink(game) then
|
|
game.stack:push(TextBox.new(game,
|
|
t._SaffronGateGuardImParchedText or "Whoa, boy!\nI'm parched!",
|
|
function()
|
|
game.stack:push(TextBox.new(game,
|
|
(t._SaffronGateGuardYouCanGoOnThroughText or
|
|
"You can go on\nthrough!"), done))
|
|
end))
|
|
return
|
|
end
|
|
game.stack:push(TextBox.new(game,
|
|
t._SaffronGateGuardGeeImThirstyText or "Gee, I'm thirsty\nthough!", done))
|
|
end,
|
|
},
|
|
-- the gate's trigger cells (each gate's PlayerInCoordsArray):
|
|
-- without the drink flag you get walked back the way you came
|
|
onStep = function(game, ow, x, y)
|
|
local hit = false
|
|
for _, c in ipairs(triggers) do
|
|
if x == c[1] and y == c[2] then hit = true break end
|
|
end
|
|
if not hit then return false end
|
|
if game.save.flags.EVENT_GAVE_GUARDS_DRINK then return false end
|
|
local TextBox = require("src.render.TextBox")
|
|
local t = game.data.text
|
|
-- Stepping on the trigger WITH a drink hands it over right here.
|
|
--
|
|
-- Route5GateDefaultScript (scripts/Route5Gate.asm) runs
|
|
-- `farcall RemoveGuardDrink` before it decides anything: the coord
|
|
-- trigger itself takes the drink and sets
|
|
-- BIT_GAVE_SAFFRON_GUARDS_DRINK, and only a player carrying nothing
|
|
-- gets the thirsty line and the walk-back. We had the removal on the
|
|
-- guard's TALK handler only, so walking up with a FRESH_WATER in the
|
|
-- bag was turned away and the four gates stayed shut unless you
|
|
-- happened to talk to him -- which vanilla never requires.
|
|
--
|
|
-- Saffron is the middle of the map, so this sealed it: every route
|
|
-- through the city (Celadon <-> Lavender, Vermilion <-> Cerulean the
|
|
-- short way) was unreachable, and the bot could not get to Lavender
|
|
-- for the POKE_FLUTE at all.
|
|
if takeGuardDrink(game) then
|
|
game.stack:push(TextBox.new(game,
|
|
t._SaffronGateGuardImParchedText or "Whoa, boy!\nI'm parched!",
|
|
function()
|
|
game.stack:push(TextBox.new(game,
|
|
(t._SaffronGateGuardYouCanGoOnThroughText or
|
|
"You can go on\nthrough!")))
|
|
end))
|
|
return true
|
|
end
|
|
local back
|
|
if horizontal then
|
|
back = ow.player.facing == "left" and "right" or "left"
|
|
else
|
|
back = ow.player.facing == "up" and "down" or "up"
|
|
end
|
|
game.stack:push(TextBox.new(game,
|
|
t._SaffronGateGuardGeeImThirstyText or "Gee, I'm thirsty\nthough!\nThe road's closed.",
|
|
function()
|
|
ow:scriptMove(ow.player, back, 1)
|
|
end))
|
|
return true
|
|
end,
|
|
}
|
|
end
|
|
|
|
M.ROUTE_5_GATE = saffronGate("TEXT_ROUTE5GATE_GUARD", { { 3, 3 }, { 4, 3 } })
|
|
M.ROUTE_6_GATE = saffronGate("TEXT_ROUTE6GATE_GUARD", { { 3, 2 }, { 4, 2 } })
|
|
M.ROUTE_7_GATE = saffronGate("TEXT_ROUTE7GATE_GUARD", { { 3, 3 }, { 3, 4 } }, true)
|
|
M.ROUTE_8_GATE = saffronGate("TEXT_ROUTE8GATE_GUARD", { { 2, 3 }, { 2, 4 } }, true)
|
|
|
|
-- -------------------------------------------------------------------
|
|
-- Bike Voucher chain (scripts/PokemonFanClub.asm, BikeShop.asm)
|
|
-- -------------------------------------------------------------------
|
|
|
|
M.POKEMON_FAN_CLUB = {
|
|
talk = {
|
|
TEXT_POKEMONFANCLUB_CHAIRMAN = {
|
|
{ "face_player" }, -- 1
|
|
{ "check_flag", "EVENT_RECEIVED_BIKE_VOUCHER" }, -- 2
|
|
{ "jump_if_true", "nothing_left" }, -- 3
|
|
-- YesNoChoice (scripts/PokemonFanClub.asm): NO forfeits the voucher (#1050)
|
|
{ "ask", "_PokemonFanClubChairmanIntroText" }, -- 4
|
|
{ "jump_if_false", "no_story" }, -- 5
|
|
{ "show_text", "_PokemonFanClubChairmanStoryText" }, -- 6
|
|
-- give-then-print like scripts/PokemonFanClub.asm (GiveItem
|
|
-- fills wStringBuffer; the received text reads it)
|
|
{ "give_item", "BIKE_VOUCHER", 1, false }, -- 7
|
|
{ "show_text", "_PokemonFanClubReceivedBikeVoucherText" }, -- 8
|
|
{ "set_flag", "EVENT_RECEIVED_BIKE_VOUCHER" }, -- 9
|
|
{ "show_text", "_PokemonFanClubExplainBikeVoucherText" }, -- 10
|
|
{ "jump", "end" }, -- 11
|
|
{ "label", "no_story" }, -- 12
|
|
{ "show_text", "_PokemonFanClubNoStoryText" }, -- 13
|
|
{ "jump", "end" }, -- 14
|
|
-- .nothingleft: the gift is done, he only reminisces now
|
|
{ "label", "nothing_left" }, -- 15
|
|
{ "show_text", "_PokemonFanClubChairFinalText" }, -- 16
|
|
},
|
|
},
|
|
}
|
|
|
|
-- BikeShopClerkText (scripts/BikeShop.asm) runs three ways: the BICYCLE is
|
|
-- already yours, you are carrying the BIKE VOUCHER, or you get the sales
|
|
-- pitch. The pitch draws its own window (TextBoxBorder hlcoord 0,0, b=4
|
|
-- c=15) holding BikeShopMenuText and BikeShopMenuPrice, and leaves it up
|
|
-- while the clerk keeps talking in the bottom box: the original never
|
|
-- erases it before TextScriptEnd (#568).
|
|
local BikeShopWindow = {}
|
|
BikeShopWindow.__index = BikeShopWindow
|
|
|
|
function BikeShopWindow.new(game, footer, onChoose)
|
|
local self = setmetatable({}, BikeShopWindow)
|
|
self.game = game
|
|
self.onChoose = onChoose
|
|
self.index = 1
|
|
self.active = true
|
|
-- BikeShopClerkDoYouLikeItText stays on screen under the window for as
|
|
-- long as the menu is up. The text box pushed on top types it out and
|
|
-- pops itself; this copy of its last page takes over from there, so the
|
|
-- bottom box never blanks between the pitch and the answer. Paginated
|
|
-- with the themed column budget so the copy breaks where the box did.
|
|
local TextBox = require("src.render.TextBox")
|
|
local box = require("src.ui.Theme").textBox or {}
|
|
local pages = TextBox.paginate(TextBox.substitute(game, footer), box.maxCols)
|
|
self.footer = pages[#pages]
|
|
return self
|
|
end
|
|
|
|
function BikeShopWindow:update()
|
|
-- one answer only: the boxes pushed by onChoose sit on top of this
|
|
-- state, but a second A on the same frame must not fire it twice
|
|
if not self.active then return end
|
|
local input = self.game.input
|
|
-- HandleMenuInput with wMenuWrappingEnabled clear: the two rows clamp
|
|
if input:wasPressed("up") then
|
|
self.index = 1
|
|
elseif input:wasPressed("down") then
|
|
self.index = 2
|
|
elseif input:wasPressed("a") or input:wasPressed("b") then
|
|
local cancelled = input:wasPressed("b") -- bit B_PAD_B -> .cancel
|
|
require("src.core.Sound").play(self.game.data, "Press_AB")
|
|
self.active = false
|
|
self.onChoose(not cancelled and self.index == 1)
|
|
end
|
|
end
|
|
|
|
function BikeShopWindow:draw()
|
|
local Font = require("src.render.Font")
|
|
local Strings = require("src.core.Strings")
|
|
local Theme = require("src.ui.Theme")
|
|
Font.drawBox(0, 0, 17, 6)
|
|
love.graphics.setColor(0, 0, 0, 1)
|
|
local bike = self.game.data.items.BICYCLE
|
|
Font.draw(bike and bike.name or "BICYCLE", 16, 16) -- hlcoord 2, 2
|
|
Font.draw("¥1000000", 64, 24) -- hlcoord 8, 3
|
|
Font.draw(Strings("CANCEL"), 16, 32) -- `next` skips a row
|
|
-- wTopMenuItemX 1, wTopMenuItemY 2, rows two apart
|
|
Font.drawCode(Theme.cursor, 8, self.index == 1 and 16 or 32)
|
|
if self.footer then
|
|
-- same geometry TextBox resolves against, so a themed box matches
|
|
local box = Theme.textBox or {}
|
|
local tx, ty = box.tx or 0, box.ty or 12
|
|
love.graphics.setColor(1, 1, 1, 1)
|
|
Font.drawBox(tx, ty, box.tw or 20, box.th or 6)
|
|
love.graphics.setColor(0, 0, 0, 1)
|
|
for i, line in ipairs(self.footer) do
|
|
Font.draw(line, (tx + 1) * 8, (ty + 2 * i) * 8)
|
|
end
|
|
end
|
|
love.graphics.setColor(1, 1, 1, 1)
|
|
end
|
|
|
|
M.BIKE_SHOP = {
|
|
talk = {
|
|
TEXT_BIKESHOP_CLERK = function(game, ow, npc, done)
|
|
local Bag = require("src.inventory.Bag")
|
|
local Flags = require("src.script.Flags")
|
|
local TextBox = require("src.render.TextBox")
|
|
local t = game.data.text
|
|
local function say(text, after, opts)
|
|
game.stack:push(TextBox.new(game, text, after, opts))
|
|
end
|
|
|
|
-- CheckEvent EVENT_GOT_BICYCLE. Saves made before the clerk started
|
|
-- setting the event still have the bike in the bag, so either counts.
|
|
if (game.save.inventory.BICYCLE or 0) > 0
|
|
or Flags.get(game.save, "EVENT_GOT_BICYCLE") then
|
|
say(t._BikeShopClerkHowDoYouLikeYourBicycleText, done)
|
|
return
|
|
end
|
|
|
|
-- .dontHaveBike: IsItemInBag BIKE_VOUCHER
|
|
if (game.save.inventory.BIKE_VOUCHER or 0) > 0 then
|
|
say(t._BikeShopClerkOhThatsAVoucherText, function()
|
|
-- GiveItem's `jr nc, .BagFull`: the voucher is only spent once
|
|
-- the BICYCLE is actually in the bag
|
|
if not Bag.add(game.save, "BICYCLE", 1) then
|
|
say(t._BikeShopBagFullText, done)
|
|
return
|
|
end
|
|
Bag.remove(game.save, "BIKE_VOUCHER", 1)
|
|
Flags.set(game.save, "EVENT_GOT_BICYCLE")
|
|
-- BikeShopExchangedVoucherText carries sound_get_key_item; the
|
|
-- map runs EnableAutoTextBoxDrawing, so the box still waits for
|
|
-- a button once the jingle has played (auto.wait, #247)
|
|
say(t._BikeShopExchangedVoucherText, done, {
|
|
auto = { wait = true, sound = function()
|
|
return require("src.core.Sound").play(game.data, "Get_Key_Item")
|
|
end },
|
|
})
|
|
end)
|
|
return
|
|
end
|
|
|
|
-- .dontHaveVoucher: welcome, then the BICYCLE/CANCEL window
|
|
say(t._BikeShopClerkWelcomeText, function()
|
|
local pitch = t._BikeShopClerkDoYouLikeItText
|
|
game.stack:push(BikeShopWindow.new(game, pitch, function(bought)
|
|
local function comeAgain()
|
|
say(t._BikeShopComeAgainText, function()
|
|
game.stack:pop() -- the window, still up under the text
|
|
done()
|
|
end)
|
|
end
|
|
if bought then
|
|
-- a million is out of anyone's reach: BikeShopCantAffordText
|
|
say(t._BikeShopCantAffordText, comeAgain)
|
|
else
|
|
comeAgain()
|
|
end
|
|
end))
|
|
-- PrintText BikeShopClerkDoYouLikeItText, then straight into
|
|
-- HandleMenuInput: the box types out and hands over without
|
|
-- waiting, leaving the window's copy of the line on screen
|
|
say(pitch, nil, { auto = { delay = 0 } })
|
|
end)
|
|
end,
|
|
},
|
|
}
|
|
|
|
-- -------------------------------------------------------------------
|
|
-- Fossils (scripts/MtMoonB2F.asm, Museum1F.asm,
|
|
-- CinnabarLabFossilRoom.asm): pick one Mt Moon fossil, revive them at
|
|
-- the Cinnabar lab (the wait is skipped).
|
|
-- -------------------------------------------------------------------
|
|
|
|
-- The Super Nerd (object index 1) claims both fossils and blocks the way
|
|
-- to them. In scripts/MtMoonB2F.asm he isn't a sight-line trainer: his
|
|
-- header carries no range, so MtMoonB2FDefaultScript force-triggers the
|
|
-- battle the instant the player steps onto (13,8) -- the chokepoint tile
|
|
-- to his right -- and reaching for a fossil intercepts you too.
|
|
local function superNerdBeaten(ow)
|
|
local nerd = ow:npcByIndex(1)
|
|
return not nerd or ow:trainerDefeated(nerd)
|
|
end
|
|
|
|
local function engageSuperNerd(game, ow, onDone)
|
|
local nerd = ow:npcByIndex(1)
|
|
if not nerd or ow:trainerDefeated(nerd) then
|
|
if onDone then onDone() end
|
|
return
|
|
end
|
|
nerd:facePlayer(ow.player)
|
|
ow:engageTrainer(nerd, onDone)
|
|
end
|
|
|
|
-- MtMoonB2FMoveSuperNerdScript: player-near-dome coords walk RIGHT then
|
|
-- UP (MoveRight falls through into MoveUp); near-helix walks UP only.
|
|
local function mtMoonNerdWalk(px, py, itemId)
|
|
if (px == 12 and py == 7) or (px == 11 and py == 6) or (px == 12 and py == 5) then
|
|
return { "right", "up" }
|
|
end
|
|
if (px == 13 and py == 7) or (px == 14 and py == 6) or (px == 14 and py == 5) then
|
|
return { "up" }
|
|
end
|
|
return (itemId == "DOME_FOSSIL") and { "right", "up" } or { "up" }
|
|
end
|
|
|
|
-- scripts/MtMoonB2F.asm Dome/Helix fossil text_asm + MoveSuperNerd +
|
|
-- SuperNerdTakesOtherFossil: pick one, hide it, walk the nerd to the
|
|
-- other, "All right. Then this is mine!", hide the other.
|
|
local function mtMoonFossil(itemId, otherName, gotFlag)
|
|
return function(game, ow, npc, done)
|
|
local TextBox = require("src.render.TextBox")
|
|
local t = game.data.text
|
|
local flags = game.save.flags
|
|
if flags.EVENT_GOT_DOME_FOSSIL or flags.EVENT_GOT_HELIX_FOSSIL then
|
|
done()
|
|
return
|
|
end
|
|
if not superNerdBeaten(ow) then
|
|
engageSuperNerd(game, ow, done)
|
|
return
|
|
end
|
|
local want = (itemId == "DOME_FOSSIL")
|
|
and (t._MtMoonB2FDomeFossilYouWantText or "You want the\nDOME FOSSIL?")
|
|
or (t._MtMoonB2FHelixFossilYouWantText or "You want the\nHELIX FOSSIL?")
|
|
game.stack:push(TextBox.new(game, want, nil, { choice = function(yes)
|
|
if not yes then done() return end
|
|
if not require("src.inventory.Bag").add(game.save, itemId, 1) then
|
|
game.stack:push(TextBox.new(game,
|
|
t._MtMoonB2FYouHaveNoRoomText or "Look, you've got\nno room for this.",
|
|
done))
|
|
return
|
|
end
|
|
local idef = game.data.items[itemId]
|
|
game.stringBuffer = idef and idef.name or itemId
|
|
require("src.core.Sound").play(game.data, "Get_Key_Item")
|
|
local dirs = mtMoonNerdWalk(ow.player.cellX, ow.player.cellY, itemId)
|
|
game.stack:push(TextBox.new(game,
|
|
t._MtMoonB2FReceivedFossilText
|
|
or ("{PLAYER} got the\n" .. game.stringBuffer .. "!"),
|
|
function()
|
|
local Commands = require("src.script.Commands")
|
|
Commands.hide_object(
|
|
{ save = game.save, overworld = ow, game = game },
|
|
"MT_MOON_B2F", npc.def.name)
|
|
flags[gotFlag] = true
|
|
ow.runner:run({
|
|
{ "walk_npc", 1, dirs },
|
|
{ "text_opts", { auto = true } },
|
|
{ "show_text", "_MtMoonB2FSuperNerdThenThisIsMineText" },
|
|
{ "play_sound", "Get_Key_Item" },
|
|
{ "hide_object", "MT_MOON_B2F", otherName },
|
|
}, { onDone = done })
|
|
end))
|
|
end }))
|
|
end
|
|
end
|
|
|
|
M.MT_MOON_B2F = {
|
|
-- MtMoonB2FDefaultScript forces the Super Nerd battle when the player
|
|
-- steps onto (13,8), the tile beside him guarding the fossils.
|
|
onStep = function(game, ow, x, y)
|
|
if x == 13 and y == 8 and not superNerdBeaten(ow) then
|
|
engageSuperNerd(game, ow, nil)
|
|
return true
|
|
end
|
|
return false
|
|
end,
|
|
talk = {
|
|
TEXT_MTMOONB2F_DOME_FOSSIL = mtMoonFossil(
|
|
"DOME_FOSSIL", "MTMOONB2F_HELIX_FOSSIL", "EVENT_GOT_DOME_FOSSIL"),
|
|
TEXT_MTMOONB2F_HELIX_FOSSIL = mtMoonFossil(
|
|
"HELIX_FOSSIL", "MTMOONB2F_DOME_FOSSIL", "EVENT_GOT_HELIX_FOSSIL"),
|
|
},
|
|
}
|
|
|
|
-- The ticket clerk (scripts/Museum1F.asm Museum1FScientist1Text):
|
|
-- Y50, once. Declining at the rope shoves the player one tile SOUTH back off
|
|
-- the exhibit rope they crossed heading north (#151); the museum floor has no
|
|
-- ledges, so a plain scriptMove("down",1) is the correct primitive.
|
|
local function museumClerk(game, ow, done, onDecline)
|
|
local TextBox = require("src.render.TextBox")
|
|
local ChoiceBox = require("src.ui.ChoiceBox")
|
|
if game.save.flags.EVENT_BOUGHT_MUSEUM_TICKET then
|
|
game.stack:push(TextBox.new(game,
|
|
"Take your time,\nand enjoy it all!", done))
|
|
return
|
|
end
|
|
game.stack:push(TextBox.new(game,
|
|
"It's ¥50 for a\nchild's ticket.\fWould you like to\ncome in?", function()
|
|
game.stack:push(ChoiceBox.new(game, function(yes)
|
|
if yes and game.save.money >= 50 then
|
|
game.save.money = game.save.money - 50
|
|
game.save.flags.EVENT_BOUGHT_MUSEUM_TICKET = true
|
|
game.stack:push(TextBox.new(game,
|
|
"Right, ¥50!\nThank you!", done))
|
|
elseif yes then
|
|
game.stack:push(TextBox.new(game,
|
|
"You don't have\nenough money.", onDecline or done))
|
|
else
|
|
game.stack:push(TextBox.new(game,
|
|
"Come again!", onDecline or done))
|
|
end
|
|
end))
|
|
end))
|
|
end
|
|
|
|
M.MUSEUM_1F = {
|
|
-- crossing the rope at (9,4)/(10,4) without a ticket calls the clerk
|
|
-- over (Museum1FDefaultScript's coordinate check)
|
|
onStep = function(game, ow, x, y)
|
|
if y == 4 and (x == 9 or x == 10)
|
|
and not game.save.flags.EVENT_BOUGHT_MUSEUM_TICKET then
|
|
museumClerk(game, ow, nil, function()
|
|
ow:scriptMove(ow.player, "down", 1)
|
|
end)
|
|
return true
|
|
end
|
|
return false
|
|
end,
|
|
talk = {
|
|
TEXT_MUSEUM1F_SCIENTIST1 = function(game, ow, npc, done)
|
|
museumClerk(game, ow, done)
|
|
end,
|
|
-- The OLD AMBER display object is plain flavor; the scientist
|
|
-- (TEXT_MUSEUM1F_SCIENTIST2, data/scripts/flavor/museum_1f.lua) is who
|
|
-- hands it over and hides this object, per scripts/Museum1F.asm.
|
|
TEXT_MUSEUM1F_OLD_AMBER = function(game, ow, npc, done)
|
|
local TextBox = require("src.render.TextBox")
|
|
game.stack:push(TextBox.new(game,
|
|
game.data.text._Museum1FOldAmberText or "The OLD AMBER.", done))
|
|
end,
|
|
},
|
|
}
|
|
|
|
local FOSSIL_MONS = {
|
|
HELIX_FOSSIL = "OMANYTE", DOME_FOSSIL = "KABUTO", OLD_AMBER = "AERODACTYL",
|
|
}
|
|
|
|
-- Deterministic scan order mirrors FossilsList (scripts/
|
|
-- CinnabarLabFossilRoom.asm lines 43-47): DOME_FOSSIL, HELIX_FOSSIL,
|
|
-- OLD_AMBER (the old pairs()-order loop this replaced was undefined).
|
|
local FOSSIL_ORDER = { "DOME_FOSSIL", "HELIX_FOSSIL", "OLD_AMBER" }
|
|
|
|
-- fills the {PLAYER}/{RAM:...} placeholders in the extracted text
|
|
-- verbatim (text/CinnabarLabFossilRoom.asm); TextBox itself only knows
|
|
-- how to substitute {RAM:wStringBuffer}, so this has to happen first.
|
|
-- RAM placeholders resolve by buffer name from subs (SeesFossilText
|
|
-- reads both wNameBuffer and wStringBuffer), falling back to subs.ram.
|
|
local function fillFossilText(s, subs)
|
|
s = s:gsub("{PLAYER}", subs.player or "")
|
|
s = s:gsub("{RAM:([^}]*)}", function(name) return subs[name] or subs.ram or "" end)
|
|
return s
|
|
end
|
|
|
|
M.CINNABAR_LAB_FOSSIL_ROOM = {
|
|
talk = {
|
|
-- Fossil revival quest (scripts/CinnabarLabFossilRoom.asm
|
|
-- CinnabarLabFossilRoomScientist1Text lines 49-99, deposit flow in
|
|
-- engine/events/cinnabar_lab.asm GiveFossilToCinnabarLab): deposit
|
|
-- a fossil -> pending for the rest of this visit -> ready once the
|
|
-- player leaves and re-enters the CINNABAR_ISLAND overworld map
|
|
-- (M.CINNABAR_ISLAND.onEnter in data/scripts/story5.lua clears
|
|
-- EVENT_LAB_STILL_REVIVING_FOSSIL there, mirroring CinnabarIsland.
|
|
-- asm line 6) -> hand over the mon and reset the whole quest so a
|
|
-- second fossil can be deposited later.
|
|
--
|
|
-- The deposit itself follows GiveFossilToCinnabarLab: a bordered
|
|
-- top-left menu of every carried fossil (A/B watched; B backs out),
|
|
-- then SeesFossilText with a Yes/No confirm; both cancel paths
|
|
-- (B on the menu, NO on the confirm) share ComeAgainText.
|
|
TEXT_CINNABARLABFOSSILROOM_SCIENTIST1 = function(game, ow, npc, done)
|
|
local TextBox = require("src.render.TextBox")
|
|
local t = game.data.text
|
|
local f = game.save.flags
|
|
local subs = { player = game.save.player.name }
|
|
|
|
if f.EVENT_GAVE_FOSSIL_TO_LAB then
|
|
if f.EVENT_LAB_STILL_REVIVING_FOSSIL then
|
|
-- .check_done_reviving -> still pending this visit
|
|
game.stack:push(TextBox.new(game,
|
|
t._CinnabarLabFossilRoomScientist1GoForAWalkText or
|
|
"I take a little\ntime!\fYou go for walk a\nlittle while!", done))
|
|
return
|
|
end
|
|
-- STILL_REVIVING was cleared (CINNABAR_ISLAND was reloaded
|
|
-- since the deposit): .done_reviving, lines 72-83
|
|
local species = game.save.labFossilMon
|
|
subs.ram = species and game.data.pokemon[species]
|
|
and game.data.pokemon[species].name or ""
|
|
f.EVENT_LAB_HANDING_OVER_FOSSIL_MON = true
|
|
game.stack:push(TextBox.new(game,
|
|
fillFossilText(
|
|
t._CinnabarLabFossilRoomScientist1FossilIsBackToLifeText or
|
|
"Where were you?\fYour fossil is\nback to life!\fIt was {RAM:x}\nlike I think!",
|
|
subs),
|
|
function()
|
|
if species then
|
|
local Commands = require("src.script.Commands")
|
|
local ctx = { save = game.save, game = game, overworld = ow }
|
|
Commands.give_pokemon(ctx, species, 30)
|
|
if not ctx.lastCheck then
|
|
-- GivePokemon failed (party+box full): pokered's
|
|
-- `jr nc, .done` leaves the quest pending so the
|
|
-- scientist re-offers the mon next visit instead of
|
|
-- destroying it.
|
|
game.stack:push(TextBox.new(game,
|
|
t._BoxIsFullText or "Box is full!", done))
|
|
return
|
|
end
|
|
end
|
|
game.save.labFossilMon = nil
|
|
f.EVENT_GAVE_FOSSIL_TO_LAB = nil
|
|
f.EVENT_LAB_STILL_REVIVING_FOSSIL = nil
|
|
f.EVENT_LAB_HANDING_OVER_FOSSIL_MON = nil
|
|
done()
|
|
end))
|
|
return
|
|
end
|
|
|
|
-- No fossil deposited yet: the intro always plays first (.Text),
|
|
-- then either the fossil-select menu (GiveFossilToCinnabarLab)
|
|
-- or NoFossilsText.
|
|
game.stack:push(TextBox.new(game,
|
|
t._CinnabarLabFossilRoomScientist1Text or
|
|
"Hiya!\fI am important\ndoctor!\fI study here rare\nPOKéMON fossils!\fYou! Have you a\nfossil for me?",
|
|
function()
|
|
-- Lab4Script_GetFossilsInBag: every carried fossil, in
|
|
-- FossilsList order
|
|
local carried = {}
|
|
for _, fossil in ipairs(FOSSIL_ORDER) do
|
|
if (game.save.inventory[fossil] or 0) > 0 then
|
|
carried[#carried + 1] = fossil
|
|
end
|
|
end
|
|
if #carried == 0 then
|
|
game.stack:push(TextBox.new(game,
|
|
t._CinnabarLabFossilRoomScientist1NoFossilsText or
|
|
"No! Is too bad!", done))
|
|
return
|
|
end
|
|
-- .cancelledGivingFossil: B on the menu and NO on the
|
|
-- confirm both land here
|
|
local function comeAgain()
|
|
game.stack:push(TextBox.new(game,
|
|
t._CinnabarLabFossilRoomScientist1ComeAgainText or
|
|
"Aiyah! You come\nagain!", done))
|
|
end
|
|
local items = {}
|
|
for _, fossil in ipairs(carried) do
|
|
items[#items + 1] = {
|
|
label = game.data.items[fossil].name,
|
|
onSelect = function()
|
|
-- LoadFossilItemAndMonName: wNameBuffer = item name,
|
|
-- wStringBuffer = mon name; then .ScientistSeesFossilText
|
|
-- with YesNoChoice (cursor starts on YES)
|
|
local species = FOSSIL_MONS[fossil]
|
|
local def = game.data.pokemon[species]
|
|
subs.wNameBuffer = game.data.items[fossil].name
|
|
subs.wStringBuffer = def and def.name or species
|
|
game.stack:push(TextBox.new(game,
|
|
fillFossilText(
|
|
t._CinnabarLabFossilRoomScientist1SeesFossilText or
|
|
"Oh! That is\n{RAM:wNameBuffer}!\fIt is fossil of\n{RAM:wStringBuffer}, a\nPOKéMON that is\nalready extinct!\fMy Resurrection\nMachine will make\nthat POKéMON live\nagain!",
|
|
subs),
|
|
nil, { choice = function(yes)
|
|
if not yes then comeAgain() return end
|
|
-- YES: TakesFossilText, RemoveItemByID, GoForAWalk2,
|
|
-- SetEvents GAVE_FOSSIL_TO_LAB + STILL_REVIVING
|
|
require("src.inventory.Bag").remove(game.save, fossil, 1)
|
|
game.save.labFossilMon = species
|
|
f.EVENT_GAVE_FOSSIL_TO_LAB = true
|
|
f.EVENT_LAB_STILL_REVIVING_FOSSIL = true
|
|
game.stack:push(TextBox.new(game,
|
|
fillFossilText(
|
|
t._CinnabarLabFossilRoomScientist1TakesFossilText or
|
|
"So! You hurry and\ngive me that!\f{PLAYER} handed\nover {RAM:wNameBuffer}!",
|
|
subs),
|
|
function()
|
|
game.stack:push(TextBox.new(game,
|
|
t._CinnabarLabFossilRoomScientist1GoForAWalkText2 or
|
|
"I take a little\ntime!\fYou go for walk a\nlittle while!", done))
|
|
end))
|
|
end }))
|
|
end,
|
|
}
|
|
end
|
|
-- GiveFossilToCinnabarLab's menu: TextBoxBorder at 0,0
|
|
-- (interior width $d, height 2 per fossil), A|B watched
|
|
local Menu = require("src.ui.Menu")
|
|
game.stack:push(Menu.new(game, items,
|
|
{ tx = 0, ty = 0, tw = 15, onCancel = comeAgain }))
|
|
end))
|
|
end,
|
|
-- the other scientist trades SAILOR: Ponyta -> Seel
|
|
-- (scripts/CinnabarLabFossilRoom.asm TRADE_FOR_SAILOR)
|
|
TEXT_CINNABARLABFOSSILROOM_SCIENTIST2 = {
|
|
{ "face_player" },
|
|
{ "trade", 4, "EVENT_TRADED_PONYTA_FOR_SEEL" },
|
|
},
|
|
},
|
|
}
|
|
|
|
-- -------------------------------------------------------------------
|
|
-- Day-care (scripts/Daycare.asm): the boarded Pokémon earns 1 exp per
|
|
-- step; the fee is ¥100 plus ¥100 per level gained.
|
|
--
|
|
-- #118: do not raise mon.level until a paid retrieve (pokered reverts
|
|
-- wDayCareMonBoxLevel on .leaveMonInDayCare). Fold pending steps into
|
|
-- mon.exp once and clear them so a second talk cannot re-apply the same
|
|
-- walk. Fill {RAM:wNameBuffer}/{RAM:wDayCareMonName}/{NUM:...} here --
|
|
-- TextBox.TOKENS.RAM only knows wStringBuffer.
|
|
-- -------------------------------------------------------------------
|
|
|
|
local function fillDaycareText(s, subs)
|
|
s = s:gsub("{PLAYER}", subs.player or "")
|
|
s = s:gsub("{RAM:([^}]*)}", function(name) return subs[name] or "" end)
|
|
-- extractor NUM spans may include flags after a comma; keep the name
|
|
s = s:gsub("{NUM:([%w_]+)[^}]*}", function(name)
|
|
return tostring(subs[name] or "0")
|
|
end)
|
|
return s
|
|
end
|
|
|
|
M.DAYCARE = {
|
|
talk = {
|
|
TEXT_DAYCARE_GENTLEMAN = function(game, ow, npc, done)
|
|
local TextBox = require("src.render.TextBox")
|
|
local t = game.data.text
|
|
local dc = game.save.daycare
|
|
local playerName = game.save.player and game.save.player.name or "RED"
|
|
|
|
local function monName(mon)
|
|
local def = game.data.pokemon[mon.species]
|
|
return mon.nickname or (def and def.name) or mon.species
|
|
end
|
|
|
|
if dc and dc.mon then
|
|
local Growth = require("src.pokemon.Growth")
|
|
local Stats = require("src.pokemon.Stats")
|
|
local Party = require("src.pokemon.Party")
|
|
local mon = dc.mon
|
|
local def = game.data.pokemon[mon.species]
|
|
-- Apply deferred step-exp once (OverworldController only bumps
|
|
-- daycare.steps). Clearing prevents double-count on re-talk.
|
|
mon.exp = (mon.exp or 0) + (dc.steps or 0)
|
|
dc.steps = 0
|
|
-- depositLevel mirrors wDayCareMonBoxLevel: fee baseline that
|
|
-- must survive a declined retrieve. Fall back to mon.level for
|
|
-- older saves that predate the field.
|
|
if dc.depositLevel == nil then dc.depositLevel = mon.level end
|
|
local startLevel = dc.depositLevel
|
|
local newLevel = Growth.levelForExp(def and def.growthRate, mon.exp)
|
|
if newLevel >= 100 then
|
|
newLevel = 100
|
|
if def then
|
|
mon.exp = Growth.expForLevel(def.growthRate, 100)
|
|
end
|
|
end
|
|
local levelsGrown = math.max(0, newLevel - startLevel)
|
|
local fee = 100 + levelsGrown * 100
|
|
local name = monName(mon)
|
|
local subs = {
|
|
player = playerName,
|
|
wNameBuffer = name,
|
|
wDayCareMonName = name,
|
|
wDayCareNumLevelsGrown = levelsGrown,
|
|
wDayCareTotalCost = fee,
|
|
}
|
|
local statusText = levelsGrown > 0
|
|
and (t._DaycareGentlemanMonHasGrownText
|
|
or "Your {RAM:wNameBuffer}\nhas grown a lot!\fBy level, it's\ngrown by {NUM:wDayCareNumLevelsGrown, 1, 3}!\fAren't I great?")
|
|
or (t._DaycareGentlemanMonNeedsMoreTimeText
|
|
or "Back already?\nYour {RAM:wNameBuffer}\nneeds some more\ntime with me.")
|
|
|
|
game.stack:push(TextBox.new(game, fillDaycareText(statusText, subs), function()
|
|
if #game.save.party >= Party.MAX then
|
|
game.stack:push(TextBox.new(game,
|
|
t._DaycareGentlemanNoRoomForMonText
|
|
or "You have no room\nfor this POKéMON!", done))
|
|
return
|
|
end
|
|
game.stack:push(TextBox.new(game,
|
|
fillDaycareText(
|
|
t._DaycareGentlemanOweMoneyText
|
|
or "You owe me ¥{NUM:wDayCareTotalCost, 2 | LEADING_ZEROES | LEFT_ALIGN}\nfor the return\nof this POKéMON.",
|
|
subs),
|
|
nil, { choice = function(yes)
|
|
if not yes then
|
|
-- .leaveMonInDayCare: revert any transient level bump
|
|
mon.level = startLevel
|
|
game.stack:push(TextBox.new(game,
|
|
(t._DaycareGentlemanAllRightThenText or "All right then,\n")
|
|
.. (t._DaycareGentlemanComeAgainText or "come again."),
|
|
done))
|
|
return
|
|
end
|
|
if (game.save.money or 0) < fee then
|
|
mon.level = startLevel
|
|
game.stack:push(TextBox.new(game,
|
|
t._DaycareGentlemanNotEnoughMoneyText
|
|
or "Hey, you don't\nhave enough ¥!", done))
|
|
return
|
|
end
|
|
game.save.money = game.save.money - fee
|
|
mon.level = newLevel
|
|
if def then
|
|
mon.stats = Stats.calc(def, mon.level, mon.dvs, mon.statExp)
|
|
mon.hp = mon.stats.hp
|
|
-- Daycare.asm: WriteMonMoves with wLearningMovesFromDayCare
|
|
local Pokemon = require("src.pokemon.Pokemon")
|
|
Pokemon.learnMovesFromDayCare(
|
|
game.data, mon, def, startLevel, newLevel)
|
|
end
|
|
table.insert(game.save.party, mon)
|
|
game.save.daycare = nil
|
|
game.stack:push(TextBox.new(game,
|
|
t._DaycareGentlemanHeresYourMonText
|
|
or "Thank you! Here's\nyour POKéMON!", function()
|
|
game.stack:push(TextBox.new(game,
|
|
fillDaycareText(
|
|
t._DaycareGentlemanGotMonBackText
|
|
or "{PLAYER} got\n{RAM:wDayCareMonName} back!",
|
|
subs), done))
|
|
end))
|
|
end }))
|
|
end))
|
|
return
|
|
end
|
|
|
|
game.stack:push(TextBox.new(game,
|
|
t._DaycareGentlemanIntroText
|
|
or "I run a DAYCARE.\nWould you like me\nto raise one of\nyour POKéMON?",
|
|
nil, { choice = function(yes)
|
|
if not yes then
|
|
game.stack:push(TextBox.new(game,
|
|
t._DaycareGentlemanComeAgainText or "come again.", done))
|
|
return
|
|
end
|
|
if #game.save.party < 2 then
|
|
game.stack:push(TextBox.new(game,
|
|
t._DaycareGentlemanOnlyHaveOneMonText
|
|
or "You only have one\nPOKéMON with you.", done))
|
|
return
|
|
end
|
|
game.stack:push(TextBox.new(game,
|
|
t._DaycareGentlemanWhichMonText or "Which POKéMON\nshould I raise?",
|
|
function()
|
|
local PartyMenu = require("src.ui.PartyMenu")
|
|
game.stack:push(PartyMenu.new(game, {
|
|
pickOnly = true,
|
|
onSwitch = function(mon)
|
|
for i, m in ipairs(game.save.party) do
|
|
if m == mon then table.remove(game.save.party, i) break end
|
|
end
|
|
local name = monName(mon)
|
|
-- depositLevel = wDayCareMonBoxLevel at deposit time
|
|
game.save.daycare = {
|
|
mon = mon, steps = 0, depositLevel = mon.level,
|
|
}
|
|
game.stack:push(TextBox.new(game,
|
|
fillDaycareText(
|
|
t._DaycareGentlemanWillLookAfterMonText
|
|
or "Fine, I'll look\nafter {RAM:wNameBuffer}\nfor a while.",
|
|
{ player = playerName, wNameBuffer = name }),
|
|
function()
|
|
game.stack:push(TextBox.new(game,
|
|
t._DaycareGentlemanComeSeeMeInAWhileText
|
|
or "Come see me in\na while.", done))
|
|
end))
|
|
end,
|
|
}))
|
|
end))
|
|
end }))
|
|
end,
|
|
},
|
|
}
|
|
|
|
return M
|