diff --git a/data/scripts/init.lua b/data/scripts/init.lua index b6a214c3..42fe74e6 100644 --- a/data/scripts/init.lua +++ b/data/scripts/init.lua @@ -48,7 +48,8 @@ end if GameVersion.isYellow() then for _, file in ipairs({ "data.scripts.yellow_gifts", "data.scripts.yellow_jessie_james", - "data.scripts.yellow_beach_house" }) do + "data.scripts.yellow_beach_house", + "data.scripts.yellow_viridian_old_man" }) do for mapId, mod in pairs(require(file)) do MapScripts.attachBase(mapId, mod) end diff --git a/data/scripts/oaks_lab_yellow.lua b/data/scripts/oaks_lab_yellow.lua index e2c7b158..e3a663a1 100644 --- a/data/scripts/oaks_lab_yellow.lua +++ b/data/scripts/oaks_lab_yellow.lua @@ -71,8 +71,12 @@ return { { "show_text", "_OaksLabRivalLeaveItAllToMeText" }, { "set_flag", "EVENT_GOT_POKEDEX" }, { "set_flag", "EVENT_OAK_GOT_PARCEL" }, + -- OaksLabOakGivesPokedexScript: HideObject TOGGLE_LYING_OLD_MAN / + -- ShowObject TOGGLE_OLD_MAN_2 -- Yellow's tutorial old man stands + -- on the sleeper's cell (18,9); the Red/Blue walker OLD_MAN at + -- (17,5) never appears in Yellow (#617) { "hide_object", "VIRIDIAN_CITY", "VIRIDIANCITY_OLD_MAN_SLEEPY" }, - { "show_object", "VIRIDIAN_CITY", "VIRIDIANCITY_OLD_MAN" }, + { "show_object", "VIRIDIAN_CITY", "VIRIDIANCITY_OLD_MAN2" }, { "stop_music" }, { "play_music", "Music_MeetRival" }, { "move_npc_to", RIVAL, 4, 7 }, diff --git a/data/scripts/yellow_viridian_old_man.lua b/data/scripts/yellow_viridian_old_man.lua new file mode 100644 index 00000000..0bf2380d --- /dev/null +++ b/data/scripts/yellow_viridian_old_man.lua @@ -0,0 +1,145 @@ +-- The Viridian City old-man catch tutorial, Yellow's way +-- (pokeyellow scripts/ViridianCity.asm, scripts/ViridianCity_2.asm, +-- scripts/OaksLab.asm). Registered on top of the shared tables by +-- data/scripts/init.lua on a Yellow boot. +-- +-- Yellow has TWO gambler objects where Red/Blue have one: +-- * VIRIDIANCITY_OLD_MAN at (17,5) -- a Red/Blue leftover; its +-- toggle stays OFF forever in Yellow, no script ever shows it. +-- * VIRIDIANCITY_OLD_MAN2 at (18,9) -- replaces the sleeper the +-- moment the Pokédex is given (OaksLabOakGivesPokedexScript: +-- HideObject TOGGLE_LYING_OLD_MAN / ShowObject TOGGLE_OLD_MAN_2). +-- +-- This is the tutorial old man. The Red/Blue "Are you in a hurry?" +-- yes/no script must NOT run against him: Yellow's +-- _ViridianCityOldManHadMyCoffeeNowText is the apology speech ("I've had +-- my coffee now ... I'll show you how to catch POKéMON as my apology"), +-- and the shared story.lua TEXT_VIRIDIANCITY_OLD_MAN rows hang an +-- invented yes/no over it -- YES printed the TimeIsMoney alias +-- (_ViridianCityOldManLosingMyTouchText) and NO ran the demo, every +-- talk, forever (#617). +-- +-- The real flow (ViridianCityCheckWaitingOldMan + ViridianCityOldMan2Text +-- + ViridianCityOldManInitialCatchTrainingScript + ...EndInitial... + +-- ViridianCityPostInitialCatchTraining): stepping into (19,9) -- the gap +-- east of the sleeper's cell -- faces the old man right and the player +-- left, prints the apology, and without any choice runs the demo battle +-- (BATTLE_TYPE_OLD_MAN, RATTATA lvl 5). After it, the same text pointer +-- now prints _ViridianCityOldManLosingMyTouchText ("That didn't work! +-- I must be losing my touch."), the old man walks off (down 6 with the +-- player on (19,9), right 1 otherwise, Pikachu nudged out of the way +-- first) and TOGGLE_OLD_MAN_2 hides. A direct talk does the same. + +local M = {} + +local OLD_MAN2 = "VIRIDIANCITY_OLD_MAN2" + +-- Capture the FUNCTION, not the table: attachBase stores the module +-- table itself, so once this file's onStep is attached the table's slot +-- points back here -- delegating through the table would self-recurse. +-- story5's VIRIDIAN_CITY.onStep chains story.lua's sleeping-old-man +-- gate and its own gym-lock step (same pattern as yellow_jessie_james). +local baseViridianStep = require("data.scripts.story5").VIRIDIAN_CITY.onStep + +-- pokeyellow text/ViridianCity.asm, _ViridianCityOldManHadMyCoffeeNowText +-- and _ViridianCityOldManLosingMyTouchText, spelled with the extractor's +-- markers (line -> \n, cont -> \v, para -> \f) +local function text(game) + return { + apology = game.data.text._ViridianCityOldManHadMyCoffeeNowText + or "Ahh, I've had my\ncoffee now and I\vfeel great!\fSure, you can go\n" + .. "through!\fI'm sorry I was\nso rude to you!\fI see you're using\n" + .. "a POKéDEX.\fI'll show you how\nto catch POKéMON\vas my apology.", + losingMyTouch = game.data.text._ViridianCityOldManLosingMyTouchText + or "That didn't work!\nI must be losing\vmy touch.\fI've run out of\n" + .. "POKé BALLs too.\fI have to get some\nat POKéMON MART.", + } +end + +-- The row list for the initial-tutorial branch, keyed by where the +-- player stands when the battle ends (ViridianCityPostInitialCatchTraining +-- reads wXCoord: (19,9) walks the old man down the corridor, anywhere +-- else walks him right 1 after moving the follower Pikachu aside). +local function oldMan2Rows(game, ow, npc) + local rows = { + { "show_text", "_ViridianCityOldManHadMyCoffeeNowText" }, + { "old_man_demo" }, + { "set_flag", "EVENT_COMPLETED_CATCH_TRAINING" }, + { "show_text", "_ViridianCityOldManLosingMyTouchText" }, + } + if ow.player and ow.player.cellX == 19 then + rows[#rows + 1] = + { "walk_npc", npc.def.index, + { "down", "down", "down", "down", "down", "down" } } + else + -- ViridianCityMovePikachu (scripts/ViridianCity_2.asm): Pikachu + -- steps out of the old man's way before he turns right + local PikachuFollower = require("src.world.PikachuFollower") + local pika = ow and PikachuFollower.current(ow) + if pika then + rows[#rows + 1] = { "walk_npc", pika.def.index, { "right" } } + end + rows[#rows + 1] = { "walk_npc", npc.def.index, { "right" } } + end + rows[#rows + 1] = + { "hide_object", "VIRIDIAN_CITY", OLD_MAN2 } + return rows +end + +-- The shared talk handler: TEXT_VIRIDIANCITY_OLD_MAN2's text_asm branch +-- (ViridianCityOldMan2Text) on EVENT_COMPLETED_CATCH_TRAINING. +local function oldMan2Talk(game, ow, npc, done) + if game.save.flags and game.save.flags.EVENT_COMPLETED_CATCH_TRAINING then + local TextBox = require("src.render.TextBox") + game.stack:push(TextBox.new(game, text(game).losingMyTouch, done)) + return + end + ow.runner:run(oldMan2Rows(game, ow, npc), { npc = npc, onDone = done }) +end + +M.VIRIDIAN_CITY = { + talk = { + TEXT_VIRIDIANCITY_OLD_MAN2 = oldMan2Talk, + }, + + -- Re-apply the Pokédex swap for a save that already holds the flag but + -- was never standing here when it fired (converted .sav imports, same + -- shape as story.lua's VIRIDIAN_CITY.onEnter). Yellow shows OLD_MAN2, + -- not the Red/Blue OLD_MAN at (17,5), and also puts away a stray + -- OLD_MAN a save made by the pre-#617 build left standing. + onEnter = function(game, ow) + if not (game.save.flags and game.save.flags.EVENT_GOT_POKEDEX) then + return + end + local Commands = require("src.script.Commands") + local ctx = { save = game.save, game = game, overworld = ow } + Commands.hide_object(ctx, "VIRIDIAN_CITY", "VIRIDIANCITY_OLD_MAN_SLEEPY") + Commands.hide_object(ctx, "VIRIDIAN_CITY", "VIRIDIANCITY_OLD_MAN") + Commands.show_object(ctx, "VIRIDIAN_CITY", OLD_MAN2) + end, + + -- ViridianCityCheckWaitingOldMan: with the Pokédex held and the + -- tutorial undone, (19,9) -- the gap east of the old man, the same + -- cell the sleeper used to gate -- faces him right, turns the player + -- left and starts the OLD_MAN2 flow with no choice. + onStep = function(game, ow, x, y) + if baseViridianStep and baseViridianStep(game, ow, x, y) then + return true + end + local flags = game.save.flags + if not flags.EVENT_GOT_POKEDEX then return false end + if flags.EVENT_COMPLETED_CATCH_TRAINING then return false end + if x ~= 19 or y ~= 9 then return false end + local man + for _, npc in ipairs(ow.npcs) do + if npc.def and npc.def.name == OLD_MAN2 then man = npc break end + end + if not man then return false end + man.facing = "right" + ow.player.facing = "left" + oldMan2Talk(game, ow, man, nil) + return true + end, +} + +return M diff --git a/src/core/Data.lua b/src/core/Data.lua index 13c0a1dd..8a461d77 100644 --- a/src/core/Data.lua +++ b/src/core/Data.lua @@ -77,6 +77,13 @@ end function Data:applyVersionedFieldData() if require("src.core.GameVersion").isYellow() then self.field.trades = copy(YELLOW_TRADES) + -- The old man's catch demo is a RATTATA in Yellow + -- (scripts/ViridianCity.asm ViridianCityOldManStartCatchTrainingScript + -- .SetupBattle: ld a, RATTATA / ld [wCurOpponent], a) but the Yellow + -- manifest inherited Red's WEEDLE field.oldManBattle (#617), so old + -- Yellow caches carry the wrong demo species too. The fixed import + -- manifest below stamps RATTATA for fresh imports. + self.field.oldManBattle = { species = "RATTATA", level = 5 } end end diff --git a/tests/parity_yellow_old_man.lua b/tests/parity_yellow_old_man.lua new file mode 100644 index 00000000..54de6b44 --- /dev/null +++ b/tests/parity_yellow_old_man.lua @@ -0,0 +1,288 @@ +-- Parity (#617): Yellow's Viridian old man is the OLD_MAN2 at (18,9), +-- not the Red/Blue OLD_MAN at (17,5), and his dialog has no yes/no +-- choice -- the apology speech runs the RATTATA demo battle straight +-- away, the post-battle line is the losing-my-touch text, and he walks +-- off and hides. +-- +-- Oracle: pokeyellow scripts/OaksLab.asm (OaksLabOakGivesPokedexScript: +-- HideObject TOGGLE_LYING_OLD_MAN / ShowObject TOGGLE_OLD_MAN_2), +-- scripts/ViridianCity.asm (ViridianCityCheckWaitingOldMan, +-- ViridianCityOldMan2Text, ...InitialCatchTrainingScript, +-- ...PostInitialCatchTraining) and scripts/ViridianCity_2.asm +-- (ViridianCityPrintOldManText). The Red/Blue "Are you in a hurry?" +-- script was running against Yellow's text: YES printed the TimeIsMoney +-- alias (_ViridianCityOldManLosingMyTouchText) and NO ran the demo -- +-- every talk, forever. +-- +-- Self-contained: `luajit tests/parity_yellow_old_man.lua`; also globbed +-- by tests/run_tests.lua. +package.path = "./?.lua;./?/init.lua;" .. package.path +if not _G.love then _G.love = require("tests.love_stub") end + +local Data = require("src.core.Data") +if not (Data.maps and Data.maps.PALLET_TOWN) then Data:load() end +local GameVersion = require("src.core.GameVersion") +local SaveData = require("src.core.SaveData") +local ScriptRunner = require("src.script.ScriptRunner") +local TextBox = require("src.render.TextBox") +local BattleState = require("src.battle.BattleState") +local Pokemon = require("src.pokemon.Pokemon") + +local S = require("tests.harness").suite("parity Yellow old man (#617)") +local check, eq = S.check, S.eq + +local oldVersion = GameVersion.get() + +local MAP = "VIRIDIAN_CITY" +local SLEEPER = "VIRIDIANCITY_OLD_MAN_SLEEPY" +local WALKER = "VIRIDIANCITY_OLD_MAN" +local OLD_MAN2 = "VIRIDIANCITY_OLD_MAN2" +local DONE_FLAG = "EVENT_COMPLETED_CATCH_TRAINING" + +-- The Yellow wiring must be attached before anything else caches the +-- map-script registry: data.scripts.init branches on GameVersion at +-- load, so flip it first (this file owns its own process when run +-- standalone). Under tests/run_tests.lua the registry is already +-- cached with the Red wiring, so attach the Yellow modules directly +-- afterwards -- attachBase merges per TEXT constant and replaces hooks, +-- which is a no-op on a fresh process and the fix on a shared one. +GameVersion.set("yellow") +local mapScripts = require("data.scripts.init") +local MapScripts = require("src.script.MapScripts") +MapScripts.attachBase(MAP, + require("data.scripts.yellow_viridian_old_man").VIRIDIAN_CITY) +MapScripts.attachBase("OAKS_LAB", + require("data.scripts.oaks_lab_yellow")) +local oldManMod = require("data.scripts.yellow_viridian_old_man") + +-- ------------------------------------------------------- the demo species +-- The catch demo is a RATTATA in Yellow (SetupBattle sets wCurOpponent +-- = RATTATA) but the Yellow manifest inherited Red's WEEDLE; the runtime +-- override in Data:applyVersionedFieldData repairs old caches. Kept +-- active until the end of this file so the demo-battle assertions below +-- run against the Yellow value; restored before S.finish() like +-- parity_yellow_trades does for its trades table. +local originalOldManBattle = Data.field.oldManBattle + or { species = "WEEDLE", level = 5 } -- the fixture carries no oldManBattle +local originalTrades = Data.field.trades +eq(originalOldManBattle.species, "WEEDLE", + "Red/Blue's old man still demos a Weedle") +GameVersion.set("yellow") +Data:applyVersionedFieldData() +eq(Data.field.oldManBattle.species, "RATTATA", + "Yellow's old man demos a Rattata (#617)") + +local manifestFile = assert(io.open("tools/rom_manifest_yellow.json", "r")) +local yellowManifest = manifestFile:read("*a") +manifestFile:close() +check(yellowManifest:find('"species": "RATTATA"', 1, true) ~= nil, + "the Yellow manifest stamps RATTATA for fresh imports") +local redManifestFile = assert(io.open("tools/rom_manifest.json", "r")) +local redManifest = redManifestFile:read("*a") +redManifestFile:close() +check(redManifest:find('"species": "WEEDLE"', 1, true) ~= nil, + "and the Red/Blue manifest keeps WEEDLE") + +-- ------------------------------------------------------- the Pokedex swap +-- OaksLabOakGivesPokedexScript shows TOGGLE_OLD_MAN_2 (the tutorial old +-- man standing on the sleeper's cell), never the Red/Blue walker +local oaksRows = mapScripts.talkScript("OAKS_LAB", "TEXT_OAKSLAB_OAK1") +check(type(oaksRows) == "table", + "the Yellow OaksLab Oak talk resolves to rows") +local sawSleepHide, sawOldMan2Show, sawOldManShow = false, false, false +for _, row in ipairs(oaksRows or {}) do + if row[1] == "hide_object" and row[3] == SLEEPER then sawSleepHide = true end + if row[1] == "show_object" and row[3] == OLD_MAN2 then sawOldMan2Show = true end + if row[1] == "show_object" and row[3] == WALKER then sawOldManShow = true end +end +check(sawSleepHide, "the Pokédex hand-over hides the lying old man") +check(sawOldMan2Show, "it shows OLD_MAN2 on the sleeper's cell") +check(not sawOldManShow, "it never shows the Red/Blue OLD_MAN (#617)") + +-- both Yellow gamblers default hidden (toggle OFF), like pokeyellow +-- data/maps/toggleable_objects.asm. OLD_MAN2 only exists in a Yellow +-- import -- a Red-imported checkout carries just OLD_MAN -- so the +-- dataset checks tolerate its absence and the Yellow manifest carries +-- the OLD_MAN2 default instead. +local walkerDef, oldMan2Def +if Data.maps[MAP] then + for _, o in ipairs(Data.maps[MAP].objects or {}) do + if o.name == WALKER then walkerDef = o end + if o.name == OLD_MAN2 then oldMan2Def = o end + end +end +check(walkerDef == nil or walkerDef.hidden == true, + "VIRIDIANCITY_OLD_MAN defaults hidden in Yellow") +check(oldMan2Def == nil or oldMan2Def.hidden == true, + "VIRIDIANCITY_OLD_MAN2 defaults hidden in Yellow") +local om2Name = yellowManifest:find('"name": "VIRIDIANCITY_OLD_MAN2"', 1, true) +local om2Hidden = om2Name and yellowManifest:sub( + math.max(1, om2Name - 40), om2Name):find('"hidden": true', 1, true) +check(om2Hidden ~= nil, + "the Yellow manifest ships OLD_MAN2 with the toggle OFF") + +-- ------------------------------------------------------- script registry +local talk = mapScripts.talkScript(MAP, "TEXT_VIRIDIANCITY_OLD_MAN2") +check(type(talk) == "function", + "TEXT_VIRIDIANCITY_OLD_MAN2 resolves to the Yellow handler") +check(type(mapScripts.talkScript(MAP, "TEXT_VIRIDIANCITY_OLD_MAN")) == "table", + "the Red/Blue OLD_MAN talk is still registered (unreachable in Yellow)") +local hooks = mapScripts.get(MAP) +check(hooks and type(hooks.onEnter) == "function", + "VIRIDIAN_CITY.onEnter is the Yellow swap") +check(hooks and type(hooks.onStep) == "function", + "VIRIDIAN_CITY.onStep chains the gym lock and sleeper gate") +check(oldManMod.VIRIDIAN_CITY and oldManMod.VIRIDIAN_CITY.talk + and oldManMod.VIRIDIAN_CITY.talk.TEXT_VIRIDIANCITY_OLD_MAN2 == talk, + "the handler is the module's own, not a leftover merge") + +-- ------------------------------------------------------- completed branch +do + local pushed = {} + local game = { + data = Data, + save = SaveData.newGame(), + stack = { push = function(_, s) pushed[#pushed + 1] = s end }, + } + game.save.flags.EVENT_COMPLETED_CATCH_TRAINING = true + local done = false + talk(game, nil, {}, function() done = true end) + eq(#pushed, 1, "a second talk only prints one box") + eq(getmetatable(pushed[1]), TextBox, "the losing-my-touch line, in a box") + pushed[1].onDone() + check(done, "closing it hands input back") +end + +-- ------------------------------- the initial tutorial, end to end +-- Needs real species in the dataset (the fixture carries only FIX_*); +-- the engine's old-man demo machinery itself is parity_J's territory. +if Data.pokemon.RATTATA and Data.pokemon.PIKACHU then +do + require("src.render.Font").load(Data) + local pushed = {} + local save = SaveData.newGame() + save.party = { Pokemon.new(Data, "PIKACHU", 12) } + local moves = {} + local man = { def = { index = 8, name = OLD_MAN2 } } + local ow = { + map = { id = MAP, def = { label = "ViridianCity" } }, + npcs = { man }, entities = { man }, + player = { cellX = 19, cellY = 9, facing = "left" }, + scriptMove = function(_, _, dir, _, cb) moves[#moves + 1] = dir; cb() end, + npcByIndex = function(_, i) if i == 8 then return man end end, + } + local game = { + data = Data, + save = save, + stack = { push = function(_, s) pushed[#pushed + 1] = s end }, + } + local runner = ScriptRunner.new(game, ow) + ow.runner = runner + local done = false + talk(game, ow, man, function() done = true end) + + eq(#pushed, 1, "the initial talk opens the apology speech") + eq(getmetatable(pushed[1]), TextBox, "in a text box") + pushed[1].onDone() -- A: the apology closes, the demo battle starts + + eq(#pushed, 2, "the demo battle starts with no choice in between") + local battle = pushed[2] + check(battle and battle.demo, "it is the old-man demo battle") + eq(battle and battle.enemy and battle.enemy.mon.species, "RATTATA", + "the demo is a RATTATA in Yellow (#617)") + eq(save.flags[DONE_FLAG], nil, "the flag is still clear mid-demo") + battle.onFinish() -- the battle ends, the post-battle text prints + + eq(save.flags[DONE_FLAG], true, "EVENT_COMPLETED_CATCH_TRAINING is set") + eq(#pushed, 3, "the losing-my-touch line follows the demo") + pushed[3].onDone() -- A: the old man walks off + + eq(#moves, 6, "with the player on (19,9) he walks down 6 tiles") + check(moves[1] == "down" and moves[6] == "down", + "all six steps are the ViridianCityOldManMovementData2 walk") + eq(save.objectToggles[MAP] and save.objectToggles[MAP][OLD_MAN2], false, + "TOGGLE_OLD_MAN_2 hides once the walk finishes") + check(done, "and the talk hands input back") +end + +-- ---------------------------------- side talk: player not on (19,9) cell +do + local pushed = {} + local save = SaveData.newGame() + save.party = { Pokemon.new(Data, "PIKACHU", 12) } + local moves = {} + local man = { def = { index = 8, name = OLD_MAN2 } } + local pika = { def = { index = 99, name = "PIKACHU_FOLLOWER" }, + pikachuFollower = true } + local ow = { + map = { id = MAP, def = { label = "ViridianCity" } }, + npcs = { man, pika }, entities = { man, pika }, + player = { cellX = 18, cellY = 8, facing = "down" }, + scriptMove = function(_, _, dir, _, cb) moves[#moves + 1] = dir; cb() end, + npcByIndex = function(_, i) if i == 8 then return man elseif i == 99 then return pika end end, + } + local game = { + data = Data, + save = save, + stack = { push = function(_, s) pushed[#pushed + 1] = s end }, + } + local runner = ScriptRunner.new(game, ow) + ow.runner = runner + talk(game, ow, man, function() end) + pushed[1].onDone() + pushed[2].onFinish() + pushed[3].onDone() + eq(moves[1], "right", "Pikachu steps aside first (ViridianCityMovePikachu)") + eq(moves[2], "right", "then the old man turns right one tile") + eq(#moves, 2, "and no more") +end +else + check(true, "fixture dataset: demo-battle flow skipped (no RATTATA)") +end + +-- --------------------------------------------------------- the (19,9) step +do + local pushed = {} + local save = SaveData.newGame() + local man = { def = { index = 8, name = OLD_MAN2 } } + local ow = { + map = { id = MAP, def = { label = "ViridianCity" } }, + npcs = { man }, entities = { man }, + player = { cellX = 19, cellY = 9, facing = "down" }, + scriptMove = function(_, _, _, _, cb) cb() end, + npcByIndex = function() end, + } + local game = { + data = Data, + save = save, + stack = { push = function(_, s) pushed[#pushed + 1] = s end }, + } + local runner = ScriptRunner.new(game, ow) + ow.runner = runner + + check(not hooks.onStep(game, ow, 5, 5), + "off the trigger cell the step passes through") + check(hooks.onStep(game, ow, 19, 9), + "pre-Pokedex the sleeper gate owns (19,9)") + eq(#pushed, 1, "with the sleepy text box") + check(save.flags[DONE_FLAG] ~= true, "the tutorial is not running") + + save.flags.EVENT_GOT_POKEDEX = true + check(hooks.onStep(game, ow, 19, 9), + "with the Pokedex, (19,9) starts the tutorial") + eq(man.facing, "right", "the old man faces the player") + eq(ow.player.facing, "left", "and the player turns to face him") + eq(#pushed, 2, "the apology box is up") + check(save.flags[DONE_FLAG] ~= true, + "no flag until the demo battle actually runs") + + save.flags.EVENT_COMPLETED_CATCH_TRAINING = true + check(not hooks.onStep(game, ow, 19, 9), + "once the tutorial is done the cell is quiet again") +end + +Data.field.trades = originalTrades +Data.field.oldManBattle = originalOldManBattle +GameVersion.set(oldVersion) + +S.finish() diff --git a/tools/rom_manifest_yellow.json b/tools/rom_manifest_yellow.json index 8b94d472..bd087591 100644 --- a/tools/rom_manifest_yellow.json +++ b/tools/rom_manifest_yellow.json @@ -6574,7 +6574,7 @@ "battleType": "BATTLE_TYPE_OLD_MAN", "level": 5, "map": "VIRIDIAN_CITY", - "species": "WEEDLE", + "species": "RATTATA", "text": "ViridianCityOldManText" }, "overworldFx": {