Lankhmar_PRC8/_module/nss/cz_realtroll_ud.nss

501 lines
20 KiB
Plaintext
Raw Permalink Normal View History

2025-04-03 12:54:47 -04:00
/*
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.5 (8/3/06, Resonance)
* Making the full round burning uninterruptible can also glitch PCs so that
they are stuck until they relog, so remove that. PCs will still play the
bending over animation, and will still queue attacks until after that
finishes, but if they run, they can break out of the animation immediately
without problems.
1.4 (8/1/06, Resonance)
* Acid, Fire, and Death Magic spells now properly act like grenades
* Forcing the PCs to run to the target on Coup de Grace causes locks, so removed.
* Burning the body from melee still takes a full round
* Flame Weapon and Darkfire now make weapons count as torches
* There is no apparent way to get Flame Weapon/Darkfire damage to
actually count as fire damage, however -- it is always subdual.
* Coup de Grace damage is now cumulative for setting DC
* Factored out fire and acid death effect code
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"
// 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);
}
// Find out if the weapon is burning from Flame Weapon or Darkfire
int GetIsBurning(object oItem)
{
// Start by checking to see if it's a torch
if(GetBaseItemType(oItem) == BASE_ITEM_TORCH) return TRUE;
// Now check for Flame Weapon or Darkfire
itemproperty ip = GetFirstItemProperty(oItem);
while (GetIsItemPropertyValid(ip))
{
if (GetItemPropertyType(ip) == ITEM_PROPERTY_ONHITCASTSPELL)
{
if (GetItemPropertySubType(ip) == 127) return TRUE; // Darkfire
if (GetItemPropertySubType(ip) == 124) return TRUE; // Flame Weapon
if (GetItemPropertySubType(ip) == 301) return TRUE; //Purist Burning Blade
}
ip = GetNextItemProperty(oItem);
}
return FALSE;
}
// Returns 0 if the spell does neither fire nor acid damage
// Returns 1 if the spell does acid damage
// Returns 2 if the spell does fire damage (or both fire and acid)
// Returns 3 if the spell is death magic
// Grenades are counted as spells
int IsAcidFireDeathSpell(int nSpellID)
{
int retval = 0; // Default to neither fire nor acid
switch(nSpellID)
{
// Check for acid spells
case SPELL_ACID_FOG:
case SPELL_ACID_SPLASH:
case SPELL_CLOUDKILL:
case SPELL_GRENADE_ACID:
case SPELL_MELFS_ACID_ARROW:
case SPELL_MESTILS_ACID_BREATH:
case SPELL_MESTILS_ACID_SHEATH:
case SPELL_STORM_OF_VENGEANCE:
retval = 1;
break;
// Check for fire spells
// Flame Weapon/Darkfire from OnHit don't count?
case SPELL_BURNING_HANDS:
case SPELL_COMBUST:
case SPELL_DARKFIRE:
case SPELL_DELAYED_BLAST_FIREBALL:
case SPELL_EPIC_HELLBALL:
case SPELL_FIRE_STORM:
case SPELL_FIREBALL:
case SPELL_FIREBRAND:
case SPELL_FLAME_ARROW:
case SPELL_FLAME_LASH:
case SPELL_FLAME_STRIKE:
case SPELL_FLAME_WEAPON:
case SPELL_GRENADE_FIRE:
case SPELL_INCENDIARY_CLOUD:
case SPELL_INFERNO:
case SPELL_METEOR_SWARM:
case SPELL_SEARING_LIGHT:
case SPELL_SHADES_FIREBALL:
case SPELL_SHADES_WALL_OF_FIRE:
case SPELL_SUNBEAM:
case SPELL_SUNBURST:
retval = 2;
break;
// Check for death magic
case SPELL_CIRCLE_OF_DEATH:
case SPELL_DESTRUCTION:
case SPELL_FINGER_OF_DEATH:
case SPELL_IMPLOSION:
case SPELL_POWER_WORD_KILL:
case SPELL_SLAY_LIVING:
case SPELL_WAIL_OF_THE_BANSHEE:
case SPELL_WORD_OF_FAITH:
retval = 3;
break;
}
return retval;
}
// Troll death effect: fire damage
void FireDeathEffect(object oTroll)
{
effect eFlame = EffectVisualEffect(VFX_DUR_INFERNO_CHEST); //VFX_IMP_FLAME_M
DelayCommand(2.0, ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eFlame, oTroll, 3.0));
DelayCommand(1.5, BurnEffect());
//object oFlame = CreateObject(OBJECT_TYPE_PLACEABLE, "plc_flamemedium", GetLocation(oTroll), TRUE);
//DestroyObject(oFlame, 3.25);
return;
}
// Troll death effect: acid damage
void AcidDeathEffect(object oTroll)
{
effect eAcid = EffectVisualEffect(VFX_FNF_GAS_EXPLOSION_ACID);
ApplyEffectToObject(DURATION_TYPE_INSTANT, eAcid, oTroll);
return;
}
void main()
{
int nUser = GetUserDefinedEventNumber();
if(nUser == EVENT_HEARTBEAT) // 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 == EVENT_DAMAGED) // 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 coup de grace cumulative damage
int nCoupDamage = GetLocalInt(OBJECT_SELF, "nCoupDamage");
// 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)
object oLeftHandItem = GetItemInSlot(INVENTORY_SLOT_LEFTHAND, oDamager);
object oRightHandItem = GetItemInSlot(INVENTORY_SLOT_RIGHTHAND, oDamager);
if( bDowned && (GetIsBurning(oLeftHandItem) || GetIsBurning(oRightHandItem)) )
{
// 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)));
FireDeathEffect(OBJECT_SELF);
effect eDeath = EffectDeath();
SetImmortal(OBJECT_SELF,FALSE);
ApplyEffectToObject(DURATION_TYPE_INSTANT, eDeath, OBJECT_SELF);
}
else if( bDowned && (GetLocalInt(oDamager, "VAR_IGNITESPELL") == 1))
{
// DelayCommand(0.2,SetCommandable(FALSE,oDamager));
AssignCommand(oDamager, ClearAllActions());
AssignCommand(oDamager, ActionPlayAnimation(ANIMATION_LOOPING_WORSHIP, 0.8, RoundsToSeconds(BURNING_TIME)));
// AssignCommand(oDamager, ActionDoCommand(SetCommandable(TRUE,oDamager)));
FireDeathEffect(OBJECT_SELF);
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)));
nCoupDamage = nCoupDamage + nFireDamage + nAcidDamage;
if(!FortitudeSave(OBJECT_SELF,10 + nCoupDamage,
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)
{
FireDeathEffect(OBJECT_SELF);
}
else
{
AcidDeathEffect(OBJECT_SELF);
}
effect eDeath = EffectDeath();
SetImmortal(OBJECT_SELF,FALSE);
ApplyEffectToObject(DURATION_TYPE_INSTANT, eDeath, OBJECT_SELF);
}
else // Fort save succeeded, store cumulative coup damage
{
// AssignCommand(oDamager, ActionDoCommand(SetCommandable(TRUE,oDamager)));
SetLocalInt(OBJECT_SELF, "nCoupDamage", nCoupDamage);
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)
{
FireDeathEffect(OBJECT_SELF);
}
else
{
AcidDeathEffect(OBJECT_SELF);
}
effect eDeath = EffectDeath();
ApplyEffectToObject(DURATION_TYPE_INSTANT, eDeath, OBJECT_SELF);
}
}
else if(nUser == EVENT_SPELL_CAST_AT) // OnSpellCastAt Event
{
int nSpellID = GetLastSpell();
int nAcidFireDeath = IsAcidFireDeathSpell(nSpellID);
object oCaster = GetLastSpellCaster();
// Will consider all instant death effects, acid and fire damage from
// both spells and grenades
if(GetLastSpellHarmful())
{
if(GetLocalInt(OBJECT_SELF,"bDowned") && nAcidFireDeath)
{
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(nAcidFireDeath == 2)
{
FireDeathEffect(OBJECT_SELF);
}
else if(nAcidFireDeath == 1)
{
AcidDeathEffect(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 == EVENT_ATTACKED) // OnPhysicalAttacked Event
{
if(GetLocalInt(OBJECT_SELF,"bDowned"))
{
SetLocalInt(OBJECT_SELF,"bCoupdeGrace",1);
}
}
}