Cataclyptic
Everything I say is a lie
- 199
- Posts
- 10
- Years
- United States of America
- Seen yesterday
When hacking poketcg, there is an annoying problem that myself and others keep running into: The inability for the AI to understand and process multiple energy types combined with colorless energy. In the official game this never comes up because the game only uses single energy types + colorless. But for many hacks out there, including my own, we want to expand the range of options for our cards by adding multiple energy types per attack, plus colorless.
I will post the code for how the AI determines what to do down below, but for now I want to give examples of how this works.
Attack 1 cost: 1 Lightning, 1 Colorless = No issue, AI can understand.
Attack 2 cost: 1 Lightning, 1 Fire = No issue, AI can understand.
Attack 3 cost: 1 Lightning, 1 Fire, 1 Colorless = The AI cannot understand; in game it cheats by doing the attack with only 1 fire and 1 lightning attached, ignoring the colorless.
Attack 4 cost: 1 Lightning, 1 Fire, 2 Colorless = The AI cannot understand; in game it cheats by doing the attack with only 1 fire, 1 lightning, and 1 of any energy.
Hopefully these examples mae it clear that two energies + colorless = the AI ignores 1 colorless in the cost of the attack.
I have tried to impliment a new code o correct this but I couldn't do it, so I am throwing it into thr wilds in hopes that someone else can.
This code is found in src > Engine > Duel > Ai > Core at line 385-486. Also contains a note from the pret developers detailling this issue.
CheckEnergyNeededForAttack:
ldh a, [hTempPlayAreaLocation_ff9d]
add DUELVARS_ARENA_CARD
call GetTurnDuelistVariable
ld d, a
ld a, [wSelectedAttack]
ld e, a
call CopyAttackDataAndDamage_FromDeckIndex
ld hl, wLoadedAttackName
ld a, [hli]
or [hl]
jr z, .no_attack
ld a, [wLoadedAttackCategory]
cp POKEMON_POWER
jr nz, .is_attack
.no_attack
lb bc, 0, 0
ld e, c
scf
ret
.is_attack
ldh a, [hTempPlayAreaLocation_ff9d]
ld e, a
call GetPlayAreaCardAttachedEnergies
bank1call HandleEnergyBurn
xor a
ld [wTempLoadedAttackEnergyCost], a
ld [wTempLoadedAttackEnergyNeededAmount], a
ld [wTempLoadedAttackEnergyNeededType], a
ld hl, wAttachedEnergies
ld de, wLoadedAttackEnergyCost
ld b, 0
ld c, (NUM_TYPES / 2) - 1
.loop
; check all basic energy cards except colorless
ld a, [de]
swap a
call CheckIfEnoughParticularAttachedEnergy
ld a, [de]
call CheckIfEnoughParticularAttachedEnergy
inc de
dec c
jr nz, .loop
; running CheckIfEnoughParticularAttachedEnergy back to back like this
; overwrites the results of a previous call of this function,
; however, no attack in the game has energy requirements for two
; different energy types (excluding colorless), so this routine
; will always just return the result for one type of basic energy,
; while all others will necessarily have an energy cost of 0
; if attacks are added to the game with energy requirements of
; two different basic energy types, then this routine only accounts
; for the type with the highest index
; colorless
ld a, [de]
swap a
and %00001111
ld b, a ; colorless energy still needed
ld a, [wTempLoadedAttackEnergyCost]
ld hl, wTempLoadedAttackEnergyNeededAmount
sub [hl]
ld c, a ; basic energy still needed
ld a, [wTotalAttachedEnergies]
sub c
sub b
jr c, .not_enough
ld a, [wTempLoadedAttackEnergyNeededAmount]
or a
ret z
; being here means the energy cost isn't satisfied,
; including with colorless energy
xor a
.not_enough
cpl
inc a
ld c, a ; colorless energy still needed
ld a, [wTempLoadedAttackEnergyNeededAmount]
ld b, a ; basic energy still needed
ld a, [wTempLoadedAttackEnergyNeededType]
call ConvertColorToEnergyCardID
ld e, a
ld d, 0
scf
ret
; takes as input the energy cost of an attack for a
; particular energy, stored in the lower nibble of a
; if the attack costs some amount of this energy, the lower nibble of a != 0,
; and this amount is stored in wTempLoadedAttackEnergyCost
; sets carry flag if not enough energy of this type attached
; input:
; a = this energy cost of attack (lower nibble)
; [hl] = attached energy
; output:
; carry set if not enough of this energy type attached
I will post the code for how the AI determines what to do down below, but for now I want to give examples of how this works.
Attack 1 cost: 1 Lightning, 1 Colorless = No issue, AI can understand.
Attack 2 cost: 1 Lightning, 1 Fire = No issue, AI can understand.
Attack 3 cost: 1 Lightning, 1 Fire, 1 Colorless = The AI cannot understand; in game it cheats by doing the attack with only 1 fire and 1 lightning attached, ignoring the colorless.
Attack 4 cost: 1 Lightning, 1 Fire, 2 Colorless = The AI cannot understand; in game it cheats by doing the attack with only 1 fire, 1 lightning, and 1 of any energy.
Hopefully these examples mae it clear that two energies + colorless = the AI ignores 1 colorless in the cost of the attack.
I have tried to impliment a new code o correct this but I couldn't do it, so I am throwing it into thr wilds in hopes that someone else can.
This code is found in src > Engine > Duel > Ai > Core at line 385-486. Also contains a note from the pret developers detailling this issue.
Spoiler:
CheckEnergyNeededForAttack:
ldh a, [hTempPlayAreaLocation_ff9d]
add DUELVARS_ARENA_CARD
call GetTurnDuelistVariable
ld d, a
ld a, [wSelectedAttack]
ld e, a
call CopyAttackDataAndDamage_FromDeckIndex
ld hl, wLoadedAttackName
ld a, [hli]
or [hl]
jr z, .no_attack
ld a, [wLoadedAttackCategory]
cp POKEMON_POWER
jr nz, .is_attack
.no_attack
lb bc, 0, 0
ld e, c
scf
ret
.is_attack
ldh a, [hTempPlayAreaLocation_ff9d]
ld e, a
call GetPlayAreaCardAttachedEnergies
bank1call HandleEnergyBurn
xor a
ld [wTempLoadedAttackEnergyCost], a
ld [wTempLoadedAttackEnergyNeededAmount], a
ld [wTempLoadedAttackEnergyNeededType], a
ld hl, wAttachedEnergies
ld de, wLoadedAttackEnergyCost
ld b, 0
ld c, (NUM_TYPES / 2) - 1
.loop
; check all basic energy cards except colorless
ld a, [de]
swap a
call CheckIfEnoughParticularAttachedEnergy
ld a, [de]
call CheckIfEnoughParticularAttachedEnergy
inc de
dec c
jr nz, .loop
; running CheckIfEnoughParticularAttachedEnergy back to back like this
; overwrites the results of a previous call of this function,
; however, no attack in the game has energy requirements for two
; different energy types (excluding colorless), so this routine
; will always just return the result for one type of basic energy,
; while all others will necessarily have an energy cost of 0
; if attacks are added to the game with energy requirements of
; two different basic energy types, then this routine only accounts
; for the type with the highest index
; colorless
ld a, [de]
swap a
and %00001111
ld b, a ; colorless energy still needed
ld a, [wTempLoadedAttackEnergyCost]
ld hl, wTempLoadedAttackEnergyNeededAmount
sub [hl]
ld c, a ; basic energy still needed
ld a, [wTotalAttachedEnergies]
sub c
sub b
jr c, .not_enough
ld a, [wTempLoadedAttackEnergyNeededAmount]
or a
ret z
; being here means the energy cost isn't satisfied,
; including with colorless energy
xor a
.not_enough
cpl
inc a
ld c, a ; colorless energy still needed
ld a, [wTempLoadedAttackEnergyNeededAmount]
ld b, a ; basic energy still needed
ld a, [wTempLoadedAttackEnergyNeededType]
call ConvertColorToEnergyCardID
ld e, a
ld d, 0
scf
ret
; takes as input the energy cost of an attack for a
; particular energy, stored in the lower nibble of a
; if the attack costs some amount of this energy, the lower nibble of a != 0,
; and this amount is stored in wTempLoadedAttackEnergyCost
; sets carry flag if not enough energy of this type attached
; input:
; a = this energy cost of attack (lower nibble)
; [hl] = attached energy
; output:
; carry set if not enough of this energy type attached