Skip to content
RealCataclyptic edited this page May 30, 2026 · 3 revisions

This tutorial will allow you to assign a point score to every card in the game and reference either individual cards or all cards in the deck. By doing this, one can create several fun scenarios in a game, like implementing alternate game modes based on point limitations, disallowing duels unless the deck contains X points or less/more, and so much more because the system is so flexible.

Note that this tutorial uses base poketcg as its code source, if you are using another base then some lines may need to be edited.

poketcg poketcg9 poketcg5 poketcg2

Contents

  1. Point System Versions
  2. Creating the Full Control Version
  3. Printing Points on Cards
  4. Print Total Points in a Deck
  5. Use Case 1: Duelists
  6. Use Case 2: Hard Mode
  7. Use Case 3: Card Trading

0. Point System Versions

There are two ways to create a point system in poketcg. The first system is the Full Control version, which allows you to assign any point value on any card, with no restrictions. This version is the harder of the two to implement because it requires adding a new variable to card data in order for it to work.

The second system is the Rarity version. This one is the easier of the two to make, it essentially assigns all cards of X rarity X points. All commons for instance would be worth 1 point, all uncommons 2, all rares 4, or whatever value you wish. The main limitation of this system is that you cannot alter points between rarities; all commons must be worth exactly 1 point with no variation, for instance.

If you wish to use the Full Control System, proceed to step 1. If you wish to use the Rarity system, proceed to step 2 instead.


1. Creating the Full Control Version

In order to maximize the most space and to ensure nigh universal compatibility between bases, the full control version will utilize the CARD_DATA_UNUSED byte that is already present in the code. Note: At any point in this tutorial, you may change the name of the Unused byte to whatever you want, but this tutorial will stick with the original "unused" name.

Warning: The following actions will increase the size of cards.asm. If you are using the base version of poketcg, you will have to delete 1 card from cards.asm in order for the data to fit. (Imakuni? is the reccomended card to delete) This warning does not apply if you can add an additional cards2.asm or any base which has that.

Go to constants -> card_data_constants, find the unused byte and delete it:

; TYPE_PKMN card only
DEF CARD_DATA_RETREAT_COST          EQU $32
DEF CARD_DATA_WEAKNESS              EQU $33
DEF CARD_DATA_RESISTANCE            EQU $34
DEF CARD_DATA_CATEGORY              EQU $35
DEF CARD_DATA_POKEDEX_NUMBER        EQU $37
- DEF CARD_DATA_UNUSED                EQU $38
...

Then go up above to the 'all cards types' section and place it back in just below rarity:

; all card types
DEF CARD_DATA_TYPE                  EQU $00
DEF CARD_DATA_GFX                   EQU $01
DEF CARD_DATA_NAME                  EQU $03
DEF CARD_DATA_RARITY                EQU $05
+DEF CARD_DATA_UNUSED                EQU $38

Now that the card data definition has been moved, we now go to data -> cards.asm in order to move all instances of the unused byte from below pokedex number to below rarity. This is incredibly tedious to do by hand, so it recommended to instead do the following:

Using VS code or a similar code modifier tool, highlight both "; pokedex number" and the db 0 below it, then right click and select "change all occurrences", replace it with just "; pokedex number" to delete the db 0. Next, using the same logic, highlight ";rarity", select "change all occurrences" and then replace it with "; rarity", enter and "db 1". This will make all card point values 1 for now and in the correct position for the game to process our new code.

This is done to ensure that ALL cards can be assigned point values, not just pokemon cards. If this is something you don't care about and only want to assign points to pokemon, you may skip the above steps. If not, you will have to delete 1 card from cards.asm if using base poketcg as stated in the warning.


2. Printing Points on Cards

Regardless of if you did step 1 or wanted to do the rarity version, they both converge here. You now have points that can be assigned to cards, however the player will never know it because it won't show up. So our next task is to make it clear which cards are worth what points.

First, go to both text_offsets and a free text document and make a points sign, this tutorial's looks like this:

PointsText:
	text "Points:"
	done

Next, go to engine -> duel -> core.asm and put in the following:

PointsText2: ; core version
+	textitem 15, 6, PointsText ; coordinates of where we want "points:" to be
+	db $ff
+	ret```

And then we will create the WritePoints code.

```diff
+; Full Control Version
+WritePoints:
+	ld hl, PointsText2      ; Loads the core version of the text we want
+	call PlaceTextItems     
+	lb bc, 15, 7 		; prints point value above the rarity symbol at these coordinates
+	ld a, [wLoadedCard1Unused]  ; Loads the value in "unused", AKA points
+	call WriteTwoDigitNumberInTxSymbolFormat ; And writes it in text format
+	ret

That is the full control version. If you are using the rarity version, instead you will write it like this:

; Rarity Version
+WritePoints: ; POINTS MOD, Full control and rarity based version
+	ld hl, PointsText2
+	call PlaceTextItems 
+	lb bc, 15, 7 		; same as above code
+	ld a, [wLoadedCard1Rarity] ; loads card rarity
+	cp CIRCLE               ; check if it's a circle card
+	jr nz, .CheckUncommon   ; if not go to check if it's an uncommon
+	ld a, 1                 ; if yes, load 1 (point)
+	jr .FoundRarity         ; and go to the end where it can written
+.CheckUncommon                 ; repeat until all card rarities have been tested
+	cp DIAMOND
+	jr nz, .CheckRare
+	ld a, 2
+	jr .FoundRarity 
+.CheckRare
+	cp STAR
+	jr nz, .CheckPromo
+	ld a, 3
+	jr .FoundRarity 
+.CheckPromo
+	ld a, 4
+.FoundRarity
+	call WriteTwoDigitNumberInTxSymbolFormat
+	ret

And then we need to find a good place to report these values. For this tutorial, they will be printed on the last page of a pokemon card and the first page of a trainer or energy card.

DisplayEnergyOrTrainerCardPage:
	...
        ...
	call DrawCardPageSet2AndRarityIcons
	pop hl
	call PrintAttackOrNonPokemonCardDescription
+	call WritePoints 
        ret


DisplayCardPage_PokemonDescription:
        ...
        ...
.print_description
	ld a, 19 ; line length
	call InitTextPrintingInTextbox
	ld hl, wLoadedCard1Description
	call ProcessTextFromPointerToID
	call SetOneLineSeparation
+	call WritePoints
	ret

And there you have it, every card in the game will now display a point value in an area that easy to access for the player. However, there's one more useful thing we can print for the player: total amount of points in the current deck.


3. Print Total Points in a Deck

Go to engine -> menus -> deck_configuration and put in the following code to tally up all the points in the current player's deck:

; Full control version
GetTotalPointsInPlayersDeck:
	push hl
	push bc
	push de
	ld hl, wCurDeckCards
	ld c, 0
.LoopCards
	ld a, [hli]
	ld e, a
;	ld a, [hli] 	; Uncomment if using the Extended Base
;	ld d, a		; Uncomment if using the Extended Base
	or e
	jr z, .done
	call LoadCardDataToBuffer1_FromCardID
	ld a, [wLoadedCard1Unused] 
	add c
	ld c, a
	jr .LoopCards
.done
	ld a, c
	pop de
	pop bc
	pop hl
	ret

Or, if you're using the rarity version,

; Rarity version
GetTotalPointsInPlayersDeck: 
	push hl
	push bc
	push de
	ld hl, wCurDeckCards
	ld c, 0
.LoopCards
	ld a, [hli]
	ld e, a
;	ld a, [hli]	; Uncomment if using the Extended Base
;	ld d, a		; Uncomment if using the Extended Base
	or e
	jr z, .done
	call LoadCardDataToBuffer1_FromCardID
	ld a, [wLoadedCard1Rarity]
	cp CIRCLE
	jr nz, .CheckUncommon
	ld a, 1
	jr .FoundRarity 
.CheckUncommon
	cp DIAMOND
	jr nz, .CheckRare
	ld a, 2
	jr .FoundRarity 
.CheckRare
	cp STAR
	jr nz, .CheckPromo
	ld a, 3
	jr .FoundRarity 
.CheckPromo
	ld a, 4
.FoundRarity 
	add c
	ld c, a
	jr .LoopCards
.done
	ld a, c
	pop de
	pop bc
	pop hl
	ret

Now that we've put in functions that can get the total number of points in a player's deck, it's time to report them to the player.

; prints the total number of points in a deck at coordinates bc
+PrintTotalPointsNumber: 
+	call GetTotalPointsInPlayersDeck
+	lb bc, 5, 0
+	bank1call WriteTwoByteNumberInTxSymbolFormat
+	ret

+"prints points:"
+PointsText: 
+	textitem 1, 0, PointsText
+	db $ff
+	ret


DrawCardTypeIconsAndPrintCardCounts:
	call Set_OBJ_8x8
	call PrepareMenuGraphics
	lb bc, 0, 5
	ld a, SYM_BOX_TOP
	call FillBGMapLineWithA
	call DrawCardTypeIcons		
	call PrintCardTypeCounts 	
	lb de, 15, 0			
	call PrintTotalCardCount	
	lb de, 17, 0			
	call PrintSlashSixty		

+	ld hl, PointsText 		; Places the text "points:" in a specified location
+	call PlaceTextItems			
+	call PrintTotalPointsNumber     ; prints total points in this space here
        call EnableLCD
	ret

The UI for the deck configuration menu will now display the total number of points in the current deck. Now this on its own is fine, but we can go even further by causing the point values to update in real time as the player adds and removes cards from the deck. To do this, add the following:

AddCardToDeckAndUpdateCount:
	call TryAddCardToDeck
	ret c ; failed to add card
	push de
	call PrintCardTypeCounts
	lb de, 15, 0
	call PrintTotalCardCount
+	call PrintTotalPointsNumber
	pop de
	call GetCountOfCardInCurDeck
	call PrintNumberValueInCursorYPos
	ret


RemoveCardFromDeckAndUpdateCount:
	call RemoveCardFromDeck
	ret nc
	push de
	call PrintCardTypeCounts
	lb de, 15, 0
	call PrintTotalCardCount
+	call PrintTotalPointsNumber 
	pop de
	call GetCountOfCardInCurDeck
	call PrintNumberValueInCursorYPos
	ret

And that's it for the basic system. Doing all of the above will create a points system and make it clear visually to the player how many points a given card is worth and how many points the current deck has. However, right now all of this is just visuals with no substance- the points still don't really do anything.

You can of course use this points to do whatever you want by referencing the new functions, but this tutorial will now diverge a little bit to explore some things one can do with the points system.


4. Use Case 1: Duelists

What if certain NPC duelists won't duel you if your deck's point total is too high? Or too low? This section will create that situation, allowing for diversity among the NPCs in your game.

First, we need to create a new script command that allows the game to calculate the number of points in the current deck, and then jump to a label if it's greater than a certain number. In order to save space, we will overwrite one of the unused "end scripts" and suit it for our purposes. Go to macros -> scripts.asm and go to the bottom of the script_command list.

const ScriptCommand_JumpIfEventFalse_index                               ; $62
	const ScriptCommand_IncrementEventValue_index                            ; $63
-       const ScriptCommand_EndScript7_index                                     ; $64
+	const ScriptCommand_JumpIfEnoughDeckPoints_index                         ; $64
	const ScriptCommand_EndScript8_index                                     ; $65
	const ScriptCommand_EndScript9_index                                     ; $66
	const ScriptCommand_EndScript10_index                                    ; $67

Then we write the actual script. Remember to place it in the same spot as the constant, so in this case we have to write it underneath IncrementEventEalue_index.

; Increments given event's value (truncates the new value)
MACRO increment_event_value
	run_command ScriptCommand_IncrementEventValue
	db \1 ; event (ex EVENT_IMAKUNI_WIN_COUNT)
ENDM

+; Jumps to a label if points are equal to or greater than a value
+MACRO Jump_If_Enough_Deck_Points
+	run_command ScriptCommand_JumpIfEnoughDeckPoints
+	db \1 ; amount of points needed
+	dw \2 ; script label
+ENDM

With the macro defined, go to data -> script_table.asm and define it there too. Again, place it in the exact position the game expects it to be.

...
dw ScriptCommand_JumpIfEventFalse
	dw ScriptCommand_IncrementEventValue
-       dw ScriptCommand_EndScript
+	dw ScriptCommand_JumpIfEnoughDeckPoints
	dw ScriptCommand_EndScript
	dw ScriptCommand_EndScript
	dw ScriptCommand_EndScript
	assert_table_length NUM_SCRIPT_COMMANDS

Finally, go to engine -> overworld -> scripting.asm where we tell the script what we need it to do. Once again, define it where the game expects it to be.

ScriptCommand_IncrementEventValue:
	ld a, c
	push af
	call GetEventValue
	inc a
	ld c, a
	pop af
	call SetEventValue
	jp IncreaseScriptPointerBy2

+ScriptCommand_JumpIfEnoughDeckPoints:
+  	push bc
+  	farcall GetTotalPointsInPlayersDeck ; gets total points in the current player's deck
+  	pop bc
+ 	cp c           ; Subtracts from a define number c in the script
+ 	jp c, ScriptCommand_JumpIfEventFalse.fail      ; if it's less, go to the false jump
+	jp ScriptCommand_JumpIfEventTrue.pass_try_jump  ; otherwise go to the true jump

We can now use this command however we want. As an example, let's pick on Jennifer from the Lightning Club. As in the photo above, let's say that if you have over 100 points in your deck, she'll refuse to duel you. To set this up, go to her script in scripts -> lightning_club.asm.

Script_Jennifer:
	start_script
	print_npc_text Text061b      ; "... "Hey, do you want to duel my Pikachu Deck?"
	ask_question_jump Text061c, .ows_e415     ; Ask yes or no, if yes, jump to .ows_e415
	print_npc_text Text061d
	quit_script_fully

.ows_e415 
; The macro now process the number next to it and stores it in c to cp it as a test.
	Jump_If_Enough_Deck_Points 100, .TooManyPoints  ; If player's deck points >100, go to .Too Many Points
	print_npc_text Text061e
	start_duel PRIZES_4, PIKACHU_DECK_ID, MUSIC_DUEL_THEME_1   ; otherwise print normal duel text and start the duel.
	quit_script_fully

.TooManyPoints
	print_npc_text TooManyPoints   ; "Hey! Your deck has over 100 points! I won't duel you!"
	quit_script_fully              ; End the dialouge

You can also switch around the logic of this jump in order to make an NPC duel you if your deck has below 100 points in this way.


5. Use Case 2: Hard Mode

We all know that the NPCs in this game aren't very hard to beat. Yes we can give them better decks, but the AI can only do so much even with the sequel's improvements.

So what if instead, it was possible to make the game harder via another axis? Using the points system, it becomes possible to limit how good the player's deck can be by capping it at a maximum number of points! In order to not make this tutorial too long, implementing a hard mode has been done in a different tutorial, found here: (LINK GOES HERE WHEN IM DONE WITH IT)

To give a brief summary though, the important thing is to go back into deck_configuration.asm and then into SaveDeckConfiguration:. Then,

+call GetTotalPointsInPlayersDeck
+	cp 100		       ; Points needed, must be equal to or less to be valid.
+	jr c, .Checkif60Cards  ; If valid, proceed to checking if the deck is 60 cards exactly.
+	ldtx hl, ThisDeckHasTooManyPoints  ; if not, print a message saying there's too many points
+	call DrawWideTextBox_WaitForInput
+	jr .ReturntoConfigQuestion_Points   ; Go to a new label to solve this issue

This is the easiest way to do it; by having the deck refuse to save until the point value is less than or equal to a defined number. Of course you'll have to define new code to handle this routine and create new graphics so that the player can know what the point limit is, but that will be covered in the New Game Mode tutorial example.


6. Use Case 3: Card Trading

The points system can also be used as a type of in game currency. For example, it is possible for an NPC to remove cards from the player's collection and store the total point value of those cards. Then, if the player has enough points, they can purchase a booster pack, or perhaps a rare card.

[This section of the tutorial is incomplete]

Clone this wiki locally