Browse Source

- Broker 'btypes' (special effects) addressed for AoM client, new table broker_item_map created check DB/updates/broker_item_map_july3_2022.sql Fix #336
* May need further expansion some day to compare other clients, we can map btype, but the itype/ltype may also need to be mapped.
- Fixed Item::HasStat to work for stats in the 100-199 range since they only match the type (1) and the subtype is the value, meaning we have to match up the skill name
Partial addressing of Issue #279
- Ministration Skill/Power reduction added with rules
RULE_INIT(R_Spells, MinistrationSkillID, "366253016"); // ministration skill id used to map power reduction rule MinistrationPowerReductionMax
RULE_INIT(R_Spells, MinistrationPowerReductionMax, "15.0"); // max percentage of power reduction for spells with ministration mastery skill (default is 15.0 for 15%)
RULE_INIT(R_Spells, MinistrationPowerReductionSkill, "25"); // divides by integer value to establish how much skill req for higher power reduction
- Subjugation, Disruption, Ordination and Aggression resistance reduction by skill of the attacker
RULE_INIT(R_Spells, MasterSkillReduceSpellResist, "25"); // divides by integer value to establish how much skill bonus for reducing spell resistance on target
- Weapon Skills item stat now added to the weapon skill (crush/slash/pierce) for determinehit

Emagi 1 year ago
parent
commit
8aee382185

+ 52 - 0
DB/updates/broker_item_map_july3_2022.sql

@@ -0,0 +1,52 @@
+-- MySQL dump 10.19  Distrib 10.3.34-MariaDB, for debian-linux-gnu (x86_64)
+--
+-- Host: localhost    Database: eq2emu
+-- ------------------------------------------------------
+-- Server version	10.3.34-MariaDB-0ubuntu0.20.04.1
+
+/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
+/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
+/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
+/*!40101 SET NAMES utf8mb4 */;
+/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
+/*!40103 SET TIME_ZONE='+00:00' */;
+/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
+/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
+/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
+/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
+
+--
+-- Table structure for table `broker_item_map`
+--
+
+DROP TABLE IF EXISTS `broker_item_map`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `broker_item_map` (
+  `version_range1` int(10) unsigned NOT NULL DEFAULT 0,
+  `version_range2` int(10) unsigned NOT NULL DEFAULT 0,
+  `client_bitmask` bigint(20) unsigned NOT NULL DEFAULT 0,
+  `server_bitmask` bigint(20) unsigned NOT NULL DEFAULT 0
+) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `broker_item_map`
+--
+
+LOCK TABLES `broker_item_map` WRITE;
+/*!40000 ALTER TABLE `broker_item_map` DISABLE KEYS */;
+INSERT INTO `broker_item_map` VALUES (60085,60116,1,2),(60085,60116,2,4),(60085,60116,4,8),(60085,60116,8,16),(60085,60116,16,32),(60085,60116,32,64),(60085,60116,64,128),(60085,60116,128,256),(60085,60116,256,512),(60085,60116,512,2048),(60085,60116,1024,16384),(60085,60116,2048,4096),(60085,60116,4096,65536),(60085,60116,8192,131072),(60085,60116,16384,262144),(60085,60116,65536,2097152),(60085,60116,131072,4194304),(60085,60116,8589934592,8388608),(60085,60116,524288,16777216),(60085,60116,2097152,33554432),(60085,60116,8388608,67108864),(60085,60116,4194304,134217728),(60085,60116,32768,268435456),(60085,60116,134217728,536870912),(60085,60116,268435456,1073741824),(60085,60116,4294967296,2147483648),(60085,60116,1048576,8589934592),(60085,60116,16777216,17179869184),(60085,60116,33554432,34359738368),(60085,60116,67108864,137438953472),(60085,60116,262144,274877906944),(60085,60116,536870912,549755813888),(60085,60116,1073741824,68719476736),(60085,60116,2147483648,4294967296);
+/*!40000 ALTER TABLE `broker_item_map` ENABLE KEYS */;
+UNLOCK TABLES;
+/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
+
+/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
+/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
+/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
+/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
+/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
+/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
+/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
+
+-- Dump completed on 2022-07-04 20:00:51

+ 28 - 11
EQ2/source/WorldServer/Combat.cpp

@@ -372,14 +372,7 @@ bool Entity::SpellAttack(Spawn* victim, float distance, LuaSpell* luaspell, int8
 		return false;
 
 	Spell* spell = luaspell->spell;
-	float bonus = 0;
 	Skill* skill = nullptr;
-	if(spell->GetSpellData()->resistibility > 0)
-		bonus -= (1 - spell->GetSpellData()->resistibility)*100;
-
-	skill = GetSkillByID(spell->GetSpellData()->mastery_skill, false);
-	if(skill)
-		bonus += skill->current_val / 25;
 
 	int8 hit_result = 0;
 	bool is_tick = false; // if spell is already active, this is a tick
@@ -390,7 +383,7 @@ bool Entity::SpellAttack(Spawn* victim, float distance, LuaSpell* luaspell, int8
 	else if(spell->GetSpellData()->type == SPELL_BOOK_TYPE_COMBAT_ART)
 		hit_result = DetermineHit(victim, damage_type, 0, false);
 	else
-		hit_result = DetermineHit(victim, damage_type, 0, true);
+		hit_result = DetermineHit(victim, damage_type, 0, true, luaspell);
 		
 	if(hit_result == DAMAGE_PACKET_RESULT_SUCCESSFUL) {
 		luaspell->last_spellattack_hit = true;
@@ -674,7 +667,7 @@ bool Entity::SpellHeal(Spawn* target, float distance, LuaSpell* luaspell, string
 	return true;
 }
 
-int8 Entity::DetermineHit(Spawn* victim, int8 damage_type, float ToHitBonus, bool spell){
+int8 Entity::DetermineHit(Spawn* victim, int8 damage_type, float ToHitBonus, bool is_caster_spell, LuaSpell* lua_spell){
 	if(!victim) {
 		return DAMAGE_PACKET_RESULT_MISS;
 	}
@@ -685,7 +678,7 @@ int8 Entity::DetermineHit(Spawn* victim, int8 damage_type, float ToHitBonus, boo
 
 	bool behind = false;
 	// Monk added with Brawler to 360 degree support per KoS Prima Official eGuide Fighter: Monk, pg 138, denoted '360-Degree Avoidance!'
-	if(!victim->IsEntity() || (!spell && victim->GetAdventureClass() != BRAWLER && victim->GetAdventureClass() != MONK && (behind = BehindTarget(victim)))) {
+	if(!victim->IsEntity() || (!is_caster_spell && victim->GetAdventureClass() != BRAWLER && victim->GetAdventureClass() != MONK && (behind = BehindTarget(victim)))) {
 		return DAMAGE_PACKET_RESULT_SUCCESSFUL;
 	}
 
@@ -700,12 +693,36 @@ int8 Entity::DetermineHit(Spawn* victim, int8 damage_type, float ToHitBonus, boo
 		{
 			MStats.lock();
 			skillAddedByWeapon = stats[skillID];
+			if(!is_caster_spell) {
+				float item_stat_weapon_skill = stats[ITEM_STAT_WEAPON_SKILLS];
+				skillAddedByWeapon += item_stat_weapon_skill;
+			}
 			MStats.unlock();
 		}
 	}
 	
 	if (skill)
 		bonus += (skill->current_val+skillAddedByWeapon) / 25;
+		
+	if(is_caster_spell && lua_spell) {
+		if(lua_spell->spell->GetSpellData()->resistibility > 0)
+			bonus -= (1.0f - lua_spell->spell->GetSpellData()->resistibility)*100.0f;
+	
+		// Here we take into account Subjugation, Disruption and Ordination (debuffs)
+		if(lua_spell->spell->GetSpellData()->mastery_skill) {
+			int32 master_skill_reduce = rule_manager.GetGlobalRule(R_Spells, MasterSkillReduceSpellResist)->GetInt32();
+			if(master_skill_reduce < 1)
+				master_skill_reduce = 25;
+			
+			Skill* master_skill = GetSkillByID(lua_spell->spell->GetSpellData()->mastery_skill, false);
+			if(master_skill && (master_skill->name.data == "Subjugation" || master_skill->name.data == "Disruption" || 
+				master_skill->name.data == "Ordination" || master_skill->name.data == "Aggression")) {
+				bonus += master_skill->current_val / master_skill_reduce;
+			}
+		}
+		
+	}
+	
 	if (victim->IsEntity())
 		bonus -= ((Entity*)victim)->GetDamageTypeResistPercentage(damage_type);
 
@@ -716,7 +733,7 @@ int8 Entity::DetermineHit(Spawn* victim, int8 damage_type, float ToHitBonus, boo
 	if(skill)
 		roll_chance -= skill->current_val / 25;
 
-	if(!spell){ // melee or range attack		
+	if(!is_caster_spell){ // melee or range attack		
 		skill = GetSkillByName("Offense", true); //add this skill for NPCs
 		if(skill)
 			roll_chance -= skill->current_val / 25;

+ 1 - 1
EQ2/source/WorldServer/Commands/Commands.cpp

@@ -4207,7 +4207,7 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 					LogWrite(ITEM__WARNING, 0, "Item", "SearchStores: %s", values);
 
 					map<string, string> str_values = TranslateBrokerRequest(values);
-					vector<Item*>* items = master_item_list.GetItems(str_values);
+					vector<Item*>* items = master_item_list.GetItems(str_values, client);
 					if(items){
 						client->SetItemSearch(items);
 						client->SearchStore(0);

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

@@ -1359,7 +1359,7 @@ public:
 	bool			SpellAttack(Spawn* victim, float distance, LuaSpell* luaspell, int8 damage_type, int32 low_damage, int32 high_damage, int8 crit_mod = 0, bool no_calcs = false);
 	bool			ProcAttack(Spawn* victim, int8 damage_type, int32 low_damage, int32 high_damage, string name, string success_msg, string effect_msg);
 	bool            SpellHeal(Spawn* target, float distance, LuaSpell* luaspell, string heal_type, int32 low_heal, int32 high_heal, int8 crit_mod = 0, bool no_calcs = false, string custom_spell_name="");
-	int8			DetermineHit(Spawn* victim, int8 damage_type, float ToHitBonus, bool spell);
+	int8			DetermineHit(Spawn* victim, int8 damage_type, float ToHitBonus, bool is_caster_spell, LuaSpell* lua_spell = nullptr);
 	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, LuaSpell* spell = 0);

+ 212 - 150
EQ2/source/WorldServer/Items/Items.cpp

@@ -86,9 +86,69 @@ void MasterItemList::AddMappedItemStat(int32 id, std::string lower_case_name)
 
 MasterItemList::~MasterItemList(){
 	RemoveAll();
+	
+	map<VersionRange*, map<int64,int64>>::iterator itr;
+	for (itr = broker_item_map.begin(); itr != broker_item_map.end(); itr++)
+	{
+		VersionRange* range = itr->first;
+		delete range;
+	}
+
+	broker_item_map.clear();
 }
 
-vector<Item*>* MasterItemList::GetItems(string name, int64 itype, int32 ltype, int32 btype, int64 minprice, int64 maxprice, int8 minskill, int8 maxskill, string seller, string adornment, int8 mintier, int8 maxtier, int16 minlevel, int16 maxlevel, sint8 itemclass){
+
+void MasterItemList::AddBrokerItemMapRange(int32 min_version, int32 max_version,
+	int64 client_bitmask, int64 server_bitmask)
+{
+	map<VersionRange*, map<int64,int64>>::iterator itr = FindBrokerItemMapVersionRange(min_version, max_version);
+	if (itr != broker_item_map.end()) {
+		itr->second.insert(make_pair(client_bitmask, server_bitmask));
+		return;
+	}
+
+	VersionRange* range = new VersionRange(min_version, max_version);
+	broker_item_map[range][client_bitmask] = server_bitmask;
+}
+
+map<VersionRange*, map<int64,int64>>::iterator MasterItemList::FindBrokerItemMapVersionRange(int32 min_version, int32 max_version)
+{
+	map<VersionRange*, map<int64,int64>>::iterator itr;
+	for (itr = broker_item_map.begin(); itr != broker_item_map.end(); itr++)
+	{
+		VersionRange* range = itr->first;
+		// if min and max version are both in range
+		if (range->GetMinVersion() <= min_version && max_version <= range->GetMaxVersion())
+			return itr;
+		// if the min version is in range, but max range is 0
+		else if (range->GetMinVersion() <= min_version && range->GetMaxVersion() == 0)
+			return itr;
+		// if min version is 0 and max_version has a cap
+		else if (range->GetMinVersion() == 0 && max_version <= range->GetMaxVersion())
+			return itr;
+	}
+
+	return broker_item_map.end();
+}
+
+map<VersionRange*, map<int64,int64>>::iterator MasterItemList::FindBrokerItemMapByVersion(int32 version)
+{
+	map<VersionRange*, map<int64,int64>>::iterator enditr = broker_item_map.end();
+	map<VersionRange*, map<int64,int64>>::iterator itr;
+	for (itr = broker_item_map.begin(); itr != broker_item_map.end(); itr++)
+	{
+		VersionRange* range = itr->first;
+		// if min and max version are both in range
+		if(range->GetMinVersion() == 0 && range->GetMaxVersion() == 0)
+			enditr = itr;
+		else if (version >= range->GetMinVersion() && version <= range->GetMaxVersion())
+			return itr;
+	}
+
+	return enditr;
+}
+
+vector<Item*>* MasterItemList::GetItems(string name, int64 itype, int64 ltype, int64 btype, int64 minprice, int64 maxprice, int8 minskill, int8 maxskill, string seller, string adornment, int8 mintier, int8 maxtier, int16 minlevel, int16 maxlevel, sint8 itemclass){
 	vector<Item*>* ret = new vector<Item*>;
 	map<int32,Item*>::iterator iter;
     Item* item = 0;
@@ -101,7 +161,7 @@ vector<Item*>* MasterItemList::GetItems(string name, int64 itype, int32 ltype, i
 	//	chkseller = seller.c_str();
 	//if(adornment.length() > 0)
 	//	chkadornment = adornment.c_str();
-	LogWrite(ITEM__WARNING, 0, "Item", "Get Items: %s (itype: %llu, ltype: %u, btype: %u, minskill: %u, maxskill: %u, mintier: %u, maxtier: %u, minlevel: %u, maxlevel: %u itemclass %i)", name.c_str(), itype, ltype, btype, minskill, maxskill, mintier, maxtier, minlevel, maxlevel, itemclass);
+	LogWrite(ITEM__WARNING, 0, "Item", "Get Items: %s (itype: %llu, ltype: %llu, btype: %llu, minskill: %u, maxskill: %u, mintier: %u, maxtier: %u, minlevel: %u, maxlevel: %u itemclass %i)", name.c_str(), itype, ltype, btype, minskill, maxskill, mintier, maxtier, minlevel, maxlevel, itemclass);
 	bool should_add = true;
 	for(iter = items.begin();iter != items.end(); iter++){
 		item = iter->second;
@@ -373,249 +433,237 @@ vector<Item*>* MasterItemList::GetItems(string name, int64 itype, int32 ltype, i
 						break;
 					}
 					case ITEM_BROKER_STAT_TYPE_DEF:{
-						for (itr = item->item_stats.begin(); itr != item->item_stats.end() && !stat_found; itr++) {
-							if ((*itr)->stat_type_combined == ITEM_STAT_DEFLECTIONCHANCE) {
-								stat_found = true;
-								break;
-							}
-						}
+						stat_found = item->HasStat(ITEM_STAT_DEFENSE, GetItemStatNameByID(ITEM_STAT_DEFENSE));
 						if (stat_found)
 							should_add = true;
 						break;
 					}
 					case ITEM_BROKER_STAT_TYPE_STR:{
-						for (itr = item->item_stats.begin(); itr != item->item_stats.end() && !stat_found; itr++) {
-							if ((*itr)->stat_type_combined == ITEM_STAT_STR) {
-								stat_found = true;
-								break;
-							}
-						}
+						stat_found = item->HasStat(ITEM_STAT_STR);
 						if (stat_found)
 							should_add = true;
 						break;
 					}
 					case ITEM_BROKER_STAT_TYPE_STA:{
-						for (itr = item->item_stats.begin(); itr != item->item_stats.end() && !stat_found; itr++) {
-							if ((*itr)->stat_type_combined == ITEM_STAT_STA) {
-								stat_found = true;
-								break;
-							}
-						}
+						stat_found = item->HasStat(ITEM_STAT_STA);
 						if (stat_found)
 							should_add = true;
 						break;
 					}
 					case ITEM_BROKER_STAT_TYPE_AGI:{
-						for (itr = item->item_stats.begin(); itr != item->item_stats.end() && !stat_found; itr++) {
-							if ((*itr)->stat_type_combined == ITEM_STAT_AGI) {
-								stat_found = true;
-								break;
-							}
-						}
+						stat_found = item->HasStat(ITEM_STAT_AGI);
 						if (stat_found)
 							should_add = true;
 						break;
 					}
 					case ITEM_BROKER_STAT_TYPE_WIS:{
-						for (itr = item->item_stats.begin(); itr != item->item_stats.end() && !stat_found; itr++) {
-							if ((*itr)->stat_type_combined == ITEM_STAT_WIS) {
-								stat_found = true;
-								break;
-							}
-						}
+						stat_found = item->HasStat(ITEM_STAT_WIS);
 						if (stat_found)
 							should_add = true;
 						break;
 					}
 					case ITEM_BROKER_STAT_TYPE_INT:{
-						for (itr = item->item_stats.begin(); itr != item->item_stats.end() && !stat_found; itr++) {
-							if ((*itr)->stat_type_combined == ITEM_STAT_INT) {
-								stat_found = true;
-								break;
-							}
-						}
+						stat_found = item->HasStat(ITEM_STAT_INT);
 						if (stat_found)
 							should_add = true;
 						break;
 					}
 					case ITEM_BROKER_STAT_TYPE_HEALTH:{
-							for (itr = item->item_stats.begin(); itr != item->item_stats.end() && !stat_found; itr++) {
-							if ((*itr)->stat_type_combined == ITEM_STAT_STR) {
-								stat_found = true;
-								break;
-							}
-						}
+						stat_found = item->HasStat(ITEM_STAT_HEALTH);
 						if (stat_found)
 							should_add = true;
 						break;
 					}
 					case ITEM_BROKER_STAT_TYPE_POWER:{
-						for (itr = item->item_stats.begin(); itr != item->item_stats.end() && !stat_found; itr++) {
-							if ((*itr)->stat_type_combined == ITEM_STAT_STR) {
-								stat_found = true;
-								break;
-							}
-						}
+						stat_found = item->HasStat(ITEM_STAT_POWER);
 						if (stat_found)
 							should_add = true;
 						break;
 					}
 					case ITEM_BROKER_STAT_TYPE_HEAT:{
-						for (itr = item->item_stats.begin(); itr != item->item_stats.end() && !stat_found; itr++) {
-							if ((*itr)->stat_type_combined == ITEM_STAT_VS_HEAT) {
-								stat_found = true;
-								break;
-							}
-						}
+						stat_found = item->HasStat(ITEM_STAT_VS_HEAT);
 						if (stat_found)
 							should_add = true;
 						break;
 					}
 					case ITEM_BROKER_STAT_TYPE_COLD:{
-						for (itr = item->item_stats.begin(); itr != item->item_stats.end() && !stat_found; itr++) {
-							if ((*itr)->stat_type_combined == ITEM_STAT_VS_COLD) {
-								stat_found = true;
-								break;
-							}
-						}
+						stat_found = item->HasStat(ITEM_STAT_VS_COLD);
 						if (stat_found)
 							should_add = true;
 						break;
 					}
 					case ITEM_BROKER_STAT_TYPE_MAGIC:{
-						for (itr = item->item_stats.begin(); itr != item->item_stats.end() && !stat_found; itr++) {
-							if ((*itr)->stat_type_combined == ITEM_STAT_VS_MAGIC) {
-								stat_found = true;
-								break;
-							}
-						}
+						stat_found = item->HasStat(ITEM_STAT_VS_MAGIC);
 						if (stat_found)
 							should_add = true;
 						break;
 					}
 					case ITEM_BROKER_STAT_TYPE_MENTAL:{
-						for (itr = item->item_stats.begin(); itr != item->item_stats.end() && !stat_found; itr++) {
-							if ((*itr)->stat_type_combined == ITEM_STAT_VS_MENTAL) {
-								stat_found = true;
-								break;
-							}
-						}
+						stat_found = item->HasStat(ITEM_STAT_VS_MENTAL);
 						if (stat_found)
 							should_add = true;
 						break;
 					}
 					case ITEM_BROKER_STAT_TYPE_DIVINE:{
-						for (itr = item->item_stats.begin(); itr != item->item_stats.end() && !stat_found; itr++) {
-							if ((*itr)->stat_type_combined == ITEM_STAT_VS_DIVINE) {
-								stat_found = true;
-								break;
-							}
-						}
+						stat_found = item->HasStat(ITEM_STAT_VS_DIVINE);
 						if (stat_found)
 							should_add = true;
 						break;
 					}
 					case ITEM_BROKER_STAT_TYPE_POISON:{
-						for (itr = item->item_stats.begin(); itr != item->item_stats.end() && !stat_found; itr++) {
-							if ((*itr)->stat_type_combined == ITEM_STAT_VS_POISON) {
-								stat_found = true;
-								break;
-							}
-						}
+						stat_found = item->HasStat(ITEM_STAT_VS_POISON);
 						if (stat_found)
 							should_add = true;
 						break;
 					}
 					case ITEM_BROKER_STAT_TYPE_DISEASE:{
-						for (itr = item->item_stats.begin(); itr != item->item_stats.end() && !stat_found; itr++) {
-							if ((*itr)->stat_type_combined == ITEM_STAT_VS_DISEASE) {
-								stat_found = true;
-								break;
-							}
-						}
+						stat_found = item->HasStat(ITEM_STAT_VS_DISEASE);
 						if (stat_found)
 							should_add = true;
 						break;
 					}
 					case ITEM_BROKER_STAT_TYPE_CRUSH:{
-						for (itr = item->item_stats.begin(); itr != item->item_stats.end() && !stat_found; itr++) {
-							if ((*itr)->stat_type_combined == ITEM_STAT_DMG_CRUSH) {
-								stat_found = true;
-								break;
-							}
-						}
+						stat_found = item->HasStat(ITEM_STAT_DMG_CRUSH);
 						if (stat_found)
 							should_add = true;
 						break;
 					}
 					case ITEM_BROKER_STAT_TYPE_SLASH:{
-						for (itr = item->item_stats.begin(); itr != item->item_stats.end() && !stat_found; itr++) {
-							if ((*itr)->stat_type_combined == ITEM_STAT_DMG_SLASH) {
-								stat_found = true;
-								break;
-							}
-						}
+						stat_found = item->HasStat(ITEM_STAT_DMG_SLASH);
 						if (stat_found)
 							should_add = true;
 						break;
 					}
 					case ITEM_BROKER_STAT_TYPE_PIERCE:{
-						for (itr = item->item_stats.begin(); itr != item->item_stats.end() && !stat_found; itr++) {
-							if ((*itr)->stat_type_combined == ITEM_STAT_DMG_PIERCE) {
-								stat_found = true;
-								break;
-							}
-						}
+						stat_found = item->HasStat(ITEM_STAT_DMG_PIERCE);
 						if (stat_found)
 							should_add = true;
 						break;
 					}
 					case ITEM_BROKER_STAT_TYPE_CRITICAL: {
-						/*for (itr = item->item_stats.begin(); itr != item->item_stats.end() && !stat_found; itr++) {
-							if ((*itr)->stat_type_combined == ITEM_STAT_STR) {
-								stat_found = true;
-								break;
-							}
-						}
+						stat_found = item->HasStat(ITEM_STAT_CRITICALMITIGATION);
 						if (stat_found)
-							should_add = true;*/
-						LogWrite(ITEM__DEBUG, 0, "Item", "Scatman debugging :).  This needs to be updated when fully support the new expansion");
+							should_add = true;
 						break;
 					}
 					case ITEM_BROKER_STAT_TYPE_DBL_ATTACK:{
-						/*for (itr = item->item_stats.begin(); itr != item->item_stats.end() && !stat_found; itr++) {
-							if ((*itr)->stat_type_combined == ITEM_STAT_STR) {
-								stat_found = true;
-								break;
-							}
-						}
+						stat_found = item->HasStat(ITEM_STAT_MULTIATTACKCHANCE);
 						if (stat_found)
-							should_add = true;*/
-						LogWrite(ITEM__DEBUG, 0, "Item", "Scatman debugging :).  This needs to be updated when fully support the new expansion");
+							should_add = true;
 						break;
 					}
 					case ITEM_BROKER_STAT_TYPE_ABILITY_MOD:{
-						/*for (itr = item->item_stats.begin(); itr != item->item_stats.end() && !stat_found; itr++) {
-							if ((*itr)->stat_type_combined == ITEM_STAT_STR) {
-								stat_found = true;
-								break;
-							}
-						}
+						stat_found = item->HasStat(ITEM_STAT_ABILITY_MODIFIER);
 						if (stat_found)
-							should_add = true;*/
-						LogWrite(ITEM__DEBUG, 0, "Item", "Scatman debugging :).  This needs to be updated when fully support the new expansion");
+							should_add = true;
 						break;
 					}
 					case ITEM_BROKER_STAT_TYPE_POTENCY:{
-						/*for (itr = item->item_stats.begin(); itr != item->item_stats.end() && !stat_found; itr++) {
-							if ((*itr)->stat_type_combined == ITEM_STAT_STR) {
-								stat_found = true;
-								break;
-							}
-						}
+						stat_found = item->HasStat(ITEM_STAT_POTENCY);
 						if (stat_found)
-							should_add = true;*/
-						LogWrite(ITEM__DEBUG, 0, "Item", "Scatman debugging :).  This needs to be updated when fully support the new expansion");
+							should_add = true;
+						break;
+					}
+					case ITEM_BROKER_STAT_TYPE_AEAUTOATTACK:{
+						stat_found = item->HasStat(ITEM_STAT_AEAUTOATTACKCHANCE);
+						if (stat_found)
+							should_add = true;
+						break;
+					}
+					case ITEM_BROKER_STAT_TYPE_ATTACKSPEED:{
+						stat_found = item->HasStat(ITEM_STAT_ATTACKSPEED);
+						if (stat_found)
+							should_add = true;
+						break;
+					}
+					case ITEM_BROKER_STAT_TYPE_BLOCKCHANCE:{
+						stat_found = item->HasStat(ITEM_STAT_EXTRASHIELDBLOCKCHANCE);
+						if (stat_found)
+							should_add = true;
+						break;
+					}
+					case ITEM_BROKER_STAT_TYPE_CASTINGSPEED:{
+						stat_found = item->HasStat(ITEM_STAT_ABILITYCASTINGSPEED);
+						if (stat_found)
+							should_add = true;
+						break;
+					}
+					case ITEM_BROKER_STAT_TYPE_CRITBONUS:{
+						stat_found = item->HasStat(ITEM_STAT_CRITBONUS);
+						if (stat_found)
+							should_add = true;
+						break;
+					}
+					case ITEM_BROKER_STAT_TYPE_CRITCHANCE:{
+						stat_found = item->HasStat(ITEM_STAT_MELEECRITCHANCE);
+						if (stat_found)
+							should_add = true;
+						break;
+					}
+					case ITEM_BROKER_STAT_TYPE_DPS:{
+						stat_found = item->HasStat(ITEM_STAT_DPS);
+						if (stat_found)
+							should_add = true;
+						break;
+					}
+					case ITEM_BROKER_STAT_TYPE_FLURRYCHANCE:{
+						stat_found = item->HasStat(ITEM_STAT_FLURRY);
+						if (stat_found)
+							should_add = true;
+						break;
+					}
+					case ITEM_BROKER_STAT_TYPE_HATEGAIN:{
+						stat_found = item->HasStat(ITEM_STAT_HATEGAINMOD);
+						if (stat_found)
+							should_add = true;
+						break;
+					}
+					case ITEM_BROKER_STAT_TYPE_MITIGATION:{
+						stat_found = item->HasStat(ITEM_STAT_ARMORMITIGATIONINCREASE);
+						if (stat_found)
+							should_add = true;
+						break;
+					}
+					case ITEM_BROKER_STAT_TYPE_MULTI_ATTACK:{
+						stat_found = item->HasStat(ITEM_STAT_MULTIATTACKCHANCE);
+						if (stat_found)
+							should_add = true;
+						break;
+					}
+					case ITEM_BROKER_STAT_TYPE_RECOVERY:{
+						stat_found = item->HasStat(ITEM_STAT_ABILITYRECOVERYSPEED);
+						if (stat_found)
+							should_add = true;
+						break;
+					}
+					case ITEM_BROKER_STAT_TYPE_REUSE_SPEED:{
+						stat_found = item->HasStat(ITEM_STAT_ABILITYREUSESPEED);
+						if (stat_found)
+							should_add = true;
+						break;
+					}
+					case ITEM_BROKER_STAT_TYPE_SPELL_WPNDMG:{
+						stat_found = item->HasStat(ITEM_STAT_SPELLWEAPONDAMAGEBONUS);
+						if (stat_found)
+							should_add = true;
+						break;
+					}
+					case ITEM_BROKER_STAT_TYPE_STRIKETHROUGH:{
+						stat_found = item->HasStat(ITEM_STAT_STRIKETHROUGH);
+						if (stat_found)
+							should_add = true;
+						break;
+					}
+					case ITEM_BROKER_STAT_TYPE_TOUGHNESS:{
+						stat_found = item->HasStat(ITEM_STAT_PVPTOUGHNESS);
+						if (stat_found)
+							should_add = true;
+						break;
+					}
+					case ITEM_BROKER_STAT_TYPE_WEAPONDMG:{
+						stat_found = item->HasStat(ITEM_STAT_WEAPONDAMAGEBONUS);
+						if (stat_found)
+							should_add = true;
 						break;
 					}
 					default: {
@@ -667,11 +715,11 @@ vector<Item*>* MasterItemList::GetItems(string name, int64 itype, int32 ltype, i
 	return ret;
 }
 
-vector<Item*>* MasterItemList::GetItems(map<string, string> criteria){
+vector<Item*>* MasterItemList::GetItems(map<string, string> criteria, Client* client_to_map){
 	string name, seller, adornment;
-	int64 itype = 0xFFFFFFFFFFFFFFFF;
-	int32 ltype = 0xFFFFFFFF;
-	int32 btype = 0xFFFFFFFF;
+	int64 itype = ITEM_BROKER_TYPE_ANY64BIT;
+	int64 ltype = ITEM_BROKER_TYPE_ANY64BIT;
+	int64 btype = ITEM_BROKER_TYPE_ANY64BIT;
 	int64 minprice = 0;
 	int64 maxprice = 0;
 	int8 minskill = 0;
@@ -711,11 +759,19 @@ vector<Item*>* MasterItemList::GetItems(map<string, string> criteria){
 	if(criteria.count("ITYPE") > 0)
 		itype = ParseLongLongValue(criteria["ITYPE"]);
 	if(criteria.count("LTYPE") > 0)
-		ltype = ParseIntValue(criteria["LTYPE"]);
+		ltype = ParseLongLongValue(criteria["LTYPE"]);
 	if(criteria.count("BTYPE") > 0)
-		btype = ParseIntValue(criteria["BTYPE"]);
+		btype = ParseLongLongValue(criteria["BTYPE"]);
 	if(criteria.count("SKILLNAME") > 0)
 		itemclass = world.GetClassID(criteria["SKILLNAME"].c_str());
+	
+	if(client_to_map) {
+		map<VersionRange*, map<int64,int64>>::iterator itr = FindBrokerItemMapByVersion(client_to_map->GetVersion());
+		if(itr != broker_item_map.end() && itr->second.find(btype) != itr->second.end()) {
+			LogWrite(ITEM__DEBUG, 0, "Item", "Found broker mapping, btype %u becomes %llu", btype, itr->second[btype]);
+			btype = itr->second[btype];
+		}
+	}
 	return GetItems(name, itype, ltype, btype, minprice, maxprice, minskill, maxskill, seller, adornment, mintier, maxtier, minlevel, maxlevel, itemclass);
 }
 
@@ -1162,11 +1218,17 @@ void Item::AddStat(ItemStat* in_stat){
 	item_stats.push_back(in_stat);
 }
 
-bool Item::HasStat(uint32 statID)
+bool Item::HasStat(uint32 statID, std::string statNameLower)
 {
 	vector<ItemStat*>::iterator itr;
 	for (itr = item_stats.begin(); itr != item_stats.end(); itr++) {
-		if ((*itr)->stat_type_combined == statID) {
+		if (statID > 99 && statID < 200 && 
+		(*itr)->stat_type == 1 && ::ToLower((*itr)->stat_name) == statNameLower) {
+			return true;
+			break;
+		}
+		else if((*itr)->stat_type_combined == statID && (statNameLower.length() < 1 || 
+			(::ToLower((*itr)->stat_name) == statNameLower))) {
 			return true;
 			break;
 		}

+ 27 - 4
EQ2/source/WorldServer/Items/Items.h

@@ -349,7 +349,23 @@ extern MasterItemList master_item_list;
 #define ITEM_BROKER_STAT_TYPE_DBL_ATTACK	1048576
 #define ITEM_BROKER_STAT_TYPE_ABILITY_MOD	2097152
 #define ITEM_BROKER_STAT_TYPE_POTENCY		4194304
-
+#define ITEM_BROKER_STAT_TYPE_AEAUTOATTACK	8388608
+#define ITEM_BROKER_STAT_TYPE_ATTACKSPEED	16777216
+#define ITEM_BROKER_STAT_TYPE_BLOCKCHANCE	33554432
+#define ITEM_BROKER_STAT_TYPE_CASTINGSPEED	67108864
+#define ITEM_BROKER_STAT_TYPE_CRITBONUS		134217728
+#define ITEM_BROKER_STAT_TYPE_CRITCHANCE	268435456
+#define ITEM_BROKER_STAT_TYPE_DPS			536870912
+#define ITEM_BROKER_STAT_TYPE_FLURRYCHANCE	1073741824
+#define ITEM_BROKER_STAT_TYPE_HATEGAIN		2147483648
+#define ITEM_BROKER_STAT_TYPE_MITIGATION	4294967296
+#define ITEM_BROKER_STAT_TYPE_MULTI_ATTACK	8589934592
+#define ITEM_BROKER_STAT_TYPE_RECOVERY		17179869184
+#define ITEM_BROKER_STAT_TYPE_REUSE_SPEED	34359738368
+#define ITEM_BROKER_STAT_TYPE_SPELL_WPNDMG	68719476736
+#define ITEM_BROKER_STAT_TYPE_STRIKETHROUGH	137438953472
+#define ITEM_BROKER_STAT_TYPE_TOUGHNESS		274877906944
+#define ITEM_BROKER_STAT_TYPE_WEAPONDMG		549755813888
 
 
 #define OVERFLOW_SLOT 0xFFFFFFFE
@@ -950,7 +966,7 @@ public:
 	void SetAppearance(int16 type, int8 red, int8 green, int8 blue, int8 highlight_red, int8 highlight_green, int8 highlight_blue);
 	void SetAppearance(ItemAppearance* appearance);
 	void AddStat(ItemStat* in_stat);
-	bool HasStat(uint32 statID);
+	bool HasStat(uint32 statID, std::string statName = std::string(""));
 	void DeleteItemSets();
 	void AddSet(ItemSet* in_set);
 	void AddStatString(ItemStatString* in_stat);
@@ -1023,8 +1039,8 @@ public:
 	Item* GetItemByName(const char *name);
 	ItemStatsValues* CalculateItemBonuses(int32 item_id, Entity* entity = 0);
 	ItemStatsValues* CalculateItemBonuses(Item* desc, Entity* entity = 0, ItemStatsValues* values = 0);
-	vector<Item*>* GetItems(string name, int64 itype, int32 ltype, int32 btype, int64 minprice, int64 maxprice, int8 minskill, int8 maxskill, string seller, string adornment, int8 mintier, int8 maxtier, int16 minlevel, int16 maxlevel, sint8 itemclass);
-	vector<Item*>* GetItems(map<string, string> criteria);
+	vector<Item*>* GetItems(string name, int64 itype, int64 ltype, int64 btype, int64 minprice, int64 maxprice, int8 minskill, int8 maxskill, string seller, string adornment, int8 mintier, int8 maxtier, int16 minlevel, int16 maxlevel, sint8 itemclass);
+	vector<Item*>* GetItems(map<string, string> criteria, Client* client_to_map);
 	void AddItem(Item* item);
 	bool IsBag(int32 item_id);
 	void RemoveAll();
@@ -1034,8 +1050,15 @@ public:
 	int32 GetItemStatIDByName(std::string name);
 	std::string GetItemStatNameByID(int32 id);
 	void AddMappedItemStat(int32 id, std::string lower_case_name);
+	
+
+	void AddBrokerItemMapRange(int32 min_version, int32 max_version, int64 client_bitmask, int64 server_bitmask);
+	map<VersionRange*, map<int64,int64>>::iterator FindBrokerItemMapVersionRange(int32 min_version, int32 max_version);
+	map<VersionRange*, map<int64,int64>>::iterator FindBrokerItemMapByVersion(int32 version);
+
 	map<std::string, int32> mappedItemStatsStrings; 
 	map<int32, std::string> mappedItemStatTypeIDs; 
+	std::map<VersionRange*, std::map<int64,int64>> broker_item_map;
 };
 class PlayerItemList {
 public:

+ 21 - 0
EQ2/source/WorldServer/Items/ItemsDB.cpp

@@ -948,6 +948,24 @@ int32 WorldDatabase::LoadItemModStrings()
 	return total;
 }
 
+void WorldDatabase::LoadBrokerItemStats()
+{
+	DatabaseResult result;
+
+	if( !database_new.Select(&result, "SELECT * FROM broker_item_map") ) {
+		LogWrite(ITEM__ERROR, 0, "Items", "Cannot load WorldDatabase::LoadItemModStrings in %s, line: %i", __FUNCTION__, __LINE__);
+	}
+	else {
+		while( result.Next() )
+		{
+			int32 version_range1 = result.GetInt32Str("version_range1");
+			int32 version_range2 = result.GetInt32Str("version_range2");
+			int64 client_bitmask = result.GetInt64Str("client_bitmask");
+			int64 server_bitmask = result.GetInt64Str("server_bitmask");
+			master_item_list.AddBrokerItemMapRange(version_range1, version_range2, client_bitmask, server_bitmask);
+		}
+	}
+}
 void WorldDatabase::ReloadItemList() 
 {
 	LogWrite(ITEM__DEBUG, 0, "Items", "Unloading Item List...");
@@ -1020,6 +1038,9 @@ void WorldDatabase::LoadItemList()
 
 	LogWrite(ITEM__DEBUG, 0, "Items", "Loading Item Level Overrides...");
 	LogWrite(ITEM__DEBUG, 0, "Items", "\tLoaded %u Item Level Overrides", LoadItemLevelOverride());
+	
+	LoadBrokerItemStats();
+	LogWrite(ITEM__DEBUG, 0, "Items", "\tLoaded Broker Item Stat Map Versioning");
 
 	LogWrite(ITEM__INFO, 0, "Items", "Loaded %u Total Item%s (took %u seconds)", total, ( total == 1 ) ? "" : "s", Timer::GetUnixTimeStamp() - t_now);
 }

+ 10 - 7
EQ2/source/WorldServer/Rules/Rules.cpp

@@ -339,13 +339,16 @@ void RuleManager::Init()
 	RULE_INIT(R_Spells, PlayerSpellSaveStateWaitInterval, "100"); // time in milliseconds we wait before performing a save when the spell save trigger is activated, allows additional actions to take place until the cap is hit
 	RULE_INIT(R_Spells, PlayerSpellSaveStateCap, "1000"); // sets a maximum wait time before we queue a spell state save to the DB, given a lot can go on in a short period with players especially in combat, maybe good to have this at a higher interval.
 	RULE_INIT(R_Spells, RequirePreviousTierScribe, "0"); // requires step up apprentice -> apprentice (handcrafted?) -> journeyman (handcrafted?) -> adept -> expert -> master
-	RULE_INIT(R_Spells, CureSpellID, "110003");
-	RULE_INIT(R_Spells, CureCurseSpellID, "110004");
-	RULE_INIT(R_Spells, CureNoxiousSpellID, "110005");
-	RULE_INIT(R_Spells, CureMagicSpellID, "210006");
-	RULE_INIT(R_Spells, CureTraumaSpellID, "0");
-	RULE_INIT(R_Spells, CureArcaneSpellID, "0");
-	
+	RULE_INIT(R_Spells, CureSpellID, "110003"); // Base Cure spell that was used after they removed cure types
+	RULE_INIT(R_Spells, CureCurseSpellID, "110004"); // Curse Spell ID in the spells database
+	RULE_INIT(R_Spells, CureNoxiousSpellID, "110005"); // Noxious/Poison Spell ID in the spells database
+	RULE_INIT(R_Spells, CureMagicSpellID, "210006"); // Magic/Elemental Spell ID in the spells database
+	RULE_INIT(R_Spells, CureTraumaSpellID, "0"); // Trauma/Mental Spell ID in the spells database
+	RULE_INIT(R_Spells, CureArcaneSpellID, "0"); // Arcane/Heat Spell ID in the spells database
+	RULE_INIT(R_Spells, MinistrationSkillID, "366253016"); // ministration skill id used to map power reduction rule MinistrationPowerReductionMax
+	RULE_INIT(R_Spells, MinistrationPowerReductionMax, "15.0"); // max percentage of power reduction for spells with ministration mastery skill (default is 15.0 for 15%)
+	RULE_INIT(R_Spells, MinistrationPowerReductionSkill, "25"); // divides by integer value to establish how much skill req for higher power reduction
+	RULE_INIT(R_Spells, MasterSkillReduceSpellResist, "25"); // divides by integer value to establish how much skill bonus for reducing spell resistance on target
 	
 	RULE_INIT(R_Expansion, GlobalExpansionFlag, "0");
 	RULE_INIT(R_Expansion, GlobalHolidayFlag, "0");

+ 4 - 0
EQ2/source/WorldServer/Rules/Rules.h

@@ -194,6 +194,10 @@ enum RuleType {
 	CureMagicSpellID,
 	CureTraumaSpellID,
 	CureArcaneSpellID,
+	MinistrationSkillID,
+	MinistrationPowerReductionMax,
+	MinistrationPowerReductionSkill,
+	MasterSkillReduceSpellResist,
 
 	/* ZONE TIMERS */
 	RegenTimer,

+ 0 - 8
EQ2/source/WorldServer/Skills.cpp

@@ -366,14 +366,6 @@ EQ2Packet* PlayerSkillList::GetSkillPacket(int16 version){
 
 				int16 skill_max_with_bonuses = CalculateSkillMaxValue(skill->skill_id, skill->max_val);
 				int16 skill_with_bonuses = int(CalculateSkillValue(skill->skill_id, skill->current_val));
-				if (skill->skill_id == 613995491) {
-					int x_current = skill->current_val;
-					int x_previous = skill->previous_val;
-					int x_max = skill->max_val;
-					int x_delta2 = skill_max_with_bonuses - skill->max_val;
-					int xxx = 1;
-				}
-
 				packet->setArrayDataByName("skill_id", skill->skill_id, i);
 				if (version <= 546 && skill->skill_type >= SKILL_TYPE_GENERAL) { //covert it to DOF types
 					packet->setArrayDataByName("type", skill->skill_type-2, i);					

+ 28 - 0
EQ2/source/WorldServer/Spells.cpp

@@ -25,6 +25,7 @@
 #include "AltAdvancement/AltAdvancement.h"
 #include <cmath>
 #include "LuaInterface.h"
+#include "Rules/Rules.h"
 
 #include <boost/regex.hpp>
 
@@ -35,6 +36,7 @@ extern MasterAAList master_aa_list;
 extern MasterSpellList master_spell_list;
 extern LuaInterface* lua_interface;
 extern MasterSkillList master_skill_list;
+extern RuleManager rule_manager;
 
 Spell::Spell(){
 	spell = new SpellData;
@@ -1249,6 +1251,32 @@ int16 Spell::GetPowerRequired(Spawn* spawn){
 			result++;
 		power_req = (int16)result;
 	}
+	if(spawn && spawn->IsPlayer()) {
+		int32 ministry_skill_id = rule_manager.GetGlobalRule(R_Spells, MinistrationSkillID)->GetInt32();
+		if(spell->mastery_skill == ministry_skill_id) { // ministration offers a power reduction
+			Skill* skill = ((Player*)spawn)->GetSkillByID(spell->mastery_skill, false);
+			if(skill) {
+				float ministry_reduction_percent = rule_manager.GetGlobalRule(R_Spells, MinistrationPowerReductionMax)->GetFloat();
+				if(ministry_reduction_percent <= 0.0f)
+					ministry_reduction_percent = 15.0f;
+				
+				int32 ministration_skill_reduce = rule_manager.GetGlobalRule(R_Spells, MinistrationPowerReductionSkill)->GetInt32();
+				if(ministration_skill_reduce < 1)
+					ministration_skill_reduce = 25;
+				float reduction = skill->current_val / ministration_skill_reduce;
+				
+				if(reduction > ministry_reduction_percent)
+					reduction = ministry_reduction_percent;
+				int16 power_to_reduce = (int16)(float)(power_req * (ministry_reduction_percent/100.0f)) * (reduction / ministry_reduction_percent);
+				if(power_to_reduce > power_req) {
+					power_req = 0;
+				}
+				else {
+					power_req = power_req - power_to_reduce;
+				}
+			}
+		}
+	}
 	return power_req;
 }
 

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

@@ -287,6 +287,7 @@ public:
 	int32	LoadWeapons();
 	int32	LoadRanged();
 	int32   LoadHouseContainers();
+	void	LoadBrokerItemStats();
 
 	map<int32, vector<LevelArray*> >*	LoadSpellClasses();
 	void	LoadTransporters(ZoneServer* zone);