Browse Source

Procyon Updates #4

Fix #366 - loot_global support for loot_tier

Fix #355 - support for item stat 7xx range, subsequently fixed some issues with spell updates and providing inventory stats reflecting changes to those spells.

#define ITEM_STAT_SPELL_DAMAGE          700
#define ITEM_STAT_HEAL_AMOUNT           701
#define ITEM_STAT_SPELL_AND_HEAL        702
#define ITEM_STAT_COMBAT_ART_DAMAGE     703
#define ITEM_STAT_SPELL_AND_COMBAT_ART_DAMAGE           704
#define ITEM_STAT_TAUNT_AMOUNT          705
#define ITEM_STAT_TAUNT_AND_COMBAT_ART_DAMAGE           706
#define ITEM_STAT_ABILITY_MODIFIER          707

Fix #369 - added error message if a global ruleset not defined in variables eg.
22:46:34 E Rules     : Variables table is missing default_ruleset_id variable name, this means the global rules will be code-default, database entries not used.  Use query such as "insert into variables set variable_name='default_ruleset_id',variable_value='1',comment='Default ruleset';" to resolve.
Image 2 years ago
parent
commit
1962e7e061

+ 125 - 38
EQ2/source/WorldServer/Combat.cpp

@@ -401,7 +401,7 @@ bool Entity::SpellAttack(Spawn* victim, float distance, LuaSpell* luaspell, int8
 			else
 				crit_mod = 2;
 		}
-		if(DamageSpawn((Entity*)victim, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, damage_type, low_damage, high_damage, spell->GetName(), crit_mod, is_tick, no_calcs) && !luaspell->crit)
+		if(DamageSpawn((Entity*)victim, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, damage_type, low_damage, high_damage, spell->GetName(), crit_mod, is_tick, no_calcs, luaspell) && !luaspell->crit)
 			luaspell->crit = true;
 		CheckProcs(PROC_TYPE_OFFENSIVE, victim);
 		CheckProcs(PROC_TYPE_MAGICAL_OFFENSIVE, victim);
@@ -580,33 +580,10 @@ bool Entity::SpellHeal(Spawn* target, float distance, LuaSpell* luaspell, string
 		}
 		
 		if (heal_amt > 0){
-			//int32 base_roll = heal_amt;
-			//potency mod
-			MStats.lock();
-			heal_amt *= (stats[ITEM_STAT_POTENCY] / 100 + 1);
-			MStats.unlock();
-
-			//primary stat mod, insert forula here when done
-			//heal_amt += base_roll * (GetPrimaryStat()
-		
-			//Ability Modifier can only be up to half of base roll + potency and primary stat bonus
-			heal_amt += (int32)min(info_struct.get_ability_modifier(), (float)(heal_amt / 2));
-		}
-
-		if(!crit_mod || crit_mod == 1){
-			if(crit_mod == 1) 
-				crit = true;
-			else {
-				// Crit Roll
-				float chance = max((float)0, info_struct.get_crit_chance());
-				crit = (MakeRandomFloat(0, 100) <= chance); 
-			}
-			if(crit){
-				//Apply total crit multiplier with crit bonus
-				heal_amt *= (info_struct.get_crit_bonus() / 100) + 1.3;
-				if(luaspell->spell)
-					luaspell->crit = true;
-			}
+			if(target->IsEntity())
+				heal_amt = (int32)CalculateHealAmount((Entity*)target, (sint32)heal_amt, crit_mod, &crit);
+			else
+				heal_amt = (int32)CalculateHealAmount(nullptr, (sint32)heal_amt, crit_mod, &crit);
 		}
 	}
 
@@ -902,7 +879,7 @@ Skill* Entity::GetSkillByWeaponType(int8 type, bool update) {
 	return 0;
 }
 
-bool Entity::DamageSpawn(Entity* victim, int8 type, int8 damage_type, int32 low_damage, int32 high_damage, const char* spell_name, int8 crit_mod, bool is_tick, bool no_calcs, bool ignore_attacker) {
+bool Entity::DamageSpawn(Entity* victim, int8 type, int8 damage_type, int32 low_damage, int32 high_damage, const char* spell_name, int8 crit_mod, bool is_tick, bool no_calcs, bool ignore_attacker, LuaSpell* spell) {
 	if(!victim || victim->GetHP() == 0)
 		return false;
 
@@ -929,17 +906,9 @@ bool Entity::DamageSpawn(Entity* victim, int8 type, int8 damage_type, int32 low_
 		}
 		//Potency and ability mod is only applied to spells/CAs
 		else { 
-			// Potency mod
-			MStats.lock();
-			damage *= ((stats[ITEM_STAT_POTENCY] / 100) + 1);
-			MStats.unlock();
-
-			// Ability mod can only give up to half of damage after potency
-			int32 mod = (int32)min(info_struct.get_ability_modifier(), (float)(damage / 2));
-			damage += mod;
+			damage = CalculateDamageAmount(victim, damage, type, damage_type, spell);
 		}
 
-
 		if(!crit_mod || crit_mod == 1){
 			//force crit if crit_mod == 1
 			if(crit_mod == 1)
@@ -1055,6 +1024,8 @@ void Entity::AddHate(Entity* attacker, sint32 hate) {
 	if (IsPet() && ((NPC*)this)->GetOwner()->IsPlayer() && ((((Player*)((NPC*)this)->GetOwner())->GetInfoStruct()->get_pet_behavior() & 2) == 0))
 		return;
 
+	hate = attacker->CalculateHateAmount(this, hate);
+	
 	if (IsNPC()) {
 		LogWrite(COMBAT__DEBUG, 3, "Combat", "Add NPC_AI Hate: Victim '%s', Attacker '%s', Hate: %i", GetName(), attacker->GetName(), hate);
 		((NPC*)this)->Brain()->AddHate(attacker, hate);
@@ -1573,3 +1544,119 @@ void Entity::ClearProcs() {
 
 	MProcList.releasewritelock(__FUNCTION__, __LINE__);
 }
+
+sint32 Entity::CalculateHateAmount(Spawn* target, sint32 amt) {
+	sint32 hate = amt;
+
+	amt = CalculateFormulaByStat(amt, ITEM_STAT_TAUNT_AMOUNT);
+
+	amt = CalculateFormulaByStat(amt, ITEM_STAT_TAUNT_AND_COMBAT_ART_DAMAGE);
+
+	amt = CalculateFormulaByStat(amt, ITEM_STAT_ABILITY_MODIFIER);
+
+	return amt;
+}
+
+sint32 Entity::CalculateHealAmount(Spawn* target, sint32 amt, int8 crit_mod, bool* crit, bool skip_crit_mod) {
+	amt = CalculateFormulaByStat(amt, ITEM_STAT_HEAL_AMOUNT);
+	
+	amt = CalculateFormulaByStat(amt, ITEM_STAT_SPELL_AND_HEAL);
+
+	//Potency Mod
+	amt = CalculateFormulaByStat(amt, ITEM_STAT_POTENCY);
+	
+	//Ability Mod
+	amt += (int32)min((int32)GetInfoStruct()->get_ability_modifier(), (int32)(amt / 2));
+
+	if(!skip_crit_mod){
+		if(!crit_mod || crit_mod == 1){
+			if(crit_mod == 1) 
+				*crit = true;
+			else if(!*crit) {
+				// Crit Roll
+				float chance = (float)max((float)0, (float)GetInfoStruct()->get_crit_chance());
+				*crit = (MakeRandomFloat(0, 100) <= chance); 
+			}
+			if(*crit){
+				//Apply total crit multiplier with crit bonus
+				amt *= ((GetInfoStruct()->get_crit_bonus() / 100) + 1.3);
+			}
+		}
+	}
+
+	return amt;
+}
+
+sint32 Entity::CalculateDamageAmount(Spawn* target, sint32 damage, int8 base_type, int8 damage_type, LuaSpell* spell) {
+	return CalculateDamageAmount(target, damage, base_type, damage_type, (spell && spell->spell) ? spell->spell->GetSpellData()->target_type : 0);
+}
+
+sint32 Entity::CalculateDamageAmount(Spawn* target, sint32 damage, int8 base_type, int8 damage_type, int8 target_type) {
+	// only spells may add spell damage item stat
+	if(damage_type >= DAMAGE_PACKET_DAMAGE_TYPE_HEAT && damage_type <= DAMAGE_PACKET_DAMAGE_TYPE_POISON)
+	{
+		// https://forums.daybreakgames.com/eq2/index.php?threads/potency-and-ability-mod-what.4316/
+		// Spell damage model assuming 100% crit chance:
+		// (Spell base damage * Base damage modifier * Int bonus * Skill bonus * Potency + Ability modifier) * Crit bonus * Spell double attack
+		/** Spell base damage: Get all master spells
+			Base damage modifier: Very very rare. Available from Wizard aa Wisdom line(Brainstorm). Get it, cherish it.
+			Int bonus: Past 1200 or so int, you will get a 10% increase in spell damage per 30% increase in int. Look at int tooltip to see the numerical value.
+			Skill bonus: Past skill cap, 100 points skill is worth 2% increase in minimum spell damage, which translates to 1% overall increase. Looks at the skill that the spell uses, for wizards mostly disruption. Cap is 6.5*level.
+			Potency: A straight damage modifier, the more you can get the better. No practical cap.
+			Ability modifier: A straight damage modifier. Limited to 50% of the spell base damage, but for a wizard this usually has little consequence. However note that this is not affected by Potency.
+			Crit bonus: A straight damage modifier, the more you can get the better. No practical cap. Wizards got 50% intrinsic Crit Bonus that does not show up in the stat window, just add 50 to stat value for calculations.
+			Spell double cast: A straight damage modifier, the more you can get the better. You won't be able to get very much of this.
+			Makes the spell cast twice with some limitations.
+		**/
+
+		damage = CalculateFormulaByStat(damage, ITEM_STAT_SPELL_DAMAGE);
+	}
+
+	if(base_type == DAMAGE_PACKET_TYPE_SPELL_DAMAGE)
+		damage = CalculateFormulaByStat(damage, ITEM_STAT_SPELL_AND_COMBAT_ART_DAMAGE);
+
+	// combat abilities only bonus
+	if(damage_type <= DAMAGE_PACKET_DAMAGE_TYPE_PIERCE)
+		damage = CalculateFormulaByStat(damage, ITEM_STAT_TAUNT_AND_COMBAT_ART_DAMAGE);
+			
+	// Potency mod
+	damage = CalculateFormulaByStat(damage, ITEM_STAT_POTENCY);
+
+	int32 modifier = 2;
+	if(target_type == SPELL_TARGET_GROUP_AE || target_type == SPELL_TARGET_RAID_AE || target_type == SPELL_TARGET_OTHER_GROUP_AE)
+	{
+		modifier = 3;
+	}
+
+	// Ability mod can only give up to half of damage after potency
+	int32 mod = (int32)min(info_struct.get_ability_modifier(), (float)(damage / modifier));
+	damage += mod;
+
+	return damage;
+}
+
+sint32 Entity::CalculateFormulaByStat(sint32 value, int16 stat) {
+	sint32 outValue = value;
+	MStats.lock();
+	std::map<int16, float>::iterator itr = stats.find(stat);
+	if(itr != stats.end())
+		outValue = (sint32)((float)value * ((itr->second / 100.0f) + 1.0f));
+	MStats.unlock();
+
+	return outValue;
+}
+
+int32 Entity::CalculateFormulaByStat(int32 value, int16 stat) {
+	int32 outValue = value;
+	MStats.lock();
+	std::map<int16, float>::iterator itr = stats.find(stat);
+	if(itr != stats.end())
+		outValue = (int32)((float)value * ((itr->second / 100.0f) + 1.0f));
+	MStats.unlock();
+
+	return outValue;
+}
+
+int32 Entity::CalculateFormulaBonus(int32 value, float percent_bonus) {
+	return (int32)((float)value * ((percent_bonus / 100.0f) + 1.0f));
+}

+ 2 - 1
EQ2/source/WorldServer/Entity.cpp

@@ -1238,6 +1238,7 @@ void Entity::CalculateBonuses(){
 	info->add_uncontested_dodge(values->uncontested_dodge);
 	info->add_uncontested_riposte(values->uncontested_riposte);
 
+	info->set_ability_modifier(values->ability_modifier);
 	
 	float full_pct_hit = 100.0f;
 
@@ -1942,7 +1943,7 @@ int32 Entity::CheckWards(Entity* attacker, int32 damage, int8 damage_type) {
 			}
 
 			if (attacker && spell->caster)
-				attacker->DamageSpawn(spell->caster, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, damage_type, redirectDamage, redirectDamage, 0);
+				attacker->DamageSpawn(spell->caster, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, damage_type, redirectDamage, redirectDamage, 0, 0, false, false, false, spell);
 		}
 
 		bool shouldRemoveSpell = false;

+ 8 - 1
EQ2/source/WorldServer/Entity.h

@@ -1233,7 +1233,7 @@ public:
 	int8			DetermineHit(Spawn* victim, int8 damage_type, float ToHitBonus, bool spell);
 	float			GetDamageTypeResistPercentage(int8 damage_type);
 	Skill*			GetSkillByWeaponType(int8 type, bool update);
-	bool			DamageSpawn(Entity* victim, int8 type, int8 damage_type, int32 low_damage, int32 high_damage, const char* spell_name, int8 crit_mod = 0, bool is_tick = false, bool no_damage_calcs = false, bool ignore_attacker = false);
+	bool			DamageSpawn(Entity* victim, int8 type, int8 damage_type, int32 low_damage, int32 high_damage, const char* spell_name, int8 crit_mod = 0, bool is_tick = false, bool no_damage_calcs = false, bool ignore_attacker = false, LuaSpell* spell = 0);
 	void			AddHate(Entity* attacker, sint32 hate);
 	bool			CheckInterruptSpell(Entity* attacker);
 	bool			CheckFizzleSpell(LuaSpell* spell);
@@ -1671,6 +1671,13 @@ public:
 	bool SetInfoStructSInt(std::string field, sint64 value);
 	bool SetInfoStructFloat(std::string field, float value);
 
+	sint32 CalculateHateAmount(Spawn* target, sint32 amt);
+	sint32 CalculateHealAmount(Spawn* target, sint32 amt, int8 crit_mod, bool* crit, bool skip_crit_mod = false);
+	sint32 CalculateDamageAmount(Spawn* target, sint32 damage, int8 base_type, int8 damage_type, LuaSpell* spell);
+	sint32 CalculateDamageAmount(Spawn* target, sint32 damage, int8 base_type, int8 damage_type, int8 spell_target_type);
+	sint32 CalculateFormulaByStat(sint32 value, int16 stat);
+	int32 CalculateFormulaByStat(int32 value, int16 stat);
+	int32 CalculateFormulaBonus(int32 value, float percent_bonus);
 	// when PacketStruct is fixed for C++17 this should become a shared_mutex and handle read/write lock
 	std::mutex		MEquipment;
 	std::mutex		MStats;

+ 10 - 4
EQ2/source/WorldServer/Items/Items.cpp

@@ -1732,11 +1732,14 @@ void Item::serialize(PacketStruct* packet, bool show_name, Player* player, int16
 			if (stat->stat_type == 9){
 				bluemod += 1;
 			}
-			if (stat->stat_type == 6){		//Convert stats to proper client
+			if (stat->stat_type == 6 || stat->stat_type == 7){		//Convert stats to proper client
 				//if (client->GetVersion() >= 63137){  //TEST
 				//	tmp_subtype =stat->stat_subtype;
 				//}
-				if ((client->GetVersion() >= 63119) || client->GetVersion() == 61331){  //KA
+				if(stat->stat_type == 7){
+					tmp_subtype = stat->stat_subtype;
+				}
+				else if ((client->GetVersion() >= 63119) || client->GetVersion() == 61331){  //KA
 					tmp_subtype = world.GetItemStatKAValue(stat->stat_subtype);
 				}
 				else if(client->GetVersion() >= 60085 ) {
@@ -1770,8 +1773,11 @@ void Item::serialize(PacketStruct* packet, bool show_name, Player* player, int16
 			
 			
 			
-			if (stat->stat_type == 6){		//Convert stats to proper client
-				if ((client->GetVersion() >= 63119) || client->GetVersion() == 61331){  //KA
+			if (stat->stat_type == 6 || stat->stat_type == 7){		//Convert stats to proper client
+				if(stat->stat_type == 7){
+					tmp_subtype = stat->stat_subtype;
+				}
+				else if ((client->GetVersion() >= 63119) || client->GetVersion() == 61331){  //KA
 					tmp_subtype = world.GetItemStatKAValue(stat->stat_subtype);
 				}
 				else if(client->GetVersion() >= 60085 ) {

+ 3 - 0
EQ2/source/WorldServer/Items/LootDB.cpp

@@ -110,6 +110,7 @@ void WorldDatabase::LoadGlobalLoot(ZoneServer* zone) {
 				loot->minLevel = result.GetInt8Str("value1");
 				loot->maxLevel = result.GetInt8Str("value2");
 				loot->table_id = table_id;
+				loot->loot_tier = result.GetInt32Str("value4");
 
 				if (loot->minLevel > loot->maxLevel)
 					loot->maxLevel = loot->minLevel;
@@ -124,6 +125,7 @@ void WorldDatabase::LoadGlobalLoot(ZoneServer* zone) {
 				loot->minLevel = result.GetInt8Str("value2");
 				loot->maxLevel = result.GetInt8Str("value3");
 				loot->table_id = table_id;
+				loot->loot_tier = result.GetInt32Str("value4");
 
 				if (loot->minLevel > loot->maxLevel)
 					loot->maxLevel = loot->minLevel;
@@ -138,6 +140,7 @@ void WorldDatabase::LoadGlobalLoot(ZoneServer* zone) {
 				loot->minLevel = result.GetInt8Str("value2");
 				loot->maxLevel = result.GetInt8Str("value3");
 				loot->table_id = table_id;
+				loot->loot_tier = result.GetInt32Str("value4");
 
 				if (loot->minLevel > loot->maxLevel)
 					loot->maxLevel = loot->minLevel;

+ 18 - 7
EQ2/source/WorldServer/Player.cpp

@@ -920,9 +920,9 @@ EQ2Packet* PlayerInfo::serialize(int16 version, int16 modifyPos, int32 modifyVal
 		packet->setDataByName("crit_chance", info_struct->get_crit_chance());// dov confirmed
 		//unknown_1096_32_MJ
 		packet->setDataByName("crit_bonus", info_struct->get_crit_bonus());// dov confirmed
-		((Entity*)player)->MStats.lock();
-		packet->setDataByName("potency", player->stats[ITEM_STAT_POTENCY]);//info_struct->get_potency);// dov confirmed
-		((Entity*)player)->MStats.unlock();
+
+		packet->setDataByName("potency", info_struct->get_potency());//info_struct->get_potency);// dov confirmed
+
 		//unknown_1096_33_MJ
 		packet->setDataByName("reuse_speed", info_struct->get_reuse_speed());// dov confirmed
 		packet->setDataByName("recovery_speed", info_struct->get_recovery_speed());// dov confirmed
@@ -1772,7 +1772,7 @@ int8 Player::ConvertSlotFromClient(int8 slot, int16 version) {
 	return slot;
 }
 
-vector<EQ2Packet*>	Player::UnequipItem(int16 index, sint32 bag_id, int8 slot, int16 version, int8 appearance_type) {
+vector<EQ2Packet*>	Player::UnequipItem(int16 index, sint32 bag_id, int8 slot, int16 version, int8 appearance_type, bool send_item_updates) {
 	vector<EQ2Packet*>	packets;
 	EquipmentItemList* equipList = &equipment_list;
 	
@@ -1982,6 +1982,13 @@ vector<EQ2Packet*>	Player::UnequipItem(int16 index, sint32 bag_id, int8 slot, in
 		if (bag && bag->IsBag())
 			packets.push_back(bag->serialize(version, false, this));
 	}
+
+	if(send_item_updates)
+	{
+		GetClient()->UpdateSentSpellList();
+		GetClient()->ClearSentSpellList();
+	}
+
 	return packets;
 }
 
@@ -2134,10 +2141,10 @@ vector<EQ2Packet*> Player::EquipItem(int16 index, int16 version, int8 appearance
 				slot = item->slot_data.at(0);
 			else
 				slot = slot_id;
-			packets = UnequipItem(slot, item->details.inv_slot_id, item->details.slot_id, version, appearance_type);
+			packets = UnequipItem(slot, item->details.inv_slot_id, item->details.slot_id, version, appearance_type, false);
 			// If item is a 2handed weapon and something is in the secondary, unequip the secondary
 			if (item->IsWeapon() && item->weapon_info->wield_type == ITEM_WIELD_TYPE_TWO_HAND && equipList->GetItem(EQ2_SECONDARY_SLOT) != 0) {
-				vector<EQ2Packet*> tmp_packets = UnequipItem(EQ2_SECONDARY_SLOT, -999, 0, version, appearance_type);
+				vector<EQ2Packet*> tmp_packets = UnequipItem(EQ2_SECONDARY_SLOT, -999, 0, version, appearance_type, false);
 				//packets.reserve(packets.size() + tmp_packets.size());
 				packets.insert(packets.end(), tmp_packets.begin(), tmp_packets.end());
 			}
@@ -2145,7 +2152,7 @@ vector<EQ2Packet*> Player::EquipItem(int16 index, int16 version, int8 appearance
 		else if (canEquip && slot < 255) {
 			// If item is a 2handed weapon and something is in the secondary, unequip the secondary
 			if (item->IsWeapon() && item->weapon_info->wield_type == ITEM_WIELD_TYPE_TWO_HAND && equipList->GetItem(EQ2_SECONDARY_SLOT) != 0) {
-				vector<EQ2Packet*> tmp_packets = UnequipItem(EQ2_SECONDARY_SLOT, -999, 0, version, appearance_type);
+				vector<EQ2Packet*> tmp_packets = UnequipItem(EQ2_SECONDARY_SLOT, -999, 0, version, appearance_type, false);
 				//packets.reserve(packets.size() + tmp_packets.size());
 				packets.insert(packets.end(), tmp_packets.begin(), tmp_packets.end());
 			}
@@ -2185,6 +2192,10 @@ vector<EQ2Packet*> Player::EquipItem(int16 index, int16 version, int8 appearance
 			SetCharSheetChanged(true);
 		}
 	}
+	
+	client->UpdateSentSpellList();
+	client->ClearSentSpellList();
+
 	return packets;
 }
 bool Player::AddItem(Item* item) {

+ 1 - 1
EQ2/source/WorldServer/Player.h

@@ -451,7 +451,7 @@ public:
 	vector<EQ2Packet*>	EquipItem(int16 index, int16 version, int8 appearance_type, int8 slot_id = 255);
 	bool CanEquipItem(Item* item);
 	void SetEquippedItemAppearances();
-	vector<EQ2Packet*>	UnequipItem(int16 index, sint32 bag_id, int8 slot, int16 version, int8 appearance_type = 0);
+	vector<EQ2Packet*>	UnequipItem(int16 index, sint32 bag_id, int8 slot, int16 version, int8 appearance_type = 0, bool send_item_updates = true);
 	int8 ConvertSlotToClient(int8 slot, int16 version);
 	int8 ConvertSlotFromClient(int8 slot, int16 version);
 	EQ2Packet* SwapEquippedItems(int8 slot1, int8 slot2, int16 version, int16 equiptype);

+ 3 - 0
EQ2/source/WorldServer/Rules/RulesDB.cpp

@@ -41,6 +41,9 @@ void WorldDatabase::LoadGlobalRuleSet() {
 
 	if (rule_set_id > 0 && !rule_manager.SetGlobalRuleSet(rule_set_id))
 		LogWrite(RULESYS__ERROR, 0, "Rules", "Error loading global rule set. A rule set with ID %u does not exist.", rule_set_id);
+	else
+		LogWrite(RULESYS__ERROR, 0, "Rules", "Variables table is missing default_ruleset_id variable name, this means the global rules will be code-default, database entries not used.  Use query such as \"insert into variables set variable_name='default_ruleset_id',variable_value='1',comment='Default ruleset';\" to resolve.");
+
 }
 
 void WorldDatabase::LoadRuleSets(bool reload) {

+ 1 - 0
EQ2/source/WorldServer/Spawn.cpp

@@ -129,6 +129,7 @@ Spawn::Spawn(){
 	is_transport_spawn = false;
 	rail_id = 0;
 	is_omitted_by_db_flag = false;
+	loot_tier = 0;
 }
 
 Spawn::~Spawn(){

+ 5 - 0
EQ2/source/WorldServer/Spawn.h

@@ -1240,6 +1240,9 @@ public:
 	void SetOmittedByDBFlag(bool val) { is_omitted_by_db_flag = val; }
 	bool IsOmittedByDBFlag() { return is_omitted_by_db_flag; }
 
+	int32 GetLootTier() { return loot_tier; }
+	void SetLootTier(int32 tier) { loot_tier = tier; }
+
 protected:
 
 	bool	has_quests_required;
@@ -1345,6 +1348,8 @@ private:
 	map<int32, bool> rail_passengers;
 	mutex m_RailMutex;
 	bool is_omitted_by_db_flag; // this particular spawn is omitted by an expansion or holiday flag
+
+	int32 loot_tier;
 };
 
 #endif

+ 67 - 36
EQ2/source/WorldServer/Spells.cpp

@@ -26,12 +26,15 @@
 #include <cmath>
 #include "LuaInterface.h"
 
+#include <boost/regex.hpp>
+
 extern ConfigReader configReader;
 extern WorldDatabase database;
 extern MasterTraitList master_trait_list;
 extern MasterAAList master_aa_list;
 extern MasterSpellList master_spell_list;
 extern LuaInterface* lua_interface;
+extern MasterSkillList master_skill_list;
 
 Spell::Spell(){
 	spell = new SpellData;
@@ -435,9 +438,8 @@ void Spell::SetAAPacketInformation(PacketStruct* packet, AltAdvanceData* data, C
 						value = lua_data[data_index]->float_value * client->GetPlayer()->GetLevel();
 					else
 						value = lua_data[data_index]->int_value * client->GetPlayer()->GetLevel();
-					value *= ((client->GetPlayer()->GetInfoStruct()->get_potency() / 100) + 1);
-					int32 mod = (int32)min(client->GetPlayer()->GetInfoStruct()->get_ability_modifier(), (float)(value / 2));
-					value += mod;
+
+					value = client->GetPlayer()->CalculateDamageAmount(nullptr, value, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, spell->type, spell->target_type);
 					string damage = to_string((int)round(value));
 					effect_message.replace(effect_message.find("%DML"), 6, damage);
 				}
@@ -450,9 +452,8 @@ void Spell::SetAAPacketInformation(PacketStruct* packet, AltAdvanceData* data, C
 						value = lua_data[data_index]->float_value * client->GetPlayer()->GetLevel();
 					else
 						value = lua_data[data_index]->int_value * client->GetPlayer()->GetLevel();
-					value *= ((client->GetPlayer()->GetInfoStruct()->get_potency() / 100) + 1);
-					int32 mod = (int32)min(client->GetPlayer()->GetInfoStruct()->get_ability_modifier(), (float)(value / 2));
-					value += mod;
+						
+					value = client->GetPlayer()->CalculateDamageAmount(nullptr, value, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, spell->type, spell->target_type);
 					string damage = to_string((int)round(value));
 					effect_message.replace(effect_message.find("%DMH"), 6, damage);
 				}
@@ -465,9 +466,8 @@ void Spell::SetAAPacketInformation(PacketStruct* packet, AltAdvanceData* data, C
 						value = lua_data[data_index]->float_value * client->GetPlayer()->GetLevel();
 					else
 						value = lua_data[data_index]->int_value * client->GetPlayer()->GetLevel();
-					value *= ((client->GetPlayer()->GetInfoStruct()->get_potency() / 100) + 1);
-					int32 mod = (int32)min(client->GetPlayer()->GetInfoStruct()->get_ability_modifier(), (float)(value / 2));
-					value += mod;
+						
+					value = client->GetPlayer()->CalculateDamageAmount(nullptr, value, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, spell->type, spell->target_type);
 					string damage = to_string((int)round(value));
 					effect_message.replace(effect_message.find("%LDML"), 7, damage);
 				}
@@ -480,9 +480,8 @@ void Spell::SetAAPacketInformation(PacketStruct* packet, AltAdvanceData* data, C
 						value = lua_data[data_index]->float_value * client->GetPlayer()->GetLevel();
 					else
 						value = lua_data[data_index]->int_value * client->GetPlayer()->GetLevel();
-					value *= ((client->GetPlayer()->GetInfoStruct()->get_potency() / 100) + 1);
-					int32 mod = (int32)min(client->GetPlayer()->GetInfoStruct()->get_ability_modifier(), (float)(value / 2));
-					value += mod;
+						
+					value = client->GetPlayer()->CalculateDamageAmount(nullptr, value, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, spell->type, spell->target_type);
 					string damage = to_string((int)round(value));
 					effect_message.replace(effect_message.find("%LDMH"), 7, damage);
 				}
@@ -625,9 +624,8 @@ void Spell::SetAAPacketInformation(PacketStruct* packet, AltAdvanceData* data, C
 						value = next_spell->lua_data[data_index]->float_value * client->GetPlayer()->GetLevel();
 					else
 						value = next_spell->lua_data[data_index]->int_value * client->GetPlayer()->GetLevel();
-					value *= ((client->GetPlayer()->GetInfoStruct()->get_potency() / 100) + 1);
-					int32 mod = (int32)min(client->GetPlayer()->GetInfoStruct()->get_ability_modifier(), (float)(value / 2));
-					value += mod;
+						
+					value = client->GetPlayer()->CalculateDamageAmount(nullptr, value, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, spell->type, spell->target_type);
 					string damage = to_string((int)round(value));
 					damage.erase(damage.find_last_not_of('0') + 1, std::string::npos);
 					effect_message.replace(effect_message.find("%DML"), 6, damage);
@@ -641,9 +639,8 @@ void Spell::SetAAPacketInformation(PacketStruct* packet, AltAdvanceData* data, C
 						value = next_spell->lua_data[data_index]->float_value * client->GetPlayer()->GetLevel();
 					else
 						value = next_spell->lua_data[data_index]->int_value * client->GetPlayer()->GetLevel();
-					value *= ((client->GetPlayer()->GetInfoStruct()->get_potency() / 100) + 1);
-					int32 mod = (int32)min(client->GetPlayer()->GetInfoStruct()->get_ability_modifier(), (float)(value / 2));
-					value += mod;
+						
+					value = client->GetPlayer()->CalculateDamageAmount(nullptr, value, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, spell->type, spell->target_type);
 					string damage = to_string((int)round(value));
 					damage.erase(damage.find_last_not_of('0') + 1, std::string::npos);
 					effect_message.replace(effect_message.find("%DMH"), 6, damage);
@@ -657,9 +654,8 @@ void Spell::SetAAPacketInformation(PacketStruct* packet, AltAdvanceData* data, C
 						value = next_spell->lua_data[data_index]->float_value * client->GetPlayer()->GetLevel();
 					else
 						value = next_spell->lua_data[data_index]->int_value * client->GetPlayer()->GetLevel();
-					value *= ((client->GetPlayer()->GetInfoStruct()->get_potency() / 100) + 1);
-					int32 mod = (int32)min(client->GetPlayer()->GetInfoStruct()->get_ability_modifier(), (float)(value / 2));
-					value += mod;
+						
+					value = client->GetPlayer()->CalculateDamageAmount(nullptr, value, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, spell->type, spell->target_type);
 					string damage = to_string((int)round(value));
 					effect_message.replace(effect_message.find("%LDML"), 7, damage);
 				}
@@ -672,9 +668,8 @@ void Spell::SetAAPacketInformation(PacketStruct* packet, AltAdvanceData* data, C
 						value = next_spell->lua_data[data_index]->float_value * client->GetPlayer()->GetLevel();
 					else
 						value = next_spell->lua_data[data_index]->int_value * client->GetPlayer()->GetLevel();
-					value *= ((client->GetPlayer()->GetInfoStruct()->get_potency() / 100) + 1);
-					int32 mod = (int32)min(client->GetPlayer()->GetInfoStruct()->get_ability_modifier(), (float)(value / 2));
-					value += mod;
+						
+					value = client->GetPlayer()->CalculateDamageAmount(nullptr, value, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, spell->type, spell->target_type);
 					string damage = to_string((int)round(value));
 					effect_message.replace(effect_message.find("%LDMH"), 7, damage);
 				}
@@ -819,9 +814,8 @@ void Spell::SetPacketInformation(PacketStruct* packet, Client* client, bool disp
 					value = lua_data[data_index]->float_value * client->GetPlayer()->GetLevel();
 				else
 					value = lua_data[data_index]->int_value * client->GetPlayer()->GetLevel();
-				value *= ((client->GetPlayer()->GetInfoStruct()->get_potency() / 100) + 1);
-				int32 mod = (int32)min(client->GetPlayer()->GetInfoStruct()->get_ability_modifier(), (float)(value / 2));
-				value += mod;
+				
+				value = client->GetPlayer()->CalculateDamageAmount(nullptr, value, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, spell->type, spell->target_type);
 				string damage = to_string((int)round(value));
 				effect_message.replace(effect_message.find("%DML"), 6, damage);
 			}
@@ -834,9 +828,8 @@ void Spell::SetPacketInformation(PacketStruct* packet, Client* client, bool disp
 					value = lua_data[data_index]->float_value * client->GetPlayer()->GetLevel();
 				else
 					value = lua_data[data_index]->int_value * client->GetPlayer()->GetLevel();
-				value *= ((client->GetPlayer()->GetInfoStruct()->get_potency() / 100) + 1);
-				int32 mod = (int32)min(client->GetPlayer()->GetInfoStruct()->get_ability_modifier(), (float)(value / 2));
-				value += mod;
+					
+				value = client->GetPlayer()->CalculateDamageAmount(nullptr, value, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, spell->type, spell->target_type);
 				string damage = to_string((int)round(value));
 				effect_message.replace(effect_message.find("%DMH"), 6, damage);
 			}
@@ -849,9 +842,8 @@ void Spell::SetPacketInformation(PacketStruct* packet, Client* client, bool disp
 					value = lua_data[data_index]->float_value * client->GetPlayer()->GetLevel();
 				else
 					value = lua_data[data_index]->int_value * client->GetPlayer()->GetLevel();
-				value *= ((client->GetPlayer()->GetInfoStruct()->get_potency() / 100) + 1);
-				int32 mod = (int32)min(client->GetPlayer()->GetInfoStruct()->get_ability_modifier(), (float)(value / 2));
-				value += mod;
+
+				value = client->GetPlayer()->CalculateDamageAmount(nullptr, value, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, spell->type, spell->target_type);
 				string damage = to_string((int)round(value));
 				effect_message.replace(effect_message.find("%LDML"), 7, damage);
 			}
@@ -864,12 +856,51 @@ void Spell::SetPacketInformation(PacketStruct* packet, Client* client, bool disp
 					value = lua_data[data_index]->float_value * client->GetPlayer()->GetLevel();
 				else
 					value = lua_data[data_index]->int_value * client->GetPlayer()->GetLevel();
-				value *= ((client->GetPlayer()->GetInfoStruct()->get_potency() / 100) + 1);
-				int32 mod = (int32)min(client->GetPlayer()->GetInfoStruct()->get_ability_modifier(), (float)(value / 2));
-				value += mod;
+				
+				value = client->GetPlayer()->CalculateDamageAmount(nullptr, value, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, spell->type, spell->target_type);
 				string damage = to_string((int)round(value));
 				effect_message.replace(effect_message.find("%LDMH"), 7, damage);
 			}
+
+			boost::regex re("([0-9]{1,10})\\s+\\-\\s+([0-9]{1,10})");
+			boost::match_results<std::string::const_iterator> base_match;
+			if (boost::regex_search(effect_message, base_match, re, boost::match_partial)) {
+				boost::ssub_match match = base_match[0];
+				std::size_t midPos = match.str().find(" - ");
+				if(midPos != std::string::npos)
+				{
+					int32 minValue = atoul(match.str().substr(0, midPos).c_str());
+					int32 maxValue = atoul(match.str().substr(midPos+3).c_str());
+
+					int32 newMin = minValue;
+					
+					bool crit = false;
+					Skill* skill = master_skill_list.GetSkill(spell->mastery_skill);
+					std::string skillName = "";
+					
+					if(skill)
+						skillName = skill->name.data;
+
+					if(skillName == "Aggression")
+						newMin = (int32)client->GetPlayer()->CalculateHateAmount(nullptr, (sint32)minValue);
+					else if(spell->friendly_spell && skillName == "Ministration")
+						newMin = (int32)client->GetPlayer()->CalculateHealAmount(nullptr, (sint32)minValue, 0, &crit, true);
+					else
+						newMin = (int32)client->GetPlayer()->CalculateDamageAmount(nullptr, (sint32)minValue, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, spell->type, spell->target_type);
+					
+					int32 newMax = maxValue;
+					if(skillName == "Aggression")
+						newMax = (int32)client->GetPlayer()->CalculateHateAmount(nullptr, (sint32)maxValue);
+					else if(spell->friendly_spell && skillName == "Ministration")
+						newMax = (int32)client->GetPlayer()->CalculateHealAmount(nullptr, (sint32)maxValue, 0, &crit, true);
+					else
+						newMax = (int32)client->GetPlayer()->CalculateDamageAmount(nullptr, (sint32)maxValue, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, spell->type, spell->target_type);
+					
+					std::string newStr = std::to_string(newMin) + " - " + std::to_string(newMax);
+
+					effect_message.replace(effect_message.find(match.str()), match.str().size(), newStr);
+				}
+			}
 			//GetZone()->SimpleMessage(CHANNEL_COLOR_SPELL_EFFECT, effect_message.c_str(), victim, 50);
 		}
 		packet->setArrayDataByName("effect", effect_message.c_str(), i);

+ 3 - 10
EQ2/source/WorldServer/World.cpp

@@ -243,19 +243,12 @@ PacketStruct* World::GetWorldTime(int16 version){
 }
 
 float World::GetXPRate(){
-	if(xp_rate >= 0)
-		return xp_rate;
-
 	xp_rate = rule_manager.GetGlobalRule(R_Player, XPMultiplier)->GetFloat();
 	LogWrite(WORLD__DEBUG, 0, "World", "Setting Global XP Rate to: %.2f", xp_rate);
 	return xp_rate;
 }
 
-float World::GetTSXPRate()
-{
-	if(ts_xp_rate >= 0)
-		return ts_xp_rate;
-
+float World::GetTSXPRate(){
 	ts_xp_rate = rule_manager.GetGlobalRule(R_Player, TSXPMultiplier)->GetFloat();
 	LogWrite(WORLD__DEBUG, 0, "World", "Setting Global Tradeskill XP Rate to: %.2f", ts_xp_rate);
 	return ts_xp_rate;
@@ -1629,10 +1622,10 @@ void World::AddBonuses(Item* item, ItemStatsValues* values, int16 type, sint32 v
 				values->critbonus += value;
 				break;
 			}
-			/*case ITEM_STAT_POTENCY:{
+			case ITEM_STAT_POTENCY:{
 				values->potency += value;
 				break;
-			}*/
+			}
 			case ITEM_STAT_HATEGAINMOD:{
 				values->hategainmod += value;
 				break;

+ 1 - 0
EQ2/source/WorldServer/World.h

@@ -365,6 +365,7 @@ struct GlobalLoot {
 	int8	minLevel;
 	int8	maxLevel;
 	int32	table_id;
+	int32	loot_tier;
 };
 
 #define TRANSPORT_TYPE_LOCATION		0

+ 33 - 10
EQ2/source/WorldServer/WorldDatabase.cpp

@@ -926,7 +926,7 @@ void WorldDatabase::LoadNPCs(ZoneServer* zone){
 	NPC* npc = 0;
 	int32 id = 0;
 	int32 total = 0;
-	MYSQL_RES* result = query.RunQuery2(Q_SELECT,"SELECT npc.spawn_id, s.name, npc.min_level, npc.max_level, npc.enc_level, s.race, s.model_type, npc.class_, npc.gender, s.command_primary, s.command_secondary, s.show_name, npc.min_group_size, npc.max_group_size, npc.hair_type_id, npc.facial_hair_type_id, npc.wing_type_id, npc.chest_type_id, npc.legs_type_id, npc.soga_hair_type_id, npc.soga_facial_hair_type_id, s.attackable, s.show_level, s.targetable, s.show_command_icon, s.display_hand_icon, s.hp, s.power, s.size, s.collision_radius, npc.action_state, s.visual_state, npc.mood_state, npc.initial_state, npc.activity_status, s.faction_id, s.sub_title, s.merchant_id, s.merchant_type, s.size_offset, npc.attack_type, npc.ai_strategy+0, npc.spell_list_id, npc.secondary_spell_list_id, npc.skill_list_id, npc.secondary_skill_list_id, npc.equipment_list_id, npc.str, npc.sta, npc.wis, npc.intel, npc.agi, npc.heat, npc.cold, npc.magic, npc.mental, npc.divine, npc.disease, npc.poison, npc.aggro_radius, npc.cast_percentage, npc.randomize, npc.soga_model_type, npc.heroic_flag, npc.alignment, npc.elemental, npc.arcane, npc.noxious, s.savagery, s.dissonance, npc.hide_hood, npc.emote_state, s.prefix, s.suffix, s.last_name, s.expansion_flag, s.holiday_flag, s.disable_sounds, s.merchant_min_level, s.merchant_max_level, s.aaxp_rewards, npc.water_type, npc.flying_type\n"
+	MYSQL_RES* result = query.RunQuery2(Q_SELECT,"SELECT npc.spawn_id, s.name, npc.min_level, npc.max_level, npc.enc_level, s.race, s.model_type, npc.class_, npc.gender, s.command_primary, s.command_secondary, s.show_name, npc.min_group_size, npc.max_group_size, npc.hair_type_id, npc.facial_hair_type_id, npc.wing_type_id, npc.chest_type_id, npc.legs_type_id, npc.soga_hair_type_id, npc.soga_facial_hair_type_id, s.attackable, s.show_level, s.targetable, s.show_command_icon, s.display_hand_icon, s.hp, s.power, s.size, s.collision_radius, npc.action_state, s.visual_state, npc.mood_state, npc.initial_state, npc.activity_status, s.faction_id, s.sub_title, s.merchant_id, s.merchant_type, s.size_offset, npc.attack_type, npc.ai_strategy+0, npc.spell_list_id, npc.secondary_spell_list_id, npc.skill_list_id, npc.secondary_skill_list_id, npc.equipment_list_id, npc.str, npc.sta, npc.wis, npc.intel, npc.agi, npc.heat, npc.cold, npc.magic, npc.mental, npc.divine, npc.disease, npc.poison, npc.aggro_radius, npc.cast_percentage, npc.randomize, npc.soga_model_type, npc.heroic_flag, npc.alignment, npc.elemental, npc.arcane, npc.noxious, s.savagery, s.dissonance, npc.hide_hood, npc.emote_state, s.prefix, s.suffix, s.last_name, s.expansion_flag, s.holiday_flag, s.disable_sounds, s.merchant_min_level, s.merchant_max_level, s.aaxp_rewards, npc.water_type, npc.flying_type, s.loot_tier\n"
 													"FROM spawn s\n"
 													"INNER JOIN spawn_npcs npc\n"
 													"ON s.id = npc.spawn_id\n"
@@ -1082,6 +1082,8 @@ void WorldDatabase::LoadNPCs(ZoneServer* zone){
 
 		info->set_water_type(atoul(row[81]));
 		info->set_flying_type(atoul(row[82]));
+
+		npc->SetLootTier(atoul(row[83]));
 		
 		zone->AddNPC(id, npc);
 		total++;
@@ -1206,7 +1208,7 @@ void WorldDatabase::LoadSigns(ZoneServer* zone){
 	Sign* sign = 0;
 	int32 id = 0;
 	int32 total = 0;
-	MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT ss.spawn_id, s.name, s.model_type, s.size, s.show_command_icon, ss.widget_id, ss.widget_x, ss.widget_y, ss.widget_z, s.command_primary, s.command_secondary, s.collision_radius, ss.icon, ss.type, ss.title, ss.description, ss.sign_distance, ss.zone_id, ss.zone_x, ss.zone_y, ss.zone_z, ss.zone_heading, ss.include_heading, ss.include_location, s.transport_id, s.size_offset, s.display_hand_icon, s.visual_state, s.expansion_flag, s.holiday_flag, s.disable_sounds, s.merchant_min_level, s.merchant_max_level, s.aaxp_rewards\n"
+	MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT ss.spawn_id, s.name, s.model_type, s.size, s.show_command_icon, ss.widget_id, ss.widget_x, ss.widget_y, ss.widget_z, s.command_primary, s.command_secondary, s.collision_radius, ss.icon, ss.type, ss.title, ss.description, ss.sign_distance, ss.zone_id, ss.zone_x, ss.zone_y, ss.zone_z, ss.zone_heading, ss.include_heading, ss.include_location, s.transport_id, s.size_offset, s.display_hand_icon, s.visual_state, s.expansion_flag, s.holiday_flag, s.disable_sounds, s.merchant_min_level, s.merchant_max_level, s.aaxp_rewards, s.loot_tier\n"
 												  "FROM spawn s\n"
 												  "INNER JOIN spawn_signs ss\n"
 												  "ON s.id = ss.spawn_id\n"
@@ -1281,6 +1283,8 @@ void WorldDatabase::LoadSigns(ZoneServer* zone){
 		
 		sign->SetAAXPRewards(atoul(row[33]));
 
+		sign->SetLootTier(atoul(row[34]));
+
 		zone->AddSign(id, sign);
 		total++;
 
@@ -1296,7 +1300,7 @@ void WorldDatabase::LoadWidgets(ZoneServer* zone){
 	Widget* widget = 0;
 	int32 id = 0;
 	int32 total = 0;
-	MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT sw.spawn_id, s.name, s.model_type, s.size, s.show_command_icon, sw.widget_id, sw.widget_x, sw.widget_y, sw.widget_z, s.command_primary, s.command_secondary, s.collision_radius, sw.include_heading, sw.include_location, sw.icon, sw.type, sw.open_heading, sw.open_y, sw.action_spawn_id, sw.open_sound_file, sw.close_sound_file, sw.open_duration, sw.closed_heading, sw.linked_spawn_id, sw.close_y, s.transport_id, s.size_offset, sw.house_id, sw.open_x, sw.open_z, sw.close_x, sw.close_z, s.display_hand_icon, s.expansion_flag, s.holiday_flag, s.disable_sounds, s.merchant_min_level, s.merchant_max_level, s.aaxp_rewards\n"
+	MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT sw.spawn_id, s.name, s.model_type, s.size, s.show_command_icon, sw.widget_id, sw.widget_x, sw.widget_y, sw.widget_z, s.command_primary, s.command_secondary, s.collision_radius, sw.include_heading, sw.include_location, sw.icon, sw.type, sw.open_heading, sw.open_y, sw.action_spawn_id, sw.open_sound_file, sw.close_sound_file, sw.open_duration, sw.closed_heading, sw.linked_spawn_id, sw.close_y, s.transport_id, s.size_offset, sw.house_id, sw.open_x, sw.open_z, sw.close_x, sw.close_z, s.display_hand_icon, s.expansion_flag, s.holiday_flag, s.disable_sounds, s.merchant_min_level, s.merchant_max_level, s.aaxp_rewards, s.loot_tier\n"
 												  "FROM spawn s\n"
 												  "INNER JOIN spawn_widgets sw\n"
 												  "ON s.id = sw.spawn_id\n"
@@ -1385,6 +1389,8 @@ void WorldDatabase::LoadWidgets(ZoneServer* zone){
 		
 		widget->SetAAXPRewards(atoul(row[38]));
 
+		widget->SetLootTier(atoul(row[39]));
+
 		zone->AddWidget(id, widget);
 		total++;
 
@@ -1400,7 +1406,7 @@ void WorldDatabase::LoadObjects(ZoneServer* zone){
 	Object* object = 0;
 	int32 id = 0;
 	int32 total = 0;
-	MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT so.spawn_id, s.name, s.race, s.model_type, s.command_primary, s.command_secondary, s.targetable, s.size, s.show_name, s.visual_state, s.attackable, s.show_level, s.show_command_icon, s.display_hand_icon, s.faction_id, s.collision_radius, s.transport_id, s.size_offset, so.device_id, s.expansion_flag, s.holiday_flag, s.disable_sounds, s.merchant_min_level, s.merchant_max_level, s.aaxp_rewards\n"
+	MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT so.spawn_id, s.name, s.race, s.model_type, s.command_primary, s.command_secondary, s.targetable, s.size, s.show_name, s.visual_state, s.attackable, s.show_level, s.show_command_icon, s.display_hand_icon, s.faction_id, s.collision_radius, s.transport_id, s.size_offset, so.device_id, s.expansion_flag, s.holiday_flag, s.disable_sounds, s.merchant_min_level, s.merchant_max_level, s.aaxp_rewards, s.loot_tier\n"
 												  "FROM spawn s\n"
 												  "INNER JOIN spawn_objects so\n"
 												  "ON s.id = so.spawn_id\n"
@@ -1462,6 +1468,8 @@ void WorldDatabase::LoadObjects(ZoneServer* zone){
 
 		object->SetAAXPRewards(atoul(row[24]));
 
+		object->SetLootTier(atoul(row[25]));
+
 		zone->AddObject(id, object);
 		total++;
 
@@ -1477,7 +1485,7 @@ void WorldDatabase::LoadGroundSpawns(ZoneServer* zone){
 	GroundSpawn* spawn = 0;
 	int32 id = 0;
 	int32 total = 0;
-	MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT sg.spawn_id, s.name, s.race, s.model_type, s.command_primary, s.command_secondary, s.targetable, s.size, s.show_name, s.visual_state, s.attackable, s.show_level, s.show_command_icon, s.display_hand_icon, s.faction_id, s.collision_radius, sg.number_harvests, sg.num_attempts_per_harvest, sg.groundspawn_id, sg.collection_skill, s.size_offset, s.expansion_flag, s.holiday_flag, s.disable_sounds, s.aaxp_rewards\n"
+	MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT sg.spawn_id, s.name, s.race, s.model_type, s.command_primary, s.command_secondary, s.targetable, s.size, s.show_name, s.visual_state, s.attackable, s.show_level, s.show_command_icon, s.display_hand_icon, s.faction_id, s.collision_radius, sg.number_harvests, sg.num_attempts_per_harvest, sg.groundspawn_id, sg.collection_skill, s.size_offset, s.expansion_flag, s.holiday_flag, s.disable_sounds, s.aaxp_rewards, s.loot_tier\n"
 												  "FROM spawn s\n"
 												  "INNER JOIN spawn_ground sg\n"
 												  "ON s.id = sg.spawn_id\n"
@@ -1540,6 +1548,8 @@ void WorldDatabase::LoadGroundSpawns(ZoneServer* zone){
 
 		spawn->SetAAXPRewards(atoul(row[24]));
 
+		spawn->SetLootTier(atoul(row[25]));
+
 		zone->AddGroundSpawn(id, spawn);
 		total++;
 		LogWrite(GROUNDSPAWN__DEBUG, 5, "GSpawn", "---Loading GroundSpawn: '%s' (%u)", spawn->appearance.name, id);
@@ -6305,7 +6315,7 @@ bool WorldDatabase::LoadSign(ZoneServer* zone, int32 spawn_id) {
 	Sign* sign = 0;
 	int32 id = 0;
 	DatabaseResult result;
-	database_new.Select(&result, "SELECT ss.spawn_id, s.name, s.model_type, s.size, s.show_command_icon, ss.widget_id, ss.widget_x, ss.widget_y, ss.widget_z, s.command_primary, s.command_secondary, s.collision_radius, ss.icon, ss.type, ss.title, ss.description, ss.sign_distance, ss.zone_id, ss.zone_x, ss.zone_y, ss.zone_z, ss.zone_heading, ss.include_heading, ss.include_location, s.transport_id, s.size_offset, s.display_hand_icon, s.visual_state, s.disable_sounds, s.merchant_min_level, s.merchant_max_level, s.aaxp_rewards\n"
+	database_new.Select(&result, "SELECT ss.spawn_id, s.name, s.model_type, s.size, s.show_command_icon, ss.widget_id, ss.widget_x, ss.widget_y, ss.widget_z, s.command_primary, s.command_secondary, s.collision_radius, ss.icon, ss.type, ss.title, ss.description, ss.sign_distance, ss.zone_id, ss.zone_x, ss.zone_y, ss.zone_z, ss.zone_heading, ss.include_heading, ss.include_location, s.transport_id, s.size_offset, s.display_hand_icon, s.visual_state, s.disable_sounds, s.merchant_min_level, s.merchant_max_level, s.aaxp_rewards, s.loot_tier\n"
 								 "FROM spawn s\n"
 								 "INNER JOIN spawn_signs ss\n"
 								 "ON ss.spawn_id = s.id\n"
@@ -6363,6 +6373,8 @@ bool WorldDatabase::LoadSign(ZoneServer* zone, int32 spawn_id) {
 		
 		sign->SetAAXPRewards(result.GetInt32(31));
 
+		sign->SetLootTier(result.GetInt32(32));
+
 		zone->AddSign(id, sign);
 
 
@@ -6378,7 +6390,7 @@ bool WorldDatabase::LoadWidget(ZoneServer* zone, int32 spawn_id) {
 	int32 id = 0;
 	DatabaseResult result;
 
-	database_new.Select(&result, "SELECT sw.spawn_id, s.name, s.model_type, s.size, s.show_command_icon, sw.widget_id, sw.widget_x, sw.widget_y, sw.widget_z, s.command_primary, s.command_secondary, s.collision_radius, sw.include_heading, sw.include_location, sw.icon, sw.type, sw.open_heading, sw.open_y, sw.action_spawn_id, sw.open_sound_file, sw.close_sound_file, sw.open_duration, sw.closed_heading, sw.linked_spawn_id, sw.close_y, s.transport_id, s.size_offset, sw.house_id, sw.open_x, sw.open_z, sw.close_x, sw.close_z, s.display_hand_icon, s.disable_sounds, s.merchant_min_level, s.merchant_max_level, s.aaxp_rewards\n"
+	database_new.Select(&result, "SELECT sw.spawn_id, s.name, s.model_type, s.size, s.show_command_icon, sw.widget_id, sw.widget_x, sw.widget_y, sw.widget_z, s.command_primary, s.command_secondary, s.collision_radius, sw.include_heading, sw.include_location, sw.icon, sw.type, sw.open_heading, sw.open_y, sw.action_spawn_id, sw.open_sound_file, sw.close_sound_file, sw.open_duration, sw.closed_heading, sw.linked_spawn_id, sw.close_y, s.transport_id, s.size_offset, sw.house_id, sw.open_x, sw.open_z, sw.close_x, sw.close_z, s.display_hand_icon, s.disable_sounds, s.merchant_min_level, s.merchant_max_level, s.aaxp_rewards, s.loot_tier\n"
 								 "FROM spawn s\n"
 								 "INNER JOIN spawn_widgets sw\n"
 								 "ON sw.spawn_id = s.id\n"
@@ -6443,6 +6455,8 @@ bool WorldDatabase::LoadWidget(ZoneServer* zone, int32 spawn_id) {
 		
 		widget->SetAAXPRewards(result.GetInt32(36));
 
+		widget->SetLootTier(result.GetInt32(37));
+
 		zone->AddWidget(id, widget);
 
 		LogWrite(WIDGET__DEBUG, 0, "Widget", "Loaded Widget: '%s' (%u).", widget->appearance.name, spawn_id);
@@ -6458,7 +6472,7 @@ bool WorldDatabase::LoadObject(ZoneServer* zone, int32 spawn_id) {
 	int32 id = 0;
 	DatabaseResult result;
 
-	database_new.Select(&result, "SELECT so.spawn_id, s.name, s.race, s.model_type, s.command_primary, s.command_secondary, s.targetable, s.size, s.show_name, s.visual_state, s.attackable, s.show_level, s.show_command_icon, s.display_hand_icon, s.faction_id, s.collision_radius, s.transport_id, s.size_offset, so.device_id, s.disable_sounds, s.merchant_min_level, s.merchant_max_level, s.aaxp_rewards\n"
+	database_new.Select(&result, "SELECT so.spawn_id, s.name, s.race, s.model_type, s.command_primary, s.command_secondary, s.targetable, s.size, s.show_name, s.visual_state, s.attackable, s.show_level, s.show_command_icon, s.display_hand_icon, s.faction_id, s.collision_radius, s.transport_id, s.size_offset, so.device_id, s.disable_sounds, s.merchant_min_level, s.merchant_max_level, s.aaxp_rewards, s.loot_tier\n"
 								 "FROM spawn s\n"
 								 "INNER JOIN spawn_objects so\n"
 								 "ON so.spawn_id = s.id\n"
@@ -6501,6 +6515,8 @@ bool WorldDatabase::LoadObject(ZoneServer* zone, int32 spawn_id) {
 		
 		object->SetAAXPRewards(result.GetInt32(22));
 
+		object->SetLootTier(result.GetInt32(23));
+
 		zone->AddObject(id, object);
 
 		LogWrite(OBJECT__DEBUG, 0, "Object", "Loaded Object: '%s' (%u).", object->appearance.name, spawn_id);
@@ -6516,7 +6532,7 @@ bool WorldDatabase::LoadGroundSpawn(ZoneServer* zone, int32 spawn_id) {
 	int32 id = 0;
 	DatabaseResult result;
 
-	database_new.Select(&result, "SELECT sg.spawn_id, s.name, s.race, s.model_type, s.command_primary, s.command_secondary, s.targetable, s.size, s.show_name, s.visual_state, s.attackable, s.show_level, s.show_command_icon, s.display_hand_icon, s.faction_id, s.collision_radius, sg.number_harvests, sg.num_attempts_per_harvest, sg.groundspawn_id, sg.collection_skill, s.size_offset\n"
+	database_new.Select(&result, "SELECT sg.spawn_id, s.name, s.race, s.model_type, s.command_primary, s.command_secondary, s.targetable, s.size, s.show_name, s.visual_state, s.attackable, s.show_level, s.show_command_icon, s.display_hand_icon, s.faction_id, s.collision_radius, sg.number_harvests, sg.num_attempts_per_harvest, sg.groundspawn_id, sg.collection_skill, s.size_offset, s.disable_sounds, s.aaxp_rewards, s.loot_tier\n"
 								 "FROM spawn s\n"
 								 "INNER JOIN spawn_ground sg\n"
 								 "ON sg.spawn_id = s.id\n"
@@ -6555,6 +6571,11 @@ bool WorldDatabase::LoadGroundSpawn(ZoneServer* zone, int32 spawn_id) {
 		spawn->SetGroundSpawnEntryID(result.GetInt32(18));
 		spawn->SetCollectionSkill(result.GetString(19));
 		spawn->SetSizeOffset(result.GetInt8(20));
+		
+		spawn->SetSoundsDisabled(result.GetInt8(21));
+		spawn->SetAAXPRewards(result.GetInt32(22));
+
+		spawn->SetLootTier(result.GetInt32(23));
 
 		zone->AddGroundSpawn(id, spawn);
 
@@ -6610,7 +6631,7 @@ bool WorldDatabase::LoadNPC(ZoneServer* zone, int32 spawn_id) {
 	int32 id = 0;
 	DatabaseResult result;
 										
-	database_new.Select(&result, "SELECT npc.spawn_id, s.name, npc.min_level, npc.max_level, npc.enc_level, s.race, s.model_type, npc.class_, npc.gender, s.command_primary, s.command_secondary, s.show_name, npc.min_group_size, npc.max_group_size, npc.hair_type_id, npc.facial_hair_type_id, npc.wing_type_id, npc.chest_type_id, npc.legs_type_id, npc.soga_hair_type_id, npc.soga_facial_hair_type_id, s.attackable, s.show_level, s.targetable, s.show_command_icon, s.display_hand_icon, s.hp, s.power, s.size, s.collision_radius, npc.action_state, s.visual_state, npc.mood_state, npc.initial_state, npc.activity_status, s.faction_id, s.sub_title, s.merchant_id, s.merchant_type, s.size_offset, npc.attack_type, npc.ai_strategy+0, npc.spell_list_id, npc.secondary_spell_list_id, npc.skill_list_id, npc.secondary_skill_list_id, npc.equipment_list_id, npc.str, npc.sta, npc.wis, npc.intel, npc.agi, npc.heat, npc.cold, npc.magic, npc.mental, npc.divine, npc.disease, npc.poison, npc.aggro_radius, npc.cast_percentage, npc.randomize, npc.soga_model_type, npc.heroic_flag, npc.alignment, npc.elemental, npc.arcane, npc.noxious, s.savagery, s.dissonance, npc.hide_hood, npc.emote_state, s.prefix, s.suffix, s.last_name, s.disable_sounds, s.merchant_min_level, s.merchant_max_level, s.aaxp_rewards\n"
+	database_new.Select(&result, "SELECT npc.spawn_id, s.name, npc.min_level, npc.max_level, npc.enc_level, s.race, s.model_type, npc.class_, npc.gender, s.command_primary, s.command_secondary, s.show_name, npc.min_group_size, npc.max_group_size, npc.hair_type_id, npc.facial_hair_type_id, npc.wing_type_id, npc.chest_type_id, npc.legs_type_id, npc.soga_hair_type_id, npc.soga_facial_hair_type_id, s.attackable, s.show_level, s.targetable, s.show_command_icon, s.display_hand_icon, s.hp, s.power, s.size, s.collision_radius, npc.action_state, s.visual_state, npc.mood_state, npc.initial_state, npc.activity_status, s.faction_id, s.sub_title, s.merchant_id, s.merchant_type, s.size_offset, npc.attack_type, npc.ai_strategy+0, npc.spell_list_id, npc.secondary_spell_list_id, npc.skill_list_id, npc.secondary_skill_list_id, npc.equipment_list_id, npc.str, npc.sta, npc.wis, npc.intel, npc.agi, npc.heat, npc.cold, npc.magic, npc.mental, npc.divine, npc.disease, npc.poison, npc.aggro_radius, npc.cast_percentage, npc.randomize, npc.soga_model_type, npc.heroic_flag, npc.alignment, npc.elemental, npc.arcane, npc.noxious, s.savagery, s.dissonance, npc.hide_hood, npc.emote_state, s.prefix, s.suffix, s.last_name, s.disable_sounds, s.merchant_min_level, s.merchant_max_level, s.aaxp_rewards, s.loot_tier\n"
 								 "FROM spawn s\n"
 								 "INNER JOIN spawn_npcs npc\n"
 								 "ON npc.spawn_id = s.id\n"
@@ -6740,6 +6761,8 @@ bool WorldDatabase::LoadNPC(ZoneServer* zone, int32 spawn_id) {
 		
 		npc->SetAAXPRewards(result.GetInt32(78));
 
+		npc->SetLootTier(result.GetInt32(79));
+
 		zone->AddNPC(id, npc);
 
 		//skipped spells/skills/equipment as it is all loaded, the following rely on a spawn to load

+ 1 - 3
EQ2/source/WorldServer/Zone/map.cpp

@@ -688,9 +688,7 @@ void MapRange::AddVersionRange(std::string zoneName) {
   BOOST_FOREACH(boost::filesystem::path
     const & i, make_pair(iter, eod)) {
     if (is_regular_file(i)) {
-		std::string fileName(i.string());
-
-      if (boost::regex_match(fileName, base_match, re)) {
+      if (boost::regex_match(i.string(), base_match, re)) {
         boost::ssub_match base_sub_match = base_match[2];
         boost::ssub_match base_sub_match2 = base_match[5];
 		boost::ssub_match base_sub_match3 = base_match[6];

+ 23 - 6
EQ2/source/WorldServer/client.cpp

@@ -205,6 +205,8 @@ Client::Client(EQStream* ieqs) : pos_update(125), quest_pos_timer(2000), lua_deb
 	save_spell_state_timer.Disable();
 	save_spell_state_time_bucket = 0;
 	player_loading_complete = false;
+	MItemDetails.SetName("Client::MItemDetails");
+	MSpellDetails.SetName("Client::MSpellDetails");
 }
 
 Client::~Client() {
@@ -2710,10 +2712,10 @@ void Client::HandleExamineInfoRequest(EQApplicationPacket* app) {
 				spell = tmpSpell->spell;
 			lua_interface->FindCustomSpellUnlock();
 		}
-
-		if (spell && sent_spell_details.count(id) == 0) {
+		
+		if (spell && !CountSentSpell(id, tier)) {
 			if (!spell->IsCopiedSpell())
-				sent_spell_details[id] = true;
+				SetSentSpell(spell->GetSpellID(), spell->GetSpellTier());
 
 			EQ2Packet* app = spell->SerializeSpell(this, display, trait_display);
 			//DumpPacket(app);
@@ -2755,7 +2757,9 @@ void Client::HandleExamineInfoRequest(EQApplicationPacket* app) {
 		if (!item)
 			item = master_item_list.GetItem(id);
 		if (item) {// && sent_item_details.count(id) == 0){
+			MItemDetails.writelock(__FUNCTION__, __LINE__);
 			sent_item_details[id] = true;
+			MItemDetails.releasewritelock(__FUNCTION__, __LINE__);
 			EQ2Packet* app = item->serialize(GetVersion(), false, GetPlayer());
 			//DumpPacket(app);
 			QueuePacket(app);
@@ -2784,7 +2788,9 @@ void Client::HandleExamineInfoRequest(EQApplicationPacket* app) {
 		if (!item)
 			item = master_item_list.GetItem(id);
 		if (item) {
+			MItemDetails.writelock(__FUNCTION__, __LINE__);
 			sent_item_details[id] = true;
+			MItemDetails.releasewritelock(__FUNCTION__, __LINE__);
 			EQ2Packet* app = item->serialize(GetVersion(), false, GetPlayer());
 			//DumpPacket(app);
 			QueuePacket(app);
@@ -2845,9 +2851,9 @@ void Client::HandleExamineInfoRequest(EQApplicationPacket* app) {
 				lua_interface->FindCustomSpellUnlock();
 			}
 
-			if (spell && sent_spell_details.count(id) == 0) {
+			if (spell && !CountSentSpell(id, tier)) {
 				if (!spell->IsCopiedSpell())
-					sent_spell_details[id] = true;
+					SetSentSpell(spell->GetSpellID(), spell->GetSpellTier());
 				int8 type = 0;
 				if (version <= 283)
 					type = 1;
@@ -2907,7 +2913,7 @@ void Client::HandleExamineInfoRequest(EQApplicationPacket* app) {
 
 		//if (spell && sent_spell_details.count(spell->GetSpellID()) == 0) {
 		if (!spell->IsCopiedSpell())
-			sent_spell_details[spell->GetSpellID()] = true;
+			SetSentSpell(spell->GetSpellID(), spell->GetSpellTier());
 		//	EQ2Packet* app = spell->SerializeAASpell(this,tier, data, false, GetItemPacketType(GetVersion()), 0x04);
 		EQ2Packet* app = master_spell_list.GetAASpellPacket(id, tier, this, false, 0x4F);//0x45 change version to match client
 		/////////////////////////////////////////GetAASpellPacket(int32 id, int8 tier, Client* client, bool display, int8 packet_type) {
@@ -10611,4 +10617,15 @@ void Client::TriggerSpellSave()
 		}
 	}
 	MSaveSpellStateMutex.unlock();
+}
+
+void Client::UpdateSentSpellList() {
+	MSpellDetails.readlock(__FUNCTION__, __LINE__);
+	std::map<int32, int32>::iterator itr;
+	for(itr = sent_spell_details.begin(); itr != sent_spell_details.end(); itr++) {
+		Spell* spell = master_spell_list.GetSpell(itr->first, itr->second);
+		EQ2Packet* app = spell->SerializeSpell(this, false, false);
+		QueuePacket(app);
+	}
+	MSpellDetails.releasereadlock(__FUNCTION__, __LINE__);
 }

+ 32 - 2
EQ2/source/WorldServer/client.h

@@ -487,11 +487,39 @@ public:
 
 	void TriggerSpellSave();
 
-	void ClearSentItemDetails() { sent_item_details.clear(); }
+	void ClearSentItemDetails() { 
+		MItemDetails.writelock(__FUNCTION__, __LINE__);
+		sent_item_details.clear();
+		MItemDetails.releasewritelock(__FUNCTION__, __LINE__);
+	}
 
 	bool IsPlayerLoadingComplete() { return player_loading_complete; }
 
 	int32 GetRejoinGroupID() { return rejoin_group_id; }
+
+	void ClearSentSpellList() { 
+		MSpellDetails.writelock(__FUNCTION__, __LINE__);
+		sent_spell_details.clear();
+		MSpellDetails.releasewritelock(__FUNCTION__, __LINE__);
+	}
+
+	void UpdateSentSpellList();
+
+	bool CountSentSpell(int32 id, int32 tier) {
+		bool res = false;
+		MSpellDetails.readlock(__FUNCTION__, __LINE__);
+		std::map<int32, int32>::iterator itr = sent_spell_details.find(id);
+		if(itr->second == tier)
+			res = true;
+		MSpellDetails.releasereadlock(__FUNCTION__, __LINE__);
+		return res;
+	}
+
+	void SetSentSpell(int32 id, int32 tier) {
+		MSpellDetails.writelock(__FUNCTION__, __LINE__);
+		sent_spell_details[id] = tier;
+		MSpellDetails.releasewritelock(__FUNCTION__, __LINE__);
+	}
 private:
 	void    SavePlayerImages();
 	void	SkillChanged(Skill* skill, int16 previous_value, int16 new_value);
@@ -526,7 +554,7 @@ private:
 	map<int32, map<int8, string> > conversation_map;
 	int32	current_quest_id;
 	Spawn*	banker;
-	map<int32, bool> sent_spell_details;
+	map<int32, int32> sent_spell_details;
 	map<int32, bool> sent_item_details;
 	Player*	player;
 	int16	version;
@@ -611,6 +639,8 @@ private:
 	int32 save_spell_state_time_bucket; // bucket as we collect over time when timer is reset by new spell effects being casted
 	std::mutex MSaveSpellStateMutex;
 	bool player_loading_complete;
+	Mutex MItemDetails;
+	Mutex MSpellDetails;
 };
 
 class ClientList {

+ 8 - 30
EQ2/source/WorldServer/zoneserver.cpp

@@ -6719,30 +6719,8 @@ void ZoneServer::ResurrectSpawn(Spawn* spawn, Client* client) {
 	}
 
 	if(!no_calcs && caster){
-		//Potency Mod
-		((Entity*)caster)->MStats.lock();
-		heal_amt *=  ((caster->stats[ITEM_STAT_POTENCY] / 100) + 1);
-		power_amt *= ((caster->stats[ITEM_STAT_POTENCY] / 100) + 1);
-		((Entity*)caster)->MStats.unlock();
-
-		//Ability Mod
-		heal_amt += (int32)min((int32)info->get_ability_modifier(), (int32)(heal_amt / 2));
-		power_amt += (int32)min((int32)info->get_ability_modifier(), (int32)(power_amt / 2));
-
-		if(!crit_mod || crit_mod == 1){
-			if(crit_mod == 1) 
-				crit = true;
-			else {
-				// Crit Roll
-				float chance = (float)max((float)0, (float)info->get_crit_chance());
-				crit = (MakeRandomFloat(0, 100) <= chance); 
-			}
-			if(crit){
-				//Apply total crit multiplier with crit bonus
-				heal_amt *= ((info->get_crit_bonus() / 100) + 1.3);
-				power_amt *= ((info->get_crit_bonus() / 100) + 1.3);
-			}
-		}
+		heal_amt = caster->CalculateHealAmount(spawn, heal_amt, crit_mod, &crit);
+		power_amt = caster->CalculateHealAmount(spawn, power_amt, crit_mod, &crit);
 	}
 
 	//Set this rez as a crit to be passed to subspell (not yet used)
@@ -7411,10 +7389,10 @@ vector<int32> ZoneServer::GetSpawnLootList(int32 spawn_id, int32 zone_id, int8 s
 					continue;
 			}
 			bool entryAdded = false;
-			if (loot->minLevel == 0 && loot->maxLevel == 0 && (entryAdded = true)) // successful plan to add set entryAdded to true
+			if (loot->minLevel == 0 && loot->maxLevel == 0 && (!loot->loot_tier || spawn->GetLootTier() >= loot->loot_tier) && (entryAdded = true)) // successful plan to add set entryAdded to true
 				ret.push_back(loot->table_id);
 			else {
-				if (spawn_level >= loot->minLevel && spawn_level <= loot->maxLevel && (entryAdded = true)) // successful plan to add set entryAdded to true
+				if (spawn_level >= loot->minLevel && spawn_level <= loot->maxLevel && (!loot->loot_tier || spawn->GetLootTier() >= loot->loot_tier) && (entryAdded = true)) // successful plan to add set entryAdded to true
 					ret.push_back(loot->table_id);
 			}
 			
@@ -7435,10 +7413,10 @@ vector<int32> ZoneServer::GetSpawnLootList(int32 spawn_id, int32 zone_id, int8 s
 					continue;
 			}
 			bool entryAdded = false;
-			if (loot->minLevel == 0 && loot->maxLevel == 0 && (entryAdded = true)) // successful plan to add set entryAdded to true
+			if (loot->minLevel == 0 && loot->maxLevel == 0 && (!loot->loot_tier || spawn->GetLootTier() >= loot->loot_tier) && (entryAdded = true)) // successful plan to add set entryAdded to true
 				ret.push_back(loot->table_id);
 			else {
-				if (spawn_level >= loot->minLevel && spawn_level <= loot->maxLevel && (entryAdded = true)) // successful plan to add set entryAdded to true
+				if (spawn_level >= loot->minLevel && spawn_level <= loot->maxLevel && (!loot->loot_tier || spawn->GetLootTier() >= loot->loot_tier) && (entryAdded = true)) // successful plan to add set entryAdded to true
 					ret.push_back(loot->table_id);
 			}
 
@@ -7459,10 +7437,10 @@ vector<int32> ZoneServer::GetSpawnLootList(int32 spawn_id, int32 zone_id, int8 s
 					continue;
 			}
 			bool entryAdded = false;
-			if (loot->minLevel == 0 && loot->maxLevel == 0 && (entryAdded = true)) // successful plan to add set entryAdded to true
+			if (loot->minLevel == 0 && loot->maxLevel == 0 && (!loot->loot_tier || spawn->GetLootTier() >= loot->loot_tier) && (entryAdded = true)) // successful plan to add set entryAdded to true
 				ret.push_back(loot->table_id);
 			else {
-				if (spawn_level >= loot->minLevel && spawn_level <= loot->maxLevel && (entryAdded = true)) // successful plan to add set entryAdded to true
+				if (spawn_level >= loot->minLevel && spawn_level <= loot->maxLevel && (!loot->loot_tier || spawn->GetLootTier() >= loot->loot_tier) && (entryAdded = true)) // successful plan to add set entryAdded to true
 					ret.push_back(loot->table_id);
 			}