NPC Tutorial

From ZeldaHacking Wiki
Jump to navigation Jump to search

This page gives an outline of how one might make a custom NPC using the disassembly.


Tools used: BGB, LynnaLab, and the disassembly, with your preferred text editor.


This can also be done with ZOSE and ZOLE, but it is much easier using the disassembly.

Background Information

Link interacting with the new mustache man NPC.

NPCs are interaction objects. They generally have two components: their assembly code, and their corresponding script. As a rough guideline, things that should be updated every frame are put in their assembly code, and sequences of events that occur over many frames are put into scripts.

In general, the NPC's sprite and animations are tied to the high byte of their ID. e.g., interactions $9400-$94ff use the same graphics. However, it is possible to set up new graphics for varying "subids" (i.e., the low byte of the NPC's ID).

You should preferably know:

  • Everything here
  • A bit about scripting
  • A bit of assembly knowledge

Follow steps here for setup of the disassembly and LynnaLab.

Assembly Code

Basics

We'll start by utilizing an existing NPC and creating a new instance. This will be expanded upon later.

Open LynnaLab and select your current project; we won't be editing anything in here yet, but it is a helpful tool to locate used objects and to see their corresponding graphics. For our example, we'll utilize the "MUSTACHE_MAN" from Ages, ID $42.

Navigate to the related NPC's assembly code, located in the object_code folder, selecting which game you are using and opening the interaction folder, then finally opening the .s file that corresponds to the NPC as it is named in LynnaLab. For our example, we'll open object_code/ages/interactions/mustacheMan.s.


After opening, we will see the following code:

interactionCode42:
	ld e,Interaction.subid
	ld a,(de)
	rst_jumpTable
	.dw @subid0
	.dw @subid1

This branches out the NPC's code to its various subids. We could either create a new subid or edit an existing one. For this tutorial, we will create a new one.

We can create a new one just by adding .dw @subid2, as the new subid is $02. The corresponding label can be placed anywhere as long as it does not interfere with the existing code.

After dropping our label somewhere, the next thing to do is to go through the list of items we would want for the NPC to do every frame, or to do only once before its even started its script, such as initialization.

This is not the specific order, but the code usually follows this structure: Check if to delete the NPC Initialize the NPC's graphics, collisions, variables, and then script


We will present our finalized assembly code and go through each item line by line:

@subid2:
	call checkInteractionState
	jr nz, @@initialized
	call @initGraphicsAndScript
	ld a,>TX_0c09
	call interactionSetHighTextIndex
@@initialized:
	call interactionRunScript
	jp interactionAnimateAsNpc

checkInteractionState

As we would only like to intialize an NPC once, the disassembly uses the NPC's state variable to indicate whether the NPC has been initialized already. All objects start at a state of $00, but can be set at any time by any write command to its designated address. The checkInteractionState function only checks if the state is nonzero, but this is all we need it for as we will only use it as an initialization variable. The following relative jump command is the branch. Note that the state will be incremented in a future call during intialization.

@initGraphicsAndScript

This call is referenced by the NPC's other subids, and for good reason. This function initializes the NPC's graphics, script, and text high byte.

; initializes graphics
 	call interactionInitGraphics

; initializes collisions
	call objectMarkSolidPosition

; sets high byte of text for the NPC to read from
	ld a,>TX_0f04
	call interactionSetHighTextIndex

; loads the NPC's script
 	ld e,Interaction.subid
	ld a,(de)
	ld hl,@scriptTable
	rst_addDoubleIndex
	ldi a,(hl)
	ld h,(hl)
	ld l,a
	call interactionSetScript

; increments the NPC's state
	jp interactionIncState

Note that the @scriptTable only has two entries, as the NPC originally only had two subids. With our third one, we'll add .dw mainScripts.mustacheManScript2 to follow the naming convention of the previous scripts. Once this call is completed, the NPC should be fully initialized and ready for us to create our script! However, if we want to set any other beginning variables, now would be the time right after this call.

call interactionSetHighTextIndex

This call is unnecessary since we already have a "bank" of text to use, which is from high byte $0f. However, for demonstration purposes, we will change the high byte to $0c so that the NPC will eventually utilize TX_0c09 when Link speaks to him.

We could also set any other variable for the NPC, such as change his collision radius, or set up a counter.

@@initialized

After we are finished initializing the NPC, all that is left is the typical functions that are needed to be run every frame for the NPC to work. i.e., running their script and animating them. As animations have already been set up via the data/<game>/interactionData.s and data/<game>/interactionAnimationData.s, we have nothing more to do in this rudimentary tutorial.

However, we will need to create the script that we set up during initialization.

Scripting

Moving on with the custom script, we'll open the file scripts/ages/scripts.s. Find the "INTERACID_MUSTACHE_MAN" section and add the label mustacheManScript2. Additionally, open text/ages/text.yaml and navigate to group 0x0c.

First, in the text file, add a new index below TX_0c08:

  - name: TX_0c09
    index: 0x09
    text: |-
      Hello, world!

Back in scripts.s, we'll then add rungenericlowindex <TX_0c09, as the text high byte was already set previously.

However, if we don't want to have to set the high byte within the assembly code, we can also use a 3-byte command of rungenericnpc TX_0c09.

Finally, if we don't want a specialized script for the NPC, we can set up the NPC's script as mainScripts.genericNpcScript, which will effectively do the same as long as we set the NPC's entire text address during initialization.

At this point, we can open LynnaLab again, select our desired room, and create an interaction instance with ID $42 and subid $02. For additional assistance through LynnaLab and creating interactions, see Stewmat's YoutTube tutorial [1].