-
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
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: (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 issueThis 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.
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]