EQEmulator Forums

EQEmulator Forums (https://www.eqemulator.org/forums/index.php)
-   Development::Bug Reports (https://www.eqemulator.org/forums/forumdisplay.php?f=591)
-   -   NPC Spell List Recast Delay Not Working (https://www.eqemulator.org/forums/showthread.php?t=25701)

trevius 07-15-2008 05:17 PM

NPC Spell List Recast Delay Not Working
 
I have done some testing with the recast delay setting for NPC spell lists and it doesn't seem to be having any effect at all. If I am wrong, or just missing something to get it working properly, this post could be moved to the general or server support section.

I first tried setting the recast delay in seconds, thinking that it would be used similar to the recast delay setting for item clickies. That did nothing and did not effect the actual reuse time that NPCs were able to cast the same spell again.

So, I then tried changing it to Milliseconds and that too seemed to have no effect. Even if I set it as high as 1200000, which should be 20 minutes in milliseconds, the NPCs could still re-cast the same spell withing just a few seconds of having cast it. And, they can just cast it over and over again as if it is ignoring that restriction completely.

I am aware that any changes to spell lists requires a server reboot to take effect and I have been doing reboots after each new change I make.

What I am trying to do is use the spell version of Melee disciplines for Warrior, Rogue, and Monk to create a spell list that will let my melee NPCs disc for the new zone I am making. I am also adding discs to my highest level pet class pets (mage, necro, beastlord). I think it will add some nice diversity.

Unfortunately, with the way it is currently working, they are firing off disciplines every few swings and without recast delays working, I will be forced to remove this fun feature.

I am wondering if the code for recast delay on item clickies and player spells might be useful in getting the NPC spells list recast delays working properly.

I also wanted to note that I have the disciplines set as type 1 and type 2 for spell type in the lists. I used type 1 (nuke setting) for damage increasing discs, and type 2 (heal setting) for defensive disciplines.

moydock 07-15-2008 10:21 PM

I believe I had this working a couple months back when I changed all my npc's. I'll test again and let you know.

moydock 07-15-2008 10:39 PM

Yeah mines working fine. I'm not on the latest build, but fairly recent.
It's in seconds for me. For example, the heal I just tested:
ID: 367
npc_spells_id: 6
spellid: 200
type: 2
minlevel: 1
maxlevel: 8
manacost: -1
recast_delay: 30
priority: 1

So_1337 07-16-2008 09:32 AM

I noticed this recently, and it's especially obvious among death-touching mobs like the Golems in Fear. I was even considering a work-around.

Currently, they cast it shortly after they're first aggro'd, and then you never see it again. On Live, it had a refresh timer of one minute, and they'd nuke whoever was at the top of their hate list. I know perl isn't the best solution for this, but I considered writing a script that would fire it off once a minute as it should, using a timer.

But if this recast delay and priority thing is straightened out, it'd be much better, for sure.

trevius 07-16-2008 04:08 PM

Maybe it has something to do with the Spell Type setting for the NPC Spell Lists. I am wondering if only certain types will recognize the Recast Delay setting and some will ignore it. IMO, they should all follow the Recast Delay restriction. The ones I have tested so far are set to type 1, which is the Nuke setting. So, maybe the Nuke setting doesn't follow that restriction. Maybe I can try setting them to another type (maybe the DoT setting) and see if that makes any difference. Though, I still think that they should all follow the Recast Delay restriction.

moydock 07-16-2008 04:21 PM

I don't think it affects it. I'm only testing lvl 5 npc's but my druids never nuke twice in a row as well as not healing. I've got 15 second delay on their nukes too. They'll cast bust of flame, and then firefist, or whatever that dot is, but never 2 burst of flame in a row.

trevius 07-16-2008 07:50 PM

The proc chance might be why you aren't seeing them very close together. Try upping the proc chance up to 100% so that they should try to cast it every time it is available. Then, try setting your recast delay to something longer like 180 seconds and see if they cast it before that 3 minutes are up.

I will have to do more testing on it, but it is hard when I can only reset my server every now and then to test the changes, or it will effect my players too much if I reset alot.

moydock 07-17-2008 02:25 AM

I tried setting the proc rate to 100 and oddly they don't seem to try to cast any quicker. Like, I attack them, and they wait awhile to cast the first nuke. Possibly because they need to wait the recast delay before casting the first spell.

Trust me though, I've sat in front of these things a lot. I would have seen one double cast by now.
I originally implemented the recast so that healers wouldn't chain heal themselves when they got low on health. And they don't anymore. I guess it's possible this has been broken in one of the somewhat recent updates.

trevius 07-23-2008 04:16 AM

Well, I am still trying to figure out why this is happening on my server. I was looking at my variables table and I have a setting for "ailevel", and it is set to 6 (which is how it came from the PEQ db), but I don't really know what that does. So far, it is the only thing I can think of that might be causing this issue. I am running the latest version of the emu (1118) with some modified code additions, but nothing that I think would relate to this issue at all.

I was looking at the source and in npc.h, I see a public section that mentions ailevel, and has some recast delay info in there. So, I am wondering if maybe my ailevel is set wrong, and maybe setting it to 6 isn't enough for it to use recast delays...

Here is the code from the public section of npc.h that I am referring to (I highlighted a few things in RED cause I think they are involved in making the decision for recast delay settings):

Code:

class NPC : public Mob
{
public:
        static NPC* SpawnNPC(const char* spawncommand, float in_x, float in_y, float in_z, float in_heading = 0, Client* client = 0);
        static sint8 GetAILevel(bool iForceReRead = false);
       
        NPC(const NPCType* data, Spawn2* respawn, float x, float y, float z, float heading, bool IsCorpse = false);
       
        virtual ~NPC();

        virtual bool IsNPC() const { return true; }

        virtual bool Process();
        virtual void        AI_Init();
        virtual void        AI_Start(int32 iMoveDelay = 0);
        virtual void        AI_Stop();
        void                        AI_DoMovement();
        bool                        AI_AddNPCSpells(int32 iDBSpellsID);
        virtual bool        AI_EngagedCastCheck();
        virtual bool        AI_PursueCastCheck();

        virtual bool        AI_IdleCastCheck();
        virtual void        AI_Event_SpellCastFinished(bool iCastSucceeded, int8 slot);
       

        virtual void SetTarget(Mob* mob);
        virtual uint16 GetSkill(SkillType skill_num) const { if (skill_num <= HIGHEST_SKILL) { return skills[skill_num]; } return 0; }
/*  virtual void SetSkill(int in_skill_num, int8 in_skill_value) { // socket 12-29-01
        if (in_skill_num <= HIGHEST_SKILL) { skills[in_skill_num + 1] = in_skill_value; } }*/

        void CalcItemBonuses(StatBonuses *newbon);
        virtual void CalcBonuses();

       
#ifdef GUILDWARS
        int32        GetGuildLocationID() { return guildlocationid; }
#endif

        // neotokyo: added frenzy
        bool        Attack(Mob* other, int Hand = 13, bool = false);
        void        Damage(Mob* other, sint32 damage, int16 spell_id, SkillType attack_skill, bool avoidable = true, sint8 buffslot = -1, bool iBuffTic = false);
        void        Death(Mob* other, sint32 damage, int16 spell_id, SkillType attack_skill);
        bool        DatabaseCastAccepted(int spell_id);
        bool        IsFactionListAlly(uint32 other_faction);
        FACTION_VALUE CheckNPCFactionAlly(sint32 other_faction);
        FACTION_VALUE GetReverseFactionCon(Mob* iOther);

        void        GoToBind()        { GMMove(org_x, org_y, org_z, org_heading); }
        void        Gate();

        void        GetPetState(SpellBuff_Struct *buffs, int32 *items, char *name);
        void        SetPetState(SpellBuff_Struct *buffs, int32 *items);
        void        InteractiveChat(int8 chan_num, int8 language, const char * message, const char* targetname,Mob* sender);
        void        TakenAction(int8 action,Mob* actiontaker);
        virtual void SpellProcess();
        virtual void FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho);

        void        AddItem(const Item_Struct* item, int8 charges, int8 slot = 0);
        void        AddItem(int32 itemid, int8 charges, int8 slot = 0);
        void        AddLootTable();
/*
        void        NPCSpecialAttacks(const char* parse, int permtag);
        void        NPCDatabaseSpells(const char* parse);
        void        NPCUnharmSpell(int spell_id);
        void        CheckFriendlySpellStatus();

        void        CheckEnemySpellStatus();
        void        NPCHarmSpell(int target,int type);
        void    HateSummon();
*/       

        void        DescribeAggro(Client *towho, Mob *mob, bool verbose);
        void    RemoveItem(uint16 item_id, int16 quantity = 0, int16 slot = 0);
//        bool        AddNPCSpells(int32 iDBSpellsID, AISpells_Struct* AIspells);
//        void        RemoveItem(uint16 item_id);
        void        ClearItemList();
        ServerLootItem_Struct*        GetItem(int slot_id);
        void        AddCash(int16 in_copper, int16 in_silver, int16 in_gold, int16 in_platinum);
        void        AddCash();
        void        RemoveCash();
        void        QueryLoot(Client* to);
        int32        CountLoot();
        void        DumpLoot(int32 npcdump_index, ZSDump_NPC_Loot* npclootdump, int32* NPCLootindex);
        inline int32        GetLoottableID()        const { return loottable_id; }
//        void        SetPetType(int16 in_type)        { typeofpet = in_type; } // put this here because only NPCs can be anything but charmed pets

        inline uint32        GetCopper()                const { return copper; }
        inline uint32        GetSilver()                const { return silver; }
        inline uint32        GetGold()                const { return gold; }
        inline uint32        GetPlatinum()        const { return platinum; }

        inline void        SetCopper(uint32 amt)                { copper = amt; }
        inline void        SetSilver(uint32 amt)                { silver = amt; }
        inline void        SetGold(uint32 amt)                        { gold = amt; }
        inline void        SetPlatinum(uint32 amt)                { platinum = amt; }



        void SetGrid(int32 grid_){ grid=grid_; }
        void SetSp2(int32 sg2){ spawn_group=sg2; }
        void SetWaypointMax(int16 wp_){ wp_m=wp_; }

        int16 GetWaypointMax() const { return wp_m; }
        int32 GetGrid() const { return grid; }
        int32 GetSp2() const { return spawn_group; }

        uint32        MerchantType;
        void        Depop(bool StartSpawnTimer = true);
        void        Stun(int duration);
        void        UnStun();
       
        inline void SignalNPC(int _signal_id) { signaled = true; signal_id = _signal_id; }
       
        inline sint32        GetNPCFactionID()        const { return npc_faction_id; }
        inline sint32                        GetPrimaryFaction()        const { return primary_faction; }
        sint32        GetNPCHate(Mob* in_ent)  {return hate_list.GetEntHate(in_ent);}
    bool    IsOnHatelist(Mob*p) { return hate_list.IsOnHateList(p);}

        void        SetNPCFactionID(sint32 in) { npc_faction_id = in; database.GetFactionIdsForNPC(npc_faction_id, &faction_list, &primary_faction); }

        float  org_x, org_y, org_z, org_heading;
       
        int16        GetMaxDMG() const {return max_dmg;}
        bool        IsAnimal() const { return(bodytype == BT_Animal); }
        int16  GetPetSpellID() const {return pet_spell_id;}
        void    SetPetSpellID(int16 amt) {pet_spell_id = amt;}

        int32        GetMaxDamage(int8 tlevel);
        void    SetTaunting(bool tog) {taunting = tog;}
        void        PickPocket(Client* thief);
        void        StartSwarmTimer(int32 duration) { swarm_timer.Start(duration); }
        void        AddLootDrop(const Item_Struct*dbitem, ItemList* itemlistconst, sint8 charges, bool equipit, bool wearchange = false);
        void        DoClassAttacks(Mob *target);
        void        CheckSignal();
       
        //waypoint crap
        int                GetMaxWp() const { return max_wp; }
        void                                DisplayWaypointInfo(Client *to);
        void                                CalculateNewWaypoint();
//        int8                                CalculateHeadingToNextWaypoint();
//        float                                CalculateDistanceToNextWaypoint();
        void                                AssignWaypoints(int32 grid);
        void                                SetWaypointPause();
        void                                UpdateWaypoint(int wp_index);
        // quest wandering commands
        void                                StopWandering();
        void                                ResumeWandering();
        void                                PauseWandering(int pausetime);
        void                                MoveTo(float mtx, float mty, float mtz);
       
        int32                                GetEquipment(int8 material_slot) const;        // returns item id
        sint32                                GetEquipmentMaterial(int8 material_slot) const;
       
        void                                NextGuardPosition();
        void                                SaveGuardSpot(bool iClearGuardSpot = false);
        inline bool                        IsGuarding() const { return(guard_heading != 0); }
/*        void                                SaveSpawnSpot();
        inline const float        GetSpawnX() const { return spawn_x; }
        inline const float        GetSpawnY() const { return spawn_y; }
        inline const float        GetSpawnZ() const { return spawn_z; }
        inline const float        GetSpawnHeading() const { return spawn_heading; }
        */
        void                                AI_SetRoambox(float iDist, float iRoamDist, int32 iDelay = 2500);
        void                                AI_SetRoambox(float iDist, float iMaxX, float iMinX, float iMaxY, float iMinY, int32 iDelay = 2500);
       
       
        inline bool WillAggroNPCs() const { return(npc_aggro); }
       
        inline void GiveNPCTypeData(NPCType *ours) { NPCTypedata_ours = ours; }
        inline const int32 GetNPCSpellsID()        const { return npc_spells_id; }
       
        ItemList        itemlist; //kathgar - why is this public?  Doing other things or I would check the code
       
        NPCProximity* proximity;

        Spawn2*        respawn2;

        AA_SwarmPetInfo *GetSwarmInfo() { return (swarmInfoPtr); }
        void SetSwarmInfo(AA_SwarmPetInfo *mSwarmInfo) { swarmInfoPtr = mSwarmInfo; }

        sint32        GetAccuracyRating() { return (accuracy_rating); }
        void        SetAccuracyRating(sint32 d) { accuracy_rating = d;}

protected:
       
        const NPCType*        NPCTypedata;
        NPCType*        NPCTypedata_ours;        //special case for npcs with uniquely created data.

        friend class EntityList;
        list<struct NPCFaction*> faction_list;
        uint32        copper;
        uint32        silver;
        uint32        gold;
        uint32        platinum;
        int32  grid;
        int32  spawn_group;
        int16        wp_m;

        sint32        npc_faction_id;
        sint32        primary_faction;
       
        Timer        attacked_timer;                //running while we are being attacked (damaged)
        Timer        combat_event_timer;        //running while we are engaged in offensive or defensive combat activities
    Timer        swarm_timer;
    Timer        classattack_timer;
        Timer        knightattack_timer;
    Timer        assist_timer;                //ask for help from nearby mobs
        Timer        global_position_update_timer;

        bool        attack_event;        //true if we have fired an EVENT_ATTACK and our attacked timer has not gone off
        bool        combat_event;        //true if we have fired an EVENT_COMBAT and our combat activity timer has not gone off

//        int8        position;        // 0 - Standing, 1 - Sitting, 2 - Crouching, 4 - Looting
    Timer        sendhpupdate_timer;
        Timer        enraged_timer;

        int32        npc_spells_id;
        struct AISpells_Struct {
                int16        type;                        // 0 = never, must be one (and only one) of the defined values
                uint16        spellid;                // <= 0 = no spell
                sint16        manacost;                // -1 = use spdat, -2 = no cast time
                int32        time_cancast;        // when we can cast this spell next
                sint32        recast_delay;
                sint16        priority;
        };

        int8        casting_spell_AIindex;
        Timer*        AIautocastspell_timer;
        int32*        pDontCastBefore_casting_spell;
        AISpells_Struct        AIspells[MAX_AISPELLS]; // expected to be pre-sorted, best at low index
        void AddSpellToNPCList(AISpells_Struct* AIspells, sint16 iPriority, sint16 iSpellID, uint16 iType, sint16 iManaCost, sint32 iRecastDelay);
        bool AICastSpell(Mob* tar, int8 iChance, int16 iSpellTypes);
        void AIDoSpellCast(int8 i, Mob* tar, sint32 mana_cost, int32* oDontDoAgainBefore = 0);

       
       
        int16        max_dmg;
        int16        min_dmg;
        sint32        accuracy_rating;
       
        //pet crap:
        int16        pet_spell_id;
        bool        taunting;
    Timer        taunt_timer;                //for pet taunting
       
        bool npc_aggro;
       
        int                signal_id;
        bool        signaled;        // used by quest signal() command
       
        //waypoint crap:
        //MyList <wplist> Waypoints;
        vector<wplist> Waypoints;
        void _ClearWaypints();
        int                max_wp;
        int                save_wp;
    float guard_x, guard_y, guard_z, guard_heading;
//    float spawn_x, spawn_y, spawn_z, spawn_heading;
        float roambox_max_x;
        float roambox_max_y;
        float roambox_min_x;
        float roambox_min_y;
        float roambox_distance;
        float roambox_movingto_x;
        float roambox_movingto_y;
        int32 roambox_delay;
       
        int16  skills[HIGHEST_SKILL+1];
        int32  equipment[MAX_MATERIALS];        //this is an array of item IDs
        int16        d_meele_texture1;                //this is an item Material value
        int16        d_meele_texture2;                //this is an item Material value (offhand)

        AA_SwarmPetInfo *swarmInfoPtr;

This issue is hanging me up from adding disciplines to NPCs to make some interesting encounters. I know I could probably do most of it via quests if I wanted, but these are mainly for my trash mobs. I am trying to make trash mob fights a little more dynamic.

trevius 07-25-2008 06:04 AM

After more testing, it seems like recast delay is working properly, but is picky about what kind of spells you set to which type:

From MobAI.cpp:
Code:

const int SpellType_Nuke=1;
const int SpellType_Heal=2;
const int SpellType_Root=4;
const int SpellType_Buff=8;
const int SpellType_Escape=16;
const int SpellType_Pet=32;
const int SpellType_Lifetap=64;
const int SpellType_Snare=128;
const int SpellType_DOT=256;

This list includes most of the spell types people might like to use for NPCs, but it seems like some just don't fit in anywhere. I would like to add a new one for in combat buffs to be used for spells like Yaulp and for Melee Disciplines. Yaulp works ok as a normal buff, but it is a little bit pointless to have NPCs constantly recasting that spell on themselves. They really should only use it during combat, but there currently isn't any way for that to work properly. The main reason I want to add this new category is for Disciplines.

It seems that when you set a discipline to type 1 (nuke), it will cast properly, but since it is self only, it apparently doesn't pass all of the checks for being a "nuke" spell. So, I think the problem is that it is never reaching the point where it is supposed to check the recast delay setting. Maybe the nuke one could just be edited to allow this, or a whole new type could be added:

Code:

const int SpellType_Combat_Buff=512;
Then, I think that would need to be added here in the MobAI.cpp:
Code:

const int SpellTypes_Detrimental = SpellType_Nuke|SpellType_Root|SpellType_Lifetap|SpellType_Snare|SpellType_DOT|SpellType_Combat_Buff;
const int SpellTypes_Beneficial = SpellType_Heal|SpellType_Buff|SpellType_Escape|SpellType_Pet;

Looking at some of the examples, we have:

Buff
Code:

                                        case SpellType_Buff: {
                                                if (
                                                        (spells[AIspells[i].spellid].targettype == ST_Target || tar == this)
                                                        && tar->DontBuffMeBefore() < Timer::GetCurrentTime()
                                                        && !tar->IsImmuneToSpell(AIspells[i].spellid, this)
                                                        && tar->CanBuffStack(AIspells[i].spellid, GetLevel(), true) >= 0
                                                        && !(tar->IsPet() && tar->GetOwner()->IsClient() && this != tar)        //no buffing PC's pets, but they can buff themself
                                                        ) {
                                                        AIDoSpellCast(i, tar, mana_cost, &tar->pDontBuffMeBefore);
                                                        return true;
                                                }
                                                break;
                                        }

Nuke
Code:

                                        case SpellType_Nuke: {
                                                if (
                                                        manaR >= 40 && (rand()%100) < 50
                                                        && tar->CanBuffStack(AIspells[i].spellid, GetLevel(), true) >= 0
                                                        ) {
                                                        if(!checked_los) {
                                                                if(!CheckLosFN(tar))
                                                                        return(false);        //cannot see target... we assume that no spell is going to work since we will only be casting detrimental spells in this call
                                                                checked_los = true;
                                                        }
                                                        AIDoSpellCast(i, tar, mana_cost);
                                                        return true;
                                                }
                                                break;
                                        }

And, I am just guessing that putting Disciplines in Nuke is failing and going to the following:

Code:

                                        default: {
                                                cout<<"Error: Unknown spell type in AICastSpell. caster:"<<this->GetName()<<" type:"<<AIspells[i].type<<" slot:"<<i<<endl;
                                                break;
                                        }

But, the actual checks after a successful cast seem to be here:
Code:

        //this gets called from InterruptSpell() for failure or SpellFinished() for success
        void NPC::AI_Event_SpellCastFinished(bool iCastSucceeded, int8 slot) {
                if (slot == 1) {
                        int32 recovery_time = 0;
                        if (iCastSucceeded) {
                                if (casting_spell_AIindex < MAX_AISPELLS) {
                                                recovery_time += spells[AIspells[casting_spell_AIindex].spellid].recovery_time;
                                                if (AIspells[casting_spell_AIindex].recast_delay >= 0){
                                                        if (AIspells[casting_spell_AIindex].recast_delay <1000)
                                                                AIspells[casting_spell_AIindex].time_cancast = Timer::GetCurrentTime() + (AIspells[casting_spell_AIindex].recast_delay*1000);
        }
                                                else
                                                        AIspells[casting_spell_AIindex].time_cancast = Timer::GetCurrentTime() + spells[AIspells[casting_spell_AIindex].spellid].recast_time;
                                }
                                if (!IsEngaged())
                                        recovery_time += RandomTimer(2000, 3000);
                                if (recovery_time < AIautocastspell_timer->GetSetAtTrigger())
                                        recovery_time = AIautocastspell_timer->GetSetAtTrigger();
                                AIautocastspell_timer->Start(recovery_time, false);
                        }
                        else
                                AIautocastspell_timer->Start(800, false);
                        casting_spell_AIindex = MAX_AISPELLS;
                }
}

So, I think that the only other thing that might need to be added is a new section for Combat_Buffs. Though, I am not sure if I need to add anything somewhere else to get it to work. I think that by copying the settings for buff and calling it Combat_Buff, and then adding Combat_Buff to detrimental spells, it will allow for NPCs to only cast these types of buffs during combat, which is the opposite from normal buffs.

Code:

                                        case SpellType_Combat_Buff: {
                                                if (
                                                        (spells[AIspells[i].spellid].targettype == ST_Target || tar == this)
                                                        && tar->DontBuffMeBefore() < Timer::GetCurrentTime()
                                                        && !tar->IsImmuneToSpell(AIspells[i].spellid, this)
                                                        && tar->CanBuffStack(AIspells[i].spellid, GetLevel(), true) >= 0
                                                        && !(tar->IsPet() && tar->GetOwner()->IsClient() && this != tar)        //no buffing PC's pets, but they can buff themself
                                                        ) {
                                                        AIDoSpellCast(i, tar, mana_cost, &tar->pDontBuffMeBefore);
                                                        return true;
                                                }
                                                break;
                                        }

I guess I will test this out on my server and just cross my fingers that I don't break anything lol. I am such a gimpy coder, but this makes at least a little sense to me... If it works, I will post it in server code submissions with better details on how to add this code in, even though everything should already be here.

If anyone else has any input on getting this to work the way I want, I would love to hear it :D

trevius 07-25-2008 06:27 AM

Just found 3 more lines that I think needed to be adjusted for it to work:

Code:

          ||        AIspells[i].type == SpellType_Combat_Buff
Code:

                                if(!AICastSpell(target, 20, SpellType_Nuke | SpellType_Lifetap | SpellType_DOT | SpellType_Combat_Buff)) {
Code:

                if(!AICastSpell(target, 90, SpellType_Root | SpellType_Nuke | SpellType_Lifetap | SpellType_Snare | SpellType_DOT | SpellType_Combat_Buff)) {
Compiling it now and will post if it works or not. Crossing my fingers.

trevius 07-25-2008 07:27 AM

Well, it compiled fine, but now when I set my NPCs spells to type 512, it doesn't seem to use them at all in combat or out of combat... Guess this needs further looking into, but I think it is closer now heh.

trevius 07-26-2008 10:50 AM

Well, I didn't have to add a new category after-all lol. I found the reason why my recast delays weren't working. It was because I was using recast timers similar to actual disc reuse timers. Some of them were 20 minutes or more. And in seconds, that is 1200.

I looked at recast delay settings in the source and I found this:

MobAI.cpp
Code:

void NPC::AI_Event_SpellCastFinished(bool iCastSucceeded, int8 slot) {
        if (slot == 1) {
                int32 recovery_time = 0;
                if (iCastSucceeded) {
                        if (casting_spell_AIindex < MAX_AISPELLS) {
                                        recovery_time += spells[AIspells[casting_spell_AIindex].spellid].recovery_time;
                                        if (AIspells[casting_spell_AIindex].recast_delay >= 0){
                                                if (AIspells[casting_spell_AIindex].recast_delay <1000)
                                                        AIspells[casting_spell_AIindex].time_cancast = Timer::GetCurrentTime() + (AIspells[casting_spell_AIindex].recast_delay*1000);
}
                                        else
                                                AIspells[casting_spell_AIindex].time_cancast = Timer::GetCurrentTime() + spells[AIspells[casting_spell_AIindex].spellid].recast_time;
                        }
                        if (!IsEngaged())
                                recovery_time += RandomTimer(2000, 3000);
                        if (recovery_time < AIautocastspell_timer->GetSetAtTrigger())
                                recovery_time = AIautocastspell_timer->GetSetAtTrigger();
                        AIautocastspell_timer->Start(recovery_time, false);
                }
                else
                        AIautocastspell_timer->Start(800, false);
                casting_spell_AIindex = MAX_AISPELLS;
        }
}

And I simply changed the check for < 1000 to < 10000 and now my recast delays are working perfectly. Here is the change I made, highlighted in RED:

Code:

void NPC::AI_Event_SpellCastFinished(bool iCastSucceeded, int8 slot) {
        if (slot == 1) {
                int32 recovery_time = 0;
                if (iCastSucceeded) {
                        if (casting_spell_AIindex < MAX_AISPELLS) {
                                        recovery_time += spells[AIspells[casting_spell_AIindex].spellid].recovery_time;
                                        if (AIspells[casting_spell_AIindex].recast_delay >= 0){
                                                if (AIspells[casting_spell_AIindex].recast_delay <10000)
                                                        AIspells[casting_spell_AIindex].time_cancast = Timer::GetCurrentTime() + (AIspells[casting_spell_AIindex].recast_delay*1000);
}
                                        else
                                                AIspells[casting_spell_AIindex].time_cancast = Timer::GetCurrentTime() + spells[AIspells[casting_spell_AIindex].spellid].recast_time;
                        }
                        if (!IsEngaged())
                                recovery_time += RandomTimer(2000, 3000);
                        if (recovery_time < AIautocastspell_timer->GetSetAtTrigger())
                                recovery_time = AIautocastspell_timer->GetSetAtTrigger();
                        AIautocastspell_timer->Start(recovery_time, false);
                }
                else
                        AIautocastspell_timer->Start(800, false);
                casting_spell_AIindex = MAX_AISPELLS;
        }
}

I don't see any reason why this couldn't be added to the source updates. I am not sure why the current limit was set to 1000 or less. I guess it wouldn't even apply in most cases, but disciplines for pets and NPC encounters that might last 20+ minutes could be effected by this.

trevius 09-02-2008 09:31 PM

Well, I am starting again to get the new spell type category working for NPCs. I haven't fully decided if I want to just make 1 new category or 2. I think Yaulp would be fine to set as a buff that is used in combat, but disciplines need a little extra rules to them. I currently have my NPC disciplines set to the nuke category and that almost works the way that I want with the only problem being that they will stack multiple discs at once which makes them way too overpowered. The idea is to make a new category just for discs that will do combat buffs, but only do 1 at a time and won't use another until the previous one has worn off.

I think the code I posted above for combat_buffs is probably very close to working for this. I just need to figure out why the new category wasn't being used when I set it for an NPC in their spell list. I imagine I am only missing something minor to get it to start using them. Then, the only other thing I would need is a way to make sure only 1 combat buff (disc) is being used at a time.

Of course since KLS added a new field for dispells and it is 512, then the new discipline field would have to use 1024. And if I added a combat_buff field as well for Yaulp and maybe others, it would have to be 2048.

If anyone has time to look over my code, I think these would make for some nice new features to help expand what NPCs can do without having to make quest scripts for every mob.

KLS 09-02-2008 10:03 PM

Sir, get out of my head. I was seriously just thinking about expanding the npc cast system further with in combat buffs classification only like 2 days ago.


All times are GMT -4. The time now is 11:06 AM.

Powered by vBulletin®, Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.