generated from Jaysyn/ModuleTemplate
376 lines
17 KiB
Plaintext
376 lines
17 KiB
Plaintext
/*
|
|
|
|
Tz-Auber's Perfect Troll Regeneration Script 1.3
|
|
By: Tz-Auber
|
|
Last Modified: 9/23/03
|
|
Developed for: 1.30/SoU
|
|
|
|
Description
|
|
-----------
|
|
This small script properly implements 3rd Edition subdual damage rules as it relates
|
|
to regeneration. Some of the highlighted features are:
|
|
- By using the SetImmortal function, feedback for the Troll's health is properly
|
|
displayed.
|
|
- Only uses OnHeartbeat and OnDamaged events. No messing with OnDeath and XP
|
|
calculations.
|
|
- Allows for quickly killing a downed Troll through a specified item (by default,
|
|
a torch item type equipped in the off-hand) or through SoU grenade type
|
|
weapons (Acid Flask and Alchemist Fire).
|
|
- A knockdown effect occurs when actual damage exceeds subdual damage, and it
|
|
also simulates attacking a prone opponent.
|
|
- Allows for automatic 3E Coup de Grace attempts if the Troll is down and the equipped
|
|
weapon is capable of doing permanent damage.
|
|
|
|
Modifications Required
|
|
----------------------
|
|
- This script must be placed in a creature's OnUserDefined event.
|
|
- The OnSpawn script must have the following SetSpawnInConditions uncommented/added:
|
|
- NW_FLAG_HEARTBEAT_EVENT
|
|
- NW_FLAG_DAMAGED_EVENT
|
|
- NW_FLAG_ATTACK_EVENT
|
|
- NW_FLAG_SPELL_CAST_AT_EVENT
|
|
- The following line must be added in the OnSpawn script at the end
|
|
- SetImmortal(OBJECT_SELF,TRUE);
|
|
- Remove the default Troll's regeneration property by removing it's hide from the
|
|
creature inventory (or remove the regeneration property if you want to keep
|
|
other hide properties).
|
|
|
|
Credits
|
|
-------
|
|
I want to thank U'lias, as I used his code for a basis and starting point
|
|
(creation date 12/29/02). His code can be found by searching the vault under
|
|
"Ulias' 3eMM D&D Style Trolls v1.3"
|
|
|
|
I would also like to thank El Magnifico Uno, one of the pioneers of Troll
|
|
regeneration code, who also provided useful critique in the development of this
|
|
work.
|
|
|
|
Version History
|
|
---------------
|
|
1.3 (9/23/03) - Found out that my code has just been released into the wilds of
|
|
a PW and have received a lot of useful feedback. New features added: support
|
|
for 3E Coup de Grace attempts and the use of Alchemist Fire and Acid Flasks
|
|
to fry a Troll in addition to the torch.
|
|
|
|
1.2 (9/22/03) - Hmm.. this was supposed to come sooner, but here it is. Changed
|
|
from a resref dependency of the burning item, to a base item type dependency.
|
|
So any BASE_ITEM_TORCH equipped in the off-hand should work regardless of
|
|
tags and resrefs. Finally added debug message considerations. I'll save
|
|
the final spell modifications when HotU comes out, but a small placeholder
|
|
is there in the meantime.
|
|
|
|
1.1 (7/24/03) - My implementation of subdual rules were a little messed up. I'm now
|
|
considering damage in excess of the limit of SetImmortal function. Also
|
|
added visual effect support for acid damaging weapons and tweaked with the
|
|
fire visual effects a little.
|
|
|
|
1.0 (7/21/03) - Initial Release
|
|
|
|
*/
|
|
|
|
// Included for the RemoveSpecificEffect function
|
|
#include "nw_i0_spells"
|
|
|
|
// It will be assumed that there is only one item used for a full-round troll burning
|
|
const int BURNING_ITEM_TYPE = BASE_ITEM_TORCH;
|
|
|
|
// The amount of hitpoints (subdual damage) regenerated per round
|
|
const int REGENERATION_VALUE = 5;
|
|
|
|
// The time it takes (in full rounds) to burn a Troll
|
|
const int BURNING_TIME = 1;
|
|
|
|
// Switching debug messages on/off
|
|
const int DEBUG_OUTPUT = 0;
|
|
|
|
void BurnEffect()
|
|
{
|
|
CreateObject(OBJECT_TYPE_PLACEABLE, "plc_weathmark", GetLocation(OBJECT_SELF), TRUE);
|
|
}
|
|
void main()
|
|
{
|
|
int nUser = GetUserDefinedEventNumber();
|
|
|
|
if(nUser == 1001) // OnHeartbeat Event
|
|
{
|
|
// the Troll's cumulative Fire and Acid damage
|
|
int nPermanentDamage = GetLocalInt(OBJECT_SELF, "nPermanentDamage");;
|
|
// the Troll's original maximum hitpoints
|
|
int nOriginalHPs = GetMaxHitPoints(OBJECT_SELF);
|
|
// the Troll's current hitpoints
|
|
int nCurrentHPs = GetCurrentHitPoints(OBJECT_SELF);
|
|
// the Troll's excess damage
|
|
int nExcessDamage = GetLocalInt(OBJECT_SELF, "nExcessDamage");
|
|
|
|
// the maximum number of HPs that the Troll can possibly regenerate due
|
|
// to permanent fire or acid damage
|
|
int nMaxHPsPossible = nOriginalHPs - nPermanentDamage;
|
|
|
|
// Debug Stuff
|
|
if(DEBUG_OUTPUT)
|
|
{
|
|
SendMessageToPC(GetFirstPC(),"Current HPs = " + IntToString(nCurrentHPs));
|
|
SendMessageToPC(GetFirstPC(),"Subdual Damage = " + IntToString(nMaxHPsPossible - nCurrentHPs));
|
|
SendMessageToPC(GetFirstPC(),"Excess Damage = " + IntToString(nExcessDamage));
|
|
SendMessageToPC(GetFirstPC(),"Effective HPs = " + IntToString(nCurrentHPs - nExcessDamage));
|
|
SendMessageToPC(GetFirstPC(),"Max Possible HPs = " + IntToString(nMaxHPsPossible) + "/" + IntToString(nOriginalHPs));
|
|
SendMessageToPC(GetFirstPC(),"-----");
|
|
}
|
|
|
|
// the Troll may only regenerate if its current HPs are less than its
|
|
// maximum possible HPs left after fire and acid damage
|
|
if(nExcessDamage >= REGENERATION_VALUE)
|
|
{
|
|
nExcessDamage -= REGENERATION_VALUE;
|
|
SetLocalInt(OBJECT_SELF, "nExcessDamage", nExcessDamage);
|
|
}
|
|
else if(nExcessDamage > 0)
|
|
{
|
|
// regeneration effect (the excess difference)
|
|
effect eHeal = EffectHeal(REGENERATION_VALUE - nExcessDamage);
|
|
ApplyEffectToObject(DURATION_TYPE_INSTANT, eHeal, OBJECT_SELF);
|
|
|
|
nExcessDamage = 0;
|
|
SetLocalInt(OBJECT_SELF, "nExcessDamage", nExcessDamage);
|
|
}
|
|
else if (nCurrentHPs <= (nMaxHPsPossible - REGENERATION_VALUE))
|
|
{
|
|
// regeneration effect (5 HPs every round)
|
|
effect eHeal = EffectHeal(REGENERATION_VALUE);
|
|
ApplyEffectToObject(DURATION_TYPE_INSTANT, eHeal, OBJECT_SELF);
|
|
}
|
|
else if (nCurrentHPs <= nMaxHPsPossible)
|
|
{
|
|
// regeneration effect (the remainder of hitpoints)
|
|
effect eHeal = EffectHeal(nMaxHPsPossible - nCurrentHPs);
|
|
ApplyEffectToObject(DURATION_TYPE_INSTANT, eHeal, OBJECT_SELF);
|
|
}
|
|
|
|
// the Troll will now see if it can get up after it's latest beating
|
|
// provided that the Troll is grounded
|
|
effect eKnockdown = EffectKnockdown();
|
|
int bDowned = GetLocalInt(OBJECT_SELF,"bDowned");
|
|
// When current subdual damage is lower than current hit points
|
|
if (((nMaxHPsPossible - nCurrentHPs + nExcessDamage) < nMaxHPsPossible)&&(bDowned))
|
|
{
|
|
RemoveSpecificEffect(GetEffectType(eKnockdown),OBJECT_SELF);
|
|
SetLocalInt(OBJECT_SELF,"bDowned",0);
|
|
}
|
|
}
|
|
else if(nUser == 1006) // OnDamaged Event
|
|
{
|
|
object oDamager = GetLastDamager();
|
|
|
|
// the Troll's cumulative Permanent damage
|
|
int nPermanentDamage = GetLocalInt(OBJECT_SELF, "nPermanentDamage");
|
|
// the Troll's current Fire damage newly received
|
|
int nFireDamage = GetDamageDealtByType(DAMAGE_TYPE_FIRE);
|
|
// the Troll's current Acid damage newly received
|
|
int nAcidDamage = GetDamageDealtByType(DAMAGE_TYPE_ACID);
|
|
// the rest of the damage inflicted on the Troll
|
|
int nOtherDamage = GetTotalDamageDealt() - nFireDamage - nAcidDamage;
|
|
// the Troll's excess damage
|
|
int nExcessDamage = GetLocalInt(OBJECT_SELF, "nExcessDamage");
|
|
// the Troll's current hitpoints
|
|
int nCurrentHPs = GetCurrentHitPoints(OBJECT_SELF);
|
|
// the Troll's previous (newly inflicted HP value)
|
|
int nPrevHPs = GetLocalInt(OBJECT_SELF, "nPrevHPs");
|
|
// in case this is the first time the Troll is damaged
|
|
if(nPrevHPs == 0)
|
|
nPrevHPs = GetMaxHitPoints(OBJECT_SELF);
|
|
|
|
// if there is damage in excess of the previous hp value,
|
|
// excess damage will be updated
|
|
if(nOtherDamage > nPrevHPs)
|
|
nExcessDamage += (nOtherDamage - nPrevHPs);
|
|
|
|
// Now the excess damage and previous hit points will be updated
|
|
SetLocalInt(OBJECT_SELF,"nExcessDamage",nExcessDamage);
|
|
SetLocalInt(OBJECT_SELF,"nPrevHPs",nCurrentHPs);
|
|
|
|
// make sure Fire damage is not less than 0
|
|
if (nFireDamage < 0) nFireDamage = 0;
|
|
// make sure Acid damage is not less than 0
|
|
if (nAcidDamage < 0) nAcidDamage = 0;
|
|
|
|
// if the Troll suffered Fire damage
|
|
if (nFireDamage > 0)
|
|
{
|
|
// keep track of cumulative Fire damage
|
|
nPermanentDamage += nFireDamage;
|
|
// won't be counted as subdual damage
|
|
effect eHeal = EffectHeal(nFireDamage);
|
|
ApplyEffectToObject(DURATION_TYPE_INSTANT, eHeal, OBJECT_SELF);
|
|
}
|
|
// if the Troll suffered Acid damage
|
|
if (nAcidDamage > 0)
|
|
{
|
|
// keep track of cumulative Acid damage
|
|
nPermanentDamage += nAcidDamage;
|
|
// won't be counted as subdual damage
|
|
effect eHeal = EffectHeal(nAcidDamage);
|
|
ApplyEffectToObject(DURATION_TYPE_INSTANT, eHeal, OBJECT_SELF);
|
|
}
|
|
|
|
// Apply the Permanent Damage
|
|
SetLocalInt(OBJECT_SELF,"nPermanentDamage",nPermanentDamage);
|
|
|
|
// the Troll's original maximum hitpoints
|
|
int nOriginalHPs = GetMaxHitPoints(OBJECT_SELF);
|
|
// the maximum number of HPs that the Troll can possibly regenerate due
|
|
// to permanent fire or acid damage
|
|
int nMaxHPsPossible = nOriginalHPs - nPermanentDamage;
|
|
|
|
// This part will simulate an unconscience effect via knockdown
|
|
// If the troll's subdual damage exceeds it's current hitpoints ...
|
|
effect eKnockdown = EffectKnockdown();
|
|
int bDowned = GetLocalInt(OBJECT_SELF,"bDowned");
|
|
// When current subdual damage is higher than current hit points
|
|
if ((nMaxHPsPossible < (nMaxHPsPossible - nCurrentHPs + nExcessDamage))&&(!bDowned))
|
|
{
|
|
ApplyEffectToObject(DURATION_TYPE_PERMANENT,eKnockdown,OBJECT_SELF);
|
|
bDowned = 1;
|
|
SetLocalInt(OBJECT_SELF,"bDowned",bDowned);
|
|
}
|
|
|
|
// Now let's check and see if the troll has attained final death
|
|
// First we'll check whether someone is carefully burning the body
|
|
// (will take a full round to perform)
|
|
if ((GetBaseItemType(GetItemInSlot(INVENTORY_SLOT_LEFTHAND, oDamager)) == BURNING_ITEM_TYPE)
|
|
&& bDowned)
|
|
{
|
|
DelayCommand(0.2,SetCommandable(FALSE,oDamager));
|
|
AssignCommand(oDamager, ClearAllActions());
|
|
AssignCommand(oDamager, ActionPlayAnimation(ANIMATION_LOOPING_GET_LOW, 1.0, RoundsToSeconds(BURNING_TIME)));
|
|
AssignCommand(oDamager, ActionDoCommand(SetCommandable(TRUE,oDamager)));
|
|
effect eFlame = EffectVisualEffect(VFX_DUR_INFERNO_CHEST); //VFX_IMP_FLAME_M
|
|
DelayCommand(2.0, ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eFlame, OBJECT_SELF, 5.0));
|
|
DelayCommand(1.5, BurnEffect());
|
|
effect eDeath = EffectDeath();
|
|
SetImmortal(OBJECT_SELF,FALSE);
|
|
ApplyEffectToObject(DURATION_TYPE_INSTANT, eDeath, OBJECT_SELF);
|
|
}
|
|
// Now let's check and see if the damager is capable of performing a
|
|
// Coup de Grace attempt, and if so they will automatically perform one.
|
|
// The Troll's save will be Fortitude of DC 10 + Permanent Damage dealt.
|
|
else if (GetLocalInt(OBJECT_SELF,"bCoupdeGrace") && !GetLocalInt(OBJECT_SELF,"bGrenade")
|
|
&& (nFireDamage || nAcidDamage) && bDowned)
|
|
{
|
|
DeleteLocalInt(OBJECT_SELF,"bCoupdeGrace");
|
|
object eSelf = OBJECT_SELF;
|
|
location lSelf = GetLocation(eSelf);
|
|
|
|
DelayCommand(0.2,SetCommandable(FALSE,oDamager));
|
|
AssignCommand(oDamager, ClearAllActions());
|
|
AssignCommand(oDamager, ActionMoveToObject(eSelf,TRUE,0.1));
|
|
AssignCommand(oDamager, ActionPlayAnimation(ANIMATION_LOOPING_GET_LOW, 1.0, 4.5));
|
|
//AssignCommand(oDamager, ActionDoCommand(SetCommandable(TRUE,oDamager)));
|
|
|
|
if(!FortitudeSave(OBJECT_SELF,10 + nFireDamage + nAcidDamage,SAVING_THROW_TYPE_ALL,oDamager))
|
|
{
|
|
effect eBlood = EffectVisualEffect(VFX_COM_CHUNK_RED_SMALL);
|
|
|
|
AssignCommand(oDamager, ActionDoCommand(ApplyEffectAtLocation(DURATION_TYPE_INSTANT, eBlood, lSelf)));
|
|
AssignCommand(oDamager, ActionDoCommand(SetCommandable(TRUE,oDamager)));
|
|
|
|
FloatingTextStringOnCreature("Coup de Grace (success)",oDamager);
|
|
|
|
// Fire will be favored over acid in the event they are equal
|
|
if(nFireDamage >= nAcidDamage)
|
|
{
|
|
effect eFlame = EffectVisualEffect(VFX_DUR_INFERNO_CHEST); //VFX_IMP_FLAME_M
|
|
DelayCommand(2.0, ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eFlame, OBJECT_SELF, 3.0));
|
|
DelayCommand(1.5, BurnEffect());
|
|
//object oFlame = CreateObject(OBJECT_TYPE_PLACEABLE, "plc_flamemedium", GetLocation(OBJECT_SELF), TRUE);
|
|
//DestroyObject(oFlame, 3.25);
|
|
}
|
|
else
|
|
{
|
|
effect eAcid = EffectVisualEffect(VFX_FNF_GAS_EXPLOSION_ACID);
|
|
ApplyEffectToObject(DURATION_TYPE_INSTANT, eAcid, OBJECT_SELF);
|
|
}
|
|
effect eDeath = EffectDeath();
|
|
SetImmortal(OBJECT_SELF,FALSE);
|
|
ApplyEffectToObject(DURATION_TYPE_INSTANT, eDeath, OBJECT_SELF);
|
|
}
|
|
else
|
|
{
|
|
AssignCommand(oDamager, ActionDoCommand(SetCommandable(TRUE,oDamager)));
|
|
FloatingTextStringOnCreature("Coup de Grace (failure)",oDamager);
|
|
}
|
|
}
|
|
// ... or if it took damage the old fashioned way ...
|
|
// (i.e. all hitpoints exhausted due to fire and/or acid)
|
|
else if (nMaxHPsPossible <= 0)
|
|
{
|
|
SetImmortal(OBJECT_SELF,FALSE);
|
|
// Fire will be favored over acid in the event they are equal
|
|
if(nFireDamage >= nAcidDamage)
|
|
{
|
|
effect eFlame = EffectVisualEffect(VFX_DUR_INFERNO_CHEST); //VFX_IMP_FLAME_M
|
|
DelayCommand(2.0, ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eFlame, OBJECT_SELF, 3.0));
|
|
DelayCommand(1.5, BurnEffect());
|
|
//object oFlame = CreateObject(OBJECT_TYPE_PLACEABLE, "plc_flamemedium", GetLocation(OBJECT_SELF), TRUE);
|
|
//DestroyObject(oFlame, 3.25);
|
|
}
|
|
else
|
|
{
|
|
effect eAcid = EffectVisualEffect(VFX_FNF_GAS_EXPLOSION_ACID);
|
|
ApplyEffectToObject(DURATION_TYPE_INSTANT, eAcid, OBJECT_SELF);
|
|
}
|
|
effect eDeath = EffectDeath();
|
|
ApplyEffectToObject(DURATION_TYPE_INSTANT, eDeath, OBJECT_SELF);
|
|
}
|
|
}
|
|
else if(nUser == 1011) // OnSpellCastAt Event
|
|
{
|
|
int nSpellID = GetLastSpell();
|
|
object oCaster = GetLastSpellCaster();
|
|
// Will consider all instant death effects and grenade weapon burnings
|
|
if(GetLastSpellHarmful())
|
|
{
|
|
if(GetLocalInt(OBJECT_SELF,"bDowned") &&
|
|
((nSpellID == SPELL_GRENADE_ACID) || (nSpellID == SPELL_GRENADE_FIRE)))
|
|
{
|
|
SetLocalInt(OBJECT_SELF,"bGrenade",1);
|
|
object eSelf = OBJECT_SELF;
|
|
|
|
DelayCommand(0.2,SetCommandable(FALSE,oCaster));
|
|
AssignCommand(oCaster, ClearAllActions());
|
|
AssignCommand(oCaster, ActionMoveToObject(eSelf,TRUE,0.1));
|
|
AssignCommand(oCaster, ActionPlayAnimation(ANIMATION_LOOPING_GET_LOW, 1.0, RoundsToSeconds(BURNING_TIME)));
|
|
AssignCommand(oCaster, ActionDoCommand(SetCommandable(TRUE,oCaster)));
|
|
|
|
if(nSpellID == SPELL_GRENADE_FIRE)
|
|
{
|
|
effect eFlame = EffectVisualEffect(VFX_DUR_INFERNO_CHEST); //VFX_IMP_FLAME_M
|
|
DelayCommand(2.0, ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eFlame, OBJECT_SELF, 3.0));
|
|
DelayCommand(1.5, BurnEffect());
|
|
//object oFlame = CreateObject(OBJECT_TYPE_PLACEABLE, "plc_flamemedium", GetLocation(OBJECT_SELF), TRUE);
|
|
//DestroyObject(oFlame, 3.25);
|
|
}
|
|
else if(nSpellID == SPELL_GRENADE_ACID)
|
|
{
|
|
effect eAcid = EffectVisualEffect(VFX_FNF_GAS_EXPLOSION_ACID);
|
|
ApplyEffectToObject(DURATION_TYPE_INSTANT, eAcid, OBJECT_SELF);
|
|
}
|
|
effect eDeath = EffectDeath();
|
|
SetImmortal(OBJECT_SELF,FALSE);
|
|
ApplyEffectToObject(DURATION_TYPE_INSTANT, eDeath, OBJECT_SELF);
|
|
}
|
|
}
|
|
// Will consider all healing effects
|
|
else
|
|
{
|
|
|
|
}
|
|
}
|
|
else if(nUser == 1005) // OnPhysicalAttacked Event
|
|
{
|
|
if(GetLocalInt(OBJECT_SELF,"bDowned"))
|
|
{
|
|
SetLocalInt(OBJECT_SELF,"bCoupdeGrace",1);
|
|
}
|
|
}
|
|
}
|