Author Topic: Conversation specific scripting questions  (Read 498 times)

Legacy_Renzatic

  • Full Member
  • ***
  • Posts: 114
  • Karma: +0/-0
Conversation specific scripting questions
« on: November 02, 2010, 12:18:10 am »


               Hey everyone. I'm rather new to this whole NWN editing and scripting thing, and I'm hoping that someone here can give me a helping hand on fleshing out my ideas. This is mainly for the GOG NWN module contest, so this isn't gonna be too complicated. I figured because of that, it'd be a great way to learn scripting.

So, on to my questions.

I've already learned how to set up triggers so that NPCs talk to each other via floating text whenever the PC walk within earshot. It works fairly well, but it has a couple of kinks in that keep it from working perfectly for me. For one, it resets the conversation when you walk back into the trigger. Relatedly, it also plays the same conversation again if you want across the trigger later. That kinda ruins the ambiance if all my NPCs keep saying the same things over and over again. So for that, I want to know how to...

1. Set it up so that a conversation plays out, and the trigger kills itself after it's done. Or....
2. Set it up so that the trigger can pick and choose multiple conversations tied to it randomly.

I can't seem to find anything on how to do that.

On an easier note, I'd like to know how to keep a hostile NPC from attacking first. I want to set it up so it doesn't attack until you make the first strike.

Oh, and set it up so my friendly party NPC's say something via the aforementioned floating text whenever they make a successful attack roll, or critical hit.

Oh! And set it up so hostile NPCs say something via, once again, the floating text after they're defeated.

Oh! And some other stuff I'll probably want to do, but can't immediately think of. 

Okay, so this isn't gonna be quite as simple as I let on. But if someone here could point me towards a good set of tutorials, or give me a few tips and hints, I'd be in your debt for about forever.

Thanks in advanced. :happy:

In the meantime, I guess I'll show off what I've been working on recently.

users.chartertn.net/greymatt/nwn1_swamp_forest.jpg - A scene from a mod I'm doing on the side.[/url]
users.chartertn.net/greymatt/gog_contest.jpg - GOG contest mod shot 1
users.chartertn.net/greymatt/gog_contest_2.jpg - GOG contest mod shot 2
               
               

               


                     Modifié par Renzatic, 02 novembre 2010 - 12:23 .
                     
                  


            

Legacy_Lightfoot8

  • Hero Member
  • *****
  • Posts: 4797
  • Karma: +0/-0
Conversation specific scripting questions
« Reply #1 on: November 02, 2010, 01:13:27 am »


               NWN Scripting Tools and Resources images/forum/rating_small.pngimages/forum/rating_small.pngimages/forum/rating_small.pngimages/forum/rating_small.pngimages/forum/rating_small.png
               
               

               
            

Legacy_GhostOfGod

  • Hero Member
  • *****
  • Posts: 1490
  • Karma: +0/-0
Conversation specific scripting questions
« Reply #2 on: November 02, 2010, 02:18:58 am »


               For the first part you could try something like this in your trigger's "OnEnter" event:


void main()
{
//Check int on the trigger named "ACTIVE" ...
int iActive = GetLocalInt(OBJECT_SELF, "ACTIVE");
//...if int is "TRUE" then end.
if (iActive) return;
//Set int on self named "ACTIVE" to TRUE.
SetLocalInt(OBJECT_SELF, "ACTIVE", TRUE);
object oNPC1 = GetNearestObjectByTag("Tag of NPC1", OBJECT_SELF);
object oNPC2 = GetNearestObjectByTag("Tag of NPC2", OBJECT_SELF);
int iRand = Random(4)+1;//Change 4 to amount of random conversations provided.
switch (iRand)
    {
    case 1:
    AssignCommand(oNPC1, SetFacingPoint(GetPosition(oNPC2)));
    AssignCommand(oNPC2, SetFacingPoint(GetPosition(oNPC1)));

    //Start of conversation lines:
    AssignCommand(oNPC1, DelayCommand(1.0, ActionSpeakString("Pleasant weather we're having today?")));
    AssignCommand(oNPC2, DelayCommand(2.0, ActionSpeakString("Yes it is isn't it.")));
    AssignCommand(oNPC1, DelayCommand(3.0, ActionSpeakString("Have any plans for the day?")));
    AssignCommand(oNPC2, DelayCommand(4.0, ActionSpeakString("I'm going to visit my grandmother in the forest.")));
    //End of conversation lines.

    //Set the "ACTIVE" int back to FALSE now that the conversation is done.
    //This delay should be as long as it takes to finish the above actions.
    DelayCommand(4.0, SetLocalInt(OBJECT_SELF, "ACTIVE", FALSE));
    break;

    case 2:
    AssignCommand(oNPC1, SetFacingPoint(GetPosition(oNPC2)));
    AssignCommand(oNPC2, SetFacingPoint(GetPosition(oNPC1)));

    //another conversation

    //Set the "ACTIVE" int back to FALSE now that the conversation is done.
    //This delay should be as long as it takes to finish the above actions.
    DelayCommand(4.0, SetLocalInt(OBJECT_SELF, "ACTIVE", FALSE));
    break;

    case 3:
    AssignCommand(oNPC1, SetFacingPoint(GetPosition(oNPC2)));
    AssignCommand(oNPC2, SetFacingPoint(GetPosition(oNPC1)));

    //another conversation
    
    //Set the "ACTIVE" int back to FALSE now that the conversation is done.
    //This delay should be as long as it takes to finish the above actions.
    DelayCommand(4.0, SetLocalInt(OBJECT_SELF, "ACTIVE", FALSE));
    break;

    case 4:
    AssignCommand(oNPC1, SetFacingPoint(GetPosition(oNPC2)));
    AssignCommand(oNPC2, SetFacingPoint(GetPosition(oNPC1)));

    //another conversation

    //Set the "ACTIVE" int back to FALSE now that the conversation is done.
    //This delay should be as long as it takes to finish the above actions.
    DelayCommand(4.0, SetLocalInt(OBJECT_SELF, "ACTIVE", FALSE));
    break;
    }
}


Hopefully you can follow it with my lousy commenting skills. haha. Good luck.
               
               

               


                     Modifié par GhostOfGod, 02 novembre 2010 - 03:58 .
                     
                  


            

Legacy_Renzatic

  • Full Member
  • ***
  • Posts: 114
  • Karma: +0/-0
Conversation specific scripting questions
« Reply #3 on: November 03, 2010, 06:34:05 am »


                Hey, appreciate the response. 
Right now, due to the fact that I'm trying to learns the ins and outs of this, I decided to take your code (which I'm sure would've worked perfectly) and experiment with it. Use it more as suggestions than a step by step guide. I figured that'd make for a better learning experience than copying you straight up.
So here's what I've done. I've more or less taken pieces that looked relevant to what I'm trying to do, and combined them with the old way I was setting up conversations. The code looks like this, with annotations on what I assume each bit does.

edit: Oh hell. Posting the script to the forum screwed up my formatting, and I don't know the command to embed it into a code window here.

I'll just link to the code on my website.
users.chartertn.net/greymatt/convo_script.txt

Sorry. : \\

And there it is. Probably sloppy, and the completely wrong approach to doing NPC conversions. But, you know, baby steps. Wanna learn to walk before I run .

The only part I don't undestand of my original conversation code is the "object oPC = GetFirstPC();" bit. I'm assuming it's saying "okay, the PC is in the trigger, go do this". which leads to the next two steps, and gets the NPCs to relay the conversation I typed up in the editor (I had to skip the PC lines in the convo editor and apply tags to the NPC bits to get each one to say their respective line).

The part that doesn't work on the newer stuff based off what you showed me is the DelayCommand action. I get my random conversations, but they still reset whenever the PC enters the trigger. From what I assumed, it'd keep the above script running uninterrupted until 15 seconds pass. Right now, it doesn't do anything. No matter which bracket set I put it in.

Once I figure this out, I can set up my crowd of 15 NPCs to sprout off random one liners (with a few blank lines thrown in so they're not all talking at once). But I have to get the delay right before I can attempt it. Otherwise it'll get annoying.

Once again, thanks for the help. I feel like I've already learned a bit just working off your code, and googling up a few commands here and there. Not enough to do anything 1400 lines deep, but enough to implement alot of my ideas for my mod.
               
               

               


                     Modifié par Renzatic, 03 novembre 2010 - 06:41 .
                     
                  


            

Legacy_GhostOfGod

  • Hero Member
  • *****
  • Posts: 1490
  • Karma: +0/-0
Conversation specific scripting questions
« Reply #4 on: November 03, 2010, 07:57:26 am »


               It's always nice to see someone taking an interest in learning to script.

The stuff I posted is just one of many different ways you could go about this. And of course breaking it apart and using chunks and seeing how it "ticks" is part of the fun of scripting.'<img'>

So..let's start with the first part of the script I posted and I'll try to explain it better. Sometimes I don't do that very well. We'll just start with the first 3 lines under the "void main" and the first "{".

void main()
{
int iActive = GetLocalInt(OBJECT_SELF, "ACTIVE");
if (iActive) return;
SetLocalInt(OBJECT_SELF, "ACTIVE", TRUE);

When a creature enters a generic trigger it triggers it's "OnEnter" event (I know you already know this). And if they repeatedly enter the trigger then the "OnEnter" script repeatedly fires. So when you only want it to fire the whole script once every so ofter then you could do something like what is posted above. The first line:

int iActive = GetLocalInt(OBJECT_SELF, "ACTIVE");

This line is declaring an integer that I decided to call iActive. You could call it whatever you want. "iActivated" would proabably have been better on my part. Now "iActive" is going to be whatever integer is stored on the trigger(OBJECT_SELF) under the name "ACTIVE". If this is the first time anyone has stepped into the trigger then this integer will be 0(same thing as FALSE) because it hasn't been set yet. It doesn't exist. The next line:

if (iActive) return;

(the above is the same as this): if (iActive == TRUE) return;

This is the same thing as saying if the int iActive = TRUE(TRUE is the same as 1) then just end the script right here and do anything else because someone has already entered the trigger. Which is what the next line indicates:

SetLocalInt(OBJECT_SELF, "ACTIVE", TRUE);

So we immediately store an integer named "ACTIVE" to TRUE(TRUE is the same thing as 1).
So now think about someone entering the trigger again.
-player enters the trigger
-script fires and checks to see if an integer has been stored on itself indicating that someone else already entered
-if the interger is there then stop right there and end.
-If integer isn't there then set it and continue on with the rest of the script.

Now let's say our integer IS set. Now the script wont ever complete again when another player enters it. So what if we want the script to be able to complete again? That is why I have the other lines on delays that look like so:

DelayCommand(4.0, SetLocalInt(OBJECT_SELF, "ACTIVE", FALSE));

And what does that line do? You proabably already guessed. It sets the stored integer named "ACTIVE" back to FALSE after a delayed amount of time. So now when a player enters the trigger again..the rest of the script will fire and then the whole process of blocking it starts all over again. So basically it is a way of temporarily kinda sorta turning off a trigger. You could do the same thing for other triggers that you never want their "OnEnter" scripts to complete after the first time someone enters it. Like for applying permanent effects and what not.

Now I can show you where you went wrong on the switch/case part of the script:


case 1: // not 100% sure here. I guess this is a int for the randomizer
   {
       object oPC = GetFirstPC(); //this is my previously used code. I sortakinda understand it.
       object oNPC = GetObjectByTag("Testman");
       object oNPC2 = GetObjectByTag("Testwoman");
       AssignCommand(oNPC2, ActionStartConversation(oNPC, "poor_talk_test"));
   }              
       DelayCommand(15.0, SetLocalInt(OBJECT_SELF, "RC3", FALSE)); // I can't get this to work. more below
       break; // ends this particular section of the script?


1.) yes..that is the int for the randomizer.
2.)cases do not use the opening and closing curly brackets "{}". So you can remove those. Here is a link from the lexicon to give a good understanding about a switch/case. Lexicon example switch/case
3.)object oPC = GetFirstPC(); doesn't seem to be needed for anything in this script. What that function does is get the first player in the list of all the players on a server. You would use it with GetNextPC(); to loop through all the players on the server. It can be used for testing purposes in single player as well since there is only one player. Then GetFirstPC(); would just return that only player in the game.
4.)now that you hopefully have a better understanding of why I set the interger on the trigger then you can fix the line where you SetLocalInt.
5.)yes the "break;" stops at that point in the switch/case. So that the rest of the cases don't fire. Just the number the randomizer picked.

Well I think that's it for now then. I hope I explained it somewhat decent. Any other questions just shoot. And good luck.
               
               

               


                     Modifié par GhostOfGod, 03 novembre 2010 - 08:09 .
                     
                  


            

Legacy_FunkySwerve

  • Hero Member
  • *****
  • Posts: 2325
  • Karma: +0/-0
Conversation specific scripting questions
« Reply #5 on: November 03, 2010, 09:44:07 am »


               

if (iActive) return;

(the above is the same as this): if (iActive == TRUE) return;

This is inaccurate. The const TRUE is equal to 1, remember. if (iActive) will return TRUE where iActive is ANY non-zero value, be it 1 or -123456. Bioware simply used 1 for the TRUE const because it's the simplest, especially if you think in terms of binary true/false values, but it is NOT the only TRUE value.

Funky
               
               

               


                     Modifié par FunkySwerve, 03 novembre 2010 - 09:44 .
                     
                  


            

Legacy_GhostOfGod

  • Hero Member
  • *****
  • Posts: 1490
  • Karma: +0/-0
Conversation specific scripting questions
« Reply #6 on: November 03, 2010, 04:13:15 pm »


               Thanks for the correction Funky. Just gos to show that what i say and what i mean to say are two completely different things. ':lol:'. If i try and teach i guess i better do it right.
               
               

               


                     Modifié par GhostOfGod, 03 novembre 2010 - 04:13 .
                     
                  


            

Legacy_Renzatic

  • Full Member
  • ***
  • Posts: 114
  • Karma: +0/-0
Conversation specific scripting questions
« Reply #7 on: November 04, 2010, 07:10:23 am »


               Whew. Okay. After reading through your stuff, and following a couple of tutorials, I think I'm finally starting to get the ebb and flow of things. Starting to being the operative word here/ I've still got a long way to go. To help me out, I've been annotating all the code I've been using from the tutorials, so it explains what each line does. That way I can use it for reference later.

Let me tell you, I've got a huge damn headache. I now have a newfound respect for people who do anything remotely approaching hardcore coding.

So anyway, I'm gonna be using this thread as a rolling diary, and, of course, using it as bait to fish some suggestions for the things I want to implement.

The first big thing I want to implement is my crowd conversations. I've got about 14 NPCs standing in 4 lines. I want them to fire off various one liners at random whenever you're in their general vicinity. I don't want them all speaking at once, usually just a couple or three at a time, so I thought I'd implement a goodly share blank lines into the conversation tree to compensate for it. The code needs to constantly loop while you're in the trigger.

I want to set it up so that it accounts for every NPC in the trigger by a variable, instead of inputing them all as "crowd 1, crowd 2, crowd ect", and assign the conversations to a variable. The way I have it set up right now, I'll have to make quite a few blocks of text, and assign lines to each NPC I want to have talking, then have the randomizer do it's thing for variety. I figured variables will make this a helluva lot easier, and make for a much simpler script.

This'll be the hardest part to implement. It's alot of work just to get some random guys to shoot off one liners.

The second thing I want to do is set it up so you get a new PC in your party after an area transition. It's a part of the story, where you leave one section, then you enter the next, it says "20 years later", and the narrator says you came across this random obnoxious dude guy during your travels. That shouldn't be too difficult. Nor will getting him to sprout off one liners while he's walking around. About the only hard thing I want to do with him is setting it up so he says something specific after certain actions (like casting a fireball, or killing a monster).

The last part will be getting my monsters in my final map to talk via word bubbles, and only attack when you strike them. The only hard part there is that I want them to say something different depending on when you attack them during their little conversation spiel. Right now, it's looking like I can do that by setting up each line as an object, and use a bunch of if then statements. Maybe.

So here I am, going overboard on a contest mod I figured I'd be done with in 3 or 4 days. Looks like it'll take a helluva lot longer than that. I've still got 20 days left, so I better get cramming.

And hey, it's good practice for when I start doing this like for realz olol.

And thanks again for the suggestions GoG (ooohhh), You're helping me out here more than you know. If you've got any more suggestions, fire em my way.