Undead 2.0

Started by Cruzel, October 16, 2019, 08:06:22 PM

Previous topic - Next topic

Cruzel

Ok.  These are scripts that work 100% as-is, but will need some tweaks to work properly with EFU systems. I left in the placeholders and debugs so other workshop friends can fiddle :)

This requires a lot of little tweaks to a lot of scripts, so here's the breakdown:

What this all does:

Creates an object rather than a summon effect. This means the zombo will remain after logout, fully buffed, enchanced and whatnot. It can be freely released and reclaimed, allowing necromancers to "Store" their undead friends in a secluded place while they visit hubs and such, and reclaim them later. They are flagged appropriately so that dismissal/banishment can still affect them, despite not being summons.

ANIMATE DEAD SPELLSCRIPT
[hide]  Not much of this is needed.  Delete the EffectSummonCreatre() and replace it with  all code between "KEEP BELOW" and "KEEP ABOVE" Swap out the variable names for what is used for EFU summoning points.  (Possibly necessary:  Add function from a later snippet to manually unsummon mirror images/other summons, but ONLY IF efu's spellhook does not handle this already)
Quote#include "x2_inc_spellhook"

void main()
{

/*
  Spellcast Hook Code
  Added 2003-06-23 by GeorgZ
  If you want to make changes to all spells,
  check x2_inc_spellhook.nss to find out more

*/

    if (!X2PreSpellCastCode())
    {
    // If code within the PreSpellCastHook (i.e. UMD) reports FALSE, do not run this spell
        return;
    }

// End of Spell Cast Hook


    //Declare major variables
    int nMetaMagic = GetMetaMagicFeat();
    object oPC = OBJECT_SELF;
    int nCasterLevel = GetCasterLevel(OBJECT_SELF);
    int nDuration = GetCasterLevel(OBJECT_SELF);
    nDuration = 24;
    //effect eVis = EffectVisualEffect(VFX_FNF_SUMMON_UNDEAD);
    effect eSummon;
    //Metamagic extension if needed
    int nSummoningPoints = GetLocalInt(oPC, "nSummonPool");   // REPLACE WITH EFU VARIABLES
    int nSummonMax       = GetLocalInt(oPC, "nSumMax");       // Replace with EFU VARIABLES

// Placeholder mostly. The ONLY line that matters here for efu is between the next comments

    if (nSummoningPoints <= nSummonMax)  // Check for room in summoning pool. Existing EFU  Function, insert below code into that function
      {
      // KEEP BELOW
       string sTag = "NW_S_ZOMBTYRANT";  // Replace this placeholder with whatever you use to determine what strref animate dead summons.
       object oZombo = CreateObject(OBJECT_TYPE_CREATURE, sTag, GetSpellTargetLocation(), FALSE, "zombo_"+GetPCPublicCDKey(oPC));
        ApplyEffectAtLocation(DURATION_TYPE_INSTANT, EffectVisualEffect(VFX_FNF_SUMMON_UNDEAD), GetSpellTargetLocation());
        SetLocalInt(oZombo, "nDismissable", 1); // Add variable for dismissal and banishment to see it as a target
        AddHenchman(oPC, oZombo);


        SetLocalInt(oZombo, "nSummonCost", 10);    // placeholder for debug so I wouldn't have to make a creature template w/ variables. Delete this.
        SetLocalInt(oPC, "nSummonPool", nSummoningPoints + GetLocalInt(oZombo, "nSummonCost")); // REPLACE WITH EFU VARIABLES FOR CURRENT POOL AND THE SUMMON'S COST

      // KEEP ABOVE

// INSERT OPTIONAL SUMMON/MIRROR HANDLES HERE IF NEEDED.





      }
}

[/hide]

Update to RELEASE UNDEAD player tool:   (Rename it to Release/Reclaim Undead)
[hide]

Very straightforward. If undead is clicked while owned, it releases.   If  undead is clicked by a PC who didn't make it, or a PC who is at/over their summoning pool limit, nothing happens.  Otherwise if clicking their own zombies while released (or after logout) the zombie is reclaimed.  Clicking on self will recalculate the summoning pool for the PC and fix any errors.

Quote
void main()
{
object oPC = OBJECT_SELF;
object oTarget = GetSpellTargetObject();
location lTarget = GetSpellTargetLocation();
string sTag = GetTag(oTarget);
int nMax = GetLocalInt(oPC, "nSumMax");               // Replace with EFU variables for summoning pool CAP
int nCost = GetLocalInt(oTarget, "nSummonCost");      // Replace with EFU variables for how much the minion costs
int nPool = GetLocalInt(oPC, "nSummonPool");          // Replace with EFU variables for how many points PC is currently using from pool.

//Manually recalculate summoning pool if self is targeted. This closes off errors caused by edge cases

if (oTarget == oPC)
{
int i =1;
SetLocalInt(oPC, "nSummonPool", 0);
object oHench = GetAssociate(ASSOCIATE_TYPE_HENCHMAN, oPC, i);
object oSummon = GetAssociate(ASSOCIATE_TYPE_SUMMONED, oPC, i);
// REPLACE WITH EFU VARIABLES. Loops through each hench, grabbing their variables, then each minion
while (GetIsObjectValid(oHench) != FALSE)
   {
    nPool = GetLocalInt(oPC, "nSummonPool");
    nCost = GetLocalInt(oHench, "nSummonCost");
    SetLocalInt(oPC, "nSummonPool", nPool + nCost);
    oHench = GetAssociate(ASSOCIATE_TYPE_HENCHMAN, oPC, i++);
   }
// Remove below, if summons + undead at same time undesired.
i = 1;

    while (GetIsObjectValid(oSummon) != FALSE)
     {
        nPool = GetLocalInt(oPC, "nSummonPool");
        nCost = GetLocalInt(oSummon, "nSummonCost");
        SetLocalInt(oPC, "nSummonPool", nPool + nCost);
         oSummon = GetAssociate(ASSOCIATE_TYPE_SUMMONED, oPC, i++);
     }
// Remove above if summons + undead undesired

//Feedback Message.
  SendMessageToPC(oPC, "Summoning Pool Recalculated: You now have " + IntToString(GetLocalInt(oPC, "nSummonPool")) + " out of " + IntToString(GetLocalInt(oPC, "nSumMax")) + " points available ");
  return;
}


// Auth Check to See if the PC created this zombo; Play message and abort if not the creator
if (sTag != "zombo_"+GetPCPublicCDKey(oPC)) { SendMessageToPC(oPC, "This creature was not animated by you, or is not undead. It will not follow your commands."); return; }



// Auth Check to verify summoning pool cap. Placeholder function, as this can be copy/pasted from summon scripts.
if ((nPool + nCost) >= nMax )
{ SendMessageToPC(oPC, "This creature is too powerful to control in addition to those you have already summoned"); return; }


// Remove the Henchman if already controlled. Modify summoning pool accordingly.

if (GetMaster(oTarget) == oPC)

  {
   RemoveHenchman(oPC, oTarget);
   SetLocalInt(oPC, "nSummonPool", nPool - nCost);  // Lower pool cost. Replace variables with EFU ones
   SendMessageToPC(oPC, "This minion has been released from your control and will act of its own accord"); //Debug, remove if desired.
   // add VFX if desired.
  }


// Otherwise, we take control and modify the pool;
else
{
   AddHenchman(oPC, oTarget);
   ClearPersonalReputation(oTarget, oPC); // Edge Case handle: if the PC had hit them with an AOE or something while released. Stop your own zombos from killing you while blue.
   SetLocalInt(oPC, "nSummonPool", nPool + nCost);  // Lower pool cost. Replace variables with EFU ones
   SendMessageToPC(oPC, "This undead creature bends once more to your will as your magics influence it."); //Debug, remove if desired.
  // add VFX if desired.


  // BELOW TWO FUNCTIONS PERFORM SAME PURPOSE. CHOOSE WHICH IS IDEAL

                                   // OPTION ONE:

   //  INSERT "Fake" SUMMON EVENT HERE TO UNSUMMON MIRROR IMAGES (AND OTHER SUMMONS IF DESIRED) Ugly, but cleanest and cheapest for CPU. Debug mostly.
   // Recommended not to use this unless necessary.
   ApplyEffectAtLocation(DURATION_TYPE_TEMPORARY, EffectSummonCreature(""),lTarget, 0.01);


                              // OPTION TWO

     //  This lets you keep summons out but will destroy mirror images. "nMirror" must be swapped to whatever identifier you have on mirror clones.
   //  Or manually loop associates and poof the images. Less ideal, more GPU cost.
   int i =1;
   object oMirror = GetAssociate(ASSOCIATE_TYPE_DOMINATED, oPC, i);

   while (GetIsObjectValid(oMirror) != FALSE)
   {
     if (GetLocalInt(oMirror, "nMirror") == 1)    // make sure they're flagged as a mirror image, prevents shenanigans with things the PC didn't create
      {
       DestroyObject(oMirror);
      }
    oMirror = GetAssociate(ASSOCIATE_TYPE_DOMINATED, oPC, i++);
   }

}


}
[/hide]

OnModuleLoad()
[hide]
Here, all we need is one line.
Quote

SetMaxHenchmen(X);

Replace X with something reasonable (20-25?).  This variable controls the maximum amount of "henchmen" type minions a PC can have. Set it too low, and a player might hit the cap despite having more room in their pool. 

[/hide]

Zombie OnDeath() Event:
[hide]
Very Simple  addition;  Allow us to  make sure summon pool updates properly as zombos die.
Quote
object oMaster = GetMaster(OBJECT_SELF);
SetLocalInt(oMaster, "nSummonPool",GetLocalInt(oMaster, "nSummonPool" ) -  GetLocalInt(OBJECT_SELF, "nSummonCost"));
[/hide]

On client enter
[hide]
This is probably already a thing. Clear the summoning pool variable, since the PC won't be controlling anything when they login.  if this isn't there, use appropriate summoning pool variable
Quote
SetLocalInt(oPC, "nSummonPool", 0);
/quote]
[/hide]
DISMISSAL/BANISHMENT CHANGES

[hide]

There's probably already another function added to these to handle mirror images. You can pretty much copy/paste those.  and all you need to add is the following, to your conditional statement.

Quote
|| GetLocalInt(oTarget, "nDismissable") == 1

The code in practice, for the workship fiddlers;

Quote
#include "X0_I0_SPELLS"
#include "x2_inc_spellhook"

void main()
{

/*
  Spellcast Hook Code
  Added 2003-06-20 by Georg
  If you want to make changes to all spells,
  check x2_inc_spellhook.nss to find out more

*/

    if (!X2PreSpellCastCode())
    {
    // If code within the PreSpellCastHook (i.e. UMD) reports FALSE, do not run this spell
        return;
    }

// End of Spell Cast Hook


    //Declare major variables
    object oMaster;
    effect eVis = EffectVisualEffect(VFX_IMP_UNSUMMON);
    effect eImpact = EffectVisualEffect(VFX_FNF_LOS_EVIL_30);
    ApplyEffectAtLocation(DURATION_TYPE_INSTANT, eImpact, GetSpellTargetLocation());
    int nSpellDC;
    //Get the first object in the are of effect
    object oTarget = GetFirstObjectInShape(SHAPE_SPHERE, RADIUS_SIZE_COLOSSAL, GetLocation(OBJECT_SELF));
    while(GetIsObjectValid(oTarget))
    {
        //does the creature have a master.
        oMaster = GetMaster(oTarget);
        //Is that master valid and is he an enemy
        if((GetIsObjectValid(oMaster) && spellsIsTarget(oMaster,SPELL_TARGET_STANDARDHOSTILE, OBJECT_SELF )) || GetLocalInt(oTarget, "nDismissable") == 1 )
        //added zombo condition to above
        {
            //Is the creature a summoned associate
            if(GetAssociate(ASSOCIATE_TYPE_SUMMONED, oMaster) == oTarget ||
               GetAssociate(ASSOCIATE_TYPE_FAMILIAR, oMaster) == oTarget ||
               GetAssociate(ASSOCIATE_TYPE_ANIMALCOMPANION, oMaster) == oTarget
               || GetLocalInt(oTarget, "nDismissable") == 1 )     // Zombo dismissing addition
            {
                SignalEvent(oTarget, EventSpellCastAt(OBJECT_SELF, SPELL_DISMISSAL));
                //Determine correct save
                nSpellDC = GetSpellSaveDC() + 6;
                //Make SR and will save checks
                if (!MyResistSpell(OBJECT_SELF, oTarget) && !MySavingThrow(SAVING_THROW_WILL, oTarget, nSpellDC))
                {
                     //Apply the VFX and delay the destruction of the summoned monster so
                     //that the script and VFX can play.
                     ApplyEffectToObject(DURATION_TYPE_INSTANT, eVis, oTarget);
                     DestroyObject(oTarget, 0.5);
                }
            }
        }
        //Get next creature in the shape.
        oTarget = GetNextObjectInShape(SHAPE_SPHERE, RADIUS_SIZE_COLOSSAL, GetLocation(OBJECT_SELF));
    }
}
[/hide]

KNOWN ISSUES:

Really the only big ones are:
- if the PC clicks "remove from party" from the radial, instead of using the release tool, the summoning pool will not change to reflect this.  A NWNX hook probably exists that can fix this. Clicking the player tool on self fixes this.
-Players cannot unsummon from radial anymore.   A player tool or /c command would be needed to destroy them manually. (Personally,  I like the idea of having undead linger unless physically destroyed)

Sem

This is absolutely phenomenal.