Browse Source

- Fix #528 - IMMUNITY_TYPE_STRIKETHROUGH type 10 for AddImmunitySpell and like functions
- Fix #501 - /assist added, /assist on, /assist off, /assist [name], /assist *target*
update commands set handler=535 where command='assist';
- Fix #511 - loot tables not properly honoring max drops
- Fixed a few crashes / deadlocks with movement
- Start of mastery work for #503 -- primary/secondary damage now supports mastery skill

Emagi 6 months ago
parent
commit
c1e8768769

+ 108 - 51
EQ2/source/WorldServer/Commands/Commands.cpp

@@ -4110,57 +4110,9 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 		}
 		case COMMAND_ATTACK:
 		case COMMAND_AUTO_ATTACK:{
-			int8 type = 1;
-			bool update = false;
-			Player* player = client->GetPlayer();
-			if(!player)
-				break;
-			bool incombat = player->EngagedInCombat();
-			if(sep && sep->arg[0] && sep->IsNumber(0))
-				type = atoi(sep->arg[0]);
-			if(!client->GetPlayer()->Alive()){
-				client->SimpleMessage(CHANNEL_COLOR_RED,"You cannot do that right now.");
-				break;
-			}
-			if(type == 0){
-				if(incombat)
-					client->SimpleMessage(CHANNEL_GENERAL_COMBAT, "You stop fighting.");
-					player->StopCombat(type);
-					update = true;
-			}
-			else {
-				if(type == 2){
-					player->InCombat(false);
-					if(incombat && player->GetRangeAttack()){
-						player->StopCombat(type);
-						client->SimpleMessage(CHANNEL_GENERAL_COMBAT, "You stop fighting.");
-						update = true;
-					}
-					else{
-						player->SetRangeAttack(true);
-						player->InCombat(true, true);
-						client->SimpleMessage(CHANNEL_GENERAL_COMBAT, "You start fighting.");
-						update = true;
-					}
-				}
-				else {
-					player->InCombat(false, true);
-					player->SetRangeAttack(false);
-					player->InCombat(true);
-					if(!incombat) {
-						client->SimpleMessage(CHANNEL_GENERAL_COMBAT, "You start fighting.");
-						update = true;
-					}
-				}
-				/*else
-					client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You cannot attack that!");*/
-			}
-			
-			if(update) {
-				player->SetCharSheetChanged(true);
-			}
+			Command_AutoAttack(client, sep);
 			break;
-								}
+		}
 		case COMMAND_DEPOP:{
 			bool allow_respawns = false;
 			if(sep && sep->arg[0] && sep->IsNumber(0)){
@@ -5724,6 +5676,7 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 		case COMMAND_SHARE_QUEST: { Command_ShareQuest(client, sep); break; }
 		case COMMAND_YELL: { Command_Yell(client, sep); break; }
 		case COMMAND_SETAUTOLOOTMODE: { Command_SetAutoLootMode(client, sep); break; }
+		case COMMAND_ASSIST: { Command_Assist(client, sep); break; }
 		default: 
 		{
 			LogWrite(COMMAND__WARNING, 0, "Command", "Unhandled command: %s", command->command.data.c_str());
@@ -10970,6 +10923,8 @@ void Commands::Command_WeaponStats(Client* client)
 	if (primary) {
 		client->Message(0, "Name: %s", primary->name.c_str());
 		client->Message(0, "Base Damage: %u - %u", primary->weapon_info->damage_low3, primary->weapon_info->damage_high3);
+		client->Message(0, "Mastery Damage: %u - %u", primary->weapon_info->damage_low2, primary->weapon_info->damage_high2);
+		client->Message(0, "Damage: %u - %u", primary->weapon_info->damage_low1, primary->weapon_info->damage_high1);
 		client->Message(0, "Actual Damage: %u - %u", target ? ((Entity*)target)->GetPrimaryWeaponMinDamage() : player->GetPrimaryWeaponMinDamage(),
 													target ? ((Entity*)target)->GetPrimaryWeaponMaxDamage() : player->GetPrimaryWeaponMaxDamage());
 		client->Message(0, "Actual Delay: %u", target ? ((Entity*)target)->GetPrimaryWeaponDelay() : player->GetPrimaryWeaponDelay());
@@ -10992,6 +10947,8 @@ void Commands::Command_WeaponStats(Client* client)
 		client->SimpleMessage(0, "Secondary:");
 		client->Message(0, "Name: %s", secondary->name.c_str());
 		client->Message(0, "Base Damage: %u - %u", secondary->weapon_info->damage_low3, secondary->weapon_info->damage_high3);
+		client->Message(0, "Mastery Damage: %u - %u", secondary->weapon_info->damage_low2, secondary->weapon_info->damage_high2);
+		client->Message(0, "Damage: %u - %u", secondary->weapon_info->damage_low1, secondary->weapon_info->damage_high1);
 		client->Message(0, "Actual Damage: %u - %u", target ? ((Entity*)target)->GetSecondaryWeaponMinDamage() : player->GetSecondaryWeaponMinDamage(), 
 													 target ? ((Entity*)target)->GetSecondaryWeaponMaxDamage() : player->GetSecondaryWeaponMaxDamage());
 		client->Message(0, "Actual Delay: %d", target ? ((Entity*)target)->GetSecondaryWeaponDelay() : player->GetSecondaryWeaponDelay() * 0.1);
@@ -11004,6 +10961,8 @@ void Commands::Command_WeaponStats(Client* client)
 	if (ranged) {
 		client->Message(0, "Name: %s", ranged->name.c_str());
 		client->Message(0, "Base Damage: %u - %u", ranged->ranged_info->weapon_info.damage_low3, ranged->ranged_info->weapon_info.damage_high3);
+		client->Message(0, "Mastery Damage: %u - %u", ranged->ranged_info->weapon_info.damage_low2, ranged->ranged_info->weapon_info.damage_high2);
+		client->Message(0, "Damage: %u - %u", ranged->ranged_info->weapon_info.damage_low1, ranged->ranged_info->weapon_info.damage_high1);
 		client->Message(0, "Actual Damage: %u - %u", target ? ((Entity*)target)->GetRangedWeaponMinDamage() : player->GetRangedWeaponMinDamage(), 
 													 target ? ((Entity*)target)->GetRangedWeaponMaxDamage() : player->GetRangedWeaponMaxDamage());
 		client->Message(0, "Actual Delay: %d",  target ? ((Entity*)target)->GetRangeWeaponDelay() : player->GetRangeWeaponDelay() * 0.1);
@@ -12155,4 +12114,102 @@ void Commands::Command_SetAutoLootMode(Client* client, Seperator* sep) {
 		database.insertCharacterProperty(client, CHAR_PROPERTY_AUTOLOOTMETHOD, (char*)std::to_string(mode).c_str());
 		client->SendDefaultGroupOptions();
 	}
-}
+}
+
+/*
+	Function: Command_AutoAttack()
+	Purpose	: Attack / Auto Attack
+	Example	: /attack, /autoattack type
+*/ 
+void Commands::Command_AutoAttack(Client* client, Seperator* sep) {
+	int8 type = 1;
+	bool update = false;
+	Player* player = client->GetPlayer();
+	if(!player)
+		return;
+	bool incombat = player->EngagedInCombat();
+	if(sep && sep->arg[0] && sep->IsNumber(0))
+		type = atoi(sep->arg[0]);
+	if(!client->GetPlayer()->Alive()){
+		client->SimpleMessage(CHANNEL_COLOR_RED,"You cannot do that right now.");
+		return;
+	}
+	if(type == 0){
+		if(incombat)
+			client->SimpleMessage(CHANNEL_GENERAL_COMBAT, "You stop fighting.");
+			player->StopCombat(type);
+			update = true;
+	}
+	else {
+		if(type == 2){
+			player->InCombat(false);
+			if(incombat && player->GetRangeAttack()){
+				player->StopCombat(type);
+				client->SimpleMessage(CHANNEL_GENERAL_COMBAT, "You stop fighting.");
+				update = true;
+			}
+			else{
+				player->SetRangeAttack(true);
+				player->InCombat(true, true);
+				client->SimpleMessage(CHANNEL_GENERAL_COMBAT, "You start fighting.");
+				update = true;
+			}
+		}
+		else {
+			player->InCombat(false, true);
+			player->SetRangeAttack(false);
+			player->InCombat(true);
+			if(!incombat) {
+				client->SimpleMessage(CHANNEL_GENERAL_COMBAT, "You start fighting.");
+				update = true;
+			}
+		}
+		/*else
+			client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You cannot attack that!");*/
+	}
+	
+	if(update) {
+		player->SetCharSheetChanged(true);
+	}
+}
+	
+/* 
+	Function: Command_Assist()
+	Purpose	: Assist target
+	Example	: /assist [name]
+	* Uses target or character name
+*/ 
+void Commands::Command_Assist(Client* client, Seperator* sep) {
+	Entity* player = (Entity*)client->GetPlayer();
+	Spawn* res = nullptr;
+	if(sep && sep->arg[0]) {
+		if(!stricmp(sep->arg[0], "on")) {
+			database.insertCharacterProperty(client, CHAR_PROPERTY_ASSISTAUTOATTACK, "1");
+			return;
+		}
+		else if(!stricmp(sep->arg[0], "off")) {
+			database.insertCharacterProperty(client, CHAR_PROPERTY_ASSISTAUTOATTACK, "0");
+			return;
+		}
+		Client* otherClient = client->GetPlayer()->GetZone()->GetClientByName(sep->arg[0]);
+		if(otherClient) {
+			res = otherClient->GetPlayer();
+		}
+	}
+	
+	if (player->GetTarget()) {
+		res = player->GetTarget(); // selected target other than self only dis-engages that encounter
+	}
+	if(res && res->GetTarget()) {
+		res = res->GetTarget();
+	}
+	
+	if(res) {
+		client->TargetSpawn(res);
+		
+		if(client->GetPlayer()->GetInfoStruct()->get_assist_auto_attack() && !player->EngagedInCombat()) {
+			Command_AutoAttack(client, nullptr);
+		}
+	}
+}
+

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

@@ -450,6 +450,8 @@ public:
 	void Command_ShareQuest(Client* client, Seperator* sep);
 	void Command_Yell(Client* client, Seperator* sep);
 	void Command_SetAutoLootMode(Client* client, Seperator* sep);
+	void Command_AutoAttack(Client* client, Seperator* sep);
+	void Command_Assist(Client* client, Seperator* sep);
 
 	// AA Commands
 	void Get_AA_Xml(Client* client, Seperator* sep);
@@ -939,6 +941,7 @@ private:
 #define COMMAND_SHARE_QUEST				533
 
 #define COMMAND_SETAUTOLOOTMODE			534
+#define COMMAND_ASSIST					535
 
 #define GET_AA_XML						750
 #define ADD_AA							751

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

@@ -363,6 +363,7 @@ void Entity::MapInfoStruct()
 	get_int8_funcs["group_lock_method"] = l::bind(&InfoStruct::get_group_lock_method, &info_struct);
 	get_int8_funcs["group_solo_autolock"] = l::bind(&InfoStruct::get_group_solo_autolock, &info_struct);
 	get_int8_funcs["group_auto_loot_method"] = l::bind(&InfoStruct::get_group_auto_loot_method, &info_struct);
+	get_int8_funcs["assist_auto_attack"] = l::bind(&InfoStruct::get_assist_auto_attack, &info_struct);
 	
 	get_string_funcs["action_state"] = l::bind(&InfoStruct::get_action_state, &info_struct);
 	get_string_funcs["combat_action_state"] = l::bind(&InfoStruct::get_combat_action_state, &info_struct);
@@ -564,6 +565,7 @@ void Entity::MapInfoStruct()
 	set_int8_funcs["group_lock_method"] = l::bind(&InfoStruct::set_group_lock_method, &info_struct, l::_1);
 	set_int8_funcs["group_solo_autolock"] = l::bind(&InfoStruct::set_group_solo_autolock, &info_struct, l::_1);
 	set_int8_funcs["group_auto_loot_method"] = l::bind(&InfoStruct::set_group_auto_loot_method, &info_struct, l::_1);
+	set_int8_funcs["assist_auto_attack"] = l::bind(&InfoStruct::set_assist_auto_attack, &info_struct, l::_1);
 	
 	set_string_funcs["action_state"] = l::bind(&InfoStruct::set_action_state, &info_struct, l::_1);
 	set_string_funcs["combat_action_state"] = l::bind(&InfoStruct::set_combat_action_state, &info_struct, l::_1);
@@ -762,6 +764,62 @@ void Entity::SetSecondaryLastAttackTime(int32 new_time){
 	GetInfoStruct()->set_secondary_last_attack_time(new_time);
 }
 
+void Entity::GetWeaponDamage(Item* item, int32* low_damage, int32* high_damage) {
+	if(!low_damage || !high_damage)
+		return;
+	int32 selected_low_dmg = item->weapon_info->damage_low3;
+	int32 selected_high_dmg = item->weapon_info->damage_high3;
+	
+	if(IsPlayer()) {
+		float skillMultiplier = rule_manager.GetGlobalRule(R_Player, LevelMasterySkillMultiplier)->GetFloat();
+		if(skillMultiplier <= 0.0f) {
+			skillMultiplier = 1.0f;
+		}
+		int32 min_level_skill = (int32)((float)item->generic_info.adventure_default_level*skillMultiplier);
+		int32 rec_level_skill = (int32)((float)item->details.recommended_level*skillMultiplier);
+		if(min_level_skill > rec_level_skill) {
+			rec_level_skill = rec_level_skill;
+		}
+		
+		Skill* masterySkill = ((Player*)this)->skill_list.GetSkill(item->generic_info.skill_req2);
+		if(masterySkill) {
+		LogWrite(PLAYER__ERROR, 0, "Player", "Item has skill %s %u requirement", masterySkill->name.data.c_str(), item->generic_info.skill_req2);
+			int16 skillID = master_item_list.GetItemStatIDByName(masterySkill->name.data);
+			int32 skill_chance = (int32)CalculateSkillWithBonus((char*)masterySkill->name.data.c_str(), master_item_list.GetItemStatIDByName(masterySkill->name.data), false);
+			if(skill_chance >= min_level_skill && skill_chance < rec_level_skill) {
+				int32 diff_skill = rec_level_skill - skill_chance;
+				if(diff_skill < 1) {
+					selected_low_dmg = item->weapon_info->damage_low2;
+					selected_high_dmg = item->weapon_info->damage_high2;
+				}
+				else {
+					diff_skill += 1;
+					double logResult = log((double)diff_skill) / skillMultiplier;
+					if(logResult > 1.0f) {
+						logResult = .95f;
+					}
+					
+					selected_low_dmg = (int32)((double)item->weapon_info->damage_low2 * (1.0 - logResult));
+					if(selected_low_dmg < item->weapon_info->damage_low3) {
+						selected_low_dmg = item->weapon_info->damage_low3;
+					}
+					selected_high_dmg = (int32)((double)item->weapon_info->damage_high2 * (1.0 - logResult));
+					if(selected_high_dmg < item->weapon_info->damage_high3) {
+						selected_high_dmg = item->weapon_info->damage_high3;
+					}
+				}
+			}
+			else if(skill_chance >= rec_level_skill) {
+				selected_low_dmg = item->weapon_info->damage_low2;
+				selected_high_dmg = item->weapon_info->damage_high2;
+			}
+		}
+	}
+
+	*low_damage = selected_low_dmg;
+	*high_damage = selected_high_dmg;
+}
+
 void Entity::ChangePrimaryWeapon(){
 	if(GetInfoStruct()->get_override_primary_weapon()) {
 		return;
@@ -770,9 +828,12 @@ void Entity::ChangePrimaryWeapon(){
 	int32 str_offset_dmg = GetStrengthDamage();
 	Item* item = equipment_list.GetItem(EQ2_PRIMARY_SLOT);
 	if(item && item->details.item_id > 0 && item->IsWeapon()){
+		int32 selected_low_dmg = item->weapon_info->damage_low3;
+		int32 selected_high_dmg = item->weapon_info->damage_high3;
+		GetWeaponDamage(item, &selected_low_dmg, &selected_high_dmg);
 		GetInfoStruct()->set_primary_weapon_delay(item->weapon_info->delay * 100);
-		GetInfoStruct()->set_primary_weapon_damage_low(item->weapon_info->damage_low3 + str_offset_dmg);
-		GetInfoStruct()->set_primary_weapon_damage_high(item->weapon_info->damage_high3 + str_offset_dmg);
+		GetInfoStruct()->set_primary_weapon_damage_low(selected_low_dmg + str_offset_dmg);
+		GetInfoStruct()->set_primary_weapon_damage_high(selected_high_dmg + str_offset_dmg);
 		GetInfoStruct()->set_primary_weapon_type(item->GetWeaponType());
 		GetInfoStruct()->set_wield_type(item->weapon_info->wield_type);
 	}
@@ -803,9 +864,12 @@ void Entity::ChangeSecondaryWeapon(){
 	
 	Item* item = equipment_list.GetItem(EQ2_SECONDARY_SLOT);
 	if(item && item->details.item_id > 0 && item->IsWeapon()){
+		int32 selected_low_dmg = item->weapon_info->damage_low3;
+		int32 selected_high_dmg = item->weapon_info->damage_high3;
+		GetWeaponDamage(item, &selected_low_dmg, &selected_high_dmg);
 		GetInfoStruct()->set_secondary_weapon_delay(item->weapon_info->delay * 100);
-		GetInfoStruct()->set_secondary_weapon_damage_low(item->weapon_info->damage_low3 + str_offset_dmg);
-		GetInfoStruct()->set_secondary_weapon_damage_high(item->weapon_info->damage_high3 + str_offset_dmg);
+		GetInfoStruct()->set_secondary_weapon_damage_low(selected_low_dmg + str_offset_dmg);
+		GetInfoStruct()->set_secondary_weapon_damage_high(selected_high_dmg + str_offset_dmg);
 		GetInfoStruct()->set_secondary_weapon_type(item->GetWeaponType());
 	}
 	else{
@@ -1563,6 +1627,8 @@ void Entity::CalculateBonuses(){
 	
 	CalculateApplyWeight();
 	
+	UpdateWeapons();
+	
 	safe_delete(values);
 }
 

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

@@ -287,6 +287,7 @@ struct InfoStruct{
 		group_lock_method_ = 0;
 		group_solo_autolock_ = 0;
 		group_auto_loot_method_ = 0;
+		assist_auto_attack_ = 0;
 		
 		action_state_ = std::string("");
 	}
@@ -697,6 +698,7 @@ struct InfoStruct{
 	int8	get_group_lock_method() { std::lock_guard<std::mutex> lk(classMutex); return group_lock_method_; }
 	int8	get_group_solo_autolock() { std::lock_guard<std::mutex> lk(classMutex); return group_solo_autolock_; }
 	int8	get_group_auto_loot_method() { std::lock_guard<std::mutex> lk(classMutex); return group_auto_loot_method_; }
+	int8	get_assist_auto_attack() { std::lock_guard<std::mutex> lk(classMutex); return assist_auto_attack_; }
 	
 	std::string get_action_state() { std::lock_guard<std::mutex> lk(classMutex); return action_state_; }
 	
@@ -1002,6 +1004,8 @@ struct InfoStruct{
 	void	set_group_lock_method(int8 value) { std::lock_guard<std::mutex> lk(classMutex); group_lock_method_ = value;  }
 	void	set_group_solo_autolock(int8 value) { std::lock_guard<std::mutex> lk(classMutex); group_solo_autolock_ = value; }
 	void	set_group_auto_loot_method(int8 value) { std::lock_guard<std::mutex> lk(classMutex); group_auto_loot_method_ = value; }
+	
+	void	set_assist_auto_attack(int8 value) { std::lock_guard<std::mutex> lk(classMutex); assist_auto_attack_ = value; }
 
 	void	set_action_state(std::string value) { std::lock_guard<std::mutex> lk(classMutex); action_state_ = value; }
 	
@@ -1223,6 +1227,8 @@ private:
 	int8			group_solo_autolock_;
 	int8			group_auto_loot_method_;
 	
+	int8			assist_auto_attack_;
+	
 	std::string		action_state_;
 	std::string		combat_action_state_;
 	
@@ -1323,6 +1329,7 @@ struct ThreatTransfer {
 #define IMMUNITY_TYPE_AOE 7
 #define IMMUNITY_TYPE_TAUNT 8
 #define IMMUNITY_TYPE_RIPOSTE 9
+#define IMMUNITY_TYPE_STRIKETHROUGH 10
 
 //class Spell;
 //class ZoneServer;
@@ -1454,6 +1461,8 @@ public:
 	bool	IsDualWield();
 	bool	BehindTarget(Spawn* target);
 	bool	FlankingTarget(Spawn* target);
+	
+	void	GetWeaponDamage(Item* item, int32* low_damage, int32* high_damage);
 	void	ChangePrimaryWeapon();
 	void	ChangeSecondaryWeapon();
 	void	ChangeRangedWeapon();

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

@@ -849,6 +849,16 @@ ItemStatsValues* MasterItemList::CalculateItemBonuses(Item* item, Entity* entity
 					id = stat->stat_type*multiplier + stat->stat_subtype;
 			}
 
+			if(entity->IsPlayer()) {
+				int32 effective_level = entity->GetInfoStructUInt("effective_level");
+				if(effective_level && effective_level < entity->GetLevel() && item->details.recommended_level > effective_level)
+				{
+					int32 diff = item->details.recommended_level - effective_level;
+					float tmpValue = (float)value;
+					value = (sint32)(float)(tmpValue / (1.0f + ((float)diff * rule_manager.GetGlobalRule(R_Player, MentorItemDecayRate)->GetFloat())));
+				}
+			}
+			
 			world.AddBonuses(item, values, id, value, entity);
 		}
 		return values;

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

@@ -13335,7 +13335,7 @@ int EQ2Emu_lua_StopMovement(lua_State* state) {
 		return 0;
 	Spawn* spawn = lua_interface->GetSpawn(state);
 	if (spawn) {
-		spawn->StopMovement();
+		spawn->ResetMovement();
 	}
 	lua_interface->ResetFunctionStack(state);
 	return 0;

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

@@ -1742,8 +1742,31 @@ bool Player::CanEquipItem(Item* item, int8 slot) {
 					else
 						client->Message(CHANNEL_COLOR_RED, "Your class may not equip %s.", item->CreateItemLink(client->GetVersion()).c_str());
 				}
-				else
-					client->SimpleMessage(0, "You lack the skill required to equip this item.");
+				else {
+					Skill* firstSkill = master_skill_list.GetSkill(item->generic_info.skill_req1);
+					Skill* secondSkill = master_skill_list.GetSkill(item->generic_info.skill_req2);
+					std::string msg("");
+					if(GetClient()->GetAdminStatus() >= 200) {
+						if(firstSkill && !skill_list.HasSkill(item->generic_info.skill_req1)) {
+							msg += "(" + std::string(firstSkill->name.data.c_str());
+						}
+						
+						if(secondSkill && !skill_list.HasSkill(item->generic_info.skill_req2)) {
+							if(msg.length() > 0) {
+								msg += ", ";
+							}
+							else {
+								msg = "(";
+							}
+							msg += std::string(secondSkill->name.data.c_str());
+						}
+						
+						if(msg.length() > 0) {
+							msg += ") ";
+						}
+					}
+					client->Message(0, "You lack the skill %srequired to equip this item.",msg.c_str());
+				}
 			}
 			else
 				client->Message(0, "Item %s isn't equipable.", item->name.c_str());

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

@@ -226,6 +226,7 @@ void RuleManager::Init()
 	RULE_INIT(R_Player, WeightPercentCap, "0.95"); // cap total impact for being overweight (.95 = 95%)
 	RULE_INIT(R_Player, CoinWeightPerHundred, "100.0"); // coin weight per stone, 100.0 = 100 coins per 1 stone
 	RULE_INIT(R_Player, WeightInflictsSpeed, "1"); // whether weight will inflict speed, 1 = on, 0 = off
+	RULE_INIT(R_Player, LevelMasterySkillMultiplier, "5"); // multiplier for adventure level / recommended level when applying mastery damage to determine if they are in mastery range
 	
 	/* PVP */
 	RULE_INIT(R_PVP, AllowPVP, "0");

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

@@ -86,6 +86,7 @@ enum RuleType {
 	WeightPercentCap,
 	CoinWeightPerHundred,
 	WeightInflictsSpeed,
+	LevelMasterySkillMultiplier,
 
 	/* PVP */
 	AllowPVP,

+ 11 - 7
EQ2/source/WorldServer/Spawn.cpp

@@ -147,6 +147,7 @@ Spawn::Spawn(){
 	looter_spawn_id = 0;
 	is_loot_complete = false;
 	is_loot_dispensed = false;
+	reset_movement = false;
 }
 
 Spawn::~Spawn(){
@@ -2847,6 +2848,10 @@ void Spawn::ProcessMovement(bool isSpawnListLocked){
 		}*/
 		return;
 	}
+	if(reset_movement) {
+		ResetMovement();
+		reset_movement = false;
+	}
 
 	if (forceMapCheck && GetZone() != nullptr && GetMap() != nullptr && GetMap()->IsMapLoaded())
 	{
@@ -3113,9 +3118,9 @@ void Spawn::ProcessMovement(bool isSpawnListLocked){
 	}*/
 }
 
-void Spawn::ResetMovement(bool inFlight){
-	if(!inFlight)
-		MMovementLoop.writelock();
+void Spawn::ResetMovement(){
+	MMovementLoop.writelock();
+	
 	vector<MovementData*>::iterator itr;
 	for(itr = movement_loop.begin(); itr != movement_loop.end(); itr++){
 		safe_delete(*itr);
@@ -3125,10 +3130,9 @@ void Spawn::ResetMovement(bool inFlight){
 	resume_movement = true;
 	ClearRunningLocations();
 
-	if(!inFlight)
-		MMovementLoop.releasewritelock();
+	MMovementLoop.releasewritelock();
 	
-	ValidateRunning(true, (inFlight == false));
+	ValidateRunning(true, true);
 }
 
 void Spawn::AddMovementLocation(float x, float y, float z, float speed, int16 delay, const char* lua_function, float heading, bool include_heading){
@@ -4541,7 +4545,7 @@ float Spawn::SpawnAngle(Spawn* target, float selfx, float selfz)
 
 void Spawn::StopMovement()
 {
-	ResetMovement(true);
+	reset_movement = true;
 }
 
 bool Spawn::PauseMovement(int32 period_of_time_ms)

+ 2 - 1
EQ2/source/WorldServer/Spawn.h

@@ -1104,7 +1104,7 @@ public:
 	void	MoveToLocation(Spawn* spawn, float distance, bool immediate = true, bool isMappedLocation = false);
 	void	AddMovementLocation(float x, float y, float z, float speed, int16 delay, const char* lua_function, float heading, bool include_heading = false);
 	void	ProcessMovement(bool isSpawnListLocked=false);
-	void	ResetMovement(bool inFlight=false);
+	void	ResetMovement();
 	bool	ValidateRunning(bool lockMovementLocation, bool lockMovementLoop);
 	bool	IsRunning();
 	void	CalculateRunningLocation(bool stop = false);
@@ -1506,6 +1506,7 @@ private:
 	int32 loot_drop_type;
 
 	std::atomic<bool> deleted_spawn;
+	std::atomic<bool> reset_movement;
 	Mutex m_GridMutex;
 	bool is_collector;
 	bool scared_by_strong_players;

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

@@ -2150,6 +2150,11 @@ bool WorldDatabase::loadCharacterProperties(Client* client) {
 			int8 val = atoul(prop_value);
 			client->GetPlayer()->GetInfoStruct()->set_group_lock_method(val);
 		}
+		else if (!stricmp(prop_name, CHAR_PROPERTY_ASSISTAUTOATTACK))
+		{
+			int8 val = atoul(prop_value);
+			client->GetPlayer()->GetInfoStruct()->set_assist_auto_attack(val);
+		}
 	}
 
 	return true;

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

@@ -122,6 +122,8 @@ using namespace std;
 #define CHAR_PROPERTY_GROUPSOLOAUTOLOCK 	"group_solo_autolock"
 #define CHAR_PROPERTY_AUTOLOOTMETHOD	 	"group_auto_loot_method"
 
+#define CHAR_PROPERTY_ASSISTAUTOATTACK	 	"assist_auto_attack"
+
 #define DB_TYPE_SPELLEFFECTS		1
 #define DB_TYPE_MAINTAINEDEFFECTS	2
 

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

@@ -726,10 +726,6 @@ void Client::SendCharInfo() {
 
 	SendControlGhost(player->GetIDWithPlayerSpawn(player), 255);
 	
-	if (version <= 283) {
-		//le: hack to allow client time to zone in, it gets stuck on Loading UI Resources if we go too fast, need to figure it out.  Probably something it doesnt like with ExamineInfo packets
-		Sleep(2000);
-	}
 	//sending bad spawn packet?
 
 	//SendAchievementsList();

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

@@ -2773,6 +2773,7 @@ void ZoneServer::AddLoot(NPC* npc, Spawn* killer, GroupLootMethod loot_method, i
 							
 							int droplistsize = loot_drops->size();
 							float chancedroptally = 0;
+							bool breakIterMaxLoot = false;
 							chancedrop = static_cast <float> (rand()) / (static_cast <float> (RAND_MAX / 100));
 							for (loot_drop_itr = loot_drops->begin(); loot_drop_itr != loot_drops->end(); loot_drop_itr++) {
 								drop = *loot_drop_itr;
@@ -2825,8 +2826,15 @@ void ZoneServer::AddLoot(NPC* npc, Spawn* killer, GroupLootMethod loot_method, i
 									//if(drop->equip_item) 
 
 								}
-								if (table->maxlootitems > 0 && count >= table->maxlootitems)
+								// so many items on this table break out and cap it!
+								if (table->maxlootitems > 0 && count >= table->maxlootitems) {
+									breakIterMaxLoot = true;
 									break;
+								}
+							}
+							// hit our max item drop for this table already, break out of numberchances
+							if(breakIterMaxLoot) {
+								break;
 							}
 						}
 					}