forked from Jaysyn/PRC8
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
|
||
}
|