Merge pull request #759 from jherediagu/fix/rom-text-statuses-items-evolve

Extend ROM-text messages to statuses, items and the learn/evolve flows
This commit is contained in:
bryanthaboi
2026-08-03 17:23:34 -04:00
committed by GitHub
7 changed files with 191 additions and 99 deletions
+6 -3
View File
@@ -385,8 +385,8 @@ local function displayName(b)
end
-- Apply the "Enemy " prefix to a pre-built message from a module that
-- only knows the raw nickname (Status.beforeMove/residual,
-- TrainerAI.useItem): splice it in before the first name occurrence.
-- only knows the raw nickname (Status.beforeMove/residual): splice it
-- in before the first name occurrence.
local function prefixEnemy(msg, battler)
if battler.isPlayer then return msg end
local s = msg:find(battler.name, 1, true)
@@ -3044,8 +3044,11 @@ function BattleState:executeAction(user, target, action)
-- trainer class AI actions (engine/battle/trainer_ai.asm)
if action.special == "aiItem" then
self.aiUses = (self.aiUses or 1) - 1
-- useItem's messages arrive final: its item line prints the raw
-- nickname on purpose (no "Enemy " in AIPrintItemUseText), so the
-- prefix splice must not touch them.
for _, m in ipairs(TrainerAI.useItem(self, action.item)) do
self:sayNext(prefixEnemy(m, self.enemy))
self:sayNext(m)
end
self:drainNext()
require("src.core.Sound").play(self.data, "Heal_Ailment")
+49 -30
View File
@@ -6,6 +6,7 @@
-- back to the vanilla records, which is bit-identical behavior.
local Strings = require("src.core.Strings")
local romText = require("src.core.RomText")
local Status = {}
@@ -34,8 +35,8 @@ end
-- sentence rather than the noun: "hurt by poison" and "hurt by the burn"
-- decline differently once translated, so a shared fragment cannot be the
-- translatable unit.
local function damageOverTime(template)
return function(battler)
local function damageOverTime(label, template)
return function(battler, _, battle)
local mon = battler.mon
local base = math.max(1, math.floor(mon.stats.hp / 16))
local dmg = base
@@ -44,7 +45,7 @@ local function damageOverTime(template)
battler.toxicCounter = battler.toxicCounter + 1
end
mon.hp = math.max(0, mon.hp - dmg)
return { Strings(template, name(battler)) }
return { romText(battle and battle.data, label, template, name(battler)) }
end
end
@@ -63,53 +64,63 @@ Status.RECORDS = {
id = "SLP", label = "SLP", hudLabel = "SLP",
catchBonus = 25, shakeBonus = 10,
beforeMovePriority = 40,
beforeMove = function(battler)
beforeMove = function(battler, _, battle)
battler.sleepTurns = (battler.sleepTurns or 1) - 1
if battler.sleepTurns <= 0 then
battler.mon.status = nil
return false, { Strings("%s\nwoke up!", name(battler)) } -- wakes, loses the turn
-- wakes, loses the turn
return false, { romText(battle and battle.data, "_WokeUpText",
"%s\nwoke up!", name(battler)) }
end
return false, { Strings("%s\nis fast asleep!", name(battler)) }
return false, { romText(battle and battle.data, "_FastAsleepText",
"%s\nis fast asleep!", name(battler)) }
end,
onInflict = function(battle, target, opts, display)
target.sleepTurns = battle.rng(1, 7)
return { Strings("%s\nfell asleep!", display) }
return { romText(battle.data, "_FellAsleepText",
"%s\nfell asleep!", display) }
end,
},
FRZ = {
id = "FRZ", label = "FRZ", hudLabel = "FRZ",
catchBonus = 25, shakeBonus = 10,
beforeMovePriority = 30,
beforeMove = function(battler)
return false, { Strings("%s\nis frozen solid!", name(battler)) }
beforeMove = function(battler, _, battle)
return false, { romText(battle and battle.data, "_IsFrozenText",
"%s\nis frozen solid!", name(battler)) }
end,
canInflict = function(target) return not hasType(target, "ICE") end,
onInflict = function(_, _, _, display)
return { Strings("%s\nwas frozen solid!", display) }
onInflict = function(battle, _, _, display)
return { romText(battle and battle.data, "_FrozenText",
"%s\nwas frozen solid!", display) }
end,
},
PSN = {
id = "PSN", label = "PSN", hudLabel = "PSN",
catchBonus = 12, shakeBonus = 5,
residual = damageOverTime(Strings.source("%s's\nhurt by poison!")),
residual = damageOverTime("_HurtByPoisonText",
Strings.source("%s's\nhurt by poison!")),
canInflict = function(target) return not hasType(target, "POISON") end,
onInflict = function(_, target, opts, display)
onInflict = function(battle, target, opts, display)
if opts.toxic then
target.toxicCounter = 1
-- _BadlyPoisonedText
return { Strings("%s's\nbadly poisoned!", display) }
return { romText(battle and battle.data, "_BadlyPoisonedText",
"%s's\nbadly poisoned!", display) }
end
return { Strings("%s\nwas poisoned!", display) }
return { romText(battle and battle.data, "_PoisonedText",
"%s\nwas poisoned!", display) }
end,
},
BRN = {
id = "BRN", label = "BRN", hudLabel = "BRN",
catchBonus = 12, shakeBonus = 5,
statPenalty = { stat = "attack", div = 2 },
residual = damageOverTime(Strings.source("%s's\nhurt by the burn!")),
residual = damageOverTime("_HurtByBurnText",
Strings.source("%s's\nhurt by the burn!")),
canInflict = function(target) return not hasType(target, "FIRE") end,
onInflict = function(_, _, _, display)
return { Strings("%s\nwas burned!", display) }
onInflict = function(battle, _, _, display)
return { romText(battle and battle.data, "_BurnedText",
"%s\nwas burned!", display) }
end,
},
PAR = {
@@ -117,10 +128,11 @@ Status.RECORDS = {
catchBonus = 12, shakeBonus = 5,
statPenalty = { stat = "speed", div = 4 },
beforeMovePriority = 10,
beforeMove = function(battler, rng)
beforeMove = function(battler, rng, battle)
-- cp 25 percent / jr nc: fully paralyzed on rand < 63 (63/256)
if rng(0, 255) < 63 then
return false, { Strings("%s's\nfully paralyzed!", name(battler)) }
return false, { romText(battle and battle.data, "_FullyParalyzedText",
"%s's\nfully paralyzed!", name(battler)) }
end
return true, {}
end,
@@ -128,9 +140,10 @@ Status.RECORDS = {
-- ParalyzeEffect_: Electric-type moves can't paralyze Ground-types
return not (opts.moveType == "ELECTRIC" and hasType(target, "GROUND"))
end,
onInflict = function(_, _, _, display)
-- _ParalyzedMayNotAttackText (primary and secondary paralysis)
return { Strings("%s's\nparalyzed! It may\nnot attack!", display) }
onInflict = function(battle, _, _, display)
-- primary and secondary paralysis share this line
return { romText(battle and battle.data, "_ParalyzedMayNotAttackText",
"%s's\nparalyzed! It may\nnot attack!", display) }
end,
},
}
@@ -166,7 +179,8 @@ function Status.beforeMove(battler, rng, battle)
end
if battler.flinched then
battler.flinched = false
return false, { Strings("%s\nflinched!", name(battler)) }
return false, { romText(battle and battle.data, "_FlinchedText",
"%s\nflinched!", name(battler)) }
end
local record = Status.recordFor(battleStatuses(battle), mon.status)
local handler = record and record.beforeMove
@@ -184,23 +198,27 @@ function Status.beforeMove(battler, rng, battle)
end
if battler.boundTurns and battler.boundTurns > 0 then
battler.boundTurns = battler.boundTurns - 1
msgs[#msgs + 1] = Strings("%s\ncan't move!", name(battler))
msgs[#msgs + 1] = romText(battle and battle.data, "_CantMoveText",
"%s\ncan't move!", name(battler))
return false, msgs
end
if battler.disabledTurns then
battler.disabledTurns = battler.disabledTurns - 1
if battler.disabledTurns <= 0 then
battler.disabledTurns, battler.disabledSlot = nil, nil
table.insert(msgs, Strings("%s's\ndisabled no more!", name(battler)))
table.insert(msgs, romText(battle and battle.data, "_DisabledNoMoreText",
"%s's\ndisabled no more!", name(battler)))
end
end
if battler.confusedTurns then
battler.confusedTurns = battler.confusedTurns - 1
if battler.confusedTurns <= 0 then
battler.confusedTurns = nil
table.insert(msgs, Strings("%s\nsnapped out of\nconfusion!", name(battler)))
table.insert(msgs, romText(battle and battle.data, "_ConfusedNoMoreText",
"%s\nsnapped out of\nconfusion!", name(battler)))
else
table.insert(msgs, Strings("%s\nis confused!", name(battler)))
table.insert(msgs, romText(battle and battle.data, "_IsConfusedText",
"%s\nis confused!", name(battler)))
-- cp 50 percent + 1 / jr c: hurt itself on rand >= 128 (128/256)
if rng(0, 255) < 128 then
return false, msgs, true -- hurt itself
@@ -241,7 +259,8 @@ function Status.residual(battler, opponent, battle)
dmg = math.min(dmg, mon.hp)
mon.hp = mon.hp - dmg
opponent.mon.hp = math.min(opponent.mon.stats.hp, opponent.mon.hp + dmg)
table.insert(msgs, Strings("LEECH SEED saps\n%s!", name(battler)))
table.insert(msgs, romText(battle and battle.data, "_HurtByLeechSeedText",
"LEECH SEED saps\n%s!", name(battler)))
end
return msgs
end
+15 -4
View File
@@ -20,9 +20,16 @@
local TypeChart = require("src.battle.TypeChart")
local Strings = require("src.core.Strings")
local romText = require("src.core.RomText")
local TrainerAI = {}
-- pokered's <USER>/<TARGET> text macros print "Enemy " before the
-- enemy mon's nickname (home/text.asm PlaceMoveUsersName)
local function displayName(b)
return b.isPlayer and b.name or ("Enemy " .. b.name)
end
local HEAL_AMOUNT = { POTION = 20, SUPER_POTION = 50, HYPER_POTION = 200 }
local X_STAT = { X_ATTACK = "attack", X_DEFEND = "defense", X_SPEED = "speed" }
@@ -95,12 +102,16 @@ function TrainerAI.switchAction(battle)
return { special = "aiSwitch", index = alive[1] }
end
-- Apply an aiItem action to the enemy battler; returns messages.
-- Apply an aiItem action to the enemy battler; returns messages, already
-- final: the item line prints the raw nickname (AIPrintItemUseText has no
-- "Enemy " prefix in pokered), the stat lines carry it via displayName, so
-- the caller must not run these through prefixEnemy.
function TrainerAI.useItem(battle, item)
local enemy = battle.enemy
local trainerName = battle.trainer.name
local itemName = battle.data.items[item] and battle.data.items[item].name or item
local msgs = { Strings("%s\nused %s!", trainerName, itemName) }
local msgs = { romText(battle.data, "_AIBattleUseItemText",
"%s\nused %s!", trainerName, itemName, enemy.name) }
if item == "FULL_HEAL" then
enemy.mon.status = nil
enemy.toxicCounter = nil
@@ -113,10 +124,10 @@ function TrainerAI.useItem(battle, item)
elseif X_STAT[item] then
local stat = X_STAT[item]
enemy.stages[stat] = math.min(6, (enemy.stages[stat] or 0) + 1)
table.insert(msgs, Strings("%s's\n%s rose!", enemy.name, stat:upper()))
table.insert(msgs, Strings("%s's\n%s rose!", displayName(enemy), stat:upper()))
elseif item == "GUARD_SPEC" then
enemy.mist = true
table.insert(msgs, Strings("%s's\nprotected against\nstat changes!", enemy.name))
table.insert(msgs, Strings("%s's\nprotected against\nstat changes!", displayName(enemy)))
end
return msgs
end
+86 -47
View File
@@ -12,9 +12,19 @@
local Flags = require("src.script.Flags")
local Strings = require("src.core.Strings")
local romText = require("src.core.RomText")
local ItemEffects = {}
local function notTime(data, save)
return romText(data, "_ItemUseNotTimeText",
"OAK: %s!\nThis isn't the\ntime to use that!", save.player.name)
end
local function noEffect(data)
return romText(data, "_ItemUseNoEffectText", "It won't have\nany effect.")
end
local HEAL_AMOUNT = {
POTION = 20, SUPER_POTION = 50, HYPER_POTION = 200,
FRESH_WATER = 50, SODA_POP = 60, LEMONADE = 80,
@@ -82,6 +92,14 @@ local function cureActiveToxic(battle, target)
end
end
-- the per-item cure lines (item_effects.asm .cureStatusAilment picks the
-- text by item id); FULL_RESTORE lands here too when it acts as a cure
local CURE_TEXT = {
ANTIDOTE = "_AntidoteText", BURN_HEAL = "_BurnHealText",
ICE_HEAL = "_IceHealText", AWAKENING = "_AwakeningText",
PARLYZ_HEAL = "_ParlyzHealText", FULL_HEAL = "_FullHealText",
}
-- battle-only stat boosters (engine/items/item_effects.asm ItemUseXStat)
local X_ITEMS = {
X_ATTACK = "attack", X_DEFEND = "defense", X_SPEED = "speed",
@@ -130,8 +148,7 @@ function ItemEffects.use(data, save, itemId, target, battle, moveIndex, ow)
if battle and (VITAMINS[itemId] or STONES[itemId] or itemId == "PP_UP"
or itemId == "RARE_CANDY" or itemId == "COIN_CASE"
or (itemDef and itemDef.machine)) then
return "failed", { Strings("OAK: %s!\nThis isn't the\ntime to use that!",
save.player.name) }
return "failed", { notTime(data, save) }
end
if BALLS[itemId] then
@@ -148,13 +165,14 @@ function ItemEffects.use(data, save, itemId, target, battle, moveIndex, ow)
-- data/scripts/story.lua's snorlaxWake)
local mapId, npc = adjacentSleepingSnorlax(save, ow)
if npc then
return "flute_wake", { data.text._PlayedFluteHadEffectText
or Strings("{PLAYER} played the\nPOKé FLUTE.") },
return "flute_wake", { romText(data, "_PlayedFluteHadEffectText",
"{PLAYER} played the\nPOKé FLUTE.") },
{ mapId = mapId, npc = npc }
end
-- otherwise: play the tune, nothing happens (ItemUsePokeFlute's
-- PlayedFluteNoEffectText branch)
return "flute_field", { Strings("Played the POKé\nFLUTE.\fNow, that's a\ncatchy tune!") }
return "flute_field", { romText(data, "_PlayedFluteNoEffectText",
"Played the POKé\nFLUTE.\fNow, that's a\ncatchy tune!") }
end
local woke = false
local function wake(mon)
@@ -169,17 +187,20 @@ function ItemEffects.use(data, save, itemId, target, battle, moveIndex, ow)
-- WakeUpEntireParty runs on the enemy's bench too
for _, mon in ipairs(battle.enemyParty or {}) do wake(mon) end
if not woke then
return "failed", { Strings("Played the POKé\nFLUTE.\fNow, that's a\ncatchy tune!") }
return "failed", { romText(data, "_PlayedFluteNoEffectText",
"Played the POKé\nFLUTE.\fNow, that's a\ncatchy tune!") }
end
return "flute", { Strings("%s played the\nPOKé FLUTE.", save.player.name),
Strings("All sleeping\nPOKéMON woke up!") }
return "flute", { romText(data, "_PlayedFluteHadEffectText",
"%s played the\nPOKé FLUTE.", save.player.name),
romText(data, "_FluteWokeUpText",
"All sleeping\nPOKéMON woke up!") }
end
-- battle-only items
if X_ITEMS[itemId] or itemId == "DIRE_HIT" or itemId == "GUARD_SPEC"
or itemId == "POKE_DOLL" then
if not battle then
return "failed", { Strings("OAK: %s!\nThis isn't the\ntime to use that!", save.player.name) }
return "failed", { notTime(data, save) }
end
local b = battle.player
-- PIKAHAPPY_USEDXITEM (item_effects.asm ItemUseXAccuracy /
@@ -201,7 +222,8 @@ function ItemEffects.use(data, save, itemId, target, battle, moveIndex, ow)
-- effect, so at +6 it is still consumed and StatModifierUpEffect
-- just prints "Nothing happened!"
if cur >= 6 then
return "consumed", { Strings("Nothing happened!") }
return "consumed", { romText(data, "_NothingHappenedText",
"Nothing happened!") }
end
b.stages[stat] = cur + 1
return "consumed", { Strings("%s's\n%s rose!", b.name, stat:upper()) }
@@ -210,7 +232,8 @@ function ItemEffects.use(data, save, itemId, target, battle, moveIndex, ow)
-- the item, even when it is already active
if itemId == "DIRE_HIT" then
b.focusEnergy = true
return "consumed", { Strings("%s's\ngetting pumped!", b.name) }
return "consumed", { romText(data, "_GettingPumpedText",
"%s's\ngetting pumped!", b.name) }
end
if itemId == "GUARD_SPEC" then
b.mist = true
@@ -219,10 +242,10 @@ function ItemEffects.use(data, save, itemId, target, battle, moveIndex, ow)
if itemId == "POKE_DOLL" then
if battle.kind ~= "wild" then
-- ItemUsePokeDoll jumps to ItemUseNotTime in trainer battles
return "failed", { Strings(
"OAK: %s!\nThis isn't the\ntime to use that!", save.player.name) }
return "failed", { notTime(data, save) }
end
return "consumed_escape", { Strings("The wild POKéMON\nran away!") }
return "consumed_escape", { romText(data, "_WildRanText",
"The wild POKéMON\nran away!", battle.enemy and battle.enemy.name) }
end
end
@@ -231,7 +254,7 @@ function ItemEffects.use(data, save, itemId, target, battle, moveIndex, ow)
-- restore every move with no menu.
if itemId == "ETHER" or itemId == "MAX_ETHER"
or itemId == "ELIXER" or itemId == "MAX_ELIXER" then
if not target then return "failed", { Strings("It won't have\nany effect.") } end
if not target then return "failed", { noEffect(data) } end
local restored = false
local full = itemId == "MAX_ETHER" or itemId == "MAX_ELIXER"
local allMoves = itemId == "ELIXER" or itemId == "MAX_ELIXER"
@@ -253,9 +276,10 @@ function ItemEffects.use(data, save, itemId, target, battle, moveIndex, ow)
restored = mv and restore(mv) or false
end
if not restored then
return "failed", { Strings("It won't have\nany effect.") }
return "failed", { noEffect(data) }
end
return "consumed", { Strings("%s's PP\nwas restored!", monName(data, target)) }
-- pokered's line names no mon, so the extracted text takes no args
return "consumed", { romText(data, "_PPRestoredText", "PP was restored.") }
end
-- PIKAHAPPY_USEDITEM (item_effects.asm ItemUseMedicine, item id up to
@@ -280,10 +304,11 @@ function ItemEffects.use(data, save, itemId, target, battle, moveIndex, ow)
target.status = nil
cureActiveToxic(battle, target)
require("src.core.Sound").play(data, "Heal_Ailment")
return "consumed", { Strings("%s's\nstatus returned\nto normal!", monName(data, target)) }
return "consumed", { romText(data, CURE_TEXT.FULL_HEAL,
"%s's\nstatus returned\nto normal!", monName(data, target)) }
end
if not target or target.hp <= 0 or target.hp >= target.stats.hp then
return "failed", { Strings("It won't have\nany effect.") }
return "failed", { noEffect(data) }
end
-- wHPBarOldHP: the bar animation starts from the HP the mon had BEFORE
-- the item landed (item_effects.asm latches it with the party menu still
@@ -295,7 +320,10 @@ function ItemEffects.use(data, save, itemId, target, battle, moveIndex, ow)
else
target.hp = math.min(target.stats.hp, target.hp + heal)
end
local msgs = { Strings("%s's HP\nwas restored!", monName(data, target)) }
-- _PotionText's second slot is the recovered amount ({NUM:
-- wHPBarHPDifference}); the engine fallback never prints it
local msgs = { romText(data, "_PotionText", "%s's HP\nwas restored!",
monName(data, target), target.hp - before) }
if itemId == "FULL_RESTORE" then
target.status = nil
cureActiveToxic(battle, target)
@@ -307,17 +335,18 @@ function ItemEffects.use(data, save, itemId, target, battle, moveIndex, ow)
local cures = STATUS_HEAL[itemId]
if cures then
if not target or not target.status or not cures[target.status] then
return "failed", { Strings("It won't have\nany effect.") }
return "failed", { noEffect(data) }
end
target.status = nil
cureActiveToxic(battle, target)
require("src.core.Sound").play(data, "Heal_Ailment")
return "consumed", { Strings("%s's\nstatus returned\nto normal!", monName(data, target)) }
return "consumed", { romText(data, CURE_TEXT[itemId],
"%s's\nstatus returned\nto normal!", monName(data, target)) }
end
if itemId == "REVIVE" or itemId == "MAX_REVIVE" then
if not target or target.hp > 0 then
return "failed", { Strings("It won't have\nany effect.") }
return "failed", { noEffect(data) }
end
target.status = nil
target.hp = itemId == "REVIVE" and math.floor(target.stats.hp / 2) or target.stats.hp
@@ -329,13 +358,14 @@ function ItemEffects.use(data, save, itemId, target, battle, moveIndex, ow)
if battle and battle.participants then
battle.participants[target] = true
end
return "consumed", { Strings("%s\nis revitalized!", monName(data, target)) },
return "consumed", { romText(data, "_ReviveText",
"%s\nis revitalized!", monName(data, target)) },
{ healedFrom = 0 }
end
if itemId == "RARE_CANDY" then
if not target or target.level >= 100 then
return "failed", { Strings("It won't have\nany effect.") }
return "failed", { noEffect(data) }
end
local Growth = require("src.pokemon.Growth")
local Stats = require("src.pokemon.Stats")
@@ -348,12 +378,13 @@ function ItemEffects.use(data, save, itemId, target, battle, moveIndex, ow)
-- PIKAHAPPY_LEVELUP on a candy level (item_effects.asm:1540)
require("src.world.PikachuFollower")
.modifyHappiness(save, "LEVELUP", target)
return "consumed", { Strings("%s grew\nto level %d!", monName(data, target), target.level) },
return "consumed", { romText(data, "_RareCandyText",
"%s grew\nto level %d!", monName(data, target), target.level) },
{ leveledTo = target.level }
end
if STONES[itemId] then
if not target then return "failed", { Strings("It won't have\nany effect.") } end
if not target then return "failed", { noEffect(data) } end
-- Yellow's starter Pikachu never evolves: ItemUseEvoStone runs
-- IsThisPartyMonStarterPikachu (OT identity match) before
-- TryEvolvingMon and bails with the voiced cry + RefusingText.
@@ -363,10 +394,8 @@ function ItemEffects.use(data, save, itemId, target, battle, moveIndex, ow)
and target.ot == save.player.name
and target.otId == save.player.id then
require("src.core.Sound").playCry(data, "PIKACHU")
local raw = data.text and data.text._RefusingText
local line = raw and raw:gsub("{RAM:[^}]*}", monName(data, target))
or Strings("%s\nis refusing!", monName(data, target))
return "failed", { line }
return "failed", { romText(data, "_RefusingText",
"%s\nis refusing!", monName(data, target)) }
end
local speciesDef = data.pokemon[target.species]
for _, evo in ipairs(speciesDef.evolutions) do
@@ -374,44 +403,48 @@ function ItemEffects.use(data, save, itemId, target, battle, moveIndex, ow)
return "consumed", nil, { evolveTo = evo.species }
end
end
return "failed", { Strings("It won't have\nany effect.") }
return "failed", { noEffect(data) }
end
-- vitamins: +2560 stat exp, refused at 25600+ (ItemUseVitamin,
-- engine/items/item_effects.asm)
local vitaminStat = VITAMINS[itemId]
if vitaminStat then
if not target then return "failed", { Strings("It won't have\nany effect.") } end
if not target then return "failed", { noEffect(data) } end
target.statExp = target.statExp or {}
local cur = target.statExp[vitaminStat] or 0
if cur >= 25600 then
return "failed", { Strings("It won't have\nany effect.") }
return "failed", { noEffect(data) }
end
target.statExp[vitaminStat] = math.min(65535, cur + 2560)
local Stats = require("src.pokemon.Stats")
target.stats = Stats.calc(data.pokemon[target.species], target.level,
target.dvs, target.statExp)
target.hp = math.min(target.hp, target.stats.hp)
-- _VitaminStatRoseText's slot order is localization-dependent (the
-- Spanish ROM puts the stat before the name), so the extracted line
-- cannot be filled positionally; the engine wording stands
return "consumed", { Strings("%s's %s\nrose!", monName(data, target),
vitaminStat == "hp" and "HP" or vitaminStat:upper()) }
end
-- PP UP boosts the move the player picked (ItemUsePPUp's move menu)
if itemId == "PP_UP" then
if not target then return "failed", { Strings("It won't have\nany effect.") } end
if not target then return "failed", { noEffect(data) } end
local mv = target.moves[moveIndex or 1]
local mdef = mv and data.moves[mv.id]
if mdef and (mv.ppUps or 0) < 3 then
mv.ppUps = (mv.ppUps or 0) + 1
-- each PP UP adds maxPP/5 uses on top of the base maximum
mv.pp = mv.pp + math.floor(mdef.pp / 5)
return "consumed", { Strings("%s's PP\nincreased!", mdef.name) }
return "consumed", { romText(data, "_PPIncreasedText",
"%s's PP\nincreased!", mdef.name) }
end
return "failed", { Strings("It won't have\nany effect.") }
return "failed", { noEffect(data) }
end
if itemDef and itemDef.machine then
if not target then return "failed", { Strings("It won't have\nany effect.") } end
if not target then return "failed", { noEffect(data) } end
local speciesDef = data.pokemon[target.species]
local ok = false
for _, m in ipairs(speciesDef.tmhm) do
@@ -422,11 +455,16 @@ function ItemEffects.use(data, save, itemId, target, battle, moveIndex, ow)
-- plays SFX_DENIED before MonCannotLearnMachineMoveText (the generic
-- ItemUseNotTime/NoCyclingAllowedHere paths are silent)
require("src.core.Sound").play(data, "Denied")
return "failed", { Strings("%s can't\nlearn that move!", monName(data, target)) }
local moveName = data.moves[itemDef.machine.move].name
return "failed", { romText(data, "_MonCannotLearnMachineMoveText",
"%s can't\nlearn that move!",
monName(data, target), moveName, moveName) }
end
for _, mv in ipairs(target.moves) do
if mv.id == itemDef.machine.move then
return "failed", { Strings("It knows that\nmove already!") }
return "failed", { romText(data, "_AlreadyKnowsText",
"It knows that\nmove already!",
monName(data, target), data.moves[itemDef.machine.move].name) }
end
end
-- HMs are never consumed; TMs are single-use
@@ -435,21 +473,21 @@ function ItemEffects.use(data, save, itemId, target, battle, moveIndex, ow)
if itemId == "OLD_ROD" or itemId == "GOOD_ROD" or itemId == "SUPER_ROD" then
if battle then
return "failed", { Strings("OAK: %s!\nThis isn't the\ntime to use that!", save.player.name) }
return "failed", { notTime(data, save) }
end
-- FishingInit (engine/items/item_effects.asm): cp wWalkBikeSurfState, 2
-- (surfing) sets carry, and every ItemUseXRod does jp c, ItemUseNotTime
-- on that carry -- surfing refuses the rod with the same OAK text as
-- the mid-battle case above, no rod-specific message (#533)
if ow and ow.player and ow.player.surfing then
return "failed", { Strings("OAK: %s!\nThis isn't the\ntime to use that!", save.player.name) }
return "failed", { notTime(data, save) }
end
return "fish", itemId
end
if itemId == "BICYCLE" then
if battle then
return "failed", { Strings("OAK: %s!\nThis isn't the\ntime to use that!", save.player.name) }
return "failed", { notTime(data, save) }
end
return "bicycle"
end
@@ -459,18 +497,19 @@ function ItemEffects.use(data, save, itemId, target, battle, moveIndex, ow)
end
if itemId == "TOWN_MAP" then
if battle then
return "failed", { Strings("OAK: %s!\nThis isn't the\ntime to use that!", save.player.name) }
return "failed", { notTime(data, save) }
end
return "townmap"
end
if itemId == "ITEMFINDER" then
if battle then
return "failed", { Strings("OAK: %s!\nThis isn't the\ntime to use that!", save.player.name) }
return "failed", { notTime(data, save) }
end
return "itemfinder"
end
if itemId == "COIN_CASE" then
return "failed", { Strings("Coin count:\n%d", save.coins or 0) }
return "failed", { romText(data, "_CoinCaseNumCoinsText",
"Coin count:\n%d", save.coins or 0) }
end
if itemId == "REPEL" or itemId == "SUPER_REPEL" or itemId == "MAX_REPEL" then
local steps = itemId == "REPEL" and 100 or itemId == "SUPER_REPEL" and 200 or 250
@@ -478,7 +517,7 @@ function ItemEffects.use(data, save, itemId, target, battle, moveIndex, ow)
return "consumed", { Strings("%s used\n%s!", save.player.name, name) }
end
return "failed", { Strings("OAK: %s!\nThis isn't the\ntime to use that!", save.player.name) }
return "failed", { notTime(data, save) }
end
return ItemEffects
+9 -3
View File
@@ -15,6 +15,7 @@ local Screens = require("src.ui.Screens")
local Stats = require("src.pokemon.Stats")
local TextBox = require("src.render.TextBox")
local Strings = require("src.core.Strings")
local romText = require("src.core.RomText")
local Evolution = {}
@@ -140,7 +141,8 @@ function Evolution.learnEvolutionMoves(game, mon, onDone)
table.insert(mon.moves, { id = moveId, pp = mdef.pp })
Runtime.emit("pokemon.move_learned", { mon = mon, moveId = moveId })
game.stack:push(TextBox.new(game,
Strings("%s learned\n%s!", name, mdef.name), nextStep))
romText(game.data, "_LearnedMove1Text",
"%s learned\n%s!", name, mdef.name), nextStep))
else
-- LearnMoveFromLevelUp with a full moveset: the forget UI
Screens.push(game, "MoveLearnMenu", mon, moveId, nextStep)
@@ -161,8 +163,12 @@ function Evolution.evolve(game, mon, newSpecies, onDone, via)
Music.play(game.data, Music.special(game.data, "evolution"))
local oldName = mon.nickname or game.data.pokemon[mon.species].name
Evolution.apply(game, mon, newSpecies, via)
local msg = Strings("What?\n%s is\nevolving!\fCongratulations!\nYour %s\nevolved into\n%s!",
oldName, oldName, game.data.pokemon[newSpecies].name)
-- the congrats page keeps the engine wording: _EvolvedText extracts
-- truncated (it stops at a dynamic marker the decoder does not follow)
local msg = romText(game.data, "_IsEvolvingText",
"What?\n%s is\nevolving!", oldName)
.. "\f" .. Strings("Congratulations!\nYour %s\nevolved into\n%s!",
oldName, game.data.pokemon[newSpecies].name)
game.stack:push(TextBox.new(game, msg, function()
Music.restoreMap(game.data)
-- re-run the evolved species' level-up learn check before onDone
+5 -2
View File
@@ -12,6 +12,7 @@
local Font = require("src.render.Font")
local Music = require("src.core.Music")
local Strings = require("src.core.Strings")
local romText = require("src.core.RomText")
local EvolutionState = {}
EvolutionState.__index = EvolutionState
@@ -89,9 +90,9 @@ function EvolutionState:update(dt)
self.done = true
self.canceled = true
local TextBox = require("src.render.TextBox")
-- mirrors data/generated/text.lua _StoppedEvolvingText
game.stack:push(TextBox.new(game,
Strings("Huh? %s\nstopped evolving!", self.oldName),
romText(game.data, "_StoppedEvolvingText",
"Huh? %s\nstopped evolving!", self.oldName),
function()
Music.restoreMap(game.data)
game.stack:pop() -- the evolution screen itself
@@ -106,6 +107,8 @@ function EvolutionState:update(dt)
require("src.core.Sound").playCry(game.data, self.newSpecies)
local TextBox = require("src.render.TextBox")
local newName = game.data.pokemon[self.newSpecies].name
-- _EvolvedText extracts truncated (it stops at a dynamic marker the
-- decoder does not follow), so the engine's wording stands here
game.stack:push(TextBox.new(game,
Strings("Congratulations!\nYour %s\nevolved into\n%s!",
self.oldName, newName),
+21 -10
View File
@@ -5,6 +5,7 @@
local Font = require("src.render.Font")
local Strings = require("src.core.Strings")
local romText = require("src.core.RomText")
local MoveLearnMenu = {}
MoveLearnMenu.__index = MoveLearnMenu
@@ -43,10 +44,13 @@ function MoveLearnMenu:enter()
local mdef = game.data.moves[self.newMoveId]
local name = self:monName()
self.selecting = false
-- _TryingToLearnText is the whole exchange in pokered, delete prompt
-- included, so the extracted line carries all four slots at once
game.stack:push(TextBox.new(game,
Strings("%s is\ntrying to learn\v%s!\fBut, %s\ncan't learn more\vthan 4 moves!\f",
name, mdef.name, name) ..
Strings("Delete an older\nmove to make room\vfor %s?", mdef.name),
romText(game.data, "_TryingToLearnText",
"%s is\ntrying to learn\v%s!\fBut, %s\ncan't learn more\vthan 4 moves!\f"
.. "Delete an older\nmove to make room\vfor %s?",
name, mdef.name, name, mdef.name),
nil, {
choice = function(yes)
if yes then
@@ -77,7 +81,8 @@ function MoveLearnMenu:update(dt)
-- HMCantDeleteText, then back to the forget list
local TextBox = require("src.render.TextBox")
self.game.stack:push(TextBox.new(self.game,
Strings("HM techniques\ncan't be deleted!")))
romText(self.game.data, "_HMCantDeleteText",
"HM techniques\ncan't be deleted!")))
return
end
local mdef = self.game.data.moves[self.newMoveId]
@@ -97,7 +102,8 @@ function MoveLearnMenu:confirmAbandon()
local mdef = game.data.moves[self.newMoveId]
self.selecting = false
game.stack:push(TextBox.new(game,
Strings("Abandon learning\n%s?", mdef.name), nil, {
romText(game.data, "_AbandonLearningText",
"Abandon learning\n%s?", mdef.name), nil, {
choice = function(yes)
if yes then self:finish(false) else self:enter() end
end,
@@ -113,12 +119,17 @@ function MoveLearnMenu:finish(learned)
game.stack:pop()
local msg
if learned then
-- OneTwoAndText/PoofText/ForgotAndText
msg = Strings("1, 2 and... Poof!\f%s forgot\n%s!\fAnd...\f%s learned\n%s!",
name, self.forgot, name, mdef.name)
-- pokered pages this as four texts in a row; _ForgotAndText carries
-- the "And..." tail
msg = romText(game.data, "_OneTwoAndText", "1, 2 and...")
.. romText(game.data, "_PoofText", " Poof!")
.. romText(game.data, "_ForgotAndText",
"\f%s forgot\n%s!\fAnd...", name, self.forgot)
.. "\f" .. romText(game.data, "_LearnedMove1Text",
"%s learned\n%s!", name, mdef.name)
else
-- DidNotLearnText
msg = Strings("%s\ndid not learn\v%s!", name, mdef.name)
msg = romText(game.data, "_DidNotLearnText",
"%s\ndid not learn\v%s!", name, mdef.name)
end
game.stack:push(TextBox.new(game, msg, function()
if self.onDone then self.onDone(learned) end