Browse Source

Fixed titles, make sure to source in DB update!

Fix #230
- Fixed titles, DB and code were inconsistent thus / commands did not work
- LUA Functions added:

AddMasterTitle(titleName, isPrefix) -- adds a new title all characters can use, isPrefix is 0 or 1, 1 for prefix, 0 for suffix
- returns master title id (sint32) -- if the title already exists, then it re-uses that id

AddCharacterTitle(Spawn, titleName) -- adds a character title to a spawn if not already present

- returns character title index id (sint32)

SetCharacterTitleSuffix(Spawn, titleName) -- sets the players suffix name, must have title already, otherwise AddCharacterTitle needs to be called first
SetCharacterTitlePrefix(Spawn, titleName) -- sets the players suffix name, must have title already, otherwise AddCharacterTitle needs to be called first

ResetCharacterTitleSuffix(Spawn) - empties title suffix
ResetCharacterTitlePrefix(Spawn) - empties title prefix

Example usage of some of the LUA commands:
	AddMasterTitle("Stupendously Special", 1) -- create new title for all players, is prefix
	AddCharacterTitle(Spawn, "Stupendously Special") -- add title to the current player
	SetCharacterTitlePrefix(Spawn, "Stupendously Special") -- set the characters current prefix to the newly created title
Image 3 years ago
parent
commit
96debb941c

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

@@ -7739,10 +7739,11 @@ void Commands::Command_Title(Client* client)
 */ 
 void Commands::Command_TitleList(Client* client)
 {
-	list<Title*>* titles = client->GetPlayer()->GetPlayerTitles()->GetAllTitles();
-	list<Title*>::iterator itr;
+	// must call release read lock before leaving function on GetPlayerTitles
+	vector<Title*>* titles = client->GetPlayer()->GetPlayerTitles()->GetAllTitles();
+	vector<Title*>::iterator itr;
 	Title* title;
-	int16 i = 0;
+	sint32 i = 0;
 
 	client->Message(CHANNEL_NARRATIVE, "Listing available titles:");
 	for(itr = titles->begin(); itr != titles->end(); itr++)
@@ -7752,6 +7753,7 @@ void Commands::Command_TitleList(Client* client)
 		i++;
 	}
 
+	client->GetPlayer()->GetPlayerTitles()->ReleaseReadLock();
 }
 
 /* 
@@ -7765,9 +7767,26 @@ void Commands::Command_TitleSetPrefix(Client* client, Seperator* sep)
 {
 	if (sep && sep->arg[0] && sep->IsNumber(0))
 	{
-		int32 index = atoul(sep->arg[0]);
+		sint32 index = atoul(sep->arg[0]);
 
-		database.SaveCharPrefixIndex(index, client->GetCharacterID(), client);
+		if(index > -1)
+		{
+			Title* title = client->GetPlayer()->GetPlayerTitles()->GetTitle(index);
+			if(!title)
+			{
+				client->Message(CHANNEL_COLOR_RED, "Missing index %i to set title", index);
+				return;
+			}
+			else if(!title->GetPrefix())
+			{
+				client->Message(CHANNEL_COLOR_RED, "%s is not a prefix.", title->GetName());
+				return;
+			}
+		}
+		else // make sure client doesn't pass some bogus negative index
+			index = -1;
+
+		database.SaveCharPrefixIndex(index, client->GetCharacterID());
 		client->SendTitleUpdate();
 	}
 }
@@ -7783,9 +7802,25 @@ void Commands::Command_TitleSetSuffix(Client* client, Seperator* sep)
 {
 	if (sep && sep->arg[0] && sep->IsNumber(0))
 	{
-		int32 index = atoul(sep->arg[0]);
+		sint32 index = atoul(sep->arg[0]);
+		if(index > -1)
+		{
+			Title* title = client->GetPlayer()->GetPlayerTitles()->GetTitle(index);
+			if(!title)
+			{
+				client->Message(CHANNEL_COLOR_RED, "Missing index %i to set title", index);
+				return;
+			}
+			else if(title->GetPrefix())
+			{
+				client->Message(CHANNEL_COLOR_RED, "%s is not a suffix.", title->GetName());
+				return;
+			}
+		}
+		else // make sure client doesn't pass some bogus negative index
+			index = -1;
 
-		database.SaveCharSuffixIndex(index, client->GetCharacterID(), client);
+		database.SaveCharSuffixIndex(index, client->GetCharacterID());
 		client->SendTitleUpdate();
 	}
 }
@@ -9711,7 +9746,7 @@ void Commands::Command_ZoneDetails(Client* client, Seperator* sep)
 			client->Message(CHANNEL_COLOR_YELLOW, "default_reenter_time: %u, default_reset_time: %u, default_lockout_time: %u", zone_info->default_reenter_time, zone_info->default_reenter_time, zone_info->default_lockout_time);
 			client->Message(CHANNEL_COLOR_YELLOW, "force_group_to_zone: %u, expansion_id: %u, min_version: %u", zone_info->force_group_to_zone, zone_info->expansion_id, zone_info->min_version);
 			client->Message(CHANNEL_COLOR_YELLOW, "always_loaded: %u, city_zone: %u, start_zone: %u, weather_allowed: %u", zone_info->always_loaded, zone_info->city_zone, zone_info->start_zone, zone_info->weather_allowed);
-			client->Message(CHANNEL_COLOR_YELLOW, "zone_type: %s", zone_info->zone_type);
+			client->Message(CHANNEL_COLOR_YELLOW, "zone_type: %s, sky_file: %s", zone_info->zone_type, zone_info->sky_file);
 			client->Message(CHANNEL_COLOR_YELLOW, "lua_script: %s", zone_info->lua_script);
 			client->Message(CHANNEL_COLOR_YELLOW, "zone_motd: %s", zone_info->zone_motd);
 		}

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

@@ -188,6 +188,7 @@ struct ZoneInfo {
 	int16	min_version;
 	bool	weather_allowed;
 	int32	ruleset_id;
+	char	sky_file[64];
 };
 
 class EQ2_CommandString : public DataBuffer{

+ 175 - 0
EQ2/source/WorldServer/LuaFunctions.cpp

@@ -36,6 +36,7 @@
 #include "RaceTypes/RaceTypes.h"
 #include "ClientPacketFunctions.h"
 #include "Transmute.h"
+#include "Titles.h"
 #include <boost/algorithm/string/predicate.hpp>
 #include <sstream> 
 #include <boost/algorithm/string.hpp>
@@ -57,6 +58,7 @@ extern MasterSkillList master_skill_list;
 extern MasterHeroicOPList master_ho_list;
 extern MasterRaceTypeList race_types_list;
 extern MasterLanguagesList master_languages_list;
+extern MasterTitlesList master_titles_list;
 extern RuleManager rule_manager;
 
 vector<string> ParseString(string strVal, char delim) {
@@ -11202,4 +11204,177 @@ int EQ2Emu_lua_SetAAInfo(lua_State* state) {
 			((Player*)spawn)->SetCharSheetChanged(true);
 	}
 	return 0;
+}
+
+int EQ2Emu_lua_AddMasterTitle(lua_State* state) {
+	if (!lua_interface)
+		return 0;
+
+	string titleName = lua_interface->GetStringValue(state);
+	int8 isPrefix = lua_interface->GetInt8Value(state, 2);
+	
+	sint32 index = database.AddMasterTitle(titleName.c_str(), isPrefix);
+	lua_interface->SetSInt32Value(state, index);
+
+	return 1;
+}
+
+int EQ2Emu_lua_AddCharacterTitle(lua_State* state) {
+	if (!lua_interface)
+		return 0;
+
+	Spawn* spawn = lua_interface->GetSpawn(state);
+	string titleName = lua_interface->GetStringValue(state, 2);
+	
+	if(!spawn->IsPlayer())
+	{
+		lua_interface->LogError("%s: LUA AddCharacterTitle command error: player is not valid", lua_interface->GetScriptName(state));
+		lua_interface->SetSInt32Value(state, -1);
+		return 1;
+	}
+
+	Player* player = (Player*)spawn;
+	// check if player already has the title, don't need to add twice
+	Title* playerHasTitle = player->GetPlayerTitles()->GetTitleByName(titleName.c_str());
+
+	if ( playerHasTitle)
+	{
+		lua_interface->SetSInt32Value(state, playerHasTitle->GetID());
+		return 1;
+	}
+
+	Title* title = master_titles_list.GetTitleByName(titleName.c_str());
+	
+	if(!title)
+	{
+		lua_interface->LogError("%s: LUA AddCharacterTitle command error: title is not valid with name '%s'", lua_interface->GetScriptName(state), titleName.c_str());
+		lua_interface->SetSInt32Value(state, -1);
+		return 1;
+	}
+	
+
+	sint32 returnIdx = database.AddCharacterTitle(title->GetID(), player->GetCharacterID(), player);
+
+	if(returnIdx < 0)
+	{
+		lua_interface->LogError("%s: LUA AddCharacterTitle command error: got invalid index (-1) returned for database.AddCharacterTitle '%s'", lua_interface->GetScriptName(state), titleName.c_str());
+	}
+
+	lua_interface->SetSInt32Value(state, returnIdx);
+
+	player->GetClient()->SendTitleUpdate();
+
+	return 1;
+}
+
+int EQ2Emu_lua_SetCharacterTitleSuffix(lua_State* state) {
+	if (!lua_interface)
+		return 0;
+
+	Spawn* spawn = lua_interface->GetSpawn(state);
+	string titleName = lua_interface->GetStringValue(state, 2);
+	
+	if(!spawn->IsPlayer())
+	{
+		lua_interface->LogError("%s: LUA SetCharacterTitleSuffix command error: player is not valid", lua_interface->GetScriptName(state));
+		return 0;
+	}
+
+	Player* player = (Player*)spawn;
+
+	Title* title = player->GetPlayerTitles()->GetTitleByName(titleName.c_str());
+	
+	if(!title)
+	{
+		lua_interface->LogError("%s: LUA SetCharacterTitleSuffix command error: title is not valid with name '%s'", lua_interface->GetScriptName(state), titleName.c_str());
+		return 0;
+	}
+	
+	if(title->GetPrefix())
+	{
+		lua_interface->LogError("%s: LUA SetCharacterTitleSuffix command error: title with name '%s' is not valid as a suffix, only prefix", lua_interface->GetScriptName(state), titleName.c_str());
+		return 0;
+	}
+
+	database.SaveCharSuffixIndex(title->GetID(), player->GetCharacterID());
+	player->GetClient()->SendTitleUpdate();
+
+	return 1;
+}
+
+int EQ2Emu_lua_SetCharacterTitlePrefix(lua_State* state) {
+	if (!lua_interface)
+		return 0;
+
+	Spawn* spawn = lua_interface->GetSpawn(state);
+	string titleName = lua_interface->GetStringValue(state, 2);
+	
+	if(!spawn->IsPlayer())
+	{
+		lua_interface->LogError("%s: LUA SetCharacterTitlePrefix command error: player is not valid", lua_interface->GetScriptName(state));
+		return 0;
+	}
+
+	Player* player = (Player*)spawn;
+
+	Title* title = player->GetPlayerTitles()->GetTitleByName(titleName.c_str());
+
+	if(!title)
+	{
+		lua_interface->LogError("%s: LUA SetCharacterTitlePrefix command error: title is not valid with name '%s'", lua_interface->GetScriptName(state), titleName.c_str());
+		return 0;
+	}
+	
+	if(!title->GetPrefix())
+	{
+		lua_interface->LogError("%s: LUA SetCharacterTitlePrefix command error: title with name '%s' is not valid as a prefix, only suffix", lua_interface->GetScriptName(state), titleName.c_str());
+		return 0;
+	}
+	
+	database.SaveCharPrefixIndex(title->GetID(), player->GetCharacterID());
+	player->GetClient()->SendTitleUpdate();
+
+	return 1;
+}
+
+int EQ2Emu_lua_ResetCharacterTitleSuffix(lua_State* state) {
+	if (!lua_interface)
+		return 0;
+
+	Spawn* spawn = lua_interface->GetSpawn(state);
+	
+	if(!spawn->IsPlayer())
+	{
+		lua_interface->LogError("%s: LUA ResetCharacterTitleSuffix command error: player is not valid", lua_interface->GetScriptName(state));
+		return 0;
+	}
+
+	Player* player = (Player*)spawn;
+
+
+	database.SaveCharSuffixIndex(-1, player->GetCharacterID());
+	player->GetClient()->SendTitleUpdate();
+
+	return 1;
+}
+
+int EQ2Emu_lua_ResetCharacterTitlePrefix(lua_State* state) {
+	if (!lua_interface)
+		return 0;
+
+	Spawn* spawn = lua_interface->GetSpawn(state);
+	
+	if(!spawn->IsPlayer())
+	{
+		lua_interface->LogError("%s: LUA ResetCharacterTitlePrefix command error: player is not valid", lua_interface->GetScriptName(state));
+		return 0;
+	}
+	
+	Player* player = (Player*)spawn;
+
+
+	database.SaveCharPrefixIndex(-1, player->GetCharacterID());
+	player->GetClient()->SendTitleUpdate();
+
+	return 1;
 }

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

@@ -525,4 +525,11 @@ int EQ2Emu_lua_GetRuleFlagInt32(lua_State* state);
 
 int EQ2Emu_lua_GetAAInfo(lua_State* state);
 int EQ2Emu_lua_SetAAInfo(lua_State* state);
+
+int EQ2Emu_lua_AddMasterTitle(lua_State* state);
+int EQ2Emu_lua_AddCharacterTitle(lua_State* state);
+int EQ2Emu_lua_SetCharacterTitleSuffix(lua_State* state);
+int EQ2Emu_lua_SetCharacterTitlePrefix(lua_State* state);
+int EQ2Emu_lua_ResetCharacterTitleSuffix(lua_State* state);
+int EQ2Emu_lua_ResetCharacterTitlePrefix(lua_State* state);
 #endif

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

@@ -1239,6 +1239,13 @@ void LuaInterface::RegisterFunctions(lua_State* state) {
 	
 	lua_register(state, "GetAAInfo", EQ2Emu_lua_GetAAInfo);
 	lua_register(state, "SetAAInfo", EQ2Emu_lua_SetAAInfo);
+	
+	lua_register(state, "AddMasterTitle", EQ2Emu_lua_AddMasterTitle);
+	lua_register(state, "AddCharacterTitle", EQ2Emu_lua_AddCharacterTitle);
+	lua_register(state, "SetCharacterTitleSuffix", EQ2Emu_lua_SetCharacterTitleSuffix);
+	lua_register(state, "SetCharacterTitlePrefix", EQ2Emu_lua_SetCharacterTitlePrefix);
+	lua_register(state, "ResetCharacterTitleSuffix", EQ2Emu_lua_ResetCharacterTitleSuffix);
+	lua_register(state, "ResetCharacterTitlePrefix", EQ2Emu_lua_ResetCharacterTitlePrefix);
 }
 
 void LuaInterface::LogError(const char* error, ...)  {

+ 3 - 1
EQ2/source/WorldServer/Player.cpp

@@ -5368,13 +5368,15 @@ int8 Player::FindFreeBankSlot() {
 	return item_list.FindFreeBankSlot();
 }
 
-void Player::AddTitle(int32 title_id, const char *name, int8 prefix, bool save_needed){
+void Player::AddTitle(sint32 title_id, const char *name, int8 prefix, bool save_needed){
 	Title* new_title = new Title;
 	new_title->SetID(title_id);
 	new_title->SetName(name);
 	new_title->SetPrefix(prefix);
+	new_title->SetSaveNeeded(save_needed);
 	player_titles_list.Add(new_title);
 }
+
 void Player::AddAAEntry(int16 template_id, int8 tab_id, int32 aa_id, int16 order,int8 treeid) {
 	
 	

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

@@ -825,7 +825,7 @@ public:
 	static bool SortSpellEntryByLevelReverse(SpellBookEntry* s1, SpellBookEntry* s2);
 
 	int8 GetSpellSlot(int32 spell_id);
-	void				AddTitle(int32 title_id, const char *name, int8 prefix, bool save_needed = false);
+	void				AddTitle(sint32 title_id, const char *name, int8 prefix, bool save_needed = false);
 	void				AddAAEntry(int16 template_id, int8 tab_id, int32 aa_id, int16 order, int8 treeid);
 	PlayerTitlesList*	GetPlayerTitles() { return &player_titles_list; }
 	void				AddLanguage(int32 id, const char *name, bool save_needed = false);

+ 61 - 19
EQ2/source/WorldServer/Titles.cpp

@@ -41,6 +41,7 @@ Title::~Title(){
 }
 
 MasterTitlesList::MasterTitlesList(){
+	MMasterTitleMutex.SetName("MasterTitlesList::MMasterTitleMutex");
 }
 
 MasterTitlesList::~MasterTitlesList(){
@@ -48,32 +49,46 @@ MasterTitlesList::~MasterTitlesList(){
 }
 
 void MasterTitlesList::Clear(){
-	map<int32, Title*>::iterator itr;
+	MMasterTitleMutex.writelock();
+	map<sint32, Title*>::iterator itr;
 	for(itr = titles_list.begin(); itr != titles_list.end(); itr++)
 		safe_delete(itr->second);
 	titles_list.clear();
+	MMasterTitleMutex.releasewritelock();
 }
 
 void MasterTitlesList::AddTitle(Title* title){
 	assert(title);
+	MMasterTitleMutex.writelock();
 	if(titles_list.count(title->GetID()) == 0)
 		titles_list[title->GetID()] = title;
+	MMasterTitleMutex.releasewritelock();
 }
 
 int32 MasterTitlesList::Size(){
-	return titles_list.size();
+	int32 size = 0;
+	MMasterTitleMutex.readlock();
+	size = titles_list.size();
+	MMasterTitleMutex.releasereadlock();
+
+	return size;
 }
 
-Title* MasterTitlesList::GetTitle(int32 id){
+Title* MasterTitlesList::GetTitle(sint32 id){
+	Title* title = 0;
+
+	MMasterTitleMutex.readlock();
 	if(titles_list.count(id) > 0)
-		return titles_list[id];
-	else
-		return 0;
+		title = titles_list[id];
+	MMasterTitleMutex.releasereadlock();
+
+	return title;
 }
 
 Title* MasterTitlesList::GetTitleByName(const char* title_name){
 	Title* title = 0;
-	map<int32, Title*>::iterator itr;
+	map<sint32, Title*>::iterator itr;
+	MMasterTitleMutex.readlock();
 	for(itr = titles_list.begin(); itr != titles_list.end(); itr++){
 		Title* current_title = itr->second;
 		if(::ToLower(string(current_title->GetName())) == ::ToLower(string(title_name))){
@@ -81,40 +96,67 @@ Title* MasterTitlesList::GetTitleByName(const char* title_name){
 			break;
 		}
 	}
+	MMasterTitleMutex.releasereadlock();
 	return title;
 }
 
-map<int32, Title*>* MasterTitlesList::GetAllTitles(){
-	return &titles_list;
-}
-
 PlayerTitlesList::PlayerTitlesList(){
+	MPlayerTitleMutex.SetName("PlayerTitlesList::MPlayerTitleMutex");
 }
 
 PlayerTitlesList::~PlayerTitlesList(){
-	list<Title*>::iterator itr;
+	MPlayerTitleMutex.writelock();
+	vector<Title*>::iterator itr;
 	for (itr = player_titles_list.begin(); itr != player_titles_list.end(); itr++)
 		safe_delete(*itr);
+
+	player_titles_list.clear();
+	MPlayerTitleMutex.releasewritelock();
 }
 
-Title* PlayerTitlesList::GetTitle(int32 index){
-	list<Title*>::iterator itr;
+Title* PlayerTitlesList::GetTitle(sint32 index){
+	MPlayerTitleMutex.readlock();
 	Title* title = 0;
 	Title* ret = 0;
+	if ( index < player_titles_list.size() )
+		ret = player_titles_list[index];
+	
+	MPlayerTitleMutex.releasereadlock();
+	return ret;
+}
+
+Title* PlayerTitlesList::GetTitleByName(const char* title_name){
+	Title* resTitle = 0;
+	vector<Title*>::iterator itr;
+	MPlayerTitleMutex.readlock();
 	for(itr = player_titles_list.begin(); itr != player_titles_list.end(); itr++){
-		title = *itr;
-		if(title->GetID() == index){
-			ret = title;
+		Title* title = *itr;
+		if(::ToLower(string(title->GetName())) == ::ToLower(string(title_name))){
+			resTitle = title;
 			break;
 		}
 	}
-	return ret;
+	MPlayerTitleMutex.releasereadlock();
+	return resTitle;
 }
 
-list<Title*>* PlayerTitlesList::GetAllTitles(){
+
+vector<Title*>* PlayerTitlesList::GetAllTitles(){
+	MPlayerTitleMutex.readlock();
 	return &player_titles_list;
 }
 
 void PlayerTitlesList::Add(Title* title){
+	MPlayerTitleMutex.writelock();
 	player_titles_list.push_back(title);
+	MPlayerTitleMutex.releasewritelock();
+}
+
+int32 PlayerTitlesList::Size(){
+	int32 size = 0;
+	MPlayerTitleMutex.readlock();
+	size = player_titles_list.size();
+	MPlayerTitleMutex.releasereadlock();
+
+	return size;
 }

+ 14 - 9
EQ2/source/WorldServer/Titles.h

@@ -22,7 +22,7 @@
 
 #include <string>
 #include <map>
-#include <list>
+#include <vector>
 #include "../common/Mutex.h"
 #include "../common/types.h"
 
@@ -38,13 +38,13 @@ public:
 	void			SetPrefix(int8 prefix) {this->prefix = prefix;}
 	void			SetSaveNeeded(bool save_needed) {this->save_needed = save_needed;}
 
-	int32			GetID() {return id;}
+	sint32			GetID() {return id;}
 	const char*		GetName() {return name;}
 	int8			GetPrefix() {return prefix;}
 	bool			GetSaveNeeded() {return save_needed;}
 	
 private:
-	int32	id;
+	sint32	id;
 	int8	prefix;
 	char	name[256];
 	bool	save_needed;
@@ -57,22 +57,27 @@ public:
 	void Clear();
 	int32 Size();
 	void AddTitle(Title* title);
-	Title* GetTitle(int32 id);
+	Title* GetTitle(sint32 id);
 	Title* GetTitleByName(const char* title_name);
-	map<int32, Title*>* GetAllTitles();
 
 private:
-	map<int32,Title*> titles_list;
+	map<sint32,Title*> titles_list;
+	Mutex MMasterTitleMutex;
 };
 
 class PlayerTitlesList {
 public:
 	PlayerTitlesList();
 	~PlayerTitlesList();
-	Title* GetTitle(int32 index);
-	list<Title*>* GetAllTitles();
+	Title* GetTitle(sint32 index);
+	Title* GetTitleByName(const char* title_name);
+	vector<Title*>* GetAllTitles();
 	void Add(Title* title);
+
+	int32 Size();
+	void ReleaseReadLock() { MPlayerTitleMutex.releasereadlock(); }
 private:
-	list<Title*> player_titles_list;
+	vector<Title*> player_titles_list;
+	Mutex MPlayerTitleMutex;
 };
 #endif

+ 79 - 18
EQ2/source/WorldServer/WorldDatabase.cpp

@@ -2564,7 +2564,7 @@ void WorldDatabase::LoadZoneInfo(ZoneServer* zone){
 void WorldDatabase::LoadZoneInfo(ZoneInfo* zone_info) {
 	Query query;
 	int32 ruleset_id;
-	MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT name, file, description, underworld, safe_x, safe_y, safe_z, min_status, min_level, max_level, instance_type, shutdown_timer, zone_motd, default_reenter_time, default_reset_time, default_lockout_time, force_group_to_zone, lua_script, xp_modifier, ruleset_id, expansion_id, always_loaded, city_zone, start_zone, zone_type, weather_allowed FROM zones WHERE id = %u", zone_info->id);
+	MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT name, file, description, underworld, safe_x, safe_y, safe_z, min_status, min_level, max_level, instance_type, shutdown_timer, zone_motd, default_reenter_time, default_reset_time, default_lockout_time, force_group_to_zone, lua_script, xp_modifier, ruleset_id, expansion_id, always_loaded, city_zone, start_zone, zone_type, weather_allowed, sky_file FROM zones WHERE id = %u", zone_info->id);
 	if (result && mysql_num_rows(result) > 0) {
 		MYSQL_ROW row;
 		row = mysql_fetch_row(result);
@@ -2598,6 +2598,8 @@ void WorldDatabase::LoadZoneInfo(ZoneInfo* zone_info) {
 		zone_info->start_zone		= atoi(row[23]);
 		row[24] == NULL ? strncpy(zone_info->zone_type, "", sizeof(zone_info->zone_type)) : strncpy(zone_info->zone_type, row[24], sizeof(zone_info->zone_type));
 		zone_info->weather_allowed	= atoi(row[25]);
+
+		strncpy(zone_info->sky_file, row[26], sizeof(zone_info->sky_file));
 	}
 }
 
@@ -5784,31 +5786,67 @@ bool WorldDatabase::CheckBannedIPs(const char* loginIP)
  	return false;
 }
 
+sint32 WorldDatabase::AddMasterTitle(const char* titleName, int8 isPrefix)
+{
+	if(titleName == nullptr || strlen(titleName) < 1)
+	{
+		LogWrite(DATABASE__ERROR, 0, "DBNew", "AddMasterTitle called with missing titleName");
+		return -1;
+	}
+
+	Query query;
+	Title* title = master_titles_list.GetTitleByName(titleName);
+	
+	if(title)
+		return title->GetID();
+
+	query.RunQuery2(Q_INSERT, "INSERT INTO titles set title='%s', prefix=%u", titleName, isPrefix);
+	if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF){
+		LogWrite(DATABASE__ERROR, 0, "Database", "Error in AddMasterTitle query '%s': %s", query.GetQuery(), query.GetError());
+		return false;
+	}
+
+	int32 last_insert_id = query.GetLastInsertedID();
+	if(last_insert_id > 0)
+	{
+		title = new Title;
+		title->SetID(last_insert_id);
+		title->SetName(titleName);
+		title->SetPrefix(isPrefix);
+		master_titles_list.AddTitle(title);
+		return (sint32)last_insert_id;
+	}
+
+
+	return -1;
+}
+
 void WorldDatabase::LoadTitles(){
-	int32 index = 0;
 	Query query;
 	MYSQL_ROW row;
+	int32 count = 0;
 	MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT id, title, prefix FROM titles");
 	if(result && mysql_num_rows(result) > 0){
 		Title* title = 0;
 		while(result && (row = mysql_fetch_row(result))){
-			LogWrite(WORLD__DEBUG, 5, "World", "\tLoading Title '%s' (%u), Prefix: %i, Index: %u", row[1], atoul(row[0]), atoi(row[2]), index);
+			sint32 idx = atoi(row[0]);
+			LogWrite(WORLD__DEBUG, 5, "World", "\tLoading Title '%s' (%u), Prefix: %i, Index: %u", row[1], idx, atoi(row[2]), count);
 			title = new Title;
-			title->SetID(index);
+			title->SetID(idx);
 			title->SetName(row[1]);
 			title->SetPrefix(atoi(row[2]));
 			master_titles_list.AddTitle(title);
-			index++;
+			count++;
 		}
 	}
-	LogWrite(WORLD__DEBUG, 0, "World", "\tLoaded %u Title%s", index, index == 1 ? "" : "s");
+	LogWrite(WORLD__DEBUG, 0, "World", "\tLoaded %u Title%s", count, count == 1 ? "" : "s");
 }
 
-int32 WorldDatabase::LoadCharacterTitles(int32 char_id, Player *player){
+sint32 WorldDatabase::LoadCharacterTitles(int32 char_id, Player *player){
 	LogWrite(WORLD__DEBUG, 0, "World", "Loading Titles for player '%s'...", player->GetName());
 	Query query;
 	MYSQL_ROW row;
-	int32 index = 0;
+	sint32 index = 0;
 	MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT title_id, title, prefix FROM character_titles, titles WHERE character_titles.title_id = titles.id AND character_titles.char_id = %u", char_id);
 	if(result && mysql_num_rows(result) > 0){
 		while(result && (row = mysql_fetch_row(result))){
@@ -5820,11 +5858,11 @@ int32 WorldDatabase::LoadCharacterTitles(int32 char_id, Player *player){
 	return index;
 }
 
-sint16 WorldDatabase::GetCharPrefixIndex(int32 char_id, Player *player){
+sint32 WorldDatabase::GetCharPrefixIndex(int32 char_id, Player *player){
 	LogWrite(PLAYER__DEBUG, 0, "Player", "Getting current title index for player '%s'...", player->GetName());
 	Query query;
 	MYSQL_ROW row;
-	sint16 ret = -1;
+	sint32 ret = 0;
 	MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT prefix_title FROM character_details WHERE char_id = %u", char_id);
 	if(result && mysql_num_rows(result) > 0)
 		while(result && (row = mysql_fetch_row(result))){
@@ -5834,11 +5872,11 @@ sint16 WorldDatabase::GetCharPrefixIndex(int32 char_id, Player *player){
 	return ret;
 }
 
-sint16 WorldDatabase::GetCharSuffixIndex(int32 char_id, Player *player){
+sint32 WorldDatabase::GetCharSuffixIndex(int32 char_id, Player *player){
 	LogWrite(PLAYER__DEBUG, 0, "Player", "Getting current title index for player '%s'...", player->GetName());
 	Query query;
 	MYSQL_ROW row;
-	sint16 ret = -1;
+	sint32 ret = 0;
 	MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT suffix_title FROM character_details WHERE char_id = %u", char_id);
 	if(result && mysql_num_rows(result) > 0)
 		while(result && (row = mysql_fetch_row(result))){
@@ -5848,16 +5886,39 @@ sint16 WorldDatabase::GetCharSuffixIndex(int32 char_id, Player *player){
 	return ret;
 }
 
-void WorldDatabase::SaveCharPrefixIndex(sint16 index, int32 char_id, Client *client){
+void WorldDatabase::SaveCharPrefixIndex(sint32 index, int32 char_id){
 	Query query;
-	query.RunQuery2(Q_UPDATE, "UPDATE character_details SET prefix_title = %i WHERE char_id = %u", index, client->GetCharacterID());
-	LogWrite(PLAYER__DEBUG, 0, "Player", "Saving Prefix Index %i for player '%s'...", index, client->GetPlayer()->GetName());
+	query.RunQuery2(Q_UPDATE, "UPDATE character_details SET prefix_title = %i WHERE char_id = %u", index, char_id);
+	LogWrite(PLAYER__DEBUG, 0, "Player", "Saving Prefix Index %i for character id '%u'...", index, char_id);
 }
 
-void WorldDatabase::SaveCharSuffixIndex(sint16 index, int32 char_id, Client *client){
+void WorldDatabase::SaveCharSuffixIndex(sint32 index, int32 char_id){
 	Query query;
-	query.RunQuery2(Q_SELECT, "UPDATE character_details SET suffix_title = %i WHERE char_id = %u", index, client->GetCharacterID());
-	LogWrite(PLAYER__DEBUG, 0, "Player", "Saving Suffix Index %i for player '%s'...", index, client->GetPlayer()->GetName());
+	query.RunQuery2(Q_SELECT, "UPDATE character_details SET suffix_title = %i WHERE char_id = %u", index, char_id);
+	LogWrite(PLAYER__DEBUG, 0, "Player", "Saving Suffix Index %i for character id %u...", index, char_id);
+}
+
+sint32 WorldDatabase::AddCharacterTitle(sint32 index, int32 char_id, Spawn* player) {
+	if(!player || !player->IsPlayer())
+	{
+		LogWrite(DATABASE__ERROR, 0, "DBNew", "AddCharacterTitle spawn is not a player: %s", player ? player->GetName() : "Unset");
+		return -1;
+	}
+
+	Title* title = master_titles_list.GetTitle(index);
+	if(!title)
+	{
+		LogWrite(DATABASE__ERROR, 0, "DBNew", "AddCharacterTitle title index %u missing from master_titles_list for player: %s (%u)", index, player ? player->GetName() : "Unset", char_id);
+		return -1;
+	}
+
+	Query query;
+	LogWrite(PLAYER__DEBUG, 0, "Player", "Adding titles for char_id: %u, index: %i", char_id, index);
+	query.RunQuery2(Q_INSERT, "INSERT IGNORE INTO character_titles (char_id, title_id) VALUES (%u, %i)", char_id, index);
+	sint32 curIndex = (sint32)((Player*)player)->GetPlayerTitles()->Size();
+	((Player*)player)->AddTitle(curIndex++, title->GetName(), title->GetPrefix(), title->GetSaveNeeded());
+
+	return curIndex;
 }
 
 void WorldDatabase::LoadLanguages()

+ 7 - 5
EQ2/source/WorldServer/WorldDatabase.h

@@ -477,12 +477,14 @@ public:
 	void LoadRuleSetDetails(RuleSet *rule_set);
 
 	/* Titles */
+	sint32				AddMasterTitle(const char* titleName, int8 isPrefix = 0);
 	void				LoadTitles();
-	int32				LoadCharacterTitles(int32 char_id, Player *player);
-	sint16				GetCharPrefixIndex(int32 char_id, Player *player);
-	sint16				GetCharSuffixIndex(int32 char_id, Player *player);
-	void				SaveCharPrefixIndex(sint16 index, int32 char_id, Client *client);
-	void				SaveCharSuffixIndex(sint16 index, int32 char_id, Client *client);
+	sint32				LoadCharacterTitles(int32 char_id, Player *player);
+	sint32				GetCharPrefixIndex(int32 char_id, Player *player);
+	sint32				GetCharSuffixIndex(int32 char_id, Player *player);
+	void				SaveCharPrefixIndex(sint32 index, int32 char_id);
+	void				SaveCharSuffixIndex(sint32 index, int32 char_id);
+	sint32				AddCharacterTitle(sint32 index, int32 char_id, Spawn* player);
 
 	/* Languages */
 	void				LoadLanguages();

+ 12 - 8
EQ2/source/WorldServer/client.cpp

@@ -8864,12 +8864,13 @@ void Client::ShowRecipeBook() {
 }
 
 void Client::SendTitleUpdate() {
-	list<Title*>* titles = player->GetPlayerTitles()->GetAllTitles();
-	list<Title*>::iterator itr;
+	// must call release read lock before leaving function on GetPlayerTitles
+	vector<Title*>* titles = player->GetPlayerTitles()->GetAllTitles();
+	vector<Title*>::iterator itr;
 	Title* title;
-	int16 i = 0;
-	sint16 prefix_index = database.GetCharPrefixIndex(GetCharacterID(), player);
-	sint16 suffix_index = database.GetCharSuffixIndex(GetCharacterID(), player);
+	sint32 i = 0;
+	sint32 prefix_index = database.GetCharPrefixIndex(GetCharacterID(), player);
+	sint32 suffix_index = database.GetCharSuffixIndex(GetCharacterID(), player);
 	PacketStruct* packet = configReader.getStruct("WS_TitleUpdate", GetVersion());
 	if (packet) {
 		packet->setArrayLengthByName("num_titles", titles->size());
@@ -8891,20 +8892,23 @@ void Client::SendTitleUpdate() {
 		safe_delete(packet);
 		SendUpdateTitles(prefix_index, suffix_index);
 	}
+	player->GetPlayerTitles()->ReleaseReadLock();
 }
 
-void Client::SendUpdateTitles(sint16 prefix, sint16 suffix) {
+void Client::SendUpdateTitles(sint32 prefix, sint32 suffix) {
 	Title* suffix_title = 0;
 	Title* prefix_title = 0;
 	if (suffix != -1) {
 		suffix_title = player->GetPlayerTitles()->GetTitle(suffix);
-		strcpy(player->appearance.suffix_title, suffix_title->GetName());
+		if(suffix_title)
+			strcpy(player->appearance.suffix_title, suffix_title->GetName());
 	}
 	else
 		memset(player->appearance.suffix_title, 0, strlen(player->appearance.suffix_title));
 	if (prefix != -1) {
 		prefix_title = player->GetPlayerTitles()->GetTitle(prefix);
-		strcpy(player->appearance.prefix_title, prefix_title->GetName());
+		if(prefix_title)
+			strcpy(player->appearance.prefix_title, prefix_title->GetName());
 	}
 	else
 		memset(player->appearance.prefix_title, 0, strlen(player->appearance.prefix_title));

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

@@ -358,7 +358,7 @@ public:
 	void	AcceptCollectionRewards(Collection *collection, int32 selectable_item_id = 0);
 	void	SendRecipeList();
 	void	SendTitleUpdate();
-	void	SendUpdateTitles(sint16 prefix, sint16 suffix);
+	void	SendUpdateTitles(sint32 prefix, sint32 suffix);
 	void	SendLanguagesUpdate(int32 id);
 	void	SendAchievementsList();
 	void	SendAchievementUpdate(bool first_login = false);