Go Back   EQEmulator Home > EQEmulator Forums > Development > Development::Bug Reports

Development::Bug Reports Post detailed bug reports and what you would like to see next in the emu here.

Reply
 
Thread Tools Display Modes
  #1  
Old 07-16-2008, 04:08 PM
trevius's Avatar
trevius
Developer
 
Join Date: Aug 2006
Location: USA
Posts: 5,946
Default

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.
__________________
Trevazar/Trevius Owner of: Storm Haven
Everquest Emulator FAQ (Frequently Asked Questions) - Read It!
Reply With Quote
  #2  
Old 07-16-2008, 04:21 PM
moydock
Discordant
 
Join Date: Jun 2005
Posts: 286
Default

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.
__________________
-Croup (the rogue)
Creator of Pandemic (PvP-Racewars)
Reply With Quote
  #3  
Old 07-16-2008, 07:50 PM
trevius's Avatar
trevius
Developer
 
Join Date: Aug 2006
Location: USA
Posts: 5,946
Default

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.
__________________
Trevazar/Trevius Owner of: Storm Haven
Everquest Emulator FAQ (Frequently Asked Questions) - Read It!
Reply With Quote
  #4  
Old 07-17-2008, 02:25 AM
moydock
Discordant
 
Join Date: Jun 2005
Posts: 286
Default

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.
__________________
-Croup (the rogue)
Creator of Pandemic (PvP-Racewars)
Reply With Quote
  #5  
Old 07-23-2008, 04:16 AM
trevius's Avatar
trevius
Developer
 
Join Date: Aug 2006
Location: USA
Posts: 5,946
Default

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 (111 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.
__________________
Trevazar/Trevius Owner of: Storm Haven
Everquest Emulator FAQ (Frequently Asked Questions) - Read It!
Reply With Quote
  #6  
Old 07-25-2008, 06:04 AM
trevius's Avatar
trevius
Developer
 
Join Date: Aug 2006
Location: USA
Posts: 5,946
Default

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
__________________
Trevazar/Trevius Owner of: Storm Haven
Everquest Emulator FAQ (Frequently Asked Questions) - Read It!
Reply With Quote
  #7  
Old 07-25-2008, 06:27 AM
trevius's Avatar
trevius
Developer
 
Join Date: Aug 2006
Location: USA
Posts: 5,946
Default

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.
__________________
Trevazar/Trevius Owner of: Storm Haven
Everquest Emulator FAQ (Frequently Asked Questions) - Read It!
Reply With Quote
Reply

Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump

   

All times are GMT -4. The time now is 10:27 PM.


 

Everquest is a registered trademark of Daybreak Game Company LLC.
EQEmulator is not associated or affiliated in any way with Daybreak Game Company LLC.
Except where otherwise noted, this site is licensed under a Creative Commons License.
       
Powered by vBulletin®, Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
Template by Bluepearl Design and vBulletin Templates - Ver3.3