Choose Your Language

Saturday, 16 May 2009

Scripting Nitty-Gritty (Party AI)

I have been busy looking at the finer details of some scripting aspects for Better The Demon and NWN2 in general. I don't know about the rest of you builders out there, but I have found a few of the functions a bit confusing as to what they actually do at times. It may be because I am trying to code from a multi-player aspect that causes some of the confusion, but here are a few I am referring to (with their descriptions rewritten by me to help explain them better for those who may also benefit from the rewording):-

GetIsOwnedByPlayer() - This returns TRUE for a player's Main PC (*) object regardless of whether the player is currently possessing it or not.
GetIsPC() - This returns TRUE on a PC or creature object if the player is possessing it at the time.
GetOwnedCharacter() - This will return the Main PC (*) of a player who is controlling the tested creature at the time. (Returns OBJECT_INVALID if creature is not being controlled.)
GetControlledCharacter() - If this is used on a PC or creature that the player can control, then it will return the object that the player is currently controlling. (Returns OBJECT_INVALID if creature is not one that can be controlled.) After more testing with this function, I am not sure I have this right even now.

(*) For those that don't know, the term Main PC (or Owned) is used to differentiate the PC that the player joins the module with as opposed to any others they may add either through party creation or as companions.

Multi-Player PC Control In A Single Party

Even with all these functions available, the confusion arose because there was a time when I needed to GET the Main PC of a creature whether it was currently possessed or not. At the time, I thought that GetOwnedCharacter() would do the trick, but this function is limited to checking when the object tested is possessed. I considered a couple of options to get around this problem, but as I also wanted to differentiate between the possibility of more than one player playing in the same party (in Single Party Mode), each one representing a Main PC, then I also needed to be able to determine which Main PC was allocated to each PC in the same party: My idea being that each player could bring along their own PCs and control them by the standard commands that can be given without affecting each others PCs. (E.g. Using the Follow Me command would make only those PCs a player currently controlled follow them and not attract another player's PCs/companions at the same time.)

In the end I wrote my own function GetMainPC(), which I now share for those who may find it of any use. For this function to work, however, it does require some extra work by the coder to ensure a variable is correctly kept up to date whenever a PC or companion joins or leaves a player's "team" within the single party. Hopefully, the following script will help explain its reason:

All this function revision was a prelude to ensuring I could give players the most flexibility when playing their PCs in a party. Whether playing SP or MP, I like to be able to give the player as much versatility with their PCs and how to control them as possible. In a MP game, however, where more than one player controls the same party (even if each player controls their own "team" within that party), extra coding is required to ensure everything plays as it should. And what if the players want to rearrange the teams?

To give players more autonomy over their selected PCs I had to edit my own companion conversation script and include within it an edited version of the #include "hench_i0_hensho" script to ensure the correct "team leader" was listened to with respect to shouted commands. It was as I was testing this and began swapping between the different PCs when I discovered the problem that companions would revert to following the original party leader rather than the team leader whenever a player changed the PC they were possessing. This problem is what led me to learn more about the possession/unpossession hooks of which I will explain in more detail next.

Possession & Unpossession

By default, the companions and the player's Main PC are assigned the same scripts. You can check this out by examining the nwn2_scriptsets.2da. Note though, the scripts do not fire for the Main PC until the player possesses another creature, so no player HB shortcut there. ;) However, this is useful information to know, and editing the nwn2_scriptsets.2da can be a very powerful tool. Add to this knowledge that you can also assign a new set of scripts to a creature using the SetCreatureScriptsToSet function and you have started along a path of interesting AI possibilities.

I mentioned all this to draw attention to the nwn2_scriptsets.2da. If you look at it closely, you will notice two scripts in particular in the ScriptUserDefine column for when a player is possessing a companion (gb_player_ud) or not (gb_comp_usrdef). And when these two scripts are used correctly, we can tell when a player has possessed or unpossessed a PC. By experimentation, I found the gb_comp_usrdef fires when a player unpossesses a PC and the gb_player_ud fires when a player possesses a PC. If you remember this, you can use OBJECT_SELF in each script for the creature it refers to.

E.g. If a player changes possession from their Main PC to a companion called Fred, then gb_comp_usrdef fires on the Main PC and any reference to OBJECT_SELF within it is to the Main PC and gb_player_ud fires on Fred, and OBJECT_SELF within this script will refer to Fred. (NB: I have found that you can refer to both possessed and unpossessed PCs just from the gb_comp_usrdef at the time of unpossession by using GetFirstPC(FALSE) to get the newly controlled PC within the gb_comp_usrdef script. However, I am not sure how reliable this will be, as it may be affected by timing. I tried the same thing by using GetControlledCharacter(OBJECT_SELF), which you would have thought did a similar thing and it only worked sometimes.)

Practical Uses

For me, this newly discovered knowledge will serve me in a couple of ways. (I say will, as I still have to make use of it in some of the things I mention next.) Firstly, it will allow me to quickly change the player who is controlling a PC. For instance, I can now easily change the object variable representing the player who now possesses a PC. i.e. The moment a player switches to another PC, this player will now be the one whose commands the PC will respond to. Currently, the player controls this by a conversation with the PC. Secondly, and for me a performance saver, I can use these scripts to determine if the Main PC Menu (GUI) requires its data updating for viewing a different PC. Previously, I was relying on a function's internal heartbeat to keep checking the PC state while the GUI was open. Now, the same function no longer has to loop through itself making the check, as the hook is now intercepted with this new possession/unpossession function discovery.

Auto-Pause Added Toggle

On a final note related to nwn2_scriptsets.2da, I updated the Main PC Menu to have a toggle switch that allowes the game to auto-pause when a PC is attacked. This works in tandem with the Auto-Pause (Turn-Based Combat Simulator) facility, in that when it is triggered it turns the Auto-Pause facility on at the same time. NB: If players don't want this facility, then it need never be toggled on and is off by default. If, however, they turn it on and then decide they do not want the Auto-Pause facility turned on for the remaining combat, then they can simply switch it off with the new feat that every PC acquires. Below is a screenshot showing the location of the switch:


Anonymous said...

That possession & unpossession information is quite valuable--I had no idea how the game handled that, to be honest.

*Leans back in chair and strokes his beard...*

I think I can come up with a few ideas for that. Thanks for the info! ;)

Does the auto-pause feature only work on attack, or does it work also with trap damage too?

Lance Botelle (Bard of Althéa) said...

Hi Chaos Wielder,

I'm glad you found teh information useful. I know its been something I was looking for for quite some time too. :)

The Auto_paue feature can be made to work on anything you like really. At the moment, I simply use the "on attacked" as the auto start feature, but there is no reason why the hook cannot be "on damaged" as well. Of course, you simply check a condition to see if it is a trap or a creature (or something else) that does the damage, but the answer is "yes"; you can have the auto-pause facility start on anything you can attach a hook to. :)