Game Development Community

dev|Pro Game Development Curriculum

RPGDialog - NPC interaction system

by Nelson A. K. Gonsalves · 11/29/2002 (12:27 pm) · 186 comments

RPGDialog is a Torque Game Engine modification that adds standard RPG Dialogs support, including multiple choice questions and question linking(underlined words on a question that perform actions when clicked on). Also included is an editor for creating and editing dialogs easily.

Instructions on installing are included on the zip file.

New download location since geocities is dead

Have fun using it! :)
And let me know of any problems you might find.

Contact me at dk_uo@yahoo.com or leave a comment here.

Features

- Multiple choice questions, present a question and a list of answers and do something depending on which answer the player picks, the choices are numbered and can be picked either by the mouse or by pressing the 1,2,...,9,0 keys on the keyboard, choices after 10 must be selected via mouse.

- Question Links, Ex: "Hello, I'm Mary, how are you?" clicking on the word Mary would trigger an action as if it was a choice in the Multiple choice window.

- Portraits, pictures that represent the NPC, change it depending on the choices of the player or even use them to show the mood of the NPC.

- Sounds, add voice overs or any sound effects that are triggered when the question is displayed.

- Fully functional in multiplayer games, only allowing one player to talk to each NPC at a time, sending a message telling the others that the NPC is busy if they try to talk to it.

- Moving the player too far away from the NPC closes the Dialog automatically.

- Each option triggers a function, allowing complete customization of the actions caused by the dialog, either by using the included functions, altering them or creating your own.

June 8th 2005.
I finnaly got around to giving this a much deserved update!

Firstly I updated the instructions and made a few changes to make it fully compatible with TGE 1.3.

Also I've added buttons to move the answers up and down in the editor, so its now easier to organize the available options.

And finnaly, multiple actions can now be triggered by one response, thanks goes to BrokeAss Games for the idea. To execute multiple actions just add them one after the other without anything separating them, there is one example of this in the test scripts. Ohh, and now clicking on the actions list will add them to the actions edit and not replace them.

I have one warning though, version 1.0 .dla files are incompatible with version 1.3, there is an easy workaround I've used on my files, just open them directly in a text editor and do a quick replace all from "<" to "<END><", add <END> to the end of each line and remove the <END> from the beginning of the line, that should make them 100% compatible.

Let me know if you find any problems.

-Nelson

About the author

Recent Blogs

#121
04/03/2006 (2:10 pm)
Can someone post a compleaset SpwanNPC's with the added %datablock paramiter..and what i need to also edit to get it to work?

Im having issues figuring this out.
#122
04/03/2006 (11:26 pm)
This is close to what I use for static NPCs that use RPGDialog.
Once you get this code in, go into the game editor and drop the NPC.
It's not SpawnNPC() but I hope it helps.

datablock PlayerData(StaticNPC : Player)
{
    className= NPC; // NPC class
    category= "NPCs"; // Add to editor
    cmdCategory= "NPCs";
    shapeFile = "~/data/shapes/player/player.dts";
    name= "NPC";
    isInvincible = true;
};

function NPC::onAdd(%this,%obj)
{
    %data= %obj.getDatablock();
    // Vehicle timeout
    %obj.mountVehicle= false;

    // Default dynamic NPC stats
    %obj.setRechargeRate(%this.rechargeRate);

    %obj.setRepairRate(0);

    if(%obj.name !$="") // If it is a special character,you can give him/her a name.
       %obj.setShapeName(%obj.name);

    %obj.team = $Team6;
    %obj.startFade(2000, 0, false);
}

function NPC::create(%datablock)
{
   %obj = new AIPlayer() {
      dataBlock = StaticNPC;
      aiPlayer = true;
      RPGDialogScript = "Test";
      RPGDialogPortrait = "Test.png";
      RPGDialogStartQuestion = 1;
      RPGDialogBusy = false;
      RPGDialogBusyText = 'Sorry but I\'m busy talking to %1 right now.';
      RPGDialogTalkingTo = 0;
      name = "Default NPC (Team 6)";
   };
   MissionCleanup.add(%obj);
   %obj.setMoveSpeed(4);
   %obj.setTransform(pickSpawnPoint());
   %obj.setEnergyLevel(1000);
   %obj.setShapeName("Default NPC (Team 6)");
   %obj.team = $Team6;

   return %obj;

}

Ari

*Edit: Modified to better fit starter.fps demo.
#123
04/06/2006 (1:15 pm)
OK didnt get much of a response a few posts back (apart form MattH, thanx.. I was being stupid)

I'll simplify..

What are people getting in their "Action list". That is... the list of actions that is to the right when in both the Question Editor and Answer editor.

In the Questions Editor I have links to add the actions "RPGDialog link", "Name" and "Player Name".

BUT... in the Answers editor, I have a big empty box on the right, with nothing in it... Isnt this supposed to contain all posible actions, so you can quickly click them and edit them???

In anticipation of the usual awe inspiring solutions! (no sarcasm intended!)

Mark.xxxx

\Oh and ps.. Anyone know why F5 is used to get into the editor... aint this something to do with the particle effects????
#124
04/16/2006 (6:10 pm)
@Mark - I had the action problem as well. If I remember correctly, it was failing because of the path used to specify the location of the file containing the Action definitions. The RPG editor code parses that file to get the list of available Actions.

In common\RPGDialogEditor\editorMain.cs, find the PopulateActionList function. There should be a line like this:


if(isfile($RPGDialogEditorPref::MainMod@"/server/scripts/core/RPGDialog.cs") && %file.openForRead($RPGDialogEditorPref::MainMod@"/server/scripts/core/RPGDialog.cs"))


For me, that path is incorrect (I don't have the "core" directory)...so that Action list came up empty. Correcting the path got Actions working properly. Something like this in my case:


if(isfile($RPGDialogEditorPref::MainMod@"/server/scripts/RPGDialog.cs") && %file.openForRead($RPGDialogEditorPref::MainMod@"/server/scripts/RPGDialog.cs"))


BTW - notice that the path occurs twice...be sure to change both.

Hope that helps...

--- T
#125
04/16/2006 (11:19 pm)
I think thats it...cant try just yet but but looks like a likely contender !!! cool thanks!!

Mark
#126
04/17/2006 (2:48 am)
This file can not be downloaded from this website!!!!????
#127
04/18/2006 (9:02 am)
I can't download too... :_(
#128
05/14/2006 (12:38 pm)
The download didn't work for me, but after many re-trys it worked!!
#129
05/14/2006 (9:00 pm)
Thanks Anubis-Ra. But after many many re-try,the download din't work yet. Would you please send the resource to me by email(huwf@cuc.edu.cn). Thnak you a lot.
#130
05/16/2006 (4:44 am)
It all downloaded fine, but im looking and there is no

example\common\client\prefs.cs

which is the first step in the whole script modifications for this, do i just create the files tha arent already there?

im using torque 1.4
#131
05/25/2006 (6:08 am)
Great Resource Nelson. Works great in TGE 1.3 with some modifications for my own project.

Muito obrigado por ter mandado os arquivos por email. ;)

Thank you very much.

-----------------------------------------------------------------------------------------------------------------
Edit:
-----------------------------------------------------------------------------------------------------------------

I want to access the StaticNPC and play an specific animation from the gotoQuestion function.

Can anyone help me?

Thanks in advance.
#132
05/29/2006 (10:21 pm)
@Outline Interactive
The RPGDialog can accept more than one command per response. Simply just append the command that you want added to the end of the response in the .dla file of the resource you are editing, and it can handle more comamnds.

Based on my experience the RPGDialog is a great resource!

The major drawback is however with the Dialog editor that turns out to be quite buggy (some text get chopped out without you knowing) even with the suggested fix. At the end, its best to use the almighty notepad to fill up the values instead of the editor and it works perfectly in the game :)
#133
06/10/2006 (12:20 pm)
I'm not sure if this question is a stupid one or not, but here goes.

I have been adding my own functions to the actionList (in RPGDialog.cs), but I am having trouble with these objects:
%sender
and
%client.player

It seems pretty obvious that these are referring to the NPC and player, respectively. Since my NPC is a AIPlayer object, I have been trying to call AIPlayer functions on the %sender object, and they are not recognised!

For example, the console spits out this error when calling the AIPlayer clearAim() function:
.../scripts/RPGDialog.cs (272): Unknown command clearAim.
Object test(1349) audioprofile -> SimDataBlock -> SimObject

What I find strange is the audioProfile - as I said it appears that %sender refers to the NPC (an AIPlayer object), so why is it testing against an audioProfile?

Any help appreciated.

//EDIT: I solved this problem - it was something silly on my part. I had the wrong number of arguments in the function declaration. Strange that the compiler didnt pick that up, but instead the one I mentioned above.
#134
06/27/2006 (11:50 pm)
Thankyou for this resource and updating it! I just plugged it into TGE 1.4 and it works like a charm.
#135
08/16/2006 (9:54 am)
>>In example\common\main.cs

Around line 33 after: exec("./client/scriptDoc.cs");

This isn't in the 1.4 engine that I can find, is this supposed to be loaded in the base client, or base server blocks?
#136
09/28/2006 (3:03 am)
Wow! Kick-ass resource! :D Works fine in 1.4 for me too. Nice work!

Dion: I added it in "initBaseClient()", at the end, and it seems to work ok here (as far as I can tell now, haven't tested it extensively yet though).
#137
10/04/2006 (8:41 pm)
I am having a problem with making my scouts bots spawn lying down any suggestions???????

HERE is the CODE!!!!!!!!!!!!!!!!!!!!!!!! any suggestions??????????????
#138
10/04/2006 (8:42 pm)
//-----------------------------------------------------------------------------
//
//*TSL All state machine functions follow:
//

///Summary: Bot SM (StateMachine) called for each alive Bot every 500ms
///Parameters: %this is the Bot handle
function AIPlayer::SM(%this) //*TSL wrote entire function
{
//__decl AIPlayer %this

//echo("botType ",%this.botType); //testing dynamic variable

//a dead Bot shouldn't do much of anything....
if (%this.botType $= "dead")
return;

//A patrolling Bot will continue to patrol until he
//is dead (disabled must execute) or
//is damaged (damage and ondamage must execute) or
//is alarmed by seeing a human or
//must awaken his sleeping scouts or
//hears a noise or
//smells a human and initiates an ambush or
//realizes another patrol ork has not waved in 5 minutes or
//must wait and wave to the other patrol orks or
//is asked to join in an ambush another ork initiates or
//......
if (%this.botType $= "patrol"){
// if (%this.MSdoscan())
// %this.MSattack();
// else if (%this.awakenScouts) //a boolean
// %this.MSawakenScouts;
// else if (%this.hearsANoise())
// %this.MShearsANoise;
// else if (%this.smelledHuman())
// %this.SMcreateAmbush();
// else if (%this.missingWave())
// %this.MSwaitForWave(elapsedTime);
// else if (%this.myWaveTime(elapsedTime))
// %this.MSwaitandWave(elapsedTime);
// else if ($ambush) //a boolean
// %this.SMjoinAmbush();
// else {
// %this.getDataBlock().DoScan(%this);
%this.DoScan();
// SMpatrol();
// }
%this.schedule(500,SM);
return;
}

if (%this.botType $= "scout"){
// SMscout();
%this.botType = "scoutAttack";
%this.schedule(500,SM);
return;
}

//A sleeping scout will continue to sleep until he
//dies or
//is awoken by his noisy wounded buddy or an explosion or an ork collision or a doorway trigger.
//Then the botType must go from "sleep" to "scout" and he must arm himself
if (%this.botType $= "sleep"){
// if (%this.hearsANoise()){
// %this.MShearsANoise;
// %this.botType = "scout";
// %this.MSarmThySelf();
// }
//***********************sept***19***06**************************************
//***********************sept***19***06**************************************
//if(
// %this.DoScan();
//
// %this.checkForThreat();
// %this.openFire();
// %this.onTargetExitLOS();
// %this.onTargetEnterLOS();

%this.schedule(500,SM);
return;
}

if (%this.botType $= "patrolAttack"){



// ???ut();
%this.schedule(500,SM);
return;
}

// %this.MSerrorRtn("noBotType");
return;
}


//-----------------------------------------------------------------------------

function AIManager::think(%this)
{
//__decl ScriptObject %this

// We could hook into the player's onDestroyed state instead of
// having to "think", but thinking allows us to consider other
// things...
if (!isObject(%this.player))
//%this.player = %this.spawn();
%this.spawn(); //*TSL changed
// %this.schedule(500,think); //*TSL deleted
}





//*DSG BEGIN

///SUMMARY: Spawns 3 patrol orks with weapons(Lork on lakePatrol, Dork on dirtPatrol, and
/// Tork on townPatrol) and 3 pairs of scouts who sleep with no weapons.
///
///Jim: Note that 3 paths are hard coded into this function
///
///PARAMETERS: %this is the handle of the AIManager; JIM does know what that is.
///RETURNS: NO VALUE
function AIManager::spawn(%this)
{
//__decl AIManager %this


//===================== SETUP THE TOWN PATROLLING BOT AND 2 SCOUTS ==========================
//SET THE PATH TO BE townPath BECAUSE WE WANT THE BOT TO FOLLOW THE TOWN PATHS
//THE NAME townPath WAS SET IN THE FILE stronghold.mis AT THE BOTTOM.
//THE BOT WILL BEGIN AT THE NODE $TorkStartNode. $TorkStartNode IS INITIALIZED WITH THE GLOBALS
%path = "MissionGroup/Paths/townPath";
%player = AIPlayer::spawnOnPath("Tork","patrol",%path,$TorkStartNode);

//MAKE THE BOT BEGIN FOLLOWING ITS RESPECTIVE PATH
%player.followPath(%path,-1);

echo("is this ggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg");

//SET THE CROSSBOW IMAGE IN THE BOTS HANDS AND INITIALIZE HIS CrossbowAmmo TO 1000 BOLTS.
%player.mountImage(CrossbowImage,0);
%player.setInventory(CrossbowAmmo,1000);

//LOAD THE PATROLLER'S TABLE OF NODE DATA DIRECTLY INTO THE BOT. THIS TABLE IS INITIALIZED WITH THE GLOBALS.
//HERE WE ARE LOADING tpath BECAUSE WE WANT HIM TO FOLLOW THE TOWN TABLE
//EXAMPLE: HERE IS THE DATA FOR TOWN NODE 0 ($tpath[0]) AND TOWN NODE 1 ($tpath[1])
// $tpath[0] = "regular 66 18 83 5 99 1";
// $tpath[1] = "door 66 4 99 15";
//bottable[0] will equal $tpath[0], bottable[1] will equal $tpath[1], etc.
//bottable WILL BE USED IN moveToNextNode. IT CAN BE ACCESSED THERE AND WITHIN OTHER FUNCTIONS
//BY USING THE FOLLOWING: %this.bottable[%x] WHERE %x IS THE NODE YOU WANT TO ACCESS
for(%i=0; %i<$TownNumberNodes; %i++)
{
%player.bottable[%i] = $tpath[%i];
}
//SETUP THE TWO SCOUTS AT THEIR RESPECTIVE STARTING NODES. $TorkScout1Node AND $TorkScout2Node ARE INITIALIZED WITH THE GLOBALS
%player = AIPlayer::spawnOnPath("TorkScout1","sleep",%path,$torkScout1Node);//.setTransform("-90 -2 20 0 0 1 0");






%player.mountImage(CrossbowImage,0);
%player.setInventory(CrossbowAmmo,1000);

%player = AIPlayer::spawnOnPath("TorkScout2","sleep",%path,$TorkScout2Node);

%player.mountImage(CrossbowImage,0);
%player.setInventory(CrossbowAmmo,1000);



//===================== SETUP THE LAKE PATROLLING BOT AND 2 SCOUTS ==========================
//SET THE PATH TO BE lakePath BECAUSE WE WANT THE BOT TO FOLLOW THE LAKE PATHS
//THE NAME lakePath WAS SET IN THE FILE stronghold.mis AT THE BOTTOM.
//THE BOT WILL BEGIN AT THE NODE $LorkStartNode. $LorkStartNode IS INITIALIZED WITH THE GLOBALS
%path = "MissionGroup/Paths/lakePath";
%player = AIPlayer::spawnOnPath("Lork","patrol",%path,$LorkStartNode);

//MAKE THE BOT BEGIN FOLLOWING ITS RESPECTIVE PATH
%player.followPath(%path,-1);

//SET THE CROSSBOW IMAGE IN THE BOTS HANDS AND INITIALIZE HIS CrossbowAmmo TO 1000 BOLTS.
%player.mountImage(CrossbowImage,0);
%player.setInventory(CrossbowAmmo,1000);

//LOAD THE PATROLLER'S TABLE OF NODE DATA DIRECTLY INTO THE BOT. THIS TABLE IS INITIALIZED WITH THE GLOBALS.
//HERE WE ARE LOADING lpath BECAUSE WE WANT HIM TO FOLLOW THE LAKE TABLE
//EXAMPLE: HERE IS THE DATA FOR LAKE NODE 0 ($lpath[0]) AND LAKE NODE 1 ($lpath[1])
// $lpath[0] = "regular 75 1 99 11";
// $lpath[1] = "regular 75 2 99 10";
//bottable[0] will equal $lpath[0], bottable[1] will equal $lpath[1], etc.
//bottable WILL BE USED IN moveToNextNode. IT CAN BE ACCESSED THERE AND WITHIN OTHER FUNCTIONS
//BY USING THE FOLLOWING: %this.bottable[%x] WHERE %x IS THE NODE YOU WANT TO ACCESS
for(%i=0; %i<$LakeNumberNodes; %i++)
{
%player.bottable[%i] = $lpath[%i];
}

//SETUP THE TWO SCOUTS AT THEIR RESPECTIVE STARTING NODES. $LorkScout1Node AND $LorkScout2Node ARE INITIALIZED WITH THE GLOBALS
%player = AIPlayer::spawnOnPath("LorkScout1","sleep",%path,$LorkScout1Node.setTransform());

%player.mountImage(CrossbowImage,0);
%player.setInventory(CrossbowAmmo,1000);

%player = AIPlayer::spawnOnPath("LorkScout2","sleep",%path,$LorkScout2Node);

%player.mountImage(CrossbowImage,0);
%player.setInventory(CrossbowAmmo,1000);

//===================== SETUP THE DIRT PATROLLING BOT AND 2 SCOUTS ==========================
//SET THE PATH TO BE dirtPath BECAUSE WE WANT THE BOT TO FOLLOW THE DIRT PATHS
//THE NAME dirtPath WAS SET IN THE FILE stronghold.mis AT THE BOTTOM.
//THE BOT WILL BEGIN AT THE NODE $DorkStartNode. $DorkStartNode IS INITIALIZED WITH THE GLOBALS
%path = "MissionGroup/Paths/dirtPath";
%player = AIPlayer::spawnOnPath("Dork","patrol",%path,$DorkStartNode);

//MAKE THE BOT BEGIN FOLLOWING ITS RESPECTIVE PATH
%player.followPath(%path,-1);

//SET THE CROSSBOW IMAGE IN THE BOTS HANDS AND INITIALIZE HIS CrossbowAmmo TO 1000 BOLTS.
%player.mountImage(CrossbowImage,0);
%player.setInventory(CrossbowAmmo,1000);

//LOAD THE PATROLLER'S TABLE OF NODE DATA DIRECTLY INTO THE BOT. THIS TABLE IS INITIALIZED WITH THE GLOBALS.
//HERE WE ARE LOADING dpath BECAUSE WE WANT HIM TO FOLLOW THE DIRT TABLE
//EXAMPLE: HERE IS THE DATA FOR DIRT NODE 0 ($dpath[0]) AND DIRT NODE 1 ($dpath[1])
// $dpath[0] = "door 99 1";
// $dpath[1] = "regular 99 2";
//bottable[0] will equal $dpath[0], bottable[1] will equal $dpath[1], etc.
//bottable WILL BE USED IN moveToNextNode. IT CAN BE ACCESSED THERE AND WITHIN OTHER FUNCTIONS
//BY USING THE FOLLOWING: %this.bottable[%x] WHERE %x IS THE NODE YOU WANT TO ACCESS
for(%i=0; %i<$DirtNumberNodes; %i++)
{
%player.bottable[%i] = $dpath[%i];
}

//SETUP THE TWO SCOUTS AT THEIR RESPECTIVE STARTING NODES. $DorkScout1Node AND $DorkScout2Node ARE INITIALIZED WITH THE GLOBALS
%player = AIPlayer::spawnOnPath("DorkScout1","sleep",%path,$DorkScout1Node);

%player.mountImage(CrossbowImage,0);
%player.setInventory(CrossbowAmmo,1000);


%player = AIPlayer::spawnOnPath("DorkScout2","sleep",%path,$DorkScout2Node);

%player.mountImage(CrossbowImage,0);
%player.setInventory(CrossbowAmmo,1000);




return; //*TSL changed
}


//*DSG END
#139
10/05/2006 (2:34 am)
This is a great idea, but how do you access the scripts after installing them in starter.fps? Which function needs to be modified to access this from the game?
#140
10/05/2006 (3:09 pm)
Access the scripts by start.fps -> server -> scripts -> aiplyer.cs and in here is where I have to transform the scout to laying down and to do this I have to do : SetTransform("px py pz rx ry rz rd") ...in the game mission editor I can't seem to make the bot seem like they are lying down???? I only seem to move the scout up, down, left and right thats it..not sideways???I know rd is degrees and rx, ry, rz are the rotations but I can't make them rotate lying down????


-not sure what you mean by accessing them. I think they have to be modified in the SM or state machine that was implemented in the preceeding code.