Extend ROM-text messages to statuses, items and the learn/evolve flows

This commit is contained in:
Juan Heredia
2026-08-03 22:54:08 +02:00
parent b831076839
commit a2bf08c6ff
7 changed files with 191 additions and 99 deletions
+6 -3
View File
@@ -385,8 +385,8 @@ local function displayName(b)
end end
-- Apply the "Enemy " prefix to a pre-built message from a module that -- Apply the "Enemy " prefix to a pre-built message from a module that
-- only knows the raw nickname (Status.beforeMove/residual, -- only knows the raw nickname (Status.beforeMove/residual): splice it
-- TrainerAI.useItem): splice it in before the first name occurrence. -- in before the first name occurrence.
local function prefixEnemy(msg, battler) local function prefixEnemy(msg, battler)
if battler.isPlayer then return msg end if battler.isPlayer then return msg end
local s = msg:find(battler.name, 1, true) 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) -- trainer class AI actions (engine/battle/trainer_ai.asm)
if action.special == "aiItem" then if action.special == "aiItem" then
self.aiUses = (self.aiUses or 1) - 1 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 for _, m in ipairs(TrainerAI.useItem(self, action.item)) do
self:sayNext(prefixEnemy(m, self.enemy)) self:sayNext(m)
end end
self:drainNext() self:drainNext()
require("src.core.Sound").play(self.data, "Heal_Ailment") 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. -- back to the vanilla records, which is bit-identical behavior.
local Strings = require("src.core.Strings") local Strings = require("src.core.Strings")
local romText = require("src.core.RomText")
local Status = {} local Status = {}
@@ -34,8 +35,8 @@ end
-- sentence rather than the noun: "hurt by poison" and "hurt by the burn" -- sentence rather than the noun: "hurt by poison" and "hurt by the burn"
-- decline differently once translated, so a shared fragment cannot be the -- decline differently once translated, so a shared fragment cannot be the
-- translatable unit. -- translatable unit.
local function damageOverTime(template) local function damageOverTime(label, template)
return function(battler) return function(battler, _, battle)
local mon = battler.mon local mon = battler.mon
local base = math.max(1, math.floor(mon.stats.hp / 16)) local base = math.max(1, math.floor(mon.stats.hp / 16))
local dmg = base local dmg = base
@@ -44,7 +45,7 @@ local function damageOverTime(template)
battler.toxicCounter = battler.toxicCounter + 1 battler.toxicCounter = battler.toxicCounter + 1
end end
mon.hp = math.max(0, mon.hp - dmg) 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
end end
@@ -63,53 +64,63 @@ Status.RECORDS = {
id = "SLP", label = "SLP", hudLabel = "SLP", id = "SLP", label = "SLP", hudLabel = "SLP",
catchBonus = 25, shakeBonus = 10, catchBonus = 25, shakeBonus = 10,
beforeMovePriority = 40, beforeMovePriority = 40,
beforeMove = function(battler) beforeMove = function(battler, _, battle)
battler.sleepTurns = (battler.sleepTurns or 1) - 1 battler.sleepTurns = (battler.sleepTurns or 1) - 1
if battler.sleepTurns <= 0 then if battler.sleepTurns <= 0 then
battler.mon.status = nil 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 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, end,
onInflict = function(battle, target, opts, display) onInflict = function(battle, target, opts, display)
target.sleepTurns = battle.rng(1, 7) target.sleepTurns = battle.rng(1, 7)
return { Strings("%s\nfell asleep!", display) } return { romText(battle.data, "_FellAsleepText",
"%s\nfell asleep!", display) }
end, end,
}, },
FRZ = { FRZ = {
id = "FRZ", label = "FRZ", hudLabel = "FRZ", id = "FRZ", label = "FRZ", hudLabel = "FRZ",
catchBonus = 25, shakeBonus = 10, catchBonus = 25, shakeBonus = 10,
beforeMovePriority = 30, beforeMovePriority = 30,
beforeMove = function(battler) beforeMove = function(battler, _, battle)
return false, { Strings("%s\nis frozen solid!", name(battler)) } return false, { romText(battle and battle.data, "_IsFrozenText",
"%s\nis frozen solid!", name(battler)) }
end, end,
canInflict = function(target) return not hasType(target, "ICE") end, canInflict = function(target) return not hasType(target, "ICE") end,
onInflict = function(_, _, _, display) onInflict = function(battle, _, _, display)
return { Strings("%s\nwas frozen solid!", display) } return { romText(battle and battle.data, "_FrozenText",
"%s\nwas frozen solid!", display) }
end, end,
}, },
PSN = { PSN = {
id = "PSN", label = "PSN", hudLabel = "PSN", id = "PSN", label = "PSN", hudLabel = "PSN",
catchBonus = 12, shakeBonus = 5, 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, canInflict = function(target) return not hasType(target, "POISON") end,
onInflict = function(_, target, opts, display) onInflict = function(battle, target, opts, display)
if opts.toxic then if opts.toxic then
target.toxicCounter = 1 target.toxicCounter = 1
-- _BadlyPoisonedText return { romText(battle and battle.data, "_BadlyPoisonedText",
return { Strings("%s's\nbadly poisoned!", display) } "%s's\nbadly poisoned!", display) }
end end
return { Strings("%s\nwas poisoned!", display) } return { romText(battle and battle.data, "_PoisonedText",
"%s\nwas poisoned!", display) }
end, end,
}, },
BRN = { BRN = {
id = "BRN", label = "BRN", hudLabel = "BRN", id = "BRN", label = "BRN", hudLabel = "BRN",
catchBonus = 12, shakeBonus = 5, catchBonus = 12, shakeBonus = 5,
statPenalty = { stat = "attack", div = 2 }, 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, canInflict = function(target) return not hasType(target, "FIRE") end,
onInflict = function(_, _, _, display) onInflict = function(battle, _, _, display)
return { Strings("%s\nwas burned!", display) } return { romText(battle and battle.data, "_BurnedText",
"%s\nwas burned!", display) }
end, end,
}, },
PAR = { PAR = {
@@ -117,10 +128,11 @@ Status.RECORDS = {
catchBonus = 12, shakeBonus = 5, catchBonus = 12, shakeBonus = 5,
statPenalty = { stat = "speed", div = 4 }, statPenalty = { stat = "speed", div = 4 },
beforeMovePriority = 10, beforeMovePriority = 10,
beforeMove = function(battler, rng) beforeMove = function(battler, rng, battle)
-- cp 25 percent / jr nc: fully paralyzed on rand < 63 (63/256) -- cp 25 percent / jr nc: fully paralyzed on rand < 63 (63/256)
if rng(0, 255) < 63 then 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 end
return true, {} return true, {}
end, end,
@@ -128,9 +140,10 @@ Status.RECORDS = {
-- ParalyzeEffect_: Electric-type moves can't paralyze Ground-types -- ParalyzeEffect_: Electric-type moves can't paralyze Ground-types
return not (opts.moveType == "ELECTRIC" and hasType(target, "GROUND")) return not (opts.moveType == "ELECTRIC" and hasType(target, "GROUND"))
end, end,
onInflict = function(_, _, _, display) onInflict = function(battle, _, _, display)
-- _ParalyzedMayNotAttackText (primary and secondary paralysis) -- primary and secondary paralysis share this line
return { Strings("%s's\nparalyzed! It may\nnot attack!", display) } return { romText(battle and battle.data, "_ParalyzedMayNotAttackText",
"%s's\nparalyzed! It may\nnot attack!", display) }
end, end,
}, },
} }
@@ -166,7 +179,8 @@ function Status.beforeMove(battler, rng, battle)
end end
if battler.flinched then if battler.flinched then
battler.flinched = false battler.flinched = false
return false, { Strings("%s\nflinched!", name(battler)) } return false, { romText(battle and battle.data, "_FlinchedText",
"%s\nflinched!", name(battler)) }
end end
local record = Status.recordFor(battleStatuses(battle), mon.status) local record = Status.recordFor(battleStatuses(battle), mon.status)
local handler = record and record.beforeMove local handler = record and record.beforeMove
@@ -184,23 +198,27 @@ function Status.beforeMove(battler, rng, battle)
end end
if battler.boundTurns and battler.boundTurns > 0 then if battler.boundTurns and battler.boundTurns > 0 then
battler.boundTurns = battler.boundTurns - 1 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 return false, msgs
end end
if battler.disabledTurns then if battler.disabledTurns then
battler.disabledTurns = battler.disabledTurns - 1 battler.disabledTurns = battler.disabledTurns - 1
if battler.disabledTurns <= 0 then if battler.disabledTurns <= 0 then
battler.disabledTurns, battler.disabledSlot = nil, nil 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
end end
if battler.confusedTurns then if battler.confusedTurns then
battler.confusedTurns = battler.confusedTurns - 1 battler.confusedTurns = battler.confusedTurns - 1
if battler.confusedTurns <= 0 then if battler.confusedTurns <= 0 then
battler.confusedTurns = nil 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 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) -- cp 50 percent + 1 / jr c: hurt itself on rand >= 128 (128/256)
if rng(0, 255) < 128 then if rng(0, 255) < 128 then
return false, msgs, true -- hurt itself return false, msgs, true -- hurt itself
@@ -241,7 +259,8 @@ function Status.residual(battler, opponent, battle)
dmg = math.min(dmg, mon.hp) dmg = math.min(dmg, mon.hp)
mon.hp = mon.hp - dmg mon.hp = mon.hp - dmg
opponent.mon.hp = math.min(opponent.mon.stats.hp, opponent.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 end
return msgs return msgs
end end
+15 -4
View File
@@ -20,9 +20,16 @@
local TypeChart = require("src.battle.TypeChart") local TypeChart = require("src.battle.TypeChart")
local Strings = require("src.core.Strings") local Strings = require("src.core.Strings")
local romText = require("src.core.RomText")
local TrainerAI = {} 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 HEAL_AMOUNT = { POTION = 20, SUPER_POTION = 50, HYPER_POTION = 200 }
local X_STAT = { X_ATTACK = "attack", X_DEFEND = "defense", X_SPEED = "speed" } 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] } return { special = "aiSwitch", index = alive[1] }
end 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) function TrainerAI.useItem(battle, item)
local enemy = battle.enemy local enemy = battle.enemy
local trainerName = battle.trainer.name local trainerName = battle.trainer.name
local itemName = battle.data.items[item] and battle.data.items[item].name or item 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 if item == "FULL_HEAL" then
enemy.mon.status = nil enemy.mon.status = nil
enemy.toxicCounter = nil enemy.toxicCounter = nil
@@ -113,10 +124,10 @@ function TrainerAI.useItem(battle, item)
elseif X_STAT[item] then elseif X_STAT[item] then
local stat = X_STAT[item] local stat = X_STAT[item]
enemy.stages[stat] = math.min(6, (enemy.stages[stat] or 0) + 1) 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 elseif item == "GUARD_SPEC" then
enemy.mist = true 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 end
return msgs return msgs
end end
+86 -47
View File
@@ -12,9 +12,19 @@
local Flags = require("src.script.Flags") local Flags = require("src.script.Flags")
local Strings = require("src.core.Strings") local Strings = require("src.core.Strings")
local romText = require("src.core.RomText")
local ItemEffects = {} 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 = { local HEAL_AMOUNT = {
POTION = 20, SUPER_POTION = 50, HYPER_POTION = 200, POTION = 20, SUPER_POTION = 50, HYPER_POTION = 200,
FRESH_WATER = 50, SODA_POP = 60, LEMONADE = 80, FRESH_WATER = 50, SODA_POP = 60, LEMONADE = 80,
@@ -82,6 +92,14 @@ local function cureActiveToxic(battle, target)
end end
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) -- battle-only stat boosters (engine/items/item_effects.asm ItemUseXStat)
local X_ITEMS = { local X_ITEMS = {
X_ATTACK = "attack", X_DEFEND = "defense", X_SPEED = "speed", 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" if battle and (VITAMINS[itemId] or STONES[itemId] or itemId == "PP_UP"
or itemId == "RARE_CANDY" or itemId == "COIN_CASE" or itemId == "RARE_CANDY" or itemId == "COIN_CASE"
or (itemDef and itemDef.machine)) then or (itemDef and itemDef.machine)) then
return "failed", { Strings("OAK: %s!\nThis isn't the\ntime to use that!", return "failed", { notTime(data, save) }
save.player.name) }
end end
if BALLS[itemId] then if BALLS[itemId] then
@@ -148,13 +165,14 @@ function ItemEffects.use(data, save, itemId, target, battle, moveIndex, ow)
-- data/scripts/story.lua's snorlaxWake) -- data/scripts/story.lua's snorlaxWake)
local mapId, npc = adjacentSleepingSnorlax(save, ow) local mapId, npc = adjacentSleepingSnorlax(save, ow)
if npc then if npc then
return "flute_wake", { data.text._PlayedFluteHadEffectText return "flute_wake", { romText(data, "_PlayedFluteHadEffectText",
or Strings("{PLAYER} played the\nPOKé FLUTE.") }, "{PLAYER} played the\nPOKé FLUTE.") },
{ mapId = mapId, npc = npc } { mapId = mapId, npc = npc }
end end
-- otherwise: play the tune, nothing happens (ItemUsePokeFlute's -- otherwise: play the tune, nothing happens (ItemUsePokeFlute's
-- PlayedFluteNoEffectText branch) -- 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 end
local woke = false local woke = false
local function wake(mon) 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 -- WakeUpEntireParty runs on the enemy's bench too
for _, mon in ipairs(battle.enemyParty or {}) do wake(mon) end for _, mon in ipairs(battle.enemyParty or {}) do wake(mon) end
if not woke then 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 end
return "flute", { Strings("%s played the\nPOKé FLUTE.", save.player.name), return "flute", { romText(data, "_PlayedFluteHadEffectText",
Strings("All sleeping\nPOKéMON woke up!") } "%s played the\nPOKé FLUTE.", save.player.name),
romText(data, "_FluteWokeUpText",
"All sleeping\nPOKéMON woke up!") }
end end
-- battle-only items -- battle-only items
if X_ITEMS[itemId] or itemId == "DIRE_HIT" or itemId == "GUARD_SPEC" if X_ITEMS[itemId] or itemId == "DIRE_HIT" or itemId == "GUARD_SPEC"
or itemId == "POKE_DOLL" then or itemId == "POKE_DOLL" then
if not battle 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 end
local b = battle.player local b = battle.player
-- PIKAHAPPY_USEDXITEM (item_effects.asm ItemUseXAccuracy / -- 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 -- effect, so at +6 it is still consumed and StatModifierUpEffect
-- just prints "Nothing happened!" -- just prints "Nothing happened!"
if cur >= 6 then if cur >= 6 then
return "consumed", { Strings("Nothing happened!") } return "consumed", { romText(data, "_NothingHappenedText",
"Nothing happened!") }
end end
b.stages[stat] = cur + 1 b.stages[stat] = cur + 1
return "consumed", { Strings("%s's\n%s rose!", b.name, stat:upper()) } 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 -- the item, even when it is already active
if itemId == "DIRE_HIT" then if itemId == "DIRE_HIT" then
b.focusEnergy = true b.focusEnergy = true
return "consumed", { Strings("%s's\ngetting pumped!", b.name) } return "consumed", { romText(data, "_GettingPumpedText",
"%s's\ngetting pumped!", b.name) }
end end
if itemId == "GUARD_SPEC" then if itemId == "GUARD_SPEC" then
b.mist = true b.mist = true
@@ -219,10 +242,10 @@ function ItemEffects.use(data, save, itemId, target, battle, moveIndex, ow)
if itemId == "POKE_DOLL" then if itemId == "POKE_DOLL" then
if battle.kind ~= "wild" then if battle.kind ~= "wild" then
-- ItemUsePokeDoll jumps to ItemUseNotTime in trainer battles -- ItemUsePokeDoll jumps to ItemUseNotTime in trainer battles
return "failed", { Strings( return "failed", { notTime(data, save) }
"OAK: %s!\nThis isn't the\ntime to use that!", save.player.name) }
end 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
end end
@@ -231,7 +254,7 @@ function ItemEffects.use(data, save, itemId, target, battle, moveIndex, ow)
-- restore every move with no menu. -- restore every move with no menu.
if itemId == "ETHER" or itemId == "MAX_ETHER" if itemId == "ETHER" or itemId == "MAX_ETHER"
or itemId == "ELIXER" or itemId == "MAX_ELIXER" then 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 restored = false
local full = itemId == "MAX_ETHER" or itemId == "MAX_ELIXER" local full = itemId == "MAX_ETHER" or itemId == "MAX_ELIXER"
local allMoves = itemId == "ELIXER" 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 restored = mv and restore(mv) or false
end end
if not restored then if not restored then
return "failed", { Strings("It won't have\nany effect.") } return "failed", { noEffect(data) }
end 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 end
-- PIKAHAPPY_USEDITEM (item_effects.asm ItemUseMedicine, item id up to -- 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 target.status = nil
cureActiveToxic(battle, target) cureActiveToxic(battle, target)
require("src.core.Sound").play(data, "Heal_Ailment") 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 end
if not target or target.hp <= 0 or target.hp >= target.stats.hp then 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 end
-- wHPBarOldHP: the bar animation starts from the HP the mon had BEFORE -- 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 -- 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 else
target.hp = math.min(target.stats.hp, target.hp + heal) target.hp = math.min(target.stats.hp, target.hp + heal)
end 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 if itemId == "FULL_RESTORE" then
target.status = nil target.status = nil
cureActiveToxic(battle, target) cureActiveToxic(battle, target)
@@ -307,17 +335,18 @@ function ItemEffects.use(data, save, itemId, target, battle, moveIndex, ow)
local cures = STATUS_HEAL[itemId] local cures = STATUS_HEAL[itemId]
if cures then if cures then
if not target or not target.status or not cures[target.status] 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 end
target.status = nil target.status = nil
cureActiveToxic(battle, target) cureActiveToxic(battle, target)
require("src.core.Sound").play(data, "Heal_Ailment") 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 end
if itemId == "REVIVE" or itemId == "MAX_REVIVE" then if itemId == "REVIVE" or itemId == "MAX_REVIVE" then
if not target or target.hp > 0 then if not target or target.hp > 0 then
return "failed", { Strings("It won't have\nany effect.") } return "failed", { noEffect(data) }
end end
target.status = nil target.status = nil
target.hp = itemId == "REVIVE" and math.floor(target.stats.hp / 2) or target.stats.hp 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 if battle and battle.participants then
battle.participants[target] = true battle.participants[target] = true
end end
return "consumed", { Strings("%s\nis revitalized!", monName(data, target)) }, return "consumed", { romText(data, "_ReviveText",
"%s\nis revitalized!", monName(data, target)) },
{ healedFrom = 0 } { healedFrom = 0 }
end end
if itemId == "RARE_CANDY" then if itemId == "RARE_CANDY" then
if not target or target.level >= 100 then if not target or target.level >= 100 then
return "failed", { Strings("It won't have\nany effect.") } return "failed", { noEffect(data) }
end end
local Growth = require("src.pokemon.Growth") local Growth = require("src.pokemon.Growth")
local Stats = require("src.pokemon.Stats") 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) -- PIKAHAPPY_LEVELUP on a candy level (item_effects.asm:1540)
require("src.world.PikachuFollower") require("src.world.PikachuFollower")
.modifyHappiness(save, "LEVELUP", target) .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 } { leveledTo = target.level }
end end
if STONES[itemId] then 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 -- Yellow's starter Pikachu never evolves: ItemUseEvoStone runs
-- IsThisPartyMonStarterPikachu (OT identity match) before -- IsThisPartyMonStarterPikachu (OT identity match) before
-- TryEvolvingMon and bails with the voiced cry + RefusingText. -- 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.ot == save.player.name
and target.otId == save.player.id then and target.otId == save.player.id then
require("src.core.Sound").playCry(data, "PIKACHU") require("src.core.Sound").playCry(data, "PIKACHU")
local raw = data.text and data.text._RefusingText return "failed", { romText(data, "_RefusingText",
local line = raw and raw:gsub("{RAM:[^}]*}", monName(data, target)) "%s\nis refusing!", monName(data, target)) }
or Strings("%s\nis refusing!", monName(data, target))
return "failed", { line }
end end
local speciesDef = data.pokemon[target.species] local speciesDef = data.pokemon[target.species]
for _, evo in ipairs(speciesDef.evolutions) do 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 } return "consumed", nil, { evolveTo = evo.species }
end end
end end
return "failed", { Strings("It won't have\nany effect.") } return "failed", { noEffect(data) }
end end
-- vitamins: +2560 stat exp, refused at 25600+ (ItemUseVitamin, -- vitamins: +2560 stat exp, refused at 25600+ (ItemUseVitamin,
-- engine/items/item_effects.asm) -- engine/items/item_effects.asm)
local vitaminStat = VITAMINS[itemId] local vitaminStat = VITAMINS[itemId]
if vitaminStat then 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 {} target.statExp = target.statExp or {}
local cur = target.statExp[vitaminStat] or 0 local cur = target.statExp[vitaminStat] or 0
if cur >= 25600 then if cur >= 25600 then
return "failed", { Strings("It won't have\nany effect.") } return "failed", { noEffect(data) }
end end
target.statExp[vitaminStat] = math.min(65535, cur + 2560) target.statExp[vitaminStat] = math.min(65535, cur + 2560)
local Stats = require("src.pokemon.Stats") local Stats = require("src.pokemon.Stats")
target.stats = Stats.calc(data.pokemon[target.species], target.level, target.stats = Stats.calc(data.pokemon[target.species], target.level,
target.dvs, target.statExp) target.dvs, target.statExp)
target.hp = math.min(target.hp, target.stats.hp) 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), return "consumed", { Strings("%s's %s\nrose!", monName(data, target),
vitaminStat == "hp" and "HP" or vitaminStat:upper()) } vitaminStat == "hp" and "HP" or vitaminStat:upper()) }
end end
-- PP UP boosts the move the player picked (ItemUsePPUp's move menu) -- PP UP boosts the move the player picked (ItemUsePPUp's move menu)
if itemId == "PP_UP" then 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 mv = target.moves[moveIndex or 1]
local mdef = mv and data.moves[mv.id] local mdef = mv and data.moves[mv.id]
if mdef and (mv.ppUps or 0) < 3 then if mdef and (mv.ppUps or 0) < 3 then
mv.ppUps = (mv.ppUps or 0) + 1 mv.ppUps = (mv.ppUps or 0) + 1
-- each PP UP adds maxPP/5 uses on top of the base maximum -- each PP UP adds maxPP/5 uses on top of the base maximum
mv.pp = mv.pp + math.floor(mdef.pp / 5) 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 end
return "failed", { Strings("It won't have\nany effect.") } return "failed", { noEffect(data) }
end end
if itemDef and itemDef.machine then 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 speciesDef = data.pokemon[target.species]
local ok = false local ok = false
for _, m in ipairs(speciesDef.tmhm) do 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 -- plays SFX_DENIED before MonCannotLearnMachineMoveText (the generic
-- ItemUseNotTime/NoCyclingAllowedHere paths are silent) -- ItemUseNotTime/NoCyclingAllowedHere paths are silent)
require("src.core.Sound").play(data, "Denied") 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 end
for _, mv in ipairs(target.moves) do for _, mv in ipairs(target.moves) do
if mv.id == itemDef.machine.move then 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
end end
-- HMs are never consumed; TMs are single-use -- 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 itemId == "OLD_ROD" or itemId == "GOOD_ROD" or itemId == "SUPER_ROD" then
if battle 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 end
-- FishingInit (engine/items/item_effects.asm): cp wWalkBikeSurfState, 2 -- FishingInit (engine/items/item_effects.asm): cp wWalkBikeSurfState, 2
-- (surfing) sets carry, and every ItemUseXRod does jp c, ItemUseNotTime -- (surfing) sets carry, and every ItemUseXRod does jp c, ItemUseNotTime
-- on that carry -- surfing refuses the rod with the same OAK text as -- on that carry -- surfing refuses the rod with the same OAK text as
-- the mid-battle case above, no rod-specific message (#533) -- the mid-battle case above, no rod-specific message (#533)
if ow and ow.player and ow.player.surfing then 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 end
return "fish", itemId return "fish", itemId
end end
if itemId == "BICYCLE" then if itemId == "BICYCLE" then
if battle 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 end
return "bicycle" return "bicycle"
end end
@@ -459,18 +497,19 @@ function ItemEffects.use(data, save, itemId, target, battle, moveIndex, ow)
end end
if itemId == "TOWN_MAP" then if itemId == "TOWN_MAP" then
if battle 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 end
return "townmap" return "townmap"
end end
if itemId == "ITEMFINDER" then if itemId == "ITEMFINDER" then
if battle 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 end
return "itemfinder" return "itemfinder"
end end
if itemId == "COIN_CASE" then 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 end
if itemId == "REPEL" or itemId == "SUPER_REPEL" or itemId == "MAX_REPEL" then 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 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) } return "consumed", { Strings("%s used\n%s!", save.player.name, name) }
end end
return "failed", { Strings("OAK: %s!\nThis isn't the\ntime to use that!", save.player.name) } return "failed", { notTime(data, save) }
end end
return ItemEffects return ItemEffects
+9 -3
View File
@@ -15,6 +15,7 @@ local Screens = require("src.ui.Screens")
local Stats = require("src.pokemon.Stats") local Stats = require("src.pokemon.Stats")
local TextBox = require("src.render.TextBox") local TextBox = require("src.render.TextBox")
local Strings = require("src.core.Strings") local Strings = require("src.core.Strings")
local romText = require("src.core.RomText")
local Evolution = {} local Evolution = {}
@@ -140,7 +141,8 @@ function Evolution.learnEvolutionMoves(game, mon, onDone)
table.insert(mon.moves, { id = moveId, pp = mdef.pp }) table.insert(mon.moves, { id = moveId, pp = mdef.pp })
Runtime.emit("pokemon.move_learned", { mon = mon, moveId = moveId }) Runtime.emit("pokemon.move_learned", { mon = mon, moveId = moveId })
game.stack:push(TextBox.new(game, 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 else
-- LearnMoveFromLevelUp with a full moveset: the forget UI -- LearnMoveFromLevelUp with a full moveset: the forget UI
Screens.push(game, "MoveLearnMenu", mon, moveId, nextStep) 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")) Music.play(game.data, Music.special(game.data, "evolution"))
local oldName = mon.nickname or game.data.pokemon[mon.species].name local oldName = mon.nickname or game.data.pokemon[mon.species].name
Evolution.apply(game, mon, newSpecies, via) Evolution.apply(game, mon, newSpecies, via)
local msg = Strings("What?\n%s is\nevolving!\fCongratulations!\nYour %s\nevolved into\n%s!", -- the congrats page keeps the engine wording: _EvolvedText extracts
oldName, oldName, game.data.pokemon[newSpecies].name) -- 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() game.stack:push(TextBox.new(game, msg, function()
Music.restoreMap(game.data) Music.restoreMap(game.data)
-- re-run the evolved species' level-up learn check before onDone -- 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 Font = require("src.render.Font")
local Music = require("src.core.Music") local Music = require("src.core.Music")
local Strings = require("src.core.Strings") local Strings = require("src.core.Strings")
local romText = require("src.core.RomText")
local EvolutionState = {} local EvolutionState = {}
EvolutionState.__index = EvolutionState EvolutionState.__index = EvolutionState
@@ -89,9 +90,9 @@ function EvolutionState:update(dt)
self.done = true self.done = true
self.canceled = true self.canceled = true
local TextBox = require("src.render.TextBox") local TextBox = require("src.render.TextBox")
-- mirrors data/generated/text.lua _StoppedEvolvingText
game.stack:push(TextBox.new(game, game.stack:push(TextBox.new(game,
Strings("Huh? %s\nstopped evolving!", self.oldName), romText(game.data, "_StoppedEvolvingText",
"Huh? %s\nstopped evolving!", self.oldName),
function() function()
Music.restoreMap(game.data) Music.restoreMap(game.data)
game.stack:pop() -- the evolution screen itself game.stack:pop() -- the evolution screen itself
@@ -106,6 +107,8 @@ function EvolutionState:update(dt)
require("src.core.Sound").playCry(game.data, self.newSpecies) require("src.core.Sound").playCry(game.data, self.newSpecies)
local TextBox = require("src.render.TextBox") local TextBox = require("src.render.TextBox")
local newName = game.data.pokemon[self.newSpecies].name 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, game.stack:push(TextBox.new(game,
Strings("Congratulations!\nYour %s\nevolved into\n%s!", Strings("Congratulations!\nYour %s\nevolved into\n%s!",
self.oldName, newName), self.oldName, newName),
+21 -10
View File
@@ -5,6 +5,7 @@
local Font = require("src.render.Font") local Font = require("src.render.Font")
local Strings = require("src.core.Strings") local Strings = require("src.core.Strings")
local romText = require("src.core.RomText")
local MoveLearnMenu = {} local MoveLearnMenu = {}
MoveLearnMenu.__index = MoveLearnMenu MoveLearnMenu.__index = MoveLearnMenu
@@ -43,10 +44,13 @@ function MoveLearnMenu:enter()
local mdef = game.data.moves[self.newMoveId] local mdef = game.data.moves[self.newMoveId]
local name = self:monName() local name = self:monName()
self.selecting = false 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, game.stack:push(TextBox.new(game,
Strings("%s is\ntrying to learn\v%s!\fBut, %s\ncan't learn more\vthan 4 moves!\f", romText(game.data, "_TryingToLearnText",
name, mdef.name, name) .. "%s is\ntrying to learn\v%s!\fBut, %s\ncan't learn more\vthan 4 moves!\f"
Strings("Delete an older\nmove to make room\vfor %s?", mdef.name), .. "Delete an older\nmove to make room\vfor %s?",
name, mdef.name, name, mdef.name),
nil, { nil, {
choice = function(yes) choice = function(yes)
if yes then if yes then
@@ -77,7 +81,8 @@ function MoveLearnMenu:update(dt)
-- HMCantDeleteText, then back to the forget list -- HMCantDeleteText, then back to the forget list
local TextBox = require("src.render.TextBox") local TextBox = require("src.render.TextBox")
self.game.stack:push(TextBox.new(self.game, 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 return
end end
local mdef = self.game.data.moves[self.newMoveId] local mdef = self.game.data.moves[self.newMoveId]
@@ -97,7 +102,8 @@ function MoveLearnMenu:confirmAbandon()
local mdef = game.data.moves[self.newMoveId] local mdef = game.data.moves[self.newMoveId]
self.selecting = false self.selecting = false
game.stack:push(TextBox.new(game, 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) choice = function(yes)
if yes then self:finish(false) else self:enter() end if yes then self:finish(false) else self:enter() end
end, end,
@@ -113,12 +119,17 @@ function MoveLearnMenu:finish(learned)
game.stack:pop() game.stack:pop()
local msg local msg
if learned then if learned then
-- OneTwoAndText/PoofText/ForgotAndText -- pokered pages this as four texts in a row; _ForgotAndText carries
msg = Strings("1, 2 and... Poof!\f%s forgot\n%s!\fAnd...\f%s learned\n%s!", -- the "And..." tail
name, self.forgot, name, mdef.name) 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 else
-- DidNotLearnText msg = romText(game.data, "_DidNotLearnText",
msg = Strings("%s\ndid not learn\v%s!", name, mdef.name) "%s\ndid not learn\v%s!", name, mdef.name)
end end
game.stack:push(TextBox.new(game, msg, function() game.stack:push(TextBox.new(game, msg, function()
if self.onDone then self.onDone(learned) end if self.onDone then self.onDone(learned) end