Updated PRC8 version. Hathran can now select an ethran as a cohort. Preliminary Circle Magic work done. Added Choke Hold, Pain Touch, Ki Shout, Great Ki Shout, Freezing the Lifeblood, Falling Star Strikea nd Unbalancing Strike feats. Warforged get Immunity Energy Drain, not Immunity: Ability Drain. Forsakers can use alchemical items. Added VectorToPerpendicular(). Added GetIsAlchemical(). Added GenerateRandomName(). Added _DoChokeHold(). Updated Shaman bonus feat list. Updated fighter bonus feat lists. Added Favored of the Companions to the Vow of Poverty bonus feat list. Ur-Priest can't enter RKV, BFZ or Thrall of Orcus.
178 lines
7.6 KiB
Plaintext
178 lines
7.6 KiB
Plaintext
//:://////////////////////////////////////////////
|
||
//:: ;-. ,-. ,-. ,-.
|
||
//:: | ) | ) / ( )
|
||
//:: |-' |-< | ;-:
|
||
//:: | | \ \ ( )
|
||
//:: ' ' ' `-' `-'
|
||
//:://////////////////////////////////////////////
|
||
//::
|
||
/*
|
||
Circle Leader
|
||
Type of Feat: Class Specific
|
||
Prerequisite: Red Wizard level 5 or Hathran 5.
|
||
Specifics: Allows caster to initiate Circle
|
||
Magic. Two to four participants can sacrifice
|
||
spells to augment the Circle Leader's spell
|
||
casting abilities for one rest cycle.
|
||
Use: Activate Feat.
|
||
|
||
*/
|
||
//::
|
||
//:://////////////////////////////////////////////
|
||
//:: Script: prc_circle_lead.nss
|
||
//:: Author: Jaysyn
|
||
//:: Created: 2026-02-10 12:19:50
|
||
//:://////////////////////////////////////////////
|
||
#include "prc_inc_spells"
|
||
#include "prc_inc_burn"
|
||
#include "x2_inc_spellhook"
|
||
|
||
// Helper to apply delayed visual transform
|
||
void DelayedApplyTransform(object oTarget)
|
||
{
|
||
if (DEBUG) DoDebug("Starting to float!");
|
||
SetObjectVisualTransform(oTarget, OBJECT_VISUAL_TRANSFORM_TRANSLATE_Z, 1.0f,
|
||
OBJECT_VISUAL_TRANSFORM_LERP_SMOOTHERSTEP, 3.0f);
|
||
}
|
||
|
||
// Helper to clean up all circle effects and events
|
||
void CleanupCircle(object oLeader)
|
||
{
|
||
if (DEBUG) DoDebug("Cleaning up circle for " + GetName(oLeader));
|
||
// Clear animation
|
||
if (GetLocalInt(oLeader, "CircleMagicAnimating"))
|
||
{
|
||
AssignCommand(oLeader, ClearAllActions());
|
||
DeleteLocalInt(oLeader, "CircleMagicAnimating");
|
||
}
|
||
// Clear VFX by tag
|
||
effect eVFX = GetFirstEffect(oLeader);
|
||
while (GetIsEffectValid(eVFX))
|
||
{
|
||
if (GetEffectTag(eVFX) == "CircleMagicVFX")
|
||
DelayCommand(2.5f, RemoveEffect(oLeader, eVFX));
|
||
eVFX = GetNextEffect(oLeader);
|
||
}
|
||
// Reset floating transform with lerp
|
||
if (GetLocalInt(oLeader, "CircleMagicFloating"))
|
||
{
|
||
SetObjectVisualTransform(oLeader, OBJECT_VISUAL_TRANSFORM_TRANSLATE_Z, 0.0f,
|
||
OBJECT_VISUAL_TRANSFORM_LERP_SMOOTHERSTEP, 3.0f);
|
||
DeleteLocalInt(oLeader, "CircleMagicFloating");
|
||
}
|
||
// Clear state variables
|
||
DeleteLocalInt(oLeader, "CircleMagicActive");
|
||
DeleteLocalInt(oLeader, "CircleMagicTotal");
|
||
DeleteLocalString(oLeader, "CircleMagicClass");
|
||
DeleteLocalInt(oLeader, "CircleMagicMaxParticipants");
|
||
DeleteLocalInt(oLeader, "CircleMagicParticipants");
|
||
DeleteLocalLocation(oLeader, "CircleMagicStartLoc");
|
||
// Remove events
|
||
if (DEBUG) DoDebug("Removing HB and OnDamaged events for " + GetName(oLeader));
|
||
RemoveEventScript(oLeader, EVENT_ONHEARTBEAT, "prc_circle_lead");
|
||
RemoveEventScript(oLeader, EVENT_VIRTUAL_ONDAMAGED, "prc_od_conc");
|
||
}
|
||
|
||
void main()
|
||
{
|
||
if (DEBUG) DoDebug("prc_circle_lead script executed for " + GetName(OBJECT_SELF));
|
||
|
||
int nEvent = GetRunningEvent();
|
||
object oLeader = OBJECT_SELF;
|
||
|
||
// Initial feat activation
|
||
if (nEvent == FALSE)
|
||
{
|
||
if (DEBUG) DoDebug("Circle Leader feat activation running");
|
||
// Toggle off if already active
|
||
if (GetLocalInt(oLeader, "CircleMagicActive"))
|
||
{
|
||
FloatingTextStringOnCreature("You stop leading the circle.", oLeader);
|
||
CleanupCircle(oLeader);
|
||
return;
|
||
}
|
||
// Determine max participants and class tag
|
||
int bIsGreat = GetHasFeat(FEAT_GREAT_CIRCLE_LEADER_THAYAN, oLeader) ||
|
||
GetHasFeat(FEAT_GREAT_CIRCLE_LEADER_RASHEMAN, oLeader);
|
||
int nMaxParticipants = bIsGreat ? 9 : 4;
|
||
string sClassTag;
|
||
if (GetHasFeat(FEAT_CIRCLE_LEADER_THAYAN, oLeader) ||
|
||
GetHasFeat(FEAT_GREAT_CIRCLE_LEADER_THAYAN, oLeader))
|
||
sClassTag = "RED_WIZARD";
|
||
else if (GetHasFeat(FEAT_CIRCLE_LEADER_RASHEMAN, oLeader) ||
|
||
GetHasFeat(FEAT_GREAT_CIRCLE_LEADER_RASHEMAN, oLeader))
|
||
sClassTag = "HATHRAN";
|
||
// Class gating for Great Circle Leader variants
|
||
if ((sClassTag == "RED_WIZARD" && GetLevelByClass(CLASS_TYPE_RED_WIZARD, oLeader) == 0) ||
|
||
(sClassTag == "HATHRAN" && GetLevelByClass(CLASS_TYPE_HATHRAN, oLeader) == 0))
|
||
{
|
||
FloatingTextStringOnCreature("You do not qualify to lead this circle.", oLeader);
|
||
return;
|
||
}
|
||
// Initialize circle state
|
||
SetLocalInt(oLeader, "CircleMagicActive", TRUE);
|
||
SetLocalInt(oLeader, "CircleMagicTotal", 0);
|
||
SetLocalInt(oLeader, "CircleMagicParticipants", 0);
|
||
SetLocalInt(oLeader, "CircleMagicMaxParticipants", nMaxParticipants);
|
||
SetLocalString(oLeader, "CircleMagicClass", sClassTag);
|
||
SetLocalLocation(oLeader, "CircleMagicStartLoc", GetLocation(oLeader));
|
||
// Start animation and mark
|
||
AssignCommand(oLeader, ActionPlayAnimation(ANIMATION_LOOPING_MEDITATE, 1.0, HoursToSeconds(24)));
|
||
SetLocalInt(oLeader, "CircleMagicAnimating", TRUE);
|
||
// Apply VFX (tagged for removal)
|
||
effect eVFX1 = EffectVisualEffect(VFX_DUR_GLOW_PURPLE);
|
||
effect eVFX2 = EffectVisualEffect(VFX_DUR_BLUESHIELDPROTECT);
|
||
effect eVFX3 = EffectVisualEffect(PSI_DUR_BURST);
|
||
// Dummy valid effect so the link isn<73>t VFX-only
|
||
effect eDummy = EffectCutsceneGhost();
|
||
effect eLink = EffectLinkEffects(eDummy, eVFX1);
|
||
eLink = EffectLinkEffects(eLink, eVFX2);
|
||
eLink = EffectLinkEffects(eLink, eVFX3);
|
||
eLink = TagEffect(eLink, "CircleMagicVFX");
|
||
ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eLink, oLeader, HoursToSeconds(24));
|
||
// Apply floating transform after a short delay
|
||
DelayCommand(0.1f, DelayedApplyTransform(oLeader));
|
||
SetLocalInt(oLeader, "CircleMagicFloating", TRUE);
|
||
// Register heartbeat and OnDamaged for concentration
|
||
AddEventScript(oLeader, EVENT_ONHEARTBEAT, "prc_circle_lead", TRUE, FALSE);
|
||
AddEventScript(oLeader, EVENT_VIRTUAL_ONDAMAGED, "prc_od_conc", TRUE, FALSE);
|
||
// Command eligible henchmen to use Circle Magic feat
|
||
int i = 1;
|
||
object oAssoc = GetAssociate(ASSOCIATE_TYPE_HENCHMAN, oLeader, i);
|
||
while (GetIsObjectValid(oAssoc))
|
||
{
|
||
if (GetHasFeat(FEAT_CIRCLE_MAGIC, oAssoc))
|
||
{
|
||
if ((sClassTag == "RED_WIZARD" && GetLevelByClass(CLASS_TYPE_RED_WIZARD, oAssoc) > 0) ||
|
||
(sClassTag == "HATHRAN" && GetLevelByClass(CLASS_TYPE_HATHRAN, oAssoc) > 0))
|
||
{
|
||
AssignCommand(oAssoc, ActionUseFeat(FEAT_CIRCLE_MAGIC, oLeader));
|
||
}
|
||
}
|
||
i++;
|
||
oAssoc = GetAssociate(ASSOCIATE_TYPE_HENCHMAN, oLeader, i);
|
||
}
|
||
FloatingTextStringOnCreature("Circle opened (" + IntToString(nMaxParticipants) + " participants max).", oLeader);
|
||
}
|
||
else if (nEvent == EVENT_ONHEARTBEAT)
|
||
{
|
||
if (DEBUG) DoDebug("Circle Leader HB running");
|
||
// Concentration break check
|
||
if (X2GetBreakConcentrationCondition(oLeader))
|
||
{
|
||
FloatingTextStringOnCreature("Your concentration is broken; the circle collapses.", oLeader);
|
||
CleanupCircle(oLeader);
|
||
return;
|
||
}
|
||
// Movement check
|
||
location lStart = GetLocalLocation(oLeader, "CircleMagicStartLoc");
|
||
float fDist = GetDistanceBetweenLocations(lStart, GetLocation(oLeader));
|
||
if (DEBUG) DoDebug("Distance from start: " + FloatToString(fDist));
|
||
if (fDist > 2.0f)
|
||
{
|
||
FloatingTextStringOnCreature("You moved too far; the circle collapses.", oLeader);
|
||
CleanupCircle(oLeader);
|
||
return;
|
||
}
|
||
}
|
||
} |