3 Commits 8522d64f39 ... 339071500e

Autore SHA1 Messaggio Data
  LethalEncounter 339071500e added /waypoint command 4 anni fa
  LethalEncounter 2afc04311b Merge branch 'master' of http://cutpon.com:3000/devn00b/EQ2EMu 4 anni fa
  LethalEncounter 65109b0d5d Glowpaths/waypoints added 4 anni fa
40 ha cambiato i file con 1267 aggiunte e 276 eliminazioni
  1. 57 0
      DB/updates/spell_errors_and_commands_10_21_2020.sql
  2. 1 0
      DB/updates/spells_update_sep_27_2020.sql
  3. 99 36
      EQ2/source/WorldServer/Commands/Commands.cpp
  4. 1 0
      EQ2/source/WorldServer/Commands/Commands.h
  5. 6 1
      EQ2/source/WorldServer/Entity.cpp
  6. 124 13
      EQ2/source/WorldServer/Items/Items.cpp
  7. 6 0
      EQ2/source/WorldServer/Items/Items.h
  8. 231 98
      EQ2/source/WorldServer/LuaFunctions.cpp
  9. 13 0
      EQ2/source/WorldServer/LuaFunctions.h
  10. 17 0
      EQ2/source/WorldServer/LuaInterface.cpp
  11. 1 0
      EQ2/source/WorldServer/LuaInterface.h
  12. 3 3
      EQ2/source/WorldServer/NPC.cpp
  13. 65 29
      EQ2/source/WorldServer/Player.cpp
  14. 7 1
      EQ2/source/WorldServer/Player.h
  15. 8 3
      EQ2/source/WorldServer/Quests.cpp
  16. 3 0
      EQ2/source/WorldServer/Quests.h
  17. 27 8
      EQ2/source/WorldServer/Skills.cpp
  18. 26 4
      EQ2/source/WorldServer/Skills.h
  19. 8 5
      EQ2/source/WorldServer/Spawn.cpp
  20. 9 1
      EQ2/source/WorldServer/Spawn.h
  21. 46 7
      EQ2/source/WorldServer/SpellProcess.cpp
  22. 1 1
      EQ2/source/WorldServer/SpellProcess.h
  23. 20 4
      EQ2/source/WorldServer/Spells.cpp
  24. 4 1
      EQ2/source/WorldServer/Spells.h
  25. 68 4
      EQ2/source/WorldServer/World.cpp
  26. 14 2
      EQ2/source/WorldServer/World.h
  27. 14 1
      EQ2/source/WorldServer/WorldDatabase.cpp
  28. 5 0
      EQ2/source/WorldServer/Zone/map.h
  29. 194 28
      EQ2/source/WorldServer/client.cpp
  30. 21 1
      EQ2/source/WorldServer/client.h
  31. 21 2
      EQ2/source/WorldServer/zoneserver.cpp
  32. 1 1
      EQ2/source/WorldServer/zoneserver.h
  33. 3 1
      EQ2/source/common/ConfigReader.cpp
  34. 19 0
      EQ2/source/common/PacketStruct.cpp
  35. 13 0
      EQ2/source/common/PacketStruct.h
  36. 7 0
      EQ2/source/common/misc.cpp
  37. 1 0
      EQ2/source/common/misc.h
  38. BIN
      server/EQ2World__Debug_x64.exe
  39. 1 1
      server/SpawnStructs.xml
  40. 102 20
      server/WorldStructs.xml

+ 57 - 0
DB/updates/spell_errors_and_commands_10_21_2020.sql

@@ -0,0 +1,57 @@
+UPDATE `eq2emu`.`commands` SET `handler`='523' WHERE  `id`=164;
+insert into spell_error_versions (`version`, `error_index`, `value`, `name`) values('546', '1','7','SPELL_ERROR_NOT_ENOUGH_KNOWLEDGE');
+insert into spell_error_versions (`version`, `error_index`, `value`, `name`) values('546', '2','9','SPELL_ERROR_INTERRUPTED');
+insert into spell_error_versions (`version`, `error_index`, `value`, `name`) values('546', '3','10','SPELL_ERROR_TAKE_EFFECT_MOREPOWERFUL');
+insert into spell_error_versions (`version`, `error_index`, `value`, `name`) values('546', '4','11','SPELL_ERROR_TAKE_EFFECT_SAMESPELL');
+insert into spell_error_versions (`version`, `error_index`, `value`, `name`) values('546', '5','15','SPELL_ERROR_CANNOT_CAST_DEAD');
+insert into spell_error_versions (`version`, `error_index`, `value`, `name`) values('546', '6','16','SPELL_ERROR_NOT_ALIVE');
+insert into spell_error_versions (`version`, `error_index`, `value`, `name`) values('546', '7','17','SPELL_ERROR_NOT_DEAD');
+insert into spell_error_versions (`version`, `error_index`, `value`, `name`) values('546', '8','18','SPELL_ERROR_CANNOT_CAST_SITTING');
+insert into spell_error_versions (`version`, `error_index`, `value`, `name`) values('546', '9','19','SPELL_ERROR_CANNOT_CAST_UNCON');
+insert into spell_error_versions (`version`, `error_index`, `value`, `name`) values('546', '10','20','SPELL_ERROR_ALREADY_CASTING');
+insert into spell_error_versions (`version`, `error_index`, `value`, `name`) values('546', '11','21','SPELL_ERROR_RECOVERING');
+insert into spell_error_versions (`version`, `error_index`, `value`, `name`) values('546', '12','22','SPELL_ERROR_NON_COMBAT_ONLY');
+insert into spell_error_versions (`version`, `error_index`, `value`, `name`) values('546', '13','23','SPELL_ERROR_CANNOT_CAST_STUNNED');
+insert into spell_error_versions (`version`, `error_index`, `value`, `name`) values('546', '14','24','SPELL_ERROR_CANNOT_CAST_STIFFLED');
+insert into spell_error_versions (`version`, `error_index`, `value`, `name`) values('546', '16','25','SPELL_ERROR_NOT_WHILE_MOUNTED');
+insert into spell_error_versions (`version`, `error_index`, `value`, `name`) values('546', '18','26','SPELL_ERROR_NOT_WHILE_CLIMBING');
+insert into spell_error_versions (`version`, `error_index`, `value`, `name`) values('546', '19','27','SPELL_ERROR_NOT_READY');
+insert into spell_error_versions (`version`, `error_index`, `value`, `name`) values('546', '20','28','SPELL_ERROR_CANT_SEE_TARGET');
+insert into spell_error_versions (`version`, `error_index`, `value`, `name`) values('546', '22','30','SPELL_ERROR_CANNOT_CAST_FEIGNDEATH');
+insert into spell_error_versions (`version`, `error_index`, `value`, `name`) values('546', '23','31','SPELL_ERROR_INVENTORY_FULL');
+insert into spell_error_versions (`version`, `error_index`, `value`, `name`) values('546', '24','32','SPELL_ERROR_NOT_ENOUGH_COIN');
+insert into spell_error_versions (`version`, `error_index`, `value`, `name`) values('546', '25','33','SPELL_ERROR_NOT_ALLOWED_HERE');
+insert into spell_error_versions (`version`, `error_index`, `value`, `name`) values('546', '26','34','SPELL_ERROR_NOT_WHILE_CRAFTING');
+insert into spell_error_versions (`version`, `error_index`, `value`, `name`) values('546', '27','35','SPELL_ERROR_ONLY_WHEN_CRAFTING');
+insert into spell_error_versions (`version`, `error_index`, `value`, `name`) values('546', '28','38','SPELL_ERROR_ITEM_NOT_ATTUNED');
+insert into spell_error_versions (`version`, `error_index`, `value`, `name`) values('546', '37','39','SPELL_ERROR_CANNOT_PREPARE');
+insert into spell_error_versions (`version`, `error_index`, `value`, `name`) values('546', '38','40','SPELL_ERROR_NO_ELIGIBLE_TARGET');
+insert into spell_error_versions (`version`, `error_index`, `value`, `name`) values('546', '39','41','SPELL_ERROR_NO_TARGETS_IN_RANGE');
+insert into spell_error_versions (`version`, `error_index`, `value`, `name`) values('546', '40','42','SPELL_ERROR_TOO_CLOSE');
+insert into spell_error_versions (`version`, `error_index`, `value`, `name`) values('546', '41','43','SPELL_ERROR_TOO_FAR_AWAY');
+insert into spell_error_versions (`version`, `error_index`, `value`, `name`) values('546', '42','44','SPELL_ERROR_TARGET_TOO_WEAK');
+insert into spell_error_versions (`version`, `error_index`, `value`, `name`) values('546', '43','45','SPELL_ERROR_TARGET_TOO_POWERFUL');
+insert into spell_error_versions (`version`, `error_index`, `value`, `name`) values('546', '44','46','SPELL_ERROR_WONT_WORK_ON_TARGET');
+insert into spell_error_versions (`version`, `error_index`, `value`, `name`) values('546', '45','47','SPELL_ERROR_TARGET_INVULNERABLE');
+insert into spell_error_versions (`version`, `error_index`, `value`, `name`) values('546', '47','48','SPELL_ERROR_TARGET_ENGAGED');
+insert into spell_error_versions (`version`, `error_index`, `value`, `name`) values('546', '48','49','SPELL_ERROR_TARGET_NOT_GROUPED');
+insert into spell_error_versions (`version`, `error_index`, `value`, `name`) values('546', '51','51','SPELL_ERROR_TARGET_ALREADY_ENGAGED');
+insert into spell_error_versions (`version`, `error_index`, `value`, `name`) values('546', '52','52','SPELL_ERROR_CANNOT_ENGAGE');
+insert into spell_error_versions (`version`, `error_index`, `value`, `name`) values('546', '53','53','SPELL_ERROR_NOT_A_FRIEND');
+insert into spell_error_versions (`version`, `error_index`, `value`, `name`) values('546', '54','54','SPELL_ERROR_NOT_AN_ENEMY');
+insert into spell_error_versions (`version`, `error_index`, `value`, `name`) values('546', '55','55','SPELL_ERROR_TARGET_INVENTORY_FULL');
+insert into spell_error_versions (`version`, `error_index`, `value`, `name`) values('546', '56','56','SPELL_ERROR_FINISH_DUELING_FIRST');
+insert into spell_error_versions (`version`, `error_index`, `value`, `name`) values('546', '57','57','SPELL_ERROR_ILLEGAL_TARGET_ATTACK');
+insert into spell_error_versions (`version`, `error_index`, `value`, `name`) values('546', '62','58','SPELL_ERROR_NOT_ENOUGH_POWER');
+insert into spell_error_versions (`version`, `error_index`, `value`, `name`) values('546', '63','59','SPELL_ERROR_NOT_ENOUGH_HEALTH');
+insert into spell_error_versions (`version`, `error_index`, `value`, `name`) values('546', '64','60','SPELL_ERROR_NOT_ENOUGH_CONC');
+insert into spell_error_versions (`version`, `error_index`, `value`, `name`) values('546', '65','61','SPELL_ERROR_MISSING_COMPONENT');
+insert into spell_error_versions (`version`, `error_index`, `value`, `name`) values('546', '66','62','SPELL_ERROR_OUT_OF_CHARGES');
+insert into spell_error_versions (`version`, `error_index`, `value`, `name`) values('546', '72','65','SPELL_ERROR_ALREADY_PREPARED');
+insert into spell_error_versions (`version`, `error_index`, `value`, `name`) values('546', '73','66','SPELL_ERROR_ALREADY_HAVE_SPELL');
+insert into spell_error_versions (`version`, `error_index`, `value`, `name`) values('546', '74','68','SPELL_ERROR_NOT_SMART_ENOUGH');
+insert into spell_error_versions (`version`, `error_index`, `value`, `name`) values('546', '77','71','SPELL_ERROR_CANNOT_MOUNT_NOW_SITTING');
+insert into spell_error_versions (`version`, `error_index`, `value`, `name`) values('546', '78','72','SPELL_ERROR_CANNOT_MOUNT_NOW_DEAD');
+insert into spell_error_versions (`version`, `error_index`, `value`, `name`) values('546', '79','73','SPELL_ERROR_CANNOT_MOUNT_NOW_CLIMBING');
+insert into spell_error_versions (`version`, `error_index`, `value`, `name`) values('546', '80','74','SPELL_ERROR_CANNOT_MOUNT_NOW_FORM');
+insert into spell_error_versions (`version`, `error_index`, `value`, `name`) values('546', '81','75','SPELL_ERROR_CANNOT_MOUNT_NOW_WATER_TO_DEEP');

+ 1 - 0
DB/updates/spells_update_sep_27_2020.sql

@@ -0,0 +1 @@
+ALTER TABLE `spells`	ADD COLUMN `fade_message_others` VARCHAR(255) NOT NULL DEFAULT '' AFTER `fade_message`;

+ 99 - 36
EQ2/source/WorldServer/Commands/Commands.cpp

@@ -1135,15 +1135,19 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 	}
 	case COMMAND_RELOADSTRUCTS: {
 		client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Reloading Structs...");
+		world.SetReloadingSubsystem("Structs");
 		configReader.ReloadStructs();
+		world.RemoveReloadingSubSystem("Structs");
 		client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Done!");
 		break;
 	}
 	case COMMAND_RELOAD_QUESTS: {
 		client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Reloading Quests...");
+		world.SetReloadingSubsystem("Quests");
 		master_quest_list.Reload();
 		client_list.ReloadQuests();
 		zone_list.ReloadClientQuests();
+		world.RemoveReloadingSubSystem("Quests");
 		client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Done!");
 		break;
 	}
@@ -1153,42 +1157,52 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 	}
 	case COMMAND_RELOAD_SPELLS: {
 		client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Reloading Spells...");
+		world.SetReloadingSubsystem("Spells");
 		zone_list.DeleteSpellProcess();
 		master_spell_list.Reload();
 		if (lua_interface)
 			lua_interface->ReloadSpells();
 		zone_list.LoadSpellProcess();
-		client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Done!");
+		world.RemoveReloadingSubSystem("Spells");
+		client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Done!");		
 		break;
 	}
 	case COMMAND_RELOAD_GROUNDSPAWNS: {
 		client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Reloading Groundspawn Entries...");
+		world.SetReloadingSubsystem("GroundSpawns");
 		client->GetCurrentZone()->DeleteGroundSpawnItems();
 		client->GetCurrentZone()->LoadGroundSpawnEntries();
+		world.RemoveReloadingSubSystem("GroundSpawns");
 		client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Done!");
 		break;
 	}
 
 	case COMMAND_RELOAD_ZONESCRIPTS: {
 		client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Reloading Zone Scripts...");
+		world.SetReloadingSubsystem("ZoneScripts");
 		world.ResetZoneScripts();
 		database.LoadZoneScriptData();
 		if (lua_interface)
 			lua_interface->DestroyZoneScripts();
+		world.RemoveReloadingSubSystem("ZoneScripts");
 		client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Done!");
 		break;
 	}
 	case COMMAND_RELOAD_ENTITYCOMMANDS: {
 		client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Reloading Entity Commands...");
+		world.SetReloadingSubsystem("EntityCommands");
 		client->GetCurrentZone()->ClearEntityCommands();
 		database.LoadEntityCommands(client->GetCurrentZone());
+		world.RemoveReloadingSubSystem("EntityCommands");
 		client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Done!");
 		break;
 	}
 	case COMMAND_RELOAD_FACTIONS: {
 		client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Reloading Factions...");
+		world.SetReloadingSubsystem("Factions");
 		master_faction_list.Clear();
 		database.LoadFactionList();
+		world.RemoveReloadingSubSystem("Factions");
 		client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Done!");
 		break;
 	}
@@ -1354,8 +1368,13 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 				}
 				else if (strcmp(sep->arg[0], "spellbook") == 0) {
 					sint32 spell_id = atol(sep->arg[1]);
-					int8 tier = atoi(sep->arg[2]);
-					EQ2Packet* outapp = master_spell_list.GetSpellPacket(spell_id, tier, client, true, 0x2A);
+					int32 tier = atoi(sep->arg[2]);
+					if (tier > 255) {
+						SpellBookEntry* ent = client->GetPlayer()->GetSpellBookSpell(spell_id);
+						if (ent)
+							tier = ent->tier;
+					}
+					EQ2Packet* outapp = master_spell_list.GetSpellPacket(spell_id, (int8)tier, client, true, 0x2A);
 					if (outapp)
 						client->QueuePacket(outapp);
 					else
@@ -1386,7 +1405,7 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 					LogWrite(COMMAND__DEBUG, 5, "Command", "Unknown Spell ID: %u", spell_id);
 					int8 tier = client->GetPlayer()->GetSpellTier(spell_id);
 					int8 type = 0;
-					if (client->GetVersion() <= 283)
+					if (client->GetVersion() <= 546)
 						type = 1;
 					EQ2Packet* outapp = master_spell_list.GetSpecialSpellPacket(spell_id, tier, client, true, type);
 					if (outapp){
@@ -1586,6 +1605,18 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 			}
 			break;
 		}
+		case COMMAND_WAYPOINT: {
+			bool success = false;
+			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");
+			}
+			else {
+				client->ClearWaypoint();
+				client->Message(CHANNEL_COLOR_YELLOW, "Usage: /waypoint x y z");
+			}
+			break;
+		}
 		case COMMAND_WHO:{
 			const char* who = 0;
 			if(sep && sep->arg[0]){
@@ -1910,8 +1941,6 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 						new_level = 255;
 
 					client->ChangeLevel(client->GetPlayer()->GetLevel(), new_level);
-					client->GetPlayer()->SetXP(1);
-					client->GetPlayer()->SetNeededXP();
 				}
 			}else
 				client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Usage: /level {new_level}");
@@ -7773,6 +7802,12 @@ void Commands::Command_Toggle_AutoConsume(Client* client, Seperator* sep)
 	{
 		int8 slot = atoi(sep->arg[0]);
 		int8 flag = atoi(sep->arg[1]);
+		if (client->GetVersion() <= 283) {
+			slot += 4;
+		}
+		else if (client->GetVersion() <= 546) {
+			slot += 2;
+		}
 		if (slot == EQ2_FOOD_SLOT)
 		{
 			player->toggle_character_flag(CF_FOOD_AUTO_CONSUME);
@@ -8477,8 +8512,8 @@ void Commands::Command_Test(Client* client, EQ2_16BitString* command_parms) {
 			}
 		}
 		else if (atoi(sep->arg[0]) == 5) {
-			int16 offset = atoi(sep->arg[0]);
-			int32 value1 = atol(sep->arg[1]);
+			int16 offset = atoi(sep->arg[1]);
+			int32 value1 = atol(sep->arg[2]);
 			EQ2Packet* outapp = client->GetPlayer()->GetPlayerInfo()->serialize(client->GetVersion(), offset, value1);
 			client->QueuePacket(outapp);
 		}
@@ -8570,21 +8605,39 @@ void Commands::Command_Test(Client* client, EQ2_16BitString* command_parms) {
 				client->QueuePacket(ret);
 			}
 		}
-		else if (atoi(sep->arg[0]) == 9) {
-			Spawn* spawn = client->GetPlayer()->GetTarget();
-			if (spawn) {	
-				if (sep->IsSet(3)) {
-					int32 amount = (int32)atoi(sep->arg[3]);
-					spawn->SetActivityStatus(amount);
-				}
+		else if (atoi(sep->arg[0]) == 9) {		
+			PacketStruct* packet2 = configReader.getStruct("WS_DeathWindow", client->GetVersion());
+			if (packet2) {
+				packet2->setArrayLengthByName("location_count", 1);
+				packet2->setArrayDataByName("location_ida", 1234);
+				packet2->setArrayDataByName("unknown2a", 3);
+				packet2->setArrayDataByName("zone_name", "Queen's Colony");
+				packet2->setArrayDataByName("location_name", "Myrrin's Tower");
+				packet2->setArrayDataByName("distance", 134);
+				EQ2Packet* app = packet2->serialize();
 				if (sep->IsSet(2)) {
-					int8 amount = (int8)atoi(sep->arg[2]);
-					spawn->SetLockedNoLoot(amount);
-				}
-				if (sep->IsSet(1)) {
-					sint8 amount = (sint8)atoi(sep->arg[1]);
-					spawn->AddIconValue(amount);
+					int8 offset = atoi(sep->arg[1]);
+					uchar* ptr2 = app->pBuffer;
+					ptr2 += offset;
+					if (sep->IsNumber(2)) {
+						int32 value1 = atol(sep->arg[2]);
+						if (value1 > 0xFFFF)
+							memcpy(ptr2, (uchar*)&value1, 4);
+						else if (value1 > 0xFF)
+							memcpy(ptr2, (uchar*)&value1, 2);
+						else
+							memcpy(ptr2, (uchar*)&value1, 1);
+					}
+					else {
+						int8 len = strlen(sep->arg[2]);
+						memcpy(ptr2, (uchar*)&len, 1);
+						ptr2 += 1;
+						memcpy(ptr2, sep->arg[2], len);
+					}
 				}
+				DumpPacket(app);
+				client->QueuePacket(app);
+				safe_delete(packet2);
 			}
 		}
 		else if (atoi(sep->arg[0]) == 10) {
@@ -8636,34 +8689,44 @@ void Commands::Command_Test(Client* client, EQ2_16BitString* command_parms) {
 		else if (atoi(sep->arg[0]) == 11) {
 			PacketStruct* packet2 = configReader.getStruct("WS_QuestJournalReply", client->GetVersion());
 			if (packet2) {
-				packet2->setDataByName("quest_id", 5);
+				packet2->setDataByName("quest_id", 524);
 				packet2->setDataByName("player_crc", 2900677088);
-				packet2->setDataByName("name", "Archetype Selection");
-				packet2->setDataByName("description", "I have reported my profession to Garven Tralk");
+				packet2->setDataByName("name", "Tasks aboard the Far Journey");
+				packet2->setDataByName("description", "I completed all the tasks assigned to me by Captain Varlos aboard the Far Journey");
 				packet2->setDataByName("type", "Hallmark");
 				packet2->setDataByName("complete_header", "To complete this quest, I must do the following tasks:");
 				packet2->setDataByName("day", 19);
 				packet2->setDataByName("month", 6);
 				packet2->setDataByName("year", 20);
-				packet2->setDataByName("level", 2);
-				packet2->setDataByName("encounter_level", 4);
-				packet2->setDataByName("difficulty", 3);
+				packet2->setDataByName("level", 1);
+				packet2->setDataByName("encounter_level", 1);
+				packet2->setDataByName("difficulty", 1);
 				packet2->setDataByName("time_obtained", Timer::GetUnixTimeStamp());
-				packet2->setDataByName("timer_start", Timer::GetUnixTimeStamp());
-				packet2->setDataByName("timer_duration", 300);
-				packet2->setDataByName("timer_running", 1);
-				packet2->setArrayLengthByName("task_groups_completed", 0);
-				packet2->setArrayLengthByName("num_task_groups", 1);
-				packet2->setArrayDataByName("task_group", "I need to talk to Garven Tralk");
-				packet2->setSubArrayLengthByName("num_tasks", 1);
+				//packet2->setDataByName("timer_start", Timer::GetUnixTimeStamp());
+				//packet2->setDataByName("timer_duration", 300);
+				//packet2->setDataByName("timer_running", 1);
+				packet2->setArrayLengthByName("task_groups_completed", 9);
+				packet2->setArrayLengthByName("num_task_groups", 10);
+				packet2->setArrayDataByName("task_group", "I spoke to Waulon as Captain Varlos had asked of me.");
+				packet2->setArrayDataByName("task_group", "I found Waulon's hat in one of the boxes.", 1);
+				packet2->setArrayDataByName("task_group", "I returned Waulon's hat.", 2);
+				packet2->setArrayDataByName("task_group", "I have spoken to Ingrid.", 3);
+				packet2->setArrayDataByName("task_group", "I purchased a Shard of Luclin.", 4);
+				packet2->setArrayDataByName("task_group", "I gave the Shard of Luclin to Ingrid.", 5);
+				packet2->setArrayDataByName("task_group", "I have spoken to Captain Varlos.", 6);
+				packet2->setArrayDataByName("task_group", "I killed the rats that Captain Varlos requested.", 7);
+				packet2->setArrayDataByName("task_group", "Captain Varlos has ordered you to kill the escaped goblin.", 8);
+				packet2->setArrayDataByName("task_group", "I killed the escaped goblin.", 9);
+				/*packet2->setSubArrayLengthByName("num_tasks", 1);
 				packet2->setSubArrayDataByName("task", "I need to talk to Garven Tralk");
 				packet2->setSubArrayLengthByName("num_updates", 1);
 				packet2->setSubArrayDataByName("update_currentval", 0);
 				packet2->setSubArrayDataByName("update_maxval", 1);
 				packet2->setSubArrayDataByName("icon", 11);
-
+				*/
 				packet2->setArrayDataByName("waypoint", 0xFFFFFFFF);
 				packet2->setDataByName("journal_updated", 1);
+				packet2->setDataByName("bullets", 1);
 				EQ2Packet* app = packet2->serialize();
 				if (sep->IsSet(2)) {
 					int16 offset = atoi(sep->arg[1]);
@@ -8693,7 +8756,7 @@ void Commands::Command_Test(Client* client, EQ2_16BitString* command_parms) {
 		else if (atoi(sep->arg[0]) == 12) {
 			PacketStruct* packet2 = configReader.getStruct("WS_QuestJournalReply", client->GetVersion());
 			if (packet2) {
-				packet2->setDataByName("quest_id", 5);
+				packet2->setDataByName("quest_id", 524);
 				packet2->setDataByName("player_crc", 2900677088);
 				packet2->setDataByName("name", "Archetype Selection");
 				packet2->setDataByName("description", "I have reported my profession to Garven Tralk.");

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

@@ -862,6 +862,7 @@ private:
 #define COMMAND_RELOAD_STARTABILITIES	522
 
 #define COMMAND_FINDSPAWN				521
+#define COMMAND_WAYPOINT				523
 
 #define GET_AA_XML						751
 #define ADD_AA							752

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

@@ -499,6 +499,7 @@ void Entity::DoRegenUpdate(){
 	if(GetPower() < GetTotalPower()){
 		if(regen_power_rate == 0)
 			regen_power_rate = level + (int)(level/10) + 1;
+		cout << "regen_power_rate: " << regen_power_rate << endl;
 		if((power + regen_power_rate) > GetTotalPower())
 			SetPower(GetTotalPower());
 		else
@@ -1376,7 +1377,11 @@ float Entity::GetSpeed() {
 		ret += stats[ITEM_STAT_OFFENSIVESPEED];
 	else if (stats.count(ITEM_STAT_SPEED) && stats.count(ITEM_STAT_MOUNTSPEED))
 		ret += max(stats[ITEM_STAT_SPEED], stats[ITEM_STAT_MOUNTSPEED]);
-
+	else if (stats.count(ITEM_STAT_SPEED))
+		ret += stats[ITEM_STAT_SPEED];
+	else if (stats.count(ITEM_STAT_MOUNTSPEED))
+		ret += stats[ITEM_STAT_MOUNTSPEED];
+	
 	ret *= speed_multiplier;
 	return ret;
 }

+ 124 - 13
EQ2/source/WorldServer/Items/Items.cpp

@@ -992,12 +992,69 @@ void Item::SetItem(Item* old_item){
 	spell_tier = old_item->spell_tier;
 }
 
+bool Item::CheckArchetypeAdvSubclass(int8 adventure_class, map<int8, int16>* adv_class_levels) {
+	if (adventure_class > FIGHTER && adventure_class < ANIMALIST) {
+		int8 check = adventure_class % 10;
+		if (check == 2 || check == 5 || check == 8) {
+			int64 adv_classes = 0;
+			int16 level = 0;
+			for (int i = adventure_class + 1; i < adventure_class + 3; i++) {				
+				if (adv_class_levels) { //need to match levels
+					if (level == 0) {
+						if (adv_class_levels->count(i) > 0)
+							level = adv_class_levels->at(i);
+						else
+							return false;
+					}
+					else{
+						if (adv_class_levels->count(i) > 0 && adv_class_levels->at(i) != level)
+							return false;
+					}
+				}
+				else {
+					adv_classes = ((int64)2) << (i - 1);
+					if (!(generic_info.adventure_classes & adv_classes))
+						return false;
+				}
+			}
+			return true;
+		}
+	}
+	return false;
+}
+
+bool Item::CheckArchetypeAdvClass(int8 adventure_class, map<int8, int16>* adv_class_levels) {
+	if (adventure_class == 1 || adventure_class == 11 || adventure_class == 21 || adventure_class == 31) {
+		//if the class is an archetype class and the subclasses have access, then allow
+		if (CheckArchetypeAdvSubclass(adventure_class + 1, adv_class_levels) && CheckArchetypeAdvSubclass(adventure_class + 4, adv_class_levels) && CheckArchetypeAdvSubclass(adventure_class + 7, adv_class_levels)) {
+			if (adv_class_levels) {
+				int16 level = 0;
+				for (int i = adventure_class + 1; i <= adventure_class + 7; i += 3) {
+					if (adv_class_levels->count(i+1) == 0 || adv_class_levels->count(i + 2) == 0)
+						return false;
+					if(level == 0)
+						level = adv_class_levels->at(i+1);
+					if (adv_class_levels->at(i+1) != level) //already verified the classes, just need to verify the subclasses have the same levels
+						return false;
+				}
+				
+			}
+			return true;
+		}
+	}
+	else if (CheckArchetypeAdvSubclass(adventure_class, adv_class_levels)) {//check archetype subclass		
+		return true;
+	}
+	return false;
+}
+
 bool Item::CheckClass(int8 adventure_class, int8 tradeskill_class) {
 	int64 adv_classes = ((int64)2) << (adventure_class - 1);
 	int64 ts_classes = ((int64)2) << (tradeskill_class - 1);
 	if( ((generic_info.adventure_classes & adv_classes) || generic_info.adventure_classes == 0) && ((generic_info.tradeskill_classes & ts_classes) || generic_info.tradeskill_classes == 0) )
 		return true;
-	return false;
+	//check arechtype classes as last resort
+	return CheckArchetypeAdvClass(adventure_class);
 }
 
 bool Item::CheckLevel(int8 adventure_class, int8 tradeskill_class, int16 level) {
@@ -1720,7 +1777,7 @@ void Item::serialize(PacketStruct* packet, bool show_name, Player* player, int16
 		if(flags.length() > 0)
 			packet->setSubstructDataByName("header_info", "flag_names", flags.c_str());
 	}
-	if(generic_info.adventure_classes > 0 || generic_info.tradeskill_classes > 0 || item_level_overrides.size() > 0){
+	if (generic_info.adventure_classes > 0 || generic_info.tradeskill_classes > 0 || item_level_overrides.size() > 0) {
 		//int64 classes = 0;
 		int16 tmp_level = 0;
 		map<int8, int16> adv_class_levels;
@@ -1736,18 +1793,18 @@ void Item::serialize(PacketStruct* packet, bool show_name, Player* player, int16
 		if (packet->GetVersion() >= 60000)
 			temp += 2;
 
-		for(int32 i=0;i<=temp;i++){
+		for (int32 i = 0; i <= temp; i++) {
 			tmpVal = (int64)pow(2.0, (double)i);
-			if((generic_info.adventure_classes & tmpVal)){
+			if ((generic_info.adventure_classes & tmpVal)) {
 				//classes += 2 << (i - 1);
 				classes += tmpVal;
 				tmp_level = GetOverrideLevel(i, 255);
-				if(tmp_level == 0)
+				if (tmp_level == 0)
 					adv_class_levels[i] = generic_info.adventure_default_level;
 				else
 					adv_class_levels[i] = tmp_level;
 			}
-			if(tmpVal == 0) {
+			if (tmpVal == 0) {
 				if (packet->GetVersion() >= 60000)
 					classes = 576379072454289112;
 				else if (packet->GetVersion() >= 57048)
@@ -1758,27 +1815,73 @@ void Item::serialize(PacketStruct* packet, bool show_name, Player* player, int16
 					classes = 36024082983773912;
 			}
 		}
-		for(int i=ALCHEMIST + 1 - ARTISAN ;i>=0;i--){
+		for (int i = ALCHEMIST + 1 - ARTISAN; i >= 0; i--) {
 			//tmpVal = 2 << i;
 			tmpVal = (int64)pow(2.0, (double)i);
-			if((generic_info.tradeskill_classes & tmpVal)){
-				classes += pow(2, (i + ARTISAN ));
+			if ((generic_info.tradeskill_classes & tmpVal)) {
+				classes += pow(2, (i + ARTISAN));
 				//classes += 2 << (i+ARTISAN-1);
 				tmp_level = GetOverrideLevel(i, 255);
-				if(tmp_level == 0)
+				if (tmp_level == 0)
 					tradeskill_class_levels[i] = generic_info.tradeskill_default_level;
 				else
 					tradeskill_class_levels[i] = tmp_level;
 			}
 		}
+		bool simplified_display = false;
+		if (client->GetVersion() <= 546) { //simplify display (if possible)
+			map<int8, int16> new_adv_class_levels;
+			for (int i = 1; i <= 31; i += 10) {
+				bool add_archetype = CheckArchetypeAdvClass(i, &adv_class_levels);
+				if (add_archetype) {
+					new_adv_class_levels[i] = 0;	
+				}
+				else {
+					for (int x = 1; x <= 7; x += 3) {
+						if (CheckArchetypeAdvSubclass(i+x, &adv_class_levels)) {
+							new_adv_class_levels[i+x] = 0;
+						}
+					}
+				}
+			}
+			if (new_adv_class_levels.size() > 0) {
+				simplified_display = true;
+				int8 i = 0;
+				for (itr = new_adv_class_levels.begin(); itr != new_adv_class_levels.end(); itr++) {
+					i = itr->first;
+					if ((i % 10) == 1) {
+						int16 level = 0;
+						for (int x = i; x < i+10; x++) {
+							if (adv_class_levels.count(x) > 0) {
+								if(level == 0)
+									level = adv_class_levels.at(x);
+								adv_class_levels.erase(x);
+							}
+						}
+						adv_class_levels[i] = level;
+					}
+					else {
+						int16 level = 0;
+						for (int x = i+1; x < i + 3; x++) {
+							if (adv_class_levels.count(x) > 0) {
+								if (level == 0)
+									level = adv_class_levels.at(x);
+								adv_class_levels.erase(x);
+							}
+						}
+						adv_class_levels[i] = level;
+					}
+				}
+			}			
+		}
 		packet->setSubstructArrayLengthByName("header_info", "class_count", adv_class_levels.size() + tradeskill_class_levels.size());
 		int i = 0;
-		for(itr = adv_class_levels.begin(); itr != adv_class_levels.end(); itr++, i++){
+		for (itr = adv_class_levels.begin(); itr != adv_class_levels.end(); itr++, i++) {
 			packet->setArrayDataByName("adventure_class", itr->first, i);
 			packet->setArrayDataByName("tradeskill_class", 255, i);
-			packet->setArrayDataByName("level", itr->second*10, i);
+			packet->setArrayDataByName("level", itr->second * 10, i);
 		}
-		for(itr = tradeskill_class_levels.begin(); itr != tradeskill_class_levels.end(); itr++, i++){
+		for (itr = tradeskill_class_levels.begin(); itr != tradeskill_class_levels.end(); itr++, i++) {
 			packet->setArrayDataByName("adventure_class", 255, i);
 			packet->setArrayDataByName("tradeskill_class", itr->first, i);
 			packet->setArrayDataByName("level", itr->second, i);
@@ -1838,6 +1941,14 @@ void Item::serialize(PacketStruct* packet, bool show_name, Player* player, int16
 				else if(slot == EQ2_DRINK_SLOT)
 					slot = EQ2_ORIG_DRINK_SLOT;
 			}
+			else if (client->GetVersion() <= 546) {
+				if (slot > EQ2_EARS_SLOT_1 && slot <= EQ2_WAIST_SLOT) //they added a second ear slot later, adjust for only 1 original slot
+					slot -= 1;
+				else if (slot == EQ2_FOOD_SLOT)
+					slot = EQ2_DOF_FOOD_SLOT;
+				else if (slot == EQ2_DRINK_SLOT)
+					slot = EQ2_DOF_DRINK_SLOT;
+			}
 			packet->setArrayDataByName("slot", slot, i);
 		}
 	}

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

@@ -65,6 +65,8 @@ extern MasterItemList master_item_list;
 #define EQ2_BACK_SLOT 30
 #define EQ2_ORIG_FOOD_SLOT 18
 #define EQ2_ORIG_DRINK_SLOT 19
+#define EQ2_DOF_FOOD_SLOT 20
+#define EQ2_DOF_DRINK_SLOT 21
 
 #define PRIMARY_SLOT 1
 #define SECONDARY_SLOT 2
@@ -99,6 +101,8 @@ extern MasterItemList master_item_list;
 #define BACK_SLOT 1073741824
 #define ORIG_FOOD_SLOT 524288
 #define ORIG_DRINK_SLOT 1048576
+#define DOF_FOOD_SLOT 1048576
+#define DOF_DRINK_SLOT 2097152
 
 #define CLASSIC_EQ_MAX_BAG_SLOTS 20
 #define NUM_BANK_SLOTS 12
@@ -855,6 +859,8 @@ public:
 	void AddLevelOverride(ItemLevelOverride* class_);
 	bool CheckClassLevel(int8 adventure_class, int8 tradeskill_class, int16 level);
 	bool CheckClass(int8 adventure_class, int8 tradeskill_class);
+	bool CheckArchetypeAdvClass(int8 adventure_class, map<int8, int16>* adv_class_levels = 0);
+	bool CheckArchetypeAdvSubclass(int8 adventure_class, map<int8, int16>* adv_class_levels = 0);
 	bool CheckLevel(int8 adventure_class, int8 tradeskill_class, int16 level);
 	void SetAppearance(int16 type, int8 red, int8 green, int8 blue, int8 highlight_red, int8 highlight_green, int8 highlight_blue);
 	void SetAppearance(ItemAppearance* appearance);

+ 231 - 98
EQ2/source/WorldServer/LuaFunctions.cpp

@@ -337,6 +337,30 @@ int EQ2Emu_lua_ChangeHandIcon(lua_State* state) {
 	return 0;
 }
 
+//this function is used to force an update packet to be sent.  
+//Useful if certain calculated things change after the player is sent the spawn packet, like quest flags or player has access to an object now
+int EQ2Emu_lua_SetVisualFlag(lua_State* state) {
+	if (!lua_interface)
+		return 0;
+	Spawn* spawn = lua_interface->GetSpawn(state);
+	if (spawn) {
+		spawn->vis_changed = true;
+	}
+	return 0;
+}
+
+//this function is used to force an update packet to be sent.  
+//Useful if certain calculated things change after the player is sent the spawn packet, like quest flags or player has access to an object now
+int EQ2Emu_lua_SetInfoFlag(lua_State* state) {
+	if (!lua_interface)
+		return 0;
+	Spawn* spawn = lua_interface->GetSpawn(state);
+	if (spawn) {
+		spawn->info_changed = true;
+	}
+	return 0;
+}
+
 int EQ2Emu_lua_SendStateCommand(lua_State* state) {
 	if (!lua_interface)
 		return 0;
@@ -3321,6 +3345,27 @@ int EQ2Emu_lua_AddQuestSelectableRewardItem(lua_State* state) {
 	return 0;
 }
 
+int EQ2Emu_lua_HasQuestRewardItem(lua_State* state) {
+	if (!lua_interface)
+		return 0;
+	Quest* quest = lua_interface->GetQuest(state);
+	if (quest) {
+		int32 item_id = lua_interface->GetInt32Value(state, 2);
+		vector<Item*>* items = quest->GetRewardItems();
+		if (items) {
+			vector<Item*>::iterator itr;
+			for (itr = items->begin(); itr != items->end(); itr++) {
+				if (*itr && (*itr)->details.item_id == item_id) {
+					lua_interface->SetBooleanValue(state, true);
+					return 1;
+				}
+			}
+		}
+	}
+	lua_interface->SetBooleanValue(state, false);
+	return 1;
+}
+
 int EQ2Emu_lua_AddQuestRewardItem(lua_State* state) {
 	if (!lua_interface)
 		return 0;
@@ -3808,8 +3853,6 @@ int EQ2Emu_lua_GiveImmediateQuestReward(lua_State* state) {
 	string factions_map_str = lua_interface->GetStringValue(state, 7);
 	string text = lua_interface->GetStringValue(state, 8);
 	int32 source_id = 0;
-	if (quest)
-		source_id = quest->GetQuestID();
 	if (playerSpawn && playerSpawn->IsPlayer()) {
 		Player* player = (Player*)playerSpawn;
 		Client* client = player->GetZone()->GetClientBySpawn(player);
@@ -3824,7 +3867,7 @@ int EQ2Emu_lua_GiveImmediateQuestReward(lua_State* state) {
 						Item* item = new Item(master_item_list.GetItem(itr->first));
 						if (item) {
 							if (itr->second > 0)
-								item->stack_count = itr->second;
+								item->details.count = itr->second;
 							reward_items.push_back(item);
 						}
 					}
@@ -3848,93 +3891,9 @@ int EQ2Emu_lua_GiveImmediateQuestReward(lua_State* state) {
 			const char* reward_type = "Quest Reward!";
 			if (!quest)
 				reward_type = "Reward!";
-			client->DisplayQuestRewards(0, coin, &reward_items, &selectable_reward_items, &faction_rewards, reward_type, status_points, text.c_str());
+			client->DisplayQuestRewards(quest, coin, &reward_items, &selectable_reward_items, &faction_rewards, reward_type, status_points, text.c_str());
 		}
 	}
-			/*PacketStruct* packet2 = configReader.getStruct("WS_QuestRewardPackMsg", client->GetVersion());
-			if (packet2) {
-				player->AddCoins(coin);
-				client->PlaySound("coin_cha_ching");
-				packet2->setSubstructDataByName("reward_data", "unknown1", 255);
-				if(quest)
-					packet2->setSubstructDataByName("reward_data", "reward", "Quest Reward!");
-				else
-					packet2->setSubstructDataByName("reward_data", "reward", "Reward!");
-				packet2->setSubstructDataByName("reward_data", "coin", coin);
-				if (player->GetGuild()) {
-					player->GetInfoStruct()->status_points += status_points;
-					packet2->setSubstructDataByName("reward_data", "status_points", status_points);
-				}
-				if (rewards_str.length() > 0) {
-					map<unsigned int, unsigned short> rewards = ParseIntMap(rewards_str);
-					vector<Item*> reward_items;
-					map<unsigned int, unsigned short>::iterator itr;
-					for (itr = rewards.begin(); itr != rewards.end(); itr++) {
-						if (itr->first > 0) {
-							Item* item = new Item(master_item_list.GetItem(itr->first));
-							if (item) {
-								if (itr->second > 0)
-									item->stack_count = itr->second;
-								reward_items.push_back(item);
-							}
-						}
-					}
-					packet2->setSubstructArrayLengthByName("reward_data", "num_rewards", reward_items.size());
-					for (int i = 0; i < reward_items.size(); i++) {
-						Item* item = reward_items[i];
-						packet2->setArrayDataByName("reward_id", item->details.item_id, i);
-						packet2->setItemArrayDataByName("item", item, client->GetPlayer(), i, 0, -1);
-						player->AddPendingItemReward(item); //item reference will be deleted after the player accepts it
-					}
-				}
-				if (select_rewards_str.length() > 0) {
-					map<unsigned int, unsigned short> rewards = ParseIntMap(select_rewards_str);
-					vector<Item*> reward_items;
-					map<unsigned int, unsigned short>::iterator itr;
-					for (itr = rewards.begin(); itr != rewards.end(); itr++) {
-						if (itr->first > 0) {
-							Item* item = new Item(master_item_list.GetItem(itr->first));
-							if (item) {
-								if (itr->second > 0)
-									item->stack_count = itr->second;
-								reward_items.push_back(item);
-							}
-						}
-					}
-					packet2->setSubstructArrayLengthByName("reward_data", "num_select_rewards", reward_items.size());
-					for (int i = 0; i < reward_items.size(); i++) {
-						Item* item = reward_items[i];
-						packet2->setArrayDataByName("select_reward_id", item->details.item_id, i);
-						packet2->setItemArrayDataByName("select_item", item, client->GetPlayer(), i, 0, -1);
-						player->AddPendingSelectableItemReward(source_id, item); //item reference will be deleted after the player selects one
-					}
-				}
-				if (factions_map_str.length() > 0) {
-					map<unsigned int, signed int> faction_rewards = ParseSInt32Map(factions_map_str);
-					map<unsigned int, signed int>::iterator itr;
-					map<Faction*, signed int> factions;
-					for (itr = faction_rewards.begin(); itr != faction_rewards.end(); itr++) {
-						Faction* faction = master_faction_list.GetFaction(itr->first);
-						if (faction)
-							factions[faction] = itr->second;
-					}
-					packet2->setSubstructArrayLengthByName("reward_data", "num_factions", factions.size());
-					map<Faction*, signed int>::iterator faction_itr;
-					int8 i = 0;
-					for (faction_itr = factions.begin(); faction_itr != factions.end(); faction_itr++) {
-						packet2->setArrayDataByName("faction_name", faction_itr->first->name.c_str(), i);
-						sint32 amount = faction_itr->second;
-						packet2->setArrayDataByName("amount", amount, i);
-						if (amount > 0)
-							player->GetFactions()->IncreaseFaction(faction_itr->first->id, amount);
-						else
-							player->GetFactions()->DecreaseFaction(faction_itr->first->id, (amount * -1));
-						i++;
-					}
-				}				
-				client->QueuePacket(packet2->serialize());
-				safe_delete(packet2);
-			}*/
 	return 0;
 }
 
@@ -4213,22 +4172,41 @@ int EQ2Emu_lua_AddPrimaryEntityCommand(lua_State* state) {
 	return 0;
 }
 
+int EQ2Emu_lua_HasSpell(lua_State* state) {
+	if (!lua_interface)
+		return 0;
+	Spawn* player = lua_interface->GetSpawn(state);
+	int32 spellid = lua_interface->GetInt32Value(state, 2);
+	int16 tier = lua_interface->GetInt16Value(state, 3);
+	if (player && player->IsPlayer()) {
+		lua_interface->SetBooleanValue(state, ((Player*)player)->HasSpell(spellid, tier, true));
+		return 1;
+	}
+	return 0;
+}
+
 int EQ2Emu_lua_AddSpellBookEntry(lua_State* state) {
 	if (!lua_interface)
 		return 0;
 	Spawn* player = lua_interface->GetSpawn(state);
 	int32 spellid = lua_interface->GetInt32Value(state, 2);
 	int16 tier = lua_interface->GetInt16Value(state, 3);
+	int8 num_args = (int8)lua_interface->GetNumberOfArgs(state);
+	bool add_silently = lua_interface->GetBooleanValue(state, 4);
+	bool add_to_hotbar = true;
+	if (num_args > 4) {
+		add_to_hotbar = lua_interface->GetBooleanValue(state, 5);
+	}
 	Spell* spell = master_spell_list.GetSpell(spellid, tier);
 	if (player && spell && player->IsPlayer()) {
-		Client* client = player->GetZone()->GetClientBySpawn(player);
+		Client* client = player->GetClient();
 		if (client) {
 			if (!client->GetPlayer()->HasSpell(spellid, tier - 1, true))
 			{
 				Spell* spell = master_spell_list.GetSpell(spellid, tier);
 				client->GetPlayer()->AddSpellBookEntry(spellid, 1, client->GetPlayer()->GetFreeSpellBookSlot(spell->GetSpellData()->spell_book_type), spell->GetSpellData()->spell_book_type, spell->GetSpellData()->linked_timer, true);
 				client->GetPlayer()->UnlockSpell(spell);
-				client->SendSpellUpdate(spell);
+				client->SendSpellUpdate(spell, add_silently, add_to_hotbar);
 			}
 			else
 			{
@@ -4237,7 +4215,7 @@ int EQ2Emu_lua_AddSpellBookEntry(lua_State* state) {
 				client->GetPlayer()->RemoveSpellBookEntry(spell->GetSpellID());
 				client->GetPlayer()->AddSpellBookEntry(spell->GetSpellID(), spell->GetSpellTier(), old_slot, spell->GetSpellData()->spell_book_type, spell->GetSpellData()->linked_timer, true);
 				client->GetPlayer()->UnlockSpell(spell);
-				client->SendSpellUpdate(spell);
+				client->SendSpellUpdate(spell, add_silently, add_to_hotbar);
 			}
 
 
@@ -5537,6 +5515,51 @@ int EQ2Emu_lua_GetArchetypeName(lua_State* state) {
 	return 0;
 }
 
+int EQ2Emu_lua_SendWaypoints(lua_State* state) {
+	if (!lua_interface)
+		return 0;
+	Spawn* player = lua_interface->GetSpawn(state);
+	if (player && player->IsPlayer()) {
+		Client* client = player->GetClient();
+		if (client)
+			client->SendWaypoints();
+	}
+	return 0;
+}
+
+int EQ2Emu_lua_AddWaypoint(lua_State* state) {
+	if (!lua_interface)
+		return 0;
+	Spawn* player = lua_interface->GetSpawn(state);
+	string name = lua_interface->GetStringValue(state, 2);	
+	int32 type = lua_interface->GetInt32Value(state, 3);
+	if (type == 0)
+		type = 2;
+	if (name.length() > 0) {
+		if (player && player->IsPlayer()) {
+			Client* client = player->GetClient();
+			if (client)
+				client->AddWaypoint(name, type);
+		}
+	}
+	return 0;
+}
+
+int EQ2Emu_lua_RemoveWaypoint(lua_State* state) {
+	if (!lua_interface)
+		return 0;
+	Spawn* player = lua_interface->GetSpawn(state);
+	string name = lua_interface->GetStringValue(state, 2);	
+	if (name.length() > 0) {
+		if (player && player->IsPlayer()) {
+			Client* client = player->GetClient();
+			if (client)
+				client->RemoveWaypoint(name);
+		}
+	}
+	return 0;
+}
+
 int EQ2Emu_lua_AddWard(lua_State* state) {
 	if (!lua_interface)
 		return 0;
@@ -7177,6 +7200,119 @@ int EQ2Emu_lua_SetSkillValue(lua_State* state) {
 	return 0;
 }
 
+int EQ2Emu_lua_HasSkill(lua_State* state) {
+	if (!lua_interface)
+		return 0;
+	Spawn* player = lua_interface->GetSpawn(state);
+	int32 skill_id = lua_interface->GetInt32Value(state, 2);
+	if (skill_id > 0 && player && player->IsPlayer()) {
+		lua_interface->SetBooleanValue(state, ((Player*)player)->skill_list.HasSkill(skill_id));
+		return 1;
+	}
+	return 0;
+}
+
+int EQ2Emu_lua_AddSkill(lua_State* state) {
+	if (!lua_interface)
+		return 0;
+	Spawn* player_spawn = lua_interface->GetSpawn(state);
+	int32 skill_id = lua_interface->GetInt32Value(state, 2);
+	int16 current_val = lua_interface->GetInt16Value(state, 3);
+	int16 max_val = lua_interface->GetInt16Value(state, 4);
+	bool more_to_add = lua_interface->GetBooleanValue(state, 5);
+	if (skill_id > 0 && current_val > 0 && max_val > 0) {
+		if (player_spawn && player_spawn->IsPlayer()) {
+			Player* player = (Player*)player_spawn;
+			bool added = false;
+			if (!player->skill_list.HasSkill(skill_id)) {
+				player->AddSkill(skill_id, current_val, max_val, true);		
+				added = true;
+			}
+			if (!more_to_add) { //need to send update regardless, even if THIS skill wasn't added, otherwise if you have a list and the last item wasn't added but the previous ones were, it wouldn't send the update
+				Client* client = player->GetClient();
+				if (client) {
+					EQ2Packet* packet = player->GetSkills()->GetSkillPacket(client->GetVersion());
+					if (packet)
+						client->QueuePacket(packet);
+				}
+			}
+			if (added) {
+				lua_interface->SetBooleanValue(state, true);
+				return 1;
+			}
+		}
+		else {
+			lua_interface->LogError("%s: LUA AddSkill command error: Given spawn is not a player", lua_interface->GetScriptName(state));
+		}
+	}
+	else {
+		lua_interface->LogError("%s: LUA AddSkill command error: Required parameters not set", lua_interface->GetScriptName(state));
+	}
+	lua_interface->SetBooleanValue(state, false);
+	return 1;
+}
+
+int EQ2Emu_lua_RemoveSkill(lua_State* state) {
+	if (!lua_interface)
+		return 0;
+	Spawn* player_spawn = lua_interface->GetSpawn(state);
+	int32 skill_id = lua_interface->GetInt32Value(state, 2);
+	bool more_to_remove = lua_interface->GetBooleanValue(state, 3);
+	if (skill_id > 0) {
+		if (player_spawn && player_spawn->IsPlayer()) {
+			Player* player = (Player*)player_spawn;
+			if (player->skill_list.HasSkill(skill_id)) {
+				player->RemovePlayerSkill(skill_id);
+				if (!more_to_remove) {
+					Client* client = player->GetClient();
+					if (client) {
+						EQ2Packet* packet = player->GetSkills()->GetSkillPacket(client->GetVersion());
+						if (packet)
+							client->QueuePacket(packet);
+					}
+				}
+			}			
+		}
+		else {
+			lua_interface->LogError("%s: LUA RemoveSkill command error: Given spawn is not a player", lua_interface->GetScriptName(state));
+		}
+	}
+	else {
+		lua_interface->LogError("%s: LUA RemoveSkill command error: skill_id not set", lua_interface->GetScriptName(state));
+	}
+	return 0;
+}
+
+int EQ2Emu_lua_IncreaseSkillCapsByType(lua_State* state) {
+	if (!lua_interface)
+		return 0;
+	Spawn* player_spawn = lua_interface->GetSpawn(state);
+	int8 skill_type = lua_interface->GetInt8Value(state, 2);
+	int16 amount = lua_interface->GetInt8Value(state, 3);
+	bool more_to_increase = lua_interface->GetBooleanValue(state, 4);
+	if (amount > 0 && skill_type < 100) {
+		if (player_spawn && player_spawn->IsPlayer()) {
+			Player* player = (Player*)player_spawn;
+			player->skill_list.IncreaseSkillCapsByType(skill_type, amount);
+			if (!more_to_increase) {
+				Client* client = player->GetClient();
+				if (client) {
+					EQ2Packet* packet = player->GetSkills()->GetSkillPacket(client->GetVersion());
+					if (packet)
+						client->QueuePacket(packet);
+				}
+			}
+		}
+		else {
+			lua_interface->LogError("%s: LUA IncreaseSkillCapsByType command error: Given spawn is not a player", lua_interface->GetScriptName(state));
+		}
+	}
+	else {
+		lua_interface->LogError("%s: LUA IncreaseSkillCapsByType command error: Invalid parameters", lua_interface->GetScriptName(state));
+	}
+	return 0;
+}
+
 int EQ2Emu_lua_GetSkill(lua_State* state) {
 	if (!lua_interface)
 		return 0;
@@ -9332,9 +9468,6 @@ int EQ2Emu_lua_SetPlayerLevel(lua_State* state) {
 	}
 
 	client->ChangeLevel(client->GetPlayer()->GetLevel(), level);
-	client->GetPlayer()->SetXP(1);
-	client->GetPlayer()->SetNeededXP();
-
 	return 0;
 }
 
@@ -10091,7 +10224,7 @@ int EQ2Emu_lua_InstructionWindow(lua_State* state) {
 		lua_interface->LogError("LUA InstructionWindow command error: could not find client");
 		return 0;
 	}
-	if (text.length() == 0 || task1.length() == 0 || signal.length() == 0) {
+	if (text.length() == 0) {
 		lua_interface->LogError("LUA InstructionWindow required parameters not given");
 		return 0;
 	}
@@ -10572,16 +10705,16 @@ int EQ2Emu_lua_GetAlignment(lua_State* state) {
 	Spawn* spawn = lua_interface->GetSpawn(state);
 
 	if (!spawn) {
-		lua_interface->LogError("%s: LUA SetAlignment command error: spawn is not valid", lua_interface->GetScriptName(state));
+		lua_interface->LogError("%s: LUA GetAlignment command error: spawn is not valid", lua_interface->GetScriptName(state));
 		return 0;
 	}
 
 	if (!spawn->IsEntity()) {
-		lua_interface->LogError("%s: LUA SetAlignment command error: spawn is not an entity", lua_interface->GetScriptName(state));
+		lua_interface->LogError("%s: LUA GetAlignment command error: spawn is not an entity", lua_interface->GetScriptName(state));
 		return 0;
 	}
 	
-	lua_interface->SetInt32Value(state, ((Entity*)spawn)->GetAlignment());
+	lua_interface->SetSInt32Value(state, ((Entity*)spawn)->GetAlignment());
 	return 1;
 }
 

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

@@ -149,6 +149,8 @@ int EQ2Emu_lua_SetRequiredQuest(lua_State* state);
 int EQ2Emu_lua_SetRequiredHistory(lua_State* state);
 int EQ2Emu_lua_Despawn(lua_State* state);
 int EQ2Emu_lua_ChangeHandIcon(lua_State* state);
+int EQ2Emu_lua_SetVisualFlag(lua_State* state);
+int EQ2Emu_lua_SetInfoFlag(lua_State* state);
 int EQ2Emu_lua_AddHate(lua_State* state);
 int EQ2Emu_lua_GetZone(lua_State* state);
 int EQ2Emu_lua_GetZoneName(lua_State* state);
@@ -211,6 +213,7 @@ int EQ2Emu_lua_SetServerControlFlag(lua_State* state);
 int EQ2Emu_lua_ToggleTracking(lua_State* state);
 int EQ2Emu_lua_AddPrimaryEntityCommand(lua_State* state);
 int EQ2Emu_lua_AddSpellBookEntry(lua_State* state);
+int EQ2Emu_lua_HasSpell(lua_State* state);
 int EQ2Emu_lua_Attack(lua_State* state);
 int EQ2Emu_lua_ApplySpellVisual(lua_State* state);
 int EQ2Emu_lua_Interrupt(lua_State* state);
@@ -241,6 +244,7 @@ int EQ2Emu_lua_AddQuestPrereqRace(lua_State* state);
 int EQ2Emu_lua_AddQuestPrereqModelType(lua_State* state);
 int EQ2Emu_lua_AddQuestPrereqTradeskillLevel(lua_State* state);
 int EQ2Emu_lua_AddQuestPrereqTradeskillClass(lua_State* state);
+int EQ2Emu_lua_HasQuestRewardItem(lua_State* state);
 int EQ2Emu_lua_AddQuestRewardItem(lua_State* state);
 int EQ2Emu_lua_AddQuestSelectableRewardItem(lua_State* state);
 int EQ2Emu_lua_AddQuestRewardCoin(lua_State* state);
@@ -320,6 +324,11 @@ int EQ2Emu_lua_GetTempVariable(lua_State* state);
 int EQ2Emu_lua_GiveQuestItem(lua_State*state);
 int EQ2Emu_lua_SetQuestRepeatable(lua_State* state);
 
+
+int EQ2Emu_lua_AddWaypoint(lua_State* state);
+int EQ2Emu_lua_RemoveWaypoint(lua_State* state);
+int EQ2Emu_lua_SendWaypoints(lua_State* state);
+
 int EQ2Emu_lua_AddWard(lua_State* state);
 int EQ2Emu_lua_AddToWard(lua_State* state);
 int EQ2Emu_lua_RemoveWard(lua_State* state);
@@ -364,6 +373,10 @@ int EQ2Emu_lua_SetSkillMaxValue(lua_State* state);
 int EQ2Emu_lua_SetSkillValue(lua_State* state);
 int EQ2Emu_lua_GetSkill(lua_State* state);
 int EQ2Emu_lua_GetSkillIDByName(lua_State* state);
+int EQ2Emu_lua_HasSkill(lua_State* state);
+int EQ2Emu_lua_AddSkill(lua_State* state);
+int EQ2Emu_lua_RemoveSkill(lua_State* state);
+int EQ2Emu_lua_IncreaseSkillCapsByType(lua_State* state);
 
 int EQ2Emu_lua_AddProc(lua_State* state);
 int EQ2Emu_lua_RemoveProc(lua_State* state);

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

@@ -123,6 +123,10 @@ LuaInterface::~LuaInterface() {
 	safe_delete(spell_delete_timer);
 }
 
+int LuaInterface::GetNumberOfArgs(lua_State* state) {
+	return lua_gettop(state);
+}
+
 void LuaInterface::Process() {
 	if(shutting_down)
 		return;
@@ -753,6 +757,7 @@ void LuaInterface::RegisterFunctions(lua_State* state) {
 	lua_register(state, "Attack", EQ2Emu_lua_Attack);
 	lua_register(state, "ApplySpellVisual", EQ2Emu_lua_ApplySpellVisual);
 	
+	
 	lua_register(state, "IsPlayer", EQ2Emu_lua_IsPlayer);
 	lua_register(state, "FaceTarget", EQ2Emu_lua_FaceTarget);
 	lua_register(state, "MoveToLocation", EQ2Emu_lua_MoveToLocation);
@@ -843,6 +848,8 @@ void LuaInterface::RegisterFunctions(lua_State* state) {
 	lua_register(state, "KillSpawnByDistance", EQ2Emu_lua_KillSpawnByDistance);
 	lua_register(state, "Despawn", EQ2Emu_lua_Despawn);
 	lua_register(state, "ChangeHandIcon", EQ2Emu_lua_ChangeHandIcon);
+	lua_register(state, "SetVisualFlag", EQ2Emu_lua_SetVisualFlag);
+	lua_register(state, "SetInfoFlag", EQ2Emu_lua_SetInfoFlag);
 	lua_register(state, "IsBindAllowed", EQ2Emu_lua_IsBindAllowed);
 	lua_register(state, "IsGateAllowed", EQ2Emu_lua_IsGateAllowed);
 	lua_register(state, "Bind", EQ2Emu_lua_Bind);
@@ -853,6 +860,7 @@ void LuaInterface::RegisterFunctions(lua_State* state) {
 	lua_register(state, "ToggleTracking", EQ2Emu_lua_ToggleTracking);
 	lua_register(state, "AddPrimaryEntityCommand", EQ2Emu_lua_AddPrimaryEntityCommand);
 	lua_register(state, "AddSpellBookEntry", EQ2Emu_lua_AddSpellBookEntry);
+	lua_register(state, "HasSpell", EQ2Emu_lua_HasSpell);
 	lua_register(state, "Interrupt", EQ2Emu_lua_Interrupt);
 	lua_register(state, "Stealth", EQ2Emu_lua_Stealth);
 	lua_register(state, "IsInvis", EQ2Emu_lua_IsInvis);
@@ -880,6 +888,7 @@ void LuaInterface::RegisterFunctions(lua_State* state) {
 	lua_register(state, "AddQuestPrereqTradeskillLevel", EQ2Emu_lua_AddQuestPrereqTradeskillLevel);
 	lua_register(state, "AddQuestPrereqTradeskillClass", EQ2Emu_lua_AddQuestPrereqTradeskillClass);
 	lua_register(state, "AddQuestSelectableRewardItem", EQ2Emu_lua_AddQuestSelectableRewardItem);
+	lua_register(state, "HasQuestRewardItem", EQ2Emu_lua_HasQuestRewardItem);
 	lua_register(state, "AddQuestRewardItem", EQ2Emu_lua_AddQuestRewardItem);
 	lua_register(state, "AddQuestRewardCoin", EQ2Emu_lua_AddQuestRewardCoin);
 	lua_register(state, "AddQuestRewardFaction", EQ2Emu_lua_AddQuestRewardFaction);
@@ -955,6 +964,10 @@ void LuaInterface::RegisterFunctions(lua_State* state) {
 	lua_register(state, "GiveQuestItem", EQ2Emu_lua_GiveQuestItem);
 	lua_register(state, "SetQuestRepeatable", EQ2Emu_lua_SetQuestRepeatable);
 
+	lua_register(state, "AddWaypoint", EQ2Emu_lua_AddWaypoint);
+	lua_register(state, "RemoveWaypoint", EQ2Emu_lua_RemoveWaypoint);
+	lua_register(state, "SendWaypoints", EQ2Emu_lua_SendWaypoints);
+
 	lua_register(state, "AddWard", EQ2Emu_lua_AddWard);
 	lua_register(state, "AddToWard", EQ2Emu_lua_AddToWard);
 	lua_register(state, "RemoveWard", EQ2Emu_lua_RemoveWard);
@@ -997,6 +1010,10 @@ void LuaInterface::RegisterFunctions(lua_State* state) {
 	lua_register(state, "SetSkillValue", EQ2Emu_lua_SetSkillValue);
 	lua_register(state, "GetSkill", EQ2Emu_lua_GetSkill);
 	lua_register(state, "GetSkillIDByName", EQ2Emu_lua_GetSkillIDByName);
+	lua_register(state, "HasSkill", EQ2Emu_lua_HasSkill);
+	lua_register(state, "AddSkill", EQ2Emu_lua_AddSkill);
+	lua_register(state, "IncreaseSkillCapsByType", EQ2Emu_lua_IncreaseSkillCapsByType);
+	lua_register(state, "RemoveSkill", EQ2Emu_lua_RemoveSkill);
 	lua_register(state, "AddProc", EQ2Emu_lua_AddProc);
 	lua_register(state, "RemoveProc", EQ2Emu_lua_RemoveProc);
 	lua_register(state, "Knockback", EQ2Emu_lua_Knockback);

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

@@ -179,6 +179,7 @@ class LuaInterface {
 public:
 	LuaInterface();
 	~LuaInterface();
+	int				GetNumberOfArgs(lua_State* state);
 	bool			LoadLuaSpell(const char* name);
 	bool			LoadLuaSpell(string name);
 	bool			LoadItemScript(string name);

+ 3 - 3
EQ2/source/WorldServer/NPC.cpp

@@ -231,14 +231,14 @@ void NPC::InCombat(bool val){
 	in_combat = val;
 	if(val){
 		LogWrite(NPC__DEBUG, 3, "NPC", "'%s' engaged in combat with '%s'", this->GetName(), ( GetTarget() ) ? GetTarget()->GetName() : "Unknown" );
-		SetLockedNoLoot(3);
+		SetLockedNoLoot(ENCOUNTER_STATE_LOCKED);
 		AddIconValue(64);
 		// In combat so lets set the NPC's speed to its max speed
 		if (GetMaxSpeed() > 0)
 			SetSpeed(GetMaxSpeed());
 	}
 	else{
-		SetLockedNoLoot(1);
+		SetLockedNoLoot(ENCOUNTER_STATE_AVAILABLE);
 		RemoveIconValue(64);
 		if (GetHP() > 0){
 			SetTempActionState(-1); //re-enable action states on exiting combat
@@ -261,7 +261,7 @@ void NPC::InCombat(bool val){
 }
 
 bool NPC::HandleUse(Client* client, string type){
-	if(!client || type.length() == 0 || appearance.show_command_icon == 0)
+	if(!client || type.length() == 0 || (appearance.show_command_icon == 0 && appearance.display_hand_icon == 0))
 		return false;
 	EntityCommand* entity_command = FindEntityCommand(type);
 	if (entity_command) {

+ 65 - 29
EQ2/source/WorldServer/Player.cpp

@@ -1330,6 +1330,8 @@ EQ2Packet* PlayerInfo::serialize(int16 version, int16 modifyPos, int32 modifyVal
 			packet->setSubstructDataByName("spell_effects", "expire_timestamp", expireTimestamp, i, 0);
 			packet->setSubstructDataByName("spell_effects", "icon", info_struct->spell_effects[i].icon, i, 0);
 			packet->setSubstructDataByName("spell_effects", "icon_type", info_struct->spell_effects[i].icon_backdrop, i, 0);
+			if(info_struct->spell_effects[i].spell && info_struct->spell_effects[i].spell->spell && info_struct->spell_effects[i].spell->spell->GetSpellData()->friendly_spell == 1)
+				packet->setSubstructDataByName("spell_effects", "cancellable", 1, i); 
 		}
 		player->GetMaintainedMutex()->releasereadlock(__FUNCTION__, __LINE__);
 		player->GetSpellEffectMutex()->releasereadlock(__FUNCTION__, __LINE__);
@@ -1682,6 +1684,14 @@ int8 Player::ConvertSlotToClient(int8 slot, int16 version) {
 		else if (slot > EQ2_EARS_SLOT_1 && slot <= EQ2_WAIST_SLOT)
 			slot -= 1;
 	}
+	else if (version <= 546) {
+		if (slot == EQ2_FOOD_SLOT)
+			slot = EQ2_DOF_FOOD_SLOT;
+		else if (slot == EQ2_DRINK_SLOT)
+			slot = EQ2_DOF_DRINK_SLOT;
+		else if (slot > EQ2_EARS_SLOT_1 && slot <= EQ2_WAIST_SLOT)
+			slot -= 1;
+	}
 	return slot;
 }
 
@@ -1694,6 +1704,14 @@ int8 Player::ConvertSlotFromClient(int8 slot, int16 version) {
 		else if (slot > EQ2_EARS_SLOT_1 && slot <= EQ2_WAIST_SLOT)
 			slot += 1;
 	}
+	else if (version <= 546) {
+		if (slot == EQ2_DOF_FOOD_SLOT)
+			slot = EQ2_FOOD_SLOT;
+		else if (slot == EQ2_DOF_DRINK_SLOT)
+			slot = EQ2_DRINK_SLOT;
+		else if (slot > EQ2_EARS_SLOT_1 && slot <= EQ2_WAIST_SLOT)
+			slot += 1;
+	}
 	return slot;
 }
 
@@ -2197,7 +2215,7 @@ EQ2Packet* Player::GetQuickbarPacket(int16 version){
 
 void Player::AddSpellBookEntry(int32 spell_id, int8 tier, sint32 slot, int32 type, int32 timer, bool save_needed){
 	SpellBookEntry* spell = new SpellBookEntry;
-	spell->status = 161;
+	spell->status = 169;
 	spell->slot = slot;
 	spell->spell_id = spell_id;
 	spell->type = type;
@@ -2442,13 +2460,21 @@ int8 Player::GetSpellSlot(int32 spell_id){
 
 void Player::AddSkill(int32 skill_id, int16 current_val, int16 max_val, bool save_needed){
 	Skill* master_skill = master_skill_list.GetSkill(skill_id);
-	Skill* skill = new Skill(master_skill);
-	skill->current_val = current_val;
-	skill->previous_val = current_val;
-	skill->max_val = max_val;
-	if(save_needed)
-		skill->save_needed = true;
-	skill_list.AddSkill(skill);
+	if (master_skill) {
+		Skill* skill = new Skill(master_skill);
+		skill->current_val = current_val;
+		skill->previous_val = current_val;
+		skill->max_val = max_val;
+		if (save_needed)
+			skill->save_needed = true;
+		skill_list.AddSkill(skill);
+	}
+}
+
+void Player::RemovePlayerSkill(int32 skill_id, bool save) {
+	Skill* skill = skill_list.GetSkill(skill_id);
+	if (skill)
+		RemoveSkillFromDB(skill, save);
 }
 
 void Player::RemoveSkillFromDB(Skill* skill, bool save) {
@@ -2501,18 +2527,20 @@ void Player::LockAllSpells() {
 	MSpellsBook.writelock(__FUNCTION__, __LINE__);
 	for (itr = spells.begin(); itr != spells.end(); itr++) {
 		if ((*itr)->type != SPELL_BOOK_TYPE_TRADESKILL)
-			AddSpellStatus((*itr), SPELL_STATUS_LOCK, false);
+			RemoveSpellStatus((*itr), SPELL_STATUS_LOCK, false);
 	}
 
 	MSpellsBook.releasewritelock(__FUNCTION__, __LINE__);
 }
 
-void Player::UnlockAllSpells(bool modify_recast) {
+void Player::UnlockAllSpells(bool modify_recast, Spell* exception) {
 	vector<SpellBookEntry*>::iterator itr;
-
+	int32 exception_spell_id = 0;
+	if (exception)
+		exception_spell_id = exception->GetSpellID();
 	MSpellsBook.writelock(__FUNCTION__, __LINE__);
 	for (itr = spells.begin(); itr != spells.end(); itr++) {
-		if ((*itr)->type != SPELL_BOOK_TYPE_TRADESKILL)
+		if ((*itr)->spell_id != exception_spell_id && (*itr)->type != SPELL_BOOK_TYPE_TRADESKILL)
 			AddSpellStatus((*itr), SPELL_STATUS_LOCK, modify_recast);
 	}
 
@@ -2532,8 +2560,10 @@ void Player::LockSpell(Spell* spell, int16 recast) {
 }
 
 void Player::UnlockSpell(Spell* spell) {
+	if (spell->GetStayLocked())
+		return;
 	vector<SpellBookEntry*>::iterator itr;
-	SpellBookEntry* spell2;
+	SpellBookEntry* spell2;	
 	MSpellsBook.writelock(__FUNCTION__, __LINE__);
 	for (itr = spells.begin(); itr != spells.end(); itr++) {
 		spell2 = *itr;
@@ -2612,24 +2642,27 @@ void Player::ModifySpellStatus(SpellBookEntry* spell, sint16 value, bool modify_
 		spell->recast = recast;
 		spell->recast_available = Timer::GetCurrentTime2()	+ (recast * 100);
 	}
-	if (modify_recast || spell->recast_available <= Timer::GetCurrentTime2() || value == 4)
+	if (modify_recast || spell->recast_available <= Timer::GetCurrentTime2() || value == 4) {
 		spell->status += value; // use set/remove spell status now
+	}
 }
 void Player::AddSpellStatus(SpellBookEntry* spell, sint16 value, bool modify_recast, int16 recast) {
 	if (modify_recast) {
 		spell->recast = recast;
 		spell->recast_available = Timer::GetCurrentTime2() + (recast * 100);
 	}
-	if (modify_recast || spell->recast_available <= Timer::GetCurrentTime2() || value == 4)
+	if (modify_recast || spell->recast_available <= Timer::GetCurrentTime2() || value == 4) {
 		spell->status = spell->status | value;
+	}
 }
 void Player::RemoveSpellStatus(SpellBookEntry* spell, sint16 value, bool modify_recast, int16 recast) {
 	if (modify_recast) {
 		spell->recast = recast;
 		spell->recast_available = Timer::GetCurrentTime2() + (recast * 100);
 	}
-	if (modify_recast || spell->recast_available <= Timer::GetCurrentTime2() || value == 4)
+	if (modify_recast || spell->recast_available <= Timer::GetCurrentTime2() || value == 4) {
 		spell->status = spell->status & ~value;
+	}
 
 }
 void Player::SetSpellStatus(Spell* spell, int8 status){
@@ -2790,9 +2823,11 @@ EQ2Packet* Player::GetSpellBookUpdatePacket(int16 version) {
 				if (spell_entry->spell_id == 0)
 					continue;
 				spell = master_spell_list.GetSpell(spell_entry->spell_id, spell_entry->tier);
-				if (spell) {
-					if (spell_entry->recast_available == 0 || Timer::GetCurrentTime2() > spell_entry->recast_available)
+				if (spell) {			
+					if (spell_entry->recast_available == 0 || Timer::GetCurrentTime2() > spell_entry->recast_available) {
 						packet->setSubstructArrayDataByName("spells", "available", 1, 0, ptr);
+					}
+										
 					packet->setSubstructArrayDataByName("spells", "spell_id", spell_entry->spell_id, 0, ptr);
 					packet->setSubstructArrayDataByName("spells", "type", spell_entry->type, 0, ptr);
 					packet->setSubstructArrayDataByName("spells", "recast_available", spell_entry->recast_available, 0, ptr);
@@ -2801,9 +2836,8 @@ EQ2Packet* Player::GetSpellBookUpdatePacket(int16 version) {
 					packet->setSubstructArrayDataByName("spells", "icon", (spell->GetSpellIcon() * -1) - 1, 0, ptr);
 					packet->setSubstructArrayDataByName("spells", "icon_type", spell->GetSpellIconBackdrop(), 0, ptr);
 					packet->setSubstructArrayDataByName("spells", "icon2", spell->GetSpellIconHeroicOp(), 0, ptr);
-					packet->setSubstructArrayDataByName("spells", "unique_id", (spell_entry->tier + 1) * -1, 0, ptr);
+					packet->setSubstructArrayDataByName("spells", "unique_id", (spell_entry->tier + 1) * -1, 0, ptr); //this is actually GetSpellNameCrc(spell->GetName()), but hijacking it for spell tier
 					packet->setSubstructArrayDataByName("spells", "charges", 255, 0, ptr);
-
 					// Beastlord and Channeler spell support
 					if (spell->GetSpellData()->savage_bar == 1)
 						packet->setSubstructArrayDataByName("spells", "unknown6", 32, 0, ptr); // advantages
@@ -3746,9 +3780,7 @@ bool Player::AddXP(int32 xp_amount){
 			return false;
 		}
 		xp_amount -= GetNeededXP() - GetXP();
-		SetLevel(GetLevel() + 1);
-		SetXP(0);
-		SetNeededXP();
+		SetLevel(GetLevel() + 1);		
 	}
 	SetXP(GetXP() + xp_amount);
 	GetPlayerInfo()->CalculateXPPercentages();
@@ -4039,7 +4071,7 @@ PacketStruct* Player::GetQuestJournalPacket(bool all_quests, int16 version, int3
 				if(!all_quests && !itr->second->GetUpdateRequired())
 					continue;
 				quest = itr->second;
-				if(!quest->GetDeleted() && !quest->GetCompleted())
+				if(!quest->GetDeleted())
 					packet->setArrayDataByName("active", 1, i);
 				packet->setArrayDataByName("name", quest->GetName(), i);
 				packet->setArrayDataByName("quest_type", quest->GetType(), i);
@@ -4053,8 +4085,10 @@ PacketStruct* Player::GetQuestJournalPacket(bool all_quests, int16 version, int3
 					packet->setArrayDataByName("visible", 1, i);
 					display_status += QUEST_DISPLAY_STATUS_COMPLETED;					
 				}
-				if (updated)
+				if (updated) {
 					packet->setArrayDataByName("quest_updated", 1, i);
+					packet->setArrayDataByName("journal_updated", 1, i);
+				}
 				packet->setArrayDataByName("quest_id", quest->GetQuestID(), i);
 				packet->setArrayDataByName("day", quest->GetDay(), i);
 				packet->setArrayDataByName("month", quest->GetMonth(), i);
@@ -4102,7 +4136,7 @@ PacketStruct* Player::GetQuestJournalPacket(bool all_quests, int16 version, int3
 			//packet->setDataByName("unknown4", 0);
 			packet->setDataByName("visible_quest_id", current_quest_id);
 		}
-		MPlayerQuests.unlock();
+		MPlayerQuests.unlock();		
 		packet->setDataByName("player_crc", crc);
 		packet->setDataByName("player_name", GetName());
 		packet->setDataByName("used_quests", total_quests_num - total_completed_quests);
@@ -4144,7 +4178,7 @@ PacketStruct* Player::GetQuestJournalPacket(Quest* quest, int16 version, int32 c
 			packet->setArrayDataByName("turned_in", 1);
 			packet->setArrayDataByName("completed", 1);			
 			display_status += QUEST_DISPLAY_STATUS_COMPLETED;
-		}
+		}		
 		packet->setArrayDataByName("quest_id", quest->GetQuestID());
 		packet->setArrayDataByName("day", quest->GetDay());
 		packet->setArrayDataByName("month", quest->GetMonth());
@@ -4189,8 +4223,10 @@ PacketStruct* Player::GetQuestJournalPacket(Quest* quest, int16 version, int32 c
 			packet->setArrayDataByName("repeatable", 1);
 
 		packet->setArrayDataByName("display_status", display_status);
-		if (updated)
-			packet->setDataByName("quest_updated", 1);
+		if (updated) {
+			packet->setArrayDataByName("quest_updated", 1);
+			packet->setArrayDataByName("journal_updated", 1);
+		}
 		packet->setDataByName("visible_quest_id", quest->GetQuestID());
 		packet->setDataByName("player_crc", crc);
 		packet->setDataByName("player_name", GetName());

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

@@ -493,6 +493,7 @@ public:
 	/// <returns>True if the player has enough coins</returns>
 	bool HasCoins(int64 val);
 	void AddSkill(int32 skill_id, int16 current_val, int16 max_val, bool save_needed = false);
+	void RemovePlayerSkill(int32 skill_id, bool save = false);
 	void RemoveSkillFromDB(Skill* skill, bool save = false);
 	void AddSpellBookEntry(int32 spell_id, int8 tier, sint32 slot, int32 type, int32 timer, bool save_needed = false);
 	SpellBookEntry* GetSpellBookSpell(int32 spell_id);
@@ -576,6 +577,11 @@ public:
 	void	ClearRemovedSpawn(Spawn* spawn);
 	bool	ShouldSendSpawn(Spawn* spawn);
 	Client* client = 0;
+	void SetLevel(int16 level, bool setUpdateFlags = true) {
+		SetInfo(&appearance.level, level, setUpdateFlags);
+		SetXP(0);
+		SetNeededXP();
+	}
 
 	Spawn* GetSpawnWithPlayerID(int32 id){
 		Spawn* spawn = 0;
@@ -890,7 +896,7 @@ public:
 	void LockAllSpells();
 
 	/// <summary>Unlocks all Spells, Combat arts, and Abilities (not trade skill spells)</summary>
-	void UnlockAllSpells(bool modify_recast = false);
+	void UnlockAllSpells(bool modify_recast = false, Spell* exception = 0);
 
 	/// <summary>Locks the given spell as well as all spells with a shared timer</summary>
 	void LockSpell(Spell* spell, int16 recast);

+ 8 - 3
EQ2/source/WorldServer/Quests.cpp

@@ -302,6 +302,7 @@ Quest::Quest(int32 in_id){
 	reward_coins = 0;
 	reward_coins_max = 0;
 	completed_flag = false;
+	has_sent_last_update = false;
 	enc_level = 0;
 	reward_exp = 0;
 	reward_tsexp = 0;
@@ -345,6 +346,7 @@ Quest::Quest(Quest* old_quest){
 	reward_tsexp = old_quest->reward_tsexp;
 	generated_coin = old_quest->generated_coin;
 	completed_flag = old_quest->completed_flag;
+	has_sent_last_update = old_quest->has_sent_last_update;
 	yellow_name = old_quest->yellow_name;
 	m_questFlags = old_quest->m_questFlags;
 	id = old_quest->id;
@@ -995,7 +997,7 @@ EQ2Packet* Quest::QuestJournalReply(int16 version, int32 player_crc, Player* pla
 
 		packet->setDataByName("bullets", 1);
 		if (old_completed_quest) {
-			if (version >= 1096) {
+			if (version >= 1096 || version == 546) {
 				packet->setDataByName("complete", 1);
 				packet->setDataByName("complete2", 1);
 				packet->setDataByName("complete3", 1);
@@ -1008,7 +1010,7 @@ EQ2Packet* Quest::QuestJournalReply(int16 version, int32 player_crc, Player* pla
 				packet->setDataByName("unknown3", 1, 6);
 			}
 		}
-		else if (version >= 1096 && GetCompleted()) {
+		else if ((version >= 1096 || version == 546) && GetCompleted() && HasSentLastUpdate()) { //need to send last quest update before erasing all progress of the quest
 			packet->setDataByName("complete", 1);
 			packet->setDataByName("complete2", 1);
 			packet->setDataByName("complete3", 1);
@@ -1225,12 +1227,15 @@ EQ2Packet* Quest::QuestJournalReply(int16 version, int32 player_crc, Player* pla
 
 				}
 			}
+			if (GetCompleted()) { //mark the last update as being sent, next time we send the quest reply, it will only be a brief portion
+				SetSentLastUpdate(true);
+			}
 		}
 		MQuestSteps.unlock();
 
 
 		string reward_str = "";
-		if (version >= 1096)
+		if (version >= 1096 || version == 546)
 			reward_str = "reward_data_";
 		string tmp = reward_str + "reward";
 		packet->setDataByName(tmp.c_str(), "Quest Reward!");

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

@@ -226,6 +226,8 @@ public:
 	Player*				GetPlayer();
 	void				SetPlayer(Player* in_player);
 	bool				GetCompleted();
+	bool				HasSentLastUpdate() { return has_sent_last_update; }
+	void				SetSentLastUpdate(bool val) { has_sent_last_update = val; }
 	void				SetCompletedDescription(string desc);
 	const char*			GetCompletedDescription();
 	int32				GetExpReward();
@@ -312,6 +314,7 @@ protected:
 	bool				turned_in;
 	bool				update_needed;
 	bool				deleted;
+	bool				has_sent_last_update;
 
 	string				completed_description;
 	

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

@@ -155,6 +155,14 @@ map<int32, Skill*>* PlayerSkillList::GetAllSkills(){
 	return &skills;
 }
 
+void PlayerSkillList::SetSkillValuesByType(int8 type, int16 value, bool send_update) {
+	map<int32, Skill*>::iterator itr;
+	for (itr = skills.begin(); itr != skills.end(); itr++) {
+		if (itr->second && itr->second->skill_type == type)
+			SetSkill(itr->second, value, send_update);
+	}
+}
+
 void PlayerSkillList::SetSkillCapsByType(int8 type, int16 value){
 	map<int32, Skill*>::iterator itr;
 	for(itr = skills.begin(); itr != skills.end(); itr++){
@@ -220,19 +228,20 @@ void PlayerSkillList::DecreaseSkill(int32 skill_id, int16 amount){
 	DecreaseSkill(GetSkill(skill_id), amount);
 }
 
-void PlayerSkillList::SetSkill(Skill* skill, int16 value){
+void PlayerSkillList::SetSkill(Skill* skill, int16 value, bool send_update){
 	if(skill){
 		skill->previous_val = skill->current_val;
 		skill->current_val = value;
 		if(skill->current_val > skill->max_val)
 			skill->max_val = skill->current_val;
 		skill->save_needed = true;
-		AddSkillUpdateNeeded(skill);
+		if(send_update)
+			AddSkillUpdateNeeded(skill);
 	}
 }
 
-void PlayerSkillList::SetSkill(int32 skill_id, int16 value){
-	SetSkill(GetSkill(skill_id), value);
+void PlayerSkillList::SetSkill(int32 skill_id, int16 value, bool send_update){
+	SetSkill(GetSkill(skill_id), value, send_update);
 }
 
 void PlayerSkillList::IncreaseSkillCap(Skill* skill, int16 amount){
@@ -319,9 +328,15 @@ EQ2Packet* PlayerSkillList::GetSkillPacket(int16 version){
 	PacketStruct* packet = configReader.getStruct("WS_UpdateSkillBook", version);
 	if(packet){
 		if(packet_count < skills.size()){
-			int16 size = 21 * skills.size() + 8;
-			if (version <= 283) {
-				size = 12 * skills.size()+6;
+			int16 size = 0;
+			if (version > 546) {
+				size = 21 * skills.size() + 8;
+			}
+			else if (version <= 283) {
+				size = 12 * skills.size() + 6;
+			}
+			else if (version <= 546) {
+				size = 21 * skills.size() + 7;
 			}
 			if(!orig_packet){				
 				xor_packet = new uchar[size];
@@ -360,7 +375,11 @@ EQ2Packet* PlayerSkillList::GetSkillPacket(int16 version){
 				}
 
 				packet->setArrayDataByName("skill_id", skill->skill_id, i);
-				packet->setArrayDataByName("type", skill->skill_type, i);
+				if (version <= 546 && skill->skill_type >= SKILL_TYPE_GENERAL) { //covert it to DOF types
+					packet->setArrayDataByName("type", skill->skill_type-2, i);					
+				}
+				else
+					packet->setArrayDataByName("type", skill->skill_type, i);
 				packet->setArrayDataByName("current_val", skill->current_val, i);
 				packet->setArrayDataByName("base_val", skill->current_val, i);// skill->
 				packet->setArrayDataByName("skill_delta", 0, i);// skill_with_bonuses- skill->current_val

+ 26 - 4
EQ2/source/WorldServer/Skills.h

@@ -25,15 +25,29 @@
 #include "../common/types.h"
 #include "MutexMap.h"
 
-#define SKILL_TYPE_COMBAT 1
+#define SKILL_TYPE_WEAPONRY 1
 #define SKILL_TYPE_SPELLCASTING 2
 #define SKILL_TYPE_AVOIDANCE 3
+#define SKILL_TYPE_ARMOR 4
+#define SKILL_TYPE_SHIELD 5
 #define SKILL_TYPE_HARVESTING 6
 #define SKILL_TYPE_ARTISAN 7
 #define SKILL_TYPE_CRAFTSMAN 8
 #define SKILL_TYPE_OUTFITTER 9
 #define SKILL_TYPE_SCHOLAR 10
-#define SKILL_TYPE_GENERAL 12
+#define SKILL_TYPE_GENERAL 13
+#define SKILL_TYPE_LANGUAGE 14
+#define SKILL_TYPE_CLASS 15
+#define SKILL_TYPE_COMBAT 16
+#define SKILL_TYPE_WEAPON 17
+#define SKILL_TYPE_TSKNOWLEDGE 18
+
+#define SKILL_TYPE_GENERAL_DOF 11
+#define SKILL_TYPE_LANGUAGE_DOF 12
+#define SKILL_TYPE_CLASS_DOF 13
+#define SKILL_TYPE_COMBAT_DOF 14
+#define SKILL_TYPE_WEAPON_DOF 15
+#define SKILL_TYPE_TSKNOWLEDGE_DOF 16
 
 #define SKILL_ID_SCULPTING 1039865549
 #define SKILL_ID_FLETCHING 3076004370
@@ -44,6 +58,13 @@
 #define SKILL_ID_SCRIBING 773137566
 #define SKILL_ID_CHEMISTRY 2557647574
 #define SKILL_ID_ARTIFICING 3330500131
+#define SKILL_ID_ARTIFICING 3330500131
+
+//the following update the current_value to the max_value as soon as the max_value is updated
+#define SKILL_ID_DUALWIELD 1852383242
+#define SKILL_ID_FISTS 3177806075
+#define SKILL_ID_DESTROYING 3429135390
+#define SKILL_ID_MAGIC_AFFINITY 2072844078
 
 /* Each SkillBonus is comprised of multiple possible skill bonus values.  This is so one spell can modify
    more than one skill */
@@ -107,8 +128,8 @@ public:
 	void IncreaseSkill(int32 skill_id, int16 amount);
 	void DecreaseSkill(Skill* skill, int16 amount);
 	void DecreaseSkill(int32 skill_id, int16 amount);
-	void SetSkill(Skill* skill, int16 value);
-	void SetSkill(int32 skill_id, int16 value);
+	void SetSkill(Skill* skill, int16 value, bool send_update = true);
+	void SetSkill(int32 skill_id, int16 value, bool send_update = true);
 
 	void IncreaseSkillCap(Skill* skill, int16 amount);
 	void IncreaseSkillCap(int32 skill_id, int16 amount);
@@ -119,6 +140,7 @@ public:
 	void IncreaseAllSkillCaps(int16 value);
 	void IncreaseSkillCapsByType(int8 type, int16 value);
 	void SetSkillCapsByType(int8 type, int16 value);
+	void SetSkillValuesByType(int8 type, int16 value, bool send_update = true);
 	void AddSkillUpdateNeeded(Skill* skill);
 
 	void AddSkillBonus(int32 spell_id, int32 skill_id, float value);

+ 8 - 5
EQ2/source/WorldServer/Spawn.cpp

@@ -339,14 +339,14 @@ void Spawn::InitializeVisPacketData(Player* player, PacketStruct* vis_packet) {
 			vis_flags += 4;
 	}
 
-	if (version <= 546 && vis_flags > 0)
+	if (version <= 546 && (vis_flags > 1 || appearance.display_hand_icon > 0)) //interactable
 		vis_flags = 1;
-	
 	vis_packet->setDataByName("vis_flags", vis_flags);
 
 
-	if (MeetsSpawnAccessRequirements(player))
+	if (MeetsSpawnAccessRequirements(player)) {
 		vis_packet->setDataByName("hand_flag", appearance.display_hand_icon);
+	}
 	else {
 		if ((req_quests_override & 256) > 0)
 			vis_packet->setDataByName("hand_flag", 1);
@@ -1008,7 +1008,9 @@ EQ2Packet* Spawn::spawn_update_packet(Player* player, int16 version, bool overri
 	ptr += pos_packet_size;
 	memcpy(ptr, vis_changes ? vis_changes : &null_byte, tmp_vis_packet_size);
 
-	EQ2Packet* ret_packet = new EQ2Packet(OP_ClientCmdMsg, tmp, size);
+	EQ2Packet* ret_packet = 0;
+	if(info_packet_size + pos_packet_size + vis_packet_size > 0)
+		ret_packet = new EQ2Packet(OP_ClientCmdMsg, tmp, size);
 	delete[] tmp;
 	safe_delete_array(info_changes);
 	safe_delete_array(vis_changes);
@@ -2226,7 +2228,8 @@ void Spawn::InitializeInfoPacketData(Player* spawn, PacketStruct* packet) {
 	}
 	if (GetHP() <= 0 && IsEntity()) {
 		packet->setDataByName("corpse", 1);
-		packet->setDataByName("loot_icon", 1); 
+		if(HasLoot())
+			packet->setDataByName("loot_icon", 1); 
 	}
 	if (!IsPlayer())
 		packet->setDataByName("npc", 1);

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

@@ -152,6 +152,13 @@
 #define INFO_VIS_FLAG_MOUNTED               4
 #define INFO_VIS_FLAG_CROUCH                8
 
+#define ENCOUNTER_STATE_NONE				0
+#define ENCOUNTER_STATE_AVAILABLE			1
+#define ENCOUNTER_STATE_BROKEN				2
+#define ENCOUNTER_STATE_LOCKED				3
+#define ENCOUNTER_STATE_OVERMATCHED			4
+#define ENCOUNTER_STATE_NO_REWARD			5
+
 using namespace std;
 class Spell;
 class ZoneServer;
@@ -293,6 +300,7 @@ public:
 		entity_command->default_allow_list = default_allow_list;
 		return entity_command;
 	}
+	virtual Client* GetClient() { return 0; }
 	void AddChangedZoneSpawn();
 	void AddPrimaryEntityCommand(const char* name, float distance, const char* command, const char* error_text, int16 cast_time, int32 spell_visual, bool defaultDenyList = false, Player* player = NULL);
 	void RemovePrimaryEntityCommand(const char* command);
@@ -446,7 +454,7 @@ public:
 	void SetEncounterLevel(int8 enc_level, bool setUpdateFlags = true){
 		SetInfo(&appearance.encounter_level, enc_level, setUpdateFlags);
 	}
-	void SetLevel(int16 level, bool setUpdateFlags = true){
+	virtual void SetLevel(int16 level, bool setUpdateFlags = true){
 		SetInfo(&appearance.level, level, setUpdateFlags);
 	}	
 	void SetTSLevel(int16 tradeskill_level, bool setUpdateFlags = true){

+ 46 - 7
EQ2/source/WorldServer/SpellProcess.cpp

@@ -392,12 +392,33 @@ bool SpellProcess::DeleteCasterSpell(LuaSpell* spell, string reason){
 				if(target && target->IsPlayer() && spell->spell->GetSpellData()->fade_message.length() > 0){
 					Client* client = target->GetZone()->GetClientBySpawn(target);
 					if(client){
+						bool send_to_sender = true;
 						string fade_message = spell->spell->GetSpellData()->fade_message;
 						if(fade_message.find("%t") != string::npos)
-							fade_message.replace(fade_message.find("%t"), 2, target->GetName());
+							fade_message.replace(fade_message.find("%t"), 2, target->GetName());						
 						client->Message(CHANNEL_SPELLS_OTHER, fade_message.c_str());
 					}
 				}
+				if (target && target->IsPlayer() && spell->spell->GetSpellData()->fade_message.length() > 0) {
+					Client* client = target->GetZone()->GetClientBySpawn(target);
+					if (client) {
+						bool send_to_sender = true;
+						string fade_message_others = spell->spell->GetSpellData()->fade_message_others;
+						if (fade_message_others.find("%t") != string::npos)
+							fade_message_others.replace(fade_message_others.find("%t"), 2, target->GetName());
+						if (fade_message_others.find("%c") != string::npos)
+							fade_message_others.replace(fade_message_others.find("%c"), 2, spell->caster->GetName());
+						if (fade_message_others.find("%T") != string::npos) {
+							fade_message_others.replace(fade_message_others.find("%T"), 2, target->GetName());
+							send_to_sender = false;
+						}
+						if (fade_message_others.find("%C") != string::npos) {
+							fade_message_others.replace(fade_message_others.find("%C"), 2, spell->caster->GetName());
+							send_to_sender = false;
+						}
+						spell->caster->GetZone()->SimpleMessage(CHANNEL_SPELLS_OTHER, fade_message_others.c_str(), target, 50, send_to_sender);
+					}
+				}
 			}
 			spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__);
 			ret = true;
@@ -522,7 +543,10 @@ void SpellProcess::SendStartCast(LuaSpell* spell, Client* client){
 
 void SpellProcess::SendFinishedCast(LuaSpell* spell, Client* client){
 	if(client && spell && spell->spell){
-		UnlockAllSpells(client);
+		if (spell->spell->GetSpellData()->cast_type == SPELL_CAST_TYPE_TOGGLE)
+			UnlockAllSpells(client, spell->spell);
+		else
+			UnlockAllSpells(client);
 		if(spell->resisted && spell->spell->GetSpellData()->recast > 0)
 			CheckRecast(spell->spell, client->GetPlayer(), 0.5); // half sec recast on resisted spells
 		else if (!spell->interrupted && spell->spell->GetSpellData()->cast_type != SPELL_CAST_TYPE_TOGGLE)
@@ -557,9 +581,9 @@ void SpellProcess::LockAllSpells(Client* client){
 	}
 }
 
-void SpellProcess::UnlockAllSpells(Client* client){
+void SpellProcess::UnlockAllSpells(Client* client, Spell* exception){
 	if(client)
-		client->GetPlayer()->UnlockAllSpells();
+		client->GetPlayer()->UnlockAllSpells(false, exception);
 }
 
 void SpellProcess::UnlockSpell(Client* client, Spell* spell){
@@ -1447,11 +1471,20 @@ bool SpellProcess::CastProcessedSpell(LuaSpell* spell, bool passive){
 			}
 			if(spell->spell->GetSpellData()->effect_message.length() > 0){
 				string effect_message = spell->spell->GetSpellData()->effect_message;
-				if(effect_message.find("%t") < 0xFFFFFFFF)
+				bool send_to_sender = true;
+				if(effect_message.find("%t") != string::npos)
 					effect_message.replace(effect_message.find("%t"), 2, target->GetName());
 				if (effect_message.find("%c") != string::npos)
 					effect_message.replace(effect_message.find("%c"), 2, spell->caster->GetName());
-				spell->caster->GetZone()->SimpleMessage(CHANNEL_SPELLS_OTHER, effect_message.c_str(), target, 50);
+				if (effect_message.find("%T") != string::npos) {
+					effect_message.replace(effect_message.find("%T"), 2, target->GetName());
+					send_to_sender = false;
+				}
+				if (effect_message.find("%C") != string::npos) {
+					effect_message.replace(effect_message.find("%C"), 2, spell->caster->GetName());
+					send_to_sender = false;
+				}
+				spell->caster->GetZone()->SimpleMessage(CHANNEL_SPELLS_OTHER, effect_message.c_str(), target, 50, send_to_sender);
 			}
 			target->GetZone()->CallSpawnScript(target, SPAWN_SCRIPT_CASTED_ON, spell->caster, spell->spell->GetName());
 		}
@@ -1466,7 +1499,13 @@ bool SpellProcess::CastProcessedSpell(LuaSpell* spell, bool passive){
 			
 			//LogWrite(SPELL__ERROR, 0, "Spell", "No precast function found for %s", ((Entity*)target)->GetName());
 			target = zone->GetSpawnByID(spell->targets.at(i));
-			
+			if (!target && spell->targets.at(i) == spell->caster->GetID()) {
+				target = spell->caster;
+			}
+			if (!target) {
+				client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Zone has not finished loading process yet.  Try again later.");
+				continue;
+			}
 			if (i == 0 && !spell->spell->GetSpellData()->not_maintained) {
 				spell->caster->AddMaintainedSpell(spell);
 				//((Entity*)target)->AddMaintainedSpell(spell);

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

@@ -244,7 +244,7 @@ public:
 
 	/// <summary>Unlock all the spells for the given client</summary>
 	/// <param name='client'>Client to unlock the spells for</param>
-	void UnlockAllSpells(Client* client);
+	void UnlockAllSpells(Client* client, Spell* exception = 0);
 
 	/// <summary>Unlock a single spell for the given client</summary>
 	/// <param name='client'>The client to unlock the spell for</param>

+ 20 - 4
EQ2/source/WorldServer/Spells.cpp

@@ -82,6 +82,7 @@ Spell::Spell(Spell* host_spell)
 		spell->duration_until_cancel = host_spell->GetSpellData()->duration_until_cancel;
 		spell->effect_message = string(host_spell->GetSpellData()->effect_message);
 		spell->fade_message = string(host_spell->GetSpellData()->fade_message);
+		spell->fade_message_others = string(host_spell->GetSpellData()->fade_message_others);
 
 		spell->friendly_spell = host_spell->GetSpellData()->friendly_spell;
 		spell->group_spell = host_spell->GetSpellData()->group_spell;
@@ -774,8 +775,12 @@ void Spell::SetPacketInformation(PacketStruct* packet, Client* client, bool disp
 	packet->setSubstructDataByName("spell_info", "tier", spell->tier);
 	packet->setSubstructDataByName("spell_info", "power_req", power_req);
 	packet->setSubstructDataByName("spell_info", "power_upkeep", spell->power_upkeep);
-
-	packet->setSubstructDataByName("spell_info", "cast_time", spell->cast_time);
+	if (packet->GetVersion() <= 546) {//cast times are displayed differently on new clients
+		packet->setSubstructDataByName("spell_info", "cast_time", spell->cast_time/10);
+	}
+	else {
+		packet->setSubstructDataByName("spell_info", "cast_time", spell->cast_time);
+	}
 	packet->setSubstructDataByName("spell_info", "recast", spell->recast);
 	packet->setSubstructDataByName("spell_info", "radius", spell->radius);
 	packet->setSubstructDataByName("spell_info", "req_concentration", spell->req_concentration);
@@ -1095,10 +1100,10 @@ EQ2Packet* Spell::SerializeSpell(Client* client, bool display, bool trait_displa
 		version = client->GetVersion();
 	if (!struct_name)
 		struct_name = "WS_ExamineSpellInfo";
-	if (version <= 283) {
+	if (version <= 546) {
 		if (packet_type == 1)
 			struct_name = "WS_ExamineEffectInfo";
-		else if (!display)
+		else if (!display && version<=283)
 			struct_name = "WS_ExaminePartialSpellInfo";
 		else
 			struct_name = "WS_ExamineSpellInfo";
@@ -1501,6 +1506,11 @@ bool Spell::GetSpellData(lua_State* state, std::string field)
 		lua_interface->SetStringValue(state, GetSpellData()->fade_message.c_str());
 		valSet = true;
 	}
+	else if (field == "fade_message_others")
+	{
+	lua_interface->SetStringValue(state, GetSpellData()->fade_message_others.c_str());
+	valSet = true;
+	}
 	else if (field == "cast_type")
 	{
 		lua_interface->SetSInt32Value(state, GetSpellData()->cast_type);
@@ -1883,6 +1893,12 @@ bool Spell::SetSpellData(lua_State* state, std::string field, int8 fieldArg)
 		GetSpellData()->fade_message = fade_message;
 		valSet = true;
 	}
+	else if (field == "fade_message_others")
+	{
+		string fade_message_others = lua_interface->GetStringValue(state, fieldArg);
+		GetSpellData()->fade_message_others = fade_message_others;
+		valSet = true;
+	}
 	else if (field == "cast_type")
 	{
 		int8 cast_type = lua_interface->GetInt8Value(state, fieldArg);

+ 4 - 1
EQ2/source/WorldServer/Spells.h

@@ -259,6 +259,7 @@ struct SpellData{
 	EQ2_16BitString description;
 	string	success_message;
 	string	fade_message;
+	string	fade_message_others;
 	int8	cast_type;
 	string	lua_script;
 	int32	call_frequency;
@@ -335,7 +336,8 @@ public:
 	bool CastWhileMezzed();
 	bool CastWhileStifled();
 	bool CastWhileFeared();
-
+	bool GetStayLocked() { return stay_locked; }
+	void StayLocked(bool val) { stay_locked = val; }
 
 	vector<SpellDisplayEffect*> effects;
 	vector<LUAData*> lua_data;
@@ -343,6 +345,7 @@ public:
 	void LockSpellInfo() { MSpellInfo.lock(); }
 	void UnlockSpellInfo() { MSpellInfo.unlock(); }
 private:
+	bool stay_locked = false;
 	bool heal_spell;
 	bool buff_spell;
 	bool damage_spell;

+ 68 - 4
EQ2/source/WorldServer/World.cpp

@@ -2276,6 +2276,43 @@ void World::PurgeStartingLists()
 	MStartingLists.releasewritelock();
 }
 
+void World::SetReloadingSubsystem(string subsystem) {
+	MReloadingSubsystems.lock();
+	reloading_subsystems[subsystem] = Timer::GetCurrentTime2();
+	MReloadingSubsystems.unlock();
+}
+
+void World::RemoveReloadingSubSystem(string subsystem) {
+	MReloadingSubsystems.lock();
+	if (reloading_subsystems.count(subsystem) > 0)
+		reloading_subsystems.erase(subsystem);
+	MReloadingSubsystems.unlock();
+}
+
+bool World::IsReloadingSubsystems() {
+	bool result = false;
+	MReloadingSubsystems.lock();
+	result = reloading_subsystems.size() > 0;
+	MReloadingSubsystems.unlock();
+	return result;
+}
+
+map<string, int32> World::GetOldestReloadingSubsystem() {
+	map<string, int32> result;
+	MReloadingSubsystems.lock();
+	int32 current_time = Timer::GetCurrentTime2();
+	map<string, int32>::iterator itr;
+	int32 oldest = current_time;
+	string oldestname = "";
+	for (itr = reloading_subsystems.begin(); itr != reloading_subsystems.end(); itr++) {
+		if (itr->second < oldest)
+			oldestname = itr->first;
+	}
+	result[oldestname] = oldest;
+	MReloadingSubsystems.unlock();
+	return result;
+}
+
 void ZoneList::WatchdogHeartbeat()
 {
 	list<ZoneServer*>::iterator zone_iter;
@@ -2290,9 +2327,8 @@ void ZoneList::WatchdogHeartbeat()
 		{
 			int32 curTime = Timer::GetCurrentTime2();
 			sint64 diff = (sint64)curTime - (sint64)tmp->GetWatchdogTime();
-			if (diff > 60000)
-			{
-				tmp->SetWatchdogTime(Timer::GetCurrentTime2()); // reset so we don't continuously flood this heartbeat
+			if (diff > 120000)
+			{				
 				LogWrite(WORLD__ERROR, 1, "World", "Zone %s is hung for %i milliseconds.. attempting to cancel threads...", tmp->GetZoneName(), diff);
 #ifndef WIN32
 				tmp->CancelThreads();
@@ -2301,10 +2337,38 @@ void ZoneList::WatchdogHeartbeat()
 #endif
 				MZoneList.releasewritelock(__FUNCTION__, __LINE__);
 				match = true;
-				break;
+				break;				
+			}
+			else if (diff > 90000 && !tmp->isZoneShuttingDown())
+			{
+				tmp->SetWatchdogTime(Timer::GetCurrentTime2()); // reset so we don't continuously flood this heartbeat
+				map<string, int32> oldest_process = world.GetOldestReloadingSubsystem();
+				if (oldest_process.size() > 0) {
+					map<string, int32>::iterator itr = oldest_process.begin();
+					LogWrite(WORLD__ERROR, 1, "World", "Zone %s is hung for %i milliseconds.. while waiting for %s to reload...attempting shutdown", tmp->GetZoneName(), diff, itr->first);
+				}
+				else
+					LogWrite(WORLD__ERROR, 1, "World", "Zone %s is hung for %i milliseconds.. attempting shutdown", tmp->GetZoneName(), diff);
+				tmp->Shutdown();
+			}
+			else if (diff > 60000)
+			{		
+				if (world.IsReloadingSubsystems()) {
+					if (world.GetSuppressedWarningTime() == 0) {
+						world.SetSuppressedWarning();
+						map<string, int32> oldest_process = world.GetOldestReloadingSubsystem();
+						if (oldest_process.size() > 0) {
+							map<string, int32>::iterator itr = oldest_process.begin();
+							LogWrite(WORLD__ERROR, 1, "World", "Zone %s is hung for %i milliseconds.. while waiting for %s to reload...", tmp->GetZoneName(), diff, itr->first);
+						}
+					}
+					continue;
+				}				
 			}
 			else if (diff > 30000 && !tmp->isZoneShuttingDown())
 			{
+				if (world.IsReloadingSubsystems())
+					continue;
 				LogWrite(WORLD__ERROR, 1, "World", "Zone %s is hung for %i milliseconds.. attempting shutdown", tmp->GetZoneName(), diff);
 				tmp->Shutdown();
 			}

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

@@ -474,7 +474,7 @@ class ZoneList {
 	void ReloadClientQuests();
 	bool DepopFinished();
 	void Depop();
-	void Repop();
+	void Repop();	
 	void DeleteSpellProcess();
 	void LoadSpellProcess();
 	void ProcessWhoQuery(const char* query, Client* client);
@@ -613,10 +613,22 @@ public:
 	multimap<int8, multimap<int8, StartingSkill>*> starting_skills;
 	multimap<int8, multimap<int8, StartingSpell>*> starting_spells;
 	Mutex MStartingLists;
+	void SetReloadingSubsystem(string subsystem);
+	void RemoveReloadingSubSystem(string subsystem);
+
+	bool IsReloadingSubsystems();
+	int32 GetSuppressedWarningTime() {
+		return suppressed_warning;
+	}
+	void SetSuppressedWarning() { suppressed_warning = Timer::GetCurrentTime2(); }
+	map<string, int32> GetOldestReloadingSubsystem();
+
 private:
+	int32 suppressed_warning = 0;
+	map<string, int32> reloading_subsystems;
 	//void RemovePlayerFromGroup(PlayerGroup* group, GroupMemberInfo* info, bool erase = true);
 	//void DeleteGroupMember(GroupMemberInfo* info);
-	
+	Mutex MReloadingSubsystems;
 	Mutex MMerchantList;
 	Mutex MSpawnScripts;
 	Mutex MZoneScripts;

+ 14 - 1
EQ2/source/WorldServer/WorldDatabase.cpp

@@ -287,6 +287,12 @@ int32 WorldDatabase::LoadSkills()
 				skill->description.data = string(row[3]);
 				skill->description.size = skill->description.data.length();
 				skill->skill_type = strtoul(row[4], NULL, 0);
+				//these two need to be converted to the correct numbers
+				if(skill->skill_type == 13)
+					skill->skill_type = SKILL_TYPE_LANGUAGE;
+				else if(skill->skill_type == 12)
+					skill->skill_type = SKILL_TYPE_GENERAL;
+
 				skill->display = atoi(row[5]);
 				master_skill_list.AddSkill(skill);
 				total++;
@@ -3284,6 +3290,7 @@ bool WorldDatabase::SaveSpawnEntry(Spawn* spawn, const char* spawn_location_name
 			LogWrite(SPAWN__ERROR, 0, "Spawn", "Error in SaveSpawnEntry query '%s': %s", query2.GetQuery(), query2.GetError());
 			return false;
 		}
+		spawn->SetSpawnLocationPlacementID(query2.GetLastInsertedID());
 	}
 	return true;
 }
@@ -3343,6 +3350,8 @@ int32 WorldDatabase::GetSpawnLocationCount(int32 location, Spawn* spawn){
 	MYSQL_RES* result = 0;
 	if(spawn)
 		result = query.RunQuery2(Q_SELECT, "SELECT count(id) FROM spawn_location_entry where spawn_location_id=%u and spawn_id=%u", location, spawn->GetDatabaseID());
+	else
+		result = query.RunQuery2(Q_SELECT, "SELECT count(id) FROM spawn_location_entry where spawn_location_id=%u", location);
 	if(result && mysql_num_rows(result) > 0){
 		MYSQL_ROW row;
 		while(result && (row = mysql_fetch_row(result)) && row[0]){
@@ -4310,7 +4319,7 @@ void WorldDatabase::LoadSpells()
 	int32 total = 0;
 	map<int32, vector<LevelArray*> >* level_data = LoadSpellClasses();
 
-	if( !database_new.Select(&result, "SELECT s.`id`, ts.spell_id, ts.index, `name`, `description`, `type`, `class_skill`, `mastery_skill`, `tier`, `is_aa`,`hp_req`, `power_req`,`power_by_level`, `cast_time`, `recast`, `radius`, `max_aoe_targets`, `req_concentration`, `range`, `duration1`, `duration2`, `resistibility`, `hp_upkeep`, `power_upkeep`, `duration_until_cancel`, `target_type`, `recovery`, `power_req_percent`, `hp_req_percent`, `icon`, `icon_heroic_op`, `icon_backdrop`, `success_message`, `fade_message`, `cast_type`, `lua_script`, `call_frequency`, `interruptable`, `spell_visual`, `effect_message`, `min_range`, `can_effect_raid`, `affect_only_group_members`, `hit_bonus`, `display_spell_tier`, `friendly_spell`, `group_spell`, `spell_book_type`, spell_type+0, s.is_active, savagery_req, savagery_req_percent, savagery_upkeep, dissonance_req, dissonance_req_percent, dissonance_upkeep, linked_timer_id, det_type, incurable, control_effect_type, cast_while_moving, casting_flags, persist_through_death, not_maintained, savage_bar, savage_bar_slot, soe_spell_crc, 0xffffffff-CRC32(s.`name`) as 'spell_name_crc' "
+	if( !database_new.Select(&result, "SELECT s.`id`, ts.spell_id, ts.index, `name`, `description`, `type`, `class_skill`, `mastery_skill`, `tier`, `is_aa`,`hp_req`, `power_req`,`power_by_level`, `cast_time`, `recast`, `radius`, `max_aoe_targets`, `req_concentration`, `range`, `duration1`, `duration2`, `resistibility`, `hp_upkeep`, `power_upkeep`, `duration_until_cancel`, `target_type`, `recovery`, `power_req_percent`, `hp_req_percent`, `icon`, `icon_heroic_op`, `icon_backdrop`, `success_message`, `fade_message`, `fade_message_others`, `cast_type`, `lua_script`, `call_frequency`, `interruptable`, `spell_visual`, `effect_message`, `min_range`, `can_effect_raid`, `affect_only_group_members`, `hit_bonus`, `display_spell_tier`, `friendly_spell`, `group_spell`, `spell_book_type`, spell_type+0, s.is_active, savagery_req, savagery_req_percent, savagery_upkeep, dissonance_req, dissonance_req_percent, dissonance_upkeep, linked_timer_id, det_type, incurable, control_effect_type, cast_while_moving, casting_flags, persist_through_death, not_maintained, savage_bar, savage_bar_slot, soe_spell_crc, 0xffffffff-CRC32(s.`name`) as 'spell_name_crc' "
 									"FROM (spells s, spell_tiers st) "
 									"LEFT JOIN spell_ts_ability_index ts "
 									"ON s.`id` = ts.spell_id "
@@ -4418,6 +4427,10 @@ void WorldDatabase::LoadSpells()
 			if( message.length() > 0 )
 				data->fade_message = string(message);
 
+			message = result.GetStringStr("fade_message_others");
+			if (message.length() > 0)
+				data->fade_message_others = string(message);
+
 			message							= result.GetStringStr("effect_message");
 			if( message.length() > 0 )
 				data->effect_message = string(message);

+ 5 - 0
EQ2/source/WorldServer/Zone/map.h

@@ -77,6 +77,11 @@ public:
 		CheckMapMutex.releasereadlock();
 		return isMapLoading;
 	}
+	float GetMinX() { return m_MinX; }
+	float GetMaxX() { return m_MaxX; }
+	float GetMinZ() { return m_MinZ; }
+	float GetMaxZ() { return m_MaxZ; }
+
 private:
 	void RotateVertex(glm::vec3 &v, float rx, float ry, float rz);
 	void ScaleVertex(glm::vec3 &v, float sx, float sy, float sz);

+ 194 - 28
EQ2/source/WorldServer/client.cpp

@@ -1348,6 +1348,18 @@ bool Client::HandlePacket(EQApplicationPacket* app) {
 		HandleLoot(app);
 		break;
 	}
+	case OP_WaypointSelectMsg: {
+		PacketStruct* packet = configReader.getStruct("WS_WaypointSelect", GetVersion());
+		if (packet) {
+			if (packet->LoadPacketData(app->pBuffer, app->size)) {
+				int32 selection = packet->getType_int32_ByName("selection");
+				if (selection > 0) {
+					SelectWaypoint(selection);
+				}
+			}
+		}
+		break;
+	}
 	case OP_KnowledgeWindowSlotMappingMsg: {
 		LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_KnowledgeWindowSlotMappingMsg", opcode, opcode);
 		PacketStruct* packet = configReader.getStruct("WS_SpellSlotMapping", GetVersion());
@@ -1572,9 +1584,18 @@ bool Client::HandlePacket(EQApplicationPacket* app) {
 			else {
 				EQ2_16BitString command = packet->getType_EQ2_16BitString_ByName("command");
 				if (command.size > 0) {
-					string command_name = command.data;
-					if (command_name.find(" ") < 0xFFFFFFFF)
-						command_name = command_name.substr(0, command_name.find(" "));
+					string command_name = command.data;					
+					if (command_name.find(" ") < 0xFFFFFFFF) {
+						if (GetVersion() <= 546) { //this version uses commands in the form "Buy From Merchant" instead of buy_from_merchant
+							string::size_type pos = command_name.find(" ");
+							while(pos != string::npos){
+								command_name.replace(pos, 1, "_");
+								pos = command_name.find(" ");
+							}
+						}
+						else
+							command_name = command_name.substr(0, command_name.find(" "));
+					}
 					int32 handler = commands.GetCommandHandler(command_name.c_str());
 					if (handler != 0xFFFFFFFF) {
 						if (command.data == command_name) {
@@ -1591,7 +1612,15 @@ bool Client::HandlePacket(EQApplicationPacket* app) {
 						if (spawn && spawn->IsNPC()) {
 							if (EntityCommandPrecheck(spawn, command.data.c_str())) {
 								if (!((NPC*)spawn)->HandleUse(this, command.data)) {
-									LogWrite(WORLD__ERROR, 0, "World", "Unhandled command in OP_EntityVerbsVerbMsg: %s", command.data.c_str());
+									command_name = command.data;
+									string::size_type pos = command_name.find(" ");
+									while (pos != string::npos) {
+										command_name.replace(pos, 1, "_");
+										pos = command_name.find(" ");
+									}
+									if (!((NPC*)spawn)->HandleUse(this, command_name)) { //convert the spaces to underscores and see if that makes a difference
+										LogWrite(WORLD__ERROR, 0, "World", "Unhandled command in OP_EntityVerbsVerbMsg: %s", command.data.c_str());
+									}
 								}
 							}
 						}
@@ -2458,7 +2487,8 @@ void Client::HandleSkillInfoRequest(EQApplicationPacket* app) {
 
 void Client::HandleExamineInfoRequest(EQApplicationPacket* app) {
 	PacketStruct* request = 0;
-
+	if (!app || app->size == 0)
+		return;
 	//LogWrite(CCLIENT__DEBUG, 0, "Client", "Request2:");
 	//DumpPacket(app);
 
@@ -3341,7 +3371,7 @@ void Client::SimpleMessage(int8 color, const char* message) {
 	}
 }
 
-void Client::SendSpellUpdate(Spell* spell) {
+void Client::SendSpellUpdate(Spell* spell, bool add_silently, bool add_to_hotbar) {
 	PacketStruct* packet = configReader.getStruct("WS_SpellGainedMsg", GetVersion());
 	if (packet) {
 		int8 xxx = spell->GetSpellData()->is_aa;
@@ -3349,6 +3379,10 @@ void Client::SendSpellUpdate(Spell* spell) {
 		packet->setDataByName("spell_id", spell->GetSpellID());
 		packet->setDataByName("unique_id", spell->GetSpellData()->spell_name_crc);
 		packet->setDataByName("spell_name", spell->GetName());
+		if(add_silently)
+			packet->setDataByName("add_silently", 1);
+		if(add_to_hotbar)
+			packet->setDataByName("add_to_hotbar", 1);
 		packet->setDataByName("unknown", xxx);
 		packet->setDataByName("display_spell_tier", 1);
 		packet->setDataByName("unknown3", 1);
@@ -4085,8 +4119,9 @@ void Client::ChangeLevel(int16 old_level, int16 new_level) {
 		}
 	}
 
-	if (new_level > old_level)
+	if (new_level > old_level) {		
 		player->UpdatePlayerHistory(HISTORY_TYPE_XP, HISTORY_SUBTYPE_ADVENTURE, new_level, player->GetAdventureClass());
+	}
 
 	if (player->GetPet()) {
 		NPC* pet = (NPC*)player->GetPet();
@@ -4165,13 +4200,37 @@ void Client::ChangeLevel(int16 old_level, int16 new_level) {
 	LogWrite(WORLD__DEBUG, 0, "World", "Player: %s leveled from %u to %u", GetPlayer()->GetName(), old_level, new_level);
 	int16 new_skill_cap = 5 * new_level;
 	PlayerSkillList* player_skills = player->GetSkills();
+
+	player_skills->SetSkillCapsByType(SKILL_TYPE_ARMOR, new_skill_cap);
+	player_skills->SetSkillCapsByType(SKILL_TYPE_SHIELD, new_skill_cap);
+	//SKILL_TYPE_ARMOR/SKILL_TYPE_SHIELD always has the same current / max values
+	player_skills->SetSkillValuesByType(SKILL_TYPE_ARMOR, new_skill_cap, false);
+	player_skills->SetSkillValuesByType(SKILL_TYPE_SHIELD, new_skill_cap, false);
+
+	player_skills->SetSkillCapsByType(SKILL_TYPE_CLASS, new_skill_cap);
+	player_skills->SetSkillCapsByType(SKILL_TYPE_WEAPON, new_skill_cap);
+	//SKILL_TYPE_CLASS/SKILL_TYPE_WEAPON always has the same current/max values
+	player_skills->SetSkillValuesByType(SKILL_TYPE_CLASS, new_skill_cap, false);
+	player_skills->SetSkillValuesByType(SKILL_TYPE_WEAPON, new_skill_cap, false);
+	
 	player_skills->SetSkillCapsByType(SKILL_TYPE_COMBAT, new_skill_cap);
+	player_skills->SetSkillCapsByType(SKILL_TYPE_GENERAL, new_skill_cap);
 	player_skills->SetSkillCapsByType(SKILL_TYPE_SPELLCASTING, new_skill_cap);
 	player_skills->SetSkillCapsByType(SKILL_TYPE_AVOIDANCE, new_skill_cap);
-	player_skills->SetSkillCapsByType(SKILL_TYPE_GENERAL, new_skill_cap);
+	
 	if (new_level > player->GetTSLevel())
 		player_skills->SetSkillCapsByType(SKILL_TYPE_HARVESTING, new_skill_cap);
 
+	//SKILL_ID_DUALWIELD, SKILL_ID_FISTS, SKILL_ID_DESTROYING, and SKILL_ID_MAGIC_AFFINITY always have the current_val equal to max_val
+	if (player_skills->HasSkill(SKILL_ID_DUALWIELD))
+		player_skills->SetSkill(SKILL_ID_DUALWIELD, new_skill_cap);
+	if (player_skills->HasSkill(SKILL_ID_FISTS))
+		player_skills->SetSkill(SKILL_ID_FISTS, new_skill_cap);
+	if (player_skills->HasSkill(SKILL_ID_DESTROYING))
+		player_skills->SetSkill(SKILL_ID_DESTROYING, new_skill_cap);
+	if (player_skills->HasSkill(SKILL_ID_MAGIC_AFFINITY))
+		player_skills->SetSkill(SKILL_ID_MAGIC_AFFINITY, new_skill_cap);
+
 	Guild* guild = GetPlayer()->GetGuild();
 	if (guild) {
 		int8 event_type = 0;
@@ -5042,8 +5101,8 @@ void Client::CheckQuestQueue() {
 	for (itr = quest_queue.begin(); itr != quest_queue.end(); itr++) {
 		queued_quest = *itr;
 		SendQuestUpdateStepImmediately(queued_quest->quest, queued_quest->step, queued_quest->display_quest_helper);
-		//if(queued_quest->quest && queued_quest->quest->GetTurnedIn()) //update the journal so the old quest isn't the one displayed in the client's quest helper
-		//	SendQuestJournal();
+		if(queued_quest->quest && queued_quest->quest->GetTurnedIn()) //update the journal so the old quest isn't the one displayed in the client's quest helper
+			SendQuestJournal();
 		safe_delete(queued_quest);
 	}
 	quest_queue.clear();
@@ -5320,9 +5379,9 @@ void Client::SendQuestUpdate(Quest* quest) {
 			step = updates->at(i);
 			if (lua_interface && step->Complete() && quest->GetCompleteAction(step->GetStepID()))
 				lua_interface->CallQuestFunction(quest, quest->GetCompleteAction(step->GetStepID()), player);
-			if (step->WasUpdated()) {
-				SendQuestJournal(false, 0, true);
+			if (step->WasUpdated()) {				
 				QueuePacket(quest->QuestJournalReply(GetVersion(), GetNameCRC(), player, step));
+				SendQuestJournal(false, 0, true);
 			}
 			LogWrite(CCLIENT__DEBUG, 0, "Client", "Send Quest Journal...");
 
@@ -5384,17 +5443,21 @@ Quest* Client::GetPendingQuestAcceptance(int32 item_id) {
 	MPendingQuestAccept.lock();
 	for (itr = pending_quest_accept.begin(); itr != pending_quest_accept.end(); itr++) {
 		quest = *itr;
-		items = quest->GetSelectableRewardItems();
-		if (items && items->size() > 0) {
-			for (int32 i = 0; i < items->size(); i++) {
-				if (items->at(i)->details.item_id == item_id) {
-					found_quest = true;
-					break;
+		items = quest->GetRewardItems();
+		if (item_id == 0 && items && items->size() > 0) {
+			found_quest = true;
+		}
+		else {
+			items = quest->GetSelectableRewardItems();
+			if (items && items->size() > 0) {
+				for (int32 i = 0; i < items->size(); i++) {
+					if (items->at(i)->details.item_id == item_id) {
+						found_quest = true;
+						break;
+					}
 				}
 			}
 		}
-		else if (item_id == 0)
-			found_quest = true;
 		if (found_quest) {
 			pending_quest_accept.erase(itr);
 			break;
@@ -5455,10 +5518,10 @@ void Client::AcceptQuestReward(Quest* quest, int32 item_id) {
 
 void Client::DisplayQuestRewards(Quest* quest, int64 coin, vector<Item*>* rewards, vector<Item*>* selectable_rewards, map<int32, sint32>* factions, const char* header, int32 status_points, const char* text) {
 	if (coin == 0 && (!rewards || rewards->size() == 0) && (!selectable_rewards || selectable_rewards->size() == 0) && (!factions || factions->size() == 0) && status_points == 0 && text == 0 && (!quest || (quest->GetCoinsReward() == 0 && quest->GetCoinsRewardMax() == 0))) {
-		if (quest)
+		/*if (quest)
 			text = quest->GetName();
-		else
-			return;//nothing to give
+		else*/
+		return;//nothing to give
 	}
 	PacketStruct* packet2 = configReader.getStruct("WS_QuestRewardPackMsg", GetVersion());
 	if (packet2) {
@@ -5478,12 +5541,14 @@ void Client::DisplayQuestRewards(Quest* quest, int64 coin, vector<Item*>* reward
 		if (rewarded_coin > coin)
 			coin = rewarded_coin;
 		if (!quest) { //this entire function is either for version <=546 or for quest rewards in middle of quest, so quest should be 0, otherwise quest will handle the rewards
-			player->AddCoins(coin);
-			PlaySound("coin_cha_ching");
+			if (coin > 0) {
+				player->AddCoins(coin);
+				PlaySound("coin_cha_ching");
+			}
 		}
 		packet2->setSubstructDataByName("reward_data", "unknown1", 255);
 		packet2->setSubstructDataByName("reward_data", "reward", header);
-		packet2->setSubstructDataByName("reward_data", "coin", coin);
+		packet2->setSubstructDataByName("reward_data", "max_coin", coin);
 		if (player->GetGuild()) {
 			if (!quest) { //this entire function is either for version <=546 or for quest rewards in middle of quest, so quest should be 0, otherwise quest will handle the rewards
 				player->GetInfoStruct()->status_points += status_points;
@@ -5694,11 +5759,11 @@ void Client::GiveQuestReward(Quest* quest) {
 
 	quest->IncrementCompleteCount();
 	player->AddCompletedQuest(quest);
-
+	
+	DisplayQuestComplete(quest);
 	LogWrite(CCLIENT__DEBUG, 0, "Client", "Send Quest Journal...");
 	SendQuestJournal();
 	player->RemoveQuest(quest->GetQuestID(), false);
-	DisplayQuestComplete(quest);
 	if (quest->GetExpReward() > 0) {
 		int16 level = player->GetLevel();
 		int32 xp = quest->GetExpReward();
@@ -8010,6 +8075,47 @@ void Client::SendIgnoreList() {
 
 }
 
+void Client::AddWaypoint(string name, int8 type) { 
+	waypoint_id++;
+	WaypointInfo info;
+	info.id = waypoint_id; 
+	info.type = type;
+	waypoints[name] = info;
+}
+
+void Client::SendWaypoints() {
+	PacketStruct* packet = configReader.getStruct("WS_WaypointUpdate", GetVersion());
+	if (packet) {
+		packet->setArrayLengthByName("num_updates", waypoints.size());
+		map<string, WaypointInfo>::iterator itr;
+		int16 i = 0;
+		for (itr = waypoints.begin(); itr != waypoints.end(); itr++) {
+			packet->setArrayDataByName("waypoint_name", itr->first.c_str(), i);
+			packet->setArrayDataByName("waypoint_category", itr->second.type, i);
+			packet->setArrayDataByName("spawn_id", itr->second.id, i);			
+			i++;
+		}
+		packet->setDataByName("unknown", 0xFFFFFFFF);
+		QueuePacket(packet->serialize());
+		safe_delete(packet);
+	}
+}
+
+void Client::SelectWaypoint(int32 id) {
+	string found_name = "";
+	map<string, WaypointInfo>::iterator itr;
+	for (itr = waypoints.begin(); itr != waypoints.end(); itr++) {
+		if (itr->second.id == id) {
+			found_name = itr->first;
+			break;
+		}
+	}
+	if (found_name.length() > 0) {
+		Spawn* spawn = current_zone->FindSpawn(player, found_name.c_str());
+		ShowPathToTarget(spawn);
+	}
+}
+
 void Client::AddWaypoint(const char* waypoint_name, int8 waypoint_category, int32 spawn_id) {
 	if (waypoint_name) {
 		PacketStruct* packet = configReader.getStruct("WS_WaypointUpdate", GetVersion());
@@ -8020,11 +8126,68 @@ void Client::AddWaypoint(const char* waypoint_name, int8 waypoint_category, int3
 			packet->setArrayDataByName("spawn_id", spawn_id, 0);
 			packet->setArrayDataByName("waypoint_category2", waypoint_category, 0);
 			packet->setArrayDataByName("spawn_id2", spawn_id, 0);
+			packet->setDataByName("unknown", 0xFFFFFFFF); 
 			QueuePacket(packet->serialize());
 			safe_delete(packet);
 		}
 	}
+}
 
+void Client::ClearWaypoint() {
+	PacketStruct* packet = configReader.getStruct("WS_GlowPath", GetVersion());
+	if (packet) {
+		QueuePacket(packet->serialize());
+		safe_delete(packet);		
+	}
+}
+
+bool Client::ShowPathToTarget(float x, float y, float z, float y_offset) {
+	if (current_zone->pathing) {
+		if (current_zone->zonemap) {
+			if (x < current_zone->zonemap->GetMinX() || x > current_zone->zonemap->GetMaxX())
+				return false;
+			if (z < current_zone->zonemap->GetMinZ() || z > current_zone->zonemap->GetMaxZ())
+				return false;
+			float new_z = current_zone->zonemap->FindBestZ(glm::vec3(x, z, y), nullptr);
+			if (new_z != BEST_Z_INVALID) //this is actually y
+				y = new_z;
+		}
+		bool partial = false;
+		bool stuck = false;
+		PathfinderOptions opts;
+		opts.smooth_path = true;
+		opts.step_size = 100.0f;//RuleR(Pathing, NavmeshStepSize);
+		opts.offset = y_offset + 1.0f;
+		opts.flags = PathingNotDisabled ^ PathingZoneLine;
+		PacketStruct* packet = configReader.getStruct("WS_GlowPath", GetVersion());
+		if (packet) {
+			auto path = current_zone->pathing->FindPath(glm::vec3(player->GetX(), player->GetZ(), player->GetY()), glm::vec3(x, z, y), partial, stuck, opts);
+			packet->setArrayLengthByName("num_points", path.size());
+			int i = 0;
+			for (auto& node : path)
+			{
+				packet->setArrayDataByName("x", node.pos.x, i);
+				packet->setArrayDataByName("y", node.pos.z, i);
+				packet->setArrayDataByName("z", node.pos.y, i);
+				packet->setDataByName("waypoint_x", x);
+				packet->setDataByName("waypoint_y", y);
+				packet->setDataByName("waypoint_z", z);
+				i++;
+			}
+			if (i > 0)
+				QueuePacket(packet->serialize());
+			safe_delete(packet);
+			return (i > 0);
+		}
+	}
+	return false;
+}
+
+bool Client::ShowPathToTarget(Spawn* spawn) {
+	if (spawn) {
+		return ShowPathToTarget(spawn->GetX(), spawn->GetY(), spawn->GetZ(), spawn->GetYOffset());	
+	}
+	return false;
 }
 
 void Client::BeginWaypoint(const char* waypoint_name, float x, float y, float z) {
@@ -9065,6 +9228,9 @@ bool Client::HandleNewLogin(int32 account_id, int32 access_code)
 				GetCurrentZone()->AddClient(this); //add to zones client list
 
 				zone_list.AddClientToMap(player->GetName(), this);
+				const char* zone_script = world.GetZoneScript(GetCurrentZone()->GetZoneID());
+				if (zone_script && lua_interface)
+					lua_interface->RunZoneScript(zone_script, "new_client", GetCurrentZone(), GetPlayer());
 			}
 			else {
 				LogWrite(WORLD__ERROR, 0, "World", "Incompatible version: %i", version);

+ 21 - 1
EQ2/source/WorldServer/client.h

@@ -134,6 +134,10 @@ struct IncomingPaperdollImage {
 	int8 last_received_packet_index;
 	int8 image_type;
 };
+struct WaypointInfo {
+	int32 id;
+	int8 type;
+};
 
 class Client {
 public:
@@ -155,7 +159,7 @@ public:
 	void	HandleTellMessage(Client* from, const char* message);
 	void	SimpleMessage(int8 color, const char* message);
 	void	Message(int8 type, const char* message, ...);
-	void	SendSpellUpdate(Spell* spell);
+	void	SendSpellUpdate(Spell* spell, bool add_silently = false, bool add_to_hotbar = true);
 	void	Zone(ZoneServer* new_zone, bool set_coords = true);
 	void	Zone(const char* new_zone, bool set_coords = true);
 	void	Zone(int32 zoneid, bool set_coords = true);
@@ -441,6 +445,20 @@ public:
 	void SetRejoinGroupID(int32 id) { rejoin_group_id = id; }
 
 	void TempRemoveGroup();
+
+	void SendWaypoints();
+
+	void AddWaypoint(string name, int8 type);
+	void RemoveWaypoint(string name) {
+		if (waypoints.count(name) > 0){
+			waypoints.erase(name);
+		}
+	}
+	void SelectWaypoint(int32 id);
+	void ClearWaypoint();
+	bool ShowPathToTarget(float x, float y, float z, float y_offset);
+	bool ShowPathToTarget(Spawn* spawn);
+
 private:
 	void    SavePlayerImages();
 	void	SkillChanged(Skill* skill, int16 previous_value, int16 new_value);
@@ -455,6 +473,8 @@ private:
 	Mutex	MQuestQueue;
 	Mutex	MDeletePlayer;
 	vector<Item*>* search_items;
+	int32 waypoint_id = 0;
+	map<string, WaypointInfo> waypoints;
 	Spawn*	transport_spawn;
 	Mutex	MBuyBack;
 	deque<BuyBackItem*> buy_back_items;

+ 21 - 2
EQ2/source/WorldServer/zoneserver.cpp

@@ -1335,6 +1335,7 @@ bool ZoneServer::Process()
 				database.LoadTransporters(this);
 				LogWrite(TRANSPORT__INFO, 0, "Transport", "-Loading Transporters complete!");
 				reloading = false;
+				world.RemoveReloadingSubSystem("Spawns");
 			}
 
 			MSpawnGroupAssociation.writelock(__FUNCTION__, __LINE__);
@@ -3167,14 +3168,14 @@ void ZoneServer::ClientProcess()
 	}
 }
 
-void ZoneServer::SimpleMessage(int8 type, const char* message, Spawn* from, float distance){
+void ZoneServer::SimpleMessage(int8 type, const char* message, Spawn* from, float distance, bool send_to_sender){
 	Client* client = 0;
 	vector<Client*>::iterator client_itr;
 
 	MClientList.readlock(__FUNCTION__, __LINE__);
 	for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) {
 		client = *client_itr;
-		if(from && client && client->IsConnected() && from->GetDistance(client->GetPlayer()) <= distance){
+		if(from && client && client->IsConnected() && (send_to_sender || from != client->GetPlayer()) && from->GetDistance(client->GetPlayer()) <= distance){
 			client->SimpleMessage(type, message);
 		}
 	}
@@ -4863,6 +4864,23 @@ void ZoneServer::StartZoneInitialSpawnThread(Client* client){
 }
 
 void ZoneServer::SendZoneSpawns(Client* client){
+	int8 count = 0;
+	while (LoadingData && count <= 6000) { //sleep for max of 60 seconds (60000ms) while the maps are loading
+		count++;
+		Sleep(10);
+	}
+	count = 0;
+	int16 size = 0;
+	//give the spawn thread a tad bit of time to add the pending_spawns to spawn_list (up to 10 seconds)
+	while (count < 1000) {		
+		MPendingSpawnListAdd.readlock(__FUNCTION__, __LINE__);
+		size = pending_spawn_list_add.size();
+		MPendingSpawnListAdd.releasereadlock(__FUNCTION__, __LINE__);
+		if (size == 0)
+			break;
+		Sleep(10);
+		count++;
+	}
 	initial_spawn_threads_active++;
 
 	map<int32, Spawn*>::iterator itr;
@@ -7499,6 +7517,7 @@ void ZoneServer::ReloadSpawns() {
 		return;
 
 	reloading = true;
+	world.SetReloadingSubsystem("Spawns");
 	// Let every one in the zone know what is happening
 	HandleBroadcast("Reloading all spawns for this zone.");
 	DeleteGlobalSpawns();

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

@@ -276,7 +276,7 @@ public:
 
 	void	AddClient(Client* client);
 	
-	void	SimpleMessage(int8 type, const char* message, Spawn* from, float distance);
+	void	SimpleMessage(int8 type, const char* message, Spawn* from, float distance, bool send_to_sender = true);
 	void	HandleChatMessage(Spawn* from, const char* to, int16 channel, const char* message, float distance = 0, const char* channel_name = 0, bool show_bubble = true, int32 language = 0);
 	void	HandleChatMessage(Client* client, Spawn* from, const char* to, int16 channel, const char* message, float distance = 0, const char* channel_name = 0, bool show_bubble = true, int32 language = 0);
 	void	HandleBroadcast(const char* message);

+ 3 - 1
EQ2/source/common/ConfigReader.cpp

@@ -237,7 +237,9 @@ void ConfigReader::loadDataStruct(PacketStruct* packet, XMLNode parentNode, bool
 							ds2->SetIfNotEqualsVariable(ds->GetIfNotEqualsVariable());
 							ds2->SetIfFlagNotSetVariable(ds->GetIfFlagNotSetVariable());
 							ds2->SetIfFlagSetVariable(ds->GetIfFlagSetVariable());
-							ds2->SetIsOptional(ds->IsOptional());							
+							ds2->SetIsOptional(ds->IsOptional());
+							ds2->AddIfSetVariable(if_variable); //add this if the modifier is on the piece that is including the substruct
+							ds2->AddIfNotSetVariable(if_not_variable); //add this if the modifier is on the piece that is including the substruct
 							packet->add(ds2);
 						}
 					}

+ 19 - 0
EQ2/source/common/PacketStruct.cpp

@@ -351,6 +351,25 @@ int8 DataStruct::GetAddType() {
 void DataStruct::SetAddType(int8 new_type) {
 	addType = new_type;
 }
+string DataStruct::AppendVariable(string orig, const char* val) {
+	if (!val)
+		return orig;
+	if(orig.length() == 0)
+		return string(val);
+	if (orig.find(",") < 0xFFFFFFFF) { //has more than one already
+		string valstr = string(val);
+		vector<string>* varnames = SplitString(orig, ',');
+		if (varnames) {
+			for (int32 i = 0; i < varnames->size(); i++) {
+				if (valstr.compare(varnames->at(i)) == 0) {
+					return orig; //already in the variable, no need to append
+				}
+			}
+			safe_delete(varnames);
+		}		
+	}
+	return orig.append(",").append(val);
+}
 int32 DataStruct::GetDataSizeInBytes() {
 	int32 ret = 0;
 	switch (type) {

+ 13 - 0
EQ2/source/common/PacketStruct.h

@@ -113,6 +113,19 @@ public:
 	bool	IsSet();
 	bool	IsOptional();
 	int32 GetDataSizeInBytes();
+	string	AppendVariable(string orig, const char* val);
+	void	AddIfSetVariable(const char* val) {
+		if (val) {
+			if_set_variable = AppendVariable(if_set_variable, val);
+			is_set = true;
+		}
+	}
+	void	AddIfNotSetVariable(const char* val) {
+		if (val) {
+			if_not_set_variable = AppendVariable(if_not_set_variable, val);
+			if_not_set = true;
+		}
+	}
 
 private:
 	bool	is_set;

+ 7 - 0
EQ2/source/common/misc.cpp

@@ -283,6 +283,13 @@ bool alpha_check(unsigned char val){
 		return false;
 }
 
+unsigned int GetSpellNameCrc(const char* src) {
+	if (!src)
+		return 0;
+	uLong crc = crc32(0L, Z_NULL, 0);
+	return crc32(crc, (unsigned const char*)src, strlen(src));
+}
+
 int GetItemNameCrc(string item_name){
 	const char *src = item_name.c_str();
 	uLong crc = crc32(0L, Z_NULL, 0);    

+ 1 - 0
EQ2/source/common/misc.h

@@ -59,6 +59,7 @@ string timestamp(time_t now=0);
 string long2ip(unsigned long ip);
 string pop_arg(string &s, string seps, bool obey_quotes);
 int EQsprintf(char *buffer, const char *pattern, const char *arg1, const char *arg2, const char *arg3, const char *arg4, const char *arg5, const char *arg6, const char *arg7, const char *arg8, const char *arg9);
+unsigned int GetSpellNameCrc(const char* src);
 int GetItemNameCrc(string item_name);
 unsigned int GetNameCrc(string name);
 #endif

BIN
server/EQ2World__Debug_x64.exe


+ 1 - 1
server/SpawnStructs.xml

@@ -520,7 +520,7 @@
 <Data ElementName="bump_size" Type="sint8" Size="1" /> <!-- 604 -->
 <Data ElementName="soga_skull_type" Type="sint8" Size="3" /> <!-- 605 -->
 <Data ElementName="soga_eye_type" Type="sint8" Size="3" /> <!-- 608 -->
-<Data ElementName="ear_type" Type="sint8" Size="3" /> <!-- 611 -->
+<Data ElementName="soga_ear_type" Type="sint8" Size="3" /> <!-- 611 -->
 <Data ElementName="soga_eye_brow_type" Type="sint8" Size="3" /> <!-- 614 -->
 <Data ElementName="soga_cheek_type" Type="sint8" Size="3" /> <!-- 617 -->
 <Data ElementName="soga_lip_type" Type="sint8" Size="3" /> <!-- 620 -->

+ 102 - 20
server/WorldStructs.xml

@@ -407,6 +407,14 @@ to zero and treated like placeholders." />
 <Data ElementName="hour" Type="int8" Size="1" />
 <Data ElementName="minute" Type="int8" Size="1" />
 <Data ElementName="unknown" Type="int8" Size="1" />
+</Struct>
+<Struct Name="WS_GameWorldTime" ClientVersion="547" OpcodeName="OP_GameWorldTimeMsg">
+<Data ElementName="year" Type="int16" Size="1" />
+<Data ElementName="month" Type="int8" Size="1" />
+<Data ElementName="day" Type="int8" Size="1" />
+<Data ElementName="hour" Type="int8" Size="1" />
+<Data ElementName="minute" Type="int8" Size="1" />
+<Data ElementName="unknown" Type="int8" Size="1" />
 <Data ElementName="unix_time" Type="int32" Size="1" />
 </Struct>
 <Struct Name="WS_GameWorldTime" ClientVersion="1193" OpcodeName="OP_GameWorldTimeMsg">
@@ -422,6 +430,10 @@ to zero and treated like placeholders." />
 <Struct Name="WS_Camp" ClientVersion="1" OpcodeName="OP_CampStartedMsg" >
 <Data ElementName="seconds" Type="int8" Size="1" />
 <Data ElementName="camp_desktop" Type="int8" Size="1" />
+</Struct>
+<Struct Name="WS_Camp" ClientVersion="547" OpcodeName="OP_CampStartedMsg" >
+<Data ElementName="seconds" Type="int8" Size="1" />
+<Data ElementName="camp_desktop" Type="int8" Size="1" />
 <Data ElementName="camp_char_select" Type="int8" Size="1" />
 <Data ElementName="char_name" Type="EQ2_16Bit_String" />
 <Data ElementName="unknown" Type="int8" Size="3" />
@@ -433,7 +445,11 @@ to zero and treated like placeholders." />
 <Data ElementName="char_name" Type="EQ2_16Bit_String" />
 <Data ElementName="unknown" Type="int8" Size="8" />
 </Struct>
-<Struct Name="WS_RequestCamp" ClientVersion="1" OpcodeName="OP_RequestCampMsg">
+<Struct Name="WS_RequestCamp" ClientVersion="" OpcodeName="OP_RequestCampMsg">
+<Data ElementName="quit" Type="int8" Size="1" />
+<Data ElementName="camp_desktop" Type="int8" Size="1" />
+</Struct>
+<Struct Name="WS_RequestCamp" ClientVersion="547" OpcodeName="OP_RequestCampMsg">
 <Data ElementName="quit" Type="int8" Size="1" />
 <Data ElementName="camp_desktop" Type="int8" Size="1" />
 <Data ElementName="camp_char_select" Type="int16" Size="1" />
@@ -459,8 +475,8 @@ to zero and treated like placeholders." />
 <Data ElementName="spell_id" Type="int32" Size="1" />
 <Data ElementName="unique_id" Type="int32" Size="1" />
 <Data ElementName="spell_name" Type="EQ2_16Bit_String" />
-<Data ElementName="display_spell_tier" Type="int8" Size="1" />
-<Data ElementName="unknown3" Type="int8" Size="1" />
+<Data ElementName="add_silently" Type="int8" Size="1" />
+<Data ElementName="add_to_hotbar" Type="int8" Size="1" />
 <Data ElementName="tier" Type="int8" Size="1" />
 <Data ElementName="icon" Type="int16" Size="1" />
 <Data ElementName="icon_type" Type="int16" Size="1" />
@@ -501,6 +517,18 @@ to zero and treated like placeholders." />
    <Data ElementName="icon" Type="int16" Size="1" />    
 </Data>
 </Struct>
+<Struct Name="WS_MacroInit" ClientVersion="546" OpcodeName="OP_MacroInitMsg" > 
+<Data ElementName="macro_count" Type="int32" />
+<Data ElementName="macro_array" Type="Array" ArraySizeVariable="macro_count">
+   <Data ElementName="number" Type="int8" />
+   <Data ElementName="name" Type="EQ2_8Bit_String" />
+   <Data ElementName="macro_details_count" Type="int8" />
+   <Data ElementName="macro_details_array" Type="Array" ArraySizeVariable="macro_details_count">
+      <Data ElementName="command" Type="EQ2_16Bit_String" />
+   </Data>
+   <Data ElementName="icon" Type="int16" Size="1" /> 
+</Data>
+</Struct>
 <Struct Name="WS_MacroInit" ClientVersion="547" OpcodeName="OP_MacroInitMsg" > 
 <Data ElementName="macro_count" Type="int32" />
 <Data ElementName="macro_array" Type="Array" ArraySizeVariable="macro_count">
@@ -1335,12 +1363,12 @@ to zero and treated like placeholders." />
 </Struct>
 <Struct Name="Substruct_SpellEffects" ClientVersion="546" >
 <Data ElementName="spell_id" Type="int32" Size="1" />
+<Data ElementName="cancellable" Type="int8" Size="1" />
 <Data ElementName="total_time" Type="float" Size="1" />
 <Data ElementName="expire_timestamp" Type="int32" Size="1" />
+<Data ElementName="unknown2" Type="int8" Size="1" />
 <Data ElementName="icon" Type="int16" Size="1" />
 <Data ElementName="icon_type" Type="int16" Size="1" />
-<Data ElementName="unknown2" Type="int8" Size="1" />
-<Data ElementName="cancellable" Type="int8" Size="1" />
 <Data ElementName="unknown3" Type="int8" Size="1" />
 </Struct>
 <Struct Name="Substruct_SpellEffects" ClientVersion="843" >
@@ -3184,7 +3212,7 @@ to zero and treated like placeholders." />
 	<Data ElementName="language_unknown" Type="int8" Size="1" />
 </Data>
 </Struct>
-<Struct Name="WS_UpdateSkillBook" ClientVersion="547" OpcodeName="OP_UpdateSkillBookMsg" >
+<Struct Name="WS_UpdateSkillBook" ClientVersion="865" OpcodeName="OP_UpdateSkillBookMsg" >
 <Data ElementName="skill_count" Type="int16" />
 <Data ElementName="packed_size" Type="int32" />
 <Data ElementName="unknown" Type="int8" />
@@ -3281,15 +3309,16 @@ to zero and treated like placeholders." />
 </Struct>
 <Struct Name="SubStruct_UpdateSpellBook" ClientVersion="546">
       <Data ElementName="spell_id" Type="int32" />
-      <Data ElementName="unique_id" Type="int32" />
-      <Data ElementName="recast_available" Type="int32" Size="1" />
-      <Data ElementName="type" Type="int16" Size="1" />
-      <Data ElementName="recast_time" Type="int16" Size="1" />
-      <Data ElementName="unknown3" Type="int16" />
+	  <Data ElementName="unique_id" Type="int32" />	  
+	  <Data ElementName="recast_available" Type="int32" Size="1" />		  	    
+	  <Data ElementName="type" Type="int8" Size="1" />	  
+	  <Data ElementName="recast_time" Type="int16" Size="1" />		  	  
+	  <Data ElementName="unknown3" Type="int8" />
+	  <Data ElementName="unknown4" Type="int16" />	    
       <Data ElementName="icon" Type="sint16" />
       <Data ElementName="icon_type" Type="int16" />
-      <Data ElementName="icon2" Type="int16" Size="1" />
-      <Data ElementName="charges" Type="int8" Size="1" />
+      <Data ElementName="icon2" Type="int16" Size="1" /> 
+	  <Data ElementName="charges" Type="int8" Size="1" />	  
       <Data ElementName="unknown5" Type="int8" Size="1" />
       <Data ElementName="status" Type="int8" Size="1" />
 </Struct>
@@ -4721,6 +4750,14 @@ to zero and treated like placeholders." />
 <Data ElementName="name" Type="EQ2_8Bit_String" Size="1" />
 <Data ElementName="description" Type="EQ2_16Bit_String" Size="1" />
 </Struct>
+<Struct Name="WS_EffectInfo" ClientVersion="546">
+<Data ElementName="id" Type="int32" />
+<Data ElementName="icon" Type="int16" Size="1" />
+<Data ElementName="icontype" Type="int16" Size="1" />
+<Data ElementName="type" Type="int16" Size="1" /> <!-- spell=0, combat_art=1, ability=2 -->
+<Data ElementName="name" Type="EQ2_8Bit_String" Size="1" />
+<Data ElementName="description" Type="EQ2_16Bit_String" Size="1" />
+</Struct>
 <Struct Name="WS_PartialSpellInfo" ClientVersion="1">
 <Data ElementName="id" Type="int32" />
 <Data ElementName="icon" Type="int16" Size="1" />
@@ -6092,6 +6129,10 @@ to zero and treated like placeholders." />
 <Data ElementName="info_header" Substruct="WS_ExamineInfoHeader" Size="1" />
 <Data ElementName="spell_info" Substruct="WS_EffectInfo" Size="1" />
 </Struct>
+<Struct Name="WS_ExamineEffectInfo" ClientVersion="546" OpcodeName="OP_ClientCmdMsg" OpcodeType="OP_EqExamineInfoCmd">
+<Data ElementName="info_header" Substruct="WS_ExamineInfoHeader" Size="1" />
+<Data ElementName="spell_info" Substruct="WS_EffectInfo" Size="1" />
+</Struct>
 <Struct Name="WS_ExaminePartialSpellInfo" ClientVersion="1" OpcodeName="OP_ClientCmdMsg" OpcodeType="OP_EqExamineInfoCmd">
 <Data ElementName="info_header" Substruct="WS_ExamineInfoHeader" Size="1" />
 <Data ElementName="spell_info" Substruct="WS_PartialSpellInfo" Size="1" />
@@ -6661,7 +6702,7 @@ to zero and treated like placeholders." />
 <Struct Name="Substruct_JournalRewardData" ClientVersion="546">
 	<Data ElementName="unknown1" Type="int8" Size="1" /> <!-- 255=quest reward, 0=enemy mastery, 1=specialized training,2=character trait, 3=racial tradition -->
 	<Data ElementName="reward" Type="EQ2_16Bit_String" Size="1" />
-	<Data ElementName="coin" Type="int64" Size="1" />
+	<Data ElementName="max_coin" Type="int64" Size="1" />
 	<Data ElementName="min_coin" Type="int64" Size="1" />
 	<Data ElementName="status_points" Type="int32" Size="1" />
 	<Data ElementName="text" Type="EQ2_16Bit_String" Size="1" />
@@ -6911,7 +6952,7 @@ to zero and treated like placeholders." />
 </Data>
 <Data ElementName="unknown10" Type="int8" />
 </Struct>
-<Struct Name="WS_WhoQueryReply" ClientVersion="547" OpcodeName="OP_WhoQueryReplyMsg" >
+<Struct Name="WS_WhoQueryReply" ClientVersion="546" OpcodeName="OP_WhoQueryReplyMsg" >
 <Data ElementName="account_id" Type="int32" />
 <Data ElementName="unknown" Type="int32" />
 <Data ElementName="response" Type="int8" />
@@ -7147,7 +7188,7 @@ to zero and treated like placeholders." />
 	<Data ElementName="name" Type="EQ2_16Bit_String" Size="1" />
 	<Data ElementName="quest_type" Type="EQ2_16Bit_String" Size="1" />
 	<Data ElementName="quest_zone" Type="EQ2_16Bit_String" Size="1" />
-	<Data ElementName="unknown1" Type="int8" Size="1" />
+	<Data ElementName="journal_updated" Type="int8" Size="1" />
 	<Data ElementName="turned_in" Type="int8" Size="1" />
 	<Data ElementName="repeatable" Type="int8" Size="1" />
 	<Data ElementName="unknown2" Type="int8" Size="1" />
@@ -7175,7 +7216,7 @@ to zero and treated like placeholders." />
 	<Data ElementName="name" Type="EQ2_16Bit_String" Size="1" />
 	<Data ElementName="quest_type" Type="EQ2_16Bit_String" Size="1" />
 	<Data ElementName="quest_zone" Type="EQ2_16Bit_String" Size="1" />
-	<Data ElementName="unknown1" Type="int8" Size="1" />
+	<Data ElementName="journal_updated" Type="int8" Size="1" />
 	<Data ElementName="turned_in" Type="int8" Size="1" />
 	<Data ElementName="repeatable" Type="int8" Size="1" />
 	<Data ElementName="unknown2" Type="int8" Size="1" />
@@ -7514,6 +7555,15 @@ to zero and treated like placeholders." />
 	<Data ElementName="unknown" Type="int8" Size="1" />
 </Struct>
 <Struct Name="WS_WaypointUpdate" ClientVersion="1" OpcodeName="OP_WaypointUpdateMsg">
+	<Data ElementName="num_updates" Type="int32" />
+	<Data ElementName="update_array" Type="Array" ArraySizeVariable="num_updates">
+		<Data ElementName="waypoint_name" Type="EQ2_16Bit_string" />
+		<Data ElementName="waypoint_category" Type="int8" />
+		<Data ElementName="spawn_id" Type="int32" />
+	</Data>
+	<Data ElementName="unknown" Type="int32" />
+</Struct>
+<Struct Name="WS_WaypointUpdate" ClientVersion="547" OpcodeName="OP_WaypointUpdateMsg">
 	<Data ElementName="num_updates" Type="int32" />
 	<Data ElementName="update_array" Type="Array" ArraySizeVariable="num_updates">
 		<Data ElementName="waypoint_name" Type="EQ2_16Bit_string" />
@@ -7524,6 +7574,9 @@ to zero and treated like placeholders." />
 	</Data>
 </Struct>
 <Struct Name="WS_WaypointSelect" ClientVersion="1" OpcodeName="OP_WaypointSelectMsg">
+	<Data ElementName="selection" Type="int32" />
+</Struct>
+<Struct Name="WS_WaypointSelect" ClientVersion="547" OpcodeName="OP_WaypointSelectMsg">
 	<Data ElementName="num_selections" Type="int32" />
 	<Data ElementName="selection_array" Type="Array" ArraySizeVariable="num_selections">
 		<Data ElementName="waypoint_name" Type="EQ2_16Bit_string" />
@@ -7586,11 +7639,19 @@ to zero and treated like placeholders." />
 <Data ElementName="month" Type="int8" Size="1" />
 <Data ElementName="year" Type="int8" Size="1" />
 <Data ElementName="time_obtained" Type="int32" Size="1" />
-<Data ElementName="unknown" Type="int8" Size="4" />
+<Data ElementName="timer_duration" Type="int16" Size="1" />
+<Data ElementName="timer_running" Type="int8" Size="1" /> <!-- start timer counting up -->
+<Data ElementName="timer_countdown" Type="int8" Size="1" /> <!-- count down instead of counting up -->
 <Data ElementName="level" Type="int8" Size="1" />
 <Data ElementName="encounter_level" Type="int8" Size="1" />
 <Data ElementName="difficulty" Type="int8" Size="1" />
-<Data ElementName="unknown3" Type="int8" Size="8" />
+<Data ElementName="complete" Type="int8" Size="1" />
+<Data ElementName="complete2" Type="int8" Size="1" />
+<Data ElementName="complete3" Type="int8" Size="1" />
+<Data ElementName="unknown3" Type="int8" Size="2" />
+<Data ElementName="deletable" Type="int8" Size="1" />
+<Data ElementName="shareable" Type="int8" Size="1" />
+<Data ElementName="unknown3b" Type="int8" Size="1" />
 <Data ElementName="task_groups_completed" Type="int16" Size="1" />
 <Data ElementName="num_task_groups" Type="int16" />
 <Data ElementName="task_group_array" Type="Array" ArraySizeVariable="num_task_groups">
@@ -7621,7 +7682,7 @@ to zero and treated like placeholders." />
 <Data ElementName="onscreen_update_text2" Type="EQ2_16Bit_String" Size="1" />
 <Data ElementName="onscreen_update_icon" Type="int16" Size="1" />
 <Data ElementName="unknown6" Type="int8" Size="1" />
-<Data ElementName="reward_data" Substruct="Substruct_JournalRewardData" Optional="true" />
+<Data ElementName="reward_data" Substruct="Substruct_JournalRewardData" IfVariableNotSet="complete" />
 </Struct>
 <Struct Name="WS_QuestJournalReply" ClientVersion="547" OpcodeName="OP_ClientCmdMsg" OpcodeType="OP_EqQuestJournalReplyCmd" >
 <Data ElementName="quest_id" Type="int32" Size="1" />
@@ -18197,6 +18258,27 @@ to zero and treated like placeholders." />
 </Struct>
 <Struct Name="WS_SatMsg" ClientVersion="1" OpcodeName="OP_SatMsg">
 </Struct>
+<Struct Name="WS_KnowledgebaseRequestMsg" ClientVersion="1" OpcodeName="OP_KnowledgebaseRequestMsg">
+	<Data ElementName="request_id" Type="int32" />
+	<Data ElementName="search_keyword" Type="EQ2_16Bit_String" />
+	<Data ElementName="search_article" Type="EQ2_16Bit_String" />
+</Struct>
+<Struct Name="WS_KnowledgebaseResponseMsg" ClientVersion="1" OpcodeName="OP_KnowledgebaseResponseMsg">
+	<Data ElementName="unknown" Type="int8" Size="6" />
+	<Data ElementName="num_articles" Type="int16" />
+	<Data ElementName="article_array" Type="Array" ArraySizeVariable="num_articles">
+	  <Data ElementName="article" Type="EQ2_16Bit_String" />
+	 </Data> 	 
+	<Data ElementName="num_match_percents2" Type="int16" />
+	<Data ElementName="match_percents_array" Type="Array" ArraySizeVariable="num_match_percents">
+	  <Data ElementName="percentage" Type="int16" />
+	 </Data> 	 
+	 <Data ElementName="num_article_summaries" Type="int16" />
+	<Data ElementName="article_summaries_array" Type="Array" ArraySizeVariable="num_article_summaries">
+	  <Data ElementName="summary" Type="EQ2_16Bit_String" />
+	 </Data>
+	 <Data ElementName="article" Type="EQ2_16Bit_String" />
+</Struct>
 <Struct Name="WS_SysClient" ClientVersion="1" OpcodeName="OP_SysClient">
    <Data ElementName="sys_client" Type="EQ2_16Bit_String" />
 </Struct>