5 Adding drawbacks or restrictions to overpowered card effects
Sha0den edited this page 2025-10-17 20:00:21 -04:00
This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

There are a lot of rather powerful card effects in the Pokémon Trading Card Game. In particular, many of the original Trainer cards were so strong that the Trainers ended up becoming more important than the Pokémon. Eventually, the Supporter subtype was created as the primary balancing mechanism for Trainer cards, but other methods are still used. This tutorial will show you how to implement a few of them by going through some specific examples. You can also see a few less specific examples if you check out the Potential Card Effects section in the Create Turn Flags tutorial.

  1. Add a Coin Flip (Energy Removal → Crushing Hammer)
  2. Require Player to Have More Prizes than the Opponent (Gust of Wind → Counter Catcher)
  3. Prevent Further Trainer Cards from Being Played That Turn (Professor Oak → Professor Elm)

1. Add a Coin Flip

Energy Removal → Crushing Hammer

Adding a coin flip to a powerful attack or card effect is one of the most common balancing mechanisms in the Pokémon Trading Card Game and one that was used almost immediately with the Energy Removal Trainer card. Near the end of the Wizards era of the card game, a coin flip was added to the card, and it was rebranded as Energy Removal 2. Then, that same effect was given to Crushing Hammer in the Black and White series, and it's been reprinted under that name in every generation since.

Most card effects are handled in src/engine/duel/effect_functions.asm, so start by opening that file and finding the functions for Energy Removal. We'll need to add a coin flip to the start of the player selection. Then, if a tails is flipped, it should skip the selection process and store an invalid deck index (-1) into the RAM address that would have been used for the target Energy.

+; output:
+;	[hTemp_ffa0] = play area location offset of the chosen Pokemon (PLAY_AREA_* constant)
+;	[hTempPlayAreaLocation_ffa1] = deck index of the selected Energy card (0-59, -1 if coin was tails)
 EnergyRemoval_PlayerSelection:
+	ldtx de, TrainerCardSuccessCheckText
+	call TossCoin
+	jr nc, .tails
	ldtx hl, ChoosePokemonToRemoveEnergyFromText
	call DrawWideTextBox_WaitForInput
	call SwapTurn
	call HandlePokemonAndEnergySelectionScreen
-	call SwapTurn
+	jp SwapTurn
+.tails
+	ld a, -1
+	ldh [hTempPlayAreaLocation_ffa1], a
	ret

Next, we'll reconfigure the discard effect to check for an invalid deck index (-1) which if found will immediately cancel the function without doing anything else.

+; input:
+;	[hTemp_ffa0] = play area location offset of the chosen Pokemon (PLAY_AREA_* constant)
+;	[hTempPlayAreaLocation_ffa1] = deck index of the selected Energy card (0-59, -1 if none)
 EnergyRemoval_DiscardEffect:
+	ldh a, [hTempPlayAreaLocation_ffa1]
+	cp -1
+	ret z ; return if no card was selected
	call SwapTurn
-	ldh a, [hTempPlayAreaLocation_ffa1]
	call PutCardInDiscardPile
	call SwapTurn
	call IsPlayerTurn
	ret c

 ; show Player which Pokemon was affected
	call SwapTurn
	ldh a, [hTemp_ffa0]
	call DrawPlayAreaScreenToShowChanges
	call SwapTurn
	ret

You might notice that we skipped the effect function for the AI selection. That's because the real AI logic is handled in src/engine/duel/ai/trainer_cards.asm, so open that file, and make the following changes to AIPlay_EnergyRemoval, which will mirror what was added to the player selection effect.

 AIPlay_EnergyRemoval:
	ld a, [wAITrainerCardToPlay]
	ldh [hTempCardIndex_ff9f], a
+	ldtx de, TrainerCardSuccessCheckText
+	call TossCoin
+	jr nc, .tails
	ld a, [wAITrainerCardParameter]
	ldh [hTemp_ffa0], a
	ld a, [wce1a]
	ldh [hTempPlayAreaLocation_ffa1], a
+	jr .play_card
+.tails
+	ld a, -1
+	ldh [hTempPlayAreaLocation_ffa1], a
+.play_card
	ld a, OPPACTION_EXECUTE_TRAINER_EFFECTS
	bank1call AIMakeDecision
	ret

The card effect should now be functional, but we're not quite done. The card's description still needs to be updated, so open src/text/text12.asm and edit EnergyRemovalDescription.

 EnergyRemovalDescription:
-	text "Choose 1 Energy card attached to 1"
-	line "of your opponent's Pokémon and"
-	line "discard it."
+	text "Flip a coin. If heads, discard an"
+	line "Energy attached to 1 of your"
+	line "opponent's Pokémon."
	done

You might also want to change the card's name (under EnergyRemovalName) and/or its sprite, but that's up to you. If you're really ambitious, you can even rename all of the labels and comments scattered throughout the disassembly, but that's definitely not necessary.


2. Require Player to Have More Prizes than the Opponent

Gust of Wind → Counter Catcher

Another common restriction for powerful effects is to require that they can't be used unless you're currently behind in Prize cards (i.e. losing the game). This was originally combined with the Gust of Wind effect in the EX era (Gen 3) of the card game, with the modal card Pow! Hand Extension, but it was later simplified and renamed Counter Catcher.

Just like with the previous tutorial, we'll start by opening src/engine/duel/effect_functions.asm and navigating to the functions for Gust of Wind. Since we're not actually changing any part of the actual effect, we'll only need to edit the initial "Check" function which determines whether or not the card can be played. More specifically, we'll set up a comparison between the number of remaining Prize cards for the current player and the number of remaining Prize cards for that player's opponent.

 ; return carry if non-turn duelist has no benched Pokemon
+; or if turn-duelist doesn't have more Prize cards left than non-turn duelist
 GustOfWind_BenchCheck:
	ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA
	call GetNonTurnDuelistVariable
	ldtx hl, EffectNoPokemonOnTheBenchText
	cp 2
+	ret c
+	call CountPrizes
+	ld b, a ; turn duelist's prize cards
+	call SwapTurn
+	call CountPrizes
+	call SwapTurn
+	; a = non-turn duelist's prize cards
+	cp b
+	ccf
+	ldtx hl, NotMorePrizesThanOpponentText
	ret

You might also consider replacing ATK_ANIM_GUST_OF_WIND in GustOfWind_SwitchEffect with ATK_ANIM_MAGNECTIC_STORM to better represent the Counter Catcher card.


Next, we need to define the new text that was referenced in GustOfWind_BenchCheck. You can either replace one of the unused texts or add it to any of the text files that isn't yet full. Feel free to review the How to add a new text tutorial if you're still unfamiliar with the process.

NotMorePrizesThanOpponentText:
	text "You don't have more Prize cards left"
	line "than your opponent."
	done

While you're at it, open src/text/text13.asm, and edit Gust of Wind's card description to match the new effect.

 GustOfWindDescription:
-	text "Choose 1 of your opponent's Benched"
+	text "You can use this card only if you"
+	line "have more Prize cards left than"
+	line "your opponent."
+	line ""
+	line "Choose 1 of your opponent's Benched"
	line "Pokémon and switch it with his or"
	line "her Active Pokémon."
	done

I recommend changing the card's name (under GustOfWindName) and sprite as well, but that's up to you. And I'll again note that you can also rename all of the labels and comments scattered throughout the disassembly if you want, but it isn't strictly necessary.


3. Prevent Further Trainer Cards from Being Played That Turn

Professor Oak → Professor Elm

Before creating Supporter cards, the developers of the card game experimented with a similar mechanic in the Neo Genesis set. These new cards prevented all other Trainer cards from being played for the rest of the turn. While this is actually a greater setback than the Supporter rule that followed, drawing seven cards is probably significant enough to warrant such a severe downside. Unsurprisingly, Professor Elm was created with this restriction in an attempt to make a more balanced version of Professor Oak. Let's see if we can do the same by updating Professor Oak's effect in the game to that of Professor Elm.

The simplest way to implement this type of restriction would be to utilize the flag for Psyduck's Headache attack (SUBSTATUS3_HEADACHE_F) because that already prevents Trainer cards from being played. Plus, it's usually a good idea to consolidate similar effects in order to conserve space. The only downside to using the Headache flag would be having to edit UnableToUseTrainerDueToHeadacheText in src/text/text2.asm to use more generic wording. If you would rather keep the unique text notifications, you can learn how to set up a separate flag in the Create Turn Flags tutorial; the Forbid Player from Attacking or Using Trainers for the Rest of the Turn example even mentions adding this specific effect.

Like with most card effects, the core effect is handled in src/engine/duel/effect_functions.asm. We'll prevent further Trainer cards from being played by setting the substatus flag for Headache. We also have to adjust the code for discarding the player's hand, which should instead be shuffled back into the deck.

 ProfessorOakEffect:
-; discard hand
+; discard this Trainer card before returning the hand to the deck
+	ldh a, [hTempCardIndex_ff9f]
+	call RemoveCardFromHand
+	call PutCardInDiscardPile
+; return hand to the deck
	call CreateHandCardList
	call SortCardsInDuelTempListByID
	ld hl, wDuelTempList
-.discard_loop
+.loop_hand_cards
	ld a, [hli]
	cp $ff
	jr z, .draw_cards
	call RemoveCardFromHand
-	call PutCardInDiscardPile
-	jr .discard_loop
+	call ReturnCardToDeck
+	jr .loop_hand_cards

 .draw_cards
+	call ShuffleCardsInDeck
	ld a, 7
	bank1call DisplayDrawNCardsScreen
	ld c, 7
 .draw_loop
	call DrawCardFromDeck
	jr c, .done
	call AddCardToHand
	dec c
	jr nz, .draw_loop
 .done
+; set the flag that will prevent other Trainer cards from being played
+	ld a, DUELVARS_ARENA_CARD_SUBSTATUS3
+	call GetTurnDuelistVariable
+	set SUBSTATUS3_HEADACHE_F, [hl]
	ret

It would also be a good idea to update the code that determines whether or not the AI decides to play Professor Oak since some of the logic is based around the hand being discarded rather than shuffled into the deck; however, that's somewhat outside the scope of this tutorial. If you're interested, open src/engine/duel/ai/trainer_cards.asm, and scroll down to AIDecide_ProfessorOak. Unfortunately, it's a rather long function, but you can probably ignore the subroutines at the end that handle specific decks, especially if you're editing or removing those decks for your romhack.


The final step is to handle the text changes. First, open src/text/text2.asm and remove "Headache" from the notification text.

 UnableToUseTrainerDueToHeadacheText:
	text "Unable to use a Trainer card"
-	line "due to the effects of Headache."
+	line "until your next turn."
	done

Then, open src/text/text12.asm, and edit Professor Oak's card text to match the new effect. If this causes a bank overflow, then you can try moving a few of the texts at the end of text12.asm to the beginning of text13.asm.

 ProfessorOakDescription:
-	text "Discard your hand, then draw 7"
-	line "cards."
+	text "Shuffle your hand into your deck."
+	line "Then, draw 7 cards. You cant play"
+	line "any more Trainer cards this turn."
	done

I recommend changing the card's name (under ProfessorOakName) and sprite as well, but that's up to you. I'll also reiterate that you can rename all of the labels and comments scattered throughout the disassembly if you want, but it isn't strictly necessary.