Aller au contenu

Photo

Rando Super-Novice Question


  • Veuillez vous connecter pour répondre
24 réponses à ce sujet

#1
Lemenhead22

Lemenhead22
  • Members
  • 17 messages
So, I'm new to all this scripting, but am very interested and just trying to learn from reading other scripts, and How-To Guides. I'm trying to create a scenario from the NWN2 OC very similar to the "Toll Road" encounter just to ractice, and I have a couple basic questions regarding the "12_a_tollroad" script. Remember...Super Novice...

1.  It begins with:

void BanditAttack()

Most stuff I've looked at so far, of course, begins with "void main()" which I honestly don't totally understand. I've read anything after "void" returns nothing so what's the point...but anyway...What exactly does void BanditAttack() refer to and why is it necessary to be there?

2. A few lines later in the script is:

void main(int nDialogNode)

Below this a switch is run based off the int "nDialogNode" and for the most part I think I understand the whole switch thing...I really don't see the difference between switch and if operations except for the fact switch ones seem easier to write. But why does the "(int nDialogNode)" have to be inserted after void main()?

3. A few more lines down is "SetLocalInt(GetModule(), "TollRoadTesult", 1):":

I understand setting and getting intergers, what kind of throws me off is the GetModule() function. The Script Assist doesn't have a newbie friendly explanation on GetModule(), I've seen this in other places and kind of don't understand what it does when it's inside or outside other functions.

Once again, lots of questions here and lots of scripting ignorance, but I'm trying! I appreciate any explainations, and if it's waaay too much to explain feel free to refer me somewhere. I've been checking out the Toolset Notes files Scripting for Noobs but nothing beats a good ol individualized explaination. Thanks again!

#2
kamal_

kamal_
  • Members
  • 5 240 messages
Hi and welcome. You probably want to refer to the Scripting for Noobs mostly, which you already have. http://nwvault.ign.c...s.Detail&id=124

http://www.nwn2scripting.com/ is also super useful for new scripters. It explains where you'd want to do things like use a switch versus an if. Technically you could use if just about everywhere, but it would be a nightmare to figure many things out when looking at your code.

void BanditAttack()
this is a function that will get used somewhere in the main() function. Because it will be used later in the script, it goes before the void main(). But defining it as a function, you can call it as many times as you feel like without needing to copy/paste all the code. Also, because the function is only in one place, any changes you make to it will affect every use of it, if you did the copy/paste way you'd have to change every place you used it.

GetModule(), I believe this gets the name of the module.

#3
Tchos

Tchos
  • Members
  • 5 042 messages
1. If you're saying that BanditAttack() is just there at the beginning without anything defined in it, it's because main() needs to refer to it, and it has to be at least mentioned before the main() function, or the compiler will give you an error that you're using some undefined function. So you just declare it at the beginning, even if you're going to actually write out the full function after the main() function.

2. Switches are basically another kind of if. You can use either one. Switches are, as you surmise, easier to write, but also easier to read. The (int nDialogNode) declares an integer that the function must receive from whatever calls that function. If it's in main(), then it must be a conversation script, because that's the only kind of script in the game that allows parameters to be passed into the main function. You type it in on the conversation's action tab.

3. The module is an object that has properties and variables, like many other things. If you open your module in the toolset, you can choose View>Module Properties, and see the scripts that are attached to the module, any variables you might want to define at the start, and all the other properties. During the game, you might want to save a variable on the module for later reference because you don't have anything else handy to save it on. I generally try to keep variables saved to the most local objects possible, such as whatever NPC or placeable they're directly affecting.

#4
kevL

kevL
  • Members
  • 4 056 messages
Ps Tchos: i know you said "in the game" but parameters are also useful in main on consoled scripts.


1. "void" is the link back to the rest of the code. Other possibilities are "int", "float", "string", & "object" ( and "struct" btw ). I don't know why but the compiler needs to know what, if anything, is coming back out of a function, assumedly in order to shuttle that something down the line (or not).

1a. The important thing is, no matter what the return is, stuff can still happen inside a function.


2. "int nDialogNode" and similar, allows values to be passed into a function as parameters. In '12_a_tollroad', for example, when the function "main" is called from a dialog (i presume) the dialog file contains a field with the parameter "nDialogNode" -- that refers to the exact dialog node that was initiated. The switch then handles the input, which really is just an easier way to write a series of if statements.

2a. Parameters can have defaults. Functions like

void Foobar(int bBork = 1);

mean that Foobar is borked by default. That is, unless whatever calls Foobar() is explicitly set to NOTborked ( "0" ) then ... you get the idea. Defaults should not be used in "main()" or "StartingConditional()" functions because the NwN2 runtime automatically assigns default values to such parameters, says Skywing's Advanced Script Compiler ( which you should get if you haven't ), and other values will be ignored.


3. these two routines are identical:

SetLocalInt(GetModule(), "TollRoadResult", 1);

and

object oModule = GetModule();
SetLocalInt(oModule, "TollRoadResult", 1);

The function GetModule() itself simply gets the module object, and then the routine uses it to store local_int "TollRoadResult". That's all.

3a. [ edit: this is not entirely correct but read it as something to be wary of ]

Usually it's safe to put a simple thing like GetModule() inside of a simple thing like SetLocalInt(). But there are places where analogous patterns won't work -- the rule to follow is that the internalized function must return void for it to work. An infamous case is PlayCustomAnimation() : you'd think it returns void but it actually returns an int.

So if you tried to delay PlayCustomAnimation() like so

DelayCommand(6.f, PlayCustomAnimation(...));

it won't work. This is where a 'wrapper' is required:

void WrapPlayCustomAnimation()
{
PlayCustomAnimation(...);
}

Then use

DelayCommand(6.f, WrapPlayCustomAnimation(...));

and it should work again,

Modifié par kevL, 12 mai 2013 - 08:35 .


#5
Tchos

Tchos
  • Members
  • 5 042 messages
Also, Lemenhead, kevL here is more experienced with scripting than I am, so if there's any contradiction, kevL's explanation should override mine.

#6
Lemenhead22

Lemenhead22
  • Members
  • 17 messages
Thank you guys a bunch! I'm slowly learning bits and pieces. Just to kind of confirm I'm understanding...

The line "SetLocalInt (GetModule(), "TollRoadResult", 1);

This is just assigning 1 to "TollRoadResult" which is saved to the module? If that's the case, then would 'SetGlobalInt ("TollRoadResult", 1)' be the exact same thing?

Also the "void BanditAttack()" line. So simply writing "void Anything()" declares a function that you can refer to later to avoid having to retype all of your code?

#7
Tchos

Tchos
  • Members
  • 5 042 messages
It would not be the exact same thing, because global integers and local integers are different. Personally, I never use globals (and I have a few reasons for that). If you're making just a module or a single-module campaign, then there's not much practical difference for what you may want to do.

Also, very basic stuff here, but 1 is the same as "TRUE" and 0 is the same as "FALSE".

Writing:
void Anything();
does, yes. Semicolon is important to end the line there if there's no actual code in it.

#8
Tchos

Tchos
  • Members
  • 5 042 messages
Let me expand a little on why they're different -- it's because if you do both, "SetLocalInt (GetModule(), "TollRoadResult", 1); and then later decide to use globals for the same variable, 'SetGlobalInt ("TollRoadResult", 1)', it's not the same variable. It has the same name, but it was saved in two different places. One in the global area, and the other on the module object. Setting one doesn't affect the other. So to prevent any mix-ups, pick one location for a particular variable and stick with it.

The same goes for local variables.  If you save a local int called "TalkedTo" on an NPC named Alexis set to 1, a different NPC named Boris will still return 0 if you checked him for a local int called "TalkedTo".

Modifié par Tchos, 12 mai 2013 - 06:59 .


#9
kevL

kevL
  • Members
  • 4 056 messages
- a bit more detail:

SetLocalInt() and SetGlobalInt() are, as Tchos says, different. Setting a global writes the value to an .Xml file ... the main reason not to like it that I see, is that an OS with paranoid permission settings seems not to allow that file write. Setting a local merely assigns a value to an index on an abstract, virtual 'object' -- in this case the 'module' object.

When creating a campaign with multiple modules, a global will be accessible from any of the modules included in the campaign. But a local on a module object is accessible only when that module is loaded by the player. If he/she transitions to another module, even in the same campaign, either use globals and face potential consequences or use a local on another object like the PC object or an item that the PC is sure to have carried along for the transition ( depending .. )



"void BanditAttack()" is the start of a definition.
"void BanditAttack();" is a declaration.

The definition can be written out entire before main() and the script will compile. Or BanditAttack() can be declared (with the semi-colon) before main() and written out (defined) after main; this also compiles. The issue is that the compiler has to know the definition and where to find it ( it might be in an #include, eg ) ....

#10
Lemenhead22

Lemenhead22
  • Members
  • 17 messages
Thanks a ton for the fast responses. I'm actually understanding this stuff. I hope ya'll don't mind answering future rookie questions.

Brandon

#11
Tchos

Tchos
  • Members
  • 5 042 messages
Just keep posting them, and I'm sure we'll keep answering. :)

#12
ColorsFade

ColorsFade
  • Members
  • 1 267 messages
I'm late to this thread, but wanted to contribute on this point:

Lemenhead22 wrote...

Below this a switch is run based off the int "nDialogNode" and for the most part I think I understand the whole switch thing...I really don't see the difference between switch and if operations except for the fact switch ones seem easier to write.


Actually, these two constructs exists for quite seprate purposes. Learning how to use them will help you be successful as a script writer and to write readable code.

The fact that these two constructs can be used interchangeably in many situations is almost unfortunate, because they are not really the same thing. 

The switch statement exists as a programming language conditional operater so that the executing code can "switch" on a single value - most often an enumeration or constant. This allows conditional code to execute for a large number of "cases"; the number of constants or enumerations you have defined. 

The if/else statement is a different conditional operator in that it can execute conditional code based on more than one value at a time. 

So, if you have an enumeration defined such as: 

enum WEAPON_TYPE
{
  BLUNT,
  PIERCING,
  EDGED
}


Then your switch statement just executes based on the single value of the enum. Like so:

switch (weaponType)
{
  case WEAPON_TYPE.BLUNT:
    DoBluntDamage();
    break;
  case WEAPON_TYPE.PIERCING:
    DoPiercingDamage();
    break;
  case WEAPON_TYPE.EDGED:
    DoEdgedDamage();
    break;
}


This is the case where the switch is the easier construct to write. Yes, you can do this with an if/else, but a switch is cleaner, easier to read, and in most modern-day compilers the IDE will acutally auto-generate alll the switch statements for you if you're using an enumeration. 

The if/else, however, is more useful when you need to execute code based on multiple values. Example: 

if(spell.IsHarmful == TRUE && spell.IsAOE == true)
{
  DoDamaqeToEveryone();
}
else if(spell.IsHarmful == TRUE && spell.IsAIO == FALSE)
{
  DoSingleTargetDamage();
}


In this case, we're executing code based on two checks: If the spell is Harmful and if it's an AOE spell. You can't really do that with a switch (or, more to the point, you shouldn't). 

I know these are lame examples. I'm making these functions up just to illustrate a point. But you get the idea - with the if/else statement, you can execute code based on multiple conditions for a single check. This is how the if/else is designed to work. 

#13
Lemenhead22

Lemenhead22
  • Members
  • 17 messages
That was actually a really good explanation on the differences. Switching gears just a bit, as I wrote earlier, I'm writing a practice script based on the tollroad encounter "12_a_tollroad". In the conversation with the Toll Leader if the player chooses to pay the toll, the Toll Leader and 3 Bandits basically step aside to their individual waypoints and uses a vector to stare down the player as he/she walks off (from my understanding).

For whatever reason, after the player pays the Toll Leader they walk off to their waypoints but they don't face the direction of the player at all. They just face the direction they were initially facing to reach their waypoint.

Here's that portion of the code:

case 20: //Pay toll and bandits remain
{
object oTollLeader = GetObjectByTag("TollLeader");
object oBandit1 = GetObjectByTag("Bandit01");
object oBandit2 = GetObjectByTag("Bandit02");
object oBandit3 = GetObjectByTag("Bandit03");

location lLeader = GetLocation(GetWaypointByTag("lTollLeader"));
location lBandit1 = GetLocation(GetWaypointByTag("lBandit01"));
location lBandit2 = GetLocation(GetWaypointByTag("lBandit02"));
location lBandit3 = GetLocation(GetWaypointByTag("lBandit03"));

vector vFace = GetPosition(GetPCSpeaker());

AssignCommand(oTollLeader, ClearAllActions(TRUE));
AssignCommand(oTollLeader, ActionMoveToLocation(lLeader));
AssignCommand(oTollLeader, SetFacingPoint(vFace));

AssignCommand(oBandit1, ClearAllActions(TRUE));
AssignCommand(oBandit1, ActionMoveToLocation(lBandit1));
AssignCommand(oBandit1, SetFacingPoint(vFace));

AssignCommand(oBandit2, ClearAllActions(TRUE));
AssignCommand(oBandit2, ActionMoveToLocation(lBandit2));
AssignCommand(oBandit2, SetFacingPoint(vFace));

AssignCommand(oBandit3, ClearAllActions(TRUE));
AssignCommand(oBandit3, ActionMoveToLocation(lBandit3));
AssignCommand(oBandit3, SetFacingPoint(vFace));
}
break;

Thanks guys

#14
Tchos

Tchos
  • Members
  • 5 042 messages
I would say it's because the script doesn't wait for the bandits to reach their waypoints before telling them to face in that direction, and since they're in the process of moving, the facing is lost. Try delaying the command to face the direction with the DelayCommand() function.

Also, if you use ActionMoveToObject(), you don't need to include the step of GetLocation(). A waypoint is an object.

#15
kevL

kevL
  • Members
  • 4 056 messages
The function ActionDoCommand()

as in

AssignCommand(oTollLeader, ActionDoCommand(SetFacingPoint(vFace)));

that puts SetFacingPoint() into the creature's action queue, so it happens after they finish their moves.


Another thing is, what if the PC is no longer standing where he/she was when the script fired? Then,

object oTarget = GetPCSpeaker();
AssignCommand(oTollLeader, ActionDoCommand(FaceTarget(oTarget, oTollLeader)));

where FaceTarget() is

void FaceTarget(object oTarget, object oTollLeader)
{
  vector vFace = GetPosition(oTarget);
  AssignCommand(oTollLeader, SetFacingPoint(vFace));
}


That gets the current position of the PC, after the bandits have finished moving to their waypoints, and since GetPCSpeaker() is no longer valid by then it (probably) needs to be passed in ...

Modifié par kevL, 14 mai 2013 - 01:14 .


#16
Lemenhead22

Lemenhead22
  • Members
  • 17 messages
I added a DelayCommand(2.0,...) prior to the facing and that did the trick!

kevL, I added that code in there just to try it but the TollLeader and Bandits have such a short walk that it's difficult to notice. How is someone like me supposed to know little tidbits like the ActionDoCommand places things in a queue. That's def something good to know.

When I was first reading the code I was kind of hoping that the enemies would continue to change positions and face the player as they walked by. This just sets the enemies to look at the original direction and not change. I suppose that would be a bit more difficult and over my head. I'm guessing the enemies would have to continually reassess the vector of where the player is and continually change. I dunno...maybe??

#17
kevL

kevL
  • Members
  • 4 056 messages

Lemenhead22 wrote...

I added that code in there just to try it but the TollLeader and Bandits have such a short walk that it's difficult to notice.

sure, i was doing my test with an umberhulk and they don't walk too fast ;)

( Another thing that occurred to me is what if a player clicks, to re-initiate dialog, as the Leader is walking away? ... but a fella can go on nearly forever tweaking that kinda stuff )

To have the bandits swivel to face PC, I'm thinking you'd have to do a 'pseudo-heartbeat'. FaceTarget() could be useful for this,

void FaceTarget(object oTarget, object oTollLeader)
{
  if (GetObjectSeen(oTarget, oTollLeader)
  {
    vector vFace = GetPosition(oTarget);
    AssignCommand(oTollLeader, SetFacingPoint(vFace));

    DelayCommand(0.1f, FaceTarget(oTarget, oTollLeader));
  }
}

- or something like that,


Anyway, I'm just dabbling and you decide where to draw the line

#18
ColorsFade

ColorsFade
  • Members
  • 1 267 messages

Lemenhead22 wrote...

I added a DelayCommand(2.0,...) prior to the facing and that did the trick!


I still get caught with timing issues in my scripts. Things don't fire like I think they should, then I remember to delay a command, and it works. It's just something that you have to be aware of. 

Lemenhead22 wrote...
 How is someone like me supposed to know little tidbits like the ActionDoCommand places things in a queue. That's def something good to know.


Just the way you're doing it: ask questions, get answers. 

The NWN2 scripts are written by lots of different programmers. Some of them commented their work halfway decent, and some not at all. Sometimes you can find a comment that clues you into what a function really does. Other times, experimentation is simply your best learning tool. 

Personally, I maintain an Excel spreadsheet for my campaign, and in it I have a sheet just for "Notes" - most of them are scripting notes. Every time I learn something new, I add an entry to that sheet. Later on, when I'm writing a new script and I can't remember how I did something before, I look at my notes. Most of the time the answer is in there. 

Do yourself a favor and keep notes. When you learn something new, make a note. Organize your notes so it's easy for you to find things. You'll thank yourself later. 

#19
ColorsFade

ColorsFade
  • Members
  • 1 267 messages

kevL wrote...

( Another thing that occurred to me is what if a player clicks, to re-initiate dialog, as the Leader is walking away? ... but a fella can go on nearly forever tweaking that kinda stuff )


Same issue came up for me recently. The answer is always have a conversation attached to the creature so they at least have a single line bark to fire. It's a simple thing to do and it prevents a rather immersion-breaking moment from happening (clicking on a character walking away and having nothing happens is kind of lame. It's probably more lame that the player decided to click on them, but hey....what are you gonna do?)

#20
kevL

kevL
  • Members
  • 4 056 messages

ColorsFade wrote...

Same issue came up for me recently. The answer is always have a conversation attached to the creature so they at least have a single line bark to fire.

well, I was thinking of it a couple of ways. First, like you say, have a bark in the dialog, then if that condition is met use the exit-dialog script to continue to the waypoint. Second: SetCommandable(FALSE)/SetCommandable(TRUE), though its a bit tricky to get right; that way the player can click on the guy's back all they want, and he has nada to say....

- what's irksome is clicking and the guy turns around and just stares, and all that careful scripting goes to waste. :\\

#21
Tchos

Tchos
  • Members
  • 5 042 messages

kevL wrote...
- what's irksome is clicking and the guy turns around and just stares, and all that careful scripting goes to waste. :

And doesn't start moving again, right?  When I want to make sure an NPC reaches its destination no matter what the PC may do, I've used two options -- a special heartbeat, or ActionForceExit() (from ginc_actions), which includes making it uncommandable.

#22
kevL

kevL
  • Members
  • 4 056 messages
yep. Lots of good notes & ideas in this thread,

--
Lemen:
I got to playing around and this seems pretty effective with an umberhulk + 3 horses ( all set unbumpable ). But i suggest you go with what you're comfortable with,

The conversation is owned by the umberhulk TollLeader ...


void MoveBandit(object oPC, object oBandit, int i = FALSE);
void FacePC(object oPC);


void main()
{
  object oPC = GetPCSpeaker();

  MoveBandit(oPC, OBJECT_SELF);

  object oBandit; int i;
  for (i = 1; i < 4; i ++)
  {
    oBandit = GetObjectByTag("Bandit0" + IntToString(i));
    if (GetIsObjectValid(oBandit))
    {
      AssignCommand(oBandit, MoveBandit(oPC, oBandit, i));
    }
  }
}



void MoveBandit(object oPC, object oBandit, int i = FALSE)
{
  ClearAllActions();

  object oWp;
  if (!i)
  {
    oWp = GetWaypointByTag("lTollLeader");
  }
  else
  {
    oWp = GetWaypointByTag("lBandit0" + IntToString(i));
  }

  ActionMoveToObject(oWp);
  ActionDoCommand(FacePC(oPC));

  if (GetCommandable(oBandit)) SetCommandable(FALSE, oBandit);
}


void FacePC(object oPC)
{
  if (!GetCommandable()) SetCommandable(TRUE);

  if (GetObjectSeen(oPC)
      && GetCurrentAction() == ACTION_INVALID
      && !IsInConversation(OBJECT_SELF))
  {
    vector vFace = GetPosition(oPC);
    SetFacingPoint(vFace);

    DelayCommand(0.1f, FacePC(oPC));
  }
}


#23
Dann-J

Dann-J
  • Members
  • 3 161 messages
The easy way to get an NPC to walk back to a point after a conversation is to give them a single walkwaypoint. If you right-click on a placed creature in the toolset, you can choose the 'make waypoint' option to create one for them. Make sure everyone has their own unique tag though, or they'll all jostle up against each other in an entirely inappropriate manner.

Waypoint walking is suspended during conversations, so you can have an NPC walk up to you or walk about in all sorts of ways while talking to them. Once the conversation is done their default heartbeat script takes over and they walk back to their post without any need for additional scripting (even if you interupt them again on their way back). Although they won't always orient themselves to match the facing direction of their waypoint, so there'll still be issues there that require some sort of scripting solution.

#24
kevL

kevL
  • Members
  • 4 056 messages
waypoint_tag : POST_TollLeader

ftw.


edit: works really good, actually

Modifié par kevL, 15 mai 2013 - 01:17 .


#25
ColorsFade

ColorsFade
  • Members
  • 1 267 messages

Tchos wrote...

And doesn't start moving again, right?  When I want to make sure an NPC reaches its destination no matter what the PC may do, I've used two options -- a special heartbeat, or ActionForceExit() (from ginc_actions), which includes making it uncommandable.


ga_force_exit at the end of the conversation is what I've been doing and that works. 

Good thread.