Browse Source

Merge branch 'master' of http://cutpon.com:3000/devn00b/EQ2EMu

LethalEncounter 3 years ago
parent
commit
3ea7ffebb6
33 changed files with 1509 additions and 127 deletions
  1. 7 0
      CMakeLists.txt
  2. 16 0
      EQ2/source/LoginServer/CMakeLists.txt
  3. 22 18
      EQ2/source/LoginServer/LWorld.cpp
  4. 3 3
      EQ2/source/LoginServer/LWorld.h
  5. 2 2
      EQ2/source/LoginServer/LoginDatabase.cpp
  6. 1 1
      EQ2/source/LoginServer/LoginDatabase.h
  7. 1 1
      EQ2/source/LoginServer/client.h
  8. 19 0
      EQ2/source/WorldServer/CMakeLists.txt
  9. 2 2
      EQ2/source/WorldServer/Combat.cpp
  10. 56 8
      EQ2/source/WorldServer/Commands/Commands.cpp
  11. 1 0
      EQ2/source/WorldServer/Commands/Commands.h
  12. 5 5
      EQ2/source/WorldServer/Entity.cpp
  13. 1 1
      EQ2/source/WorldServer/Entity.h
  14. 212 7
      EQ2/source/WorldServer/LuaFunctions.cpp
  15. 8 0
      EQ2/source/WorldServer/LuaFunctions.h
  16. 65 6
      EQ2/source/WorldServer/LuaInterface.cpp
  17. 11 1
      EQ2/source/WorldServer/LuaInterface.h
  18. 6 1
      EQ2/source/WorldServer/Spawn.cpp
  19. 64 44
      EQ2/source/WorldServer/SpellProcess.cpp
  20. 4 3
      EQ2/source/WorldServer/SpellProcess.h
  21. 858 1
      EQ2/source/WorldServer/Spells.cpp
  22. 10 1
      EQ2/source/WorldServer/Spells.h
  23. 1 0
      EQ2/source/WorldServer/Zone/mob_movement_manager.h
  24. 3 3
      EQ2/source/WorldServer/zoneserver.cpp
  25. 2 2
      EQ2/source/WorldServer/zoneserver.h
  26. 2 2
      EQ2/source/common/ConfigReader.cpp
  27. 1 1
      EQ2/source/common/servertalk.h
  28. 2 2
      EQ2/source/common/version.h
  29. 112 0
      cmake/FindMySQL.cmake
  30. BIN
      server/EQ2Login__Debug64.exe
  31. BIN
      server/EQ2World__Debug_x64.exe
  32. 9 9
      server/SpawnScripts/ThunderingSteppes/Brianna.lua
  33. 3 3
      server/SpawnScripts/ThunderingSteppes/Jacques.lua

+ 7 - 0
CMakeLists.txt

@@ -0,0 +1,7 @@
+cmake_minimum_required(VERSION 2.8)
+project (eq2emu)
+
+set(EXECUTABLE_OUTPUT_PATH ${CMAKE_BINARY_DIR})
+
+add_subdirectory(EQ2/source/LoginServer)
+add_subdirectory(EQ2/source/WorldServer)

+ 16 - 0
EQ2/source/LoginServer/CMakeLists.txt

@@ -0,0 +1,16 @@
+cmake_minimum_required(VERSION 2.8)
+project (LoginServer)
+
+file(GLOB LOGIN_SRCS "*.cpp" "*.h")
+file(GLOB COMMON_SRCS "../common/*.cpp" "../common/*.h")
+set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/")  
+find_package(MySQL)
+find_package(Threads)
+find_package(ZLIB)
+
+add_definitions(-DEQ2 -DLOGIN)
+add_executable(login ${LOGIN_SRCS} ${COMMON_SRCS})
+
+target_include_directories(login PUBLIC ${MySQL_INCLUDE_DIRS} ../common/)
+target_link_libraries(login PUBLIC ${MySQL_LIBRARIES} ${ZLIB_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
+

+ 22 - 18
EQ2/source/LoginServer/LWorld.cpp

@@ -396,8 +396,8 @@ bool LWorld::Process() {
 			if (pack->size != sizeof(ServerLSInfo_Struct)) {
 				this->Kick(ERROR_BADVERSION);
 				ret = false;
-				struct in_addr  in;
-				in.s_addr = GetIP();
+				//struct in_addr  in;
+				//in.s_addr = GetIP();
 			}
 			else {
 				ServerLSInfo_Struct* lsi = (ServerLSInfo_Struct*) pack->pBuffer;
@@ -406,14 +406,14 @@ bool LWorld::Process() {
 					cout << "To allow all world server versions to login, run query on your login database (alternatively replacing * with the database version if preferred): insert into login_versions set version = '*';" << endl;
 					this->Kick(ERROR_BADVERSION);
 					ret = false;
-					struct in_addr  in;
-					in.s_addr = GetIP();
+					//struct in_addr  in;
+					//in.s_addr = GetIP();
 				}
 				else if (!SetupWorld(lsi->name, lsi->address, lsi->account, lsi->password, lsi->serverversion)) {
 					this->Kick(ERROR_BADPASSWORD);
 					ret = false;
-					struct in_addr  in;
-					in.s_addr = GetIP();
+					//struct in_addr  in;
+					//in.s_addr = GetIP();
 				}
 				else{
 					isAuthenticated = true;
@@ -461,8 +461,8 @@ bool LWorld::Process() {
 			}
 			ServerSyncWorldList_Struct* sswls = (ServerSyncWorldList_Struct*) pack->pBuffer;
 			if (!CheckServerName(sswls->name)) {
-				struct in_addr  in;
-				in.s_addr = sswls->ip;
+				//struct in_addr  in;
+				//in.s_addr = sswls->ip;
 				break; // Someone needs to tell the other login server to update it's exe!
 			}
 			LWorld* world = world_list.FindByLink(this->Link, sswls->RemoteID);
@@ -621,7 +621,7 @@ void LWorld::SendPacket(ServerPacket* pack) {
 	}
 }
 
-void LWorld::Message(char* to, char* message, ...) {
+void LWorld::Message(const char* to, const char* message, ...) {
 	va_list argptr;
 	char buffer[256];
 
@@ -637,7 +637,7 @@ void LWorld::Message(char* to, char* message, ...) {
 	delete pack;
 }
 
-void LWorld::Kick(char* message, bool iSetKickedFlag) {
+void LWorld::Kick(const char* message, bool iSetKickedFlag) {
 	if (iSetKickedFlag)
 		kicked = true;
 	if (message) {
@@ -649,10 +649,10 @@ void LWorld::Kick(char* message, bool iSetKickedFlag) {
 	if (Link && GetRemoteID() == 0)
 		Link->Disconnect();
 }
-bool LWorld::CheckServerName(char* name) {
+bool LWorld::CheckServerName(const char* name) {
 	if (strlen(name) < 10)
 		return false;
-	for (int i=0; i<strlen(name); i++) {
+	for (size_t i=0; i<strlen(name); i++) {
 		if (!((name[i] >= 'a' && name[i] <= 'z') || (name[i] >= 'A' && name[i] <= 'Z') || (name[i] >= '0' && name[i] <= '9') || name[i] == ' ' || name[i] == '\'' || name[i] == '-' || name[i] == '(' || name[i] == ')' || name[i] == '[' || name[i] == ']' || name[i] == '/' || name[i] == '.' || name[i] == ',' || name[i] == '_' || name[i] == '+' || name[i] == '=' || name[i] == ':' || name[i] == '~'))
 			return false;
 	}
@@ -819,6 +819,7 @@ void LWorldList::KickGhostIP(int32 ip, LWorld* NotMe, int16 iClientPort) {
 				in.s_addr = world->GetIP();
 				//				cout << "Removing GhostIP LWorld(" << world->GetID() << ") from ip: " << inet_ntoa(in) << " port: " << (int16)(world->GetPort());
 				if (!world->Connected())
+				{
 					//					cout << " (it wasnt connected)";
 					//				cout << endl;
 					if (NotMe) {
@@ -826,6 +827,7 @@ void LWorldList::KickGhostIP(int32 ip, LWorld* NotMe, int16 iClientPort) {
 						cout << "NotMe(" << NotMe->GetID() << ") = " << inet_ntoa(in) << ":" << NotMe->GetPort() << " (" << NotMe->GetClientPort() << ")" << endl;
 					}
 					world->Kick("Ghost IP kick");
+				}
 			}
 		}
 	}
@@ -839,8 +841,8 @@ void LWorldList::KickGhost(ConType in_type, int32 in_accountid, LWorld* ButNotMe
 		LWorld* world = map_list->second;
 		if (!world->IsKicked() && world->GetType() == in_type && world != ButNotMe && (in_accountid == 0 || world->GetAccountID() == in_accountid)) {
 			if (world->GetIP() != 0) {
-				struct in_addr  in;
-				in.s_addr = world->GetIP();
+				//struct in_addr  in;
+				//in.s_addr = world->GetIP();
 				//				cout << "Removing GhostAcc LWorld(" << world->GetID() << ") from ip: " << inet_ntoa(in) << " port: " << (int16)(world->GetPort()) << endl;
 			}
 			if (world->GetType() == Login && world->IsOutgoingUplink()) {
@@ -915,8 +917,8 @@ void LWorldList::Process() {
 			continue;
 		}
 		else if (!world->Process()) {
-			struct in_addr  in;
-			in.s_addr = world->GetIP();
+			//struct in_addr  in;
+			//in.s_addr = world->GetIP();
 			if (world->GetAccountID() == 0 || !(world->ShowDown()) || world->GetType() == Chat) {
 				map_list++;
 				worldmap.erase ( account_id );
@@ -1120,8 +1122,7 @@ EQ2Packet* LWorldList::MakeServerListPacket(int8 lsadmin, int16 version) {
 		return ServerListData[version];
 	}
 
-	LWorld* world = 0;
-	uchar num_servers[2];
+	//LWorld* world = 0;
 	int32 ServerNum = 0;
 	/*	while(iterator.MoreElements()){
 	world = iterator.GetData();
@@ -1191,8 +1192,11 @@ EQ2Packet* LWorldList::MakeServerListPacket(int8 lsadmin, int16 version) {
 	}
 
 	EQ2Packet* pack = packet->serialize();
+	#ifdef DEBUG
+	//Only dump these for people trying to debug this...
 	printf("WorldList:\n");
 	DumpPacket(pack->pBuffer, pack->size);
+	#endif
 	if (ServerListData.count(version))
 	{
 		map<int32, EQ2Packet*>::iterator it = ServerListData.find(version);

+ 3 - 3
EQ2/source/LoginServer/LWorld.h

@@ -50,11 +50,11 @@ public:
 	LWorld(TCPConnection* in_RemoteLink, int32 in_ip, int32 in_RemoteID, int32 in_accountid, char* in_accountname, char* in_worldname, char* in_address, sint32 in_status, int32 in_adminid, bool in_showdown, int8 in_authlevel, bool in_placeholder, int32 iLinkWorldID);
     ~LWorld();
 
-	static bool CheckServerName(char* name);
+	static bool CheckServerName(const char* name);
 	
 	bool	Process();
 	void	SendPacket(ServerPacket* pack);
-	void	Message(char* to, char* message, ...);
+	void	Message(const char* to, const char* message, ...);
 
 	bool	SetupWorld(char* in_worldname, char* in_worldaddress, char* in_account, char* in_password, char* in_version);
 	void	UpdateStatus(sint32 in_status, sint32 in_players, sint32 in_zones) {
@@ -91,7 +91,7 @@ public:
 	int8			GetWorldStatus();
 
 	void			ChangeToPlaceholder();
-	void			Kick(char* message = ERROR_GHOST, bool iSetKickedFlag = true);
+	void			Kick(const char* message = ERROR_GHOST, bool iSetKickedFlag = true);
 	inline bool		IsKicked()				{ return kicked; }
 	inline bool		IsNeverKick()			{ return pNeverKick; }
 	inline  ConType	GetType()				{ return ptype; }

+ 2 - 2
EQ2/source/LoginServer/LoginDatabase.cpp

@@ -229,7 +229,7 @@ void LoginDatabase::LoadCharacters(LoginAccount* acct, int16 version){
 			player->packet->setColorByName("unknown14", 0xFF, 0xFF, 0xFF);
 
 			uchar tmp[] = {0xFF, 0xFF, 0xFF, 0x61, 0x00, 0x2C, 0x04, 0xA5, 0x09, 0x02, 0x0F, 0x00, 0x00};
-			for(int y=0;y<sizeof(tmp);y++)
+			for(size_t y=0;y<sizeof(tmp);y++)
 				player->packet->setDataByName("unknown11", tmp[y], y);
 
 			MYSQL_RES* result3 = query2.RunQuery2(Q_SELECT, "SELECT slot, equip_type, red, green, blue, highlight_red, highlight_green, highlight_blue from login_equipment where login_characters_id=%i order by slot",id);
@@ -623,7 +623,7 @@ void LoginDatabase::GetServerAccounts(vector<LWorld*>* server_list){
 		server_list->push_back(world);
 	}
 }
-void LoginDatabase::SaveClientLog(char* type, char* message, char* player_name, int16 version){
+void LoginDatabase::SaveClientLog(const char* type, const char* message, const char* player_name, int16 version){
 	Query query;
 	query.escaped_data1 = getEscapeString(message);
 	query.escaped_name = getEscapeString(player_name);

+ 1 - 1
EQ2/source/LoginServer/LoginDatabase.h

@@ -61,7 +61,7 @@ public:
 	int32 GetRaceID(char* name);
 	void UpdateRaceID(char* name);
 	bool DeleteCharacter(int32 account_id, int32 character_id, int32 server_id);
-	void SaveClientLog(char* type, char* message, char* player_name, int16 version);
+	void SaveClientLog(const char* type, const char* message, const char* player_name, int16 version);
 	bool CheckVersion(char* version);
 	void GetLatestTableVersions(LatestTableVersions* table_versions);
 	TableQuery* GetLatestTableQuery(int32 server_ip, char* name, int16 version);

+ 1 - 1
EQ2/source/LoginServer/client.h

@@ -49,7 +49,7 @@ public:
 	int32	GetIP()    { return ip; }
 	int16	GetPort()  { return port; }
 	int32	GetAccountID() { return account_id; }
-	char*	GetAccountName(){ return (char*)account_name.c_str(); }
+	const char*	GetAccountName(){ return (char*)account_name.c_str(); }
 	void	SetAccountName(const char* name){ account_name = string(name); }
 	void	ProcessLogin(char* name, char* pass,int seq=0);
 	void	QueuePacket(EQ2Packet* app);

+ 19 - 0
EQ2/source/WorldServer/CMakeLists.txt

@@ -0,0 +1,19 @@
+cmake_minimum_required(VERSION 2.8)
+project (WorldServer)
+
+file(GLOB WORLD_SRCS "*.cpp" "*.h" "*/*.cpp" "*/*.h")
+file(GLOB COMMON_SRCS "../common/*.cpp" "../common/*.h")
+file(GLOB LUA_SRCS "../LUA/*.c" "../LUA/*.h")
+set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/")  
+find_package(MySQL)
+find_package(Threads)
+find_package(ZLIB)
+
+add_definitions(-DEQ2 -DWORLD)
+add_executable(eq2world ${WORLD_SRCS} ${COMMON_SRCS} ${LUA_SRCS})
+
+set(RECAST_LIBRARIES -L${CMAKE_SOURCE_DIR}/EQ2/source/depends/recastnavigation/RecastDemo/Build/gmake/lib/Debug -lDebugUtils -lDetour -lDetourCrowd -lDetourTileCache -lRecast)
+
+target_include_directories(eq2world PUBLIC ${MySQL_INCLUDE_DIRS} ../common/ ../depends/recastnavigation/Detour/Include)
+target_link_libraries(eq2world PUBLIC ${MySQL_LIBRARIES} ${ZLIB_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} ${RECAST_LIBRARIES})
+

+ 2 - 2
EQ2/source/WorldServer/Combat.cpp

@@ -545,7 +545,7 @@ bool Entity::ProcAttack(Spawn* victim, int8 damage_type, int32 low_damage, int32
 	return true;
 }
 
-bool Entity::SpellHeal(Spawn* target, float distance, LuaSpell* luaspell, string heal_type, int32 low_heal, int32 high_heal, int8 crit_mod, bool no_calcs){
+bool Entity::SpellHeal(Spawn* target, float distance, LuaSpell* luaspell, string heal_type, int32 low_heal, int32 high_heal, int8 crit_mod, bool no_calcs, string custom_spell_name){
 	 if(!target || !luaspell || !luaspell->spell)
 		return false;
 
@@ -673,7 +673,7 @@ bool Entity::SpellHeal(Spawn* target, float distance, LuaSpell* luaspell, string
 
 	target->GetZone()->TriggerCharSheetTimer();
 	if (heal_amt > 0)
-		GetZone()->SendHealPacket(this, target, type, heal_amt, luaspell->spell->GetName());
+		GetZone()->SendHealPacket(this, target, type, heal_amt, custom_spell_name.length() > 0 ? (char*)custom_spell_name.c_str() : luaspell->spell->GetName());
 	CheckProcs(PROC_TYPE_HEALING, target);
 	CheckProcs(PROC_TYPE_BENEFICIAL, target);
 

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

@@ -154,6 +154,7 @@ Commands::Commands(){
 	spawn_set_values["holiday_flag"] = SPAWN_SET_VALUE_HOLIDAY_FLAG;
 	spawn_set_values["merchant_min_level"] = SPAWN_SET_VALUE_MERCHANT_MIN_LEVEL;
 	spawn_set_values["merchant_max_level"] = SPAWN_SET_VALUE_MERCHANT_MAX_LEVEL;
+	spawn_set_values["skin_color"] = SPAWN_SET_SKIN_COLOR;
 
 	zone_set_values["expansion_id"] = ZONE_SET_VALUE_EXPANSION_ID;
 	zone_set_values["name"] = ZONE_SET_VALUE_NAME;
@@ -197,7 +198,9 @@ bool Commands::SetSpawnCommand(Client* client, Spawn* target, int8 type, const c
 		return false;
 	int32 val = 0;
 	try{
-		if(type != SPAWN_SET_VALUE_NAME && !(type >= SPAWN_SET_VALUE_SPAWN_SCRIPT && type <= SPAWN_SET_VALUE_SUB_TITLE) && !(type >= SPAWN_SET_VALUE_PREFIX && type <= SPAWN_SET_VALUE_EXPANSION_FLAG || type == SPAWN_SET_VALUE_HOLIDAY_FLAG))
+		if(type != SPAWN_SET_VALUE_NAME && 
+			!(type >= SPAWN_SET_VALUE_SPAWN_SCRIPT && type <= SPAWN_SET_VALUE_SUB_TITLE) && !(type >= SPAWN_SET_VALUE_PREFIX && type <= SPAWN_SET_VALUE_EXPANSION_FLAG || type == SPAWN_SET_VALUE_HOLIDAY_FLAG)
+			&& type != SPAWN_SET_SKIN_COLOR)
 			val = atoul(value);
 	}
 	catch(...){
@@ -205,7 +208,7 @@ bool Commands::SetSpawnCommand(Client* client, Spawn* target, int8 type, const c
 			client->Message(CHANNEL_COLOR_RED, "Invalid numeric spawn value: %s", value);
 		return false;
 	}
-	if(temporary && temp_value){
+	if(temporary){
 		char tmp[128] = {0};
 		switch(type){
 			case SPAWN_SET_VALUE_NAME:{
@@ -500,8 +503,26 @@ bool Commands::SetSpawnCommand(Client* client, Spawn* target, int8 type, const c
 				target->SetMerchantLevelRange(target->GetMerchantMinLevel(), atoul(value));
 				break;
 			}
+			case SPAWN_SET_SKIN_COLOR: {
+				if (target->IsNPC())
+				{
+					Seperator* skinsep = new Seperator(value, ' ', 3, 500, true);
+					if (skinsep->IsNumber(0) && skinsep->IsNumber(1) && skinsep->IsNumber(2))
+					{
+						EQ2_Color clr;
+						clr.red = atoul(skinsep->arg[0]);
+						clr.green = atoul(skinsep->arg[1]);
+						clr.blue = atoul(skinsep->arg[2]);
+
+						((Entity*)target)->SetSkinColor(clr);
+					}
+					safe_delete(skinsep);
+				}
+				break;
+			}
 
-			*temp_value = string(tmp);
+			if(temp_value)
+				*temp_value = string(tmp);
 		}
 	}
 	else{
@@ -838,7 +859,28 @@ bool Commands::SetSpawnCommand(Client* client, Spawn* target, int8 type, const c
 				else
 					target->SetSpawnScript(value);
 				break;
-												   }
+			}
+
+			case SPAWN_SET_SKIN_COLOR: {
+				if (target->IsNPC())
+				{
+					Seperator* skinsep = new Seperator(value, ' ', 3, 500, true);
+					if (skinsep->IsNumber(0) && skinsep->IsNumber(1) && skinsep->IsNumber(2))
+					{
+						EQ2_Color clr;
+						clr.red = atoul(skinsep->arg[0]);
+						clr.green = atoul(skinsep->arg[1]);
+						clr.blue = atoul(skinsep->arg[2]);
+
+						((Entity*)target)->SetSkinColor(clr);
+						Query replaceSkinQuery;
+						replaceSkinQuery.AddQueryAsync(0, &database, Q_DELETE, "delete from npc_appearance where spawn_id=%u and type='skin_color'", target->GetDatabaseID());
+						replaceSkinQuery.AddQueryAsync(0, &database, Q_INSERT, "insert into npc_appearance set spawn_id=%u, type='skin_color', red=%u, green=%u, blue=%u", target->GetDatabaseID(), clr.red, clr.green, clr.blue);
+					}
+					safe_delete(skinsep);
+				}
+				break;
+			}
 		}
 	}
 	return true;
@@ -1390,6 +1432,8 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 							item->save_needed = true;
 							client->QueuePacket(item->serialize(client->GetVersion(), false, client->GetPlayer()));
 						}
+						else
+							LogWrite(PLAYER__ERROR, 0, "Command", "%s: Item %s (%i) attempted to be used, however display_charges is 0.", client->GetPlayer()->GetName(), item->name.c_str(), item->details.item_id);
 					}
 				}
 			}
@@ -3675,16 +3719,20 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 					else
 					{
 						string name = string(spawn->GetName());
-						if(SetSpawnCommand(client, spawn, set_type, sep->arg[1]))
+						if(SetSpawnCommand(client, spawn, set_type, sep->argplus[1]))
 						{
 							if (set_type == SPAWN_SET_VALUE_EXPANSION_FLAG || set_type == SPAWN_SET_VALUE_HOLIDAY_FLAG)
 							{
 								client->SimpleMessage(CHANNEL_COLOR_YELLOW, "A /reload spawns is required to properly update the spawns with the xpack/holiday flag.");
 							}
-							else if(set_type == SPAWN_SET_VALUE_NAME)
+							else if (set_type == SPAWN_SET_VALUE_NAME)
 							{
 								client->SimpleMessage(CHANNEL_COLOR_YELLOW, "New name will not be effective until zone reload.");
 							}
+							else if (set_type == SPAWN_SET_SKIN_COLOR)
+							{
+								client->Message(CHANNEL_COLOR_YELLOW, "Successfully set skin_color to R G B: %s.", sep->argplus[1]);
+							}
 							else if(set_type == SPAWN_SET_VALUE_LOCATION)
 							{
 								spawn->SetLocation(client->GetPlayer()->GetLocation());
@@ -3706,7 +3754,7 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 								}
 								default:
 								{
-									client->GetCurrentZone()->ApplySetSpawnCommand(client, spawn, set_type, sep->arg[1]);
+									client->GetCurrentZone()->ApplySetSpawnCommand(client, spawn, set_type, sep->argplus[1]);
 									break;
 								}
 							}
@@ -4557,7 +4605,7 @@ void Commands::Command_CancelMaintained(Client* client, Seperator* sep)
 	//	if (spell && spell->GetSpellData()->friendly_spell)  -- NOTE::You can cancel hostile maintained spells, 
 		                                                     // just not spelleffects/dets - Foof
 		//{
-			if (!client->GetPlayer()->GetZone()->GetSpellProcess()->DeleteCasterSpell(mEffects.spell))
+			if (!client->GetPlayer()->GetZone()->GetSpellProcess()->DeleteCasterSpell(mEffects.spell, "canceled"))
 				client->Message(CHANNEL_COLOR_RED, "The maintained spell could not be cancelled.");
 	//	}
 		//else

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

@@ -515,6 +515,7 @@ private:
 #define SPAWN_SET_VALUE_MERCHANT_MIN_LEVEL  60
 #define SPAWN_SET_VALUE_MERCHANT_MAX_LEVEL  61
 #define SPAWN_SET_VALUE_HOLIDAY_FLAG		62
+#define SPAWN_SET_SKIN_COLOR				63
 
 #define ZONE_SET_VALUE_EXPANSION_ID			0
 #define ZONE_SET_VALUE_NAME					1

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

@@ -1135,7 +1135,7 @@ void Entity::DismissPet(NPC* pet, bool from_death) {
 	// Remove the spell maintained spell
 	Spell* spell = master_spell_list.GetSpell(pet->GetPetSpellID(), pet->GetPetSpellTier());
 	if (spell)
-		GetZone()->GetSpellProcess()->DeleteCasterSpell(this, spell);
+		GetZone()->GetSpellProcess()->DeleteCasterSpell(this, spell, from_death == true ? (string)"pet_death" : (string)"canceled");
 
 	if (pet->GetPetType() == PET_TYPE_CHARMED) {
 		PetOwner->SetCharmedPet(0);
@@ -1292,7 +1292,7 @@ int32 Entity::CheckWards(Entity* attacker, int32 damage, int8 damage_type) {
 			if (!ward->keepWard) {
 				hasSpellBeenRemoved = true;
 				RemoveWard(spell->spell->GetSpellID());
-				GetZone()->GetSpellProcess()->DeleteCasterSpell(spell);
+				GetZone()->GetSpellProcess()->DeleteCasterSpell(spell, "purged");
 			}
 		}
 		else {
@@ -1317,12 +1317,12 @@ int32 Entity::CheckWards(Entity* attacker, int32 damage, int8 damage_type) {
 			if (this->IsPlayer())
 			{
 				Client* client = GetZone()->GetClientBySpawn(this);
-				client->Message(CHANNEL_COLOR_COMBAT, "%s intercepted some of the damage intended for you!", spell->caster->GetName());
+				client->Message(CHANNEL_COMBAT, "%s intercepted some of the damage intended for you!", spell->caster->GetName());
 			}
 			if (spell->caster && spell->caster->IsPlayer())
 			{
 				Client* client = GetZone()->GetClientBySpawn(spell->caster);
-				client->Message(CHANNEL_COLOR_COMBAT, "YOU intercept some of the damage intended for %s!", this->GetName());
+				client->Message(CHANNEL_COMBAT, "YOU intercept some of the damage intended for %s!", this->GetName());
 			}
 
 			if (attacker && spell->caster)
@@ -1344,7 +1344,7 @@ int32 Entity::CheckWards(Entity* attacker, int32 damage, int8 damage_type) {
 		if (shouldRemoveSpell && !hasSpellBeenRemoved)
 		{
 			RemoveWard(spell->spell->GetSpellID());
-			GetZone()->GetSpellProcess()->DeleteCasterSpell(spell);
+			GetZone()->GetSpellProcess()->DeleteCasterSpell(spell, "purged");
 		}
 
 		// Reset ward pointer

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

@@ -472,7 +472,7 @@ public:
 	void			RangeAttack(Spawn* victim, float distance, Item* weapon, Item* ammo, bool multi_attack = false);
 	bool			SpellAttack(Spawn* victim, float distance, LuaSpell* luaspell, int8 damage_type, int32 low_damage, int32 high_damage, int8 crit_mod = 0, bool no_calcs = false);
 	bool			ProcAttack(Spawn* victim, int8 damage_type, int32 low_damage, int32 high_damage, string name, string success_msg, string effect_msg);
-	bool            SpellHeal(Spawn* target, float distance, LuaSpell* luaspell, string heal_type, int32 low_heal, int32 high_heal, int8 crit_mod = 0, bool no_calcs = false);
+	bool            SpellHeal(Spawn* target, float distance, LuaSpell* luaspell, string heal_type, int32 low_heal, int32 high_heal, int8 crit_mod = 0, bool no_calcs = false, string custom_spell_name="");
 	int8			DetermineHit(Spawn* victim, int8 damage_type, float ToHitBonus, bool spell);
 	float			GetDamageTypeResistPercentage(int8 damage_type);
 	Skill*			GetSkillByWeaponType(int8 type, bool update);

+ 212 - 7
EQ2/source/WorldServer/LuaFunctions.cpp

@@ -37,6 +37,7 @@
 #include "Transmute.h"
 #include <boost/algorithm/string/predicate.hpp>
 #include <sstream> 
+#include <boost/algorithm/string.hpp>
 
 extern MasterFactionList master_faction_list;
 extern WorldDatabase database;
@@ -353,9 +354,11 @@ int EQ2Emu_lua_SpawnSet(lua_State* state) {
 	Spawn* spawn = lua_interface->GetSpawn(state);
 	string variable = lua_interface->GetStringValue(state, 2);
 	string value = lua_interface->GetStringValue(state, 3);
+	bool no_update = lua_interface->GetBooleanValue(state, 4); // send update is true by default in SetSpawnCommand, so allow user to specify 'true' to disable send update.
+	bool temporary_flag = lua_interface->GetBooleanValue(state, 5); // default false as originally designed, allow user to set temporary_flag true to not update DB
 	int32 type = commands.GetSpawnSetType(variable);
 	if (type != 0xFFFFFFFF && value.length() > 0 && spawn)
-		commands.SetSpawnCommand(0, spawn, type, value.c_str());
+		commands.SetSpawnCommand(0, spawn, type, value.c_str(), !no_update, temporary_flag);
 	return 0;
 }
 
@@ -1116,13 +1119,14 @@ int EQ2Emu_lua_SpellHeal(lua_State* state) {
 	Spawn* target = lua_interface->GetSpawn(state, 4);
 	int8 crit_mod = lua_interface->GetInt32Value(state, 5);
 	bool no_calcs = lua_interface->GetInt32Value(state, 6) == 1;
+	string custom_spell_name = lua_interface->GetStringValue(state, 7);//custom spell name
 	lua_interface->ResetFunctionStack(state);
 	if (caster && caster->IsEntity()) {
 		bool success = false;
 		luaspell->resisted = false;
 		if (target) {
 			float distance = caster->GetDistance(target, true);
-			if (((Entity*)caster)->SpellHeal(target, distance, luaspell, heal_type, min_heal, max_heal, crit_mod, no_calcs))
+			if (((Entity*)caster)->SpellHeal(target, distance, luaspell, heal_type, min_heal, max_heal, crit_mod, no_calcs, custom_spell_name))
 				success = true;
 		}
 		if (luaspell->targets.size() > 0) {
@@ -1132,7 +1136,7 @@ int EQ2Emu_lua_SpellHeal(lua_State* state) {
 			for (int32 i = 0; i < luaspell->targets.size(); i++) {
 				if ((target = zone->GetSpawnByID(luaspell->targets[i]))) {
 					float distance = caster->GetDistance(target, true);
-					((Entity*)caster)->SpellHeal(target, distance, luaspell, heal_type, min_heal, max_heal, crit_mod, no_calcs);
+					((Entity*)caster)->SpellHeal(target, distance, luaspell, heal_type, min_heal, max_heal, crit_mod, no_calcs, custom_spell_name);
 				}
 			}
 			luaspell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__);
@@ -1424,6 +1428,7 @@ int EQ2Emu_lua_CastSpell(lua_State* state) {
 	int32 spell_id = lua_interface->GetInt32Value(state, 2);
 	int8 spell_tier = lua_interface->GetInt8Value(state, 3);
 	Spawn* caster = lua_interface->GetSpawn(state, 4);
+	int16 custom_cast_time = lua_interface->GetInt16Value(state, 5);
 
 	if (!target) {
 		lua_interface->LogError("%s: LUA CastSpell command error: target is not a valid spawn", lua_interface->GetScriptName(state));
@@ -1451,7 +1456,7 @@ int EQ2Emu_lua_CastSpell(lua_State* state) {
 	if (!caster)
 		caster = target;
 
-	target->GetZone()->ProcessSpell(master_spell_list.GetSpell(spell_id, spell_tier), (Entity*)caster, (Entity*)target);
+	target->GetZone()->ProcessSpell(master_spell_list.GetSpell(spell_id, spell_tier), (Entity*)caster, (Entity*)target, true, false, NULL, custom_cast_time);
 	return 0;
 }
 
@@ -2802,12 +2807,25 @@ int EQ2Emu_lua_SetStepComplete(lua_State* state) {
 	if (!lua_interface)
 		return 0;
 	Spawn* player = lua_interface->GetSpawn(state);
+	if (!player || !player->IsPlayer()) {
+		lua_interface->LogError("%s: LUA SetStepComplete command error: player is not valid", lua_interface->GetScriptName(state));
+		return 0;
+	}
 	int32 quest_id = lua_interface->GetInt32Value(state, 2);
+	if (quest_id <= 0) {
+		lua_interface->LogError("%s: LUA SetStepComplete command error: quest_id is not valid", lua_interface->GetScriptName(state));
+		return 0;
+	} else if ((((Player*)player)->player_quests.count(quest_id) <= 0)) {
+		lua_interface->LogError("%s: LUA SetStepComplete command error: player does not have quest", lua_interface->GetScriptName(state));
+		return 0;
+	}
 	int32 step = lua_interface->GetInt32Value(state, 3);
-	if (player && player->IsPlayer() && quest_id > 0 && step > 0 && (((Player*)player)->player_quests.count(quest_id) > 0)) {
+	if (step > 0) {
 		Client* client = player->GetZone()->GetClientBySpawn(player);
 		if (client)
 			client->AddPendingQuestUpdate(quest_id, step);
+	} else {
+		lua_interface->LogError("%s: LUA SetStepComplete command error: step is not valid", lua_interface->GetScriptName(state));
 	}
 	return 0;
 }
@@ -2945,10 +2963,16 @@ int EQ2Emu_lua_HasQuest(lua_State* state) {
 	if (!lua_interface)
 		return 0;
 	Spawn* player = lua_interface->GetSpawn(state);
+	if(!player || !player->IsPlayer()) {
+		lua_interface->LogError("%s: LUA HasQuest command error: player is not valid", lua_interface->GetScriptName(state));
+		return 0;
+	}
 	int32 quest_id = lua_interface->GetInt32Value(state, 2);
-	if (player && player->IsPlayer() && quest_id > 0) {
+	if (quest_id > 0) {
 		lua_interface->SetBooleanValue(state, (((Player*)player)->player_quests.count(quest_id) > 0));
 		return 1;
+	} else {
+		lua_interface->LogError("%s: LUA HasQuest command error: quest_id is not valid", lua_interface->GetScriptName(state));
 	}
 	return 0;
 }
@@ -3063,8 +3087,15 @@ int EQ2Emu_lua_OfferQuest(lua_State* state) {
 		Quest* master_quest = master_quest_list.GetQuest(quest_id);
 		if (master_quest) {
 			Client* client = player->GetZone()->GetClientBySpawn(player);
+			if (!client) {
+				lua_interface->LogError("%s: LUA OfferQuest command error: client is not set", lua_interface->GetScriptName(state));
+			}
 			Quest* quest = new Quest(master_quest);
-			if (client && quest) {				
+			if (!quest) {
+				lua_interface->LogError("%s: LUA OfferQuest command error: new Quest() failed.", lua_interface->GetScriptName(state));
+			}
+			if (client && quest) {
+				client->AddPendingQuest(quest);
 				if (npc)
 					quest->SetQuestGiver(npc->GetDatabaseID());
 				else
@@ -3072,6 +3103,12 @@ int EQ2Emu_lua_OfferQuest(lua_State* state) {
 				client->AddPendingQuest(quest, forced);
 			}
 		}
+		else {
+			lua_interface->LogError("%s: LUA OfferQuest command error: failed to get quest %d", lua_interface->GetScriptName(state), quest_id);
+		}
+	}
+	else {
+		lua_interface->LogError("%s: LUA OfferQuest command error: player is not set or bad quest id %p %d", lua_interface->GetScriptName(state), player, quest_id);
 	}
 	return 0;
 }
@@ -10371,4 +10408,172 @@ int EQ2Emu_lua_GetTemporaryTransportID(lua_State* state) {
 		return 1;
 	}
 	return 0;
+}
+
+int EQ2Emu_lua_SetAlignment(lua_State* state) {
+	if (!lua_interface)
+		return 0;
+
+	Spawn* spawn = lua_interface->GetSpawn(state);
+	sint32 alignment = lua_interface->GetSInt32Value(state, 2);
+	LuaSpell* spell = lua_interface->GetCurrentSpell(state);
+
+	if (!spawn) {
+		lua_interface->LogError("%s: LUA SetAlignment 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));
+		return 0;
+	}
+
+	if (alignment < SCHAR_MIN || alignment > SCHAR_MAX)
+	{
+		lua_interface->LogError("%s: LUA SetAlignment command error: alignment value beyond supported min: %i and max: %i", lua_interface->GetScriptName(state), SCHAR_MIN, SCHAR_MAX);
+		return 0;
+	}
+
+	if (spell && spell->targets.size() > 0) {
+		ZoneServer* zone = spell->caster->GetZone();
+		for (int8 i = 0; i < spell->targets.size(); i++) {
+			Spawn* target = zone->GetSpawnByID(spell->targets.at(i));
+			if (target->IsEntity()) {
+				((Entity*)target)->GetInfoStruct()->alignment = (sint8)alignment;
+				if (target->IsPlayer())
+					((Player*)target)->SetCharSheetChanged(true);
+			}
+		}
+	}
+	else {
+		((Entity*)spawn)->GetInfoStruct()->alignment = (sint8)alignment;
+		if (spawn->IsPlayer())
+			((Player*)spawn)->SetCharSheetChanged(true);
+	}
+	return 0;
+}
+
+int EQ2Emu_lua_GetAlignment(lua_State* state) {
+	if (!lua_interface)
+		return 0;
+	Spawn* spawn = lua_interface->GetSpawn(state);
+
+	if (!spawn) {
+		lua_interface->LogError("%s: LUA SetAlignment 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));
+		return 0;
+	}
+	
+	lua_interface->SetInt32Value(state, ((Entity*)spawn)->GetAlignment());
+	return 1;
+}
+
+
+int EQ2Emu_lua_GetSpell(lua_State* state) {
+	if (!lua_interface)
+		return 0;
+	int32 spell_id = lua_interface->GetInt32Value(state);
+	int8 spell_tier = lua_interface->GetInt8Value(state, 2);
+	if (spell_id > 0) {
+
+		if (spell_tier == 0)
+			spell_tier = 1;
+
+		Spell* spell = master_spell_list.GetSpell(spell_id, spell_tier);
+		LuaSpell* lua_spell = 0;
+		if (lua_interface)
+			lua_spell = lua_interface->GetSpell(spell->GetSpellData()->lua_script.c_str());
+
+		if (!lua_spell)
+			return 0;
+
+		lua_spell->spell = new Spell(spell);
+
+		lua_interface->SetSpellValue(state, lua_spell);
+		return 1;
+	}
+	return 0;
+}
+
+int EQ2Emu_lua_GetSpellData(lua_State* state) {
+	if (!lua_interface)
+		return 0;
+	LuaSpell* spell = lua_interface->GetSpell(state);
+	string field = lua_interface->GetStringValue(state, 2);
+
+	if (!spell) {
+		lua_interface->LogError("%s: Spell not given in GetSpellData!", lua_interface->GetScriptName(state));
+		return 0;
+	}
+	if (!spell->spell || !spell->spell->GetSpellData()) {
+		lua_interface->LogError("%s: Inner Spell or SpellData not given in GetSpellData!", lua_interface->GetScriptName(state));
+		return 0;
+	}
+
+	boost::to_lower(field);
+
+
+	return spell->spell->GetSpellData(state, field);
+}
+
+
+int EQ2Emu_lua_SetSpellData(lua_State* state) {
+	if (!lua_interface)
+		return 0;
+	LuaSpell* spell = lua_interface->GetSpell(state);
+	string field = lua_interface->GetStringValue(state, 2);
+	int8 fieldArg = 3; // field value after the initial set
+
+	if (!spell) {
+		lua_interface->LogError("%s: Spell not given in SetSpellData!", lua_interface->GetScriptName(state));
+		return 0;
+	}
+	if (!spell->spell || !spell->spell->GetSpellData()) {
+		lua_interface->LogError("%s: Inner Spell or SpellData not given in SetSpellData!", lua_interface->GetScriptName(state));
+		return 0;
+	}
+
+	boost::to_lower(field);
+
+	bool valSet = false;
+
+	spell->spell->SetSpellData(state, field, fieldArg);
+
+	return valSet;
+}
+
+
+int EQ2Emu_lua_CastCustomSpell(lua_State* state) {
+	if (!lua_interface)
+		return 0;
+	LuaSpell* spell = lua_interface->GetSpell(state);
+	Spawn* caster = lua_interface->GetSpawn(state, 2);
+	Spawn* target = lua_interface->GetSpawn(state, 3);
+
+	if (!target) {
+		lua_interface->LogError("%s: LUA CastCustomSpell command error: target is not a valid spawn", lua_interface->GetScriptName(state));
+		return 0;
+	}
+
+	if (!target->IsEntity()) {
+		lua_interface->LogError("%s: LUA CastCustomSpell command error: target (%s) is not an entity", lua_interface->GetScriptName(state), target->GetName());
+		return 0;
+	}
+
+	if (!spell) {
+		lua_interface->LogError("%s: LUA CastCustomSpell command error: spell is not valid", lua_interface->GetScriptName(state));
+		return 0;
+	}
+
+	if (caster && !caster->IsEntity()) {
+		lua_interface->LogError("%s: LUA CastSpell command error: caster (%s) is not an entity", lua_interface->GetScriptName(state), caster->GetName());
+		return 0;
+	}
+
+	target->GetZone()->ProcessSpell(NULL, (Entity*)caster, (Entity*)target, true, false, spell, 0);
+	return 0;
 }

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

@@ -476,4 +476,12 @@ int EQ2Emu_lua_SendUpdateDefaultCommand(lua_State* state);
 int EQ2Emu_lua_SendTransporters(lua_State* state);
 int EQ2Emu_lua_SetTemporaryTransportID(lua_State* state);
 int EQ2Emu_lua_GetTemporaryTransportID(lua_State* state);
+
+int EQ2Emu_lua_GetAlignment(lua_State* state);
+int EQ2Emu_lua_SetAlignment(lua_State* state);
+
+int EQ2Emu_lua_GetSpell(lua_State* state);
+int EQ2Emu_lua_GetSpellData(lua_State* state);
+int EQ2Emu_lua_SetSpellData(lua_State* state);
+int EQ2Emu_lua_CastCustomSpell(lua_State* state);
 #endif

+ 65 - 6
EQ2/source/WorldServer/LuaInterface.cpp

@@ -606,7 +606,7 @@ lua_State* LuaInterface::LoadLuaFile(const char* name) {
 	return 0;
 }
 
-void LuaInterface::RemoveSpell(LuaSpell* spell, bool call_remove_function, bool can_delete) {
+void LuaInterface::RemoveSpell(LuaSpell* spell, bool call_remove_function, bool can_delete, string reason) {
 	if(shutting_down)
 		return;
 
@@ -628,10 +628,15 @@ void LuaInterface::RemoveSpell(LuaSpell* spell, bool call_remove_function, bool
 		else
 			lua_pushlightuserdata(spell->state, 0);
 
+		if (spell->caster && !spell->caster->Alive())
+			reason = "dead";
+
+		lua_pushstring(spell->state, (char*)reason.c_str());
+
 		MSpells.lock();
 		current_spells[spell->state] = spell;
 		MSpells.unlock();
-		lua_pcall(spell->state, 2, 0, 0);
+		lua_pcall(spell->state, 3, 0, 0);
 	}
 
 	spell->MSpellTargets.readlock(__FUNCTION__, __LINE__);
@@ -644,6 +649,10 @@ void LuaInterface::RemoveSpell(LuaSpell* spell, bool call_remove_function, bool
 	}
 	spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__);
 
+	// we need to make sure all memory is purged for a copied spell, its only used once
+	if (spell->spell->IsCopiedSpell())
+		can_delete = true;
+
 	if (can_delete) {
 		AddPendingSpellDelete(spell);
 	}
@@ -1089,6 +1098,14 @@ void LuaInterface::RegisterFunctions(lua_State* state) {
 	lua_register(state, "SendTransporters", EQ2Emu_lua_SendTransporters);
 	lua_register(state, "SetTemporaryTransportID", EQ2Emu_lua_SetTemporaryTransportID);
 	lua_register(state, "GetTemporaryTransportID", EQ2Emu_lua_GetTemporaryTransportID);
+
+	lua_register(state, "SetAlignment", EQ2Emu_lua_SetAlignment);
+	lua_register(state, "GetAlignment", EQ2Emu_lua_GetAlignment);
+
+	lua_register(state, "GetSpell", EQ2Emu_lua_GetSpell);
+	lua_register(state, "GetSpellData", EQ2Emu_lua_GetSpellData);
+	lua_register(state, "SetSpellData", EQ2Emu_lua_SetSpellData);
+	lua_register(state, "CastCustomSpell", EQ2Emu_lua_CastCustomSpell);
 }
 
 void LuaInterface::LogError(const char* error, ...)  {
@@ -1131,6 +1148,10 @@ void LuaInterface::DeletePendingSpells(bool all) {
 		for (del_itr = tmp_deletes.begin(); del_itr != tmp_deletes.end(); del_itr++) {
 			spell = *del_itr;
 			spells_pending_delete.erase(spell);
+
+			if (spell->spell->IsCopiedSpell())
+				safe_delete(spell->spell);
+
 			safe_delete(spell);
 		}
 	}
@@ -1286,12 +1307,12 @@ Skill* LuaInterface::GetSkill(lua_State* state, int8 arg_num) {
 	Skill* ret = 0;
 	if (lua_islightuserdata(state, arg_num)) {
 		LUAUserData* data = (LUAUserData*)lua_touserdata(state, arg_num);
-		if(!data || !data->IsCorrectlyInitialized()){
+		if (!data || !data->IsCorrectlyInitialized()) {
 			LogError("%s: GetSkill error while processing %s", GetScriptName(state), lua_tostring(state, 0));
 		}
-		else if(!data->IsSkill()){
+		else if (!data->IsSkill()) {
 			lua_Debug ar;
- 			lua_getstack (state, 1, &ar);
+			lua_getstack(state, 1, &ar);
 			lua_getinfo(state, "Sln", &ar);
 			LogError("%s: Invalid data type used for GetSkill in %s (line %d)", GetScriptName(state), ar.source, ar.currentline);
 		}
@@ -1301,6 +1322,25 @@ Skill* LuaInterface::GetSkill(lua_State* state, int8 arg_num) {
 	return ret;
 }
 
+LuaSpell* LuaInterface::GetSpell(lua_State* state, int8 arg_num) {
+	LuaSpell* ret = 0;
+	if (lua_islightuserdata(state, arg_num)) {
+		LUAUserData* data = (LUAUserData*)lua_touserdata(state, arg_num);
+		if (!data || !data->IsCorrectlyInitialized()) {
+			LogError("%s: GetSpell error while processing %s", GetScriptName(state), lua_tostring(state, 0));
+		}
+		else if (!data->IsSpell()) {
+			lua_Debug ar;
+			lua_getstack(state, 1, &ar);
+			lua_getinfo(state, "Sln", &ar);
+			LogError("%s: Invalid data type used for GetSpell in %s (line %d)", GetScriptName(state), ar.source, ar.currentline);
+		}
+		else
+			ret = data->spell;
+	}
+	return ret;
+}
+
 ZoneServer* LuaInterface::GetZone(lua_State* state, int8 arg_num) {
 	ZoneServer* ret = 0;
 	if(lua_islightuserdata(state, arg_num)){
@@ -1450,6 +1490,13 @@ void LuaInterface::SetZoneValue(lua_State* state, ZoneServer* zone) {
 	lua_pushlightuserdata(state, zone_wrapper);
 }
 
+void LuaInterface::SetSpellValue(lua_State* state, LuaSpell* spell) {
+	LUASpellWrapper* spell_wrapper = new LUASpellWrapper();
+	spell_wrapper->spell = spell;
+	AddUserDataPtr(spell_wrapper);
+	lua_pushlightuserdata(state, spell_wrapper);
+}
+
 LuaSpell* LuaInterface::GetSpell(const char* name)  {
 	string lua_script = string(name);
 	if (lua_script.find(".lua") == string::npos)
@@ -1460,7 +1507,7 @@ LuaSpell* LuaInterface::GetSpell(const char* name)  {
 		LuaSpell* spell = spells[lua_script];
 		LuaSpell* new_spell = new LuaSpell;
 		new_spell->state = spell->state;
-		new_spell->file_name = spell->file_name;
+		new_spell->file_name = string(spell->file_name);
 		new_spell->timer = spell->timer;
 		new_spell->timer.Disable();
 		new_spell->resisted = false;
@@ -1854,6 +1901,10 @@ bool LUAUserData::IsSkill() {
 	return false;
 }
 
+bool LUAUserData::IsSpell() {
+	return false;
+}
+
 LUAConversationOptionWrapper::LUAConversationOptionWrapper(){
 	correctly_initialized = true;
 }
@@ -1916,4 +1967,12 @@ LUASkillWrapper::LUASkillWrapper() {
 
 bool LUASkillWrapper::IsSkill() {
 	return true;
+}
+
+LUASpellWrapper::LUASpellWrapper() {
+	correctly_initialized = true;
+}
+
+bool LUASpellWrapper::IsSpell() {
+	return true;
 }

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

@@ -107,6 +107,7 @@ public:
 	virtual bool IsZone();
 	virtual bool IsItem();
 	virtual bool IsSkill();
+	virtual bool IsSpell();
 	bool correctly_initialized;
 	Item* item;
 	ZoneServer* zone;
@@ -116,6 +117,7 @@ public:
 	vector<Spawn*>* spawn_list;
 	Quest* quest;
 	Skill* skill;
+	LuaSpell* spell;
 };
 
 class LUAConversationOptionWrapper : public LUAUserData{
@@ -167,6 +169,12 @@ public:
 	bool IsSkill();
 };
 
+class LUASpellWrapper : public LUAUserData {
+public:
+	LUASpellWrapper();
+	bool IsSpell();
+};
+
 class LuaInterface {
 public:
 	LuaInterface();
@@ -179,12 +187,13 @@ public:
 	bool			LoadSpawnScript(const char* name);
 	bool			LoadZoneScript(string name);
 	bool			LoadZoneScript(const char* name);
-	void			RemoveSpell(LuaSpell* spell, bool call_remove_function = true, bool can_delete = true);
+	void			RemoveSpell(LuaSpell* spell, bool call_remove_function = true, bool can_delete = true, string reason = "");
 	Spawn*			GetSpawn(lua_State* state, int8 arg_num = 1);
 	Item*			GetItem(lua_State* state, int8 arg_num = 1);
 	Quest*			GetQuest(lua_State* state, int8 arg_num = 1);
 	ZoneServer*		GetZone(lua_State* state, int8 arg_num = 1);
 	Skill*			GetSkill(lua_State* state, int8 arg_num = 1);
+	LuaSpell*		GetSpell(lua_State* state, int8 arg_num = 1);
 	vector<ConversationOption>*	GetConversation(lua_State* state, int8 arg_num = 1);
 	vector<Spawn*>* GetSpawnList(lua_State* state, int8 arg_num = 1);
 	vector<OptionWindowOption>* GetOptionWindow(lua_State* state, int8 arg_num = 1);
@@ -209,6 +218,7 @@ public:
 	void			SetQuestValue(lua_State* state, Quest* quest);
 	void			SetZoneValue(lua_State* state, ZoneServer* zone);
 	void			SetSpawnListValue(lua_State* state, vector<Spawn*>* spawnList);
+	void			SetSpellValue(lua_State* state, LuaSpell* spell);
 	void			SetConversationValue(lua_State* state, vector<ConversationOption>* conversation);
 	void			SetOptionWindowValue(lua_State* state, vector<OptionWindowOption>* optionWindow);
 

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

@@ -2865,6 +2865,7 @@ void Spawn::ResetMovement(){
 	for(itr = movement_loop.begin(); itr != movement_loop.end(); itr++){
 		safe_delete(*itr);
 	}
+	movement_loop.clear();
 	MMovementLoop.unlock();
 	resume_movement = true;
 	movement_index = 0;
@@ -2941,7 +2942,11 @@ void Spawn::AddRunningLocation(float x, float y, float z, float speed, float dis
 	if(speed == 0)
 		return;
 
-	((Entity*)this)->SetSpeed(speed);
+	if ( IsEntity() )
+		((Entity*)this)->SetSpeed(speed);
+	else
+		this->SetSpeed(speed);
+
 	MovementLocation* current_location = 0;
 
 	float distance = GetDistance(x, y, z, distance_away != 0);

+ 64 - 44
EQ2/source/WorldServer/SpellProcess.cpp

@@ -132,7 +132,7 @@ void SpellProcess::Process(){
 				if (spell->spell->GetSpellData()->call_frequency > 0 && !ProcessSpell(spell, false))
 					active_spells.Remove(spell, true, 2000);
 				else if ((spell->timer.GetDuration() * spell->num_calls) >= spell->spell->GetSpellData()->duration1 * 100)
-					DeleteCasterSpell(spell);
+					DeleteCasterSpell(spell, "expired");
 			}
 			else
 				CheckRemoveTargetFromSpell(spell);
@@ -151,7 +151,7 @@ void SpellProcess::Process(){
 
 		itr = tmpList.begin();
 		while (itr != tmpList.end()) {
-			DeleteCasterSpell(*itr);
+			DeleteCasterSpell(*itr, "canceled");
 			itr++;
 		}
 	}
@@ -342,7 +342,7 @@ void SpellProcess::CheckInterrupt(InterruptStruct* interrupt){
 		entity->GetZone()->SendSpellFailedPacket(client, interrupt->error_code);
 }
 
-bool SpellProcess::DeleteCasterSpell(Spawn* caster, Spell* spell){
+bool SpellProcess::DeleteCasterSpell(Spawn* caster, Spell* spell, string reason){
 
 	bool ret = false;
 	// need to use size(true) to get pending updates to the list as well
@@ -352,7 +352,7 @@ bool SpellProcess::DeleteCasterSpell(Spawn* caster, Spell* spell){
 		while (itr.Next()){
 			lua_spell = itr->value;
 			if (lua_spell->spell == spell && lua_spell->caster == caster) {
-				ret = DeleteCasterSpell(lua_spell);
+				ret = DeleteCasterSpell(lua_spell, reason);
 				break;
 			}
 		}
@@ -360,7 +360,7 @@ bool SpellProcess::DeleteCasterSpell(Spawn* caster, Spell* spell){
 	return ret;
 }
 
-bool SpellProcess::DeleteCasterSpell(LuaSpell* spell){
+bool SpellProcess::DeleteCasterSpell(LuaSpell* spell, string reason){
 	bool ret = false;
 	Spawn* target = 0;
 	if(spell) {
@@ -403,7 +403,7 @@ bool SpellProcess::DeleteCasterSpell(LuaSpell* spell){
 			ret = true;
 		}
 		if(lua_interface)
-			lua_interface->RemoveSpell(spell, true, SpellScriptTimersHasSpell(spell));	
+			lua_interface->RemoveSpell(spell, true, SpellScriptTimersHasSpell(spell), reason);
 	}
 	return ret;
 }
@@ -783,20 +783,25 @@ Spawn* SpellProcess::GetSpellTarget(Entity* caster){
 	return target;
 }
 
-void SpellProcess::ProcessSpell(ZoneServer* zone, Spell* spell, Entity* caster, Spawn* target, bool lock, bool harvest_spell)
+void SpellProcess::ProcessSpell(ZoneServer* zone, Spell* spell, Entity* caster, Spawn* target, bool lock, bool harvest_spell, LuaSpell* customSpell, int16 custom_cast_time)
 {
-	if(spell && caster)
+	if((customSpell != 0 || spell != 0) && caster)
 	{
 
 		Client* client = 0;
-		int8 target_type = spell->GetSpellData()->target_type;
 		//int16 version = 0;
 
 		LuaSpell* lua_spell = 0;
 
-		if(lua_interface)
+		if (customSpell)
+		{
+			lua_spell = customSpell;
+			spell = lua_spell->spell;
+		}
+		else if(lua_interface)
 			lua_spell = lua_interface->GetSpell(spell->GetSpellData()->lua_script.c_str());
 
+		// this will only hit if customSpell is null and we go through the lua_interface
 		if(!lua_spell)
 		{
 			string lua_script = spell->GetSpellData()->lua_script;
@@ -814,6 +819,8 @@ void SpellProcess::ProcessSpell(ZoneServer* zone, Spell* spell, Entity* caster,
 		if (!target)
 			target = caster;
 
+		int8 target_type = spell->GetSpellData()->target_type;
+
 		lua_spell->caster = caster;
 		lua_spell->spell = spell;
 		int32 target_id = target->GetID();
@@ -848,7 +855,7 @@ void SpellProcess::ProcessSpell(ZoneServer* zone, Spell* spell, Entity* caster,
 		//If this spell is the toggle cast type and is being toggled off, do this now
 		if (spell->GetSpellData()->cast_type == SPELL_CAST_TYPE_TOGGLE)
 		{
-			bool ret_val = DeleteCasterSpell(caster, spell);
+			bool ret_val = DeleteCasterSpell(caster, spell, "purged");
 
 			if (ret_val)
 			{
@@ -862,7 +869,7 @@ void SpellProcess::ProcessSpell(ZoneServer* zone, Spell* spell, Entity* caster,
 				}
 
 				lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell);
-				safe_delete(lua_spell);
+				DeleteSpell(lua_spell);
 				return;
 			}
 		}
@@ -889,7 +896,7 @@ void SpellProcess::ProcessSpell(ZoneServer* zone, Spell* spell, Entity* caster,
 					// make sure to release the lock before we return out
 					zone->GetTradeskillMgr()->ReleaseReadLock(__FUNCTION__, __LINE__);
 					lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell);
-					safe_delete(lua_spell);
+					DeleteSpell(lua_spell);
 					return;
 				}
 				// need to make sure the lock is released if the if passed
@@ -900,7 +907,7 @@ void SpellProcess::ProcessSpell(ZoneServer* zone, Spell* spell, Entity* caster,
 				LogWrite(SPELL__DEBUG, 1, "Spell", "%s cannot cast a tradeskill spell (%s) while not crafting.", caster->GetName(), spell->GetName());
 				zone->SendSpellFailedPacket(client, SPELL_ERROR_ONLY_WHEN_CRAFTING);
 				lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell);
-				safe_delete(lua_spell);
+				DeleteSpell(lua_spell);
 				return;
 			}
 		}
@@ -910,7 +917,7 @@ void SpellProcess::ProcessSpell(ZoneServer* zone, Spell* spell, Entity* caster,
 			LogWrite(SPELL__DEBUG, 1, "Spell", "%s cannot see target %s.", caster->GetName(), target->GetName());
 			zone->SendSpellFailedPacket(client, SPELL_ERROR_CANT_SEE_TARGET);
 			lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell);
-			safe_delete(lua_spell);
+			DeleteSpell(lua_spell);
 			return;
 		}
 
@@ -919,7 +926,7 @@ void SpellProcess::ProcessSpell(ZoneServer* zone, Spell* spell, Entity* caster,
 			LogWrite(SPELL__DEBUG, 1, "Spell", "%s cannot cast (mezzed or stunned).", caster->GetName());
 			zone->SendSpellFailedPacket(client, SPELL_ERROR_CANNOT_CAST_STUNNED);
 			lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell);
-			safe_delete(lua_spell);
+			DeleteSpell(lua_spell);
 			return;
 		}
 
@@ -928,7 +935,7 @@ void SpellProcess::ProcessSpell(ZoneServer* zone, Spell* spell, Entity* caster,
 			LogWrite(SPELL__DEBUG, 1, "Spell", "%s cannot cast (stifled).", caster->GetName());
 			zone->SendSpellFailedPacket(client, SPELL_ERROR_CANNOT_CAST_STIFFLED);
 			lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell);
-			safe_delete(lua_spell);
+			DeleteSpell(lua_spell);
 			return;
 		}
 
@@ -937,7 +944,7 @@ void SpellProcess::ProcessSpell(ZoneServer* zone, Spell* spell, Entity* caster,
 			LogWrite(SPELL__DEBUG, 1, "Spell", "%s cannot cast (feared).", caster->GetName());
 			zone->SendSpellFailedPacket(client, SPELL_ERROR_CANNOT_CAST_FEARED);
 			lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell);
-			safe_delete(lua_spell);
+			DeleteSpell(lua_spell);
 			return;
 		}
 
@@ -946,7 +953,7 @@ void SpellProcess::ProcessSpell(ZoneServer* zone, Spell* spell, Entity* caster,
 			LogWrite(SPELL__DEBUG, 1, "Spell", "Queuing spell for %s.", caster->GetName());
 			CheckSpellQueue(spell, caster);
 			lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell);
-			safe_delete(lua_spell);
+			DeleteSpell(lua_spell);
 			return;
 		}
 
@@ -959,7 +966,7 @@ void SpellProcess::ProcessSpell(ZoneServer* zone, Spell* spell, Entity* caster,
 				LogWrite(SPELL__DEBUG, 1, "Spell", "%s: No target.", spell->GetName());
 				zone->SendSpellFailedPacket(client, SPELL_ERROR_NO_ELIGIBLE_TARGET);
 				lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell);
-				safe_delete(lua_spell);
+				DeleteSpell(lua_spell);
 				return;
 			}
 
@@ -996,7 +1003,7 @@ void SpellProcess::ProcessSpell(ZoneServer* zone, Spell* spell, Entity* caster,
 				LogWrite(SPELL__DEBUG, 1, "Spell", "%s: Too far.", spell->GetName());
 				zone->SendSpellFailedPacket(client, SPELL_ERROR_TOO_FAR_AWAY);
 				lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell);
-				safe_delete(lua_spell);
+				DeleteSpell(lua_spell);
 				return;
 			}
 
@@ -1005,7 +1012,7 @@ void SpellProcess::ProcessSpell(ZoneServer* zone, Spell* spell, Entity* caster,
 				LogWrite(SPELL__DEBUG, 1, "Spell", "%s: Too close.", spell->GetName());
 				zone->SendSpellFailedPacket(client, SPELL_ERROR_TOO_CLOSE);
 				lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell);
-				safe_delete(lua_spell);
+				DeleteSpell(lua_spell);
 				return;
 			}
 		}
@@ -1021,7 +1028,7 @@ void SpellProcess::ProcessSpell(ZoneServer* zone, Spell* spell, Entity* caster,
 					LogWrite(SPELL__DEBUG, 1, "Spell", "%s: No target or not groundspawn.", spell->GetName());
 					zone->SendSpellFailedPacket(client, SPELL_ERROR_NO_ELIGIBLE_TARGET);
 					lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell);
-					safe_delete(lua_spell);
+					DeleteSpell(lua_spell);
 					return;
 				}
 			}
@@ -1046,7 +1053,7 @@ void SpellProcess::ProcessSpell(ZoneServer* zone, Spell* spell, Entity* caster,
 					LogWrite(SPELL__DEBUG, 1, "Spell", "%s: No target.", spell->GetName());
 					zone->SendSpellFailedPacket(client, SPELL_ERROR_NO_ELIGIBLE_TARGET);
 					lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell);
-					safe_delete(lua_spell);
+					DeleteSpell(lua_spell);
 					return;
 				}
 				/*if (target->appearance.attackable) {
@@ -1064,7 +1071,7 @@ void SpellProcess::ProcessSpell(ZoneServer* zone, Spell* spell, Entity* caster,
 					{
 						zone->SendSpellFailedPacket(client, SPELL_ERROR_NOT_A_FRIEND);
 						lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell);
-						safe_delete(lua_spell);
+						DeleteSpell(lua_spell);
 						return;
 					}
 				}
@@ -1075,7 +1082,7 @@ void SpellProcess::ProcessSpell(ZoneServer* zone, Spell* spell, Entity* caster,
 				{
 					zone->SendSpellFailedPacket(client, SPELL_ERROR_NOT_A_FRIEND);
 					lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell);
-					safe_delete(lua_spell);
+					DeleteSpell(lua_spell);
 					return;
 				}
 			}
@@ -1092,7 +1099,7 @@ void SpellProcess::ProcessSpell(ZoneServer* zone, Spell* spell, Entity* caster,
 					LogWrite(SPELL__DEBUG, 1, "Spell", "%s: No target.", spell->GetName());
 					zone->SendSpellFailedPacket(client, SPELL_ERROR_NO_ELIGIBLE_TARGET);
 					lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell);
-					safe_delete(lua_spell);
+					DeleteSpell(lua_spell);
 					return;
 				}
 
@@ -1101,7 +1108,7 @@ void SpellProcess::ProcessSpell(ZoneServer* zone, Spell* spell, Entity* caster,
 					LogWrite(SPELL__DEBUG, 1, "Spell", "%s: Not an Enemy (Target: %s).", spell->GetName(), target->GetName());
 					zone->SendSpellFailedPacket(client, SPELL_ERROR_NOT_AN_ENEMY);
 					lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell);
-					safe_delete(lua_spell);
+					DeleteSpell(lua_spell);
 					return;
 				}
 
@@ -1110,7 +1117,7 @@ void SpellProcess::ProcessSpell(ZoneServer* zone, Spell* spell, Entity* caster,
 					LogWrite(SPELL__DEBUG, 1, "Spell", "%s: Target is not alive (Target: %s).", spell->GetName(), target->GetName());
 					zone->SendSpellFailedPacket(client, SPELL_ERROR_NOT_ALIVE);
 					lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell);
-					safe_delete(lua_spell);
+					DeleteSpell(lua_spell);
 					return;
 				}
 
@@ -1119,7 +1126,7 @@ void SpellProcess::ProcessSpell(ZoneServer* zone, Spell* spell, Entity* caster,
 					LogWrite(SPELL__DEBUG, 1, "Spell", "%s: Target is invulnerable (Target: %s).", spell->GetName(), target->GetName());
 					zone->SendSpellFailedPacket(client, SPELL_ERROR_TARGET_INVULNERABLE);
 					lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell);
-					safe_delete(lua_spell);
+					DeleteSpell(lua_spell);
 					return;
 				}
 
@@ -1131,7 +1138,7 @@ void SpellProcess::ProcessSpell(ZoneServer* zone, Spell* spell, Entity* caster,
 						LogWrite(SPELL__DEBUG, 1, "Spell", "%s: Target (%s) is player and not attackable.", spell->GetName(), target->GetName());
 						zone->SendSpellFailedPacket(client, SPELL_ERROR_NOT_AN_ENEMY);
 						lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell);
-						safe_delete(lua_spell);
+						DeleteSpell(lua_spell);
 						return;
 					}
 				}
@@ -1140,7 +1147,7 @@ void SpellProcess::ProcessSpell(ZoneServer* zone, Spell* spell, Entity* caster,
 					LogWrite(SPELL__DEBUG, 1, "Spell", "%s: Target (%s) is casters pet and not attackable by caster.", spell->GetName(), target->GetName());
 					zone->SendSpellFailedPacket(client, SPELL_ERROR_NOT_AN_ENEMY);
 					lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell);
-					safe_delete(lua_spell);
+					DeleteSpell(lua_spell);
 					return;
 				}
 			}
@@ -1150,7 +1157,7 @@ void SpellProcess::ProcessSpell(ZoneServer* zone, Spell* spell, Entity* caster,
 		{
 			LogWrite(SPELL__ERROR, 0, "Spell", "SpellProcess::ProcessSpell Unable to find any spell targets for spell '%s'.", spell->GetName());
 			lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell);
-			safe_delete(lua_spell);
+			DeleteSpell(lua_spell);
 			return;
 		}
 
@@ -1160,13 +1167,13 @@ void SpellProcess::ProcessSpell(ZoneServer* zone, Spell* spell, Entity* caster,
 			{
 				zone->SendSpellFailedPacket(client, SPELL_ERROR_NOT_DEAD);
 				lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell);
-				safe_delete(lua_spell);
+				DeleteSpell(lua_spell);
 				return;
 			}
 			if(target->IsPlayer() && zone->GetClientBySpawn(target)->GetCurrentRez()->active){
 				zone->SendSpellFailedPacket(client, SPELL_ERROR_ALREADY_CAST);
 				lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell);
-				safe_delete(lua_spell);
+				DeleteSpell(lua_spell);
 				return;
 			}
 		}
@@ -1175,7 +1182,7 @@ void SpellProcess::ProcessSpell(ZoneServer* zone, Spell* spell, Entity* caster,
 		{
 			zone->SendSpellFailedPacket(client, SPELL_ERROR_NOT_ENOUGH_POWER);
 			lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell);
-			safe_delete(lua_spell);
+			DeleteSpell(lua_spell);
 			return;
 		}
 
@@ -1183,7 +1190,7 @@ void SpellProcess::ProcessSpell(ZoneServer* zone, Spell* spell, Entity* caster,
 		{ 
 			zone->SendSpellFailedPacket(client, SPELL_ERROR_NOT_ENOUGH_HEALTH);
 			lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell);
-			safe_delete(lua_spell);
+			DeleteSpell(lua_spell);
 			return; 
 		}
 
@@ -1191,7 +1198,7 @@ void SpellProcess::ProcessSpell(ZoneServer* zone, Spell* spell, Entity* caster,
 		{
 			zone->SendSpellFailedPacket(client, SPELL_ERROR_NOT_ENOUGH_SAVAGERY);
 			lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell);
-			safe_delete(lua_spell);
+			DeleteSpell(lua_spell);
 			return;
 		}
 
@@ -1199,7 +1206,7 @@ void SpellProcess::ProcessSpell(ZoneServer* zone, Spell* spell, Entity* caster,
 		{
 			zone->SendSpellFailedPacket(client, SPELL_ERROR_NOT_ENOUGH_DISSONANCE);
 			lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell);
-			safe_delete(lua_spell);
+			DeleteSpell(lua_spell);
 			return;
 		}
 
@@ -1207,7 +1214,7 @@ void SpellProcess::ProcessSpell(ZoneServer* zone, Spell* spell, Entity* caster,
 		{
 			zone->SendSpellFailedPacket(client, SPELL_ERROR_NOT_ENOUGH_CONC);
 			lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell);
-			safe_delete(lua_spell);
+			DeleteSpell(lua_spell);
 			return;
 		}
 
@@ -1223,7 +1230,7 @@ void SpellProcess::ProcessSpell(ZoneServer* zone, Spell* spell, Entity* caster,
 				if (!result) {
 					zone->SendSpellFailedPacket(client, error);
 					lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell);
-					safe_delete(lua_spell);
+					DeleteSpell(lua_spell);
 					return;
 				}
 			}
@@ -1235,6 +1242,9 @@ void SpellProcess::ProcessSpell(ZoneServer* zone, Spell* spell, Entity* caster,
 		else
 			LogWrite(SPELL__DEBUG, 1, "Spell", "Unable to do precast check as there was no lua_interface");
 		
+		if (custom_cast_time > 0)
+			spell->GetSpellData()->cast_time = custom_cast_time;
+
 		//Apply casting speed mod
 		spell->ModifyCastTime(caster);
 
@@ -1265,7 +1275,7 @@ void SpellProcess::ProcessSpell(ZoneServer* zone, Spell* spell, Entity* caster,
 			if(!CastProcessedSpell(lua_spell))
 			{
 				lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell);
-				safe_delete(lua_spell);
+				DeleteSpell(lua_spell);
 				return;
 			}
 		}
@@ -1643,7 +1653,7 @@ void SpellProcess::RemoveSpellTimersFromSpawn(Spawn* spawn, bool remove_all, boo
 			if (spell->spell->GetSpellData()->persist_though_death)
 				continue;
 			if(spell->caster == spawn){
-				DeleteCasterSpell(spell);
+				DeleteCasterSpell(spell, "expired");
 				continue;
 			}
 
@@ -2324,7 +2334,7 @@ void SpellProcess::CheckRemoveTargetFromSpell(LuaSpell* spell, bool allow_delete
 		safe_delete(remove_targets);
 		MRemoveTargetList.releasewritelock(__FUNCTION__, __LINE__);
 		if (should_delete)
-			DeleteCasterSpell(spell);
+			DeleteCasterSpell(spell, "purged");
 	}
 }
 
@@ -2429,4 +2439,14 @@ void SpellProcess::AddSpellCancel(LuaSpell* spell){
 	MSpellCancelList.writelock(__FUNCTION__, __LINE__);
 	SpellCancelList.push_back(spell);
 	MSpellCancelList.releasewritelock(__FUNCTION__, __LINE__);
+}
+
+void SpellProcess::DeleteSpell(LuaSpell* spell)
+{
+	RemoveSpellFromQueue(spell->spell, spell->caster);
+
+	if (spell->spell->IsCopiedSpell())
+		safe_delete(spell->spell);
+
+	safe_delete(spell);
 }

+ 4 - 3
EQ2/source/WorldServer/SpellProcess.h

@@ -171,7 +171,7 @@ public:
 	/// <param name='target'>The target(Spawn) of the spell</param>
 	/// <param name='lock'>??? not currently used</param>
 	/// <param name='harvest_spell'>Is this a harvest spell?</param>
-	void ProcessSpell(ZoneServer* zone, Spell* spell, Entity* caster, Spawn* target = 0, bool lock = true, bool harvest_spell = false);
+	void ProcessSpell(ZoneServer* zone, Spell* spell, Entity* caster, Spawn* target = 0, bool lock = true, bool harvest_spell = false, LuaSpell* customSpell = 0, int16 custom_cast_time = 0);
 
 	/// <summary>Cast an EntityCommand (right click menu)</summary>
 	/// <param name='zone'>The current ZoneServer</param>
@@ -254,11 +254,11 @@ public:
 	/// <summary>Remove the given spell for the given caster from the SpellProcess</summary>
 	/// <param name='caster'>The spawn to remove the spell for</param>
 	/// <param name='spell'>The spell to remove</param>
-	bool DeleteCasterSpell(Spawn* caster, Spell* spell);
+	bool DeleteCasterSpell(Spawn* caster, Spell* spell, string reason = "");
 
 	/// <summary>Remove the given spell from the ZpellProcess</summary>
 	/// <param name='spell'>LuaSpell to remove</param>
-	bool DeleteCasterSpell(LuaSpell* spell);
+	bool DeleteCasterSpell(LuaSpell* spell, string reason="");
 
 	/// <summary>Interrupt the spell</summary>
 	/// <param name='interrupt'>InterruptStruct that contains all the info</param>
@@ -379,6 +379,7 @@ public:
 
 	void AddSpellCancel(LuaSpell* spell);
 
+	void DeleteSpell(LuaSpell* spell);
 private:
 	/// <summary>Sends the spell data to the lua script</summary>
 	/// <param name='spell'>LuaSpell to call the lua script for</param>

+ 858 - 1
EQ2/source/WorldServer/Spells.cpp

@@ -24,12 +24,14 @@
 #include "Traits/Traits.h"
 #include "AltAdvancement/AltAdvancement.h"
 #include <cmath>
+#include "LuaInterface.h"
 
 extern ConfigReader configReader;
 extern WorldDatabase database;
 extern MasterTraitList master_trait_list;
 extern MasterAAList master_aa_list;
 extern MasterSpellList master_spell_list;
+extern LuaInterface* lua_interface;
 
 Spell::Spell(){
 	spell = new SpellData;
@@ -38,6 +40,127 @@ Spell::Spell(){
 	damage_spell = false;
 	control_spell = false;
 	offense_spell = false;
+	copied_spell = false;
+	MSpellInfo.SetName("Spell::MSpellInfo");
+}
+
+Spell::Spell(Spell* host_spell)
+{
+	copied_spell = true;
+
+	spell = new SpellData;
+
+	if (host_spell->GetSpellData())
+	{
+		spell->affect_only_group_members = host_spell->GetSpellData()->affect_only_group_members;
+		spell->call_frequency = host_spell->GetSpellData()->call_frequency;
+		spell->can_effect_raid = host_spell->GetSpellData()->can_effect_raid;
+		spell->casting_flags = host_spell->GetSpellData()->casting_flags;
+		spell->cast_time = host_spell->GetSpellData()->cast_time;
+		spell->cast_type = host_spell->GetSpellData()->cast_type;
+		spell->cast_while_moving = host_spell->GetSpellData()->cast_while_moving;
+		spell->class_skill = host_spell->GetSpellData()->class_skill;
+		spell->control_effect_type = host_spell->GetSpellData()->control_effect_type;
+		spell->description = EQ2_16BitString(host_spell->GetSpellData()->description);
+		spell->det_type = host_spell->GetSpellData()->det_type;
+		spell->display_spell_tier = host_spell->GetSpellData()->display_spell_tier;
+
+		spell->dissonance_req = host_spell->GetSpellData()->dissonance_req;
+		spell->dissonance_req_percent = host_spell->GetSpellData()->dissonance_req_percent;
+		spell->dissonance_upkeep = host_spell->GetSpellData()->dissonance_upkeep;
+		spell->duration1 = host_spell->GetSpellData()->duration1;
+		spell->duration2 = host_spell->GetSpellData()->duration2;
+		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->friendly_spell = host_spell->GetSpellData()->friendly_spell;
+		spell->group_spell = host_spell->GetSpellData()->group_spell;
+
+		spell->hit_bonus = host_spell->GetSpellData()->hit_bonus;
+
+		spell->hp_req = host_spell->GetSpellData()->hp_req;
+		spell->hp_req_percent = host_spell->GetSpellData()->hp_req_percent;
+		spell->hp_upkeep = host_spell->GetSpellData()->hp_upkeep;
+
+		spell->icon = host_spell->GetSpellData()->icon;
+		spell->icon_backdrop = host_spell->GetSpellData()->icon_backdrop;
+
+		spell->icon_heroic_op = host_spell->GetSpellData()->icon_heroic_op;
+
+		spell->id = host_spell->GetSpellData()->id;
+
+		spell->incurable = host_spell->GetSpellData()->incurable;
+		spell->interruptable = host_spell->GetSpellData()->interruptable;
+		spell->is_aa = host_spell->GetSpellData()->is_aa;
+
+		spell->is_active = host_spell->GetSpellData()->is_active;
+		spell->linked_timer = host_spell->GetSpellData()->linked_timer;
+		spell->lua_script = string(host_spell->GetSpellData()->lua_script);
+
+		spell->mastery_skill = host_spell->GetSpellData()->mastery_skill;
+		spell->max_aoe_targets = host_spell->GetSpellData()->max_aoe_targets;
+
+		spell->min_range = host_spell->GetSpellData()->min_range;
+		spell->name = EQ2_8BitString(host_spell->GetSpellData()->name);
+		spell->not_maintained = host_spell->GetSpellData()->not_maintained;
+		spell->num_levels = host_spell->GetSpellData()->num_levels;
+		spell->persist_though_death = host_spell->GetSpellData()->persist_though_death;
+		spell->power_by_level = host_spell->GetSpellData()->power_by_level;
+		spell->power_req = host_spell->GetSpellData()->power_req;
+		spell->power_req_percent = host_spell->GetSpellData()->power_req_percent;
+		spell->power_upkeep = host_spell->GetSpellData()->power_upkeep;
+		spell->radius = host_spell->GetSpellData()->radius;
+		spell->range = host_spell->GetSpellData()->range;
+		spell->recast = host_spell->GetSpellData()->recast;
+		spell->recovery = host_spell->GetSpellData()->recovery;
+		spell->req_concentration = host_spell->GetSpellData()->req_concentration;
+		spell->resistibility = host_spell->GetSpellData()->resistibility;
+		spell->savagery_req = host_spell->GetSpellData()->savagery_req;
+		spell->savagery_req_percent = host_spell->GetSpellData()->savagery_req_percent;
+		spell->savagery_upkeep = host_spell->GetSpellData()->savagery_upkeep;
+		spell->savage_bar = host_spell->GetSpellData()->savage_bar;
+		spell->savage_bar_slot = host_spell->GetSpellData()->savage_bar_slot;
+		spell->soe_spell_crc = host_spell->GetSpellData()->soe_spell_crc;
+		spell->spell_book_type = host_spell->GetSpellData()->spell_book_type;
+		spell->spell_name_crc = host_spell->GetSpellData()->spell_name_crc;
+		spell->spell_type = host_spell->GetSpellData()->spell_type;
+		spell->spell_visual = host_spell->GetSpellData()->spell_visual;
+		spell->success_message = string(host_spell->GetSpellData()->success_message);
+		spell->target_type = host_spell->GetSpellData()->target_type;
+		spell->tier = host_spell->GetSpellData()->tier;
+		spell->ts_loc_index = host_spell->GetSpellData()->ts_loc_index;
+		spell->type = host_spell->GetSpellData()->type;
+	}
+
+	heal_spell = host_spell->IsHealSpell();
+	buff_spell = host_spell->IsBuffSpell();
+	damage_spell = host_spell->IsDamageSpell();;
+	control_spell = host_spell->IsControlSpell();
+	offense_spell = host_spell->IsOffenseSpell();
+
+	host_spell->LockSpellInfo();
+	std::vector<LevelArray*>::iterator itr;
+	for (itr = host_spell->levels.begin(); itr != host_spell->levels.end(); itr++)
+	{
+		LevelArray* lvlArray = *itr;
+		AddSpellLevel(lvlArray->adventure_class, lvlArray->tradeskill_class, lvlArray->spell_level);
+	}
+
+	std::vector<SpellDisplayEffect*>::iterator sdeitr;
+	for (sdeitr = host_spell->effects.begin(); sdeitr != host_spell->effects.end(); sdeitr++)
+	{
+		SpellDisplayEffect* sde = *sdeitr;
+		AddSpellEffect(sde->percentage, sde->subbullet, sde->description);
+	}
+
+	vector<LUAData*>::iterator luaitr;
+	for (luaitr = host_spell->lua_data.begin(); luaitr != host_spell->lua_data.end(); luaitr++) {
+		LUAData* data = *luaitr;
+		AddSpellLuaData(data->type, data->int_value, data->int_value2, data->float_value, data->float_value2, data->bool_value, string(data->string_value), string(data->string_value2), string(data->string_helper));
+	}
+	host_spell->UnlockSpellInfo();
+
 	MSpellInfo.SetName("Spell::MSpellInfo");
 }
 
@@ -48,6 +171,7 @@ Spell::Spell(SpellData* in_spell){
 	damage_spell = false;
 	control_spell = false;
 	offense_spell = false;
+	copied_spell = false;
 	MSpellInfo.SetName("Spell::MSpellInfo");
 }
 
@@ -1158,6 +1282,735 @@ SpellData* Spell::GetSpellData(){
 	return spell;
 }
 
+bool Spell::GetSpellData(lua_State* state, std::string field)
+{
+	if (!lua_interface)
+		return false;
+
+	bool valSet = false;
+
+	if (field == "spell_book_type")
+	{
+		lua_interface->SetInt32Value(state, GetSpellData()->spell_book_type);
+		valSet = true;
+	}
+	else if (field == "icon")
+	{
+		lua_interface->SetSInt32Value(state, GetSpellData()->icon);
+		valSet = true;
+	}
+	else if (field == "icon_heroic_op")
+	{
+		lua_interface->SetInt32Value(state, GetSpellData()->icon_heroic_op);
+		valSet = true;
+	}
+	else if (field == "icon_backdrop")
+	{
+		lua_interface->SetInt32Value(state, GetSpellData()->icon_backdrop);
+		valSet = true;
+	}
+	else if (field == "type")
+	{
+		lua_interface->SetInt32Value(state, GetSpellData()->type);
+		valSet = true;
+	}
+	else if (field == "class_skill")
+	{
+		lua_interface->SetInt32Value(state, GetSpellData()->class_skill);
+		valSet = true;
+	}
+	else if (field == "mastery_skill")
+	{
+		lua_interface->SetInt32Value(state, GetSpellData()->mastery_skill);
+		valSet = true;
+	}
+	else if (field == "ts_loc_index")
+	{
+		lua_interface->SetSInt32Value(state, GetSpellData()->ts_loc_index);
+		valSet = true;
+	}
+	else if (field == "num_levels")
+	{
+		lua_interface->SetSInt32Value(state, GetSpellData()->num_levels);
+		valSet = true;
+	}
+	else if (field == "tier")
+	{
+		lua_interface->SetSInt32Value(state, GetSpellData()->tier);
+		valSet = true;
+	}
+	else if (field == "hp_req")
+	{
+		lua_interface->SetSInt32Value(state, GetSpellData()->hp_req);
+		valSet = true;
+	}
+	else if (field == "hp_upkeep")
+	{
+		lua_interface->SetSInt32Value(state, GetSpellData()->hp_upkeep);
+		valSet = true;
+	}
+	else if (field == "power_req")
+	{
+		lua_interface->SetFloatValue(state, GetSpellData()->power_req);
+		valSet = true;
+	}
+	else if (field == "power_by_level")
+	{
+		lua_interface->SetBooleanValue(state, GetSpellData()->power_by_level);
+		valSet = true;
+	}
+	else if (field == "power_upkeep")
+	{
+		lua_interface->SetSInt32Value(state, GetSpellData()->power_upkeep);
+		valSet = true;
+	}
+	else if (field == "savagery_req")
+	{
+		lua_interface->SetSInt32Value(state, GetSpellData()->savagery_req);
+		valSet = true;
+	}
+	else if (field == "savagery_upkeep")
+	{
+		lua_interface->SetSInt32Value(state, GetSpellData()->savagery_upkeep);
+		valSet = true;
+	}
+	else if (field == "dissonance_req")
+	{
+		lua_interface->SetSInt32Value(state, GetSpellData()->dissonance_req);
+		valSet = true;
+	}
+	else if (field == "dissonance_upkeep")
+	{
+		lua_interface->SetSInt32Value(state, GetSpellData()->dissonance_upkeep);
+		valSet = true;
+	}
+	else if (field == "target_type")
+	{
+		lua_interface->SetSInt32Value(state, GetSpellData()->target_type);
+		valSet = true;
+	}
+	else if (field == "cast_time")
+	{
+		lua_interface->SetSInt32Value(state, GetSpellData()->cast_time);
+		valSet = true;
+	}
+	else if (field == "recovery")
+	{
+		lua_interface->SetFloatValue(state, GetSpellData()->recovery);
+		valSet = true;
+	}
+	else if (field == "recast")
+	{
+		lua_interface->SetFloatValue(state, GetSpellData()->recast);
+		valSet = true;
+	}
+	else if (field == "linked_timer")
+	{
+		lua_interface->SetSInt32Value(state, GetSpellData()->linked_timer);
+		valSet = true;
+	}
+	else if (field == "radius")
+	{
+		lua_interface->SetFloatValue(state, GetSpellData()->radius);
+		valSet = true;
+	}
+	else if (field == "max_aoe_targets")
+	{
+		lua_interface->SetSInt32Value(state, GetSpellData()->max_aoe_targets);
+		valSet = true;
+	}
+	else if (field == "friendly_spell")
+	{
+		lua_interface->SetSInt32Value(state, GetSpellData()->friendly_spell);
+		valSet = true;
+	}
+	else if (field == "req_concentration")
+	{
+		lua_interface->SetSInt32Value(state, GetSpellData()->req_concentration);
+		valSet = true;
+	}
+	else if (field == "range")
+	{
+		lua_interface->SetFloatValue(state, GetSpellData()->range);
+		valSet = true;
+	}
+	else if (field == "duration1")
+	{
+		lua_interface->SetSInt32Value(state, GetSpellData()->duration1);
+		valSet = true;
+	}
+	else if (field == "duration2")
+	{
+		lua_interface->SetSInt32Value(state, GetSpellData()->duration2);
+		valSet = true;
+	}
+	else if (field == "resistibility")
+	{
+		lua_interface->SetFloatValue(state, GetSpellData()->resistibility);
+		valSet = true;
+	}
+	else if (field == "duration_until_cancel")
+	{
+		lua_interface->SetBooleanValue(state, GetSpellData()->duration_until_cancel);
+		valSet = true;
+	}
+	else if (field == "power_req_percent")
+	{
+		lua_interface->SetSInt32Value(state, GetSpellData()->power_req_percent);
+		valSet = true;
+	}
+	else if (field == "hp_req_percent")
+	{
+		lua_interface->SetSInt32Value(state, GetSpellData()->hp_req_percent);
+		valSet = true;
+	}
+	else if (field == "savagery_req_percent")
+	{
+		lua_interface->SetSInt32Value(state, GetSpellData()->savagery_req_percent);
+		valSet = true;
+	}
+	else if (field == "dissonance_req_percent")
+	{
+		lua_interface->SetSInt32Value(state, GetSpellData()->dissonance_req_percent);
+		valSet = true;
+	}
+	else if (field == "name")
+	{
+		lua_interface->SetStringValue(state, GetSpellData()->name.data.c_str());
+		valSet = true;
+	}
+	else if (field == "description")
+	{
+		lua_interface->SetStringValue(state, GetSpellData()->description.data.c_str());
+		valSet = true;
+	}
+	else if (field == "success_message")
+	{
+		lua_interface->SetStringValue(state, GetSpellData()->success_message.c_str());
+		valSet = true;
+	}
+	else if (field == "fade_message")
+	{
+		lua_interface->SetStringValue(state, GetSpellData()->fade_message.c_str());
+		valSet = true;
+	}
+	else if (field == "cast_type")
+	{
+		lua_interface->SetSInt32Value(state, GetSpellData()->cast_type);
+		valSet = true;
+	}
+	else if (field == "lua_script")
+	{
+		lua_interface->SetStringValue(state, GetSpellData()->lua_script.c_str());
+		valSet = true;
+	}
+	else if (field == "interruptable")
+	{
+		lua_interface->SetBooleanValue(state, GetSpellData()->interruptable);
+		valSet = true;
+	}
+	else if (field == "spell_visual")
+	{
+		lua_interface->SetSInt32Value(state, GetSpellData()->spell_visual);
+		valSet = true;
+	}
+	else if (field == "effect_message")
+	{
+		lua_interface->SetStringValue(state, GetSpellData()->effect_message.c_str());
+		valSet = true;
+	}
+	else if (field == "min_range")
+	{
+		lua_interface->SetFloatValue(state, GetSpellData()->min_range);
+		valSet = true;
+	}
+	else if (field == "can_effect_raid")
+	{
+		lua_interface->SetSInt32Value(state, GetSpellData()->can_effect_raid);
+		valSet = true;
+	}
+	else if (field == "affect_only_group_members")
+	{
+		lua_interface->SetSInt32Value(state, GetSpellData()->affect_only_group_members);
+		valSet = true;
+	}
+	else if (field == "group_spell")
+	{
+		lua_interface->SetSInt32Value(state, GetSpellData()->group_spell);
+		valSet = true;
+	}
+	else if (field == "hit_bonus")
+	{
+		lua_interface->SetFloatValue(state, GetSpellData()->hit_bonus);
+		valSet = true;
+	}
+	else if (field == "display_spell_tier")
+	{
+		lua_interface->SetSInt32Value(state, GetSpellData()->display_spell_tier);
+		valSet = true;
+	}
+	else if (field == "is_active")
+	{
+		lua_interface->SetSInt32Value(state, GetSpellData()->is_active);
+		valSet = true;
+	}
+	else if (field == "det_type")
+	{
+		lua_interface->SetSInt32Value(state, GetSpellData()->det_type);
+		valSet = true;
+	}
+	else if (field == "incurable")
+	{
+		lua_interface->SetBooleanValue(state, GetSpellData()->incurable);
+		valSet = true;
+	}
+	else if (field == "control_effect_type")
+	{
+		lua_interface->SetSInt32Value(state, GetSpellData()->control_effect_type);
+		valSet = true;
+	}
+	else if (field == "casting_flags")
+	{
+		lua_interface->SetSInt32Value(state, GetSpellData()->casting_flags);
+		valSet = true;
+	}
+	else if (field == "cast_while_moving")
+	{
+		lua_interface->SetBooleanValue(state, GetSpellData()->cast_while_moving);
+		valSet = true;
+	}
+	else if (field == "persist_though_death")
+	{
+		lua_interface->SetBooleanValue(state, GetSpellData()->persist_though_death);
+		valSet = true;
+	}
+	else if (field == "not_maintained")
+	{
+		lua_interface->SetBooleanValue(state, GetSpellData()->not_maintained);
+		valSet = true;
+	}
+	else if (field == "is_aa")
+	{
+		lua_interface->SetBooleanValue(state, GetSpellData()->is_aa);
+		valSet = true;
+	}
+	else if (field == "savage_bar")
+	{
+		lua_interface->SetSInt32Value(state, GetSpellData()->savage_bar);
+		valSet = true;
+	}
+	else if (field == "savage_bar_slot")
+	{
+		lua_interface->SetSInt32Value(state, GetSpellData()->savage_bar_slot);
+		valSet = true;
+	}
+	else if (field == "soe_spell_crc")
+	{
+		lua_interface->SetSInt32Value(state, GetSpellData()->soe_spell_crc);
+		valSet = true;
+	}
+	else if (field == "spell_type")
+	{
+		lua_interface->SetSInt32Value(state, GetSpellData()->spell_type);
+		valSet = true;
+	}
+	else if (field == "spell_name_crc")
+	{
+		lua_interface->SetSInt32Value(state, GetSpellData()->spell_name_crc);
+		valSet = true;
+	}
+
+	return valSet;
+}
+
+bool Spell::SetSpellData(lua_State* state, std::string field, int8 fieldArg)
+{
+	if (!lua_interface)
+		return false;
+
+	bool valSet = false;
+
+	if (field == "spell_book_type")
+	{
+		int32 spell_book_type = lua_interface->GetInt32Value(state, fieldArg);
+		GetSpellData()->spell_book_type = spell_book_type;
+		valSet = true;
+	}
+	else if (field == "icon")
+	{
+		sint16 icon = lua_interface->GetSInt32Value(state, fieldArg);
+		GetSpellData()->icon = icon;
+		valSet = true;
+	}
+	else if (field == "icon_heroic_op")
+	{
+		int16 icon_heroic_op = lua_interface->GetInt16Value(state, fieldArg);
+		GetSpellData()->icon_heroic_op = icon_heroic_op;
+		valSet = true;
+	}
+	else if (field == "icon_backdrop")
+	{
+		int16 icon_backdrop = lua_interface->GetInt16Value(state, fieldArg);
+		GetSpellData()->icon_backdrop = icon_backdrop;
+		valSet = true;
+	}
+	else if (field == "type")
+	{
+		int16 type = lua_interface->GetInt16Value(state, fieldArg);
+		GetSpellData()->type = type;
+		valSet = true;
+	}
+	else if (field == "class_skill")
+	{
+		int32 class_skill = lua_interface->GetInt32Value(state, fieldArg);
+		GetSpellData()->class_skill = class_skill;
+		valSet = true;
+	}
+	else if (field == "mastery_skill")
+	{
+		int32 mastery_skill = lua_interface->GetInt32Value(state, fieldArg);
+		GetSpellData()->mastery_skill = mastery_skill;
+		valSet = true;
+	}
+	else if (field == "ts_loc_index")
+	{
+		int8 ts_loc_index = lua_interface->GetInt8Value(state, fieldArg);
+		GetSpellData()->ts_loc_index = ts_loc_index;
+		valSet = true;
+	}
+	else if (field == "num_levels")
+	{
+		int8 num_levels = lua_interface->GetInt8Value(state, fieldArg);
+		GetSpellData()->num_levels = num_levels;
+		valSet = true;
+	}
+	else if (field == "tier")
+	{
+		int8 tier = lua_interface->GetInt8Value(state, fieldArg);
+		GetSpellData()->tier = tier;
+		valSet = true;
+	}
+	else if (field == "hp_req")
+	{
+		int16 hp_req = lua_interface->GetInt16Value(state, fieldArg);
+		GetSpellData()->hp_req = hp_req;
+		valSet = true;
+	}
+	else if (field == "hp_upkeep")
+	{
+		int16 hp_upkeep = lua_interface->GetInt16Value(state, fieldArg);
+		GetSpellData()->hp_upkeep = hp_upkeep;
+		valSet = true;
+	}
+	else if (field == "power_req")
+	{
+		float power_req = lua_interface->GetFloatValue(state, fieldArg);
+		GetSpellData()->power_req = power_req;
+		valSet = true;
+	}
+	else if (field == "power_by_level")
+	{
+		bool power_by_level = lua_interface->GetBooleanValue(state, fieldArg);
+		GetSpellData()->power_by_level = power_by_level;
+		valSet = true;
+	}
+	else if (field == "power_upkeep")
+	{
+		int16 power_upkeep = lua_interface->GetInt16Value(state, fieldArg);
+		GetSpellData()->power_upkeep = power_upkeep;
+		valSet = true;
+	}
+	else if (field == "savagery_req")
+	{
+		int16 savagery_req = lua_interface->GetInt16Value(state, fieldArg);
+		GetSpellData()->savagery_req = savagery_req;
+		valSet = true;
+	}
+	else if (field == "savagery_upkeep")
+	{
+		int16 savagery_upkeep = lua_interface->GetInt16Value(state, fieldArg);
+		GetSpellData()->savagery_upkeep = savagery_upkeep;
+		valSet = true;
+	}
+	else if (field == "dissonance_req")
+	{
+		int16 dissonance_req = lua_interface->GetInt16Value(state, fieldArg);
+		GetSpellData()->dissonance_req = dissonance_req;
+		valSet = true;
+	}
+	else if (field == "dissonance_upkeep")
+	{
+		int16 dissonance_upkeep = lua_interface->GetInt16Value(state, fieldArg);
+		GetSpellData()->dissonance_upkeep = dissonance_upkeep;
+		valSet = true;
+	}
+	else if (field == "target_type")
+	{
+		int16 target_type = lua_interface->GetInt8Value(state, fieldArg);
+		GetSpellData()->target_type = target_type;
+		valSet = true;
+	}
+	else if (field == "cast_time")
+	{
+		int16 cast_time = lua_interface->GetInt16Value(state, fieldArg);
+		GetSpellData()->cast_time = cast_time;
+		valSet = true;
+	}
+	else if (field == "recovery")
+	{
+		float recovery = lua_interface->GetFloatValue(state, fieldArg);
+		GetSpellData()->recovery = recovery;
+		valSet = true;
+	}
+	else if (field == "recast")
+	{
+		float recast = lua_interface->GetFloatValue(state, fieldArg);
+		GetSpellData()->recast = recast;
+		valSet = true;
+	}
+	else if (field == "linked_timer")
+	{
+		int32 linked_timer = lua_interface->GetInt32Value(state, fieldArg);
+		GetSpellData()->linked_timer = linked_timer;
+		valSet = true;
+	}
+	else if (field == "radius")
+	{
+		float radius = lua_interface->GetFloatValue(state, fieldArg);
+		GetSpellData()->radius = radius;
+		valSet = true;
+	}
+	else if (field == "max_aoe_targets")
+	{
+		int16 max_aoe_targets = lua_interface->GetInt16Value(state, fieldArg);
+		GetSpellData()->max_aoe_targets = max_aoe_targets;
+		valSet = true;
+	}
+	else if (field == "friendly_spell")
+	{
+		int8 friendly_spell = lua_interface->GetInt8Value(state, fieldArg);
+		GetSpellData()->friendly_spell = friendly_spell;
+		valSet = true;
+	}
+	else if (field == "req_concentration")
+	{
+		int16 req_concentration = lua_interface->GetInt16Value(state, fieldArg);
+		GetSpellData()->req_concentration = req_concentration;
+		valSet = true;
+	}
+	else if (field == "range")
+	{
+		float range = lua_interface->GetFloatValue(state, fieldArg);
+		GetSpellData()->range = range;
+		valSet = true;
+	}
+	else if (field == "duration1")
+	{
+		sint32 duration = lua_interface->GetSInt32Value(state, fieldArg);
+		GetSpellData()->duration1 = duration;
+		valSet = true;
+	}
+	else if (field == "duration2")
+	{
+		sint32 duration = lua_interface->GetSInt32Value(state, fieldArg);
+		GetSpellData()->duration2 = duration;
+		valSet = true;
+	}
+	else if (field == "resistibility")
+	{
+		float resistibility = lua_interface->GetFloatValue(state, fieldArg);
+		GetSpellData()->resistibility = resistibility;
+		valSet = true;
+	}
+	else if (field == "duration_until_cancel")
+	{
+		bool duration_until_cancel = lua_interface->GetBooleanValue(state, fieldArg);
+		GetSpellData()->duration_until_cancel = duration_until_cancel;
+		valSet = true;
+	}
+	else if (field == "power_req_percent")
+	{
+		int8 power_req_percent = lua_interface->GetInt8Value(state, fieldArg);
+		GetSpellData()->power_req_percent = power_req_percent;
+		valSet = true;
+	}
+	else if (field == "hp_req_percent")
+	{
+		int8 hp_req_percent = lua_interface->GetInt8Value(state, fieldArg);
+		GetSpellData()->hp_req_percent = hp_req_percent;
+		valSet = true;
+	}
+	else if (field == "savagery_req_percent")
+	{
+		int8 savagery_req_percent = lua_interface->GetInt8Value(state, fieldArg);
+		GetSpellData()->savagery_req_percent = savagery_req_percent;
+		valSet = true;
+	}
+	else if (field == "dissonance_req_percent")
+	{
+		int8 dissonance_req_percent = lua_interface->GetInt8Value(state, fieldArg);
+		GetSpellData()->dissonance_req_percent = dissonance_req_percent;
+		valSet = true;
+	}
+	else if (field == "name")
+	{
+		string name = lua_interface->GetStringValue(state, fieldArg);
+		GetSpellData()->name.data = name;
+		valSet = true;
+	}
+	else if (field == "description")
+	{
+		string description = lua_interface->GetStringValue(state, fieldArg);
+		GetSpellData()->description.data = description;
+		valSet = true;
+	}
+	else if (field == "success_message")
+	{
+		string success_message = lua_interface->GetStringValue(state, fieldArg);
+		GetSpellData()->success_message = success_message;
+		valSet = true;
+	}
+	else if (field == "fade_message")
+	{
+		string fade_message = lua_interface->GetStringValue(state, fieldArg);
+		GetSpellData()->fade_message = fade_message;
+		valSet = true;
+	}
+	else if (field == "cast_type")
+	{
+		int8 cast_type = lua_interface->GetInt8Value(state, fieldArg);
+		GetSpellData()->cast_type = cast_type;
+		valSet = true;
+	}
+	else if (field == "cast_type")
+	{
+		int32 call_frequency = lua_interface->GetInt32Value(state, fieldArg);
+		GetSpellData()->call_frequency = call_frequency;
+		valSet = true;
+	}
+	else if (field == "interruptable")
+	{
+		bool interruptable = lua_interface->GetBooleanValue(state, fieldArg);
+		GetSpellData()->interruptable = interruptable;
+		valSet = true;
+	}
+	else if (field == "spell_visual")
+	{
+		int32 spell_visual = lua_interface->GetInt32Value(state, fieldArg);
+		GetSpellData()->spell_visual = spell_visual;
+		valSet = true;
+	}
+	else if (field == "effect_message")
+	{
+		string effect_message = lua_interface->GetStringValue(state, fieldArg);
+		GetSpellData()->effect_message = effect_message;
+		valSet = true;
+	}
+	else if (field == "min_range")
+	{
+		float min_range = lua_interface->GetFloatValue(state, fieldArg);
+		GetSpellData()->min_range = min_range;
+		valSet = true;
+	}
+	else if (field == "can_effect_raid")
+	{
+		int8 can_effect_raid = lua_interface->GetInt8Value(state, fieldArg);
+		GetSpellData()->can_effect_raid = can_effect_raid;
+		valSet = true;
+	}
+	else if (field == "affect_only_group_members")
+	{
+		int8 affect_only_group_members = lua_interface->GetInt8Value(state, fieldArg);
+		GetSpellData()->affect_only_group_members = affect_only_group_members;
+		valSet = true;
+	}
+	else if (field == "group_spell")
+	{
+		int8 group_spell = lua_interface->GetInt8Value(state, fieldArg);
+		GetSpellData()->group_spell = group_spell;
+		valSet = true;
+	}
+	else if (field == "hit_bonus")
+	{
+		float hit_bonus = lua_interface->GetFloatValue(state, fieldArg);
+		GetSpellData()->hit_bonus = hit_bonus;
+		valSet = true;
+	}
+	else if (field == "display_spell_tier")
+	{
+		int8 display_spell_tier = lua_interface->GetInt8Value(state, fieldArg);
+		GetSpellData()->display_spell_tier = display_spell_tier;
+		valSet = true;
+	}
+	else if (field == "is_active")
+	{
+		int8 is_active = lua_interface->GetInt8Value(state, fieldArg);
+		GetSpellData()->is_active = is_active;
+		valSet = true;
+	}
+	else if (field == "det_type")
+	{
+		int8 det_type = lua_interface->GetInt8Value(state, fieldArg);
+		GetSpellData()->det_type = det_type;
+		valSet = true;
+	}
+	else if (field == "incurable")
+	{
+		bool incurable = lua_interface->GetBooleanValue(state, fieldArg);
+		GetSpellData()->incurable = incurable;
+		valSet = true;
+	}
+	else if (field == "control_effect_type")
+	{
+		int8 control_effect_type = lua_interface->GetInt8Value(state, fieldArg);
+		GetSpellData()->control_effect_type = control_effect_type;
+		valSet = true;
+	}
+	else if (field == "casting_flags")
+	{
+		int32 casting_flags = lua_interface->GetInt32Value(state, fieldArg);
+		GetSpellData()->casting_flags = casting_flags;
+		valSet = true;
+	}
+	else if (field == "cast_while_moving")
+	{
+		bool cast_while_moving = lua_interface->GetBooleanValue(state, fieldArg);
+		GetSpellData()->cast_while_moving = cast_while_moving;
+		valSet = true;
+	}
+	else if (field == "persist_though_death")
+	{
+		bool persist_though_death = lua_interface->GetBooleanValue(state, fieldArg);
+		GetSpellData()->persist_though_death = persist_though_death;
+		valSet = true;
+	}
+	else if (field == "not_maintained")
+	{
+		bool not_maintained = lua_interface->GetBooleanValue(state, fieldArg);
+		GetSpellData()->not_maintained = not_maintained;
+		valSet = true;
+	}
+	else if (field == "is_aa")
+	{
+		bool is_aa = lua_interface->GetBooleanValue(state, fieldArg);
+		GetSpellData()->is_aa = is_aa;
+		valSet = true;
+	}
+	else if (field == "savage_bar")
+	{
+		int8 savage_bar = lua_interface->GetInt8Value(state, fieldArg);
+		GetSpellData()->savage_bar = savage_bar;
+		valSet = true;
+	}
+	else if (field == "spell_type")
+	{
+		int8 spell_type = lua_interface->GetInt8Value(state, fieldArg);
+		GetSpellData()->spell_type = spell_type;
+		valSet = true;
+	}
+
+	return valSet;
+}
 int16 Spell::GetSpellIcon(){
 	if (spell)
 		return spell->icon;
@@ -1191,10 +2044,14 @@ bool Spell::IsControlSpell(){
 	return control_spell;
 }
 
-bool Spell::IsOffenseSpell(){
+bool Spell::IsOffenseSpell() {
 	return offense_spell;
 }
 
+bool Spell::IsCopiedSpell() {
+	return copied_spell;
+}
+
 void Spell::ModifyCastTime(Entity* caster){
 	int16 cast_time = spell->cast_time;
 	float cast_speed = caster->GetInfoStruct()->casting_speed;

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

@@ -28,6 +28,8 @@
 #include "../common/Mutex.h"
 #include "AltAdvancement/AltAdvancement.h"
 
+#include "../LUA/lua.hpp"
+
 #define SPELL_TARGET_SELF			0
 #define SPELL_TARGET_ENEMY			1
 #define SPELL_TARGET_GROUP_AE		2
@@ -289,6 +291,7 @@ public:
 	~Spell();
 	Spell();
 	Spell(SpellData* in_spell);
+	Spell(Spell* host_spell);
 	EQ2Packet* SerializeSpell(Client* client, bool display, bool trait_display = false, int8 packet_type = 0, int8 sub_packet_type = 0, const char* struct_name = 0);
 	EQ2Packet* SerializeSpecialSpell(Client* client, bool display, int8 packet_type = 0, int8 sub_packet_type = 0);
 	EQ2Packet* SerializeAASpell(Client* client,int8 tier, AltAdvanceData* data, bool display, bool trait_display = false, int8 packet_type = 0, int8 sub_packet_type = 0, const char* struct_name = 0);
@@ -313,6 +316,8 @@ public:
 	int16 GetSavageryRequired(Spawn* spawn);
 	int16 GetDissonanceRequired(Spawn* spawn);
 	SpellData* GetSpellData();
+	bool GetSpellData(lua_State* state, std::string field);
+	bool SetSpellData(lua_State* state, std::string field, int8 fieldArg);
 	bool ScribeAllowed(Player* player);
 	vector<LUAData*>* GetLUAData();
 	vector <LevelArray*>* GetSpellLevels();
@@ -324,6 +329,7 @@ public:
 	bool IsDamageSpell();
 	bool IsControlSpell();
 	bool IsOffenseSpell();
+	bool IsCopiedSpell();
 	void ModifyCastTime(Entity* caster);
 	bool CastWhileStunned();
 	bool CastWhileMezzed();
@@ -331,15 +337,18 @@ public:
 	bool CastWhileFeared();
 
 
-
 	vector<SpellDisplayEffect*> effects;
 	vector<LUAData*> lua_data;
+
+	void LockSpellInfo() { MSpellInfo.lock(); }
+	void UnlockSpellInfo() { MSpellInfo.unlock(); }
 private:
 	bool heal_spell;
 	bool buff_spell;
 	bool damage_spell;
 	bool control_spell;
 	bool offense_spell;
+	bool copied_spell;
 
 	SpellData* spell;
 	

+ 1 - 0
EQ2/source/WorldServer/Zone/mob_movement_manager.h

@@ -86,6 +86,7 @@ public:
 		MobListMutex.writelock();
 		RunningCommandProcess = status;
 		MobListMutex.releasewritelock();
+		return true;
 	}
 private:
 	MobMovementManager(const MobMovementManager&);

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

@@ -3680,7 +3680,7 @@ void ZoneServer::SetSpawnCommand(int32 spawn_id, int8 type, char* value, Client*
 	LogWrite(MISC__TODO, 1, "TODO", "%s does nothing!\n%s, %i", __FUNCTION__, __FILE__, __LINE__);
 }
 
-void ZoneServer::ApplySetSpawnCommand(Client* client, Spawn* target, int8 type, char* value){
+void ZoneServer::ApplySetSpawnCommand(Client* client, Spawn* target, int8 type, const char* value){
 	// This will apply the /spawn set command to all the spawns in the zone with the same DB ID, we do not want to set
 	// location values (x, y, z, heading, grid) for all spawns in the zone with the same DB ID, only the targeted spawn
 	if(type == SPAWN_SET_VALUE_SPAWNENTRY_SCRIPT || type == SPAWN_SET_VALUE_SPAWNLOCATION_SCRIPT || (type >= SPAWN_SET_VALUE_X && type <= SPAWN_SET_VALUE_LOCATION) ||
@@ -5599,9 +5599,9 @@ Spell* ZoneServer::GetSpell(Entity* caster){
 	return spell;
 }
 
-void ZoneServer::ProcessSpell(Spell* spell, Entity* caster, Spawn* target, bool lock, bool harvest_spell){
+void ZoneServer::ProcessSpell(Spell* spell, Entity* caster, Spawn* target, bool lock, bool harvest_spell, LuaSpell* customSpell, int16 custom_cast_time){
 	if(spellProcess)
-		spellProcess->ProcessSpell(this, spell, caster, target, lock, harvest_spell);
+		spellProcess->ProcessSpell(this, spell, caster, target, lock, harvest_spell, customSpell, custom_cast_time);
 }
 
 void ZoneServer::ProcessEntityCommand(EntityCommand* entity_command, Entity* caster, Spawn* target, bool lock) {

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

@@ -258,7 +258,7 @@ public:
 	
 	int16	SetSpawnTargetable(Spawn* spawn, float distance);
 	int16	SetSpawnTargetable(int32 spawn_id);
-	void	ApplySetSpawnCommand(Client* client, Spawn* target, int8 type, char* value);
+	void	ApplySetSpawnCommand(Client* client, Spawn* target, int8 type, const char* value);
 	void	SetSpawnCommand(Spawn* spawn, int8 type, char* value, Client* client = 0);
 	void	SetSpawnCommand(int32 spawn_id, int8 type, char* value, Client* client = 0);
 	void	AddLoot(NPC* npc);
@@ -368,7 +368,7 @@ public:
 	void	RemoveSpellTimersFromSpawn(Spawn* spawn, bool remove_all, bool delete_recast = true);
 	void	Interrupted(Entity* caster, Spawn* interruptor, int16 error_code, bool cancel = false, bool from_movement = false);
 	Spell*	GetSpell(Entity* caster);
-	void	ProcessSpell(Spell* spell, Entity* caster, Spawn* target = 0, bool lock = true, bool harvest_spell = false);
+	void	ProcessSpell(Spell* spell, Entity* caster, Spawn* target = 0, bool lock = true, bool harvest_spell = false, LuaSpell* customSpell = 0, int16 custom_cast_time = 0);
 	void	ProcessEntityCommand(EntityCommand* entity_command, Entity* caster, Spawn* target, bool lock = true);
 	void	AddPlayerTracking(Player* player);
 	void	RemovePlayerTracking(Player* player, int8 mode);

+ 2 - 2
EQ2/source/common/ConfigReader.cpp

@@ -212,9 +212,9 @@ void ConfigReader::loadDataStruct(PacketStruct* packet, XMLNode parentNode, bool
 					vector<DataStruct*>* structs = substruct_packet->getStructs();
 					DataStruct* ds = 0;
 					int i = 0;
-					char tmp[10] = {0};
+					char tmp[12] = {0};
 					for(i=0;i<num_size;i++){
-						sprintf(tmp,"%i",i);
+						snprintf(tmp, sizeof(tmp)-1, "%i", i);
 						for(itr=structs->begin();itr!=structs->end();itr++) {
 							ds = *itr;
 							string new_name;

+ 1 - 1
EQ2/source/common/servertalk.h

@@ -637,7 +637,7 @@ struct ZoneUpdate_Struct{
 };
 
 struct ZoneUpdateList_Struct{
-	sint16	total_updates;
+	uint16	total_updates;
 	char data[0];
 };
 

+ 2 - 2
EQ2/source/common/version.h

@@ -38,9 +38,9 @@
 #endif
 
 #if defined(LOGIN)
-#define CURRENT_VERSION	"0.8.1-virgo1"
+#define CURRENT_VERSION	"0.8.1-virgo2"
 #elif defined(WORLD)
-#define CURRENT_VERSION	"0.8.1-virgo1"
+#define CURRENT_VERSION	"0.8.1-virgo2"
 #else
 #define CURRENT_VERSION	"0.7.3-dev"
 #endif

+ 112 - 0
cmake/FindMySQL.cmake

@@ -0,0 +1,112 @@
+#[==[
+Provides the following variables:
+
+  * `MySQL_INCLUDE_DIRS`: Include directories necessary to use MySQL.
+  * `MySQL_LIBRARIES`: Libraries necessary to use MySQL.
+  * A `MySQL::MySQL` imported target.
+#]==]
+
+# No .pc files are shipped with MySQL on Windows.
+set(_MySQL_use_pkgconfig 0)
+if (NOT WIN32)
+  find_package(PkgConfig)
+  if (PkgConfig_FOUND)
+    set(_MySQL_use_pkgconfig 1)
+  endif ()
+endif ()
+
+if (_MySQL_use_pkgconfig)
+  pkg_check_modules(_libmariadb "libmariadb" QUIET IMPORTED_TARGET)
+  unset(_mysql_target)
+  if (_libmariadb_FOUND)
+    set(_mysql_target "_libmariadb")
+  else ()
+    pkg_check_modules(_mariadb "mariadb" QUIET IMPORTED_TARGET)
+    if (NOT _mariadb_FOUND)
+      pkg_check_modules(_mysql "mysql" QUIET IMPORTED_TARGET)
+      if (_mysql_FOUND)
+        set(_mysql_target "_mysql")
+      endif ()
+    else ()
+      set(_mysql_target "_mariadb")
+      if (_mariadb_VERSION VERSION_LESS 10.4)
+        get_property(_include_dirs
+          TARGET    "PkgConfig::_mariadb"
+          PROPERTY  "INTERFACE_INCLUDE_DIRECTORIES")
+        # Remove "${prefix}/mariadb/.." from the interface since it breaks other
+        # projects.
+        list(FILTER _include_dirs EXCLUDE REGEX "\\.\\.")
+        set_property(TARGET "PkgConfig::_mariadb"
+          PROPERTY
+            "INTERFACE_INCLUDE_DIRECTORIES" "${_include_dirs}")
+        unset(_include_dirs)
+      endif ()
+    endif ()
+  endif ()
+
+  set(MySQL_FOUND 0)
+  if (_mysql_target)
+    set(MySQL_FOUND 1)
+    set(MySQL_INCLUDE_DIRS ${${_mysql_target}_INCLUDE_DIRS})
+    set(MySQL_LIBRARIES ${${_mysql_target}_LINK_LIBRARIES})
+    if (NOT TARGET MySQL::MySQL)
+      add_library(MySQL::MySQL INTERFACE IMPORTED)
+      target_link_libraries(MySQL::MySQL
+        INTERFACE "PkgConfig::${_mysql_target}")
+    endif ()
+  endif ()
+  unset(_mysql_target)
+else ()
+  set(_MySQL_mariadb_versions 10.2 10.3)
+  set(_MySQL_versions 5.0)
+  set(_MySQL_paths)
+  foreach (_MySQL_version IN LISTS _MySQL_mariadb_versions)
+    list(APPEND _MySQL_paths
+      "[HKEY_LOCAL_MACHINE\\SOFTWARE\\MariaDB ${_MySQL_version};INSTALLDIR]"
+      "[HKEY_LOCAL_MACHINE\\SOFTWARE\\MariaDB ${_MySQL_version} (x64);INSTALLDIR]")
+  endforeach ()
+  foreach (_MySQL_version IN LISTS _MySQL_versions)
+    list(APPEND _MySQL_paths
+      "C:/Program Files/MySQL/MySQL Server ${_MySQL_version}/lib/opt"
+      "[HKEY_LOCAL_MACHINE\\SOFTWARE\\MySQL AB\\MySQL Server ${_MySQL_version};Location]"
+      "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\MySQL AB\\MySQL Server ${_MySQL_version};Location]")
+  endforeach ()
+  unset(_MySQL_version)
+  unset(_MySQL_versions)
+  unset(_MySQL_mariadb_versions)
+
+  find_path(MySQL_INCLUDE_DIR
+    NAMES mysql.h
+    PATHS
+      "C:/Program Files/MySQL/include"
+      "C:/MySQL/include"
+      ${_MySQL_paths}
+    PATH_SUFFIXES include include/mysql
+    DOC "Location of mysql.h")
+  mark_as_advanced(MySQL_INCLUDE_DIR)
+  find_library(MySQL_LIBRARY
+    NAMES libmariadb mysql libmysql mysqlclient
+    PATHS
+      "C:/Program Files/MySQL/lib"
+      "C:/MySQL/lib/debug"
+      ${_MySQL_paths}
+    PATH_SUFFIXES lib lib/opt
+    DOC "Location of the mysql library")
+  mark_as_advanced(MySQL_LIBRARY)
+
+  include(FindPackageHandleStandardArgs)
+  find_package_handle_standard_args(MySQL
+    REQUIRED_VARS MySQL_INCLUDE_DIR MySQL_LIBRARY)
+
+  if (MySQL_FOUND)
+    set(MySQL_INCLUDE_DIRS "${MySQL_INCLUDE_DIR}")
+    set(MySQL_LIBRARIES "${MySQL_LIBRARY}")
+    if (NOT TARGET MySQL::MySQL)
+      add_library(MySQL::MySQL UNKNOWN IMPORTED)
+      set_target_properties(MySQL::MySQL PROPERTIES
+      IMPORTED_LOCATION "${MySQL_LIBRARY}"
+        INTERFACE_INCLUDE_DIRECTORIES "${MySQL_INCLUDE_DIR}")
+    endif ()
+  endif ()
+endif ()
+unset(_MySQL_use_pkgconfig)

BIN
server/EQ2Login__Debug64.exe


BIN
server/EQ2World__Debug_x64.exe


+ 9 - 9
server/SpawnScripts/ThunderingSteppes/Brianna.lua

@@ -31,7 +31,12 @@ function hailed(NPC, Spawn)
 		PlayFlavor(NPC, "voiceover/english/voice_emotes/greetings/greetings_3_1009.mp3", "", "", 0, 0, Spawn)
 	end
 	
-	if HasQuest(Spawn, WatchYourStepinTheTSPartIII) and GetQuestStep(Spawn, WatchYourStepinTheTSPartIII) == 3 then
+	if HasQuest(Spawn, SuppliesForBrianna) and GetQuestStep(Spawn, SuppliesForBrianna) == 2 then
+		-- turn in SuppliesForBrianna
+		AddConversationOption(conversation, "Yes right here.", "dlg_1_1")
+		AddConversationOption(conversation, "Um, I have some but not for you.")
+		StartConversation(conversation, NPC, Spawn, "Well, do you have the supplies?")
+	elseif HasQuest(Spawn, WatchYourStepinTheTSPartIII) and GetQuestStep(Spawn, WatchYourStepinTheTSPartIII) == 2 then
 		-- start SuppliesForBrianna
 		AddConversationOption(conversation, "No, I'm here to deliver a package to you.", "dlg_0_1")
 		AddConversationOption(conversation, "Oh okay. Thanks anyways.")
@@ -40,11 +45,6 @@ function hailed(NPC, Spawn)
 		-- on SuppliesForBrianna or HidesForBrianna but not ready for turn in
 		AddConversationOption(conversation, "No but I have my best people working on it.")
 		StartConversation(conversation, NPC, Spawn, "Well, did you bring the supplies yet?")
-	elseif HasQuest(Spawn, SuppliesForBrianna) and GetQuestStep(Spawn, SuppliesForBrianna) == 2 then
-		-- turn in SuppliesForBrianna
-		AddConversationOption(conversation, "Yes right here.", "dlg_1_1")
-		AddConversationOption(conversation, "Um, I have some but not for you.")
-		StartConversation(conversation, NPC, Spawn, "Well, do you have the supplies?")
 	elseif HasCompletedQuest(Spawn, SuppliesForBrianna) and not HasQuest(Spawn, HidesForBrianna) and not HasCompletedQuest(Spawn, HidesForBrianna) then
 		-- start HidesForBrianna
 		AddConversationOption(conversation, "Yes I am.", "dlg_2_1")
@@ -79,7 +79,7 @@ function dlg_0_2(NPC, Spawn)
 end
 
 function dlg_0_3(NPC, Spawn)
-	OfferQuest(NPC, Player, SuppliesForBrianna)
+	OfferQuest(NPC, Spawn, SuppliesForBrianna)
 end
 
 function dlg_1_1(NPC, Spawn)
@@ -102,7 +102,7 @@ function dlg_2_1(NPC, Spawn)
 end
 
 function dlg_2_2(NPC, Spawn)
-	OfferQuest(NPC, Player, HidesForBrianna)
+	OfferQuest(NPC, Spawn, HidesForBrianna)
 end
 
 function dlg_3_1(NPC, Spawn)
@@ -112,4 +112,4 @@ function dlg_3_1(NPC, Spawn)
 	SetStepComplete(Spawn, HidesForBrianna, 2)
 	AddConversationOption(conversation, "Thanks.")
 	StartConversation(conversation, NPC, Spawn, "Great, these look like it's the first time they have ever seen the sun. Thanks for your help. I don't have anything else for you today, but check back again.")
-end
+end

+ 3 - 3
server/SpawnScripts/ThunderingSteppes/Jacques.lua

@@ -133,7 +133,7 @@ function dlg_3_1(NPC, Spawn)
 end
 
 function dlg_4_1(NPC, Spawn)
-	OfferQuest(NPC, Player, WatchYourStepInTheTSPartIII)
+	OfferQuest(NPC, Spawn, WatchYourStepInTheTSPartIII)
 end
 	
 function dlg_4_2(NPC, Spawn)
@@ -154,5 +154,5 @@ function dlg_5_1(NPC, Spawn)
 end
 
 function dlg_6_1(NPC, Spawn)
-	OfferQuest(NPC, Player, WatchYourStepInTheTSPartIV)
-end
+	OfferQuest(NPC, Spawn, WatchYourStepInTheTSPartIV)
+end