diff --git a/data/palettes_yellow.lua b/data/palettes_yellow.lua new file mode 100644 index 00000000..a7728db4 --- /dev/null +++ b/data/palettes_yellow.lua @@ -0,0 +1,686 @@ +-- Generated by tools/build_data.py. DO NOT EDIT. +-- Pokemon Yellow palettes (COLORS=OG YELLOW): SuperPalettes + +-- CGBBasePalettes (authentic GBC look) as 4 8-bit RGB colors +-- per palette (color 0 first), plus MonsterPalettes. +return { + cgbBase = { + ["0F"] = { + { 255, 255, 255 }, + { 107, 8, 255 }, + { 0, 74, 255 }, + { 8, 8, 8 }, + }, + BADGE = { + { 255, 255, 255 }, + { 189, 66, 0 }, + { 140, 115, 90 }, + { 25, 25, 25 }, + }, + BLACK = { + { 255, 255, 255 }, + { 25, 25, 25 }, + { 25, 25, 25 }, + { 25, 25, 25 }, + }, + BLUEMON = { + { 255, 255, 255 }, + { 132, 148, 255 }, + { 0, 8, 206 }, + { 25, 25, 25 }, + }, + BROWNMON = { + { 255, 255, 255 }, + { 239, 148, 82 }, + { 140, 74, 41 }, + { 25, 25, 25 }, + }, + CAVE = { + { 255, 255, 255 }, + { 189, 66, 0 }, + { 140, 115, 90 }, + { 25, 25, 25 }, + }, + CELADON = { + { 255, 255, 255 }, + { 41, 255, 41 }, + { 90, 189, 255 }, + { 25, 25, 25 }, + }, + CERULEAN = { + { 255, 255, 255 }, + { 41, 66, 255 }, + { 90, 189, 255 }, + { 25, 25, 25 }, + }, + CINNABAR = { + { 255, 255, 255 }, + { 255, 66, 66 }, + { 90, 189, 255 }, + { 25, 25, 25 }, + }, + CYANMON = { + { 255, 255, 255 }, + { 132, 214, 255 }, + { 0, 140, 255 }, + { 25, 25, 25 }, + }, + FUCHSIA = { + { 255, 255, 255 }, + { 255, 123, 123 }, + { 90, 189, 255 }, + { 25, 25, 25 }, + }, + GAMEFREAK = { + { 255, 255, 255 }, + { 255, 156, 0 }, + { 156, 156, 0 }, + { 25, 25, 25 }, + }, + GRAYMON = { + { 255, 255, 255 }, + { 165, 189, 82 }, + { 90, 90, 41 }, + { 25, 25, 25 }, + }, + GREENBAR = { + { 255, 255, 255 }, + { 255, 255, 0 }, + { 0, 255, 0 }, + { 25, 25, 25 }, + }, + GREENMON = { + { 255, 255, 255 }, + { 140, 255, 90 }, + { 8, 181, 49 }, + { 25, 25, 25 }, + }, + INDIGO = { + { 255, 255, 255 }, + { 90, 66, 255 }, + { 90, 189, 255 }, + { 25, 25, 25 }, + }, + LAVENDER = { + { 255, 255, 255 }, + { 206, 33, 255 }, + { 90, 189, 255 }, + { 25, 25, 25 }, + }, + LOGO1 = { + { 255, 255, 255 }, + { 255, 255, 0 }, + { 255, 0, 0 }, + { 255, 0, 0 }, + }, + LOGO2 = { + { 255, 255, 255 }, + { 255, 255, 0 }, + { 58, 58, 206 }, + { 0, 0, 140 }, + }, + MEWMON = { + { 255, 255, 255 }, + { 255, 255, 0 }, + { 255, 8, 8 }, + { 25, 25, 25 }, + }, + PALLET = { + { 255, 255, 255 }, + { 189, 140, 255 }, + { 90, 189, 255 }, + { 25, 25, 25 }, + }, + PEWTER = { + { 255, 255, 255 }, + { 148, 148, 123 }, + { 90, 189, 255 }, + { 25, 25, 25 }, + }, + PIKACHUS_BEACH = { + { 255, 255, 255 }, + { 255, 255, 0 }, + { 90, 189, 255 }, + { 25, 25, 25 }, + }, + PIKACHUS_BEACH_TITLE = { + { 255, 255, 255 }, + { 74, 74, 74 }, + { 255, 173, 0 }, + { 25, 25, 25 }, + }, + PIKACHU_PORTRAIT = { + { 255, 255, 255 }, + { 255, 148, 0 }, + { 156, 58, 8 }, + { 25, 25, 25 }, + }, + PINKMON = { + { 255, 255, 255 }, + { 255, 123, 148 }, + { 255, 0, 49 }, + { 25, 25, 25 }, + }, + PURPLEMON = { + { 255, 255, 255 }, + { 206, 123, 255 }, + { 156, 0, 181 }, + { 25, 25, 25 }, + }, + REDBAR = { + { 255, 255, 255 }, + { 255, 255, 0 }, + { 255, 0, 0 }, + { 25, 25, 25 }, + }, + REDMON = { + { 255, 255, 255 }, + { 255, 140, 0 }, + { 255, 0, 0 }, + { 25, 25, 25 }, + }, + ROUTE = { + { 255, 255, 255 }, + { 132, 255, 33 }, + { 90, 189, 255 }, + { 25, 25, 25 }, + }, + SAFFRON = { + { 255, 255, 255 }, + { 255, 255, 0 }, + { 90, 189, 255 }, + { 25, 25, 25 }, + }, + SLOTS1 = { + { 255, 255, 255 }, + { 206, 8, 255 }, + { 255, 0, 0 }, + { 25, 25, 25 }, + }, + SLOTS2 = { + { 255, 255, 255 }, + { 255, 33, 156 }, + { 255, 255, 0 }, + { 25, 25, 25 }, + }, + SLOTS3 = { + { 255, 255, 255 }, + { 66, 255, 0 }, + { 255, 255, 0 }, + { 25, 25, 25 }, + }, + SLOTS4 = { + { 255, 255, 255 }, + { 0, 255, 255 }, + { 255, 255, 0 }, + { 25, 25, 25 }, + }, + TOWNMAP = { + { 255, 255, 255 }, + { 0, 173, 255 }, + { 82, 230, 0 }, + { 8, 8, 8 }, + }, + VERMILION = { + { 255, 255, 255 }, + { 255, 156, 0 }, + { 90, 189, 255 }, + { 25, 25, 25 }, + }, + VIRIDIAN = { + { 255, 255, 255 }, + { 156, 255, 0 }, + { 90, 189, 255 }, + { 25, 25, 25 }, + }, + YELLOWBAR = { + { 255, 255, 255 }, + { 255, 255, 0 }, + { 255, 148, 0 }, + { 25, 25, 25 }, + }, + YELLOWMON = { + { 255, 255, 255 }, + { 255, 255, 0 }, + { 230, 115, 0 }, + { 25, 25, 25 }, + }, + }, + order = { + "ROUTE", + "PALLET", + "VIRIDIAN", + "PEWTER", + "CERULEAN", + "LAVENDER", + "VERMILION", + "CELADON", + "FUCHSIA", + "CINNABAR", + "INDIGO", + "SAFFRON", + "TOWNMAP", + "LOGO1", + "LOGO2", + "0F", + "MEWMON", + "BLUEMON", + "REDMON", + "CYANMON", + "PURPLEMON", + "BROWNMON", + "GREENMON", + "PINKMON", + "YELLOWMON", + "GRAYMON", + "SLOTS1", + "SLOTS2", + "SLOTS3", + "SLOTS4", + "BLACK", + "GREENBAR", + "YELLOWBAR", + "REDBAR", + "BADGE", + "CAVE", + "GAMEFREAK", + "PIKACHUS_BEACH", + "PIKACHU_PORTRAIT", + "PIKACHUS_BEACH_TITLE", + }, + palettes = { + ["0F"] = { + { 255, 255, 247 }, + { 197, 165, 247 }, + { 90, 165, 247 }, + { 25, 16, 16 }, + }, + BADGE = { + { 255, 255, 247 }, + { 165, 123, 90 }, + { 181, 173, 165 }, + { 49, 49, 49 }, + }, + BLACK = { + { 255, 255, 247 }, + { 49, 49, 49 }, + { 49, 49, 49 }, + { 49, 49, 49 }, + }, + BLUEMON = { + { 255, 255, 247 }, + { 173, 181, 255 }, + { 74, 82, 165 }, + { 49, 49, 49 }, + }, + BROWNMON = { + { 255, 255, 247 }, + { 214, 189, 148 }, + { 148, 115, 82 }, + { 49, 49, 49 }, + }, + CAVE = { + { 255, 255, 247 }, + { 165, 123, 90 }, + { 181, 173, 165 }, + { 49, 49, 49 }, + }, + CELADON = { + { 255, 255, 247 }, + { 181, 255, 181 }, + { 189, 222, 255 }, + { 49, 49, 49 }, + }, + CERULEAN = { + { 255, 255, 247 }, + { 181, 189, 255 }, + { 189, 222, 255 }, + { 49, 49, 49 }, + }, + CINNABAR = { + { 255, 255, 247 }, + { 255, 123, 115 }, + { 189, 222, 255 }, + { 49, 49, 49 }, + }, + CYANMON = { + { 255, 255, 247 }, + { 214, 230, 255 }, + { 58, 197, 230 }, + { 49, 49, 49 }, + }, + FUCHSIA = { + { 255, 255, 247 }, + { 255, 214, 214 }, + { 189, 222, 255 }, + { 49, 49, 49 }, + }, + GAMEFREAK = { + { 255, 255, 247 }, + { 230, 197, 115 }, + { 165, 165, 90 }, + { 49, 49, 49 }, + }, + GRAYMON = { + { 255, 255, 247 }, + { 206, 206, 148 }, + { 132, 132, 115 }, + { 49, 49, 49 }, + }, + GREENBAR = { + { 255, 255, 247 }, + { 255, 255, 156 }, + { 0, 173, 0 }, + { 49, 49, 49 }, + }, + GREENMON = { + { 255, 255, 247 }, + { 197, 230, 148 }, + { 107, 173, 123 }, + { 49, 49, 49 }, + }, + INDIGO = { + { 255, 255, 247 }, + { 140, 140, 206 }, + { 189, 222, 255 }, + { 49, 49, 49 }, + }, + LAVENDER = { + { 255, 255, 247 }, + { 222, 189, 239 }, + { 189, 222, 255 }, + { 49, 49, 49 }, + }, + LOGO1 = { + { 255, 255, 247 }, + { 247, 247, 140 }, + { 173, 0, 33 }, + { 173, 0, 33 }, + }, + LOGO2 = { + { 255, 255, 247 }, + { 247, 247, 140 }, + { 148, 148, 197 }, + { 58, 58, 132 }, + }, + MEWMON = { + { 255, 255, 247 }, + { 255, 247, 181 }, + { 222, 132, 132 }, + { 49, 49, 49 }, + }, + PALLET = { + { 255, 255, 247 }, + { 230, 222, 255 }, + { 189, 222, 255 }, + { 49, 49, 49 }, + }, + PEWTER = { + { 255, 255, 247 }, + { 189, 189, 181 }, + { 189, 222, 255 }, + { 49, 49, 49 }, + }, + PIKACHUS_BEACH = { + { 255, 255, 247 }, + { 255, 247, 181 }, + { 189, 222, 255 }, + { 49, 49, 49 }, + }, + PIKACHUS_BEACH_TITLE = { + { 255, 255, 247 }, + { 132, 132, 132 }, + { 255, 206, 74 }, + { 49, 49, 49 }, + }, + PIKACHU_PORTRAIT = { + { 255, 255, 247 }, + { 230, 189, 74 }, + { 148, 115, 82 }, + { 49, 49, 49 }, + }, + PINKMON = { + { 255, 255, 247 }, + { 255, 197, 214 }, + { 255, 148, 173 }, + { 49, 49, 49 }, + }, + PURPLEMON = { + { 255, 255, 247 }, + { 222, 181, 247 }, + { 181, 123, 189 }, + { 49, 49, 49 }, + }, + REDBAR = { + { 255, 255, 247 }, + { 255, 255, 156 }, + { 214, 74, 49 }, + { 49, 49, 49 }, + }, + REDMON = { + { 255, 255, 247 }, + { 255, 197, 90 }, + { 214, 74, 49 }, + { 49, 49, 49 }, + }, + ROUTE = { + { 255, 255, 247 }, + { 189, 214, 156 }, + { 189, 222, 255 }, + { 49, 49, 49 }, + }, + SAFFRON = { + { 255, 255, 247 }, + { 255, 255, 156 }, + { 189, 222, 255 }, + { 49, 49, 49 }, + }, + SLOTS1 = { + { 255, 255, 247 }, + { 222, 181, 247 }, + { 214, 74, 49 }, + { 49, 49, 49 }, + }, + SLOTS2 = { + { 255, 255, 247 }, + { 255, 189, 214 }, + { 239, 239, 66 }, + { 49, 49, 49 }, + }, + SLOTS3 = { + { 255, 255, 247 }, + { 189, 255, 165 }, + { 239, 239, 66 }, + { 49, 49, 49 }, + }, + SLOTS4 = { + { 255, 255, 247 }, + { 189, 239, 255 }, + { 239, 239, 66 }, + { 49, 49, 49 }, + }, + TOWNMAP = { + { 255, 255, 247 }, + { 165, 214, 255 }, + { 140, 189, 82 }, + { 25, 16, 16 }, + }, + VERMILION = { + { 255, 255, 247 }, + { 255, 206, 132 }, + { 189, 222, 255 }, + { 49, 49, 49 }, + }, + VIRIDIAN = { + { 255, 255, 247 }, + { 214, 255, 173 }, + { 189, 222, 255 }, + { 49, 49, 49 }, + }, + YELLOWBAR = { + { 255, 255, 247 }, + { 255, 255, 156 }, + { 230, 189, 74 }, + { 49, 49, 49 }, + }, + YELLOWMON = { + { 255, 255, 247 }, + { 255, 255, 156 }, + { 230, 189, 74 }, + { 49, 49, 49 }, + }, + }, + pokemon = { + ABRA = "YELLOWMON", + AERODACTYL = "GRAYMON", + ALAKAZAM = "YELLOWMON", + ARBOK = "PURPLEMON", + ARCANINE = "REDMON", + ARTICUNO = "BLUEMON", + BEEDRILL = "YELLOWMON", + BELLSPROUT = "GREENMON", + BLASTOISE = "CYANMON", + BULBASAUR = "GREENMON", + BUTTERFREE = "CYANMON", + CATERPIE = "GREENMON", + CHANSEY = "PINKMON", + CHARIZARD = "REDMON", + CHARMANDER = "REDMON", + CHARMELEON = "REDMON", + CLEFABLE = "PINKMON", + CLEFAIRY = "PINKMON", + CLOYSTER = "GRAYMON", + CUBONE = "GRAYMON", + DEWGONG = "BLUEMON", + DIGLETT = "BROWNMON", + DITTO = "GRAYMON", + DODRIO = "BROWNMON", + DODUO = "BROWNMON", + DRAGONAIR = "BLUEMON", + DRAGONITE = "BROWNMON", + DRATINI = "GRAYMON", + DROWZEE = "YELLOWMON", + DUGTRIO = "BROWNMON", + EEVEE = "GRAYMON", + EKANS = "PURPLEMON", + ELECTABUZZ = "YELLOWMON", + ELECTRODE = "YELLOWMON", + EXEGGCUTE = "PINKMON", + EXEGGUTOR = "GREENMON", + FARFETCHD = "BROWNMON", + FEAROW = "BROWNMON", + FLAREON = "REDMON", + GASTLY = "PURPLEMON", + GENGAR = "PURPLEMON", + GEODUDE = "GRAYMON", + GLOOM = "REDMON", + GOLBAT = "BLUEMON", + GOLDEEN = "REDMON", + GOLDUCK = "CYANMON", + GOLEM = "GRAYMON", + GRAVELER = "GRAYMON", + GRIMER = "PURPLEMON", + GROWLITHE = "BROWNMON", + GYARADOS = "BLUEMON", + HAUNTER = "PURPLEMON", + HITMONCHAN = "BROWNMON", + HITMONLEE = "BROWNMON", + HORSEA = "CYANMON", + HYPNO = "YELLOWMON", + IVYSAUR = "GREENMON", + JIGGLYPUFF = "PINKMON", + JOLTEON = "YELLOWMON", + JYNX = "MEWMON", + KABUTO = "BROWNMON", + KABUTOPS = "BROWNMON", + KADABRA = "YELLOWMON", + KAKUNA = "YELLOWMON", + KANGASKHAN = "BROWNMON", + KINGLER = "REDMON", + KOFFING = "PURPLEMON", + KRABBY = "REDMON", + LAPRAS = "CYANMON", + LICKITUNG = "PINKMON", + MACHAMP = "GRAYMON", + MACHOKE = "GRAYMON", + MACHOP = "GRAYMON", + MAGIKARP = "REDMON", + MAGMAR = "REDMON", + MAGNEMITE = "GRAYMON", + MAGNETON = "GRAYMON", + MANKEY = "BROWNMON", + MAROWAK = "GRAYMON", + MEOWTH = "YELLOWMON", + METAPOD = "GREENMON", + MEW = "MEWMON", + MEWTWO = "MEWMON", + MOLTRES = "REDMON", + MR_MIME = "PINKMON", + MUK = "PURPLEMON", + NIDOKING = "PURPLEMON", + NIDOQUEEN = "BLUEMON", + NIDORAN_F = "BLUEMON", + NIDORAN_M = "PURPLEMON", + NIDORINA = "BLUEMON", + NIDORINO = "PURPLEMON", + NINETALES = "YELLOWMON", + ODDISH = "GREENMON", + OMANYTE = "BLUEMON", + OMASTAR = "BLUEMON", + ONIX = "GRAYMON", + PARAS = "REDMON", + PARASECT = "REDMON", + PERSIAN = "YELLOWMON", + PIDGEOT = "BROWNMON", + PIDGEOTTO = "BROWNMON", + PIDGEY = "BROWNMON", + PIKACHU = "YELLOWMON", + PINSIR = "BROWNMON", + POLIWAG = "BLUEMON", + POLIWHIRL = "BLUEMON", + POLIWRATH = "BLUEMON", + PONYTA = "REDMON", + PORYGON = "GRAYMON", + PRIMEAPE = "BROWNMON", + PSYDUCK = "YELLOWMON", + RAICHU = "YELLOWMON", + RAPIDASH = "REDMON", + RATICATE = "GRAYMON", + RATTATA = "GRAYMON", + RHYDON = "GRAYMON", + RHYHORN = "GRAYMON", + SANDSHREW = "BROWNMON", + SANDSLASH = "BROWNMON", + SCYTHER = "GREENMON", + SEADRA = "CYANMON", + SEAKING = "REDMON", + SEEL = "BLUEMON", + SHELLDER = "GRAYMON", + SLOWBRO = "PINKMON", + SLOWPOKE = "PINKMON", + SNORLAX = "PINKMON", + SPEAROW = "BROWNMON", + SQUIRTLE = "CYANMON", + STARMIE = "GRAYMON", + STARYU = "REDMON", + TANGELA = "BLUEMON", + TAUROS = "GRAYMON", + TENTACOOL = "CYANMON", + TENTACRUEL = "CYANMON", + VAPOREON = "CYANMON", + VENOMOTH = "PURPLEMON", + VENONAT = "PURPLEMON", + VENUSAUR = "GREENMON", + VICTREEBEL = "GREENMON", + VILEPLUME = "REDMON", + VOLTORB = "YELLOWMON", + VULPIX = "REDMON", + WARTORTLE = "CYANMON", + WEEDLE = "YELLOWMON", + WEEPINBELL = "GREENMON", + WEEZING = "PURPLEMON", + WIGGLYTUFF = "PINKMON", + ZAPDOS = "YELLOWMON", + ZUBAT = "BLUEMON", + }, + source = "pokeyellow data/sgb/sgb_palettes.asm + data/pokemon/palettes.asm", +} diff --git a/docs/new-features.md b/docs/new-features.md index 42d72087..3f33a59d 100644 --- a/docs/new-features.md +++ b/docs/new-features.md @@ -86,14 +86,20 @@ Game Boy equivalent: ## Colors mode The `2` key (and the Options menu COLORS row) cycles the display mode -through **OG RED → SGB → ADVANCED → OG → OG INV → SGB INV → CLASSIC → OG RED**. +through **OG RED → SGB → ADVANCED → OG → OG INV → SGB INV → CLASSIC → OG RED** +(on Blue the first slot labels **OG BLUE**; on Yellow, **OG YELLOW**). The first three are the real colorizations; the rest are DMG-shade novelties: -- **OG RED**: the Game Boy Color boot-ROM look for Pokemon Red -- one global - red BG palette + one green OBJ palette, every map, no per-map variation - (Pokemon Red has no CGB code, so on a GBC the boot ROM colors it globally). - The player/NPCs stay green over the red terrain via the OBP bake + - post-zone redraw (`PaletteFX.GBC_BG` / `GBC_OBJ`). +- **OG RED** / **OG BLUE**: the Game Boy Color boot-ROM look for that cart -- + one global BG palette + one OBJ palette, every map, no per-map variation + (Red/Blue ship no CGB code, so on a GBC the boot ROM colors them globally). + The player/NPCs keep the boot-ROM OBJ color over the terrain via the OBP + bake + post-zone redraw (`PaletteFX.GBC_BG` / `GBC_OBJ`, or Blue's blue/pink + pair). +- **OG YELLOW** (Yellow playthrough, same `ogred` save id): Pokemon Yellow's + authentic GBC look from `CGBBasePalettes` (`data/palettes_yellow.lua`, + sourced from pret/pokeyellow). Per-map / per-species colors, not a single + boot-ROM ramp -- Yellow was CGB-enhanced. - **SGB** (default): the per-map Super Game Boy region palettes (`data/sgb/sgb_palettes.asm`). Sprites tint with the region palette, as on real SGB. (This is the mode formerly mislabeled "GBC".) diff --git a/src/battle/BattleState.lua b/src/battle/BattleState.lua index a2ae60d5..e9f96024 100644 --- a/src/battle/BattleState.lua +++ b/src/battle/BattleState.lua @@ -4483,7 +4483,9 @@ function BattleState:sgbBattlePals() -- true white, which is what drew a white box around each pic on the pink -- field. Snap every zone's color 0/3 to the global GBC white/black; the -- mid shades (and the green bar the user prefers) stay untouched. - if PaletteFX.mode == "ogred" then + -- Boot-ROM OG only (Red/Blue): snap zone paper to the global GBC white/black. + -- OG YELLOW keeps each CGBBasePalettes endpoint (already near-white / near-black). + if PaletteFX.mode == "ogred" and not require("src.core.GameVersion").isYellow() then local white, black = PaletteFX.GBC_BG[1], PaletteFX.GBC_BG[4] for i = 0, 3 do local c = out[i] diff --git a/src/render/PaletteFX.lua b/src/render/PaletteFX.lua index eceb6e9f..7990f88b 100644 --- a/src/render/PaletteFX.lua +++ b/src/render/PaletteFX.lua @@ -5,11 +5,13 @@ -- then drawn once per zone through a shader that remaps the four DMG -- shades to that zone's palette. -- --- Port display option: COLORS (OG RED / SGB / ADVANCED / OG / OG INV / --- SGB INV / CLASSIC) transforms every zone's palette at send time via --- effectiveColors. ADVANCED (the `redpp` id below) swaps the named-palette --- pack for pokered-gbc SuperPalettes (data/palettes_gbc.lua), including --- per-species mon colors. +-- Port display option: COLORS (OG RED / OG BLUE / OG YELLOW / SGB / +-- ADVANCED / OG / OG INV / SGB INV / CLASSIC) transforms every zone's +-- palette at send time via effectiveColors. ADVANCED (the `redpp` id +-- below) swaps the named-palette pack for pokered-gbc SuperPalettes +-- (data/palettes_gbc.lua), including per-species mon colors. OG YELLOW +-- (Yellow playthrough + `ogred` id) uses pokeyellow CGBBasePalettes +-- (data/palettes_yellow.lua / ROM cgbBase). local GameVersion = require("src.core.GameVersion") @@ -17,10 +19,11 @@ local PaletteFX = {} local shader -- false = unavailable (headless / no shader support) local gbcPack -- false = missing; nil = not loaded yet +local yellowPack -- false = missing; nil = not loaded yet -- Cycle order matches OptionsMenu / hotkey 2. The three real colorizations --- come first (OG RED = GBC hardware, SGB = per-map Super Game Boy, ADVANCED = --- pokered-gbc per-tile), then the DMG-shade novelty modes. +-- come first (OG RED/BLUE/YELLOW = GBC hardware, SGB = per-map Super Game Boy, +-- ADVANCED = pokered-gbc per-tile), then the DMG-shade novelty modes. PaletteFX.MODES = { "ogred", "gbc", "redpp", "og", "og_inv", "gbc_inv", "classic" } -- `gbc`/`gbc_inv`/`redpp` keep their save-value ids for back-compat while -- their LABELS say what the mode actually is: "SGB"/"SGB INV" because the old @@ -117,14 +120,19 @@ PaletteFX.GBC_OBJ_BLUE = { } -- The active game's OG boot-ROM background palette: blue for a Blue --- playthrough, red otherwise. White (index 1) and black (index 4) are --- identical across versions, so callers that only touch the endpoints --- (e.g. BattleState's zone white/black snap) need no version branch. --- Yellow is CGB-enhanced (pokeyellow CGBBasePalettes) and has no extracted --- boot-ROM auto-palette here; keep the Red ramp -- never Blue's GBC_BG_BLUE. +-- playthrough, red for Red. White (index 1) and black (index 4) are +-- identical across Red/Blue, so callers that only touch the endpoints +-- (e.g. BattleState's zone white/black snap) need no version branch there. +-- Yellow is CGB-enhanced (pokeyellow CGBBasePalettes): named zones go through +-- pal() / usesYellowCgb(), and ogBg() falls back to CGBBase PAL_ROUTE for any +-- remaining whole-screen callers -- never Blue's GBC_BG_BLUE. function PaletteFX.ogBg() if GameVersion.isBlue() then return PaletteFX.GBC_BG_BLUE end - if GameVersion.isYellow() then return PaletteFX.GBC_BG end + if GameVersion.isYellow() then + local y = PaletteFX.yellowPack() + local route = y and y.cgbBase and y.cgbBase.ROUTE + if route then return route end + end return PaletteFX.GBC_BG end @@ -133,14 +141,12 @@ end -- version-distinct cache-group string, because SpriteRenderer.getObpImage keys -- its baked-image cache by (image path, group): a shared group would collide a -- Red bake with a Blue one and one version would show the other's colors. --- Yellow: same Red OBJ green as above until Yellow-specific tables land. +-- Yellow OG does not bake a boot-ROM OBJ (usesSpriteObp is false); this path +-- is unused there and kept as Red green only as a safe leftover. function PaletteFX.ogObj() if GameVersion.isBlue() then return PaletteFX.darkObp(PaletteFX.GBC_OBJ_BLUE, "gbcobj_blue") end - if GameVersion.isYellow() then - return PaletteFX.darkObp(PaletteFX.GBC_OBJ, "gbcobj") - end return PaletteFX.darkObp(PaletteFX.GBC_OBJ, "gbcobj") end @@ -271,13 +277,31 @@ function PaletteFX.gbcPack() return gbcPack or nil end +-- pokeyellow SuperPalettes + CGBBasePalettes (committed; not wiped by import). +-- ROM import may also stamp palettes.cgbBase; yellowCgbNamedPal prefers that. +function PaletteFX.yellowPack() + if yellowPack == nil then + local ok, pack = pcall(require, "data.palettes_yellow") + yellowPack = ok and pack or false + end + return yellowPack or nil +end + function PaletteFX.usesGbcPack(mode) mode = mode or PaletteFX.mode return mode == "redpp" end +-- Yellow's authentic GBC look is CGBBasePalettes (per-map), not a boot-ROM +-- auto-palette. The shared `ogred` save id wears that table on a Yellow +-- playthrough and labels itself "OG YELLOW". +function PaletteFX.usesYellowCgb(mode) + mode = mode or PaletteFX.mode + return GameVersion.isYellow() and mode == "ogred" +end + -- Whether the active mode bakes a per-OBJ palette onto overworld sprites --- (the OBP bake + post-zone redraw path). OG RED alone does: the Game Boy +-- (the OBP bake + post-zone redraw path). OG RED / OG BLUE do: the Game Boy -- Color boot ROM hands the game one global object palette (PaletteFX.ogObj -- -- green over Red's red background, pink over Blue's blue background), so on -- that machine the player and NPCs carry a fixed object color instead of @@ -288,6 +312,10 @@ end -- regression. Terrain is unaffected -- pal() below still hands SGB its per-map -- BG palette; only OG RED short-circuits BG to the one global red palette. -- +-- OG YELLOW does NOT. Yellow ships CGB code and colors regions via +-- CGBBasePalettes the same way SGB mode colors SuperPalettes, so sprites tint +-- with the map zone (usesSpriteObp stays off). +-- -- SGB does NOT. The Super Game Boy colorizes the composited DMG picture it is -- handed and cannot tell an OBJ pixel from a BG one; pokered never sends the -- OBJ_TRN packet that would enable SGB sprite mode (data/sgb/sgb_packets.asm @@ -306,7 +334,7 @@ end -- usesGbcPack() path in SpriteRenderer instead. function PaletteFX.usesSpriteObp(mode) mode = mode or PaletteFX.mode - return mode == "ogred" + return mode == "ogred" and not GameVersion.isYellow() end -- ------- post-zone sprite redraw (OG RED) @@ -363,8 +391,8 @@ end -- read these from the ROM-imported table or the title ribbon stays red -- and the Game Corner reels keep Red's pink (issue #128). -- Yellow is intentionally NOT in BLUE_VERSIONED: skip Blue LOGO1/SLOTS* --- recolors. When CGBBasePalettes were imported (palettes.cgbBase), Yellow --- prefers those over SGB SuperPalettes for named zones. +-- recolors. OG YELLOW (usesYellowCgb) reads CGBBasePalettes; SGB mode on +-- Yellow keeps SuperPalettes. local BLUE_VERSIONED = { LOGO1 = true, SLOTS2 = true, SLOTS3 = true, SLOTS4 = true, } @@ -376,31 +404,42 @@ end local function yellowCgbNamedPal(data, name) local p = data and data.palettes - return p and p.cgbBase and p.cgbBase[name] + local fromRom = p and p.cgbBase and p.cgbBase[name] + if fromRom then return fromRom end + local y = PaletteFX.yellowPack() + return y and y.cgbBase and y.cgbBase[name] or nil end -- named palette from the active pack (nil on stale builds / missing name). -- RED++ falls back to the ROM pack for names the gbc table omits (rare). --- OG RED short-circuits EVERY name to the one global GBC boot-ROM BG palette --- (the hardware had a single BGP for the whole game), so terrain zones, --- battle HP bars / text, and menu boxes all come out red -- everything a --- background tile drew. Objects do not come through here (they bake --- GBC_OBJ green), so this stays a BG-only hook. +-- OG RED / OG BLUE short-circuit EVERY name to the one global GBC boot-ROM +-- BG palette (the hardware had a single BGP for the whole game), so terrain +-- zones, battle HP bars / text, and menu boxes all come out red/blue -- +-- everything a background tile drew. Objects do not come through here +-- (they bake GBC_OBJ), so this stays a BG-only hook. +-- OG YELLOW instead resolves each name through CGBBasePalettes. function PaletteFX.pal(data, name) - if PaletteFX.mode == "ogred" then return PaletteFX.ogBg() end + if PaletteFX.mode == "ogred" and not GameVersion.isYellow() then + return PaletteFX.ogBg() + end -- Blue-only ROM override for versioned SuperPals. Yellow (isYellow) and -- Red keep the active pack / Red-like path -- do not apply Blue recolors. if GameVersion.isBlue() and BLUE_VERSIONED[name] then local fromRom = romNamedPal(data, name) if fromRom then return fromRom end end - if GameVersion.isYellow() then + if PaletteFX.usesYellowCgb() then local fromCgb = yellowCgbNamedPal(data, name) if fromCgb then return fromCgb end end local p = PaletteFX.pack(data) local c = p and p.palettes[name] if c then return c end + if GameVersion.isYellow() then + local y = PaletteFX.yellowPack() + local yc = y and y.palettes and y.palettes[name] + if yc then return yc end + end if PaletteFX.usesGbcPack() then return romNamedPal(data, name) end @@ -413,14 +452,20 @@ end -- Transformed mon's pic is tinted gray, not the copied species' own -- SGB color). RED++ uses per-species pals from mon_palettes.asm. function PaletteFX.monPal(data, species, transformed) - -- OG RED: a battle mon pic is a BG tile on the Game Boy Color (drawn into - -- the tilemap, colored by BGP), so it wears the global red BG palette, not - -- a per-species one -- matching the hardware capture where both mons are - -- red/pink on the white field. - if PaletteFX.mode == "ogred" then return PaletteFX.ogBg() end + -- OG RED / OG BLUE: a battle mon pic is a BG tile on the Game Boy Color + -- (drawn into the tilemap, colored by BGP), so it wears the global boot-ROM + -- BG palette, not a per-species one -- matching the hardware capture where + -- both mons are red/pink (or blue/pink) on the white field. + -- OG YELLOW keeps per-species CGBBasePalettes (Yellow had real CGB code). + if PaletteFX.mode == "ogred" and not GameVersion.isYellow() then + return PaletteFX.ogBg() + end local p = PaletteFX.pack(data) if not p then return nil end if transformed then + if PaletteFX.usesYellowCgb() then + return PaletteFX.pal(data, "GRAYMON") + end return p.palettes.GRAYMON or (data and data.palettes and data.palettes.palettes.GRAYMON) end @@ -430,17 +475,32 @@ function PaletteFX.monPal(data, species, transformed) -- so fall back to it when the pack itself doesn't carry the name. local def = data and data.pokemon and data.pokemon[species] if def and def.palette then + if PaletteFX.usesYellowCgb() then + local yc = PaletteFX.pal(data, def.palette) + if yc then return yc end + end local pal = p.palettes[def.palette] or (data and data.palettes and data.palettes.palettes[def.palette]) if pal then return pal end end local name = p.pokemon[species] or "MEWMON" + if PaletteFX.usesYellowCgb() then + local yc = PaletteFX.pal(data, name) + if yc then return yc end + end local c = p.palettes[name] if c then return c end if PaletteFX.usesGbcPack() and data and data.palettes then name = data.palettes.pokemon[species] or "MEWMON" return data.palettes.palettes[name] end + if GameVersion.isYellow() then + local y = PaletteFX.yellowPack() + if y and y.pokemon then + name = y.pokemon[species] or "MEWMON" + return y.palettes and y.palettes[name] + end + end return nil end @@ -707,12 +767,10 @@ end function PaletteFX.modeLabel(mode) mode = mode or PaletteFX.mode - -- The GBC boot-ROM mode wears the running game's name: it is red for Red and - -- blue for Blue (see ogBg), so a Blue playthrough shows "OG BLUE". - -- Yellow still uses the Red boot-ROM ramp (no Yellow table yet), so keep - -- the "OG RED" label rather than inventing an "OG YELLOW" without colors. + -- The GBC hardware mode wears the running game's name: OG RED / OG BLUE + -- (boot-ROM auto-palette) or OG YELLOW (pokeyellow CGBBasePalettes). if mode == "ogred" and GameVersion.isBlue() then return "OG BLUE" end - if mode == "ogred" and GameVersion.isYellow() then return "OG RED" end + if mode == "ogred" and GameVersion.isYellow() then return "OG YELLOW" end return PaletteFX.MODE_LABELS[mode] or "GBC" end diff --git a/src/world/OverworldController.lua b/src/world/OverworldController.lua index 0ea2ac3e..1bd90ef0 100644 --- a/src/world/OverworldController.lua +++ b/src/world/OverworldController.lua @@ -550,9 +550,26 @@ end -- UI-pass palette (text boxes and menus tint with the current map). OG RED -- resolves every name to the one global red BG palette inside PaletteFX.pal, -- so this needs no mode-specific branch. +-- +-- TalkToPikachu's framed frontpic is the one exception: pokeyellow +-- LoadOverworldPikachuFrontpicPalettes loads the map pal as slot 0 and +-- PAL_PIKACHU_PORTRAIT as slot 1, then ATTR_BLK's the 5x5 pic at +-- (7,6)-(11,10) onto slot 1 (engine/gfx/palettes.asm:345-391). Without +-- that zone the pic wears the route/town palette and looks washed out. function OverworldState:sgbPalettes() local PaletteFX = require("src.render.PaletteFX") - return PaletteFX.wholeNamed(Game.data, self:paletteNameFor(self.map)) + local mapName = self:paletteNameFor(self.map) + if self.emote and self.emote.pikaPic then + local base = PaletteFX.pal(Game.data, mapName) + if not base then return nil end + local zones = { PaletteFX.whole(base) } + local portrait = PaletteFX.pal(Game.data, "PIKACHU_PORTRAIT") + if portrait then + zones[#zones + 1] = PaletteFX.zone(portrait, 7, 6, 11, 10) + end + return zones + end + return PaletteFX.wholeNamed(Game.data, mapName) end -- World-pass palette zones in world-canvas pixels: each visible map @@ -4479,7 +4496,8 @@ function OverworldState:drawUI() -- per-emotion frame gfx (gfx/pikachu/unknown_*) are not extracted, so the -- front pic stands in for every frame of the script; PikachuFollower -- .picLift lifts it on the runs that draw the alternate pose, and the - -- script's own duration times the beat (#407, #424). + -- script's own duration times the beat (#407, #424). Palette zone + -- PAL_PIKACHU_PORTRAIT covers (7,6)-(11,10) via sgbPalettes above. if self.emote and self.emote.pikaPic then require("src.render.Font").drawBox(6, 5, 7, 7) -- one image per path, cached: this draws every frame of the hold, and diff --git a/tools/extract/palettes.py b/tools/extract/palettes.py index 6be5a70e..bf67e387 100644 --- a/tools/extract/palettes.py +++ b/tools/extract/palettes.py @@ -21,6 +21,16 @@ Output: data/palettes_gbc.lua (committed; not wiped by ROM import) same shape, plus per-species palette entries (BULBASAUR, …), plus a `world` table (below) for true overworld tile/roof/sprite coloring. +Sources (pokeyellow / COLORS OG YELLOW): + data/sgb/sgb_palettes.asm SuperPalettes + CGBBasePalettes (Yellow is + CGB-enhanced; CGBBase is the authentic GBC look) + data/pokemon/palettes.asm MonsterPalettes (same shape as Red) + +Output: data/palettes_yellow.lua (committed; not wiped by ROM import) + palettes[NAME] = SuperPalettes RGB + cgbBase[NAME] = CGBBasePalettes RGB + pokemon[SPECIES] = NAME + The HP bar fill is GB color 2 of PAL_GREENBAR / PAL_YELLOWBAR / PAL_REDBAR; the thresholds live in home/palettes.asm GetHealthBarColor (>= 27 pixels green, >= 10 yellow, else red). @@ -536,6 +546,89 @@ def extract_gbc(pokered_gbc, out_path): return palettes, mon_pals +def _parse_pokeyellow_palette_tables(path): + """Parse SuperPalettes and CGBBasePalettes from pokeyellow sgb_palettes.asm.""" + tables = {"SuperPalettes": {}, "CGBBasePalettes": {}} + orders = {"SuperPalettes": [], "CGBBasePalettes": []} + current = None + for lineno, line in _read_raw(path): + s = line.strip() + if s.startswith("SuperPalettes:"): + current = "SuperPalettes" + continue + if s.startswith("CGBBasePalettes:"): + current = "CGBBasePalettes" + continue + if s.startswith("assert_table_length") or s.endswith(":") and " " not in s: + if s.endswith(":") and not s.startswith(("Super", "CGB")): + current = None + continue + if current is None: + continue + m = re.match(r"RGB\s+([\d,\s]+);\s*PAL_(\w+)", s) + if not m: + continue + nums = [n for n in re.split(r"[,\s]+", m.group(1).strip()) if n] + if len(nums) != 12: + util.die(f"{path}:{lineno}: expected 12 components, got {len(nums)}") + name = m.group(2) + tables[current][name] = _rgb8(nums) + orders[current].append(name) + return tables, orders + + +def extract_yellow(pokeyellow, out_path): + """Extract Yellow SuperPalettes + CGBBasePalettes for COLORS=OG YELLOW.""" + pal_path = os.path.join(pokeyellow, "data/sgb/sgb_palettes.asm") + mon_path = os.path.join(pokeyellow, "data/pokemon/palettes.asm") + tables, orders = _parse_pokeyellow_palette_tables(pal_path) + palettes = tables["SuperPalettes"] + cgb_base = tables["CGBBasePalettes"] + order = orders["SuperPalettes"] + if not palettes: + util.die(f"{pal_path}: SuperPalettes empty") + if not cgb_base: + util.die(f"{pal_path}: CGBBasePalettes empty") + if orders["CGBBasePalettes"] != order: + util.die(f"{pal_path}: CGBBasePalettes order differs from SuperPalettes") + + mon_pals = {} + in_table = False + for lineno, line in _read_raw(mon_path): + s = line.strip() + if s.startswith("MonsterPalettes:"): + in_table = True + continue + if not in_table: + continue + m = re.match(r"db\s+PAL_(\w+)\s*;\s*(\w+)", s) + if not m: + continue + pal, species = m.group(1), m.group(2) + if pal not in palettes: + util.die(f"{mon_path}:{lineno}: unknown palette PAL_{pal}") + if species != "MISSINGNO": + mon_pals[species] = pal + + if len(mon_pals) != 151: + util.die(f"MonsterPalettes parsed {len(mon_pals)} species (want 151)") + for name in ("MEWMON", "GREENBAR", "YELLOWBAR", "REDBAR", "ROUTE"): + if name not in cgb_base: + util.die(f"{pal_path}: CGBBase PAL_{name} missing") + + util.write_lua( + out_path, + {"source": "pokeyellow data/sgb/sgb_palettes.asm + data/pokemon/palettes.asm", + "palettes": palettes, + "cgbBase": cgb_base, + "order": order, + "pokemon": mon_pals}, + header="Pokemon Yellow palettes (COLORS=OG YELLOW): SuperPalettes +\n" + "CGBBasePalettes (authentic GBC look) as 4 8-bit RGB colors\n" + "per palette (color 0 first), plus MonsterPalettes.") + return palettes, cgb_base, mon_pals + + def main(argv=None): parser = argparse.ArgumentParser(description=__doc__.split("\n\n")[0]) sub = parser.add_subparsers(dest="cmd", required=True) @@ -548,11 +641,17 @@ def main(argv=None): p_gbc.add_argument("--pokered-gbc", required=True) p_gbc.add_argument("--out", default="data/palettes_gbc.lua") + p_yel = sub.add_parser("yellow", help="extract pokeyellow CGBBasePalettes") + p_yel.add_argument("--pokeyellow", required=True) + p_yel.add_argument("--out", default="data/palettes_yellow.lua") + args = parser.parse_args(argv) if args.cmd == "vanilla": extract(args.pokered, args.out_dir) - else: + elif args.cmd == "gbc": extract_gbc(args.pokered_gbc, args.out) + else: + extract_yellow(args.pokeyellow, args.out) return 0