Browse Source

Multiple combat / functionality updates

Fix #277 - hp / power regen rewrite
Also added power_regen_override and hp_regen_override, when set to 1, you can LUA manually set hp_regen and power_regen to enforce it in code

RULE R_Spawn, ClassicRegen added.  Set to 1 means we do not have both regens when out of combat (eg. out of combat = out of combat regen + in combat regen).  In classic you only received in combat or out of combat regen individually.

Fix #278 - HatedBy now properly handled, we know when a player/spawn is being hated by other targets

Fix #275 - Parry/Riposte, Block and Dodge implemented.  Missing Block formula which will become its own issue.

Entity GetInfoStruct/SetInfoStruct, cur_avoidance, parry, parry_base, deflection, block are now floats.  Added sint16 power_regen adn hp_regen, lastly power_regen_override and hp_regen_override are int8's.

Fix #274 - Implemented stats Crushing, Defense, Deflection, Disruption, Fishing, Focus, Foresting, Gathering, Mining, Parry, Piercing, Safe Fall, Slashing and Trapping

/waypoint command now allows flushing waypoint if you do not have active target

/spawn details [x] supports behind, infront and flank

/craftitem added per EmemJr update

INSERT INTO commands SET TYPE=1,command='craftitem',subcommand='',HANDLER=526,required_status=100;

Crash fix for /add_aa hitting bad spell id

LUA Functions:

RemoveSpawnSpellBonus(spawn) - used in LUASpell script
GetSpell(spell_id, tier, custom_lua_script) - third argument added to setup custom script file

AddIconValue(spawn, value)
RemoveIconValue(spawn, value)

Fix #169 - evac now works correctly, no ghost spawn of self and you can go into combat and see damage taken/given.

Also simplified the player spawn / index map (had duplicates unneeded)

Fixed region_map_v1 throwing errors on special variable for signed vs unsigned
Image 3 years ago
parent
commit
6f92367102

+ 0 - 3
EQ2/source/WorldServer/Bots/Bot.cpp

@@ -687,9 +687,6 @@ void Bot::ChangeLevel(int16 old_level, int16 new_level) {
 	GetInfoStruct()->set_magic_base((int16)(new_level*1.5 + 10));
 	GetInfoStruct()->set_divine_base((int16)(new_level*1.5 + 10));
 	GetInfoStruct()->set_poison_base((int16)(new_level*1.5 + 10));
-
-	SetHPRegen((int)(new_level*.75) + (int)(new_level / 10) + 3);
-	SetPowerRegen(new_level + (int)(new_level / 10) + 4);
 	/*UpdateTimeStampFlag(LEVEL_UPDATE_FLAG);
 	GetPlayer()->SetCharSheetChanged(true);
 

+ 88 - 22
EQ2/source/WorldServer/Combat.cpp

@@ -582,7 +582,9 @@ bool Entity::SpellHeal(Spawn* target, float distance, LuaSpell* luaspell, string
 		if (heal_amt > 0){
 			//int32 base_roll = heal_amt;
 			//potency mod
+			MStats.lock();
 			heal_amt *= (stats[ITEM_STAT_POTENCY] / 100 + 1);
+			MStats.unlock();
 
 			//primary stat mod, insert forula here when done
 			//heal_amt += base_roll * (GetPrimaryStat()
@@ -682,12 +684,14 @@ bool Entity::SpellHeal(Spawn* target, float distance, LuaSpell* luaspell, string
 	if (target->IsEntity()) {
 		int32 hate_amt = heal_amt / 2;
 		set<int32>::iterator itr;
+		((Entity*)target)->MHatedBy.lock();
 		for (itr = ((Entity*)target)->HatedBy.begin(); itr != ((Entity*)target)->HatedBy.end(); itr++) {
 			Spawn* spawn = GetZone()->GetSpawnByID(*itr);
 			if (spawn && spawn->IsEntity()) {
 				((Entity*)spawn)->AddHate(this, hate_amt);
 			}
 		}
+		((Entity*)target)->MHatedBy.unlock();
 	}
 
 	return true;
@@ -702,14 +706,28 @@ int8 Entity::DetermineHit(Spawn* victim, int8 damage_type, float ToHitBonus, boo
 		return DAMAGE_PACKET_RESULT_INVULNERABLE;
 	}
 
-	if(!victim->IsEntity() || (!spell && BehindTarget(victim))) {
+	bool behind = false;
+	if(!victim->IsEntity() || (!spell && victim->GetAdventureClass() != BRAWLER && (behind = BehindTarget(victim)))) {
 		return DAMAGE_PACKET_RESULT_SUCCESSFUL;
 	}
 
 	float bonus = ToHitBonus;
 	Skill* skill = GetSkillByWeaponType(damage_type, true);
+
+	float skillAddedByWeapon = 0.0f;
+	if(skill)
+	{
+		int16 skillID = master_item_list.GetItemStatIDByName(skill->name.data);
+		if(skillID != 0xFFFFFFFF)
+		{
+			MStats.lock();
+			skillAddedByWeapon = stats[skillID];
+			MStats.unlock();
+		}
+	}
+	
 	if (skill)
-		bonus += skill->current_val / 25;
+		bonus += (skill->current_val+skillAddedByWeapon) / 25;
 	if (victim->IsEntity())
 		bonus -= ((Entity*)victim)->GetDamageTypeResistPercentage(damage_type);
 
@@ -725,38 +743,76 @@ int8 Entity::DetermineHit(Spawn* victim, int8 damage_type, float ToHitBonus, boo
 		if(skill)
 			roll_chance -= skill->current_val / 25;
 
-		skill = entity_victim->GetSkillByName("Defense", true);
-		if (skill)
-			chance -= skill->current_val / 25;
-
-		if(rand()%roll_chance >= (chance - entity_victim->GetAgi()/50)){
-			entity_victim->CheckProcs(PROC_TYPE_EVADE, this);
-			return DAMAGE_PACKET_RESULT_DODGE;//successfully dodged
-		}
-		if(rand() % roll_chance >= chance)
-			return DAMAGE_PACKET_RESULT_MISS; //successfully avoided
-
 		if(entity_victim->IsImmune(IMMUNITY_TYPE_RIPOSTE))
 		return DAMAGE_PACKET_RESULT_RIPOSTE;
 
+		// Avoidance Instructions: https://forums.daybreakgames.com/eq2/index.php?threads/avoidance-faq.482979/
+
+		/*Parry: reads as parry in the avoidance tooltip, increased by items with +parry on them
+		  Caps at 70%. For plate tanks, works in the front quadrant only, for brawlers this is 360 degrees.
+		  A small % of parries will be ripostes, which not only avoid the attack but also damage your attacker
+		*/
+
 		skill = entity_victim->GetSkillByName("Parry", true);
 		if(skill){
-			if(rand()%roll_chance >= (chance - 5 - skill->current_val/25)){ //successful parry
-				if(rand()%100 <= 20) {
-					entity_victim->CheckProcs(PROC_TYPE_RIPOSTE, this);
-					return DAMAGE_PACKET_RESULT_RIPOSTE;
+			float parryChance = entity_victim->GetInfoStruct()->get_parry();
+			float chanceValue = (100.0f - parryChance);
+
+			if(rand()%roll_chance >= chanceValue){ //successful parry
+				/* You may only riposte things in the front quadrant.
+				Riposte is based off of parry: a certain % of parries turn into ripostes.
+				*/
+				if(!behind && victim->InFrontSpawn((Spawn*)this, victim->GetX(), victim->GetZ())) { // if the attacker is not behind the victim, and the victim is facing the attacker (in front of spawn) then we can riposte
+					float riposteChanceValue = parryChance / 7.0f; //  Riposte is based off of parry: a certain % of parries turn into ripostes. Unknown what the value is divided by, 7 to make it 10% even.
+						if(rand()%100 <= riposteChanceValue) {
+							entity_victim->CheckProcs(PROC_TYPE_RIPOSTE, this);
+							return DAMAGE_PACKET_RESULT_RIPOSTE;
+						}
 				}
 				entity_victim->CheckProcs(PROC_TYPE_PARRY, this);
 				return DAMAGE_PACKET_RESULT_PARRY;
 			}
 		}
 
-		skill = entity_victim->GetSkillByName("Deflection", true);
-		if(skill){
-			if(rand()%100 >= (chance - skill->current_val/25)) { //successfully deflected
-				return DAMAGE_PACKET_RESULT_DEFLECT;
+		skill = nullptr;
+
+		
+		float blockChance = 0.0f;
+		if(victim->GetAdventureClass() == BRAWLER)
+			skill = entity_victim->GetSkillByName("Deflection", true);
+
+		blockChance = entity_victim->GetInfoStruct()->get_block();
+		
+		if(blockChance > 0.0f)
+		{
+			blockChance += (blockChance*(entity_victim->GetInfoStruct()->get_block_chance()/100.0f));
+			float chanceValue = (100.0f - blockChance);
+			/* Non-brawlers may only block things in the front quadrant.
+			Riposte is based off of parry: a certain % of parries turn into ripostes.
+			*/
+			float rnd = rand()%roll_chance;	
+			if(rnd >= chanceValue){ //successful block
+				if(victim->GetAdventureClass() == BRAWLER || (!behind && victim->InFrontSpawn((Spawn*)this, victim->GetX(), victim->GetZ()))) { // if the attacker is not behind the victim, and the victim is facing the attacker (in front of spawn) then we can block				
+					entity_victim->CheckProcs(PROC_TYPE_BLOCK, this);
+					return (victim->GetAdventureClass() == BRAWLER) ? DAMAGE_PACKET_RESULT_DEFLECT : DAMAGE_PACKET_RESULT_BLOCK;
+				}
 			}
 		}
+
+		skill = entity_victim->GetSkillByName("Defense", true);
+
+		float dodgeChance = entity_victim->GetInfoStruct()->get_avoidance_base();
+		if(dodgeChance > 0.0f)
+		{
+			float chanceValue = (100.0f - dodgeChance);
+			float rnd = rand()%roll_chance;	
+			if(rnd >= chanceValue){ //successful dodge
+				entity_victim->CheckProcs(PROC_TYPE_EVADE, this);
+				return DAMAGE_PACKET_RESULT_DODGE;//successfully dodged
+			}
+		}
+		if(rand() % roll_chance >= chance)
+			return DAMAGE_PACKET_RESULT_MISS; //successfully avoided
 	}
 	else{
 		skill = entity_victim->GetSkillByName("Spell Avoidance", true);
@@ -873,7 +929,9 @@ bool Entity::DamageSpawn(Entity* victim, int8 type, int8 damage_type, int32 low_
 		//Potency and ability mod is only applied to spells/CAs
 		else { 
 			// Potency mod
+			MStats.lock();
 			damage *= ((stats[ITEM_STAT_POTENCY] / 100) + 1);
+			MStats.unlock();
 
 			// Ability mod can only give up to half of damage after potency
 			int32 mod = (int32)min(info_struct.get_ability_modifier(), (float)(damage / 2));
@@ -888,7 +946,9 @@ bool Entity::DamageSpawn(Entity* victim, int8 type, int8 damage_type, int32 low_
 
 			// Crit Roll
 			else {
+				victim->MStats.lock();
 				float chance = max((float)0, (info_struct.get_crit_chance() - victim->stats[ITEM_STAT_CRITAVOIDANCE]));
+				victim->MStats.unlock();
 				if (MakeRandomFloat(0, 100) <= chance)
 					crit = true;
 			}
@@ -1052,8 +1112,14 @@ bool Entity::CheckInterruptSpell(Entity* attacker) {
 	//modified to 50% and added global rule, 30% was too small at starting levels
 	int8 percent = rule_manager.GetGlobalRule(R_Spells, NoInterruptBaseChance)->GetInt32();
 	Skill* skill = GetSkillByName("Focus", true);
+
+	float focusSkillPts = 0.0f;
+	MStats.lock();
+	focusSkillPts = stats[ITEM_STAT_FOCUS];
+	MStats.unlock();
+
 	if(skill)
-		percent += ((skill->current_val + 1)/6);
+		percent += ((skill->current_val + 1 + focusSkillPts)/6);
 	if(MakeRandomInt(1, 100) > percent) {
 		LogWrite(COMBAT__DEBUG, 0, "Combat", "'%s' interrupted spell for '%s': %i%%", attacker->GetName(), GetName(), percent);
 		GetZone()->Interrupted(this, attacker, SPELL_ERROR_INTERRUPTED);

+ 53 - 9
EQ2/source/WorldServer/Commands/Commands.cpp

@@ -318,7 +318,7 @@ bool Commands::SetSpawnCommand(Client* client, Spawn* target, int8 type, const c
 										   }
 			case SPAWN_SET_VALUE_FACIAL_HAIR_TYPE:{
 				if(target->IsEntity()){
-					sprintf(tmp, "%i", ((Entity*)target)->GetHairType());
+					sprintf(tmp, "%i", ((Entity*)target)->GetFacialHairType());
 					((Entity*)target)->SetFacialHairType(val, send_update);
 				}
 				break;
@@ -1638,6 +1638,7 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 		}
 		case COMMAND_WAYPOINT: {
 			bool success = false;
+			client->ClearWaypoint();
 			if (sep && sep->IsNumber(0) && sep->IsNumber(1) && sep->IsNumber(2)) {
 				if (!client->ShowPathToTarget(atof(sep->arg[0]), atof(sep->arg[1]), atof(sep->arg[2]), 0))
 					client->Message(CHANNEL_COLOR_RED, "Invalid coordinates given");
@@ -1647,7 +1648,6 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 					client->Message(CHANNEL_COLOR_RED, "Invalid coordinates given");
 			}
 			else {
-				client->ClearWaypoint();
 				client->Message(CHANNEL_COLOR_YELLOW, "Usage: /waypoint x y z");
 			}
 			break;
@@ -3123,16 +3123,13 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 			if(type == 0){
 				if(incombat)
 					client->SimpleMessage(CHANNEL_GENERAL_COMBAT, "You stop fighting.");
-				player->InCombat(false);
-				player->InCombat(false, true);
-				player->SetRangeAttack(false);
+					player->StopCombat(type);
 			}
 			else {
 				if(type == 2){
 					player->InCombat(false);
 					if(incombat && player->GetRangeAttack()){
-						player->SetRangeAttack(false);
-						player->InCombat(false, true);
+						player->StopCombat(type);
 						client->SimpleMessage(CHANNEL_GENERAL_COMBAT, "You stop fighting.");
 					}
 					else{
@@ -3705,12 +3702,22 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 						}
 						break;
 					}
-					else if (ToLower(string(sep->arg[0])) == "pathto")
+					else if (ToLower(string(sep->arg[0])) == "behind")
 					{
+						bool isBehind = client->GetPlayer()->BehindTarget(cmdTarget);
+						client->Message(CHANNEL_COLOR_YELLOW, "%s %s.", isBehind ? "YOU are behind" : "YOU are NOT behind", cmdTarget->GetName());
 						break;
 					}
-					else if (ToLower(string(sep->arg[0])) == "pathfrom")
+					else if (ToLower(string(sep->arg[0])) == "infront")
 					{
+						bool isBehind = client->GetPlayer()->InFrontSpawn(cmdTarget, client->GetPlayer()->GetX(), client->GetPlayer()->GetZ());
+						client->Message(CHANNEL_COLOR_YELLOW, "%s %s.", isBehind ? "YOU are infront of" : "YOU are NOT infront of", cmdTarget->GetName());
+						break;
+					}
+					else if (ToLower(string(sep->arg[0])) == "flank")
+					{
+						bool isFlanking = client->GetPlayer()->FlankingTarget(cmdTarget);
+						client->Message(CHANNEL_COLOR_YELLOW, "%s is %s.", isFlanking ? "YOU are flanking" : "YOU are NOT flanking", cmdTarget->GetName());
 						break;
 					}
 				}
@@ -4571,6 +4578,39 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 		case COMMAND_TARGETITEM			: { Command_TargetItem(client, sep); break; }
 		case COMMAND_FINDSPAWN: { Command_FindSpawn(client, sep); break; }
 		case COMMAND_MOVECHARACTER: { Command_MoveCharacter(client, sep); break; }
+		case COMMAND_CRAFTITEM: {
+					Item* item = 0;
+					if (sep && sep->IsNumber(0)) {
+						int32 item_id = atol(sep->arg[0]);
+						int32 quantity = 1;
+
+						if (sep->arg[1] && sep->IsNumber(1))
+							quantity = atoi(sep->arg[1]);
+						item = new Item(master_item_list.GetItem(item_id));
+						if (!item) {
+							LogWrite(TRADESKILL__ERROR, 0, "CraftItem", "Item (%u) not found.", item_id);
+						}
+						else {
+							item->details.count = quantity;
+							// use CHANNEL_COLOR_CHAT_RELATIONSHIP as that is the same value (4) as it is in a log for this message
+							client->Message(CHANNEL_COLOR_CHAT_RELATIONSHIP, "You created %s.", item->CreateItemLink(client->GetVersion()).c_str());
+							client->AddItem(item);
+							//Check for crafting quest updates
+							int8 update_amt = 0;
+							if (item->stack_count > 1)
+								update_amt = 1;
+							else
+								update_amt = quantity;
+							client->GetPlayer()->CheckQuestsCraftUpdate(item, update_amt);
+						}
+
+					}
+					
+					else {
+						client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /craftitem {item_id} [quantity] ");
+						}
+					break;
+				}
 
 
 		default: 
@@ -10380,6 +10420,10 @@ void Commands::Add_AA(Client* client, Seperator* sep) {
 		spell_tier = client->GetPlayer()->GetSpellTier(spell_id);
 		AltAdvanceData* data = master_aa_list.GetAltAdvancement(spell_id);
 		// addspellbookentry here
+		if(!data) {
+		LogWrite(COMMAND__ERROR, 0, "Command", "Error in Add_AA no data for spell_id %u spell_tier %u", spell_id, spell_tier);
+			return;
+		}
 		if (spell_tier >= data->maxRank) {
 		LogWrite(COMMAND__ERROR, 0, "Command", "Error in Add_AA spell_tier %u >= maxRank %u", spell_tier, data->maxRank);
 			return;

+ 3 - 0
EQ2/source/WorldServer/Commands/Commands.h

@@ -872,6 +872,9 @@ private:
 
 #define COMMAND_MOVECHARACTER			525
 
+#define COMMAND_CRAFTITEM                526
+
+
 #define GET_AA_XML						750
 #define ADD_AA							751
 #define COMMIT_AA_PROFILE				752				

+ 237 - 91
EQ2/source/WorldServer/Entity.cpp

@@ -28,10 +28,14 @@
 #include "classes.h"
 #include "LuaInterface.h"
 #include "ClientPacketFunctions.h"
+#include "Skills.h"
+#include "Rules/Rules.h"
 
 extern World world;
 extern MasterItemList master_item_list;
 extern MasterSpellList master_spell_list;
+extern MasterSkillList master_skill_list;
+extern RuleManager rule_manager;
 extern Classes classes;
 
 Entity::Entity(){
@@ -77,7 +81,6 @@ Entity::Entity(){
 	MCommandMutex.SetName("Entity::MCommandMutex");
 	hasSeeInvisSpell = false;
 	hasSeeHideSpell = false;
-
 }
 
 Entity::~Entity(){
@@ -118,15 +121,15 @@ void Entity::MapInfoStruct()
 	get_int16_funcs["max_mitigation"] = l::bind(&InfoStruct::get_max_mitigation, &info_struct);
 	get_int16_funcs["mitigation_base"] = l::bind(&InfoStruct::get_mitigation_base, &info_struct);
 	get_int16_funcs["avoidance_display"] = l::bind(&InfoStruct::get_avoidance_display, &info_struct);
-	get_int16_funcs["cur_avoidance"] = l::bind(&InfoStruct::get_cur_avoidance, &info_struct);
+	get_float_funcs["cur_avoidance"] = l::bind(&InfoStruct::get_cur_avoidance, &info_struct);
 	get_int16_funcs["base_avoidance_pct"] = l::bind(&InfoStruct::get_base_avoidance_pct, &info_struct);
 	get_int16_funcs["avoidance_base"] = l::bind(&InfoStruct::get_avoidance_base, &info_struct);
 	get_int16_funcs["max_avoidance"] = l::bind(&InfoStruct::get_max_avoidance, &info_struct);
-	get_int16_funcs["parry"] = l::bind(&InfoStruct::get_parry, &info_struct);
-	get_int16_funcs["parry_base"] = l::bind(&InfoStruct::get_parry_base, &info_struct);
-	get_int16_funcs["deflection"] = l::bind(&InfoStruct::get_deflection, &info_struct);
+	get_float_funcs["parry"] = l::bind(&InfoStruct::get_parry, &info_struct);
+	get_float_funcs["parry_base"] = l::bind(&InfoStruct::get_parry_base, &info_struct);
+	get_float_funcs["deflection"] = l::bind(&InfoStruct::get_deflection, &info_struct);
 	get_int16_funcs["deflection_base"] = l::bind(&InfoStruct::get_deflection_base, &info_struct);
-	get_int16_funcs["block"] = l::bind(&InfoStruct::get_block, &info_struct);
+	get_float_funcs["block"] = l::bind(&InfoStruct::get_block, &info_struct);
 	get_int16_funcs["block_base"] = l::bind(&InfoStruct::get_block_base, &info_struct);
 	get_float_funcs["str"] = l::bind(&InfoStruct::get_str, &info_struct);
 	get_float_funcs["sta"] = l::bind(&InfoStruct::get_sta, &info_struct);
@@ -231,6 +234,12 @@ void Entity::MapInfoStruct()
 	get_string_funcs["biography"] = l::bind(&InfoStruct::get_biography, &info_struct);
 	get_float_funcs["drunk"] = l::bind(&InfoStruct::get_drunk, &info_struct);
 
+	get_sint16_funcs["power_regen"] = l::bind(&InfoStruct::get_power_regen, &info_struct);
+	get_sint16_funcs["hp_regen"] = l::bind(&InfoStruct::get_hp_regen, &info_struct);
+
+	get_int8_funcs["power_regen_override"] = l::bind(&InfoStruct::get_power_regen_override, &info_struct);
+	get_int8_funcs["hp_regen_override"] = l::bind(&InfoStruct::get_hp_regen_override, &info_struct);
+
 
 /** SETS **/
 	set_string_funcs["name"] = l::bind(&InfoStruct::set_name, &info_struct, l::_1);
@@ -252,15 +261,15 @@ void Entity::MapInfoStruct()
 	set_int16_funcs["max_mitigation"] = l::bind(&InfoStruct::set_max_mitigation, &info_struct, l::_1);
 	set_int16_funcs["mitigation_base"] = l::bind(&InfoStruct::set_mitigation_base, &info_struct, l::_1);
 	set_int16_funcs["avoidance_display"] = l::bind(&InfoStruct::set_avoidance_display, &info_struct, l::_1);
-	set_int16_funcs["cur_avoidance"] = l::bind(&InfoStruct::set_cur_avoidance, &info_struct, l::_1);
+	set_float_funcs["cur_avoidance"] = l::bind(&InfoStruct::set_cur_avoidance, &info_struct, l::_1);
 	set_int16_funcs["base_avoidance_pct"] = l::bind(&InfoStruct::set_base_avoidance_pct, &info_struct, l::_1);
 	set_int16_funcs["avoidance_base"] = l::bind(&InfoStruct::set_avoidance_base, &info_struct, l::_1);
 	set_int16_funcs["max_avoidance"] = l::bind(&InfoStruct::set_max_avoidance, &info_struct, l::_1);
-	set_int16_funcs["parry"] = l::bind(&InfoStruct::set_parry, &info_struct, l::_1);
-	set_int16_funcs["parry_base"] = l::bind(&InfoStruct::set_parry_base, &info_struct, l::_1);
-	set_int16_funcs["deflection"] = l::bind(&InfoStruct::set_deflection, &info_struct, l::_1);
+	set_float_funcs["parry"] = l::bind(&InfoStruct::set_parry, &info_struct, l::_1);
+	set_float_funcs["parry_base"] = l::bind(&InfoStruct::set_parry_base, &info_struct, l::_1);
+	set_float_funcs["deflection"] = l::bind(&InfoStruct::set_deflection, &info_struct, l::_1);
 	set_int16_funcs["deflection_base"] = l::bind(&InfoStruct::set_deflection_base, &info_struct, l::_1);
-	set_int16_funcs["block"] = l::bind(&InfoStruct::set_block, &info_struct, l::_1);
+	set_float_funcs["block"] = l::bind(&InfoStruct::set_block, &info_struct, l::_1);
 	set_int16_funcs["block_base"] = l::bind(&InfoStruct::set_block_base, &info_struct, l::_1);
 	set_float_funcs["str"] = l::bind(&InfoStruct::set_str, &info_struct, l::_1);
 	set_float_funcs["sta"] = l::bind(&InfoStruct::set_sta, &info_struct, l::_1);
@@ -365,6 +374,12 @@ void Entity::MapInfoStruct()
 	set_string_funcs["biography"] = l::bind(&InfoStruct::set_biography, &info_struct, l::_1);
 	set_float_funcs["drunk"] = l::bind(&InfoStruct::set_drunk, &info_struct, l::_1);
 
+	set_sint16_funcs["power_regen"] = l::bind(&InfoStruct::set_power_regen, &info_struct, l::_1);
+	set_sint16_funcs["hp_region"] = l::bind(&InfoStruct::set_hp_regen, &info_struct, l::_1);
+
+	set_int8_funcs["power_regen_override"] = l::bind(&InfoStruct::set_power_regen_override, &info_struct, l::_1);
+	set_int8_funcs["hp_region_override"] = l::bind(&InfoStruct::set_hp_regen_override, &info_struct, l::_1);
+
 }
 
 bool Entity::HasMoved(bool include_heading){
@@ -667,55 +682,11 @@ int8 Entity::GetWieldType(){
 }
 
 bool Entity::BehindTarget(Spawn* target){
-	float target_angle = 360 - target->GetHeading();	
-	float angle = 360 - GetHeading();
-	if(target_angle > angle)
-		angle = target_angle - angle;
-	else
-		angle -= target_angle;
-	return (angle < 90 || angle > 270);
+	return BehindSpawn(target, GetX(), GetZ());
 }
 
 bool Entity::FlankingTarget(Spawn* target) {
-	float angle;
-	double diff_x = target->GetX() - GetX();
-	double diff_z = target->GetZ() - GetZ();
-	if (diff_z == 0) {
-	   if (diff_x > 0)
-		   angle = 90;
-	   else
-		   angle = 270;
-	}
-	else
-		angle = ((atan(diff_x / diff_z)) * 180) / 3.14159265358979323846;
-	if (angle < 0)
-		angle = angle + 360;
-	else
-		angle = angle + 180;
-	if (diff_x < 0)
-		angle = angle + 180;
-	
-	if (angle > GetHeading())
-		angle = angle - GetHeading();
-	else
-		angle = GetHeading() - angle;
-
-	if (angle > 360)
-		angle -= 360;
-
-	//LogWrite(SPAWN__ERROR, 0, "Angle", "spawn heading = %f", GetHeading());
-	//LogWrite(SPAWN__ERROR, 0, "Angle", "angle = %f", angle);
-
-	return (angle >= 45 && angle <= 315);
-}
-
-float Entity::GetShieldBlockChance(){
-	float ret = 0;
-	Item* item = equipment_list.GetItem(1);
-	if(item && item->details.item_id > 0 && item->IsShield()){
-	
-	}
-	return ret;
+	return IsFlankingSpawn(target, GetX(), GetZ());
 }
 
 float Entity::GetDodgeChance(){
@@ -729,20 +700,14 @@ bool Entity::EngagedInCombat(){
 }
 
 void Entity::InCombat(bool val){
+	bool changeCombatState = false;
+	if((in_combat && !val) || (!in_combat && val))
+		changeCombatState = true;
+
 	in_combat = val;
-}
 
-void Entity::SetHPRegen(int16 new_val){
-	regen_hp_rate = new_val;
-}
-void Entity::SetPowerRegen(int16 new_val){
-	regen_power_rate = new_val;
-}
-int16 Entity::GetHPRegen(){
-	return regen_hp_rate;
-}
-int16 Entity::GetPowerRegen(){
-	return regen_power_rate;
+	if(changeCombatState)
+		SetRegenValues(GetInfoStruct()->get_effective_level());
 }
 
 void Entity::DoRegenUpdate(){
@@ -751,19 +716,9 @@ void Entity::DoRegenUpdate(){
 	sint32 hp = GetHP();
 	sint32 power = GetPower();
 
-	int16 effective_level = GetInfoStruct()->get_effective_level();
-	if(!effective_level)
-		effective_level = GetLevel();
-
-	// No regen for NPC's while in combat
-	// Temp solution for now
-	if (IsNPC() && EngagedInCombat())
-		return;
-
 	if(hp < GetTotalHP()){
-		if(regen_hp_rate == 0)
-			regen_hp_rate = (int)(effective_level*.75)+(int)(effective_level/10) + 1;
-		int16 temp = regen_hp_rate + stats[ITEM_STAT_HPREGEN];
+		sint16 temp = GetInfoStruct()->get_hp_regen();
+
 		if((hp + temp) > GetTotalHP())
 			SetHP(GetTotalHP());
 		else
@@ -773,16 +728,14 @@ void Entity::DoRegenUpdate(){
 
 	}
 	if(GetPower() < GetTotalPower()){
-		if(regen_power_rate == 0)
-			regen_power_rate = effective_level + (int)(effective_level/10) + 1;
-		cout << "regen_power_rate: " << regen_power_rate << endl;
-		if((power + regen_power_rate) > GetTotalPower())
+		sint16 temp = GetInfoStruct()->get_power_regen();
+		
+		if((power + temp) > GetTotalPower())
 			SetPower(GetTotalPower());
 		else
-			SetPower(power + regen_power_rate);
+			SetPower(power + temp);
 
 		LogWrite(MISC__TODO, 1, "TODO", "Fix this later for mobs\n\t(%s, function: %s, line #: %i)", __FILE__, __FUNCTION__, __LINE__);
-
 	}
 }
 
@@ -979,8 +932,39 @@ void Entity::SetMaxSpeed(float val){
 	max_speed = val;
 }
 
+float Entity::CalculateSkillStatChance(char* skillName, int16 item_stat, float max_cap, float modifier, bool add_to_skill)
+{
+	float skillAndItemsChance = 0.0f;
+
+	Skill* skill = GetSkillByName(skillName, false);
+	if(skill){
+		MStats.lock();
+		float item_chance_or_skill = stats[item_stat];
+		MStats.unlock();
+		if(add_to_skill)
+		{
+			skillAndItemsChance = (((float)skill->current_val+item_chance_or_skill)/10.0f); // do we know 25 is accurate?  10 gives more 'skill' space, most cap at 70% with items
+		}
+		else
+		{
+			skillAndItemsChance = ((float)skill->current_val/10.0f); // do we know 25 is accurate?  10 gives more 'skill' space, most cap at 70% with items
+
+			// take chance percentage and add the item stats % (+1 = 1% or .01f)
+			skillAndItemsChance += (skillAndItemsChance*((item_chance_or_skill + modifier)/100.0f));
+		}
+	}
+
+	if ( max_cap > 0.0f && skillAndItemsChance > max_cap )
+		skillAndItemsChance = max_cap;
+
+	return skillAndItemsChance;
+}
+
 void Entity::CalculateBonuses(){
 	InfoStruct* info = &info_struct;
+
+	int16 effective_level = info->get_effective_level() != 0 ? info->get_effective_level() : GetLevel();
+
 	info->set_block(info->get_block_base());
 	
 	info->set_cur_attack(info->get_attack_base());
@@ -989,10 +973,6 @@ void Entity::CalculateBonuses(){
 
 	LogWrite(MISC__TODO, 1, "TODO", "Calculate via current spells\n\t(%s, function: %s, line #: %i)", __FILE__, __FUNCTION__, __LINE__);
 
-	//info->cur_concentration = 0;
-	info->set_parry(info->get_parry_base());
-	info->set_deflection(info->get_deflection_base());
-
 	info->set_disease(info->get_disease_base());
 	info->set_divine(info->get_divine_base());
 	info->set_heat(info->get_heat_base());
@@ -1102,9 +1082,173 @@ void Entity::CalculateBonuses(){
 	info->add_uncontested_parry(values->uncontested_parry);
 	info->add_uncontested_dodge(values->uncontested_dodge);
 	info->add_uncontested_riposte(values->uncontested_riposte);
+
+	
+	float full_pct_hit = 100.0f;
+
+	//info->cur_concentration = 0;
+	MStats.lock();
+	float parryStat = stats[ITEM_STAT_PARRY];
+	MStats.unlock();
+	float parry_pct = CalculateSkillStatChance("Parry", ITEM_STAT_PARRYCHANCE, 70.0f, parryStat);
+	parry_pct += parry_pct * (info->get_cur_avoidance()/100.0f);
+	if(parry_pct > 70.0f)
+		parry_pct = 70.0f;
+
+	info->set_parry(parry_pct);
+
+	full_pct_hit -= parry_pct;
+	
+	float block_pct = 0.0f;
+
+	if(GetAdventureClass() != BRAWLER)
+	{
+		Item* item = equipment_list.GetItem(EQ2_SECONDARY_SLOT);
+		if(item && item->details.item_id > 0 && item->IsShield()){
+			// if high is set and greater than low use high, otherwise use low
+			int16 mitigation = item->armor_info->mitigation_high > item->armor_info->mitigation_low ? item->armor_info->mitigation_high : item->armor_info->mitigation_low;
+			// we frankly don't know the formula for Block, only that it uses the 'Protection' of the shield, which is the mitigation_low/mitigation_high in the armor_info
+			if(mitigation)
+			{
+				/*DOF Prima Guide: Shields now have the following base chances
+				to block: Tower (10%), Kite (10%), Round
+				(5%), Buckler (3%). Your chances to block
+				scale up or down based on the con of your
+				opponent.*/
+				Skill* skill = master_skill_list.GetSkill(item->generic_info.skill_req1);
+				float baseBlock = 0.0f;
+				if(skill)
+				{
+					if(skill->short_name.data == "towershield" || skill->short_name.data == "kiteshield")
+						baseBlock = 10.0f;
+					else if (skill->short_name.data == "roundshield")
+						baseBlock = 5.0f;
+					else if (skill->short_name.data == "buckler") 
+						baseBlock = 3.0f;
+				}
+				if(GetLevel() > mitigation)
+					block_pct = log10f((float)mitigation/((float)GetLevel()*10.0f));
+				else
+					block_pct = log10f(((float)GetLevel()/(float)mitigation)) * log10f(GetLevel()) * 2.0f;
+				
+				if(block_pct < 0.0f)
+					block_pct *= -1.0f;
+
+				block_pct += baseBlock;
+
+				block_pct += block_pct * (info->get_cur_avoidance()/100.0f);
+				if(block_pct > 70.0f)
+					block_pct = 70.0f;
+			}
+		}
+	}
+	else
+	{
+		//info->cur_concentration = 0;
+		MStats.lock();
+		float deflectionStat = stats[ITEM_STAT_DEFLECTION];
+		MStats.unlock();
+		block_pct = CalculateSkillStatChance("Deflection", ITEM_STAT_MINIMUMDEFLECTIONCHANCE, 70.0f, deflectionStat+1.0f);
+		block_pct += block_pct * (info->get_cur_avoidance()/100.0f);
+	}
+
+	float block_actual = 0.0f;
+	if(full_pct_hit > 0.0f)
+		block_actual = block_pct * (full_pct_hit / 100.0f);
+
+	info->set_block(block_actual);
+	full_pct_hit -= block_actual;
+
+
+	//info->cur_concentration = 0;
+	MStats.lock();
+	float defenseStat = stats[ITEM_STAT_DEFENSE];
+	MStats.unlock();
+	
+	float dodge_pct = CalculateSkillStatChance("Defense", ITEM_STAT_DODGECHANCE, 100.0f, defenseStat);
+	dodge_pct += dodge_pct * (info->get_cur_avoidance()/100.0f);
+
+	float dodge_actual = 0.0f;
+	if(full_pct_hit > 0.0f)
+		dodge_actual = dodge_pct * (full_pct_hit / 100.0f) + (log10f(GetLevel() * GetAgi()) / 100.0f);
+
+	info->set_avoidance_base(dodge_actual);
+
+	float total_avoidance = parry_pct + block_actual + dodge_actual;
+	info->set_avoidance_display(total_avoidance);
+
+	SetRegenValues(effective_level);
+
 	safe_delete(values);
 }
 
+void Entity::SetRegenValues(int16 effective_level)
+{
+	bool classicRegen = rule_manager.GetGlobalRule(R_Spawn, ClassicRegen)->GetBool();
+	if(!GetInfoStruct()->get_hp_regen_override())
+	{
+		sint16 regen_hp_rate = 0;
+		sint16 temp = 0;
+
+		MStats.lock();
+		if(!IsAggroed())
+		{
+			if(classicRegen)
+			{
+				// classic regen only gives OUT OF COMBAT, doesn't combine in+out of combat
+				regen_hp_rate = (int)(effective_level*.75)+1;
+				temp = regen_hp_rate + stats[ITEM_STAT_HPREGEN];
+				temp += stats[ITEM_STAT_HPREGENPPT];
+			}
+			else
+			{
+				regen_hp_rate = (int)(effective_level*.75)+(int)(effective_level/10) + 1;
+				temp = regen_hp_rate + stats[ITEM_STAT_HPREGEN];
+				temp += stats[ITEM_STAT_HPREGENPPT] + stats[ITEM_STAT_COMBATHPREGENPPT];
+			}
+		}
+		else
+		{
+			regen_hp_rate = (sint16)(effective_level / 10) + 1;
+			temp = regen_hp_rate + stats[ITEM_STAT_COMBATHPREGENPPT];
+		}
+		MStats.unlock();
+
+		GetInfoStruct()->set_hp_regen(temp);
+	}
+
+	if(!GetInfoStruct()->get_power_regen_override())
+	{
+		sint16 regen_power_rate = 0;
+		sint16 temp = 0;
+
+		MStats.lock();
+		if(!IsAggroed())
+		{
+			if(classicRegen)
+			{				
+				regen_power_rate = effective_level + 1;
+				temp = regen_power_rate + stats[ITEM_STAT_MANAREGEN];
+				temp += stats[ITEM_STAT_MPREGENPPT];
+			}
+			else
+			{
+				regen_power_rate = effective_level + (int)(effective_level/10) + 1;
+				temp = regen_power_rate + stats[ITEM_STAT_MANAREGEN];
+				temp += stats[ITEM_STAT_MPREGENPPT] + stats[ITEM_STAT_COMBATMPREGENPPT];
+			}
+		}
+		else
+		{
+			regen_power_rate = (sint16)(effective_level / 10) + 1;
+			temp = regen_power_rate + stats[ITEM_STAT_COMBATMPREGENPPT];
+		}
+		MStats.unlock();
+
+		GetInfoStruct()->set_power_regen(temp);
+	}
+}
+
 EquipmentItemList* Entity::GetEquipmentList(){
 	return &equipment_list;
 }
@@ -1664,6 +1808,7 @@ float Entity::GetSpeed() {
 	if (EngagedInCombat() && GetMaxSpeed() > 0.0f)
 		ret = GetMaxSpeed();
 
+	MStats.lock();
 	if ((IsStealthed() || IsInvis()) && stats.count(ITEM_STAT_STEALTHINVISSPEEDMOD))
 		ret += stats[ITEM_STAT_STEALTHINVISSPEEDMOD];
 	else if (EngagedInCombat() && stats.count(ITEM_STAT_OFFENSIVESPEED))
@@ -1674,6 +1819,7 @@ float Entity::GetSpeed() {
 		ret += stats[ITEM_STAT_SPEED];
 	else if (stats.count(ITEM_STAT_MOUNTSPEED))
 		ret += stats[ITEM_STAT_MOUNTSPEED];
+	MStats.unlock();
 	
 	ret *= speed_multiplier;
 	return ret;

+ 59 - 21
EQ2/source/WorldServer/Entity.h

@@ -133,12 +133,12 @@ struct InfoStruct{
 		max_mitigation_ = 0;
 		mitigation_base_ = 0;
 		avoidance_display_ = 0;
-		cur_avoidance_ = 0;
+		cur_avoidance_ = 0.0f;
 		base_avoidance_pct_ = 0;
 		avoidance_base_ = 0;
 		max_avoidance_ = 0;
-		parry_ = 0;
-		parry_base_ = 0;
+		parry_ = 0.0f;
+		parry_base_ = 0.0f;
 		deflection_ = 0;
 		deflection_base_ = 0;
 		block_ = 0;
@@ -246,7 +246,11 @@ struct InfoStruct{
 		breathe_underwater_ = 0;
 		biography_ = std::string("");
 		drunk_ = 0;
+		power_regen_ = 0;
+		hp_regen_ = 0;
 
+		power_regen_override_ = 0;
+		hp_regen_override_ = 0;
 	}
 
 
@@ -388,7 +392,11 @@ struct InfoStruct{
 		breathe_underwater_ = oldStruct->get_breathe_underwater();
 		biography_ = std::string(oldStruct->get_biography());
 		drunk_ = oldStruct->get_drunk();
+		power_regen_ = oldStruct->get_power_regen();
+		hp_regen_ = oldStruct->get_hp_regen();
 
+		power_regen_override_ = oldStruct->get_power_regen_override();
+		hp_regen_override_ = oldStruct->get_hp_regen_override();
 	}
 
 	//mutable std::shared_mutex mutex_;
@@ -413,19 +421,19 @@ struct InfoStruct{
 
 	int16	 get_mitigation_base() { std::lock_guard<std::mutex> lk(classMutex); return mitigation_base_; }
 	int16	 get_avoidance_display() { std::lock_guard<std::mutex> lk(classMutex); return avoidance_display_; }
-	int16	 get_cur_avoidance() { std::lock_guard<std::mutex> lk(classMutex); return cur_avoidance_; }
+	float	 get_cur_avoidance() { std::lock_guard<std::mutex> lk(classMutex); return cur_avoidance_; }
 	int16	 get_base_avoidance_pct() { std::lock_guard<std::mutex> lk(classMutex); return base_avoidance_pct_; }
 	int16	 get_avoidance_base() { std::lock_guard<std::mutex> lk(classMutex); return avoidance_base_; }
 
-	int16	 get_parry() { std::lock_guard<std::mutex> lk(classMutex); return parry_; }
-	int16	 get_parry_base() { std::lock_guard<std::mutex> lk(classMutex); return parry_base_; }
+	float	 get_parry() { std::lock_guard<std::mutex> lk(classMutex); return parry_; }
+	float	 get_parry_base() { std::lock_guard<std::mutex> lk(classMutex); return parry_base_; }
 	
 	int16	 get_max_avoidance() { std::lock_guard<std::mutex> lk(classMutex); return max_avoidance_; }
 
-	int16	 get_deflection() { std::lock_guard<std::mutex> lk(classMutex); return deflection_; }
+	float	 get_deflection() { std::lock_guard<std::mutex> lk(classMutex); return deflection_; }
 	int16	 get_deflection_base() { std::lock_guard<std::mutex> lk(classMutex); return deflection_base_; }
 
-	int16	 get_block() { std::lock_guard<std::mutex> lk(classMutex); return block_; }
+	float	 get_block() { std::lock_guard<std::mutex> lk(classMutex); return block_; }
 	int16	 get_block_base() { std::lock_guard<std::mutex> lk(classMutex); return block_base_; }
 
 	float	 get_str() { std::lock_guard<std::mutex> lk(classMutex); return str_; }
@@ -540,6 +548,12 @@ struct InfoStruct{
 	std::string get_biography() { std::lock_guard<std::mutex> lk(classMutex); return biography_; }
 	float	 get_drunk() { std::lock_guard<std::mutex> lk(classMutex); return drunk_; }
 
+	sint16	 get_power_regen() { std::lock_guard<std::mutex> lk(classMutex); return power_regen_; }
+	sint16	 get_hp_regen() { std::lock_guard<std::mutex> lk(classMutex); return hp_regen_; }
+
+	int8	 get_power_regen_override() { std::lock_guard<std::mutex> lk(classMutex); return power_regen_override_; }
+	int8	 get_hp_regen_override() { std::lock_guard<std::mutex> lk(classMutex); return hp_regen_override_; }
+
 	void	set_name(std::string value) { std::lock_guard<std::mutex> lk(classMutex); name_ = value; }
 	
 	void	set_deity(std::string value) { std::lock_guard<std::mutex> lk(classMutex); deity_ = value; }
@@ -568,15 +582,15 @@ struct InfoStruct{
 	void	add_mitigation_base(int16 value) { std::lock_guard<std::mutex> lk(classMutex); mitigation_base_ += value; }
 
 	void	set_avoidance_display(int16 value) { std::lock_guard<std::mutex> lk(classMutex); avoidance_display_ = value; }
-	void	set_cur_avoidance(int16 value) { std::lock_guard<std::mutex> lk(classMutex); cur_avoidance_ = value; }
+	void	set_cur_avoidance(float value) { std::lock_guard<std::mutex> lk(classMutex); cur_avoidance_ = value; }
 	void	set_base_avoidance_pct(int16 value) { std::lock_guard<std::mutex> lk(classMutex); base_avoidance_pct_ = value; }
 	void	set_avoidance_base(int16 value) { std::lock_guard<std::mutex> lk(classMutex); avoidance_base_ = value; }
 	void	set_max_avoidance(int16 value) { std::lock_guard<std::mutex> lk(classMutex); max_avoidance_ = value; }
-	void	set_parry(int16 value) { std::lock_guard<std::mutex> lk(classMutex); parry_ = value; }
-	void	set_parry_base(int16 value) { std::lock_guard<std::mutex> lk(classMutex); parry_base_ = value; }
+	void	set_parry(float value) { std::lock_guard<std::mutex> lk(classMutex); parry_ = value; }
+	void	set_parry_base(float value) { std::lock_guard<std::mutex> lk(classMutex); parry_base_ = value; }
 	void	set_deflection(int16 value) { std::lock_guard<std::mutex> lk(classMutex); deflection_ = value; }
-	void	set_deflection_base(int16 value) { std::lock_guard<std::mutex> lk(classMutex); deflection_base_ = value; }
-	void	set_block(int16 value) { std::lock_guard<std::mutex> lk(classMutex); block_ = value; }
+	void	set_deflection_base(float value) { std::lock_guard<std::mutex> lk(classMutex); deflection_base_ = value; }
+	void	set_block(float value) { std::lock_guard<std::mutex> lk(classMutex); block_ = value; }
 	void	set_block_base(int16 value) { std::lock_guard<std::mutex> lk(classMutex); block_base_ = value; }
 
 	void	set_str(float value) { std::lock_guard<std::mutex> lk(classMutex); str_ = value; }
@@ -773,6 +787,12 @@ struct InfoStruct{
 
 	void	set_biography(std::string value) { std::lock_guard<std::mutex> lk(classMutex); biography_ = value; }
 
+	void	set_power_regen(sint16 value) { std::lock_guard<std::mutex> lk(classMutex); power_regen_ = value; }
+	void	set_hp_regen(sint16 value) { std::lock_guard<std::mutex> lk(classMutex); hp_regen_ = value; }
+
+	void	set_power_regen_override(int8 value) { std::lock_guard<std::mutex> lk(classMutex); power_regen_override_ = value; }
+	void	set_hp_regen_override(int8 value) { std::lock_guard<std::mutex> lk(classMutex); hp_regen_override_ = value; }
+
 	void	ResetEffects(Spawn* spawn)
 	{
 		for(int i=0;i<45;i++){
@@ -811,16 +831,18 @@ private:
 	int16			max_mitigation_;
 	int16			mitigation_base_;
 	int16			avoidance_display_;
-	int16			cur_avoidance_;
+	float			cur_avoidance_;
 	int16			base_avoidance_pct_;
 	int16			avoidance_base_;
 	int16			max_avoidance_;
-	int16			parry_;
-	int16			parry_base_;
-	int16			deflection_;
+	float			parry_;
+	float			parry_base_;
+	float			deflection_;
 	int16			deflection_base_;
-	int16			block_;
+	float			block_;
 	int16			block_base_;
+	float			riposte_;
+	float			riposte_base_;
 	float			str_; //int16
 	float			sta_; //int16
 	float			agi_;//int16
@@ -928,6 +950,11 @@ private:
 	std::string		biography_;
 	float			drunk_;
 
+	sint16			power_regen_;
+	sint16			hp_regen_;
+
+	int8			power_regen_override_;
+	int8			hp_regen_override_;
 	// when PacketStruct is fixed for C++17 this should become a shared_mutex and handle read/write lock
 	std::mutex		classMutex;
 };
@@ -1034,7 +1061,6 @@ public:
 	virtual ~Entity();
 
 	void MapInfoStruct();
-	virtual float GetShieldBlockChance();
 	virtual float GetDodgeChance();
 	virtual void AddMaintainedSpell(LuaSpell* spell);
 	virtual void AddSpellEffect(LuaSpell* spell);
@@ -1054,7 +1080,9 @@ public:
 	EquipmentItemList* GetEquipmentList();
 
 	bool IsEntity(){ return true; }
+	float CalculateSkillStatChance(char* skill, int16 item_stat, float max_cap = 0.0f, float modifier = 0.0f, bool add_to_skill = false);
 	void CalculateBonuses();
+	void SetRegenValues(int16 effective_level);
 	float CalculateBonusMod();
 	float CalculateDPSMultiplier();
 	float CalculateCastingSpeedMod();
@@ -1098,9 +1126,7 @@ public:
 
 	bool	HasMoved(bool include_heading);
 	void	SetHPRegen(int16 new_val);
-	void	SetPowerRegen(int16 new_val);
 	int16	GetHPRegen();
-	int16	GetPowerRegen();
 	void	DoRegenUpdate();
 	MaintainedEffects* GetFreeMaintainedSpellSlot();
 	SpellEffects* GetFreeSpellEffectSlot();
@@ -1499,6 +1525,17 @@ public:
 
 	// Keep track of entities that hate this spawn.
 	set<int32> HatedBy;
+	std::mutex MHatedBy;
+
+	bool IsAggroed() { 
+			int32 size = 0;
+
+			MHatedBy.lock();
+			size = HatedBy.size();
+			MHatedBy.unlock();
+
+			return size > 0;
+		}
 
 	Mutex	MCommandMutex;
 
@@ -1539,6 +1576,7 @@ public:
 
 	// when PacketStruct is fixed for C++17 this should become a shared_mutex and handle read/write lock
 	std::mutex		MEquipment;
+	std::mutex		MStats;
 protected:
 	bool	in_combat;
 

+ 12 - 3
EQ2/source/WorldServer/GroundSpawn.cpp

@@ -132,6 +132,15 @@ void GroundSpawn::ProcessHarvest(Client* client) {
 		return;
 	}
 
+	int16 totalSkill = skill->current_val;
+	int32 skillID = master_item_list.GetItemStatIDByName(collection_skill);
+	if(skillID != 0xFFFFFFFF)
+	{
+		((Entity*)client->GetPlayer())->MStats.lock();
+		totalSkill += ((Entity*)client->GetPlayer())->stats[skillID];
+		((Entity*)client->GetPlayer())->MStats.unlock();
+	}
+
 	for (int8 i = 0; i < num_attempts_per_harvest; i++) {
 		vector<GroundSpawnEntry*> mod_groundspawn_entries;
 
@@ -147,7 +156,7 @@ void GroundSpawn::ProcessHarvest(Client* client) {
 				entry = *itr;
 
 				// if player lacks skill, skip table
-				if (entry->min_skill_level > skill->current_val)
+				if (entry->min_skill_level > totalSkill)
 					continue;
 				// if bonus, but player lacks level, skip table
 				if (entry->bonus_table && (client->GetPlayer()->GetLevel() < entry->min_adventure_level))
@@ -182,7 +191,7 @@ void GroundSpawn::ProcessHarvest(Client* client) {
 			}
 
 			// now roll to see which table to use
-			table_choice = MakeRandomInt(lowest_skill_level, skill->current_val);
+			table_choice = MakeRandomInt(lowest_skill_level, totalSkill);
 			LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "Random INT for Table by skill level: %i", table_choice);
 
 			int16 highest_score = 0;
@@ -251,7 +260,7 @@ void GroundSpawn::ProcessHarvest(Client* client) {
 					harvest_type = 2;
 					reward_total = 3;
 				}
-				else if (chance <= selected_table->harvest1 || skill->current_val == skill->max_val || is_collection) {
+				else if (chance <= selected_table->harvest1 || totalSkill >= skill->max_val || is_collection) {
 					LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "Harvest 1 Item from table : %i", selected_table->min_skill_level);
 					harvest_type = 1;
 				}

+ 85 - 2
EQ2/source/WorldServer/Items/Items.cpp

@@ -30,6 +30,7 @@
 #include "../Recipes/Recipe.h"
 #include <algorithm>
 #include <sstream>
+#include <boost/algorithm/string.hpp>
 
 extern World world;
 extern MasterSpellList master_spell_list;
@@ -38,6 +39,49 @@ extern MasterRecipeList master_recipe_list;
 extern ConfigReader configReader;
 extern LuaInterface* lua_interface;
 
+MasterItemList::MasterItemList(){
+	AddMappedItemStat(ITEM_STAT_ADORNING, std::string("adorning"));
+	AddMappedItemStat(ITEM_STAT_AGGRESSION, std::string("aggression"));
+	AddMappedItemStat(ITEM_STAT_ARTIFICING, std::string("artificing"));
+	AddMappedItemStat(ITEM_STAT_ARTISTRY, std::string("artistry"));
+	AddMappedItemStat(ITEM_STAT_CHEMISTRY, std::string("chemistry"));
+	AddMappedItemStat(ITEM_STAT_CRUSHING, std::string("crushing"));
+	AddMappedItemStat(ITEM_STAT_DEFENSE, std::string("defense"));
+	AddMappedItemStat(ITEM_STAT_DEFLECTION, std::string("deflection"));
+	AddMappedItemStat(ITEM_STAT_DISRUPTION, std::string("disruption"));
+	AddMappedItemStat(ITEM_STAT_FISHING, std::string("fishing"));
+	AddMappedItemStat(ITEM_STAT_FLETCHING, std::string("fletching"));
+	AddMappedItemStat(ITEM_STAT_FOCUS, std::string("focus"));
+	AddMappedItemStat(ITEM_STAT_FORESTING, std::string("foresting"));
+	AddMappedItemStat(ITEM_STAT_GATHERING, std::string("gathering"));
+	AddMappedItemStat(ITEM_STAT_METAL_SHAPING, std::string("metal shaping"));
+	AddMappedItemStat(ITEM_STAT_METALWORKING, std::string("metalworking"));
+	AddMappedItemStat(ITEM_STAT_MINING, std::string("mining"));
+	AddMappedItemStat(ITEM_STAT_MINISTRATION, std::string("ministration"));
+	AddMappedItemStat(ITEM_STAT_ORDINATION, std::string("ordination"));
+	AddMappedItemStat(ITEM_STAT_ADORNING, std::string("adorning"));
+	AddMappedItemStat(ITEM_STAT_PARRY, std::string("parry"));
+	AddMappedItemStat(ITEM_STAT_PIERCING, std::string("piercing"));
+	AddMappedItemStat(ITEM_STAT_RANGED, std::string("ranged"));
+	AddMappedItemStat(ITEM_STAT_SAFE_FALL, std::string("safe fall"));
+	AddMappedItemStat(ITEM_STAT_SCRIBING, std::string("scribing"));
+	AddMappedItemStat(ITEM_STAT_SCULPTING, std::string("sculpting"));
+	AddMappedItemStat(ITEM_STAT_SLASHING, std::string("slashing"));
+	AddMappedItemStat(ITEM_STAT_SUBJUGATION, std::string("subjugation"));
+	AddMappedItemStat(ITEM_STAT_SWIMMING, std::string("swimming"));
+	AddMappedItemStat(ITEM_STAT_TAILORING, std::string("tailoring"));
+	AddMappedItemStat(ITEM_STAT_TINKERING, std::string("tinkering"));
+	AddMappedItemStat(ITEM_STAT_TRANSMUTING, std::string("transmuting"));
+	AddMappedItemStat(ITEM_STAT_TRAPPING, std::string("trapping"));
+	AddMappedItemStat(ITEM_STAT_WEAPON_SKILLS, std::string("weapon skills"));
+}
+
+void MasterItemList::AddMappedItemStat(int32 id, std::string lower_case_name)
+{
+	mappedItemStatsStrings[lower_case_name] = id;
+	mappedItemStatTypeIDs[id] = lower_case_name;
+}
+
 MasterItemList::~MasterItemList(){
 	RemoveAll();
 }
@@ -707,7 +751,28 @@ ItemStatsValues* MasterItemList::CalculateItemBonuses(Item* item, Entity* entity
 		}
 		for(int32 i=0;i<item->item_stats.size();i++){
 			ItemStat* stat = item->item_stats[i];
-			world.AddBonuses(values, stat->stat_type*100 + stat->stat_subtype, stat->value, entity);
+				int multiplier = 100;
+				if(stat->stat_subtype > 99)
+					multiplier = 1000;
+			
+			int32 id = 0;
+			sint32 value = stat->value;
+			if(stat->stat_type != 1)
+					id = stat->stat_type*multiplier + stat->stat_subtype;
+			else
+			{
+				int32 tmp_id = master_item_list.GetItemStatIDByName(stat->stat_name);
+				if(tmp_id != 0xFFFFFFFF)
+				{
+					id = tmp_id;
+					if(!value)
+						value = stat->stat_subtype;		
+				}
+				else
+					id = stat->stat_type*multiplier + stat->stat_subtype;
+			}
+
+			world.AddBonuses(values, id, stat->value, entity);
 		}
 		return values;
 	}
@@ -3122,7 +3187,6 @@ bool PlayerItemList::MoveItem(sint32 to_bag_id, int16 from_index, sint8 to, int8
 				}
 			}
 			else{
-				printf("7\n");
 				MoveItem(item_from, item_to->details.bag_id, 0);
 				MPlayerItems.releasewritelock(__FUNCTION__, __LINE__);
 				return true;
@@ -3841,4 +3905,23 @@ string Item::CreateItemLink(int16 client_Version, bool bUseUniqueID) {
 			ss << "\\aITEM " << details.item_id << ' ' << name << ':' << name << "\\/a";
 	}
 	return ss.str();
+}
+
+int32 MasterItemList::GetItemStatIDByName(std::string name)
+{
+	boost::to_lower(name);
+	map<std::string, int32>::iterator itr = mappedItemStatsStrings.find(name.c_str());
+	if(itr != mappedItemStatsStrings.end())
+		return itr->second;
+
+	return 0xFFFFFFFF;
+}
+
+std::string MasterItemList::GetItemStatNameByID(int32 id)
+{
+	map<int32, std::string>::iterator itr = mappedItemStatTypeIDs.find(id);
+	if(itr != mappedItemStatTypeIDs.end())
+		return itr->second;
+
+	return std::string("");
 }

+ 40 - 0
EQ2/source/WorldServer/Items/Items.h

@@ -347,6 +347,40 @@ extern MasterItemList master_item_list;
 #define ITEM_STAT_WIS					3
 #define ITEM_STAT_INT					4
 
+#define ITEM_STAT_ADORNING				100
+#define ITEM_STAT_AGGRESSION			101
+#define ITEM_STAT_ARTIFICING			102
+#define ITEM_STAT_ARTISTRY				103
+#define ITEM_STAT_CHEMISTRY				104
+#define ITEM_STAT_CRUSHING				105
+#define ITEM_STAT_DEFENSE				106
+#define ITEM_STAT_DEFLECTION			107
+#define ITEM_STAT_DISRUPTION			108
+#define ITEM_STAT_FISHING				109
+#define ITEM_STAT_FLETCHING				110
+#define ITEM_STAT_FOCUS					111
+#define ITEM_STAT_FORESTING				112
+#define ITEM_STAT_GATHERING				113
+#define ITEM_STAT_METAL_SHAPING			114
+#define ITEM_STAT_METALWORKING			115
+#define ITEM_STAT_MINING				116
+#define ITEM_STAT_MINISTRATION			117
+#define ITEM_STAT_ORDINATION			118
+#define ITEM_STAT_PARRY     			119
+#define ITEM_STAT_PIERCING     			120
+#define ITEM_STAT_RANGED     			121
+#define ITEM_STAT_SAFE_FALL    			122
+#define ITEM_STAT_SCRIBING     			123
+#define ITEM_STAT_SCULPTING    			124
+#define ITEM_STAT_SLASHING     			125
+#define ITEM_STAT_SUBJUGATION  			126
+#define ITEM_STAT_SWIMMING  			127
+#define ITEM_STAT_TAILORING  			128
+#define ITEM_STAT_TINKERING  			129
+#define ITEM_STAT_TRANSMUTING  			130
+#define ITEM_STAT_TRAPPING  			131
+#define ITEM_STAT_WEAPON_SKILLS			132
+
 #define ITEM_STAT_VS_PHYSICAL			200
 #define ITEM_STAT_VS_HEAT				201 //elemental
 #define ITEM_STAT_VS_POISON				202 //noxious
@@ -932,6 +966,7 @@ public:
 };
 class MasterItemList{
 public:
+	MasterItemList();
 	~MasterItemList();
 	map<int32,Item*> items;
 
@@ -947,6 +982,11 @@ public:
 	static int32 NextUniqueID();
 	static void ResetUniqueID(int32 new_id);
 	static int32 next_unique_id;
+	int32 GetItemStatIDByName(std::string name);
+	std::string GetItemStatNameByID(int32 id);
+	void AddMappedItemStat(int32 id, std::string lower_case_name);
+	map<std::string, int32> mappedItemStatsStrings; 
+	map<int32, std::string> mappedItemStatTypeIDs; 
 };
 class PlayerItemList {
 public:

+ 34 - 0
EQ2/source/WorldServer/Items/Items_CoE.h

@@ -248,6 +248,40 @@ extern MasterItemList master_item_list;
 #define ITEM_STAT_WIS					3
 #define ITEM_STAT_INT					4
 
+#define ITEM_STAT_ADORNING				100
+#define ITEM_STAT_AGGRESSION			101
+#define ITEM_STAT_ARTIFICING			102
+#define ITEM_STAT_ARTISTRY				103
+#define ITEM_STAT_CHEMISTRY				104
+#define ITEM_STAT_CRUSHING				105
+#define ITEM_STAT_DEFENSE				106
+#define ITEM_STAT_DEFLECTION			107
+#define ITEM_STAT_DISRUPTION			108
+#define ITEM_STAT_FISHING				109
+#define ITEM_STAT_FLETCHING				110
+#define ITEM_STAT_FOCUS					111
+#define ITEM_STAT_FORESTING				112
+#define ITEM_STAT_GATHERING				113
+#define ITEM_STAT_METAL_SHAPING			114
+#define ITEM_STAT_METALWORKING			115
+#define ITEM_STAT_MINING				116
+#define ITEM_STAT_MINISTRATION			117
+#define ITEM_STAT_ORDINATION			118
+#define ITEM_STAT_PARRY     			119
+#define ITEM_STAT_PIERCING     			120
+#define ITEM_STAT_RANGED     			121
+#define ITEM_STAT_SAFE_FALL    			122
+#define ITEM_STAT_SCRIBING     			123
+#define ITEM_STAT_SCULPTING    			124
+#define ITEM_STAT_SLASHING     			125
+#define ITEM_STAT_SUBJUGATION  			126
+#define ITEM_STAT_SWIMMING  			127
+#define ITEM_STAT_TAILORING  			128
+#define ITEM_STAT_TINKERING  			129
+#define ITEM_STAT_TRANSMUTING  			130
+#define ITEM_STAT_TRAPPING  			131
+#define ITEM_STAT_WEAPON_SKILLS			132
+
 #define ITEM_STAT_VS_PHYSICAL			200
 #define ITEM_STAT_VS_ELEMENTAL			201
 #define ITEM_STAT_VS_NOXIOUS			202

+ 34 - 0
EQ2/source/WorldServer/Items/Items_DoV.h

@@ -261,6 +261,40 @@ extern MasterItemList master_item_list;
 #define ITEM_STAT_WIS					3
 #define ITEM_STAT_INT					4
 
+#define ITEM_STAT_ADORNING				100
+#define ITEM_STAT_AGGRESSION			101
+#define ITEM_STAT_ARTIFICING			102
+#define ITEM_STAT_ARTISTRY				103
+#define ITEM_STAT_CHEMISTRY				104
+#define ITEM_STAT_CRUSHING				105
+#define ITEM_STAT_DEFENSE				106
+#define ITEM_STAT_DEFLECTION			107
+#define ITEM_STAT_DISRUPTION			108
+#define ITEM_STAT_FISHING				109
+#define ITEM_STAT_FLETCHING				110
+#define ITEM_STAT_FOCUS					111
+#define ITEM_STAT_FORESTING				112
+#define ITEM_STAT_GATHERING				113
+#define ITEM_STAT_METAL_SHAPING			114
+#define ITEM_STAT_METALWORKING			115
+#define ITEM_STAT_MINING				116
+#define ITEM_STAT_MINISTRATION			117
+#define ITEM_STAT_ORDINATION			118
+#define ITEM_STAT_PARRY     			119
+#define ITEM_STAT_PIERCING     			120
+#define ITEM_STAT_RANGED     			121
+#define ITEM_STAT_SAFE_FALL    			122
+#define ITEM_STAT_SCRIBING     			123
+#define ITEM_STAT_SCULPTING    			124
+#define ITEM_STAT_SLASHING     			125
+#define ITEM_STAT_SUBJUGATION  			126
+#define ITEM_STAT_SWIMMING  			127
+#define ITEM_STAT_TAILORING  			128
+#define ITEM_STAT_TINKERING  			129
+#define ITEM_STAT_TRANSMUTING  			130
+#define ITEM_STAT_TRAPPING  			131
+#define ITEM_STAT_WEAPON_SKILLS			132
+
 #define ITEM_STAT_VS_PHYSICAL			200
 #define ITEM_STAT_VS_HEAT				201 //elemental
 #define ITEM_STAT_VS_POISON				202 //noxious

+ 34 - 0
EQ2/source/WorldServer/Items/Items_ToV.h

@@ -175,3 +175,37 @@
 #define TOV_ITEM_STAT_INT					4
 
 
+
+#define TOV_ITEM_STAT_ADORNING				100
+#define TOV_ITEM_STAT_AGGRESSION			101
+#define TOV_ITEM_STAT_ARTIFICING			102
+#define TOV_ITEM_STAT_ARTISTRY				103
+#define TOV_ITEM_STAT_CHEMISTRY				104
+#define TOV_ITEM_STAT_CRUSHING				105
+#define TOV_ITEM_STAT_DEFENSE				106
+#define TOV_ITEM_STAT_DEFLECTION			107
+#define TOV_ITEM_STAT_DISRUPTION			108
+#define TOV_ITEM_STAT_FISHING				109
+#define TOV_ITEM_STAT_FLETCHING				110
+#define TOV_ITEM_STAT_FOCUS					111
+#define TOV_ITEM_STAT_FORESTING				112
+#define TOV_ITEM_STAT_GATHERING				113
+#define TOV_ITEM_STAT_METAL_SHAPING			114
+#define TOV_ITEM_STAT_METALWORKING			115
+#define TOV_ITEM_STAT_MINING				116
+#define TOV_ITEM_STAT_MINISTRATION			117
+#define TOV_ITEM_STAT_ORDINATION			118
+#define TOV_ITEM_STAT_PARRY     			119
+#define TOV_ITEM_STAT_PIERCING     			120
+#define TOV_ITEM_STAT_RANGED     			121
+#define TOV_ITEM_STAT_SAFE_FALL    			122
+#define TOV_ITEM_STAT_SCRIBING     			123
+#define TOV_ITEM_STAT_SCULPTING    			124
+#define TOV_ITEM_STAT_SLASHING     			125
+#define TOV_ITEM_STAT_SUBJUGATION  			126
+#define TOV_ITEM_STAT_SWIMMING  			127
+#define TOV_ITEM_STAT_TAILORING  			128
+#define TOV_ITEM_STAT_TINKERING  			129
+#define TOV_ITEM_STAT_TRANSMUTING  			130
+#define TOV_ITEM_STAT_TRAPPING  			131
+#define TOV_ITEM_STAT_WEAPON_SKILLS			132

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

@@ -2132,6 +2132,34 @@ int EQ2Emu_lua_AddSpawnSpellBonus(lua_State* state) {
 	return 0;
 }
 
+int EQ2Emu_lua_RemoveSpawnSpellBonus(lua_State* state) {
+	if (!lua_interface)
+		return 0;
+	Spawn* spawn = lua_interface->GetSpawn(state);
+	LuaSpell* luaspell = lua_interface->GetCurrentSpell(state);
+	
+	if (!spawn) {
+		lua_interface->LogError("%s: LUA AddSpawnSpellBonus command error: spawn is not valid", lua_interface->GetScriptName(state));
+		return 0;
+	}
+
+	if (!spawn->IsEntity()) {
+		lua_interface->LogError("%s: LUA AddSpawnSpellBonus command error: spawn is not an entity", lua_interface->GetScriptName(state));
+		return 0;
+	}
+
+	if (!luaspell || !luaspell->spell) {
+		lua_interface->LogError("%s: LUA AddSpawnSpellBonus command error: can only be used in a spell script", lua_interface->GetScriptName(state));
+		return 0;
+	}
+	
+	((Entity*)spawn)->RemoveSpellBonus(luaspell);
+	if (spawn->IsPlayer())
+		((Player*)spawn)->SetCharSheetChanged(true);
+	
+	return 0;
+}
+
 int EQ2Emu_lua_RemoveSpellBonus(lua_State* state) {
 	if (!lua_interface)
 		return 0;
@@ -7065,7 +7093,15 @@ int EQ2Emu_lua_CanHarvest(lua_State* state) {
 	GroundSpawnEntry* entry = 0;
 	bool can_harvest = false;
 	sint32 min_skill = -1;
-
+	int16 totalSkill = skill->current_val;
+	int32 skillID = master_item_list.GetItemStatIDByName(collection_skill);
+	if(skillID != 0xFFFFFFFF)
+	{
+		((Entity*)player)->MStats.lock();
+		totalSkill += ((Entity*)player)->stats[skillID];
+		((Entity*)player)->MStats.unlock();
+	}
+	
 	// first, iterate through groundspawn_entries, discard tables player cannot use
 	for (itr = groundspawn_entries->begin(); itr != groundspawn_entries->end(); itr++)
 	{
@@ -7074,7 +7110,7 @@ int EQ2Emu_lua_CanHarvest(lua_State* state) {
 		if (min_skill == -1 || entry->min_skill_level < min_skill)
 			min_skill = entry->min_skill_level;
 		// if player lacks skill, skip table
-		if (entry->min_skill_level > skill->current_val)
+		if (entry->min_skill_level > totalSkill)
 			continue;
 		// if bonus, but player lacks level, skip table
 		if (entry->bonus_table && (player->GetLevel() < entry->min_adventure_level))
@@ -7103,7 +7139,7 @@ int EQ2Emu_lua_CanHarvest(lua_State* state) {
 				msg.append("catch");
 
 			msg.append(" the %s. It requires %i %s skill, and your skill is %i.");
-			client->Message(CHANNEL_HARVESTING_WARNINGS, msg.c_str(), ground->GetName(), min_skill, skill->name.data.c_str(), skill->current_val);
+			client->Message(CHANNEL_HARVESTING_WARNINGS, msg.c_str(), ground->GetName(), min_skill, skill->name.data.c_str(), totalSkill);
 			// You do not have enough skill to catch the band of fish.  It requires 20 Fishing skill, and your skill is 12.
 		}
 	}
@@ -9984,12 +10020,16 @@ int EQ2Emu_lua_Evac(lua_State* state) {
 					PacketStruct* packet = configReader.getStruct("WS_TeleportWithinZone", client->GetVersion());
 					if (packet)
 					{
+						client->SetReloadingZone(true);
 						packet->setDataByName("x", x);
 						packet->setDataByName("y", y);
 						packet->setDataByName("z", z);
 						client->QueuePacket(packet->serialize());
 						safe_delete(packet);
 					}
+					
+					client->GetCurrentZone()->ClearHate(client->GetPlayer());
+					client->GetCurrentZone()->RemoveSpawn(client->GetPlayer(), false, false, false, false);
 				}
 			}
 		}
@@ -10807,6 +10847,7 @@ int EQ2Emu_lua_GetSpell(lua_State* state) {
 		return 0;
 	int32 spell_id = lua_interface->GetInt32Value(state);
 	int8 spell_tier = lua_interface->GetInt8Value(state, 2);
+	string custom_lua_script = lua_interface->GetStringValue(state, 3);
 	if (spell_id > 0) {
 
 		if (spell_tier == 0)
@@ -10814,8 +10855,10 @@ int EQ2Emu_lua_GetSpell(lua_State* state) {
 
 		Spell* spell = master_spell_list.GetSpell(spell_id, spell_tier);
 		LuaSpell* lua_spell = 0;
+		if(custom_lua_script.size() < 1)
+			custom_lua_script = spell->GetSpellData()->lua_script;
 		if (lua_interface)
-			lua_spell = lua_interface->GetSpell(spell->GetSpellData()->lua_script.c_str());
+			lua_spell = lua_interface->GetSpell(custom_lua_script.c_str());
 
 		if (!lua_spell)
 			return 0;
@@ -11793,5 +11836,49 @@ int EQ2Emu_lua_MakeRandomFloat(lua_State* state) {
 	float max = lua_interface->GetFloatValue(state, 2);
 	float result = MakeRandomFloat(min, max);
 	lua_interface->SetFloatValue(state, result);
+	return 1;
+}
+
+int EQ2Emu_lua_AddIconValue(lua_State* state) {
+	if (!lua_interface)
+		return 0;
+
+	Spawn* spawn = lua_interface->GetSpawn(state);
+	int32 value = lua_interface->GetInt32Value(state, 2);
+
+	lua_interface->ResetFunctionStack(state);
+	
+	if(!spawn)
+	{
+		lua_interface->LogError("%s: LUA AddIconValue command error: spawn is not valid, does not exist", lua_interface->GetScriptName(state));
+		lua_interface->SetBooleanValue(state, false);
+		return 1;
+	}
+
+	spawn->AddIconValue(value);
+	lua_interface->SetBooleanValue(state, true);
+
+	return 1;
+}
+
+int EQ2Emu_lua_RemoveIconValue(lua_State* state) {
+	if (!lua_interface)
+		return 0;
+
+	Spawn* spawn = lua_interface->GetSpawn(state);
+	int32 value = lua_interface->GetInt32Value(state, 2);
+
+	lua_interface->ResetFunctionStack(state);
+	
+	if(!spawn)
+	{
+		lua_interface->LogError("%s: LUA RemoveIconValue command error: spawn is not valid, does not exist", lua_interface->GetScriptName(state));
+		lua_interface->SetBooleanValue(state, false);
+		return 1;
+	}
+
+	spawn->RemoveIconValue(value);
+	lua_interface->SetBooleanValue(state, true);
+
 	return 1;
 }

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

@@ -409,6 +409,7 @@ int EQ2Emu_lua_RemoveThreatTransfer(lua_State* state);
 int EQ2Emu_lua_CureByType(lua_State* state);
 int EQ2Emu_lua_CureByControlEffect(lua_State* state);
 int EQ2Emu_lua_AddSpawnSpellBonus(lua_State* state);
+int EQ2Emu_lua_RemoveSpawnSpellBonus(lua_State* state);
 int EQ2Emu_lua_CancelSpell(lua_State* state);
 int EQ2Emu_lua_RemoveStealth(lua_State* state);
 int EQ2Emu_lua_RemoveInvis(lua_State* state);
@@ -559,4 +560,7 @@ int EQ2Emu_lua_IsOpen(lua_State* state);
 
 int EQ2Emu_lua_MakeRandomInt(lua_State* state);
 int EQ2Emu_lua_MakeRandomFloat(lua_State* state);
+
+int EQ2Emu_lua_AddIconValue(lua_State* state);
+int EQ2Emu_lua_RemoveIconValue(lua_State* state);
 #endif

+ 4 - 0
EQ2/source/WorldServer/LuaInterface.cpp

@@ -1124,6 +1124,7 @@ void LuaInterface::RegisterFunctions(lua_State* state) {
 	lua_register(state, "CureByType", EQ2Emu_lua_CureByType);
 	lua_register(state, "CureByControlEffect", EQ2Emu_lua_CureByControlEffect);
 	lua_register(state, "AddSpawnSpellBonus", EQ2Emu_lua_AddSpawnSpellBonus);
+	lua_register(state, "RemoveSpawnSpellBonus", EQ2Emu_lua_RemoveSpawnSpellBonus);
 	lua_register(state, "CancelSpell", EQ2Emu_lua_CancelSpell);
 	lua_register(state, "RemoveStealth", EQ2Emu_lua_RemoveStealth);
 	lua_register(state, "RemoveInvis", EQ2Emu_lua_RemoveInvis);
@@ -1273,6 +1274,9 @@ void LuaInterface::RegisterFunctions(lua_State* state) {
 	
 	lua_register(state, "MakeRandomInt", EQ2Emu_lua_MakeRandomInt);
 	lua_register(state, "MakeRandomFloat", EQ2Emu_lua_MakeRandomFloat);
+	
+	lua_register(state, "AddIconValue", EQ2Emu_lua_AddIconValue);
+	lua_register(state, "RemoveIconValue", EQ2Emu_lua_RemoveIconValue);
 }
 
 void LuaInterface::LogError(const char* error, ...)  {

+ 9 - 1
EQ2/source/WorldServer/NPC_AI.cpp

@@ -212,8 +212,10 @@ void Brain::AddHate(Entity* entity, sint32 hate) {
 	else
 		m_hatelist.insert(std::pair<int32, sint32>(entity->GetID(), hate));
 
+	entity->MHatedBy.lock();
 	if (entity->HatedBy.count(m_body->GetID()) == 0)
 		entity->HatedBy.insert(m_body->GetID());
+	entity->MHatedBy.unlock();
 
 	// Unlock the list
 	MHateList.releasewritelock(__FUNCTION__, __LINE__);
@@ -227,7 +229,11 @@ void Brain::ClearHate() {
 	for (itr = m_hatelist.begin(); itr != m_hatelist.end(); itr++) {
 		Spawn* spawn = m_body->GetZone()->GetSpawnByID(itr->first);
 		if (spawn && spawn->IsEntity())
-			((Entity*)spawn)->HatedBy.erase(itr->first);
+		{
+			((Entity*)spawn)->MHatedBy.lock();
+			((Entity*)spawn)->HatedBy.erase(m_body->GetID());
+			((Entity*)spawn)->MHatedBy.unlock();
+		}
 	}
 
 	// Clear the list
@@ -245,7 +251,9 @@ void Brain::ClearHate(Entity* entity) {
 		// Erase the entity from the hate list
 		m_hatelist.erase(entity->GetID());
 
+	entity->MHatedBy.lock();
 	entity->HatedBy.erase(m_body->GetID());
+	entity->MHatedBy.unlock();
 
 	// Unlock the hate list
 	MHateList.releasewritelock(__FUNCTION__, __LINE__);

+ 66 - 33
EQ2/source/WorldServer/Player.cpp

@@ -171,8 +171,6 @@ Player::~Player(){
 	world.RemoveLottoPlayer(GetCharacterID());
 	safe_delete(info);
 	index_mutex.writelock(__FUNCTION__, __LINE__);
-	player_spawn_index_map.clear();
-	player_spawn_map.clear();
 	player_spawn_reverse_id_map.clear();
 	player_removed_spawns.clear();
 	player_spawn_id_map.clear();
@@ -444,12 +442,8 @@ PacketStruct* PlayerInfo::serialize2(int16 version){
 		else
 			packet->setDataByName("house_zone", "None");
 		//packet->setDataByName("account_age_base", 14);
-		if(player->GetHPRegen() == 0)
-			player->SetHPRegen((int)(info_struct->get_level()*.75)+(int)(info_struct->get_level()/10)+3);
-		if(player->GetPowerRegen() == 0)
-			player->SetPowerRegen(info_struct->get_level()+(int)(info_struct->get_level()/10)+4);
-		packet->setDataByName("hp_regen", player->GetHPRegen());
-		packet->setDataByName("power_regen", player->GetPowerRegen());
+		packet->setDataByName("hp_regen", info_struct->get_hp_regen());
+		packet->setDataByName("power_regen", info_struct->get_power_regen());
 		/*packet->setDataByName("unknown11", -1, 0);
 		packet->setDataByName("unknown11", -1, 1);
 		packet->setDataByName("unknown13", 201, 0);
@@ -677,12 +671,9 @@ EQ2Packet* PlayerInfo::serialize(int16 version, int16 modifyPos, int32 modifyVal
 		packet->setDataByName("base_power", player->GetTotalPowerBase());
 		packet->setDataByName("conc_used", info_struct->get_cur_concentration());
 		packet->setDataByName("conc_max", info_struct->get_max_concentration());
-		if (player->GetHPRegen() == 0)
-			player->SetHPRegen((int)(info_struct->get_level() * .75) + (int)(info_struct->get_level() / 10) + 1);
-		if (player->GetPowerRegen() == 0)
-			player->SetPowerRegen(info_struct->get_level() + (int)(info_struct->get_level() / 10) + 4);
-		packet->setDataByName("hp_regen", player->GetHPRegen() + player->stats[ITEM_STAT_HPREGEN]);
-		packet->setDataByName("power_regen", player->GetPowerRegen() + player->stats[ITEM_STAT_MANAREGEN]);
+		packet->setDataByName("hp_regen", player->GetInfoStruct()->get_hp_regen());
+		packet->setDataByName("power_regen", player->GetInfoStruct()->get_power_regen());
+
 		//	packet->setDataByName("unknown_1_4a_MJ", 96); //-1// was unknown11
 		//	packet->setDataByName("unknown_1_4b_MJ", 96); //-1
 		packet->setDataByName("stat_bonus_health", player->CalculateBonusMod());//bonus health and bonus power getting same value?
@@ -697,15 +688,19 @@ EQ2Packet* PlayerInfo::serialize(int16 version, int16 modifyPos, int32 modifyVal
 		packet->setDataByName("mitigation_pct_pvp", 559); // % calculation Mitigation % vs PvP 559 = 55.9%// 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("avoidance_pct", 0);//avoidance_pct 192 = 19.2% // confirmed DoV
-		packet->setDataByName("avoidance_base", info_struct->get_avoidance_base()); // confirmed DoV
+		packet->setDataByName("avoidance_pct", (int16)info_struct->get_avoidance_display()*10.0f);//avoidance_pct 192 = 19.2% // confirmed DoV
+		packet->setDataByName("avoidance_base", (int16)info_struct->get_avoidance_base()*10.0f); // confirmed DoV
 		packet->setDataByName("avoidance", info_struct->get_cur_avoidance());
 		//		packet->setDataByName("unknown_1096_1_MJ", 90);//unknown_1096_1_MJ
 		packet->setDataByName("base_avoidance_pct", info_struct->get_base_avoidance_pct());// confirmed DoV
 		//		packet->setDataByName("unknown_1096_2_MJ", 89);//unknown_1096_2_MJ
-		packet->setDataByName("parry", info_struct->get_parry_base());// confirmed DoV
+		float parry_pct = info_struct->get_parry(); // client works off of int16, but we use floats to track the actual x/100%
+		packet->setDataByName("parry",(int16)(parry_pct*10.0f));// confirmed DoV
 		//		packet->setDataByName("unknown_1096_3_MJ", 88);//unknown_1096_3_MJ
-		packet->setDataByName("block", info_struct->get_block_base());// confirmed DoV
+
+		float block_pct = info_struct->get_block()*10.0f;
+		
+		packet->setDataByName("block", (int16)block_pct);// confirmed DoV
 		//		packet->setDataByName("unknown_1096_4_MJ", 87);//unknown_1096_4_MJ
 		packet->setDataByName("uncontested_block", info_struct->get_uncontested_block());// confirmed DoV
 		//		packet->setDataByName("unknown_1096_5_MJ", 86);//unknown_1096_5_MJ
@@ -895,7 +890,9 @@ EQ2Packet* PlayerInfo::serialize(int16 version, int16 modifyPos, int32 modifyVal
 		packet->setDataByName("crit_chance", info_struct->get_crit_chance());// dov confirmed
 		//unknown_1096_32_MJ
 		packet->setDataByName("crit_bonus", info_struct->get_crit_bonus());// dov confirmed
+		((Entity*)player)->MStats.lock();
 		packet->setDataByName("potency", player->stats[ITEM_STAT_POTENCY]);//info_struct->get_potency);// dov confirmed
+		((Entity*)player)->MStats.unlock();
 		//unknown_1096_33_MJ
 		packet->setDataByName("reuse_speed", info_struct->get_reuse_speed());// dov confirmed
 		packet->setDataByName("recovery_speed", info_struct->get_recovery_speed());// dov confirmed
@@ -910,12 +907,14 @@ EQ2Packet* PlayerInfo::serialize(int16 version, int16 modifyPos, int32 modifyVal
 		//unknown_1096_37_MJ
 		//toughness_resist_crit_pvp
 		//unknown_1096_38_MJ
+		((Entity*)player)->MStats.lock();
 		packet->setDataByName("durability_mod", player->stats[ITEM_STAT_DURABILITY_MOD]);// dov confirmed
 		packet->setDataByName("durability_add", player->stats[ITEM_STAT_DURABILITY_ADD]);// dov confirmed
 		packet->setDataByName("progress_mod", player->stats[ITEM_STAT_PROGRESS_MOD]);// dov confirmed
 		packet->setDataByName("progress_add", player->stats[ITEM_STAT_PROGRESS_ADD]);// dov confirmed
 		packet->setDataByName("success_mod", player->stats[ITEM_STAT_SUCCESS_MOD]);// dov confirmed
 		packet->setDataByName("crit_success_mod", player->stats[ITEM_STAT_CRIT_SUCCESS_MOD]);// dov confirmed
+		((Entity*)player)->MStats.unlock();
 
 		//unknown_1096_39_MJ
 		/////GRoup Members
@@ -966,6 +965,7 @@ EQ2Packet* PlayerInfo::serialize(int16 version, int16 modifyPos, int32 modifyVal
 
 
 
+		((Entity*)player)->MStats.lock();
 		packet->setDataByName("rare_harvest_chance", player->stats[ITEM_STAT_RARE_HARVEST_CHANCE]);
 		packet->setDataByName("max_crafting", player->stats[ITEM_STAT_MAX_CRAFTING]);
 		packet->setDataByName("component_refund", player->stats[ITEM_STAT_COMPONENT_REFUND]);
@@ -976,6 +976,7 @@ EQ2Packet* PlayerInfo::serialize(int16 version, int16 modifyPos, int32 modifyVal
 		packet->setDataByName("ex_progress_mod", player->stats[ITEM_STAT_EX_PROGRESS_MOD]);
 		packet->setDataByName("ex_progress_add", player->stats[ITEM_STAT_EX_PROGRESS_ADD]);
 		packet->setDataByName("ex_success_mod", player->stats[ITEM_STAT_EX_SUCCESS_MOD]);
+		((Entity*)player)->MStats.unlock();
 
 		packet->setDataByName("flurry", info_struct->get_flurry());
 		packet->setDataByName("unknown153", 153);
@@ -3010,8 +3011,6 @@ SpellEffects* Player::GetSpellEffects() {
 void Player::ClearEverything(){
 	index_mutex.writelock(__FUNCTION__, __LINE__);
 	player_removed_spawns.clear();
-	player_spawn_map.clear();
-	player_spawn_index_map.clear();
 	player_spawn_id_map.clear();
 	player_spawn_reverse_id_map.clear();
 	index_mutex.releasewritelock(__FUNCTION__, __LINE__);
@@ -3524,13 +3523,22 @@ void Player::InCombat(bool val, bool range) {
 	else
 		GetInfoStruct()->set_flags(GetInfoStruct()->get_flags() & ~(1 << (range?CF_RANGED_AUTO_ATTACK:CF_AUTO_ATTACK)));
 
+	bool changeCombatState = false;
+	
+	if((in_combat && !val) || (!in_combat && val))
+		changeCombatState = true;
+
 	in_combat = val;
 	if(in_combat)
 		AddIconValue(64);
 	else
 		RemoveIconValue(64);
 
+	if(changeCombatState)
+		SetRegenValues(GetInfoStruct()->get_effective_level());
+
 	charsheet_changed = true;
+	info_changed = true;
 }
 
 void Player::SetCharSheetChanged(bool val){
@@ -3821,7 +3829,9 @@ int32 Player::GetTSXP() {
 }
 
 bool Player::AddXP(int32 xp_amount){
+	MStats.lock();
 	xp_amount += ((xp_amount) * stats[ITEM_STAT_COMBATEXPMOD]) / 100;
+	MStats.unlock();
 
 	float current_xp_percent = ((float)GetXP()/(float)GetNeededXP())*100;
 	float miniding_min_percent = ((int)(current_xp_percent/10)+1)*10;
@@ -3845,7 +3855,9 @@ bool Player::AddXP(int32 xp_amount){
 }
 
 bool Player::AddTSXP(int32 xp_amount){
+	MStats.lock();
 	xp_amount += ((xp_amount)*stats[ITEM_STAT_TRADESKILLEXPMOD]) / 100;
+	MStats.unlock();
 
 	float current_xp_percent = ((float)GetTSXP()/(float)GetNeededTSXP())*100;
 	float miniding_min_percent = ((int)(current_xp_percent/10)+1)*10;
@@ -3901,8 +3913,8 @@ Spawn* Player::GetSpawnByIndex(int16 index){
 	Spawn* spawn = 0;
 
 	index_mutex.readlock(__FUNCTION__, __LINE__);
-	if(player_spawn_map.count(index) > 0)
-		spawn = player_spawn_map[index];
+	if(player_spawn_id_map.count(index) > 0)
+		spawn = player_spawn_id_map[index];
 	index_mutex.releasereadlock(__FUNCTION__, __LINE__);
 
 	return spawn;
@@ -3912,8 +3924,8 @@ int16 Player::GetIndexForSpawn(Spawn* spawn) {
 	int16 val = 0;
 
 	index_mutex.readlock(__FUNCTION__, __LINE__);
-	if(player_spawn_index_map.count(spawn) > 0)
-		val = player_spawn_index_map[spawn];
+	if(player_spawn_reverse_id_map.count(spawn) > 0)
+		val = player_spawn_reverse_id_map[spawn];
 	index_mutex.releasereadlock(__FUNCTION__, __LINE__);
 
 	return val;
@@ -3942,11 +3954,11 @@ void Player::RemoveSpawn(Spawn* spawn)
 
 	player_removed_spawns[spawn] = 1;
 
-	if (player_spawn_index_map[spawn] && player_spawn_map.count(player_spawn_index_map[spawn]) > 0)
-		player_spawn_map.erase(player_spawn_index_map[spawn]);
+	if (player_spawn_reverse_id_map[spawn] && player_spawn_id_map.count(player_spawn_reverse_id_map[spawn]) > 0)
+		player_spawn_id_map.erase(player_spawn_reverse_id_map[spawn]);
 
-	if (player_spawn_index_map.count(spawn) > 0)
-		player_spawn_index_map.erase(spawn);
+	if (player_spawn_reverse_id_map.count(spawn) > 0)
+		player_spawn_reverse_id_map.erase(spawn);
 
 	if (player_spawn_id_map.count(spawn->GetID()) && player_spawn_id_map[spawn->GetID()] == spawn)
 		player_spawn_id_map.erase(spawn->GetID());
@@ -5019,8 +5031,11 @@ PlayerItemList*	Player::GetPlayerItemList(){
 	return &item_list;
 }
 
-void Player::ResetSavedSpawns(){
+void Player::ResetRemovedSpawns(){
 	player_removed_spawns.clear();
+}
+void Player::ResetSavedSpawns(){
+	ResetRemovedSpawns();
 
 	vis_mutex.writelock(__FUNCTION__, __LINE__);
 	spawn_vis_packet_list.clear();
@@ -5037,8 +5052,6 @@ void Player::ResetSavedSpawns(){
 	index_mutex.writelock(__FUNCTION__, __LINE__);
 	player_spawn_reverse_id_map.clear();
 	player_spawn_id_map.clear();
-	player_spawn_map.clear();
-	player_spawn_index_map.clear();
 	index_mutex.releasewritelock(__FUNCTION__, __LINE__);
 
 	m_playerSpawnQuestsRequired.writelock(__FUNCTION__, __LINE__);
@@ -5116,7 +5129,10 @@ bool Player::CheckLevelStatus(int16 new_level) {
 Skill* Player::GetSkillByName(const char* name, bool check_update){
 	Skill* ret = skill_list.GetSkillByName(name);
 	if(check_update)
-		skill_list.CheckSkillIncrease(ret);
+		{
+			if(skill_list.CheckSkillIncrease(ret))
+				CalculateBonuses();
+		}
 	return ret;
 }
 
@@ -5793,7 +5809,6 @@ void Player::InitXPTable() {
 void Player::SendQuestRequiredSpawns(int32 quest_id){
 	bool locked = true;
 	m_playerSpawnQuestsRequired.readlock(__FUNCTION__, __LINE__);
-	Quest* quest = GetQuest(quest_id);
 	if (player_spawn_quests_required.size() > 0 ) {
 		ZoneServer* zone = GetZone();
 		Client* client = zone->GetClientBySpawn(this);
@@ -6122,4 +6137,22 @@ void Player::UpdateTargetInvisHistory(int32 targetID, bool canSeeStatus)
 void Player::RemoveTargetInvisHistory(int32 targetID)
 {
 	target_invis_history.erase(targetID);
+}
+
+void Player::SetSpawnMap(Spawn* spawn)
+{
+	index_mutex.writelock(__FUNCTION__, __LINE__);
+	spawn_id += 1;
+	if (spawn_index == 255)
+		spawn_index += 1; //just so we dont have to worry about overloading
+	
+	int32 tmp_id = spawn_id;
+
+	player_spawn_id_map[tmp_id] = spawn;
+
+	if(player_spawn_reverse_id_map.count(spawn))
+		player_spawn_reverse_id_map.erase(spawn);
+
+	player_spawn_reverse_id_map.insert(make_pair(spawn,tmp_id));
+	index_mutex.releasewritelock(__FUNCTION__, __LINE__);
 }

+ 23 - 30
EQ2/source/WorldServer/Player.h

@@ -599,30 +599,19 @@ public:
 		if (player_spawn_reverse_id_map.count(spawn) > 0)
 			id = player_spawn_reverse_id_map[spawn];
 		index_mutex.releasereadlock(__FUNCTION__, __LINE__);
+
 		return id;
 	}
 
-	void SetSpawnMap(Spawn* spawn)
-	{
-		index_mutex.writelock(__FUNCTION__, __LINE__);
-		spawn_id += 1;
-		int32 tmp_id = spawn_id;
-		player_spawn_id_map[tmp_id] = spawn;
-
-		if(player_spawn_reverse_id_map.count(spawn))
-			player_spawn_reverse_id_map.erase(spawn);
-
-		player_spawn_reverse_id_map.insert(make_pair(spawn,tmp_id));
-		index_mutex.releasewritelock(__FUNCTION__, __LINE__);
-	}
+	void SetSpawnMap(Spawn* spawn);
 
-	void SetSpawnMapIndex(Spawn* spawn, int16 index)
+	void SetSpawnMapIndex(Spawn* spawn, int32 index)
 	{
 		index_mutex.writelock(__FUNCTION__, __LINE__);
-		if (player_spawn_map.count(index))
-			player_spawn_map[index] = spawn;
+		if (player_spawn_id_map.count(index))
+			player_spawn_id_map[index] = spawn;
 		else
-			player_spawn_map[index] = spawn;
+			player_spawn_id_map[index] = spawn;
 		index_mutex.releasewritelock(__FUNCTION__, __LINE__);
 	}
 
@@ -636,16 +625,8 @@ public:
 
 		new_index = spawn_index;
 
-		if (player_spawn_index_map.count(spawn))
-			player_spawn_index_map.erase(spawn);
-
-		player_spawn_index_map.insert(make_pair(spawn,new_index));
-
-		if (player_spawn_map.count(new_index))
-			player_spawn_map[new_index] = spawn;
-		else
-			player_spawn_map.insert(make_pair(new_index, spawn));
-
+		player_spawn_id_map[new_index] = spawn;
+		player_spawn_reverse_id_map[spawn] = new_index;
 		index_mutex.releasewritelock(__FUNCTION__, __LINE__);
 
 		return new_index;
@@ -711,7 +692,7 @@ public:
 	void				SetGroupInformation(PacketStruct* packet);
 
 
-
+	void				ResetRemovedSpawns();
 	void				ResetSavedSpawns();
 	bool				IsReturningFromLD();
 	void				SetReturningFromLD(bool val);
@@ -960,6 +941,20 @@ public:
 	bool HasGMVision() { return gm_vision; }
 	void SetGMVision(bool val) { gm_vision = val; }
 
+	void StopCombat(int8 type=0) { 
+		switch(type)
+		{
+			case 2:
+				SetRangeAttack(false);
+				InCombat(false, true);
+			break;
+			default:
+				InCombat(false);
+				InCombat(false, true);
+				SetRangeAttack(false);
+			break;
+		}
+	}
 
 
 
@@ -1092,8 +1087,6 @@ private:
 
 	bool gm_vision;
 
-	map<Spawn*, int16>	player_spawn_index_map;
-	map<int16, Spawn*>	player_spawn_map;
 	map<int32, Spawn*>	player_spawn_id_map;
 	map<Spawn*, int32>	player_spawn_reverse_id_map;
 	map<Spawn*, int8>	player_removed_spawns;

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

@@ -220,7 +220,7 @@ void RuleManager::Init()
 	RULE_INIT(R_Combat, MaxCombatRange, "4.0");
 	/* 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, SpeedRatio, "0");		// was 1280/7.5 and 600/7.5 until it became 300.
+	RULE_INIT(R_Spawn, ClassicRegen, "0");
 
 	/* TIMER */
 

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

@@ -81,6 +81,7 @@ enum RuleType {
 
 	/* SPAWN */
 	SpeedMultiplier,
+	ClassicRegen,
 	//SpeedRatio,
 
 	/* UI */

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

@@ -3950,3 +3950,40 @@ int32 Spawn::GetRegionType(Region_Node* inNode, ZBSP_Node* rootNode)
 	
 	return false;
 }
+
+float Spawn::SpawnAngle(Spawn* target, float selfx, float selfz)
+{
+	if (!target || target == this)
+		return 0.0f;
+
+	float angle, lengthb, vectorx, vectorz, dotp;
+	float spx = (target->GetX());	// mob xloc (inverse because eq)
+	float spz = -(target->GetZ());		// mob yloc
+	float heading = target->GetHeading();	// mob heading
+	if (heading < 270)
+		heading += 90;
+	else
+		heading -= 270;
+
+	heading = heading * 3.1415f / 180.0f;	// convert to radians
+	vectorx = spx + (10.0f * std::cos(heading));	// create a vector based on heading
+	vectorz = spz + (10.0f * std::sin(heading));	// of spawn length 10
+
+	// length of spawn to player vector
+	lengthb = (float) std::sqrt(((selfx - spx) * (selfx - spx)) + ((-selfz - spz) * (-selfz - spz)));
+
+	// calculate dot product to get angle
+	// Handle acos domain errors due to floating point rounding errors
+	dotp = ((vectorx - spx) * (selfx - spx) +
+			(vectorz - spz) * (-selfz - spz)) / (10.0f * lengthb);
+
+	if (dotp > 1)
+		return 0.0f;
+	else if (dotp < -1)
+		return 180.0f;
+
+	angle = std::acos(dotp);
+	angle = angle * 180.0f / 3.1415f;
+
+	return angle;
+}

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

@@ -1194,6 +1194,14 @@ public:
 	bool InRegion(Region_Node* inNode, ZBSP_Node* rootNode);
 	int32 GetRegionType(Region_Node* inNode, ZBSP_Node* rootNode);
 	
+	float SpawnAngle(Spawn* target, float selfx, float selfz);
+	bool BehindSpawn(Spawn *target, float selfx, float selfz)
+	{ return (!target || target == this) ? false : SpawnAngle(target, selfx, selfz) > 90.0f; }
+	bool InFrontSpawn(Spawn *target, float selfx, float selfz)
+	{ return (!target || target == this) ? false : SpawnAngle(target, selfx, selfz) < 63.0f; }
+	bool IsFlankingSpawn(Spawn *target, float selfx, float selfz)
+	{ return (!target || target == this) ? false : SpawnAngle(target, selfx, selfz) > 63.0f; }
+
 	std::map<std::map<Region_Node*, ZBSP_Node*>, Region_Status> Regions;
 	Mutex RegionMutex;
 protected:

+ 4 - 0
EQ2/source/WorldServer/Tradeskills/Tradeskills.cpp

@@ -102,8 +102,10 @@ void TradeskillMgr::Process() {
 			float crit_fail = m_critFail;
 
 			// Modify the % chance for success based off of stats
+			client->GetPlayer()->MStats.lock();
 			fail -= client->GetPlayer()->stats[ITEM_STAT_SUCCESS_MOD];
 			success += client->GetPlayer()->stats[ITEM_STAT_SUCCESS_MOD];
+			client->GetPlayer()->MStats.unlock();
 
 			// add values together for the if
 			crit_success += crit_fail;
@@ -157,8 +159,10 @@ void TradeskillMgr::Process() {
 			}
 
 			// Modify the progress/durability by the players stats
+			client->GetPlayer()->MStats.lock();
 			progress += client->GetPlayer()->stats[ITEM_STAT_PROGRESS_ADD];
 			durability += client->GetPlayer()->stats[ITEM_STAT_DURABILITY_ADD];
+			client->GetPlayer()->MStats.unlock();
 
 			tradeskill->currentDurability += durability;
 			tradeskill->currentProgress += progress;

+ 4 - 4
EQ2/source/WorldServer/Zone/region_map_v1.cpp

@@ -430,9 +430,9 @@ void RegionMapV1::TicRegionsNearSpawn(Spawn *spawn, Client *client) const
 					node->dist, node->x, node->y, node->z, node->regionScriptName.c_str());
 			WaterRegionType whatWasRegionType = RegionTypeNormal;	// default will be 0
 
-			if (BSP_Root->special == -3)
+			if (BSP_Root->special == SPECIAL_REGION_LAVA_OR_DEATH)
 				whatWasRegionType = RegionTypeLava;	// 2
-			else if (BSP_Root->special == 1)
+			else if (BSP_Root->special == SPECIAL_REGION_WATER)
 				whatWasRegionType = RegionTypeWater;	// 1
 
 			int32 returnValue = 0;
@@ -652,10 +652,10 @@ WaterRegionType RegionMapV1::BSPReturnRegionWaterRegion(const Region_Node* regio
 		else if (current_node->left == -2) {
 			switch(current_node->special)
 			{
-				case -3:
+				case SPECIAL_REGION_LAVA_OR_DEATH:
 					return(RegionTypeLava);
 					break;
-				case 1:
+				case SPECIAL_REGION_WATER:
 					return(RegionTypeWater);
 					break;
 				default:

+ 3 - 0
EQ2/source/WorldServer/Zone/region_map_v1.h

@@ -7,6 +7,9 @@
 class Client;
 class Spawn;
 
+#define SPECIAL_REGION_LAVA_OR_DEATH 4294967293
+#define SPECIAL_REGION_WATER		 1
+
 #pragma pack(1)
 typedef struct ZBSP_Node {
 	int32 node_number;

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

@@ -198,6 +198,7 @@ Client::Client(EQStream* ieqs) : pos_update(125), quest_pos_timer(2000), lua_deb
 	rejoin_group_id = 0;
 	lastRegionRemapTime = 0;
 	regionDebugMessaging = false;
+	client_reloading_zone = false;
 }
 
 Client::~Client() {
@@ -632,7 +633,13 @@ void Client::HandlePlayerRevive(int32 point_id)
 void Client::SendCharInfo() {
 	EQ2Packet* app;
 
+	if(IsReloadingZone())
+	{
+		GetPlayer()->ResetRemovedSpawns();
+		SetReloadingZone(false);
+	}
 
+	
 	player->SetEquippedItemAppearances();
 
 	ClientPacketFunctions::SendCharacterData(this);
@@ -663,7 +670,7 @@ void Client::SendCharInfo() {
 	ClientPacketFunctions::SendAbilities(this);
 
 	ClientPacketFunctions::SendSkillBook(this);
-	if (!player->IsResurrecting()) {
+	if (!IsReloadingZone() && !player->IsResurrecting()) {
 		if(GetVersion() > 546) //we will send this later on for 546 and below
 			ClientPacketFunctions::SendUpdateSpellBook(this);
 	}
@@ -673,7 +680,7 @@ void Client::SendCharInfo() {
 	ClientPacketFunctions::SendLoginCommandMessages(this);
 
 	GetCurrentZone()->AddSpawn(player);
-
+	
 	//SendCollectionList();
 	Guild* guild = player->GetGuild();
 	if (guild)
@@ -1420,8 +1427,11 @@ bool Client::HandlePacket(EQApplicationPacket* app) {
 			}*/
 			float safe_height = 13.0f;
 			Skill* skill = GetPlayer()->GetSkillByName("Safe Fall", true);
+			GetPlayer()->MStats.lock();
+			float deflectionStat = GetPlayer()->stats[ITEM_STAT_SAFE_FALL];
+			GetPlayer()->MStats.unlock();
 			if (skill)
-				safe_height += (skill->current_val + 1) / 5;
+				safe_height += (skill->current_val + 1 + deflectionStat) / 5;
 
 			if (height > safe_height) {
 				int16 damage = (int16)ceil((height - safe_height) * 125);
@@ -4210,8 +4220,6 @@ void Client::ChangeLevel(int16 old_level, int16 new_level) {
 	info->set_magic_base((int16)(new_level * 1.5 + 10));
 	info->set_divine_base((int16)(new_level * 1.5 + 10));
 	info->set_poison_base((int16)(new_level * 1.5 + 10));
-	GetPlayer()->SetHPRegen((int)(new_level * .75) + (int)(new_level / 10) + 3);
-	GetPlayer()->SetPowerRegen(new_level + (int)(new_level / 10) + 4);
 	GetPlayer()->GetInfoStruct()->set_poison_base((int16)(new_level * 1.5 + 10));
 	UpdateTimeStampFlag(LEVEL_UPDATE_FLAG);
 	GetPlayer()->SetCharSheetChanged(true);
@@ -8283,7 +8291,7 @@ void Client::InspectPlayer(Player* player_to_inspect) {
 			packet->setDataByName("power_base", player_to_inspect->GetTotalPowerBase());
 			packet->setDataByName("mitigation", 0);
 			packet->setDataByName("unknown1", 0);
-			packet->setDataByName("avoidance", 0);
+			packet->setDataByName("avoidance", player_to_inspect->GetInfoStruct()->get_cur_avoidance());
 			packet->setDataByName("unknown2", 0);
 			packet->setDataByName("mitigation_percentage", 0);
 			packet->setDataByName("strength", player_to_inspect->GetStr());

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

@@ -467,6 +467,9 @@ public:
 		int8 mailType, int32 copper, int32 silver, int32 gold, int32 platinum, int32 item_id, int16 stack_size, int32 time_sent, int32 expire_time);
 
 	void SendEquipOrInvUpdateBySlot(int8 slot);
+
+	void SetReloadingZone(bool val) { client_reloading_zone = val; }
+	bool IsReloadingZone() { return client_reloading_zone; }
 private:
 	void    SavePlayerImages();
 	void	SkillChanged(Skill* skill, int16 previous_value, int16 new_value);
@@ -576,6 +579,8 @@ private:
 	int32 lastRegionRemapTime;
 	
 	bool regionDebugMessaging;
+
+	bool client_reloading_zone;
 };
 
 class ClientList {

+ 15 - 4
EQ2/source/WorldServer/zoneserver.cpp

@@ -96,6 +96,8 @@ extern int errno;
 // extern volatile bool RunLoops;										// never used in the zone server?
 // extern Classes classes;												// never used in the zone server?
 
+#define NO_CATCH 1
+
 extern WorldDatabase	database;
 extern sint32			numzones;
 extern ClientList		client_list;
@@ -1821,7 +1823,7 @@ void ZoneServer::ProcessDrowning(){
 				Client* client = itr->first;
 				Player* player = client->GetPlayer();
 				drowning_victims.Get(client) = Timer::GetCurrentTime2() + 2000;
-				damage = player->GetTotalHP()/20 + player->GetHPRegen();
+				damage = player->GetTotalHP()/20 + player->GetInfoStruct()->get_hp_regen();
 				player->TakeDamage(damage);
 				if(player->GetHP() == 0)
 					dead_list.push_back(client);
@@ -3780,7 +3782,7 @@ void ZoneServer::RemoveFromRangeMap(Client* client){
 }
 */
 
-void ZoneServer::RemoveSpawn(Spawn* spawn, bool delete_spawn, bool respawn, bool lock) 
+void ZoneServer::RemoveSpawn(Spawn* spawn, bool delete_spawn, bool respawn, bool lock, bool erase_from_spawn_list) 
 {
 	LogWrite(ZONE__DEBUG, 3, "Zone", "Processing RemoveSpawn function for %s (%i)...", spawn->GetName(),spawn->GetID());
 
@@ -3807,7 +3809,8 @@ void ZoneServer::RemoveSpawn(Spawn* spawn, bool delete_spawn, bool respawn, bool
 		spawn_expire_timers.erase(spawn->GetID());
 	
 	// we will remove the spawn ptr and entry in the spawn_list later.. it is not safe right now (lua? client process? spawn process? etc? too many factors)
-	AddPendingSpawnRemove(spawn->GetID());
+	if(erase_from_spawn_list)
+		AddPendingSpawnRemove(spawn->GetID());
 
 	PacketStruct* packet = 0;
 	int16 packet_version = 0;
@@ -4172,7 +4175,7 @@ void ZoneServer::KillSpawn(bool spawnListLocked, Spawn* dead, Spawn* killer, boo
 
 		// Remove hate towards dead from all npc's in the zone
 		ClearHate((Entity*)dead);
-
+		
 		// Check kill and death procs
 		if (killer && dead != killer){
 			if (dead->IsEntity())
@@ -4525,6 +4528,7 @@ void ZoneServer::SendDamagePacket(Spawn* attacker, Spawn* victim, int8 type1, in
 				packet->setArrayDataByName("damage_type", damage_type);
 				packet->setArrayDataByName("damage", damage);
 			}
+
 			if (!attacker)
 				packet->setSubstructDataByName("header", "attacker", 0xFFFFFFFF);
 			else
@@ -4796,6 +4800,11 @@ void ZoneServer::SendZoneSpawns(Client* client){
 	for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) {
 		Spawn* spawn = itr->second;
 		if (spawn) {
+			if(spawn == client->GetPlayer() && client->IsReloadingZone())
+			{
+				client->GetPlayer()->SetSpawnMap(spawn);
+			}
+			
 			CheckSpawnRange(client, spawn, true);
 		}
 	}
@@ -6508,8 +6517,10 @@ void ZoneServer::ResurrectSpawn(Spawn* spawn, Client* client) {
 
 	if(!no_calcs && caster){
 		//Potency Mod
+		((Entity*)caster)->MStats.lock();
 		heal_amt *=  ((caster->stats[ITEM_STAT_POTENCY] / 100) + 1);
 		power_amt *= ((caster->stats[ITEM_STAT_POTENCY] / 100) + 1);
+		((Entity*)caster)->MStats.unlock();
 
 		//Ability Mod
 		heal_amt += (int32)min((int32)info->get_ability_modifier(), (int32)(heal_amt / 2));

+ 3 - 2
EQ2/source/WorldServer/zoneserver.h

@@ -304,7 +304,7 @@ public:
 	
 	void	AddSpawnGroupChance(int32 group_id, float percent);
 	
-	void	RemoveSpawn(Spawn* spawn, bool delete_spawn = true, bool respawn = true, bool lock = true);
+	void	RemoveSpawn(Spawn* spawn, bool delete_spawn = true, bool respawn = true, bool lock = true, bool erase_from_spawn_list = true);
 	void	ProcessSpawnLocations();
 	void	SendQuestUpdates(Client* client, Spawn* spawn = 0);
 	
@@ -664,6 +664,8 @@ public:
 
 	void AddPendingSpawnRemove(int32 id);
 	void ProcessSpawnRemovals();
+
+	bool	SendRemoveSpawn(Client* client, Spawn* spawn, PacketStruct* packet = 0, bool delete_spawn = false);
 private:
 #ifndef WIN32
 	pthread_t ZoneThread;
@@ -697,7 +699,6 @@ private:
 	void	SaveClients();																						// never used outside zone server
 	void	CheckSendSpawnToClient();																			// never used outside zone server
 	void	CheckSendSpawnToClient(Client* client, bool initial_login = false);									// never used outside zone server
-	bool	SendRemoveSpawn(Client* client, Spawn* spawn, PacketStruct* packet = 0, bool delete_spawn = false);	// never used outside zone server
 	void	CheckRemoveSpawnFromClient(Spawn* spawn);															// never used outside zone server
 	void	SaveClient(Client* client);																			// never used outside zone server
 	void	ProcessFaction(Spawn* spawn, Client* client);														// never used outside zone server