-
Notifications
You must be signed in to change notification settings - Fork 106
Points System
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.
- Point System Versions
- Creating the Full Control Version
- Printing Points on Cards
- Print Total Points in a Deck
- Use Case 1: Duelists
- Use Case 2: Hard Mode
- Use Case 3: Card Trading
- Use Case 4: Pocket TCG Style Points
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.
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 $38Now 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.
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
+ retThat 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
+ retAnd 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
retAnd 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.
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
retThe 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
retAnd 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.
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 ; $67Then 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
+ENDMWith 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_COMMANDSFinally, 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 jumpWe 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 dialougeYou 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.
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: New Game Modes. But you have a new game mode enabled, read on for how to do a hard mode using the points system.
Since the points system assigns point values to every card, ranging from 0 to 4, these points can be tallied up for the entire deck and displayed on screen. This fact can be used to create a new, more difficult mode, wherein the NPCs have no restrictions but the player's deck must be below a certain number of points. In the example above, the maximum point total was 100, so we will reference that as an example of how to do hard mode using points.
Special note: It is not recommended to have your card's point values be 5 or above. This is because the game can only display values between 0-255, with 4 as the max point value, the maximum number of points in a deck is 240, under the limit. But with 5+ it may go over the limit and cause strange graphical errors.
First to establish a points system hard mode, go to engine -> menus -> deck_configuration and add the following under DrawCardTypeIconsAndPrintCardCounts:
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
+ farcall IsHardModeEnabled ; Check if hard mode is enablded
+ jr c, .No ; if not, go to .no
+ ld hl, PointsText ; places "Points:" on the deck building screen
+ call PlaceTextItems
+ call PrintTotalPointsNumber ; Places the total number of points in the deck on the deck building screen
+ lb de, 8, 0 ; coordinates of...
+ call PrintSlashMaxPoints ; A symbol displaying the maximum number of points a deck should have
+.No
call EnableLCD
ret
+PrintSlashMaxPoints: ; Prints "/X" where x is the maximum amount of points
+ ld hl, wDefaultText
+ ld a, TX_SYMBOL
+ ld [hli], a
+ ld a, SYM_SLASH ; print /
+ ld [hli], a
+ ld a, TX_SYMBOL
+ ld [hli], a
+ ld a, SYM_1 ; print 1
+ ld [hli], a
+ ld a, TX_SYMBOL
+ ld [hli], a
+ ld a, SYM_0 ; print 0
+ ld [hli], a
+ ld a, TX_SYMBOL
+ ld [hli], a
+ ld a, SYM_0 ; print 0
+ ld [hli], a
+ ld [hl], TX_END
+ call InitTextPrinting
+ ld hl, wDefaultText
+ call ProcessText
+ retDo a similar check for hard mode in engine -> duel -> core.asm for the card page descriptions
DisplayEnergyOrTrainerCardPage:
...
...
call FillRectangle
; print the set 2 icon and rarity symbol of the card
call DrawCardPageSet2AndRarityIcons
pop hl
call PrintAttackOrNonPokemonCardDescription
+ farcall IsHardModeEnabled ; Hard mode mod
+ jr c, .No
+ call WritePoints ; print points above rarity symbol if trainer or energy.
+.No
ret
DisplayCardPage_PokemonDescription:
...
...
jr nc, .print_description
inc e ; move a line down, as the description is short enough to fit in three lines
.print_description
ld a, 19 ; line length
call InitTextPrintingInTextbox
ld hl, wLoadedCard1Description
call ProcessTextFromPointerToID
call SetOneLineSeparation
+ farcall IsHardModeEnabled ; hard mode mod
+ jr c, .No
+ call WritePoints
+.No
retSo, what just happened? Well, assuming the codebase already has the points system in place, we've created a situation where in the deck building menu and in the card descriptions menu, the game checks if hard mode has been enabled and if so, it will display the number of points in the current card or deck and a /XX in the deck building menu where X is the number of maximum points you want the player to have (in this example, 100).
All of this only occurs if hard mode is enabled, since we defined normal mode to have no restrictions, so adding points in normal mode would just be confusing for the player. With that being said, currently the code only displays visual elements of hard mode, so the next task is to force the player into abiding by the point restrictions of hard mode.
Further down in deck_configuration, we find the SaveDeckConfiguration: function. Here is where we insert some new code.
SaveDeckConfiguration:
+ farcall IsHardModeEnabled ; check if hard mode is enabled
+ jr c, .Checkif60Cards ; if not, go to the second check of if the deck has 60 cards
+ call GetTotalPointsInPlayersDeck ; Get all the points in the players deck
+ cp 100 ; Points needed, must be equal to or less to be valid.
+ jr c, .Checkif60Cards ; If < 100, go to the second check of if the deck has 60 cards
+ ldtx hl, ThisDeckHasTooManyPoints ; if >100, tell the player that they have too many points in deck
+ call DrawWideTextBox_WaitForInput
+ jr .ReturntoConfigQuestion_Points ; then go to a specific menu for points
+.Checkif60Cards
; handle deck configuration size
ld a, [wTotalCardCount]
cp DECK_SIZE
jr z, .ask_to_save_deck ; can be jr
ldtx hl, ThisIsntA60CardDeckText
call DrawWideTextBox_WaitForInput
ldtx hl, ReturnToOriginalConfigurationText
call YesOrNoMenuWithText
jr c, .print_deck_size_warning
; return no carry
add sp, $2
or a
ret
.print_deck_size_warning
ldtx hl, TheDeckMustInclude60CardsText
call DrawWideTextBox_WaitForInput
jr .go_back
; basically the same as the "not 60 card" warning above, but with text specific to points.
+.ReturntoConfigQuestion_Points
+ ldtx hl, ReturnToOriginalConfigurationText
+ call YesOrNoMenuWithText ; ask to return deck to its original configuration
+ jr c, .print_points_warning ; if "no" is selected, print a second warning and do not save deck
+; return no carry
+ add sp, $2
+ or a
+ ret
+.print_points_warning
+ ldtx hl, ThisDeckNeedsXOrLessPoints
+ call DrawWideTextBox_WaitForInput
+ jr .go_back
.ask_to_save_deck
ldtx hl, SaveThisDeckText
call YesOrNoMenuWithTextAnd then in text_offsets and another text document, add the text for all of this.
textpointer ThisDeckHasTooManyPoints
textpointer ThisDeckNeedsXOrLessPoints
ThisDeckHasTooManyPoints:
text "This deck has over 100 points!"
done
ThisDeckNeedsXOrLessPoints:
text "This deck needs 100 points or less!"
done
With this code, the game checks if hard mode is enabled; if no, go to the normal 60 card deck check from the base game. But if yes, check the player's total deck points against a number (100 in this case) and if the points value is greater, go through a series of options where the player either has to return their deck to the original configuration or the deck simply will not save. Either way, the player is now forced to abide by the point restrictions set by you, thus making it harder to build a good deck!
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 not made]
Pokemon TCG Pocket, the popular mobile app made in 2024, uses a unique system compared to the official trading card game in that players do not take prizes when KOing a pokemon, but instead take a certain number of points. When enough points are accumulated, that player wins the duel. The points system described here can be used to replicate that effect by setting all pokemon to give some points on KO and altering the win condition to being victorious after a certain number of points have been gotten.
[This section of the tutorial is not made]