Easiest way to get PC to narrate bits of story?


#21

Yep. :smiley:

https://neverwintervault.org/project/nwn1/other/tool/colours-and-codes-stringtorgbstring
^- and Tarot’s made colourcharts for StringToRGBString. The second parameter there, (“700”) alters the colour.


#22

I saw that earlier. Yeah. Thanks again.

I’m really keen to have a muck about with this. I’m gonna call it a day there, though. My head is nipping a bit.

But tomorrow I’m gonna have a stab at looking at the code you’ve given me to try to figure out what each bit is doing.


#23

Anytime. Shout when the next issue pops up. :slight_smile:


#24

I’ll hold you to that !


#25

Maybe unhelpful at this point, but for future readers…

This is so much easier if you tell the PC to have a conversation with themself (a one line script).

Plus, the player can read at their own pace. A bunch of floating strings can be frustrating, either vanishing before the player has read them, or persisting long after.


#26

Not unhelpful at all, Proleric. Thanks for jumping in.

Barbarian, as you can see, was a huge help last night and also suggested this method. I’m interested in what you said about the one line script ( for reasons that are maybe obvious !) but also because eventually I’ll start to understand some of this.

I’ve tried this but its not working at all. I’ve put what I’ve tried below but all I’ve really done is shoehorn what Barbarian showed me into a different way of doing it and I suspect that there may be a bigger difference in the type of script than I’m appreciating.

Trigger Properties ( is it still an onentering script?

38

Script itself

Error when compiling (I’m assuming its in line 3?)

The last bit is that I’m not sure if I still use the variables (SPEAK_0, SPEAK_01 etc) to contain the dialogue or if it’s all in the script as above.


#27

You lost this part of the script to declare and initialize the oPC variable:

object oPC = GetEnteringObject();


#28

Expanding on that:

Declaring and defining variables.

Within a script, a variable needs to be declared before it can be used for anything. Until oPC has been declared, it doesn’t exist, and the compiler has no idea what “oPC” means or what it’s supposed to do with it.

You can declare variables like this:

    object oCake;
    object oPC, oNPC, oSuperScaryVillain;
    string sNPCTag;
    int nCookiesConsumed;
    float fSecondsUntilExplosion;
    location lSomewhereElse;
    vector vIamaVectorandmynameisHector;

Optionally, you can immediately define a variable when you declare it, too:

    object oPC = GetFirstPC();
    string sCake = "CAKE IS GREAT WE SHOULD BAKE SOME CAKE.";
    float fBouncyCastleJumpIntervals = 0.355;
    int nAmountOfCookiesWeHaveLeft = 256;

You can pick and choose any names you like, but it’s very advisable to go with something descriptive. Neglecting readability comes back to bite you in the butt sooner or later.

If a new variable is declared but not further defined, it stays at it’s default value. An undefined integer equals 0 (which equals FALSE). An undefined object equals OBJECT_INVALID. An undefined float equals 0.0.

So, to assign a command to the object we’re calling “oPC” within this script, we need to declare an object type variable oPC first, and then link an actual, existing object within the module, to that name (within the context of this script). Hence:

    object oPC = GetEnteringObject();

Further reading:
https://neverwintervault.org/project/nwn1/other/trs-basics-variables-types-and-functions-tbc
https://nwnlexicon.com/index.php?title=Category:Primers



The thing with the local strings, versus the thing with starting self-conversations.

OK, now, these are two different approaches.

In one variant, the object entering the trigger is made to speak several text strings that are stored directly on the trigger. We’re checking whether any string has been stored on the trigger under the name “SPEAK_0”, and then cycling upwards through “SPEAK_1”, “SPEAK_2”, and so on, until eventually we do not find a non-empty string anymore, after which we’re done - so, for this method, local strings need to be stored on the trigger.

In the other, the object entering the trigger is told to start a conversation. The second route does not reference any local strings, so we don’t need local strings stored on the trigger, but we do need a conversation file. OK, going off on a bit of a tangent:

ActionStartConversation is a function, and it’s one of many. Functions may require parameters to work.

// Starts a conversation with oObjectToConverseWith - this will cause their
// OnDialog event to fire.
// - oObjectToConverseWith
// - sDialogResRef: If this is blank, the creature's own dialogue file will be used
// - bPrivateConversation
// Turn off bPlayHello if you don't want the initial greeting to play
void ActionStartConversation(object oObjectToConverseWith, string sDialogResRef="", int bPrivateConversation=FALSE, int bPlayHello=TRUE)

In this case, ActionStartConversation needs to be given an object as it’s first parameter; this is the object that the caller of the function should be starting a conversation with.

The second parameter, a string, is optional, as if you do not call ActionStartConversation with a second parameter, it has a built-in definition to default to. In this case, that definition is “” - an empty string. Note the explanation in the comments - if you leave sDialogResRef blank, the target’s own conversation file will be used. For this to work, the target needs to have a conversation linked to it, though. If it doesn’t, you’d be trying to start a non-existent conversation, and nothing would happen.

Be aware that the caller of the script may vary, depending on how and by whom a script got executed/started (called), and if you do not assign them to somebody else, it will be the caller of the script who will be executing the functions.

In the following example (wrong way!), the trigger is assigned the command to start a conversation with the entering object:

    object oPC = GetEnteringObject();

    ActionStartConversation(oPC, "caketownconvo");

So you’ll always want to keep an eye out for who is executing the function, and assign commands to other actors as needed:

    object oPC = GetEnteringObject();

    AssignCommand(oPC, ActionStartConversation(oPC, "caketownconvo"));

Action commands are added at the end of the action queue, though, so watch out for the action queue, and make sure to clear it where needed:

    AssignCommand(oPC, ClearAllActions(TRUE));

If you want to set up an all-purpose trigger that makes the entering PC start a conversation, once, it could look like this:

void main()
{
    object oPC = GetEnteringObject();
    string sIdentifier = ObjectToString(oPC);
    string sTriggerTag = GetTag(OBJECT_SELF);

    // We only do this for PCs, and only once per PC.
    if (!GetIsPC(oPC) || GetLocalInt(OBJECT_SELF, sIdentifier))
        return;
    SetLocalInt(OBJECT_SELF, sIdentifier, TRUE);

    // Clear the PC's current action queue, and take them out of combat mode if necessary so they can converse.
    AssignCommand(oPC, ClearAllActions(TRUE));
    // The trigger tells the PC to start a conversation with the PC, using the tag of the trigger to find the right conversation.
    AssignCommand(oPC, ActionStartConversation(oPC, sTriggerTag));
}

In this variant, you’d be setting the tag of the trigger to be the name of the conversation file that you want the PC to start when they enter it.

There’s a whole bunch of neat tricks to make the whole thing more stable, using functions like SetCommandable, ActionWait, putting normally instantly-firing functions into the action queue using ActionDoCommand, and altering the abort condition of the trigger + adding a “conversation completed” script into dialogue nodes to make a “start self-conversation” floor trigger definitely fire as well as keep firing until the player definitely has completed the conversation. ::rubs hands together, gleefully:: So the rabbithole goes deeper, and we can get into it further if you’d like.

For the self-conversation route, you’ll need to add a new conversation:

image

Give it some lines:

… and save it under a name. I’ve called it “selfconv_001” here.

Now, the trigger will be trying to start a conversation using the string parameter you’re using in ActionStartConversation. In the last example, I’d been using the tag of the trigger for this, so in this case, you’d want to set the tag of the trigger to be the name of the conversation file:

image




::cheerfully drags in more reading material:: :smiley: :smiley: :smiley: :smiley: :smiley:

https://neverwintervault.org/project/nwn1/other/trs-basics-mostly-operators
https://neverwintervault.org/project/nwn1/other/trs-basics-decisions
https://neverwintervault.org/project/nwn1/other/trs-basics-boolean-algebra

https://nwnlexicon.com/index.php?title=BubaDragon_-_Guide_to_Debugging

https://nwnlexicon.com/index.php?title=Grimlar_-_Introduction_To_Tag_Based_Scripting

:smiley: :smiley: :smiley: :smiley: :smiley: :smiley: :smiley: :smiley: :smiley: :smiley:

I have more links and I will post them if given the opportunity, I warn you now.

#29

Thanks, Meaglyn. I’m understanding that now. It wasn’t working and I figured that was part. However, the other part was wondering where the actual words of the conversation go and I think that’s what Barbarian is trying to explain below. I’m off to read that and see if I can make sense of it. I’m starting to understand terms which are recurring and loosely what they do but putting it together is way beyond me at the moment.


#30

Thanks, Barbarian. That actually makes a little sense to me now. I’ve used the Conversation editor ( badly) before and I realise I was busy trying to get the script to use some variables like before because I couldn’t get where else the conversation would be stored.

In your example I’m a little confused by the need for anything in the conversation editor other than the first OWNER line.

Can it not just go on for whatever length I need or is there a purpose to the replies ? I wasn’t looking for him actually to talk to himself and giving himself multiple choice for the replies I’m guessing you put them there for dramatic effect? :grinning:

Will have a go.


#31

It’s purely for entertainment purposes, yeah. :grin: A linear conversation will work just the same:

Or a single-node one, too:


#32

Ok, had a go but not working. I’ve saved a conversation as selfconv_1 and included it in code below but no dice !

Script

Any thoughts?


#33

Two thoughts:

1.) Without quotation marks, selfconv_1 in your excerpt won’t be recognized as a string. This should throw a compiler error, since the compiler has no idea what selfconv_1 is meant to be, as is.

Change the following line from

    AssignCommand(oPC, ActionStartConversation(oPC, selfconv_1));

to

    AssignCommand(oPC, ActionStartConversation(oPC, "selfconv_1"));

… and it should work, providing a matching conversation named “selfconv_1” exists.

2.) The original setup uses the previously declared variable “sTriggerTag”, which retrieves the tag of the trigger.

If you enter a fixed string (“selfconv_1”) as being the conversation the trigger should start, the trigger will only ever be able to start that one particular conversation. If you use the original version, and have it start a conversation that matches it’s tag, you can re-use the same trigger blueprint for many different conversations. This will save you the annoyance of having to have a separate trigger script per conversation.


#34

Ok, so we now have success. ( I’m ashamed to admit that the mistake was mine. I’d saved the conversation as “selfconv_01” not “selfconv_1” ) :blush:

However, it appeared just above PC’s head not in a dialogue box as per your example. I had thought that this method would produced the normal dialogue box. I’m happy with that, I’m just getting confused.

Am I right in saying that options at the moment that we’ve explored are

  1. Original one with SPEAK variables which allows the script to put up each of the variables one at a time and me to set a delay number? ( works although when I try to set delay above 9 I get an error.Doesnt matter as I can break up what I want to say into smaller chunks) OR

  2. This method where we link to a conversation file which can have one long OWNER node or more with no response between? I thought Proleric was saying that this second method was a one liner of code?

Both seem now to work fine although the original would seem to have the advantage of being reusable ?

Am I understanding this at all ?


#35

Welcome to coding, the activity of the world where a single missed parenthesis or semicolon, or a minor typo, are leading causes of premature balding due to hairs being pulled out in frustration while spending hours and hours trying to figure out where on earth the error is. It’s normal. The sooner you get used to it, the less you’ll suffer. :grin: We’ve all been there. Still are. Remember me missing the “++” earlier? :wink: It never stops.

Could you take a screenshot of your conversation file, please? :smiley: If it’s showing up as an one-liner right now, I suspect you probably don’t have a PC answer node (even if it’s just a blank one, defaulting to “[END DIALOGUE]”). If a conversation contains only a single OWNER node, and nothing else, then the dialogue box won’t open, because there is no option for the player to select in response.

Yes, those are the two options we’ve explored. :smiley: Although a delay above IntToFloat(i) * 9.0 should work just fine.

Both methods are equally reusable. It’s pretty much just that the one version uses SpeakString (which might fade away before the player has finished reading), and the other starts conversations (which, set up correctly, will stay as a dialogue box until the player has finished reading), so the latter is arguably more stable and comfortable.

The trick to making the conversation file option as reusable as the local string variant is is to use the tag of the trigger (or a local string stored on the trigger) to specify which conversation file that trigger should start.


#36

Yup, spot on. I forgot to include the blank reply to end the conversation. Works fine now.

At least I’m getting the rough gist of this. I think I could use both methods as I’ve now saved the code and am starting to understand snippets of it.

However, the last bit has thrown me. Making the tag of the trigger specify a particular conversation I can understand but how would you then go on to ask it to pull another one or two after that ? Or are you talking about laying down separate triggers?

Just to check, if I wanted a longer delay IntToFloat(i) * 12.0 would be fine?


#37

You certainly are getting the gist of it. Yes, that’s the thought. You’d be laying down separate triggers, which use the same identical script, and altering the tags of the individual triggers to make each start a different conversation. :smiley:

The advantage is that you only have a single script that handles all self-conversation starting triggers, so you can alter all of their behaviour at once if something needs changing, and it’s overall more orderly and takes up less space (compare: 1 script that takes one exchangeable value from elsewhere, versus 50 scripts that are identical except for one single line being different in all of them).

:+1: It should, yes. Try it. :smiley:


#38

Ok, it works ! I fear that I accidentally deleted an asterisk when c/p ing it. Timed it to 11.5 secs.

Thanks for all your help, (again) Barbarian. I’m calling it a night there as I’m driving my daughter to work early tomorrow after an all nighter (her, not me!) at a Super Bowl Party. Given that we live in Scotland I’m not sure when this caught on.

I think I now have everything to actually start thinking about ideas for a module which is the bit I’ll enjoy most.


#39

Have fun. Shout when the next issue pops up. :smiley: