Skip to content

Conversation Script Variables

AikurouAikurou Member Posts: 5
Hello everyone,

I'm not sure if this is the correct place to ask for new features. If it isn't, please let me know where I should post this.

One thing that I've come to hate about NWN conversations is the scripting side of it. There's a lot of potential, but the way it is currently implemented requires you to create multiple copies of the same script.

For example: if you have a generic conversation with 3 options, you'll need a different script for every option, so you know if the user clicked on option 1, 2 or 3. Often all the scripts are the same and the only difference is the option number.

This creates a bloat of conversation scripts and prevents some system designs from being realized.

I found this problem particularly limiting when I decided to create a conversation driven quest system. I wanted a simple way to start an arbitrary quest, like say quest #158, from a conversation. Or perform other operations, like completing a quest, checking if a player meets the necessary requirements to start a quest, etc.

This system would require one new script per action per quest. However, thanks to NWNX, I was able to create a workaround.

By hooking the function that runs a script on the server, I could modify the script name. So I created a system that would detect a # in the script name, take the value after if and make it available through a local variable set on the module that the script could use. The system detected multiple # and set the appropriate variables.

This meant that instead of creating a script "start_quest_158" to start one quest, I could have a generic script "start_quest", and the builders of my module could use it to start a quest by placing the script name "start_quest#158".

This feature was a blessing, it not only allowed my quest system to exist, but also allowed me to significantly clean up my module's scripts by condensing most conversation scripts.

So I would like to propose this becomes a feature in NWN:EE. It could be extended to any script, but to keep things simple I will keep it to conversations.

The idea is to have a variable input field, like the we have for local variables on creatures, for each dialog option. The game would check for these values before running a script, and store them in a place accessible by the script virtual machine.

A new set of functions would be needed to access these, like the ones we have for local variables:

int GetConversationInt(string sVarName);

string GetConversationString(string sVarName);

float GetConversationFloat(string sVarName);

This would allow for much more powerful conversation-driven systems to be define and would help a lot when creating generic conversations.

Comments

  • dTddTd Member Posts: 182
    I like this idea but I'd take it a step further, let's have all these options built into the conversation editor. Why have a script placed for if the thread is seen, let's have it built in, with drop down dialog options and even a text input field. Surely there's a way to embed them right into the conversations themselves.
  • AikurouAikurou Member Posts: 5
    dTd said:

    I like this idea but I'd take it a step further, let's have all these options built into the conversation editor. Why have a script placed for if the thread is seen, let's have it built in, with drop down dialog options and even a text input field. Surely there's a way to embed them right into the conversations themselves.

    Hey dTd,

    Those are some nice ideas, however they would require significant changes to both the game and the editor. Sometimes it's better to take a step back and propose a simpler feature than to take a step forward. The bigger it is, the harder it is for beamdog to accept the idea!
  • dTddTd Member Posts: 182
    Well, nwn2 has some of these features, should be pretty easy to figure out how to add them I would think but I agree with you about large changes vs small ones. Don't mind me, I want the editor robust and strong and feature complete so my priorities are probably different than most. :)
  • pscythepscythe Member Posts: 116
    Aikurou said:


    This meant that instead of creating a script "start_quest_158" to start one quest, I could have a generic script "start_quest", and the builders of my module could use it to start a quest by placing the script name "start_quest#158".

    Let's make it truly generic by allowing arbitrary arguments to be passed onto scripts as they're invoked by conversation condition-checks and action-takens, so that we can even have just one script for an entire conversation if we want all the logic to be in the one place.

    Arguments for scripts have plenty of other uses too beyond just conversations.
  • AikurouAikurou Member Posts: 5
    pscythe said:


    Let's make it truly generic by allowing arbitrary arguments to be passed onto scripts as they're invoked by conversation condition-checks and action-takens, so that we can even have just one script for an entire conversation if we want all the logic to be in the one place.

    Arguments for scripts have plenty of other uses too beyond just conversations.

    Hey pscythe,

    That's is exactly what I'm proposing: A variable input field like the one we have for local variables (allows you to pick the name, type and value), that can then be accessed inside the conversation script.

    While yes, I agree that this kind of functionality is useful beyond conversations, most other script-holding objects can also store local variables. So for the most part, you can use that as a replacement in order to customize the script. My quest system also uses local variables on creatures, placeables, trigger, etc, to do exactly what I have to use this system for conversations.

    For that reason, I think pursuing a more widespread implementation of a system like this would be unnecessary.

    For conversations, you would only need one set of variables per conversation node (per conditional/action script pair). I don't think it would make it possible to put the entire conversation in a single script, as the entry point for a conditional script is different. It would allow you to place your entire conversation on two scripts, however, one for actions and one for conditions.
  • virusmanvirusman Member, Developer Posts: 173
    edited July 2018
    Are there things this approach won’t be able to handle vs. NWN2 approach?
  • virusmanvirusman Member, Developer Posts: 173
    dTd said:

    I like this idea but I'd take it a step further, let's have all these options built into the conversation editor. Why have a script placed for if the thread is seen, let's have it built in, with drop down dialog options and even a text input field. Surely there's a way to embed them right into the conversations themselves.

    I’m not sure what you mean. Can you show an example?
  • AikurouAikurou Member Posts: 5
    edited July 2018
    virusman said:

    Are there things this approach won’t be able to handle vs. NWN2 approach?

    Hey virusman,

    I had no idea that NWN2 had this already. It would make explaining the idea way easier...
    Since I don't have the game installed here, I can not check how it works exactly. From a tutorial I found (https://neverwintervault.org/rolovault/projects/nwn2/nwn2tutorials/31/NWN2_BetaTest_Toolset.pdf), it seems that NWN2 allows you to pass arbitrary values as key/value pairs. That is exactly what I want.

    I do not know, however, how those values are passed to the script. From a screenshot I found, they seem to be passed as an argument to the script's entry point (main function). This approach requires more significant changes to the scripting engine than what I initially proposed. As far as I know, NWN1's script engine does not deal with entry function arguments.

    If the idea is already to upgrade to NWN2's script virtual machine, that would be the best way to do it. If that is not the case, my approach may be simpler to implement in the current engine.

    In the meantime, I'll see if there's interest in putting my current solution in one of the NWNX default plugins. It has it's limits (only integer values, total script name plus values length must fit in 16 bytes), but it has served me very well so far.
  • virusmanvirusman Member, Developer Posts: 173
    The difference is that NWN2 gets the parameters from the script and builds the list of parameters automatically. You can’t pass extra parameters, though. They all have to be defined in main() or StartingConditional().
  • AikurouAikurou Member Posts: 5
    In that case, the method I proposed does have a way of passing "arrays" to the script, by defining an arbitrary number of variables with numbered names. I can't think right now of any situation where this would be particularly useful and couldn't be accomplished in another, simpler way.

    Other than that, the script would have to check for the specific variable name, so it would be pretty much the same.

    Having the UI automatically identify the parameters names and types would lead to a less error-prone system overall.
  • meaglynmeaglyn Member Posts: 151
    z-dialog is your friend.
  • TarotRedhandTarotRedhand Member Posts: 1,481
    You are aware of the 200+ built-in starting conditional scripts aren't you? Ruelk made a list in .doc format and I converted it to pdf including it as part of this tutorial. Between them, these scripts just about cover all the common starting conditional needs of conversations.

    TR
  • TarotRedhandTarotRedhand Member Posts: 1,481
    edited August 2018
    BTW, I think that your generic questing system could probably be fairly easily implemented by creating one or more custom 2da files coupled with local variables. I have done something that you could maybe adapt, that uses this approach. It was designed to be edited to whatever you want. This approach will obviate the need for NwNX thus making it available to sp modules as well.

    TR

  • JapualtahJapualtah Member Posts: 165
    Facing the same old problem right now for the uptenth time, google brought me here.
    I have 8 options in a dialog, I want to code a SINGLE action taken script which can get which dialog line number triggered it.

    My custom crafting system uses a Z-Dialog like system, it sucks because of the bloat. tokens are nice, but the overall module code is a mess because whatever you do you need multiple action taken scripts and you can get many very fast...

    Cheers.
  • drillerdriller Member Posts: 17
    edited September 2018
    You can have one action taken script by displaying the choices one at a time as a token and incrementing/decrementing a local variable via Forward and Back scripts.

    1."A Token displaying a choice" //call your function to set your quest choice based on the variable
    2.Forward. //increments the local variable and sets the token
    3.Back. //decrements the local variable and sets the token

    Its not exactly what you want, but will allow you to use one script to set quest states.
    Japualtah said:

    Facing the same old problem right now for the uptenth time, google brought me here.
    I have 8 options in a dialog, I want to code a SINGLE action taken script which can get which dialog line number triggered it.

    My custom crafting system uses a Z-Dialog like system, it sucks because of the bloat. tokens are nice, but the overall module code is a mess because whatever you do you need multiple action taken scripts and you can get many very fast...

    Cheers.

  • JapualtahJapualtah Member Posts: 165
    Neat idea, but more of a bandaid though :)
  • fot1fot1 Member Posts: 74
    edited December 2018
    My take on this topic: Lets keep it orthogonal with the existing way of passing data around, that is through local variables. Any good developer knows this isn't the best because explicit is always better than implicit, but there is no need to complicate things by using another method considering that there are people that are learning how to program using the toolset. By complicate, I mean using arrays or passing parameters to the script as function parameters.

    This change as first suggested has a great general impact, reducing the number of scripts and the cost of maintenance for a PW. In my opinion this should be top priority to implement, since is low effort and high impact.

    Here is a simple mockup of how this could work:



    As you can see, each line of the conversation will have the same old boring variable screen that other objects do have. On a conversation, script, be it on test or action taken callbacks, a new function needs to be disponible:

    object GetConversationLine();

    This simply returns an "empty" object with the set local variables for that conversation line, which can be get by using the traditional GetLocal methods.
  • ProlericProleric Member Posts: 1,316
    Absolutely endorse the request for a simple parameter.

    There is a workaround using the journal. It so happens that the journal is updated before the Action Taken script executes, so if every action sets a different journal entry, a single script can be used. Since it's not always convenient to have a bespoke journal for every conversation, a dummy journal with a null title and null entries can be used, as long as the script clears the journal entry once it's read the journal value. The downside is that players see the message "Your Journal has been updated" even though it hasn't.
  • badstrrefbadstrref Member Posts: 124
    I wish these dlg variable were implemented, or resolving something like this : <CUSTOM<CUSTOM10>> (wich turns undefined atm)
  • KamirynKamiryn Member Posts: 74
    badstrref wrote: »
    I wish these dlg variable were implemented
    It's possible already and requires very little extra work.

    For N=0,1,2,... write scripts
    #include "incfile"
    void main()
    {
    	SetAction(N)
    }
    

    and in the include file
    void SetAction(int nAction)
    {
    	SetLocalInt(OBJECT_SELF, "ACTION", nAction)
    	string sScript = GetLocalString(OBJECT_SELF, "ACTION_SCRIPT");
    	if (sScript!="")
    	{
    		ExecuteScript(sScript, OBJECT_SELF);
    	}
    }
    

    In your 'action script' you write
    void main()
    {
    	int nAction = GetLocalInt(OBJECT_SELF, "ACTION");
    	switch (nAction)
    	{
    	case 0:
    		...
    	case 1:
    		...
    	
    	}
    }
    

    Set the action script in the previous conditional script with
    SetLocalString(OBJECT_SELF, "ACTION_SCRIPT", "MyActionScript");
    

    You can make it even more simple if you drop the 'action script' and move the case statement into the next conditional script (that what I'm doing in https://neverwintervault.org/project/nwn1/hakpak/original-hakpak/customize-character-override-hak-ccoh ).
  • TsiZTsiZ Member Posts: 6
    Sorry but I didn't understand the explanation.

    "For N=0,1,2,... write scripts" where put this code?

    Please if you can be more clear why this thing you say would be fantastic.

    Thanks in advance

  • KamirynKamiryn Member Posts: 74
    It's not so complex as it looks ;) ...

    You have to write N action scripts (e.g. call them action_01.nss, action_02.nss, ...

    action_01.nss:
    #include "act_include"
    void main()
    {
    	SetAction(1)
    }
    

    action_02.nss:
    #include "act_include"
    void main()
    {
    	SetAction(2)
    }
    

    action_03.nss:
    #include "act_include"
    void main()
    {
    	SetAction(3)
    }
    

    all using this include file:
    // include file 'act_include.nss'
    void SetAction(int nAction)
    {
    	// set the 'action' (i.e., the dialog line the user has selected.
    	SetLocalInt(OBJECT_SELF, "ACTION", nAction)
    	// retrieve the action script which was set previously
    	string sScript = GetLocalString(OBJECT_SELF, "ACTION_SCRIPT");
    	if (sScript!="")
    	{
    		DeleteLocalString(OBJECT_SELF, "ACTION_SCRIPT");
    		ExecuteScript(sScript, OBJECT_SELF);
    	}
    }
    

    Now your dialog has to look like

    (cond_script)node
    |- line 1 (action_01)
    |- line 2 (action_02)
    |- line 3 (action_03)

    using the action scripts from above and the conditional script cond_script.nss has to look like:
    // cond_script.nss
    int StartingConditional()
    {
    	...
    	// set the 'action script' to be used in 'action_01', 'action_02', ...
    	SetLocalString(GetPCSpeaker(), "ACTION_SCRIPT", "myactionscript");
    	...
    	return TRUE;
    }
    

    and in 'myactionscript' you have write
    void main()
    {
    	// get the line the user has selected
    	int nAction = GetLocalInt(OBJECT_SELF, "ACTION");
    	// depending on the selected line perform different actions...
    	switch (nAction)
    	{
    	case 0:
    		...
    	case 1:
    		...
    	
    	}
    }
    

    The advantage is that you write the script action_01, action_02, ... once and you can use them throughout all your dialogs. This will reduce the number of scripts and especially the number of script you have to maintain.

    You can improve this system even more by also writing contitional scripts condition_01, condition_02, ...:
    // condition_01
    int StartingConditional()
    {
    	object oPC = GetPCSpeaker();
    	int nReturn = GetLocalInt(oPC, "CONDITION_01")==1;
    	DeleteLocalInt(oPC, "CONDITION_01");
    	return nReturn;
    }
    
    // condition_02
    int StartingConditional()
    {
    	object oPC = GetPCSpeaker();
    	int nReturn = GetLocalInt(oPC, "CONDITION_02")==1;
    	DeleteLocalInt(oPC, "CONDITION_02");
    	return nReturn;
    }
    
    ...
    and make your dialog look like:

    (cond_script)node
    |- (condition_01) line 1 (action_01)
    |- (condition_02) line 2 (action_02)
    |- (condition_03) line 3 (action_03)
    ...

    and set the conditions "CONDITION_01", "CONDITION_02"... inside the 'cond_script':
    // cond_script.nss
    int StartingConditional()
    {
    	object oPC = GetPCSpeaker();
    	...
    	// set the 'action script' to be used in 'action_01', 'action_02', ...
    	SetLocalString(oPC, "ACTION_SCRIPT", "myactionscript");
    
    	// show line 1
    	SetLocalInt(oPC, "CONTITION_01", 1);
    
    	// hide line 2
    	SetLocalInt(oPC, "CONTITION_02", 0);
    
    	// show line 3
    	SetLocalInt(oPC, "CONTITION_03", 1);
    
    	...
    	return TRUE;
    }
    

    This is just a rough write down. The https://neverwintervault.org/project/nwn1/hakpak/original-hakpak/customize-character-override-hak-ccoh uses this and the source code is included in the download (inside the erf file - mk_inc_generic.nss - documentation lacks a little bit however).

  • TsiZTsiZ Member Posts: 6
    Thanks for the explanation.
    Great job ccoh :)

    TsiZ

  • ForSeriousForSerious Member Posts: 467
    Because my search to set variables in conversations, to be used in scripts, led me here. I would like to link an updated approach found in this post.
  • GenisysGenisys Member Posts: 37
    I believe the easiest way to handle HUGE conversations is with simply Custom Tokens, StartingConditional & Action Taken Scripts for EVERY LINE, and then coding and include script to handle settings Custom Tokens, Checking Conditionals & Actions Taken for each line...

    It might seem super complex, but it's really not, you just use Switch/Case statements for two variables...

    Conversation Section & Conversation Line is fed by the actual Starting Conditional & Action Taken Scripts.

    e..g DoLineAction(oPC, nLine); / GetStartingConditional(oPC, nLine);

    Those are just custom functions you can create to quickly swim through MILES of code!

    Objectively, the draw back is, it's a bit tedious to create, but it can VERY SIMPLE TOO!

    With just 3 tokens you can create A LOT of various conversations on the fly from ONE Conversation...
    You'd just check the tag of the NPC / OBJECT_SELF to determine which conversation you are running.
Sign In or Register to comment.