mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 02:43:44 +00:00
The chat server is the guild authority (docs/Guilds.md). GuildManager keeps guilds in the database (IGuilds) and tells clients through their worlds, with every outside dependency behind hooks so the rules are tested without a server: - Create: name rules (3-30 of letters, digits, space ' - ., no space at either end or twice), the chat filter's deny list refuses a name (BAD_NAME), names not on the allow list make the guild but wait for moderation (other players see no guild name until then), a taken name (case ignored) is EXISTS, someone in a guild can't make one. - Invite / answer: leaders and officers invite; the client's answers for not online, already in a guild, invite pending and could not invite; one invite per player, answerable for 10 minutes and gone when the player logs off; a full guild (guild_max_members) takes nobody. The new member is a recruit; the inviter hears the answer and gets the list again, the other members get GUILD_ADD_PLAYER. - Leave, and DLU's kick, rank and disband (the client has no controls for them): the leader kicks anyone, officers kick veterans and recruits; a leader who leaves hands the guild to the highest-ranked, longest-serving member, and the last one out ends it. Rank changes send everyone the list again (the client's GuildSetPlayerRank does nothing). - GUILD_DATA for GUILD_GET_ALL, login/logout/world-change updates to guildmates, a guild that lost its leader (character deleted) gets one again, and GuildChanged catches online players up with what the dashboard did. - Every change is a guild_events row. Each character's world gets GUILD_GET_STATUS with the guild and the name others may see. Not connected to the chat server's packets yet. Check: GuildManagerTests (18 tests) run every rule against an in-memory IGuilds. Nothing to check in game yet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
117 lines
4.7 KiB
C++
117 lines
4.7 KiB
C++
#ifndef GUILDMANAGER_H
|
|
#define GUILDMANAGER_H
|
|
|
|
#include <cstdint>
|
|
#include <functional>
|
|
#include <optional>
|
|
#include <string>
|
|
#include <unordered_map>
|
|
#include <vector>
|
|
|
|
#include "dCommonVars.h"
|
|
#include "eGuildRank.h"
|
|
#include "IGuilds.h"
|
|
|
|
struct LUBitStream;
|
|
|
|
/**
|
|
* The chat server's guilds (docs/Guilds.md): it is the guild authority. State is in the database (IGuilds); what it
|
|
* tells clients goes through their worlds. It only runs on the chat server's main thread.
|
|
*
|
|
* Everything outside the guild tables is reached through Hooks, so the rules can be tested without a server.
|
|
*/
|
|
class GuildManager {
|
|
public:
|
|
// What the chat server knows about an online player
|
|
struct OnlinePlayer {
|
|
LWOOBJID id{};
|
|
std::string name;
|
|
LWOZONEID zone{};
|
|
};
|
|
|
|
// The chat filter's verdict on a guild name
|
|
enum class NameCheck {
|
|
APPROVED, // on the allow list
|
|
PENDING, // not on the deny list, not (all) on the allow list: a moderator decides
|
|
DENIED, // on the deny list
|
|
};
|
|
|
|
struct Hooks {
|
|
std::function<std::optional<OnlinePlayer>(LWOOBJID)> findOnline;
|
|
std::function<std::optional<OnlinePlayer>(const std::string&)> findOnlineByName;
|
|
// Whether a character with this name exists (for "not online" versus "could not invite")
|
|
std::function<bool(const std::string&)> characterExists;
|
|
// A client packet to an online player (routed through their world)
|
|
std::function<void(LWOOBJID, const LUBitStream&)> sendToPlayer;
|
|
// A chat -> world packet to the world an online player is in
|
|
std::function<void(LWOOBJID, const LUBitStream&)> sendToWorldOf;
|
|
// A system chat line to an online player
|
|
std::function<void(LWOOBJID, const std::string&)> notify;
|
|
std::function<NameCheck(const std::string&)> checkName;
|
|
std::function<int64_t()> now;
|
|
};
|
|
|
|
struct Settings {
|
|
uint32_t maxMembers = 100;
|
|
int64_t inviteTimeout = 600; // seconds an invite can be answered
|
|
};
|
|
|
|
GuildManager(IGuilds& db, Hooks hooks, Settings settings);
|
|
|
|
// GuildNameRules::IsValid
|
|
static bool IsValidName(const std::u16string& name);
|
|
// The client's rank rules as DLU sets them (the client lets anyone try: GuildCanWeInvite/Kick are always true)
|
|
static bool CanInvite(eGuildRank rank);
|
|
static bool CanKick(eGuildRank actor, eGuildRank target);
|
|
static bool CanSetRank(eGuildRank actor, eGuildRank target, eGuildRank newRank);
|
|
|
|
// From the client (through its world)
|
|
void Create(LWOOBJID playerID, const std::u16string& name);
|
|
void Invite(LWOOBJID playerID, const std::string& targetName);
|
|
void AnswerInvite(LWOOBJID playerID, bool declined);
|
|
void Leave(LWOOBJID playerID);
|
|
void GetAll(LWOOBJID playerID);
|
|
// DLU slash commands (the client has no controls for them)
|
|
void Kick(LWOOBJID playerID, const std::string& targetName);
|
|
void SetRank(LWOOBJID playerID, const std::string& targetName, eGuildRank rank);
|
|
void Disband(LWOOBJID playerID);
|
|
|
|
// A player came online (login) or changed worlds
|
|
void PlayerOnline(LWOOBJID playerID, bool login);
|
|
// A player went offline (before the chat server forgets them)
|
|
void PlayerOffline(LWOOBJID playerID);
|
|
|
|
// The dashboard changed a guild in the database (renamed, name moderated, disbanded, member removed): tell the
|
|
// online players what changed for them
|
|
void GuildChanged(int64_t guildID);
|
|
|
|
// Online members of the player's guild, the player included (empty: not in a guild)
|
|
std::vector<LWOOBJID> OnlineGuildmates(LWOOBJID playerID);
|
|
|
|
// The name other players see: nothing while it waits for moderation
|
|
static std::string ShownName(const IGuilds::Guild& guild);
|
|
// MM/DD/YYYY, UTC
|
|
static std::string FormatDate(int64_t time);
|
|
|
|
private:
|
|
void SendStatus(LWOOBJID characterID, int64_t guildID, const std::string& shownName);
|
|
void SendData(LWOOBJID playerID);
|
|
void SendDataToOnline(int64_t guildID);
|
|
void LogEvent(int64_t guildID, const std::string& kind, LWOOBJID characterID, const std::string& characterName, const std::string& actor, const std::string& detail = "");
|
|
// The members, with a leader again if the guild lost its (the leader's character was deleted). Empty: the guild is
|
|
// gone (deleted when it had no members left).
|
|
std::vector<IGuilds::Member> Members(int64_t guildID);
|
|
std::optional<IGuilds::Member> FindMember(const std::vector<IGuilds::Member>& members, const std::string& name) const;
|
|
// Removes a member and tells everyone; hands the guild on when the leader goes, deletes it with the last member
|
|
void RemoveMember(const IGuilds::Guild& guild, const IGuilds::Member& member, eGuildLeaveReason reason, const std::string& actor);
|
|
std::string NameOf(LWOOBJID playerID, const std::string& fallback = "");
|
|
|
|
IGuilds& m_Db;
|
|
Hooks m_Hooks;
|
|
Settings m_Settings;
|
|
// Online players' guilds, to know who to tell when the dashboard changes a guild behind the chat server's back
|
|
std::unordered_map<LWOOBJID, int64_t> m_OnlineGuild;
|
|
};
|
|
|
|
#endif // GUILDMANAGER_H
|