Automatic battle weather on certain maps - pret/pokecrystal GitHub Wiki

Gen 2 introduced the weather moves Sunny Day, Rain Dance, and Sandstorm; but Gen 3 introduced automatic weather on certain maps, like rain on Routes 119 and 120. We can't replicate the overworld effects of weather on the GameBoy Color, but we can trigger weather in battle based on your map.

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

Contents

  1. Define new constants and data tables
  2. Start weather based on the data tables

1. Define new constants and data tables

Create data/battle/automatic_weather.asm:

+; AutomaticWeatherEffects indexes
+	const_def 1
+	const AUTOMATIC_SUN
+	const AUTOMATIC_RAIN
+	const AUTOMATIC_SANDSTORM
+
+AutomaticWeatherMaps:
+auto_weather_map: MACRO
+	map_id \1 ; map
+	db \2 ; AUTOMATIC_* weather index
+ENDM
+	auto_weather_map TIN_TOWER_ROOF, AUTOMATIC_SUN
+	auto_weather_map ROUTE_43, AUTOMATIC_RAIN
+	auto_weather_map LAKE_OF_RAGE, AUTOMATIC_RAIN
+	auto_weather_map ROUTE_45, AUTOMATIC_SANDSTORM
+	db 0 ; end
+
+AutomaticWeatherEffects:
+; entries correspond to AUTOMATIC_* constants
+auto_weather_effect: MACRO
+	db \1 ; battle weather
+	dw \2 ; animation
+	dw \3 ; text
+ENDM
+	auto_weather_effect WEATHER_SUN, SUNNY_DAY, SunGotBrightText
+	auto_weather_effect WEATHER_RAIN, RAIN_DANCE, DownpourText
+	auto_weather_effect WEATHER_SANDSTORM, ANIM_IN_SANDSTORM, SandstormBrewedText

This file defines a set of constants and two data tables.

The AUTOMATIC_* constants are used by entries in the AutomaticWeatherMaps table, and are indexes for the AutomaticWeatherEffects table.

The AutomaticWeatherMaps table lists our maps and their corresponding automatic weather effects, with a 0 to mark the end of the table.

The AutomaticWeatherEffects table lists the data needed to start a weather effect: the effect ID, animation ID, and starting text.

Both of these tables use macros to define their entries. This helps to avoid mixing up db, dw, etc, and clearly marks each entry on its own line.

2. Start weather based on the data tables

Edit engine/battle/core.asm:

 DoBattle:
 	...

 .not_linked_2
+	call StartAutomaticBattleWeather
 	jp BattleTurn

 .tutorial_debug
 	jp BattleMenu
+
+StartAutomaticBattleWeather:
+	call GetAutomaticBattleWeather
+	and a
+	ret z
+; get current AutomaticWeatherEffects entry
+	dec a
+	ld hl, AutomaticWeatherEffects
+	ld bc, 5 ; size of one entry
+	call AddNTimes
+; [wBattleWeather] = weather
+	ld a, [hli]
+	ld [wBattleWeather], a
+; de = animation
+	ld a, [hli]
+	ld e, a
+	ld a, [hli]
+	ld d, a
+; hl = text pointer
+	ld a, [hli]
+	ld h, [hl]
+	ld l, a
+; start weather for 255 turns
+	ld a, 255
+	ld [wWeatherCount], a
+	push hl
+	call Call_PlayBattleAnim ; uses de
+	pop hl
+	call StdBattleTextbox ; uses hl
+	jp EmptyBattleTextbox
+
+GetAutomaticBattleWeather:
+	ld hl, AutomaticWeatherMaps
+	ld a, [wMapGroup]
+	ld b, a
+	ld a, [wMapNumber]
+	ld c, a
+.loop
+	ld a, [hli] ; group
+	and a
+	ret z ; end
+	cp b
+	jr nz, .wrong_group
+	ld a, [hli] ; map
+	cp c
+	jr nz, .wrong_map
+	ld a, [hl] ; weather
+	ret
+
+.wrong_group:
+	inc hl ; skip map
+.wrong_map
+	inc hl ; skip weather
+	jr .loop
+
+INCLUDE "data/battle/automatic_weather.asm"

Here we define two routines, StartAutomaticBattleWeather and GetAutomaticBattleWeather.

StartAutomaticBattleWeather is called at the very end of battle setup, just before the alternating player and enemy turns start. It calls GetAutomaticBattleWeather to see if any weather should start, and if it should, it does so in five steps:

  1. Set [wBattleWeather] to the right weather ID
  2. Set [wWeatherCount] to 255 turns (the maximum possible in one byte)
  3. Call Call_PlayBattleAnim to play the right animation, stored in de
  4. Call StdBattleTextbox to show the right starting text, stored in hl
  5. Clear the text it just printed so the player's turn can start

These steps are adapted from the individual weather move effects in engine/battle/move_effects/sunny_day.asm, rain_dance.asm, and sandstorm.asm. The relevant data values are read from AutomaticWeatherEffects.

GetAutomaticBattleWeather compares the map group and number, in [wMapGroup] and [wMapNumber], to the values in each entry of AutomaticWeatherMaps. If one matches, it uses the automatic weather index at the end of the entry.

Now we have automatic sun in Ho-Oh's domain, the roof of Tin Tower; rain around Lake of Rage; and sandstorm in the canyon of Route 45:

Screenshot

By implementing this feature with abstract data tables, instead of hard-coding everything, it's easy to update. You can add automatic rain in WHIRL_ISLAND_LUGIA_CHAMBER just by adding one line to AutomaticWeatherMaps, without having to rewrite any assembly code.

The techniques used here can be applied to many different features. If you want to take similar but different actions depending on certain game state, then make tables that associate the state values with the corresponding action parameters; then implement the action in an abstract way so that it can get its parameters from the table.