-
Notifications
You must be signed in to change notification settings - Fork 993
Adding an In Game Trade
Hello! I've been playing around with these a lot, so I've made a guide to adding and improving the In-Game Trades in pokered. This covers everything from adding entries to reworking certain bits that may make them a better experience.
Trades are stored in data\events\trades.asm
. Adding them is as simple as replacing or filling out another entry. The first Pokemon is what's given, and the second is what's received. The dialogue set refers to the set of text the NPC will use. The last column is for the nickname the Pokemon will have; any empty spaces must be filled out with @s.
TradeMons:
; entries correspond to TRADE_FOR_* constants
table_width 3 + NAME_LENGTH, TradeMons
...
db NIDORINO, NIDORINA, TRADE_DIALOGSET_CASUAL, "TERRY@@@@@@"
+ db MON_NAME, MON_NAME, TRADE_DIALOGSET_X, "NICK@@@@@@@"
...
assert_table_length NUM_NPC_TRADES
Now that you have this all set up, you'll need to add the trade to the index. The constants here correspond with the entry's position in data\events\trades.asm
, so make sure they're in line!
To add a new trade...
; in game trades
; TradeMons indexes (see data/events/trades.asm)
const_def
const TRADE_FOR_TERRY
+ const TRADE_FOR_NAMEHERE ; it could just be anything, but uniformity is nice.
DEF NUM_NPC_TRADES EQU const_value
Adding a Trade NPC goes roughly the same as adding a standard NPC. However, because they all have their text pre-defined, it's a lot simpler in my experience.
In your appropriate maps\objects\map_name.asm
file...
def_object_events
object_event 2, 3, SPRITE_GENTLEMAN, STAY, LEFT, 1, OPP_GENTLEMAN, 1
+ object_event 4, 4, SPRITE_GENTLEMAN, STAY, LEFT, TextPointerIDHere ; ideally a comment telling you it's your trader
Adjust the position on the map and such as needed. I recommend doing this with Polished Map.
And then, in your appropriate scripts\map_name.asm
file...
MapName_TextPointers:
...
+ dw NewTrade
...
+NewTrade:
+ text_asm
+ ld a, TRADE_FOR_NAMEHERE ; This is the constant you added earlier!
+ ld [wWhichTrade], a
+ predef DoInGameTradeDialogue
+ jp TextScriptEnd
I recommend placing it at the end, just so it's easier to go back to if something goes wrong. Make sure the NPC's Text Pointer ID corresponds to its position in Text Pointers. If it's the 5th entry in the list, then the ID is 5, and so on.
There are quite a few drawbacks to In-Game Trades that make them difficult to work with and even a controversial gameplay experience. Therefore, the following extras may be enticing to implement.
Due to the way RBY loads trades, the game cannot include load entries beyond 16, returning error messages instead. Like an angel from the heavens, Chatot4444 found a fix for this, so I'm adding it here for anyone else who has similar troubles.
The loading of trades beyond 16 entries doesn't work because the game multiplies the entry's number in trades.asm
by 14 to get the right spot. It's a quicker method, but if you want to store more, you need to do it without using swap a
.
Therefore, the following needs to be done in engine\events\in_game_trades.asm
DoInGameTradeDialogue:
; trigger the trade offer/action specified by wWhichTrade
call SaveScreenTilesToBuffer2
ld hl, TradeMons
ld a, [wWhichTrade]
- ld b, a
- swap a
- sub b
- sub b
- ld c, a
ld b, 0
- add hl, bc
+ ld c, 3 + NAME_LENGTH ; new code from Chatot4444, this bypasses the 16-limit on in-game trades.
+ call AddNTimes ; Also from chatot4444
ld a, [hli]
ld [wInGameTradeGiveMonSpecies], a
ld a, [hli]
With that, you can store more than 16.
Credit goes to Chatot4444 for figuring this bit out for me. Lifesaver.
In Pokemon Red and Blue, Pokemon traded through in-game trades won't evolve. If you're adding an in-game trade, you're most likely looking for this exact thing to work around it. If I'm right, luckily for you, this can be easily worked around. The following fix was taken and adapted from the Tradeback NPC tutorial here.
In engine\events\evolve_trade.asm
...
; This was fixed in Yellow.
ld a, [wInGameTradeReceiveMonName]
; GRAVELER
cp "G"
jr z, .ok
; "SPECTRE" (HAUNTER)
cp "S"
ret nz
ld a, [wInGameTradeReceiveMonName + 1]
cp "P"
ret nz
.ok
- ld a, [wPartyCount]
- dec a
- ld [wWhichPokemon], a
ld a, $1
ld [wForceEvolution], a
ld a, LINK_STATE_TRADING
ld [wLinkState], a
callfar TryEvolvingMon
xor a ; LINK_STATE_NONE
ld [wLinkState], a
jp PlayDefaultMusic
There are three separate dialogue sets: Casual, Happy, and Evolution. Casual and Happy are mostly the same, but Evolution will reference the Pokemon evolving after the trade. Evolution is left over from Japanese Blue and why the Electrode trader talks about Raichu evolving. The text for these can be edited like normal text in data\text\text_7.asm
.
However, if these aren't enough, more dialogue sets can be added.
Pointers that load them can be added in engine\events\in_game_trades.asm
, along with the text these pointers will pull.
InGameTradeTextPointers:
; entries correspond to TRADE_DIALOGSET_* constants
dw TradeTextPointers1
dw TradeTextPointers2
dw TradeTextPointers3
+ dw TradeTextPointers4
TradeTextPointers1:
dw WannaTrade1Text
dw NoTrade1Text
dw WrongMon1Text
dw Thanks1Text
dw AfterTrade1Text
...
+TradeTextPointers4:
+ dw WannaTrade4Text
+ dw NoTrade4Text
+ dw WrongMon4Text
+ dw Thanks4Text
+ dw AfterTrade4Text
Then, later down the file, you'll want to add all your respective calls for the text, like so.
AfterTrade3Text:
text_far _AfterTrade3Text
text_end
+WannaTrade4Text:
+ text_far _WannaTrade4Text
+ text_end
+
+NoTrade4Text:
+ text_far _NoTrade4Text
+ text_end
+
+WrongMon4Text:
+ text_far _NoTrade4Text
+ text_end
+
+Thanks4Text:
+ text_far _Thanks4Text
+ text_end
+
+AfterTrade4Text:
+ text_far _Thanks4Text
+ text_end
Once that's all done, simply add the text to data\text\text_7.asm
, using the same text pointers as names. For compression's sake, the same text can be used for multiple dialogue sets. The character limit in text boxes is 17 before it starts scrolling weirdly, so make sure you add a new line in those cases.
Finally, like new trades, you'll need to store the new dialogue set in constants\script_constants.asm
.
; InGameTradeTextPointers indexes (see engine/events/in_game_trades.asm)
const_def
const TRADE_DIALOGSET_CASUAL
const TRADE_DIALOGSET_POLITE
const TRADE_DIALOGSET_EVOLUTION
const TRADE_DIALOGSET_HAPPY
+ const TRADE_DIALOGSET_NAMEHERE ; again, could be anything, but uniformity is king!
Then, simply apply it to the trade you're adding.
Obedience is a very cumbersome mechanic making traded Pokemon not listen at a certain level, but it's easy to render the function useless.
In engine\battle\core.asm
...
; what level might disobey?
ld hl, wObtainedBadges
bit BIT_EARTHBADGE, [hl]
ld a, 101
jr nz, .next
bit BIT_MARSHBADGE, [hl]
- ld a, 70
+ ld a, 101
jr nz, .next
bit BIT_RAINBOWBADGE, [hl]
- ld a, 50
+ ld a, 101
jr nz, .next
bit BIT_CASCADEBADGE, [hl]
- ld a, 30
+ ld a, 101
jr nz, .next
- ld a, 10
+ ld a, 101
Yep! Just set the badge thresholds to all be 101, like the Earth Badge. The fix I have is a classic case of "Ok I removed this code that seemed unnecessary but definitely was so let's just make the function useless instead", but it works, so until someone inevitably sees this monkey fix and edits something definitive in with a blind rage, it'll do. You can probably remove the badge checks entirely and get the same result.
For immersion purposes, it's a good idea to change the Gym Leader text associated with obedience. These are found in text\CityNameGym.asm
, where CityName would be Saffron Gym, and so on. There's also a house in Pewter City that talks about traded Pokemon and obedience.
If you're removing obedience you're probably after this too. This stops it from being called, but for good measure, the multiplier and all text calls are chopped off too. There's probably a way to completely remove this, but I have the coding expertise of a capuchin monkey on crack, so...
In engine\battle\experience.asm
...
ld a, 0
jr z, .next
.tradedMon
- call BoostExp ; traded mon exp boost
ld a, 1
.next
ld [wGainBoostedExp], a
...
BoostExp:
- ldh a, [hQuotient + 2]
- ld b, a
- ldh a, [hQuotient + 3]
- ld c, a
- srl b
- rr c
- add c
- ldh [hQuotient + 3], a
- ldh a, [hQuotient + 2]
- adc b
- ldh [hQuotient + 2], a
ret
; I am 90% sure this is called in some capacity elsewhere - if it isn't, just remove the whole thing.
...
and a
ret nz
ld hl, ExpPointsText
- ld a, [wGainBoostedExp]
- and a
- ret z
- ld hl, BoostedText