[Bug Fix] Fix Bot/Character ID Overlap in Groups (#4093)

* [Bug Fix] Fix Bot/Character ID Overlap in Groups

- Attempt to fix bot/character ID overlap in groups keeping bots with the same unique identifier as players from not spawning on zone.
- Adds `bot_id` to `group_id` to differentiate bots from characters and hopefully alleviate this issue.

* Update base_group_id_repository.h

* Final push
This commit is contained in:
Alex King 2024-03-23 18:55:03 -04:00 committed by GitHub
parent abdec39cdd
commit 21cec87ac4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 361 additions and 281 deletions

View File

@ -35,6 +35,8 @@
#include "../common/repositories/character_languages_repository.h" #include "../common/repositories/character_languages_repository.h"
#include "../common/repositories/character_leadership_abilities_repository.h" #include "../common/repositories/character_leadership_abilities_repository.h"
#include "../common/repositories/character_skills_repository.h" #include "../common/repositories/character_skills_repository.h"
#include "../common/repositories/group_id_repository.h"
#include "../common/repositories/group_leaders_repository.h"
// Disgrace: for windows compile // Disgrace: for windows compile
#ifdef _WINDOWS #ifdef _WINDOWS
@ -1280,24 +1282,6 @@ void Database::AddReport(std::string who, std::string against, std::string lines
safe_delete_array(escape_str); safe_delete_array(escape_str);
} }
void Database::SetGroupID(const char* name, uint32 id, uint32 charid, uint32 ismerc) {
std::string query;
if (id == 0) {
// removing from group
query = StringFormat("delete from group_id where charid=%i and name='%s' and ismerc=%i",charid, name, ismerc);
auto results = QueryDatabase(query);
if (!results.Success())
LogError("Error deleting character from group id: {}", results.ErrorMessage().c_str());
return;
}
/* Add to the Group */
query = StringFormat("REPLACE INTO `group_id` SET `charid` = %i, `groupid` = %i, `name` = '%s', `ismerc` = '%i'", charid, id, name, ismerc);
QueryDatabase(query);
}
void Database::ClearAllGroups(void) void Database::ClearAllGroups(void)
{ {
std::string query("DELETE FROM `group_id`"); std::string query("DELETE FROM `group_id`");
@ -1305,71 +1289,65 @@ void Database::ClearAllGroups(void)
return; return;
} }
void Database::ClearGroup(uint32 gid) { void Database::ClearGroup(uint32 group_id) {
ClearGroupLeader(gid); ClearGroupLeader(group_id);
if(gid == 0) if (!group_id) {
{
//clear all groups //clear all groups
ClearAllGroups(); ClearAllGroups();
return; return;
} }
//clear a specific group //clear a specific group
std::string query = StringFormat("delete from group_id where groupid = %lu", (unsigned long)gid); GroupIdRepository::DeleteWhere(
QueryDatabase(query); *this,
} fmt::format(
"`group_id` = {}",
uint32 Database::GetGroupID(const char* name){
std::string query = StringFormat("SELECT groupid from group_id where name='%s'", name);
auto results = QueryDatabase(query);
if (!results.Success()) {
return 0;
}
if (results.RowCount() == 0)
{
// Commenting this out until logging levels can prevent this from going to console
//LogDebug(, "Character not in a group: [{}]", name);
return 0;
}
auto row = results.begin();
return Strings::ToUnsignedInt(row[0]);
}
std::string Database::GetGroupLeaderForLogin(std::string character_name) {
uint32 group_id = 0;
auto query = fmt::format(
"SELECT `groupid` FROM `group_id` WHERE `name` = '{}'",
character_name
);
auto results = QueryDatabase(query);
if (results.Success() && results.RowCount()) {
auto row = results.begin();
group_id = Strings::ToUnsignedInt(row[0]);
}
if (!group_id) {
return std::string();
}
query = fmt::format(
"SELECT `leadername` FROM `group_leaders` WHERE `gid` = {} LIMIT 1",
group_id group_id
)
); );
results = QueryDatabase(query); }
if (results.Success() && results.RowCount()) { uint32 Database::GetGroupID(const std::string& name)
auto row = results.begin(); {
return row[0]; const auto& l = GroupIdRepository::GetWhere(
*this,
fmt::format(
"`name` = '{}'",
Strings::Escape(name)
)
);
if (l.empty()) {
return 0;
} }
auto e = l.front();
return e.group_id;
}
std::string Database::GetGroupLeaderForLogin(const std::string& character_name)
{
const auto& g = GroupIdRepository::GetWhere(
*this,
fmt::format(
"`name` = '{}'",
Strings::Escape(character_name)
)
);
if (g.empty()) {
return std::string(); return std::string();
}
auto group = g.front();
const uint32 group_id = group.group_id;
const auto& e = GroupLeadersRepository::FindOne(*this, group_id);
return e.gid ? e.leadername : std::string();
} }
void Database::SetGroupLeaderName(uint32 gid, const char* name) { void Database::SetGroupLeaderName(uint32 gid, const char* name) {

View File

@ -202,16 +202,15 @@ public:
/* Groups */ /* Groups */
std::string GetGroupLeaderForLogin(std::string character_name); std::string GetGroupLeaderForLogin(const std::string& character_name);
char* GetGroupLeadershipInfo(uint32 gid, char* leaderbuf, char* maintank = nullptr, char* assist = nullptr, char* puller = nullptr, char *marknpc = nullptr, char *mentoree = nullptr, int *mentor_percent = nullptr, GroupLeadershipAA_Struct* GLAA = nullptr); char* GetGroupLeadershipInfo(uint32 gid, char* leaderbuf, char* maintank = nullptr, char* assist = nullptr, char* puller = nullptr, char *marknpc = nullptr, char *mentoree = nullptr, int *mentor_percent = nullptr, GroupLeadershipAA_Struct* GLAA = nullptr);
std::string GetGroupLeaderName(uint32 group_id); std::string GetGroupLeaderName(uint32 group_id);
uint32 GetGroupID(const char* name); uint32 GetGroupID(const std::string& name);
void ClearGroup(uint32 gid = 0); void ClearGroup(uint32 group_id = 0);
void ClearGroupLeader(uint32 gid = 0); void ClearGroupLeader(uint32 gid = 0);
void SetGroupID(const char* name, uint32 id, uint32 charid, uint32 ismerc = false);
void SetGroupLeaderName(uint32 gid, const char* name); void SetGroupLeaderName(uint32 gid, const char* name);
/* Raids */ /* Raids */

View File

@ -5433,6 +5433,25 @@ ADD PRIMARY KEY (`id`);
ALTER TABLE `rule_values` ALTER TABLE `rule_values`
MODIFY COLUMN `rule_value` text CHARACTER SET latin1 COLLATE latin1_swedish_ci NULL DEFAULT NULL AFTER `rule_name`; MODIFY COLUMN `rule_value` text CHARACTER SET latin1 COLLATE latin1_swedish_ci NULL DEFAULT NULL AFTER `rule_name`;
)" )"
},
ManifestEntry{
.version = 9267,
.description = "2024_02_18_group_id_bot_id.sql",
.check = "SHOW COLUMNS FROM `group_id` LIKE 'bot_id'",
.condition = "empty",
.match = "",
.sql = R"(
ALTER TABLE `group_id`
CHANGE COLUMN `groupid` `group_id` int(11) UNSIGNED NOT NULL DEFAULT 0 FIRST,
CHANGE COLUMN `charid` `character_id` int(11) UNSIGNED NOT NULL DEFAULT 0 AFTER `group_id`,
CHANGE COLUMN `ismerc` `merc_id` int(11) UNSIGNED NOT NULL DEFAULT 0 AFTER `name`,
ADD COLUMN `bot_id` int(11) UNSIGNED NOT NULL DEFAULT 0 AFTER `character_id`,
MODIFY COLUMN `name` varchar(64) NOT NULL DEFAULT '' AFTER `character_id`,
DROP PRIMARY KEY,
ADD PRIMARY KEY (`group_id`, `character_id`, `bot_id`, `merc_id`) USING BTREE;
ALTER TABLE `group_id`
MODIFY COLUMN `character_id` int(11) UNSIGNED NOT NULL DEFAULT 0 AFTER `name`;
)"
} }
// -- template; copy/paste this when you need to create a new entry // -- template; copy/paste this when you need to create a new entry
// ManifestEntry{ // ManifestEntry{

View File

@ -421,20 +421,25 @@ void Database::AssignGroupToInstance(uint32 group_id, uint32 instance_id)
auto zone_id = GetInstanceZoneID(instance_id); auto zone_id = GetInstanceZoneID(instance_id);
auto version = GetInstanceVersion(instance_id); auto version = GetInstanceVersion(instance_id);
auto l = GroupIdRepository::GetWhere( const auto& l = GroupIdRepository::GetWhere(
*this, *this,
fmt::format( fmt::format(
"groupid = {}", "`group_id` = {}",
group_id group_id
) )
); );
if (l.empty()) { if (l.empty()) {
return; return;
} }
for (const auto& e : l) { for (const auto& e : l) {
if (!GetInstanceID(zone_id, e.charid, version)) { if (!e.character_id) {
AddClientToInstance(instance_id, e.charid); continue;
}
if (!GetInstanceID(zone_id, e.character_id, version)) {
AddClientToInstance(instance_id, e.character_id);
} }
} }
} }

View File

@ -19,34 +19,37 @@
class BaseGroupIdRepository { class BaseGroupIdRepository {
public: public:
struct GroupId { struct GroupId {
int32_t groupid; uint32_t group_id;
int32_t charid;
std::string name; std::string name;
int8_t ismerc; uint32_t character_id;
uint32_t bot_id;
uint32_t merc_id;
}; };
static std::string PrimaryKey() static std::string PrimaryKey()
{ {
return std::string("groupid"); return std::string("group_id");
} }
static std::vector<std::string> Columns() static std::vector<std::string> Columns()
{ {
return { return {
"groupid", "group_id",
"charid",
"name", "name",
"ismerc", "character_id",
"bot_id",
"merc_id",
}; };
} }
static std::vector<std::string> SelectColumns() static std::vector<std::string> SelectColumns()
{ {
return { return {
"groupid", "group_id",
"charid",
"name", "name",
"ismerc", "character_id",
"bot_id",
"merc_id",
}; };
} }
@ -87,10 +90,11 @@ public:
{ {
GroupId e{}; GroupId e{};
e.groupid = 0; e.group_id = 0;
e.charid = 0;
e.name = ""; e.name = "";
e.ismerc = 0; e.character_id = 0;
e.bot_id = 0;
e.merc_id = 0;
return e; return e;
} }
@ -101,7 +105,7 @@ public:
) )
{ {
for (auto &group_id : group_ids) { for (auto &group_id : group_ids) {
if (group_id.groupid == group_id_id) { if (group_id.group_id == group_id_id) {
return group_id; return group_id;
} }
} }
@ -127,10 +131,11 @@ public:
if (results.RowCount() == 1) { if (results.RowCount() == 1) {
GroupId e{}; GroupId e{};
e.groupid = row[0] ? static_cast<int32_t>(atoi(row[0])) : 0; e.group_id = row[0] ? static_cast<uint32_t>(strtoul(row[0], nullptr, 10)) : 0;
e.charid = row[1] ? static_cast<int32_t>(atoi(row[1])) : 0; e.name = row[1] ? row[1] : "";
e.name = row[2] ? row[2] : ""; e.character_id = row[2] ? static_cast<uint32_t>(strtoul(row[2], nullptr, 10)) : 0;
e.ismerc = row[3] ? static_cast<int8_t>(atoi(row[3])) : 0; e.bot_id = row[3] ? static_cast<uint32_t>(strtoul(row[3], nullptr, 10)) : 0;
e.merc_id = row[4] ? static_cast<uint32_t>(strtoul(row[4], nullptr, 10)) : 0;
return e; return e;
} }
@ -164,10 +169,11 @@ public:
auto columns = Columns(); auto columns = Columns();
v.push_back(columns[0] + " = " + std::to_string(e.groupid)); v.push_back(columns[0] + " = " + std::to_string(e.group_id));
v.push_back(columns[1] + " = " + std::to_string(e.charid)); v.push_back(columns[1] + " = '" + Strings::Escape(e.name) + "'");
v.push_back(columns[2] + " = '" + Strings::Escape(e.name) + "'"); v.push_back(columns[2] + " = " + std::to_string(e.character_id));
v.push_back(columns[3] + " = " + std::to_string(e.ismerc)); v.push_back(columns[3] + " = " + std::to_string(e.bot_id));
v.push_back(columns[4] + " = " + std::to_string(e.merc_id));
auto results = db.QueryDatabase( auto results = db.QueryDatabase(
fmt::format( fmt::format(
@ -175,7 +181,7 @@ public:
TableName(), TableName(),
Strings::Implode(", ", v), Strings::Implode(", ", v),
PrimaryKey(), PrimaryKey(),
e.groupid e.group_id
) )
); );
@ -189,10 +195,11 @@ public:
{ {
std::vector<std::string> v; std::vector<std::string> v;
v.push_back(std::to_string(e.groupid)); v.push_back(std::to_string(e.group_id));
v.push_back(std::to_string(e.charid));
v.push_back("'" + Strings::Escape(e.name) + "'"); v.push_back("'" + Strings::Escape(e.name) + "'");
v.push_back(std::to_string(e.ismerc)); v.push_back(std::to_string(e.character_id));
v.push_back(std::to_string(e.bot_id));
v.push_back(std::to_string(e.merc_id));
auto results = db.QueryDatabase( auto results = db.QueryDatabase(
fmt::format( fmt::format(
@ -203,7 +210,7 @@ public:
); );
if (results.Success()) { if (results.Success()) {
e.groupid = results.LastInsertedID(); e.group_id = results.LastInsertedID();
return e; return e;
} }
@ -222,10 +229,11 @@ public:
for (auto &e: entries) { for (auto &e: entries) {
std::vector<std::string> v; std::vector<std::string> v;
v.push_back(std::to_string(e.groupid)); v.push_back(std::to_string(e.group_id));
v.push_back(std::to_string(e.charid));
v.push_back("'" + Strings::Escape(e.name) + "'"); v.push_back("'" + Strings::Escape(e.name) + "'");
v.push_back(std::to_string(e.ismerc)); v.push_back(std::to_string(e.character_id));
v.push_back(std::to_string(e.bot_id));
v.push_back(std::to_string(e.merc_id));
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
} }
@ -259,10 +267,11 @@ public:
for (auto row = results.begin(); row != results.end(); ++row) { for (auto row = results.begin(); row != results.end(); ++row) {
GroupId e{}; GroupId e{};
e.groupid = row[0] ? static_cast<int32_t>(atoi(row[0])) : 0; e.group_id = row[0] ? static_cast<uint32_t>(strtoul(row[0], nullptr, 10)) : 0;
e.charid = row[1] ? static_cast<int32_t>(atoi(row[1])) : 0; e.name = row[1] ? row[1] : "";
e.name = row[2] ? row[2] : ""; e.character_id = row[2] ? static_cast<uint32_t>(strtoul(row[2], nullptr, 10)) : 0;
e.ismerc = row[3] ? static_cast<int8_t>(atoi(row[3])) : 0; e.bot_id = row[3] ? static_cast<uint32_t>(strtoul(row[3], nullptr, 10)) : 0;
e.merc_id = row[4] ? static_cast<uint32_t>(strtoul(row[4], nullptr, 10)) : 0;
all_entries.push_back(e); all_entries.push_back(e);
} }
@ -287,10 +296,11 @@ public:
for (auto row = results.begin(); row != results.end(); ++row) { for (auto row = results.begin(); row != results.end(); ++row) {
GroupId e{}; GroupId e{};
e.groupid = row[0] ? static_cast<int32_t>(atoi(row[0])) : 0; e.group_id = row[0] ? static_cast<uint32_t>(strtoul(row[0], nullptr, 10)) : 0;
e.charid = row[1] ? static_cast<int32_t>(atoi(row[1])) : 0; e.name = row[1] ? row[1] : "";
e.name = row[2] ? row[2] : ""; e.character_id = row[2] ? static_cast<uint32_t>(strtoul(row[2], nullptr, 10)) : 0;
e.ismerc = row[3] ? static_cast<int8_t>(atoi(row[3])) : 0; e.bot_id = row[3] ? static_cast<uint32_t>(strtoul(row[3], nullptr, 10)) : 0;
e.merc_id = row[4] ? static_cast<uint32_t>(strtoul(row[4], nullptr, 10)) : 0;
all_entries.push_back(e); all_entries.push_back(e);
} }
@ -365,10 +375,11 @@ public:
{ {
std::vector<std::string> v; std::vector<std::string> v;
v.push_back(std::to_string(e.groupid)); v.push_back(std::to_string(e.group_id));
v.push_back(std::to_string(e.charid));
v.push_back("'" + Strings::Escape(e.name) + "'"); v.push_back("'" + Strings::Escape(e.name) + "'");
v.push_back(std::to_string(e.ismerc)); v.push_back(std::to_string(e.character_id));
v.push_back(std::to_string(e.bot_id));
v.push_back(std::to_string(e.merc_id));
auto results = db.QueryDatabase( auto results = db.QueryDatabase(
fmt::format( fmt::format(
@ -391,10 +402,11 @@ public:
for (auto &e: entries) { for (auto &e: entries) {
std::vector<std::string> v; std::vector<std::string> v;
v.push_back(std::to_string(e.groupid)); v.push_back(std::to_string(e.group_id));
v.push_back(std::to_string(e.charid));
v.push_back("'" + Strings::Escape(e.name) + "'"); v.push_back("'" + Strings::Escape(e.name) + "'");
v.push_back(std::to_string(e.ismerc)); v.push_back(std::to_string(e.character_id));
v.push_back(std::to_string(e.bot_id));
v.push_back(std::to_string(e.merc_id));
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
} }

View File

@ -105,7 +105,7 @@ public:
e.gid = 0; e.gid = 0;
e.leadername = ""; e.leadername = "";
e.marknpc = ""; e.marknpc = "";
e.leadershipaa = 0; e.leadershipaa = "";
e.maintank = ""; e.maintank = "";
e.assist = ""; e.assist = "";
e.puller = ""; e.puller = "";
@ -150,7 +150,7 @@ public:
e.gid = row[0] ? static_cast<int32_t>(atoi(row[0])) : 0; e.gid = row[0] ? static_cast<int32_t>(atoi(row[0])) : 0;
e.leadername = row[1] ? row[1] : ""; e.leadername = row[1] ? row[1] : "";
e.marknpc = row[2] ? row[2] : ""; e.marknpc = row[2] ? row[2] : "";
e.leadershipaa = row[3] ? row[3] : 0; e.leadershipaa = row[3] ? row[3] : "";
e.maintank = row[4] ? row[4] : ""; e.maintank = row[4] ? row[4] : "";
e.assist = row[5] ? row[5] : ""; e.assist = row[5] ? row[5] : "";
e.puller = row[6] ? row[6] : ""; e.puller = row[6] ? row[6] : "";
@ -302,7 +302,7 @@ public:
e.gid = row[0] ? static_cast<int32_t>(atoi(row[0])) : 0; e.gid = row[0] ? static_cast<int32_t>(atoi(row[0])) : 0;
e.leadername = row[1] ? row[1] : ""; e.leadername = row[1] ? row[1] : "";
e.marknpc = row[2] ? row[2] : ""; e.marknpc = row[2] ? row[2] : "";
e.leadershipaa = row[3] ? row[3] : 0; e.leadershipaa = row[3] ? row[3] : "";
e.maintank = row[4] ? row[4] : ""; e.maintank = row[4] ? row[4] : "";
e.assist = row[5] ? row[5] : ""; e.assist = row[5] ? row[5] : "";
e.puller = row[6] ? row[6] : ""; e.puller = row[6] ? row[6] : "";
@ -335,7 +335,7 @@ public:
e.gid = row[0] ? static_cast<int32_t>(atoi(row[0])) : 0; e.gid = row[0] ? static_cast<int32_t>(atoi(row[0])) : 0;
e.leadername = row[1] ? row[1] : ""; e.leadername = row[1] ? row[1] : "";
e.marknpc = row[2] ? row[2] : ""; e.marknpc = row[2] ? row[2] : "";
e.leadershipaa = row[3] ? row[3] : 0; e.leadershipaa = row[3] ? row[3] : "";
e.maintank = row[4] ? row[4] : ""; e.maintank = row[4] ? row[4] : "";
e.assist = row[5] ? row[5] : ""; e.assist = row[5] ? row[5] : "";
e.puller = row[6] ? row[6] : ""; e.puller = row[6] ? row[6] : "";

View File

@ -60,7 +60,7 @@ SharedTaskRequest SharedTask::GetRequestCharacters(Database &db, uint32_t reques
request.group_type = SharedTaskRequestGroupType::Group; request.group_type = SharedTaskRequestGroupType::Group;
auto characters = CharacterDataRepository::GetWhere( auto characters = CharacterDataRepository::GetWhere(
db, fmt::format( db, fmt::format(
"id IN (select charid from group_id where groupid = (select groupid from group_id where charid = {}))", "id IN (select charid from group_id where group_id = (select group_id from group_id where charid = {}))",
requested_character_id requested_character_id
) )
); );

View File

@ -42,7 +42,7 @@
* Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt * Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt
*/ */
#define CURRENT_BINARY_DATABASE_VERSION 9266 #define CURRENT_BINARY_DATABASE_VERSION 9267
#define CURRENT_BINARY_BOTS_DATABASE_VERSION 9043 #define CURRENT_BINARY_BOTS_DATABASE_VERSION 9043
#endif #endif

View File

@ -53,6 +53,7 @@
#include "../common/repositories/inventory_repository.h" #include "../common/repositories/inventory_repository.h"
#include "../common/events/player_event_logs.h" #include "../common/events/player_event_logs.h"
#include "../common/content/world_content_service.h" #include "../common/content/world_content_service.h"
#include "../common/repositories/group_id_repository.h"
#include <iostream> #include <iostream>
#include <iomanip> #include <iomanip>
@ -881,7 +882,14 @@ bool Client::HandleEnterWorldPacket(const EQApplicationPacket *app) {
} }
if(!is_player_zoning) { if(!is_player_zoning) {
database.SetGroupID(char_name, 0, charid); GroupIdRepository::DeleteWhere(
database,
fmt::format(
"`character_id` = {} AND `name` = '{}'",
charid,
Strings::Escape(char_name)
)
);
database.SetLoginFlags(charid, false, false, 1); database.SetLoginFlags(charid, false, false, 1);
} else { } else {
auto group_id = database.GetGroupID(char_name); auto group_id = database.GetGroupID(char_name);

View File

@ -3343,7 +3343,7 @@ void Bot::LoadAndSpawnAllZonedBots(Client* bot_owner) {
} }
if (bot_spawn_limit >= 0 && spawned_bots_count >= bot_spawn_limit) { if (bot_spawn_limit >= 0 && spawned_bots_count >= bot_spawn_limit) {
database.SetGroupID(b->GetCleanName(), 0, b->GetBotID()); Group::RemoveFromGroup(b);
g->UpdatePlayer(bot_owner); g->UpdatePlayer(bot_owner);
continue; continue;
} }
@ -3355,7 +3355,7 @@ void Bot::LoadAndSpawnAllZonedBots(Client* bot_owner) {
bot_spawn_limit_class >= 0 && bot_spawn_limit_class >= 0 &&
spawned_bot_count_class >= bot_spawn_limit_class spawned_bot_count_class >= bot_spawn_limit_class
) { ) {
database.SetGroupID(b->GetCleanName(), 0, b->GetBotID()); Group::RemoveFromGroup(b);
g->UpdatePlayer(bot_owner); g->UpdatePlayer(bot_owner);
continue; continue;
} }
@ -3375,7 +3375,7 @@ void Bot::LoadAndSpawnAllZonedBots(Client* bot_owner) {
} }
if (!bot_owner->HasGroup()) { if (!bot_owner->HasGroup()) {
database.SetGroupID(b->GetCleanName(), 0, b->GetBotID()); Group::RemoveFromGroup(b);
} }
} }
} }
@ -3632,7 +3632,7 @@ bool Bot::RemoveBotFromGroup(Bot* bot, Group* group) {
bot->SetFollowID(0); bot->SetFollowID(0);
if (group->DelMember(bot)) { if (group->DelMember(bot)) {
group->DelMemberOOZ(bot->GetName()); group->DelMemberOOZ(bot->GetName());
database.SetGroupID(bot->GetCleanName(), 0, bot->GetBotID()); Group::RemoveFromGroup(bot);
if (group->GroupCount() < 2) { if (group->GroupCount() < 2) {
group->DisbandGroup(); group->DisbandGroup();
} }
@ -3646,7 +3646,7 @@ bool Bot::RemoveBotFromGroup(Bot* bot, Group* group) {
group->members[i]->SetFollowID(0); group->members[i]->SetFollowID(0);
} }
group->DisbandGroup(); group->DisbandGroup();
database.SetGroupID(bot->GetCleanName(), 0, bot->GetBotID()); Group::RemoveFromGroup(bot);
} }
Result = true; Result = true;
} }
@ -6600,14 +6600,14 @@ void Bot::ProcessBotGroupInvite(Client* c, std::string const& botName) {
entity_list.AddGroup(g); entity_list.AddGroup(g);
database.SetGroupLeaderName(g->GetID(), c->GetName()); database.SetGroupLeaderName(g->GetID(), c->GetName());
g->SaveGroupLeaderAA(); g->SaveGroupLeaderAA();
database.SetGroupID(c->GetName(), g->GetID(), c->CharacterID()); g->AddToGroup(c);
database.SetGroupID(invitedBot->GetCleanName(), g->GetID(), invitedBot->GetBotID()); g->AddToGroup(invitedBot);
} else { } else {
delete g; delete g;
} }
} else { } else {
if (AddBotToGroup(invitedBot, c->GetGroup())) { if (AddBotToGroup(invitedBot, c->GetGroup())) {
database.SetGroupID(invitedBot->GetCleanName(), c->GetGroup()->GetID(), invitedBot->GetBotID()); c->GetGroup()->AddToGroup(invitedBot);
} }
} }
} else if (invitedBot->HasGroup()) { } else if (invitedBot->HasGroup()) {

View File

@ -1922,18 +1922,14 @@ bool BotDatabase::LoadGroupedBotsByGroupID(const uint32 owner_id, const uint32 g
const auto& l = GroupIdRepository::GetWhere( const auto& l = GroupIdRepository::GetWhere(
database, database,
fmt::format( fmt::format(
"`groupid` = {} AND `name` IN (SELECT `name` FROM `bot_data` WHERE `owner_id` = {})", "`group_id` = {} AND `bot_id` != 0 AND `name` IN (SELECT `name` FROM `bot_data` WHERE `owner_id` = {})",
group_id, group_id,
owner_id owner_id
) )
); );
if (l.empty()) {
return true;
}
for (const auto& e : l) { for (const auto& e : l) {
group_list.push_back(e.charid); group_list.emplace_back(e.bot_id);
} }
return true; return true;

View File

@ -98,7 +98,7 @@ void Raid::HandleBotGroupDisband(uint32 owner, uint32 gid)
auto r_group_members = GetRaidGroupMembers(GetGroup(b->GetName())); auto r_group_members = GetRaidGroupMembers(GetGroup(b->GetName()));
auto g = new Group(b); auto g = new Group(b);
entity_list.AddGroup(g); entity_list.AddGroup(g);
database.SetGroupID(b->GetCleanName(), g->GetID(), b->GetBotID()); g->AddToGroup(b);
database.SetGroupLeaderName(g->GetID(), b->GetName()); database.SetGroupLeaderName(g->GetID(), b->GetName());
for (auto m: r_group_members) { for (auto m: r_group_members) {

View File

@ -4442,7 +4442,7 @@ bool Client::GroupFollow(Client* inviter) {
} }
//now we have a group id, can set inviter's id //now we have a group id, can set inviter's id
database.SetGroupID(inviter->GetName(), group->GetID(), inviter->CharacterID(), false); group->AddToGroup(inviter);
database.SetGroupLeaderName(group->GetID(), inviter->GetName()); database.SetGroupLeaderName(group->GetID(), inviter->GetName());
group->UpdateGroupAAs(); group->UpdateGroupAAs();

View File

@ -1530,8 +1530,9 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app)
} }
} //else, somebody from our group is already here... } //else, somebody from our group is already here...
if (!group) if (!group) { //cannot re-establish group, kill it
database.SetGroupID(GetName(), 0, CharacterID(), false); //cannot re-establish group, kill it Group::RemoveFromGroup(this);
}
} }
else { //no group id else { //no group id
@ -7189,7 +7190,7 @@ void Client::Handle_OP_GroupCancelInvite(const EQApplicationPacket *app)
if (!GetMerc()) if (!GetMerc())
{ {
database.SetGroupID(GetName(), 0, CharacterID(), false); Group::RemoveFromGroup(this);
} }
return; return;
} }

View File

@ -203,155 +203,141 @@ void Group::SplitMoney(uint32 copper, uint32 silver, uint32 gold, uint32 platinu
} }
} }
bool Group::AddMember(Mob* newmember, const char *NewMemberName, uint32 CharacterID, bool ismerc) bool Group::AddMember(Mob* new_member, std::string new_member_name, uint32 character_id, bool is_merc)
{ {
bool InZone = true; bool in_zone = true;
// This method should either be passed a Mob*, if the new member is in this zone, or a nullptr Mob* // This method should either be passed a Mob*, if the new member is in this zone, or a nullptr Mob*
// and the name and CharacterID of the new member, if they are out of zone. // and the name and character_id of the new member, if they are out of zone.
if(!newmember && !NewMemberName) if (!new_member && new_member_name.empty()) {
{
return false; return false;
} }
if(GroupCount() >= MAX_GROUP_MEMBERS) //Sanity check for merging groups together. if (GroupCount() >= MAX_GROUP_MEMBERS) { //Sanity check for merging groups together.
{
return false; return false;
} }
if(!newmember) if (!new_member) {
{ in_zone = false;
InZone = false; } else {
} new_member_name = new_member->GetCleanName();
else
{
NewMemberName = newmember->GetCleanName();
if(newmember->IsClient()) if (new_member->IsClient()) {
{ character_id = new_member->CastToClient()->CharacterID();
CharacterID = newmember->CastToClient()->CharacterID();
} }
if(newmember->IsMerc())
{ if (new_member->IsMerc()) {
Client* owner = newmember->CastToMerc()->GetMercenaryOwner(); Client* o = new_member->CastToMerc()->GetMercenaryOwner();
if(owner) if (o) {
{ character_id = o->CastToClient()->CharacterID();
CharacterID = owner->CastToClient()->CharacterID();
} }
ismerc = true;
is_merc = true;
} }
} }
// See if they are already in the group // See if they are already in the group
uint32 i = 0; for (const auto& m : membername) {
for (i = 0; i < MAX_GROUP_MEMBERS; ++i) if (Strings::EqualFold(m, new_member_name)) {
{
if (!strcasecmp(membername[i], NewMemberName))
{
return false; return false;
} }
} }
// Put them in the group // Put them in the group
for (i = 0; i < MAX_GROUP_MEMBERS; ++i) for (int slot_id = 0; slot_id < MAX_GROUP_MEMBERS; ++slot_id) {
{ if (membername[slot_id][0] == '\0') {
if (membername[i][0] == '\0') if (in_zone) {
{ members[slot_id] = new_member;
if(InZone)
{
members[i] = newmember;
} }
strcpy(membername[i], NewMemberName);
MemberRoles[i] = 0; strcpy(membername[slot_id], new_member_name.c_str());
MemberRoles[slot_id] = 0;
break; break;
} }
} }
// Is this even possible based on the above loops? Remove? int x = 1;
if (i == MAX_GROUP_MEMBERS)
{
return false;
}
int x=1;
//build the template join packet //build the template join packet
auto outapp = new EQApplicationPacket(OP_GroupUpdate, sizeof(GroupJoin_Struct)); auto outapp = new EQApplicationPacket(OP_GroupUpdate, sizeof(GroupJoin_Struct));
GroupJoin_Struct* gj = (GroupJoin_Struct*) outapp->pBuffer;
strcpy(gj->membername, NewMemberName); auto gj = (GroupJoin_Struct*) outapp->pBuffer;
strcpy(gj->membername, new_member_name.c_str());
gj->action = groupActJoin; gj->action = groupActJoin;
gj->leader_aas = LeaderAbilities; gj->leader_aas = LeaderAbilities;
for (i = 0;i < MAX_GROUP_MEMBERS; i++) for (int slot_id = 0; slot_id < MAX_GROUP_MEMBERS; slot_id++) {
{ if (members[slot_id] && members[slot_id] != new_member) {
if (members[i] != nullptr && members[i] != newmember)
{
//fill in group join & send it //fill in group join & send it
strcpy(gj->yourname, members[i]->GetCleanName()); strcpy(gj->yourname, members[slot_id]->GetCleanName());
if(members[i]->IsClient()) if (members[slot_id]->IsClient()) {
{ members[slot_id]->CastToClient()->QueuePacket(outapp);
members[i]->CastToClient()->QueuePacket(outapp);
//put new member into existing group members' list(s) //put new member into existing group members' list(s)
strcpy(members[i]->CastToClient()->GetPP().groupMembers[GroupCount()-1], NewMemberName); strcpy(
members[slot_id]->CastToClient()->GetPP().groupMembers[GroupCount() - 1],
new_member_name.c_str()
);
} }
//put existing group member(s) into the new member's list //put existing group member(s) into the new member's list
if(InZone && newmember && newmember->IsClient()) if (in_zone && new_member && new_member->IsClient()) {
{ if (IsLeader(members[slot_id])) {
if(IsLeader(members[i])) strcpy(new_member->CastToClient()->GetPP().groupMembers[0], members[slot_id]->GetCleanName());
{ } else {
strcpy(newmember->CastToClient()->GetPP().groupMembers[0], members[i]->GetCleanName()); strcpy(new_member->CastToClient()->GetPP().groupMembers[x], members[slot_id]->GetCleanName());
}
else
{
strcpy(newmember->CastToClient()->GetPP().groupMembers[x], members[i]->GetCleanName());
++x; ++x;
} }
} }
} }
} }
if(InZone && newmember) if (in_zone && new_member) {
{
//put new member in his own list. //put new member in his own list.
newmember->SetGrouped(true); new_member->SetGrouped(true);
if(newmember->IsClient()) if (new_member->IsClient()) {
{ strcpy(new_member->CastToClient()->GetPP().groupMembers[x], new_member_name.c_str());
strcpy(newmember->CastToClient()->GetPP().groupMembers[x], NewMemberName);
newmember->CastToClient()->Save();
database.SetGroupID(NewMemberName, GetID(), newmember->CastToClient()->CharacterID(), false);
SendMarkedNPCsToMember(newmember->CastToClient());
NotifyMainTank(newmember->CastToClient(), 1); new_member->CastToClient()->Save();
NotifyMainAssist(newmember->CastToClient(), 1);
NotifyPuller(newmember->CastToClient(), 1); AddToGroup(new_member);
SendMarkedNPCsToMember(new_member->CastToClient());
NotifyMainTank(new_member->CastToClient(), 1);
NotifyMainAssist(new_member->CastToClient(), 1);
NotifyPuller(new_member->CastToClient(), 1);
} }
if(newmember->IsMerc()) if (new_member->IsMerc()) {
{ Client* o = new_member->CastToMerc()->GetMercenaryOwner();
Client* owner = newmember->CastToMerc()->GetMercenaryOwner(); if (o) {
if(owner) AddToGroup(new_member);
{
database.SetGroupID(NewMemberName, GetID(), owner->CharacterID(), true);
} }
} }
Group* group = newmember->CastToClient()->GetGroup(); Group* g = new_member->CastToClient()->GetGroup();
if (group) { if (g) {
group->SendHPManaEndPacketsTo(newmember); g->SendHPManaEndPacketsTo(new_member);
group->SendHPPacketsFrom(newmember); g->SendHPPacketsFrom(new_member);
} }
} else {
AddToGroup(
AddToGroupRequest{
.mob = nullptr,
.member_name = new_member_name,
.character_id = character_id,
} }
else );
{
database.SetGroupID(NewMemberName, GetID(), CharacterID, ismerc);
} }
if (newmember && newmember->IsClient()) if (new_member && new_member->IsClient()) {
newmember->CastToClient()->JoinGroupXTargets(this); new_member->CastToClient()->JoinGroupXTargets(this);
}
safe_delete(outapp); safe_delete(outapp);
@ -362,20 +348,18 @@ bool Group::AddMember(Mob* newmember, const char *NewMemberName, uint32 Characte
return true; return true;
} }
void Group::AddMember(const char *NewMemberName) void Group::AddMember(const std::string& new_member_name)
{ {
// This method should be called when both the new member and the group leader are in a different zone to this one. // This method should be called when both the new member and the group leader are in a different zone to this one.
for (uint32 i = 0; i < MAX_GROUP_MEMBERS; ++i) for (const auto& m : membername) {
if(!strcasecmp(membername[i], NewMemberName)) if (Strings::EqualFold(m, new_member_name)) {
{
return; return;
} }
}
for (uint32 i = 0; i < MAX_GROUP_MEMBERS; ++i) for (uint32 i = 0; i < MAX_GROUP_MEMBERS; ++i) {
{ if (membername[i][0] == '\0') {
if (membername[i][0] == '\0') strcpy(membername[i], new_member_name.c_str());
{
strcpy(membername[i], NewMemberName);
MemberRoles[i] = 0; MemberRoles[i] = 0;
break; break;
} }
@ -752,7 +736,7 @@ bool Group::DelMember(Mob* oldmember, bool ignoresender)
if(oldmember->IsClient()) if(oldmember->IsClient())
{ {
database.SetGroupID(oldmember->GetCleanName(), 0, oldmember->CastToClient()->CharacterID(), false); RemoveFromGroup(oldmember);
} }
if(oldmember->IsMerc()) if(oldmember->IsMerc())
@ -760,7 +744,7 @@ bool Group::DelMember(Mob* oldmember, bool ignoresender)
Client* owner = oldmember->CastToMerc()->GetMercenaryOwner(); Client* owner = oldmember->CastToMerc()->GetMercenaryOwner();
if(owner) if(owner)
{ {
database.SetGroupID(oldmember->GetCleanName(), 0, owner->CharacterID(), true); RemoveFromGroup(oldmember);
} }
} }
@ -943,7 +927,7 @@ void Group::DisbandGroup(bool joinraid) {
} }
strcpy(gu->yourname, members[i]->GetCleanName()); strcpy(gu->yourname, members[i]->GetCleanName());
database.SetGroupID(members[i]->GetCleanName(), 0, members[i]->CastToClient()->CharacterID(), false); RemoveFromGroup(members[i]);
members[i]->CastToClient()->QueuePacket(outapp); members[i]->CastToClient()->QueuePacket(outapp);
SendMarkedNPCsToMember(members[i]->CastToClient(), true); SendMarkedNPCsToMember(members[i]->CastToClient(), true);
if (!joinraid) if (!joinraid)
@ -955,7 +939,7 @@ void Group::DisbandGroup(bool joinraid) {
Client* owner = members[i]->CastToMerc()->GetMercenaryOwner(); Client* owner = members[i]->CastToMerc()->GetMercenaryOwner();
if(owner) if(owner)
{ {
database.SetGroupID(members[i]->GetCleanName(), 0, owner->CharacterID(), true); RemoveFromGroup(members[i]);
} }
} }
@ -1148,31 +1132,30 @@ void Group::TeleportGroup(Mob* sender, uint32 zoneID, uint16 instance_id, float
bool Group::LearnMembers() { bool Group::LearnMembers() {
auto rows = GroupIdRepository::GetWhere( const auto& l = GroupIdRepository::GetWhere(
database, database,
fmt::format( fmt::format(
"groupid = {}", "`group_id` = {}",
GetID() GetID()
) )
); );
if (rows.empty()) { if (l.empty()) {
LogError( LogError(
"Error getting group members for group [{}]", "Error getting group members for group [{}]",
GetID() GetID()
); );
} }
for(int i = 0; i < MAX_GROUP_MEMBERS; ++i) for (int i = 0; i < MAX_GROUP_MEMBERS; ++i) {
{
members[i] = nullptr; members[i] = nullptr;
memset(membername[i],0,64); memset(membername[i], 0, 64);
MemberRoles[i] = 0; MemberRoles[i] = 0;
} }
int memberIndex = 0; int member_index = 0;
for (const auto& member : rows) { for (const auto& e : l) {
if (memberIndex >= MAX_GROUP_MEMBERS) { if (member_index >= MAX_GROUP_MEMBERS) {
LogError( LogError(
"Too many members in group [{}]", "Too many members in group [{}]",
GetID() GetID()
@ -1180,14 +1163,15 @@ bool Group::LearnMembers() {
break; break;
} }
if (member.name.empty()) { if (e.name.empty()) {
members[memberIndex] = nullptr; members[member_index] = nullptr;
membername[memberIndex][0] = '\0'; membername[member_index][0] = '\0';
} else { } else {
members[memberIndex] = nullptr; members[member_index] = nullptr;
strn0cpy(membername[memberIndex], member.name.c_str(), 64); strn0cpy(membername[member_index], e.name.c_str(), 64);
} }
++memberIndex;
++member_index;
} }
VerifyGroup(); VerifyGroup();
@ -1264,10 +1248,10 @@ void Client::LeaveGroup() {
else else
{ {
//force things a little //force things a little
database.SetGroupID(GetCleanName(), 0, CharacterID(), false); Group::RemoveFromGroup(this);
if (GetMerc())
{ if (GetMerc()) {
database.SetGroupID(GetMerc()->GetCleanName(), 0, CharacterID(), true); Group::RemoveFromGroup(GetMerc());
} }
} }
@ -2514,3 +2498,69 @@ bool Group::IsLeader(const char* name) {
std::string Group::GetLeaderName() { std::string Group::GetLeaderName() {
return database.GetGroupLeaderName(GetID()); return database.GetGroupLeaderName(GetID());
} }
void Group::RemoveFromGroup(Mob* m)
{
uint32 bot_id = 0;
uint32 character_id = 0;
uint32 merc_id = 0;
if (m->IsBot()) {
bot_id = m->CastToBot()->GetBotID();
} else if (m->IsClient()) {
character_id = m->CastToClient()->CharacterID();
} else if (m->IsMerc()) {
merc_id = m->CastToMerc()->GetMercenaryID();
}
GroupIdRepository::DeleteWhere(
database,
fmt::format(
"`character_id` = {} AND `bot_id` = {} AND `merc_id` = {}",
character_id,
bot_id,
merc_id
)
);
}
void Group::AddToGroup(Mob* m)
{
AddToGroup(
AddToGroupRequest{
.mob = m
}
);
}
// Handles database-side, should eventually be consolidated to handle memory-based group stuff as well
void Group::AddToGroup(AddToGroupRequest r)
{
uint32 bot_id = 0;
uint32 character_id = 0;
uint32 merc_id = 0;
std::string name = "";
if (r.mob) {
if (r.mob->IsBot()) {
bot_id = r.mob->CastToBot()->GetBotID();
} else if (r.mob->IsClient()) {
character_id = r.mob->CastToClient()->CharacterID();
} else if (r.mob->IsMerc()) {
merc_id = r.mob->CastToMerc()->GetMercenaryID();
}
name = r.mob->GetCleanName();
}
GroupIdRepository::ReplaceOne(
database,
GroupIdRepository::GroupId{
.group_id = GetID(),
.name = name,
.character_id = character_id,
.bot_id = bot_id,
.merc_id = merc_id
}
);
}

View File

@ -52,8 +52,15 @@ public:
Group(uint32 gid); Group(uint32 gid);
~Group(); ~Group();
bool AddMember(Mob* newmember, const char* NewMemberName = nullptr, uint32 CharacterID = 0, bool ismerc = false); struct AddToGroupRequest {
void AddMember(const char* NewMemberName); Mob* mob = nullptr;
// Only used cross-zone, otherwise use Mob* mob
std::string member_name = std::string();
uint32 character_id = 0;
};
bool AddMember(Mob* new_member, std::string new_member_name = std::string(), uint32 character_id = 0, bool is_merc = false);
void AddMember(const std::string& new_member_name);
void SendUpdate(uint32 type,Mob* member); void SendUpdate(uint32 type,Mob* member);
void SendLeadershipAAUpdate(); void SendLeadershipAAUpdate();
void SendWorldGroup(uint32 zone_id,Mob* zoningmember); void SendWorldGroup(uint32 zone_id,Mob* zoningmember);
@ -145,6 +152,9 @@ public:
void SetDirtyAutoHaters(); void SetDirtyAutoHaters();
inline XTargetAutoHaters *GetXTargetAutoMgr() { return &m_autohatermgr; } inline XTargetAutoHaters *GetXTargetAutoMgr() { return &m_autohatermgr; }
void JoinRaidXTarget(Raid *raid, bool first = false); void JoinRaidXTarget(Raid *raid, bool first = false);
void AddToGroup(AddToGroupRequest r);
void AddToGroup(Mob* m);
static void RemoveFromGroup(Mob* m);
void SetGroupMentor(int percent, char *name); void SetGroupMentor(int percent, char *name);
void ClearGroupMentor(); void ClearGroupMentor();

View File

@ -5377,7 +5377,7 @@ bool Merc::RemoveMercFromGroup(Merc* merc, Group* group) {
{ {
if(merc->GetMercenaryCharacterID() != 0) if(merc->GetMercenaryCharacterID() != 0)
{ {
database.SetGroupID(merc->GetName(), 0, merc->GetMercenaryCharacterID(), true); Group::RemoveFromGroup(merc);
} }
} }
} }
@ -5462,7 +5462,7 @@ bool Merc::MercJoinClientGroup() {
if (AddMercToGroup(this, g)) if (AddMercToGroup(this, g))
{ {
database.SetGroupID(mercOwner->GetName(), g->GetID(), mercOwner->CharacterID(), false); g->AddToGroup(mercOwner);
database.SetGroupLeaderName(g->GetID(), mercOwner->GetName()); database.SetGroupLeaderName(g->GetID(), mercOwner->GetName());
database.RefreshGroupFromDB(mercOwner); database.RefreshGroupFromDB(mercOwner);
g->SaveGroupLeaderAA(); g->SaveGroupLeaderAA();

View File

@ -1092,7 +1092,7 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p)
break; break;
} }
database.SetGroupID(Inviter->GetName(), group->GetID(), Inviter->CastToClient()->CharacterID(), false); group->AddToGroup(Inviter);
database.SetGroupLeaderName(group->GetID(), Inviter->GetName()); database.SetGroupLeaderName(group->GetID(), Inviter->GetName());
group->UpdateGroupAAs(); group->UpdateGroupAAs();
@ -1192,9 +1192,11 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p)
group->UpdatePlayer(client); group->UpdatePlayer(client);
else else
{ {
if (client->GetMerc()) if (client->GetMerc()) {
database.SetGroupID(client->GetMerc()->GetCleanName(), 0, client->CharacterID(), true); Group::RemoveFromGroup(client->GetMerc());
database.SetGroupID(client->GetName(), 0, client->CharacterID(), false); //cannot re-establish group, kill it }
Group::RemoveFromGroup(client); //cannot re-establish group, kill it
} }
} }

View File

@ -2649,7 +2649,7 @@ void ZoneDatabase::RefreshGroupFromDB(Client *client){
int index = 0; int index = 0;
auto query = fmt::format( auto query = fmt::format(
"SELECT name FROM group_id WHERE groupid = {}", "SELECT name FROM group_id WHERE group_id = {}",
group->GetID() group->GetID()
); );
auto results = QueryDatabase(query); auto results = QueryDatabase(query);