///////////////////////////////////////////////////// // // // Invizible420's Ghost w/ visuals and Possession // // // // Created 03/11/03 By: Invizible420 // // // // Send Bug Reports to: Digiddy777@yahoo.com // // // ///////////////////////////////////////////////////// /* Description: This is the on_heartbeat script for a ghost. I tried to make it fairly accurate to 3rd edition rules (at least as close as NWN will let me). This heartbeat script will give the ghost a percent chance to use Malevolence (posses) the pc as well as Frightening Moan. This script will only fire if a PC is within fDist (See Below) meters of the ghost and if a PC is in the area. So to not consume unnecessary CPU cycles. To simplify code, the moan and malevolence will only affect the PC once if the below ONE_SHOT options are TRUE. This goes for all the ghosts, if set to TRUE and a PC either saves or is possessed, then no ghosts in the module will be able to affect the PC. Update 04.29.05: Streamlined code into 2 scripts renamed them to better suit a filename format. ** If you are updating from an older version of my ghost, delete i420_ghost* files. Then in Custom Creatures > Undead --- Update all instances of the Ghost. New features: Starting at line 66 you will find 5 new control variables... These are effects that are applied when the PC is possessed and will last until they are unpossessed. Also, fixed a minor bug with the ghost using moan if the PC is dead. Last, I changed the demo module so you can respawn if you die... oops! */ #include "NW_I0_GENERIC" #include "nw_i0_plot" #include "prc_inc_racial" // Customizable Variables: int MOAN_ONE_SHOT = FALSE; // Change to TRUE to make Moan effect one shot (Default: FALSE) int MALEVOLENCE_ONE_SHOT = FALSE; // Change to TRUE to make Malevolence one shot (Default: FALSE) float fDIST = 10.0; // How far the ghost must be away from a PC (Default: 10.0m) int POSSESS_CHANCE = 20; // % chance last attacker will become possessed [Malevolence] (Default: 20%) int MOAN_CHANCE = 10; // % chance last ghost will use Moan attack (Default: 10%) float POSSESS_DUR = IntToFloat(d20(1)+10); // Duration of possession (Default: 1d20+10) float POSSESSED_PC_ATTACK_DIST = 15.0; // Maximum range another PC must be get attacked by another possessed PC (Default: 15m) int DO_DMG = TRUE; // if TRUE ghost will damage PC when possessed, if FALSE ghost will not damage PC float DAMAGE_TIME = IntToFloat(d6(1)+3); // This is the amount of time to wait before damaging the PC when possessed float DAMAGE_TIME_2 = IntToFloat(d6(1)+FloatToInt(DAMAGE_TIME)+3); // Same as above for 2nd hit float DAMAGE_TIME_3 = IntToFloat(d6(1)+FloatToInt(DAMAGE_TIME_2)+3); // Same as above for 3rd hit int GHOST_DAMAGE_TYPE = DAMAGE_TYPE_NEGATIVE; // The type of damage the ghost will do to a PC when possessed effect GHOST_DMG_VIS = EffectVisualEffect(VFX_IMP_NEGATIVE_ENERGY); // The visual effect for the ghost possession damage float MOAN_DIST = 10.0; // Maximum range of Frightful Moan (Default: 10m about 30 ft) float MOAN_DUR = IntToFloat(d4(2)+3); // Duration of Moan Effect in turns (Default: 2d4 + 3 turns) /********/ // New variables for 4.39.05 update int PC_POSSESS_SIT = TRUE; // TRUE means the below chances will affect the PC, FALSE means they won't int ABILITY_LOSS_CHANCE = 10; // chance the a (random) ability will be lowered for possess dur int AC_LOSS_CHANCE = 10; // Chance the PC's AC will be decreased int BLIND_CHANCE = 10; // Chance the ghost will close the PC's eyes (blind them) float BLIND_DUR = POSSESS_DUR-IntToFloat(d4(1)-1); // How long to stay blind object oGHOST_LIMBO_WP = GetObjectByTag("GHOST_LIMBO"); // Get the Ghost Limbo Waypoint // Text Color Stuff object oCOLOR_OBJ = GetObjectByTag("i420_COLOR_OBJ"); string COLOR_RED = GetStringLeft(GetName(oCOLOR_OBJ),6); string COLOR_BLUE = GetStringRight(GetName(oCOLOR_OBJ),6); //Debugger int DEBUGGER = FALSE; void DB(string sDebugStr) { if (DEBUGGER == TRUE) SendMessageToPC(GetFirstPC(),sDebugStr); } // Begin Function Declarations: // Define Function Variables // Returns TRUE if there is a PC in oArea else returns FALSE int CheckAreaForPCs(object oArea); // Returns the Nearest PC to oObject object GetNearestPCToObject(object oObject); // Applies Ghost Possession Effects to oTarget (1 of 4 Effects) void ApplyGhostPossessionEffect(object oTarget,object oGhost); int CheckAreaForPCs(object oArea) { object oPC; int iCheck = FALSE; oPC = GetFirstPC(); while (oPC != OBJECT_INVALID) { if (GetArea(oPC) == oArea) { iCheck = TRUE; } oPC = GetNextPC(); } return iCheck; } object GetNearestPCToObject(object oObject) { object oNearestPC; oNearestPC = GetNearestCreature(CREATURE_TYPE_PLAYER_CHAR,PLAYER_CHAR_IS_PC,oObject); if (GetDistanceBetween(OBJECT_SELF,oNearestPC) <= POSSESSED_PC_ATTACK_DIST) { return oNearestPC; } else { oNearestPC = OBJECT_INVALID; return oNearestPC; } } void RecreateGhostAtLocation(object oTarget,object oGhost, location lLoc) { ApplyEffectToObject(DURATION_TYPE_INSTANT,EffectVisualEffect(VFX_IMP_DEATH),oTarget); AssignCommand(oGhost,JumpToLocation(lLoc)); DeleteLocalLocation(oGhost,"GHOST_PC_LAST_LOC"); ClearAllActions(FALSE); } void RecreateGhost(object oTarget,object oGhost) { if (GetIsDead(oTarget) == FALSE && GetLocalInt(oGhost,"GHOST_PC_DEAD") != 1 && oTarget != OBJECT_INVALID) { ApplyEffectToObject(DURATION_TYPE_INSTANT,EffectVisualEffect(VFX_IMP_DEATH),oTarget); AssignCommand(oGhost,JumpToObject(oTarget,FALSE)); } else if (GetIsDead(oTarget) == TRUE || oTarget == OBJECT_INVALID && GetLocalInt(oGhost, "GHOST_PC_DEAD") != 1) { RecreateGhostAtLocation(oTarget,oGhost,GetLocalLocation(oGhost,"GHOST_PC_LAST_LOC")); DeleteLocalLocation(oGhost,"GHOST_PC_LAST_LOC"); } ClearAllActions(FALSE); } void DoGhostDmg(object oPC, object oGhost, int iDmgPos) { if(GetIsDead(oPC) != TRUE && GetLocalInt(oGhost,"GHOST_PC_DEAD") != 1) { int DAMAGE_AMOUNT_1 = (Random(GetHitDice(oPC))+1*2)+GetAbilityModifier(ABILITY_STRENGTH,oPC); // Amount to damage the PC when possessed first hit int DAMAGE_AMOUNT_2 = (Random(GetHitDice(oPC))+1*2)+GetAbilityModifier(ABILITY_STRENGTH,oPC); // Amount to damage the PC when possessed second hit int DAMAGE_AMOUNT_3 = (Random(GetHitDice(oPC))+1*2)+GetAbilityModifier(ABILITY_STRENGTH,oPC); // Amount to damage the PC when possessed third hit int iDmg; if (iDmgPos == 1) iDmg = DAMAGE_AMOUNT_1; if (iDmgPos == 2) iDmg = DAMAGE_AMOUNT_2; if (iDmgPos == 3) iDmg = DAMAGE_AMOUNT_3; if (iDmg > 0) { effect eLink1 = EffectLinkEffects(GHOST_DMG_VIS,EffectDamage(iDmg,GHOST_DAMAGE_TYPE)); ApplyEffectToObject(DURATION_TYPE_INSTANT,eLink1,oPC); } ClearAllActions(FALSE); } if (GetIsDead(oPC) == TRUE && GetLocalInt(oGhost,"GHOST_PC_DEAD") != 1) { DelayCommand(2.5,RecreateGhostAtLocation(oPC,oGhost,GetLocalLocation(oGhost,"GHOST_PC_LAST_LOC"))); SetLocalInt(oGhost, "GHOST_PC_DEAD",1); DelayCommand(POSSESS_DUR+0.5,DeleteLocalInt(oGhost,"GHOST_PC_DEAD")); } } void DelayAction(object oTarget, int iSit) { // Check which situation(s) the PC has and apply the effect if (iSit == 1) { int ABILITY_LOSS_AMT = Random(GetHitDice(oTarget))+1; // amount of ability loss int iAbility; switch(d6(1)) { case 1: iAbility = ABILITY_CHARISMA; break; case 2: iAbility = ABILITY_CONSTITUTION; break; case 3: iAbility = ABILITY_DEXTERITY; break; case 4: iAbility = ABILITY_INTELLIGENCE; break; case 5: iAbility = ABILITY_STRENGTH; break; case 6: iAbility = ABILITY_WISDOM; break; } ApplyEffectToObject(DURATION_TYPE_TEMPORARY,EffectVisualEffect(VFX_DUR_MIND_AFFECTING_DISABLED),oTarget,POSSESS_DUR-3.5); ApplyEffectToObject(DURATION_TYPE_TEMPORARY,EffectAbilityDecrease(iAbility,ABILITY_LOSS_AMT),oTarget,POSSESS_DUR-3.5); } if (iSit == 2) { int AC_LOSS_AMT = Random(GetHitDice(oTarget))+1; // amount of Ac loss ApplyEffectToObject(DURATION_TYPE_TEMPORARY,EffectVisualEffect(VFX_DUR_MIND_AFFECTING_NEGATIVE),oTarget,POSSESS_DUR-5.0); ApplyEffectToObject(DURATION_TYPE_TEMPORARY,EffectACDecrease(AC_LOSS_AMT),oTarget,POSSESS_DUR-5.0); } if (iSit == 3) { ApplyEffectToObject(DURATION_TYPE_TEMPORARY,EffectVisualEffect(VFX_DUR_BLIND),oTarget,BLIND_DUR-6.0); ApplyEffectToObject(DURATION_TYPE_TEMPORARY,EffectBlindness(),oTarget,BLIND_DUR-6.0); } } void GhostEffects2(object oTarget,object oGhost) { DB("POSSESS_DUR = "+FloatToString(POSSESS_DUR)); // DEBUG LINE ApplyEffectToObject(DURATION_TYPE_INSTANT,EffectVisualEffect(VFX_DUR_MIND_AFFECTING_DOMINATED),oTarget); ApplyEffectToObject(DURATION_TYPE_INSTANT,EffectVisualEffect(VFX_IMP_DEATH),oTarget); ApplyEffectToObject(DURATION_TYPE_INSTANT,EffectVisualEffect(VFX_FNF_SUMMON_UNDEAD),oTarget); // Sets an int so the PC can't be dominated multiple times simoltaneously, then deletes the int after the ghost effect SetLocalInt(oTarget,"PC_IS_DOMINATED",1); DelayCommand(POSSESS_DUR,DeleteLocalInt(oTarget,"PC_IS_DOMINATED")); // Dominate the oTarget ApplyEffectToObject(DURATION_TYPE_TEMPORARY,EffectCutsceneDominated(),oTarget,POSSESS_DUR); // Do the NEW ossessed situations if (PC_POSSESS_SIT == TRUE) { // Check to see if PC will be affected by a possessed situation if (d100(1) < ABILITY_LOSS_CHANCE) DelayCommand(2.5,DelayAction(oTarget,1)); if (d100(1) < AC_LOSS_CHANCE) DelayCommand(4.0,DelayAction(oTarget,2)); if (d100(1) < BLIND_CHANCE) DelayCommand(5.5,DelayAction(oTarget,3)); } if(DO_DMG == TRUE) { DelayCommand(DAMAGE_TIME,DoGhostDmg(oTarget, oGhost, 1)); if (FloatToInt(POSSESS_DUR)-1 > FloatToInt(DAMAGE_TIME_2)) DelayCommand(DAMAGE_TIME_2,DoGhostDmg(oTarget, oGhost, 2)); if (FloatToInt(POSSESS_DUR)-1 > FloatToInt(DAMAGE_TIME_3)) DelayCommand(DAMAGE_TIME_3,DoGhostDmg(oTarget, oGhost, 3)); } DelayCommand(POSSESS_DUR,RecreateGhost(oTarget,oGhost)); } void fFace(object oPC, object oGhost) { float fFacing = GetFacing(oPC); AssignCommand(oGhost,SetFacing(fFacing)); } void ApplyGhostPossessionEffect(object oTarget,object oGhost) { // Apply effect to make ghost look like it leaps into the PC and disappears ApplyEffectToObject(DURATION_TYPE_INSTANT,EffectVisualEffect(VFX_FNF_SUMMON_UNDEAD),OBJECT_SELF); AssignCommand(oGhost,ClearAllActions()); AssignCommand(oGhost,JumpToObject(oTarget,FALSE)); DelayCommand(0.4,AssignCommand(oGhost,JumpToObject(oGHOST_LIMBO_WP,FALSE))); DelayCommand(0.5,fFace(oTarget,oGhost)); GhostEffects2(oTarget,oGhost); } void main() { switch(GetUserDefinedEventNumber()) { case 1001: // This insures that the script is only fired if a PC is // within the area. if (CheckAreaForPCs(GetArea(OBJECT_SELF)) == TRUE) { object oNearestPC = GetNearestPC(); if ( GetIsPC(oNearestPC) == TRUE && GetDistanceBetween(OBJECT_SELF,GetNearestPC()) <= fDIST && GetLocalInt(oNearestPC,"i420_GHOST_MALEVOLENCE") != 1 && GetLocalInt(oNearestPC,"PC_IS_DOMINATED") != 1 && GetIsDead(oNearestPC) != TRUE) { // Roll a % die against the POSSESS_CHANCE if (d100(1) <= POSSESS_CHANCE) { // Get DC and Will Saves int DC = GetAbilityModifier(ABILITY_CHARISMA,OBJECT_SELF)+15; int Will = GetWillSavingThrow(oNearestPC); int id20 = d20(1); int SavingThrow = Will+id20; // If Save fails if (DC > SavingThrow ) { //SettheLastlocationofPC SetLocalLocation(OBJECT_SELF,"GHOST_PC_LAST_LOC",GetLocation(oNearestPC)); // Send Message to PC SendMessageToPC(oNearestPC,COLOR_RED+"Ghost uses Malevolence!"); SendMessageToPC(oNearestPC,COLOR_BLUE+GetName(oNearestPC)+" : Will Save vs. Malevolence : *failure* : ("+IntToString(id20)+" + "+IntToString(Will)+" = "+IntToString(SavingThrow)+" vs. DC: "+IntToString(DC)+")"); // Apply the 1st Ghost Effects ApplyGhostPossessionEffect(oNearestPC,OBJECT_SELF); } // If Save succeeds else { // Send Message to PC SendMessageToPC(oNearestPC,COLOR_RED+"Ghost uses Malevolence!"); SendMessageToPC(oNearestPC,COLOR_BLUE+GetName(oNearestPC)+" : Will Save vs. Malevolence : *success* : ("+IntToString(id20)+" + "+IntToString(Will)+" = "+IntToString(SavingThrow)+" vs. DC: "+IntToString(DC)+")"); // Set the int that ghosts can not affect the PC if (MALEVOLENCE_ONE_SHOT == TRUE) { SetLocalInt(oNearestPC,"i420_GHOST_MALEVOLENCE",1); } } } } // Roll a % die against the MOAN_CHANCE if (d100(1) <= MOAN_CHANCE && GetLocalInt(oNearestPC,"PC_IS_MOANED") != 1 && GetLocalInt(oNearestPC,"i420_GHOST_MALEVOLENCE") != 1 ) { PlayAnimation(ANIMATION_FIREFORGET_TAUNT); DelayCommand(0.75,ApplyEffectToObject(DURATION_TYPE_INSTANT,EffectVisualEffect(VFX_IMP_SILENCE),OBJECT_SELF)); // Loop through all the Objects in a MOAN_DIST m radius object oObjects; oObjects = GetFirstObjectInShape(SHAPE_SPHERE,MOAN_DIST,GetLocation(OBJECT_SELF),TRUE,OBJECT_TYPE_CREATURE); while (oObjects != OBJECT_INVALID) { if ((MyPRCGetRacialType(oObjects) != RACIAL_TYPE_UNDEAD) && (GetLocalInt(oNearestPC,"i420_GHOST_MOAN") != 1) && GetIsDead(oNearestPC) != TRUE) { int iDC = GetAbilityModifier(ABILITY_CHARISMA,OBJECT_SELF)+15; int iWill = GetWillSavingThrow(oNearestPC); int D20 = d20(1); int iSavingThrow = iWill+D20; // If Save fails if (iDC > iSavingThrow ) { // Sets an int to make sure PC can't be moaned multiple times simoltaneously, deletes aftet moan effect SetLocalInt(oNearestPC,"PC_IS_MOANED",1); DelayCommand(MOAN_DUR,DeleteLocalInt(oNearestPC,"PC_IS_MOANED")); SendMessageToPC(oNearestPC,COLOR_RED+"Ghost uses Frightful Moan!"); SendMessageToPC(oNearestPC,COLOR_BLUE+GetName(oNearestPC)+" : Will Save vs. Fear : *failure* : ("+IntToString(D20)+" + "+IntToString(iWill)+" = "+IntToString(iSavingThrow)+" vs. DC: "+IntToString(iDC)+")"); effect eVis = EffectVisualEffect(VFX_DUR_MIND_AFFECTING_FEAR); effect eLink = EffectLinkEffects(eVis, EffectFrightened()); AssignCommand(oNearestPC,ClearAllActions(FALSE)); AssignCommand(oNearestPC,ActionMoveAwayFromObject(OBJECT_SELF,TRUE)); ApplyEffectToObject(DURATION_TYPE_TEMPORARY,eLink,oObjects,MOAN_DUR); } else { SendMessageToPC(oNearestPC,COLOR_RED+"Ghost uses Frightful Moan!"); // Send Message to PC SendMessageToPC(oNearestPC,COLOR_BLUE+GetName(oNearestPC)+" : Will Save vs. Fear : *success* : ("+IntToString(D20)+" + "+IntToString(iWill)+" = "+IntToString(iSavingThrow)+" vs. DC: "+IntToString(iDC)+")"); // Set the int that ghosts can not affect the PC if (MOAN_ONE_SHOT == TRUE) { SetLocalInt(oNearestPC,"i420_GHOST_MOAN",1); } } } oObjects = GetNextObjectInShape(SHAPE_SPHERE,MOAN_DIST,GetLocation(OBJECT_SELF),TRUE,OBJECT_TYPE_CREATURE); } } } break; case 1007: object oPlayer = GetLastPlayerDied(); SetIsTemporaryEnemy(GetLastDamager(),oPlayer); DelayCommand(5.0, PopUpDeathGUIPanel(oPlayer,1,1,0,"To test what ghost does after PC dies, press the 'Wait for help' button. (Must be in Multi-player to see the button!)")); break; } }