Adding an In Game Trade - pret/pokered GitHub Wiki

Introduction

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.

Adding the entry

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

Adding the constant

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 your Trade NPC

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.

Extras

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.

Breaking the 16-trade Limit

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.

Making In-Game Trades evolve when given

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

Adding Dialogue Sets

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.

Removing Obedience

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.

Removing Boosted Exp

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