Updated Release Archive. Fixed Mage-killer prereqs. Removed old LETO & ConvoCC related files. Added organized spell scroll store. Fixed Gloura spellbook. Various TLK fixes. Reorganized Repo. Removed invalid user folders. Added DocGen back in.
438 lines
21 KiB
Plaintext
438 lines
21 KiB
Plaintext
/*
|
||
----------------
|
||
Energy Current
|
||
|
||
psi_pow_encurr
|
||
----------------
|
||
|
||
2/8/05 by Stratovarius
|
||
*/ /** @file
|
||
|
||
Energy Current
|
||
|
||
Psychokinesis [see text]
|
||
Level: Kineticist 5
|
||
Manifesting Time: 1 standard action
|
||
Range: Close (25 ft. + 5 ft./2 levels)
|
||
Target: Any two creatures no more than 15 ft. apart
|
||
Duration: Concentration, up to 1 round/level
|
||
Saving Throw: Reflex half or Fortitude half; see text
|
||
Power Resistance: Yes
|
||
Power Points: 9
|
||
Metapsionics: Empower, Extend, Maximize
|
||
|
||
Upon manifesting this power, you choose cold, electricity, fire, or sonic.
|
||
Your body’s psionically fueled bioenergetic currents produce an arc of
|
||
energy of the chosen type that targets a creature you designate as the
|
||
primary foe for 9d6 points of damage in every round when the power remains
|
||
in effect. Energy also arcs off the primary foe to strike one additional foe
|
||
that is initially within 15 feet of the primary foe, or that subsequently
|
||
moves within 15 feet of the primary foe while the duration lasts. Secondary
|
||
foes take half the damage that the primary foe takes in every round while
|
||
the duration lasts.
|
||
|
||
Should either the primary or secondary foe fall to less than 0 hit points
|
||
(or should a target completely evade the effect with a special ability or
|
||
power), the energy current ’s arc randomly retargets another primary and/or
|
||
secondary foe while the duration lasts. Targeted foes can move normally,
|
||
possibly moving out of range of the effect, but each round they are targeted
|
||
and remain in range they must make a saving throw to avoid taking full
|
||
damage in that round.
|
||
|
||
Concentrating to maintain energy current is a full-round action. If you take
|
||
damage while maintaining energy current, you must make a successful
|
||
Concentration check (DC 10 + damage dealt) to avoid losing your
|
||
concentration on the power.
|
||
|
||
Cold: A current of this energy type deals +1 point of damage per die. The
|
||
saving throw to reduce damage from a cold current is a Fortitude save
|
||
instead of a Reflex save.
|
||
Electricity: Manifesting a current of this energy type provides a +2 bonus
|
||
to the save DC and a +2 bonus on manifester level checks for
|
||
the purpose of overcoming power resistance.
|
||
Fire: A current of this energy type deals +1 point of damage per die.
|
||
Sonic: A current of this energy type deals -1 point of damage per die and
|
||
ignores an object’s hardness.
|
||
|
||
This power’s subtype is the same as the type of energy you manifest.
|
||
|
||
Augment: You can augment this power in one or both of the following ways.
|
||
1. For every additional power point you spend, this power’s damage increases
|
||
by one die (d6). For each extra two dice of damage, this power’s save DC
|
||
increases by 1.
|
||
2. For every 4 additional power points you spend, this power can affect an
|
||
additional secondary target. Any additional secondary target cannot be
|
||
more than 15 feet from another target of the power.
|
||
|
||
@todo 2da
|
||
*/
|
||
|
||
#include "psi_inc_psifunc"
|
||
#include "psi_inc_pwresist"
|
||
#include "psi_spellhook"
|
||
#include "prc_inc_spells"
|
||
#include "psi_inc_enrgypow"
|
||
|
||
|
||
const string SECONDARY_TARGETS_ARRAY = "PRC_Power_EnergyCurrent_SecondaryTargets";
|
||
|
||
|
||
//////////////////////////////////////////////////
|
||
/* Function prototypes */
|
||
//////////////////////////////////////////////////
|
||
|
||
void EnergyCurrentHB(struct manifestation manif, struct energy_adjustments enAdj,
|
||
object oMainTarget, int nDC, int nPen, int nNumberOfDice, int nSecondaryTargets, float fRange,
|
||
location lManifesterOld, int nBeatsRemaining, int bFirst);
|
||
|
||
void SecondaryTargetsCheck(object oManifester, object oMainTarget, int nSecondaryTargets, int nPen);
|
||
|
||
void DoEnergyCurrentDamage(struct manifestation manif, struct energy_adjustments enAdj,
|
||
object oMainTarget, int nDC, int nPen, int nNumberOfDice);
|
||
|
||
|
||
//////////////////////////////////////////////////
|
||
/* Function definitions */
|
||
//////////////////////////////////////////////////
|
||
|
||
void main()
|
||
{
|
||
// Are we running the manifestation part or the eventhook
|
||
if(GetRunningEvent() != EVENT_ONHIT)
|
||
{
|
||
// Power use hook
|
||
if(!PsiPrePowerCastCode()) return;
|
||
|
||
object oManifester = OBJECT_SELF;
|
||
object oMainTarget = PRCGetSpellTargetObject();
|
||
struct manifestation manif =
|
||
EvaluateManifestation(oManifester, oMainTarget,
|
||
PowerAugmentationProfile(PRC_NO_GENERIC_AUGMENTS,
|
||
1, PRC_UNLIMITED_AUGMENTATION,
|
||
4, PRC_UNLIMITED_AUGMENTATION
|
||
),
|
||
METAPSIONIC_EMPOWER | METAPSIONIC_EXTEND | METAPSIONIC_MAXIMIZE
|
||
);
|
||
|
||
if(manif.bCanManifest)
|
||
{
|
||
struct energy_adjustments enAdj =
|
||
EvaluateEnergy(manif.nSpellID, POWER_ENERGYCURRENT_COLD, POWER_ENERGYCURRENT_ELEC, POWER_ENERGYCURRENT_FIRE, POWER_ENERGYCURRENT_SONIC,
|
||
VFX_BEAM_COLD, VFX_BEAM_LIGHTNING, VFX_BEAM_FIRE, VFX_BEAM_ODD);
|
||
int nDC = GetManifesterDC(oManifester) + enAdj.nDCMod + (manif.nTimesAugOptUsed_1 / 2);
|
||
int nPen = GetPsiPenetration(oManifester) + enAdj.nPenMod;
|
||
int nNumberOfDice = 9 + manif.nTimesAugOptUsed_1;
|
||
int nSecondaryTargets = 1 + manif.nTimesAugOptUsed_2;
|
||
effect eDurManifester = EffectVisualEffect(VFX_DUR_PARALYZE_HOLD);
|
||
float fRange = 25.0f + (5.0f * (manif.nManifesterLevel / 2));
|
||
float fDuration = 6.0f * manif.nManifesterLevel;
|
||
if(manif.bExtend) fDuration *= 2;
|
||
|
||
// Get the OnHitCast: Unique on the manifester's armor / hide
|
||
ExecuteScript("prc_keep_onhit_a", oManifester);
|
||
|
||
// Hook eventscript for concentration checks due to being damaged
|
||
AddEventScript(oManifester, EVENT_ONHIT, "psi_pow_encurr", TRUE, FALSE);
|
||
|
||
// Apply a VFX for the dispelling check to look for
|
||
SPApplyEffectToObject(DURATION_TYPE_TEMPORARY, eDurManifester, oManifester, fDuration, TRUE, manif.nSpellID, manif.nManifesterLevel);
|
||
|
||
// Power resistance for the first main target
|
||
if(!PRCMyResistPower(oManifester, oMainTarget, nPen))
|
||
{
|
||
// Set the main target to be invalid so the loop will select a new one
|
||
oMainTarget = OBJECT_INVALID;
|
||
}
|
||
|
||
// Start the heartbeat
|
||
EnergyCurrentHB(manif, enAdj, oMainTarget, nDC, nPen, nNumberOfDice, nSecondaryTargets, fRange, GetLocation(oManifester), FloatToInt(fDuration) / 6, TRUE);
|
||
}// end if - Successfull manifestation
|
||
}// end if - Manifesting the power
|
||
else
|
||
{
|
||
object oManifester = OBJECT_SELF;
|
||
object oItem = GetSpellCastItem();
|
||
|
||
// Make sure the one doing the triggering hit was someone else
|
||
if(GetBaseItemType(oItem) == BASE_ITEM_ARMOR ||
|
||
GetBaseItemType(oItem) == BASE_ITEM_CREATUREITEM
|
||
)
|
||
{
|
||
// DC 10 + damage dealt to keep the Energy Current running
|
||
if(!GetIsSkillSuccessful(oManifester, SKILL_CONCENTRATION, (10 + GetTotalDamageDealt())))
|
||
{
|
||
// Set a marker that tells the HB to stop
|
||
SetLocalInt(oManifester, "PRC_Power_EnergyCurrent_ConcentrationBroken", TRUE);
|
||
}
|
||
}// end if - Manifester was the one hit in the triggering attack
|
||
}// end else - Running eventhook
|
||
}
|
||
|
||
void EnergyCurrentHB(struct manifestation manif, struct energy_adjustments enAdj,
|
||
object oMainTarget, int nDC, int nPen, int nNumberOfDice, int nSecondaryTargets, float fRange,
|
||
location lManifesterOld, int nBeatsRemaining, int bFirst)
|
||
{
|
||
// Check expiration
|
||
if(!GetLocalInt(manif.oManifester, "PRC_Power_EnergyCurrent_ConcentrationBroken") && // The manifester's concentration hasn't been broken due to damage
|
||
nBeatsRemaining-- > 0 && // The power's duration hasn't run out yet
|
||
!PRCGetDelayedSpellEffectsExpired(manif.nSpellID, manif.oManifester, manif.oManifester) && // Not dispelled
|
||
!GetBreakConcentrationCheck(manif.oManifester) // The manifester isn't doing anything that breaks their concentration
|
||
)
|
||
{
|
||
location lManifester = GetLocation(manif.oManifester);
|
||
// First, check if the primary target needs to be switched
|
||
if(!bFirst && // Don't reselect even if the original primary target succeeded at it's PR
|
||
(!GetIsObjectValid(oMainTarget) || // The creature no longer exists
|
||
GetCurrentHitPoints(oMainTarget) < 0 || // The creature's HP has gone under zero
|
||
GetDistanceBetween(manif.oManifester, oMainTarget) > fRange // The creature is out of the power's range
|
||
)
|
||
)
|
||
{
|
||
// Select a new main target
|
||
// NOTE: This intentionally ignores the My*ObjectInShape wrapper
|
||
object oTest = GetFirstObjectInShape(SHAPE_SPHERE, fRange, lManifester, TRUE, OBJECT_TYPE_CREATURE);
|
||
while(GetIsObjectValid(oTest))
|
||
{
|
||
// Target only hostiles, and only ones that the manifester can see
|
||
if(oTest != manif.oManifester &&
|
||
spellsIsTarget(oTest, SPELL_TARGET_SELECTIVEHOSTILE, manif.oManifester) &&
|
||
!GetIsDead(oTest) &&
|
||
GetObjectSeen(oTest, manif.oManifester)
|
||
)
|
||
{
|
||
AddToTargetList(oTest, manif.oManifester, INSERTION_BIAS_DISTANCE, FALSE);
|
||
}
|
||
|
||
// Get next potential target
|
||
oTest = GetNextObjectInShape(SHAPE_SPHERE, fRange, lManifester, TRUE, OBJECT_TYPE_CREATURE);
|
||
}// end while - Target selection loop
|
||
|
||
// Select the hostile creature closest to the manifester
|
||
oMainTarget = GetTargetListHead(manif.oManifester);
|
||
|
||
// Power resistance
|
||
if(!PRCMyResistPower(manif.oManifester, oMainTarget, nPen))
|
||
{
|
||
// Set the target to be invalid - No current this round
|
||
oMainTarget = OBJECT_INVALID;
|
||
}
|
||
|
||
// Nuke the secondary targets array so we are forced to fully recalculate it
|
||
array_delete(manif.oManifester, SECONDARY_TARGETS_ARRAY);
|
||
}// end if - Main target selection
|
||
|
||
// Make sure we have a valid primary target at this point
|
||
if(GetIsObjectValid(oMainTarget))
|
||
{
|
||
// Check secondary targets array existence
|
||
if(!array_exists(manif.oManifester, SECONDARY_TARGETS_ARRAY))
|
||
array_create(manif.oManifester, SECONDARY_TARGETS_ARRAY);
|
||
|
||
// If the array contains empty slots or slots with creatures that are now outside the range, reselect secondary targets
|
||
SecondaryTargetsCheck(manif.oManifester, oMainTarget, nSecondaryTargets, nPen);
|
||
|
||
// Run the actual damage dealing
|
||
DoEnergyCurrentDamage(manif, enAdj, oMainTarget, nDC, nPen, nNumberOfDice);
|
||
}// end if - The primary target is valid
|
||
|
||
// Schedule next HB
|
||
DelayCommand(6.0f, EnergyCurrentHB(manif, enAdj, oMainTarget, nDC, nPen, nNumberOfDice,
|
||
nSecondaryTargets, fRange, lManifester, nBeatsRemaining, FALSE
|
||
)
|
||
);
|
||
}
|
||
// Power expired for some reason, make sure VFX are gone
|
||
else
|
||
{
|
||
if(DEBUG) DoDebug("psi_pow_encurr: Power expired");
|
||
PRCRemoveSpellEffects(manif.nSpellID, manif.oManifester, manif.oManifester);
|
||
DeleteLocalInt(manif.oManifester, "PRC_Power_EnergyCurrent_ConcentrationBroken");
|
||
array_delete(manif.oManifester, SECONDARY_TARGETS_ARRAY);
|
||
}
|
||
}
|
||
|
||
void SecondaryTargetsCheck(object oManifester, object oMainTarget, int nSecondaryTargets, int nPen)
|
||
{
|
||
int i, nFirstEmpty = -1;
|
||
float fRange = FeetToMeters(15.0f);
|
||
object oTest;
|
||
// Loop over the secondary targets array
|
||
for(i = 0; i < nSecondaryTargets; i++)
|
||
{
|
||
// Check if each of the secondary targets still qualifies
|
||
oTest = array_get_object(oManifester, SECONDARY_TARGETS_ARRAY, i);
|
||
//DoDebug("SecondaryTargetsCheck(): Testing if needs replacement: " + DebugObject2Str(oTest));
|
||
if(!GetIsObjectValid(oTest) || // The creature no longer exists
|
||
GetCurrentHitPoints(oTest) < 0 || // The creature's HP has gone under zero
|
||
GetDistanceBetween(oTest, oMainTarget) > fRange // The creature is out of the power's range
|
||
)
|
||
{
|
||
/*DoDebug("SecondaryTargetsCheck(): Needs replacement\n"
|
||
+ "!GetIsObjectValid(oTest) = " + DebugBool2String(!GetIsObjectValid(oTest)) + "\n"
|
||
+ "GetCurrentHitPoints(oTest) < 0 = " + DebugBool2String(GetCurrentHitPoints(oTest) < 0) + "\n"
|
||
+ "GetDistanceBetween(oTest, oMainTarget) > fRange = " + DebugBool2String(GetDistanceBetween(oTest, oMainTarget) > fRange) + "\n"
|
||
);*/
|
||
// If one doesn't, clear the array entry and set the return value to indicate that secondary targets need to be reselected
|
||
array_set_object(oManifester, SECONDARY_TARGETS_ARRAY, i, OBJECT_INVALID);
|
||
|
||
// Store the first empty index, which indicates the need for reselection
|
||
if(nFirstEmpty == -1)
|
||
nFirstEmpty = i;
|
||
}
|
||
}
|
||
|
||
// If the secondary targets need reselection, do so
|
||
if(nFirstEmpty != -1)
|
||
{
|
||
// An array to store names of temporary variables in
|
||
if(array_exists(oManifester, "PRC_Power_EnCur_Temp"))
|
||
array_delete(oManifester, "PRC_Power_EnCur_Temp");
|
||
array_create(oManifester, "PRC_Power_EnCur_Temp");
|
||
|
||
string sName;
|
||
|
||
// Store the UIDs (memory address) of the current valid secondary targets into a hash table
|
||
for(i = 0; i < nSecondaryTargets; i++)
|
||
{
|
||
oTest = array_get_object(oManifester, SECONDARY_TARGETS_ARRAY, i);
|
||
if(oTest != OBJECT_INVALID)// Do not store OBJECT_INVALID, since it wouldn't get cleared after reselection
|
||
{
|
||
sName = "PRC_Power_EnCur_Target_" + ObjectToString(oTest);
|
||
SetLocalInt(oManifester, sName, TRUE);
|
||
array_set_string(oManifester, "PRC_Power_EnCur_Temp", array_get_size(oManifester, "PRC_Power_EnCur_Temp"), sName);
|
||
}
|
||
}
|
||
|
||
// Get creatures that are eligible for secondary targethood until all slots are filled
|
||
// or there are no more eligible targets left unselected
|
||
i = nFirstEmpty;
|
||
location lTarget = GetLocation(oMainTarget);
|
||
oTest = GetFirstObjectInShape(SHAPE_SPHERE, fRange, lTarget, TRUE, OBJECT_TYPE_CREATURE);
|
||
while(GetIsObjectValid(oTest) && i < nSecondaryTargets)
|
||
{
|
||
// Targeting limitations, yay
|
||
if(oTest != oManifester && // Not the manifester
|
||
oTest != oMainTarget && // Not the main target
|
||
!GetIsDead(oTest) && // Target is alive...
|
||
!GetLocalInt(oManifester, "PRC_Power_EnCur_Target_" + ObjectToString(oTest)) && // Not an existing secondary target
|
||
spellsIsTarget(oTest, SPELL_TARGET_SELECTIVEHOSTILE, oManifester) // And the target is hostile
|
||
)
|
||
{
|
||
// Power resistance
|
||
if(!PRCMyResistPower(oManifester, oTest, nPen))
|
||
{
|
||
// Set the target to be invalid - The slot will be left empty this round
|
||
oTest = OBJECT_INVALID;
|
||
}
|
||
|
||
// Store the target in the secondary target array
|
||
array_set_object(oManifester, SECONDARY_TARGETS_ARRAY, i, oTest);
|
||
|
||
// Find next empty slot
|
||
while(array_get_object(oManifester, SECONDARY_TARGETS_ARRAY, ++i) != OBJECT_INVALID)
|
||
;
|
||
}
|
||
|
||
// Get next potential target
|
||
oTest = GetNextObjectInShape(SHAPE_SPHERE, fRange, lTarget, TRUE, OBJECT_TYPE_CREATURE);
|
||
}// end while - Target selection loop
|
||
|
||
// Remove the UID locals
|
||
int nMax = nSecondaryTargets;//array_get_size(oManifester, "PRC_Power_EnCur_Temp");
|
||
for(i = 0; i < nMax; i++)
|
||
{
|
||
DeleteLocalInt(oManifester, array_get_string(oManifester, "PRC_Power_EnCur_Temp", i));
|
||
DeleteLocalInt(oManifester, "PRC_Power_EnCur_Target_"
|
||
+ ObjectToString(array_get_object(oManifester, SECONDARY_TARGETS_ARRAY, i))
|
||
);
|
||
}
|
||
|
||
array_delete(oManifester, "PRC_Power_EnCur_Temp");
|
||
}// end if - Reselect secondary targets
|
||
}
|
||
|
||
void DoEnergyCurrentDamage(struct manifestation manif, struct energy_adjustments enAdj,
|
||
object oMainTarget, int nDC, int nPen, int nNumberOfDice)
|
||
{
|
||
int nDieSize = 6;
|
||
int nDamage, nSecondaryDamage, i;
|
||
effect eVis = EffectVisualEffect(enAdj.nVFX1);
|
||
effect eDamage;
|
||
object oSecondaryTarget;
|
||
|
||
// Try to affect the main target
|
||
// Roll damage
|
||
nDamage = MetaPsionicsDamage(manif, nDieSize, nNumberOfDice, 0, enAdj.nBonusPerDie, TRUE, FALSE);
|
||
// Target-specific stuff
|
||
nDamage = GetTargetSpecificChangesToDamage(oMainTarget, manif.oManifester, nDamage, TRUE, TRUE);
|
||
|
||
// Do save
|
||
if(enAdj.nSaveType == SAVING_THROW_TYPE_COLD)
|
||
{
|
||
// Cold has a fort save for half
|
||
if(PRCMySavingThrow(SAVING_THROW_FORT, oMainTarget, nDC, enAdj.nSaveType))
|
||
{
|
||
if (GetHasMettle(oMainTarget, SAVING_THROW_FORT))
|
||
// This script does nothing if it has Mettle, bail
|
||
nDamage = 0;
|
||
nDamage /= 2;
|
||
}
|
||
}
|
||
else
|
||
// Adjust damage according to Reflex Save, Evasion or Improved Evasion
|
||
nDamage = PRCGetReflexAdjustedDamage(nDamage, oMainTarget, nDC, enAdj.nSaveType);
|
||
|
||
// Fire the ray
|
||
SPApplyEffectToObject(DURATION_TYPE_TEMPORARY, EffectBeam(enAdj.nVFX2, manif.oManifester, BODY_NODE_HAND, nDamage == 0), oMainTarget, 1.7f, FALSE);
|
||
|
||
// Let the main target's AI know it's being targeted with a hostile spell
|
||
PRCSignalSpellEvent(oMainTarget, TRUE, manif.nSpellID, manif.oManifester);
|
||
|
||
// Deal damage if the target didn't Evade it
|
||
if(nDamage > 0)
|
||
{
|
||
eDamage = EffectDamage(nDamage, enAdj.nDamageType);
|
||
SPApplyEffectToObject(DURATION_TYPE_INSTANT, eDamage, oMainTarget);
|
||
SPApplyEffectToObject(DURATION_TYPE_INSTANT, eVis, oMainTarget);
|
||
|
||
// Secondary targets take half the amount the primary took
|
||
nDamage /= 2;
|
||
|
||
// Deal with the secondary targets
|
||
for(i = 0; i < array_get_size(manif.oManifester, SECONDARY_TARGETS_ARRAY); i++)
|
||
{
|
||
// Get target to affect
|
||
oSecondaryTarget = array_get_object(manif.oManifester, SECONDARY_TARGETS_ARRAY, i);
|
||
// Determine damage
|
||
nSecondaryDamage = nDamage;
|
||
// Target-specific stuff
|
||
nSecondaryDamage = GetTargetSpecificChangesToDamage(oSecondaryTarget, manif.oManifester, nSecondaryDamage, TRUE, TRUE);
|
||
|
||
// Do save
|
||
if(enAdj.nSaveType == SAVING_THROW_TYPE_COLD)
|
||
{
|
||
// Cold has a fort save for half
|
||
if(PRCMySavingThrow(SAVING_THROW_FORT, oSecondaryTarget, nDC, enAdj.nSaveType))
|
||
nSecondaryDamage /= 2;
|
||
}
|
||
else
|
||
// Adjust damage according to Reflex Save, Evasion or Improved Evasion
|
||
nSecondaryDamage = PRCGetReflexAdjustedDamage(nSecondaryDamage, oSecondaryTarget, nDC, enAdj.nSaveType);
|
||
|
||
// Fire the ray
|
||
SPApplyEffectToObject(DURATION_TYPE_TEMPORARY, EffectBeam(enAdj.nVFX2, oMainTarget, BODY_NODE_CHEST, nSecondaryDamage == 0), oSecondaryTarget, 1.7f, FALSE);
|
||
|
||
// Let the secondary target's AI know it's being targeted with a hostile spell
|
||
PRCSignalSpellEvent(oSecondaryTarget, TRUE, manif.nSpellID, manif.oManifester);
|
||
|
||
// Deal damage if the target didn't Evade it
|
||
if(nSecondaryDamage > 0)
|
||
{
|
||
eDamage = EffectDamage(nSecondaryDamage, enAdj.nDamageType);
|
||
SPApplyEffectToObject(DURATION_TYPE_INSTANT, eDamage, oSecondaryTarget);
|
||
SPApplyEffectToObject(DURATION_TYPE_INSTANT, eVis, oSecondaryTarget);
|
||
}// end if - There was still damage remaining to be dealt after adjustments
|
||
}// end for - Secondary targets
|
||
}// end if - There was still damage remaining to be dealt after adjustments
|
||
}
|