Go Back   EQEmulator Home > EQEmulator Forums > Development > Development::Server Code Submissions

Reply
 
Thread Tools Display Modes
  #1  
Old 09-04-2010, 10:00 PM
Vanicae
Fire Beetle
 
Join Date: May 2010
Posts: 9
Default COMMITTED: Tribute Focus, SE_DivineSave, SE_Flurry, SE_Accuracy

Sorry for posting all of these together, but for the most part they are all related changes. I may over-explain some of this, especially to the devs who already know what this stuff is anyways, but I want it to be very clear what this is implementing.

The main change is adding the active tribute inventory slots to the Client::GetFocusEffect routine. All tribute focuses should now be working. (Actually, I think ALL personal tribute is now working)

The rest of the changes came about while working on tribute.
The tributes "Fury of Combat" (SE_Flurry), "Eyes of the Hunter" (SE_Accuracy) and "Second Chance" (SE_DivineSave), are now implemented.

"Fury of Combat" is actually misdescribed in the tribute window, it is not a higher chance to double attack, but a chance to flurry (chance per round to execute 2 extra attacks). For reference see spell 5608 from tribute item Benefit: Flurry 03.
The spell effect is 279 (SE_Flurry). I can provide forum posts confirming this is flurry on live if needed (and mislabeled on live too).

"Second Chance" is a Divine Save. This is not Death Save, but is very similar. The spell Divine Intervention provides a Death Save, when your hp hits 0, it is simply a chance for a full heal, and then it's gone.
A Divine Save happens when your hp hits 0 as well, but if it activates, you are given the buff Touch of the Divine, with a HoT and invulnerability for 6 tics. I have added code to force the duration to ignore focus/AA extensions.
I don't know if it gets extended or not on live.

Some notes on the Accuracy code. Accuracy = HitChance, except it affects all skills automatically. Hit chance always either specifies a specific skill or All Skills.

Itembonus Accuracy and HitChance don't stack with each other, it will choose whichever is higher (but won't choose HitChance if it's higher but for the wrong skill).
The same goes for Spellbonus Accuracy and HitChance. However, the results from Itembonus and Spellbonus do combine. This is an assumption based on how other things work, I don't really have any live data to work with on it.

Like the current HitChance implementation, a negative Accuracy mod doesn't work. I modelled Accuracy based on HitChance (as it is effectively the same), and it was already noted in the source that something needs to be reworked to allow for that.
I think this should still be added for the time being, and someone needs to think about how to work this problem out. I plan to take a look at it, but it may be beyond me.

As for the Flurry implementation, any class is able to buy the flurry tribute, so I let it activate for any class. If an enchanter is meleeing with the flurry tribute at rank 3, they should get an extra 2 attacks per round 6% of the time, etc.
I don't think thats a big deal. From reading around, it seems like a lot of people think a flurry can only happen on a successful triple attack. I don't know how it really works, but in the current revision 1639,
it works fine without a triple attack and gets a bonus on a successful one with the right AAs. Obviously only a few classes can triple, but all classes can buy this tribute... so I guess this implementation should be fine.

This diff adds the 3 new spell effects, adds the tribute focus fix, and changes Mob::TryDeathSave() to account for the SE_DivineSave from tribute/other spells.

Code:
Index: spell_effects.cpp
===================================================================
--- spell_effects.cpp	(revision 1639)
+++ spell_effects.cpp	(working copy)
@@ -1123,6 +1123,8 @@
 #ifdef SPELL_EFFECT_SPAM
 				snprintf(effect_desc, _EDLEN, "Invulnerability");
 #endif
+				if(spell_id==4789) // Touch of the Divine - Divine Save
+					buffs[buffslot].ticsremaining = spells[spell_id].buffduration; // Prevent focus/aa buff extension
 				SetInvul(true);
 				break;
 			}
@@ -2825,7 +2827,38 @@
 
 				break;
 			}
-
+			case SE_DivineSave:
+			{
+#ifdef SPELL_EFFECT_SPAM
+				snprintf(effect_desc, _EDLEN, "Divine Save: %+i", effect_value);
+#endif
+				// Handled with bonuses
+				break;
+			}
+			case SE_Accuracy:
+			{
+#ifdef SPELL_EFFECT_SPAM
+				snprintf(effect_desc, _EDLEN, "Accuracy: %+i", effect_value);
+#endif
+				// Handled with bonuses
+				break;
+			}
+			case SE_Flurry:
+			{
+#ifdef SPELL_EFFECT_SPAM
+				snprintf(effect_desc, _EDLEN, "Flurry: %+i", effect_value);
+#endif
+				// Handled with bonuses
+				break;
+			}
+			case SE_Purify:
+			{
+				// NOT IMPLEMENTED.
+				// A Divine Save casts spell 4789 Touch of the Divine, which contains SE_Purify.
+				// Touch of the Divine is implemented enough to be used.
+				// This just here to remove unknown spell ID message.
+				break;
+			}
 			case SE_ImprovedDamage:
 			case SE_ImprovedHeal:
 			case SE_IncreaseSpellHaste:
@@ -4060,7 +4093,27 @@
 			}
 		}
 	}
-
+	
+	//Tribute Focus
+	for(int x=TRIBUTE_SLOT_START; x < (TRIBUTE_SLOT_START + MAX_PLAYER_TRIBUTES); x++)
+	{
+		TempItem = NULL;
+		ItemInst* ins = GetInv().GetItem(x);
+		if (!ins)
+			continue;
+		TempItem = ins->GetItem();
+		if (TempItem && TempItem->Focus.Effect > 0 && TempItem->Focus.Effect != SPELL_UNKNOWN) {
+			Total = CalcFocusEffect(type, TempItem->Focus.Effect, spell_id);
+			if (Total > 0 && realTotal >= 0 && Total > realTotal) {
+				realTotal = Total;
+				UsedItem = TempItem;
+			} else if (Total < 0 && Total < realTotal) {
+				realTotal = Total;
+				UsedItem = TempItem;
+			}
+		}
+	}
+	
 	if (realTotal != 0 && UsedItem && spells[spell_id].buffduration == 0) {
 		Message_StringID(MT_Spells, BEGINS_TO_GLOW, UsedItem->Name);
 	}
@@ -4124,11 +4177,13 @@
 	bool Result = false;
 
 	int aaClientTOTD = IsClient() ? CastToClient()->GetAA(aaTouchoftheDivine) : -1;
+	int8 SuccessChance = IsClient() ? (1.2 * CastToClient()->GetAA(aaTouchoftheDivine)) : 0;
+	SuccessChance += spellbonuses.DivineSaveChance + itembonuses.DivineSaveChance;
+	int SaveRoll = MakeRandomInt(0, 100);
 
-	if (aaClientTOTD > 0) {
-		int aaChance = (1.2 * CastToClient()->GetAA(aaTouchoftheDivine));
-		
-		if (MakeRandomInt(0,100) < aaChance) {
+	if (SuccessChance > 0) {
+		LogFile->write(EQEMuLog::Debug, "%s chance for a divine save was %i and the roll was %i", GetCleanName(), SuccessChance, SaveRoll);
+		if (SaveRoll <= SuccessChance) {
 			Result = true;
 			/*
 			int touchHealSpellID = 4544;
@@ -4169,28 +4224,31 @@
 				case 5:
 					touchHealAmount = 1.0;
 					break;
+				default:
+					touchHealAmount = 0.05; // No AA, still get small heal from Divine Save
 			}
 
 			this->Message(0, "Divine power heals your wounds.");
 			SetHP(this->max_hp * touchHealAmount);
-
+			
+			BuffFadeByEffect(SE_DivineSave);
 			// and "Touch of the Divine", an Invulnerability/HoT/Purify effect, only one for all 5 levels
 			SpellOnTarget(4789, this);
 
 			// skip checking for DI fire if this goes off...
-			if (Result == true) {
-				return Result;
-			}
+			//if (Result == true) { //Looks like a redundant If to me.
+			return Result;
+			//}
 		}
 	}
 
 	int buffSlot = GetBuffSlotFromType(SE_DeathSave);
 
 	if(buffSlot >= 0) {
-		int8 SuccessChance = buffs[buffSlot].deathSaveSuccessChance;
+		SuccessChance = buffs[buffSlot].deathSaveSuccessChance;
 		int8 CasterUnfailingDivinityAARank = buffs[buffSlot].casterAARank;
 		int16 BuffSpellID = buffs[buffSlot].spellid;
-		int SaveRoll = MakeRandomInt(0, 100);
+		SaveRoll = MakeRandomInt(0, 100);
 
 		LogFile->write(EQEMuLog::Debug, "%s chance for a death save was %i and the roll was %i", GetCleanName(), SuccessChance, SaveRoll);
 
@@ -4210,8 +4268,8 @@
 
 			// Fade the buff
 			BuffFadeBySlot(buffSlot);
-
-			SetDeathSaveChance(false);
+			if (spellbonuses.DivineSaveChance + itembonuses.DivineSaveChance == 0) // Don't turn this off if we still have Divine Save
+				SetDeathSaveChance(false);
 		}
 		else if (CasterUnfailingDivinityAARank >= 1) {
 			// Roll the virtual dice to see if the target atleast gets a heal out of this
@@ -4223,6 +4281,7 @@
 			if(SuccessChance >= SaveRoll) {
 				// Yep, target gets a modest heal
 				SetHP(1500);
+				Result = true; // This is technically a save, was giving this heal but still killing you
 			}
 		}
 	}
This diff modifies #peekinv to show active tribute affects as well.
It also adds #showbonusstats, which I was using to show specific stats I was implementing for testing. If you only want to add the addition to peekinv, and not the showbonusstats, you can use this diff instead: http://pastebin.com/YbN2T2Kw

Code:
Index: command.h
===================================================================
--- command.h	(revision 1639)
+++ command.h	(working copy)
@@ -307,6 +307,7 @@
 void command_distance(Client *c, const Seperator *sep);
 void command_cvs(Client *c, const Seperator *sep);
 void command_max_all_skills(Client *c, const Seperator *sep);
+void command_showbonusstats(Client *c, const Seperator *sep);
 
 #ifdef EMBPERL
 void command_embperl_plugin(Client *c, const Seperator *sep);
Index: command.cpp
===================================================================
--- command.cpp	(revision 1639)
+++ command.cpp	(working copy)
@@ -265,7 +265,7 @@
 		command_add("appearance","[type] [value] - Send an appearance packet for you or your target",150,command_appearance) ||
 		command_add("charbackup","[list/restore] - Query or restore character backups",150,command_charbackup) ||
 		command_add("nukeitem","[itemid] - Remove itemid from your player target's inventory",150,command_nukeitem) ||
-		command_add("peekinv","[worn/cursor/inv/bank/trade/all] - Print out contents of your player target's inventory",100,command_peekinv) ||
+		command_add("peekinv","[worn/cursor/inv/bank/trade/trib/all] - Print out contents of your player target's inventory",100,command_peekinv) ||
 		command_add("findnpctype","[search criteria] - Search database NPC types",100,command_findnpctype) ||
 		command_add("findzone","[search criteria] - Search database zones",100,command_findzone) ||
 		command_add("fz",NULL,100,command_findzone) ||
@@ -445,7 +445,8 @@
 		command_add("globalview","Lists all qglobals in cache if you were to do a quest with this target.",80,command_globalview) ||
 		command_add("distance","- Reports the distance between you and your target.", 80, command_distance) ||
 		command_add("cvs","- Summary of client versions currently online.", 200, command_cvs) ||
-		command_add("maxskills","Maxes skills for you.", 200, command_max_all_skills)
+		command_add("maxskills","Maxes skills for you.", 200, command_max_all_skills) ||
+		command_add("showbonusstats","[item|spell|all] Shows bonus stats for target from items or spells. Shows both by default.",50, command_showbonusstats)
 		)
 	{
 		command_deinit();
@@ -3121,6 +3122,27 @@
 			}
 		}
 	}
+	if (bAll || (strcasecmp(sep->arg[1], "trib")==0)) {
+		// Active tribute effect items
+		bFound = true;
+		for (sint16 i=TRIBUTE_SLOT_START; i<(TRIBUTE_SLOT_START + MAX_PLAYER_TRIBUTES); i++) {
+			const ItemInst* inst = client->GetInv().GetItem(i);
+			item = (inst) ? inst->GetItem() : NULL;
+			if (c->GetClientVersion() >= EQClientSoF)
+			{
+				c->Message((item==0), "TributeSlot: %i, Item: %i (%c%06X00000000000000000000000000000000000000000000%s%c)", i,
+				((item==0)?0:item->ID),0x12, ((item==0)?0:item->ID),
+				((item==0)?"null":item->Name), 0x12);
+			}
+			else
+			{
+			c->Message((item==0), "TributeSlot: %i, Item: %i (%c%06X000000000000000000000000000000000000000%s%c)", i,
+				((item==0)?0:item->ID),0x12, ((item==0)?0:item->ID),
+				((item==0)?"null":item->Name), 0x12);
+			}
+		}
+	}
+	
 	if (bAll || (strcasecmp(sep->arg[1], "bank")==0)) {
 		// Bank and shared bank items
 		bFound = true;
@@ -3244,7 +3266,7 @@
 	}
 		
 	if (!bFound) {
-		c->Message(0, "Usage: #peekinv [worn|cursor|inv|bank|trade|all]");
+		c->Message(0, "Usage: #peekinv [worn|cursor|inv|bank|trade|trib|all]");
 		c->Message(0, "  Displays a portion of the targetted user's inventory");
 		c->Message(0, "  Caution: 'all' is a lot of information!");
 	}
@@ -10971,3 +10993,30 @@
 		}
 	}
 }
+
+void command_showbonusstats(Client *c, const Seperator *sep)
+{
+	if (c->GetTarget() == 0)
+		c->Message(0, "ERROR: No target!");
+	else if (!c->GetTarget()->IsMob() && !c->GetTarget()->IsClient())
+		c->Message(0, "ERROR: Target is not a Mob or Player!");
+	else {
+		bool bAll = false;
+		if(sep->arg[1][0] == '\0' || strcasecmp(sep->arg[1], "all") == 0)
+			bAll = true;
+		if (bAll || (strcasecmp(sep->arg[1], "item")==0)) {
+			c->Message(0, "Target Item Bonuses:");
+			c->Message(0, "  Accuracy: %i%%   Divine Save: %i%%",c->GetTarget()->GetItemBonuses().Accuracy, c->GetTarget()->GetItemBonuses().DivineSaveChance);
+			c->Message(0, "  Flurry: %i%%     HitChance: %i%% (Skill: %i)",c->GetTarget()->GetItemBonuses().FlurryChance, c->GetTarget()->GetItemBonuses().HitChance / 15, c->GetTarget()->GetItemBonuses().HitChanceSkill);
+		}
+		if (bAll || (strcasecmp(sep->arg[1], "spell")==0)) {
+			c->Message(0, "  Target Spell Bonuses:");
+			c->Message(0, "  Accuracy: %i%%   Divine Save: %i%%",c->GetTarget()->GetSpellBonuses().Accuracy, c->GetTarget()->GetSpellBonuses().DivineSaveChance);
+			c->Message(0, "  Flurry: %i%%     HitChance: %i%% (Skill: %i)",c->GetTarget()->GetSpellBonuses().FlurryChance, c->GetTarget()->GetSpellBonuses().HitChance / 15, c->GetTarget()->GetSpellBonuses().HitChanceSkill);
+			int deathsaveslot = c->GetTarget()->GetBuffSlotFromType(SE_DeathSave);
+			int dschance = deathsaveslot >= 0 ? c->GetTarget()->GetBuffs()[deathsaveslot].deathSaveSuccessChance : 0;
+			c->Message(0, "  Death Save: %i%%",dschance);
+		}
+		c->Message(0, "  Effective Casting Level: %i",c->GetTarget()->GetCasterLevel(0));
+	}
+}
This diff adds the variables to Mob to support the new spell effects. It also exposes the mob buff_struct so I could get a buff property in my #showstatbonuses command above.
A specific slot buff would be accessed as GetBuffs()[slot] .. maybe not the prettiest hack.
If you don't implement that command you can take out that line from here, if you want.

Code:
Index: mob.h
===================================================================
--- mob.h	(revision 1639)
+++ mob.h	(working copy)
@@ -269,6 +269,9 @@
 	sint16 ProcChance;			// ProcChance/10 == % increase i
 	sint16 ExtraAttackChance;
 	sint16 DoTShielding;
+	sint16 DivineSaveChance;		// Second Chance
+	sint16 FlurryChance;
+	sint16 Accuracy;		// Works like HitChance but on all skills
 
 	sint8 HundredHands;		//extra haste, stacks with all other haste  i
 	bool MeleeLifetap;  //i
@@ -767,6 +770,7 @@
 	inline void SetDeathSaveChance(bool hasDeathSaveChance) { m_hasDeathSaveChance = hasDeathSaveChance; }
 	EQApplicationPacket *MakeBuffsPacket(bool for_target = true);
 	void SendBuffsToClient(Client *c);
+	inline Buffs_Struct* GetBuffs() const { return buffs; }
 
 	//effect related
 	sint16 CalcFocusEffect(focusType type, int16 focus_id, int16 spell_id);
Adds the actual bonuses from a spell effect:

Code:
Index: bonuses.cpp
===================================================================
--- bonuses.cpp	(revision 1639)
+++ bonuses.cpp	(working copy)
@@ -1184,6 +1184,27 @@
 					newbon->XPRateMod = effect_value;
 				break;
 			}
+			case SE_DivineSave:
+			{
+				if(newbon->DivineSaveChance < effect_value) {
+					newbon->DivineSaveChance = effect_value;
+					SetDeathSaveChance(true);
+				}
+				break;
+			}
+			case SE_Flurry:
+			{
+				if(newbon->FlurryChance < effect_value)
+					newbon->FlurryChance = effect_value;
+				break;
+			}
+			case SE_Accuracy:
+			{
+				if(newbon->Accuracy < effect_value)
+					newbon->Accuracy = effect_value;
+				break;
+			}
+
 				
 		}
 	}
This last diff implements the attack routine code for Accuracy and Flurry.

Code:
Index: attack.cpp
===================================================================
--- attack.cpp	(revision 1639)
+++ attack.cpp	(working copy)
@@ -298,13 +298,28 @@
 
 	//add in our hit chance bonuses if we are using the right skill
 	//does the hit chance cap apply to spell bonuses from disciplines?
+	float hitBonus = 0;
 	if(attacker->spellbonuses.HitChanceSkill == 255 || attacker->spellbonuses.HitChanceSkill == skillinuse) {
-		chancetohit += (chancetohit * (attacker->spellbonuses.HitChance / 15.0f) / 100);
-		mlog(COMBAT__TOHIT, "Applied spell melee hit chance %d/15, yeilding %.2f", attacker->spellbonuses.HitChance, chancetohit);
+		hitBonus = (attacker->spellbonuses.HitChance / 15.0f > attacker->spellbonuses.Accuracy) ? attacker->spellbonuses.HitChance / 15.0f : attacker->spellbonuses.Accuracy;
+		chancetohit += chancetohit * hitBonus / 100;
+		mlog(COMBAT__TOHIT, "Applied spell melee hit chance %.2f, yeilding %.2f", hitBonus, chancetohit);
 	}
+	else if(attacker->spellbonuses.Accuracy) {
+		hitBonus = attacker->spellbonuses.Accuracy;
+		chancetohit += chancetohit * hitBonus / 100;
+		mlog(COMBAT__TOHIT, "Applied spell melee accuracy chance %.2f, yeilding %.2f", hitBonus, chancetohit);
+	}
+
+	hitBonus = 0;
 	if(attacker->itembonuses.HitChanceSkill == 255 || attacker->itembonuses.HitChanceSkill == skillinuse) {
-		chancetohit += (chancetohit * (attacker->itembonuses.HitChance / 15.0f) / 100);
-		mlog(COMBAT__TOHIT, "Applied item melee hit chance %d/15, yeilding %.2f", attacker->itembonuses.HitChance, chancetohit);
+		hitBonus = (attacker->itembonuses.HitChance / 15.0f > attacker->itembonuses.Accuracy) ? attacker->itembonuses.HitChance / 15.0f : attacker->itembonuses.Accuracy;
+		chancetohit += chancetohit * hitBonus / 100;
+		mlog(COMBAT__TOHIT, "Applied item melee hit chance %.2f, yeilding %.2f", hitBonus, chancetohit);
+	} 
+	else if(attacker->itembonuses.Accuracy) {
+		hitBonus = attacker->itembonuses.Accuracy;
+		chancetohit += chancetohit * hitBonus / 100;
+		mlog(COMBAT__TOHIT, "Applied item melee accuracy chance %.2f, yeilding %.2f", hitBonus, chancetohit);
 	}
 
 	if (attacker->IsClient()) {
Index: client_process.cpp
===================================================================
--- client_process.cpp	(revision 1639)
+++ client_process.cpp	(working copy)
@@ -411,8 +411,11 @@
 						Attack(auto_attack_target, 13, false);
 					}
 				}
-				if (auto_attack_target && GetAA(aaFlurry) > 0) {
-					int32 flurrychance = 0;
+				if (auto_attack_target && (GetAA(aaFlurry) > 0 || spellbonuses.FlurryChance > 0 || itembonuses.FlurryChance > 0)) {
+					// Assuming Flurry Chance (X) effects = X%
+					// Can any class flurry with tribute?
+					// Is flurry supposed to have a chance even without a successful triple attack?
+					int32 flurrychance = (itembonuses.FlurryChance + spellbonuses.FlurryChance) * 10; 
 
 					switch (GetAA(aaFlurry)) 
 					{
I have tested all of these changes, mostly through in-game debug #logs/#mlog etc, hitting things, changing things... hitting more things. Killing myself 70+ times testing the divine save (and death save).
Killing a mob a rediculous number of times with Divine Intervention cast on it. Fun stuff. I've spent hours making sure this works as intended.

If anything is implemented wrong, let me know and I'll correct it.

Sorry for writing a book here. I hope this is formatted well enough for you.

Vanicae@PEQ
Reply With Quote
  #2  
Old 09-11-2010, 05:44 AM
Derision
Developer
 
Join Date: Feb 2004
Location: UK
Posts: 1,540
Default

Thanks for this. I've committed it, but I only really tested the Tribute part of it.
Reply With Quote
  #3  
Old 02-27-2020, 01:24 AM
GM Ash
Fire Beetle
 
Join Date: Apr 2018
Posts: 8
Default

Sorry for the ten year thread necro, but I have used search and advanced search to no avail. Given the community movement towards UF and RoF1+2, what is the top level as of 2020 for personal tribute offerings?

I am not finding gear upgrades to many of the staple spell focus benefits like affliction haste IV, V, + or burning affliction IV, V, + on a new server with numerous updates and high level zones available... the level limit is 70 but the LDoN and Tribute selections are apparently stuck at level 65.

What to suggest to the server op? Any help welcome and appreciated (and sorry, no, I was a GM for a "Wrath Era" WoW server, not a past or present EQ1 server).

Thanks!
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 06:14 AM.


 

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 - 2024, Jelsoft Enterprises Ltd.
Template by Bluepearl Design and vBulletin Templates - Ver3.3