Mitch Freeman 91f5932c6d
[Feature] Add RoF2 Guild features (#3699)
* [Feature] Add additional Guild Features

This adds the following guild features and design pattern
- the existing guild system was used
- guild features are based on RoF2 within source with translaters used to converted between client differences
- backward compatible with Ti and UF, and allows for mixed client servers
- Guild Back for Ti and UF is based on RoF2 Permissions for banking if Guild Leader does not use Ti/UF
- Guild Ranks and Permissions are enabled.
- Guild Tributes are enabled.
- Event logging via rules for donating tribute items and plat
- Rules to limit Guild Tributes based on max level of server
- Rewrote guild communications to client using specific opcodes
-- Server no longer sends a guild member list on each zone
-- Guild window is updated when a member levels, rank changes, zone changes, banker/alt status using individual opcodes
-- When a member is removed or added to a guild, a single opcode is sent to each guild member
-- This reduces network traffic considerably

Known issues:
- Visual bug only. Guild Tributes window will display a 0 for level if tribute is above max level rule setting.
- Visual bug only. Guild Mgmt Window will not display an online member if the player has 'show offline' unchecked and a guild member zones within the Notes/Tribute tab.  This is resolved by selecting and de-selecting the 'Show Offline' checkbox.

* Updated RoF2 Guild Comms

Updated RoF2 Guild Comms
Update RoF2 Opcodes
Rewrote RoF2 Guild Communications using specific opcodes.
Added database changes - they are irreversible

* Formatting

* Update base_guild_members_repository.h

* Format GuildInfo

* Format GuildAction enum

* Formatting in clientlist

* quantity vs quantity

* desc vs description

* Format structs

* Inline struct values

* Formatting

* Formatting

* Formatting fixes

* Formatting items

* Formatting

* Formatting

* struct formatting updates

* Updated formatting

* Updated
- std:string items
- naming conventions
- magic numbers

* Repo refactors
Other formatting updates

* Remove test guild commands

* Updated #guild info command

* Add new repo methods for Neckolla ReplaceOne and ReplaceMany

* Fix guild_tributes repo

* Update database_update_manifest.cpp

* Phase 1 of final testing with RoF2 -> RoF2.
Next phase will be inter compatibility review

* Remove #guild testing commands

* Fix uf translator error
Rewrite LoadGuilds

* Use extended repository

* FIx guild window on member add

* LoadGuild Changes

* Update guild_base.cpp

* Few small fixes for display issue with UF

* Update guild_base.cpp

* Update guild_members_repository.h

* Update zoneserver.cpp

* Update guild.cpp

* Update entity.h

* Switch formatting

* Formatting

* Update worldserver.cpp

* Switch formatting

* Formatting switch statement

* Update guild.cpp

* Formatting in guild_base

* We don't need to validate m_db everywhere

* More formatting / spacing issues

* Switch format

* Update guild_base.cpp

* Fix an UF issue displaying incorrect guildtag as <>

* Updated several constants, fixed a few issues with Ti/UF and guild tributes not being removed or sent when a member is removed/disbands from a guild.

* Formatting and logging updates

* Fix for Loadguilds and permissions after repo updates.

* Cleanup unnecessary m_db checks

* Updated logging to use player_event_logs

* Updated to use the single opcodes for guild traffic for Ti/UF/RoF2.  Several enhancements for guild functionality for more reusable code and readability.

* Update to fix Demote Self and guild invites declining when option set to not accept guild invites

* Potential fix for guild notes/tribute display issues when client has 'Show Offline' unchecked.

* Updates to fox recent master changes

Updates to fix recent master changes

* Updates in response to comments

* Further Updates in response to comments

* Comment updates and refactor for SendAppearance functions

* Comment updates

* Update client spawn process for show guild name

Add show guild tag to default spawn process

* Update to use zone spawn packets for RoF2
Removed several unused functions as a result
Updated MemberRankUpdate to properly update guild_show on rank change.
Updated OP_GuildURLAndChannel opcode for UF/RoF2

* Cleanup of world changes
Created function for repetitive zonelist sendpackets to only booted zones
Re-Inserted accidental delete of scanclosemobs

* Fixes

* Further world cleanup

* Fix a few test guild bank cases for backward compat
Removed a duplicate db call
Fixed a fallthrough issue

* Update guild_mgr.cpp

* Cleanup

---------

Co-authored-by: Akkadius <akkadius1@gmail.com>
2024-02-10 03:27:58 -06:00

623 lines
16 KiB
C++
Executable File

#include "../client.h"
#include "../worldserver.h"
extern WorldServer worldserver;
#include "../guild_mgr.h"
#include "../doors.h"
void command_guild(Client* c, const Seperator* sep)
{
const auto arguments = sep->argnum;
if (!arguments) {
SendGuildSubCommands(c);
return;
}
auto t = c;
if (c->GetTarget() && c->GetTarget()->IsClient()) {
t = c->GetTarget()->CastToClient();
}
bool is_create = !strcasecmp(sep->arg[1], "create");
bool is_delete = !strcasecmp(sep->arg[1], "delete");
bool is_help = !strcasecmp(sep->arg[1], "help");
bool is_info = !strcasecmp(sep->arg[1], "info");
bool is_list = !strcasecmp(sep->arg[1], "list");
bool is_rename = !strcasecmp(sep->arg[1], "rename");
bool is_search = !strcasecmp(sep->arg[1], "search");
bool is_set = !strcasecmp(sep->arg[1], "set");
bool is_set_leader = !strcasecmp(sep->arg[1], "setleader");
bool is_set_rank = !strcasecmp(sep->arg[1], "setrank");
bool is_status = !strcasecmp(sep->arg[1], "status");
bool is_details = !strcasecmp(sep->arg[1], "details");
bool is_test = !strcasecmp(sep->arg[1], "test");
if (
!is_create &&
!is_delete &&
!is_help &&
!is_info &&
!is_list &&
!is_rename &&
!is_search &&
!is_set &&
!is_set_leader &&
!is_set_rank &&
!is_status &&
!is_details &&
!is_test
) {
SendGuildSubCommands(c);
return;
}
if (is_create) {
if (arguments < 3) {
c->Message(Chat::White, "Usage: #guild create [Character ID|Character Name] [Guild Name]");
}
else {
auto leader_id = (
sep->IsNumber(2) ?
Strings::ToUnsignedInt(sep->arg[2]) :
database.GetCharacterID(sep->arg[2])
);
auto leader_name = database.GetCharNameByID(leader_id);
if (!leader_id || leader_name.empty()) {
c->Message(
Chat::White,
fmt::format(
"Character ID {} could not be found.",
leader_id
).c_str()
);
return;
}
auto guild_id = guild_mgr.FindGuildByLeader(leader_id);
if (guild_id != GUILD_NONE) {
c->Message(
Chat::White,
fmt::format(
"{} ({}) is already the leader of {} ({}).",
leader_name,
leader_id,
guild_mgr.GetGuildNameByID(guild_id),
guild_id
).c_str()
);
}
else {
auto guild_name = sep->argplus[3];
auto guild_id = guild_mgr.CreateGuild(sep->argplus[3], leader_id);
LogGuilds(
"[{}]: Creating guild [{}] with leader [{}] with GM command. It was given id [{}]",
c->GetName(),
guild_name,
leader_id,
guild_id
);
if (guild_id == GUILD_NONE) {
c->Message(Chat::White, "Guild creation failed.");
}
else {
c->Message(
Chat::White,
fmt::format(
"Guild Created | Name: {} ({}) Leader: {} ({})",
guild_name,
guild_id,
leader_name,
leader_id
).c_str()
);
if (!guild_mgr.SetGuild(leader_id, guild_id, GUILD_LEADER)) {
c->Message(
Chat::White,
fmt::format(
"Unable to put {} ({}) in to {} ({}) in the database.",
leader_name,
leader_id,
guild_name,
guild_id
).c_str()
);
c->Message(Chat::White, "Note: Run #guild set to resolve this.");
}
}
}
}
}
else if (is_delete) {
if (!sep->IsNumber(2)) {
c->Message(Chat::White, "Usage: #guild delete [Guild ID]");
}
else {
auto guild_id = Strings::ToUnsignedInt(sep->arg[2]);
if (!guild_mgr.GuildExists(guild_id)) {
c->Message(
Chat::White,
fmt::format(
"Guild ID {} could not be found.",
guild_id
).c_str()
);
return;
}
LogGuilds(
"[{}]: Deleting guild [{}] ([{}]) with GM command",
c->GetName(),
guild_mgr.GetGuildNameByID(guild_id),
guild_id
);
c->Message(
Chat::White,
fmt::format(
"Guild {} Deleted | Name: {} ({})",
guild_mgr.DeleteGuild(guild_id) ? "Successfully" : "Unsuccessfully",
guild_mgr.GetGuildNameByID(guild_id),
guild_id
).c_str()
);
}
}
else if (is_help) {
SendGuildSubCommands(c);
}
else if (is_info) {
if (arguments != 2 && c->IsInAGuild()) {
c->Message(Chat::White, "#guild info [Guild ID]");
}
else {
auto guild_id = GUILD_NONE;
if (sep->IsNumber(2)) {
guild_id = Strings::ToUnsignedInt(sep->arg[2]);
}
if (guild_id != GUILD_NONE) {
guild_mgr.DescribeGuild(c, guild_id);
}
}
}
else if (is_list) {
guild_mgr.ListGuilds(c, std::string());
}
else if (is_rename) {
if (!sep->IsNumber(2)) {
c->Message(Chat::White, "Usage: #guild rename [Guild ID] [New Guild Name]");
}
else {
auto guild_id = Strings::ToUnsignedInt(sep->arg[2]);
if (!guild_mgr.GuildExists(guild_id)) {
c->Message(
Chat::White,
fmt::format(
"Guild ID {} could not be found.",
guild_id
).c_str()
);
return;
}
auto new_guild_name = sep->argplus[3];
LogGuilds(
"[{}]: Renaming guild [{}] ([{}]) to [{}] with GM command",
c->GetName(),
guild_mgr.GetGuildNameByID(guild_id),
guild_id,
new_guild_name
);
c->Message(
Chat::White,
fmt::format(
"Guild {} Renamed | Name: {} ({})",
guild_mgr.RenameGuild(guild_id, new_guild_name) ? "Successfully" : "Unsuccessfully",
new_guild_name,
guild_id
).c_str()
);
}
}
else if (is_search) {
if (Strings::IsNumber(sep->arg[2])) {
const auto guild_id = Strings::ToUnsignedInt(sep->arg[2]);
guild_mgr.ListGuilds(c, guild_id);
}
else {
const std::string search_criteria = sep->argplus[2];
guild_mgr.ListGuilds(c, search_criteria);
}
}
else if (is_set) {
if (
arguments != 3 ||
!sep->IsNumber(3)
) {
c->Message(Chat::White, "#guild set [Character ID|Character Name] [Guild ID] (Guild ID 0 is Guildless)");
return;
}
else {
auto guild_id = Strings::ToUnsignedInt(sep->arg[3]);
if (!guild_id) {
guild_id = GUILD_NONE;
}
else if (!guild_mgr.GuildExists(guild_id)) {
c->Message(
Chat::White,
fmt::format(
"Guild ID {} could not be found.",
guild_id
).c_str()
);
return;
}
auto character_id = (
sep->IsNumber(2) ?
Strings::ToUnsignedInt(sep->arg[2]) :
database.GetCharacterID(sep->arg[2])
);
auto character_name = database.GetCharNameByID(character_id);
if (!character_id || character_name.empty()) {
c->Message(
Chat::White,
fmt::format(
"Character ID {} could not be found.",
character_id
).c_str()
);
return;
}
if (!guild_id || guild_id == GUILD_NONE) {
LogGuilds(
"[{}]: Removing [{}] ([{}]) from guild with GM command",
c->GetName(),
character_name,
character_id
);
}
else {
LogGuilds(
"[{}]: Putting [{}] ([{}]) into guild [{}] ([{}]) with GM command",
c->GetName(),
character_name,
character_id,
guild_mgr.GetGuildNameByID(guild_id),
guild_id
);
}
if (guild_id && guild_id != GUILD_NONE) {
c->Message(
Chat::White,
fmt::format(
"{} ({}) has {} put into {} ({}).",
character_name,
character_id,
guild_mgr.SetGuild(character_id, guild_id, GUILD_MEMBER) ? "been" : "failed to be",
guild_mgr.GetGuildNameByID(guild_id),
guild_id
).c_str()
);
}
else {
guild_mgr.SetGuild(character_id, GUILD_NONE, 0);
c->Message(
Chat::White,
fmt::format(
"{} ({}) is now Guildless.",
character_name,
character_id
).c_str()
);
}
}
}
else if (is_set_leader) {
if (
arguments != 3 ||
!sep->IsNumber(2)
) {
c->Message(Chat::White, "Usage: #guild setleader [Guild ID] [Character ID|Character Name]");
}
else {
auto leader_id = (
sep->IsNumber(2) ?
Strings::ToUnsignedInt(sep->arg[2]) :
database.GetCharacterID(sep->arg[2])
);
auto leader_name = database.GetCharNameByID(leader_id);
if (!leader_id || leader_name.empty()) {
c->Message(
Chat::White,
fmt::format(
"Character ID {} could not be found.",
leader_id
).c_str()
);
return;
}
auto guild_id = guild_mgr.FindGuildByLeader(leader_id);
if (guild_id != 0) {
c->Message(
Chat::White,
fmt::format(
"{} ({}) is already the leader of {} ({}).",
leader_name,
leader_id,
guild_mgr.GetGuildNameByID(guild_id),
guild_id
).c_str()
);
}
else {
auto guild_id = Strings::ToUnsignedInt(sep->arg[2]);
if (!guild_mgr.GuildExists(guild_id)) {
c->Message(
Chat::White,
fmt::format(
"Guild ID {} could not be found.",
guild_id
).c_str()
);
return;
}
LogGuilds(
"[{}]: Setting leader of guild [{}] ([{}]) to [{}] with GM command",
c->GetName(),
guild_mgr.GetGuildNameByID(guild_id),
guild_id,
leader_id
);
c->Message(
Chat::White,
fmt::format(
"Guild Leader {} Changed | Name: {} ({})",
guild_mgr.SetGuildLeader(guild_id, leader_id) ? "Successfully" : "Unsuccessfully",
guild_mgr.GetGuildNameByID(guild_id),
guild_id
).c_str()
);
}
}
}
else if (is_set_rank) {
auto rank = static_cast<uint8>(Strings::ToUnsignedInt(sep->arg[3]));
if (!sep->IsNumber(3)) {
c->Message(Chat::White, "#guild setrank [Character ID|Character Name] [Rank]");
}
else if (rank < 0 || rank > GUILD_MAX_RANK) {
c->Message(
Chat::White,
fmt::format(
"Guild Ranks are from 0 to {}.",
GUILD_MAX_RANK
).c_str()
);
}
else {
auto character_id = (
sep->IsNumber(2) ?
Strings::ToUnsignedInt(sep->arg[2]) :
database.GetCharacterID(sep->arg[2])
);
auto character_name = database.GetCharNameByID(character_id);
if (!character_id || character_name.empty()) {
c->Message(
Chat::White,
fmt::format(
"Character ID {} could not be found.",
character_id
).c_str()
);
return;
}
if (!guild_mgr.IsCharacterInGuild(character_id)) {
c->Message(
Chat::White,
fmt::format(
"{} ({}) is not in a guild.",
character_name,
character_id
).c_str()
);
return;
}
LogGuilds(
"[{}]: Setting [{}] ([{}])'s guild rank to [{}] with GM command",
c->GetName(),
sep->arg[2],
character_id,
rank
);
c->Message(
Chat::White,
fmt::format(
"{} ({}) has {} to rank {} ({}).",
character_name,
character_id,
guild_mgr.SetGuildRank(character_id, rank) ? "been set" : "failed to be set",
!guild_mgr.GetGuildRankName(guild_mgr.GetGuildIDByCharacterID(character_id), rank).empty() ? guild_mgr.GetGuildRankName(guild_mgr.GetGuildIDByCharacterID(character_id), rank) : "Nameless",
rank
).c_str()
);
}
}
else if (is_status) {
auto client = (
t ?
t :
(
arguments == 2 ?
entity_list.GetClientByName(sep->arg[2]) :
c
)
);
if (!client) {
c->Message(Chat::White, "You must target someone or specify a character name.");
}
else {
if (!client->IsInAGuild()) {
c->Message(
Chat::White,
fmt::format(
"{} {} not in a guild.",
c->GetTargetDescription(client, TargetDescriptionType::UCYou),
c == t ? "are" : "is"
).c_str()
);
}
else if (guild_mgr.IsGuildLeader(client->GuildID(), client->CharacterID())) {
c->Message(
Chat::White,
fmt::format(
"{} {} the leader of {}.",
c->GetTargetDescription(client, TargetDescriptionType::UCYou),
c == t ? "are" : "is",
guild_mgr.GetGuildNameByID(client->GuildID())
).c_str()
);
}
else {
c->Message(
Chat::White,
fmt::format(
"{} {} a(n) {} of {}.",
c->GetTargetDescription(client, TargetDescriptionType::UCYou),
c == t ? "are" : "is",
guild_mgr.GetRankName(client->GuildID(), client->GuildRank()),
guild_mgr.GetGuildNameByID(client->GuildID())
).c_str()
);
}
}
}
else if (is_details) {
if (!sep->IsNumber(2)) {
c->Message(Chat::White, "Usage: #guild details [Guild ID]");
}
else {
auto guild_id = Strings::ToUnsignedInt(sep->arg[2]);
if (!guild_mgr.GuildExists(guild_id)) {
c->Message(
Chat::White,
fmt::format(
"Guild ID {} could not be found.",
guild_id
).c_str()
);
return;
}
auto guild = guild_mgr.GetGuildByGuildID(guild_id);
if (!guild) {
c->Message(
Chat::Yellow,
fmt::format(
"Guild {} not found. #guild list can be used to get guild ids.",
guild_id
).c_str());
return;
}
if (guild) {
c->Message(Chat::Yellow, fmt::format("Guild ID: {}.", sep->arg[2]).c_str());
c->Message(Chat::Yellow, fmt::format("Guild Name: {}.", guild->name.c_str()).c_str());
c->Message(Chat::Yellow, fmt::format("Guild Leader ID: {}.", guild->leader).c_str());
c->Message(Chat::Yellow, fmt::format("Guild MinStatus: {}.", guild->minstatus).c_str());
c->Message(Chat::Yellow, fmt::format("Guild MOTD: {}.", guild->motd.c_str()).c_str());
c->Message(Chat::Yellow, fmt::format("Guild MOTD Setter:{}.", guild->motd_setter.c_str()).c_str());
c->Message(Chat::Yellow, fmt::format("Guild Channel: {}.", guild->channel.c_str()).c_str());
c->Message(Chat::Yellow, fmt::format("Guild URL: {}.", guild->url.c_str()).c_str());
for (int i = 1; i <= GUILD_MAX_RANK; i++) {
c->Message(
Chat::Yellow,
fmt::format("Guild Rank: {} - {}.", i, guild->rank_names[i].c_str()).c_str());
}
c->Message(Chat::Yellow, "Guild Functions: {db_id} - {guild_id} - {perm_id} - {perm_value}.");
for (int i = 1; i <= GUILD_MAX_FUNCTIONS; i++) {
c->Message(
Chat::Yellow, fmt::format(
"Guild Function: {} - {} - {} - {}.",
guild->functions[i].id,
guild->functions[i].guild_id,
guild->functions[i].perm_id,
guild->functions[i].perm_value
).c_str());
}
c->Message(Chat::Yellow, fmt::format("Guild Tribute: Favor {}", guild->tribute.favor).c_str());
c->Message(
Chat::Yellow, fmt::format(
"Guild Tribute: Tribute 1 {}/{} - Tribute 2 {}/{}",
guild->tribute.id_1,
guild->tribute.id_1_tier,
guild->tribute.id_2,
guild->tribute.id_2_tier
).c_str());
c->Message(
Chat::Yellow, fmt::format(
"Guild Tribute: Time Remaining {} - Enabled {}",
guild->tribute.time_remaining,
guild->tribute.enabled
).c_str());
}
for (auto &c1: entity_list.GetClientList()) {
if (c1.second->GuildID() == guild_id) {
c->Message(
Chat::Yellow, fmt::format(
"PlayerName: {} ID: {} Rank: {} OptIn: {} DirtyList: {}.",
c1.second->GetCleanName(),
c1.second->GuildID(),
c1.second->GuildRank(),
c1.second->GuildTributeOptIn(),
c1.second->GetGuildListDirty()
).c_str());
}
}
}
}
else if (is_test) {
if (!sep->IsNumber(2)) {
c->Message(Chat::White, "Usage: #guild test [Guild ID]");
}
else {
auto guild_id = Strings::ToUnsignedInt(sep->arg[2]);
auto guild = guild_mgr.GetGuildByGuildID(guild_id);
c->SendGuildMembersList();
}
}
}
void SendGuildSubCommands(Client* c)
{
c->Message(Chat::White, "#guild create [Character ID|Character Name] [Guild Name]");
c->Message(Chat::White, "#guild delete [Guild ID]");
c->Message(Chat::White, "#guild details [Guild ID]");
c->Message(Chat::White, "#guild help");
c->Message(Chat::White, "#guild info [Guild ID]");
c->Message(Chat::White, "#guild list");
c->Message(Chat::White, "#guild rename [Guild ID] [New Name]");
c->Message(Chat::White, "#guild search [Search Criteria]");
c->Message(Chat::White, "#guild set [Character ID|Character Name] [Guild ID] (Guild ID 0 is Guildless)");
c->Message(Chat::White, "#guild setleader [Guild ID] [Character ID|Character Name]");
c->Message(Chat::White, "#guild setrank [Character ID|Character Name] [Rank]");
c->Message(Chat::White, "#guild status [Character ID|Character Name]");
}