Note: LucasForums Archive Project
The content here was reconstructed by scraping the Wayback Machine in an effort to restore some of what was lost when LF went down. The LucasForums Archive Project claims no ownership over the content or assets that were archived on archive.org.

This project is meant for research purposes only.

[TSL] Creating Puppets (ie: such as Bao-Dur's Remote)

Page: 1 of 1
 stoffe
08-26-2005, 6:01 AM
#1
Original Thread (http://www.lucasforums.com/showthread.php?t=151436)

--------------------


second, i could really use a good puppet tutorial for a mod im about to start work on



As said, making a puppet is not very complicated, but I tend to be notoriously long-winded in my explanations. Anyway, here is my attempt at explaining it. :)

There are essentially two steps to it, preparing your Puppet creature template, and doing the scripts that will turn it into a working party puppet. So:


1. MAKING THE PUPPET UTC:

Create a new creature template, or modify and existing one. You will need KotorTool and a GFF Editor for this. Set the Tag and Resref of something descriptive, I'll use st_puppet1 in this example.

Set the faction number to 23, which is the puppet faction. This will make the Puppet attack hostile creatures, but hostile creatures will not attack it directly.

Give the puppet all the feats and, if its a force user, force powers it should know, and assign it a class. Give it the ability scores it should have. Equip the weapons and items you want your puppet to carry. Make sure you set everything to be Undroppable.

In this example we want the puppet to increase in strength along with the party, so set the level in the template to something low, like 1 or 2. Also set the hitpoints to something low, no more than 15 unless you want it to end up with an insane amount of health in-game. Since the saving throws and Defense also will be automatically adjusted, don't give any Save of Natural AC bonus unless you want your puppet to be near unbeatable.

Next assign it the party member set of AI scripts, those which begin with k_hen_ rather than k_def_ which most other creatures use. Most of those scripts should work fine for a puppet too, but we'll need to make a custom Heartbeat script for it. More about that below.

So, with everything set up, you might end up with a creature template (UTC file) that might look something like:


Basic:
Name: Bastila the Puppet
Tag: st_puppet1
Race: Human
Appearance: Party_NPC_Bastila
Conversation:

Statistics:
Strength: 12
Dexterity: 18
Constitution: 16
Intelligence: 10
Wisdom: 14
Charisma 16
Save bonuses: 0/0/0
Natural AC: 0
Base Hit Points: 14
Current Hit Points: 14
Max Hit Points: 14

Advanced:
Template Resref: st_puppet1
Faction: 23
Challenge Rating: 3

Class:
Alignment: 70
Class: Jedi Sentinel
Level: 2

Scripts: (Leave all unmentioned slots blank)
OnHeartbeat: st_ai_puppethb
OnNotice: k_hen_percept01
OnAttacked: k_hen_attacked01
OnDamaged k_hen_damage01
OnEndRound: k_hen_combend01
OnDialogue: k_hen_dialogue01
OnSpawn: k_hen_spawn01
OnBlocked: k_hen_blocked01
OnUserDefine: k_hen_userdef01


Save the template with a filename matching your Template Resref, in this example the filename would be st_puppet1.utc. Now open this file in your GFF Editor. I will use K-GFF in this example.

Find the field called MultiplierSet inside the top-level struct. If it doesn't exist, right-click on the toplevel STRUCT and choose Add field: BYTE in the popup-menu, and set the Label to MultiplierSet in the panel to the right. Set the value of this field to 2. This will make the puppet go up in level along with your party, but not make it insanely powerful. The MultiplierSet field is used by the game's autobalancer functionality to adjust the strength of NPCs according to the level of the Exile. This is why you left the Health and Level so low previously.

Next, find the field called IgnoreCrePath. If it does not exist, add it as a BYTE field just like for the MultiplierSet field. Set this field to 1. This will make the creature incorporeal, making it possible to walk right through it. While it may look a bit silly it eliminates the annoyance of the puppet getting in the way in narrow corridors or in the heat of battle.

Save your modified UTC file and put it in the Override folder. Next you'll have to create the scripts that make it into a puppet, and implement the st_ai_puppethb script that was set as OnHeartbeat script above.


2. MAKING THE SCRIPTS:

First we need to create a custom OnHeartbeat script for the puppet to make it follow its owner around. The standard party heartbeat script will only make party members follow, not puppets. Above I called this script st_ai_puppethb, and it should look something like:


// ST: st_ai_puppethb.nss
#include "k_inc_generic"

void main() {
object oEnemy = GetNearestCreature(
CREATURE_TYPE_PERCEPTION, PERCEPTION_SEEN,
OBJECT_SELF, 1,
CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_ENEMY,
CREATURE_TYPE_IS_ALIVE, TRUE
);

if (!GN_GetSpawnInCondition(SW_FLAG_AI_OFF)) {
if (GetIsPuppet()
&& (GetIsObjectValid(GetPUPOwner())
&& (GetCurrentAction(OBJECT_SELF) != ACTION_MOVETOPOINT)
&& (GetCurrentAction(OBJECT_SELF) != ACTION_FOLLOWOWNER)
&& !GetIsConversationActive()
&& !GN_GetSpawnInCondition(SW_FLAG_SPECTATOR_STATE)
&& GetCommandable()
&& (GetDistanceBetween2D(OBJECT_SELF, GetPUPOwner()) > 3.0)

&& (!GetIsObjectValid(oEnemy) || (GetDistanceBetween(oEnemy, OBJECT_SELF) > 20.0))))
{
ClearAllActions();
ActionFollowOwner(2.0);
}
}

if (GN_GetSpawnInCondition( SW_FLAG_EVENT_ON_HEARTBEAT ))
SignalEvent(OBJECT_SELF, EventUserDefined(1001));
}


This script essentially checks if the creature is a puppet with an owner assigned, that it isn't already following, that its AI hasn't been disabled, that it's more than 3 meters from its owner and that there are no enemies nearby. If all those conditions are met, the puppet will begin to follow its owner around.

If you aren't good at scripting, feel free to use the above script for your puppet. There shouldn't be any need to modify it.

In order to compile that script and produce the NCS file the game can use, you must extract the include files k_inc_generic.nss, k_inc_gensupport.nss, k_inc_walkways.nss and k_inc_drop.nss. You can find these files with KotorTool in Kotor II --> BIFs --> scripts.bif --> Script, Source. Extract them and save them in the same folder where you have the nwnnsscomp.exe script compiler. (If you are using the script editor in the latest version of KotorTool you shouldn't need to extract the include files manually; it will find them automatically when needed.)

Next you will have to make a script that turns your creature into a Puppet and assigns it to a party member. For those who aren't good at scripting I've made a function, ST_GivePuppet() that contains all that is necessary to accomplish this. The following example contains the function and an example of its use:


// -------------------------------------------------------
// Support function: Assign a puppet to a party member
// -------------------------------------------------------
void ST_GivePuppet(int nNPC, int nPUP, string sTemplate) {
string sTag = "";
switch (nNPC) {
case NPC_ATTON: sTag = "atton"; break;
case NPC_BAO_DUR: sTag = "baodur"; break;
case NPC_CANDEROUS: sTag = "mand"; break;
case NPC_G0T0: sTag = "g0t0"; break;
case NPC_HANDMAIDEN: sTag = "handmaiden"; break;
case NPC_HK_47: sTag = "hk47"; break;
case NPC_KREIA: sTag = "kreia"; break;
case NPC_MIRA: sTag = "mira"; break;
case NPC_T3_M4: sTag = "t3m4"; break;
case NPC_VISAS: sTag = "visasmarr"; break;
case NPC_HANHARR: sTag = "hanharr"; break;
case NPC_DISCIPLE: sTag = "disciple"; break;
}

object oOwner = GetObjectByTag(sTag);
if (GetIsObjectValid(oOwner) && IsObjectPartyMember(oOwner)) {
location lLoc = Location(GetPosition(oOwner) + AngleToVector(GetFacing(oOwner)) * 2.0, GetFacing(oOwner)-180.0);

AddAvailablePUPByTemplate(nPUP, sTemplate);
AssignPUP(nPUP, nNPC);
object oPUP = SpawnAvailablePUP(nPUP, lLoc);
AddPartyPuppet(nPUP, oPUP);

SetNPCAIStyle(oPUP, NPC_AISTYLE_PARTY_SUPPORT);
}
}


// -------------------------------------------------------
// Main function: Example, call above function to assign
// a puppet in st_puppet1.utc to Kreia.
// -------------------------------------------------------
void main() {
// Spawn the "st_puppet1" UTC as a puppet, assign it to Kreia
ST_GivePuppet(NPC_KREIA, PUP_OTHER1, "st_puppet1");
}


This script would add a creature made from the template created above to the Puppet table, assign it as a puppet to the creature occupying Kreia's slot in the Party table, spawn it into the game and make it an active puppet. It also sets it to use the Party Support AI, which will make a force using puppet use its force powers.

Confusing? Here are a few clarifications:

The Puppet table is a special table within the game that can store creatures. Creatures added to the Puppet table will be globally saved in the savegame, and as such can move between areas. All standard creatures, except the player character, Puppets and Party members are stuck in the area they were spawned in.

The puppet table has 3 slots, indexed from 0 to 2. Bao-Dur's Remote already occupies the first slot, but the two others are vacant and can be used by modders. For simplicity, the game has 3 constants declared to specify puppet table indexes:
PUP_SENSORBALL = Slot 0, occupied by Bao-Dur's remote.
PUP_OTHER1 = Slot 1, vacant;
PUP_OTHER2 = Slot 2, vacant;

These index constants can be used to add a creature to the puppet table, or to retrieve a creature that has been stored there. In our example, AddAvailablePUPByTemplate(PUP_OTHER1, "st_puppet1") would have added our custom creature to the second slot (index 1) in the puppet table.

Just adding a creature to the Puppet Table is not enough to make it into an actual puppet however. To do that it will first have to be linked to an Owner. The owner is defined by providing an index to a slot in the Party Table.

The Party Table is, like the puppet table, a special table that can store creatures globally, allowing them to move between areas. The party table contains 12 slots, indexed from 0 to 11, and is where everyone that has joined your party is stored. For simplicity, the game has a set of index constants defined to make accessing the slots in the party table a bit more intuitive to humans:

NPC_ATTON = Slot 0, usually occupied by Atton Rand;
NPC_BAO_DUR = Slot 1, usually occupied by Bao-Dur, though B4D4 and the Remote use it on two occasions;
NPC_CANDEROUS = Slot 2, usually occupied by Mandalore.
NPC_G0T0 = Slot 3, usually occupied by G0T0.
NPC_HANDMAIDEN = Slot 4, usually occupied by the Handmaiden.
NPC_HK_47 = Slot 5, usually occupied by HK-47.
NPC_KREIA = Slot 6; usually occupied by Kreia.
NPC_MIRA = Slot 7, usually occupied by Mira.
NPC_T3_M4 = Slot 8, usually occupied by T3-M4.
NPC_VISAS = Slot 9, usually occupied by Visas Marr.
NPC_HANHARR = Slot 10, usually occupied by Hanharr.
NPC_DISCIPLE = Slot 11, usually occupied by the Disciple.

Note the reoccurring word usually. You assign a puppet to a Party Table slot, not directly to a party member creature. If another creature occupies one of the party table slots, such as one from a 3rd party recruitment mod, your puppet will get assigned to that creature instead.

You have also probably noticed that your main player character is missing from the Party Table. This character is handled separately and is not stored in the party table. This means that you cannot assign a puppet to the Exile. Only party members can have puppets.

This also means that if your puppet owner gets assigned as the temporary main character (such as being the lead on G0T0's yacht, Mira, Atton or T3 on their solo adventurers, or the Group leader in the Dxun Tomb), their puppet will vanish until they return to being a normal party member again.

Back to the script posted above. The custom function ST_GivePuppet() takes 3 parameters. The first is the party table slot of the one you want to be the puppet owner. The second is the puppet table slot you want to assign your puppet to. The third is the Resref of the creature template we created above. So:

ST_GivePuppet(NPC_KREIA, PUP_OTHER1, "st_puppet1");

...would assign the puppet to Kreia, store it in the first free puppet slot, and use the creature in st_puppet1.utc as the puppet.

If you want to use the above script, modify the ST_GivePuppet() call in the main() function to change the st_puppet1 template into the one you use for your creature, change NPC_KREIA to the party table slot of your intended puppet owner, and if you have more than one puppet, change PUP_OTHER1 to PUP_OTHER2 for the second one. The compile your script and put in override.

In order for that script to work, your intended puppet owner must be in the active party when the script is run. As such a good place to run the script would be as part of the script that makes the NPC in question join your party.

Once you have run this script, the Puppet will appear whenever its Owner is added to the party, and will follow you around. If you transition to a new area, the puppet will follow. If the puppet is killed in battle, it will reappear at full health when you either transition to another area or remove and re-add its owner to the party.



OPTIONAL: GENERIC SCRIPTING EXPLANATIONS:

In more general terms, key scripting functions useful for handling puppets are:

AddAvailablePUPByTemplate(int nPUP, string sTemplate)
This function adds a creature, created from the template whose resref is specified in the sTemplate parameter, to the puppet table slot specified by the nPUP parameter. The puppet creature does not have to exist in the game world yet for this to work. This will not turn the creature into a puppet. It will merely add it to the puppet table so it can later be turned into a puppet.

AddAvailablePUPByObject(int nPUP, object oPuppet)
A variant of the previous function, this will use a creature that already exists in the game world and add it to the party puppet table. oPuppet is the object to "puppetize", and nPUP is the puppet table slot to add it to.

AssignPUP(int nPUP, int nNPC)
This function assigns the puppet in the puppet table slot nPUP to the party member in party table slot nNPC. One of the AddAvailablePUP... functions above must have been used before calling this, and the Owner must be a member of the player's party.

SpawnAvailablePUP(int nPUP, location lLocation)
This spawns the creature stored in the puppet table slot nPUP into the game world at the coordinates indicated by lLocation.

AddPartyPuppet(int nPUP, object oidCreature)
This adds the creature in puppet table slot nPUP as an Active party puppet. This will make it appear whenever the owner is added to the party, and make it follow when moving between areas. For this to work the creature must have already been added to the puppet table with one of the AddAvailablePUP... functions, assigned an owner with AssignPUP(), and spawned into the area with SpawnAvailablePUP(). The oidCreature must be set to this spawned puppet creature.

GetPUPOwner(object oPUP)
This function returns a reference to the creature that is assigned as owner of the puppet specified by the oPUP parameter.

GetIsPuppet(object oPUP)
This function returns TRUE if the object set in the oPUP parameter is a party puppet.


ActionFollowOwner(float fRange)
This function will make the object running it follow its assigned owner at fRange meters distance. The creature must be a puppet and have an owner assigned, or this action will do nothing.
 stoffe
09-12-2007, 2:55 PM
#2
- Is it possible for puppets to equipped items or feats (droid trick ect.) on enemies? I have a couple of modified unlimited use droid items equipped on a droid puppet, but I have yet to see the puppet ever use them.

What have you set the AI style of the puppet to? This can affect what abilities and items the puppet uses during combat. If, for example, you use NPC_AISTYLE_PARTY_REMOTE (14) the puppet will just follow the owner around and use standard attack actions on enemies, never any powers, feats or items. If you use one of the Grenade or Support AI styles it should use items/powers/feats as well.

Though if you don't use the tweaked AI scripts I've done it may be rather sporadic in its item/power use. If you want it to only use droid weapons and never any standard attacks you'll need to make a custom AI script for it though.


-Maybe this is something I missed but, how exactly do you get a recruit script and puppet creation script in the same dialogue? (Like when you recruit Bao-Dur/Remote) Every time I put them in the same dialogue entry (or a following one), only the recruit script seems to fire, and it seems to ignore the puppet script.

Hmm, have you tried putting the scripts on separate nodes? I.e. first the recruit script, and then on the next node the puppet script?

You can also try to modify the recruit script to make it add the puppet as well right after the NPC has been added to the party.
Page: 1 of 1