I set out to create a quest the progress and ending of which would depend on multiple choices and actions. How do I go about that? Unfortunately, while I know quests from the main campaign and TMoB well enough to understand vaguely what is what in the scripts, I know nothing about writing the scripts myself - and writing scripts seems inevitable if I want to have multiple endings. Do you know any tutorials on that? On writing scripts that have several mutually exclusive states, putting choices that lead player to different actions and options, etc.?
Examples of things Iād like to do:
Have two factions fighting each other and give the player a choice to side with one of them or neither (like with the fire giants and Tholapsyx or inter-demon fights in Ammon Jerroās Haven).
A riddle: either or both like the ones in the Neverwinter Archives or, even better, like the one in the Mysteries of Westgate with player having to spell out a word.
High/low influence on companions triggering certain options.
High/low influence on NPCs (like with Nasher) that decides the ending of the quest and who or what the player will have to fight, depending on who she/he supported more often during the quest.
Basic dialogue action options (for example - save someone or let them die).
Changing appearance of a location based on the playerās choices (for example player chose to side with NPC A and when she/he gets back to a certain location, someoneās standing there / thereās a light / an object appears, etc.)
Do you have ony pointers? What are the basic commands that Iād have to use here? How do I juggle that? Would it be easier to put all different things that can influence the ending in one script or should I write a separate script for every point that creates nonlinearity and then another one that rules them all?
scripts are fired by events. Just about every type of object in the game has a set of events; a door-object has for example an OnOpen event, so if you write a script for door(s) when it/they open and slot the scriptname on doorās/doorsā instances or blueprints (and your compiled script is an accessible resource ofc ā ie. in the module folder or its Campaign folder or in an associated .hak, etc) then when said door(s) open(s) that script fires.
[that didnāt come out right hehe]
note: Items donāt use events and rely instead on tag-based scripting ā¦ one of the events is, eg. OnItemAcquired
Dialogs can have ActionScripts and ConditionalScripts on the dialog-nodes.
ā¦ it shouldnāt be too hard to find tutorials on how to script in nwn1/2 (theyāre largely the same language with mostly the same game-mechanics)
google ānwn lexiconā
and @Tarot_Redhand has pdf-tutorials for scripting, i believe; search the vault for āTRās Basicsā
moving on to what youāre actually talking about ā
To do all that stuff ā¦ i imagine youāre going to be setting and getting a whole bunch of local_variables. Eg,
// PC gives Beorn a fish
SetLocalInt(oPC, "GaveBeornFish", TRUE);
or store the var on Beorn:
// PC gives Beorn a fish
SetLocalInt(oBeorn, "GotFish", TRUE);
that would probly be done in a dialogās actionscript. Youāll have to learn to define objects like āoPCā and āoBeornā ā by using OBJECT_SELF when Beorn is the owner of a dialog, or GetObjectByTag("beorn") ā there are many ways to define an object and such decisions are up to you.
If you want to keep a tally of the PCās influence with Beorn, thatād be done in another variable that is incremented/decremented based on the PCās actions and dialog. And perhaps stored on the module-object via a custom function:
// changes PC's current influence with a creature or organization
void Influence(string sCreatureOrOrganization, int iInfluenceChange)
{
object oModule = GetModule();
int iInfluenceCurrent = GetLocalInt(oModule, sCreatureOrOrganization);
SetLocalInt(oModule, sCreatureOrOrganization, iInfluenceCurrent + iInfluenceChange);
}
Then you can write dialogs with conditionalscripts with nodes that show or donāt show based on PCās influence with Beorn ā¦ etc
// checks if PC has enough influence with a creature or organization
int HasInfluence(string sCreatureOrOrganization, int iInfluenceRequired)
{
return GetLocalInt(GetModule(), sCreatureOrOrganization) >= iInfluenceRequired;
}
in practice whatās above is very simplistic. Multiplayer modules add complications, and Campaigns with multiple modules might need to use global_vars instead of local_vars ā¦
but
variables donāt have to be used always. Journal entries can be set and tested against and used much like locals/globals.
And rewards might be given to the PC. For example, Beorn might grant the PC a unique staff when heās given a fish ā¦ the staff can be flagged Plot (canāt be sold) and Cursed (note: cursed does not mean cursed, it means that its possessor canāt get rid of it) ā¦ then a subsequent script could check if PC has the staff, if he does then he gave Beorn a fish ā¦
etc.
(the deep end of a very big lake)
probably handled best in an areaās OnEnter script. Test against one (or more) of your variables, journal states, etc and create and destroy creatures, placeables, and effects (etc) as you desire,
Or even have two different areas, and store a waypointās Tag as a string_variable that the party gets sent to when a specific event fires ( remember scripting stuff is typically about events so you just have to learn what events the executable allows us to intercept aka hook )
// set "beorn_state" in his dialog (eg.) and run this when the decisions matter for the plot
switch (GetLocalInt(GetModule(), "beorn_state"))
{
default:
case 0: // none
break;
case 1: // gave beorn a fish
// set other vars and/or do whatever
break;
case 2: // fish done, gave beorn an acorn
// set other vars and/or do whatever
break;
case 3: // fish and acorn done, gave beorn his staff back
// set other vars and/or do whatever
break;
}
again, you might want to use global_vars (or journal entries) if youāre designing a multi-module Campaign,
non-linear scripting gets very complicated fast. Handling all possible cases when only 3 variables are involved could look something like this
int a = ; // define these
int b = ;
int c = ;
if (a && b && c)
{}
else if (a && b && !c)
{}
else if (a && !b && !c)
{}
else if (!a && !b && !c)
{}
else if (!a && b && c)
{}
else if (!a && !b && c)
{}
else // ok am tired now ...
but you get the idea
advice (technically it can be done either way) : use individual scripts to set vars or sets of related vars ā¦ then have larger scripts that check whatever vars are relevant and decides an outcome(s) *
* if the plot is short and simple, thereās likely only one āmasterā checker script; but if you got a sort of round-robin variable tournament going on, then youād likely want to funnel variables down through several decision-making scripts, at various points in the plot ā¦
kevL_s has already answered most of your questions, but I just thought to add a bit regarding:
In my fourth module A Strange World IV - The traveler I have riddles that use the function DisplayInputBox. Itās really handy and lets the player write the riddle answer. I then have a few scripts (itās a bit complicated) that checks for what the player writes by using custom tokens. Hereās some of the scripts I use for this:
Then the gui_checkanswer and gui_noanswer scripts look like this:
//gui_noanswer
#include "custom_token_inc"
void main()
{
object oPC = GetFirstPC(FALSE);
SetCustomTokenEx(1002, "no answer");
//SetLocalString(GetModule(), "CUSTOM24", sPlayerAnswer); //To be able to call this inside a script and just not just in a conversation by <CUSTOM1001>.
//According to 4760 one should use numbers above 1000 to be safe that it's not already used by the engine, but I read in NWNlexicon that number 24 wasn't used.
SetGlobalInt("riddleanswer", 1);
AssignCommand(oPC, ClearAllActions());
SetCutsceneMode(oPC);
AssignCommand(GetNearestObjectByTag("npc_frank"), ActionStartConversation(oPC, "c_v_elfriddle"));
}
//gui_checkanswer
#include "custom_token_inc"
void main(string sPlayerAnswer)
{
object oPC = GetFirstPC(FALSE);
if (sPlayerAnswer == " ") // <-- keep one space between quotes
{
SetCustomTokenEx(1002, "no answer");
}
else
{
SetCustomTokenEx(1002, sPlayerAnswer);
}
SetGlobalInt("riddleanswer", 1);
AssignCommand(oPC, ClearAllActions());
SetCutsceneMode(oPC);
AssignCommand(GetNearestObjectByTag("npc_frank"), ActionStartConversation(oPC, "c_v_elfriddle"));
}
The include script custom_token_inc:
// Custom Token Library script - custom_token_inc
//::////////////////////////////////////////////////////////////////
// void SetCustomTokenEx(int iToken, string sValue)
// Sets the custom token identified by the number in iToken to the
// string value specified in sValue. Also duplicates the value as a
// local string stored on the module object so the GetCustomTokenEx
// function can retrieve it at any time.
void SetCustomTokenEx(int iToken, string sValue);
// string GetCustomTokenEx(int iToken)
// Retrieves the current value of the custom token identified by the
// number in iToken by reading the local string variable containing
// the duplicated token value which was stored on the module object
// by the SetCustomTokenEx function. If the custom token was set using
// the default SetCustomToken function instead of SetCustomTokenEx,
// this function may return an incorrect or blank string.
string GetCustomTokenEx(int iToken);
void SetCustomTokenEx(int iToken, string sValue)
{
if (iToken <= 0)
return;
// Change the custom token variable and duplicate it.
SetCustomToken(iToken, sValue);
SetLocalString(GetModule(), "CUSTOM" + IntToString(iToken), sValue);
}
string GetCustomTokenEx(int iToken)
{
if (iToken <= 0)
return "";
// Return the content of the module variable being used to duplicate
// the token variable.
return GetLocalString(GetModule(), "CUSTOM" +IntToString(iToken));
}
Just to be clear: I didnāt write these scripts on my own, but I had kevL_s, travus, 4760 and others help me with them (I donāt actually recall now exactly what is my own code and what the others have done, but the main gist of it is I canāt take credit for these). In any case, they work really well.
Then in the dialogue you have to use something like this for it to work: You think the word is: <CUSTOM1002>? Let's see if that is correct...
When it comes to scripting kevL_s, travus, Lance_Botelle, my brother and 4760 has taught me everything I now know.
And @kevL_s is the standout here. Heās a very good teacher, IMHO. Very clear and to the point.
Wow, thatās a helpful community for you! Thank you so much! I have to digest what you wrote, check out the tutorials, and determine what is within my capabilities and what isnāt - and then check my priorities and maybe lower my ambitions a bit for now :P.
Iāll probably get back to you with some specifics after I try writing a script and make it actually work (successfully or not.)
Thanks again, you really made my day! I wasnāt expecting this much of a response :).
All very good comments from the wise people here but I canāt script so Iād do the whole thing with conversations the scripts are already there in the actions tab and checks can be done in the conditions tab you just fill in the blanks.
Kill damsel in distress ga_global_int ādamsel_distressā ā1ā
Save damsel in dstress ga_global_int ādamsel_distressā ā2ā
Distress damsel even more and bugger off ga_global_int ādamsel_distressā ā3ā
Then just use the conditions tab for global_ints in the conversation with the damselās husband and you have all your options. Theyāll all have similar names but with gc not ga.
When you start a fight with whatever faction during a conversation set another int then everybody knows what you did later when it comes to the conversation conditions.
You can also spawn ( ga_creat_obj ) / destroy ( ga_destroy ) an npc via the actions tab in another area at the same time as you have a chat, put a speak trigger nearby the spawn point and assign it to the spawned npc and when you walk past theyāll start a conversation. If you did whatever doesnāt need them theyāll never arrive and thereāll be no conversation ever.
Back to the damselās husband, heās got a new girlfriend now standing next to him and owning the conversation trigger. He couldnāt care less about his wife but was just asking you to help because his mother in law was listening ( she got destroyed ga_destroy ) ! If you turn up with his wife she can now have a fight with the girlfriend.
Anything you do in a conversation can also have a journal entry then use the conditions to check what was actually done according to the journal number.
@Rimavelle If you want to look at those tutorials I made here are the links to the project pages. One thing to remember is that I made them for NwN. In a small number of cases there are differences to NwN 2.
I have written a couple of NWN2 tutorials that may also help you move in the right direction. Itās along the lines of the info you have probably already received, but in case you would like some extra ā¦
If you have any questions above those already raised, just ask.
EDIT: Also, sometimes looking over other peopleās work can help you to understand what to do ā¦ I have a number of factions and lots of various puzzles (including word puzzle doors) in my module, which you can take a closer look at here:-
Rimavelleā¦ Another useful ready made one is gp_talk_object. Go to any placeable properties, look at the scripts, select on used and open it ( arrow thing ), type in gp, scroll down to the talk object one and select it.
Now follow the instructions, make it usable, plot ( so it canāt be wrecked ) and give it a conversation. It is now scripted to make choices, explode or whatever according to your conversation.
There will be times when you need something more than what is already available and you canāt pre plan or work it in via a conversation. Thatās when itās vital to know how to script or in my caseā¦
Come here being annoying and asking for help, sorry all you scripting wizards, but you have saved my modules and sanity
I agree with @Tsongo that the gp_talk_object is a very useable tool. Itās sometimes a lot easier to do stuff through conversations and stock scriptsā¦but unfortunately there are times when you absolutely need to do your own scripts too. And if you want to go further down the rabbit whole you begin messing with XML scripts (which is quite confusing. If you find normal scripting hard, like I still do, XML is quite a lot worse and, at least to me, quite a bit more illogical), which @Lance_Botelle is very skilled at.
Donāt be a afraid to ask here on the forums. Iām here almost every day and there are quite a lot of people very skilled at scripting that can help you. If itās basic stuff, even I can be of help.
Iāve been working on my quest, using actions and conditions in my conversations and itās definitely a good start! However, I ran into a problem: Iām using an NWN1 style of conversation and I canāt freeze the PC until the conversation ends (which means that the player can still move during the conversation and stop it - and basically crash the entire quest that way.) ga_party_freeze doesnāt work. How do I go about that? How do I stop the PC from moving for the entire conversation?
I always use a small custom script with the function SetCutsceneMode set to TRUE. I think the flag for Multiplayer Cutscene might perhaps do the same thing. There are other functions also like freezing the legs of a charater. I had that done in my arm wrestling scene in ASW4.
EDIT: Here is the small script for preventing the PC to move without the whole cutscene thing:
Another day, another problem - my quest is based on āside with one, piss off the otherā scenario and letās say we have Candidate1 and Candidate2. With one dialogue line I wanted to do two things:
Spawn hostile Candidate1 in front of the PC to have a final battle with them.
Spawn friendly Candidate2 in another area for the final talk and rewards.
Or in the other scenario, do the opposite:
Spawn hostile Candidate2 in front of the PC to have a final battle with them.
Spawn friendly Candidate1 in another area for the final talk and rewards.
Since I wanted them to have custom names, I created an area not linked to the story in any way. I decided Candidate1 will be c_dryad and Candidate2 - n_moire. So I created two versions of both candidates (c_dryad, faction: hostile; c_dryad2, faction: merchant - and the same with n_moire) and then used ga_create_obj in conversation, pointing the appropriate waypoints. But it didnāt work. Is it because said creatures already exist in another area?
Any idea how to do it, hopefully without scripting? Or maybe with a copy-paste type of scripting where I can just create 4 templates and change names and factions, leaving other things the same?
I can look into it for you tomorrow (if noone else gets to it before then). I would probably use a custom script for this, but maybe thatās just me. I find it easier when there are quite specific things you want done, to do a custom script instead of reliying on stock scripts.
Alright, so hereās my script. It is untested. I hope it will work for you. Iām sure travus or kevL_s will correct me if Iāve made any mistakes.
#include "nw_i0_generic"
void main()
{
/*
I believe that if you have an area with two instances of both the NPCs, the ones that are hostile
will probably fight the others, which might then end up with dead NPCs before things are about to take
place. Therefore I have in this script only one of each (faction Commoner or something non-hostile
for both), and instead change their faction when someone is to attack the PC. The script assumes the
NPCs are already in the world just in a different area. They are therefore jumped to waypoints or the
PC. If they are NOT in the world we need to spawn them instead and that requires different scripting.
/andgalf
*/
object oPC = GetPCSpeaker();
object oCandidate1 = GetObjectByTag("c_dryad"); //I hope these are the correct tags for the NPCs. If not, change them.
object oCandidate2 = GetObjectByTag("n_moire");
object oWaypoint = GetObjectByTag("thewaypointtag");
//This needs to be changed to the tag where the PC is to have final talk and reward.
//On the node of where this scene is to take place
//do a ga_global_int and set either "c1hostilec2friend" or "c1friendc2hostile")
if(GetGlobalInt("c1hostilec2friend"))
{
AssignCommand(oCandidate1, ClearAllActions());
AssignCommand(oCandidate1, ActionJumpToObject(oPC));
AssignCommand(oCandidate2, ClearAllActions());
AssignCommand(oCandidate2,JumpToObject(oWaypoint));
ChangeToStandardFaction(oCandidate1, STANDARD_FACTION_HOSTILE);
SetIsTemporaryEnemy(oPC, oCandidate1);
AssignCommand(oCandidate1, ActionAttack(oPC));
AssignCommand(oCandidate1, DetermineCombatRound(oPC));
}
else if(GetGlobalInt("c1friendc2hostile"))
{
AssignCommand(oCandidate2, ClearAllActions());
AssignCommand(oCandidate2, ActionJumpToObject(oPC));
AssignCommand(oCandidate1, ClearAllActions());
AssignCommand(oCandidate1,JumpToObject(oWaypoint));
ChangeToStandardFaction(oCandidate2, STANDARD_FACTION_HOSTILE);
SetIsTemporaryEnemy(oPC, oCandidate2);
AssignCommand(oCandidate2, ActionAttack(oPC));
AssignCommand(oCandidate2, DetermineCombatRound(oPC));
}
}
Just in case this is what happened, to create an object you use itās template not tag ( near bottom of properties ). Make blueprints of anything you want to spawn and check itās template number to be sure you get the right thing as sometimes 0 is added to it. This is shown as resref in the blueprints window ( black writing on all templates so you canāt miss them ) and template in the creatureās properties.
You can also turn anything hostile in a conversation with ga_faction_join and put itās tag in the first box and $HOSTILE in the second box so the creatures can be in the area already, no need to spawn.
So you can have the player talking with candidate 1 annoying them and on the last line of the conversation they go hostile. Or the other one in the distance candidate 2 goes hostile etc.
Iām pretty sure you can change things to hostile this way in any area so the PC will walk into hell when they return somewhere, but canāt remember.
Hereās an awesome script from Claudius33 that turns three tags hostile in any area so you can have as many creatures as you want going mad so long as they share the same three tags. It works just like a normal conversation script you fill in the blanks with the three tags.
The second one is for making anything hostile if you did something to upset them and altered the journal accordingly. eg. Youāve upset the orcs now ! Put it on any creatureās āon perceptionā script. So now you have the ability to turn entire towns/ gangs etc. against the PC according to his/ her actions.
//Script by Claudius33// to set hostile the tagged creatures. Many thanks for making people in my mod angry!
void SetHostile(string sTag)
{
int n = 0;
object oHostile = GetObjectByTag(sTag);
while (GetIsObjectValid(oHostile))
{
ChangeToStandardFaction(oHostile, STANDARD_FACTION_HOSTILE);
n++;
oHostile = GetObjectByTag(sTag, n);
}
}
void main(string sT1, string sT2="", string sT3="")
{
if (sT1 != "") SetHostile(sT1);
if (sT2 != "") SetHostile(sT2);
if (sT3 != "") SetHostile(sT3);
}
//second script for turning anything hostile according to the journal. the nint< is
because it's checking to see if you've reached the point of no return. eg. journal
entry "you annoyed the orcs" is number 3 they will attack, if number 2 is "you have
to speak to the orc chief" then no attacking yet. This is good because you could
sneak away without being spotted by anything hostile.
void main()
{
object oPC=GetLastPerceived();
object oNPC=OBJECT_SELF;
if(!GetIsPC(oPC))return;
int nInt;
nInt=GetLocalInt(oPC,"NW_JOURNAL_ENTRYjournal tag");
if(nInt< whatever your number is for attack)
return;
{
ChangeToStandardFaction(oNPC,STANDARD_FACTION_HOSTILE);
AssignCommand(oNPC,ActionAttack(oPC));
}