mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 10:53:44 +00:00
feat(world): guilds in the world: create box, character component, guild commands
The world side of guilds (docs/Guilds.md): - TMP_GUILD_CREATE, which the client's guild create box sends to its world, goes to the chat server as GUILD_CREATE. - The character component reads the character's guild from the database when it loads (instead of the old unused "gn" and "gid" charxml attributes) and takes GUILD_GET_STATUS from the chat server; a change is serialized once (the client redraws the name billboard every time it reads a guild name). A name waiting for moderation isn't shown. - Slash commands (the client has none for guilds): /g and /guild (guild chat; the client's guild tab sends /g, which goes through the chat filter and mute like zone chat, then to the chat server as channel 10), /guildcreate (opens the create box with DisplayGuildCreateBox), /gkick, /grank <name> <officer|veteran|recruit>, /gleader and /gdisband confirm. - The Guild Master script (LOT 3001, L_GUILD_CREATE.lua: using it opens the create box). Live never placed it; GMs can spawn it. Check in game (two accounts, a client with a FeatureGating row "guilds", 1, 0, 0 in its cdclient.fdb): /guildcreate, make a guild (the name shows under yours, the guild button appears on the status bar); invite the second character from the guild window, accept, both lists show both; guild chat tab; /grank, /gkick, /gleader; leave from the window; log the second character out and in (guildmate logged off/in lines). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,5 +2,6 @@ set(DGAME_DUTILITIES_SLASHCOMMANDS
|
||||
"DEVGMCommands.cpp"
|
||||
"GMGreaterThanZeroCommands.cpp"
|
||||
"GMZeroCommands.cpp"
|
||||
"GuildCommands.cpp"
|
||||
PARENT_SCOPE
|
||||
)
|
||||
|
||||
120
dGame/dUtilities/SlashCommands/GuildCommands.cpp
Normal file
120
dGame/dUtilities/SlashCommands/GuildCommands.cpp
Normal file
@@ -0,0 +1,120 @@
|
||||
#include "GuildCommands.h"
|
||||
|
||||
#include <cctype>
|
||||
|
||||
#include "Character.h"
|
||||
#include "ChatPackets.h"
|
||||
#include "ChatServerLink.h"
|
||||
#include "dChatFilter.h"
|
||||
#include "Entity.h"
|
||||
#include "eChatChannel.h"
|
||||
#include "eGameMasterLevel.h"
|
||||
#include "eGuildRank.h"
|
||||
#include "Game.h"
|
||||
#include "GeneralUtils.h"
|
||||
#include "PlayerMessages.h"
|
||||
#include "User.h"
|
||||
|
||||
namespace {
|
||||
std::string Lower(std::string text) {
|
||||
for (auto& c : text) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
||||
return text;
|
||||
}
|
||||
|
||||
std::string Trimmed(const std::string& text) {
|
||||
const auto start = text.find_first_not_of(' ');
|
||||
if (start == std::string::npos) return "";
|
||||
return text.substr(start, text.find_last_not_of(' ') - start + 1);
|
||||
}
|
||||
|
||||
LUWString PlayerName(const std::string& name) {
|
||||
return LUWString(GeneralUtils::UTF8ToUTF16(name), 33);
|
||||
}
|
||||
|
||||
void SendRank(Entity* entity, const std::string& name, const eGuildRank rank) {
|
||||
ChatPackets::GuildSetRank request;
|
||||
request.playerID = entity->GetObjectID();
|
||||
request.targetPlayer = PlayerName(name);
|
||||
request.rank = static_cast<uint8_t>(rank);
|
||||
ChatServerLink::Send(request);
|
||||
}
|
||||
}
|
||||
|
||||
void GuildCommands::Chat(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
||||
const auto message = Trimmed(args);
|
||||
if (message.empty()) return;
|
||||
auto* character = entity->GetCharacter();
|
||||
if (!character) return;
|
||||
auto* user = character->GetParentUser();
|
||||
if (user && user->GetIsMuted()) {
|
||||
character->SendMuteNotice();
|
||||
return;
|
||||
}
|
||||
// The same filter as the zone's chat
|
||||
if (!Game::chatFilter->IsSentenceOkay(message, character->GetGMLevel()).empty()) {
|
||||
ChatPackets::SendSystemMessage(sysAddr, u"Your message was not sent to your guild.");
|
||||
return;
|
||||
}
|
||||
|
||||
auto text = GeneralUtils::UTF8ToUTF16(message);
|
||||
ChatPackets::GeneralChatMessage chat;
|
||||
chat.playerID = entity->GetObjectID();
|
||||
chat.chatChannel = eChatChannel::GUILD;
|
||||
chat.messageLength = static_cast<uint32_t>(text.size());
|
||||
chat.senderName = PlayerName(character->GetName());
|
||||
chat.senderID = entity->GetObjectID();
|
||||
chat.senderGMLevel = static_cast<uint8_t>(character->GetGMLevel());
|
||||
chat.message = LUWString(text, static_cast<uint32_t>(text.size()));
|
||||
ChatServerLink::Send(chat);
|
||||
}
|
||||
|
||||
void GuildCommands::OpenCreateBox(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
||||
GameMessages::DisplayGuildCreateBox box;
|
||||
box.target = entity->GetObjectID();
|
||||
box.Send(sysAddr);
|
||||
}
|
||||
|
||||
void GuildCommands::Kick(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
||||
const auto name = Trimmed(args);
|
||||
if (name.empty()) {
|
||||
ChatPackets::SendSystemMessage(sysAddr, u"Usage: /gkick <name>");
|
||||
return;
|
||||
}
|
||||
ChatPackets::GuildKick request;
|
||||
request.playerID = entity->GetObjectID();
|
||||
request.kickedPlayer = PlayerName(name);
|
||||
ChatServerLink::Send(request);
|
||||
}
|
||||
|
||||
void GuildCommands::Rank(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
||||
const auto split = GeneralUtils::SplitString(Trimmed(args), ' ');
|
||||
const std::string rankName = split.size() == 2 ? Lower(split[1]) : "";
|
||||
eGuildRank rank = eGuildRank::NONE;
|
||||
if (rankName == "officer") rank = eGuildRank::OFFICER;
|
||||
else if (rankName == "veteran") rank = eGuildRank::VETERAN;
|
||||
else if (rankName == "recruit") rank = eGuildRank::RECRUIT;
|
||||
if (rank == eGuildRank::NONE) {
|
||||
ChatPackets::SendSystemMessage(sysAddr, u"Usage: /grank <name> <officer|veteran|recruit>");
|
||||
return;
|
||||
}
|
||||
SendRank(entity, split[0], rank);
|
||||
}
|
||||
|
||||
void GuildCommands::Leader(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
||||
const auto name = Trimmed(args);
|
||||
if (name.empty()) {
|
||||
ChatPackets::SendSystemMessage(sysAddr, u"Usage: /gleader <name>");
|
||||
return;
|
||||
}
|
||||
SendRank(entity, name, eGuildRank::LEADER);
|
||||
}
|
||||
|
||||
void GuildCommands::Disband(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
||||
if (Trimmed(args) != "confirm") {
|
||||
ChatPackets::SendSystemMessage(sysAddr, u"This removes every member and the guild itself. To do it, type /gdisband confirm");
|
||||
return;
|
||||
}
|
||||
ChatPackets::GuildDisband request;
|
||||
request.playerID = entity->GetObjectID();
|
||||
ChatServerLink::Send(request);
|
||||
}
|
||||
23
dGame/dUtilities/SlashCommands/GuildCommands.h
Normal file
23
dGame/dUtilities/SlashCommands/GuildCommands.h
Normal file
@@ -0,0 +1,23 @@
|
||||
#ifndef GUILDCOMMANDS_H
|
||||
#define GUILDCOMMANDS_H
|
||||
|
||||
#include <string>
|
||||
|
||||
class Entity;
|
||||
struct SystemAddress;
|
||||
|
||||
/**
|
||||
* Guild slash commands (docs/Guilds.md). The client has none: its guild chat tab sends "/g <text>", which reaches the
|
||||
* server like any unknown command, and it has no controls for kicking, ranks or disbanding. The chat server decides
|
||||
* everything; these only pass the request on.
|
||||
*/
|
||||
namespace GuildCommands {
|
||||
void Chat(Entity* entity, const SystemAddress& sysAddr, const std::string args);
|
||||
void OpenCreateBox(Entity* entity, const SystemAddress& sysAddr, const std::string args);
|
||||
void Kick(Entity* entity, const SystemAddress& sysAddr, const std::string args);
|
||||
void Rank(Entity* entity, const SystemAddress& sysAddr, const std::string args);
|
||||
void Leader(Entity* entity, const SystemAddress& sysAddr, const std::string args);
|
||||
void Disband(Entity* entity, const SystemAddress& sysAddr, const std::string args);
|
||||
}
|
||||
|
||||
#endif // GUILDCOMMANDS_H
|
||||
Reference in New Issue
Block a user