5 Create turn flags to limit retreating to once per turn and make new card effects
Sha0den edited this page 2025-10-07 17:59:41 -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.

This tutorial will help you to set up turn flags. To better understand the concept of flags, you should know that every byte is made up of 8 bits. Usually, those bits are combined to represent a number between 0 and 255, but each of those bits can also be used separately as flags to record variables with only two values. More specifically, turn flags can be used to track whether a certain action has occurred during the current turn of a duel and/or prevent players from performing that action (excepting anything that references a specific play area Pokémon, as that would require using the DUELVARS_ARENA_CARD_FLAGS). Most notably, these turn flags can be used to implement once per turn restrictions on any number of actions, such as retreating, playing a Trainer/Supporter card, or even evolving a Pokémon. The first of those examples will actually be covered during this tutorial. Limiting retreating to just once per turn was one of the earliest rule changes in the Pokémon TCG (added with EX Ruby & Sapphire), and its likely something that most romhacks will want to include.

Contents

  1. Designate a WRAM Byte for Once Per Turn Flags
  2. Implement a Once Per Turn Restriction for Retreating
  3. Potential Card Effects:

1. Designate a WRAM Byte for Once Per Turn Flags

Normally, we would label an unused byte in src/wram.asm, but in this instance, it makes more sense to repurpose one of the wram bytes that already acts like a once per turn flag. wConfusionRetreatCheckWasUnsuccessful is a good option. It's already set up as a boolean, meaning that there are only 2 possible values for the byte (TRUE/FALSE), and it's reset at the start of each turn by InitVariablesToBeginTurn. wAlreadyPlayedEnergy would also work, but it's referenced a lot more than wConfusionRetreatCheckWasUnsuccessful in the AI files, so converting it into a flag would require more adjustments to the engine code. We'll also be using our new retreat flag during step 2.

Warning: If you end up using a different wram byte for turn flags, make sure that it's listed in DuelDataToSave, which can be found in src/engine/duel/core.asm. Otherwise, the flags won't work properly after resetting the game and selecting the "Continue Duel" option.

 wAlreadyPlayedEnergy:: ; cc0b
	ds $1

-; set to TRUE if the confusion check coin toss in AttemptRetreat is tails
-wConfusionRetreatCheckWasUnsuccessful:: ; cc0c
+; a series of bit flags that are reset each turn in a duel
+; see wOncePerTurnFlags constants
+wOncePerTurnFlags:: ; cc0c
	ds $1

 ; DUELIST_TYPE_* of the turn holder
 wDuelistType:: ; cc0d

Next, we'll need to set up the initial constants for our reallocated byte, so add the following to src/constants/duel_constants.asm.

 DEF DUEL_WIN  EQU $0
 DEF DUEL_LOSS EQU $1

+; wOncePerTurnFlags constants
+DEF UNABLE_TO_RETREAT_THIS_TURN_F   EQU 0
+
+DEF UNABLE_TO_RETREAT_THIS_TURN     EQU 1 << UNABLE_TO_RETREAT_THIS_TURN_F
+
 ; wPlayerDuelVariables or wOpponentDuelVariables constants
 DEF DUELVARS_CARD_LOCATIONS                   EQUS "LOW(wPlayerCardLocations)"               ; 00
 DEF DUELVARS_PRIZE_CARDS                      EQUS "LOW(wPlayerPrizeCards)"                  ; 3c

So far, we're only using the one flag (bit 0). That leaves seven more bits (1-7) that can still be used for other turn flags. The constant with "_F" represents the value of the entire byte with only that flag set. In ascending order, the hexadecimal values for each of the bits being set by themselves are: $01, $02, $04, $08, $10, $20, $40, and $80. You could also use those values to define the secondary constants, but writing it the way that we did will allow us to change the bit in the first constant without having to update the second.


Now, we need to update all of the code that referenced the original wram byte. The former retreat code only cared if wConfusionRetreatCheckWasUnsuccessful was greater than zero, so we should also zero out any unrelated flags that we might add to wOncePerTurnFlags when making a comparison; this can be achieved by replacing the or a instruction with and UNABLE_TO_RETREAT_THIS_TURN. There's one section to change in src/engine/duel/ai/retreat.asm...

; determine AI score for retreating
; return carry if AI decides to retreat
 AIDecideWhetherToRetreat:
-	ld a, [wConfusionRetreatCheckWasUnsuccessful]
-	or a
+	ld a, [wOncePerTurnFlags]
+	and UNABLE_TO_RETREAT_THIS_TURN
	jp nz, .no_carry
	xor a
	ld [wAIPlayEnergyCardForRetreat], a
	...

... and a few more in src/engine/duel/core.asm.

 DuelMenu_Retreat:
	ld a, DUELVARS_ARENA_CARD_STATUS
	call GetTurnDuelistVariable
	and CNF_SLP_PRZ
	cp CONFUSED
	ldh [hTemp_ffa0], a
	jr nz, .not_confused
-	ld a, [wConfusionRetreatCheckWasUnsuccessful]
-	or a
+	ld a, [wOncePerTurnFlags]
+	and UNABLE_TO_RETREAT_THIS_TURN
	jr nz, .unable_due_to_confusion
	call CheckAbleToRetreat
	jr c, .unable_to_retreat
	...

The edits to AttemptRetreat will be a little different since we need to set/reset the new flag rather than change the value of the entire byte. Fortunately, this function doesn't care about preserving the hl register.

 AttemptRetreat:
	call DiscardRetreatCostCards
	ldh a, [hTemp_ffa0]
	and CNF_SLP_PRZ
	cp CONFUSED
	jr nz, .success
	ldtx de, ConfusionCheckRetreatText
	call TossCoin
	jr c, .success
-	ld a, TRUE
-	ld [wConfusionRetreatCheckWasUnsuccessful], a
+	ld hl, wOncePerTurnFlags
+	set UNABLE_TO_RETREAT_THIS_TURN_F, [hl]
	scf
	ret
 .success
	ldh a, [hTempPlayAreaLocation_ffa1]
	ld e, a
	call SwapArenaWithBenchPokemon
-	xor a ; FALSE
-	ld [wConfusionRetreatCheckWasUnsuccessful], a
+	ld hl, wOncePerTurnFlags
+	res UNABLE_TO_RETREAT_THIS_TURN_F, [hl]
	ret

The final reference is in the function that resets variables at the start of each turn. You may notice that there are 2 other wram bytes being reset. wAlreadyPlayedEnergy, which we mentioned earlier, and wGotHeadsFromSandAttackOrSmokescreenCheck also function as booleans, so they could safely be added to wOncePerTurnFlags if you want to use those bytes for some other purpose. You just need to copy the above steps and change all of the references to the original bytes. Depending on the scope of your game, you might even want to create a second byte for turn flags (e.g. wMiscTurnFlags). If you do, remember to add any new flag bytes to DuelDataToSave; although, both wGotHeadsFromSandAttackOrSmokescreenCheck and wAlreadyPlayedEnergy are already on the list.

 InitVariablesToBeginTurn:
	xor a
	ld [wAlreadyPlayedEnergy], a
-	ld [wConfusionRetreatCheckWasUnsuccessful], a
+	ld [wOncePerTurnFlags], a
	ld [wGotHeadsFromSandAttackOrSmokescreenCheck], a
	ldh a, [hWhoseTurn]
	ld [wWhoseTurn], a
	ret

2. Implement a Once Per Turn Restriction for Retreating

The bulk of the retreat code for the duel engine can be found in src/engine/duel/core.asm. We'll at least need to modify DuelMenu_Retreat and AttemptRetreat, which were both already edited during Step 1, but I also recommend some minor refactoring of DuelMenu_Retreat, which will move the once per turn flag check to CheckAbleToRetreat. We'll start with the changes to DuelMenu_Retreat.

 DuelMenu_Retreat:
+	call CheckAbleToRetreat
+	jr c, .unable_to_retreat
	ld a, DUELVARS_ARENA_CARD_STATUS
	call GetTurnDuelistVariable
	and CNF_SLP_PRZ
	cp CONFUSED
	ldh [hTemp_ffa0], a
	jr nz, .not_confused
-	ld a, [wOncePerTurnFlags]
-	and UNABLE_TO_RETREAT_THIS_TURN
-	jr nz, .unable_due_to_confusion
-	call CheckAbleToRetreat
-	jr c, .unable_to_retreat
	call DisplayRetreatScreen
	jr c, .done
	ldtx hl, SelectPkmnOnBenchToSwitchWithActiveText
	call DrawWideTextBox_WaitForInput
	call OpenPlayAreaScreenForSelection
	jr c, .done
	ld [wBenchSelectedPokemon], a
	ld a, [wBenchSelectedPokemon] ; unnecessary
	ldh [hTempPlayAreaLocation_ffa1], a
	ld a, OPPACTION_ATTEMPT_RETREAT
	call SetOppAction_SerialSendDuelData
	call AttemptRetreat
	jr nc, .done
+	; retreat unsuccessful due to confusion
	call DrawDuelMainScene
-
-.unable_due_to_confusion
	ldtx hl, UnableToRetreatText
+.unable_to_retreat
	call DrawWideTextBox_WaitForInput
	jp PrintDuelMenuAndHandleInput

 .not_confused
	; note that the energy cards are discarded (DiscardRetreatCostCards), then returned
	; (ReturnRetreatCostCardsToArena), then discarded again for good (AttemptRetreat).
	; It's done this way so that the retreating Pokemon is listed with its energies updated
	; when the Play Area screen is shown to select the Pokemon to switch to. The reason why
	; AttemptRetreat is responsible for discarding the energy cards is because, if the
	; Pokemon is confused, it may not be able to retreat, so they cannot be discarded earlier.
-	call CheckAbleToRetreat
-	jr c, .unable_to_retreat
	call DisplayRetreatScreen
	jr c, .done
	call DiscardRetreatCostCards
	ldtx hl, SelectPkmnOnBenchToSwitchWithActiveText
	call DrawWideTextBox_WaitForInput
	call OpenPlayAreaScreenForSelection
	ld [wBenchSelectedPokemon], a
	ldh [hTempPlayAreaLocation_ffa1], a
	push af
	call ReturnRetreatCostCardsToArena
	pop af
	jp c, DuelMainInterface
	ld a, OPPACTION_ATTEMPT_RETREAT
	call SetOppAction_SerialSendDuelData
	call AttemptRetreat

 .done
	jp DuelMainInterface
-
-.unable_to_retreat
-	call DrawWideTextBox_WaitForInput
-	jp PrintDuelMenuAndHandleInput

Now, scroll down to CheckAbleToRetreat, and add in the check for the once per turn retreat flag. We'll also give it its own notification text.

 ; check if the turn holder's arena Pokemon is unable to retreat due to
 ; some status condition or due the bench containing no alive Pokemon.
 ; return carry if unable, nc if able.
 CheckAbleToRetreat:
+	ld a, [wOncePerTurnFlags]
+	and UNABLE_TO_RETREAT_THIS_TURN
+	ldtx hl, MayOnlyRetreatOncePerTurnText
+	jr nz, .done
	call CheckUnableToRetreatDueToEffect
	ret c
	call CheckIfActiveCardParalyzedOrAsleep
	ret c
	call HasAlivePokemonInBench
	jr c, .unable_to_retreat
	ld a, DUELVARS_ARENA_CARD
	call GetTurnDuelistVariable
	call GetCardIDFromDeckIndex
	call LoadCardDataToBuffer1_FromCardID
	ld a, [wLoadedCard1Type]
	cp TYPE_TRAINER
	jr z, .unable_to_retreat
	call CheckIfEnoughEnergiesToRetreat
	jr c, .not_enough_energies
	or a
	ret
.not_enough_energies
	ld a, [wEnergyCardsRequiredToRetreat]
	ld l, a
	ld h, $00
	call LoadTxRam3
	ldtx hl, EnergyCardsRequiredToRetreatText
	jr .done
.unable_to_retreat
	ldtx hl, UnableToRetreatText
.done
	scf
	ret

The final function is located much farther down in the file. The following modifications to AttemptRetreat might seem complicated, but a lot of that is simply removing unnecessary code. The only real change is that the once per turn retreat flag now gets set regardless of whether or not the retreat was successful.

; discard retreat cost energy cards and attempt retreat of the arena card.
; return carry if unable to retreat this turn due to unsuccessful confusion check
; if successful, the retreated card is replaced with a bench Pokemon card
AttemptRetreat:
	call DiscardRetreatCostCards
	ldh a, [hTemp_ffa0]
	and CNF_SLP_PRZ
	cp CONFUSED
	jr nz, .success
	ldtx de, ConfusionCheckRetreatText
	call TossCoin
-	jr c, .success
-	ld hl, wOncePerTurnFlags
-	set UNABLE_TO_RETREAT_THIS_TURN_F, [hl]
-	scf
-	ret
+	ccf
+	jr c, .prevent_further_retreating
 .success
	ldh a, [hTempPlayAreaLocation_ffa1]
	ld e, a
	call SwapArenaWithBenchPokemon
+.prevent_further_retreating
	ld hl, wOncePerTurnFlags
-	res UNABLE_TO_RETREAT_THIS_TURN_F, [hl]
+	set UNABLE_TO_RETREAT_THIS_TURN_F, [hl]
	ret

Before moving on, make sure you define the text that we referenced in CheckAbleToRetreat. 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.

MayOnlyRetreatOncePerTurnText:
	text "You may only retreat once per turn."
	done

The only other thing that you would normally need to consider is the AI, but in this instance, that isn't actually necessary. The AI already limits itself to only retreating once per turn, so you can stop here if you want. Although, I recommend getting rid of the redundant variable to free up the following byte in src/wram.asm.

 wAIAttackIsNonDamaging:: ; ce02
	ds $1

-; whether AI already retreated this turn or not.
-;	- $0 has not retreated;
-;	- $1 has retreated.
-wAIRetreatedThisTurn:: ; ce03
+; unused wram byte
	ds $1

 ; used by AI to store information of VenusaurLv67
 ; while handling Energy Trans logic.
 wAIVenusaurLv67DeckIndex:: ; ce04

You'll also need to delete the reference in src/engine/duel/ai/init.asm...

 InitAITurnVars:
	...
	ld [wPreviousAIFlags], a
	ld [wAITriedAttack], a
	ld [wcddc], a
-	ld [wAIRetreatedThisTurn], a

; checks if the Player used an attack last turn
; and if it was the second attack of their card.
	ld a, [wPlayerAttackingAttackIndex]
	...

... and all of the references in src/engine/duel/ai/decks/general.asm. The first few lines can simply be deleted since the once per turn retreat flag is already checked at the beginning of AIDecideWhetherToRetreat, but you'll have to set UNABLE_TO_RETREAT_THIS_TURN_F somewhere in the function to prevent the AI from retreating more than once each turn. If you keep the original timing that occurs before checking for Switch, then the AI will only switch their Active Pokémon once each turn, either by retreating or by using the Trainer card Switch. The alternative would be to set the flag after the jr nz, .used_switch line to only apply the limit after retreating and not after using a Switch card. The second option is a better application of the rules of the game, but we'll go with the first since that's how the AI logic was originally constructed. Technically, the AI only tries to switch their Active Pokémon a second time if they played a Professor Oak card, so this decision isn't terribly important.

 AIProcessRetreat:
-	ld a, [wAIRetreatedThisTurn]
-	or a
-	ret nz ; return, already retreated this turn
-
	call AIDecideWhetherToRetreat
	ret nc ; return if not retreating

	call AIDecideBenchPokemonToSwitchTo
	ret c ; return if no Bench Pokemon

 ; store Play Area to retreat to and
-; set wAIRetreatedThisTurn to true
+; set flag to prevent retreating again this turn
	ld [wAIPlayAreaCardToSwitch], a
-	ld a, TRUE
-	ld [wAIRetreatedThisTurn], a
+	ld hl, wOncePerTurnFlags
+	set UNABLE_TO_RETREAT_THIS_TURN_F, [hl]

 ; if AI can use Switch from hand, use it instead...
	ld a, AI_TRAINER_CARD_PHASE_09
	call AIProcessHandTrainerCards
	ld a, AI_TRAINER_CARD_PHASE_09
	call AIProcessHandTrainerCards
	ld a, [wPreviousAIFlags]
	and AI_FLAG_USED_SWITCH
	jr nz, .used_switch
; ... else try retreating normally.
	ld a, [wAIPlayAreaCardToSwitch]
	call AITryToRetreat
	ret

3. Potential Card Effects

Only Allow a Specific Card or Effect to Be Used Once Each Turn

One of the simplest card effects you could create would probably be for an overpowered Trainer card that prevented any other copy of that card from being played during the same turn, sort of like a weaker Supporter effect. It's worth noting that this type of restriction could also be used to limit a strong Pokémon Power, like the ones found on the Legendary Cards, but our example will be for a Trainer card.

First, you would update the wOncePerTurnFlags constants in src/constants/duel_constants.asm. Replace "CARD_NAME" with whatever the Trainer card's name happens to be (e.g. DOWSING_MACHINE), and the 7 in "EQU 7" could be any unused bit (0-7).

DEF PLAYED_CARD_NAME_THIS_TURN_F     EQU 7

DEF PLAYED_CARD_NAME_THIS_TURN       EQU 1 << PLAYED_CARD_NAME_F

Next, open src/engine/duel/effect_commands.asm. You would need at least the following two effect commands. Like before, replace "CardName" with the name of the Trainer card in the following effect commands/functions.

CardNameEffectCommands:
	dbw EFFECTCMDTYPE_INITIAL_EFFECT_1, CardName_PreviouslyPlayedCheck
	dbw EFFECTCMDTYPE_BEFORE_DAMAGE, CardNameEffect
	db  $00

Finally, define the above functions in src/engine/duel/effect_functions.asm. Depending on the actual effect of the Trainer card, you might need to merge the provided contents for CardName_PreviouslyPlayedCheck with other checks that would prevent the card from being played. Also, the "..." in CardName_Effect is where the core effect of the Trainer card would go; although, any player selection aspects would probably go in another effect function/command. Remember to define the notification text as well (AlreadyPlayedCardNameText); see the How to add a new text tutorial for more details.

; returns carry if another copy of this card was already played this turn
CardName_PreviouslyPlayedCheck:
	ld a, [wOncePerTurnFlags]
	and PLAYED_CARD_NAME_THIS_TURN
	ret z
	ldtx hl, AlreadyPlayedCardNameText
	scf
	ret

CardName_Effect:
	ld hl, wOncePerTurnFlags
	set PLAYED_CARD_NAME_THIS_TURN_F, [hl]
	...
	ret

Forbid Player from Attacking or Using Trainers for the Rest of the Turn

You could also use turn flags to keep the player from performing certain actions, like with the secondary effects of Geeta, which prevents the player from attacking, or PokéGear/Professor Elm/Time Capsule, which all prevent other Trainer cards from being used during the same turn. These examples from the TCG are all Trainer cards, but you could just as easily apply this type of effect to a Pokémon Power.

First, you would update the wOncePerTurnFlags constants in src/constants/duel_constants.asm. The numbers for "EQU 6" and "EQU 7" can be any unused bits (0-7).

DEF UNABLE_TO_ATTACK_THIS_TURN_F       EQU 6
DEF UNABLE_TO_USE_TRAINERS_THIS_TURN_F EQU 7

DEF UNABLE_TO_ATTACK_THIS_TURN         EQU 1 << UNABLE_TO_ATTACK_THIS_TURN_F
DEF UNABLE_TO_USE_TRAINERS_THIS_TURN   EQU 1 << UNABLE_TO_USE_TRAINERS_THIS_TURN_F

Either of these flags would be set by the main effect function for the Trainer card or Pokémon Power with the restriction, just like with the previous example. That's usually the function linked with EFFECTCMDTYPE_BEFORE_DAMAGE. Just open src/engine/duel/effect_functions.asm, and add the appropriate 2 lines to the beginning of your effect function.

	ld hl, wOncePerTurnFlags
	set UNABLE_TO_ATTACK_THIS_TURN_F, [hl]
	ld hl, wOncePerTurnFlags
	set UNABLE_TO_USE_TRAINERS_THIS_TURN_F, [hl]

Most prevention effects are handled by functions in src/home/substatus.asm. Modify HandleCantAttackSubstatus if you're adding UNABLE_TO_ATTACK_THIS_TURN_F...

-; return carry if the turn holder's arena Pokemon is under a condition that makes
-; it unable to attack. also return in hl the text id to be displayed
+; return carry if an effect is preventing the turn holder's Active Pokémon from attacking
+; with text ID for corresponding notification text in hl.
+; preserves bc and de
HandleCantAttackSubstatus::
+	ld a, [wOncePerTurnFlags]
+	bit UNABLE_TO_ATTACK_THIS_TURN_F, a
+	ldtx hl, UnableToAttackThisTurnText
+	jr nz, .return_with_cant_attack
	ld a, DUELVARS_ARENA_CARD_SUBSTATUS2
	call GetTurnDuelistVariable
	or a
	ret z
	ldtx hl, UnableToAttackDueToTailWagText
	cp SUBSTATUS2_TAIL_WAG
	jr z, .return_with_cant_attack
	ldtx hl, UnableToAttackDueToLeerText
	cp SUBSTATUS2_LEER
	jr z, .return_with_cant_attack
	ldtx hl, UnableToAttackDueToBoneAttackText
	cp SUBSTATUS2_BONE_ATTACK
	jr z, .return_with_cant_attack
	or a
	ret
.return_with_cant_attack
	scf
	ret

... and CheckCantUseTrainerDueToEffect if you're adding UNABLE_TO_USE_TRAINERS_THIS_TURN_F.

-; return carry if the turn holder is affected by Headache and trainer cards can't be used
+; return carry if an effect is preventing the turn holder from playing Trainer cards
+; with text ID for corresponding notification text in hl.
+; preserves bc and de
 CheckCantUseTrainerDueToEffect::
+	ld a, [wOncePerTurnFlags]
+	bit UNABLE_TO_USE_TRAINERS_THIS_TURN_F, a
+	ldtx hl, UnableToUseAnotherTrainerThisTurnText
+	jr nz, .set_carry
	ld a, DUELVARS_ARENA_CARD_SUBSTATUS3
	call GetTurnDuelistVariable
	or a
	bit SUBSTATUS3_HEADACHE_F, [hl]
	ret z
	ldtx hl, UnableToUseTrainerDueToHeadacheText
+.set_carry
	scf
	ret

The final step is to define the newly referenced text(s). See the How to add a new text tutorial for more details. If your flag is used for several different effects, then you'll have to write something more generic, like these:

UnableToAttackThisTurnText:
	text "You are no longer able to attack."
	line "this turn."
	done

UnableToUseAnotherTrainerThisTurnText:
	text "You can't play any other Trainer"
	line "cards for the rest of the turn."
	done

However, if the flag is only tied to a single effect, then you might consider adjusting the labels and contents of the text to reference the source of the restriction, like so:

UnableToAttackDueToGeetaText:
	text "You are unable to attack"
	line "due to the effect of Geeta."
	done

UnableToUseTrainerDueToProfessorElmText:
	text "You can't play any other Trainer"
	line "cards after using Professor Elm."
	done

Increased Attack Damage if a Specific Card Was Played That Turn

Quite a few Pokémon cards in the TCG possess attacks which deal increased damage if a specific card (generally a Supporter) was played during the same turn. These can be implemented rather easily using a turn flag. Let's see if we can recreate the Tactful Tangling attack on Tangela (Scarlet & Violet—151).

First, you would update the wOncePerTurnFlags constants in src/constants/duel_constants.asm. Like with the previous examples, the 7 in "EQU 7" could be any unused bit (0-7).

DEF PLAYED_ERIKAS_INVITATION_THIS_TURN_F EQU 7

DEF PLAYED_ERIKAS_INVITATION_THIS_TURN   EQU 1 << PLAYED_ERIKAS_INVITATION_THIS_TURN_F

Next, open src/engine/duel/effect_commands.asm, and add the following effect.

TangelaTactfulTanglingEffectCommands:
	dbw EFFECTCMDTYPE_BEFORE_DAMAGE, TactfulTanglingEffect
	dbw EFFECTCMDTYPE_AI, TactfulTangling_AIEffect
	db  $00

Then, define the above functions in src/engine/duel/effect_functions.asm. We'll implement the attack effect exactly as it's written, but you should probably consider lowering the damage boost if you're keeping HP values similar to what's already in the original game.

; increases attack damage by 60 if Erika's Invitation was played this turn
TactfulTanglingEffect:
	ld a, [wOncePerTurnFlags]
	and PLAYED_ERIKAS_INVITATION_THIS_TURN
	ret z ; cancel effect if Erika's Invitation wasn't played this turn
	ld a, 60
	jp AddToDamage

TactfulTangling_AIEffect:
	call TactfulTanglingEffect
	jp SetDefiniteAIDamage

You'll also need to set the new turn flag in the main effect function for the Erika's Invitation card (listed after EFFECTCMDTYPE_BEFORE_DAMAGE in effect commands). You would obviously need to add the card to the game first. Suffice it to say that this is beyond the parameters of the current example, but if you would like to do so, then you would most likely be able to base its effect commands/functions off of the ones for Pokémon Flute. After creating the card, you would add the following lines to the beginning of its final effect function.

 ErikasInvitation_PlaceInPlayEffect:
+	ld hl, wOncePerTurnFlags
+	set PLAYED_ERIKAS_INVITATION_THIS_TURN_F, [hl]
	...
	ret

Finally, create the attack data for Tactful Tangling in src/data/cards.asm...

	; attack 1
	energy GRASS, 1 ; energies
	tx TactfulTanglingName ; name
	tx TactfulTanglingDescription ; description
	dw NONE ; description (cont)
	db 10 ; damage
	db DAMAGE_PLUS ; category
	dw TangelaTactfulTanglingEffectCommands ; effect commands
	db NONE ; flags 1
	db NONE ; flags 2
	db NONE ; flags 3
	db 0
	db ATK_ANIM_WHIP ; animation

...and then define the texts for the attack name and description. See the How to add a new text tutorial for more details.

TactfulTanglingName:
	text "Tactful Tangling"
	done

TactfulTanglingDescription:
	text "If you played Erikas Invitation"
	line "from your hand during this turn,"
	line "this attack does 60 more damage."
	done