//:://///////////////////////////////////////////// //:: Scrying include //:: prc_inc_scry //:://///////////////////////////////////////////// /** @file This include contains all of the code that is used to scry. At the moment, this is the basic scrying and will be expanded with the additional material as coding goes along. This is based on the code of the Baelnorn Project written by Ornedan. All the operations work only on PCs, as there is no AI that could have NPCs take any advantage of the system. */ //::////////////////////////////////////////////// //:: Created By: Stratovarius //:: Created On: 30.04.2007 //::////////////////////////////////////////////// #include "psi_inc_psifunc" #include "inc_utility" #include "true_utter_const" #include "shd_myst_const" /////////////////////// /* Public Constants */ /////////////////////// const string COPY_LOCAL_NAME = "Scry_Copy"; const string ALREADY_IMMORTAL_LOCAL_NAME = "Scry_ImmortalAlready"; const float SCRY_HB_DELAY = 1.0f; ////////////////////////////////////////////////// /* Function prototypes */ ////////////////////////////////////////////////// /** * ONLY CALL THIS FUNCTION * Does all of the necessary set up for scrying * Also checks for all of the anti-scrying features * if oPC == oTarget, will use PRCGetSpellLocation * as the location for the effects to begin * * @param oPC The PC on whom to operate. * @param oTarget The object to target */ void ScryMain(object oPC, object oTarget); /** * Moves all of the PC's items to the copy creature * Switches locations of the two creatures * * @param oPC The PC on whom to operate. * @param oCopy The copy of the PC */ void DoScryBegin(object oPC, object oCopy); /** * Undoes all the effects of DoScryBegin * * @param oPC The PC on whom to operate. * @param oCopy The copy of the PC */ void DoScryEnd(object oPC, object oCopy); /** * Runs tests to see if the Scry effect can still continue. * If the copy is dead, end Scry and kill the PC. * * @param oPC The PC on whom to operate. * @param oCopy The copy of the PC */ void ScryMonitor(object oPC, object oCopy); /** * Reverses ApplyScryEffects * * @param oPC The PC on whom to operate. */ void RemoveScryEffects(object oPC); /** * Checks whether the PC is scrying or not * * @param oPC The PC on whom to operate. */ int GetIsScrying(object oPC); /** * Does the Discern Location content * * @param oPC The PC on whom to operate. * @param oTarget The spell Target */ void DiscernLocation(object oPC, object oTarget); /** * Does the Locate Creature and Object content * * @param oPC The PC on whom to operate. * @param oTarget The spell Target */ void LocateCreatureOrObject(object oPC, object oTarget); /** * Prevents the copy from dropping goods when dead. * * @param oImage The PC's copy. */ void CleanCopy(object oImage); ////////////////////////////////////////////////// /* Function definitions */ ////////////////////////////////////////////////// void ScryMain(object oPC, object oTarget) { // Get the main variables used. object oCopy = GetLocalObject(oPC, COPY_LOCAL_NAME); effect eLight = EffectVisualEffect(VFX_IMP_HEALING_X , FALSE); effect eGlow = EffectVisualEffect(VFX_DUR_ETHEREAL_VISAGE, FALSE); // Use prestored variables because of time delay int nCasterLevel = GetLocalInt(oPC, "ScryCasterLevel"); int nSpell = PRCGetSpellId(); //GetLocalInt(oPC, "ScrySpellId"); int nDC = GetLocalInt(oPC, "ScrySpellDC"); float fDur = GetLocalFloat(oPC, "ScryDuration"); if(DEBUG) DoDebug("prc_inc_scry: Beginning ScryMain. nSpell: " + IntToString(nSpell)); if (oPC != oTarget) // No hitting yourself with defenses { if(GetHasSpellEffect(POWER_REMOTE_VIEW_TRAP, oTarget)) { effect eVis = EffectVisualEffect(VFX_IMP_LIGHTNING_S); // Failed Save if(!PRCMySavingThrow(SAVING_THROW_WILL, oTarget, nDC, SAVING_THROW_TYPE_NONE)) { FloatingTextStringOnCreature("You have been caught in a Remote View Trap", oPC, FALSE); eVis = EffectLinkEffects(eVis, EffectDamage(d6(8), DAMAGE_TYPE_ELECTRICAL)); ApplyEffectToObject(DURATION_TYPE_INSTANT, eVis, oTarget); return; } else { FloatingTextStringOnCreature("You have saved against a Remote View Trap", oPC, FALSE); eVis = EffectLinkEffects(eVis, EffectDamage(d6(4), DAMAGE_TYPE_ELECTRICAL)); ApplyEffectToObject(DURATION_TYPE_INSTANT, eVis, oTarget); } } if(GetHasSpellEffect(SPELL_SEQUESTER, oTarget) || GetHasSpellEffect(POWER_SEQUESTER, oTarget)) { // Spell auto-fails if this is the case FloatingTextStringOnCreature(GetName(oTarget) + " has been Sequestered.", oPC, FALSE); return; } if(GetHasSpellEffect(SPELL_OBSCURE_OBJECT, oTarget)) { // Spell auto-fails if this is the case FloatingTextStringOnCreature(GetName(oTarget) + " has been Obscured.", oPC, FALSE); return; } if(GetHasSpellEffect(SPELL_NONDETECTION, oTarget) || GetLevelByClass(CLASS_TYPE_WILD_MAGE, oTarget) >= 6 || GetLevelByClass(CLASS_TYPE_UNSEEN_SEER, oTarget) >= 5 || GetHasSpellEffect(MELD_ENIGMA_HELM, oTarget) || GetRacialType(oTarget) == RACIAL_TYPE_SKULK) { // Caster level check or the Divination fails. int nTarget = PRCGetCasterLevel(oTarget) + 11; if (GetRacialType(oTarget) == RACIAL_TYPE_SKULK && 20 > nTarget) nTarget = 20; if(nTarget > nCasterLevel + d20()) { FloatingTextStringOnCreature(GetName(oTarget) + " has Nondetection active.", oPC, FALSE); return; } } if(GetHasSpellEffect(POWER_ESCAPE_DETECTION, oTarget)) { // Caster level check or the Divination fails. // Max of 10 if(max(10, GetManifesterLevel(oTarget)) + 13 > nCasterLevel + d20()) { FloatingTextStringOnCreature(GetName(oTarget) + " has Escape Detection active.", oPC, FALSE); return; } } if(GetHasSpellEffect(POWER_DETECT_REMOTE_VIEWING, oTarget)) { // Caster level check for the target to learn where the caster is if(GetManifesterLevel(oTarget) + d20() >= nCasterLevel + d20()) { FloatingTextStringOnCreature(GetName(oPC) + " is viewing you from " + GetName(GetArea(oPC)), oTarget, FALSE); } } } // Discern Location skips all of this if(nSpell == SPELL_DISCERN_LOCATION) { DiscernLocation(oPC, oTarget); return; } // So do the Locate Twins if(nSpell == SPELL_LOCATE_CREATURE || nSpell == SPELL_LOCATE_OBJECT || nSpell == MYST_TRAIL_OF_HAZE) { LocateCreatureOrObject(oPC, oTarget); return; } if(nSpell != SPELL_CLAIRAUDIENCE_AND_CLAIRVOYANCE && nSpell != POWER_CLAIRTANGENT_HAND && nSpell != SPELL_PNP_SCRY_FAMILIAR && nSpell != POWER_CLAIRVOYANT_SENSE && nSpell != TRUE_SEE_THE_NAMED && oPC != oTarget) // No save if you target yourself. { //Make SR Check if(PRCDoResistSpell(oPC, oTarget, nCasterLevel)) { FloatingTextStringOnCreature(GetName(oTarget) + " made Spell Resistance check vs Scrying", oPC, FALSE); DoScryEnd(oPC, oCopy); return; } //Save if(PRCMySavingThrow(SAVING_THROW_WILL, oTarget, nDC, SAVING_THROW_TYPE_MIND_SPELLS)) { FloatingTextStringOnCreature(GetName(oTarget) + " saved vs Scrying", oPC, FALSE); DoScryEnd(oPC, oCopy); return; } } if(DEBUG) DoDebug("prc_inc_scry running.\n" + "oPC = '" + GetName(oPC) + "' - '" + GetTag(oPC) + "' - " + ObjectToString(oPC) + "Copy exists: " + DebugBool2String(GetIsObjectValid(oCopy)) ); location lTarget = GetLocation(oTarget); if (oTarget == oPC || !GetIsObjectValid(oTarget)) // Some spells just target ground for this lTarget = PRCGetSpellTargetLocation(); // Create the copy oCopy = CopyObject(oPC, lTarget, OBJECT_INVALID, GetName(oPC) + "_" + COPY_LOCAL_NAME); CleanCopy(oCopy); // Attempted Fix to the Copy get's murdered problem. int nMaxHenchmen = GetMaxHenchmen(); SetMaxHenchmen(99); AddHenchman(oPC, oCopy); SetMaxHenchmen(nMaxHenchmen); SetIsTemporaryFriend(oPC, oCopy, FALSE); // Set the copy to be undestroyable, so that it won't vanish to the ether // along with the PC's items. AssignCommand(oCopy, SetIsDestroyable(FALSE, FALSE, FALSE)); // Make the copy immobile and minimize the AI on it ApplyEffectToObject(DURATION_TYPE_PERMANENT, ExtraordinaryEffect(EffectCutsceneImmobilize()), oCopy); SetAILevel(oCopy, AI_LEVEL_VERY_LOW); // Store a referece to the copy on the PC SetLocalObject(oPC, COPY_LOCAL_NAME, oCopy); //Set Immortal flag on the PC or if they were already immortal, //leave a note about it on them. if(GetImmortal(oPC)) { if(DEBUG) DoDebug("prc_inc_scry: The PC was already immortal"); SetLocalInt(oPC, ALREADY_IMMORTAL_LOCAL_NAME, TRUE); } else { if(DEBUG) DoDebug("prc_inc_scry: Setting PC immortal"); SetImmortal(oPC, TRUE); DeleteLocalInt(oPC, ALREADY_IMMORTAL_LOCAL_NAME); // Paranoia } // Do VFX on PC and copy SPApplyEffectToObject(DURATION_TYPE_INSTANT, eLight, oCopy); SPApplyEffectToObject(DURATION_TYPE_INSTANT, eLight, oPC); // Do the switching around DoScryBegin(oPC, oCopy); //Set up duration marker for ending effect DelayCommand(fDur, SetLocalInt(oPC, "SCRY_EXPIRED", 1)); } // Moves the PC's items to the copy and switches their locations around void DoScryBegin(object oPC, object oCopy) { if(DEBUG) DoDebug("prc_inc_scry: DoScryBegin():\n" + "oPC = '" + GetName(oPC) + "'\n" + "oCopy = '" + GetName(oCopy) + "'" ); // Make sure both objects are valid if(!GetIsObjectValid(oCopy) || !GetIsObjectValid(oPC)){ if(DEBUG) DoDebug("DoScryBegin called, but one of the parameters wasn't a valid object. Object status:" + "\nPC - " + (GetIsObjectValid(oPC) ? "valid":"invalid") + "\nCopy - " + (GetIsObjectValid(oCopy) ? "valid":"invalid") ); // Some cleanup before aborting if(!GetLocalInt(oPC, ALREADY_IMMORTAL_LOCAL_NAME)) { SetImmortal(oPC, FALSE); DeleteLocalInt(oPC, ALREADY_IMMORTAL_LOCAL_NAME); } MyDestroyObject(oCopy); DeleteLocalInt(oPC, "Scry_Active"); RemoveScryEffects(oPC); return; } // Set a local on the PC telling that it's a scry. This is used // to keep the PC from picking up or losing objects. // Also stops it from casting spells SetLocalInt(oPC, "Scry_Active", TRUE); // Start a pseudo-hb to monitor the status of both PC and copy DelayCommand(SCRY_HB_DELAY, ScryMonitor(oPC, oCopy)); // Add eventhooks if(DEBUG) DoDebug("AddEventScripts"); AddEventScript(oPC, EVENT_ONPLAYEREQUIPITEM, "prc_scry_event", TRUE, FALSE); // OnEquip AddEventScript(oPC, EVENT_ONACQUIREITEM, "prc_scry_event", TRUE, FALSE); // OnAcquire AddEventScript(oPC, EVENT_ONPLAYERREST_STARTED, "prc_scry_event", FALSE, FALSE); // OnRest AddEventScript(oPC, EVENT_ONCLIENTENTER, "prc_scry_event", TRUE, FALSE); //OnClientEnter // Adjust reputation so the target monster doesn't attack you //ApplyEffectToObject(DURATION_TYPE_TEMPORARY, ExtraordinaryEffect(EffectCharmed()), oTarget, GetLocalFloat(oPC, "ScryDuration")); // Swap the copy and PC location lPC = GetLocation(oPC); location lCopy = GetLocation(oCopy); DelayCommand(1.5f,AssignCommand(oPC, JumpToLocation(lCopy))); DelayCommand(1.5f,AssignCommand(oCopy, JumpToLocation(lPC))); } // Switches the PC's inventory back from the copy and returns the PC to the copy's location. void DoScryEnd(object oPC, object oCopy) { if(DEBUG) DoDebug("prc_inc_scry: DoScryEnd():\n" + "oPC = '" + GetName(oPC) + "'\n" + "oCopy = '" + GetName(oCopy) + "'" ); //effect eGlow = EffectVisualEffect(VFX_DUR_ETHEREAL_VISAGE, FALSE); effect eLight = EffectVisualEffect(VFX_IMP_HEALING_X , FALSE); // Remove Immortality from the PC if necessary if(!GetLocalInt(oPC, ALREADY_IMMORTAL_LOCAL_NAME)) SetImmortal(oPC, FALSE); // Remove the VFX and the attack penalty from all spells. PRCRemoveSpellEffects(SPELL_SCRY , oPC, oPC); PRCRemoveSpellEffects(SPELL_GREATER_SCRYING , oPC, oPC); PRCRemoveSpellEffects(SPELL_DISCERN_LOCATION , oPC, oPC); PRCRemoveSpellEffects(SPELL_LOCATE_CREATURE , oPC, oPC); PRCRemoveSpellEffects(SPELL_LOCATE_OBJECT , oPC, oPC); PRCRemoveSpellEffects(SPELL_ARCANE_EYE , oPC, oPC); PRCRemoveSpellEffects(MYST_BEND_PERSPECTIVE , oPC, oPC); PRCRemoveSpellEffects(MYST_FAR_SIGHT , oPC, oPC); PRCRemoveSpellEffects(MYST_EPHEMERAL_IMAGE , oPC, oPC); PRCRemoveSpellEffects(SPELL_SHADOWDOUBLE , oPC, oPC); PRCRemoveSpellEffects(SPELL_OBSCURE_OBJECT , oPC, oPC); PRCRemoveSpellEffects(SPELL_SEQUESTER , oPC, oPC); PRCRemoveSpellEffects(SPELL_PNP_SCRY_FAMILIAR, oPC, oPC); PRCRemoveSpellEffects(SPELL_CLAIRAUDIENCE_AND_CLAIRVOYANCE, oPC, oPC); // Remove the local signifying that the PC is a projection DeleteLocalInt(oPC, "Scry_Active"); // Remove the local signifying projection being terminated by an external cause DeleteLocalInt(oPC, "Scry_Abort"); // Remove the heartbeat HP tracking local DeleteLocalInt(oPC, "Scry_HB_HP"); // Delete all of the marker ints for the spell DeleteLocalInt(oPC, "ScryCasterLevel"); DeleteLocalInt(oPC, "ScrySpellId"); DeleteLocalInt(oPC, "ScrySpellDC"); DeleteLocalFloat(oPC, "ScryDuration"); // Remove weapons nerfing RemoveScryEffects(oPC); // Remove eventhooks RemoveEventScript(oPC, EVENT_ONPLAYEREQUIPITEM, "prc_scry_event", TRUE, FALSE); // OnEquip RemoveEventScript(oPC, EVENT_ONACQUIREITEM, "prc_scry_event", TRUE, FALSE); // OnAcquire RemoveEventScript(oPC, EVENT_ONPLAYERREST_STARTED, "prc_scry_event", FALSE, FALSE); // OnRest RemoveEventScript(oPC, EVENT_ONCLIENTENTER, "prc_scry_event", TRUE, FALSE); //OnClientEnter // Move PC and inventory location lCopy = GetLocation(oCopy); DelayCommand(1.5f, AssignCommand(oPC, JumpToLocation(lCopy))); // Set the PC's hitpoints to be whatever the copy has int nOffset = GetCurrentHitPoints(oCopy) - GetCurrentHitPoints(oPC); if(nOffset > 0) ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectHeal(nOffset), oPC); else if (nOffset < 0) ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectDamage(-nOffset, DAMAGE_TYPE_MAGICAL, DAMAGE_POWER_ENERGY), oPC); // Schedule deletion of the copy DelayCommand(0.3f, MyDestroyObject(oCopy)); //Delete the object reference DeleteLocalObject(oPC, COPY_LOCAL_NAME); // VFX ApplyEffectAtLocation(DURATION_TYPE_TEMPORARY, eLight, lCopy, 3.0); DestroyObject(oCopy); //Remove duration marker DeleteLocalInt(oPC, "SCRY_EXPIRED"); } //Runs tests to see if the projection effect can still continue. //If the PC has reached 1 HP, end projection normally. //If the copy is dead, end projection and kill the PC. void ScryMonitor(object oPC, object oCopy) { if(DEBUG) DoDebug("prc_inc_scry: ScryMonitor():\n" + "oPC = '" + GetName(oPC) + "'\n" + "oCopy = '" + GetName(oCopy) + "'" ); // Abort if the projection is no longer marked as being active if(!GetLocalInt(oPC, "Scry_Active")) return; // Some paranoia in case something interfered and either PC or copy has been destroyed if(!(GetIsObjectValid(oPC) && GetIsObjectValid(oCopy))){ WriteTimestampedLogEntry("Baelnorn Projection hearbeat aborting due to an invalid object. Object status:" + "\nPC - " + (GetIsObjectValid(oPC) ? "valid":"invalid") + "\nCopy - " + (GetIsObjectValid(oCopy) ? "valid":"invalid")); return; } // Start the actual work by checking the copy's status. The death thing should take priority if(GetIsDead(oCopy)) { if (DEBUG) DoDebug("prc_inc_scry: Copy is dead, killing PC"); DoScryEnd(oPC, oCopy); effect eKill = EffectDamage(9999, DAMAGE_TYPE_MAGICAL, DAMAGE_POWER_ENERGY); DelayCommand(3.0, ApplyEffectToObject(DURATION_TYPE_INSTANT, eKill, oPC)); } else { // Check if the "projection" has been destroyed or if some other event has caused the projection to end if(GetLocalInt(oPC, "Scry_Abort")) { if(DEBUG) DoDebug("prc_inc_scry: ScryMonitor(): The Projection has been terminated, ending projection"); DoScryEnd(oPC, oCopy); } else { // This makes sure you are invisible. //AssignCommand(oPC, ClearAllActions(TRUE)); ApplyEffectToObject(DURATION_TYPE_TEMPORARY, ExtraordinaryEffect(EffectEthereal()), oPC, SCRY_HB_DELAY + 0.3); DelayCommand(SCRY_HB_DELAY, ScryMonitor(oPC, oCopy)); } //If duration expired, end effect if(GetLocalInt(oPC, "SCRY_EXPIRED")) { DoScryEnd(oPC, oCopy); } // End due to being in combat, except when ephemeral image if(GetIsInCombat(oPC) && GetLocalInt(oPC, "ScrySpellId") != MYST_EPHEMERAL_IMAGE) { DoScryEnd(oPC, oCopy); if(DEBUG) DoDebug("prc_inc_scry: ScryMonitor(): Scryer in combat, ending projection"); AssignCommand(oPC, ClearAllActions(TRUE)); } // In PnP, once you lose line of effect, the spell ends float fDistance = FeetToMeters(100.0 + GetLocalInt(oPC, "ScryCasterLevel") * 10.0); if (GetLocalInt(oPC, "ScrySpellId") == MYST_EPHEMERAL_IMAGE && GetDistanceBetween(oPC, oCopy) > fDistance) { DoScryEnd(oPC, oCopy); if(DEBUG) DoDebug("prc_inc_scry: ScryMonitor(): Ephemeral Image distance exceeded, ending projection"); AssignCommand(oPC, ClearAllActions(TRUE)); } } } //Undoes changes made in ApplyScryEffects(). void RemoveScryEffects(object oPC) { if(DEBUG) DoDebug("prc_inc_scry: RemoveScryEffects():\n" + "oPC = '" + GetName(oPC) + "'" ); effect eCheck = GetFirstEffect(oPC); while(GetIsEffectValid(eCheck)){ if(GetEffectSpellId(eCheck) == GetLocalInt(oPC, "ScrySpellId")) { RemoveEffect(oPC, eCheck); } eCheck = GetNextEffect(oPC); } // Remove the no-damage property from all weapons it was added to int i; object oWeapon; for(i = 0; i < array_get_size(oPC, "Scry_Nerfed"); i++) { oWeapon = array_get_object(oPC, "Scry_Nerfed", i); IPRemoveMatchingItemProperties(oWeapon, ITEM_PROPERTY_NO_DAMAGE, DURATION_TYPE_PERMANENT); } array_delete(oPC, "Scry_Nerfed"); } int GetIsScrying(object oPC) { return GetLocalInt(oPC, "Scry_Active"); } void DiscernLocation(object oPC, object oTarget) { // Tells the name of the area the target is in // Yes, this is a little crappy for an 8th level spell. FloatingTextStringOnCreature(GetName(oTarget) + " is in " + GetName(GetArea(oTarget)), oPC, FALSE); // Delete all of the marker ints for the spell DeleteLocalInt(oPC, "ScryCasterLevel"); DeleteLocalInt(oPC, "ScrySpellId"); DeleteLocalInt(oPC, "ScrySpellDC"); DeleteLocalFloat(oPC, "ScryDuration"); } void LocateCreatureOrObject(object oPC, object oTarget) { location lPC = GetLocation(oPC); location lTarget = GetLocation(oTarget); // Cause the caller to face the target SetFacingPoint(GetPosition(oTarget)); // Now spit out the distance float fDist = GetDistanceBetweenLocations(lPC, lTarget); FloatingTextStringOnCreature(GetName(oTarget) + " is " + FloatToString(MetersToFeet(fDist)) + " feet away from you.", oPC, FALSE); // Delete all of the marker ints for the spell DeleteLocalInt(oPC, "ScryCasterLevel"); DeleteLocalInt(oPC, "ScrySpellId"); DeleteLocalInt(oPC, "ScrySpellDC"); DeleteLocalFloat(oPC, "ScryDuration"); } void CleanCopy(object oImage) { SetLootable(oImage, FALSE); // remove inventory contents object oItem = GetFirstItemInInventory(oImage); while(GetIsObjectValid(oItem)) { SetPlotFlag(oItem,FALSE); if(GetHasInventory(oItem)) { object oItem2 = GetFirstItemInInventory(oItem); while(GetIsObjectValid(oItem2)) { object oItem3 = GetFirstItemInInventory(oItem2); while(GetIsObjectValid(oItem3)) { SetPlotFlag(oItem3,FALSE); DestroyObject(oItem3); oItem3 = GetNextItemInInventory(oItem2); } SetPlotFlag(oItem2,FALSE); DestroyObject(oItem2); oItem2 = GetNextItemInInventory(oItem); } } DestroyObject(oItem); oItem = GetNextItemInInventory(oImage); } // remove non-visible equipped items int i; for(i=0;i