Improving the Swarm System - pret/pokecrystal GitHub Wiki

As was requested, here is a tutorial for reworking the swarm system, and adding extra features to it.

Contents

  1. Rework Base Swarm System
  2. Implement System for Increased Shiny Odds
  3. Create an NPC for Triggering a Daily Swarm
  4. Remove Old Phone Call Swarm Triggers
  5. Add New Swarms
  6. Bonus: Multiple Swarms on Same Route

1. Rework Base Swarm System

First things first, we'll be reworking the base swarm system to be more like GS's- no wram labels for individual swarms, but one shared 'Swarm' label. This will make things much easier for adding any future swarms.

Edit ram/wram.asm:

-wDunsparceMapGroup:: db
-wDunsparceMapNumber:: db
+wSwarmMapGroup:: db
+wSwarmMapNumber:: db

Edit constants/engine_flags.asm:

-       const QWILFISH_SWARM
+       const ENGINE_SWARM

Edit constants/wram_constants.asm:

-	const DAILYFLAGS1_FISH_SWARM_F
+       const DAILYFLAGS1_SWARM_F

Edit data/events/engine_flags.asm:

-       engine_flag wDailyFlags1, DAILYFLAGS1_FISH_SWARM_F
+       engine_flag wDailyFlags1, DAILYFLAGS1_SWARM_F

Edit macros/scripts/events.asm:

 swarm: MACRO
	db swarm_command
-	db \1 ; flag
-	map_id \2 ; map
+       map_id \1 ; map

Edit engine/events/fish.asm:

 GetFishGroupIndex:
 ; Return the index of fishgroup d in de.

-	push hl
-	ld hl, wDailyFlags1
-       bit DAILYFLAGS1_FISH_SWARM_F, [hl]
-	pop hl
-	jr z, .done

Edit engine/events/specials.asm:

 ActivateFishingSwarm:
	ld a, [wScriptVar]
	ld [wFishingSwarmFlag], a
-	ret
+       jr SetSwarmFlag

 StoreSwarmMapIndices::
-	ld a, c
-	and a
-	jr nz, .yanma
-; swarm dark cave violet entrance
-	ld a, d
-	ld [wDunsparceMapGroup], a
-	ld a, e
-	ld [wDunsparceMapNumber], a
-	ret
-
-.yanma
-	ld a, d
-	ld [wYanmaMapGroup], a
-	ld a, e
-	ld [wYanmaMapNumber], a
-	ret
+	ld a, d
+	ld [wSwarmMapGroup], a
+	ld a, e
+	ld [wSwarmMapNumber], a
+
+SetSwarmFlag:
+	ld hl, wDailyFlags1
+	set DAILYFLAGS1_SWARM_F, [hl]
+	ret
+
+CheckSwarmFlag::
+	ld hl, wDailyFlags1
+	bit DAILYFLAGS1_SWARM_F, [hl]
+	jr z, .clear_swarm
+	xor a
+	ld [wScriptVar], a
+	ret
+
+.clear_swarm
+	ld a, 1
+	ld [wScriptVar], a
+	xor a
+	ld [wFishingSwarmFlag], a
+	ld [wSwarmMapGroup], a
+	ld [wSwarmMapNumber], a
+	ret

Edit engine/overworld/scripting.asm:

 Script_swarm:
-	call GetScriptByte
-	ld c, a

Edit engine/overworld/time.asm:

-UnusedSetSwarmFlag: ; unreferenced
-	ld hl, wDailyFlags1
-	set DAILYFLAGS1_FISH_SWARM_F, [hl]
-	ret
-
-UnusedCheckSwarmFlag: ; unreferenced
-	and a
-	ld hl, wDailyFlags1
-	bit DAILYFLAGS1_FISH_SWARM_F, [hl]
-	ret nz
-	scf
-	ret

Edit engine/overworld/wildmons.asm:

 _SwarmWildmonCheck:
	call CopyCurrMapDE
-	push hl
-	ld hl, wSwarmFlags
-	bit SWARMFLAGS_DUNSPARCE_SWARM_F, [hl]
-	pop hl
-	jr z, .CheckYanma
-	ld a, [wDunsparceMapGroup]
-	cp d
-	jr nz, .CheckYanma
-	ld a, [wDunsparceMapNumber]
-	cp e
-	jr nz, .CheckYanma
-	call LookUpWildmonsForMapDE
-	jr nc, _NoSwarmWildmon
-	scf
-	ret
-
-.CheckYanma:
-	push hl
-	ld hl, wSwarmFlags
-	bit SWARMFLAGS_YANMA_SWARM_F, [hl]
-	pop hl
-	jr z, _NoSwarmWildmon
-	ld a, [wYanmaMapGroup]
-	cp d
-	jr nz, _NoSwarmWildmon
-	ld a, [wYanmaMapNumber]
+       ld a, [wSwarmMapGroup]
+	cp d
+	jr nz, _NoSwarmWildmon
+	ld a, [wSwarmMapNumber]
	cp e
	jr nz, _NoSwarmWildmon
	call LookUpWildmonsForMapDE
	jr nc, _NoSwarmWildmon
	scf
	ret

Lastly, edit data/wild/swarm_water.asm:

SwarmWaterWildMons:

-	; No swarms encountered while surfing in Crystal
+       ; qwilfish
+       map_id ROUTE_32
+       db 6 percent ; encounter rate
+	db 15, QWILFISH
+	db 20, QWILFISH
+	db 20, TENTACRUEL

Note that surfing swarms are possible, so I chose to replace the single fishing swarm and just reuse some of the leftovers of that here.

2. Implement System for Increased Shiny Odds

The existing ATKDEFDV_SHINY constant will almost always result in a male shiny due to gender being tied to dvs as well, so we're just creating a female weighted constant here.

Edit constants/battle_constants.asm:

 ; shiny dvs
 DEF ATKDEFDV_SHINY EQU $EA
+DEF ATKDEFDV_SHINYF EQU $1A
 DEF SPDSPCDV_SHINY EQU $AA

Create engine/battle/swarm_shiny.asm:

+GenerateSwarmShiny:
+	ld a, [wSwarmMapGroup]
+	ld b, a
+	ld a, [wSwarmMapNumber]
+	ld c, a
+	call GetWorldMapLocation
+       cp LANDMARK_ROUTE_35
+       jr z, .yanma
+       cp LANDMARK_DARK_CAVE
+	jr z, .dunsparce
+       cp LANDMARK_ROUTE_32
+	jr z, .qwilfish
+       jr .skipshine
+
+.yanma
+       ld a, [wCurPartySpecies]
+       cp YANMA
+       jr nz, .skipshine
+       jr .rollshiny
+.dunsparce
+       ld a, [wCurPartySpecies]
+       cp DUNSPARCE
+       jr nz, .skipshine
+       jr .rollshiny
+.qwilfish
+       ld a, [wCurPartySpecies]
+       cp QWILFISH
+       jr nz, .skipshine
+       ;fallthrough
+.rollshiny
+       call Random
+	cp 7 ; adjust to desired percentage
+	jr nc, .trynext
+	ld b, ATKDEFDV_SHINY
+	ld c, SPDSPCDV_SHINY
+	jr .UpdateDVs
+.trynext:
+	call Random
+	cp 7 ; adjust to desired percentage
+	jr nc, .skipshine
+	ld b, ATKDEFDV_SHINYF
+	ld c, SPDSPCDV_SHINY
+	jr .UpdateDVs
+
+.skipshine:
+; Generate new random DVs
+	call BattleRandom
+	ld b, a
+	call BattleRandom
+	ld c, a
+
+.UpdateDVs:
+; Input DVs in register bc
+	ld hl, wEnemyMonDVs
+	ld a, b
+	ld [hli], a
+	ld [hl], c
+	ret

Edit main.asm:

 INCLUDE "engine/battle/link_result.asm"
+
+
+SECTION "Own Section", ROMX
+
+INCLUDE "engine/battle/swarm_shiny.asm"

Edit engine/battle/core.asm:

 .GenerateDVs:
+;checkswarm
+	ld hl, wDailyFlags1
+	bit DAILYFLAGS1_SWARM_F, [hl]
+	jr z, .skipshine
+	
+	farcall GenerateSwarmShiny
+	jr .next
+
+.skipshine:
 ; Generate new random DVs
	call BattleRandom
	ld b, a
	call BattleRandom
	ld c, a

 .UpdateDVs:
 ; Input DVs in register bc
	ld hl, wEnemyMonDVs
	ld a, b
	ld [hli], a
	ld [hl], c
	
+.next

This code will check for the swarming species, while a swarm is active, and boost the chance of that species being shiny. This can be set to as common or as infrequent as desired.

3. Create an NPC for Triggering a Daily Swarm

Edit maps/[whereveryouwant].asm:

+SwarmGrampsScript:
+	faceplayer
+	opentext
+	checkflag ENGINE_SWARM
+	iftrue .skiprandomswarm
+	random 3
+	ifequal 0, .dunsparce
+	ifequal 1, .yanma
+       ifequal 2, .qwilfish
+
+.dunsparce
+	setflag ENGINE_SWARM
+	swarm DARK_CAVE_VIOLET_ENTRANCE
+	writetext SwarmDunsparceText
+	waitbutton
+	closetext
+	end
+
+.yanma
+	setflag ENGINE_SWARM
+	swarm ROUTE_35
+	writetext SwarmYanmaText
+	waitbutton
+	closetext
+	end
+
+.qwilfish
+	setflag ENGINE_SWARM
+	swarm ROUTE_32
+	writetext SwarmQwilfishText
+	waitbutton
+	closetext
+	end
+
+.skiprandomswarm
+	writetext SkipSwarmText
+	waitbutton
+	closetext
+	end
+
+SwarmDunsparceText:
+	text "Let me see…"
+	line "What did the news"
+	cont "say?"
+
+	para "Oh yes! There's a"
+	line "swarm of DUNSPARCE"
+	cont "at DARK CAVE."
+	done
+	
+SwarmYanmaText:
+	text "Let me see…"
+	line "What did the news"
+	cont "say?"
+
+	para "Oh yes! There's a"
+	line "swarm of YANMA"
+	cont "on ROUTE 35."
+	done
+	
+SwarmQwilfishText:
+	text "Let me see…"
+	line "What did the news"
+	cont "say?"
+
+	para "Oh yes! There's a"
+	line "swarm of QWILFISH"
+	cont "on ROUTE 32."
+	done
+
+SkipSwarmText:
+	text "Often #MON"
+	line "of unusual colors"
+
+	para "are reported in"
+	line "swarms."
+	done

(...)

 def_object_events
+object_event  2,  3, SPRITE_GRAMPS, SPRITEMOVEDATA_STANDING_UP, 0, 0, -1, -1, 0, OBJECTTYPE_SCRIPT, 0, SwarmGrampsScript, -1

Another improvement that has been made here is to create an npc that the player can talk directly with to trigger a swarm, once per day. No more repetitively changing DST, hoping someone will call you.

4. Remove Old Phone Call Swarm Triggers

Edit engine/phone/scripts/ralph.asm:

	farscall PhoneScript_AnswerPhone_Male
-	checkflag ENGINE_RALPH_WEDNESDAY_MORNING
-	iftrue .CheckSwarm
-	readvar VAR_WEEKDAY
-	ifnotequal WEDNESDAY, .CheckSwarm
	checktime MORN
	iftrue Ralph_WednesdayMorning
-.CheckSwarm:
-	checkflag ENGINE_QWILFISH_SWARM
-	iftrue .ReportSwarm
	farsjump RalphNoItemScript

(...)

-.ReportSwarm:
-	getlandmarkname STRING_BUFFER_5, LANDMARK_ROUTE_32
-	farsjump RalphHurryScript

(...)

-       checkflag ENGINE_FLYPOINT_GOLDENROD
-	iffalse .CheckSwarm
-	checkflag ENGINE_RALPH_READY_FOR_REMATCH
-	iftrue .CheckSwarm
-	checkflag ENGINE_RALPH_WEDNESDAY_MORNING
-	iftrue .CheckSwarm
	farscall PhoneScript_Random2
	ifequal 0, Ralph_FightMe
-.CheckSwarm:
-	farscall PhoneScript_Random5
-	ifequal 0, Ralph_SetUpSwarm
	farsjump Phone_GenericCall_Male

(...)

-Ralph_SetUpSwarm:
-	checkflag ENGINE_QWILFISH_SWARM
-	iftrue .Generic
-	setflag ENGINE_QWILFISH_SWARM
-	getmonname STRING_BUFFER_4, QWILFISH
-	getlandmarkname STRING_BUFFER_5, LANDMARK_ROUTE_32
-	setval FISHSWARM_QWILFISH
-	special ActivateFishingSwarm
-	farsjump RalphItemScript
-
-.Generic:
-	farsjump Phone_GenericCall_Male

Edit engine/phone/scripts/anthony.asm:

-       checkflag ENGINE_ANTHONY_FRIDAY_NIGHT
-	iftrue .NotFriday
-	readvar VAR_WEEKDAY
-	ifnotequal FRIDAY, .NotFriday
	checktime NITE
	iftrue AnthonyFridayNight
-
-.NotFriday:
-	checkflag ENGINE_DUNSPARCE_SWARM
-	iftrue .AlreadySwarming
	farsjump AnthonyHangUpScript

(...)

-.AlreadySwarming:
-	getlandmarkname STRING_BUFFER_5, LANDMARK_ROUTE_33
-	farsjump AnthonyHurryScript

(...)

-       checkflag ENGINE_FLYPOINT_GOLDENROD
-	iffalse .TriesSwarm
-	checkflag ENGINE_ANTHONY_READY_FOR_REMATCH
-	iftrue .TriesSwarm
-	checkflag ENGINE_ANTHONY_FRIDAY_NIGHT
-	iftrue .TriesSwarm
	farscall PhoneScript_Random2
	ifequal 0, AnthonyWantsBattle
-
-.TriesSwarm:
-	farscall PhoneScript_Random5
-	ifequal 0, AnthonyTriesDunsparceSwarm
	farsjump Phone_GenericCall_Male

(...)

-AnthonyTriesDunsparceSwarm:
-	checkflag ENGINE_DUNSPARCE_SWARM
-	iftrue .Generic
-	setflag ENGINE_DUNSPARCE_SWARM
-	getmonname STRING_BUFFER_4, DUNSPARCE
-	swarm SWARM_DUNSPARCE, DARK_CAVE_VIOLET_ENTRANCE
-	getlandmarkname STRING_BUFFER_5, LANDMARK_DARK_CAVE
-	farsjump AnthonySwarmScript
-
-.Generic:
-	farsjump Phone_GenericCall_Male

Edit engine/phone/scripts/arnie.asm:

-       checkflag ENGINE_ARNIE_TUESDAY_MORNING
-	iftrue .NotTuesday
-	readvar VAR_WEEKDAY
-	ifnotequal TUESDAY, .NotTuesday
	checktime MORN
	iftrue ArnieTuesdayMorning
-
-.NotTuesday:
-	checkflag ENGINE_YANMA_SWARM
-	iftrue .AlreadySwarming
	farsjump ArnieHangUpScript

(...)

-.AlreadySwarming:
-	getlandmarkname STRING_BUFFER_5, LANDMARK_ROUTE_35
-	farsjump ArnieHurryScript

(...)

-       checkflag ENGINE_ARNIE_READY_FOR_REMATCH
-	iftrue .Swarm
-	checkflag ENGINE_ARNIE_TUESDAY_MORNING
-	iftrue .Swarm
	farscall PhoneScript_Random2
	ifequal 0, ArnieWantsBattle
-
-.Swarm:
-	farscall PhoneScript_Random5
-	ifequal 0, ArnieYanmaSwarm
-	farscall PhoneScript_Random3
-	ifequal 0, ArnieFoundRare
	farsjump Phone_GenericCall_Male

(...)

-ArnieYanmaSwarm: ; start swarm
-	checkflag ENGINE_YANMA_SWARM
-	iftrue ArnieYanmaAlreadySwarming
-	setflag ENGINE_YANMA_SWARM
-	getmonname STRING_BUFFER_4, YANMA
-	swarm SWARM_YANMA, ROUTE_35
-	getlandmarkname STRING_BUFFER_5, LANDMARK_ROUTE_35
-	farsjump ArnieSwarmScript
-
-ArnieFoundRare:
-	farsjump Phone_CheckIfUnseenRare_Male
-
-ArnieYanmaAlreadySwarming:
-	farsjump Phone_GenericCall_Male

Since we're no longer using these, we're just removing them.

5. Add New Swarms

Should you want to add more swarms, it is now as simple as this-

Edit data/wild/swarm_grass.asm:

+; Vulpix swarm
+	map_id ROUTE_37
+	db 10 percent, 10 percent, 10 percent ; encounter rates: morn/day/nite
+	; morn
+	db 13, LEDYBA
+	db 14, VULPIX
+	db 15, PIDGEY
+	db 16, VULPIX
+	db 15, PIDGEOTTO
+	db 15, VULPIX
+	db 15, LEDIAN
+	; day
+	db 13, PIDGEY
+	db 14, VULPIX
+	db 15, PIDGEY
+	db 16, VULPIX
+	db 15, PIDGEOTTO
+	db 15, VULPIX
+	db 15, PIDGEY
+	; nite
+	db 13, SPINARAK
+	db 14, VULPIX
+	db 15, HOOTHOOT
+	db 16, VULPIX
+	db 15, NOCTOWL
+	db 15, VULPIX
+	db 15, ARIADOS

Edit engine/battle/swarm_shiny.asm:

	jr z, .qwilfish
+       cp LANDMARK_ROUTE_37
+	jr z, .vulpix
        jr .skipshine

+.vulpix
+       ld a, [wCurPartySpecies]
+       cp VULPIX
+       jr nz, .skipshine
+       jr .rollshiny
 .yanma

Edit maps/[whereveryouwant].asm:

-	random 3
+	random 4
	ifequal 0, .dunsparce
	ifequal 1, .yanma
        ifequal 2, .qwilfish
+       ifequal 3, .vulpix

(...)

+.vulpix
+	setflag ENGINE_SWARM
+	swarm ROUTE_37
+	writetext SwarmVulpixText
+	waitbutton
+	closetext
+	end

(...)

+SwarmVulpixText:
+	text "Let me see…"
+	line "What did the news say?"
+
+	para "Oh yes! There's a"
+	line "swarm of VULPIX"
+	cont "on ROUTE 37."
+	done

And that should be all.

6. Bonus: Multiple Swarms on Same Route

Per request, here we will explore how to add an additional swarm to a route that already has one. Start off by repurposing the now unused Dunsparce swarm flag.

Edit constants/engine_flags.asm:

 ; wSwarmFlags
-	const ENGINE_DUNSPARCE_SWARM
+       const ENGINE_ALT_SWARM

Edit constants/wram_constants.asm:

-	const SWARMFLAGS_DUNSPARCE_SWARM_F ; 2
+       const SWARMFLAGS_ALT_SWARM_F       ; 2

Edit data/events/engine_flags.asm:

-	engine_flag wSwarmFlags, SWARMFLAGS_DUNSPARCE_SWARM_F
+       engine_flag wSwarmFlags, SWARMFLAGS_ALT_SWARM_F

Edit maps/Route33.asm:

-	checkflag ENGINE_DUNSPARCE_SWARM
-	iftrue .Swarm

Now we're going to add some code to set and check for the ALT_SWARM flag to determine if the swarm is the secondary swarm on the route, and how to handle it.

Create data/wild/swarm_grass_alt.asm:

+; Pokémon swarms in grass
+
+SwarmGrassWildMonsAlt:
+
+; Murkrow swarm
+	map_id ROUTE_37
+	db 10 percent, 10 percent, 10 percent ; encounter rates: morn/day/nite
+	; morn
+	db 12, MURKROW
+	db 14, MURKROW
+	db 13, MURKROW
+	db 10, ABRA
+	db 12, MURKROW
+	db 10, DITTO
+	db 12, YANMA
+	; day
+	db 12, MURKROW
+	db 14, MURKROW
+	db 13, MURKROW
+	db 10, ABRA
+	db 12, MURKROW
+	db 10, DITTO
+	db 12, YANMA
+	; nite
+	db 12, MURKROW
+	db 14, MURKROW
+	db 13, MURKROW
+	db 10, ABRA
+	db 12, MURKROW
+	db 10, DITTO
+	db 12, YANMA
+
+	db -1 ; end

Edit maps/[whereveryouwant].asm:

-	random 4
+	random 5
	ifequal 0, .dunsparce
	ifequal 1, .yanma
        ifequal 2, .qwilfish
        ifequal 3, .vulpix
+       ifequal 4, .murkrow

(...)

+.murkrow
+       setflag ENGINE_ALT_SWARM
+	setflag ENGINE_SWARM
+	swarm ROUTE_35
+	writetext SwarmMurkrowText
+	waitbutton
+	closetext
+	end

(...)

+SwarmMurkrowText:
+	text "Let me see…"
+	line "What did the news say?"
+
+	para "Oh yes! There's a"
+	line "swarm of MURKROW"
+	cont "on ROUTE 35."
+	done

Edit engine/overworld/wildmons.asm:

 _GrassWildmonLookup:
+	ld hl, SwarmGrassWildMonsAlt
+	push hl
+	ld hl, wSwarmFlags
+	bit SWARMFLAGS_ALT_SWARM_F, [hl]
+	pop hl
+	jr nz, .nextgrass
        ld hl, SwarmGrassWildMons
+.nextgrass
        ld bc, GRASS_WILDDATA_LENGTH
        call _SwarmWildmonCheck
        ret c

(...)

 INCLUDE "data/wild/swarm_grass.asm"
+INCLUDE "data/wild/swarm_grass_alt.asm"

Edit engine/battle/swarm_shiny.asm:

 .yanma
+	ld hl, wSwarmFlags
+	bit SWARMFLAGS_ALT_SWARM_F, [hl]
+	jr nz, .murkrow
        ld a, [wCurPartySpecies]

(...)

+.murkrow
+       ld a, [wCurPartySpecies]
+       cp MURKROW
+       jr nz, .skipshine
+       jr .rollshiny

And now two swarms on the same route are functional. If for some reason you should want to add a third, you can repurpose the Yanma swarm flag the same way.