Browse Source

Glowpaths/waypoints added
Fixed spell error messages for DoF client
Fixed food/drinks for DoF client
Changed items to allow archetypes to use an item if all the classes in the archetype are allowed to use it
Added a ton of LUA functions to support various gameplay features
Loot icon only added to corpse if the corpse has loot
Class skills will now rise appropriately with level
Fixed various bugs

LethalEncounter 3 years ago
parent
commit
65109b0d5d
36 changed files with 1164 additions and 276 deletions
  1. 1 0
      DB/updates/spells_update_sep_27_2020.sql
  2. 87 36
      EQ2/source/WorldServer/Commands/Commands.cpp
  3. 6 1
      EQ2/source/WorldServer/Entity.cpp
  4. 124 13
      EQ2/source/WorldServer/Items/Items.cpp
  5. 6 0
      EQ2/source/WorldServer/Items/Items.h
  6. 231 98
      EQ2/source/WorldServer/LuaFunctions.cpp
  7. 13 0
      EQ2/source/WorldServer/LuaFunctions.h
  8. 17 0
      EQ2/source/WorldServer/LuaInterface.cpp
  9. 1 0
      EQ2/source/WorldServer/LuaInterface.h
  10. 3 3
      EQ2/source/WorldServer/NPC.cpp
  11. 65 29
      EQ2/source/WorldServer/Player.cpp
  12. 7 1
      EQ2/source/WorldServer/Player.h
  13. 8 3
      EQ2/source/WorldServer/Quests.cpp
  14. 3 0
      EQ2/source/WorldServer/Quests.h
  15. 27 8
      EQ2/source/WorldServer/Skills.cpp
  16. 26 4
      EQ2/source/WorldServer/Skills.h
  17. 8 5
      EQ2/source/WorldServer/Spawn.cpp
  18. 9 1
      EQ2/source/WorldServer/Spawn.h
  19. 46 7
      EQ2/source/WorldServer/SpellProcess.cpp
  20. 1 1
      EQ2/source/WorldServer/SpellProcess.h
  21. 20 4
      EQ2/source/WorldServer/Spells.cpp
  22. 4 1
      EQ2/source/WorldServer/Spells.h
  23. 68 4
      EQ2/source/WorldServer/World.cpp
  24. 14 2
      EQ2/source/WorldServer/World.h
  25. 14 1
      EQ2/source/WorldServer/WorldDatabase.cpp
  26. 169 28
      EQ2/source/WorldServer/client.cpp
  27. 19 1
      EQ2/source/WorldServer/client.h
  28. 21 2
      EQ2/source/WorldServer/zoneserver.cpp
  29. 1 1
      EQ2/source/WorldServer/zoneserver.h
  30. 3 1
      EQ2/source/common/ConfigReader.cpp
  31. 19 0
      EQ2/source/common/PacketStruct.cpp
  32. 13 0
      EQ2/source/common/PacketStruct.h
  33. 7 0
      EQ2/source/common/misc.cpp
  34. 1 0
      EQ2/source/common/misc.h
  35. 1 1
      server/SpawnStructs.xml
  36. 101 20
      server/WorldStructs.xml

+ 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`;

+ 87 - 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){
@@ -1910,8 +1929,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}");
@@ -7746,6 +7763,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);
@@ -8450,8 +8473,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);
 		}
@@ -8543,21 +8566,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) {
@@ -8609,34 +8650,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]);
@@ -8666,7 +8717,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.");

+ 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;
@@ -3207,6 +3231,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;
@@ -3694,8 +3739,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);
@@ -3710,7 +3753,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);
 						}
 					}
@@ -3734,93 +3777,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;
 }
 
@@ -4099,22 +4058,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
 			{
@@ -4123,7 +4101,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);
 			}
 
 
@@ -5423,6 +5401,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;
@@ -7063,6 +7086,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;
@@ -9218,9 +9354,6 @@ int EQ2Emu_lua_SetPlayerLevel(lua_State* state) {
 	}
 
 	client->ChangeLevel(client->GetPlayer()->GetLevel(), level);
-	client->GetPlayer()->SetXP(1);
-	client->GetPlayer()->SetNeededXP();
-
 	return 0;
 }
 
@@ -9977,7 +10110,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;
 	}
@@ -10458,16 +10591,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

@@ -140,6 +140,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);
@@ -202,6 +204,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);
@@ -232,6 +235,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);
@@ -311,6 +315,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);
@@ -355,6 +364,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);
@@ -831,6 +836,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);
@@ -841,6 +848,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);
@@ -868,6 +876,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);
@@ -943,6 +952,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);
@@ -985,6 +998,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

@@ -228,14 +228,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
@@ -258,7 +258,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

@@ -337,14 +337,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);
@@ -1006,7 +1006,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);
@@ -2224,7 +2226,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);

+ 169 - 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,49 @@ 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);
+			if(itr->second.type == 3)
+				packet->setArrayDataByName("spawn_id", 0xFFFFFFFF, i);
+			else
+				packet->setArrayDataByName("spawn_id", itr->second.id, i);			
+			i++;
+		}		
+		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());
@@ -8024,7 +8132,37 @@ void Client::AddWaypoint(const char* waypoint_name, int8 waypoint_category, int3
 			safe_delete(packet);
 		}
 	}
+}
 
+void Client::ShowPathToTarget(Spawn* spawn) {
+	if (spawn && current_zone->pathing) {
+		bool partial = false;
+		bool stuck = false;
+		PathfinderOptions opts;
+		opts.smooth_path = true;
+		opts.step_size = 100.0f;//RuleR(Pathing, NavmeshStepSize);
+		opts.offset = spawn->GetYOffset() + 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(spawn->GetX(), spawn->GetZ(), spawn->GetY()), 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", spawn->GetX());
+				packet->setDataByName("waypoint_y", spawn->GetY());
+				packet->setDataByName("waypoint_z", spawn->GetZ());
+				i++;
+			}
+			if(i>0)
+				QueuePacket(packet->serialize());
+			safe_delete(packet);
+		}
+	}
 }
 
 void Client::BeginWaypoint(const char* waypoint_name, float x, float y, float z) {
@@ -9065,6 +9203,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);

+ 19 - 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,18 @@ 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 ShowPathToTarget(Spawn* spawn);
+
 private:
 	void    SavePlayerImages();
 	void	SkillChanged(Skill* skill, int16 previous_value, int16 new_value);
@@ -455,6 +471,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

@@ -1328,6 +1328,7 @@ bool ZoneServer::Process()
 				database.LoadTransporters(this);
 				LogWrite(TRANSPORT__INFO, 0, "Transport", "-Loading Transporters complete!");
 				reloading = false;
+				world.RemoveReloadingSubSystem("Spawns");
 			}
 
 			MSpawnGroupAssociation.writelock(__FUNCTION__, __LINE__);
@@ -3160,14 +3161,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);
 		}
 	}
@@ -4856,6 +4857,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;
@@ -7492,6 +7510,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

@@ -275,7 +275,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

+ 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 -->

+ 101 - 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,14 @@ 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>
+</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 +7573,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 +7638,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 +7681,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 +18257,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>