Create Battle Palettes for Different Times of Day ( Environments, Weather Changes) - pret/pokecrystal GitHub Wiki

In gen 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.

Contents

  1. Create a New wram Label
  2. Load the Time into wBattleTimeOfDay
  3. Create PALRGB_NIGHT
  4. Edit LoadPalette_White_Col1_Col2_Black
  5. Mask Out R, G, and B and Edit Colors
  6. Bonus: Add Additional Checks for Indoors and Caves
  7. Apply Palette to entire Battle Screen
  8. Bonus: Add Additional Checks for the Stats Screen and Sunny Day

1. Create a New wram Label

Edit ram/wram.asm:

 wMapTimeOfDay:: db
+wBattleTimeOfDay:: db

-	ds 3
+       ds 2

We're going to store a numeric value in wBattleTimeOfDay, and load it at a later point to determine what palette we should load. It is important that this label be created in WRAM0, so it can be called in any bank.

2. Load the Time into wBattleTimeOfDay

Edit engine/battle/start_battle.asm:

 FindFirstAliveMonAndStartBattle:
+       ld a, [wTimeOfDay]
+	cp NITE_F
+       jr z, .nightpal
+
+.daypal
+       ld a, 0
+	ld [wBattleTimeOfDay], a
+       jr .timeofdaypalset
+
+.nightpal
+       ld a, 1
+	ld [wBattleTimeOfDay], a
+
+.timeofdaypalset
	xor a

Then edit engine/battle/core.asm:

 CleanUpBattleRAM:
	call BattleEnd_HandleRoamMons
	xor a
+	ld [wBattleTimeOfDay], 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. We also want to make sure the label is cleared at the end of the battle.

3. Create PALRGB_NIGHT

Create the desired battle background color palette in constants/gfx_constants.asm:

 DEF PALRGB_WHITE EQU palred 31 + palgreen 31 + palblue 31 ; $7fff
+DEF PALRGB_NIGHT EQU palred 15 + palgreen 15 + 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.

4. 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

+       ld a, [wBattleTimeOfDay]
+       and a
+	jr z, .day
+
+       ld a, LOW(PALRGB_NIGHT)
+	ld [de], a
+	inc de
+	ld a, HIGH(PALRGB_NIGHT)
+	ld [de], a
+	inc de
+
+       call NightColors
+	ld c, 2 * PAL_COLOR_SIZE
+
+	jr .black
+
+.day
	ld a, LOW(PALRGB_WHITE)
	ld [de], a
	inc de
	ld a, HIGH(PALRGB_WHITE)
	ld [de], a
	inc de

	ld c, 2 * PAL_COLOR_SIZE
.loop
	ld a, [hli]
	ld [de], a
	inc de
	dec c
	jr nz, .loop

+.black
	xor a

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 color modification. Now comes the tricky part; dissecting and modifying the middle colors.

5. Mask Out R, G, and B and Edit Colors

Edit engine/gfx/color.asm again to create NightColors: & NightColorSwap:

+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

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.

6. Bonus: Add Additional Checks for Indoors, Dungeons and Caves

Here we'll add individual checks for indoors and cave environments, and load in the same Day/Night palettes determined by [wBattleTimeOfDay] with 0 = Day and 1 = Night. This just adds an easier structure if you were to later add new unique battle palettes for caves and indoors. Edit engine/battle/start_battle.asm again:

 FindFirstAliveMonAndStartBattle:
+      call GetMapEnvironment
+      cp DUNGEON
+      jr z, .dungeonpal
+
+      call GetMapEnvironment
+      cp CAVE
+      jr z, .cavepal
+
+      call GetMapEnvironment
+      cp INDOOR
+      jr z, .indoorpal
+
+       ld a, [wTimeOfDay]
+      cp MORN_F
+      jr z, .mornpal 
+ 	
+       ld a, [wTimeOfDay]
+      cp DAY_F
+      jr z, .daypal  	
+	
+      ld a, [wTimeOfDay]
+      cp NITE_F
+       jr z, .nightpal 
+
+.mornpal
+      ld a, 0
+	ld [wBattleTimeOfDay], a
+      jr .timeofdaypalset
+
+.daypal
+      ld a, 0
+	ld [wBattleTimeOfDay], a
+      jr .timeofdaypalset

+.nightpal
+       ld a, 1
+	ld [wBattleTimeOfDay], a
+	jr .timeofdaypalset 
	   
+.dungeonpal
+      ld a, 0
+	ld [wBattleTimeOfDay], a
+      jr .timeofdaypalset 

+.indoorpal
+       ld a, 0
+	ld [wBattleTimeOfDay], a
+	jr .timeofdaypalset 
	
+.cavepal
+      ld a, 1
+      ld [wBattleTimeOfDay], a	

.timeofdaypalset 
 xor a
 ldh [hMapAnims], a

And that's it!

7. Apply Palette to entire Battle Screen

Now you have a night palette 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.

Alter the file - 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

There! Now the entire battle area will have the new palette applied with no impact to overworld palettes.

8. Bonus: Add Additional Checks for the Stats Screen and Sunny Day.

This step will fix the palette errors that occur on the Stats Screen when accessed through battle, in particular shifting between pages and Pokemon with the D-Pad, meaning that the battle palette will only affect the battle arena itself. NOTE: It's recommended to do Step 7. as a prelude to this step.

Again, we'll 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
	
+; Check whether Stat Pages are active, set them to day palette.

+; First, check whether in a battle, then jump to the Stats Page checks.
+    ld a, [wBattleMode]
+    cp 0
+    jr nz, .StatsFlags
+
+; NOTE: The Stats Pages use flag values 1, 2, and 3. In the case that you added a fourth stats page, it uses value 0. But if we check for page 0, a state that usually would be a zero value free to be overwritten elsewhere, an error occurs in which the incorrect battle palette (wBattleTimeOfDay) gets called in battle due to it being set to the day palette. Avoid checking for 0 (cp 0) here.
+
+.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
+; If weather is not Sunny, jump to Night Colors.
+	ld a, [wBattleWeather]
+	cp WEATHER_SUN
+	jr nz, .night1
+
+.night1
+; Call Night background colors
        ld a, LOW(PALRGB_NIGHT)
	ld [de], a
	inc de
	ld a, HIGH(PALRGB_NIGHT)
	ld [de], a
	inc de
+
+; Call Night sprite colors
    call NightColors
	ld c, 2 * PAL_COLOR_SIZE

	jr .black

.day
+; Call Day background colors
	ld a, LOW(PALRGB_WHITE)
	ld [de], a
	inc de
	ld a, HIGH(PALRGB_WHITE)
	ld [de], a
	inc de
+; Call Day 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

Now, we need to go edit wram.asm master/ram/wram.asm: Due to wStatsScreenFlags and wPackJumptableIndex sharing a UNION, and thus sharing the same initial address, entering and exiting the pack in battle can now interfere with your night palette. The fix is simple. Locate ; pack wPackJumptableIndex:: db wCurPocket:: db wPackUsedItem:: db and move it to just above it's UNION label.

wJumptableIndex::
wBattleTowerBattleEnded::
	db

+; pack
+wPackJumptableIndex:: db
+wCurPocket:: db
+wPackUsedItem:: db
+
UNION
; intro data
wIntroSceneFrameCounter:: db
wIntroSceneTimer:: db
...

This will result in wPackJumptableIndex and wStatsScreenFlags no longer sharing the same address, meaning the pack state won't interfere with your stat page palettes.

Now, when you return to the Battle Screen from the Stats Screen, we need to load the correct palette back into memory. We don't need to do complicated environment checks upon returning, since the battle palette has a lot of variables between the Map Environment, the Time Of Day and the Weather State.

Instead, we need to edit wram.asm again... master/ram/wram.asm:

Underneath wBattleTimeOfDay we create another label called wBattleTimeOfDayBackup.

wMapTimeOfDay:: db
wBattleTimeOfDay:: db
+wBattleTimeOfDayBackup:: db
-       ds 2
+	ds 1 ; (that is, subtract 1 from whatever value you had here, this presumes it was 2)

Then we need to hop on over to master/engine/pokemon/stats_screen.asm:

We find BattleStatsScreenInit:

BattleStatsScreenInit:
+; Load current Battle Palette into 'a', then load 'a' into [wBattleTimeOfDayBackup]. This ensures you have the correct palette to return to.
+    ld a, [wBattleTimeOfDay]
+	ld [wBattleTimeOfDayBackup], a
+
+ ; Now we load 0, the day palette into [wBattleTimeOfDay], ensuring that the stats screen keeps it's white background palette.
+	ld a, 0
+	ld [wBattleTimeOfDay], a
+
	ld a, [wLinkMode]
	cp LINK_MOBILE
	jr nz, StatsScreenInit

	ld a, [wBattleMode]
	and a
	jr z, StatsScreenInit
	jr _MobileStatsScreenInit

Then in the same file, we find StatsScreen_Exit:

 StatsScreen_Exit:
     ld hl, wJumptableIndex
     set 7, [hl]
+    jp StatsScreen_Exit2
	ret
+
+; This loads the backed up palette back into [wBattleTimeOfDay] in time for the Stats Screen to exit and the Battle Screen to refresh.
+ StatsScreen_Exit2:
+  ld a, [wBattleTimeOfDayBackup]
+  ld [wBattleTimeOfDay], a

Now, we make the necessary changes to the move Sunny Day and the other weather moves, to trigger the active change in palette. In master/engine/battle/move_effects/, find the files for Sunny Day, Sandstorm, Rain Dance, and if you've added the Hail weather condition, then that too.

The fix is simple. In each file, just insert one line of farcall _CGB_BattleColors before the animation call. The correct battle palette has already been chosen, due to the value of [wBattleWeather] being "WEATHER_SUN", calling back to the code we added in color.asm, this simply tells the battle screen to prepare those colours to be refreshed, which occurs during the animation sequence, for a reason we'll see in a moment.

BattleCommand_StartSun:
;	call ClearBGPalettes
;	call ClearTilemap
	ld a, WEATHER_SUN
	ld [wBattleWeather], a
	ld a, 5
	ld [wWeatherCount], a
+	farcall _CGB_BattleColors
	call AnimateCurrentMove
	ld hl, SunGotBrightText
	jp StdBattleTextbox

Adding it to Sandstorm, Rain Dance and Hail, makes sure that the battle palette is rechecked upon the activation of a new weather condition. If it is night time and the weather changes from Sunny Day to any of the others, the palette will return to it's night time state.

Now, what about when your weather condition wears off naturally after so many turns?

If we go to master/engine/battle/core.asm, and locate HandleBetweenTurnEffects, you can add another farcall _CGB_BattleColors just after call HandleWeather.

HandleBetweenTurnEffects:
	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

Now, it turns out that farcall _CGB_BattleColors doesn't actually request the attrmap/palettes update, only prepares it. That's why the palette wouldn't update when the weather faded. You have to actually make sure to signal the update by setting hCGBPalUpdate to 1. Perhaps this signal could be added to earlier steps to streamline them, but this code works currently.

So, we stay in master/engine/battle/core.asm, and go to 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

Done! Now the palettes correctly change in the myriad of instances of the following:

Cycling through the Pack Pages and returning to battle.

Cycling through the Stats Screen states.

Exiting Stats Screen and returning to battle.

In an environmental change (CAVE, DUNGEON, ROUTE, etc.).

Time Of Day effects.

Using weather effects.

Changing weather effects.

When weather effects end naturally.