PDA

View Full Version : Force Confusion spell for Kotor 1


WRFan
06-11-2009, 03:01 PM
I created a script that works the same way as Force Confusion in TSL - it will dominate an enemy for 30 sec. and let them fight for you. Here's the script:


#include "k_inc_generic"

void Changeback(object oTarget, int AffectedFaction)
{

if (GetIsObjectValid(oTarget))
{
ChangeToStandardFaction(oTarget,AffectedFaction);
AssignCommand(oTarget, ClearAllActions());
AssignCommand(oTarget, GN_DetermineCombatRound());
}
SetGlobalNumber( "000_Human_Conf_Active", 0 );
}


void Sp_RunForcePowers()
{
// official scripts are here

case 132: //FORCE_POWER_CONFUSION:
{
int nResist = 0;
int nSaves = 0;


if( GetGlobalNumber( "000_Human_Conf_Active" ) == 0 ) {




// SWFP_HARMFUL = FALSE;
SignalEvent(OBJECT_SELF, EventSpellCastAt(OBJECT_SELF, 132, FALSE));

SWFP_PRIVATE_SAVE_TYPE = SAVING_THROW_WILL;

if( GetPlotFlag( oTarget ) )
{
return;
}

if (GetRacialType(oTarget) == RACIAL_TYPE_DROID)
{
return;
}

if (GetSubRace(oTarget) == 2)
{
return;
}


if(!ResistForce(OBJECT_SELF, oTarget))
{

nSaves = Sp_MySavingThrows(oTarget);
if(nSaves == 0)
{

int AffectedFaction = GetStandardFaction(oTarget);

ChangeToStandardFaction(oTarget, STANDARD_FACTION_FRIENDLY_1);
AssignCommand(oTarget, ClearAllActions());
// AssignCommand(oTarget, GN_DetermineCombatRound());

SetGlobalNumber( "000_Human_Conf_Active", 1 );


DelayCommand(30.0,Changeback(oTarget, AffectedFaction));
}
}
}


}

break;



Only problem is that you become visible when you cast the spell. Didn't happen in Kotor 2. Don't know how to fix this.

I also have to use the stupid DelayCommand function, since the real effect used in Kotor 2 is obviously not present in Kotor 1. Not the best solution, but it works

RedHawke
06-12-2009, 02:38 AM
Moved to Holowan Labs as this is more a resource than a mod itself, only downloadable mods to be posted in T.U.C.E. thanks.

WRFan
06-13-2009, 06:01 PM
I uploaded the changed files, you can download the mod here:

http://home.arcor.de/wrfan/files/kotor/Kotor_Force_Confusion.rar

The spell is kind of funny. I dominated the bodyguard guarding the swoop accelerator on Taris and she killed all the other thugs. Some bodyguard she is. lol. I also dominated Davik and he helped me to dispose of Calo. So funny.

WRFan
06-17-2009, 12:14 PM
Interesting, I found a way to stay invisible when casting a spell in Kotor. This is needed to simulate proper behaviour of Force Confusion (casting this spell in TSL doesn't make you visible). It's really complicated, though, since Bioware removed many useful NWN functions from Kotor engine.

This is how it works:

Assign void to oTarget (oEnemy):

AssignCommand(oTarget, CheckMe());

CheckMe function:

1) Define the Attacker (oPC):

object oNoticed = GetNearestCreature(CREATURE_TYPE_PLAYER_CHAR, PLAYER_CHAR_IS_PC, OBJECT_SELF,1, CREATURE_TYPE_PERCEPTION, PERCEPTION_SEEN);

2) Check if Object_Self is fighting. Now, this one is really tricky due to the way Kotor engine checks this. If you are in stealth mode and attempt to cast a spell / attack on enemy (which cancels stealth mode), the engine returns the following integers:

code:

int agitated = GN_GetSpawnInCondition(SW_FLAG_STATE_AGITATED);
int fighting1 = GN_GetIsFighting(OBJECT_SELF);
int fighting2 = GetIsInCombat(OBJECT_SELF);

GetSpawnInCondition(SW_FLAG_STATE_AGITATED) returns TRUE

GetIsFighting(OBJECT_SELF) returns TRUE

GetIsInCombat(OBJECT_SELF) returns FALSE

Now, this is interesting, isn't it? Using GetIsInCombat you can determine if your PC was actually in stealth mode prior to casting the spell on an enemy.

So the condition looks like this:


int agitated = GN_GetSpawnInCondition(SW_FLAG_STATE_AGITATED);
int fighting1 = GN_GetIsFighting(OBJECT_SELF);
int fighting2 = GetIsInCombat(OBJECT_SELF);
object WhoWasAttacked = GetAttemptedAttackTarget();

if(!fighting2 || (fighting2 == TRUE && WhoWasAttacked != oNoticed) )
{
// make me invisible again!
}


How to make yourself invisible in Kotor? Here's the code:


int nDuration = 10;
float fDuration=RoundsToSeconds(nDuration);
effect eInvisibility = EffectInvisibility(INVISIBILITY_TYPE_NORMAL);
effect eInvisibilityVFX = EffectVisualEffect( 8000 );
ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eInvisibility, oNoticed, fDuration);
ApplyEffectToObject(DURATION_TYPE_PERMANENT, eInvisibilityVFX, oNoticed);


The invisibility is applied for 10 rounds (=30 seconds, the duration of Force Confusion spell). You can re-enter proper Stealth mode during this time (hit the Stealth button or press "G" keyboard key), otherwise invisibility is lifted after 30 sec.

the invisibility VFX is set to permanent to avoid conflicts with the VFX applied to oPC when you enter stealth mode. So I wouldn't recommend linking Invisibility VFX and the actual Invisibility effect.

WRFan
06-18-2009, 09:59 AM
crap, it's simply impossible to simulate standard invisibility behaviour using this crappy stealth function, because it's not a real invisibility function, it works like the Hide/Move Silently skill in D&D. So the SpellIsHarmful = FALSE flag doesn't apply, and simulating it is impossible due to how the engine interprets enemy perception. I've played around with the following functions:

int agitated = GN_GetSpawnInCondition(SW_FLAG_STATE_AGITATED);
int fighting1 = GN_GetIsFighting(OBJECT_SELF);
int fighting2 = GetIsInCombat(OBJECT_SELF);
int fightingPC = GetIsInCombat(oNoticed);
object WhoWasAttacked = GetAttemptedAttackTarget();
object WhoWasSpelled = GetAttemptedSpellTarget();
object oGetAttackTarget = GetAttackTarget(OBJECT_SELF);
int PerceptionReallySeen = GetLastPerceptionSeen();

But really, it's pointless. Sometimes enemies lose perception of you even if you are in plain sight right in front of them, GetAttemptedAttackTarget returns TRUE even if it shouldn't and so on. So this is pointless. The only real solution is to use a real Invisibility spell which places a real invisibility effect on the target. Then the code applies:

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

Basically, if SWFP_HARMFUL is set to FALSE, you will not become visible. This is how it's supposed to work anyway in D&D. I mean, a spellcaster simply doesn't have enough skill points to invest in Hide/Stealth skill. I am using Invisibility/Etherealness in NWN2, and I am going to use it in Kotor too. I'll post my Mass Invisibility spell script on another topic in this forum.

About my Force Confusion spell: I didn't consider the possibility that the dominated target may die before the spell duration wears off in which case the global var that determines if the spell is currently active is never reset. I've completely re-written the script, now it includes a DelayCommand loop, which checks for two conditions:

- Dominated target is dead
- Spell wears off (duration is down to 0)

Here's the code. It may be interesting for modders who are looking for a way to create long-time loops. "While" loops won't help, cause Kotor won't allow "while" loops that exceed a certain number of operations, the engine will simply terminate the loop:

Main script (#include "k_inc_generic" needed to work):

case 132: //FORCE_POWER_CONFUSION:
{
float fDuration = 0.0f;
int nResist = 0;
int nSaves = 0;

if( GetGlobalNumber( "000_Human_Conf_Active" ) == 0 ) {
SWFP_HARMFUL = FALSE;
SignalEvent(oTarget, EventSpellCastAt(OBJECT_SELF, GetSpellId(), SWFP_HARMFUL));
SWFP_PRIVATE_SAVE_TYPE = SAVING_THROW_WILL;

if( GetPlotFlag( oTarget ) )
{
return;
}

if (GetRacialType(oTarget) == RACIAL_TYPE_DROID)
{
return;
}

if (GetSubRace(oTarget) == 2)
{
return;
}


if(!ResistForce(OBJECT_SELF, oTarget))
{

nSaves = Sp_MySavingThrows(oTarget);
if(nSaves == 0)
{

int AffectedFaction = GetStandardFaction(oTarget);

ChangeToStandardFaction(oTarget, STANDARD_FACTION_FRIENDLY_1);
AssignCommand(oTarget, ClearAllActions());
AssignCommand(oTarget, GN_DetermineCombatRound());

SetGlobalNumber( "000_Human_Conf_Active", 1 );
effect eTimer = EffectSpellLevelAbsorption(9,15);
ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eTimer, oTarget, RoundsToSeconds(10));

CheckDead(oTarget, AffectedFaction);
}
}
}

}

break;


CheckTargetIsDead /Check SpellDuration script:


void CheckDead(object oTarget, int AffectedFaction)
{
int TesteTimer = GetHasSpellEffect(132, oTarget);
if ( GetIsDead(oTarget) )
{
SetGlobalNumber( "000_Human_Conf_Active", 0 );
PauseGame( TRUE );
return;
}
if (TesteTimer == 0)
{
ChangeToStandardFaction(oTarget,AffectedFaction);
AssignCommand(oTarget, ClearAllActions());
AssignCommand(oTarget, GN_DetermineCombatRound());
SetGlobalNumber( "000_Human_Conf_Active", 0 );
PauseGame( TRUE );
return;
}

DelayCommand(0.1f,CheckDead(oTarget, AffectedFaction));
}


You may be amazed at the way I check for the duration of the spell. Yep, a strange way to do it, but a very precise way. I apply a decrepit useless effect to oTarget for 30 sec. (effect SpellLevelAbsorption that is not used in Kotor, but still present in the engine) and check for its validity. When it wears off, it means 30 sec. have passed and it's time to terminate the Force Confusion effect (which is not a real effect, which causes all the complications)

stoffe
06-18-2009, 11:48 AM
If you just want the character to stay invisible for the duration of the effect even if they attack try using the EffectInvisibility() function with a INVISIBILITY_TYPE_IMPROVED parameter instead of INVISIBILITY_TYPE_NORMAL. That should prevent the effect from expiring when the character attacks.

Be aware though that the KOTOR AI does not anticipate this kind of invisibility and as such enemies will just stand there like idiots and be killed without reacting.

(Also be aware when using long-running recursive function calls that performs cleanups/resets that the function call loop will be terminated if the player zones out of the area, thus preventing that cleanup/reset from happening and in the above case leaving them stuck in the Friendly faction.)

WRFan
06-18-2009, 08:21 PM
Be aware though that the KOTOR AI does not anticipate this kind of invisibility and as such enemies will just stand there like idiots and be killed without reacting.


yes, so normal invisibility is actually better, since it is cancelled when you cast harmful spells. So if you cast Choke on an enemy, invisibility is cancelled, but if you cast Master Valor, it stays active.

I've noticed an AI problem - if you dominate an enemy (change an enemy's faction to your own faction, like my script above does) then other enemies will sometimes fail to attack the dominated enemy, they just stand there and do nothing. They will only attack if they themselves are attacked by the dominated enemy. This happens very often if enemies lose perception of your PC (if you go in stealth mode while they are horrified or in stasis for example).

I think this has something to do with the OnPerception script (k_ai_master.nss), but I can't pinpoint the error. However, I found a different solution to this. After you dominate an enemy (change his/her faction), tell every enemy in the area to re-determine their combat rounds.

The following script checks all creature objects in the area around the dominated human and if they are hostile towards him, they are told to re-determine their combat rounds. I tested it and now it seems to be working properly:



object oArea = oTarget;
object oObject = GetFirstObjectInArea(oArea,OBJECT_TYPE_CREATURE);
while(GetIsObjectValid(oObject))
{

if(GetIsEnemy(oTarget,oObject))
{
AssignCommand(oObject, GN_DetermineCombatRound());
}
oObject = GetNextObjectInArea(oArea,OBJECT_TYPE_CREATURE);
}


#include "k_inc_generic" required for the script to work

stoffe
06-19-2009, 04:31 AM
I think this has something to do with the OnPerception script (k_ai_master.nss), but I can't pinpoint the error. However, I found a different solution to this. After you dominate an enemy (change his/her faction), tell every enemy in the area to re-determine their combat rounds.


The problem, if I remember correctly, is that the OnPerception event only triggers when the NPC first notices another NPC, and again when they subsequently can no longer see/hear them. Thus it won't trigger for NPCs that change faction after they have already been seen.

WRFan
06-19-2009, 04:47 PM
The problem, if I remember correctly, is that the OnPerception event only triggers when the NPC first notices another NPC, and again when they subsequently can no longer see/hear them. Thus it won't trigger for NPCs that change faction after they have already been seen.


yes, I suspected as much. I actually tried to force NPCs to re-trigger the OnPerception script by applying the invisibility effect to the dominated NPC, so if he vanishes for a second, enemies should actually try to find him. But it doesn't work. Possibly, because invisibility is not meant to be used in Kotor, so the AI can't cope with this