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.

Scripting a Force Power

Page: 1 of 1
 m16965
01-10-2007, 1:26 AM
#1
Hi people!
I have benn getting into more advanced scripts latley and have been trying (and failing :ears1:) to get it just right. I have been Trying to spawn things for X seconds using visual effects.

#include "k_inc_force"

int FORCE_POWER_MANDALORIAN_HELP = 397;

void main()
{

object oTarget = GetSpellTargetObject();
effect eTargetVisual;
int CasterLevel = GetHitDice(OBJECT_SELF);

SWFP_HARMFUL = FALSE;

if(GetHasSpellEffect(FORCE_POWER_MANDALORIAN_HELP) )



SignalEvent(oTarget, EventSpellCastAt(OBJECT_SELF, GetSpellId(), SWFP_HARMFUL));

eTargetVisual = EffectVisualEffect(VFX_PRO_FORCE_AURA);
eTargetVisual = EffectLinkEffects(eTargetVisual, EffectVisualEffect(VFX_FNF_GRENADE_THERMAL_DETONAT ));



ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eTargetVisual, oTarget, 5.0);
{
if (!GetIsObjectValid(GetObjectByTag("n_mt")))


{
CreateObject( OBJECT_TYPE_CREATURE,
"n_mt", GetLocation(GetFirstPC()));
{
void delay

fDelayInSeconds=60.0;
DelayCommand(fDelayInSeconds,DestroyObject);
DestroyObject(OBJECT_SELF);
}
}
}
}

I get the messgae that "syntax error at fDelayInSeconds"

Also for spells.2da how do i get the nos. for the "name" and "spelldesc" collums. I tried downloading the Ktlk.exe to get it but it dosent work.

Thanks!

~M
 Kitty Kitty
01-10-2007, 2:16 AM
#2
Well for the first question, fDelayInSeconds=60.0; isn't valid. You need to declare it as something, and by the look of things, you want to use float.

So: float fDelayInSeconds=60.0; might work a little better.

Note that I haven't actually looked into the functions and such to ensure you should be using a float there or not, but everything that isn't defined already (usually in the include files) has to be declared first as either a function definition, data type, etc.

As for the other question, I'd have to poke around and do some fiddling to find out. To be honest, I haven't yet done any scripting that's required me to read 2da information by column etc.

On a guess, I'd assume they're numbered in order from left to right, probably starting with column 0 (zero), but again, that's just an off the wall guess.

Either someone who knows should be along sooner or later, or if not I'll do some fiddling and digging and see if I can get a better handle on it. :)

-Kitt
 m16965
01-10-2007, 2:23 AM
#3
Thanks Kitty but float isn't the declarable.

And as for the .2da they have all sorts of numbers ranging from 054 to 14536 or whatever?!?!? :confused:
 Kitty Kitty
01-10-2007, 3:07 AM
#4
Well looking again, your script has some other problems.

Like right above there, where you have

void delay

That would be how you would begin to define a new function, but like the void main () function, those parentheses are required.

Furthermore, if the function accepts any parameters passed into it, those have to be declared in between the parentheses.

So to declare a delay function that does NOT need any parameters passed into it, you'd do something like:


void delay ()
{
float fDelayInSeconds = 60.0;
DelayCommand(fDelayInSeconds,DestroyObject(OBJECT_ SELF));
}


You would then have to put this somewhere in the main function, where when you want this command run, you simply type: delay();

I would highly suggest you look through a lot of the scripting (and perhaps some of the 2da) tutorial posts. There's a lot of information there, and although I wouldn't know offhand exactly how to script what you're trying to do (I've never tried it and would have to play around with it some), you seem to be making some basic fundamental errors which those tutorials could help a lot with.

Specifically, you might check:
Introduction to Scripting Syntax (http://www.lucasforums.com/showthread.php?s=&threadid=143390)
Freequently used script functions (http://www.lucasforums.com/showthread.php?s=&threadid=143412)
How to Script a Buff Force Power (http://lucasforums.com/showthread.php?s=&threadid=130898)
And
Creating A Custom Force Power (http://lucasforums.com/showthread.php?s=&threadid=130898)

All of those look like they might be of at least some value.

I was going to try to show you how I would probably write that script (assuming everything else was correct, which I'd have to look into), but already I can see a lot of basic issues like misplaced braces, improper declarations and so on. Those tutorials would be a really good first step, and in fact a good way to begin is to try to find source for a script that has a similar arrangement (or function if you can get that lucky) and rip it apart to do what you want. That tends to make it a lot harder to goof up the basic syntax and conventions you need to follow.

Like I said though, it's hard for me to be terribly more helpful, since I'd need to go fiddle around with it myself to actually know what I needed to do for the effect you're after. It's not something I've ever attempted, so I don't offhand know how I would actually go about it.

-Kitt
 stoffe
01-10-2007, 7:39 AM
#5
object oTarget = GetSpellTargetObject();

Is this a Friendly (activated in the GUI panel in the lower corner of the screen) or a Hostile (activated above the selected enemy character) power? If it's the former you don't need this line since you can just use OBJECT_SELF instead to refer to the force user. If it's the latter you'll need to use oTarget instead below when getting the spawn location. It's currently only used to determine where to display the visual effects, as far as I can see.


int CasterLevel = GetHitDice(OBJECT_SELF);
SWFP_HARMFUL = FALSE;

This part isn't really necessary since you never use the CasterLevel and can put the Harmful value directly as a function parameter to the EventSpellCastAt() event signal instead.

if (GetHasSpellEffect(FORCE_POWER_MANDALORIAN_HELP))

You are missing a { block start character after this if-statement, making it only affect the line directly following it, (i.e. the SignalEvent() line).

Furthermore, what this line does is that it checks if the spell caster/force power user has effects from spell ID 397 applied to them, and only proceeds if they do.

If spell ID 397 is your power this has several problems. First is that you'd never be able to cast the power to begin with since it only can be cast if it's already active. Second is that you apply no duration effects that can be checked for, since the only effects you apply are two visual effects which are of the "Fire & Forget" type (applied with INSTANT duration), and not any duration visuals.

So I assume you want to negate the check to see if they do not have spell effects on them, and use a duration effect that can be checked for. Though in this case it's probably safer to check if an object with the same tag as the summoned character already exists in the area, if you only want to allow one to be summoned at a time.

eTargetVisual = EffectLinkEffects(eTargetVisual, EffectVisualEffect(VFX_FNF_GRENADE_THERMAL_DETONAT ));

This line has two potential problems. First is that VFX_FNF_GRENADE_THERMAL_DETONAT is not a valid declared constant. I assume you meant to use VFX_FNF_GRENADE_THERMAL_DETONATOR? :) Second is that you are linking only two visual effects. This is usually a bad idea since visuals don't count as "valid" effects, and an effect link expires when there aren't any "valid" effects in it, or when one of the linked effects expire. I'd suggest applying the visuals individually just in case.

After this line there was also an extra { block start character for no apparent reason.

CreateObject( OBJECT_TYPE_CREATURE,"n_mt", GetLocation(GetFirstPC()));

You forget to store the object reference to your newly created creature here, which you'll need shortly.

void delay
fDelayInSeconds=60.0;

As Kitty said above, this line is not valid script code, and fDelayInSeconds needs to be declared before it can be used. Though like the "harmful" line it isn't really necessary to declare a new variable for this since you never modify the duration and it's only used in one place. You can set it directly as a parameter to DelayCommand() below.

DelayCommand(fDelayInSeconds,DestroyObject);

You've forgotten to pass any parameter to the DestroyObject() function which informs it of what object it should destroy. This is what you need the object reference from CreateObject() for, mentioned above.

DestroyObject(OBJECT_SELF)

This line would attempt to destroy the force user. If used by an NPC they would be removed from the game world. If used by the main character nothing would happen. I assume this isn't what you want your power to do. :)

(Furthermore, keep in mind that if you destroy the character like this it might stick around in the area if the party transitions to another module before the duration has expired, since scripts only run on the area the player is currently in. This can be worked around by having the summoned character destroy itself in its heartbeat script when the time is up.)

So, summing up those things you might end up with something like this, if you check the tag to determine if there already is a summoned character in the area:

void main() {
if (!GetIsObjectValid(GetObjectByTag("n_mt"))) {
SignalEvent(OBJECT_SELF, EventSpellCastAt(OBJECT_SELF, GetSpellId(), FALSE));

object oNPC = CreateObject( OBJECT_TYPE_CREATURE,"n_mt", GetLocation(GetFirstPC()));
DelayCommand(60.0, DestroyObject(oNPC));

effect eVis1 = EffectVisualEffect( VFX_PRO_FORCE_AURA );
effect eVis2 = EffectVisualEffect( VFX_FNF_GRENADE_THERMAL_DETONATOR );
ApplyEffectToObject(DURATION_TYPE_INSTANT, eVis1, OBJECT_SELF);
ApplyEffectAtLocation(DURATION_TYPE_INSTANT, eVis2, GetLocation(oNPC));
}
}


Or, if checking for spell effects (though you'd probably want to use a more suitable Visual on the force user, I just picked one as an example :))


int FORCE_POWER_MANDALORIAN_HELP = 397;

void main() {
if (!GetHasSpellEffect(FORCE_POWER_MANDALORIAN_HELP)) {
SignalEvent(OBJECT_SELF, EventSpellCastAt(OBJECT_SELF, GetSpellId(), FALSE));

float fDuration = 60.0;
object oNPC = CreateObject( OBJECT_TYPE_CREATURE,"n_mt", GetLocation(GetFirstPC()));
DelayCommand(fDuration, DestroyObject(oNPC));

effect eVis1 = EffectVisualEffect( VFX_DUR_SHIELD_RED_MARK_I );
effect eVis2 = EffectVisualEffect( VFX_FNF_GRENADE_THERMAL_DETONATOR );
ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eVis1, OBJECT_SELF, fDuration);
ApplyEffectAtLocation(DURATION_TYPE_INSTANT, eVis2, GetLocation(oNPC));
}
}




Also for spells.2da how do i get the nos. for the "name" and "spelldesc" collums. I tried downloading the Ktlk.exe to get it but it dosent work.

The numbers in these columns must be StrRef indexes in the dialog.tlk file which contains the text that should be used for name and description. You'll need a TLK file editor to add new entries for your power to this file (TalkEd will for for this, for example).
 m16965
01-10-2007, 12:16 PM
#6
Thanks very much to both of you. This has been a great help in my scripting knowledge. I added those useless bits in as extra percautions for scripting. Those few errors i amde screwed up the script so thanks again!

The TalkEd was a bit strange for me. I tired making new .Tlk entries Saying Spawn a Blue mandalorian but they came up with stuf like "Eat The meat"?

Also is there a (simple) way to add into my script that if there is "n_mt" already spawned do not spawn another one.

Thanks!
 Kitty Kitty
01-10-2007, 7:03 PM
#7
Thanks for jumping in Stoffe. I could see some of the glaring errors, but much of it was a bit over my head (without a lot of digging to figure out what was what at least). :)

And you're welcome m16965. As for the simple way to not spawn another, that first block of code Stoffe gave as an example of what she might have done above that starts:


void main() {
if (!GetIsObjectValid(GetObjectByTag("n_mt"))) {

Translated into more general terms basically means:
"Begin main function" - everything between the next { and it's MATCHING } (which will be the last one found as they MUST always be in pairs) is part of this function.

And then in the next line (the conditional IF) we're using the functions GetIsObjectValid and GetObjectByTag. The second one simply 'scans' the loaded game area for an instance of any object with the tag "m_nt". If one IS found, it returns an object reference to that object. If not, it returns as OBJECT_INVALID --which is merely a constant defined as FALSE or 0 (zero).

Now we pass the result of that function to the function GetIsObjectValid (remember that like in C, everything works from the INSIDE out. You solve the deepest set of () within each statement first, then work outward) -which is used here as a cleaner way of checking whether the conditional is equal or not equal.

So, that line essentially says:
IF there is NOT (see the ! right at the front? That means not) a valid object with the tag n_mt here, then DO everything from the NEXT { until the matching }.

If there IS a valid object with that tag, everything from that { until its matching } will be skipped and ignored, which in this case, winds up the entire function, so we simply do nothing and exit.

Hope that helps a bit. :)

-Kitt
 m16965
01-11-2007, 12:18 AM
#8
Thanks Kitty, i actualy had that somewhere :headbump When i started modding i wrote down a few rules about C+ like all {}()[] must be used in a pair, == equals equals :D //, /* */ and all that!

Okay after a bit of edting i came up with this

int FORCE_POWER_MANDALORIAN_HELP = 397;

void main()
{
if (!GetHasSpellEffect(FORCE_POWER_MANDALORIAN_HELP))
if (!GetIsObjectValid(GetObjectByTag("n_mt")))

{
SignalEvent(OBJECT_SELF, EventSpellCastAt(OBJECT_SELF, GetSpellId(), FALSE));


{
float fDuration = 60.0;
object oNPC = CreateObject( OBJECT_TYPE_CREATURE,"n_mt", GetLocation(GetFirstPC()));
DelayCommand(fDuration, DestroyObject(oNPC));
{

float fRange = 2.5;
ActionFollowOwner(fRange);

effect eVis2 = EffectVisualEffect( VFX_FNF_GRENADE_THERMAL_DETONATOR );
ApplyEffectAtLocation(DURATION_TYPE_INSTANT, eVis2, GetLocation(oNPC));
}
}
}
}

The problem is i seem to be able to spawn loads of n_mt's
 stoffe
01-11-2007, 7:26 AM
#9
Okay after a bit of edting i came up with this
(snip)
The problem is i seem to be able to spawn loads of n_mt's

You still have no persistant effect applied on the force user that lasts for the duration of the power, making the line... if (!GetHasSpellEffect(FORCE_POWER_MANDALORIAN_HELP)) ...pointless since it will always return TRUE.

Also, check in n_mt.utc that the Tag field is also set to n_mt. If it's not you'll either have to change the Tag, or change the script to check for its proper tag in the GetObjectByTag() function call (but not in CreateObject(), since that uses the ResRef and not the tag).

Furthermore, the line... ActionFollowOwner(fRange); ...will not work since that action only does something if the character is applied to is a party puppet (and in your script you issue it to the force user and not the summoned character anyway).

If you want a non-partymember and non-puppet to follow you around you'll have to alter its heartbeat script to do so by checking the distance to its summoner and queueing ActionMoveToObject() actions when they get too far away.



Other than that, the block markers you add after the SignalEvent and DelayCommand lines serve no purpose and could be removed (along with the closing pair). You use blocks so group together several lines of code into a single entity that usually belongs to or is affected by what comes directly before it. It's most commonly used in functions, if-statements and loops to make them affect more than just the statement/line directly following them. For example:


// ST: All the lines inside this { } block belongs to the main() function.
void main() {
// ST: all the lines inside this { } are only run if the main character exists.
// I.e. the script runner would shout "Hello World" and move to the PC only
// if the main character exists.
if (GetIsObjectValid(GetFirstPC())) {
ActionMoveToObject(GetFirstPC());
ActionSpeakString("Hello World!");
}

// ST: Unlike here, which is identical to the above except the block.
// Here the Move action will only be done if the main character exists,
// while the Speak action always will be done, since it's no longer a part
// of the if-statement.
if (GetIsObjectValid(GetFirstPC()))
ActionMoveToObject(GetFirstPC());
ActionSpeakString("Hello World!");

}
 m16965
01-13-2007, 10:55 AM
#10
if (!GetHasSpellEffect(FORCE_POWER_MANDALORIAN_HELP)) ...pointless since it will always return TRUE.
Oops! Forgot that!

Furthermore, the line...

ActionFollowOwner(fRange); not work since that action only does something if the character is applied to is a party puppet (and in your script you issue it to the force user and not the summoned character anyway).

If you want a non-partymember and non-puppet to follow you around you'll have to alter its heartbeat script to do so by checking the distance to its summoner and queueing ActionMoveToObject() actions when they get too far away.
Woah. Didn't know that. Thanks!

So the hearbeat script should look like this...
void main()
{
//Origonal script +
if (GetIsObjectValid(GetFirstPC()))
ActionMoveToObject(GetFirstPC());
}

:D

..And the force power script should look like this...
int FORCE_POWER_MANDALORIAN_HELP = 397;

void main()
{
if (!GetIsObjectValid(GetObjectByTag("n_mt")))

SignalEvent(OBJECT_SELF, EventSpellCastAt(OBJECT_SELF, GetSpellId(), FALSE));


{
float fDuration = 60.0;
object oNPC = CreateObject( OBJECT_TYPE_CREATURE,"n_mt", GetLocation(GetFirstPC()));
DelayCommand(fDuration, DestroyObject(oNPC));


effect eVis2 = EffectVisualEffect( VFX_FNF_GRENADE_THERMAL_DETONATOR );
ApplyEffectAtLocation(DURATION_TYPE_INSTANT, eVis2, GetLocation(oNPC));
}
}

:D Thanks!
 m16965
01-15-2007, 11:58 AM
#11
Just a Question. Could i Set a local Number so that is the force power has already been cast do not spawn another "m_mt"?
 stoffe
01-15-2007, 12:17 PM
#12
Just a Question. Could i Set a local Number so that is the force power has already been cast do not spawn another "m_mt"?

You can, just make sure you pick a LocalNum that isn't already used by the AI scripts or to keep track of any conversations or plots.

It's a somewhat risky method though since you'll need to unset the variable as well, otherwise you'll only be able to cast the power once, ever. And if you queue a delayed command to unset the LocalNum that command will be discarded if the force user transitions to a new area before the duration has run out, and you'll end up being unable to cast the power for the rest of the game.

That said, using Local variables shouldn't really be necessary; in the scripts above you are already checking if the creature exists in the area already before spawning it. If this check does not work and you can spawn multiple copies you likely check for the wrong tag. Make sure the tag in the script match the value in the Tag field in the UTC file you spawn the creature from.
 m16965
01-15-2007, 2:35 PM
#13
The tag is correct as i made a few new UCTs ;)

In the scripts above i still seem to be able to spawn limitless "n_mt"s
if (!GetIsObjectValid(GetObjectByTag("n_mt")))
Dosen't this check just that the object is valid but not spawned before? If not please excuse my n00bish questions :D
 stoffe
01-15-2007, 2:42 PM
#14
In the scripts above i still seem to be able to spawn limitless "n_mt"s
if (!GetIsObjectValid(GetObjectByTag("n_mt")))
Dosen't this check just that the object is valid but not spawned before? If not please excuse my n00bish questions :D

The check attempts to get any object instance in the currently loaded area that has the tag n_mt. If no such object exists (i.e. the GetObjectByTag() function returns no valid object) then next block of code is executed. If any object with that tag exists in the area the next block of code is skipped.

So, if you are 100% sure that the Tag set in the UTC the creature is spawned from is exactly n_mt check that the following block of code is encased inside { ... } markers. If not the IF-statement will only affect the statement directly following it.

Post the full code you are currently using if it still won't work properly.
 m16965
01-15-2007, 4:02 PM
#15
Okay, most of the functions are working its just i seem to be able to spawn limitless amounts of them

int FORCE_POWER_MANDALORIAN_HELP = 397;

void main()
{
if (!GetIsObjectValid(GetObjectByTag("n_mt")))

SignalEvent(OBJECT_SELF, EventSpellCastAt(OBJECT_SELF, GetSpellId(), FALSE));


{
float fDuration = 60.0;
object oNPC = CreateObject( OBJECT_TYPE_CREATURE,"n_mt", GetLocation(GetFirstPC()));
DelayCommand(fDuration, DestroyObject(oNPC));


effect eVis2 = EffectVisualEffect( VFX_FNF_GRENADE_THERMAL_DETONATOR );
ApplyEffectAtLocation(DURATION_TYPE_INSTANT, eVis2, GetLocation(oNPC));
}
}

And the heartbeat script

#include "k_inc_switch"
#include "k_inc_debug"

void main()
{
{
if (GetIsObjectValid(GetFirstPC()))
ActionMoveToObject(GetFirstPC());

ExecuteScript("k_ai_master", OBJECT_SELF, KOTOR_DEFAULT_EVENT_ON_HEARTBEAT);
/*
object oEnemy = GetNearestCreature(CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_ENEMY, OBJECT_SELF,1, CREATURE_TYPE_PERCEPTION, PERCEPTION_SEEN);

if(!GN_GetSpawnInCondition(SW_FLAG_AI_OFF))
{
if(GN_GetSpawnInCondition(SW_FLAG_AMBIENT_ANIMATIO NS) || GN_GetSpawnInCondition(SW_FLAG_AMBIENT_ANIMATIONS_ MOBILE))
{
string sWay = "WP_" + GetTag(OBJECT_SELF) + "_01";
int nSeries = GetLocalNumber(OBJECT_SELF, WALKWAYS_SERIES_NUMBER);
if(!GetIsObjectValid(GetObjectByTag(sWay)) && nSeries <= 0)
{
if(GetCurrentAction(OBJECT_SELF) != ACTION_MOVETOPOINT)
{
if(!GN_GetIsFighting(OBJECT_SELF) && !GetIsObjectValid(oEnemy))
{
GN_PlayAmbientAnimation();
}
}
}
}
}
if(GN_GetSpawnInCondition(SW_FLAG_EVENT_ON_HEARTBE AT))
{
SignalEvent(OBJECT_SELF, EventUserDefined(1001));
}
*/
}
}

Thanks for the help Stoffe! :D
 stoffe
01-15-2007, 5:27 PM
#16
Okay, most of the functions are working its just i seem to be able to spawn limitless amounts of them

int FORCE_POWER_MANDALORIAN_HELP = 397;

void main() {

if (!GetIsObjectValid(GetObjectByTag("n_mt")))

SignalEvent(OBJECT_SELF, EventSpellCastAt(OBJECT_SELF, GetSpellId(), FALSE));


{
float fDuration = 60.0;
object oNPC = CreateObject( OBJECT_TYPE_CREATURE,"n_mt", GetLocation(GetFirstPC()));
DelayCommand(fDuration, DestroyObject(oNPC));


effect eVis2 = EffectVisualEffect( VFX_FNF_GRENADE_THERMAL_DETONATOR );
ApplyEffectAtLocation(DURATION_TYPE_INSTANT, eVis2, GetLocation(oNPC));
}
}

See the quoted code above. The parts in bold yellow is what I was talking about in my previous post. You have no block markers enveloping the code that should be affected by the IF-statement. Only the SignalEvent() line will currently be skipped if the creature already exists, nothing else.

Further, the block markers colored red above serve no purpose and should be removed.

The end result would be something like:

void main() {
if (!GetIsObjectValid(GetObjectByTag("n_mt")))
{
SignalEvent(OBJECT_SELF, EventSpellCastAt(OBJECT_SELF, GetSpellId(), FALSE));
object oNPC = CreateObject( OBJECT_TYPE_CREATURE,"n_mt", GetLocation(GetFirstPC()));
DelayCommand(60.0, DestroyObject(oNPC));

effect eVis2 = EffectVisualEffect( VFX_FNF_GRENADE_THERMAL_DETONATOR );
ApplyEffectAtLocation(DURATION_TYPE_INSTANT, eVis2, GetLocation(oNPC));
}
}


As for the heartbeat script you'll probably need to check if the character is already moving before issuing another move action or they will queue up like mad. You also need to block execution of the standard heartbeat while moving, or the character may stop to perform ambient idle animations and such. You should also do a distance check so the character won't try to move if they are already nearby. Then you might end up with something like:

#include "k_inc_switch"

void main() {
object oPC = GetFirstPC();
int bMoving = (GetCurrentAction() == ACTION_MOVETOPOINT);

if (!bMoving && (GetDistanceToObject(oPC) > 3.0)) {
ClearAllActions();
ActionMoveToObject(oPC, TRUE, 2.0);
}
else if (!bMoving) {
ExecuteScript("k_ai_master", OBJECT_SELF, KOTOR_DEFAULT_EVENT_ON_HEARTBEAT);
}
}
Page: 1 of 1