Create Battle Palettes for Different Times of Day and Environments - pret/pokecrystal GitHub Wiki
In Generation II, the color palette of the overworld changes depending on the time of day, but this is not reflected in Battles. Here, we will learn how to make a Battle Palette for Night Battles, as well as provide the foundation for further Battle Palettes for environments such as Caves and Forests. Furthermore, this will allow the move Sunny Day to alter the current battle Palette to the Day Palette.
-
Add a check to to reload the appropriate battle palette when weather fades
-
Apply Palette Fix for Trainer Card screen, Pokemon PC, and Pokedex
1. Create some new wRAM Labels
Edit ram/wram.asm:
wMapTimeOfDay:: db
+wBattleTimeOfDay:: db
+wBattleTimeOfDayBackup:: db
- ds 3
+ ds 1
Edit ram/wram.asm again, adding wBattleWeatherBackup below wBattleWeather.
wBattleWeather::
; 00 normal
; 01 rain
; 02 sun
; 03 sandstorm
; 04 rain stopped
; 05 sunliight faded
; 06 sandstorm subsided
db
+wBattleWeatherBackup::
+ db
We need these backup values to avoid even longer branching value checks. Ultimately these values will be used to return us to the correct battle palettes for the corresponding time, environment or weather after returning from the Stats Screen when accessed in battle.
2. Load the Time or Environment Value into wBattleTimeOfDay
Edit engine/battle/start_battle.asm:
FindFirstAliveMonAndStartBattle:
+; First we do environment checks. Adapt this however you want. Since both Forests and Caves use the "CAVE" environment,
+; we'll do a check for the forest environment first. In this case, it's using Ilex Forest.
+
+ ld a, [wMapGroup]
+ ld b, a
+ ld a, [wMapNumber]
+ ld c, a
+ call GetWorldMapLocation
+ cp LANDMARK_ILEX_FOREST
+ jr z, .forestpal
+
+ call GetMapEnvironment
+ cp DUNGEON
+ jr z, .indoorpal
+
+ call GetMapEnvironment
+ cp INDOOR
+ jr z, .indoorpal
+
+ call GetMapEnvironment
+ cp CAVE
+ jr z, .cavepal
+
+ ; Now we check for the Time of Day
+ ld a, [wTimeOfDay]
+ cp DAY_F
+ jr z, .daypal
+
+ ld a, [wTimeOfDay]
+ cp MORN_F
+ jr z, .daypal
+
+ ld a, [wTimeOfDay]
+ cp NITE_F
+ jr z, .nightpal
+
+
+.indoorpal
+ ld a, 0
+ ld [wBattleTimeOfDay], a
+ jr .timeofdaypalset
+
+.cavepal
+ ld a, 2
+ ld [wBattleTimeOfDay], a
+ jr .timeofdaypalset
+
+.forestpal
+ ld a, 3
+ ld [wBattleTimeOfDay], a
+ jr .timeofdaypalset
+
+.daypal
+ ld a, 0
+ ld [wBattleTimeOfDay], a
+ jr .timeofdaypalset
+
+
+.nightpal
+ ld a, 1
+ ld [wBattleTimeOfDay], a
+ jr .timeofdaypalset
+
+.timeofdaypalset
+ xor a
+ ldh [hMapAnims], a
+ call DelayFrame
+ ld b, PARTY_LENGTH
+ ld hl, wPartyMon1HP
+ ld de, PARTYMON_STRUCT_LENGTH - 1
Then edit engine/battle/core.asm:
CleanUpBattleRAM:
call BattleEnd_HandleRoamMons
xor a
+ ld [wStatsScreenFlags], a
+ ld [wBattleTimeOfDay], a
+ ld [wBattleWeather], a
ld [wLowHealthAlarm], a
Here, we are setting up for the Time of Day to be read at the start of battle, and loaded into our label. Day = 0, and Night = 1 in this example, with 2 and 3 being used for Cave and Forest palettes, and 0 being re-used for indoor maps. We also want to make sure the label is cleared at the end of the battle, as well as the Stats Screen Flags.
3. Back up the Time and Weather values when activating the Stats Screen and display the correct palette.
Go to engine/pokemon/stats_screen.asm and edit BattleStatsScreenInit and StatsScreenInit:
BattleStatsScreenInit:
+ ; Load current Battle Time of Day into Backup
+ ld a, [wBattleTimeOfDay]
+ ld [wBattleTimeOfDayBackup], a
+
+ ; Load current Weather into Backup,
+ ld a, [wBattleWeather]
+ ld [wBattleWeatherBackup], a
+
+ ; Clear Weather and Time to 0, giving the stats screen the day palette.
+ ld a, 0
+ ld [wBattleTimeOfDay], a
+
+ ld a, 0
+ ld [wBattleWeather], a
+
ld a, [wLinkMode]
cp LINK_MOBILE
jr nz, StatsScreenInit
ld a, [wBattleMode]
and a
jr z, StatsScreenInit
jr _MobileStatsScreenInit
StatsScreenInit:
+ xor a
+ ld [wBattleTimeOfDay], a
+ ld [wBattleWeather], a
+ ld a, 0
+ ld [wBattleTimeOfDay], a
+ farcall _CGB_BattleColors
ld hl, StatsScreenMain
jr StatsScreenInit_gotaddress
Now edit StatsScreen_Exit
in the same file, to call the appropriate palettes back from the backup upon exiting the menu.
StatsScreen_Exit:
ld hl, wJumptableIndex
set 7, [hl]
+ jp StatsScreen_Exit2
ret
+StatsScreen_Exit2:
+ ld a, [wBattleTimeOfDayBackup]
+ ld [wBattleTimeOfDay], a
+
+ ld a, [wBattleWeatherBackup]
+ ld [wBattleWeather], a
4. Add a check to to reload the appropriate battle palette when weather fades.
Edit engine/battle/core.asm:
First, edit HandleWeather:
HandleWeather:
ld a, [wBattleWeather]
cp WEATHER_NONE
ret z
ld hl, wWeatherCount
dec [hl]
jr nz, .continues
jr z, .ended
.ended
ld hl, .WeatherEndedMessages
call .PrintWeatherMessage
+ farcall _CGB_BattleColors
+ ld a, 1
+ ld [hCGBPalUpdate], a
xor a
ld [wBattleWeather], a
ret
Then edit HandleBetweenTurnEffects:
and .CheckEnemyFirst
ldh a, [hSerialConnectionStatus]
cp USING_EXTERNAL_CLOCK
jr z, .CheckEnemyFirst
call CheckFaint_PlayerThenEnemy
ret c
call HandleFutureSight
call CheckFaint_PlayerThenEnemy
ret c
call HandleWeather
+ farcall _CGB_BattleColors
call CheckFaint_PlayerThenEnemy
ret c
call HandleWrap
call CheckFaint_PlayerThenEnemy
ret c
call HandlePerishSong
call CheckFaint_PlayerThenEnemy
ret c
jr .NoMoreFaintingConditions
.CheckEnemyFirst:
call CheckFaint_EnemyThenPlayer
ret c
call HandleFutureSight
call CheckFaint_EnemyThenPlayer
ret c
call HandleWeather
+ farcall _CGB_BattleColors
call CheckFaint_EnemyThenPlayer
ret c
call HandleWrap
call CheckFaint_EnemyThenPlayer
ret c
call HandlePerishSong
call CheckFaint_EnemyThenPlayer
ret c
NOTE: farcall _CGB_BattleColors
doesn't request attrmap/pals update, only prepares them. You have to make sure to signal update by setting hCGBPalUpdate to 1
5. Create new PALRGB_ constants
Create the desired battle background color palettes in constants/gfx_constants.asm:
DEF PALRGB_WHITE EQU palred 31 + palgreen 31 + palblue 31 ; $7fff
+ DEF PALRGB_NIGHT EQU palred 20 + palgreen 20 + palblue 31
+ DEF PALRGB_CAVE EQU palred 31 + palgreen 20 + palblue 20
+ DEF PALRGB_FOREST EQU palred 9 + palgreen 18 + palblue 20
This is the color that will replace the normal 'white' battle BG color. For night colors, we are taking half from red and green, and a bit less from blue. For Forest and Cave colors, I just fiddled with them for an approximate palette for proof of concept. Feel free to change these to whatever you like.
Remember! The white of your Pokemon and Trainer Sprites is transparent! Thus it will appear as whatever your background color is. Therefore, I recommend that you err on the lighter side of the shades you want. A light brown for caves, a light green/teal for forests, etc. Then it will still be a visibly different palette without destroying the four-tone shading of your sprites.
In this way, the palette "suggests" an environment.
6. Edit LoadPalette_White_Col1_Col2_Black
Edit LoadPalette_White_Col1_Col2_Black in engine/gfx/color.asm:
LoadPalette_White_Col1_Col2_Black:
ldh a, [rSVBK]
push af
ld a, BANK(wBGPals1)
ldh [rSVBK], a
+; A double-check to see whether Stat Pages 1, 2 or 3 are active, to make sure that palettes stay consistent when switching between pages and Pokemon,
+ set them to day palette to avoid graphical error. Assuming you added a fourth stats page, we skip the 0 check here due to the value's default state
+ being 0, which causes inconsistent battle palette loading when on particular tiles due to memory overlap, but that's handled by the Stats Screen
+ initialization in the prior steps.
+
+ .StatsFlags
+ ld a, [wStatsScreenFlags]
+ cp 1
+ jr z, .day
+ cp 2
+ jr z, .day
+ cp 3
+ jr z, .day
+
+ ; Check whether the weather is Sunny (re: Sunny Day), set to day palette.
+
+ ld a, [wBattleWeather]
+ cp WEATHER_SUN
+ jr z, .day
+
+ ld a, [wBattleTimeOfDay]
+ and a
+ jr z, .day
+
+ ld a, [wBattleTimeOfDay]
+ cp 1
+ jr z, .night
+
+ ld a, [wBattleTimeOfDay]
+ cp 2
+ jr z, .cave
+
+ ld a, [wBattleTimeOfDay]
+ cp 3
+ jr z, .forest
+
+
+ ; Below are the sections which call the correct colors, formatted into easily marked commented blocks, for easy recognition amongst the extended code,
+ ; which can be easily copy+pasted for your own custom palettes.
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; NIGHT START
+.night
+;call background colors
+ ld a, LOW(PALRGB_NIGHT)
+ ld [de], a
+ inc de
+ ld a, HIGH(PALRGB_NIGHT)
+ ld [de], a
+ inc de
+
+
+;call sprite colors
+ call NightColors
+ ld c, 2 * PAL_COLOR_SIZE
+
+ jr .black
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; NIGHT END
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; CAVE START
+.cave
+;call background colors
+ ld a, LOW(PALRGB_CAVE)
+ ld [de], a
+ inc de
+ ld a, HIGH(PALRGB_CAVE)
+ ld [de], a
+ inc de
+
+
+;call sprite colors
+ call CaveColors
+ ld c, 2 * PAL_COLOR_SIZE
+
+ jr .black
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; CAVE END
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; FOREST START
+.forest
+;call background colors
+ ld a, LOW(PALRGB_FOREST)
+ ld [de], a
+ inc de
+ ld a, HIGH(PALRGB_FOREST)
+ ld [de], a
+ inc de
+
+
+;call sprite colors
+ call ForestColors
+ ld c, 2 * PAL_COLOR_SIZE
+
+ jr .black
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; FOREST END
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; DAY START
+.day
+;call background colors
+ ld a, LOW(PALRGB_WHITE)
+ ld [de], a
+ inc de
+ ld a, HIGH(PALRGB_WHITE)
+ ld [de], a
+ inc de
+;call sprite colors
+ ld c, 2 * PAL_COLOR_SIZE
+
+.loop
+ ld a, [hli]
+ ld [de], a
+ inc de
+ dec c
+ jr nz, .loop
+
+.black
+ xor a
+ ld [de], a
+ inc de
+ ld [de], a
+ inc de
+
+ pop af
+ ldh [rSVBK], a
+ ret
FillBoxCGB:
.row
push bc
push hl
Now, we are editing the function that loads the palettes. Add a check for our label, and if it does not pass the day check, it will fall through and begin to load our night, cave, forest color modifications. Now comes the tricky part; dissecting and modifying the middle colors.
7. Mask Out R, G, and B and Edit Colors
Edit engine/gfx/color.asm again to create NightColors: & NightColorSwap at the end of the file.
+NightColors:
+ call NightColorSwap
+
+; b = gggrrrrr, c = 0bbbbbGG
+.loop
+ ld a, b
+ ld [de], a
+ inc de
+ inc hl
+
+ ld a, c
+ ld [de], a
+ inc de
+ inc hl
+
+ call NightColorSwap
+
+; b = gggrrrrr, c = 0bbbbbGG
+.loop2
+ ld a, b
+ ld [de], a
+ inc hl
+ inc de
+
+ ld a, c
+ ld [de], a
+ inc hl
+ inc de
+
+ ret
+
+NightColorSwap:
+ push de
+
+; red
+ ld a, [hl] ; gggrrrrr
+ and $1f ; 00011111 -> 000rrrrr
+
+ ld e, a ; red in e
+
+;green
+ ld a, [hli] ; gggrrrrr
+ and $e0 ; 11100000 -> ggg00000
+ ld b, a
+ ld a, [hl] ; 0bbbbbGG
+ and 3 ; 00000011 -> 000000GG
+ or b ; 000000GG + ggg00000
+ swap a ; ggg0 00GG -> 00GGggg0
+ rrca ; 000GGggg
+
+ ld d, a ; green in d
+
+;blue
+ ld a, [hld] ; 0bbbbbGG
+ and $7c ; 1111100 -> 0bbbbb00
+
+ ld c, a ; blue in c
+
+;modify colors here
+ srl e ; 1/2 red
+ srl d ; 1/2 green
+
+; 3/4 blue
+ ld a, c
+ rrca ; 1/2
+ ld b, a
+ rrca ; 1/4
+ add b ; 2/4 + 1/4 = 3/4
+ and %01111100 ; mask the blue bits
+ ld c, a
+
+;reassemble green
+ ld a, d
+ rlca ; 00GGggg0
+ swap a ; 00GG ggg0 -> ggg000GG
+ and $e0 ; 11100000 -> ggg00000
+ ld b, a
+ ld a, d
+ rlca ; 00GGggg0
+ swap a ; 00GG ggg0 -> ggg000GG
+ and 3 ; 00000011 -> 000000GG
+ ld d, a
+
+;red in e, low green in b, high green in d, blue in c
+ ld a, e
+ or b ; 000rrrrr + ggg00000
+ ld b, a ; gggrrrrr
+ ld a, d
+ or c ; 0bbbbb00 + 000000GG
+ ld c, a ; 0bbbbbGG
+ pop de
+ ret
Add Duplicate Checks for Cave Colors and Forest Colors:
CaveColors:
call CaveColorSwap
; b = gggrrrrr, c = 0bbbbbGG
.loop
ld a, b
ld [de], a
inc de
inc hl
ld a, c
ld [de], a
inc de
inc hl
call CaveColorSwap
; b = gggrrrrr, c = 0bbbbbGG
.loop2
ld a, b
ld [de], a
inc hl
inc de
ld a, c
ld [de], a
inc hl
inc de
ret
CaveColorSwap:
push de
; red
ld a, [hl] ; gggrrrrr
and $1f ; 00011111 -> 000rrrrr
ld e, a ; red in e
;green
ld a, [hli] ; gggrrrrr
and $e0 ; 11100000 -> ggg00000
ld b, a
ld a, [hl] ; 0bbbbbGG
and 3 ; 00000011 -> 000000GG
or b ; 000000GG + ggg00000
swap a ; ggg0 00GG -> 00GGggg0+ rrca ; 000GGggg
ld d, a ; green in d
;blue
ld a, [hld] ; 0bbbbbGG
and $7c ; 1111100 -> 0bbbbb00
ld c, a ; blue in c
;modify colors here
srl e ; 1/2 red
srl d ; 1/2 green
srl d ; 1/2 green
; 3/4 blue
ld a, c
rrca ; 1/2
ld b, a
rrca ; 1/4
add b ; 2/4 + 1/4 = 3/4
and %01111100 ; mask the blue bits
ld c, a
;reassemble green
ld a, d
rlca ; 00GGggg0
swap a ; 00GG ggg0 -> ggg000GG
and $e0 ; 11100000 -> ggg00000
ld b, a
ld a, d
rlca ; 00GGggg0
swap a ; 00GG ggg0 -> ggg000GG
and 3 ; 00000011 -> 000000GG
ld d, a
;red in e, low green in b, high green in d, blue in c
ld a, e
or b ; 000rrrrr + ggg00000
ld b, a ; gggrrrrr
ld a, d
or c ; 0bbbbb00 + 000000GG
ld c, a ; 0bbbbbGG
pop de
ret
ForestColors:
call ForestColorSwap
; b = gggrrrrr, c = 0bbbbbGG
.loop
ld a, b
ld [de], a
inc de
inc hl
ld a, c
ld [de], a
inc de
inc hl
call ForestColorSwap
; b = gggrrrrr, c = 0bbbbbGG
.loop2
ld a, b
ld [de], a
inc hl
inc de
ld a, c
ld [de], a
inc hl
inc de
ret
ForestColorSwap:
push de
; red
ld a, [hl] ; gggrrrrr
and $1f ; 00011111 -> 000rrrrr
ld e, a ; red in e
;green
ld a, [hli] ; gggrrrrr
and $e0 ; 11100000 -> ggg00000
ld b, a
ld a, [hl] ; 0bbbbbGG
and 3 ; 00000011 -> 000000GG
or b ; 000000GG + ggg00000
swap a ; ggg0 00GG -> 00GGggg0+ rrca ; 000GGggg
ld d, a ; green in d
;blue
ld a, [hld] ; 0bbbbbGG
and $7c ; 1111100 -> 0bbbbb00
ld c, a ; blue in c
;modify colors here
srl e ; 1/2 red
srl d ; 1/2 green
srl d ; 1/2 green
; 3/4 blue
ld a, c
rrca ; 1/2
ld b, a
rrca ; 1/4
add b ; 2/4 + 1/4 = 3/4
and %01111100 ; mask the blue bits
ld c, a
;reassemble green
ld a, d
rlca ; 00GGggg0
swap a ; 00GG ggg0 -> ggg000GG
and $e0 ; 11100000 -> ggg00000
ld b, a
ld a, d
rlca ; 00GGggg0
swap a ; 00GG ggg0 -> ggg000GG
and 3 ; 00000011 -> 000000GG
ld d, a
;red in e, low green in b, high green in d, blue in c
ld a, e
or b ; 000rrrrr + ggg00000
ld b, a ; gggrrrrr
ld a, d
or c ; 0bbbbb00 + 000000GG
ld c, a ; 0bbbbbGG
pop de
ret
Each color is stored in two pairs, gggrrrrr and 0bbbbbGG. Since we want to modify the 3 RGB values individually, we need to mask out each color and store them in e, d, and c before we can adjust them. Then we must reassemble them before we finish.
8. Apply Palette to entire Battle Screen
Now you have multiple battle palettes for night, day, and environments -- but it only applies to the upper battle section of the screen! The lower text boxes still appear with a normal palette. If you would rather the entire battle screen have the new night palette this can be easily achieved with the following changes. Ideally you will use this, as the default white palette for the text box doesn't apply to Move Info textboxes due to their appearance on different lines.
Alter the file - engine/gfx/cgb_layouts.asm
_CGB_FinishBattleScreenLayout:
....
call FillBoxCGB
hlcoord 0, 12, wAttrmap
ld bc, 6 * SCREEN_WIDTH
- ld a, PAL_BATTLE_BG_TEXT
+ ld a, PAL_BATTLE_BG_PLAYER
And also the file - home/text.asm
TextboxPalette::
; Fill text box width c height b at hl with pal 7
ld de, wAttrmap - wTilemap
add hl, de
inc b
inc b
inc c
inc c
+ ld a, [wBattleMode]
+ and a
+ jr nz, .battle
+ ld a, PAL_BG_TEXT
+ jr .col
+.battle
+ ld a, PAL_BATTLE_BG_PLAYER
.col
push bc
push hl
9. Alter the Weather Move code to farcall _CGB_BattleColors
Go to engine/battle/move_effects and find 'sunny_day.asm', 'sandstorm.asm', and 'rain_dance.asm' (as well as the files for any custom weather effects you've added), and simply add farcall _CGB_BattleColors
just before call AnimateCurrentMove
as seen below in the example for Sunny Day:
BattleCommand_StartSun:
ld a, WEATHER_SUN
ld [wBattleWeather], a
ld a, 5
ld [wWeatherCount], a
+ farcall _CGB_BattleColors
call AnimateCurrentMove
ld hl, SunGotBrightText
jp StdBattleTextbox
This simply makes sure that the correct battle palette is refreshed when you use a weather move, which in this case due to weather checks shown previously in this tutorial, is the usual white Day palette used for normal day battles.
Thus, if you use the move Sunny Day in a cave, a forest, or at night-time, the palette will transition to white as the move Sunny Day animates.
Likewise, when Sunny Day fades, or a different weather move is used that cancels out Sunny Day, the battle palette will return to whatever palette is appropriate for the environment, be that day, night, cave or forest.
10. Apply Palette Fix for Trainer Card screen, Pokemon PC, and Pokedex:
This is similar to what we did in Step 3. When accessing the Pokémon PC to deposit/withdraw Pokemon, and the Trainer Card page, we clear the current wBattleWeather and wBattleTimeOfDay. This ensures that it doesn't ever accidentally load an incorrect battle palette in place of the standard white one. This doesn't really have any negative effects, since wBattleWeather and wBattleTimeOfDay should be 0 anyway when outside of battle. The reason it may not be, is due to shared values in wRAM somewhere being impacted by an unrelated value, but this will fix that.
First, open engine/gfx/cgb_layouts.asm. Find _CGB_TrainerCard:
and make the following changes:
_CGB_TrainerCard:
+ xor a
+ ld [wBattleWeather], a
+ ld [wBattleTimeOfDay], a
ld de, wBGPals1
xor a ; CHRIS
call GetTrainerPalettePointer
call LoadPalette_White_Col1_Col2_Black
ld a, FALKNER ; KRIS
call GetTrainerPalettePointer
...
In the same file, find _CGB_BillsPC
and go down to .GetMonPalette
. Make the following changes.
.GetMonPalette:
ld bc, wTempMonDVs
call GetPlayerOrMonPalettePointer
+ xor a
+ ld [wBattleWeather], a
+ ld [wBattleTimeOfDay], a
call LoadPalette_White_Col1_Col2_Black
Now open engine/pokedex/new_pokedex_entry.asm and just add the same thing to the start of the file:
NewPokedexEntry:
+ xor a
+ ld [wBattleWeather], a
+ ld [wBattleTimeOfDay], a
ldh a, [hMapAnims]
push af
xor a
ldh [hMapAnims], a
call LowVolume
...
There! Now you should have a functional and modular battle palette system which doesn't impact displaying correct palettes elsewhere.