Browse Source

Code updates for auriga

RULE_INIT(R_Player, MentorItemDecayRate, ".05"); // 5% per level lost when mentoring
mentoring now lowers the item stats although display needs work to display lowered stat value (higher stat value)

reordering some of the mutex locks for info/pos/vis to avoid deadlocking (still a few more to address)
Added StopMovement(Spawn) for NPCs in a movement loop, purges movement loop and stops npc at position
indestructable items supported
appearance items limited to appearance only

makeadmin charname status added to console commands (windows)

fixed "used" to allow removal of an item without crashing the world.  The function can return a value less than 0 to omit from calling RemoveItem or decrementing the charges by code.  Having no return (0) or any return larger than 0 means we will try to code decrement the item if it is still there.
Image 3 years ago
parent
commit
1bd2cbf596

+ 19 - 8
EQ2/source/WorldServer/Commands/Commands.cpp

@@ -1737,15 +1737,26 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 						lua_interface->RunItemScript(item->GetItemScript(), "used", item, player);
 					else {
 						if (item->details.count > 0) {
-							lua_interface->RunItemScript(item->GetItemScript(), "used", item, player);
-							if(!item->generic_info.display_charges && item->generic_info.max_charges == 1)
-								client->RemoveItem(item, 1); // end of a set of charges OR an item that uses a stack count of actual item quantity
-							else
+							std::string itemName = string(item->name);
+							int32 item_id = item->details.item_id;
+							sint64 flags = 0;
+							if(lua_interface->RunItemScript(item->GetItemScript(), "used", item, player, &flags) && flags >= 0)
 							{
-								item->details.count--; // charges
-								item->save_needed = true;
-								client->QueuePacket(item->serialize(client->GetVersion(), false, client->GetPlayer()));
+								//reobtain item make sure it wasn't removed
+								Item* item = player->item_list.GetItemFromIndex(item_index);
+								if(!item)
+									LogWrite(PLAYER__WARNING, 0, "Command", "%s: Item %s (%i) was used, however after the item looks to be removed.", client->GetPlayer()->GetName(), itemName.c_str(), item_id);
+								else if(!item->generic_info.display_charges && item->generic_info.max_charges == 1)
+									client->RemoveItem(item, 1); // end of a set of charges OR an item that uses a stack count of actual item quantity
+								else
+								{
+									item->details.count--; // charges
+									item->save_needed = true;
+									client->QueuePacket(item->serialize(client->GetVersion(), false, client->GetPlayer()));
+								}
 							}
+							else
+									LogWrite(PLAYER__WARNING, 0, "Command", "%s: Item %s (%i) was used, after it returned %i, bypassing any removal/update of items.", client->GetPlayer()->GetName(), itemName.c_str(), item_id, flags);
 						}
 						else
 						{
@@ -9938,7 +9949,7 @@ void Commands::Command_Test(Client* client, EQ2_16BitString* command_parms) {
 			}
 		}
 		else if (atoi(sep->arg[0]) == 28 && sep->IsNumber(1)) {
-			World::newValue = atoi(sep->arg[1]);
+			World::newValue = strtoull(sep->arg[1], NULL, 0);
 		}
 	}
 	else {

+ 32 - 0
EQ2/source/WorldServer/Commands/ConsoleCommands.cpp

@@ -57,6 +57,7 @@ void ProcessConsoleInput(const char * cmdInput)
 		// world system controls
 		{ &ConsoleGuildCommand,			"guild",	"[params]",	"" },
 		{ &ConsolePlayerCommand,		"player",	"[params]", "" },
+		{ &ConsoleSetAdminPlayer,		"makeadmin",	"[charname] [status=0]", "" },
 		{ &ConsoleZoneCommand,			"zone",		"[command][value]",	"command = help to get help" },
 		{ &ConsoleWorldCommand,			"world",	"[params]", "" },
 		{ &ConsoleGetMOTDCommand,		"getmotd",	"",	"Display current MOTD" },
@@ -220,6 +221,37 @@ bool ConsolePlayerCommand(Seperator *sep)
 	return true;
 }
 
+bool ConsoleSetAdminPlayer(Seperator *sep)
+{
+	if(!sep->arg[1] ||  strlen(sep->arg[1]) == 0)
+		return false;
+
+	sint16 status = 0;
+	if(sep->IsNumber(2))
+		status = atoi(sep->arg[2]);
+	
+	Client* client = zone_list.GetClientByCharName(sep->arg[1]);
+	
+	if(!client) {
+		printf("Client not found by char name, must be logged in\n");
+		return true;
+	}
+
+	if(!client->GetPlayer()) {
+
+		printf("Player is not available for client class, try again\n");
+		return true;
+	}
+
+	client->SetAdminStatus(status);
+	if(status)
+		client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Admin status updated.");
+	database.UpdateAdminStatus(client->GetPlayer()->GetName(), status);
+	printf("Admin status for %s is updated to %i\n", client->GetPlayer()->GetName(), status);
+	
+	return true;
+}
+
 bool ConsoleWorldCommand(Seperator *sep)
 {
 	if( strlen(sep->arg[1]) == 0 ) 

+ 1 - 0
EQ2/source/WorldServer/Commands/ConsoleCommands.h

@@ -45,6 +45,7 @@ struct ConsoleCommand
 
 	bool ConsoleGuildCommand(Seperator *sep);
 	bool ConsolePlayerCommand(Seperator *sep);
+	bool ConsoleSetAdminPlayer(Seperator *sep);
 	bool ConsoleWorldCommand(Seperator *sep);
 	bool ConsoleZoneCommand(Seperator *sep);
 	bool ConsoleGetMOTDCommand(Seperator *sep);

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

@@ -1532,7 +1532,7 @@ void Entity::CalculateSpellBonuses(ItemStatsValues* stats){
 			//We've found the highest tier for this spell id, so add the bonuses
 			vector<BonusValues*>* final_bonuses = &sort_itr->second[key];
 			for (int8 i = 0; i < final_bonuses->size(); i++)
-				world.AddBonuses(stats, final_bonuses->at(i)->type, final_bonuses->at(i)->value, this);
+				world.AddBonuses(nullptr, stats, final_bonuses->at(i)->type, final_bonuses->at(i)->value, this);
 		}
 	}
 }

+ 62 - 18
EQ2/source/WorldServer/Items/Items.cpp

@@ -792,7 +792,7 @@ ItemStatsValues* MasterItemList::CalculateItemBonuses(Item* item, Entity* entity
 					id = stat->stat_type*multiplier + stat->stat_subtype;
 			}
 
-			world.AddBonuses(values, id, stat->value, entity);
+			world.AddBonuses(item, values, id, stat->value, entity);
 		}
 		return values;
 	}
@@ -1445,18 +1445,18 @@ bool Item::CheckFlag2(int32 flag){
 	while (flag_val > 0){
 		if (flag_val >= FLAGS2_32768) 
 			value = FLAGS2_32768;
-		else if (flag_val >= FLAGS2_16384)
-			value = FLAGS2_16384;
-		else if (flag_val >= FLAGS2_8192)
-			value = FLAGS2_8192;
+		else if (flag_val >= FREE_REFORGE)
+			value = FREE_REFORGE;
+		else if (flag_val >= BUILDING_BLOCK)
+			value = BUILDING_BLOCK;
 		else if (flag_val >= FLAGS2_4096)
 			value = FLAGS2_4096;
-		else if (flag_val >= FLAGS2_2048)
-			value = FLAGS2_2048;
-		else if (flag_val >= FLAGS2_1024)
-			value = FLAGS2_1024;
-		else if (flag_val >= FLAGS2_512)
-			value = FLAGS2_512;
+		else if (flag_val >= HOUSE_LORE)
+			value = HOUSE_LORE;
+		else if (flag_val >= NO_EXPERIMENT)
+			value = NO_EXPERIMENT;
+		else if (flag_val >= INDESTRUCTABLE)
+			value = INDESTRUCTABLE;
 		else if (flag_val >= NO_SALVAGE)
 			value = NO_SALVAGE;
 		else if (flag_val >= REFINED)
@@ -1673,6 +1673,16 @@ void Item::serialize(PacketStruct* packet, bool show_name, Player* player, int16
 	else
 		packet->setSubstructSubstructDataByName("header", "info_header", "packettype", packet_type);
 	packet->setSubstructSubstructDataByName("header", "info_header", "packetsubtype", subtype);  // should be substype
+
+	/*
+0 red
+1 orange
+2 yellow
+3 white
+4 blue
+5 green
+6 grey
+7 purple*/
 	packet->setSubstructDataByName("header_info", "footer_type", 3);
 	packet->setSubstructDataByName("header_info", "item_id", details.item_id);
 	
@@ -1695,6 +1705,13 @@ void Item::serialize(PacketStruct* packet, bool show_name, Player* player, int16
 		int8 bluemod = 0;
 		for (int32 i = 0; i < item_stats.size(); i++){
 			ItemStat* stat = item_stats[i];
+
+			if(!stat)
+			{
+				LogWrite(ITEM__ERROR, 0, "Item", "%s: %s (itemid: %u) Error Serializing Item: Invalid item in item_stats position %u", client->GetPlayer()->GetName(), this->name.c_str(), this->details.item_id, i);
+				continue;
+			}
+			
 			if (stat->stat_type == 9){
 				bluemod += 1;
 			}
@@ -1769,30 +1786,41 @@ void Item::serialize(PacketStruct* packet, bool show_name, Player* player, int16
 			if (stat->stat_name.length() > 0)
 				packet->setArrayDataByName("stat_name", stat->stat_name.c_str(), i-dropstat);
 			/* SF client */
-			
+			float statValue = stat->value;
+			if(player)
+			{
+				int32 effective_level = player->GetInfoStructUInt("effective_level");
+				if(effective_level && effective_level < player->GetLevel() && details.recommended_level > effective_level)
+				{
+					int32 diff = details.recommended_level - effective_level;
+					float tmpValue = (float)statValue;
+					statValue = (sint32)(float)(tmpValue / (1.0f + ((float)diff * rule_manager.GetGlobalRule(R_Player, MentorItemDecayRate)->GetFloat())));
+				}
+			}
+
 			if ((client->GetVersion() >= 63119) || client->GetVersion() == 61331) {
 				if (stat->stat_type == 6){
-					packet->setArrayDataByName("value", stat->value , i - dropstat);//63119 or when diety started (this is actually the modified stat
+					packet->setArrayDataByName("value", statValue , i - dropstat);//63119 or when diety started (this is actually the modified stat
 					packet->setArrayDataByName("value2", stat->value, i - dropstat);//63119 temp will be replace by modified value (this is the unmodified stat
 				}
 				else	{
-					packet->setArrayDataByName("value", (sint16)stat->value , i - dropstat, 0U, true);
+					packet->setArrayDataByName("value", (sint16)statValue , i - dropstat, 0U, true);
 					packet->setArrayDataByName("value2", stat->value, i - dropstat);//63119 temp will be replace by modified value
 				}
 			}
 			 else if (client->GetVersion() >= 1028) {
 				if (stat->stat_type == 6){
-					packet->setArrayDataByName("value", stat->value , i - dropstat);//63119 or when diety started (this is actually the infused modified stat
+					packet->setArrayDataByName("value", statValue , i - dropstat);//63119 or when diety started (this is actually the infused modified stat
 					packet->setArrayDataByName("value2", stat->value, i - dropstat);//63119 temp will be replace by modified value (this is the unmodified stat
 				}
 				else {
-					packet->setArrayDataByName("value", (sint16)stat->value , i - dropstat, 0U, true);
+					packet->setArrayDataByName("value", (sint16)statValue , i - dropstat, 0U, true);
 					packet->setArrayDataByName("value2", stat->value, i - dropstat);//63119 temp will be replace by modified value
 				}
 				
 			}
 			else{
-				packet->setArrayDataByName("value", (sint16)stat->value , i - dropstat);
+				packet->setArrayDataByName("value", (sint16)statValue , i - dropstat);
 				packet->setArrayDataByName("value2", stat->value, i - dropstat);//63119 temp will be replace by modified value
 			}
 		}
@@ -3360,7 +3388,7 @@ void PlayerItemList::AddItemToPacket(PacketStruct* packet, Player* player, Item*
 		menu_data += ITEM_MENU_TYPE_DISPLAY_CHARGES;
 		menu_data += ITEM_MENU_TYPE_TEST1;
 		menu_data += ITEM_MENU_TYPE_NAMEPET;
-		menu_data += ITEM_MENU_TYPE_TEST2;
+		menu_data += ITEM_MENU_TYPE_MENTORED;
 		menu_data += ITEM_MENU_TYPE_CONSUME;
 		menu_data += ITEM_MENU_TYPE_USE;
 	}
@@ -3786,6 +3814,16 @@ Item* EquipmentItemList::GetItem(int8 slot_id){
 	return items[slot_id];
 }
 
+void EquipmentItemList::SendEquippedItems(Player* player){
+	if(!player->GetClient())
+		return;
+		for(int16 i=0;i<NUM_SLOTS;i++){
+			Item* item = items[i];
+			if(item && item->details.item_id > 0)
+				player->GetClient()->QueuePacket(item->serialize(player->GetClient()->GetVersion(), false, player));
+		}
+}
+
 EQ2Packet* EquipmentItemList::serialize(int16 version, Player* player){
 	EQ2Packet* app = 0;
 	Item* item = 0;
@@ -3807,6 +3845,10 @@ EQ2Packet* EquipmentItemList::serialize(int16 version, Player* player){
 		}
 		MEquipmentItems.lock();
 		int32 menu_data = 3;
+		int32 effective_level = player->GetInfoStructUInt("effective_level");
+
+		int32 levelsLowered = (effective_level > 0 && effective_level < player->GetLevel()) ? player->GetLevel() - effective_level : 0;
+
 		for(int16 i=0;i<NUM_SLOTS;i++){
 			menu_data = 3;
 			item = items[i];
@@ -3868,6 +3910,8 @@ EQ2Packet* EquipmentItemList::serialize(int16 version, Player* player){
 					else
 						menu_data += ITEM_MENU_TYPE_DISPLAY_CHARGES;
 				}
+				if(levelsLowered && item->details.recommended_level > effective_level)
+					menu_data += ITEM_MENU_TYPE_MENTORED;
 				packet->setSubstructArrayDataByName("items", "menu_type", menu_data, 0, i);
 				packet->setSubstructArrayDataByName("items", "icon", item->details.icon, 0, i);
 				packet->setSubstructArrayDataByName("items", "slot_id", item->details.slot_id, 0, i);

+ 13 - 9
EQ2/source/WorldServer/Items/Items.h

@@ -128,6 +128,8 @@ extern MasterItemList master_item_list;
 #define BANK_SLOT6 1500
 #define BANK_SLOT7 1600
 #define BANK_SLOT8 1700
+
+// FLAGS
 #define ATTUNED			1
 #define ATTUNEABLE		2
 #define ARTIFACT		4
@@ -145,6 +147,7 @@ extern MasterItemList master_item_list;
 #define NO_TRANSMUTE	16384
 #define CURSED			32768
 
+// FLAGS2
 #define ORNATE			1
 #define HEIRLOOM		2
 #define APPEARANCE_ONLY	4
@@ -153,14 +156,14 @@ extern MasterItemList master_item_list;
 #define NO_REPAIR		32
 #define ETHERAL			64
 #define REFINED			128
-#define NO_SALVAGE		256 //not used at this time
-#define FLAGS2_512		512//not used at this time
-#define FLAGS2_1024		1024//not used at this time
-#define FLAGS2_2048		2048//not used at this time
-#define FLAGS2_4096		4096//not used at this time
-#define FLAGS2_8192		8192//not used at this time
-#define FLAGS2_16384	16384//not used at this time
-#define FLAGS2_32768	32768//not used at this time
+#define NO_SALVAGE		256
+#define INDESTRUCTABLE	512
+#define NO_EXPERIMENT	1024
+#define HOUSE_LORE		2048
+#define FLAGS2_4096		4096//AoM: not used at this time
+#define BUILDING_BLOCK	8192
+#define FREE_REFORGE	16384
+#define FLAGS2_32768	32768//AoM: not used at this time
 
 
 #define ITEM_WIELD_TYPE_DUAL		1
@@ -217,7 +220,7 @@ extern MasterItemList master_item_list;
 #define ITEM_MENU_TYPE_DISPLAY_CHARGES  16384//14
 #define ITEM_MENU_TYPE_TEST1			32768//15 Possibly toogle decorator mode
 #define ITEM_MENU_TYPE_NAMEPET		    65536 //16 Right CLick Menu
-#define ITEM_MENU_TYPE_TEST2			131072 //sets a purple background on item
+#define ITEM_MENU_TYPE_MENTORED			131072 //sets a purple background on item
 #define ITEM_MENU_TYPE_CONSUME			262144//18
 #define ITEM_MENU_TYPE_USE			    524288//19
 #define ITEM_MENU_TYPE_CONSUME_OFF		1048576//20
@@ -1112,6 +1115,7 @@ public:
 	int8	GetSlotByItem(Item* item);
 	ItemStatsValues*	CalculateEquipmentBonuses(Entity* entity = 0);
 	EQ2Packet* serialize(int16 version, Player* player);
+	void SendEquippedItems(Player* player);
 	uchar* xor_packet;
 	uchar* orig_packet;
 

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

@@ -229,6 +229,20 @@ void WorldDatabase::LoadDataFromRow(DatabaseResult* result, Item* item)
 	if (result->GetInt8Str("no_salvage") == 1)
 		item->generic_info.item_flags2 += NO_SALVAGE;
 
+	if (result->GetInt8Str("indestructable") == 1)
+		item->generic_info.item_flags2 += INDESTRUCTABLE;
+		
+	if (result->GetInt8Str("no_experiment") == 1)
+		item->generic_info.item_flags2 += NO_EXPERIMENT;
+
+	if (result->GetInt8Str("house_lore") == 1)
+		item->generic_info.item_flags2 += HOUSE_LORE;
+
+	if (result->GetInt8Str("building_block") == 1)
+		item->generic_info.item_flags2 += BUILDING_BLOCK;
+
+	if (result->GetInt8Str("free_reforge") == 1)
+		item->generic_info.item_flags2 += FREE_REFORGE;
 	
 
 	if( result->GetInt32Str("skill_id_req") == 0 )

+ 13 - 2
EQ2/source/WorldServer/LuaFunctions.cpp

@@ -1247,7 +1247,7 @@ int EQ2Emu_lua_SpellHeal(lua_State* state) {
 			if (((Entity*)caster)->SpellHeal(target, distance, luaspell, heal_type, min_heal, max_heal, crit_mod, no_calcs, custom_spell_name))
 				success = true;
 		}
-		if (luaspell->targets.size() > 0) {
+		if ((!success || luaspell->spell->GetSpellData()->group_spell) && luaspell->targets.size() > 0) {
 			Spawn* target = 0;
 			ZoneServer* zone = luaspell->caster->GetZone();
 			luaspell->MSpellTargets.readlock(__FUNCTION__, __LINE__);
@@ -1338,7 +1338,7 @@ int EQ2Emu_lua_SpellHealPct(lua_State* state) {
 			if (((Entity*)caster)->SpellHeal(target, distance, luaspell, heal_type, min_heal, max_heal, crit_mod, no_calcs, custom_spell_name))
 				success = true;
 		}
-		if (luaspell->targets.size() > 0) {
+		if ((!success || luaspell->spell->GetSpellData()->group_spell) && luaspell->targets.size() > 0) {
 			Spawn* target = 0;
 			ZoneServer* zone = luaspell->caster->GetZone();
 			luaspell->MSpellTargets.readlock(__FUNCTION__, __LINE__);
@@ -12114,6 +12114,17 @@ int EQ2Emu_lua_PauseMovement(lua_State* state) {
 	return 0;
 }
 
+int EQ2Emu_lua_StopMovement(lua_State* state) {
+	if (!lua_interface)
+		return 0;
+	Spawn* spawn = lua_interface->GetSpawn(state);
+	if (spawn) {
+		spawn->StopMovement();
+	}
+	lua_interface->ResetFunctionStack(state);
+	return 0;
+}
+
 int EQ2Emu_lua_GetArrowColor(lua_State* state) {
 	Player* player = (Player*)lua_interface->GetSpawn(state);
 	int8 level = lua_interface->GetInt8Value(state, 2);

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

@@ -577,6 +577,7 @@ int EQ2Emu_lua_GetShardCreatedTimestamp(lua_State* state);
 int EQ2Emu_lua_DeleteDBShardID(lua_State* state);
 
 int EQ2Emu_lua_PauseMovement(lua_State* state);
+int EQ2Emu_lua_StopMovement(lua_State* state);
 
 int EQ2Emu_lua_GetArrowColor(lua_State* state);
 int EQ2Emu_lua_GetTSArrowColor(lua_State* state);

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

@@ -1408,6 +1408,7 @@ void LuaInterface::RegisterFunctions(lua_State* state) {
 	lua_register(state, "DeleteDBShardID", EQ2Emu_lua_DeleteDBShardID);
 	
 	lua_register(state, "PauseMovement", EQ2Emu_lua_PauseMovement);
+	lua_register(state, "StopMovement", EQ2Emu_lua_StopMovement);
 	
 	lua_register(state, "GetArrowColor", EQ2Emu_lua_GetArrowColor);
 	lua_register(state, "GetTSArrowColor", EQ2Emu_lua_GetTSArrowColor);

+ 26 - 11
EQ2/source/WorldServer/Player.cpp

@@ -1716,7 +1716,8 @@ bool Player::DamageEquippedItems(int8 amount, Client* client) {
 		item = equipment_list.items[i];
 		if(item) {
 			item_type = item->generic_info.item_type;
-			if (item->details.item_id > 0 && item_type != ITEM_TYPE_FOOD && item_type != ITEM_TYPE_BAUBLE && item_type != ITEM_TYPE_THROWN){
+			if (item->details.item_id > 0 && item_type != ITEM_TYPE_FOOD && item_type != ITEM_TYPE_BAUBLE && item_type != ITEM_TYPE_THROWN && 
+			!item->CheckFlag2(INDESTRUCTABLE)){
 				ret = true;
 				if((item->generic_info.condition - amount) > 0)
 					item->generic_info.condition -= amount;
@@ -2100,7 +2101,12 @@ vector<EQ2Packet*> Player::EquipItem(int16 index, int16 version, int8 appearance
 			return packets;
 		int8 slot = equipList->GetFreeSlot(item, slot_id);
 		bool canEquip = CanEquipItem(item);
-		if (canEquip && item->CheckFlag(ATTUNEABLE)) {
+		if(canEquip && !appearance_type && item->CheckFlag2(APPEARANCE_ONLY))
+		{
+			GetClient()->SimpleMessage(CHANNEL_COLOR_RED, "This item is for appearance slots only.");
+			return packets;
+		}
+		else if (canEquip && item->CheckFlag(ATTUNEABLE)) {
 			PacketStruct* packet = configReader.getStruct("WS_ChoiceWindow", version);
 			char text[255];
 			sprintf(text, "%s must be attuned before it can be equipped. Would you like to attune it now?", item->name.c_str());
@@ -3193,14 +3199,14 @@ void Player::ClearEverything(){
 	player_spawn_history_required.clear();
 	m_playerSpawnHistoryRequired.releasewritelock(__FUNCTION__, __LINE__);
 
-	vis_mutex.writelock(__FUNCTION__, __LINE__);
-	spawn_vis_packet_list.clear();
-	vis_mutex.releasewritelock(__FUNCTION__, __LINE__);
-
 	info_mutex.writelock(__FUNCTION__, __LINE__);
 	spawn_info_packet_list.clear();
 	info_mutex.releasewritelock(__FUNCTION__, __LINE__);
 
+	vis_mutex.writelock(__FUNCTION__, __LINE__);
+	spawn_vis_packet_list.clear();
+	vis_mutex.releasewritelock(__FUNCTION__, __LINE__);
+
 	pos_mutex.writelock(__FUNCTION__, __LINE__);
 	spawn_pos_packet_list.clear();
 	pos_mutex.releasewritelock(__FUNCTION__, __LINE__);
@@ -4178,8 +4184,8 @@ void Player::RemoveSpawn(Spawn* spawn)
 	LogWrite(PLAYER__DEBUG, 3, "Player", "Remove Spawn '%s' (%u)", spawn->GetName(), spawn->GetID());
 
 	info_mutex.writelock(__FUNCTION__, __LINE__);
-	pos_mutex.writelock(__FUNCTION__, __LINE__);
 	vis_mutex.writelock(__FUNCTION__, __LINE__);
+	pos_mutex.writelock(__FUNCTION__, __LINE__);
 
 	index_mutex.writelock(__FUNCTION__, __LINE__);
 
@@ -5272,14 +5278,14 @@ void Player::ResetRemovedSpawns(){
 void Player::ResetSavedSpawns(){
 	ResetRemovedSpawns();
 
-	vis_mutex.writelock(__FUNCTION__, __LINE__);
-	spawn_vis_packet_list.clear();
-	vis_mutex.releasewritelock(__FUNCTION__, __LINE__);
-
 	info_mutex.writelock(__FUNCTION__, __LINE__);
 	spawn_info_packet_list.clear();
 	info_mutex.releasewritelock(__FUNCTION__, __LINE__);
 
+	vis_mutex.writelock(__FUNCTION__, __LINE__);
+	spawn_vis_packet_list.clear();
+	vis_mutex.releasewritelock(__FUNCTION__, __LINE__);
+
 	pos_mutex.writelock(__FUNCTION__, __LINE__);
 	spawn_pos_packet_list.clear();
 	pos_mutex.releasewritelock(__FUNCTION__, __LINE__);
@@ -6677,4 +6683,13 @@ void Player::SetMentorStats(int32 effective_level, int32 target_char_id)
 	info->set_magic_base((int16)(effective_level * 1.5 + 10));
 	info->set_divine_base((int16)(effective_level * 1.5 + 10));
 	info->set_poison_base((int16)(effective_level * 1.5 + 10));
+	GetClient()->ClearSentItemDetails();
+	if(GetClient())
+	{
+		EQ2Packet* app = GetEquipmentList()->serialize(GetClient()->GetVersion(), this);
+		if (app) {
+			GetClient()->QueuePacket(app);
+		}
+	}
+	GetEquipmentList()->SendEquippedItems(this);
 }

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

@@ -211,6 +211,7 @@ void RuleManager::Init()
 	RULE_INIT(R_Player, MaxLastNameLength, "20");
 	RULE_INIT(R_Player, MinLastNameLength, "4");
 	RULE_INIT(R_Player, DisableHouseAlignmentRequirement, "1");
+	RULE_INIT(R_Player, MentorItemDecayRate, ".05"); // 5% per level lost when mentoring
 
 	/* PVP */
 	RULE_INIT(R_PVP, AllowPVP, "0");

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

@@ -71,6 +71,7 @@ enum RuleType {
 	MaxLastNameLength,
 	MinLastNameLength,
 	DisableHouseAlignmentRequirement,
+	MentorItemDecayRate,
 
 	/* PVP */
 	AllowPVP,

+ 79 - 64
EQ2/source/WorldServer/Spawn.cpp

@@ -458,15 +458,16 @@ EQ2Packet* Spawn::spawn_serialize(Player* player, int16 version, int16 offset, i
 	PacketStruct* info_struct = player->GetSpawnInfoStruct();
 	PacketStruct* pos_struct = player->GetSpawnPosStruct();
 
+	player->info_mutex.writelock(__FUNCTION__, __LINE__);
 	player->vis_mutex.writelock(__FUNCTION__, __LINE__);
-	vis_struct->ResetData();
-	InitializeVisPacketData(player, vis_struct);
+	player->pos_mutex.writelock(__FUNCTION__, __LINE__);
 
-	player->info_mutex.writelock(__FUNCTION__, __LINE__);
 	info_struct->ResetData();
 	InitializeInfoPacketData(player, info_struct);
+	
+	vis_struct->ResetData();
+	InitializeVisPacketData(player, vis_struct);
 
-	player->pos_mutex.writelock(__FUNCTION__, __LINE__);
 	pos_struct->ResetData();
 	InitializePosPacketData(player, pos_struct);
 
@@ -2810,7 +2811,7 @@ void Spawn::ProcessMovement(bool isSpawnListLocked){
 	bool movementCase = false;
 	// Movement loop is only for scripted paths
 	if(!EngagedInCombat() && !IsPauseMovementTimerActive() && !NeedsToResumeMovement() && (!IsNPC() || !((NPC*)this)->m_runningBack)){
-		MMovementLoop.lock();
+		MMovementLoop.writelock();
 		if(movement_loop.size() > 0 && movement_index < movement_loop.size())
 		{
 			movementCase = true;
@@ -2828,24 +2829,27 @@ void Spawn::ProcessMovement(bool isSpawnListLocked){
 
 				data = movement_loop[movement_index];
 				
-				((Entity*)this)->SetSpeed(data->speed);
-				SetSpeed(data->speed);
-				if(!IsWidget())
-					FaceTarget(data->x, data->z);
-				// 0 delay at target location, need to set multiple locations
-				if(data->delay == 0 && movement_loop.size() > 0) {
-					int16 tmp_index = movement_index+1;
-					MovementData* data2 = 0;
-					if(tmp_index < movement_loop.size()) 
-						data2 = movement_loop[tmp_index];
+				if(data)
+				{
+					((Entity*)this)->SetSpeed(data->speed);
+					SetSpeed(data->speed);
+					if(!IsWidget())
+						FaceTarget(data->x, data->z);
+					// 0 delay at target location, need to set multiple locations
+					if(data->delay == 0 && movement_loop.size() > 0) {
+						int16 tmp_index = movement_index+1;
+						MovementData* data2 = 0;
+						if(tmp_index < movement_loop.size()) 
+							data2 = movement_loop[tmp_index];
+						else
+							data2 = movement_loop[0];
+						AddRunningLocation(data->x, data->y, data->z, data->speed, 0, true, false, "", true);				
+						AddRunningLocation(data2->x, data2->y, data2->z, data2->speed, 0, true, true, "", true);
+					}
+					// delay at target location, only need to set 1 location
 					else
-						data2 = movement_loop[0];
-					AddRunningLocation(data->x, data->y, data->z, data->speed, 0, true, false, "", true);				
-					AddRunningLocation(data2->x, data2->y, data2->z, data2->speed, 0, true, true, "", true);
+						AddRunningLocation(data->x, data->y, data->z, data->speed);
 				}
-				// delay at target location, only need to set 1 location
-				else
-					AddRunningLocation(data->x, data->y, data->z, data->speed);
 				movement_start_time = 0;
 				resume_movement = false;
 			}
@@ -2856,7 +2860,7 @@ void Spawn::ProcessMovement(bool isSpawnListLocked){
 					RemoveRunningLocation();
 
 				// If this waypoint has a delay and we just arrived here (movement_start_time == 0)
-				if(data->delay > 0 && movement_start_time == 0){
+				if(data && data->delay > 0 && movement_start_time == 0){
 					// Set the current time
 					movement_start_time = Timer::GetCurrentTime2();
 					// If this waypoint had a lua function then call it
@@ -2875,49 +2879,53 @@ void Spawn::ProcessMovement(bool isSpawnListLocked){
 					FaceTarget(data->x, data->z);
 				}
 				// If this waypoint has no delay or we have waited the required time (current time >= delay + movement_start_time)
-				else if(data->delay == 0 || (data->delay > 0 && Timer::GetCurrentTime2() >= (data->delay+movement_start_time))) {
+				else if(data && data->delay == 0 || (data && data->delay > 0 && Timer::GetCurrentTime2() >= (data->delay+movement_start_time))) {
 					// if no delay at this waypoint but a lua function for it then call the function
 					if(data->delay == 0 && data->lua_function.length() > 0)
 						GetZone()->CallSpawnScript(this, SPAWN_SCRIPT_CUSTOM, 0, data->lua_function.c_str());
-					// Advance the current movement loop index
-					if((int16)(movement_index+1) < movement_loop.size())
-						movement_index++;
-					else
-						movement_index = 0;
-					// Get the next target location
-					data = movement_loop[movement_index];
-					// set the speed for that location
-					SetSpeed(data->speed);
-
-					if(!IsWidget())
-					// turn towards the location
-						FaceTarget(data->x, data->z);
-					// If 0 delay at location get and set data for the point after it
-					if(data->delay == 0 && movement_loop.size() > 0){
-						while (movement_locations->size()){
-							safe_delete(movement_locations->front());
-							movement_locations->pop_front();
+					// since we ran a lua function make sure the movement loop is still alive and accurate
+					if(movement_loop.size() > 0)
+					{
+						// Advance the current movement loop index
+						if((int16)(movement_index+1) < movement_loop.size())
+							movement_index++;
+						else
+							movement_index = 0;
+						// Get the next target location
+						data = movement_loop[movement_index];
+						// set the speed for that location
+						SetSpeed(data->speed);
+
+						if(!IsWidget())
+						// turn towards the location
+							FaceTarget(data->x, data->z);
+						// If 0 delay at location get and set data for the point after it
+						if(data->delay == 0 && movement_loop.size() > 0){
+							while (movement_locations->size()){
+								safe_delete(movement_locations->front());
+								movement_locations->pop_front();
+							}
+							// clear current target locations
+							movement_locations->clear();
+							// get the data for the location after out new location
+							int16 tmp_index = movement_index+1;
+							MovementData* data2 = 0;
+							if(tmp_index < movement_loop.size()) 
+								data2 = movement_loop[tmp_index];
+							else
+								data2 = movement_loop[0];
+							// set the first location (adds it to movement_locations that we just cleared)
+							AddRunningLocation(data->x, data->y, data->z, data->speed, 0, true, false, "", true);
+							// set the location after that
+							AddRunningLocation(data2->x, data2->y, data2->z, data2->speed, 0, true, true, "", true);
 						}
-						// clear current target locations
-						movement_locations->clear();
-						// get the data for the location after out new location
-						int16 tmp_index = movement_index+1;
-						MovementData* data2 = 0;
-						if(tmp_index < movement_loop.size()) 
-							data2 = movement_loop[tmp_index];
+						// there is a delay at the next location so we only need to set it
 						else
-							data2 = movement_loop[0];
-						// set the first location (adds it to movement_locations that we just cleared)
-						AddRunningLocation(data->x, data->y, data->z, data->speed, 0, true, false, "", true);
-						// set the location after that
-						AddRunningLocation(data2->x, data2->y, data2->z, data2->speed, 0, true, true, "", true);
-					}
-					// there is a delay at the next location so we only need to set it
-					else
-						AddRunningLocation(data->x, data->y, data->z, data->speed);
+							AddRunningLocation(data->x, data->y, data->z, data->speed);
 
-					// reset this timer to 0 now that we are moving again
-					movement_start_time = 0;
+						// reset this timer to 0 now that we are moving again
+						movement_start_time = 0;
+					}
 				}
 			}
 			// moving and not at target location yet
@@ -2929,7 +2937,7 @@ void Spawn::ProcessMovement(bool isSpawnListLocked){
 				AddRunningLocation(data->x, data->y, data->z, data->speed);
 			}
 		}
-		MMovementLoop.unlock();
+		MMovementLoop.releasewritelock();
 	}
 	
 	if (!movementCase && IsRunning() && !IsPauseMovementTimerActive()) {
@@ -2944,16 +2952,18 @@ void Spawn::ProcessMovement(bool isSpawnListLocked){
 	}*/
 }
 
-void Spawn::ResetMovement(){
-	MMovementLoop.lock();
+void Spawn::ResetMovement(bool inFlight){
+	if(!inFlight)
+		MMovementLoop.writelock();
 	vector<MovementData*>::iterator itr;
 	for(itr = movement_loop.begin(); itr != movement_loop.end(); itr++){
 		safe_delete(*itr);
 	}
 	movement_loop.clear();
-	MMovementLoop.unlock();
-	resume_movement = true;
 	movement_index = 0;
+	resume_movement = true;
+	if(!inFlight)
+		MMovementLoop.releasewritelock();
 }
 
 void Spawn::AddMovementLocation(float x, float y, float z, float speed, int16 delay, const char* lua_function){
@@ -4033,6 +4043,11 @@ float Spawn::SpawnAngle(Spawn* target, float selfx, float selfz)
 	return angle;
 }
 
+void Spawn::StopMovement()
+{
+	ResetMovement(true);
+}
+
 bool Spawn::PauseMovement(int32 period_of_time_ms)
 {
 	if(period_of_time_ms < 1)

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

@@ -1013,7 +1013,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);
 	void	ProcessMovement(bool isSpawnListLocked=false);
-	void	ResetMovement();
+	void	ResetMovement(bool inFlight=false);
 	bool	IsRunning();
 	void	CalculateRunningLocation(bool stop = false);
 	void	RunToLocation(float x, float y, float z, float following_x = 0, float following_y = 0, float following_z = 0);
@@ -1204,6 +1204,7 @@ public:
 	std::map<std::map<Region_Node*, ZBSP_Node*>, Region_Status> Regions;
 	Mutex RegionMutex;
 
+	virtual void StopMovement();
 	virtual bool PauseMovement(int32 period_of_time_ms);
 	virtual bool IsPauseMovementTimerActive();
 protected:

+ 12 - 2
EQ2/source/WorldServer/World.cpp

@@ -1495,8 +1495,18 @@ bool World::RejoinGroup(Client* client, int32 group_id){
 }
 
 
-void World::AddBonuses(ItemStatsValues* values, int16 type, sint32 value, Entity* entity){
+void World::AddBonuses(Item* item, ItemStatsValues* values, int16 type, sint32 value, Entity* entity){
 	if(values){
+		if(item && entity && 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 * .05f)));
+			}
+		}
 		switch(type){
 			case ITEM_STAT_STR:{
 				values->str += value;
@@ -2100,7 +2110,7 @@ int32 World::LoadItemBlueStats() {
 	return count;
 }
 
-sint16 World::newValue = 27;
+sint64 World::newValue = 27;
 
 sint16 World::GetItemStatAOMValue(sint16 subtype) {
 	sint16 tmp_subtype = subtype;

+ 2 - 2
EQ2/source/WorldServer/World.h

@@ -571,7 +571,7 @@ public:
 	bool RejoinGroup(Client* client, int32 group_id);
 	//bool MakeLeader(Client* leader, string new_leader);
 	
-	void AddBonuses(ItemStatsValues* values, int16 type, sint32 value, Entity* entity);
+	void AddBonuses(Item* item, ItemStatsValues* values, int16 type, sint32 value, Entity* entity);
 	void CreateGuild(const char* guild_name, Client* leader = 0, int32 group_id = 0);
 	void SaveGuilds();
 	void PickRandomLottoDigits(int32* digits);
@@ -632,7 +632,7 @@ public:
 	
 	void LoadMaps(std::string zoneFile);
 	Map* GetMap(std::string zoneFile, int32 client_version);
-	static sint16 newValue;
+	static sint64 newValue;
 private:
 	int32 suppressed_warning = 0;
 	map<string, int32> reloading_subsystems;

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

@@ -483,6 +483,8 @@ public:
 	void AwardCoins(int64 total_coins, std::string reason = string(""));
 
 	void TriggerSpellSave();
+
+	void ClearSentItemDetails() { sent_item_details.clear(); }
 private:
 	void    SavePlayerImages();
 	void	SkillChanged(Skill* skill, int16 previous_value, int16 new_value);

+ 6 - 0
EQ2/source/common/EQStream.cpp

@@ -658,9 +658,14 @@ void EQStream::ProcessPacket(EQProtocolPacket *p, EQProtocolPacket* lastp)
 				//EQApplicationPacket *ap = p->MakeApplicationPacket(app_opcode_size);
 				//InboundQueuePush(ap);
 
+				cout << "Orig Packet: " << p->opcode << endl;
+				DumpPacket(p->pBuffer, p->size);
 				MCombineQueueLock.lock();
 				EQProtocolPacket* p2 = ProcessEncryptedData(p->pBuffer, p->size, OP_Fragment);
 				MCombineQueueLock.unlock();
+				cout << "Decrypted Packet: " << p2->opcode << endl;
+				DumpPacket(p2->pBuffer, p2->size);
+
 				EQApplicationPacket* ap = p2->MakeApplicationPacket(2);
 				if (ap->version == 0)
 					ap->version = client_version;
@@ -669,6 +674,7 @@ void EQStream::ProcessPacket(EQProtocolPacket *p, EQProtocolPacket* lastp)
 #endif
 				//InboundQueuePush(ap);
 				LogWrite(PACKET__INFO, 0, "Packet", "Received unknown packet type, not adding to inbound queue");
+				cout << "AppPacket: " << p2->opcode << endl;
 				DumpPacket(ap->pBuffer, ap->size);
 				safe_delete(ap);
 				safe_delete(p2);