Skip to content

Make evening the fourth time of day

Idain edited this page Nov 2, 2021 · 18 revisions

Gen 2 used the Game Boy Color's real-time clock to implement a time of day system:

  • Morning: 6 hours, 4:00 AM–9:59 AM
  • Day: 8 hours, 10:00 AM–5:59 PM
  • Night: 10 hours, 6:00 PM–3:59 AM

The whole feature is restricted to storing times of day in two bits. That's enough to store four values. The fourth is not a real time; it's used to represent pitch-dark areas where you need Flash (since a major effect of the time of day is to change the map colors).

This tutorial will represent Flash darkness in a different way, and use the freed fourth time value for evening. (Gen 5 was the first to have evening as a fourth time of day; it also varied the time boundaries by season.)

It depends on the DARKNESS_PALSET constant added to pokecrystal on August 11, 2020, so if your copy of pokecrystal is older than that, apply commit ed3e70b before following this tutorial.

(The code for this feature was adapted from Pokémon Polished Crystal.)

Contents

  1. Redefine the boundaries for times of day
  2. Handle the new time boundaries in the code
  3. Define palette colors for evening
  4. Change how Flash darkness works
  5. Load the right colors for the time of day
  6. Reuse night wild encounters for evening
  7. Display evening when setting the time
  8. Handle a caught time of evening for the Poké Seer
  9. Buena's Password plays in the evening
  10. Eevee evolves into Umbreon in the evening
  11. Johto wild battles use night music in the evening
  12. The player's Mom watches TV in the evening
  13. Prof. Oak's intro changes in the evening
  14. Pokémon Center nurses greet you in the evening
  15. Phone callers reuse night text in the evening

1. Redefine the boundaries for times of day

The new times of day will be:

  • Morning: 6 hours, 4:00 AM–9:59 AM
  • Day: 7 hours, 10:00 AM–4:59 PM
  • Evening: 3 hours, 5:00 PM–7:59 PM
  • Night: 8 hours, 8:00 PM–3:59 AM

Edit constants/wram_constants.asm:

 ; wTimeOfDay::
 	const_def
 	const MORN_F     ; 0
 	const DAY_F      ; 1
 	const NITE_F     ; 2
-	const DARKNESS_F ; 3
+	const EVE_F      ; 3
 NUM_DAYTIMES EQU const_value

 MORN     EQU 1 << MORN_F
 DAY      EQU 1 << DAY_F
 NITE     EQU 1 << NITE_F
-DARKNESS EQU 1 << DARKNESS_F
+EVE      EQU 1 << EVE_F

-ANYTIME EQU MORN | DAY | NITE
+ANYTIME EQU MORN | DAY | EVE | NITE

And edit constants/misc_constants.asm:

 ; time of day boundaries
 MORN_HOUR EQU 4  ; 4 AM
 DAY_HOUR  EQU 10 ; 10 AM
-NITE_HOUR EQU 18 ; 6 PM
+EVE_HOUR  EQU 17 ; 5 PM
+NITE_HOUR EQU 20 ; 8 PM
 NOON_HOUR EQU 12 ; 12 PM
 MAX_HOUR  EQU 24 ; 12 AM

2. Handle the new time boundaries in the code

Edit engine/rtc/rtc.asm:

 TimesOfDay:
 ; hours for the time of day
-; 0400-0959 morn | 1000-1759 day | 1800-0359 nite
+; 0400-0959 morn | 1000-1659 day | 1700-1959 eve | 2000-0359 nite
 	db MORN_HOUR, NITE_F
 	db DAY_HOUR,  MORN_F
-	db NITE_HOUR, DAY_F
+	db EVE_HOUR,  DAY_F
+	db NITE_HOUR, EVE_F
 	db MAX_HOUR,  NITE_F
 	db -1, MORN_F

This will make the GetTimeOfDay routine return EVE_F for evening hours.

Edit engine/events/checktime.asm:

 .TimeOfDayTable:
 	db MORN_F, MORN
 	db DAY_F,  DAY
-	db NITE_F, NITE
+	db EVE_F,  EVE
 	db NITE_F, NITE
 	db -1

This will make the checktime script command work for EVE.

Edit home/map_objects.asm:

 .TimesOfDay:
 ; entries correspond to TimeOfDay values
 	db MORN
 	db DAY
 	db NITE
+	db EVE

This will make NPCs' object_events work with the EVE value to only appear at certain times of day.

3. Define palette colors for evening

The "typical" colors are during the day; morning has a yellow tint and night has a dark blue tint. Evening will have an orange tint. The roofs in different cities have unique colors that reuse the same ones for morning and day, but we'll add a third set of evening roof colors.

Edit gfx/tilesets/bg_tiles.pal:

-; dark
-	RGB 01,01,02, 00,00,00, 00,00,00, 00,00,00 ; gray
-	RGB 01,01,02, 00,00,00, 00,00,00, 00,00,00 ; red
-	RGB 01,01,02, 00,00,00, 00,00,00, 00,00,00 ; green
-	RGB 01,01,02, 00,00,00, 00,00,00, 00,00,00 ; water
-	RGB 30,30,11, 00,00,00, 00,00,00, 00,00,00 ; yellow
-	RGB 01,01,02, 00,00,00, 00,00,00, 00,00,00 ; brown
-	RGB 01,01,02, 00,00,00, 00,00,00, 00,00,00 ; roof
-	RGB 31,31,16, 31,31,16, 14,09,00, 00,00,00 ; text
+; eve
+	RGB 31,21,14, 18,16,16, 11,10,10, 06,05,05 ; gray
+	RGB 31,21,14, 25,14,18, 24,08,05, 06,05,05 ; red
+	RGB 19,23,08, 10,19,01, 04,10,00, 06,05,05 ; green
+	RGB 31,21,14, 07,09,23, 01,03,23, 06,05,05 ; water
+	RGB 31,31,07, 27,21,10, 25,12,01, 06,05,05 ; yellow
+	RGB 31,21,14, 21,13,05, 16,09,01, 06,05,05 ; brown
+	RGB 31,21,14, 13,23,23, 04,13,23, 06,05,05 ; roof
+	RGB 31,31,16, 31,31,16, 14,09,00, 00,00,00 ; text
 ; overworld water
-	RGB 23,23,31, 18,19,31, 13,12,31, 07,07,07 ; morn/day
+	RGB 23,23,31, 18,19,31, 13,12,31, 07,07,07 ; morn
+	RGB 23,23,31, 18,19,31, 13,12,31, 07,07,07 ; day
 	RGB 15,13,27, 10,09,20, 04,03,18, 00,00,00 ; nite
+	RGB 31,21,14, 16,14,23, 11,09,23, 06,05,05 ; eve

And create gfx/tilesets/darkness.pal:

+	RGB 01,01,02, 00,00,00, 00,00,00, 00,00,00 ; gray
+	RGB 01,01,02, 00,00,00, 00,00,00, 00,00,00 ; red
+	RGB 01,01,02, 00,00,00, 00,00,00, 00,00,00 ; green
+	RGB 01,01,02, 00,00,00, 00,00,00, 00,00,00 ; water
+	RGB 30,30,11, 00,00,00, 00,00,00, 00,00,00 ; yellow
+	RGB 01,01,02, 00,00,00, 00,00,00, 00,00,00 ; brown
+	RGB 01,01,02, 00,00,00, 00,00,00, 00,00,00 ; roof
+	RGB 31,31,16, 31,31,16, 14,09,00, 00,00,00 ; text

Then edit gfx/overworld/npc_sprites.pal:

-; dark
-	RGB 01,01,02, 31,19,10, 31,07,01, 00,00,00 ; red
-	RGB 01,01,02, 31,19,10, 10,09,31, 00,00,00 ; blue
-	RGB 01,01,02, 31,19,10, 07,23,03, 00,00,00 ; green
-	RGB 01,01,02, 31,19,10, 15,10,03, 00,00,00 ; brown
-	RGB 01,01,02, 31,19,10, 30,10,06, 00,00,00 ; pink
-	RGB 31,31,31, 31,31,31, 13,13,13, 00,00,00 ; silver
-	RGB 01,01,02, 00,00,00, 00,00,00, 00,00,00 ; tree
-	RGB 01,01,02, 00,00,00, 00,00,00, 00,00,00 ; rock
+; eve
+	RGB 31,21,14, 31,19,10, 31,07,01, 00,00,00 ; red
+	RGB 31,21,14, 31,19,10, 10,09,31, 00,00,00 ; blue
+	RGB 31,21,14, 31,19,10, 07,23,03, 00,00,00 ; green
+	RGB 31,21,14, 31,19,10, 15,10,03, 00,00,00 ; brown
+	RGB 31,21,14, 31,19,10, 30,10,06, 00,00,00 ; pink
+	RGB 31,31,31, 31,31,31, 13,13,13, 00,00,00 ; silver
+	RGB 19,23,08, 10,19,01, 04,10,00, 06,05,05 ; tree
+	RGB 31,21,14, 21,13,05, 16,09,01, 06,05,05 ; rock

And create gfx/overworld/npc_sprites_darkness.pal:

+	RGB 01,01,02, 31,19,10, 31,07,01, 00,00,00 ; red
+	RGB 01,01,02, 31,19,10, 10,09,31, 00,00,00 ; blue
+	RGB 01,01,02, 31,19,10, 07,23,03, 00,00,00 ; green
+	RGB 01,01,02, 31,19,10, 15,10,03, 00,00,00 ; brown
+	RGB 01,01,02, 31,19,10, 30,10,06, 00,00,00 ; pink
+	RGB 31,31,31, 31,31,31, 13,13,13, 00,00,00 ; silver
+	RGB 01,01,02, 00,00,00, 00,00,00, 00,00,00 ; tree
+	RGB 01,01,02, 00,00,00, 00,00,00, 00,00,00 ; rock

Edit gfx/tilesets/roofs.pal:

 ; group 0 (unused)
 	RGB 21,21,21, 11,11,11 ; morn/day
 	RGB 21,21,21, 11,11,11 ; nite
+	RGB 18,16,16, 09,08,08 ; eve

 ; group 1 (Olivine)
 	RGB 14,17,31, 07,11,15 ; morn/day
 	RGB 09,09,17, 05,07,13 ; nite
+	RGB 12,13,23, 06,08,11 ; eve

 ; group 2 (Mahogany)
 	RGB 12,19,00, 06,10,00 ; morn/day
 	RGB 06,09,07, 04,05,06 ; nite
+	RGB 10,14,00, 05,08,00 ; eve

 ; group 3 (dungeons)
 	RGB 21,21,21, 11,11,11 ; morn/day
 	RGB 21,21,21, 17,08,07 ; nite
+	RGB 18,16,16, 09,08,08 ; eve

 ; group 4 (Ecruteak)
 	RGB 31,19,00, 27,10,05 ; morn/day
 	RGB 15,07,02, 11,04,02 ; nite
+	RGB 25,14,00, 22,08,03 ; eve

 ; group 5 (Blackthorn)
 	RGB 11,10,16, 05,06,07 ; morn/day
 	RGB 03,04,08, 00,00,00 ; nite
+	RGB 09,08,12, 04,04,05 ; eve

 ; group 6 (Cinnabar)
 	RGB 31,10,00, 18,06,00 ; morn/day
 	RGB 18,05,09, 17,08,07 ; nite
+	RGB 25,08,00, 15,04,00 ; eve

 ; group 7 (Cerulean)
 	RGB 17,27,31, 05,15,31 ; morn/day
 	RGB 07,08,22, 07,07,16 ; nite
+	RGB 14,21,23, 04,11,23 ; eve

 ; group 8 (Azalea)
 	RGB 22,20,10, 17,14,03 ; morn/day
 	RGB 11,11,05, 10,09,07 ; nite
+	RGB 19,15,08, 14,10,01 ; eve

 ; group 9 (Lake of Rage)
 	RGB 31,08,04, 09,09,08 ; morn/day
 	RGB 18,05,09, 09,09,08 ; nite
+	RGB 25,06,02, 08,07,06 ; eve

 ; group 10 (Violet)
 	RGB 24,14,31, 13,07,21 ; morn/day
 	RGB 12,03,18, 09,03,15 ; nite
+	RGB 21,10,23, 11,05,16 ; eve

 ; group 11 (Goldenrod)
 	RGB 25,25,00, 20,17,08 ; morn/day
 	RGB 12,12,00, 10,09,05 ; nite
+	RGB 21,19,00, 17,13,06 ; eve

 ; group 12 (Vermilion)
 	RGB 27,23,01, 23,11,00 ; morn/day
 	RGB 15,11,01, 11,10,01 ; nite
+	RGB 22,18,01, 20,08,00 ; eve

 ; group 13 (Pallet)
 	RGB 27,28,31, 17,19,22 ; morn/day
 	RGB 14,14,18, 10,09,13 ; nite
+	RGB 25,12,10, 21,08,06 ; eve

 ; group 14 (Pewter)
 	RGB 19,19,16, 10,12,15 ; morn/day
 	RGB 09,09,11, 04,05,07 ; nite
+	RGB 16,14,12, 08,09,11 ; eve

 ; group 15 (Mount Moon Square)
 	RGB 14,17,31, 07,11,15 ; morn/day
 	RGB 09,13,19, 07,07,16 ; nite
+	RGB 12,13,23, 06,08,11 ; eve

 ; group 16 (Indigo)
 	RGB 21,21,21, 13,13,13 ; morn/day
 	RGB 11,11,19, 07,07,12 ; nite
+	RGB 20,19,19, 11,10,10 ; eve

 ; group 17 (Fuchsia)
 	RGB 31,18,29, 17,13,20 ; morn/day
 	RGB 14,06,12, 11,03,10 ; nite
+	RGB 25,13,21, 14,10,15 ; eve

 ; group 18 (Lavender)
 	RGB 23,15,31, 16,05,31 ; morn/day
 	RGB 12,07,17, 08,06,10 ; nite
+	RGB 20,11,23, 13,03,23 ; eve

 ; group 19 (Silver Cave)
 	RGB 21,21,25, 16,16,16 ; morn/day
 	RGB 13,13,13, 07,07,07 ; nite
+	RGB 08,18,22, 08,10,19 ; eve

 ; group 20 (Cable Club)
 	RGB 21,21,21, 11,11,11 ; morn/day
 	RGB 21,21,21, 11,11,11 ; nite
+	RGB 20,19,19, 10,09,09 ; eve

 ; group 21 (Celadon)
 	RGB 19,31,15, 31,22,02 ; morn/day
 	RGB 12,13,09, 09,12,03 ; nite
+	RGB 11,19,02, 04,14,04 ; eve

 ; group 22 (Cianwood)
 	RGB 15,10,31, 07,05,15 ; morn/day
 	RGB 06,05,17, 02,02,08 ; nite
+	RGB 13,08,23, 06,03,11 ; eve

 ; group 23 (Viridian)
 	RGB 21,31,07, 13,25,04 ; morn/day
 	RGB 09,14,08, 06,10,04 ; nite
+	RGB 18,24,05, 11,19,02 ; eve

 ; group 24 (New Bark)
 	RGB 20,31,14, 11,23,05 ; morn/day
 	RGB 09,13,08, 06,09,04 ; nite
+	RGB 17,24,10, 09,18,03 ; eve

 ; group 25 (Saffron)
 	RGB 31,26,00, 31,15,00 ; morn/day
 	RGB 13,13,01, 08,08,01 ; nite
+	RGB 25,20,00, 25,11,00 ; eve

 ; group 26 (Cherrygrove)
 	RGB 31,14,28, 31,05,21 ; morn/day
 	RGB 14,07,17, 13,00,08 ; nite
+	RGB 25,10,20, 25,03,16 ; eve

And finally, edit engine/gfx/color.asm:

 RoofPals:
-	table_width PAL_COLOR_SIZE * 2 * 2, RoofPals
+	table_width PAL_COLOR_SIZE * 3 * 2, RoofPals
 INCLUDE "gfx/tilesets/roofs.pal"
 	assert_table_length NUM_MAP_GROUPS + 1

We'll write code to handle these new colors soon; but first let's fix Flash.

4. Change how Flash darkness works

We're replacing darkness constants and data with evening ones, so we'll need to handle darkness differently.

Edit constants/map_data_constants.asm:

 ; map palettes (wEnvironment)
 	const_def
 	const PALETTE_AUTO
 	const PALETTE_DAY
 	const PALETTE_NITE
 	const PALETTE_MORN
-	const PALETTE_DARK
+	const PALETTE_EVE
 NUM_MAP_PALETTES EQU const_value
+
+IN_DARKNESS_F EQU 3
+IN_DARKNESS EQU 1 << IN_DARKNESS_F ; masked with a PALETTE_* constant

Then edit data/maps/maps.asm: replace all 13 uses of PALETTE_DARK with PALETTE_NITE | IN_DARKNESS. The maps affected are:

  • All 8 WhirlIsland* maps
  • SilverCaveRoom1
  • DarkCaveVioletEntrance and DarkCaveBlackthornEntrance
  • RockTunnel1F and RockTunnelB1F

Next, edit constants/wram_constants.asm again:

; wTimeOfDayPalset::
+; Must be different from any in ReplaceTimeOfDayPals.BrightnessLevels
-DARKNESS_PALSET EQU (DARKNESS_F << 6) | (DARKNESS_F << 4) | (DARKNESS_F << 2) | DARKNESS_F
+DARKNESS_PALSET EQU (MORN_F << 6) | (DAY_F << 4) | (EVE_F << 2) | NITE_F

Then edit engine/tilesets/timeofday_pals.asm:

 ReplaceTimeOfDayPals:
-	ld hl, .BrightnessLevels
 	ld a, [wMapTimeOfDay]
-	cp PALETTE_DARK
-	jr z, .NeedsFlash
+	bit IN_DARKNESS_F, a
+	jr z, .not_dark
+	ld a, [wStatusFlags]
+	bit STATUSFLAGS_FLASH_F, a
+	jr nz, .not_dark
+	ld a, DARKNESS_PALSET
+	jr .done
+
+.not_dark:
+	ld hl, .BrightnessLevels
+	ld a, [wMapTimeOfDay]
 	maskbits NUM_MAP_PALETTES
 	add l
 	ld l, a
 	ld a, 0
 	adc h
 	ld h, a
 	ld a, [hl]
+.done:
 	ld [wTimeOfDayPalset], a
 	ret
-
-.NeedsFlash:
-	ld a, [wStatusFlags]
-	bit STATUSFLAGS_FLASH_F, a
-	jr nz, .UsedFlash
-	ld a, DARKNESS_PALSET
-	ld [wTimeOfDayPalset], a
-	ret
-
-.UsedFlash:
-	ld a, (NITE_F << 6) | (NITE_F << 4) | (NITE_F << 2) | NITE_F
-	ld [wTimeOfDayPalset], a
-	ret

 .BrightnessLevels:
 ; actual palettes used when time is
-; DARKNESS_F, NITE_F, DAY_F, MORN_F
+; EVE_F, NITE_F, DAY_F, MORN_F
-	dc DARKNESS_F, NITE_F,     DAY_F,      MORN_F     ; PALETTE_AUTO
+	dc EVE_F,      NITE_F,     DAY_F,      MORN_F     ; PALETTE_AUTO
	dc DAY_F,      DAY_F,      DAY_F,      DAY_F      ; PALETTE_DAY
	dc NITE_F,     NITE_F,     NITE_F,     NITE_F     ; PALETTE_NITE
	dc MORN_F,     MORN_F,     MORN_F,     MORN_F     ; PALETTE_MORN
-	dc DARKNESS_F, DARKNESS_F, DARKNESS_F, DARKNESS_F ; PALETTE_DARK
+	dc EVE_F,      EVE_F,      EVE_F,      EVE_F      ; PALETTE_EVE
-	dc DARKNESS_F, NITE_F,     DAY_F,      MORN_F
-	dc DARKNESS_F, NITE_F,     DAY_F,      MORN_F
-	dc DARKNESS_F, NITE_F,     DAY_F,      MORN_F

 GetTimePalette:
 	jumptable .TimePalettes, wTimeOfDay

 .TimePalettes:
 	dw .MorningPalette  ; MORN_F
 	dw .DayPalette      ; DAY_F
 	dw .NitePalette     ; NITE_F
-	dw .DarknessPalette ; DARKNESS_F
+	dw .EveningPalette  ; EVE_F

 .MorningPalette:
 	ld a, [wTimeOfDayPalset]
 	and %00000011
 	ret

 .DayPalette:
 	ld a, [wTimeOfDayPalset]
 	and %00001100
 	srl a
 	srl a
 	ret

 .NitePalette:
 	ld a, [wTimeOfDayPalset]
 	and %00110000
 	swap a
 	ret

-.DarknessPalette:
+.EveningPalette:
 	ld a, [wTimeOfDayPalset]
 	and %11000000
 	rlca
 	rlca
 	ret

So here's how the above code works. We have four times of day, and four brightness levels, but they have to be related by the map palette according to the data in ReplaceTimeOfDayPals.BrightnessLevels. For example, if the current map palette is PALETTE_AUTO and the current time is NITE_F, then the NITE_F column in the PALETTE_AUTO has the value NITE_F. (These relations are all pretty obvious—PALETTE_AUTO pairs every time of day with itself, and the other PALETTE_s always use the same time of day—so the whole system could probably be simplified; but it's easier to stick with what already works.)

Anyway, it used to check for PALETTE_DARK, and use a palette set for that which had all DARKNESS_F values (DARKNESS_PALSET). Instead, we check for the IN_DARKNESS mask on the PALETTE_ value (IN_DARKNESS_F is bit 3, which doesn't collide with the palette bits 0–2), and use a palette set that's a sentinel value (the actual value doesn't matter, it just has to not be a real entry in the .BrightnessLevels table). Also, when Flash is used, we're not hard-coding an all-NITE_F palette set; instead, it will use whatever is in the map. So if a map's palette is PALETTE_DAY | IN_DARKNESS, using Flash will make it have the day palette.

There are a couple more pieces of code that check for Flash darkness, so let's fix them too.

Edit engine/battle/battle_transition.asm:

 	ld hl, .pals
-	ld a, [wTimeOfDayPal]
-	maskbits NUM_DAYTIMES
-	cp DARKNESS_F
+	ld a, [wTimeOfDayPalset]
+	cp DARKNESS_PALSET
 	jr nz, .not_dark
 	ld hl, .darkpals
 .not_dark

And edit engine/events/poisonstep_pals.asm:

-	ld a, [wTimeOfDayPal]
-	maskbits NUM_DAYTIMES
-	cp DARKNESS_F
+	ld a, [wTimeOfDayPalset]
+	cp DARKNESS_PALSET
 	ld a, %00000000
 	jr z, .convert_pals
 	ld a, %10101010

 .convert_pals

5. Load the right colors for the time of day

Loading the right colors from bg_tiles.pal is simple. Just edit data/maps/environment_colors.asm:

-; Valid indices: $00 - $29 (see gfx/tilesets/bg_tiles.pal)
+; Valid indices: $00 - $2b (see gfx/tilesets/bg_tiles.pal)
 .OutdoorColors:
 	db $00, $01, $02, $28, $04, $05, $06, $07 ; morn
-	db $08, $09, $0a, $28, $0c, $0d, $0e, $0f ; day
-	db $10, $11, $12, $29, $14, $15, $16, $17 ; nite
-	db $18, $19, $1a, $1b, $1c, $1d, $1e, $1f ; dark
+	db $08, $09, $0a, $29, $0c, $0d, $0e, $0f ; day
+	db $10, $11, $12, $2a, $14, $15, $16, $17 ; nite
+	db $18, $19, $1a, $2b, $1c, $1d, $1e, $1f ; eve

 .IndoorColors:
 	db $20, $21, $22, $23, $24, $25, $26, $07 ; morn
 	db $20, $21, $22, $23, $24, $25, $26, $07 ; day
 	db $10, $11, $12, $13, $14, $15, $16, $07 ; nite
-	db $18, $19, $1a, $1b, $1c, $1d, $1e, $07 ; dark
+	db $18, $19, $1a, $1b, $1c, $1d, $1e, $07 ; eve

 .DungeonColors:
 	db $00, $01, $02, $03, $04, $05, $06, $07 ; morn
 	db $08, $09, $0a, $0b, $0c, $0d, $0e, $0f ; day
 	db $10, $11, $12, $13, $14, $15, $16, $17 ; nite
-	db $18, $19, $1a, $1b, $1c, $1d, $1e, $1f ; dark
+	db $18, $19, $1a, $1b, $1c, $1d, $1e, $1f ; eve

 .Env5Colors:
 	db $00, $01, $02, $03, $04, $05, $06, $07 ; morn
 	db $08, $09, $0a, $0b, $0c, $0d, $0e, $0f ; day
 	db $10, $11, $12, $13, $14, $15, $16, $17 ; nite
-	db $18, $19, $1a, $1b, $1c, $1d, $1e, $1f ; dark
+	db $18, $19, $1a, $1b, $1c, $1d, $1e, $1f ; eve

This was mostly a change to comments; the only data changed was in .OutdoorColors to make the WATER palette use the overworld water colors. (Maps with the DUNGEON or ENVIRONMENT_5 environment do not use the overworld water colors; they have a darker, more saturated blue.)

To load the darkness.pal colors for Flash maps, we'll treat it like any other special palette (Ice Path, Battle Tower, etc). Edit engine/tilesets/tileset_palettes.asm:

 LoadSpecialMapPalette:
+	call GetMapTimeOfDay
+	bit IN_DARKNESS_F, a
+	jr z, .not_dark
+	ld a, [wStatusFlags]
+	bit STATUSFLAGS_FLASH_F, a
+	jr z, .darkness
+
+.not_dark
 	ld a, [wMapTileset]
 	cp TILESET_POKECOM_CENTER
 	jr z, .pokecom_2f
 	cp TILESET_BATTLE_TOWER_INSIDE
 	jr z, .battle_tower_inside
 	cp TILESET_ICE_PATH
 	jr z, .ice_path
 	cp TILESET_HOUSE
 	jr z, .house
 	cp TILESET_RADIO_TOWER
 	jr z, .radio_tower
 	cp TILESET_MANSION
 	jr z, .mansion_mobile
 	jr .do_nothing
+
+.darkness
+	call LoadDarknessPalette
+	scf
+	ret

 ...

 .do_nothing
 	and a
 	ret
+
+LoadDarknessPalette:
+	ld a, BANK(wBGPals1)
+	ld de, wBGPals1
+	ld hl, DarknessPalette
+	ld bc, 8 palettes
+	jp FarCopyWRAM
+
+DarknessPalette:
+INCLUDE "gfx/tilesets/darkness.pal"

As for NPCs, the npc_sprites.pal will already get loaded correctly, but we need to handle npc_sprites_darkness.pal. Add this to the end of engine/tilesets/tileset_palettes.asm:

+LoadSpecialNPCPalette:
+	call GetMapTimeOfDay
+	bit IN_DARKNESS_F, a
+	jr z, .do_nothing
+	ld a, [wStatusFlags]
+	bit STATUSFLAGS_FLASH_F, a
+	jr nz, .do_nothing
+
+;darkness
+	call LoadNPCDarknessPalette
+	scf
+	ret
+
+.do_nothing
+	and a
+	ret
+
+LoadNPCDarknessPalette:
+	ld a, BANK(wOBPals1)
+	ld de, wOBPals1
+	ld hl, NPCDarknessPalette
+	ld bc, 8 palettes
+	jp FarCopyWRAM
+
+NPCDarknessPalette:
+INCLUDE "gfx/overworld/npc_sprites_darkness.pal"

We'll just have to call LoadSpecialNPCPalette in the right place to load the npc_sprites_darkness.pal when necessary. We also still need to handle the new roofs.pal evening colors. Both of those belong in the same place. Edit engine/gfx/color.asm:

 LoadMapPals:
 	farcall LoadSpecialMapPalette
 	jr c, .got_pals

 	...

 .got_pals
 	ld a, [wTimeOfDayPal]
 	maskbits NUM_DAYTIMES
 	ld bc, 8 palettes
 	ld hl, MapObjectPals
 	call AddNTimes
 	ld de, wOBPals1
 	ld bc, 8 palettes
 	ld a, BANK(wOBPals1)
 	call FarCopyWRAM

+	farcall LoadSpecialNPCPalette

 	ld a, [wEnvironment]
 	cp TOWN
 	jr z, .outside
 	cp ROUTE
 	ret nz
 .outside
 	ld a, [wMapGroup]
-	ld l, a
-	ld h, 0
-	add hl, hl
-	add hl, hl
-	add hl, hl
-	ld de, RoofPals
+	add a
+	add a
+	ld e, a
+	ld d, 0
+	ld hl, RoofPals
+	add hl, de
+	add hl, de
 	add hl, de
 	ld a, [wTimeOfDayPal]
 	maskbits NUM_DAYTIMES
 	cp NITE_F
+	ld de, 4
+	jr z, .nite
 	jr c, .morn_day
-rept 4
-	inc hl
-endr
+; eve
+	add hl, de
+.nite
+	add hl, de
 .morn_day
 	...

6. Reuse night wild encounters for evening

We could define unique evening wild encounters, but defining four sets of wild data for every map would take up extra space and be mostly redundant. For now evening will have the same encounters as night.

Edit wram.asm:

-	ds 2
+	ds 1

 wMornEncounterRate::  db
 wDayEncounterRate::   db
 wNiteEncounterRate::  db
+wEveEncounterRate::   db
 wWaterEncounterRate:: db

Then edit engine/overworld/wildmons.asm:

 LoadWildMonData:
 	call _GrassWildmonLookup
 	jr c, .copy
 	ld hl, wMornEncounterRate
 	xor a
 	ld [hli], a
 	ld [hli], a
+	ld [hli], a
 	ld [hl], a
 	jr .done_copy

 .copy
 	inc hl
 	inc hl
 	ld de, wMornEncounterRate
 	ld bc, 3
 	call CopyBytes
+	ld a, [wNiteEncounterRate]
+	ld [wEveEncounterRate], a
 .done_copy
 	call _WaterWildmonLookup
 	ld a, 0
 	jr nc, .no_copy
 	inc hl
 	inc hl
 	ld a, [hl]
 .no_copy
 	ld [wWaterEncounterRate], a
 	ret
+
+GetTimeOfDayNotEve:
+	ld a, [wTimeOfDay]
+	cp EVE_F
+	ret nz
+	ld a, NITE_F ; ld a, DAY_F to make evening use day encounters
+	ret
 ChooseWildEncounter:
 	...

 	inc hl
 	inc hl
 	inc hl
 	call CheckOnWater
 	ld de, WaterMonProbTable
 	jr z, .watermon
 	inc hl
 	inc hl
-	ld a, [wTimeOfDay]
+	call GetTimeOfDayNotEve
 	ld bc, NUM_GRASSMON * 2
 	call AddNTimes
 	ld de, GrassMonProbTable

 .watermon
 	...
 RandomUnseenWildMon:
 	...

 .GetGrassmon:
 	push hl
 	ld bc, 5 + 4 * 2 ; Location of the level of the 5th wild Pokemon in that map
 	add hl, bc
-	ld a, [wTimeOfDay]
+	call GetTimeOfDayNotEve
 	ld bc, NUM_GRASSMON * 2
 	call AddNTimes
 	...
 RandomPhoneWildMon:
 	...

 .ok
 	ld bc, 5 + 0 * 2
 	add hl, bc
-	ld a, [wTimeOfDay]
+	call GetTimeOfDayNotEve
 	inc a
 	ld bc, NUM_GRASSMON * 2

Edit engine/pokegear/radio.asm:

 OaksPKMNTalk4:
 ; Choose a random route, and a random Pokemon from that route.
 	...

 	; Point hl to the list of morning Pokémon., skipping percentages
 rept 4
 	inc hl
 endr
 	; Generate a number, either 0, 1, or 2, to choose a time of day.
+	; Can't pick 3 since evening does not have wild data.
 .loop2
 	call Random
 	maskbits NUM_DAYTIMES
-	cp DARKNESS_F
+	cp EVE_F
 	jr z, .loop2

And edit maps/Route29.asm:

 Route29CooltrainerMScript:
 	faceplayer
 	opentext
 	checktime DAY
 	iftrue .day_morn
-	checktime NITE
+	checktime EVE | NITE
 	iftrue .nite
 .day_morn
 	writetext Route29CooltrainerMText_WaitingForNight
 	waitbutton
 	closetext
 	end

 .nite
 	writetext Route29CooltrainerMText_WaitingForMorning
 	waitbutton
 	closetext
 	end
 Route29CooltrainerMText_WaitingForNight:
 	text "I'm waiting for"
 	line "#MON that"

-	para "appear only at"
-	line "night."
+	para "appear only in"
+	line "the evening or"
+	cont "at night."
 	done

7. Display evening when setting the time

Edit engine/rtc/timeset.asm:

 GetTimeOfDayString:
 	ld a, c
 	cp MORN_HOUR
 	jr c, .nite
 	cp DAY_HOUR
 	jr c, .morn
-	cp NITE_HOUR
-	jr c, .day
+	cp EVE_HOUR
+	jr c, .day
+	cp NITE_HOUR
+	jr c, .eve
 .nite
 	ld de, .nite_string
 	ret
 .morn
 	ld de, .morn_string
 	ret
 .day
 	ld de, .day_string
 	ret
+.eve
+	ld de, .eve_string
+	ret

 .nite_string: db "NITE@"
 .morn_string: db "MORN@"
 .day_string:  db "DAY@"
+.eve_string:  db "EVE@"

The time-setting textbox is designed to fit a four-character string, so you could say "DUSK" instead of "EVE", but not "EVENING" (unless you resize the textbox). (I picked "EVE" as a pair with "MORN", since "DUSK" would go more with "DAWN".)

8. Handle a caught time of evening for the Poké Seer

In Crystal, Pokémon record the time, location, level, and OT gender when they're caught. The Poké Seer in Cianwood City can tell you this information for any Pokémon. (Although since the time and level are packed into one byte, the two bits for time leave only six bits for level, which can't accurately report caught levels above 63—probably the reason why Ho-Oh and Lugia are caught at level 60, not 70.)

Edit engine/pokemon/caught_data.asm:

 SetCaughtData:
 	ld a, [wPartyCount]
 	dec a
 	ld hl, wPartyMon1CaughtLevel
 	call GetPartyLocation
 SetBoxmonOrEggmonCaughtData:
 	ld a, [wTimeOfDay]
 	inc a
 	rrca
 	rrca
+	and CAUGHT_TIME_MASK
 	ld b, a
 	ld a, [wCurPartyLevel]
 	or b
 	ld [hli], a
 	...

And edit engine/events/poke_seer.asm:

 GetCaughtTime:
 	ld a, [wSeerCaughtData]
 	and CAUGHT_TIME_MASK
-	jr z, .none
-
 	rlca
 	rlca
 	dec a
+	maskbits NUM_DAYTIMES
 	ld hl, .times
 	call GetNthString
 	ld d, h
 	ld e, l
 	ld hl, wSeerTimeOfDay
 	call CopyName2
 	and a
 	ret
-
-.none
-	ld de, wSeerTimeOfDay
-	call UnknownCaughtData
-	ret

 .times
 	db "Morning@"
 	db "Day@"
 	db "Night@"
+	db "Evening@"

The constants MORN_F, DAY_F, and NITE_F are 0, 1, and 2; originally they get incremented to 1, 2, and 3 when stored as caught data, with 0 representing an unknown time (i.e. a Pokémon without caught data, traded from Gold, Silver, or Gen 1). Now that EVE_F is 3, we use modular arithmetic with bitmasking so that 3 incremented is 0 instead of 4, and 0 decremented is 3 instead of −1.

9. Buena's Password plays in the evening

Edit engine/pokegear/radio.asm again:

 BuenasPasswordCheckTime:
 	call UpdateTime
 	ldh a, [hHours]
-	cp NITE_HOUR
+	cp EVE_HOUR
 	ret

And edit maps/RadioTower2F.asm:

Buena:
	faceplayer
	opentext
	checkflag ENGINE_ROCKETS_IN_RADIO_TOWER
	iftrue .MidRocketTakeover
	checkevent EVENT_MET_BUENA
	iffalse .Introduction
	checkflag ENGINE_BUENAS_PASSWORD_2
	iftrue .PlayedAlready
	readvar VAR_HOUR
-	ifless NITE_HOUR, .TooEarly
+	ifless EVE_HOUR, .TooEarly
	...
 RadioTower2FBuenaTuneInAfterSixText:
 	text "BUENA: Tune in to"
 	line "PASSWORD every"

-	para "night from six to"
-	line "midnight!"
+	para "night from five"
+	line "to midnight!"

 	para "Tune in, then drop"
 	line "in for a visit!"
 	done

(Of course, you can use any time range here; it doesn't have to line up with a time of day boundary.)

10. Eevee evolves into Umbreon in the evening

Edit constants/pokemon_data_constants.asm:

 ; EVOLVE_HAPPINESS triggers
 	const_def 1
 	const TR_ANYTIME
 	const TR_MORNDAY
-	const TR_NITE
+	const TR_EVENITE

Then edit data/pokemon/evos_attacks.asm:

 ; - Evolution methods:
 ;    * db EVOLVE_LEVEL, level, species
 ;    * db EVOLVE_ITEM, used item, species
 ;    * db EVOLVE_TRADE, held item (or -1 for none), species
-;    * db EVOLVE_HAPPINESS, TR_* constant (ANYTIME, MORNDAY, NITE), species
+;    * db EVOLVE_HAPPINESS, TR_* constant (ANYTIME, MORNDAY, EVENITE), species
 ;    * db EVOLVE_STAT, level, ATK_*_DEF constant (LT, GT, EQ), species
 ; - db 0 ; no more evolutions
 EeveeEvosAttacks:
 	db EVOLVE_ITEM, THUNDERSTONE, JOLTEON
 	db EVOLVE_ITEM, WATER_STONE, VAPOREON
 	db EVOLVE_ITEM, FIRE_STONE, FLAREON
 	db EVOLVE_HAPPINESS, TR_MORNDAY, ESPEON
-	db EVOLVE_HAPPINESS, TR_NITE, UMBREON
+	db EVOLVE_HAPPINESS, TR_EVENITE, UMBREON
 	db 0 ; no more evolutions

And edit engine/pokemon/evolve.asm:

-; TR_NITE
+; TR_EVENITE
 	ld a, [wTimeOfDay]
 	cp NITE_F
-	jp nz, .dont_evolve_3
+	jp c, .dont_evolve_3 ; MORN_F or DAY_F < NITE_F
 	jr .proceed

 .happiness_daylight
 	ld a, [wTimeOfDay]
 	cp NITE_F
-	jp z, .dont_evolve_3
+	jp nc, .dont_evolve_3 ; NITE_F or EVE_F >= NITE_F
 	jr .proceed

This was mostly a renaming of TR_NITE to TR_EVENITE; the only code changes are the two jp lines. We're relying on the fact that EVE_F > NITE_F for the two cp comparisons to work. If you skip this step, then TR_MORNDAY will really be "TR_MORNDAYEVE", and Eevee will evolve into Espeon in the evening.

11. Johto wild battles use night music in the evening

Edit engine/battle/start_battle.asm:

 	ld de, MUSIC_JOHTO_WILD_BATTLE
 	ld a, [wTimeOfDay]
 	cp NITE_F
-	jr nz, .done
+	jr c, .done ; not NITE_F or EVE_F
 	ld de, MUSIC_JOHTO_WILD_BATTLE_NIGHT
 	jr .done

Just like the previous step, if you skip this one, evening will use the regular wild battle music.

12. The player's Mom watches TV in the evening

After her first scene where she gives you the Pokégear, your mom has different object_events, one for each time of day. To add one for evening, edit maps/PlayersHouse1F.asm:

 	def_object_events
 	object_event  7,  4, SPRITE_MOM, SPRITEMOVEDATA_STANDING_LEFT, 0, 0, -1, -1, 0, OBJECTTYPE_SCRIPT, 0, MomScript, EVENT_PLAYERS_HOUSE_MOM_1
 	object_event  2,  2, SPRITE_MOM, SPRITEMOVEDATA_STANDING_UP, 0, 0, -1, MORN, 0, OBJECTTYPE_SCRIPT, 0, MomScript, EVENT_PLAYERS_HOUSE_MOM_2
 	object_event  7,  4, SPRITE_MOM, SPRITEMOVEDATA_STANDING_LEFT, 0, 0, -1, DAY, 0, OBJECTTYPE_SCRIPT, 0, MomScript, EVENT_PLAYERS_HOUSE_MOM_2
+	object_event  4,  3, SPRITE_MOM, SPRITEMOVEDATA_STANDING_UP, 0, 0, -1, EVE, 0, OBJECTTYPE_SCRIPT, 0, MomScript, EVENT_PLAYERS_HOUSE_MOM_2
 	object_event  0,  2, SPRITE_MOM, SPRITEMOVEDATA_STANDING_UP, 0, 0, -1, NITE, 0, OBJECTTYPE_SCRIPT, 0, MomScript, EVENT_PLAYERS_HOUSE_MOM_2
 	object_event  4,  4, SPRITE_POKEFAN_F, SPRITEMOVEDATA_STANDING_RIGHT, 0, 0, -1, -1, PAL_NPC_RED, OBJECTTYPE_SCRIPT, 0, NeighborScript, EVENT_PLAYERS_HOUSE_1F_NEIGHBOR

The neighbor should also have appropriate text for evening; luckily the same "Good evening!" greeting makes sense for both evening and night.

 NeighborScript:
 	faceplayer
 	opentext
 	checktime MORN
 	iftrue .MornScript
 	checktime DAY
 	iftrue .DayScript
-	checktime NITE
+	checktime EVE | NITE
 	iftrue .NiteScript

13. Prof. Oak's intro changes in the evening

Edit data/text/common_1.asm:

 _OakTimeOversleptText::
 	text "!"
 	line "I overslept!"
 	done

 _OakTimeYikesText::
 	text "!"
 	line "Yikes! I over-"
 	cont "slept!"
 	done

 _OakTimeSoDarkText::
 	text "!"
 	line "No wonder it's so"
 	cont "dark!"
 	done
+
+_OakTimeNappedText::
+	text "!"
+	line "I napped for"
+	cont "too long!"
+	done

And edit engine/rtc/timeset.asm again:

 OakText_ResponseToSetTime:
 	text_asm
 	decoord 1, 14
 	ld a, [wInitHourBuffer]
 	ld c, a
 	call PrintHour
 	ld [hl], ":"
 	inc hl
 	ld de, wInitMinuteBuffer
 	lb bc, PRINTNUM_LEADINGZEROS | 1, 2
 	call PrintNum
 	ld b, h
 	ld c, l
 	ld a, [wInitHourBuffer]
 	cp MORN_HOUR
 	jr c, .nite
 	cp DAY_HOUR + 1
 	jr c, .morn
-	cp NITE_HOUR
-	jr c, .day
+	cp EVE_HOUR
+	jr c, .day
+	cp NITE_HOUR
+	jr c, .eve
 .nite
 	ld hl, .OakTimeSoDarkText
 	ret
 .morn
 	ld hl, .OakTimeOversleptText
 	ret
 .day
 	ld hl, .OakTimeYikesText
 	ret
+.eve
+	ld hl, .OakTimeNappedText
+	ret

 .OakTimeOversleptText:
 	text_far _OakTimeOversleptText
 	text_end

 .OakTimeYikesText:
 	text_far _OakTimeYikesText
 	text_end

 .OakTimeSoDarkText:
 	text_far _OakTimeSoDarkText
 	text_end
+
+.OakTimeNappedText:
+	text_far _OakTimeNappedText
+	text_end

14. Pokémon Center nurses greet you in the evening

Edit data/text/std_text.asm:

 NurseMornText:
 	text "Good morning!"
 	line "Welcome to our"
 	cont "#MON CENTER."
 	done

 NurseDayText:
 	text "Hello!"
 	line "Welcome to our"
 	cont "#MON CENTER."
 	done
+
+NurseEveText:
+	text "Good evening!"
+	line "Welcome to our"
+	cont "#MON CENTER."
+	done

 NurseNiteText:
 	text "Good evening!"
 	line "You're out late."

 	para "Welcome to our"
 	line "#MON CENTER."
 	done

 PokeComNurseMornText:
 	text "Good morning!"

 	para "This is the #-"
 	line "MON COMMUNICATION"

 	para "CENTER--or the"
 	line "#COM CENTER."
 	done

 PokeComNurseDayText:
 	text "Hello!"

 	para "This is the #-"
 	line "MON COMMUNICATION"

 	para "CENTER--or the"
 	line "#COM CENTER."
 	done
+
+PokeComNurseEveText:
+	text "Good evening."
+
+	para "This is the #-"
+	line "MON COMMUNICATION"
+
+	para "CENTER--or the"
+	line "#COM CENTER."
+	done

 PokeComNurseNiteText:
 	text "Good to see you"
 	line "working so late."

 	para "This is the #-"
 	line "MON COMMUNICATION"

 	para "CENTER--or the"
 	line "#COM CENTER."
 	done

And edit engine/events/std_scripts.asm:

 PokecenterNurseScript:
 ; EVENT_WELCOMED_TO_POKECOM_CENTER is never set

 	opentext
 	checktime MORN
 	iftrue .morn
 	checktime DAY
 	iftrue .day
+	checktime EVE
+	iftrue .eve
 	checktime NITE
 	iftrue .nite
 	sjump .ok

 .morn
 	checkevent EVENT_WELCOMED_TO_POKECOM_CENTER
 	iftrue .morn_comcenter
 	farwritetext NurseMornText
 	promptbutton
 	sjump .ok
 .morn_comcenter
 	farwritetext PokeComNurseMornText
 	promptbutton
 	sjump .ok

 .day
 	checkevent EVENT_WELCOMED_TO_POKECOM_CENTER
 	iftrue .day_comcenter
 	farwritetext NurseDayText
 	promptbutton
 	sjump .ok
 .day_comcenter
 	farwritetext PokeComNurseDayText
 	promptbutton
 	sjump .ok
+
+.eve
+	checkevent EVENT_WELCOMED_TO_POKECOM_CENTER
+	iftrue .eve_comcenter
+	farwritetext NurseEveText
+	promptbutton
+	sjump .ok
+.eve_comcenter
+	farwritetext PokeComNurseEveText
+	promptbutton
+	sjump .ok

 .nite
 	checkevent EVENT_WELCOMED_TO_POKECOM_CENTER
 	iftrue .nite_comcenter
 	farwritetext NurseNiteText
 	promptbutton
 	sjump .ok
 .nite_comcenter
 	farwritetext PokeComNurseNiteText
 	promptbutton
 	sjump .ok

15. Phone callers reuse night text in the evening

Edit engine/phone/scripts/generic_callee.asm:

 PhoneScript_AnswerPhone_Male:
 	checktime DAY
 	iftrue PhoneScript_AnswerPhone_Male_Day
-	checktime NITE
+	checktime EVE | NITE
 	iftrue PhoneScript_AnswerPhone_Male_Nite
 	...
 PhoneScript_AnswerPhone_Female:
 	checktime DAY
 	iftrue PhoneScript_AnswerPhone_Female_Day
-	checktime NITE
+	checktime EVE | NITE
 	iftrue PhoneScript_AnswerPhone_Female_Nite
 	...
 PhoneScript_GreetPhone_Male:
 	checktime DAY
 	iftrue PhoneScript_GreetPhone_Male_Day
-	checktime NITE
+	checktime EVE | NITE
 	iftrue PhoneScript_GreetPhone_Male_Nite
 	...
 PhoneScript_GreetPhone_Female:
 	checktime DAY
 	iftrue PhoneScript_GreetPhone_Female_Day
-	checktime NITE
+	checktime EVE | NITE
 	iftrue PhoneScript_GreetPhone_Female_Nite
 	...

Edit engine/phone/scripts/bill.asm:

 BillPhoneCalleeScript:
 	checktime DAY
 	iftrue .daygreet
-	checktime NITE
+	checktime EVE | NITE
 	iftrue .nitegreet
 	farwritetext BillPhoneMornGreetingText
 	promptbutton
 	sjump .main

Edit engine/phone/scripts/hangups.asm:

 KenjiAnswerPhoneScript:
 	...

 .OnBreak:
 	checktime MORN
 	iftrue .Morning
-	checktime NITE
+	checktime EVE | NITE
 	iftrue .Night
 	setevent EVENT_KENJI_ON_BREAK
 	farwritetext KenjiTakingABreakText
 	promptbutton
 	sjump PhoneScript_HangUpText_Male

And edit maps/Route45.asm:

 TrainerBlackbeltKenji:
 	...

 .Registered:
 	readvar VAR_KENJI_BREAK
 	ifnotequal 1, Route45NumberAcceptedM
 	checktime MORN
 	iftrue .Morning
-	checktime NITE
+	checktime EVE | NITE
 	iftrue .Night
 	checkevent EVENT_KENJI_ON_BREAK
 	iffalse Route45NumberAcceptedM
 	...

Unless I've missed something, that's all the RTC-related features and content, now with support for the evening!

Screenshot

There are some features limited to night that I left alone, so they aren't extended to happen in the evening. Two in particular: Officer trainers only battle you at night, and phone call trainers who give you items only change behavior at night. So in the evening, they act like it's morning or day.

You'll need at least version 4.5.2 (or 2.5.2++) of Polished Map to view and edit evening colors. It detects that darkness has been replaced by evening in bg_tiles.pal because there are four overworld water colors. (If there were three, it would assume they apply to morning, day, and night, and that darkness still exists.) It can't automatically detect that evening roof colors exist, so you have to change the Options→Roof Palettes selection to Morn + Day, Night, Custom; then the evening colors will be loaded into the Custom palette.

Screenshot

Clone this wiki locally