mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-25 23:11:15 +02:00
fix(yellow): address version:yellow issues and title pupil bake
Seed Mt Moon Super Nerd dialogue (#1743), play item-use heal jingles (#1635), use white fades for fly/teleport warps (#1644), add a Yellow-only Advanced palette overlay (#1639), parse Yellow Super Rod data including Safari Dragonair (#1074), and apply title rOBP0 when baking Pikachu eye OAM so pupils stay black with white glints.
This commit is contained in:
@@ -0,0 +1,270 @@
|
|||||||
|
-- Yellow-only Advanced (redpp) deltas. Merged on top of data/palettes_gbc.lua
|
||||||
|
-- world tables when GameVersion.isYellow(); Red/Blue never load this file's
|
||||||
|
-- world path. Named SuperPalettes stay washed on Yellow -- PaletteFX.pal
|
||||||
|
-- prefers CGBBase under Advanced instead (title MEWMON/LOGO, YELLOWMON).
|
||||||
|
-- See #1639.
|
||||||
|
|
||||||
|
return {
|
||||||
|
world = {
|
||||||
|
-- Summer Beach House: Yellow-only tileset. Tile→group map mirrors HOUSE
|
||||||
|
-- (closest Advanced indoor profile); colors lean sand / wood / water.
|
||||||
|
tileGroups = {
|
||||||
|
BEACH_HOUSE = {
|
||||||
|
[0] = 5,
|
||||||
|
[1] = 0,
|
||||||
|
[2] = 0,
|
||||||
|
[3] = 0,
|
||||||
|
[4] = 1,
|
||||||
|
[5] = 0,
|
||||||
|
[6] = 3,
|
||||||
|
[7] = 3,
|
||||||
|
[8] = 2,
|
||||||
|
[9] = 2,
|
||||||
|
[10] = 2,
|
||||||
|
[11] = 2,
|
||||||
|
[12] = 2,
|
||||||
|
[13] = 2,
|
||||||
|
[14] = 5,
|
||||||
|
[15] = 5,
|
||||||
|
[16] = 0,
|
||||||
|
[17] = 0,
|
||||||
|
[18] = 0,
|
||||||
|
[19] = 0,
|
||||||
|
[20] = 1,
|
||||||
|
[21] = 0,
|
||||||
|
[22] = 3,
|
||||||
|
[23] = 3,
|
||||||
|
[24] = 5,
|
||||||
|
[25] = 5,
|
||||||
|
[26] = 5,
|
||||||
|
[27] = 5,
|
||||||
|
[28] = 0,
|
||||||
|
[29] = 0,
|
||||||
|
[30] = 5,
|
||||||
|
[31] = 5,
|
||||||
|
[32] = 0,
|
||||||
|
[33] = 0,
|
||||||
|
[34] = 3,
|
||||||
|
[35] = 5,
|
||||||
|
[36] = 5,
|
||||||
|
[37] = 5,
|
||||||
|
[38] = 5,
|
||||||
|
[39] = 5,
|
||||||
|
[40] = 5,
|
||||||
|
[41] = 5,
|
||||||
|
[42] = 2,
|
||||||
|
[43] = 2,
|
||||||
|
[44] = 5,
|
||||||
|
[45] = 5,
|
||||||
|
[46] = 5,
|
||||||
|
[47] = 5,
|
||||||
|
[48] = 5,
|
||||||
|
[49] = 5,
|
||||||
|
[50] = 5,
|
||||||
|
[51] = 5,
|
||||||
|
[52] = 5,
|
||||||
|
[53] = 5,
|
||||||
|
[54] = 5,
|
||||||
|
[55] = 5,
|
||||||
|
[56] = 5,
|
||||||
|
[57] = 5,
|
||||||
|
[58] = 5,
|
||||||
|
[59] = 5,
|
||||||
|
[60] = 5,
|
||||||
|
[61] = 5,
|
||||||
|
[62] = 5,
|
||||||
|
[63] = 5,
|
||||||
|
[64] = 0,
|
||||||
|
[65] = 0,
|
||||||
|
[66] = 0,
|
||||||
|
[67] = 0,
|
||||||
|
[68] = 0,
|
||||||
|
[69] = 0,
|
||||||
|
[70] = 5,
|
||||||
|
[71] = 5,
|
||||||
|
[72] = 5,
|
||||||
|
[73] = 5,
|
||||||
|
[74] = 5,
|
||||||
|
[75] = 5,
|
||||||
|
[76] = 5,
|
||||||
|
[77] = 5,
|
||||||
|
[78] = 5,
|
||||||
|
[79] = 5,
|
||||||
|
[80] = 5,
|
||||||
|
[81] = 5,
|
||||||
|
[82] = 5,
|
||||||
|
[83] = 5,
|
||||||
|
[84] = 5,
|
||||||
|
[85] = 5,
|
||||||
|
[86] = 5,
|
||||||
|
[87] = 5,
|
||||||
|
[88] = 5,
|
||||||
|
[89] = 5,
|
||||||
|
[90] = 5,
|
||||||
|
[91] = 5,
|
||||||
|
[92] = 5,
|
||||||
|
[93] = 5,
|
||||||
|
[94] = 5,
|
||||||
|
[95] = 5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
groupColors = {
|
||||||
|
BEACH_HOUSE = {
|
||||||
|
-- sand / wood floor
|
||||||
|
{
|
||||||
|
{ 255, 239, 198 },
|
||||||
|
{ 206, 173, 115 },
|
||||||
|
{ 156, 123, 74 },
|
||||||
|
{ 58, 58, 58 },
|
||||||
|
},
|
||||||
|
-- signs / accents (warm coral)
|
||||||
|
{
|
||||||
|
{ 255, 239, 198 },
|
||||||
|
{ 255, 156, 148 },
|
||||||
|
{ 230, 82, 66 },
|
||||||
|
{ 58, 58, 58 },
|
||||||
|
},
|
||||||
|
-- foliage
|
||||||
|
{
|
||||||
|
{ 255, 239, 198 },
|
||||||
|
{ 123, 189, 82 },
|
||||||
|
{ 66, 123, 41 },
|
||||||
|
{ 58, 58, 58 },
|
||||||
|
},
|
||||||
|
-- water / windows
|
||||||
|
{
|
||||||
|
{ 255, 239, 198 },
|
||||||
|
{ 99, 189, 230 },
|
||||||
|
{ 41, 115, 189 },
|
||||||
|
{ 58, 58, 58 },
|
||||||
|
},
|
||||||
|
-- yellow props
|
||||||
|
{
|
||||||
|
{ 255, 239, 198 },
|
||||||
|
{ 255, 255, 82 },
|
||||||
|
{ 230, 148, 25 },
|
||||||
|
{ 58, 58, 58 },
|
||||||
|
},
|
||||||
|
-- wood walls / furniture
|
||||||
|
{
|
||||||
|
{ 255, 239, 198 },
|
||||||
|
{ 189, 140, 74 },
|
||||||
|
{ 140, 99, 41 },
|
||||||
|
{ 58, 58, 58 },
|
||||||
|
},
|
||||||
|
-- sky / cool trim
|
||||||
|
{
|
||||||
|
{ 255, 239, 198 },
|
||||||
|
{ 148, 189, 255 },
|
||||||
|
{ 99, 148, 230 },
|
||||||
|
{ 58, 58, 58 },
|
||||||
|
},
|
||||||
|
-- text
|
||||||
|
{
|
||||||
|
{ 255, 255, 255 },
|
||||||
|
{ 255, 255, 255 },
|
||||||
|
{ 255, 255, 255 },
|
||||||
|
{ 0, 0, 0 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
-- Yellow spriteOrder is 82 entries; Red Advanced assignment is 72.
|
||||||
|
-- Indices 60-69 are Yellow inserts (Pikachu, Jenny, mons, Jessie/James);
|
||||||
|
-- Red's ball/fossil/snorlax/... shift to 70-81.
|
||||||
|
spriteAssignment = {
|
||||||
|
[0] = 0,
|
||||||
|
[1] = 1,
|
||||||
|
[2] = 3,
|
||||||
|
[3] = "random",
|
||||||
|
[4] = 0,
|
||||||
|
[5] = "random",
|
||||||
|
[6] = "random",
|
||||||
|
[7] = "random",
|
||||||
|
[8] = 0,
|
||||||
|
[9] = "random",
|
||||||
|
[10] = "random",
|
||||||
|
[11] = "random",
|
||||||
|
[12] = "random",
|
||||||
|
[13] = "random",
|
||||||
|
[14] = "random",
|
||||||
|
[15] = 1,
|
||||||
|
[16] = 1,
|
||||||
|
[17] = "random",
|
||||||
|
[18] = "random",
|
||||||
|
[19] = "random",
|
||||||
|
[20] = "random",
|
||||||
|
[21] = 2,
|
||||||
|
[22] = 1,
|
||||||
|
[23] = 3,
|
||||||
|
[24] = "random",
|
||||||
|
[25] = "random",
|
||||||
|
[26] = "random",
|
||||||
|
[27] = "random",
|
||||||
|
[28] = "random",
|
||||||
|
[29] = 0,
|
||||||
|
[30] = 3,
|
||||||
|
[31] = 3,
|
||||||
|
[32] = "random",
|
||||||
|
[33] = "random",
|
||||||
|
[34] = "random",
|
||||||
|
[35] = "random",
|
||||||
|
[36] = "random",
|
||||||
|
[37] = "random",
|
||||||
|
[38] = "random",
|
||||||
|
[39] = "random",
|
||||||
|
[40] = 0,
|
||||||
|
[41] = 2,
|
||||||
|
[42] = "random",
|
||||||
|
[43] = "random",
|
||||||
|
[44] = "random",
|
||||||
|
[45] = "random",
|
||||||
|
[46] = "random",
|
||||||
|
[47] = "random",
|
||||||
|
[48] = "random",
|
||||||
|
[49] = "random",
|
||||||
|
[50] = "random",
|
||||||
|
[51] = "random",
|
||||||
|
[52] = "random",
|
||||||
|
[53] = "random",
|
||||||
|
[54] = "random",
|
||||||
|
[55] = 0,
|
||||||
|
[56] = 1,
|
||||||
|
[57] = 3,
|
||||||
|
[58] = 0,
|
||||||
|
[59] = 0,
|
||||||
|
[60] = 4, -- SPRITE_PIKACHU
|
||||||
|
[61] = 1, -- SPRITE_OFFICER_JENNY
|
||||||
|
[62] = 3, -- SPRITE_SANDSHREW
|
||||||
|
[63] = 2, -- SPRITE_ODDISH
|
||||||
|
[64] = 2, -- SPRITE_BULBASAUR
|
||||||
|
[65] = "random", -- SPRITE_JIGGLYPUFF
|
||||||
|
[66] = "random", -- SPRITE_CLEFAIRY
|
||||||
|
[67] = "random", -- SPRITE_CHANSEY
|
||||||
|
[68] = "random", -- SPRITE_JESSIE
|
||||||
|
[69] = "random", -- SPRITE_JAMES
|
||||||
|
[70] = 0, -- SPRITE_POKE_BALL
|
||||||
|
[71] = 0, -- SPRITE_FOSSIL
|
||||||
|
[72] = 3, -- SPRITE_BOULDER
|
||||||
|
[73] = 3, -- SPRITE_PAPER
|
||||||
|
[74] = 0, -- SPRITE_POKEDEX
|
||||||
|
[75] = 3, -- SPRITE_CLIPBOARD
|
||||||
|
[76] = 0, -- SPRITE_SNORLAX
|
||||||
|
[77] = 3, -- SPRITE_UNUSED_OLD_AMBER
|
||||||
|
[78] = 3, -- SPRITE_OLD_AMBER
|
||||||
|
[79] = "random",
|
||||||
|
[80] = "random",
|
||||||
|
[81] = 3, -- SPRITE_GAMBLER_ASLEEP
|
||||||
|
},
|
||||||
|
|
||||||
|
-- Group 4 is Pikachu yellow on Yellow Advanced (Red keeps its own [4]).
|
||||||
|
spritePalettes = {
|
||||||
|
[4] = {
|
||||||
|
{ 222, 255, 222 },
|
||||||
|
{ 255, 255, 0 },
|
||||||
|
{ 230, 115, 0 },
|
||||||
|
{ 0, 0, 0 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -114,6 +114,7 @@ fi
|
|||||||
|
|
||||||
run_tier "T0 ROM builder version routing" python3 tests/build_rom_data_cli_test.py
|
run_tier "T0 ROM builder version routing" python3 tests/build_rom_data_cli_test.py
|
||||||
run_tier "T0 ROM manifest generator pin/overrides" python3 tests/rom_manifest_generator_test.py
|
run_tier "T0 ROM manifest generator pin/overrides" python3 tests/rom_manifest_generator_test.py
|
||||||
|
run_tier "T0 Yellow title OBP eye remap" python3 tests/title_pikachu_obp_test.py
|
||||||
run_tier "T0 Crystal manifest + specials coverage" "$LUA" tests/crystal_import_test.lua
|
run_tier "T0 Crystal manifest + specials coverage" "$LUA" tests/crystal_import_test.lua
|
||||||
run_tier "T0 switch CI workflow content gate" "$LUA" tests/switch_ci_workflows_test.lua
|
run_tier "T0 switch CI workflow content gate" "$LUA" tests/switch_ci_workflows_test.lua
|
||||||
run_tier "T0 switch transfer docs gate" "$LUA" tests/switch_transfer_docs_test.lua
|
run_tier "T0 switch transfer docs gate" "$LUA" tests/switch_transfer_docs_test.lua
|
||||||
|
|||||||
@@ -170,6 +170,10 @@ function Data:seedDefaults(version)
|
|||||||
-- writes no header for him -- seed one so he engages on sight and has
|
-- writes no header for him -- seed one so he engages on sight and has
|
||||||
-- his defeat / re-talk lines (same idea as the Cinnabar seed above).
|
-- his defeat / re-talk lines (same idea as the Cinnabar seed above).
|
||||||
Data.seedFightingDojoKarateMaster(self)
|
Data.seedFightingDojoKarateMaster(self)
|
||||||
|
-- #1743: Mt. Moon B2F Super Nerd is text_asm (no def_trainers), so Yellow's
|
||||||
|
-- extractor never writes his header -- seed one so engageTrainer finds
|
||||||
|
-- battle/won/after text instead of the "I like shorts!" fallback.
|
||||||
|
Data.seedMtMoonB2FSuperNerd(self)
|
||||||
-- #189: 1F cabin door order vs rooms map (survey zoom)
|
-- #189: 1F cabin door order vs rooms map (survey zoom)
|
||||||
require("src.world.SsAnneLayout").apply(self.maps)
|
require("src.world.SsAnneLayout").apply(self.maps)
|
||||||
end
|
end
|
||||||
@@ -201,6 +205,25 @@ function Data:seedFightingDojoKarateMaster()
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Mt. Moon B2F Super Nerd (object index 1) is text_asm with no def_trainers
|
||||||
|
-- row, so Yellow never gets a trainerHeaders.MtMoonB2F[1] entry (Red/Blue
|
||||||
|
-- pin one in make_rom_manifest.py). Without it, engageTrainer falls through
|
||||||
|
-- to the hard-coded "I like shorts!" fallback (#1743). trainerDefeated still
|
||||||
|
-- tracks him via defeatedTrainers[npc.id]; the fabricated event name matches
|
||||||
|
-- the shipped Red/Blue manifest pin for consistency with fossil drivers.
|
||||||
|
function Data:seedMtMoonB2FSuperNerd()
|
||||||
|
local headers = self.trainer_headers
|
||||||
|
if not headers then return end
|
||||||
|
headers.MtMoonB2F = headers.MtMoonB2F or {}
|
||||||
|
if headers.MtMoonB2F[1] then return end
|
||||||
|
headers.MtMoonB2F[1] = {
|
||||||
|
battle = "_MtMoonB2FSuperNerdTheyreBothMineText",
|
||||||
|
won = "_MtMoonB2FSuperNerdOkIllShareText",
|
||||||
|
after = "_MtMoonB2FSuperNerdTheresAPokemonLabText",
|
||||||
|
event = "EVENT_BEAT_MT_MOON_3_SUPER_NERD",
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
function Data:seedCinnabarGymTrainerHeaders()
|
function Data:seedCinnabarGymTrainerHeaders()
|
||||||
local headers = self.trainer_headers
|
local headers = self.trainer_headers
|
||||||
if not headers or headers.CinnabarGym then return end
|
if not headers or headers.CinnabarGym then return end
|
||||||
|
|||||||
@@ -7,6 +7,28 @@ local SHADES = {
|
|||||||
{ 0, 0, 0, 1 },
|
{ 0, 0, 0, 1 },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ImageWriter.SHADES = SHADES
|
||||||
|
|
||||||
|
-- Title screen rOBP0 = %11100000 ($E0): OBJ shades 1 and 2 draw as white,
|
||||||
|
-- shade 3 as black (pokeyellow engine/movie/title.asm after PlacePikachu).
|
||||||
|
-- Eye OAM is baked into the MEWMON-colored BG PNG; without this remap the
|
||||||
|
-- shade-1 glints become body yellow under the title palette.
|
||||||
|
function ImageWriter.applyTitleObp0(image)
|
||||||
|
local mid, dark = SHADES[2][1], SHADES[3][1]
|
||||||
|
local w, h = image:getWidth(), image:getHeight()
|
||||||
|
for y = 0, h - 1 do
|
||||||
|
for x = 0, w - 1 do
|
||||||
|
local r, g, b, a = image:getPixel(x, y)
|
||||||
|
if a ~= 0 then
|
||||||
|
if math.abs(r - mid) < 0.02 or math.abs(r - dark) < 0.02 then
|
||||||
|
image:setPixel(x, y, 1, 1, 1, 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return image
|
||||||
|
end
|
||||||
|
|
||||||
local function assertDimensions(raw, width, height, bits)
|
local function assertDimensions(raw, width, height, bits)
|
||||||
assert(width % 8 == 0 and height % 8 == 0,
|
assert(width % 8 == 0 and height % 8 == 0,
|
||||||
("%dbpp dimensions must be tile-aligned: %dx%d")
|
("%dbpp dimensions must be tile-aligned: %dx%d")
|
||||||
|
|||||||
@@ -1535,6 +1535,14 @@ function RomExtractor:extractYellowTitleArt()
|
|||||||
local bg = sheetTiles("TitlePikachuBGGraphics", 64)
|
local bg = sheetTiles("TitlePikachuBGGraphics", 64)
|
||||||
local ob = sheetTiles("TitlePikachuOBGraphics", 12)
|
local ob = sheetTiles("TitlePikachuOBGraphics", 12)
|
||||||
local obClear = sheetTiles("TitlePikachuOBGraphics", 12, true)
|
local obClear = sheetTiles("TitlePikachuOBGraphics", 12, true)
|
||||||
|
-- Title rOBP0=$E0: remap eye OAM shades 1/2 → white before baking into the
|
||||||
|
-- MEWMON BG composition (otherwise glints read as body yellow).
|
||||||
|
local eyeOb = {}
|
||||||
|
for i, tile in ipairs(obClear) do
|
||||||
|
local copy = ImageWriter.blank(8, 8, 0, 0, 0, 0)
|
||||||
|
ImageWriter.blit(copy, tile, 0, 0)
|
||||||
|
eyeOb[i] = ImageWriter.applyTitleObp0(copy)
|
||||||
|
end
|
||||||
local function tileFor(id)
|
local function tileFor(id)
|
||||||
if id < 0x80 then return logo[id + 1] end
|
if id < 0x80 then return logo[id + 1] end
|
||||||
if id < 0xF0 then return bg[id - 0x80 + 1] end
|
if id < 0xF0 then return bg[id - 0x80 + 1] end
|
||||||
@@ -1623,13 +1631,13 @@ function RomExtractor:extractYellowTitleArt()
|
|||||||
local overlay = ImageWriter.blank(48, 16, 1, 1, 1, 0)
|
local overlay = ImageWriter.blank(48, 16, 1, 1, 1, 0)
|
||||||
ImageWriter.blit(overlay, pikachu, 0, 0, 24, 16, 48, 16)
|
ImageWriter.blit(overlay, pikachu, 0, 0, 24, 16, 48, 16)
|
||||||
for _, e in ipairs(EYE_LAYOUT) do
|
for _, e in ipairs(EYE_LAYOUT) do
|
||||||
blitSprite(overlay, obClear[base + e[1]], e[2] - 24, e[3] - 16, e[4])
|
blitSprite(overlay, eyeOb[base + e[1]], e[2] - 24, e[3] - 16, e[4])
|
||||||
end
|
end
|
||||||
overlays[suffix] = overlay
|
overlays[suffix] = overlay
|
||||||
end
|
end
|
||||||
-- open eyes bake into pikachu.png AFTER the blank-face crops
|
-- open eyes bake into pikachu.png AFTER the blank-face crops
|
||||||
for _, e in ipairs(EYE_LAYOUT) do
|
for _, e in ipairs(EYE_LAYOUT) do
|
||||||
blitSprite(pikachu, obClear[e[1]], e[2], e[3], e[4])
|
blitSprite(pikachu, eyeOb[e[1]], e[2], e[3], e[4])
|
||||||
end
|
end
|
||||||
self:save(pikachu, "title/pikachu.png")
|
self:save(pikachu, "title/pikachu.png")
|
||||||
for suffix, overlay in pairs(overlays) do
|
for suffix, overlay in pairs(overlays) do
|
||||||
|
|||||||
@@ -184,6 +184,14 @@ local function adjacentSleepingSnorlax(save, ow)
|
|||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function itemUseLine(data, save, name)
|
||||||
|
return romText(data, "_ItemUseText001", "%s used\n%s!", save.player.name, name)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- PrintItemUseTextAndRemoveItem (item_effects.asm): used-line + SFX_HEAL_AILMENT.
|
||||||
|
-- BagMenu plays Heal_Ailment via TextBox.soundOpts when extra.useJingle is set.
|
||||||
|
local USE_JINGLE = { useJingle = true }
|
||||||
|
|
||||||
-- Use an item on a target party mon (target may be nil for targetless
|
-- Use an item on a target party mon (target may be nil for targetless
|
||||||
-- items). data = generated data tables; battle = BattleState when used
|
-- items). data = generated data tables; battle = BattleState when used
|
||||||
-- mid-battle; ow = the overworld (OverworldState), needed only to check
|
-- mid-battle; ow = the overworld (OverworldState), needed only to check
|
||||||
@@ -280,7 +288,7 @@ function ItemEffects.use(data, save, itemId, target, battle, moveIndex, ow)
|
|||||||
"All sleeping\nPOKéMON woke up!") }
|
"All sleeping\nPOKéMON woke up!") }
|
||||||
end
|
end
|
||||||
|
|
||||||
-- battle-only items
|
-- battle-only items (PrintItemUseTextAndRemoveItem + Heal_Ailment, #1635)
|
||||||
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
|
||||||
@@ -293,21 +301,23 @@ function ItemEffects.use(data, save, itemId, target, battle, moveIndex, ow)
|
|||||||
require("src.world.PikachuFollower")
|
require("src.world.PikachuFollower")
|
||||||
.modifyHappiness(save, "USEDXITEM", b and b.mon)
|
.modifyHappiness(save, "USEDXITEM", b and b.mon)
|
||||||
end
|
end
|
||||||
|
local used = itemUseLine(data, save, name)
|
||||||
if itemId == "X_ACCURACY" then
|
if itemId == "X_ACCURACY" then
|
||||||
-- ItemUseXAccuracy sets USING_X_ACCURACY: moves never miss
|
-- ItemUseXAccuracy sets USING_X_ACCURACY: moves never miss
|
||||||
-- (not an accuracy stage)
|
-- (not an accuracy stage); vanilla prints only the used line
|
||||||
b.xAccuracy = true
|
b.xAccuracy = true
|
||||||
return "consumed", { Strings("%s's\nhits will never\nmiss!", b.name) }
|
return "consumed", { used }, USE_JINGLE
|
||||||
end
|
end
|
||||||
if X_ITEMS[itemId] then
|
if X_ITEMS[itemId] then
|
||||||
local stat = X_ITEMS[itemId]
|
local stat = X_ITEMS[itemId]
|
||||||
local cur = b.stages[stat] or 0
|
local cur = b.stages[stat] or 0
|
||||||
-- ItemUseXStat removes the item BEFORE running the stat-up
|
-- ItemUseXStat: PrintItemUseTextAndRemoveItem, then StatModifierUpEffect
|
||||||
-- effect, so at +6 it is still consumed and StatModifierUpEffect
|
|
||||||
-- just prints "Nothing happened!"
|
|
||||||
if cur >= 6 then
|
if cur >= 6 then
|
||||||
return "consumed", { romText(data, "_NothingHappenedText",
|
return "consumed", { used }, {
|
||||||
"Nothing happened!") }
|
useJingle = true,
|
||||||
|
afterMessages = { romText(data, "_NothingHappenedText",
|
||||||
|
"Nothing happened!") },
|
||||||
|
}
|
||||||
end
|
end
|
||||||
b.stages[stat] = cur + 1
|
b.stages[stat] = cur + 1
|
||||||
b.hazeStatReset = nil
|
b.hazeStatReset = nil
|
||||||
@@ -315,26 +325,29 @@ function ItemEffects.use(data, save, itemId, target, battle, moveIndex, ow)
|
|||||||
and battle.kind ~= "link" then
|
and battle.kind ~= "link" then
|
||||||
require("src.battle.Damage").reapplyBadgeBoosts(b, stat)
|
require("src.battle.Damage").reapplyBadgeBoosts(b, stat)
|
||||||
end
|
end
|
||||||
return "consumed", { Strings("%s's\n%s rose!", b.name, Strings(STAT_LABEL[stat])) }
|
return "consumed", { used }, {
|
||||||
|
useJingle = true,
|
||||||
|
afterMessages = { Strings("%s's\n%s rose!", b.name,
|
||||||
|
Strings(STAT_LABEL[stat])) },
|
||||||
|
}
|
||||||
end
|
end
|
||||||
-- ItemUseDireHit/ItemUseGuardSpec always set the bit and consume
|
-- ItemUseDireHit/ItemUseGuardSpec always set the bit and consume
|
||||||
-- the item, even when it is already active
|
-- the item, even when it is already active; vanilla prints only used
|
||||||
if itemId == "DIRE_HIT" then
|
if itemId == "DIRE_HIT" then
|
||||||
b.focusEnergy = true
|
b.focusEnergy = true
|
||||||
return "consumed", { romText(data, "_GettingPumpedText",
|
return "consumed", { used }, USE_JINGLE
|
||||||
"%s's\ngetting pumped!", b.name) }
|
|
||||||
end
|
end
|
||||||
if itemId == "GUARD_SPEC" then
|
if itemId == "GUARD_SPEC" then
|
||||||
b.mist = true
|
b.mist = true
|
||||||
return "consumed", { Strings("%s's\nprotected against\nstat changes!", b.name) }
|
return "consumed", { used }, USE_JINGLE
|
||||||
end
|
end
|
||||||
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", { notTime(data, save) }
|
return "failed", { notTime(data, save) }
|
||||||
end
|
end
|
||||||
return "consumed_escape", { romText(data, "_WildRanText",
|
-- PrintItemUseTextAndRemoveItem then escape
|
||||||
"The wild POKéMON\nran away!", battle.enemy and battle.enemy.name) }
|
return "consumed_escape", { used }, USE_JINGLE
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -613,7 +626,7 @@ function ItemEffects.use(data, save, itemId, target, battle, moveIndex, ow)
|
|||||||
if REPELS[itemId] then
|
if REPELS[itemId] 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
|
||||||
save.repelSteps = steps
|
save.repelSteps = steps
|
||||||
return "consumed", { Strings("%s used\n%s!", save.player.name, name) }
|
return "consumed", { itemUseLine(data, save, name) }, USE_JINGLE
|
||||||
end
|
end
|
||||||
|
|
||||||
return "failed", { notTime(data, save) }
|
return "failed", { notTime(data, save) }
|
||||||
|
|||||||
+49
-13
@@ -20,6 +20,8 @@ local PaletteFX = {}
|
|||||||
local shader -- false = unavailable (headless / no shader support)
|
local shader -- false = unavailable (headless / no shader support)
|
||||||
local gbcPack -- false = missing; nil = not loaded yet
|
local gbcPack -- false = missing; nil = not loaded yet
|
||||||
local yellowPack -- false = missing; nil = not loaded yet
|
local yellowPack -- false = missing; nil = not loaded yet
|
||||||
|
local gbcYellowPack -- Yellow Advanced deltas; false = missing; nil = not loaded yet
|
||||||
|
|
||||||
|
|
||||||
-- Cycle order matches OptionsMenu / hotkey 2. The three real colorizations
|
-- Cycle order matches OptionsMenu / hotkey 2. The three real colorizations
|
||||||
-- come first (OG RED/BLUE/YELLOW = GBC hardware, SGB = per-map Super Game Boy,
|
-- come first (OG RED/BLUE/YELLOW = GBC hardware, SGB = per-map Super Game Boy,
|
||||||
@@ -300,6 +302,38 @@ function PaletteFX.yellowPack()
|
|||||||
return yellowPack or nil
|
return yellowPack or nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Yellow-only Advanced deltas (BEACH_HOUSE, sprite remap, YELLOWMON). Never
|
||||||
|
-- consulted on Red/Blue (#1639).
|
||||||
|
function PaletteFX.gbcYellowPack()
|
||||||
|
if gbcYellowPack == nil then
|
||||||
|
local ok, pack = pcall(require, "data.palettes_gbc_yellow")
|
||||||
|
gbcYellowPack = ok and pack or false
|
||||||
|
end
|
||||||
|
return gbcYellowPack or nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Active world bake tables for Advanced. Yellow merges gbc_yellow deltas
|
||||||
|
-- via __index so shared Red tilesets stay identical byte-for-byte.
|
||||||
|
function PaletteFX.worldPack()
|
||||||
|
local pack = PaletteFX.gbcPack()
|
||||||
|
local w = pack and pack.world
|
||||||
|
if not w then return nil end
|
||||||
|
if not GameVersion.isYellow() then return w end
|
||||||
|
local y = PaletteFX.gbcYellowPack()
|
||||||
|
local yw = y and y.world
|
||||||
|
if not yw then return w end
|
||||||
|
return {
|
||||||
|
tileGroups = setmetatable(yw.tileGroups or {}, { __index = w.tileGroups }),
|
||||||
|
groupColors = setmetatable(yw.groupColors or {}, { __index = w.groupColors }),
|
||||||
|
roofGroup = w.roofGroup,
|
||||||
|
roofByMapIndex = w.roofByMapIndex,
|
||||||
|
spriteAssignment = yw.spriteAssignment or w.spriteAssignment,
|
||||||
|
spritePalettes = yw.spritePalettes
|
||||||
|
and setmetatable(yw.spritePalettes, { __index = w.spritePalettes })
|
||||||
|
or w.spritePalettes,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
function PaletteFX.usesGbcPack(mode)
|
function PaletteFX.usesGbcPack(mode)
|
||||||
mode = mode or PaletteFX.mode
|
mode = mode or PaletteFX.mode
|
||||||
return mode == "redpp"
|
return mode == "redpp"
|
||||||
@@ -453,7 +487,10 @@ function PaletteFX.pal(data, name)
|
|||||||
if fromRom then return fromRom end
|
if fromRom then return fromRom end
|
||||||
end
|
end
|
||||||
if GameVersion.isYellow() then
|
if GameVersion.isYellow() then
|
||||||
if PaletteFX.usesYellowCgb() then
|
-- OG YELLOW and Advanced both use CGBBase for named pals: SuperPalettes
|
||||||
|
-- wash out yellows (title MEWMON/LOGO, YELLOWMON) to pale cream. World
|
||||||
|
-- tile bake still comes from the Advanced GBC pack via worldPack (#1639).
|
||||||
|
if PaletteFX.usesYellowCgb() or PaletteFX.usesGbcPack() then
|
||||||
local fromCgb = yellowCgbNamedPal(data, name)
|
local fromCgb = yellowCgbNamedPal(data, name)
|
||||||
if fromCgb then return fromCgb end
|
if fromCgb then return fromCgb end
|
||||||
end
|
end
|
||||||
@@ -511,7 +548,8 @@ function PaletteFX.monPal(data, species, transformed)
|
|||||||
if pal then return pal end
|
if pal then return pal end
|
||||||
end
|
end
|
||||||
local name = p.pokemon[species] or "MEWMON"
|
local name = p.pokemon[species] or "MEWMON"
|
||||||
if PaletteFX.usesYellowCgb() then
|
if PaletteFX.usesYellowCgb()
|
||||||
|
or (GameVersion.isYellow() and PaletteFX.usesGbcPack()) then
|
||||||
local yc = PaletteFX.pal(data, name)
|
local yc = PaletteFX.pal(data, name)
|
||||||
if yc then return yc end
|
if yc then return yc end
|
||||||
end
|
end
|
||||||
@@ -618,16 +656,14 @@ local ROUTE_6_SAFFRON = { mapId = "ROUTE_6", useMapId = "SAFFRON_CITY", cellYBel
|
|||||||
-- (false for a mod tileset with no pokered-gbc counterpart, or when the
|
-- (false for a mod tileset with no pokered-gbc counterpart, or when the
|
||||||
-- pack failed to load at all)
|
-- pack failed to load at all)
|
||||||
function PaletteFX.hasWorldTileset(tileset)
|
function PaletteFX.hasWorldTileset(tileset)
|
||||||
local pack = PaletteFX.gbcPack()
|
local w = PaletteFX.worldPack()
|
||||||
local w = pack and pack.world
|
|
||||||
return (w and w.tileGroups[tileset]) ~= nil
|
return (w and w.tileGroups[tileset]) ~= nil
|
||||||
end
|
end
|
||||||
|
|
||||||
-- the palette-group (0-7) a tile GRAPHIC id resolves to in this tileset,
|
-- the palette-group (0-7) a tile GRAPHIC id resolves to in this tileset,
|
||||||
-- with the current map's tile-id exceptions (if any) applied first
|
-- with the current map's tile-id exceptions (if any) applied first
|
||||||
function PaletteFX.worldGroupAt(tileset, mapId, tileId)
|
function PaletteFX.worldGroupAt(tileset, mapId, tileId)
|
||||||
local pack = PaletteFX.gbcPack()
|
local w = PaletteFX.worldPack()
|
||||||
local w = pack and pack.world
|
|
||||||
local groups = w and w.tileGroups[tileset]
|
local groups = w and w.tileGroups[tileset]
|
||||||
if not groups then return nil end
|
if not groups then return nil end
|
||||||
local exc = TILE_GROUP_EXCEPTIONS[mapId]
|
local exc = TILE_GROUP_EXCEPTIONS[mapId]
|
||||||
@@ -642,8 +678,7 @@ end
|
|||||||
-- Saffron's roof colors while the player stands in its top 2 cell rows,
|
-- Saffron's roof colors while the player stands in its top 2 cell rows,
|
||||||
-- like pokered's wYCoord check -- data is Game.data, for the map lookup)
|
-- like pokered's wYCoord check -- data is Game.data, for the map lookup)
|
||||||
function PaletteFX.worldGroupColors(data, tileset, mapId, playerCellY)
|
function PaletteFX.worldGroupColors(data, tileset, mapId, playerCellY)
|
||||||
local pack = PaletteFX.gbcPack()
|
local w = PaletteFX.worldPack()
|
||||||
local w = pack and pack.world
|
|
||||||
local base = w and w.groupColors[tileset]
|
local base = w and w.groupColors[tileset]
|
||||||
if not base then return nil end
|
if not base then return nil end
|
||||||
if not w.roofGroup[tileset] then return darkGroups(base) end
|
if not w.roofGroup[tileset] then return darkGroups(base) end
|
||||||
@@ -678,17 +713,18 @@ end
|
|||||||
-- here): a stable hash instead, so the same NPC instance always shows the
|
-- here): a stable hash instead, so the same NPC instance always shows the
|
||||||
-- same one of the 4 SPR_PAL_* colors.
|
-- same one of the 4 SPR_PAL_* colors.
|
||||||
function PaletteFX.spriteObp(spriteDef, seed)
|
function PaletteFX.spriteObp(spriteDef, seed)
|
||||||
local pack = PaletteFX.gbcPack()
|
local w = PaletteFX.worldPack()
|
||||||
local w = pack and pack.world
|
|
||||||
local src = spriteDef and (spriteDef.paletteSource or spriteDef.source)
|
local src = spriteDef and (spriteDef.paletteSource or spriteDef.source)
|
||||||
if not (w and src) then return nil end
|
if not (w and src) then return nil end
|
||||||
local idx = tonumber(src:match("%[(%d+)%]"))
|
local idx = tonumber(src:match("%[(%d+)%]"))
|
||||||
-- RedBikeSprite and SurfingPikachuSprite load outside
|
-- RedBikeSprite and SurfingPikachuSprite load outside
|
||||||
-- SpriteSheetPointerTable, so their source has no bracketed index;
|
-- SpriteSheetPointerTable, so their source has no bracketed index;
|
||||||
-- they wear the player's OBP palette (spriteAssignment[0]).
|
-- they wear the player's OBP palette (spriteAssignment[0]) on Red/Blue.
|
||||||
if not idx and (src:find("RedBikeSprite", 1, true)
|
-- On Yellow, Surfing Pikachu uses the Pikachu yellow OBJ group (#1639).
|
||||||
or src:find("SurfingPikachuSprite", 1, true)) then
|
if not idx and src:find("RedBikeSprite", 1, true) then
|
||||||
idx = 0
|
idx = 0
|
||||||
|
elseif not idx and src:find("SurfingPikachuSprite", 1, true) then
|
||||||
|
idx = GameVersion.isYellow() and 60 or 0
|
||||||
end
|
end
|
||||||
local group = idx and w.spriteAssignment[idx]
|
local group = idx and w.spriteAssignment[idx]
|
||||||
if group == nil then return nil end
|
if group == nil then return nil end
|
||||||
|
|||||||
@@ -338,6 +338,7 @@ function Renderer:beginFrame(transparent)
|
|||||||
-- warp-fade overlay from Transition (issue #121); cleared each frame so
|
-- warp-fade overlay from Transition (issue #121); cleared each frame so
|
||||||
-- a popped transition cannot leave a sticky black veil
|
-- a popped transition cannot leave a sticky black veil
|
||||||
self.worldFadeAlpha = nil
|
self.worldFadeAlpha = nil
|
||||||
|
self.worldFadeColor = nil
|
||||||
-- battle-transition wipe, drawn over the whole surface (BattleTransition)
|
-- battle-transition wipe, drawn over the whole surface (BattleTransition)
|
||||||
self.battleWipe = nil
|
self.battleWipe = nil
|
||||||
-- whole-surface veil in screen space (battle-transition flash, the
|
-- whole-surface veil in screen space (battle-transition flash, the
|
||||||
@@ -1045,7 +1046,8 @@ function Renderer:endFrame(zones, worldZones)
|
|||||||
-- the screen-space overlays the flat path draws over its composite
|
-- the screen-space overlays the flat path draws over its composite
|
||||||
local fade = self.worldFadeAlpha
|
local fade = self.worldFadeAlpha
|
||||||
if fade and fade > 0 then
|
if fade and fade > 0 then
|
||||||
love.graphics.setColor(0, 0, 0, fade)
|
local c = self.worldFadeColor or { 0, 0, 0 }
|
||||||
|
love.graphics.setColor(c[1], c[2], c[3], fade)
|
||||||
love.graphics.rectangle("fill", vux, vuy, vuw, vuh)
|
love.graphics.rectangle("fill", vux, vuy, vuw, vuh)
|
||||||
love.graphics.setColor(1, 1, 1, 1)
|
love.graphics.setColor(1, 1, 1, 1)
|
||||||
end
|
end
|
||||||
@@ -1132,7 +1134,8 @@ function Renderer:endFrame(zones, worldZones)
|
|||||||
-- composite normally if one is ever stacked that way.
|
-- composite normally if one is ever stacked that way.
|
||||||
local fade = self.worldFadeAlpha
|
local fade = self.worldFadeAlpha
|
||||||
if fade and fade > 0 then
|
if fade and fade > 0 then
|
||||||
love.graphics.setColor(0, 0, 0, fade)
|
local c = self.worldFadeColor or { 0, 0, 0 }
|
||||||
|
love.graphics.setColor(c[1], c[2], c[3], fade)
|
||||||
love.graphics.rectangle("fill", vux, vuy, vuw, vuh)
|
love.graphics.rectangle("fill", vux, vuy, vuw, vuh)
|
||||||
love.graphics.setColor(1, 1, 1, 1)
|
love.graphics.setColor(1, 1, 1, 1)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -65,16 +65,24 @@ end
|
|||||||
-- (ViridianGym.asm .afterBeat, RocketHideoutB4F BeatGiovanniScript) call
|
-- (ViridianGym.asm .afterBeat, RocketHideoutB4F BeatGiovanniScript) call
|
||||||
-- GBFadeOutToBlack -> GBFadeInFromBlack instead, so the default keeps the
|
-- GBFadeOutToBlack -> GBFadeInFromBlack instead, so the default keeps the
|
||||||
-- symmetric 32-frame fade back in (home/fade.asm:21, b = 4).
|
-- symmetric 32-frame fade back in (home/fade.asm:21, b = 4).
|
||||||
function Transition.new(game, onMidpoint, onDone, warp)
|
--
|
||||||
|
-- opts.color = {r,g,b} in 0..1 (default black). opts.frames / opts.framesIn
|
||||||
|
-- override duration. Fly/Teleport/Dig/Escape Rope use white
|
||||||
|
-- GBFadeOutToWhite / GBFadeInFromWhite (#1644).
|
||||||
|
function Transition.new(game, onMidpoint, onDone, warp, opts)
|
||||||
|
opts = opts or {}
|
||||||
local self = setmetatable({}, Transition)
|
local self = setmetatable({}, Transition)
|
||||||
self.game = game
|
self.game = game
|
||||||
self.onMidpoint = onMidpoint
|
self.onMidpoint = onMidpoint
|
||||||
self.onDone = onDone
|
self.onDone = onDone
|
||||||
self.t = 0
|
self.t = 0
|
||||||
self.phase = "out"
|
self.phase = "out"
|
||||||
|
self.color = opts.color or { 0, 0, 0 }
|
||||||
local style = styleOf(game, "warp_fade")
|
local style = styleOf(game, "warp_fade")
|
||||||
self.frames = style.frames or FRAMES
|
self.frames = opts.frames or style.frames or FRAMES
|
||||||
if warp then
|
if opts.framesIn ~= nil then
|
||||||
|
self.framesIn = opts.framesIn
|
||||||
|
elseif warp then
|
||||||
-- a style may still ask for a fade in (mods, and the record is
|
-- a style may still ask for a fade in (mods, and the record is
|
||||||
-- data-driven); the built-in warp is 0, matching hardware
|
-- data-driven); the built-in warp is 0, matching hardware
|
||||||
self.framesIn = style.framesIn or FRAMES_IN
|
self.framesIn = style.framesIn or FRAMES_IN
|
||||||
@@ -125,6 +133,7 @@ end
|
|||||||
|
|
||||||
function Transition:draw()
|
function Transition:draw()
|
||||||
local alpha = self:alpha()
|
local alpha = self:alpha()
|
||||||
|
local c = self.color or { 0, 0, 0 }
|
||||||
-- Survey zoom draws the overworld into a window-filling world canvas
|
-- Survey zoom draws the overworld into a window-filling world canvas
|
||||||
-- while the UI pass stays the classic 160x144 letterbox. A rect on the
|
-- while the UI pass stays the classic 160x144 letterbox. A rect on the
|
||||||
-- UI canvas only darkens that center box (issue #121); when the world
|
-- UI canvas only darkens that center box (issue #121); when the world
|
||||||
@@ -133,9 +142,10 @@ function Transition:draw()
|
|||||||
local r = self.game and self.game.renderer
|
local r = self.game and self.game.renderer
|
||||||
if r and r.worldActive then
|
if r and r.worldActive then
|
||||||
r.worldFadeAlpha = alpha
|
r.worldFadeAlpha = alpha
|
||||||
|
r.worldFadeColor = c
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
love.graphics.setColor(0, 0, 0, alpha)
|
love.graphics.setColor(c[1], c[2], c[3], alpha)
|
||||||
love.graphics.rectangle("fill", 0, 0, 160, 144)
|
love.graphics.rectangle("fill", 0, 0, 160, 144)
|
||||||
love.graphics.setColor(1, 1, 1, 1)
|
love.graphics.setColor(1, 1, 1, 1)
|
||||||
end
|
end
|
||||||
|
|||||||
+19
-4
@@ -54,6 +54,21 @@ local function showMessages(game, msgs, onDone, opts)
|
|||||||
game.stack:push(TextBox.new(game, table.concat(msgs, "\f"), onDone, opts))
|
game.stack:push(TextBox.new(game, table.concat(msgs, "\f"), onDone, opts))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- PrintItemUseTextAndRemoveItem: used-line TextBox carries Heal_Ailment, then
|
||||||
|
-- optional afterMessages (X Stat effect line) before onDone (#1635).
|
||||||
|
local function showUseMessages(game, msgs, onDone, extra)
|
||||||
|
local opts = (extra and extra.useJingle)
|
||||||
|
and TextBox.soundOpts(game, "Heal_Ailment") or nil
|
||||||
|
local after = extra and extra.afterMessages
|
||||||
|
if after and #after > 0 then
|
||||||
|
showMessages(game, msgs, function()
|
||||||
|
showMessages(game, after, onDone)
|
||||||
|
end, opts)
|
||||||
|
else
|
||||||
|
showMessages(game, msgs, onDone, opts)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- run the use-flow for an item on a chosen target. `picker` is the party
|
-- run the use-flow for an item on a chosen target. `picker` is the party
|
||||||
-- menu when it was opened with keepOpen (HP medicine only): it is still on
|
-- menu when it was opened with keepOpen (HP medicine only): it is still on
|
||||||
-- the stack, so every exit that prints has to close it afterwards. For
|
-- the stack, so every exit that prints has to close it afterwards. For
|
||||||
@@ -115,7 +130,7 @@ local function vanillaUseOn(game, battle, id, target, list, moveIndex, picker)
|
|||||||
if result == "consumed_escape" then -- Poké Doll
|
if result == "consumed_escape" then -- Poké Doll
|
||||||
consume(game, id, list)
|
consume(game, id, list)
|
||||||
list:close()
|
list:close()
|
||||||
showMessages(game, payload, function()
|
showUseMessages(game, payload, function()
|
||||||
-- ItemUsePokeDoll sets wEscapedFromBattle and never touches
|
-- ItemUsePokeDoll sets wEscapedFromBattle and never touches
|
||||||
-- wBattleResult, so a script that reads the result afterwards sees
|
-- wBattleResult, so a script that reads the result afterwards sees
|
||||||
-- 0 -- "defeated". The ghost MAROWAK's script keys on exactly that
|
-- 0 -- "defeated". The ghost MAROWAK's script keys on exactly that
|
||||||
@@ -125,7 +140,7 @@ local function vanillaUseOn(game, battle, id, target, list, moveIndex, picker)
|
|||||||
battle.result = "run"
|
battle.result = "run"
|
||||||
battle.afterQueue = "finish"
|
battle.afterQueue = "finish"
|
||||||
battle.phase = "messages"
|
battle.phase = "messages"
|
||||||
end)
|
end, extra)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -403,9 +418,9 @@ local function vanillaUseOn(game, battle, id, target, list, moveIndex, picker)
|
|||||||
end
|
end
|
||||||
if battle then
|
if battle then
|
||||||
list:close()
|
list:close()
|
||||||
showMessages(game, payload, function() battle:itemUsed({}) end)
|
showUseMessages(game, payload, function() battle:itemUsed({}) end, extra)
|
||||||
else
|
else
|
||||||
showMessages(game, payload, closePicker)
|
showUseMessages(game, payload, closePicker, extra)
|
||||||
end
|
end
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4818,6 +4818,15 @@ function OverworldState:startWarpTo(mapId, x, y, facing, onDone, opts)
|
|||||||
require("src.core.Sound").play(Game.data,
|
require("src.core.Sound").play(Game.data,
|
||||||
outdoor and "Go_Outside" or "Go_Inside")
|
outdoor and "Go_Outside" or "Go_Inside")
|
||||||
end
|
end
|
||||||
|
-- Fly/Teleport/Dig/Escape Rope: GBFadeOutToWhite / GBFadeInFromWhite
|
||||||
|
-- (player_animations.asm). Door warps stay black with no fade-in (#1644).
|
||||||
|
local Timing = require("src.core.Timing")
|
||||||
|
local specialWarp = arriveWarp == "fly" or arriveWarp == "teleport"
|
||||||
|
local fadeOpts = specialWarp and {
|
||||||
|
color = { 1, 1, 1 },
|
||||||
|
frames = Timing.FADE_OUT_TO_WHITE,
|
||||||
|
framesIn = Timing.FADE_IN_FROM_WHITE,
|
||||||
|
} or nil
|
||||||
Game.stack:push(Transition.new(Game, function()
|
Game.stack:push(Transition.new(Game, function()
|
||||||
self:setMap(mapId, x, y, facing or "down", opts)
|
self:setMap(mapId, x, y, facing or "down", opts)
|
||||||
-- the departure-side hide from flyAnim/teleportOut ends here, on the new
|
-- the departure-side hide from flyAnim/teleportOut ends here, on the new
|
||||||
@@ -4884,7 +4893,7 @@ function OverworldState:startWarpTo(mapId, x, y, facing, onDone, opts)
|
|||||||
end, function()
|
end, function()
|
||||||
self.transitioning = false
|
self.transitioning = false
|
||||||
if onDone then onDone() end
|
if onDone then onDone() end
|
||||||
end, true)) -- warp shape: no fade back in (LoadGBPal restores in one write)
|
end, not specialWarp, fadeOpts))
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Re-read a map record after its data changed (WorldAPI:invalidateMap,
|
-- Re-read a map record after its data changed (WorldAPI:invalidateMap,
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
-- #1635: PrintItemUseTextAndRemoveItem items must request Heal_Ailment
|
||||||
|
-- via extra.useJingle after the "X used Y!" line.
|
||||||
|
--
|
||||||
|
-- luajit tests/engine/item_use_jingle_bug1635.lua
|
||||||
|
|
||||||
|
package.path = "./?.lua;./?/init.lua;" .. package.path
|
||||||
|
if not _G.love then _G.love = require("tests.love_stub") end
|
||||||
|
|
||||||
|
local S = require("tests.harness").suite("item use Heal_Ailment jingle #1635")
|
||||||
|
local check, eq = S.check, S.eq
|
||||||
|
|
||||||
|
local Data = require("src.core.Data")
|
||||||
|
local ItemEffects = require("src.inventory.ItemEffects")
|
||||||
|
local SaveData = require("src.core.SaveData")
|
||||||
|
|
||||||
|
if not Data.maps then Data:load() end
|
||||||
|
|
||||||
|
local save = SaveData.newGame()
|
||||||
|
save.player.name = "RED"
|
||||||
|
|
||||||
|
local function battleStub(playerName)
|
||||||
|
return {
|
||||||
|
kind = "wild",
|
||||||
|
player = {
|
||||||
|
name = playerName or "PIKACHU",
|
||||||
|
stages = { attack = 0, defense = 0, speed = 0, special = 0, accuracy = 0 },
|
||||||
|
mon = {},
|
||||||
|
},
|
||||||
|
enemy = { name = "RATTATA" },
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
local function assertJingle(itemId, battle)
|
||||||
|
local result, msgs, extra = ItemEffects.use(Data, save, itemId, nil, battle)
|
||||||
|
check(result == "consumed" or result == "consumed_escape",
|
||||||
|
itemId .. " succeeds (" .. tostring(result) .. ")")
|
||||||
|
check(extra and extra.useJingle, itemId .. " sets useJingle")
|
||||||
|
check(msgs and msgs[1] and msgs[1]:find("used"),
|
||||||
|
itemId .. " prints used line")
|
||||||
|
return result, msgs, extra
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, id in ipairs({ "REPEL", "SUPER_REPEL", "MAX_REPEL" }) do
|
||||||
|
assertJingle(id, nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
local b = battleStub()
|
||||||
|
for _, id in ipairs({
|
||||||
|
"X_ATTACK", "X_DEFEND", "X_SPEED", "X_SPECIAL",
|
||||||
|
"X_ACCURACY", "DIRE_HIT", "GUARD_SPEC",
|
||||||
|
}) do
|
||||||
|
local _, _, extra = assertJingle(id, b)
|
||||||
|
if id == "X_ATTACK" then
|
||||||
|
check(extra.afterMessages and #extra.afterMessages > 0,
|
||||||
|
"X_ATTACK follows used line with effect text")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local _, _, dollExtra = assertJingle("POKE_DOLL", battleStub())
|
||||||
|
eq(dollExtra.afterMessages, nil, "Poké Doll is used-line only")
|
||||||
|
|
||||||
|
S.finish()
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
-- #1743: Mt. Moon B2F Super Nerd is text_asm with no def_trainers row, so
|
||||||
|
-- Yellow never gets trainerHeaders.MtMoonB2F[1]. Without a seed,
|
||||||
|
-- engageTrainer falls through to the "I like shorts!" fallback.
|
||||||
|
--
|
||||||
|
-- luajit tests/engine/mt_moon_super_nerd_header_bug1743.lua
|
||||||
|
|
||||||
|
package.path = "./?.lua;./?/init.lua;" .. package.path
|
||||||
|
if not _G.love then _G.love = require("tests.love_stub") end
|
||||||
|
|
||||||
|
local S = require("tests.harness").suite("mt moon super nerd header #1743")
|
||||||
|
local check, eq = S.check, S.eq
|
||||||
|
|
||||||
|
local Data = require("src.core.Data")
|
||||||
|
|
||||||
|
local empty = { trainer_headers = {} }
|
||||||
|
Data.seedMtMoonB2FSuperNerd(empty)
|
||||||
|
local seeded = empty.trainer_headers.MtMoonB2F[1]
|
||||||
|
check(seeded ~= nil, "seed fills MtMoonB2F[1] when missing")
|
||||||
|
eq(seeded.battle, "_MtMoonB2FSuperNerdTheyreBothMineText",
|
||||||
|
"pre-battle text is They're both mine")
|
||||||
|
eq(seeded.won, "_MtMoonB2FSuperNerdOkIllShareText", "won text seeded")
|
||||||
|
eq(seeded.after, "_MtMoonB2FSuperNerdTheresAPokemonLabText", "after text seeded")
|
||||||
|
eq(seeded.event, "EVENT_BEAT_MT_MOON_3_SUPER_NERD",
|
||||||
|
"event name matches shipped Red/Blue pin")
|
||||||
|
|
||||||
|
-- idempotent: existing header wins
|
||||||
|
empty.trainer_headers.MtMoonB2F[1] = { battle = "KEEP" }
|
||||||
|
Data.seedMtMoonB2FSuperNerd(empty)
|
||||||
|
eq(empty.trainer_headers.MtMoonB2F[1].battle, "KEEP",
|
||||||
|
"seed does not overwrite an existing header")
|
||||||
|
|
||||||
|
-- live Data load (Red/Blue pin or Yellow seed) must expose the battle label
|
||||||
|
if not Data.maps then Data:load() end
|
||||||
|
local header = Data:trainerHeader("MtMoonB2F", 1)
|
||||||
|
check(header ~= nil, "loaded Data has MtMoonB2F[1]")
|
||||||
|
eq(header.battle, "_MtMoonB2FSuperNerdTheyreBothMineText",
|
||||||
|
"engageTrainer can resolve real pre-battle text")
|
||||||
|
|
||||||
|
S.finish()
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
-- #1644: Fly/Teleport special warps fade white (GBFadeOutToWhite /
|
||||||
|
-- GBFadeInFromWhite), not black door-warp shape.
|
||||||
|
--
|
||||||
|
-- luajit tests/engine/special_warp_white_fade_bug1644.lua
|
||||||
|
|
||||||
|
package.path = "./?.lua;./?/init.lua;" .. package.path
|
||||||
|
if not _G.love then _G.love = require("tests.love_stub") end
|
||||||
|
|
||||||
|
local S = require("tests.harness").suite("special warp white fade #1644")
|
||||||
|
local check, eq = S.check, S.eq
|
||||||
|
|
||||||
|
local Timing = require("src.core.Timing")
|
||||||
|
local Transition = require("src.render.Transition")
|
||||||
|
|
||||||
|
local white = { 1, 1, 1 }
|
||||||
|
local t = Transition.new(nil, function() end, nil, false, {
|
||||||
|
color = white,
|
||||||
|
frames = Timing.FADE_OUT_TO_WHITE,
|
||||||
|
framesIn = Timing.FADE_IN_FROM_WHITE,
|
||||||
|
})
|
||||||
|
eq(t.color[1], 1, "special warp veil is white R")
|
||||||
|
eq(t.color[2], 1, "special warp veil is white G")
|
||||||
|
eq(t.color[3], 1, "special warp veil is white B")
|
||||||
|
eq(t.frames, Timing.FADE_OUT_TO_WHITE, "fade out uses FADE_OUT_TO_WHITE")
|
||||||
|
eq(t.framesIn, Timing.FADE_IN_FROM_WHITE, "fade in uses FADE_IN_FROM_WHITE")
|
||||||
|
|
||||||
|
local door = Transition.new(nil, function() end, nil, true)
|
||||||
|
eq(door.color[1], 0, "door warp stays black")
|
||||||
|
eq(door.framesIn, 0, "door warp has no fade in")
|
||||||
|
check(door.frames == Timing.WARP_FADE_OUT, "door warp uses WARP_FADE_OUT")
|
||||||
|
|
||||||
|
S.finish()
|
||||||
@@ -31,16 +31,19 @@ local save = SaveData.newGame()
|
|||||||
local player = { name = "FIXMON", stages = {} }
|
local player = { name = "FIXMON", stages = {} }
|
||||||
local xBattle = { player = player, kind = "wild" }
|
local xBattle = { player = player, kind = "wild" }
|
||||||
|
|
||||||
local _, baseline = ItemEffects.use(Data, save, "X_ATTACK", nil, xBattle)
|
local _, baseline, baselineExtra = ItemEffects.use(Data, save, "X_ATTACK", nil, xBattle)
|
||||||
T.check(baseline[1]:find("ATTACK", 1, true) ~= nil,
|
local baselineRose = baselineExtra and baselineExtra.afterMessages
|
||||||
|
and baselineExtra.afterMessages[1] or baseline[1]
|
||||||
|
T.check(baselineRose:find("ATTACK", 1, true) ~= nil,
|
||||||
"X ATTACK's rose! message names the stat in English with no catalog")
|
"X ATTACK's rose! message names the stat in English with no catalog")
|
||||||
|
|
||||||
withCatalog({ ATTACK = "ATTAQUE" }, function()
|
withCatalog({ ATTACK = "ATTAQUE" }, function()
|
||||||
player.stages.attack = nil
|
player.stages.attack = nil
|
||||||
local _, msgs = ItemEffects.use(Data, save, "X_ATTACK", nil, xBattle)
|
local _, msgs, extra = ItemEffects.use(Data, save, "X_ATTACK", nil, xBattle)
|
||||||
T.check(msgs[1]:find("ATTAQUE", 1, true) ~= nil,
|
local rose = extra and extra.afterMessages and extra.afterMessages[1] or msgs[1]
|
||||||
|
T.check(rose:find("ATTAQUE", 1, true) ~= nil,
|
||||||
"a catalog translating ATTACK reaches the X ATTACK rose! message")
|
"a catalog translating ATTACK reaches the X ATTACK rose! message")
|
||||||
T.check(msgs[1]:find("ATTACK", 1, true) == nil,
|
T.check(rose:find("ATTACK", 1, true) == nil,
|
||||||
"...and the untranslated English stat name is gone")
|
"...and the untranslated English stat name is gone")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
-- Title eye OBP remap lives in ImageWriter.applyTitleObp0; pixel-level
|
||||||
|
-- coverage is tests/title_pikachu_obp_test.py (love_stub ImageData is a
|
||||||
|
-- no-op). This file only pins the export so the Lua bake path stays wired.
|
||||||
|
--
|
||||||
|
-- luajit tests/engine/title_pikachu_obp_bug.lua
|
||||||
|
|
||||||
|
package.path = "./?.lua;./?/init.lua;" .. package.path
|
||||||
|
if not _G.love then _G.love = require("tests.love_stub") end
|
||||||
|
|
||||||
|
local S = require("tests.harness").suite("title pikachu OBP export")
|
||||||
|
local check = S.check
|
||||||
|
|
||||||
|
local ImageWriter = require("src.import.ImageWriter")
|
||||||
|
check(type(ImageWriter.applyTitleObp0) == "function",
|
||||||
|
"ImageWriter.applyTitleObp0 is exported for RomExtractor")
|
||||||
|
check(type(ImageWriter.SHADES) == "table" and #ImageWriter.SHADES == 4,
|
||||||
|
"ImageWriter.SHADES exposes the 4 DMG ramps")
|
||||||
|
|
||||||
|
S.finish()
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
-- #1639: Yellow-only Advanced overlay — BEACH_HOUSE world bake, sprite
|
||||||
|
-- coverage past Red's 72 entries, saturated YELLOWMON — without changing
|
||||||
|
-- Red/Blue Advanced.
|
||||||
|
--
|
||||||
|
-- luajit tests/engine/yellow_advanced_palette_bug1639.lua
|
||||||
|
|
||||||
|
package.path = "./?.lua;./?/init.lua;" .. package.path
|
||||||
|
if not _G.love then _G.love = require("tests.love_stub") end
|
||||||
|
|
||||||
|
local S = require("tests.harness").suite("yellow advanced palette #1639")
|
||||||
|
local check, eq = S.check, S.eq
|
||||||
|
|
||||||
|
local GameVersion = require("src.core.GameVersion")
|
||||||
|
local PaletteFX = require("src.render.PaletteFX")
|
||||||
|
|
||||||
|
local prevVer = GameVersion.get()
|
||||||
|
local prevMode = PaletteFX.mode
|
||||||
|
|
||||||
|
PaletteFX.setMode("redpp")
|
||||||
|
|
||||||
|
-- Red: no BEACH_HOUSE, shared HOUSE still present
|
||||||
|
GameVersion.set("red")
|
||||||
|
check(not PaletteFX.hasWorldTileset("BEACH_HOUSE"),
|
||||||
|
"Red Advanced has no BEACH_HOUSE tileset")
|
||||||
|
check(PaletteFX.hasWorldTileset("HOUSE"), "Red Advanced still has HOUSE")
|
||||||
|
local redHouse = PaletteFX.worldGroupColors(nil, "HOUSE", "PALLET_TOWN", 0)
|
||||||
|
|
||||||
|
-- Blue unchanged for BEACH_HOUSE
|
||||||
|
GameVersion.set("blue")
|
||||||
|
check(not PaletteFX.hasWorldTileset("BEACH_HOUSE"),
|
||||||
|
"Blue Advanced has no BEACH_HOUSE tileset")
|
||||||
|
|
||||||
|
-- Yellow: BEACH_HOUSE present, HOUSE still inherits from Red pack
|
||||||
|
GameVersion.set("yellow")
|
||||||
|
check(PaletteFX.hasWorldTileset("BEACH_HOUSE"),
|
||||||
|
"Yellow Advanced profiles BEACH_HOUSE")
|
||||||
|
check(PaletteFX.hasWorldTileset("HOUSE"),
|
||||||
|
"Yellow Advanced still sees shared HOUSE")
|
||||||
|
local beach = PaletteFX.worldGroupColors(nil, "BEACH_HOUSE", "SUMMER_BEACH_HOUSE", 0)
|
||||||
|
check(beach ~= nil and #beach == 8, "BEACH_HOUSE has 8 group colors")
|
||||||
|
eq(beach[1][1][1], 255, "BEACH_HOUSE sand group is warm (R=255)")
|
||||||
|
|
||||||
|
-- Sprite: Pikachu @60 and high indices resolve; Red path for bike stays 0
|
||||||
|
local pika = PaletteFX.spriteObp({ source = "ROM:SpriteSheetPointerTable[60]" }, "pika")
|
||||||
|
check(pika ~= nil, "Yellow SPRITE_PIKACHU gets an OBP")
|
||||||
|
eq(pika[2][2], 255, "Pikachu OBP mid shade is saturated yellow G=255")
|
||||||
|
local boulder = PaletteFX.spriteObp({ source = "ROM:SpriteSheetPointerTable[72]" }, "b")
|
||||||
|
check(boulder ~= nil, "Yellow boulder (index 72) gets an OBP")
|
||||||
|
|
||||||
|
GameVersion.set("red")
|
||||||
|
local redBall = PaletteFX.spriteObp({ source = "ROM:SpriteSheetPointerTable[60]" }, "ball")
|
||||||
|
check(redBall ~= nil, "Red poke-ball @60 still resolves")
|
||||||
|
-- Red group 0 mid is orange skin, not pure yellow
|
||||||
|
check(redBall[2][2] < 200, "Red @60 is not Yellow's Pikachu ramp")
|
||||||
|
|
||||||
|
-- YELLOWMON / MEWMON / LOGO: Advanced uses CGBBase (saturated), not washed
|
||||||
|
-- SuperPalettes
|
||||||
|
GameVersion.set("yellow")
|
||||||
|
local yellowMon = PaletteFX.pal(nil, "YELLOWMON")
|
||||||
|
eq(yellowMon[2][2], 255, "Advanced Yellow YELLOWMON mid is saturated yellow G")
|
||||||
|
eq(yellowMon[2][3], 0, "Advanced Yellow YELLOWMON mid is pure yellow B=0")
|
||||||
|
local mewmon = PaletteFX.pal(nil, "MEWMON")
|
||||||
|
eq(mewmon[2][1], 255, "MEWMON stays Yellow title yellow (not Red++ purple)")
|
||||||
|
eq(mewmon[2][2], 255, "MEWMON body is saturated yellow G=255 (not washed 156)")
|
||||||
|
eq(mewmon[2][3], 0, "MEWMON body is pure yellow B=0")
|
||||||
|
check(mewmon[3][3] < 150, "MEWMON cheek stays red, not purple")
|
||||||
|
local logo2 = PaletteFX.pal(nil, "LOGO2")
|
||||||
|
eq(logo2[2][2], 255, "LOGO2 fill is saturated yellow under Advanced")
|
||||||
|
eq(logo2[2][3], 0, "LOGO2 fill is pure yellow B=0")
|
||||||
|
|
||||||
|
-- Red HOUSE colors identical before/after Yellow merge path
|
||||||
|
GameVersion.set("red")
|
||||||
|
local redHouse2 = PaletteFX.worldGroupColors(nil, "HOUSE", "PALLET_TOWN", 0)
|
||||||
|
eq(redHouse2[1][1][1], redHouse[1][1][1], "Red HOUSE colors unchanged")
|
||||||
|
|
||||||
|
PaletteFX.setMode(prevMode)
|
||||||
|
GameVersion.set(prevVer)
|
||||||
|
|
||||||
|
S.finish()
|
||||||
@@ -102,5 +102,44 @@ class ApplyKnownNonreproducibleOverridesTest(TestCase):
|
|||||||
texts, field_data)
|
texts, field_data)
|
||||||
|
|
||||||
|
|
||||||
|
class YellowSuperRodParseTest(TestCase):
|
||||||
|
"""#1074: Yellow's inline Super Rod table (species, level), not Red's."""
|
||||||
|
|
||||||
|
def test_parse_super_rod_yellow_safari_dragonair(self):
|
||||||
|
import json
|
||||||
|
import tempfile
|
||||||
|
from extract import field
|
||||||
|
|
||||||
|
asm = (
|
||||||
|
"SuperRodFishingSlots::\n"
|
||||||
|
"\tdb SAFARI_ZONE_CENTER, MAGIKARP, 5, MAGIKARP, 10, "
|
||||||
|
"DRATINI, 10, DRAGONAIR, 15\n"
|
||||||
|
"\tdb SAFARI_ZONE_EAST, MAGIKARP, 5, MAGIKARP, 10, "
|
||||||
|
"MAGIKARP, 15, DRATINI, 15\n"
|
||||||
|
"\tdb -1 ; end\n"
|
||||||
|
)
|
||||||
|
with tempfile.TemporaryDirectory() as td:
|
||||||
|
wild = Path(td) / "data" / "wild"
|
||||||
|
wild.mkdir(parents=True)
|
||||||
|
(wild / "super_rod.asm").write_text(asm)
|
||||||
|
parsed = field.parse_super_rod_yellow(td)
|
||||||
|
|
||||||
|
self.assertEqual(parsed["SAFARI_ZONE_CENTER"], [
|
||||||
|
{"level": 5, "species": "MAGIKARP"},
|
||||||
|
{"level": 10, "species": "MAGIKARP"},
|
||||||
|
{"level": 10, "species": "DRATINI"},
|
||||||
|
{"level": 15, "species": "DRAGONAIR"},
|
||||||
|
])
|
||||||
|
self.assertNotIn("DRAGONAIR",
|
||||||
|
[s["species"] for s in parsed["SAFARI_ZONE_EAST"]])
|
||||||
|
|
||||||
|
def test_shipped_yellow_manifest_has_safari_dragonair(self):
|
||||||
|
import json
|
||||||
|
path = Path(__file__).resolve().parents[1] / "tools" / "rom_manifest_yellow.json"
|
||||||
|
data = json.loads(path.read_text(encoding="utf-8"))
|
||||||
|
center = data["field"]["superRod"]["SAFARI_ZONE_CENTER"]
|
||||||
|
self.assertIn({"level": 15, "species": "DRAGONAIR"}, center)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Title rOBP0=$E0 remaps eye OAM shades 1/2 to white (#1639 pupils)."""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from unittest import TestCase, main
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "tools"))
|
||||||
|
import build_rom_data as b # noqa: E402
|
||||||
|
from PIL import Image # noqa: E402
|
||||||
|
|
||||||
|
|
||||||
|
class TitleObp0Test(TestCase):
|
||||||
|
def test_mid_and_dark_become_white(self):
|
||||||
|
img = Image.new("RGBA", (4, 1), (0, 0, 0, 0))
|
||||||
|
img.putpixel((0, 0), b.GB_SHADES[1])
|
||||||
|
img.putpixel((1, 0), b.GB_SHADES[2])
|
||||||
|
img.putpixel((2, 0), b.GB_SHADES[3])
|
||||||
|
img.putpixel((3, 0), b.GB_SHADES[0])
|
||||||
|
b._apply_title_obp0(img)
|
||||||
|
self.assertEqual(img.getpixel((0, 0)), b.GB_SHADES[0])
|
||||||
|
self.assertEqual(img.getpixel((1, 0)), b.GB_SHADES[0])
|
||||||
|
self.assertEqual(img.getpixel((2, 0)), b.GB_SHADES[3])
|
||||||
|
self.assertEqual(img.getpixel((3, 0)), b.GB_SHADES[0])
|
||||||
|
|
||||||
|
def test_transparent_untouched(self):
|
||||||
|
img = Image.new("RGBA", (1, 1), (0, 0, 0, 0))
|
||||||
|
b._apply_title_obp0(img)
|
||||||
|
self.assertEqual(img.getpixel((0, 0))[3], 0)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -22,9 +22,12 @@ eq(mewmonSgb[3][1], 222, "SGB: Color 2 is red R=222")
|
|||||||
PaletteFX.setMode("redpp")
|
PaletteFX.setMode("redpp")
|
||||||
local mewmonAdv = PaletteFX.pal(nil, "MEWMON")
|
local mewmonAdv = PaletteFX.pal(nil, "MEWMON")
|
||||||
check(mewmonAdv ~= nil, "MEWMON palette exists in ADVANCED mode on Yellow")
|
check(mewmonAdv ~= nil, "MEWMON palette exists in ADVANCED mode on Yellow")
|
||||||
-- Must NOT be Red++'s Mew purple {115, 33, 165}! It must be Yellow's Pikachu palette!
|
-- Must NOT be Red++'s Mew purple {115, 33, 165}! It must be Yellow's Pikachu
|
||||||
|
-- CGBBase yellow {255,255,0}, not washed SuperPalette cream {255,255,156}.
|
||||||
eq(mewmonAdv[1][1], 255, "ADVANCED: Color 0 is white R=255 (Pikachu eye sclera)")
|
eq(mewmonAdv[1][1], 255, "ADVANCED: Color 0 is white R=255 (Pikachu eye sclera)")
|
||||||
eq(mewmonAdv[2][1], 255, "ADVANCED: Color 1 is yellow R=255 (Pikachu body)")
|
eq(mewmonAdv[2][1], 255, "ADVANCED: Color 1 is yellow R=255 (Pikachu body)")
|
||||||
|
eq(mewmonAdv[2][2], 255, "ADVANCED: Color 1 is saturated yellow G=255")
|
||||||
|
eq(mewmonAdv[2][3], 0, "ADVANCED: Color 1 is pure yellow B=0 (not washed cream)")
|
||||||
check(mewmonAdv[3][3] < 150, "ADVANCED: Color 2 is not purple (B < 150, red cheeks)")
|
check(mewmonAdv[3][3] < 150, "ADVANCED: Color 2 is not purple (B < 150, red cheeks)")
|
||||||
|
|
||||||
-- Test 3: OG YELLOW mode (ogred on Yellow)
|
-- Test 3: OG YELLOW mode (ogred on Yellow)
|
||||||
|
|||||||
+42
-1
@@ -77,6 +77,26 @@ GB_SHADES = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _apply_title_obp0(image):
|
||||||
|
"""Title rOBP0=%11100000 ($E0): OBJ shades 1 and 2 → white, 3 → black.
|
||||||
|
|
||||||
|
Eye OAM is baked into the MEWMON-colored BG PNG; without this remap the
|
||||||
|
shade-1 glints become body yellow under the title palette
|
||||||
|
(pokeyellow engine/movie/title.asm after PlacePikachu).
|
||||||
|
"""
|
||||||
|
pixels = image.load()
|
||||||
|
w, h = image.size
|
||||||
|
mid, dark = GB_SHADES[1][0], GB_SHADES[2][0]
|
||||||
|
for y in range(h):
|
||||||
|
for x in range(w):
|
||||||
|
r, g, b, a = pixels[x, y]
|
||||||
|
if a == 0:
|
||||||
|
continue
|
||||||
|
if abs(r - mid) <= 2 or abs(r - dark) <= 2:
|
||||||
|
pixels[x, y] = GB_SHADES[0]
|
||||||
|
return image
|
||||||
|
|
||||||
|
|
||||||
def _symbol(symbols, name):
|
def _symbol(symbols, name):
|
||||||
try:
|
try:
|
||||||
return symbols[name]
|
return symbols[name]
|
||||||
@@ -1880,12 +1900,33 @@ def extract_field(rom, symbols, manifest, out_dir, assets_dir):
|
|||||||
(3, 24, 24, True), (2, 32, 24, True),
|
(3, 24, 24, True), (2, 32, 24, True),
|
||||||
(0, 56, 16, False), (1, 64, 16, False),
|
(0, 56, 16, False), (1, 64, 16, False),
|
||||||
(2, 56, 24, False), (3, 64, 24, False)):
|
(2, 56, 24, False), (3, 64, 24, False)):
|
||||||
eye = ob_clear[ob_index]
|
eye = ob_clear[ob_index].copy()
|
||||||
|
_apply_title_obp0(eye)
|
||||||
if flip:
|
if flip:
|
||||||
eye = eye.transpose(Image.Transpose.FLIP_LEFT_RIGHT)
|
eye = eye.transpose(Image.Transpose.FLIP_LEFT_RIGHT)
|
||||||
pikachu.paste(eye, (px, py), eye)
|
pikachu.paste(eye, (px, py), eye)
|
||||||
_save_png(pikachu, os.path.join(assets_dir, "title/pikachu.png"))
|
_save_png(pikachu, os.path.join(assets_dir, "title/pikachu.png"))
|
||||||
|
|
||||||
|
# Blink overlays (half/closed) — same OBP remap as open eyes.
|
||||||
|
eye_layout = (
|
||||||
|
(1, 24, 16, True), (0, 32, 16, True),
|
||||||
|
(3, 24, 24, True), (2, 32, 24, True),
|
||||||
|
(0, 56, 16, False), (1, 64, 16, False),
|
||||||
|
(2, 56, 24, False), (3, 64, 24, False),
|
||||||
|
)
|
||||||
|
# Re-compose blank-face pika for overlays (open eyes already baked).
|
||||||
|
blank_face = matte_color0(compose(13, 9, pika_cells))
|
||||||
|
for suffix, base in (("eyes_half", 4), ("eyes_closed", 8)):
|
||||||
|
overlay = Image.new("RGBA", (48, 16), (255, 255, 255, 0))
|
||||||
|
overlay.paste(blank_face.crop((24, 16, 72, 32)), (0, 0))
|
||||||
|
for ob_index, px, py, flip in eye_layout:
|
||||||
|
eye = ob_clear[base + ob_index].copy()
|
||||||
|
_apply_title_obp0(eye)
|
||||||
|
if flip:
|
||||||
|
eye = eye.transpose(Image.Transpose.FLIP_LEFT_RIGHT)
|
||||||
|
overlay.paste(eye, (px - 24, py - 16), eye)
|
||||||
|
_save_png(overlay, os.path.join(assets_dir, f"title/{suffix}.png"))
|
||||||
|
|
||||||
falling_star = raw_2bpp(
|
falling_star = raw_2bpp(
|
||||||
"FallingStar", 8, 8, "intro/falling_star.png",
|
"FallingStar", 8, 8, "intro/falling_star.png",
|
||||||
transparent=True)
|
transparent=True)
|
||||||
|
|||||||
@@ -86,6 +86,43 @@ def parse_super_rod(pokered):
|
|||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def parse_super_rod_yellow(pokeyellow):
|
||||||
|
"""pokeyellow data/wild/super_rod.asm: inline species,level rows.
|
||||||
|
|
||||||
|
Yellow stores four (species, level) pairs per map on one `db` line
|
||||||
|
(`db MAP, SPECIES, LEVEL, SPECIES, LEVEL, ...`), unlike Red's
|
||||||
|
`dbw MAP, .Group` + `db level, species` groups. Slot order is kept
|
||||||
|
so Super Rod weighted rolls and DexNav lists stay faithful (#1074).
|
||||||
|
"""
|
||||||
|
out = {}
|
||||||
|
path = os.path.join(pokeyellow, "data/wild/super_rod.asm")
|
||||||
|
for lineno, line in read_asm(path):
|
||||||
|
s = line.strip()
|
||||||
|
if not s.startswith("db ") or s == "db -1" or s.startswith("db -1 ;"):
|
||||||
|
continue
|
||||||
|
# db MAP, SPECIES, LEVEL, SPECIES, LEVEL, SPECIES, LEVEL, SPECIES, LEVEL
|
||||||
|
parts = [p.strip() for p in s[3:].split(",")]
|
||||||
|
if len(parts) < 3 or not re.match(r"^[A-Z][A-Z0-9_]*$", parts[0]):
|
||||||
|
continue
|
||||||
|
map_id = parts[0]
|
||||||
|
slots = []
|
||||||
|
rest = parts[1:]
|
||||||
|
i = 0
|
||||||
|
while i + 1 < len(rest):
|
||||||
|
species, level = rest[i], rest[i + 1]
|
||||||
|
if not re.match(r"^[A-Z][A-Z0-9_]*$", species):
|
||||||
|
break
|
||||||
|
try:
|
||||||
|
level_n = int(level)
|
||||||
|
except ValueError:
|
||||||
|
break
|
||||||
|
slots.append({"level": level_n, "species": species})
|
||||||
|
i += 2
|
||||||
|
if slots:
|
||||||
|
out[map_id] = slots
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
def parse_trades(pokered):
|
def parse_trades(pokered):
|
||||||
"""data/events/trades.asm: npctrade give, get, dialogset, nickname."""
|
"""data/events/trades.asm: npctrade give, get, dialogset, nickname."""
|
||||||
# TRADE_DIALOGSET_* order (constants/script_constants.asm) indexes
|
# TRADE_DIALOGSET_* order (constants/script_constants.asm) indexes
|
||||||
|
|||||||
@@ -453,6 +453,7 @@ def derive(red, pokeyellow, symbols_path):
|
|||||||
print(f"warning: parse_credits failed ({exc}); keeping Red credits")
|
print(f"warning: parse_credits failed ({exc}); keeping Red credits")
|
||||||
# TODO: hand-author a Yellow credits banner if pret layout drifts.
|
# TODO: hand-author a Yellow credits banner if pret layout drifts.
|
||||||
yellow["field"]["trades"] = field.parse_trades(pokeyellow)
|
yellow["field"]["trades"] = field.parse_trades(pokeyellow)
|
||||||
|
yellow["field"]["superRod"] = field.parse_super_rod_yellow(pokeyellow)
|
||||||
finally:
|
finally:
|
||||||
util.ASM_DEFINES = saved
|
util.ASM_DEFINES = saved
|
||||||
|
|
||||||
|
|||||||
+306
-250
@@ -8360,94 +8360,74 @@
|
|||||||
"superRod": {
|
"superRod": {
|
||||||
"CELADON_CITY": [
|
"CELADON_CITY": [
|
||||||
{
|
{
|
||||||
"level": 23,
|
"level": 5,
|
||||||
"species": "POLIWHIRL"
|
"species": "GOLDEEN"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": 10,
|
||||||
|
"species": "GOLDEEN"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"level": 15,
|
"level": 15,
|
||||||
"species": "SLOWPOKE"
|
"species": "GOLDEEN"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": 20,
|
||||||
|
"species": "GOLDEEN"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"CERULEAN_CAVE_1F": [
|
"CERULEAN_CAVE_1F": [
|
||||||
{
|
{
|
||||||
"level": 23,
|
"level": 25,
|
||||||
"species": "SLOWBRO"
|
"species": "GOLDEEN"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"level": 23,
|
"level": 35,
|
||||||
"species": "SEAKING"
|
"species": "SEAKING"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"level": 23,
|
"level": 45,
|
||||||
"species": "KINGLER"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"level": 23,
|
|
||||||
"species": "SEADRA"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"CERULEAN_CAVE_2F": [
|
|
||||||
{
|
|
||||||
"level": 23,
|
|
||||||
"species": "SLOWBRO"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"level": 23,
|
|
||||||
"species": "SEAKING"
|
"species": "SEAKING"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"level": 23,
|
"level": 55,
|
||||||
"species": "KINGLER"
|
"species": "SEAKING"
|
||||||
},
|
|
||||||
{
|
|
||||||
"level": 23,
|
|
||||||
"species": "SEADRA"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"CERULEAN_CAVE_B1F": [
|
"CERULEAN_CAVE_B1F": [
|
||||||
{
|
{
|
||||||
"level": 23,
|
"level": 30,
|
||||||
"species": "SLOWBRO"
|
"species": "GOLDEEN"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"level": 23,
|
"level": 40,
|
||||||
"species": "SEAKING"
|
"species": "SEAKING"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"level": 23,
|
"level": 50,
|
||||||
"species": "KINGLER"
|
"species": "SEAKING"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"level": 23,
|
"level": 60,
|
||||||
"species": "SEADRA"
|
"species": "SEAKING"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"CERULEAN_CITY": [
|
"CERULEAN_CITY": [
|
||||||
{
|
{
|
||||||
"level": 15,
|
"level": 25,
|
||||||
"species": "PSYDUCK"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"level": 15,
|
|
||||||
"species": "GOLDEEN"
|
"species": "GOLDEEN"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"level": 15,
|
"level": 30,
|
||||||
"species": "KRABBY"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"CERULEAN_GYM": [
|
|
||||||
{
|
|
||||||
"level": 15,
|
|
||||||
"species": "PSYDUCK"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"level": 15,
|
|
||||||
"species": "GOLDEEN"
|
"species": "GOLDEEN"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"level": 15,
|
"level": 30,
|
||||||
"species": "KRABBY"
|
"species": "SEAKING"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": 40,
|
||||||
|
"species": "SEAKING"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"CINNABAR_ISLAND": [
|
"CINNABAR_ISLAND": [
|
||||||
@@ -8457,99 +8437,123 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"level": 15,
|
"level": 15,
|
||||||
"species": "HORSEA"
|
"species": "TENTACOOL"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"level": 15,
|
"level": 10,
|
||||||
"species": "SHELLDER"
|
"species": "STARYU"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"level": 15,
|
"level": 30,
|
||||||
"species": "GOLDEEN"
|
"species": "TENTACOOL"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"FUCHSIA_CITY": [
|
"FUCHSIA_CITY": [
|
||||||
{
|
{
|
||||||
"level": 23,
|
"level": 5,
|
||||||
"species": "SEAKING"
|
"species": "MAGIKARP"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"level": 10,
|
||||||
|
"species": "MAGIKARP"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": 15,
|
||||||
|
"species": "MAGIKARP"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": 15,
|
||||||
|
"species": "GYARADOS"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"PALLET_TOWN": [
|
||||||
|
{
|
||||||
|
"level": 10,
|
||||||
|
"species": "STARYU"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": 10,
|
||||||
|
"species": "TENTACOOL"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": 5,
|
||||||
|
"species": "STARYU"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": 20,
|
||||||
|
"species": "TENTACOOL"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ROUTE_10": [
|
||||||
{
|
{
|
||||||
"level": 15,
|
"level": 15,
|
||||||
"species": "KRABBY"
|
"species": "KRABBY"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"level": 15,
|
"level": 20,
|
||||||
"species": "GOLDEEN"
|
"species": "KRABBY"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"level": 15,
|
"level": 10,
|
||||||
"species": "MAGIKARP"
|
"species": "HORSEA"
|
||||||
}
|
|
||||||
],
|
|
||||||
"PALLET_TOWN": [
|
|
||||||
{
|
|
||||||
"level": 15,
|
|
||||||
"species": "TENTACOOL"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"level": 15,
|
"level": 25,
|
||||||
"species": "POLIWAG"
|
"species": "KINGLER"
|
||||||
}
|
|
||||||
],
|
|
||||||
"ROUTE_10": [
|
|
||||||
{
|
|
||||||
"level": 23,
|
|
||||||
"species": "POLIWHIRL"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"level": 15,
|
|
||||||
"species": "SLOWPOKE"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"ROUTE_11": [
|
"ROUTE_11": [
|
||||||
{
|
{
|
||||||
"level": 15,
|
"level": 15,
|
||||||
"species": "KRABBY"
|
"species": "TENTACOOL"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"level": 15,
|
"level": 20,
|
||||||
"species": "SHELLDER"
|
"species": "TENTACOOL"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": 10,
|
||||||
|
"species": "TENTACOOL"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": 5,
|
||||||
|
"species": "HORSEA"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"ROUTE_12": [
|
"ROUTE_12": [
|
||||||
{
|
{
|
||||||
"level": 5,
|
"level": 20,
|
||||||
"species": "TENTACOOL"
|
"species": "HORSEA"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"level": 15,
|
"level": 25,
|
||||||
"species": "KRABBY"
|
"species": "HORSEA"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"level": 15,
|
"level": 25,
|
||||||
"species": "GOLDEEN"
|
"species": "SEADRA"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"level": 15,
|
"level": 35,
|
||||||
"species": "MAGIKARP"
|
"species": "SEADRA"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"ROUTE_13": [
|
"ROUTE_13": [
|
||||||
{
|
{
|
||||||
"level": 5,
|
"level": 15,
|
||||||
|
"species": "HORSEA"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": 20,
|
||||||
|
"species": "HORSEA"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": 10,
|
||||||
"species": "TENTACOOL"
|
"species": "TENTACOOL"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"level": 15,
|
"level": 20,
|
||||||
"species": "KRABBY"
|
"species": "SEADRA"
|
||||||
},
|
|
||||||
{
|
|
||||||
"level": 15,
|
|
||||||
"species": "GOLDEEN"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"level": 15,
|
|
||||||
"species": "MAGIKARP"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"ROUTE_17": [
|
"ROUTE_17": [
|
||||||
@@ -8559,304 +8563,356 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"level": 15,
|
"level": 15,
|
||||||
"species": "KRABBY"
|
"species": "TENTACOOL"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"level": 15,
|
"level": 25,
|
||||||
"species": "GOLDEEN"
|
"species": "SHELLDER"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"level": 15,
|
"level": 35,
|
||||||
"species": "MAGIKARP"
|
"species": "SHELLDER"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"ROUTE_18": [
|
"ROUTE_18": [
|
||||||
{
|
{
|
||||||
"level": 5,
|
"level": 15,
|
||||||
"species": "TENTACOOL"
|
"species": "TENTACOOL"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"level": 15,
|
"level": 20,
|
||||||
"species": "KRABBY"
|
"species": "SHELLDER"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"level": 15,
|
"level": 30,
|
||||||
"species": "GOLDEEN"
|
"species": "SHELLDER"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"level": 15,
|
"level": 40,
|
||||||
"species": "MAGIKARP"
|
"species": "SHELLDER"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"ROUTE_19": [
|
"ROUTE_19": [
|
||||||
{
|
{
|
||||||
"level": 15,
|
"level": 15,
|
||||||
|
"species": "TENTACOOL"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": 20,
|
||||||
"species": "STARYU"
|
"species": "STARYU"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"level": 15,
|
"level": 30,
|
||||||
"species": "HORSEA"
|
"species": "TENTACOOL"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"level": 15,
|
"level": 30,
|
||||||
"species": "SHELLDER"
|
"species": "TENTACRUEL"
|
||||||
},
|
|
||||||
{
|
|
||||||
"level": 15,
|
|
||||||
"species": "GOLDEEN"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"ROUTE_20": [
|
"ROUTE_20": [
|
||||||
{
|
{
|
||||||
"level": 15,
|
"level": 20,
|
||||||
|
"species": "TENTACOOL"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": 20,
|
||||||
|
"species": "TENTACRUEL"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": 30,
|
||||||
"species": "STARYU"
|
"species": "STARYU"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"level": 15,
|
"level": 40,
|
||||||
"species": "HORSEA"
|
"species": "TENTACRUEL"
|
||||||
},
|
|
||||||
{
|
|
||||||
"level": 15,
|
|
||||||
"species": "SHELLDER"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"level": 15,
|
|
||||||
"species": "GOLDEEN"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"ROUTE_21": [
|
"ROUTE_21": [
|
||||||
{
|
{
|
||||||
"level": 15,
|
"level": 15,
|
||||||
|
"species": "TENTACOOL"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": 20,
|
||||||
"species": "STARYU"
|
"species": "STARYU"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"level": 15,
|
"level": 30,
|
||||||
"species": "HORSEA"
|
"species": "TENTACOOL"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"level": 15,
|
"level": 30,
|
||||||
"species": "SHELLDER"
|
"species": "TENTACRUEL"
|
||||||
},
|
|
||||||
{
|
|
||||||
"level": 15,
|
|
||||||
"species": "GOLDEEN"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"ROUTE_22": [
|
"ROUTE_22": [
|
||||||
{
|
{
|
||||||
"level": 15,
|
"level": 5,
|
||||||
"species": "GOLDEEN"
|
"species": "POLIWAG"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": 10,
|
||||||
|
"species": "POLIWAG"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"level": 15,
|
"level": 15,
|
||||||
"species": "POLIWAG"
|
"species": "POLIWAG"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": 15,
|
||||||
|
"species": "POLIWHIRL"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"ROUTE_23": [
|
"ROUTE_23": [
|
||||||
{
|
{
|
||||||
"level": 23,
|
"level": 25,
|
||||||
"species": "SLOWBRO"
|
"species": "POLIWAG"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"level": 23,
|
"level": 30,
|
||||||
"species": "SEAKING"
|
"species": "POLIWAG"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"level": 23,
|
"level": 30,
|
||||||
"species": "KINGLER"
|
"species": "POLIWHIRL"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"level": 23,
|
"level": 40,
|
||||||
"species": "SEADRA"
|
"species": "POLIWHIRL"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"ROUTE_24": [
|
"ROUTE_24": [
|
||||||
{
|
{
|
||||||
"level": 15,
|
"level": 20,
|
||||||
"species": "PSYDUCK"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"level": 15,
|
|
||||||
"species": "GOLDEEN"
|
"species": "GOLDEEN"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"level": 15,
|
"level": 25,
|
||||||
"species": "KRABBY"
|
"species": "GOLDEEN"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": 30,
|
||||||
|
"species": "GOLDEEN"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": 30,
|
||||||
|
"species": "SEAKING"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"ROUTE_25": [
|
"ROUTE_25": [
|
||||||
{
|
{
|
||||||
"level": 15,
|
"level": 10,
|
||||||
"species": "PSYDUCK"
|
"species": "KRABBY"
|
||||||
},
|
|
||||||
{
|
|
||||||
"level": 15,
|
|
||||||
"species": "GOLDEEN"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"level": 15,
|
"level": 15,
|
||||||
"species": "KRABBY"
|
"species": "KRABBY"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": 15,
|
||||||
|
"species": "KINGLER"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": 25,
|
||||||
|
"species": "KINGLER"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"ROUTE_4": [
|
"ROUTE_4": [
|
||||||
{
|
{
|
||||||
"level": 15,
|
"level": 20,
|
||||||
"species": "PSYDUCK"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"level": 15,
|
|
||||||
"species": "GOLDEEN"
|
"species": "GOLDEEN"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"level": 15,
|
"level": 25,
|
||||||
"species": "KRABBY"
|
"species": "GOLDEEN"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": 30,
|
||||||
|
"species": "GOLDEEN"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": 30,
|
||||||
|
"species": "SEAKING"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"ROUTE_6": [
|
"ROUTE_6": [
|
||||||
{
|
{
|
||||||
"level": 15,
|
"level": 5,
|
||||||
"species": "KRABBY"
|
"species": "GOLDEEN"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": 10,
|
||||||
|
"species": "GOLDEEN"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"level": 15,
|
"level": 15,
|
||||||
"species": "SHELLDER"
|
"species": "GOLDEEN"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": 20,
|
||||||
|
"species": "GOLDEEN"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"SAFARI_ZONE_CENTER": [
|
"SAFARI_ZONE_CENTER": [
|
||||||
{
|
{
|
||||||
"level": 15,
|
"level": 5,
|
||||||
|
"species": "MAGIKARP"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": 10,
|
||||||
|
"species": "MAGIKARP"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": 10,
|
||||||
"species": "DRATINI"
|
"species": "DRATINI"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"level": 15,
|
"level": 15,
|
||||||
"species": "KRABBY"
|
"species": "DRAGONAIR"
|
||||||
},
|
|
||||||
{
|
|
||||||
"level": 15,
|
|
||||||
"species": "PSYDUCK"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"level": 15,
|
|
||||||
"species": "SLOWPOKE"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"SAFARI_ZONE_EAST": [
|
"SAFARI_ZONE_EAST": [
|
||||||
|
{
|
||||||
|
"level": 5,
|
||||||
|
"species": "MAGIKARP"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": 10,
|
||||||
|
"species": "MAGIKARP"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": 15,
|
||||||
|
"species": "MAGIKARP"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"level": 15,
|
"level": 15,
|
||||||
"species": "DRATINI"
|
"species": "DRATINI"
|
||||||
},
|
|
||||||
{
|
|
||||||
"level": 15,
|
|
||||||
"species": "KRABBY"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"level": 15,
|
|
||||||
"species": "PSYDUCK"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"level": 15,
|
|
||||||
"species": "SLOWPOKE"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"SAFARI_ZONE_NORTH": [
|
"SAFARI_ZONE_NORTH": [
|
||||||
|
{
|
||||||
|
"level": 5,
|
||||||
|
"species": "MAGIKARP"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": 10,
|
||||||
|
"species": "MAGIKARP"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": 15,
|
||||||
|
"species": "MAGIKARP"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"level": 15,
|
"level": 15,
|
||||||
"species": "DRATINI"
|
"species": "DRATINI"
|
||||||
},
|
|
||||||
{
|
|
||||||
"level": 15,
|
|
||||||
"species": "KRABBY"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"level": 15,
|
|
||||||
"species": "PSYDUCK"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"level": 15,
|
|
||||||
"species": "SLOWPOKE"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"SAFARI_ZONE_WEST": [
|
"SAFARI_ZONE_WEST": [
|
||||||
|
{
|
||||||
|
"level": 5,
|
||||||
|
"species": "MAGIKARP"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": 10,
|
||||||
|
"species": "MAGIKARP"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": 15,
|
||||||
|
"species": "MAGIKARP"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"level": 15,
|
"level": 15,
|
||||||
"species": "DRATINI"
|
"species": "DRATINI"
|
||||||
},
|
|
||||||
{
|
|
||||||
"level": 15,
|
|
||||||
"species": "KRABBY"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"level": 15,
|
|
||||||
"species": "PSYDUCK"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"level": 15,
|
|
||||||
"species": "SLOWPOKE"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"SEAFOAM_ISLANDS_B3F": [
|
"SEAFOAM_ISLANDS_B3F": [
|
||||||
{
|
{
|
||||||
"level": 15,
|
"level": 25,
|
||||||
|
"species": "KRABBY"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": 20,
|
||||||
"species": "STARYU"
|
"species": "STARYU"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"level": 15,
|
"level": 35,
|
||||||
"species": "HORSEA"
|
"species": "KINGLER"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"level": 15,
|
"level": 40,
|
||||||
"species": "SHELLDER"
|
"species": "STARYU"
|
||||||
},
|
|
||||||
{
|
|
||||||
"level": 15,
|
|
||||||
"species": "GOLDEEN"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"SEAFOAM_ISLANDS_B4F": [
|
"SEAFOAM_ISLANDS_B4F": [
|
||||||
{
|
{
|
||||||
"level": 15,
|
"level": 25,
|
||||||
|
"species": "KRABBY"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": 20,
|
||||||
"species": "STARYU"
|
"species": "STARYU"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"level": 15,
|
"level": 35,
|
||||||
"species": "HORSEA"
|
"species": "KINGLER"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"level": 15,
|
"level": 40,
|
||||||
"species": "SHELLDER"
|
"species": "STARYU"
|
||||||
},
|
|
||||||
{
|
|
||||||
"level": 15,
|
|
||||||
"species": "GOLDEEN"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"VERMILION_CITY": [
|
"VERMILION_CITY": [
|
||||||
{
|
{
|
||||||
"level": 15,
|
"level": 15,
|
||||||
"species": "KRABBY"
|
"species": "TENTACOOL"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"level": 15,
|
"level": 20,
|
||||||
"species": "SHELLDER"
|
"species": "TENTACOOL"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": 10,
|
||||||
|
"species": "TENTACOOL"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": 5,
|
||||||
|
"species": "HORSEA"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"VERMILION_DOCK": [
|
"VERMILION_DOCK": [
|
||||||
{
|
{
|
||||||
"level": 15,
|
"level": 10,
|
||||||
"species": "KRABBY"
|
"species": "TENTACOOL"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"level": 15,
|
|
||||||
"species": "SHELLDER"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"VIRIDIAN_CITY": [
|
|
||||||
{
|
{
|
||||||
"level": 15,
|
"level": 15,
|
||||||
"species": "TENTACOOL"
|
"species": "TENTACOOL"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"level": 15,
|
"level": 15,
|
||||||
|
"species": "STARYU"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": 10,
|
||||||
|
"species": "SHELLDER"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"VIRIDIAN_CITY": [
|
||||||
|
{
|
||||||
|
"level": 5,
|
||||||
|
"species": "POLIWAG"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": 10,
|
||||||
|
"species": "POLIWAG"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": 15,
|
||||||
|
"species": "POLIWAG"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": 10,
|
||||||
"species": "POLIWAG"
|
"species": "POLIWAG"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
Reference in New Issue
Block a user