Rando Super-Novice Question
#1
Posté 12 mai 2013 - 04:05
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
Posté 12 mai 2013 - 04:53
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
Posté 12 mai 2013 - 05:04
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
Posté 12 mai 2013 - 05:54
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
Posté 12 mai 2013 - 06:45
#6
Posté 12 mai 2013 - 06:48
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
Posté 12 mai 2013 - 06:53
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
Posté 12 mai 2013 - 06:56
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
Posté 12 mai 2013 - 07:15
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
Posté 12 mai 2013 - 08:10
Brandon
#11
Posté 12 mai 2013 - 09:18
#12
Posté 13 mai 2013 - 01:55
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
Posté 13 mai 2013 - 11:40
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
Posté 14 mai 2013 - 12:29
Also, if you use ActionMoveToObject(), you don't need to include the step of GetLocation(). A waypoint is an object.
#15
Posté 14 mai 2013 - 01:09
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
Posté 14 mai 2013 - 01:43
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
Posté 14 mai 2013 - 09:19
sure, i was doing my test with an umberhulk and they don't walk too fastLemenhead22 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.
( 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
Posté 14 mai 2013 - 02:21
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
Posté 14 mai 2013 - 02:23
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
Posté 14 mai 2013 - 02:38
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....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.
- what's irksome is clicking and the guy turns around and just stares, and all that careful scripting goes to waste. :\\
#21
Posté 14 mai 2013 - 07:03
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.kevL wrote...
- what's irksome is clicking and the guy turns around and just stares, and all that careful scripting goes to waste. :
#22
Posté 14 mai 2013 - 09:37
--
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
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
Posté 14 mai 2013 - 11:01
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
Posté 15 mai 2013 - 01:15
ftw.
edit: works really good, actually
Modifié par kevL, 15 mai 2013 - 01:17 .
#25
Posté 15 mai 2013 - 03:35
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.





Retour en haut






