forked from Jaysyn/PRC8
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.
318 lines
14 KiB
Plaintext
318 lines
14 KiB
Plaintext
#include "prc_inc_combat"
|
|
#include "prc_inc_sneak"
|
|
|
|
int SkirmishDamage(object oPC, object oTarget, int nClass)
|
|
{
|
|
int nDamage = 0;
|
|
float fRange = 30.0; // default range is 30 feat
|
|
if (GetHasFeat(FEAT_CROSSBOW_SNIPER, oPC) &&
|
|
((GetBaseItemType(GetItemInSlot(INVENTORY_SLOT_RIGHTHAND, oPC)) == BASE_ITEM_LIGHTCROSSBOW && GetHasFeat(FEAT_WEAPON_FOCUS_LIGHT_CROSSBOW, oPC)) ||
|
|
(GetBaseItemType(GetItemInSlot(INVENTORY_SLOT_RIGHTHAND, oPC)) == BASE_ITEM_HEAVYCROSSBOW && GetHasFeat(FEAT_WEAPON_FOCUS_HEAVY_CROSSBOW, oPC))))
|
|
fRange = 60.0; // Crossbow Sniper doubles the range
|
|
|
|
// Only works on non-crit immune and they're within range
|
|
if (!GetIsImmune(oTarget, IMMUNITY_TYPE_CRITICAL_HIT) && FeetToMeters(fRange) >= GetDistanceBetween(oPC, oTarget))
|
|
{
|
|
// Swift Amubusher
|
|
if (GetHasFeat(FEAT_SWIFT_AMBUSHER, oPC))
|
|
nClass += GetLevelByClass(CLASS_TYPE_ROGUE, oPC);
|
|
|
|
// Increased Dice every 4 levels (1, 5, 9 and so on)
|
|
int nDice = (nClass + 3) / 4;
|
|
|
|
// Improved Skirmish
|
|
if (GetLocalInt(oPC, "ScoutSkirmish") == 20) nDice += 2;
|
|
|
|
//Dragon Devotee and Hand of the Winged Masters damage bonus feats
|
|
int nBonusFeatDice = 0;
|
|
int nCount;
|
|
for(nCount = FEAT_SPECIAL_SKIRMISH_5D6; nCount >= FEAT_SPECIAL_SKIRMISH_1D6; nCount--)
|
|
{
|
|
if (GetHasFeat(nCount,oPC))
|
|
{
|
|
nBonusFeatDice = nCount - FEAT_SPECIAL_SKIRMISH_1D6 + 1;
|
|
if (DEBUG) DoDebug("prc_scout: Bonus Skirmish Dice: " + IntToString(nBonusFeatDice));
|
|
break;
|
|
}
|
|
}
|
|
nDice += nBonusFeatDice;
|
|
|
|
nDamage = d6(nDice);
|
|
|
|
if(GetHasFeat(FEAT_DRAGONFIRE_STRIKE, oPC) && GetLocalInt(oPC, "DragonFireOn"))
|
|
nDamage += d6();
|
|
if(GetHasFeat(FEAT_IMP_DRAGONFIRE_STRIKE, oPC) && GetLocalInt(oPC, "DragonFireOn"))
|
|
nDamage += nDice;
|
|
nDamage += (GetEssentiaInvestedFeat(oPC, FEAT_INDIGO_STRIKE)*2);
|
|
}
|
|
if (nDamage) FloatingTextStringOnCreature("Skirmished for "+IntToString(nDamage)+" damage", oPC, FALSE);
|
|
return nDamage;
|
|
}
|
|
|
|
// This is only called if you have Skirmished successfully
|
|
void SkirmishAC(object oPC, int nClass)
|
|
{
|
|
// Swift Amubusher
|
|
if (GetHasFeat(FEAT_SWIFT_AMBUSHER, oPC))
|
|
nClass += GetLevelByClass(CLASS_TYPE_ROGUE, oPC);
|
|
// Increased Dice every 4 levels (3, 7, 11 and so on)
|
|
int nDice = (nClass + 1) / 4;
|
|
// Improved Skirmish
|
|
if (GetLocalInt(oPC, "ScoutSkirmish") == 20) nDice += 2;
|
|
effect eAC = ExtraordinaryEffect(EffectACIncrease(nDice));
|
|
ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eAC, oPC, 6.0);
|
|
}
|
|
|
|
void BattleFortitude(object oPC, int nClass)
|
|
{
|
|
// Increased Fort every 9 levels (2, 11, 20 and so on)
|
|
int nFort = (nClass + 7) / 9;
|
|
//check Battle Fort lock to make sure it doesn't stack inappropriately
|
|
if(GetLocalInt(oPC, "BattleFortitude") >= nFort) return;
|
|
effect eFort = ExtraordinaryEffect(EffectSavingThrowIncrease(SAVING_THROW_FORT, nFort));
|
|
ApplyEffectToObject(DURATION_TYPE_PERMANENT, eFort, oPC);
|
|
|
|
//Set Battle Fortitude Lock
|
|
SetLocalInt(oPC, "BattleFortitude", nFort);
|
|
}
|
|
|
|
// Permanent Freedom of movement spell
|
|
void FreeMovement(object oPC, int nClass)
|
|
{
|
|
if (nClass >= 18)
|
|
{
|
|
effect eParal = EffectImmunity(IMMUNITY_TYPE_PARALYSIS);
|
|
effect eEntangle = EffectImmunity(IMMUNITY_TYPE_ENTANGLE);
|
|
effect eSlow = EffectImmunity(IMMUNITY_TYPE_SLOW);
|
|
effect eMove = EffectImmunity(IMMUNITY_TYPE_MOVEMENT_SPEED_DECREASE);
|
|
|
|
//Link effects
|
|
effect eLink = EffectLinkEffects(eParal, eEntangle);
|
|
eLink = EffectLinkEffects(eLink, eSlow);
|
|
eLink = EffectLinkEffects(eLink, eMove);
|
|
|
|
ApplyEffectToObject(DURATION_TYPE_PERMANENT, ExtraordinaryEffect(eLink), oPC);
|
|
}
|
|
}
|
|
|
|
void FastMovement(object oPC, int nClass)
|
|
{
|
|
// Speed bonus. +20 feet at level 11, +10 feet at level 3
|
|
// In NWN this is +66% and +33% (Assume 30 feet as base speed)
|
|
if (nClass >= 11) ApplyEffectToObject(DURATION_TYPE_PERMANENT, ExtraordinaryEffect(EffectMovementSpeedIncrease(66)), oPC);
|
|
else if (nClass >= 3) ApplyEffectToObject(DURATION_TYPE_PERMANENT, ExtraordinaryEffect(EffectMovementSpeedIncrease(33)), oPC);
|
|
}
|
|
|
|
void BlindSight(object oPC, int nClass)
|
|
{
|
|
// Blindsense -> Darkvis
|
|
// Blindsight -> True Seeing
|
|
if (nClass >= 20) ApplyEffectToObject(DURATION_TYPE_PERMANENT, ExtraordinaryEffect(EffectTrueSeeing()), oPC);
|
|
else if (nClass >= 10) ApplyEffectToObject(DURATION_TYPE_PERMANENT, ExtraordinaryEffect(EffectUltravision()), oPC);
|
|
}
|
|
|
|
void main()
|
|
{
|
|
int nEvent = GetRunningEvent();
|
|
if(DEBUG) DoDebug("prc_scout running, event: " + IntToString(nEvent));
|
|
|
|
// Get the PC. This is event-dependent
|
|
object oPC;
|
|
switch(nEvent)
|
|
{
|
|
case EVENT_ITEM_ONHIT: oPC = OBJECT_SELF; break;
|
|
case EVENT_ONPLAYEREQUIPITEM: oPC = GetItemLastEquippedBy(); break;
|
|
case EVENT_ONPLAYERUNEQUIPITEM: oPC = GetItemLastUnequippedBy(); break;
|
|
case EVENT_ONHEARTBEAT: oPC = OBJECT_SELF; break;
|
|
|
|
default:
|
|
oPC = OBJECT_SELF;
|
|
}
|
|
|
|
object oItem;
|
|
object oArmour = GetItemInSlot(INVENTORY_SLOT_CHEST, oPC);
|
|
object oAmmo;
|
|
int nClass = GetLevelByClass(CLASS_TYPE_SCOUT, oPC);
|
|
int nArmour = GetBaseAC(oArmour);
|
|
|
|
|
|
// We aren't being called from any event, instead from EvalPRCFeats
|
|
if(nEvent == FALSE)
|
|
{
|
|
// Light armour only
|
|
if (3 >= nArmour)
|
|
{
|
|
FastMovement(oPC, nClass);
|
|
BattleFortitude(oPC, nClass);
|
|
FreeMovement(oPC, nClass);
|
|
}
|
|
// Doesn't depend on light armour
|
|
BlindSight(oPC, nClass);
|
|
|
|
// Hook in the events, needed from level 1 for Skirmish
|
|
if(DEBUG) DoDebug("prc_scout: Adding eventhooks");
|
|
AddEventScript(oPC, EVENT_ONPLAYEREQUIPITEM, "prc_scout", TRUE, FALSE);
|
|
AddEventScript(oPC, EVENT_ONPLAYERUNEQUIPITEM, "prc_scout", TRUE, FALSE);
|
|
AddEventScript(oPC, EVENT_ONHEARTBEAT, "prc_scout", TRUE, FALSE);
|
|
}
|
|
// We're being called from the OnHit eventhook, so deal the damage
|
|
// Light armour only
|
|
else if(nEvent == EVENT_ITEM_ONHIT && 3 >= nArmour)
|
|
{
|
|
oItem = GetSpellCastItem();
|
|
object oTarget = PRCGetSpellTargetObject();
|
|
if(DEBUG) DoDebug("prc_scout: OnHit:\n"
|
|
+ "oPC = " + DebugObject2Str(oPC) + "\n"
|
|
+ "oItem = " + DebugObject2Str(oItem) + "\n"
|
|
+ "oTarget = " + DebugObject2Str(oTarget) + "\n"
|
|
);
|
|
|
|
// Only applies to weapons, and the Scout must have moved this round.
|
|
if(GetLocalInt(oPC, "ScoutSkirmish") && ( GetWeaponRanged(oItem) || IPGetIsProjectile(oItem) || IPGetIsMeleeWeapon(oItem)) ) // oItem is the ammo in the case of bows and crossbows
|
|
{
|
|
// Calculate Skirmish damage and apply
|
|
|
|
int nDamageType = GetWeaponDamageType(oItem);
|
|
|
|
//Dragonfire Strike Handling
|
|
if(GetHasFeat(FEAT_DRAGONFIRE_STRIKE, oPC) && GetLocalInt(oPC, "DragonFireOn"))
|
|
nDamageType = GetDragonfireDamageType(oPC);
|
|
|
|
else if(IPGetIsProjectile(oItem) && (GetBaseItemType(oItem) == BASE_ITEM_BULLET))
|
|
nDamageType = DAMAGE_TYPE_BLUDGEONING;
|
|
|
|
else if (IPGetIsProjectile(oItem))
|
|
nDamageType = DAMAGE_TYPE_PIERCING;
|
|
|
|
effect eDam = EffectDamage(SkirmishDamage(oPC, oTarget, nClass), nDamageType);
|
|
ApplyEffectToObject(DURATION_TYPE_INSTANT, eDam, oTarget);
|
|
}// end if - Item is a melee weapon
|
|
}// end if - Running OnHit event
|
|
// We are called from the OnPlayerEquipItem eventhook. Add OnHitCast: Unique Power to oPC's weapon
|
|
else if(nEvent == EVENT_ONPLAYEREQUIPITEM)
|
|
{
|
|
oPC = GetItemLastEquippedBy();
|
|
oItem = GetItemLastEquipped();
|
|
if(DEBUG) DoDebug("prc_scout - OnEquip\n"
|
|
+ "oPC = " + DebugObject2Str(oPC) + "\n"
|
|
+ "oItem = " + DebugObject2Str(oItem) + "\n"
|
|
);
|
|
|
|
// Only applies to weapons
|
|
// IPGetIsMeleeWeapon is bugged and returns true on items it should not
|
|
if(oItem == GetItemInSlot(INVENTORY_SLOT_RIGHTHAND, oPC) ||
|
|
(oItem == GetItemInSlot(INVENTORY_SLOT_LEFTHAND, oPC) && !GetIsShield(oItem)) ||
|
|
GetWeaponRanged(oItem))
|
|
{
|
|
|
|
if (DEBUG)
|
|
{
|
|
if (IPGetIsMeleeWeapon(oItem)) DoDebug("IPGetIsMeleeWeapon: TRUE");
|
|
if (GetWeaponRanged(oItem)) DoDebug("GetWeaponRanged: TRUE");
|
|
}
|
|
|
|
if (GetWeaponRanged(oItem))
|
|
{
|
|
// Add eventhook to the ranged weapons like darts
|
|
IPSafeAddItemProperty(oItem, ItemPropertyOnHitCastSpell(IP_CONST_ONHIT_CASTSPELL_ONHIT_UNIQUEPOWER, 1), 99999.0, X2_IP_ADDPROP_POLICY_KEEP_EXISTING, FALSE, FALSE);
|
|
AddEventScript(oItem, EVENT_ITEM_ONHIT, "prc_scout", TRUE, FALSE);
|
|
|
|
oAmmo = GetItemInSlot(INVENTORY_SLOT_BOLTS, oPC);
|
|
IPSafeAddItemProperty(oAmmo, ItemPropertyOnHitCastSpell(IP_CONST_ONHIT_CASTSPELL_ONHIT_UNIQUEPOWER, 1), 99999.0, X2_IP_ADDPROP_POLICY_KEEP_EXISTING, FALSE, FALSE);
|
|
AddEventScript(oAmmo, EVENT_ITEM_ONHIT, "prc_scout", TRUE, FALSE);
|
|
|
|
oAmmo = GetItemInSlot(INVENTORY_SLOT_BULLETS, oPC);
|
|
IPSafeAddItemProperty(oAmmo, ItemPropertyOnHitCastSpell(IP_CONST_ONHIT_CASTSPELL_ONHIT_UNIQUEPOWER, 1), 99999.0, X2_IP_ADDPROP_POLICY_KEEP_EXISTING, FALSE, FALSE);
|
|
AddEventScript(oAmmo, EVENT_ITEM_ONHIT, "prc_scout", TRUE, FALSE);
|
|
|
|
oAmmo = GetItemInSlot(INVENTORY_SLOT_ARROWS, oPC);
|
|
IPSafeAddItemProperty(oAmmo, ItemPropertyOnHitCastSpell(IP_CONST_ONHIT_CASTSPELL_ONHIT_UNIQUEPOWER, 1), 99999.0, X2_IP_ADDPROP_POLICY_KEEP_EXISTING, FALSE, FALSE);
|
|
AddEventScript(oAmmo, EVENT_ITEM_ONHIT, "prc_scout", TRUE, FALSE);
|
|
}
|
|
else if (IPGetIsMeleeWeapon(oItem))
|
|
{
|
|
// Add eventhook to the item
|
|
AddEventScript(oItem, EVENT_ITEM_ONHIT, "prc_scout", TRUE, FALSE);
|
|
|
|
// Add the OnHitCastSpell: Unique needed to trigger the event
|
|
// Makes sure to get ammo if its a ranged weapon
|
|
IPSafeAddItemProperty(oItem, ItemPropertyOnHitCastSpell(IP_CONST_ONHIT_CASTSPELL_ONHIT_UNIQUEPOWER, 1), 99999.0, X2_IP_ADDPROP_POLICY_KEEP_EXISTING, FALSE, FALSE);
|
|
}
|
|
|
|
}
|
|
}
|
|
// We are called from the OnPlayerUnEquipItem eventhook. Remove OnHitCast: Unique Power from oPC's weapon
|
|
else if(nEvent == EVENT_ONPLAYERUNEQUIPITEM)
|
|
{
|
|
oPC = GetItemLastUnequippedBy();
|
|
oItem = GetItemLastUnequipped();
|
|
if(DEBUG) DoDebug("prc_scout - OnUnEquip\n"
|
|
+ "oPC = " + DebugObject2Str(oPC) + "\n"
|
|
+ "oItem = " + DebugObject2Str(oItem) + "\n"
|
|
);
|
|
|
|
// Only applies to weapons
|
|
if(IPGetIsMeleeWeapon(oItem) || GetWeaponRanged(oItem))
|
|
{
|
|
// Add eventhook to the item
|
|
RemoveEventScript(oItem, EVENT_ITEM_ONHIT, "prc_scout", TRUE, FALSE);
|
|
|
|
// Remove the temporary OnHitCastSpell: Unique
|
|
// Makes sure to get ammo if its a ranged weapon
|
|
RemoveSpecificProperty(oItem, ITEM_PROPERTY_ONHITCASTSPELL, IP_CONST_ONHIT_CASTSPELL_ONHIT_UNIQUEPOWER, 0, 1, "", -1, DURATION_TYPE_TEMPORARY);
|
|
|
|
oAmmo = GetItemInSlot(INVENTORY_SLOT_BOLTS, oPC);
|
|
RemoveSpecificProperty(oAmmo, ITEM_PROPERTY_ONHITCASTSPELL, IP_CONST_ONHIT_CASTSPELL_ONHIT_UNIQUEPOWER, 0, 1, "", -1, DURATION_TYPE_TEMPORARY);
|
|
|
|
oAmmo = GetItemInSlot(INVENTORY_SLOT_BULLETS, oPC);
|
|
RemoveSpecificProperty(oAmmo, ITEM_PROPERTY_ONHITCASTSPELL, IP_CONST_ONHIT_CASTSPELL_ONHIT_UNIQUEPOWER, 0, 1, "", -1, DURATION_TYPE_TEMPORARY);
|
|
|
|
oAmmo = GetItemInSlot(INVENTORY_SLOT_ARROWS, oPC);
|
|
RemoveSpecificProperty(oAmmo, ITEM_PROPERTY_ONHITCASTSPELL, IP_CONST_ONHIT_CASTSPELL_ONHIT_UNIQUEPOWER, 0, 1, "", -1, DURATION_TYPE_TEMPORARY);
|
|
}
|
|
}
|
|
// This is used to determine the Scout's AC bonus for skirmishing
|
|
// Light armour only
|
|
else if(nEvent == EVENT_ONHEARTBEAT && 3 >= nArmour)
|
|
{
|
|
// Check to see if the WP is valid
|
|
string sWPTag = "PRC_ScoutWP_" + GetName(oPC);
|
|
object oTestWP = GetWaypointByTag(sWPTag);
|
|
if (!GetIsObjectValid(oTestWP))
|
|
{
|
|
// Create waypoint for the movement
|
|
CreateObject(OBJECT_TYPE_WAYPOINT, "nw_waypoint001", GetLocation(oPC), FALSE, sWPTag);
|
|
if(DEBUG) DoDebug("prc_scout: Scout WP for " + DebugObject2Str(oPC) + " didn't exist, creating. Tag: " + sWPTag);
|
|
}
|
|
else // We have a test waypoint, now to check the distance
|
|
{
|
|
// Distance moved in the last round
|
|
float fDist = GetDistanceBetween(oPC, oTestWP);
|
|
// Distance needed to move
|
|
float fCheck = FeetToMeters(10.0);
|
|
|
|
// Now clean up the WP and create a new one for next round's check
|
|
DestroyObject(oTestWP);
|
|
CreateObject(OBJECT_TYPE_WAYPOINT, "nw_waypoint001", GetLocation(oPC), FALSE, sWPTag);
|
|
|
|
if(DEBUG) DoDebug("prc_scout: Moved enough: " + DebugBool2String(fDist >= fCheck));
|
|
|
|
// Moved the distance
|
|
if (fDist >= fCheck)
|
|
{
|
|
// We have skirmished
|
|
SetLocalInt(oPC, "ScoutSkirmish", TRUE);
|
|
|
|
// We have improved skirmished
|
|
if (GetHasFeat(FEAT_IMPROVED_SKIMISH, oPC) && fDist >= FeetToMeters(20.0))
|
|
SetLocalInt(oPC, "ScoutSkirmish", 20);
|
|
|
|
// Only lasts for a round
|
|
DelayCommand(6.0, DeleteLocalInt(oPC, "ScoutSkirmish"));
|
|
// Give the AC bonus
|
|
SkirmishAC(oPC, nClass);
|
|
}
|
|
}
|
|
}
|
|
}
|