Browse Source

Support for starting skills/spells added after char created

Fix #165 - support adding starting skills/spells to characters after their initial creation

/reload startabilities added

insert into commands set type=1,command='reload',subcommand='startabilities',handler=522,required_status=100;
image 3 years ago
parent
commit
7fb275ee44

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

@@ -1088,6 +1088,7 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 		client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/reload locations");
 		client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/reload rules");
 		client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/reload transporters");
+		client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/reload startabilities");
 		break;
 	}
 	case COMMAND_RELOADSTRUCTS: {
@@ -1180,6 +1181,13 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 		client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Done!");
 		break;
 	}
+	case COMMAND_RELOAD_STARTABILITIES: {
+		client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Reloading Starting Skills/Spells...");
+		world.PurgeStartingLists();
+		world.LoadStartingLists();
+		client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Done!");
+		break;
+	}
 	case COMMAND_READ: {
 		if (sep && sep->arg[1][0] && sep->IsNumber(1)) {
 			if (strcmp(sep->arg[0], "read") == 0) {

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

@@ -898,6 +898,7 @@ private:
 
 #define COMMAND_RELOAD_RULES			519
 #define COMMAND_RELOAD_TRANSPORTERS		520
+#define COMMAND_RELOAD_STARTABILITIES	522
 
 #define COMMAND_FINDSPAWN				521
 

+ 125 - 0
EQ2/source/WorldServer/World.cpp

@@ -125,6 +125,8 @@ World::~World(){
 	m_houseZones.clear();
 
 	tov_itemstat_conversion.clear();
+
+	PurgeStartingLists();
 }
 
 void World::init(){
@@ -154,6 +156,8 @@ void World::init(){
 	database.LoadVisualStates();
 	LogWrite(WORLD__DEBUG, 1, "World", "-Load `visual states` complete!");
 
+	LoadStartingLists();
+
 	LogWrite(WORLD__DEBUG, 1, "World", "-Setting system parameters...");
 	Variable* var = variables.FindVariable("gametime");
 	const char* time_string = 0;
@@ -2129,3 +2133,124 @@ int64 World::GetThreadUsageCPUTime(){
 #else
 
 #endif
+
+
+void World::SyncCharacterAbilities(Client* client)
+{
+	MStartingLists.readlock();
+
+	int8 baseClass = classes.GetBaseClass(client->GetPlayer()->GetAdventureClass());
+	int8 secondaryClass = classes.GetSecondaryBaseClass(client->GetPlayer()->GetAdventureClass());
+	int8 actualClass = client->GetPlayer()->GetAdventureClass();
+	int8 baseRace = client->GetPlayer()->GetRace();
+
+	bool reloadSpells = false;
+
+	map<int8, map<int8, StartingSkill>*>::iterator skill_itr = starting_skills.begin();
+	map<int8, map<int8, StartingSpell>*>::iterator spell_itr = starting_spells.begin();
+	bool isComplete = false;
+	do
+	{
+		bool isProcessing = false;
+		if (skill_itr != starting_skills.end())
+		{
+			isProcessing = true;
+
+			if ((skill_itr->first & baseRace) == baseRace)
+			{
+				map<int8, StartingSkill>::iterator child_itr;
+				for (child_itr = skill_itr->second->begin(); child_itr != skill_itr->second->end(); child_itr++)
+				{
+					if ((skill_itr->first & baseClass) == baseClass ||
+						(skill_itr->first & secondaryClass) == secondaryClass ||
+						(skill_itr->first & actualClass) == actualClass)
+					{
+						if (!client->GetPlayer()->skill_list.HasSkill(child_itr->second.skill_id))
+						{
+							Query query;
+							LogWrite(PLAYER__DEBUG, 0, "Player", "Adding skill %i for race: %i, class: %i for char_id: %u", child_itr->second.skill_id, baseRace, baseClass, client->GetCharacterID());
+							query.AddQueryAsync(client->GetCharacterID(), &database, Q_INSERT, "INSERT IGNORE INTO character_skills (char_id, skill_id, current_val, max_val) VALUES (%u, %u, %u, %u)",
+								client->GetCharacterID(), child_itr->second.skill_id, child_itr->second.current_val, child_itr->second.max_val);
+
+							client->GetPlayer()->AddSkill(child_itr->second.skill_id, child_itr->second.current_val, child_itr->second.max_val);
+						}
+					}
+				}
+			}
+			skill_itr++;
+		}
+
+		if (spell_itr != starting_spells.end())
+		{
+			isProcessing = true;
+
+			if ((spell_itr->first & baseRace) == baseRace)
+			{
+				map<int8, StartingSpell>::iterator child_itr;
+				for (child_itr = spell_itr->second->begin(); child_itr != spell_itr->second->end(); child_itr++)
+				{
+					if ((spell_itr->first & baseClass) == baseClass ||
+						(spell_itr->first & secondaryClass) == secondaryClass ||
+						(spell_itr->first & actualClass) == actualClass)
+					{
+						if (!client->GetPlayer()->HasSpell(child_itr->second.spell_id, child_itr->second.tier, true))
+						{
+							Query query;
+							LogWrite(PLAYER__DEBUG, 0, "Player", "Adding spell %i for race: %i, class: %i for char_id: %u", child_itr->second.spell_id, baseRace, baseClass, client->GetCharacterID());
+							query.AddQueryAsync(client->GetCharacterID(), &database, Q_INSERT, "INSERT IGNORE INTO character_spells (char_id, spell_id, tier, knowledge_slot) VALUES (%u, %u, %u, %u)",
+								client->GetCharacterID(), child_itr->second.spell_id, child_itr->second.tier, child_itr->second.knowledge_slot);
+
+							// reload spells, we don't know the spellbook or timer info
+							reloadSpells = true;
+						}
+					}
+				}
+			}
+			spell_itr++;
+		}
+
+		if (!isProcessing)
+			isComplete = true;
+
+	} while (!isComplete);
+
+	MStartingLists.releasereadlock();
+
+	if (reloadSpells)
+		database.LoadCharacterSpells(client->GetCharacterID(), client->GetPlayer());
+}
+
+void World::LoadStartingLists()
+{
+	LogWrite(WORLD__DEBUG, 1, "World", "-Loading `starting_skills`...");
+	database.LoadStartingSkills(this);
+
+	LogWrite(WORLD__DEBUG, 1, "World", "-Loading `starting_spells`...");
+	database.LoadStartingSpells(this);
+}
+
+void World::PurgeStartingLists()
+{
+	MStartingLists.writelock();
+
+	map<int8, map<int8, StartingSkill>*>::iterator skill_itr;
+
+	for (skill_itr = starting_skills.begin(); skill_itr != starting_skills.end(); skill_itr++)
+	{
+		map<int8, StartingSkill>* tmpMap = skill_itr->second;
+		safe_delete(tmpMap);
+	}
+	starting_skills.clear();
+
+
+	map<int8, map<int8, StartingSpell>*>::iterator spell_itr;
+
+	for (spell_itr = starting_spells.begin(); spell_itr != starting_spells.end(); spell_itr++)
+	{
+		map<int8, StartingSpell>* tmpMap = spell_itr->second;
+		safe_delete(tmpMap);
+	}
+	starting_spells.clear();
+
+	MStartingLists.releasewritelock();
+}

+ 33 - 0
EQ2/source/WorldServer/World.h

@@ -368,6 +368,31 @@ struct GlobalLoot {
 #define TRANSPORT_TYPE_GENERIC		2
 #define TRANSPORT_TYPE_FLIGHT		3
 
+
+// structs MUST start with class_id and race_id, in that order as int8's
+struct StartingStructHeader
+{
+	int8 class_id;
+	int8 race_id;
+};
+
+struct StartingSkill
+{
+	StartingStructHeader header;
+	int32 skill_id;
+	int16 current_val;
+	int16 max_val;
+	int32 progress; // what is this for..?
+};
+
+struct StartingSpell
+{
+	StartingStructHeader header;
+	int32 spell_id;
+	int8 tier;
+	int32 knowledge_slot;
+};
+
 class ZoneList {
 	public:
 	ZoneList();
@@ -578,6 +603,14 @@ public:
 	PlayerGroupManager* GetGroupManager() { return &m_playerGroupManager; }
 
 	bool CheckTempBugCRC(char* msg);
+
+	void SyncCharacterAbilities(Client* client);
+
+	void LoadStartingLists();
+	void PurgeStartingLists();
+	map<int8, map<int8, StartingSkill>*> starting_skills;
+	map<int8, map<int8, StartingSpell>*> starting_spells;
+	Mutex MStartingLists;
 private:
 	//void RemovePlayerFromGroup(PlayerGroup* group, GroupMemberInfo* info, bool erase = true);
 	//void DeleteGroupMember(GroupMemberInfo* info);

+ 99 - 3
EQ2/source/WorldServer/WorldDatabase.cpp

@@ -191,10 +191,16 @@ int32 WorldDatabase::LoadCharacterSpells(int32 char_id, Player* player)
 			if(new_spell_id == old_spell_id)
 				continue;
 
+			old_spell_id = new_spell_id;
+
 			LogWrite(SPELL__DEBUG, 5, "Spells", "\tLoading SpellID: %u, tier: %i, slot: %i, type: %u linked_timer_id: %u", new_spell_id, atoi(row[1]), atoi(row[2]), atoul(row[3]), atoul(row[4]));
 
-			player->AddSpellBookEntry(new_spell_id, atoi(row[1]), atoi(row[2]), atoul(row[3]), atoul(row[4]));
-			old_spell_id = new_spell_id;
+			int8 tier = atoi(row[1]);
+
+			if (player->HasSpell(new_spell_id, tier, true))
+				continue;
+
+			player->AddSpellBookEntry(new_spell_id, tier, atoi(row[2]), atoul(row[3]), atoul(row[4]));
 		}
 	}
 
@@ -6857,4 +6863,94 @@ int32 WorldDatabase::FindHouseInstanceSpawn(Spawn* spawn)
 	}
 
 	return 0;
-}
+}
+
+
+void WorldDatabase::LoadStartingSkills(World* world)
+{
+	world->MStartingLists.writelock();
+
+	int32 total = 0;
+	Query query;
+	MYSQL_ROW row;
+	MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT race_id, class_id, skill_id, current_val, max_val, progress FROM starting_skills");
+
+	if (result)
+	{
+		if (mysql_num_rows(result) > 0)
+		{
+			Skill* skill = 0;
+
+			while (result && (row = mysql_fetch_row(result)))
+			{
+
+				StartingSkill skill;
+				skill.header.race_id = atoul(row[0]);
+				skill.header.class_id = atoul(row[1]);
+				skill.skill_id = atoul(row[2]);
+				skill.current_val = atoul(row[3]);
+				skill.max_val = atoul(row[4]);
+
+				if (!world->starting_skills.count(skill.header.race_id))
+				{
+					map<int8, StartingSkill>* skills = new map<int8, StartingSkill>();
+					skills->insert(make_pair(skill.header.class_id, skill));
+					world->starting_skills.insert(make_pair(skill.header.race_id, skills));
+				}
+				else
+				{
+					world->starting_skills[skill.header.race_id]->insert(make_pair(skill.header.class_id, skill));
+				}
+				total++;
+			}
+		}
+	}
+	LogWrite(WORLD__DEBUG, 3, "World", "--Loaded %u Starting Skill(s)", total);
+
+	world->MStartingLists.releasewritelock();
+}
+
+
+void WorldDatabase::LoadStartingSpells(World* world)
+{
+	world->MStartingLists.writelock();
+
+	int32 total = 0;
+	Query query;
+	MYSQL_ROW row;
+	MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT race_id, class_id, spell_id, tier, knowledge_slot FROM starting_spells");
+
+	if (result)
+	{
+		if (mysql_num_rows(result) > 0)
+		{
+			Skill* skill = 0;
+
+			while (result && (row = mysql_fetch_row(result)))
+			{
+
+				StartingSpell spell;
+				spell.header.race_id = atoul(row[0]);
+				spell.header.class_id = atoul(row[1]);
+				spell.spell_id = atoul(row[2]);
+				spell.tier = atoul(row[3]);
+				spell.knowledge_slot = atoul(row[4]);
+
+				if (!world->starting_spells.count(spell.header.race_id))
+				{
+					map<int8, StartingSpell>* spells = new map<int8, StartingSpell>();
+					spells->insert(make_pair(spell.header.class_id, spell));
+					world->starting_spells.insert(make_pair(spell.header.race_id, spells));
+				}
+				else
+				{
+					world->starting_spells[spell.header.race_id]->insert(make_pair(spell.header.class_id, spell));
+				}
+				total++;
+			}
+		}
+	}
+	LogWrite(WORLD__DEBUG, 3, "World", "--Loaded %u Starting Spell(s)", total);
+
+	world->MStartingLists.releasewritelock();
+}

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

@@ -586,6 +586,11 @@ public:
 	bool				CheckHolidayFlags(ZoneServer* zone, int32 spawnHolidayFlag);
 	void				GetHouseSpawnInstanceData(ZoneServer* zone, Spawn* spawn);
 	int32				FindHouseInstanceSpawn(Spawn* spawn);
+
+	/* Starting Character Abilities */
+
+	void				LoadStartingSkills(World* world);
+	void				LoadStartingSpells(World* world);
 private:
 	DatabaseNew			database_new;
 	map<int32, string>	zone_names;