This tutorial is designed primarily to show the implementation of a tradeback NPC, inspired by the one in crystal clear. However, other items are within the scope of this tutorial such as adding new in game trades and enabling evolution through in game trades. ## Contents - [Add an NPC to trade with](#1-add-an-npc-to-trade-with) - [Add a new in game trade](#2-add-a-new-in-game-trade) - [Allow pokemon to evolve from in game trades](#3-allow-pokemon-to-evolve-from-in-game-trades) - [Edit the in game trading system](#4-edit-the-in-game-trading-system) ## 1. Add an NPC to trade with The first step to get your very own tradeback guy is adding an NPC to do the trading with, this is extremely simple. Just pick a location. For our example we will use CeladonMartF1. We'll start by opening it's header file and placing an NPC in it. The NPCs are added in the following format: object SPRITE constant, Coord1, Coord2, Movement Type, Movement Direction, Map Script Pointer Number That in mind edit [data/maps/objects/CeladonMart1F.asm](../blob/master/data/maps/objects/CeladonMart1F.asm): ```diff ... def_signs sign 11, 4, 2 ; CeladonMart1Text2 sign 14, 1, 3 ; CeladonMart1Text3 def_objects object SPRITE_LINK_RECEPTIONIST, 8, 3, STAY, DOWN, 1 ; person + object SPRITE_MOM, 3, 2, STAY, DOWN, 4 ; person def_warps_to CELADON_MART_1F ``` I've used your Mom as the sprite just to be cheeky, you can obviously use a different sprite. We've placed her near the elevator against the wall. She stands in place and looks down, and she's attached to the 4th script that well be adding next. Next we need to add the script the NPC will be using to the map's script file. Edit [scripts/CeladonMart1F.asm](../blob/master/scripts/CeladonMart1F.asm): ```diff CeladonMart1F_Script: jp EnableAutoTextBoxDrawing CeladonMart1F_TextPointers: dw CeladonMart1Text1 dw CeladonMart1Text2 dw CeladonMart1Text3 + dw CeladonMartTrader CeladonMart1Text1: text_far _CeladonMart1Text1 text_end CeladonMart1Text2: text_far _CeladonMart1Text2 text_end CeladonMart1Text3: text_far _CeladonMart1Text3 text_end +CeladonMartTrader: + text_asm + ld a, TRADE_WITH_SELF + ld [wWhichTrade], a + predef DoInGameTradeDialogue + jp TextScriptEnd ``` If you're familiar with in game trades in red, you may recognize the script we attached to our NPC. We'll be diving more into that in the next step. ## 2. Add a new in game trade The way we'll be implementing our tradeback NPC is by hijacking the already existing in game trades code. We're getting ahead of ourselves though. First we need to add a new trade that we can tie that to. For this step we'll start by creating a new constant for said trade. If you wanted to add custom in game trades this is how you would do it by the way. Edit [constants/script_constants.asm](../blob/master/constants/script_constants.asm): ```diff ... ; in game trades ; TradeMons indexes (see data/events/trades.asm) const_def const TRADE_FOR_TERRY const TRADE_FOR_MARCEL const TRADE_FOR_CHIKUCHIKU const TRADE_FOR_SAILOR const TRADE_FOR_DUX const TRADE_FOR_MARC const TRADE_FOR_LOLA const TRADE_FOR_DORIS const TRADE_FOR_CRINKLES const TRADE_FOR_SPOT + const TRADE_WITH_SELF ; in game trade dialog sets ; InGameTradeTextPointers indexes (see engine/events/in_game_trades.asm) const_def const TRADE_DIALOGSET_CASUAL const TRADE_DIALOGSET_POLITE const TRADE_DIALOGSET_HAPPY + const TRADE_DIALOGSET_SELF ; badges ; wObtainedBadges and wBeatGymFlags bits const_def ``` While we were here we also added a constant for the dialogset we'll be using. Our Tradeback NPC shouldn't say the same things that the other in game trade NPCS say after all. Next we add the details for the new trade itself, if you were just adding a new trade and not making a tradeback guy you would obviously input the relevant information here, and in that case, provided you use an already existing dialogset, you would be done. We're going for the tradeback guy though so we'll continue on. Edit [data/events/trades.asm](../blob/master/data/events/trades.asm): ```diff TradeMons: ; entries correspond to TRADE_FOR_* constants ; give mon, get mon, dialog id, nickname db NIDORINO, NIDORINA, TRADE_DIALOGSET_CASUAL, "TERRY@@@@@@" db ABRA, MR_MIME, TRADE_DIALOGSET_CASUAL, "MARCEL@@@@@" db BUTTERFREE, BEEDRILL, TRADE_DIALOGSET_HAPPY, "CHIKUCHIKU@" db PONYTA, SEEL, TRADE_DIALOGSET_CASUAL, "SAILOR@@@@@" db SPEAROW, FARFETCHD, TRADE_DIALOGSET_HAPPY, "DUX@@@@@@@@" db SLOWBRO, LICKITUNG, TRADE_DIALOGSET_CASUAL, "MARC@@@@@@@" db POLIWHIRL, JYNX, TRADE_DIALOGSET_POLITE, "LOLA@@@@@@@" db RAICHU, ELECTRODE, TRADE_DIALOGSET_POLITE, "DORIS@@@@@@" db VENONAT, TANGELA, TRADE_DIALOGSET_HAPPY, "CRINKLES@@@" db NIDORAN_M, NIDORAN_F, TRADE_DIALOGSET_HAPPY, "SPOT@@@@@@@" + db NO_MON, NO_MON, TRADE_DIALOGSET_SELF, "Unseen@@@@@" ``` Our choice of NO_MON here actually serves a purpose, no matter how many trades you add it is unlikely that you will ever trade missingno. Though if you intend to, change things accordingly. You'll also notice that we're using our new dialogset constant here, we'll add the text for that towards the end. The nickname is entirely unimportant as it won't ever be used. ## 3. Allow pokemon to evolve from in game trades Before we change that code for in game trades to accommodate our Tradeback NPC, we're going to make sure they can evolve through this method. this is exceedingly easy as it just involves deleting some code in one file. It also would enable you to have players trade for a mon that would actually evolve upon receipt. Edit [engine/events/evolve_trade.asm](../blob/master/engine/events/evolve_trade.asm): ```diff ... ; 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 ``` Note that if for some reason you weren't interested in a Tradeback NPC, you could instead stop deleting just above ```ld a, [wPartyCount]```, your traded mons would evolve just fine and you would be done here. That bit of code is moved to the next file for our continued purposes though. ## 4. Edit the in game trading system Now I promise that sounds and looks a lot more complicated that anything we're actually doing here. The vast majority of what we're doing is just checking to see if the trade was called by the Tradeback NPC and skipping portions of code if so. Things will only be skipped if you're trading with the tradeback NPC, other in game trades will be entirely unaffected. The next several edits all take place in the same file. They are all done in descending fashion so you can do them by scrolling down through the file rather than jumping around within it. I'll explain each as we go. Edit [engine/events/in_game_trades.asm](../blob/master/engine/events/in_game_trades.asm): Here we skip the "PLAYER traded X for Y" text that comes after the trade, that information is a bit redundant and it also loads names using the table from step 2, meaning if we showed it it would claim we traded MISSINGNO for MISSINGNO. It could be fixed, but like I said before, it's a tad redundant for the tradback NPC anyway ```diff ... ; if the trade hasn't been done yet xor a ld [wInGameTradeTextPointerTableIndex], a call .printText ld a, $1 ld [wInGameTradeTextPointerTableIndex], a call YesNoChoice ld a, [wCurrentMenuItem] and a jr nz, .printText call InGameTrade_DoTrade jr c, .printText + ld a, [wInGameTradeGiveMonSpecies] + cp NO_MON + jr z, .printText ld hl, TradedForText call PrintText .printText ``` Here we skip the check to see that you offered the mon from the table in step 2, we also need to change a jr to a jp because our additions may push some code out of jr range ```diff ... InGameTrade_DoTrade: xor a ; NORMAL_PARTY_MENU ld [wPartyMenuTypeOrMessageID], a dec a ld [wUpdateSpritesEnabled], a call DisplayPartyMenu push af call InGameTrade_RestoreScreen pop af ld a, $1 jp c, .tradeFailed ; jump if the player didn't select a pokemon ld a, [wInGameTradeGiveMonSpecies] + cp NO_MON + jr z, .skip_mon_check ld b, a ld a, [wcf91] cp b ld a, $2 - jr nz, .tradeFailed ; jump if the selected mon's species is not the required one + jp nz, .tradeFailed ; jump if the selected mon's species is not the required one +.skip_mon_check ``` Just under that we're skipping the setting of the flag that tells the game we've done this trade and shouldn't be able to do it again, if we didn't do this our tradeback NPC would only be good once. This way we can trade infinitely. ```diff ... call AddNTimes ld a, [hl] ld [wCurEnemyLVL], a + ld a, [wInGameTradeGiveMonSpecies] + cp NO_MON + jr z, .skip_flag_set ld hl, wCompletedInGameTradeFlags ld a, [wWhichTrade] ld c, a ld b, FLAG_SET predef FlagActionPredef +.skip_flag_set ld hl, ConnectCableText call PrintText ld a, [wWhichPokemon] ``` There is no space between the above code and this code, we broke for explanation. With this edit we're setting up a different beginning to the code that determines the pokemon being traded and received. This is mostly for the sake of the trade movie as will become apparent in a bit. ```diff push af ld a, [wCurEnemyLVL] push af call LoadHpBarAndStatusTilePatterns + ld a, [wInGameTradeGiveMonSpecies] + cp NO_MON + jr nz, .normal_in_game_trade_data + call TradeSelf_PrepareTradeData + jr .self_trade_data +.normal_in_game_trade_data call InGameTrade_PrepareTradeData +.self_trade_data predef InternalClockTradeAnim ``` This first line doesn't NEED to be removed, but it's kinda just wasting space, the addition below it actually skips the removal of a mon from your party and the addition of another one. You might call this the actual trade part, but for our purposes we don't actually NEED to remove a mon from the party for the evolve attempt to be made, the smoke and mirrors of it are enough. The addition below that is the block of code from step 3 that we could have left alone if we weren't making a Tradeback NPC. Also the point we skip to is on the other side of it. This enables normal trades to use the code while the tradeback NPC avoids it. ```diff ... - ld [wMonDataLocation], a ; not used + push af + ld a, [wInGameTradeGiveMonSpecies] + cp NO_MON + jr z, .skip_swap_mons + pop af ld [wRemoveMonFromBox], a call RemovePokemon ld a, $80 ; prevent the player from naming the mon ld [wMonDataLocation], a call AddPartyMon call InGameTrade_CopyDataToReceivedMon + ld a, [wPartyCount] + dec a + ld [wWhichPokemon], a +.skip_swap_mons ``` The next edit is the different beginning to the code that determines the pokemon being traded and received, that was mentioned earlier, basically it loads the pokemon you choose as both the sending and receiving pokemon. This marks the end of anything you could call heavy lifting in this process. ```diff ... InGameTrade_RestoreScreen: call GBPalWhiteOutWithDelay3 call RestoreScreenTilesAndReloadTilePatterns call ReloadTilesetTilePatterns call LoadScreenTilesFromBuffer2 call Delay3 call LoadGBPal ld c, 10 call DelayFrames farjp LoadWildData +TradeSelf_PrepareTradeData: + ld a, [wWhichPokemon] + ld hl, wPartySpecies + ld b, 0 + ld c, a + add hl, bc + ld a, [hl] + ld [wTradedPlayerMonSpecies], a + ld [wInGameTradeReceiveMonSpecies], a + ld hl, wTradedPlayerMonSpecies + jr InGameTrade_PrepareTradeData.loaded_self_trade_instead InGameTrade_PrepareTradeData: ld hl, wTradedPlayerMonSpecies ld a, [wInGameTradeGiveMonSpecies] +.loaded_self_trade_instead ``` Here is where we tie in the text for our new dialog set, it ultimately farcalls the actual text, I'm pretty sure this isn't necessary and that you could just put the text here, but for consistency's sake we'll do it the way the files are structured. Note that the wrong mon text and the after trade text are copied from a different set, this is because those scenarios aren't possible with the changes we've made and therefore would never be used. ```diff ... TradeTextPointers3: dw WannaTrade3Text dw NoTrade3Text dw WrongMon3Text dw Thanks3Text dw AfterTrade3Text +TradeTextPointers4: + dw WannaTrade4Text + dw NoTrade4Text + dw WrongMon1Text + dw Thanks4Text + dw AfterTrade1Text ... ... AfterTrade3Text: text_far _AfterTrade3Text text_end + +WannaTrade4Text: + text_far _WannaTrade4Text + text_end + +NoTrade4Text: + text_far _NoTrade4Text + text_end + +Thanks4Text: + text_far _Thanks4Text + text_end ``` And the final step, add in what the Trader actually says. The following is very basic text to get the point across, You can edit this to say whatever you'd like. As a nugget of general knowledge, lines of text can be 18 characters long (including spaces and punctuation) before breaking the edge of the textbox and needing to be continued in another line. Edit [data/text/text_7.asm](../blob/master/data/text/text_7.asm): ```diff ... _UsedCutText:: text_ram wcd6d text " hacked" line "away with Cut!" prompt + +_WannaTrade4Text:: + text "I'm the TRADER, I" + line "can trade your own" + para "#mon back to" + line "you. Wanna trade?" + done + +_NoTrade4Text:: + text "Ok, maybe next" + line "time then." + done + +_Thanks4Text:: + text "All done, I hope" + line "that helped." + done ``` We're all done. With a tradeback NPC you don't have to change evolution methods for you mons if you don't want to. Enjoy!