Jump to content


Photo

Getting a non-joinable NPC in the game


  • Please log in to reply
3 replies to this topic

#1 berelinde

berelinde

    Troublemaker

  • Modder
  • 4916 posts

Posted 10 May 2007 - 08:15 AM

If you want your NPC to appear in a specific area, your first step is always organization. Define the particulars.

For this and all other tutorials, I'll assume that your mod folder is MyNPC and its sub-folders are CRE for creatures and portraits, BAF for scripts, and DLG for dialogue files.

Name: MyNPC
Creature code for the NPC: NPCCRE.cre
Area: Waukeen's Promenade, standing outside the Temple of Ilmater
(please, don't anybody else use these coordinates, as I'm planning to use them for Gavin)
Conditions: nothing special, the NPC should be there 24/7
(special conditions require special scripting, best saved as the subject of a future tutorial)
Death Variable/Script Name: MyNPCtheNPC
Dialogue file: NPCDLG.dlg
Override script: NPCBCS.bcs

You'll note that I'm using different names for the NPC name, the creature code, the DV, the dialogue file, and the override script. If I were writing this as a mod, I'd make them all the same just to make life easy for myself, but for the purposes of this tutorial, they will all be different. This will hopefully give you a better understanding of what resource is being referenced in which line.

First thing I'm going to do is get the coordinates. The easiest way to do this is just to fire up the game, move to the area, position the cursor over the spot you want the NPC to appear, and hit CTRL-X. That will give you the mouse pointer position with the x,y coordinates, the tile position (whatever that is), the search square coordinates (whatever they are), and the area code.

For Waukeen's Promenade, outside the Temple of Ilmater, it's area=AR0700, x=3263, y=777

Next, I'm going to write a script file that I will eventually use to extend the bottom of the script for AR0700.

First, the tidy code:
/* Area script for MyNPC spawn - MyNPCAR0700.baf - extend bottom of AR0700.bcs */
IF
  AreaCheck("AR0700")
  Global("MyNPCExists","GLOBAL",0)
THEN
  RESPONSE #100
	SetGlobal("MyNPCExists","GLOBAL",1)
	CreateCreature("NPCCRE",[3263.777],4)
END
Now, the commented code:
// As usual, a header identifying the file and saying where it's going to go
/* Area script for MyNPC spawn - MyNPCAR0700.baf - extend bottom of AR0700.bcs */
IF

// AR0700 is the area code for Waukeen's Promenade
  AreaCheck("AR0700")
  
// Check variables are just as important here, or else you'll wind up with multiple copies of your NPC
  Global("MyNPCExists","GLOBAL",0)
THEN
  RESPONSE #100
  
// Don't forget to close that variable, or you will cause an endless spawn cycle that will be very funny to watch, but impossible to play
	SetGlobal("MyNPCExists","GLOBAL",1)
	
// NPCCRE is the creature code for MyNPC. As we found out during area investigation, the x,y coordinates for the area outside the Temple of Ilmater are 3263,777. Note that the CreateCreature action uses the syntax [xxxx.yyyy]. The third term in the expression is the face direction. Its values start at 0, pointing due south, and increase in a clockwise direction to 15, just a smidgen east of due south. 4 is a something like WWNW or something.
	CreateCreature("NPCCRE",[3263.777],4)
END

OK, now save your file as MyNPCAR0700.baf, in your BAF folder.

Now you have two choices. You can either have the NPC stand there and wait for the PC to come over and talk to him, or you can have the NPC approach the PC. I'll give examples of both.

For the NPC that is going to stand there and wait for the PC to come over and talk to him, it's fairly easy. You just give him a dialogue file where the conditions determine which dialogue block is used. Just to make things interesting, let's assume this NPC is part of a quest. This means that we need to think about what happens if 1) the PC walks up and talks to MyNPC before the quest is fully underway, 2) what happens if the PC walks up to MyNPC a second time before the quest is underway, 3) what happens if the PC walks up to MyNPC as part of the quest. NumTimesTalkedTo() is used to count how many times the PC talks to MyNPC. The engine will increase this value for you without any intervention on your part. Very convenient.

You have to keep in mind that any conditional dialogue, i.e. one with IF ~conditions~ THEN, can be used as an un-called state. In other words, if there are conditions there, and those conditions are true, dialogue can be started from that state. This means that you have to be careful that the conditions you use will *only* apply to the dialogue you want. But you'll see that during the example.

For the NPC that is just going to stand there waiting for the PC, here is the tidy code:
/* MyNPC dialogue file - NPCDLG.d */

/* CONTENTS */
/* First meeting, no quest */
/* Subsequent meetings, no quest */
/* Quest, first meeting */
/* Quest, subsequent meetings */

BEGIN ~NPCDLG~

/* First meeting, no quest */

APPEND ~NPCDLG~

IF ~NumTimesTalkedTo(0) !GlobalGT("MyNPCQuest","GLOBAL",3)~ THEN BEGIN  MyNPC1
SAY ~You look like a good sort. Nice weather, isn't it?~
IF ~~ THEN EXIT
END

/* Subsequent meetings, no quest */
IF ~NumTimesTalkedToGT(0) !GlobalGT("MyNPCQuest","GLOBAL",3)~ THEN BEGIN  MyNPC2
SAY ~Hello again.~
IF ~~ THEN EXIT
END
END

/* Quest, first meeting */

CHAIN
IF ~Global("MyNPCQuest","GLOBAL",4)~ THEN ~NPCDLG~  MyNPC3
~QuestNPC said I could expect to talk to you. I have the potion you seek. Take it to the Graveyard District.~
DO ~GiveItemCreate("POTN08",Player1,0,0,0) SetGlobal("MyNPCQuest","GLOBAL",5)~ 
EXIT

/* Quest, subsequent meetings */
CHAIN
IF ~GlobalGT("MyNPCQuest","GLOBAL",4)~ THEN ~NPCDLG~  MyNPC4
~Was there something else you wanted?~
END
++ ~No, thanks.~ EXTERN ~NPCDLG~ MyNPC4.1
+ ~GlobalLT("MyNPCQuest","GLOBAL",6)~ + ~Where was I supposed to take these documents?~ EXTERN ~NPCDLG~ MyNPC4.2

APPEND ~NPCDLG~

IF ~~ THEN BEGIN MyNPC4.1
SAY ~Have a nice day.~
IF ~~ THEN EXIT
END

IF ~~ THEN BEGIN MyNPC4.2
SAY ~To the Graveyard District. The agent will meet you there.~
IF ~~ THEN EXIT
END
END
Now, the commented version:
// As always, the file header
/* MyNPC dialogue file - NPCDLG.d */

// Tables of contents make it easier to find things if something goes wrong
/* CONTENTS */
/* First meeting, no quest */
/* Subsequent meetings, no quest */
/* Quest, first meeting */
/* Quest, subsequent meetings */

// You're going to have to begin this dialogue file. This will do it.
BEGIN ~NPCDLG~

/* First meeting, no quest */

// You've begun the dialogue file, but you're adding stuff to it. So thow in an APPEND.
APPEND ~NPCDLG~

// NumTimesTalkedTo(0) will only return true the first meeting. If you're using the same quest coding as in the previous example, the quest variable will be at 4 after speaking to the quest giver, so you want to make sure that this dialogue will only happen if the PC has not yet spoken to the messenger. !GlobalGT accomplishes this. However, if the quest giver has already spoken to the PC, MyNPCQuest will be at 4, so this state will be skipped. That's OK, though as there is another further down that is valid.
IF ~NumTimesTalkedTo(0) !GlobalGT("MyNPCQuest","GLOBAL",3)~ THEN BEGIN  MyNPC1
SAY ~You look like a good sort. Nice weather, isn't it?~
IF ~~ THEN EXIT

// Hey! Why wasn't there a check variable here? Won't this repeat? No, because NumTimesTalkedTo(0) will only be true once.
END

/* Subsequent meetings, no quest */
// NumTimesTalkedTo() will increase every time MyNPC is spoken to (see Noober or Neeber for examples). Here, the only condition that matters is that the quest giver has not yet spoken to the PC. The above state gives the explanation.
IF ~NumTimesTalkedToGT(0) !GlobalGT("MyNPCQuest","GLOBAL",3)~ THEN BEGIN  MyNPC2
SAY ~Hello again.~
IF ~~ THEN EXIT

// Once again with no check variable! Why? Well, this state can repeat as often as necessary. There is no need to add one if the dialogue is intended to repeat indefinitely until the quest conditions are met.
END

// Why the second END? We're moving from a standard D format to a CHAIN. You need to end the APPEND before moving to CHAIN.
END

/* Quest, first meeting */

// Here's that chain state.
CHAIN

// Once the quest giver has spoken to the PC, I don't care whether or not the PC has spoken to MyNPC. So, I'm not going to bother with the NumTimesTalkedTo(), just the quest variable.
IF ~Global("MyNPCQuest","GLOBAL",4)~ THEN ~NPCDLG~  MyNPC3
~QuestNPC said I could expect to talk to you. I have the potion you seek. Take it to the Graveyard District.~

// "POTN08" is an item that the NPC will give to the player. MyNPC doesn't have to have it in his inventory, as it will be created as part of GiveItemCreate(). The first term in the action is the item given, the second is the person the item will be given to, and the next set have to do with quantity and charges. 0,0,0 means one item. Please note that here, we are increasing the quest variable, because this dialogue advances the quest. This will prevent this dialogue from looping and will also prevent MyNPC from being used as a neverending supply of healing potions.
DO ~GiveItemCreate("POTN08",Player1,0,0,0) SetGlobal("MyNPCQuest","GLOBAL",5)~ 
EXIT

/* Quest, subsequent meetings */
CHAIN

// Every time MyNPC is spoken to after he has given the potion, he will say the same thing.
IF ~GlobalGT("MyNPCQuest","GLOBAL",4)~ THEN ~NPCDLG~  MyNPC4
~Was there something else you wanted?~
END
++ ~No, thanks.~ EXTERN ~NPCDLG~ MyNPC4.1

// Once the PC talks to the agent in the Graveyard District, the quest variable will increase to 6. I don't want MyNPC to keep telling the PC where to take a potion he no longer has, so I'm going to put a condition on this reply so that it is only available if the PC has not yet met the agent.
+ ~GlobalLT("MyNPCQuest","GLOBAL",6)~ + ~Where was I supposed to take these documents?~ EXTERN ~NPCDLG~ MyNPC4.2

APPEND ~NPCDLG~

IF ~~ THEN BEGIN MyNPC4.1
SAY ~Have a nice day.~
IF ~~ THEN EXIT
END

IF ~~ THEN BEGIN MyNPC4.2
SAY ~To the Graveyard District. The agent will meet you there.~
IF ~~ THEN EXIT
END
END
Save this in the DLG folder, as NPCDLG.d.

Now, the NPC that is going to walk up and talk to the PC requires slightly different handling. This one is not going to be involved with the earlier quest at all, but is going to just walk up, hand the PC a potion, and ask the PC to take it to an agent in the Graveyard District. After that, he'll just stand around, so if the PC wants to ask him to repeat the instructions, he can.

This NPC needs a script to tell him to walk up to the PC and start talking. So we'll start there, with the tidy code:
/* MyNPC script file - NPCBCS.baf */
IF
   Global("MyNPCExists","GLOBAL",1)
   Global("GavePotion","GLOBAL",0)
   See(Player1)
   CombatCounter(0)
   !See([ENEMY])
THEN
   RESPONSE #100
	  SetGlobal("GavePotion","GLOBAL",1)
	  MoveToObject(Player1)
	  Dialogue(Player1)
END
And the commented code:
/* MyNPC script file - NPCBCS.baf */
IF

// The NPC has to exist, right?
   Global("MyNPCExists","GLOBAL",1)
   
// MyNPC has not yet spoken to the PC.
   Global("GavePotion","GLOBAL",0)
   
// MyNPC has to be able to see the PC in order for him to approach him. Hopefully, the PC won't be invisible.
   See(Player1)
   
// Nothing is worse than having some random NPC walk up to you in the middle of combat and start blabbing. This ensures it won't happen.
   CombatCounter(0)
   !See([ENEMY])
THEN
   RESPONSE #100
   
// Have to increase the variable
	  SetGlobal("GavePotion","GLOBAL",1)
	  
// MyNPC walks up to the PC and starts talking
	  MoveToObject(Player1)
	  Dialogue(Player1)
END
Save this in the BAF folder as NPCBCS.baf

The dialogue looks a little different for this one, since the PC isn't required to go up and talk to MyNPC:
/* MyNPC dialogue file - NPCDLG.d */
/* CONTENTS */
/* Give the potion */
/* PC has potion */
/* PC gave potion */

BEGIN ~NPCDLG~

CHAIN
IF ~Global("GavePotion","GLOBAL",1)~ THEN ~NPCDLG~ MyNPC1
~You look like a trusting sort. Here is this potion that you absolutely must take to my agent in the Graveyard District. Thanks.~
DO ~GiveItemCreate("POTN08",Player1,0,0,0) SetGlobal("GavePotion","GLOBAL",2)~ 
EXIT

CHAIN
IF ~Global("GavePotion","GLOBAL",2)~ THEN ~NPCDLG~ MyNPC2
~What are you still standing around for? I said the Graveyard District!~
EXIT

CHAIN
IF ~Global("GavePotion","GLOBAL",3)~ THEN ~NPCDLG~ MyNPC2
~Nice day, innit?~
EXIT

This doesn't introduce any coding ideas that the last one didn't cover, so no commented code for this one. Save it as NPCDLG.d, in the DLG folder.

Now, you're going to want to put this in the tp2. I used the same name, MyNPC, for both this and the last example. Probably unwise, but you're going to want to change it anyway. I'm not going to go over all the material from the quest NPC tutorial, so this is just the part you would add to the tp2 for *this* NPC.

/* extending the area script */

  COPY_EXISTING ~AR0700.ARE~ ~override/AR0700.ARE~
	WRITE_ASCII 0x94 ~AR0700~ #8
	BUT_ONLY_IF_IT_CHANGES
  
	EXTEND_BOTTOM ~AR0700.bcs~ ~MyNPC/BAF/MyNPCAR0700.baf~

/* compiling the dialogue */
COMPILE ~MyNPC/DLG/NPCDLG.d~

/* compiling the script - skip this if you chose to have the NPC stand and wait as opposed to approaching the PC */
COMPILE ~MyNPC/BAF/NPCBCS.baf~

/* copying the CRE */
COPY ~MyNPC/CRE/NPCS.bmp~ ~override/npcs.bmp~
COPY_EXISTING ~SHOP8.CRE~ ~override/NPCCRE.cre~ // using Galoomp as a template for this NPC
SAY NAME1 ~MyNPC~
SAY NAME2 ~MyNPC~
WRITE_ASCII 0x248 ~NPCBCS~ #8 // override
WRITE_ASCII 0x2cc ~NPCDLG~ #8 // dialogue
WRITE_ASCII 0X34 ~npcs~ #8 //small portrait
WRITE_ASCII 0x280 ~MyNPCtheNPC~ #32 //DV
SAY INITIAL_MEETING ~ 'Ello.~
SAY DAMAGE ~~
SAY DYING ~~ 
SAY SELECT_COMMON1 ~'Ello.~ 
SAY DIALOG_HOSTILE ~You're not welcome here!~ 
SAY DIALOG_DEFAULT ~'Ello.~

"Imagination is given to man to console him for what he is not; a sense of humor, for what he is." - Oscar Wilde

berelinde's mods
TolkienAcrossTheWater website
TolkienAcrossTheWater Forum


#2 Qwallath

Qwallath
  • Member
  • 7 posts

Posted 17 March 2008 - 01:43 PM

Excellent tutorial - very instructive and thorough! I'm just beginning to get the hang of things, and this one will surely be useful for me.

A newbie question:

Could you comment on the hex editing in the final TP2 file? What do these do, for example?:

[COPY_EXISTING ~AR0700.ARE~ ~override/AR0700.ARE~]

WRITE_ASCII 0x94 ~AR0700~ #8

[BUT_ONLY_IF_IT_CHANGES]

[...]

WRITE_ASCII 0x248 ~NPCBCS~ #8 // override
WRITE_ASCII 0x2cc ~NPCDLG~ #8 // dialogue
WRITE_ASCII 0X34 ~npcs~ #8 //small portrait
WRITE_ASCII 0x280 ~MyNPCtheNPC~ #32 //DV


I assume they edit some of the variables in the ARE and CRE files, but which ones? And is there a handy reference for these things somewhere?

Much obliged!

Edited by Qwallath, 17 March 2008 - 01:44 PM.


#3 Miloch

Miloch

    Barbarian

  • Modder
  • 6579 posts

Posted 20 March 2008 - 02:13 AM

And is there a handy reference for these things somewhere?

Yep.

Infinity Engine Contributions
Aurora * BG1 NPC * BG1 Fixpack * Haiass * Infinity Animations * Level 1 NPCs * P5Tweaks
PnP Free Action * Thrown Hammers * Unique Containers * BG:EE * BGII:EE * IWD:EE
================================================================
Player & Modder Resources
BAM Batcher * Creature Lister * Creature Checker * Creature Fixer * Tutu/BGT Area Map & List * Tutu Mod List
================================================================
"Infinity turns out to be the opposite of what people say it is. It is not 'that which has nothing beyond itself' that is infinite, but 'that which always has something beyond itself'." -Aristotle


#4 BCaesar

BCaesar
  • Modder
  • 62 posts

Posted 14 May 2017 - 10:53 PM

First thing I'm going to do is get the coordinates. The easiest way to do this is just to fire up the game, move to the area, position the cursor over the spot you want the NPC to appear, and hit CTRL-X. That will give you the mouse pointer position with the x,y coordinates, the tile position (whatever that is), the search square coordinates (whatever they are), and the area code.

 

In BG2:EE at least you need to press Alt-X not CTRL-X.


Mod: All Things Mazzy (banters, romance, expanded friendship and more)
Tutorial/Coding: Rest Check (coding for anyone to make their Mod's rest scenes yield to those from BG2:EE and additional coding to make them yield to those from a list of other mods as well.