Browse Source

Fix #451 - basic pvp mitigation / mitigation integration
Fix #458 - fixed memory leaks in lua quest step location/zone loc functions and also languages memory cleanup

New Rules:
RULE_INIT(R_PVP, PVPMitigationModByLevel, "25"); // gives a bonus to mitigation for PVP combat to offset the percentage level * mod (default 25)
RULE_INIT(R_Combat, EffectiveMitigationCapLevel, "80"); // level multiplier for max effective cap, level * 80 (default)
RULE_INIT(R_Combat, CalculatedMitigationCapLevel, "100"); // The cap to calculate your mitigation from is [level*100].
RULE_INIT(R_Combat, MitigationLevelEffectivenessMax, "1.5"); // ratio victim level / attacker level for max effectiveness, when victim is higher level cap can reach 1.5
RULE_INIT(R_Combat, MitigationLevelEffectivenessMin, ".5"); // ratio victim level / attacker level for min effectiveness
RULE_INIT(R_Combat, MaxMitigationAllowed, ".75"); // percentage max mitigation allowed, eg. 75% of damage can be mitigated max in PVE
RULE_INIT(R_Combat, MaxMitigationAllowedPVP, ".75"); // percentage max mitigation allowed, eg. 75% of damage can be mitigated max in PVP

InfoStruct now has two unsigned int16 values that offer mitigation percentage values in integer formats, eg 155 = 15.5% mitigation
mitigation_pve
mitigation_pvp

Updated formulas to use effective_level (mentor/actual level) vs just player level
* Dodge
* Block

Bug fix for crash in non existent quest being called for /modify quest advance
Bug fix for improperly trying to stack items that are not stackable (count of 0 items, stack count of 1)
Bug fix for inventory updates, typically with overflow slots and after deleting items from inventory (packet count needs to be updated with current size in PlayerItemList::serialize)

Collections/Rewards:
- Display is now one reward at a time
- Display of award now only allows the reward cash, status to be provided once
- Database persistence of unaccepted rewards cross-zone with two new tables
- Selectable collections now checks if either field provided in /accept_reward is a potential item id (this is likely due to a client versioning)

Emagi 1 year ago
parent
commit
1068849ef8

+ 17 - 0
DB/updates/character_quest_rewards_aug6th_2022.sql

@@ -0,0 +1,17 @@
+CREATE TABLE `character_quest_rewards` (
+  `char_id` int(10) unsigned NOT NULL default 0,
+  `quest_id` int(10) unsigned NOT NULL default 0,
+  `indexed` int(10) unsigned NOT NULL default 0,
+  `is_temporary` tinyint(3) unsigned NOT NULL default 0,
+  `is_collection` tinyint(3) unsigned NOT NULL default 0,
+  `has_displayed` tinyint(3) unsigned NOT NULL default 0,
+  `tmp_coin` bigint unsigned NOT NULL DEFAULT 0,
+  `tmp_status` int(10) unsigned NOT NULL default 0,
+  `description` text not null default ''
+) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+
+CREATE TABLE `character_quest_temporary_rewards` (
+  `char_id` int(10) unsigned NOT NULL default 0,
+  `quest_id` int(10) unsigned NOT NULL default 0,
+  `item_id` int(10) unsigned NOT NULL default 0
+) ENGINE=InnoDB DEFAULT CHARSET=latin1;

+ 84 - 2
EQ2/source/WorldServer/Combat.cpp

@@ -912,6 +912,7 @@ bool Entity::DamageSpawn(Entity* victim, int8 type, int8 damage_type, int32 low_
 	int8 hit_result = 0;
 	int8 hit_result = 0;
 	int16 blow_type = 0;
 	int16 blow_type = 0;
 	sint32 damage = 0;
 	sint32 damage = 0;
+	sint32 damage_before_crit = 0;
 	bool crit = false;
 	bool crit = false;
 
 
 	if(low_damage > high_damage)
 	if(low_damage > high_damage)
@@ -949,6 +950,7 @@ bool Entity::DamageSpawn(Entity* victim, int8 type, int8 damage_type, int32 low_
 					crit = true;
 					crit = true;
 			}
 			}
 			if(crit){
 			if(crit){
+				damage_before_crit = damage;
 				//Apply total crit multiplier with crit bonus
 				//Apply total crit multiplier with crit bonus
 				if(info_struct.get_crit_bonus() > 0)
 				if(info_struct.get_crit_bonus() > 0)
 					damage *= (1.3 + (info_struct.get_crit_bonus() / 100));
 					damage *= (1.3 + (info_struct.get_crit_bonus() / 100));
@@ -963,8 +965,21 @@ bool Entity::DamageSpawn(Entity* victim, int8 type, int8 damage_type, int32 low_
 			}
 			}
 		}
 		}
 
 
-		// TODO: Mitigation equation from http://www.guildportal.com/Guild.aspx?GuildID=20881&TabID=189653&ForumID=95908&TopicID=9024250
-		
+		if(type == DAMAGE_PACKET_TYPE_SIMPLE_DAMAGE || type == DAMAGE_PACKET_TYPE_SIMPLE_CRIT_DMG || type == DAMAGE_PACKET_TYPE_RANGE_DAMAGE) {			
+			int16 effective_level_attacker = GetInfoStruct()->get_effective_level() != 0 ? GetInfoStruct()->get_effective_level() : GetLevel();
+			float mit_percentage = CalculateMitigation(type, damage_type, effective_level_attacker, (IsPlayer() && victim->IsPlayer()));
+			sint32 damage_to_reduce = (damage * mit_percentage);
+			if(damage_to_reduce > damage)
+				damage = 0;
+			else
+				damage -= damage_to_reduce;
+			
+			// if we reduce damage back below crit level then its no longer a crit, but we don't go below base damage
+			if(type == DAMAGE_PACKET_TYPE_SIMPLE_CRIT_DMG && damage <= damage_before_crit) {
+				damage = damage_before_crit;
+				type = DAMAGE_PACKET_TYPE_SIMPLE_DAMAGE;
+			}
+		}
 	}
 	}
 
 
 	LogWrite(MISC__TODO, 3, "TODO", "Take players armor into account\nfile: %s, func: %s, line: %i)", __FILE__, __FUNCTION__, __LINE__);
 	LogWrite(MISC__TODO, 3, "TODO", "Take players armor into account\nfile: %s, func: %s, line: %i)", __FILE__, __FUNCTION__, __LINE__);
@@ -1057,6 +1072,73 @@ bool Entity::DamageSpawn(Entity* victim, int8 type, int8 damage_type, int32 low_
 	return crit;
 	return crit;
 }
 }
 
 
+float Entity::CalculateMitigation(int8 type, int8 damage_type, int16 effective_level_attacker, bool for_pvp) {
+	int16 effective_level_victim = GetInfoStruct()->get_effective_level() != 0 ? GetInfoStruct()->get_effective_level() : GetLevel();
+	if(effective_level_attacker < 1 && effective_level_victim)
+		effective_level_attacker = effective_level_victim;
+	else
+		effective_level_attacker = 1;
+
+	int32 effective_mit_cap = effective_level_victim * rule_manager.GetGlobalRule(R_Combat, EffectiveMitigationCapLevel)->GetInt32();
+	float max_mit = (float)GetInfoStruct()->get_max_mitigation();
+	if(max_mit == 0.0f)
+		max_mit = effective_level_victim * 100.0f;
+	
+	int32 mit_to_use = 0;
+	switch(type) {
+	
+		case DAMAGE_PACKET_TYPE_SIMPLE_DAMAGE:
+		case DAMAGE_PACKET_TYPE_RANGE_DAMAGE:
+			mit_to_use = GetInfoStruct()->get_cur_mitigation();
+		break;
+		case DAMAGE_PACKET_TYPE_SIMPLE_CRIT_DMG:
+			// since critical mitigation is a percentage we will reverse the mit value so we can add skill from specific types of weapons
+			mit_to_use = (int32)((float)GetInfoStruct()->get_max_mitigation() * (float)(GetInfoStruct()->get_critical_mitigation()/100.0f));
+		break;
+		
+	}
+	
+	switch(damage_type) {
+			case DAMAGE_PACKET_DAMAGE_TYPE_SLASH:
+				mit_to_use += GetInfoStruct()->get_mitigation_skill1(); // slash
+			break;
+			case DAMAGE_PACKET_DAMAGE_TYPE_PIERCE:
+				mit_to_use += GetInfoStruct()->get_mitigation_skill2(); // pierce
+			break;
+			case DAMAGE_PACKET_DAMAGE_TYPE_CRUSH:
+				mit_to_use += GetInfoStruct()->get_mitigation_skill3(); // crush
+			break;
+			default:
+			// do nothing
+			break;
+	}
+	
+	if(for_pvp) {
+		mit_to_use += effective_level_victim * rule_manager.GetGlobalRule(R_Combat, MitigationLevelEffectivenessMax)->GetInt32();
+	}
+	
+	if(mit_to_use > effective_mit_cap) {
+		mit_to_use = effective_mit_cap;
+	}
+	float level_diff = ((float)effective_level_victim / (float)effective_level_attacker);
+	if(level_diff > rule_manager.GetGlobalRule(R_Combat, MitigationLevelEffectivenessMax)->GetFloat()) {
+		level_diff = rule_manager.GetGlobalRule(R_Combat, MitigationLevelEffectivenessMax)->GetFloat();
+	}
+	else if(level_diff < rule_manager.GetGlobalRule(R_Combat, MitigationLevelEffectivenessMax)->GetFloat()) {
+		level_diff = rule_manager.GetGlobalRule(R_Combat, MitigationLevelEffectivenessMin)->GetFloat();
+	}
+	float mit_percentage = ((float)mit_to_use / max_mit) * level_diff;
+	
+	if(!for_pvp && mit_percentage > rule_manager.GetGlobalRule(R_Combat, MaxMitigationAllowed)->GetFloat()) {
+		mit_percentage = rule_manager.GetGlobalRule(R_Combat, MaxMitigationAllowed)->GetFloat();
+	}
+	else if(for_pvp && mit_percentage > rule_manager.GetGlobalRule(R_Combat, MaxMitigationAllowedPVP)->GetFloat()) {
+		mit_percentage = rule_manager.GetGlobalRule(R_Combat, MaxMitigationAllowedPVP)->GetFloat();
+	} 
+	
+	return mit_percentage;
+}
+
 void Entity::AddHate(Entity* attacker, sint32 hate) {
 void Entity::AddHate(Entity* attacker, sint32 hate) {
 	if(!attacker || GetHP() <= 0 || attacker->GetHP() <= 0)
 	if(!attacker || GetHP() <= 0 || attacker->GetHP() <= 0)
 		return;
 		return;

+ 17 - 11
EQ2/source/WorldServer/Commands/Commands.cpp

@@ -4133,7 +4133,6 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 			int32 selectable_item_id = 0;
 			int32 selectable_item_id = 0;
 			//Quest *quest = 0;
 			//Quest *quest = 0;
 			Collection *collection = 0;
 			Collection *collection = 0;
-			
 			/* no idea what the first argument is for (faction maybe?)
 			/* no idea what the first argument is for (faction maybe?)
 			   if the reward has a selectable item reward, it's sent as the second argument
 			   if the reward has a selectable item reward, it's sent as the second argument
 			   if neither of these are included in the reward, there is no sep
 			   if neither of these are included in the reward, there is no sep
@@ -4144,10 +4143,10 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 				selectable_item_id = atoul(sep->arg[1]);
 				selectable_item_id = atoul(sep->arg[1]);
 			}
 			}
 
 
-			//if ((quest = player->GetPendingQuestReward()))
-			//	client->AcceptQuestRewards(quest, selectable_item_id);
-
-			/* the below needs to go away eventually and be redone */
+			/* this logic here may seem unexpected, but the quest queue response for GetPendingQuestAcceptance is only populated if it is the current reward displayed to the client based on a quest
+			** Otherwise it will likely be a DoF client scenario (pending item rewards, selectable item rewards) which is specifying an item id
+			** lastly it will be a collection which also supplies an item id and you can only have one pending collection turn in at a time (they queue against Client::HandInCollections
+			*/
 			int32 item_id = 0;
 			int32 item_id = 0;
 			if(sep && sep->arg[0][0] && sep->IsNumber(0))
 			if(sep && sep->arg[0][0] && sep->IsNumber(0))
 				item_id = atoul(sep->arg[0]);
 				item_id = atoul(sep->arg[0]);
@@ -4157,12 +4156,7 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 				break;
 				break;
 			}
 			}
 			bool collectedItems = false;
 			bool collectedItems = false;
-			if (collection = player->GetPendingCollectionReward())
-			{
-				client->AcceptCollectionRewards(collection, selectable_item_id);
-				collectedItems = true;
-			}
-			else if (client->GetPlayer()->HasPendingItemRewards()) {
+			if (client->GetPlayer()->HasPendingItemRewards()) {
 				vector<Item*> items = client->GetPlayer()->GetPendingItemRewards();
 				vector<Item*> items = client->GetPlayer()->GetPendingItemRewards();
 				if (items.size() > 0) {
 				if (items.size() > 0) {
 					collectedItems = true;
 					collectedItems = true;
@@ -4170,6 +4164,7 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 						client->GetPlayer()->AddItem(new Item(items[i]));
 						client->GetPlayer()->AddItem(new Item(items[i]));
 					}
 					}
 					client->GetPlayer()->ClearPendingItemRewards();
 					client->GetPlayer()->ClearPendingItemRewards();
+					client->GetPlayer()->SetActiveReward(false);
 				}
 				}
 				map<int32, Item*> selectable_item = client->GetPlayer()->GetPendingSelectableItemReward(item_id);
 				map<int32, Item*> selectable_item = client->GetPlayer()->GetPendingSelectableItemReward(item_id);
 				if (selectable_item.size() > 0) {
 				if (selectable_item.size() > 0) {
@@ -4179,8 +4174,15 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 						client->GetPlayer()->AddItem(new Item(itr->second));
 						client->GetPlayer()->AddItem(new Item(itr->second));
 						client->GetPlayer()->ClearPendingSelectableItemRewards(itr->first);
 						client->GetPlayer()->ClearPendingSelectableItemRewards(itr->first);
 					}
 					}
+					client->GetPlayer()->SetActiveReward(false);
 				}
 				}
 			}
 			}
+			else if (collection = player->GetPendingCollectionReward())
+			{
+				client->AcceptCollectionRewards(collection, (selectable_item_id > 0) ? selectable_item_id : item_id);
+				collectedItems = true;
+			}
+			
 			if (collectedItems) {
 			if (collectedItems) {
 				EQ2Packet* outapp = client->GetPlayer()->SendInventoryUpdate(client->GetVersion());
 				EQ2Packet* outapp = client->GetPlayer()->SendInventoryUpdate(client->GetVersion());
 				if (outapp)
 				if (outapp)
@@ -7729,6 +7731,10 @@ void Commands::Command_ModifyQuest(Client* client, Seperator* sep)
 				quest_id = atoul(sep->arg[1]);
 				quest_id = atoul(sep->arg[1]);
 				Quest* quest = client->GetPlayer()->player_quests[quest_id];
 				Quest* quest = client->GetPlayer()->player_quests[quest_id];
 
 
+				if(!quest) {
+					client->Message(CHANNEL_COLOR_RED, "Quest not found!");
+					return;
+				}
 				if (sep && sep->arg[2] && sep->IsNumber(1))
 				if (sep && sep->arg[2] && sep->IsNumber(1))
 				{
 				{
 					step = atoul(sep->arg[2]);
 					step = atoul(sep->arg[2]);

+ 17 - 4
EQ2/source/WorldServer/Entity.cpp

@@ -249,6 +249,8 @@ void Entity::MapInfoStruct()
 	get_int16_funcs["mitigation_skill1"] = l::bind(&InfoStruct::get_mitigation_skill1, &info_struct);
 	get_int16_funcs["mitigation_skill1"] = l::bind(&InfoStruct::get_mitigation_skill1, &info_struct);
 	get_int16_funcs["mitigation_skill2"] = l::bind(&InfoStruct::get_mitigation_skill2, &info_struct);
 	get_int16_funcs["mitigation_skill2"] = l::bind(&InfoStruct::get_mitigation_skill2, &info_struct);
 	get_int16_funcs["mitigation_skill3"] = l::bind(&InfoStruct::get_mitigation_skill3, &info_struct);
 	get_int16_funcs["mitigation_skill3"] = l::bind(&InfoStruct::get_mitigation_skill3, &info_struct);
+	get_int16_funcs["mitigation_pve"] = l::bind(&InfoStruct::get_mitigation_pve, &info_struct);
+	get_int16_funcs["mitigation_pvp"] = l::bind(&InfoStruct::get_mitigation_pvp, &info_struct);
 	get_float_funcs["ability_modifier"] = l::bind(&InfoStruct::get_ability_modifier, &info_struct);
 	get_float_funcs["ability_modifier"] = l::bind(&InfoStruct::get_ability_modifier, &info_struct);
 	get_float_funcs["critical_mitigation"] = l::bind(&InfoStruct::get_critical_mitigation, &info_struct);
 	get_float_funcs["critical_mitigation"] = l::bind(&InfoStruct::get_critical_mitigation, &info_struct);
 	get_float_funcs["block_chance"] = l::bind(&InfoStruct::get_block_chance, &info_struct);
 	get_float_funcs["block_chance"] = l::bind(&InfoStruct::get_block_chance, &info_struct);
@@ -427,6 +429,8 @@ void Entity::MapInfoStruct()
 	set_int16_funcs["mitigation_skill1"] = l::bind(&InfoStruct::set_mitigation_skill1, &info_struct, l::_1);
 	set_int16_funcs["mitigation_skill1"] = l::bind(&InfoStruct::set_mitigation_skill1, &info_struct, l::_1);
 	set_int16_funcs["mitigation_skill2"] = l::bind(&InfoStruct::set_mitigation_skill2, &info_struct, l::_1);
 	set_int16_funcs["mitigation_skill2"] = l::bind(&InfoStruct::set_mitigation_skill2, &info_struct, l::_1);
 	set_int16_funcs["mitigation_skill3"] = l::bind(&InfoStruct::set_mitigation_skill3, &info_struct, l::_1);
 	set_int16_funcs["mitigation_skill3"] = l::bind(&InfoStruct::set_mitigation_skill3, &info_struct, l::_1);
+	set_int16_funcs["mitigation_pve"] = l::bind(&InfoStruct::set_mitigation_pve, &info_struct, l::_1);
+	set_int16_funcs["mitigation_pvp"] = l::bind(&InfoStruct::set_mitigation_pvp, &info_struct, l::_1);
 	set_float_funcs["ability_modifier"] = l::bind(&InfoStruct::set_ability_modifier, &info_struct, l::_1);
 	set_float_funcs["ability_modifier"] = l::bind(&InfoStruct::set_ability_modifier, &info_struct, l::_1);
 	set_float_funcs["critical_mitigation"] = l::bind(&InfoStruct::set_critical_mitigation, &info_struct, l::_1);
 	set_float_funcs["critical_mitigation"] = l::bind(&InfoStruct::set_critical_mitigation, &info_struct, l::_1);
 	set_float_funcs["block_chance"] = l::bind(&InfoStruct::set_block_chance, &info_struct, l::_1);
 	set_float_funcs["block_chance"] = l::bind(&InfoStruct::set_block_chance, &info_struct, l::_1);
@@ -1289,6 +1293,15 @@ void Entity::CalculateBonuses(){
 	CalculateSpellBonuses(values);
 	CalculateSpellBonuses(values);
 	
 	
 	info->set_cur_mitigation(info->get_mitigation_base());
 	info->set_cur_mitigation(info->get_mitigation_base());
+	
+	int32 calc_mit_cap = effective_level * rule_manager.GetGlobalRule(R_Combat, CalculatedMitigationCapLevel)->GetInt32();
+	info->set_max_mitigation(calc_mit_cap);
+	
+	int16 mit_percent = (int16)(CalculateMitigation() * 1000.0f);
+	info->set_mitigation_pve(mit_percent);
+	mit_percent = (int16)(CalculateMitigation(DAMAGE_PACKET_TYPE_SIMPLE_DAMAGE,0,0,true) * 1000.0f);
+	info->set_mitigation_pvp(mit_percent);
+	
 	info->add_sta((float)values->sta);
 	info->add_sta((float)values->sta);
 	info->add_str((float)values->str);
 	info->add_str((float)values->str);
 	info->add_agi((float)values->agi);
 	info->add_agi((float)values->agi);
@@ -1397,10 +1410,10 @@ void Entity::CalculateBonuses(){
 					else if (skill->short_name.data == "buckler") 
 					else if (skill->short_name.data == "buckler") 
 						baseBlock = 3.0f;
 						baseBlock = 3.0f;
 				}
 				}
-				if(GetLevel() > mitigation)
-					block_pct = log10f((float)mitigation/((float)GetLevel()*10.0f));
+				if(effective_level > mitigation)
+					block_pct = log10f((float)mitigation/((float)effective_level*10.0f));
 				else
 				else
-					block_pct = log10f(((float)GetLevel()/(float)mitigation)) * log10f(GetLevel()) * 2.0f;
+					block_pct = log10f(((float)effective_level/(float)mitigation)) * log10f(effective_level) * 2.0f;
 				
 				
 				if(block_pct < 0.0f)
 				if(block_pct < 0.0f)
 					block_pct *= -1.0f;
 					block_pct *= -1.0f;
@@ -1441,7 +1454,7 @@ void Entity::CalculateBonuses(){
 
 
 	float dodge_actual = 0.0f;
 	float dodge_actual = 0.0f;
 	if(full_pct_hit > 0.0f)
 	if(full_pct_hit > 0.0f)
-		dodge_actual = dodge_pct * (full_pct_hit / 100.0f) + (log10f(GetLevel() * GetAgi()) / 100.0f);
+		dodge_actual = dodge_pct * (full_pct_hit / 100.0f) + (log10f(effective_level * GetAgi()) / 100.0f);
 
 
 	info->set_avoidance_base(dodge_actual);
 	info->set_avoidance_base(dodge_actual);
 
 

+ 13 - 0
EQ2/source/WorldServer/Entity.h

@@ -190,6 +190,8 @@ struct InfoStruct{
 		mitigation_skill1_ = 0;
 		mitigation_skill1_ = 0;
 		mitigation_skill2_ = 0;
 		mitigation_skill2_ = 0;
 		mitigation_skill3_ = 0;
 		mitigation_skill3_ = 0;
+		mitigation_pve_ = 0;
+		mitigation_pvp_ = 0;
 		ability_modifier_ = 0;
 		ability_modifier_ = 0;
 		critical_mitigation_ = 0;
 		critical_mitigation_ = 0;
 		block_chance_ = 0;
 		block_chance_ = 0;
@@ -370,6 +372,8 @@ struct InfoStruct{
 		mitigation_skill1_ = oldStruct->get_mitigation_skill1();
 		mitigation_skill1_ = oldStruct->get_mitigation_skill1();
 		mitigation_skill2_ = oldStruct->get_mitigation_skill2();
 		mitigation_skill2_ = oldStruct->get_mitigation_skill2();
 		mitigation_skill3_ = oldStruct->get_mitigation_skill3();
 		mitigation_skill3_ = oldStruct->get_mitigation_skill3();
+		mitigation_pve_ = oldStruct->get_mitigation_pve();
+		mitigation_pvp_ = oldStruct->get_mitigation_pvp();
 		ability_modifier_ = oldStruct->get_ability_modifier();
 		ability_modifier_ = oldStruct->get_ability_modifier();
 		critical_mitigation_ = oldStruct->get_critical_mitigation();
 		critical_mitigation_ = oldStruct->get_critical_mitigation();
 		block_chance_ = oldStruct->get_block_chance();
 		block_chance_ = oldStruct->get_block_chance();
@@ -557,6 +561,9 @@ struct InfoStruct{
 	int16	 get_mitigation_skill1() { std::lock_guard<std::mutex> lk(classMutex); return mitigation_skill1_; }
 	int16	 get_mitigation_skill1() { std::lock_guard<std::mutex> lk(classMutex); return mitigation_skill1_; }
 	int16	 get_mitigation_skill2() { std::lock_guard<std::mutex> lk(classMutex); return mitigation_skill2_; }
 	int16	 get_mitigation_skill2() { std::lock_guard<std::mutex> lk(classMutex); return mitigation_skill2_; }
 	int16	 get_mitigation_skill3() { std::lock_guard<std::mutex> lk(classMutex); return mitigation_skill3_; }
 	int16	 get_mitigation_skill3() { std::lock_guard<std::mutex> lk(classMutex); return mitigation_skill3_; }
+	
+	int16	 get_mitigation_pve() { std::lock_guard<std::mutex> lk(classMutex); return mitigation_pve_; }
+	int16	 get_mitigation_pvp() { std::lock_guard<std::mutex> lk(classMutex); return mitigation_pvp_; }
 
 
 	float	 get_ability_modifier() { std::lock_guard<std::mutex> lk(classMutex); return ability_modifier_; }
 	float	 get_ability_modifier() { std::lock_guard<std::mutex> lk(classMutex); return ability_modifier_; }
 	float	 get_critical_mitigation() { std::lock_guard<std::mutex> lk(classMutex); return critical_mitigation_; }
 	float	 get_critical_mitigation() { std::lock_guard<std::mutex> lk(classMutex); return critical_mitigation_; }
@@ -792,6 +799,9 @@ struct InfoStruct{
 	void	set_mitigation_skill1(int16 value) { std::lock_guard<std::mutex> lk(classMutex); mitigation_skill1_ = value; }
 	void	set_mitigation_skill1(int16 value) { std::lock_guard<std::mutex> lk(classMutex); mitigation_skill1_ = value; }
 	void	set_mitigation_skill2(int16 value) { std::lock_guard<std::mutex> lk(classMutex); mitigation_skill2_ = value; }
 	void	set_mitigation_skill2(int16 value) { std::lock_guard<std::mutex> lk(classMutex); mitigation_skill2_ = value; }
 	void	set_mitigation_skill3(int16 value) { std::lock_guard<std::mutex> lk(classMutex); mitigation_skill3_ = value; }
 	void	set_mitigation_skill3(int16 value) { std::lock_guard<std::mutex> lk(classMutex); mitigation_skill3_ = value; }
+	
+	void	set_mitigation_pve(int16 value) { std::lock_guard<std::mutex> lk(classMutex); mitigation_pve_ = value; }
+	void	set_mitigation_pvp(int16 value) { std::lock_guard<std::mutex> lk(classMutex); mitigation_pvp_ = value; }
 
 
 	void	add_mitigation_skill1(int16 value) { std::lock_guard<std::mutex> lk(classMutex); mitigation_skill1_ += value; }
 	void	add_mitigation_skill1(int16 value) { std::lock_guard<std::mutex> lk(classMutex); mitigation_skill1_ += value; }
 	void	add_mitigation_skill2(int16 value) { std::lock_guard<std::mutex> lk(classMutex); mitigation_skill2_ += value; }
 	void	add_mitigation_skill2(int16 value) { std::lock_guard<std::mutex> lk(classMutex); mitigation_skill2_ += value; }
@@ -1037,6 +1047,8 @@ private:
 	int16			mitigation_skill1_;
 	int16			mitigation_skill1_;
 	int16			mitigation_skill2_;
 	int16			mitigation_skill2_;
 	int16			mitigation_skill3_;
 	int16			mitigation_skill3_;
+	int16			mitigation_pve_;
+	int16			mitigation_pvp_;
 	float			ability_modifier_;
 	float			ability_modifier_;
 	float			critical_mitigation_;
 	float			critical_mitigation_;
 	float			block_chance_;
 	float			block_chance_;
@@ -1363,6 +1375,7 @@ public:
 	float			GetDamageTypeResistPercentage(int8 damage_type);
 	float			GetDamageTypeResistPercentage(int8 damage_type);
 	Skill*			GetSkillByWeaponType(int8 type, bool update);
 	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);
 	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);
+	float			CalculateMitigation(int8 type = DAMAGE_PACKET_TYPE_SIMPLE_DAMAGE, int8 damage_type = 0, int16 attacker_level = 0, bool for_pvp = false);
 	void			AddHate(Entity* attacker, sint32 hate);
 	void			AddHate(Entity* attacker, sint32 hate);
 	bool			CheckInterruptSpell(Entity* attacker);
 	bool			CheckInterruptSpell(Entity* attacker);
 	bool			CheckFizzleSpell(LuaSpell* spell);
 	bool			CheckFizzleSpell(LuaSpell* spell);

+ 9 - 6
EQ2/source/WorldServer/Items/Items.cpp

@@ -3126,8 +3126,9 @@ bool PlayerItemList::GetFirstFreeSlot(sint32* bag_id, sint16* slot) {
 }
 }
 
 
 Item* PlayerItemList::CanStack(Item* item, bool include_bank){
 Item* PlayerItemList::CanStack(Item* item, bool include_bank){
-	if(!item)
+	if(!item || item->stack_count < 2)
 		return 0;
 		return 0;
+	
 	Item* ret = 0;
 	Item* ret = 0;
 	map<sint32, map<int8, map<int16, Item*>> >::iterator itr;
 	map<sint32, map<int8, map<int16, Item*>> >::iterator itr;
 	map<int16, Item*>::iterator slot_itr;
 	map<int16, Item*>::iterator slot_itr;
@@ -3135,13 +3136,13 @@ Item* PlayerItemList::CanStack(Item* item, bool include_bank){
 	for(itr = items.begin(); itr != items.end(); itr++){
 	for(itr = items.begin(); itr != items.end(); itr++){
 		if(include_bank || (!include_bank && itr->first >= 0)){
 		if(include_bank || (!include_bank && itr->first >= 0)){
 			for(slot_itr=itr->second[0].begin();slot_itr!=itr->second[0].end(); slot_itr++){
 			for(slot_itr=itr->second[0].begin();slot_itr!=itr->second[0].end(); slot_itr++){
-				if(slot_itr->second && slot_itr->second->details.item_id == item->details.item_id && ((slot_itr->second->details.count + item->details.count) <= slot_itr->second->stack_count)){
+				if(slot_itr->second && slot_itr->second->details.item_id == item->details.item_id && (((slot_itr->second->details.count ? slot_itr->second->details.count : 1) + (item->details.count > 0 ? item->details.count : 1)) <= slot_itr->second->stack_count)){
 					ret = slot_itr->second;
 					ret = slot_itr->second;
 					break;
 					break;
 				}
 				}
 			}
 			}
 			for(slot_itr=itr->second[1].begin();slot_itr!=itr->second[1].end(); slot_itr++){
 			for(slot_itr=itr->second[1].begin();slot_itr!=itr->second[1].end(); slot_itr++){
-				if(slot_itr->second && slot_itr->second->details.item_id == item->details.item_id && ((slot_itr->second->details.count + item->details.count) <= slot_itr->second->stack_count)){
+				if(slot_itr->second && slot_itr->second->details.item_id == item->details.item_id && (((slot_itr->second->details.count ? slot_itr->second->details.count : 1) + (item->details.count > 0 ? item->details.count : 1)) <= slot_itr->second->stack_count)){
 					ret = slot_itr->second;
 					ret = slot_itr->second;
 					break;
 					break;
 				}
 				}
@@ -3416,9 +3417,10 @@ EQ2Packet* PlayerItemList::serialize(Player* player, int16 version){
 				safe_delete_array(xor_packet);
 				safe_delete_array(xor_packet);
 				xor_packet = new uchar[packet_size * size];
 				xor_packet = new uchar[packet_size * size];
 			}
 			}
-			packet_count = size;
 		}
 		}
 		
 		
+		packet_count = size;
+		
 		for(int16 i = 0; i < indexed_items.size(); i++){
 		for(int16 i = 0; i < indexed_items.size(); i++){
 			item = indexed_items[i];
 			item = indexed_items[i];
 			if (item && item->details.item_id > 0)
 			if (item && item->details.item_id > 0)
@@ -3574,9 +3576,10 @@ void PlayerItemList::AddItemToPacket(PacketStruct* packet, Player* player, Item*
 	packet->setSubstructArrayDataByName("items", "menu_type", menu_data, 0, i);
 	packet->setSubstructArrayDataByName("items", "menu_type", menu_data, 0, i);
 	if (overflow)
 	if (overflow)
 		packet->setSubstructArrayDataByName("items", "index", 0xFFFF, 0, i);
 		packet->setSubstructArrayDataByName("items", "index", 0xFFFF, 0, i);
-	else
+	else {
 		packet->setSubstructArrayDataByName("items", "index", i, 0, i);
 		packet->setSubstructArrayDataByName("items", "index", i, 0, i);
-	item->details.index = i;
+		item->details.index = i;
+	}
 	packet->setSubstructArrayDataByName("items", "icon", item->details.icon, 0, i);
 	packet->setSubstructArrayDataByName("items", "icon", item->details.icon, 0, i);
 	packet->setSubstructArrayDataByName("items", "slot_id", item->details.slot_id, 0, i);
 	packet->setSubstructArrayDataByName("items", "slot_id", item->details.slot_id, 0, i);
 	if (client->GetVersion() <= 1208) {
 	if (client->GetVersion() <= 1208) {

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

@@ -41,7 +41,15 @@ MasterLanguagesList::~MasterLanguagesList(){
 	Clear();
 	Clear();
 }
 }
 
 
+
+// don't bother calling this beyond its deconstructor its not thread-safe
 void MasterLanguagesList::Clear(){
 void MasterLanguagesList::Clear(){
+	list<Language*>::iterator itr;
+	Language* language = 0;
+	for(itr = languages_list.begin(); itr != languages_list.end(); itr++){
+		language = *itr;
+		safe_delete(language);
+	}
 	languages_list.clear();
 	languages_list.clear();
 }
 }
 
 

+ 4 - 5
EQ2/source/WorldServer/LuaFunctions.cpp

@@ -4092,6 +4092,7 @@ int EQ2Emu_lua_AddQuestStepZoneLoc(lua_State* state) {
 			i += 4;
 			i += 4;
 		}
 		}
 		QuestStep* quest_step = quest->AddQuestStep(step, QUEST_STEP_TYPE_LOCATION, description, 0, 1, taskgroup, locations, max_variation);
 		QuestStep* quest_step = quest->AddQuestStep(step, QUEST_STEP_TYPE_LOCATION, description, 0, 1, taskgroup, locations, max_variation);
+		safe_delete(locations); // gets duplicated into new table in QuestStep constructor
 		if (quest_step && icon > 0)
 		if (quest_step && icon > 0)
 			quest_step->SetIcon(icon);
 			quest_step->SetIcon(icon);
 		if (quest->GetPlayer()) {
 		if (quest->GetPlayer()) {
@@ -4134,6 +4135,7 @@ int EQ2Emu_lua_AddQuestStepLocation(lua_State* state) {
 			i += 3;
 			i += 3;
 		}
 		}
 		QuestStep* quest_step = quest->AddQuestStep(step, QUEST_STEP_TYPE_LOCATION, description, 0, 1, taskgroup, locations, max_variation);
 		QuestStep* quest_step = quest->AddQuestStep(step, QUEST_STEP_TYPE_LOCATION, description, 0, 1, taskgroup, locations, max_variation);
+		safe_delete(locations); // gets duplicated into new table in QuestStep constructor
 		if (quest_step && icon > 0)
 		if (quest_step && icon > 0)
 			quest_step->SetIcon(icon);
 			quest_step->SetIcon(icon);
 		if (quest->GetPlayer()) {
 		if (quest->GetPlayer()) {
@@ -4407,9 +4409,7 @@ int EQ2Emu_lua_GiveQuestReward(lua_State* state) {
 	if (quest && spawn) {
 	if (quest && spawn) {
 		if (spawn->IsPlayer()) {
 		if (spawn->IsPlayer()) {
 			Client* client = spawn->GetZone()->GetClientBySpawn(spawn);
 			Client* client = spawn->GetZone()->GetClientBySpawn(spawn);
-			if (client)
-			{
-				client->AddPendingQuestAcceptReward(quest);
+			if (client) {
 				client->AddPendingQuestReward(quest);
 				client->AddPendingQuestReward(quest);
 			}
 			}
 		}
 		}
@@ -6034,8 +6034,7 @@ int EQ2Emu_lua_GiveQuestItem(lua_State* state)
 				itemsAddedSuccessfully = false;
 				itemsAddedSuccessfully = false;
 		}
 		}
 	}
 	}
-	client->AddPendingQuestAcceptReward(quest);
-	client->DisplayQuestComplete(quest, true, description);
+	client->AddPendingQuestReward(quest, true, true, description); // queue for display
 	
 	
 	lua_interface->SetBooleanValue(state, itemsAddedSuccessfully);
 	lua_interface->SetBooleanValue(state, itemsAddedSuccessfully);
 	return 1;
 	return 1;

+ 7 - 8
EQ2/source/WorldServer/LuaInterface.cpp

@@ -403,9 +403,9 @@ Mutex*  LuaInterface::GetQuestMutex(Quest* quest) {
 	return ret;
 	return ret;
 }
 }
 
 
-void LuaInterface::CallQuestFunction(Quest* quest, const char* function, Spawn* player, int32 step_id) {
+bool LuaInterface::CallQuestFunction(Quest* quest, const char* function, Spawn* player, int32 step_id, int32* returnValue) {
 	if(shutting_down)
 	if(shutting_down)
-		return;
+		return false;
 	lua_State* state = 0;
 	lua_State* state = 0;
 	if(quest){
 	if(quest){
 		LogWrite(LUA__DEBUG, 0, "LUA", "Quest: %s, function: %s", quest->GetName(), function);
 		LogWrite(LUA__DEBUG, 0, "LUA", "Quest: %s, function: %s", quest->GetName(), function);
@@ -413,6 +413,7 @@ void LuaInterface::CallQuestFunction(Quest* quest, const char* function, Spawn*
 		mutex->lock();
 		mutex->lock();
 		if(quest_states.count(quest->GetQuestID()) > 0)
 		if(quest_states.count(quest->GetQuestID()) > 0)
 			state = quest_states[quest->GetQuestID()];
 			state = quest_states[quest->GetQuestID()];
+		bool success = false; // if no state then we return false
 		if(state){
 		if(state){
 			int8 arg_count = 3;
 			int8 arg_count = 3;
 			lua_getglobal(state, function);
 			lua_getglobal(state, function);
@@ -424,16 +425,14 @@ void LuaInterface::CallQuestFunction(Quest* quest, const char* function, Spawn*
 				SetInt32Value(state, step_id);
 				SetInt32Value(state, step_id);
 				arg_count++;
 				arg_count++;
 			}
 			}
-			if(lua_pcall(state, arg_count, 0, 0) != 0){
-				LogError("%s: Error processing quest function '%s': %s ", GetScriptName(state), function, lua_tostring(state, -1));
-				lua_pop(state, 1);
-				mutex->unlock();
-				return;
-			}
+			
+			success = CallScriptInt32(state, arg_count, returnValue);
 		}
 		}
 		mutex->unlock();
 		mutex->unlock();
 		LogWrite(LUA__DEBUG, 0, "LUA", "Done!");
 		LogWrite(LUA__DEBUG, 0, "LUA", "Done!");
+		return success;
 	}
 	}
+	return false;
 }
 }
 
 
 Quest* LuaInterface::LoadQuest(int32 id, const char* name, const char* type, const char* zone, int8 level, const char* description, char* script_name) {
 Quest* LuaInterface::LoadQuest(int32 id, const char* name, const char* type, const char* zone, int8 level, const char* description, char* script_name) {

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

@@ -276,7 +276,7 @@ public:
 	void			LogError(const char* error, ...);
 	void			LogError(const char* error, ...);
 
 
 
 
-	void			CallQuestFunction(Quest* quest, const char* function, Spawn* player, int32 step_id = 0xFFFFFFFF);
+	bool			CallQuestFunction(Quest* quest, const char* function, Spawn* player, int32 step_id = 0xFFFFFFFF, int32* returnValue = 0);
 	void			RemoveDebugClients(Client* client);
 	void			RemoveDebugClients(Client* client);
 	void			UpdateDebugClients(Client* client);
 	void			UpdateDebugClients(Client* client);
 	void			ProcessErrorMessage(const char* message);
 	void			ProcessErrorMessage(const char* message);

+ 25 - 6
EQ2/source/WorldServer/Player.cpp

@@ -124,6 +124,7 @@ Player::Player(){
 	reset_mentorship = false;
 	reset_mentorship = false;
 	all_spells_locked = false;
 	all_spells_locked = false;
 	current_language_id = 0;
 	current_language_id = 0;
+	active_reward = false;
 }
 }
 Player::~Player(){
 Player::~Player(){
 	SetSaveSpellEffects(true);
 	SetSaveSpellEffects(true);
@@ -703,8 +704,9 @@ EQ2Packet* PlayerInfo::serialize(int16 version, int16 modifyPos, int32 modifyVal
 		packet->setDataByName("stat_bonus_damage", 95); //stat_bonus_damage
 		packet->setDataByName("stat_bonus_damage", 95); //stat_bonus_damage
 		packet->setDataByName("mitigation_cur", info_struct->get_cur_mitigation());// confirmed DoV
 		packet->setDataByName("mitigation_cur", info_struct->get_cur_mitigation());// confirmed DoV
 		packet->setDataByName("mitigation_base", info_struct->get_mitigation_base());// confirmed DoV
 		packet->setDataByName("mitigation_base", info_struct->get_mitigation_base());// confirmed DoV
-		packet->setDataByName("mitigation_pct_pve", 392); // % calculation Mitigation % vs PvE 392 = 39.2%// confirmed DoV
-		packet->setDataByName("mitigation_pct_pvp", 559); // % calculation Mitigation % vs PvP 559 = 55.9%// confirmed DoV
+		
+		packet->setDataByName("mitigation_pct_pve", info_struct->get_mitigation_pve()); // % calculation Mitigation % vs PvE 392 = 39.2%// confirmed DoV
+		packet->setDataByName("mitigation_pct_pvp", info_struct->get_mitigation_pvp()); // % calculation Mitigation % vs PvP 559 = 55.9%// confirmed DoV
 		packet->setDataByName("toughness", 0);//toughness// confirmed DoV
 		packet->setDataByName("toughness", 0);//toughness// confirmed DoV
 		packet->setDataByName("toughness_resist_dmg_pvp", 0);//toughness_resist_dmg_pvp 73 = 7300% // confirmed DoV 
 		packet->setDataByName("toughness_resist_dmg_pvp", 0);//toughness_resist_dmg_pvp 73 = 7300% // confirmed DoV 
 		packet->setDataByName("avoidance_pct", (int16)info_struct->get_avoidance_display()*10.0f);//avoidance_pct 192 = 19.2% // confirmed DoV
 		packet->setDataByName("avoidance_pct", (int16)info_struct->get_avoidance_display()*10.0f);//avoidance_pct 192 = 19.2% // confirmed DoV
@@ -5058,10 +5060,10 @@ map<int32, Quest*>*	Player::GetCompletedPlayerQuests(){
 }
 }
 
 
 Quest* Player::GetAnyQuest(int32 quest_id) {
 Quest* Player::GetAnyQuest(int32 quest_id) {
-	if(completed_quests.count(quest_id) > 0)
-		return completed_quests[quest_id];
 	if(player_quests.count(quest_id) > 0)
 	if(player_quests.count(quest_id) > 0)
 		return player_quests[quest_id];
 		return player_quests[quest_id];
+	if(completed_quests.count(quest_id) > 0)
+		return completed_quests[quest_id];
 	
 	
 	return 0;
 	return 0;
 }
 }
@@ -5120,7 +5122,12 @@ int8 Player::CheckQuestFlag(Spawn* spawn){
 				}
 				}
 			}
 			}
 			MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__);
 			MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__);
-			if (CanReceiveQuest(quests->at(i))){
+			int8 flag = 0;
+			if (CanReceiveQuest(quests->at(i), &flag)){
+				if(flag) {
+					ret = flag;
+					break;
+				}
 				master_quest_list.LockQuests();
 				master_quest_list.LockQuests();
 				quest = master_quest_list.GetQuest(quests->at(i), false);
 				quest = master_quest_list.GetQuest(quests->at(i), false);
 				master_quest_list.UnlockQuests();
 				master_quest_list.UnlockQuests();
@@ -5155,7 +5162,7 @@ int8 Player::CheckQuestFlag(Spawn* spawn){
 	return ret;
 	return ret;
 }
 }
 
 
-bool Player::CanReceiveQuest(int32 quest_id){
+bool Player::CanReceiveQuest(int32 quest_id, int8* ret){
 	bool passed = true;
 	bool passed = true;
 	int32 x;
 	int32 x;
 	master_quest_list.LockQuests();
 	master_quest_list.LockQuests();
@@ -5266,6 +5273,18 @@ bool Player::CanReceiveQuest(int32 quest_id){
 				passed = false;
 				passed = false;
 		}
 		}
 	}
 	}
+	
+	int32 flag = 0;
+	if(lua_interface->CallQuestFunction(quest, "ReceiveQuestCriteria", this, 0xFFFFFFFF, &flag)) {
+		if(ret)
+			*ret = flag;
+		if(!flag) {
+			passed = false;
+		}
+		else {
+			passed = true;
+		}
+	}
 
 
 	return passed;
 	return passed;
 }
 }

+ 7 - 2
EQ2/source/WorldServer/Player.h

@@ -265,7 +265,7 @@ public:
 
 
 	void RemoveEquipmentUpdates()
 	void RemoveEquipmentUpdates()
 	{
 	{
-		appearanceList->clear();
+	 	appearanceList->clear();
 		safe_delete(appearanceList);
 		safe_delete(appearanceList);
 	}
 	}
 
 
@@ -865,7 +865,7 @@ public:
 	PlayerLanguagesList* GetPlayerLanguages() { return &player_languages_list; }
 	PlayerLanguagesList* GetPlayerLanguages() { return &player_languages_list; }
 	bool				HasLanguage(int32 id);
 	bool				HasLanguage(int32 id);
 	bool				HasLanguage(const char* name);
 	bool				HasLanguage(const char* name);
-	bool                CanReceiveQuest(int32 quest_id);
+	bool                CanReceiveQuest(int32 quest_id, int8* ret = 0);
 	float               GetBoatX() { if (info) return info->GetBoatX(); return 0; }
 	float               GetBoatX() { if (info) return info->GetBoatX(); return 0; }
 	float               GetBoatY() { if (info) return info->GetBoatY(); return 0; }
 	float               GetBoatY() { if (info) return info->GetBoatY(); return 0; }
 	float               GetBoatZ() { if (info) return info->GetBoatZ(); return 0; }
 	float               GetBoatZ() { if (info) return info->GetBoatZ(); return 0; }
@@ -1051,6 +1051,9 @@ public:
 	int32	GetCurrentLanguage() { return current_language_id; }
 	int32	GetCurrentLanguage() { return current_language_id; }
 	void	SetCurrentLanguage(int32 language_id) { current_language_id = language_id; }
 	void	SetCurrentLanguage(int32 language_id) { current_language_id = language_id; }
 	
 	
+	void	SetActiveReward(bool val) { active_reward = val; }
+	bool	IsActiveReward() { return active_reward; }
+	
 	Mutex MPlayerQuests;
 	Mutex MPlayerQuests;
 	float   pos_packet_speed;
 	float   pos_packet_speed;
 private:
 private:
@@ -1185,6 +1188,8 @@ private:
 	vector<GMTagFilter> gm_visual_filters;
 	vector<GMTagFilter> gm_visual_filters;
 	
 	
 	int32 current_language_id;
 	int32 current_language_id;
+	
+	bool active_reward;
 };
 };
 #pragma pack()
 #pragma pack()
 #endif
 #endif

+ 7 - 0
EQ2/source/WorldServer/Quests.cpp

@@ -1475,6 +1475,13 @@ void Quest::AddTmpRewardItem(Item* item){
 	tmp_reward_items.push_back(item);
 	tmp_reward_items.push_back(item);
 }
 }
 
 
+void Quest::GetTmpRewardItemsByID(std::vector<int32>* items) {
+	if(!items)
+		return;
+	for(int32 i=0;i<tmp_reward_items.size();i++)
+		items->push_back(tmp_reward_items[i]->details.item_id);
+}
+
 void Quest::AddRewardCoins(int32 copper, int32 silver, int32 gold, int32 plat){
 void Quest::AddRewardCoins(int32 copper, int32 silver, int32 gold, int32 plat){
 	reward_coins = copper + (silver*100) + (gold*10000) + ((int64)plat*1000000);
 	reward_coins = copper + (silver*100) + (gold*10000) + ((int64)plat*1000000);
 }
 }

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

@@ -140,6 +140,7 @@ public:
 
 
 	void				AddRewardItem(Item* item);
 	void				AddRewardItem(Item* item);
 	void				AddTmpRewardItem(Item* item);
 	void				AddTmpRewardItem(Item* item);
+	void				GetTmpRewardItemsByID(std::vector<int32>* items);
 	void				AddSelectableRewardItem(Item* item);
 	void				AddSelectableRewardItem(Item* item);
 	void				AddRewardCoins(int32 copper, int32 silver, int32 gold, int32 plat);
 	void				AddRewardCoins(int32 copper, int32 silver, int32 gold, int32 plat);
 	void                AddRewardCoinsMax(int64 coins);
 	void                AddRewardCoinsMax(int64 coins);

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

@@ -224,6 +224,7 @@ void RuleManager::Init()
 	RULE_INIT(R_PVP, AllowPVP, "0");
 	RULE_INIT(R_PVP, AllowPVP, "0");
 	RULE_INIT(R_PVP, LevelRange, "4");
 	RULE_INIT(R_PVP, LevelRange, "4");
 	RULE_INIT(R_PVP, InvisPlayerDiscoveryRange, "20"); // value > 0 sets radius inner to see, = 0 means always seen, -1 = never seen
 	RULE_INIT(R_PVP, InvisPlayerDiscoveryRange, "20"); // value > 0 sets radius inner to see, = 0 means always seen, -1 = never seen
+	RULE_INIT(R_PVP, PVPMitigationModByLevel, "25"); // gives a bonus to mitigation for PVP combat to offset the percentage level * mod (default 25)
 
 
 	/* COMBAT */
 	/* COMBAT */
 	RULE_INIT(R_Combat, MaxCombatRange, "4.0");
 	RULE_INIT(R_Combat, MaxCombatRange, "4.0");
@@ -237,6 +238,12 @@ void RuleManager::Init()
 	RULE_INIT(R_Combat, SpiritShardSpawnScript, "SpawnScripts/Generic/SpiritShard.lua");
 	RULE_INIT(R_Combat, SpiritShardSpawnScript, "SpawnScripts/Generic/SpiritShard.lua");
 	RULE_INIT(R_Combat, ShardDebtRecoveryPercent, "25.00"); // recovered percentage of debt upon obtainig shard, 25/100 means 25%.  If there is .5 DeathExperienceDebt, .5*25% = .125,  .5 - .125 = .375
 	RULE_INIT(R_Combat, ShardDebtRecoveryPercent, "25.00"); // recovered percentage of debt upon obtainig shard, 25/100 means 25%.  If there is .5 DeathExperienceDebt, .5*25% = .125,  .5 - .125 = .375
 	RULE_INIT(R_Combat, ShardRecoveryByRadius, "1"); // allow shards to auto pick up by radius, not requiring to click/right click the shard
 	RULE_INIT(R_Combat, ShardRecoveryByRadius, "1"); // allow shards to auto pick up by radius, not requiring to click/right click the shard
+	RULE_INIT(R_Combat, EffectiveMitigationCapLevel, "80"); // level multiplier for max effective cap, level * 80 (default)
+	RULE_INIT(R_Combat, CalculatedMitigationCapLevel, "100"); // The cap to calculate your mitigation from is [level*100].
+	RULE_INIT(R_Combat, MitigationLevelEffectivenessMax, "1.5"); // ratio victim level / attacker level for max effectiveness, when victim is higher level cap can reach 1.5
+	RULE_INIT(R_Combat, MitigationLevelEffectivenessMin, ".5"); // ratio victim level / attacker level for min effectiveness
+	RULE_INIT(R_Combat, MaxMitigationAllowed, ".75"); // percentage max mitigation allowed, eg. 75% of damage can be mitigated max in PVE
+	RULE_INIT(R_Combat, MaxMitigationAllowedPVP, ".75"); // percentage max mitigation allowed, eg. 75% of damage can be mitigated max in PVP
 
 
 	/* SPAWN */
 	/* SPAWN */
 	RULE_INIT(R_Spawn, SpeedMultiplier, "300"); // note: this value was 1280 until 6/1/2009, then was 600 til Sep 2009, when it became 300...?
 	RULE_INIT(R_Spawn, SpeedMultiplier, "300"); // note: this value was 1280 until 6/1/2009, then was 600 til Sep 2009, when it became 300...?

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

@@ -83,6 +83,7 @@ enum RuleType {
 	AllowPVP,
 	AllowPVP,
 	LevelRange,
 	LevelRange,
 	InvisPlayerDiscoveryRange,
 	InvisPlayerDiscoveryRange,
+	PVPMitigationModByLevel,
 
 
 	/* COMBAT */
 	/* COMBAT */
 	MaxCombatRange,
 	MaxCombatRange,
@@ -96,6 +97,12 @@ enum RuleType {
 	SpiritShardSpawnScript,
 	SpiritShardSpawnScript,
 	ShardDebtRecoveryPercent,
 	ShardDebtRecoveryPercent,
 	ShardRecoveryByRadius,
 	ShardRecoveryByRadius,
+	EffectiveMitigationCapLevel,
+	CalculatedMitigationCapLevel,
+	MitigationLevelEffectivenessMax,
+	MitigationLevelEffectivenessMin,
+	MaxMitigationAllowed,
+	MaxMitigationAllowedPVP,
 
 
 	/* SPAWN */
 	/* SPAWN */
 	SpeedMultiplier,
 	SpeedMultiplier,

+ 86 - 0
EQ2/source/WorldServer/WorldDatabase.cpp

@@ -1833,6 +1833,88 @@ SOGA chars looked ok in LoginServer screen tho... odd.
 	return false;
 	return false;
 }
 }
 
 
+void WorldDatabase::LoadCharacterQuestRewards(Client* client) {
+		Query query;
+	MYSQL_ROW row;
+	MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT indexed, quest_id, is_temporary, is_collection, has_displayed, tmp_coin, tmp_status, description FROM character_quest_rewards where char_id = %u ORDER BY indexed asc", client->GetCharacterID());
+	int8 count = 0;
+	if(result)
+	{
+		while(result && (row = mysql_fetch_row(result)))
+		{
+			int32 index = atoul(row[0]);
+			int32 quest_id = atoul(row[1]);
+
+			bool is_temporary = atoul(row[2]);
+
+			bool is_collection = atoul(row[3]);
+
+			bool has_displayed = atoul(row[4]);
+
+			
+		int64 tmp_coin = 0;
+#ifdef WIN32
+			tmp_coin = _strtoui64(row[5], NULL, 10);
+#else
+			tmp_coin = strtoull(row[5], 0, 10);
+#endif
+
+
+			int32 tmp_status = atoul(row[6]);
+			
+			std::string description = std::string("");
+			
+			if(row[7]) {
+				std::string description = std::string(row[7]);
+			}
+			
+			if(is_collection) { 			
+				map<int32, Collection*>* collections = client->GetPlayer()->GetCollectionList()->GetCollections();
+				map<int32, Collection*>::iterator itr;
+				Collection* collection = 0;
+				for (itr = collections->begin(); itr != collections->end(); itr++) {
+					collection = itr->second;
+				if (collection->GetIsReadyToTurnIn()) {
+						client->GetPlayer()->SetPendingCollectionReward(collection);
+						break;
+					}
+				}
+			}
+			if(is_temporary) {
+				LoadCharacterQuestTemporaryRewards(client, quest_id);
+			}
+			client->QueueQuestReward(quest_id, is_temporary, is_collection, has_displayed, tmp_coin, tmp_status, description, true, index);
+			count++;
+		}
+	}
+	
+	if(count) {
+		client->SetQuestUpdateState(true);
+	}
+}
+
+
+void WorldDatabase::LoadCharacterQuestTemporaryRewards(Client* client, int32 quest_id) {
+		Query query;
+	MYSQL_ROW row;
+	MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT item_id FROM character_quest_temporary_rewards where char_id = %u and quest_id = %u", client->GetCharacterID(), quest_id);
+	int8 count = 0;
+	if(result)
+	{
+		while(result && (row = mysql_fetch_row(result)))
+		{
+			int32 item_id = atoul(row[0]);
+			Quest* quest = client->GetPlayer()->GetAnyQuest(quest_id);
+			if(quest) {
+				Item* item = master_item_list.GetItem(item_id);
+				if(item) {
+					quest->AddTmpRewardItem(new Item(item));
+				}
+			}
+		}
+	}
+}
+
 bool WorldDatabase::InsertCharacterStats(int32 character_id, int8 class_id, int8 race_id){
 bool WorldDatabase::InsertCharacterStats(int32 character_id, int8 class_id, int8 race_id){
 	Query query1;
 	Query query1;
 	Query query2;
 	Query query2;
@@ -4085,6 +4167,7 @@ void WorldDatabase::Save(Client* client){
 	SavePlayerSpells(client);
 	SavePlayerSpells(client);
 	SavePlayerMail(client);
 	SavePlayerMail(client);
 	SavePlayerCollections(client);
 	SavePlayerCollections(client);
+	client->SaveQuestRewardData();
 
 
 	LogWrite(PLAYER__INFO, 3, "Player", "Player '%s' (%u) data saved.", player->GetName(), player->GetCharacterID());
 	LogWrite(PLAYER__INFO, 3, "Player", "Player '%s' (%u) data saved.", player->GetName(), player->GetCharacterID());
 }
 }
@@ -4924,6 +5007,9 @@ bool WorldDatabase::DeleteCharacter(int32 account_id, int32 character_id){
 	query.RunQuery2(Q_DELETE, "DELETE FROM characters WHERE id=%u AND account_id=%u", character_id, account_id);
 	query.RunQuery2(Q_DELETE, "DELETE FROM characters WHERE id=%u AND account_id=%u", character_id, account_id);
 	//delete languages
 	//delete languages
 	query2.RunQuery2(Q_DELETE, "DELETE FROM character_languages WHERE char_id=%u", character_id);
 	query2.RunQuery2(Q_DELETE, "DELETE FROM character_languages WHERE char_id=%u", character_id);
+	//delete quest rewards
+	query2.RunQuery2(Q_DELETE, "DELETE FROM character_quest_rewards WHERE char_id=%u", character_id);
+	query2.RunQuery2(Q_DELETE, "DELETE FROM character_quest_temporary_rewards WHERE char_id=%u", character_id);
 
 
 	if(!query.GetAffectedRows())
 	if(!query.GetAffectedRows())
 	{
 	{

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

@@ -296,6 +296,8 @@ public:
 	void	LoadCharacterItemList(int32 account_id, int32 char_id, Player* player, int16);
 	void	LoadCharacterItemList(int32 account_id, int32 char_id, Player* player, int16);
 	bool	loadCharacter(const char* name, int32 account_id, Client* client);
 	bool	loadCharacter(const char* name, int32 account_id, Client* client);
 	bool	LoadCharacterStats(int32 id, int32 account_id, Client* client);
 	bool	LoadCharacterStats(int32 id, int32 account_id, Client* client);
+	void	LoadCharacterQuestRewards(Client* client);
+	void	LoadCharacterQuestTemporaryRewards(Client* client, int32 quest_id);
 	bool	InsertCharacterStats(int32 character_id, int8 class_id, int8 race_id);
 	bool	InsertCharacterStats(int32 character_id, int8 class_id, int8 race_id);
 	bool	UpdateCharacterTimeStamp(int32 account_id, int32 character_id, int32 timestamp);
 	bool	UpdateCharacterTimeStamp(int32 account_id, int32 character_id, int32 timestamp);
 	bool	insertCharacterProperty(Client* client, char* propName, char* propValue);
 	bool	insertCharacterProperty(Client* client, char* propName, char* propValue);

+ 245 - 66
EQ2/source/WorldServer/client.cpp

@@ -402,6 +402,7 @@ void Client::SendLoginInfo() {
 		}
 		}
 		database.LoadPlayerFactions(this);
 		database.LoadPlayerFactions(this);
 		database.LoadCharacterQuests(this);
 		database.LoadCharacterQuests(this);
+		database.LoadCharacterQuestRewards(this);
 		database.LoadPlayerMail(this);
 		database.LoadPlayerMail(this);
 	}
 	}
 	LogWrite(CCLIENT__DEBUG, 0, "Client", "Send Quest Journal...");
 	LogWrite(CCLIENT__DEBUG, 0, "Client", "Send Quest Journal...");
@@ -5444,12 +5445,73 @@ void Client::AddPendingQuestAcceptReward(Quest* quest)
 	MPendingQuestAccept.unlock();
 	MPendingQuestAccept.unlock();
 }
 }
 
 
-void Client::AddPendingQuestReward(Quest* quest, bool update) {
-	MQuestPendingUpdates.writelock(__FUNCTION__, __LINE__);
-	quest_pending_reward.push_back(quest->GetQuestID());
+void Client::AddPendingQuestReward(Quest* quest, bool update, bool is_temporary, std::string description) {
+	QueueQuestReward(quest->GetQuestID(), is_temporary, false, false, (is_temporary ? quest->GetCoinTmpReward() : 0), 
+	(is_temporary ? quest->GetStatusTmpReward() : 0), description, false, 0);
 	quest_updates = update;
 	quest_updates = update;
+	if(quest_updates) {
+		SaveQuestRewardData(true);
+	}
+
+}
+
+void Client::QueueQuestReward(int32 quest_id, bool is_temporary, bool is_collection, bool has_displayed, int64 tmp_coin, int32 tmp_status, std::string description, bool db_saved, int32 index) {
+	if(HasQuestRewardQueued(quest_id, is_temporary, is_collection))
+		return;
+	
+	QuestRewardData data;
+	data.quest_id = quest_id;
+	data.is_temporary = is_temporary;
+	data.is_collection = is_collection;
+	data.has_displayed = has_displayed;
+	data.tmp_coin = tmp_coin;
+	data.tmp_status = tmp_status;
+	data.description = std::string(description);
+	data.db_saved = db_saved;
+	data.db_index = index;
+	MQuestPendingUpdates.writelock(__FUNCTION__, __LINE__);
+	quest_pending_reward.push_back(data);
 	MQuestPendingUpdates.releasewritelock(__FUNCTION__, __LINE__);
 	MQuestPendingUpdates.releasewritelock(__FUNCTION__, __LINE__);
+}
 
 
+bool Client::HasQuestRewardQueued(int32 quest_id, bool is_temporary, bool is_collection) {
+	
+	bool success = false;
+	MQuestPendingUpdates.readlock(__FUNCTION__, __LINE__);
+	if (quest_pending_reward.size() > 0) {
+		vector<QuestRewardData>::iterator itr;
+		
+		for (itr = quest_pending_reward.begin(); itr != quest_pending_reward.end(); itr++) {
+			int32 questID = (*itr).quest_id;
+			bool temporary = (*itr).is_temporary;
+			bool collection = (*itr).is_collection;
+			if( questID == quest_id && is_temporary == temporary && is_collection == collection ) {
+				success = true;
+				break;
+			}
+		}
+	}
+	MQuestPendingUpdates.releasereadlock(__FUNCTION__, __LINE__);
+	
+	return success;
+}
+
+void Client::RemoveQueuedQuestReward() {
+	MQuestPendingUpdates.writelock(__FUNCTION__, __LINE__);
+	if(quest_pending_reward.size() > 0) {
+		QuestRewardData data = quest_pending_reward.at(0);
+		if(data.db_saved) {
+			Query query;
+			query.AddQueryAsync(GetCharacterID(), &database, Q_DELETE, "delete FROM character_quest_rewards where char_id = %u and indexed = %u", GetCharacterID(), data.db_index);
+			if(data.is_temporary && data.quest_id) {
+				query.AddQueryAsync(GetCharacterID(), &database, Q_DELETE, "delete FROM character_quest_temporary_rewards where char_id = %u and quest_id = %u", GetCharacterID(), data.quest_id);
+			}
+		}
+		quest_pending_reward.erase(quest_pending_reward.begin());
+	}
+	MQuestPendingUpdates.releasewritelock(__FUNCTION__, __LINE__);
+	
+	SaveQuestRewardData(true);
 }
 }
 
 
 void Client::AddPendingQuestUpdate(int32 quest_id, int32 step_id, int32 progress) {
 void Client::AddPendingQuestUpdate(int32 quest_id, int32 step_id, int32 progress) {
@@ -5461,6 +5523,9 @@ void Client::AddPendingQuestUpdate(int32 quest_id, int32 step_id, int32 progress
 }
 }
 
 
 void Client::ProcessQuestUpdates() {
 void Client::ProcessQuestUpdates() {
+	if(!GetPlayer()->IsFullyLoggedIn())
+		return;
+
 	if (quest_pending_updates.size() > 0) {
 	if (quest_pending_updates.size() > 0) {
 		map<int32, map<int32, int32> > tmp_quest_updates;
 		map<int32, map<int32, int32> > tmp_quest_updates;
 		MQuestPendingUpdates.writelock(__FUNCTION__, __LINE__);
 		MQuestPendingUpdates.writelock(__FUNCTION__, __LINE__);
@@ -5483,17 +5548,41 @@ void Client::ProcessQuestUpdates() {
 	MQuestPendingUpdates.readlock(__FUNCTION__, __LINE__);
 	MQuestPendingUpdates.readlock(__FUNCTION__, __LINE__);
 	if (quest_pending_reward.size() > 0) {
 	if (quest_pending_reward.size() > 0) {
 		MQuestPendingUpdates.releasereadlock(__FUNCTION__, __LINE__);
 		MQuestPendingUpdates.releasereadlock(__FUNCTION__, __LINE__);
-		vector<int32>::iterator itr;
-		vector<int32> tmp_quest_rewards;
+		
+		// only able to display one reward at a time
+		if(GetPlayer()->IsActiveReward())
+			return;
+		
+		Query query;
+		vector<QuestRewardData>::iterator itr;
+		vector<QuestRewardData> tmp_quest_rewards;
 		MQuestPendingUpdates.writelock(__FUNCTION__, __LINE__);
 		MQuestPendingUpdates.writelock(__FUNCTION__, __LINE__);
-		tmp_quest_rewards.insert(tmp_quest_rewards.begin(), quest_pending_reward.begin(), quest_pending_reward.end());
-		quest_pending_reward.clear();
+		tmp_quest_rewards.insert(tmp_quest_rewards.begin(), quest_pending_reward.begin(), quest_pending_reward.begin()+1);
 		MQuestPendingUpdates.releasewritelock(__FUNCTION__, __LINE__);
 		MQuestPendingUpdates.releasewritelock(__FUNCTION__, __LINE__);
+		
 		for (itr = tmp_quest_rewards.begin(); itr != tmp_quest_rewards.end(); itr++) {
 		for (itr = tmp_quest_rewards.begin(); itr != tmp_quest_rewards.end(); itr++) {
-			int32 questID = *itr;
-			Quest* quest = GetPlayer()->GetAnyQuest(questID);
-			if(quest) {
-				GiveQuestReward(quest);
+			int32 questID = (*itr).quest_id;
+			Quest* quest = 0;
+			if((*itr).is_collection && GetPlayer()->GetPendingCollectionReward()) {
+				DisplayCollectionComplete(GetPlayer()->GetPendingCollectionReward());
+				GetPlayer()->SetActiveReward(true);
+				(*itr).has_displayed = true;
+				
+				UpdateCharacterRewardData(&(*itr));
+			}
+			else if(questID > 0 && (quest = GetPlayer()->GetAnyQuest(questID))) {
+				quest->SetQuestTemporaryState((*itr).is_temporary, (*itr).description);
+				if((*itr).is_temporary) {
+					quest->SetStatusTmpReward((*itr).tmp_status);
+					quest->SetCoinTmpReward((*itr).tmp_coin);
+				}
+				GiveQuestReward(quest, (*itr).has_displayed);
+				GetPlayer()->SetActiveReward(true);
+				(*itr).has_displayed = true;
+				
+				UpdateCharacterRewardData(&(*itr));
+				// only able to display one reward at a time
+				break;
 			} else {
 			} else {
 				LogWrite(CCLIENT__ERROR, 0, "Client", "Quest ID %u missing for Player %s, skipping quest id from tmp_quest_rewards.", questID, GetPlayer()->GetName());
 				LogWrite(CCLIENT__ERROR, 0, "Client", "Quest ID %u missing for Player %s, skipping quest id from tmp_quest_rewards.", questID, GetPlayer()->GetName());
 			}
 			}
@@ -5501,7 +5590,15 @@ void Client::ProcessQuestUpdates() {
 	} else {
 	} else {
 		MQuestPendingUpdates.releasereadlock(__FUNCTION__, __LINE__);
 		MQuestPendingUpdates.releasereadlock(__FUNCTION__, __LINE__);
 	}
 	}
-	quest_updates = false;
+	
+	MQuestPendingUpdates.readlock(__FUNCTION__, __LINE__);
+	if (quest_pending_reward.size() > 0) {
+		quest_updates = true;
+	}
+	else {
+		quest_updates = false;
+	}
+	MQuestPendingUpdates.releasereadlock(__FUNCTION__, __LINE__);
 
 
 }
 }
 
 
@@ -5961,7 +6058,11 @@ void Client::AcceptQuestReward(Quest* quest, int32 item_id) {
 			totalItems += items->size();
 			totalItems += items->size();
 		}
 		}
 	}
 	}
-
+	
+	RemoveQueuedQuestReward();
+	
+	GetPlayer()->SetActiveReward(false);
+		
 	if (free_slots >= num_slots_needed || (player->item_list.HasFreeBagSlot() && master_item && master_item->IsBag() && master_item->bag_info->num_slots >= totalItems)) {
 	if (free_slots >= num_slots_needed || (player->item_list.HasFreeBagSlot() && master_item && master_item->IsBag() && master_item->bag_info->num_slots >= totalItems)) {
 		if (master_item)
 		if (master_item)
 			AddItem(item_id);
 			AddItem(item_id);
@@ -5994,31 +6095,34 @@ void Client::AcceptQuestReward(Quest* quest, int32 item_id) {
 				AwardCoins(total_coins, std::string("for completing ").append(quest->GetName()));
 				AwardCoins(total_coins, std::string("for completing ").append(quest->GetName()));
 
 
 			player->GetInfoStruct()->add_status_points(quest->GetStatusTmpReward());
 			player->GetInfoStruct()->add_status_points(quest->GetStatusTmpReward());
-			
-			quest->SetQuestTemporaryState(false);
 		}
 		}
-		else
-			player->GetInfoStruct()->add_status_points(quest->GetStatusPoints());
+		else {
+			player->GetInfoStruct()->add_status_points(quest->GetStatusPoints());		
+		}
 		
 		
+		quest->SetQuestTemporaryState(false);
 		player->SetCharSheetChanged(true);
 		player->SetCharSheetChanged(true);
 	}
 	}
 	else {
 	else {
-		MPendingQuestAccept.lock();
-		pending_quest_accept.push_back(quest->GetQuestID());
-		MPendingQuestAccept.unlock();
+		GetPlayer()->SetActiveReward(true);
+		AddPendingQuestAcceptReward(quest);
 		SimpleMessage(CHANNEL_COLOR_RED, "You do not have enough free slots!  Free some slots and try again.");
 		SimpleMessage(CHANNEL_COLOR_RED, "You do not have enough free slots!  Free some slots and try again.");
 		DisplayQuestComplete(quest, quest->GetQuestTemporaryState(), quest->GetQuestTemporaryDescription());
 		DisplayQuestComplete(quest, quest->GetQuestTemporaryState(), quest->GetQuestTemporaryDescription());
 	}
 	}
 
 
 }
 }
 
 
-void Client::DisplayQuestRewards(Quest* quest, int64 coin, vector<Item*>* rewards, vector<Item*>* selectable_rewards, map<int32, sint32>* factions, const char* header, int32 status_points, const char* text) {
+void Client::DisplayQuestRewards(Quest* quest, int64 coin, vector<Item*>* rewards, vector<Item*>* selectable_rewards, map<int32, sint32>* factions, const char* header, int32 status_points, const char* text, bool was_displayed) {
 	if (coin == 0 && (!rewards || rewards->size() == 0) && (!selectable_rewards || selectable_rewards->size() == 0) && (!factions || factions->size() == 0) && status_points == 0 && text == 0 && (!quest || (quest->GetCoinsReward() == 0 && quest->GetCoinsRewardMax() == 0))) {
 	if (coin == 0 && (!rewards || rewards->size() == 0) && (!selectable_rewards || selectable_rewards->size() == 0) && (!factions || factions->size() == 0) && status_points == 0 && text == 0 && (!quest || (quest->GetCoinsReward() == 0 && quest->GetCoinsRewardMax() == 0))) {
 		/*if (quest)
 		/*if (quest)
 			text = quest->GetName();
 			text = quest->GetName();
 		else*/
 		else*/
 		return;//nothing to give
 		return;//nothing to give
 	}
 	}
+	
+	GetPlayer()->ClearPendingSelectableItemRewards(0, true);
+	GetPlayer()->ClearPendingItemRewards();
+	
 	PacketStruct* packet2 = configReader.getStruct("WS_QuestRewardPackMsg", GetVersion());
 	PacketStruct* packet2 = configReader.getStruct("WS_QuestRewardPackMsg", GetVersion());
 	if (packet2) {
 	if (packet2) {
 		int32 source_id = 0;
 		int32 source_id = 0;
@@ -6036,7 +6140,7 @@ void Client::DisplayQuestRewards(Quest* quest, int64 coin, vector<Item*>* reward
 		}
 		}
 		if (rewarded_coin > coin)
 		if (rewarded_coin > coin)
 			coin = rewarded_coin;
 			coin = rewarded_coin;
-		if (!quest) { //this entire function is either for version <=546 or for quest rewards in middle of quest, so quest should be 0, otherwise quest will handle the rewards
+		if (!quest && !was_displayed) { //this entire function is either for version <=546 or for quest rewards in middle of quest, so quest should be 0, otherwise quest will handle the rewards
 			if (coin > 0) {
 			if (coin > 0) {
 				player->AddCoins(coin);
 				player->AddCoins(coin);
 				PlaySound("coin_cha_ching");
 				PlaySound("coin_cha_ching");
@@ -6045,7 +6149,7 @@ void Client::DisplayQuestRewards(Quest* quest, int64 coin, vector<Item*>* reward
 		packet2->setSubstructDataByName("reward_data", "unknown1", 255);
 		packet2->setSubstructDataByName("reward_data", "unknown1", 255);
 		packet2->setSubstructDataByName("reward_data", "reward", header);
 		packet2->setSubstructDataByName("reward_data", "reward", header);
 		packet2->setSubstructDataByName("reward_data", "max_coin", coin);
 		packet2->setSubstructDataByName("reward_data", "max_coin", coin);
-		if (player->GetGuild()) {
+		if (player->GetGuild() && !was_displayed) {
 			if (!quest) { //this entire function is either for version <=546 or for quest rewards in middle of quest, so quest should be 0, otherwise quest will handle the rewards
 			if (!quest) { //this entire function is either for version <=546 or for quest rewards in middle of quest, so quest should be 0, otherwise quest will handle the rewards
 				player->GetInfoStruct()->add_status_points(status_points);
 				player->GetInfoStruct()->add_status_points(status_points);
 				player->SetCharSheetChanged(true);
 				player->SetCharSheetChanged(true);
@@ -6107,16 +6211,12 @@ void Client::DisplayQuestRewards(Quest* quest, int64 coin, vector<Item*>* reward
 	}
 	}
 }
 }
 
 
-void Client::DisplayQuestComplete(Quest* quest, bool tempReward, std::string customDescription) {	
+void Client::DisplayQuestComplete(Quest* quest, bool tempReward, std::string customDescription, bool was_displayed) {	
 	if (!quest)
 	if (!quest)
 		return;
 		return;
 	
 	
-	quest->SetQuestTemporaryState(tempReward, customDescription);
-	
-	AddPendingQuestAcceptReward(quest);
-	
 	if (GetVersion() <= 546) {
 	if (GetVersion() <= 546) {
-		DisplayQuestRewards(quest, 0, quest->GetRewardItems(), quest->GetSelectableRewardItems(), quest->GetRewardFactions(), "Quest Complete!", quest->GetStatusPoints());
+		DisplayQuestRewards(quest, 0, quest->GetRewardItems(), quest->GetSelectableRewardItems(), quest->GetRewardFactions(), "Quest Complete!", quest->GetStatusPoints(), customDescription.c_str(), was_displayed);
 		return;
 		return;
 	}
 	}
 	PacketStruct* packet = configReader.getStruct("WS_QuestComplete", GetVersion());
 	PacketStruct* packet = configReader.getStruct("WS_QuestComplete", GetVersion());
@@ -6289,51 +6389,59 @@ void Client::DisplayRandomizeFeatures(int32 flags) {
 
 
 }
 }
 
 
-void Client::GiveQuestReward(Quest* quest) {
+void Client::GiveQuestReward(Quest* quest, bool has_displayed) {
 	current_quest_id = 0;
 	current_quest_id = 0;
 
 
-	if(!quest->GetQuestTemporaryState())
+	if(!quest->GetQuestTemporaryState() && !has_displayed)
 	{
 	{
 		quest->IncrementCompleteCount();
 		quest->IncrementCompleteCount();
 		player->AddCompletedQuest(quest);
 		player->AddCompletedQuest(quest);
 	}
 	}
 	
 	
+	AddPendingQuestAcceptReward(quest);
+		
 	DisplayQuestComplete(quest, quest->GetQuestTemporaryState(), quest->GetQuestTemporaryDescription());
 	DisplayQuestComplete(quest, quest->GetQuestTemporaryState(), quest->GetQuestTemporaryDescription());
 	LogWrite(CCLIENT__DEBUG, 0, "Client", "Send Quest Journal...");
 	LogWrite(CCLIENT__DEBUG, 0, "Client", "Send Quest Journal...");
 	SendQuestJournal();
 	SendQuestJournal();
 	
 	
-	if(quest->GetQuestTemporaryState())
+	if(quest->GetQuestTemporaryState()) {
 		return;
 		return;
-	
-	player->RemoveQuest(quest->GetQuestID(), false);
-	if (quest->GetExpReward() > 0) {
-		int16 level = player->GetLevel();
-		int32 xp = quest->GetExpReward();
-		if (player->AddXP(xp)) {
-			Message(CHANNEL_REWARD, "You gain %u experience!", (int32)xp);
-			if (player->GetLevel() != level)
-				ChangeLevel(level, player->GetLevel());
-			player->SetCharSheetChanged(true);
-		}
-	}
-	if (quest->GetTSExpReward() > 0) {
-		int8 ts_level = player->GetTSLevel();
-		int32 xp = quest->GetTSExpReward();
-		if (player->AddTSXP(xp)) {
-			Message(CHANNEL_REWARD, "You gain %u tradeskill experience!", (int32)xp);
-			if (player->GetTSLevel() != ts_level)
-				ChangeTSLevel(ts_level, player->GetTSLevel());
-			player->SetCharSheetChanged(true);
-		}
-	}
-	int64 total_coins = quest->GetGeneratedCoin();
-	if (total_coins > 0)
-		AwardCoins(total_coins, std::string("for completing ").append(quest->GetName()));
+	}
+
+	if(!has_displayed) {
+		if (quest->GetExpReward() > 0) {
+			int16 level = player->GetLevel();
+			int32 xp = quest->GetExpReward();
+			if (player->AddXP(xp)) {
+				Message(CHANNEL_REWARD, "You gain %u experience!", (int32)xp);
+				if (player->GetLevel() != level)
+					ChangeLevel(level, player->GetLevel());
+				player->SetCharSheetChanged(true);
+			}
+		}
+		if (quest->GetTSExpReward() > 0) {
+			int8 ts_level = player->GetTSLevel();
+			int32 xp = quest->GetTSExpReward();
+			if (player->AddTSXP(xp)) {
+				Message(CHANNEL_REWARD, "You gain %u tradeskill experience!", (int32)xp);
+				if (player->GetTSLevel() != ts_level)
+					ChangeTSLevel(ts_level, player->GetTSLevel());
+				player->SetCharSheetChanged(true);
+			}
+		}
+		int64 total_coins = quest->GetGeneratedCoin();
+		if (total_coins > 0)
+			AwardCoins(total_coins, std::string("for completing ").append(quest->GetName()));
+		
+		player->RemoveQuest(quest->GetQuestID(), false);
+	}
 	
 	
 	if (quest->GetQuestGiver() > 0)
 	if (quest->GetQuestGiver() > 0)
 		GetCurrentZone()->SendSpawnChangesByDBID(quest->GetQuestGiver(), this, false, true);
 		GetCurrentZone()->SendSpawnChangesByDBID(quest->GetQuestGiver(), this, false, true);
 	
 	
-	RemovePlayerQuest(quest->GetQuestID(), true, false);	
+	if(!has_displayed) {
+		RemovePlayerQuest(quest->GetQuestID(), true, false);	
+	}
 }
 }
 
 
 void Client::DisplayConversation(int32 conversation_id, int32 spawn_id, vector<ConversationOption>* conversations, const char* text, const char* mp3, int32 key1, int32 key2, int8 language, int8 can_close) {
 void Client::DisplayConversation(int32 conversation_id, int32 spawn_id, vector<ConversationOption>* conversations, const char* text, const char* mp3, int32 key1, int32 key2, int8 language, int8 can_close) {
@@ -8805,6 +8913,7 @@ void Client::SetReadyForSpawns(bool val) {
 			world.GetGroupManager()->GroupMessage(GetPlayer()->GetGroupMemberInfo()->group_id, "%s has returned from Linkdead.", GetPlayer()->GetName());
 			world.GetGroupManager()->GroupMessage(GetPlayer()->GetGroupMemberInfo()->group_id, "%s has returned from Linkdead.", GetPlayer()->GetName());
 		}
 		}
 	}
 	}
+	GetPlayer()->SetActiveReward(false);
 	zone_list.CheckFriendZoned(this);
 	zone_list.CheckFriendZoned(this);
 
 
 }
 }
@@ -9043,9 +9152,8 @@ void Client::InspectPlayer(Player* player_to_inspect) {
 			packet->setDataByName("gender", player_to_inspect->GetGender());
 			packet->setDataByName("gender", player_to_inspect->GetGender());
 			packet->setDataByName("adventure_level", player_to_inspect->GetLevel());
 			packet->setDataByName("adventure_level", player_to_inspect->GetLevel());
 
 
-			LogWrite(MISC__TODO, 1, "TODO", "Put mentored level here (adventure_level_effective)\nfile: %s, func: %s, line: %i", __FILE__, __FUNCTION__, __LINE__);
-
-			packet->setDataByName("adventure_level_effective", player_to_inspect->GetLevel());
+			int16 effective_level = player_to_inspect->GetInfoStruct()->get_effective_level() != 0 ? player_to_inspect->GetInfoStruct()->get_effective_level() : player_to_inspect->GetLevel();
+			packet->setDataByName("adventure_level_effective", effective_level);
 			packet->setDataByName("adventure_class", player_to_inspect->GetAdventureClass());
 			packet->setDataByName("adventure_class", player_to_inspect->GetAdventureClass());
 			packet->setDataByName("tradeskill_level", player_to_inspect->GetTSLevel());
 			packet->setDataByName("tradeskill_level", player_to_inspect->GetTSLevel());
 			packet->setDataByName("tradeskill_class", player_to_inspect->GetTradeskillClass());
 			packet->setDataByName("tradeskill_class", player_to_inspect->GetTradeskillClass());
@@ -9543,11 +9651,26 @@ void Client::HandInCollections() {
 		collection = itr->second;
 		collection = itr->second;
 		if (collection->GetIsReadyToTurnIn()) {
 		if (collection->GetIsReadyToTurnIn()) {
 			player->SetPendingCollectionReward(collection);
 			player->SetPendingCollectionReward(collection);
-			DisplayCollectionComplete(collection);
-
-			return;
+			MQuestPendingUpdates.writelock(__FUNCTION__, __LINE__);
+			QuestRewardData data;
+			data.quest_id = 0;
+			data.is_temporary = false;
+			data.description = std::string("");
+			data.is_collection = true;
+			data.has_displayed = false;
+			data.tmp_coin = 0;
+			data.tmp_status = 0;
+			data.db_saved = false;
+			data.db_index = 0;
+			quest_pending_reward.push_back(data);
+			MQuestPendingUpdates.releasewritelock(__FUNCTION__, __LINE__);
+			quest_updates = true;
+			break;
 		}
 		}
 	}
 	}
+	if(quest_updates) {
+		SaveQuestRewardData(true);
+	}
 }
 }
 
 
 void Client::AcceptCollectionRewards(Collection* collection, int32 selectable_item_id) {
 void Client::AcceptCollectionRewards(Collection* collection, int32 selectable_item_id) {
@@ -9567,8 +9690,7 @@ void Client::AcceptCollectionRewards(Collection* collection, int32 selectable_it
 	num_slots = player->GetPlayerItemList()->GetNumberOfFreeSlots();
 	num_slots = player->GetPlayerItemList()->GetNumberOfFreeSlots();
 	if (num_slots < num_slots_needed) {
 	if (num_slots < num_slots_needed) {
 		SimpleMessage(CHANNEL_COLOR_RED, "You do not have enough free slots. Free up some slots and try again");
 		SimpleMessage(CHANNEL_COLOR_RED, "You do not have enough free slots. Free up some slots and try again");
-		HandInCollections();
-
+		DisplayCollectionComplete(collection);
 		return;
 		return;
 	}
 	}
 
 
@@ -9606,6 +9728,11 @@ void Client::AcceptCollectionRewards(Collection* collection, int32 selectable_it
 
 
 	/* reset the pending collection reward and check for my collections that the player needs to hand in */
 	/* reset the pending collection reward and check for my collections that the player needs to hand in */
 	player->SetPendingCollectionReward(0);
 	player->SetPendingCollectionReward(0);
+	
+	RemoveQueuedQuestReward();
+	
+	GetPlayer()->SetActiveReward(false);
+	
 	HandInCollections();
 	HandInCollections();
 
 
 }
 }
@@ -11041,4 +11168,56 @@ void Client::SendPlayFlavor(Spawn* spawn, int8 language, VoiceOverStruct* non_ga
 		if(resStruct) {
 		if(resStruct) {
 			GetPlayer()->GetZone()->PlayFlavor(this, spawn, resStruct->mp3_string.c_str(), resStruct->text_string.c_str(), resStruct->emote_string.c_str(), resStruct->key1, resStruct->key2, language);
 			GetPlayer()->GetZone()->PlayFlavor(this, spawn, resStruct->mp3_string.c_str(), resStruct->text_string.c_str(), resStruct->emote_string.c_str(), resStruct->key1, resStruct->key2, language);
 		}
 		}
+}
+
+void Client::SaveQuestRewardData(bool force_refresh) {
+		Query query;
+		if(force_refresh) {
+			query.AddQueryAsync(GetCharacterID(), &database, Q_DELETE, "delete from character_quest_rewards where char_id = %u", 
+							GetCharacterID());
+							
+			query.AddQueryAsync(GetCharacterID(), &database, Q_DELETE, "delete from character_quest_temporary_rewards where char_id = %u", 
+							GetCharacterID());
+		}
+		vector<QuestRewardData>::iterator itr;
+		vector<QuestRewardData> tmp_quest_rewards;
+		MQuestPendingUpdates.writelock(__FUNCTION__, __LINE__);
+		int index = 0;
+		for (itr = quest_pending_reward.begin(); itr != quest_pending_reward.end(); itr++) {
+			int32 questID = (*itr).quest_id;
+			if(!(*itr).db_saved || force_refresh) {
+				query.AddQueryAsync(GetCharacterID(), &database, Q_REPLACE, "replace into character_quest_rewards (char_id, indexed, quest_id, is_temporary, is_collection, has_displayed, tmp_coin, tmp_status, description) values(%u, %u, %u, %u, %u, %u, %I64u, %u, '%s')", 
+					GetCharacterID(), index, questID, (*itr).is_temporary, (*itr).is_collection, (*itr).has_displayed, (*itr).tmp_coin, (*itr).tmp_status, database.getSafeEscapeString((*itr).description.c_str()).c_str());
+				(*itr).db_saved = true;
+				(*itr).db_index = index;
+				if((*itr).is_temporary) {
+					std::vector<int32> items;
+					Quest* quest = GetPlayer()->GetAnyQuest(questID);
+					if(quest) {
+						quest->GetTmpRewardItemsByID(&items);
+						if(!force_refresh && items.size() > 0) {
+							query.AddQueryAsync(GetCharacterID(), &database, Q_REPLACE, "delete from character_quest_temporary_rewards where char_id = %u and quest_id = %u", 
+								GetCharacterID(), questID);
+						}
+						for(int i=0;i<items.size();i++) {
+							query.AddQueryAsync(GetCharacterID(), &database, Q_REPLACE, "replace into character_quest_temporary_rewards (char_id, quest_id, item_id) values(%u, %u, %u)", 
+								GetCharacterID(), questID, items[i]);
+						}
+					}
+				}
+			}
+			index++;
+		}
+		MQuestPendingUpdates.releasewritelock(__FUNCTION__, __LINE__);
+}
+
+void Client::UpdateCharacterRewardData(QuestRewardData* data) {
+	
+	if(!data)
+		return;
+	if(data->db_saved) {
+		Query query;
+		query.AddQueryAsync(GetCharacterID(), &database, Q_INSERT, "update character_quest_rewards set is_temporary = %u, is_collection = %u, has_displayed = %u, tmp_coin = %I64u, tmp_status = %u, description = '%s' where char_id=%u and indexed=%u and quest_id=%u", 
+			data->is_temporary, data->is_collection, data->has_displayed, data->tmp_coin, data->tmp_status, database.getSafeEscapeString(data->description.c_str()).c_str(), GetCharacterID(), data->db_index, data->quest_id);
+	}
 }
 }

+ 23 - 5
EQ2/source/WorldServer/client.h

@@ -142,6 +142,18 @@ struct WaypointInfo {
 	int8 type;
 	int8 type;
 };
 };
 
 
+struct QuestRewardData {
+	int32 quest_id;
+	bool is_temporary;
+	std::string description;
+	bool is_collection;
+	bool has_displayed;
+	int64 tmp_coin;
+	int32 tmp_status;
+	bool db_saved;
+	int32 db_index;
+};
+
 class Client {
 class Client {
 public:
 public:
 	Client(EQStream* ieqs);
 	Client(EQStream* ieqs);
@@ -280,8 +292,8 @@ public:
 	void	SendQuestFailure(Quest* quest);
 	void	SendQuestFailure(Quest* quest);
 	void	SendQuestUpdateStep(Quest* quest, int32 step, bool display_quest_helper = true);
 	void	SendQuestUpdateStep(Quest* quest, int32 step, bool display_quest_helper = true);
 	void	SendQuestUpdateStepImmediately(Quest* quest, int32 step, bool display_quest_helper = true);
 	void	SendQuestUpdateStepImmediately(Quest* quest, int32 step, bool display_quest_helper = true);
-	void	DisplayQuestRewards(Quest* quest, int64 coin, vector<Item*>* rewards=0, vector<Item*>* selectable_rewards=0, map<int32, sint32>* factions=0, const char* header="Quest Reward!", int32 status_points=0, const char* text=0);
-	void	DisplayQuestComplete(Quest* quest, bool tempReward = false, std::string customDescription = string(""));
+	void	DisplayQuestRewards(Quest* quest, int64 coin, vector<Item*>* rewards=0, vector<Item*>* selectable_rewards=0, map<int32, sint32>* factions=0, const char* header="Quest Reward!", int32 status_points=0, const char* text=0, bool was_displayed = false);
+	void	DisplayQuestComplete(Quest* quest, bool tempReward = false, std::string customDescription = string(""), bool was_displayed = false);
 	void	DisplayRandomizeFeatures(int32 features);
 	void	DisplayRandomizeFeatures(int32 features);
 	void	AcceptQuestReward(Quest* quest, int32 item_id);
 	void	AcceptQuestReward(Quest* quest, int32 item_id);
 	Quest*	GetPendingQuestAcceptance(int32 item_id);
 	Quest*	GetPendingQuestAcceptance(int32 item_id);
@@ -348,7 +360,10 @@ public:
 	void	SetPlayer(Player* new_player);
 	void	SetPlayer(Player* new_player);
 
 
 	void	AddPendingQuestAcceptReward(Quest* quest);
 	void	AddPendingQuestAcceptReward(Quest* quest);
-	void	AddPendingQuestReward(Quest* quest, bool update=true);
+	void	AddPendingQuestReward(Quest* quest, bool update=true, bool is_temporary = false, std::string description = std::string(""));
+	bool	HasQuestRewardQueued(int32 quest_id, bool is_temporary, bool is_collection);
+	void	QueueQuestReward(int32 quest_id, bool is_temporary, bool is_collection, bool has_displayed, int64 tmp_coin, int32 tmp_status, std::string description, bool db_saved=false, int32 index=0);
+	void	RemoveQueuedQuestReward();
 	void	AddPendingQuestUpdate(int32 quest_id, int32 step_id, int32 progress = 0xFFFFFFFF);
 	void	AddPendingQuestUpdate(int32 quest_id, int32 step_id, int32 progress = 0xFFFFFFFF);
 	void	ProcessQuestUpdates();	
 	void	ProcessQuestUpdates();	
 	void	AddWaypoint(const char* waypoint_name, int8 waypoint_category, int32 spawn_id);
 	void	AddWaypoint(const char* waypoint_name, int8 waypoint_category, int32 spawn_id);
@@ -543,15 +558,18 @@ public:
 	bool	UseItem(Item* item, Spawn* target = nullptr);
 	bool	UseItem(Item* item, Spawn* target = nullptr);
 	
 	
 	void	SendPlayFlavor(Spawn* spawn, int8 language, VoiceOverStruct* non_garble, VoiceOverStruct* garble, bool success = false, bool garble_success = false);
 	void	SendPlayFlavor(Spawn* spawn, int8 language, VoiceOverStruct* non_garble, VoiceOverStruct* garble, bool success = false, bool garble_success = false);
+	void	SaveQuestRewardData(bool force_refresh = false);
+	void	UpdateCharacterRewardData(QuestRewardData* data);
+	void	SetQuestUpdateState(bool val) { quest_updates = val; }
 private:
 private:
 	void    SavePlayerImages();
 	void    SavePlayerImages();
 	void	SkillChanged(Skill* skill, int16 previous_value, int16 new_value);
 	void	SkillChanged(Skill* skill, int16 previous_value, int16 new_value);
-	void	GiveQuestReward(Quest* quest);
+	void	GiveQuestReward(Quest* quest, bool has_displayed = false);
 	void	SetStepComplete(int32 quest_id, int32 step);
 	void	SetStepComplete(int32 quest_id, int32 step);
 	void	AddStepProgress(int32 quest_id, int32 step, int32 progress);
 	void	AddStepProgress(int32 quest_id, int32 step, int32 progress);
 	map<int32, map<int32, int32> > quest_pending_updates;
 	map<int32, map<int32, int32> > quest_pending_updates;
 	vector<QueuedQuest*> quest_queue;
 	vector<QueuedQuest*> quest_queue;
-	vector<int32> quest_pending_reward;
+	vector<QuestRewardData> quest_pending_reward;
 	volatile bool	quest_updates;
 	volatile bool	quest_updates;
 	Mutex	MQuestPendingUpdates;
 	Mutex	MQuestPendingUpdates;
 	Mutex	MQuestQueue;
 	Mutex	MQuestQueue;