Custom summons (and PC mode switch)



  • A few people are interested to know how to make custom summon monsters so here is some information on how it all works.

    Mr.Moloch made a system like this available on the nwn vault. I have not tested it though and it is a bit different from what we use in CoA.

    So if you want to make custom summons, you will have to first build your summons as hostile NPC creatures to have them available in your module's palette. They are made hostile to avoid having people kicking them from their party to summon a huge army of allies. They are also hostile because some hostile NPCs might also cast that spell! Don't forget to give a unique resref for each of your summons. Lastly, you have to give the correct set of scripts on those creatures. Use the one that henchmen have.

    Once you have made your custom summons, you have to change the nw_s0_summon script. Here is a template of what it should look like, but this will not work alone, you will need to edit a few things, read the part I marked in those comments /* */.

    //::///////////////////////////////////////////////
    //:: Summon Creature Series
    //:: NW_S0_Summon
    //:: Copyright (c) 2001 Bioware Corp.
    //:://////////////////////////////////////////////
    /**
     ** Carries out the summoning of the appropriate creature
     ** for the Summon Monster series of spells I to IX
     **
     ** Since the summoned creatures are added as henchmen, you need to modify
     ** the module OnRest event to remove all summoned creatures (unless that
     ** behavior is intended)
     **/
    #include "x0_i0_common"
    #include "x2_inc_spellhook"
    
    //creatures for each themes are listed in this file
    #include "cs_summon__inc"
    
    void main()
    {
        if (!X2PreSpellCastCode())
        {
            // If code within the PreSpellCastHook (i.e. UMD) reports FALSE, do not run this spell
            return;
        }
    
        //** the spellcaster
        object oPC = OBJECT_SELF;
        //** the spell ID
        int nSpellID = GetSpellId();
        //** this will store the resref of what is summoned
        string sSummon = "";
        //** the duration in round/level can be changed here
        float nDuration = IntToFloat(GetCasterLevel(OBJECT_SELF)) * 1.0;
    /*
    You should change the duration to what fits your seting
    */
    
        //** make metamagic check for extended spells
        int nMetaMagic = GetMetaMagicFeat();
        if (nMetaMagic == METAMAGIC_EXTEND)
        {
            nDuration *= 2.0;
        }
    
        //** determine which visual effect I should use.
        int iVFX;
        //** the level of the summon is stored here
        int iLevel = 0;
        switch(nSpellID)
        {
            case SPELL_SUMMON_CREATURE_I:
                iLevel = 1; iVFX = VFX_FNF_SUMMON_MONSTER_1; break;
            case SPELL_SUMMON_CREATURE_II:
                iLevel = 2; iVFX = VFX_FNF_SUMMON_MONSTER_1; break;
            case SPELL_SUMMON_CREATURE_III:
                iLevel = 3; iVFX = VFX_FNF_SUMMON_MONSTER_1; break;
            case SPELL_SUMMON_CREATURE_IV:
                iLevel = 4; iVFX = VFX_FNF_SUMMON_MONSTER_2; break;
            case SPELL_SUMMON_CREATURE_V:
                iLevel = 5; iVFX = VFX_FNF_SUMMON_MONSTER_2; break;
            case SPELL_SUMMON_CREATURE_VI:
                iLevel = 6; iVFX = VFX_FNF_SUMMON_MONSTER_2; break;
            case SPELL_SUMMON_CREATURE_VII:
                iLevel = 7; iVFX = VFX_FNF_SUMMON_MONSTER_3; break;
            case SPELL_SUMMON_CREATURE_VIII:
                iLevel = 8; iVFX = VFX_FNF_SUMMON_MONSTER_3; break;
            case SPELL_SUMMON_CREATURE_IX:
                iLevel = 9; iVFX = VFX_FNF_SUMMON_MONSTER_3; break;
        }
    
        // By default, reagents are used unless it's turned off somehow
        int iDoNotUseReagents = GetLocalInt(oPC, "iDoNotUseReagents");
    
        // Determine What I Summon Here!
        // Let's see if I used a reagent that overrides my normal summons.
        if (sSummon == "" && iDoNotUseReagents == 0)
        {
    /*
    You'll have to use something similar to the PC mode switch on Arabel to make this work properly by targeting items and runing a script.
    */
    
            object oObj = GetLocalObject(oPC, "oLastTargettedItem");
            if (GetIsObjectValid(oObj) && GetItemPossessor(oObj) == oPC)
            {
                // Get the tag of the targeted item and determine what it would summon
                string sTag = GetTag(oObj);
                if (sTag == "ReagentTagForTheme1") {            // summon theme 1
                    sSummon = GetSummonResRefTheme1(nSpellID);
                } else if (sTag == "ReagentTagForTheme2") {     // summon theme 2
                    sSummon = GetSummonResRefTheme2(nSpellID);
                } else if (sTag == "ReagentTagForTheme3") {     // summon theme 3
                    sSummon = GetSummonResRefTheme3(nSpellID);
                }
    /*
    You can add more reagents if you want
    I strongly suggest you give better names to GetSummonResRefTheme# and the reagents tags
    Those I wrote here are just to keep the example generic
    */
                if (sSummon != "")
                {
                    SpeakString("*magical energy courses from your hands to the " + GetName(oObj) + " in your pack*", TALKVOLUME_WHISPER);
                    DestroyObject(oObj);
                }
            }
        }
    
        // Let's see if I have a summoning book or item that overrides my normal summons.
        if (sSummon == "" && iDoNotUseReagents == 0)
        {
    /*
    You'll have to use something similar to the PC mode switch on Arabel to make this work properly by targeting items and runing a script.
    */
            object oObj = GetLocalObject(oPC, "oLastTargettedItem" );
            if (GetIsObjectValid(oObj) && GetItemPossessor(oObj) == oPC)
            {
                // Get the tag of the targeted item and determine what it would summon
                string sTag = GetTag(oObj);
                if (sTag == "BookTagForTheme1") {            // summon theme 1
                    sSummon = GetSummonResRefTheme1(nSpellID);
                } else if (sTag == "BookTagForTheme2") {     // summon theme 2
                    sSummon = GetSummonResRefTheme2(nSpellID);
                } else if (sTag == "BookTagForTheme3") {     // summon theme 3
                    sSummon = GetSummonResRefTheme3(nSpellID);
                }
    /*
    You can add more reagents if you want
    I strongly suggest you give better names to GetSummonResRefTheme# and the reagents tags
    Those I wrote here are just to keep the example generic
    */
            }
        }
    
        // Let's see if I have a custom theme
        if (sSummon == "")
        {
    /*
    I completely removed this portion since it's really specific to CoA, but basically, it would look like
    the two sections above except instead of checking if they have reagents or books, you will check if they
    have a deity, a classe, a race, a feat, an alignement or anything special that would grant them a special
    summoning theme.
    It's really up to you to determine who will have access to which theme in your module and the main default
    ones that work without reagents or books should be defined here.
    I'll just leave an example
    */
            //I have no regeants, so continue.
            if (GetDeity(oPC) == "DeityTheme1") {             // summon theme 1
                sSummon = GetSummonResRefTheme1(nSpellID);
            } else if (GetRacialType(oPC) == RACIAL_TYPE_DWARF) { // summon theme 2 for dwarf, but you could specify another race!
                sSummon = GetSummonResRefTheme2(nSpellID);
            }
        }
    
        //** in case I could not determine anything, fall back on the old functions
        if (sSummon == "")
        {
            sSummon = GetSummonResRefDefault(nSpellID);
        }
    
        //** here I will store the summoned creature effect
        effect eSummon;
    
        //** if it's not a PC
        if (!GetIsPC(OBJECT_SELF))
        {
            //** NPCs do not have henches
            eSummon = EffectSummonCreature(sSummon, iVFX);
            ApplyEffectAtLocation(DURATION_TYPE_TEMPORARY, eSummon, GetSpellTargetLocation(), RoundsToSeconds(FloatToInt(nDuration)));
        } else {
            //** here I will store the summoned creature object
            object oSummon;
            int iMaxSummons = 1;
    
            //** Mages specialising in conjuration will have special perks, as in
            //** having the summons longer and being able to summon more at the same time.
            if (GetHasFeat(FEAT_SPELL_FOCUS_CONJURATION))
            {
                iMaxSummons = 2;
                nDuration *= 1.25;
            }
            if (GetHasFeat(FEAT_GREATER_SPELL_FOCUS_CONJURATION))
            {
                //** does iMaxSummons ++; twice since taking GSF CONJ removes SF CONJ
                iMaxSummons = 3;
                //** this might have to be increased too, not sure?
                nDuration *= 1.25;
            }
    
            //** count the henchmen the PC has, and how many of them are summons
            int iCount;
            int iSummonCount = 0;
            object oHench;
            for (iCount=1; iCount= iMaxSummons)
            {
                //** the guy has more than he can hold, unsummon the first summons
                int i0;
                for (i0=1; i0 Lastly, make an include file (or define the function in the same script?) that will define each GetSummonResRefTheme# functions. They will look like this, but you can add even more diversification on what is summoned based on alignement, deity or even randomness!
    
    

    // cs_summon__inc made by Mr.Moloch, modified by SFP and Snowstorm

    // Function declarations
    string GetSummonResRefDefault(int nSpellID);
    string GetSummonResRefTheme1(int nSpellID);
    string GetSummonResRefTheme2(int nSpellID);
    /*
    You can add more function for new themes of course...
    */

    // Default theme
    string GetSummonResRefDefault(int nSpellID)
    {
    string sSummon;
    /*
    I'll leave it blank here, but don't forget to make a great default theme.
    The one we use on CoA gives different creatures depending of the caster's alignement.
    */
    return sSummon;
    }

    // Theme1
    string GetSummonResRefTheme1(int nSpellID)
    {
    string sSummon;

    switch (nSpellID) {
    case SPELL_SUMMON_CREATURE_I:
        sSummon = "Resref_Level1_Summon_Theme1";
        break;
    case SPELL_SUMMON_CREATURE_II:
        sSummon = "Resref_Level2_Summon_Theme1";
        break;
    case SPELL_SUMMON_CREATURE_III:
        sSummon = "Resref_Level3_Summon_Theme1";
        break;
    case SPELL_SUMMON_CREATURE_IV:
        sSummon = "Resref_Level4_Summon_Theme1";
        break;
    case SPELL_SUMMON_CREATURE_V:
        sSummon = "Resref_Level5_Summon_Theme1";
        break;
    case  SPELL_SUMMON_CREATURE_VI:
        sSummon = "Resref_Level6_Summon_Theme1";
        break;
    case SPELL_SUMMON_CREATURE_VII:
        sSummon = "Resref_Level7_Summon_Theme1";
        break;
    case SPELL_SUMMON_CREATURE_VIII:
        sSummon = "Resref_Level8_Summon_Theme1";
        break;
    case SPELL_SUMMON_CREATURE_IX:
        sSummon = "Resref_Level9_Summon_Theme1";
        break;
    }
    return sSummon;
    

    }

    // Theme2
    string GetSummonResRefTheme2(int nSpellID)
    {
    string sSummon;

    switch (nSpellID) {
    case SPELL_SUMMON_CREATURE_I:
        sSummon = "Resref_Level1_Summon_Theme2";
        break;
    case SPELL_SUMMON_CREATURE_II:
        sSummon = "Resref_Level2_Summon_Theme2";
        break;
    case SPELL_SUMMON_CREATURE_III:
        sSummon = "Resref_Level3_Summon_Theme2";
        break;
    case SPELL_SUMMON_CREATURE_IV:
        sSummon = "Resref_Level4_Summon_Theme2";
        break;
    case SPELL_SUMMON_CREATURE_V:
        sSummon = "Resref_Level5_Summon_Theme2";
        break;
    case  SPELL_SUMMON_CREATURE_VI:
        sSummon = "Resref_Level6_Summon_Theme2";
        break;
    case SPELL_SUMMON_CREATURE_VII:
        sSummon = "Resref_Level7_Summon_Theme2";
        break;
    case SPELL_SUMMON_CREATURE_VIII:
        sSummon = "Resref_Level8_Summon_Theme2";
        break;
    case SPELL_SUMMON_CREATURE_IX:
        sSummon = "Resref_Level9_Summon_Theme2";
        break;
    }
    return sSummon;
    

    }

    // Theme3
    string GetSummonResRefTheme3(int nSpellID)
    {
    string sSummon;

    switch (nSpellID) {
    case SPELL_SUMMON_CREATURE_I:
        sSummon = "Resref_Level1_Summon_Theme3";
        break;
    case SPELL_SUMMON_CREATURE_II:
        sSummon = "Resref_Level2_Summon_Theme3";
        break;
    case SPELL_SUMMON_CREATURE_III:
        sSummon = "Resref_Level3_Summon_Theme3";
        break;
    case SPELL_SUMMON_CREATURE_IV:
        sSummon = "Resref_Level4_Summon_Theme3";
        break;
    case SPELL_SUMMON_CREATURE_V:
        sSummon = "Resref_Level5_Summon_Theme3";
        break;
    case  SPELL_SUMMON_CREATURE_VI:
        sSummon = "Resref_Level6_Summon_Theme3";
        break;
    case SPELL_SUMMON_CREATURE_VII:
        sSummon = "Resref_Level7_Summon_Theme3";
        break;
    case SPELL_SUMMON_CREATURE_VIII:
        sSummon = "Resref_Level8_Summon_Theme3";
        break;
    case SPELL_SUMMON_CREATURE_IX:
        sSummon = "Resref_Level9_Summon_Theme3";
        break;
    }
    return sSummon;
    

    }

    /*
    You can add more function for new themes of course...
    */

    /*
    This one may be useful if you take alignement in consideration to determine what will be summoned.
    /*

    // LG=0 NG=1 CG=2
    // LN=3 TN=4 CN=5
    // LE=6 NE=7 CE=8
    int DNDAlignment(object oPC);
    int DNDAlignment(object oPC)
    {
    int i;
    if(GetAlignmentGoodEvil(oPC)==ALIGNMENT_GOOD) {
    switch(GetAlignmentLawChaos(oPC)) {
    case ALIGNMENT_LAWFUL: i=0; break;
    case ALIGNMENT_NEUTRAL: i=1; break;
    case ALIGNMENT_CHAOTIC: i=2; break;
    }
    }
    if(GetAlignmentGoodEvil(oPC)==ALIGNMENT_NEUTRAL) {
    switch(GetAlignmentLawChaos(oPC)) {
    case ALIGNMENT_LAWFUL: i=3; break;
    case ALIGNMENT_NEUTRAL: i=4; break;
    case ALIGNMENT_CHAOTIC: i=5; break;
    }
    } else if(GetAlignmentGoodEvil(oPC)==ALIGNMENT_EVIL) {
    switch(GetAlignmentLawChaos(oPC)) {
    case ALIGNMENT_LAWFUL: i=6; break;
    case ALIGNMENT_NEUTRAL: i=7; break;
    case ALIGNMENT_CHAOTIC: i=8; break;
    }
    }
    return i;
    }

    
    With those template and informations, you'll have much less debugging to do and should be able to develop a nice summoning system.
    
    Something similar could also be done for polymorph spells, but it would be a lot of work.
    


  • Thank you for this!



  • For the record, my system is slightly bugged. I'll offer suggestions on adapting it or happily even fix the bugs someday soon.



  • I edited very slightely the include file needed for this to work.



  • Updated so that it compiles correctly when you plug this script directly in a blank module.



  • This scripting is for a working Mode Switch. For those who don't understand or know how it works.

    I have confirmed this will work with the nw_s0_summon and cs_summon__inc scripts linked here, in a blank module.

    To create a Mode Switch:

    1. Use the Toolset to create a new Miscellaneous Small item. Name it "Mode Switch" (without the "). This way, the tag/resref will be modeswitch.

    2. Create two blank scripts in the Script Editor. They should look like this:

    //modeswitch
    
    #include "x2_inc_switches"
    
    void main()
    {
        int nEvent =GetUserDefinedItemEventNumber();
        switch (nEvent)
        {
            case X2_ITEM_EVENT_ACTIVATE: ExecuteScript("ac_"+GetTag(GetItemActivated()), OBJECT_SELF); break;
        }
    }
    
    /* Script generated by
    Lilac Soul's NWN Script Generator, v. 2.3
    
    For download info, please visit:
    http://nwvault.ign.com/View.php?view=Other.Detail&id=4683&id=625 */
    
    //ac_modeswitch
    
    void main()
    {
        object oPC = GetItemActivator();
        object oTarget = GetItemActivatedTarget();
    
        SetLocalObject(oPC, "oLastTargettedItem", oTarget);
        SendMessageToPC(GetItemActivator(), "You take hold of the " + GetName(oTarget) + " and concentrate.");
    }
    

    The second script is special, in that when you target a reagent, it will speak a line in your dialog. This line will fire no matter what you target:

    You take hold of the Light Crossbow and concentrate.
    You take hold of the Fiery Stick of Dhoom and concentrate.
    You take hold of the Slaad's Tongue and concentrate.

    Any of these could pop up, depending on what you target. But they aren't all going to have reagent themes. (Slaad's Tongue, however, does.)

    The Mode Switch just selects what you want to attempt to use as a reagent. If it is, in fact, a reagent, when you cast Summon Creature (1-9), you will receive a special creature.

    If you target something that isn't a reagent, you will fall back to either a Deity/Racial/Alignment theme, or the default theme, as a last resort. So the spell won't be wasted.

    I hope it was okay to post this here for other scripters, to help those who may not have understood how a Mode Switch worked. If not, you can deleted as needed.


Log in to reply