mirror of
https://github.com/EQEmu/Server.git
synced 2025-12-11 21:01:29 +00:00
2447 lines
62 KiB
C++
2447 lines
62 KiB
C++
/* EQEMu: Everquest Server Emulator
|
|
Copyright (C) 2001-2008 EQEMu Development Team (http://eqemulator.net)
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; version 2 of the License.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY except by those people which sell it, which
|
|
are required to give you total support for your newly bought product;
|
|
without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
|
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
|
|
*/
|
|
|
|
#include "../common/global_define.h"
|
|
#include "../common/strings.h"
|
|
#include "../common/eqemu_logsys.h"
|
|
#include "../common/misc_functions.h"
|
|
|
|
#include "ucsconfig.h"
|
|
#include "clientlist.h"
|
|
#include "database.h"
|
|
#include "chatchannel.h"
|
|
#include "../common/path_manager.h"
|
|
|
|
#include <list>
|
|
#include <vector>
|
|
#include <string>
|
|
#include <cstdlib>
|
|
#include <algorithm>
|
|
|
|
extern UCSDatabase database;
|
|
extern std::string WorldShortName;
|
|
extern std::string GetMailPrefix();
|
|
extern ChatChannelList *ChannelList;
|
|
extern Clientlist *g_Clientlist;
|
|
extern uint32 ChatMessagesSent;
|
|
extern uint32 MailMessagesSent;
|
|
|
|
int LookupCommand(const char *ChatCommand) {
|
|
|
|
if (!ChatCommand) return -1;
|
|
|
|
for (int i = 0; i < CommandEndOfList; i++) {
|
|
|
|
if (!strcasecmp(Commands[i].CommandString, ChatCommand)) {
|
|
return Commands[i].CommandCode;
|
|
}
|
|
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
void Client::SendUptime() {
|
|
|
|
uint32 ms = Timer::GetCurrentTime();
|
|
uint32 d = ms / 86400000;
|
|
ms -= d * 86400000;
|
|
uint32 h = ms / 3600000;
|
|
ms -= h * 3600000;
|
|
uint32 m = ms / 60000;
|
|
ms -= m * 60000;
|
|
uint32 s = ms / 1000;
|
|
|
|
auto message = fmt::format("UCS has been up for {:02}d {:02}h {:02}m {:02}s", d, h, m, s);
|
|
GeneralChannelMessage(message);
|
|
message = fmt::format("Chat Messages Sent: {}, Mail Messages Sent: {}", ChatMessagesSent, MailMessagesSent);
|
|
GeneralChannelMessage(message);
|
|
}
|
|
|
|
std::vector<std::string> ParseRecipients(std::string RecipientString) {
|
|
|
|
// This method parses the Recipient List in the mailto command, which can look like this example:
|
|
//
|
|
// "Baalinor <SOE.EQ.BTG2.Baalinor>,
|
|
// -Friends <SOE.EQ.BTG2.Playedtest SOE.EQ.BTG2.Dyetest>,
|
|
// Guild <SOE.EQ.BTG2.Dsfvxcbcx SOE.EQ.BTG2.Necronor>, SOE.EQ.BTG2.luccerathe, SOE.EQ.BTG2.codsas
|
|
//
|
|
// First, it splits it up at commas, so it looks like this:
|
|
//
|
|
// Baalinor <SOE.EQ.BTG2.Baalinor>
|
|
// -Friends <SOE.EQ.BTG2.Playedtest SOE.EQ.BTG2.Dyetest>
|
|
// Guild <SOE.EQ.BTG2.Dsfvxcbcx SOE.EQ.BTG2.Necronor>
|
|
// SOE.EQ.BTG2.luccerathe
|
|
// SOE.EQ.BTG2
|
|
//
|
|
// Then, if an entry has a '<' in it, it extracts the text between the < and >
|
|
// If the text between the < and > has a space in it, then there are multiple addresses in there, so those are extracted.
|
|
//
|
|
// The prefix (SOE.EQ.<Shortname>) is discarded, the names are normalised so they begin with a single upper case character
|
|
// followed by lower case.
|
|
//
|
|
// The vector is sorted and any duplicates discarded, so the vector we return, in our example, looks like this:
|
|
//
|
|
// Baalinor
|
|
// -Playedtest
|
|
// -Dyetest
|
|
// Dsfvxcbcx
|
|
// Necronor
|
|
// Luccerathe
|
|
// Codsas
|
|
//
|
|
// The '-' prefix indicates 'Secret To' (like BCC:)
|
|
//
|
|
std::vector<std::string> RecipientList;
|
|
|
|
std::string Secret;
|
|
|
|
std::string::size_type CurrentPos, Comma, FirstLT, LastGT, Space, LastPeriod;
|
|
|
|
CurrentPos = 0;
|
|
|
|
while (CurrentPos != std::string::npos) {
|
|
|
|
Comma = RecipientString.find_first_of(",", CurrentPos);
|
|
|
|
if (Comma == std::string::npos) {
|
|
|
|
RecipientList.emplace_back(RecipientString.substr(CurrentPos));
|
|
|
|
break;
|
|
}
|
|
RecipientList.emplace_back(RecipientString.substr(CurrentPos, Comma - CurrentPos));
|
|
|
|
CurrentPos = Comma + 2;
|
|
}
|
|
|
|
std::vector<std::string>::iterator Iterator;
|
|
|
|
Iterator = RecipientList.begin();
|
|
|
|
while (Iterator != RecipientList.end()) {
|
|
|
|
if ((*Iterator)[0] == '-') {
|
|
|
|
Secret = "-";
|
|
|
|
while ((*Iterator)[0] == '-')
|
|
(*Iterator) = (*Iterator).substr(1);
|
|
}
|
|
else
|
|
Secret = "";
|
|
|
|
FirstLT = (*Iterator).find_first_of("<");
|
|
|
|
if (FirstLT != std::string::npos) {
|
|
|
|
LastGT = (*Iterator).find_last_of(">");
|
|
|
|
if (LastGT != std::string::npos) {
|
|
|
|
(*Iterator) = (*Iterator).substr(FirstLT + 1, LastGT - FirstLT - 1);
|
|
|
|
if ((*Iterator).find_first_of(" ") != std::string::npos) {
|
|
|
|
std::string Recips = (*Iterator);
|
|
|
|
RecipientList.erase(Iterator);
|
|
|
|
CurrentPos = 0;
|
|
|
|
while (CurrentPos != std::string::npos) {
|
|
|
|
Space = Recips.find_first_of(" ", CurrentPos);
|
|
|
|
if (Space == std::string::npos) {
|
|
|
|
RecipientList.push_back(Secret + Recips.substr(CurrentPos));
|
|
|
|
break;
|
|
}
|
|
RecipientList.push_back(Secret + Recips.substr(CurrentPos,
|
|
Space - CurrentPos));
|
|
CurrentPos = Space + 1;
|
|
}
|
|
Iterator = RecipientList.begin();
|
|
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
(*Iterator) = Secret + (*Iterator);
|
|
|
|
++Iterator;
|
|
|
|
}
|
|
|
|
for (Iterator = RecipientList.begin(); Iterator != RecipientList.end(); ++Iterator) {
|
|
|
|
if ((*Iterator).length() > 0) {
|
|
|
|
if ((*Iterator)[0] == '-')
|
|
Secret = "-";
|
|
else
|
|
Secret = "";
|
|
|
|
LastPeriod = (*Iterator).find_last_of(".");
|
|
|
|
if (LastPeriod != std::string::npos) {
|
|
|
|
(*Iterator) = (*Iterator).substr(LastPeriod + 1);
|
|
|
|
for (unsigned int i = 0; i < (*Iterator).length(); i++) {
|
|
|
|
if (i == 0)
|
|
(*Iterator)[i] = toupper((*Iterator)[i]);
|
|
else
|
|
(*Iterator)[i] = tolower((*Iterator)[i]);
|
|
}
|
|
|
|
(*Iterator) = Secret + (*Iterator);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
sort(RecipientList.begin(), RecipientList.end());
|
|
|
|
auto new_end_pos = unique(RecipientList.begin(), RecipientList.end());
|
|
|
|
RecipientList.erase(new_end_pos, RecipientList.end());
|
|
|
|
return RecipientList;
|
|
|
|
}
|
|
|
|
static void ProcessMailTo(Client *c, std::string MailMessage) {
|
|
|
|
LogDebug("MAILTO: From [{}], [{}]", c->MailBoxName().c_str(), MailMessage.c_str());
|
|
|
|
std::vector<std::string> Recipients;
|
|
|
|
std::string::size_type FirstQuote = MailMessage.find_first_of("\"", 0);
|
|
|
|
std::string::size_type NextQuote = MailMessage.find_first_of("\"", FirstQuote + 1);
|
|
|
|
std::string RecipientsString = MailMessage.substr(FirstQuote + 1, NextQuote - FirstQuote - 1);
|
|
|
|
Recipients = ParseRecipients(RecipientsString);
|
|
|
|
// Now extract the subject field. This is in quotes if it is more than one word
|
|
//
|
|
std::string Subject;
|
|
|
|
std::string::size_type SubjectStart = NextQuote + 2;
|
|
|
|
std::string::size_type SubjectEnd;
|
|
|
|
if (MailMessage.substr(SubjectStart, 1) == "\"") {
|
|
|
|
SubjectEnd = MailMessage.find_first_of("\"", SubjectStart + 1);
|
|
|
|
Subject = MailMessage.substr(SubjectStart + 1, SubjectEnd - SubjectStart - 1);
|
|
|
|
SubjectEnd += 2;
|
|
|
|
}
|
|
else {
|
|
SubjectEnd = MailMessage.find_first_of(" ", SubjectStart);
|
|
|
|
Subject = MailMessage.substr(SubjectStart, SubjectEnd - SubjectStart);
|
|
|
|
SubjectEnd++;
|
|
|
|
}
|
|
std::string Body = MailMessage.substr(SubjectEnd);
|
|
|
|
bool Success = true;
|
|
|
|
RecipientsString.clear();
|
|
|
|
int VisibleRecipients = 0;
|
|
|
|
for (auto &Recipient : Recipients) {
|
|
|
|
if (Recipient[0] == '-') {
|
|
|
|
Recipient = Recipient.substr(1);
|
|
|
|
}
|
|
else {
|
|
if (VisibleRecipients > 0)
|
|
|
|
RecipientsString += ", ";
|
|
|
|
VisibleRecipients++;
|
|
|
|
RecipientsString = RecipientsString + GetMailPrefix() + Recipient;
|
|
}
|
|
}
|
|
if (VisibleRecipients == 0)
|
|
RecipientsString = "<UNDISCLOSED RECIPIENTS>";
|
|
|
|
for (auto &Recipient : Recipients) {
|
|
|
|
if (!database.SendMail(Recipient, c->MailBoxName(), Subject, Body, RecipientsString)) {
|
|
|
|
LogError("Failed in SendMail([{}], [{}], [{}], [{}])", Recipient.c_str(),
|
|
c->MailBoxName().c_str(), Subject.c_str(), RecipientsString.c_str());
|
|
|
|
int PacketLength = 10 + Recipient.length() + Subject.length();
|
|
|
|
// Failure
|
|
auto outapp = new EQApplicationPacket(OP_MailDeliveryStatus, PacketLength);
|
|
|
|
char *PacketBuffer = (char *)outapp->pBuffer;
|
|
|
|
VARSTRUCT_ENCODE_STRING(PacketBuffer, "1");
|
|
VARSTRUCT_ENCODE_TYPE(uint8, PacketBuffer, 0x20);
|
|
VARSTRUCT_ENCODE_STRING(PacketBuffer, Recipient.c_str());
|
|
VARSTRUCT_ENCODE_STRING(PacketBuffer, Subject.c_str());
|
|
VARSTRUCT_ENCODE_STRING(PacketBuffer, "0");
|
|
VARSTRUCT_ENCODE_TYPE(uint16, PacketBuffer, 0x3237);
|
|
VARSTRUCT_ENCODE_TYPE(uint8, PacketBuffer, 0x0);
|
|
|
|
|
|
c->QueuePacket(outapp);
|
|
|
|
safe_delete(outapp);
|
|
|
|
Success = false;
|
|
}
|
|
}
|
|
|
|
if (Success) {
|
|
// Success
|
|
auto outapp = new EQApplicationPacket(OP_MailDeliveryStatus, 10);
|
|
|
|
char *PacketBuffer = (char *)outapp->pBuffer;
|
|
|
|
VARSTRUCT_ENCODE_STRING(PacketBuffer, "1");
|
|
VARSTRUCT_ENCODE_TYPE(uint8, PacketBuffer, 0);
|
|
VARSTRUCT_ENCODE_STRING(PacketBuffer, "test"); // Doesn't matter what we send in this text field.
|
|
VARSTRUCT_ENCODE_STRING(PacketBuffer, "1");
|
|
|
|
|
|
c->QueuePacket(outapp);
|
|
|
|
safe_delete(outapp);
|
|
}
|
|
}
|
|
|
|
static void ProcessMailTo(Client *c, const std::string& from, const std::string& subject, const std::string& message) {
|
|
}
|
|
|
|
static void ProcessSetMessageStatus(std::string SetMessageCommand) {
|
|
|
|
int MessageNumber;
|
|
|
|
int Status;
|
|
|
|
switch (SetMessageCommand[0]) {
|
|
|
|
case 'R': // READ
|
|
Status = 3;
|
|
break;
|
|
|
|
case 'T': // TRASH
|
|
Status = 4;
|
|
break;
|
|
|
|
default: // DELETE
|
|
Status = 0;
|
|
|
|
}
|
|
std::string::size_type NumStart = SetMessageCommand.find_first_of("123456789");
|
|
|
|
while (NumStart != std::string::npos) {
|
|
|
|
std::string::size_type NumEnd = SetMessageCommand.find_first_of(" ", NumStart);
|
|
|
|
if (NumEnd == std::string::npos) {
|
|
|
|
MessageNumber = Strings::ToInt(SetMessageCommand.substr(NumStart));
|
|
|
|
database.SetMessageStatus(MessageNumber, Status);
|
|
|
|
break;
|
|
}
|
|
|
|
MessageNumber = Strings::ToInt(SetMessageCommand.substr(NumStart, NumEnd - NumStart));
|
|
|
|
database.SetMessageStatus(MessageNumber, Status);
|
|
|
|
NumStart = SetMessageCommand.find_first_of("123456789", NumEnd);
|
|
}
|
|
}
|
|
|
|
static void ProcessCommandBuddy(Client *c, std::string Buddy) {
|
|
|
|
LogInfo("Received buddy command with parameters [{}]", Buddy.c_str());
|
|
c->GeneralChannelMessage("Buddy list modified");
|
|
|
|
uint8 SubAction = 1;
|
|
|
|
if (Buddy.substr(0, 1) == "-")
|
|
SubAction = 0;
|
|
|
|
auto outapp = new EQApplicationPacket(OP_Buddy, Buddy.length() + 2);
|
|
char *PacketBuffer = (char *)outapp->pBuffer;
|
|
VARSTRUCT_ENCODE_TYPE(uint8, PacketBuffer, SubAction);
|
|
|
|
if (SubAction == 1) {
|
|
VARSTRUCT_ENCODE_STRING(PacketBuffer, Buddy.c_str());
|
|
database.AddFriendOrIgnore(c->GetCharID(), 1, Buddy);
|
|
}
|
|
else {
|
|
VARSTRUCT_ENCODE_STRING(PacketBuffer, Buddy.substr(1).c_str());
|
|
database.RemoveFriendOrIgnore(c->GetCharID(), 1, Buddy.substr(1));
|
|
}
|
|
|
|
c->QueuePacket(outapp);
|
|
|
|
safe_delete(outapp);
|
|
|
|
}
|
|
|
|
static void ProcessCommandIgnore(Client *c, std::string Ignoree) {
|
|
|
|
LogInfo("Received ignore command with parameters [{}]", Ignoree.c_str());
|
|
c->GeneralChannelMessage("Ignore list modified");
|
|
|
|
uint8 SubAction = 0;
|
|
|
|
if (Ignoree.substr(0, 1) == "-") {
|
|
SubAction = 1;
|
|
Ignoree = Ignoree.substr(1);
|
|
|
|
// Strip off the SOE.EQ.<shortname>.
|
|
//
|
|
std::string CharacterName;
|
|
|
|
std::string::size_type LastPeriod = Ignoree.find_last_of(".");
|
|
|
|
if (LastPeriod == std::string::npos)
|
|
CharacterName = Ignoree;
|
|
else
|
|
CharacterName = Ignoree.substr(LastPeriod + 1);
|
|
|
|
database.RemoveFriendOrIgnore(c->GetCharID(), 0, CharacterName);
|
|
|
|
}
|
|
else
|
|
{
|
|
database.AddFriendOrIgnore(c->GetCharID(), 0, Ignoree);
|
|
Ignoree = "SOE.EQ." + WorldShortName + "." + Ignoree;
|
|
}
|
|
|
|
auto outapp = new EQApplicationPacket(OP_Ignore, Ignoree.length() + 2);
|
|
char *PacketBuffer = (char *)outapp->pBuffer;
|
|
VARSTRUCT_ENCODE_TYPE(uint8, PacketBuffer, SubAction);
|
|
|
|
VARSTRUCT_ENCODE_STRING(PacketBuffer, Ignoree.c_str());
|
|
|
|
c->QueuePacket(outapp);
|
|
|
|
safe_delete(outapp);
|
|
|
|
}
|
|
|
|
Clientlist::Clientlist(int ChatPort) {
|
|
EQStreamManagerInterfaceOptions chat_opts(ChatPort, false, false);
|
|
chat_opts.opcode_size = 1;
|
|
chat_opts.reliable_stream_options.stale_connection_ms = 600000;
|
|
chat_opts.reliable_stream_options.resend_delay_ms = RuleI(Network, ResendDelayBaseMS);
|
|
chat_opts.reliable_stream_options.resend_delay_factor = RuleR(Network, ResendDelayFactor);
|
|
chat_opts.reliable_stream_options.resend_delay_min = RuleI(Network, ResendDelayMinMS);
|
|
chat_opts.reliable_stream_options.resend_delay_max = RuleI(Network, ResendDelayMaxMS);
|
|
|
|
chatsf = new EQ::Net::EQStreamManager(chat_opts);
|
|
|
|
ChatOpMgr = new RegularOpcodeManager;
|
|
|
|
const ucsconfig *Config = ucsconfig::get();
|
|
|
|
|
|
std::string opcodes_file = fmt::format("{}/{}", PathManager::Instance()->GetServerPath(), Config->MailOpCodesFile);
|
|
|
|
LogInfo("Loading [{}]", opcodes_file);
|
|
if (!ChatOpMgr->LoadOpcodes(opcodes_file.c_str()))
|
|
exit(1);
|
|
|
|
chatsf->OnNewConnection([this](std::shared_ptr<EQ::Net::EQStream> stream) {
|
|
LogInfo("New Client UDP connection from [{0}] [{1}]", stream->GetRemoteIP(), stream->GetRemotePort());
|
|
stream->SetOpcodeManager(&ChatOpMgr);
|
|
|
|
auto c = new Client(stream);
|
|
ClientChatConnections.push_back(c);
|
|
});
|
|
}
|
|
|
|
Client::Client(std::shared_ptr<EQStreamInterface> eqs) {
|
|
|
|
ClientStream = eqs;
|
|
|
|
CurrentMailBox = 0;
|
|
|
|
Announce = false;
|
|
|
|
Status = 0;
|
|
|
|
HideMe = 0;
|
|
|
|
AccountID = 0;
|
|
|
|
AllowInvites = true;
|
|
Revoked = false;
|
|
|
|
for (auto &elem : JoinedChannels)
|
|
elem = nullptr;
|
|
|
|
TotalKarma = 0;
|
|
AttemptedMessages = 0;
|
|
ForceDisconnect = false;
|
|
|
|
AccountGrabUpdateTimer = new Timer(60000); //check every minute
|
|
GlobalChatLimiterTimer = new Timer(RuleI(Chat, IntervalDurationMS));
|
|
|
|
TypeOfConnection = ConnectionTypeUnknown;
|
|
ClientVersion_ = EQ::versions::ClientVersion::Unknown;
|
|
|
|
UnderfootOrLater = false;
|
|
}
|
|
|
|
Client::~Client() {
|
|
|
|
CloseConnection();
|
|
|
|
LeaveAllChannels(false);
|
|
|
|
if (AccountGrabUpdateTimer)
|
|
{
|
|
delete AccountGrabUpdateTimer;
|
|
AccountGrabUpdateTimer = nullptr;
|
|
}
|
|
|
|
if (GlobalChatLimiterTimer)
|
|
{
|
|
delete GlobalChatLimiterTimer;
|
|
GlobalChatLimiterTimer = nullptr;
|
|
}
|
|
}
|
|
|
|
void Client::CloseConnection() {
|
|
|
|
ClientStream->RemoveData();
|
|
|
|
ClientStream->Close();
|
|
|
|
ClientStream->ReleaseFromUse();
|
|
}
|
|
|
|
void Clientlist::CheckForStaleConnectionsAll()
|
|
{
|
|
LogDebug("Checking for stale connections");
|
|
|
|
auto it = ClientChatConnections.begin();
|
|
while (it != ClientChatConnections.end()) {
|
|
(*it)->SendKeepAlive();
|
|
++it;
|
|
}
|
|
}
|
|
|
|
void Clientlist::CheckForStaleConnections(Client *c) {
|
|
|
|
if (!c) {
|
|
return;
|
|
}
|
|
|
|
std::list<Client*>::iterator Iterator;
|
|
|
|
for (Iterator = ClientChatConnections.begin(); Iterator != ClientChatConnections.end(); ++Iterator) {
|
|
|
|
if (((*Iterator) != c) && ((c->GetName() == (*Iterator)->GetName())
|
|
&& (c->GetConnectionType() == (*Iterator)->GetConnectionType()))) {
|
|
|
|
LogInfo("Removing old connection for [{}]", c->GetName().c_str());
|
|
|
|
struct in_addr in;
|
|
|
|
in.s_addr = (*Iterator)->ClientStream->GetRemoteIP();
|
|
|
|
LogInfo("Client connection from [{}]:[{}] closed", inet_ntoa(in),
|
|
ntohs((*Iterator)->ClientStream->GetRemotePort()));
|
|
|
|
safe_delete((*Iterator));
|
|
|
|
Iterator = ClientChatConnections.erase(Iterator);
|
|
}
|
|
}
|
|
}
|
|
|
|
std::string RemoveDuplicateChannels(const std::string& in_channels) {
|
|
// Split the string by ", " and store the names in a vector
|
|
std::vector<std::string> channel_names = Strings::Split(in_channels, ", ");
|
|
|
|
// Remove duplicates by inserting the names of the channels into an unordered set
|
|
// and then copying the unique elements back into the original vector
|
|
std::unordered_set<std::string> unique_channels;
|
|
channel_names.erase(
|
|
std::remove_if(
|
|
channel_names.begin(), channel_names.end(),
|
|
[&unique_channels](const std::string &channel) {
|
|
return !unique_channels.insert(channel).second;
|
|
}
|
|
), channel_names.end()
|
|
);
|
|
|
|
// Concatenate the names of the unique channels into a single string
|
|
std::string unique_channels_string = Strings::Implode(", ", channel_names);
|
|
|
|
return unique_channels_string;
|
|
}
|
|
|
|
void Clientlist::Process()
|
|
{
|
|
auto it = ClientChatConnections.begin();
|
|
while (it != ClientChatConnections.end()) {
|
|
(*it)->AccountUpdate();
|
|
if ((*it)->ClientStream->CheckState(CLOSED)) {
|
|
struct in_addr in;
|
|
in.s_addr = (*it)->ClientStream->GetRemoteIP();
|
|
|
|
LogInfo("Client connection from [{}]:[{}] closed", inet_ntoa(in),
|
|
ntohs((*it)->ClientStream->GetRemotePort()));
|
|
|
|
safe_delete((*it));
|
|
|
|
it = ClientChatConnections.erase(it);
|
|
continue;
|
|
}
|
|
|
|
EQApplicationPacket *app = nullptr;
|
|
|
|
bool KeyValid = true;
|
|
|
|
while (KeyValid && !(*it)->GetForceDisconnect() && (app = (*it)->ClientStream->PopPacket())) {
|
|
EmuOpcode opcode = app->GetOpcode();
|
|
|
|
auto o = (*it)->ClientStream->GetOpcodeManager();
|
|
LogPacketClientServer(
|
|
"[{}] [{:#06x}] Size [{}] {}",
|
|
OpcodeManager::EmuToName(app->GetOpcode()),
|
|
o->EmuToEQ(app->GetOpcode()) == 0 ? app->GetProtocolOpcode() : o->EmuToEQ(app->GetOpcode()),
|
|
app->Size(),
|
|
(EQEmuLogSys::Instance()->IsLogEnabled(Logs::Detail, Logs::PacketClientServer) ? DumpPacketToString(app) : "")
|
|
);
|
|
|
|
switch (opcode) {
|
|
case OP_MailLogin: {
|
|
char *PacketBuffer = (char *)app->pBuffer + 1;
|
|
char MailBox[64];
|
|
char Key[64];
|
|
char ConnectionTypeIndicator;
|
|
|
|
VARSTRUCT_DECODE_STRING(MailBox, PacketBuffer);
|
|
|
|
if (strlen(PacketBuffer) != 9) {
|
|
LogInfo("Mail key is the wrong size. Version of world incompatible with UCS.");
|
|
KeyValid = false;
|
|
break;
|
|
}
|
|
ConnectionTypeIndicator = VARSTRUCT_DECODE_TYPE(char, PacketBuffer);
|
|
|
|
(*it)->SetConnectionType(ConnectionTypeIndicator);
|
|
|
|
VARSTRUCT_DECODE_STRING(Key, PacketBuffer);
|
|
|
|
std::string MailBoxString = MailBox, CharacterName;
|
|
|
|
// Strip off the SOE.EQ.<shortname>.
|
|
//
|
|
std::string::size_type LastPeriod = MailBoxString.find_last_of(".");
|
|
|
|
if (LastPeriod == std::string::npos) {
|
|
CharacterName = MailBoxString;
|
|
}
|
|
else {
|
|
CharacterName = MailBoxString.substr(LastPeriod + 1);
|
|
}
|
|
|
|
LogInfo("Received login for user [{}] with key [{}]",
|
|
MailBox, Key);
|
|
|
|
if (!database.VerifyMailKey(CharacterName, (*it)->ClientStream->GetRemoteIP(), Key)) {
|
|
LogError("Chat Key for [{}] does not match, closing connection.", MailBox);
|
|
KeyValid = false;
|
|
break;
|
|
}
|
|
|
|
(*it)->SetAccountID(database.FindAccount(CharacterName.c_str(), (*it)));
|
|
|
|
database.GetAccountStatus((*it));
|
|
|
|
if ((*it)->GetConnectionType() == ConnectionTypeCombined) {
|
|
(*it)->SendFriends();
|
|
}
|
|
|
|
(*it)->SendMailBoxes();
|
|
|
|
CheckForStaleConnections((*it));
|
|
break;
|
|
}
|
|
case OP_Mail: {
|
|
std::string command_string = (const char *)app->pBuffer + 1;
|
|
bool command_directed = false;
|
|
if (command_string.empty()) {
|
|
break;
|
|
}
|
|
|
|
if (Strings::Contains(Strings::ToLower(command_string), "leave")) {
|
|
command_directed = true;
|
|
}
|
|
|
|
ProcessOPMailCommand((*it), command_string, command_directed);
|
|
break;
|
|
}
|
|
|
|
default: {
|
|
LogInfo("Unhandled chat opcode {:#04x}", opcode);
|
|
break;
|
|
}
|
|
}
|
|
safe_delete(app);
|
|
}
|
|
if (!KeyValid || (*it)->GetForceDisconnect()) {
|
|
struct in_addr in;
|
|
in.s_addr = (*it)->ClientStream->GetRemoteIP();
|
|
|
|
LogInfo("Force disconnecting client: [{}]:[{}], KeyValid=[{}], GetForceDisconnect()=[{}]",
|
|
inet_ntoa(in), ntohs((*it)->ClientStream->GetRemotePort()), KeyValid,
|
|
(*it)->GetForceDisconnect());
|
|
|
|
(*it)->ClientStream->Close();
|
|
|
|
safe_delete((*it));
|
|
|
|
it = ClientChatConnections.erase(it);
|
|
continue;
|
|
}
|
|
|
|
++it;
|
|
}
|
|
}
|
|
|
|
void Clientlist::ProcessOPMailCommand(Client *c, std::string command_string, bool command_directed)
|
|
{
|
|
|
|
if (command_string.length() == 0)
|
|
return;
|
|
|
|
if (isdigit(command_string[0]))
|
|
{
|
|
|
|
c->SendChannelMessageByNumber(command_string);
|
|
|
|
return;
|
|
}
|
|
|
|
if (command_string[0] == '#') {
|
|
|
|
c->SendChannelMessage(command_string);
|
|
|
|
return;
|
|
}
|
|
|
|
std::string command, parameters;
|
|
|
|
std::string::size_type Space = command_string.find_first_of(" ");
|
|
|
|
if (Space != std::string::npos) {
|
|
|
|
command = command_string.substr(0, Space);
|
|
|
|
std::string::size_type parameters_start = command_string.find_first_not_of(" ", Space);
|
|
|
|
if (parameters_start != std::string::npos)
|
|
parameters = command_string.substr(parameters_start);
|
|
}
|
|
else {
|
|
command = command_string;
|
|
}
|
|
|
|
auto command_code = LookupCommand(command.c_str());
|
|
switch (command_code) {
|
|
|
|
case CommandJoin:
|
|
if (!command_directed) {
|
|
//Append saved channels to params
|
|
const auto saved_channels = database.CurrentPlayerChannels(c->GetName());
|
|
if (!saved_channels.empty()) {
|
|
parameters += fmt::format(", {}", Strings::Join(saved_channels, ", "));
|
|
}
|
|
parameters = RemoveDuplicateChannels(parameters);
|
|
}
|
|
c->JoinChannels(parameters, command_directed);
|
|
break;
|
|
|
|
case CommandLeaveAll:
|
|
c->LeaveAllChannels(true, true);
|
|
break;
|
|
|
|
case CommandLeave:
|
|
c->LeaveChannels(parameters, command_directed);
|
|
break;
|
|
|
|
case CommandListAll:
|
|
|
|
ChannelList->SendAllChannels(c);
|
|
break;
|
|
|
|
case CommandList:
|
|
c->ProcessChannelList(parameters);
|
|
break;
|
|
|
|
case CommandSet:
|
|
c->LeaveAllChannels(false);
|
|
c->JoinChannels(parameters, command_directed);
|
|
break;
|
|
|
|
case CommandAnnounce:
|
|
c->ToggleAnnounce(parameters);
|
|
break;
|
|
|
|
case CommandSetOwner:
|
|
c->SetChannelOwner(parameters);
|
|
break;
|
|
|
|
case CommandOPList:
|
|
c->OPList(parameters);
|
|
break;
|
|
|
|
case CommandInvite:
|
|
c->ChannelInvite(parameters);
|
|
break;
|
|
|
|
case CommandGrant:
|
|
c->ChannelGrantModerator(parameters);
|
|
break;
|
|
|
|
case CommandModerate:
|
|
c->ChannelModerate(parameters);
|
|
break;
|
|
|
|
case CommandVoice:
|
|
c->ChannelGrantVoice(parameters);
|
|
break;
|
|
|
|
case CommandKick:
|
|
c->ChannelKick(parameters);
|
|
break;
|
|
|
|
case CommandPassword:
|
|
c->SetChannelPassword(parameters);
|
|
break;
|
|
|
|
case CommandToggleInvites:
|
|
c->ToggleInvites();
|
|
break;
|
|
|
|
case CommandAFK:
|
|
break;
|
|
|
|
case CommandUptime:
|
|
c->SendUptime();
|
|
break;
|
|
|
|
case CommandGetHeaders:
|
|
database.SendHeaders(c);
|
|
break;
|
|
|
|
case CommandGetBody:
|
|
database.SendBody(c, Strings::ToInt(parameters));
|
|
break;
|
|
|
|
case CommandMailTo:
|
|
ProcessMailTo(c, parameters);
|
|
break;
|
|
|
|
case CommandSetMessageStatus:
|
|
LogInfo("Set Message Status, Params: [{}]", parameters.c_str());
|
|
ProcessSetMessageStatus(parameters);
|
|
break;
|
|
|
|
case CommandSelectMailBox:
|
|
{
|
|
std::string::size_type NumStart = parameters.find_first_of("0123456789");
|
|
c->ChangeMailBox(Strings::ToInt(parameters.substr(NumStart)));
|
|
break;
|
|
}
|
|
case CommandSetMailForwarding:
|
|
break;
|
|
|
|
case CommandBuddy:
|
|
RemoveApostrophes(parameters);
|
|
ProcessCommandBuddy(c, parameters);
|
|
break;
|
|
|
|
case CommandIgnorePlayer:
|
|
RemoveApostrophes(parameters);
|
|
ProcessCommandIgnore(c, parameters);
|
|
break;
|
|
|
|
default:
|
|
c->SendHelp();
|
|
LogInfo("Unhandled OP_Mail command: [{}]", command_string.c_str());
|
|
}
|
|
}
|
|
|
|
|
|
void Clientlist::CloseAllConnections() {
|
|
|
|
|
|
std::list<Client*>::iterator Iterator;
|
|
|
|
for (Iterator = ClientChatConnections.begin(); Iterator != ClientChatConnections.end(); ++Iterator) {
|
|
|
|
LogInfo("Removing client [{}]", (*Iterator)->GetName().c_str());
|
|
|
|
(*Iterator)->CloseConnection();
|
|
}
|
|
}
|
|
|
|
void Client::AddCharacter(int CharID, const char *CharacterName, int Level) {
|
|
|
|
if (!CharacterName) return;
|
|
|
|
LogDebug("Adding character [{}] with ID [{}] for [{}]", CharacterName, CharID, GetName().c_str());
|
|
|
|
CharacterEntry NewCharacter;
|
|
NewCharacter.CharID = CharID;
|
|
NewCharacter.Name = CharacterName;
|
|
NewCharacter.Level = Level;
|
|
|
|
Characters.push_back(NewCharacter);
|
|
}
|
|
|
|
void Client::SendKeepAlive() {
|
|
EQApplicationPacket outapp(OP_SessionReady, 0);
|
|
QueuePacket(&outapp);
|
|
}
|
|
|
|
void Client::SendMailBoxes() {
|
|
|
|
int Count = Characters.size();
|
|
|
|
int PacketLength = 10;
|
|
|
|
std::string s;
|
|
|
|
for (int i = 0; i < Count; i++) {
|
|
|
|
s += GetMailPrefix() + Characters[i].Name;
|
|
|
|
if (i != (Count - 1))
|
|
s = s + ",";
|
|
}
|
|
|
|
PacketLength += s.length() + 1;
|
|
|
|
auto outapp = new EQApplicationPacket(OP_MailLogin, PacketLength);
|
|
|
|
char *PacketBuffer = (char *)outapp->pBuffer;
|
|
|
|
VARSTRUCT_ENCODE_TYPE(uint8, PacketBuffer, 1);
|
|
VARSTRUCT_ENCODE_TYPE(uint32, PacketBuffer, Count);
|
|
VARSTRUCT_ENCODE_TYPE(uint32, PacketBuffer, 0);
|
|
|
|
VARSTRUCT_ENCODE_STRING(PacketBuffer, s.c_str());
|
|
VARSTRUCT_ENCODE_TYPE(uint8, PacketBuffer, 0);
|
|
|
|
|
|
QueuePacket(outapp);
|
|
|
|
safe_delete(outapp);
|
|
}
|
|
|
|
Client *Clientlist::FindCharacter(const std::string& CharacterName) {
|
|
|
|
std::list<Client*>::iterator Iterator;
|
|
|
|
for (Iterator = ClientChatConnections.begin(); Iterator != ClientChatConnections.end(); ++Iterator) {
|
|
|
|
if ((*Iterator)->GetName() == CharacterName)
|
|
return ((*Iterator));
|
|
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void Client::AddToChannelList(ChatChannel *JoinedChannel) {
|
|
|
|
if (!JoinedChannel) return;
|
|
|
|
for (int i = 0; i < MAX_JOINED_CHANNELS; i++)
|
|
if (JoinedChannels[i] == nullptr) {
|
|
JoinedChannels[i] = JoinedChannel;
|
|
LogDebug("Added Channel [{}] to slot [{}] for [{}]", JoinedChannel->GetName().c_str(), i + 1, GetName().c_str());
|
|
return;
|
|
}
|
|
}
|
|
|
|
void Client::RemoveFromChannelList(ChatChannel *JoinedChannel) {
|
|
|
|
for (int i = 0; i < MAX_JOINED_CHANNELS; i++)
|
|
if (JoinedChannels[i] == JoinedChannel) {
|
|
|
|
// Shuffle all the channels down. Client likes them all nice and consecutive.
|
|
//
|
|
for (int j = i; j < (MAX_JOINED_CHANNELS - 1); j++)
|
|
JoinedChannels[j] = JoinedChannels[j + 1];
|
|
|
|
JoinedChannels[MAX_JOINED_CHANNELS - 1] = nullptr;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
int Client::ChannelCount() {
|
|
|
|
int NumberOfChannels = 0;
|
|
|
|
for (auto &elem : JoinedChannels)
|
|
if (elem)
|
|
NumberOfChannels++;
|
|
|
|
return NumberOfChannels;
|
|
|
|
}
|
|
|
|
void Client::JoinChannels(std::string& channel_name_list, bool command_directed) {
|
|
|
|
for (auto &elem : channel_name_list) {
|
|
if (elem == '%') {
|
|
elem = '/';
|
|
}
|
|
}
|
|
|
|
LogInfo("Client: [{}] joining channels [{}]", GetName().c_str(), channel_name_list.c_str());
|
|
|
|
auto number_of_channels = ChannelCount();
|
|
|
|
auto current_pos = channel_name_list.find_first_not_of(" ");
|
|
|
|
while (current_pos != std::string::npos) {
|
|
|
|
if (number_of_channels == MAX_JOINED_CHANNELS) {
|
|
|
|
GeneralChannelMessage("You have joined the maximum number of channels. /leave one before trying to join another.");
|
|
|
|
break;
|
|
}
|
|
|
|
auto comma = channel_name_list.find_first_of(", ", current_pos);
|
|
|
|
if (comma == std::string::npos) {
|
|
auto* joined_channel = ChannelList->AddClientToChannel(channel_name_list.substr(current_pos), this, command_directed);
|
|
|
|
if (joined_channel) {
|
|
AddToChannelList(joined_channel);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
auto* joined_channel = ChannelList->AddClientToChannel(channel_name_list.substr(current_pos, comma - current_pos), this, command_directed);
|
|
|
|
if (joined_channel) {
|
|
|
|
AddToChannelList(joined_channel);
|
|
|
|
number_of_channels++;
|
|
}
|
|
|
|
current_pos = channel_name_list.find_first_not_of(", ", comma);
|
|
}
|
|
|
|
std::string JoinedChannelsList, ChannelMessage;
|
|
|
|
ChannelMessage = "Channels: ";
|
|
|
|
char tmp[200];
|
|
|
|
int ChannelCount = 0;
|
|
|
|
for (int i = 0; i < MAX_JOINED_CHANNELS; i++) {
|
|
|
|
if (JoinedChannels[i] != nullptr) {
|
|
|
|
if (ChannelCount) {
|
|
|
|
JoinedChannelsList = JoinedChannelsList + ",";
|
|
|
|
ChannelMessage = ChannelMessage + ",";
|
|
|
|
}
|
|
|
|
JoinedChannelsList = JoinedChannelsList + JoinedChannels[i]->GetName();
|
|
|
|
sprintf(tmp, "%i=%s(%i)", i + 1, JoinedChannels[i]->GetName().c_str(), JoinedChannels[i]->MemberCount(Status));
|
|
|
|
ChannelMessage += tmp;
|
|
|
|
ChannelCount++;
|
|
}
|
|
}
|
|
|
|
auto outapp = new EQApplicationPacket(OP_Mail, JoinedChannelsList.length() + 1);
|
|
|
|
char *PacketBuffer = (char *)outapp->pBuffer;
|
|
|
|
sprintf(PacketBuffer, "%s", JoinedChannelsList.c_str());
|
|
|
|
|
|
QueuePacket(outapp);
|
|
|
|
safe_delete(outapp);
|
|
|
|
if (ChannelCount == 0)
|
|
ChannelMessage = "You are not on any channels.";
|
|
|
|
outapp = new EQApplicationPacket(OP_ChannelMessage, ChannelMessage.length() + 3);
|
|
|
|
PacketBuffer = (char *)outapp->pBuffer;
|
|
|
|
VARSTRUCT_ENCODE_TYPE(uint8, PacketBuffer, 0x00);
|
|
VARSTRUCT_ENCODE_TYPE(uint8, PacketBuffer, 0x00);
|
|
VARSTRUCT_ENCODE_STRING(PacketBuffer, ChannelMessage.c_str());
|
|
|
|
|
|
QueuePacket(outapp);
|
|
|
|
safe_delete(outapp);
|
|
}
|
|
|
|
void Client::LeaveChannels(std::string& channel_name_list, bool command_directed) {
|
|
LogInfo("Client: [{}] leaving channels [{}]", GetName().c_str(), channel_name_list.c_str());
|
|
|
|
auto current_pos = 0;
|
|
|
|
while (current_pos != std::string::npos) {
|
|
|
|
std::string::size_type Comma = channel_name_list.find_first_of(", ", current_pos);
|
|
|
|
if (Comma == std::string::npos) {
|
|
|
|
auto* joined_channel = ChannelList->RemoveClientFromChannel(channel_name_list.substr(current_pos), this, command_directed);
|
|
|
|
if (joined_channel)
|
|
RemoveFromChannelList(joined_channel);
|
|
|
|
break;
|
|
}
|
|
|
|
auto* joined_channel = ChannelList->RemoveClientFromChannel(channel_name_list.substr(current_pos, Comma - current_pos), this, command_directed);
|
|
|
|
if (joined_channel)
|
|
RemoveFromChannelList(joined_channel);
|
|
|
|
current_pos = channel_name_list.find_first_not_of(", ", Comma);
|
|
}
|
|
|
|
std::string joined_channels_list, channel_message;
|
|
|
|
channel_message = "Channels: ";
|
|
|
|
char tmp[200];
|
|
|
|
int ChannelCount = 0;
|
|
|
|
for (int i = 0; i < MAX_JOINED_CHANNELS; i++) {
|
|
|
|
if (JoinedChannels[i] != nullptr) {
|
|
|
|
if (ChannelCount) {
|
|
|
|
joined_channels_list = joined_channels_list + ",";
|
|
|
|
channel_message = channel_message + ",";
|
|
}
|
|
|
|
joined_channels_list = joined_channels_list + JoinedChannels[i]->GetName();
|
|
|
|
sprintf(tmp, "%i=%s(%i)", i + 1, JoinedChannels[i]->GetName().c_str(), JoinedChannels[i]->MemberCount(Status));
|
|
|
|
channel_message += tmp;
|
|
|
|
ChannelCount++;
|
|
}
|
|
}
|
|
|
|
auto outapp = new EQApplicationPacket(OP_Mail, joined_channels_list.length() + 1);
|
|
|
|
char *PacketBuffer = (char *)outapp->pBuffer;
|
|
|
|
sprintf(PacketBuffer, "%s", joined_channels_list.c_str());
|
|
|
|
|
|
QueuePacket(outapp);
|
|
|
|
safe_delete(outapp);
|
|
|
|
if (ChannelCount == 0)
|
|
channel_message = "You are not on any channels.";
|
|
|
|
outapp = new EQApplicationPacket(OP_ChannelMessage, channel_message.length() + 3);
|
|
|
|
PacketBuffer = (char *)outapp->pBuffer;
|
|
|
|
VARSTRUCT_ENCODE_TYPE(uint8, PacketBuffer, 0x00);
|
|
VARSTRUCT_ENCODE_TYPE(uint8, PacketBuffer, 0x00);
|
|
VARSTRUCT_ENCODE_STRING(PacketBuffer, channel_message.c_str());
|
|
|
|
|
|
QueuePacket(outapp);
|
|
|
|
safe_delete(outapp);
|
|
}
|
|
|
|
void Client::LeaveAllChannels(bool send_updated_channel_list, bool command_directed) {
|
|
|
|
for (auto &elem : JoinedChannels) {
|
|
|
|
if (elem) {
|
|
|
|
ChannelList->RemoveClientFromChannel(elem->GetName(), this, command_directed);
|
|
|
|
elem = nullptr;
|
|
}
|
|
}
|
|
|
|
if (send_updated_channel_list)
|
|
SendChannelList();
|
|
}
|
|
|
|
|
|
void Client::ProcessChannelList(const std::string& Input) {
|
|
|
|
if (Input.length() == 0) {
|
|
|
|
SendChannelList();
|
|
|
|
return;
|
|
}
|
|
|
|
std::string ChannelName = Input;
|
|
|
|
if (isdigit(ChannelName[0]))
|
|
ChannelName = ChannelSlotName(Strings::ToInt(ChannelName));
|
|
|
|
ChatChannel *RequiredChannel = ChannelList->FindChannel(ChannelName);
|
|
|
|
if (RequiredChannel)
|
|
RequiredChannel->SendChannelMembers(this);
|
|
else
|
|
GeneralChannelMessage("Channel " + Input + " not found.");
|
|
}
|
|
|
|
|
|
|
|
void Client::SendChannelList() {
|
|
|
|
std::string ChannelMessage;
|
|
|
|
ChannelMessage = "Channels: ";
|
|
|
|
char tmp[200];
|
|
|
|
int ChannelCount = 0;
|
|
|
|
for (int i = 0; i < MAX_JOINED_CHANNELS; i++) {
|
|
|
|
if (JoinedChannels[i] != nullptr) {
|
|
|
|
if (ChannelCount)
|
|
ChannelMessage = ChannelMessage + ",";
|
|
|
|
sprintf(tmp, "%i=%s(%i)", i + 1, JoinedChannels[i]->GetName().c_str(), JoinedChannels[i]->MemberCount(Status));
|
|
|
|
ChannelMessage += tmp;
|
|
|
|
ChannelCount++;
|
|
}
|
|
}
|
|
|
|
if (ChannelCount == 0)
|
|
ChannelMessage = "You are not on any channels.";
|
|
|
|
auto outapp = new EQApplicationPacket(OP_ChannelMessage, ChannelMessage.length() + 3);
|
|
|
|
char *PacketBuffer = (char *)outapp->pBuffer;
|
|
|
|
VARSTRUCT_ENCODE_TYPE(uint8, PacketBuffer, 0x00);
|
|
VARSTRUCT_ENCODE_TYPE(uint8, PacketBuffer, 0x00);
|
|
VARSTRUCT_ENCODE_STRING(PacketBuffer, ChannelMessage.c_str());
|
|
|
|
|
|
QueuePacket(outapp);
|
|
|
|
safe_delete(outapp);
|
|
}
|
|
|
|
void Client::SendChannelMessage(std::string Message)
|
|
{
|
|
|
|
std::string::size_type MessageStart = Message.find_first_of(" ");
|
|
|
|
if (MessageStart == std::string::npos)
|
|
return;
|
|
|
|
std::string ChannelName = Message.substr(1, MessageStart - 1);
|
|
|
|
LogInfo("[{}] tells [{}], [[{}]]", GetName().c_str(), ChannelName.c_str(), Message.substr(MessageStart + 1).c_str());
|
|
|
|
ChatChannel *RequiredChannel = ChannelList->FindChannel(ChannelName);
|
|
|
|
if (IsRevoked())
|
|
{
|
|
GeneralChannelMessage("You are Revoked, you cannot chat in global channels.");
|
|
return;
|
|
}
|
|
|
|
if (ChannelName.compare("Newplayers") != 0)
|
|
{
|
|
if (GetKarma() < RuleI(Chat, KarmaGlobalChatLimit))
|
|
{
|
|
CharacterEntry *char_ent = nullptr;
|
|
for (auto &elem : Characters) {
|
|
if (elem.Name.compare(GetName()) == 0) {
|
|
char_ent = &elem;
|
|
break;
|
|
}
|
|
}
|
|
if (char_ent)
|
|
{
|
|
if (char_ent->Level < RuleI(Chat, GlobalChatLevelLimit))
|
|
{
|
|
GeneralChannelMessage("You are either not high enough level or high enough karma to talk in this channel right now.");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (RequiredChannel) {
|
|
if (RuleB(Chat, EnableAntiSpam))
|
|
{
|
|
if (!RequiredChannel->IsModerated() || RequiredChannel->HasVoice(GetName()) || RequiredChannel->IsOwner(GetName()) ||
|
|
RequiredChannel->IsModerator(GetName()) || IsChannelAdmin())
|
|
{
|
|
if (GlobalChatLimiterTimer)
|
|
{
|
|
if (GlobalChatLimiterTimer->Check(false))
|
|
{
|
|
GlobalChatLimiterTimer->Start(RuleI(Chat, IntervalDurationMS));
|
|
AttemptedMessages = 0;
|
|
}
|
|
}
|
|
int AllowedMessages = RuleI(Chat, MinimumMessagesPerInterval) + GetKarma();
|
|
AllowedMessages = AllowedMessages > RuleI(Chat, MaximumMessagesPerInterval) ? RuleI(Chat, MaximumMessagesPerInterval) : AllowedMessages;
|
|
|
|
if (RuleI(Chat, MinStatusToBypassAntiSpam) <= Status)
|
|
AllowedMessages = 10000;
|
|
|
|
AttemptedMessages++;
|
|
if (AttemptedMessages > AllowedMessages)
|
|
{
|
|
if (AttemptedMessages > RuleI(Chat, MaxMessagesBeforeKick))
|
|
{
|
|
ForceDisconnect = true;
|
|
}
|
|
if (GlobalChatLimiterTimer)
|
|
{
|
|
char TimeLeft[256];
|
|
sprintf(TimeLeft, "You are currently rate limited, you cannot send more messages for %i seconds.",
|
|
(GlobalChatLimiterTimer->GetRemainingTime() / 1000));
|
|
GeneralChannelMessage(TimeLeft);
|
|
}
|
|
else
|
|
{
|
|
GeneralChannelMessage("You are currently rate limited, you cannot send more messages for up to 60 seconds.");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
RequiredChannel->SendMessageToChannel(Message.substr(MessageStart + 1), this);
|
|
}
|
|
}
|
|
else
|
|
GeneralChannelMessage("Channel " + ChannelName + " is moderated and you have not been granted a voice.");
|
|
}
|
|
else
|
|
{
|
|
if (!RequiredChannel->IsModerated() || RequiredChannel->HasVoice(GetName()) || RequiredChannel->IsOwner(GetName()) ||
|
|
RequiredChannel->IsModerator(GetName()) || IsChannelAdmin())
|
|
RequiredChannel->SendMessageToChannel(Message.substr(MessageStart + 1), this);
|
|
else
|
|
GeneralChannelMessage("Channel " + ChannelName + " is moderated and you have not been granted a voice.");
|
|
}
|
|
}
|
|
}
|
|
|
|
void Client::SendChannelMessageByNumber(std::string Message) {
|
|
|
|
std::string::size_type MessageStart = Message.find_first_of(" ");
|
|
|
|
if (MessageStart == std::string::npos)
|
|
return;
|
|
|
|
int ChannelNumber = Strings::ToInt(Message.substr(0, MessageStart));
|
|
|
|
if ((ChannelNumber < 1) || (ChannelNumber > MAX_JOINED_CHANNELS)) {
|
|
|
|
GeneralChannelMessage("Invalid channel name/number specified.");
|
|
|
|
return;
|
|
}
|
|
|
|
ChatChannel *RequiredChannel = JoinedChannels[ChannelNumber - 1];
|
|
|
|
if (!RequiredChannel) {
|
|
|
|
GeneralChannelMessage("Invalid channel name/number specified.");
|
|
|
|
return;
|
|
}
|
|
|
|
if (IsRevoked())
|
|
{
|
|
GeneralChannelMessage("You are Revoked, you cannot chat in global channels.");
|
|
return;
|
|
}
|
|
|
|
if (RequiredChannel->GetName().compare("Newplayers") != 0)
|
|
{
|
|
if (GetKarma() < RuleI(Chat, KarmaGlobalChatLimit))
|
|
{
|
|
CharacterEntry *char_ent = nullptr;
|
|
for (auto &elem : Characters) {
|
|
if (elem.Name.compare(GetName()) == 0) {
|
|
char_ent = &elem;
|
|
break;
|
|
}
|
|
}
|
|
if (char_ent)
|
|
{
|
|
if (char_ent->Level < RuleI(Chat, GlobalChatLevelLimit))
|
|
{
|
|
GeneralChannelMessage("You are either not high enough level or high enough karma to talk in this channel right now.");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
LogInfo("[{}] tells [{}], [[{}]]", GetName().c_str(), RequiredChannel->GetName().c_str(),
|
|
Message.substr(MessageStart + 1).c_str());
|
|
|
|
if (RuleB(Chat, EnableAntiSpam))
|
|
{
|
|
if (!RequiredChannel->IsModerated() || RequiredChannel->HasVoice(GetName()) || RequiredChannel->IsOwner(GetName()) ||
|
|
RequiredChannel->IsModerator(GetName()))
|
|
{
|
|
if (GlobalChatLimiterTimer)
|
|
{
|
|
if (GlobalChatLimiterTimer->Check(false))
|
|
{
|
|
GlobalChatLimiterTimer->Start(RuleI(Chat, IntervalDurationMS));
|
|
AttemptedMessages = 0;
|
|
}
|
|
}
|
|
int AllowedMessages = RuleI(Chat, MinimumMessagesPerInterval) + GetKarma();
|
|
AllowedMessages = AllowedMessages > RuleI(Chat, MaximumMessagesPerInterval) ? RuleI(Chat, MaximumMessagesPerInterval) : AllowedMessages;
|
|
if (RuleI(Chat, MinStatusToBypassAntiSpam) <= Status)
|
|
AllowedMessages = 10000;
|
|
|
|
AttemptedMessages++;
|
|
if (AttemptedMessages > AllowedMessages)
|
|
{
|
|
if (AttemptedMessages > RuleI(Chat, MaxMessagesBeforeKick))
|
|
{
|
|
ForceDisconnect = true;
|
|
}
|
|
if (GlobalChatLimiterTimer)
|
|
{
|
|
char TimeLeft[256];
|
|
sprintf(TimeLeft, "You are currently rate limited, you cannot send more messages for %i seconds.",
|
|
(GlobalChatLimiterTimer->GetRemainingTime() / 1000));
|
|
GeneralChannelMessage(TimeLeft);
|
|
}
|
|
else
|
|
{
|
|
GeneralChannelMessage("You are currently rate limited, you cannot send more messages for up to 60 seconds.");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
RequiredChannel->SendMessageToChannel(Message.substr(MessageStart + 1), this);
|
|
}
|
|
}
|
|
else
|
|
GeneralChannelMessage("Channel " + RequiredChannel->GetName() + " is moderated and you have not been granted a voice.");
|
|
}
|
|
else
|
|
{
|
|
if (!RequiredChannel->IsModerated() || RequiredChannel->HasVoice(GetName()) || RequiredChannel->IsOwner(GetName()) ||
|
|
RequiredChannel->IsModerator(GetName()))
|
|
RequiredChannel->SendMessageToChannel(Message.substr(MessageStart + 1), this);
|
|
else
|
|
GeneralChannelMessage("Channel " + RequiredChannel->GetName() + " is moderated and you have not been granted a voice.");
|
|
}
|
|
|
|
}
|
|
|
|
void Client::SendChannelMessage(const std::string& ChannelName, const std::string& Message, Client *Sender) {
|
|
|
|
if (!Sender) return;
|
|
|
|
std::string FQSenderName = WorldShortName + "." + Sender->GetName();
|
|
|
|
int PacketLength = ChannelName.length() + Message.length() + FQSenderName.length() + 3;
|
|
|
|
if (UnderfootOrLater)
|
|
PacketLength += 8;
|
|
|
|
auto outapp = new EQApplicationPacket(OP_ChannelMessage, PacketLength);
|
|
|
|
char *PacketBuffer = (char *)outapp->pBuffer;
|
|
|
|
VARSTRUCT_ENCODE_STRING(PacketBuffer, ChannelName.c_str());
|
|
VARSTRUCT_ENCODE_STRING(PacketBuffer, FQSenderName.c_str());
|
|
VARSTRUCT_ENCODE_STRING(PacketBuffer, Message.c_str());
|
|
|
|
if (UnderfootOrLater)
|
|
VARSTRUCT_ENCODE_STRING(PacketBuffer, "SPAM:0:");
|
|
|
|
QueuePacket(outapp);
|
|
|
|
safe_delete(outapp);
|
|
}
|
|
|
|
void Client::ToggleAnnounce(const std::string& State)
|
|
{
|
|
if (State == "")
|
|
Announce = !Announce;
|
|
else if (State == "on")
|
|
Announce = true;
|
|
else
|
|
Announce = false;
|
|
|
|
std::string Message = "Announcing now ";
|
|
|
|
if (Announce)
|
|
Message += "on";
|
|
else
|
|
Message += "off";
|
|
|
|
GeneralChannelMessage(Message);
|
|
}
|
|
|
|
void Client::AnnounceJoin(ChatChannel *Channel, Client *c) {
|
|
|
|
if (!Channel || !c) return;
|
|
|
|
int PacketLength = Channel->GetName().length() + c->GetName().length() + 2;
|
|
|
|
auto outapp = new EQApplicationPacket(OP_ChannelAnnounceJoin, PacketLength);
|
|
|
|
char *PacketBuffer = (char *)outapp->pBuffer;
|
|
|
|
VARSTRUCT_ENCODE_STRING(PacketBuffer, Channel->GetName().c_str());
|
|
VARSTRUCT_ENCODE_STRING(PacketBuffer, c->GetName().c_str());
|
|
|
|
|
|
QueuePacket(outapp);
|
|
|
|
safe_delete(outapp);
|
|
}
|
|
|
|
void Client::AnnounceLeave(ChatChannel *Channel, Client *c) {
|
|
|
|
if (!Channel || !c) return;
|
|
|
|
int PacketLength = Channel->GetName().length() + c->GetName().length() + 2;
|
|
|
|
auto outapp = new EQApplicationPacket(OP_ChannelAnnounceLeave, PacketLength);
|
|
|
|
char *PacketBuffer = (char *)outapp->pBuffer;
|
|
|
|
VARSTRUCT_ENCODE_STRING(PacketBuffer, Channel->GetName().c_str());
|
|
VARSTRUCT_ENCODE_STRING(PacketBuffer, c->GetName().c_str());
|
|
|
|
|
|
QueuePacket(outapp);
|
|
|
|
safe_delete(outapp);
|
|
}
|
|
|
|
void Client::GeneralChannelMessage(const char *Characters) {
|
|
|
|
if (!Characters) return;
|
|
|
|
std::string Message = Characters;
|
|
|
|
GeneralChannelMessage(Message);
|
|
|
|
}
|
|
|
|
void Client::GeneralChannelMessage(const std::string& Message) {
|
|
|
|
auto outapp = new EQApplicationPacket(OP_ChannelMessage, Message.length() + 3);
|
|
char *PacketBuffer = (char *)outapp->pBuffer;
|
|
VARSTRUCT_ENCODE_TYPE(uint8, PacketBuffer, 0x00);
|
|
VARSTRUCT_ENCODE_TYPE(uint8, PacketBuffer, 0x00);
|
|
VARSTRUCT_ENCODE_STRING(PacketBuffer, Message.c_str());
|
|
|
|
QueuePacket(outapp);
|
|
|
|
safe_delete(outapp);
|
|
}
|
|
|
|
void Client::SetChannelPassword(std::string ChannelPassword) {
|
|
|
|
std::string::size_type PasswordStart = ChannelPassword.find_first_not_of(" ");
|
|
|
|
if (PasswordStart == std::string::npos) {
|
|
std::string Message = "Incorrect syntax: /chat password <new password> <channel name>";
|
|
GeneralChannelMessage(Message);
|
|
return;
|
|
}
|
|
|
|
std::string::size_type Space = ChannelPassword.find_first_of(" ", PasswordStart);
|
|
|
|
if (Space == std::string::npos) {
|
|
std::string Message = "Incorrect syntax: /chat password <new password> <channel name>";
|
|
GeneralChannelMessage(Message);
|
|
return;
|
|
}
|
|
|
|
std::string Password = ChannelPassword.substr(PasswordStart, Space - PasswordStart);
|
|
|
|
std::string::size_type ChannelStart = ChannelPassword.find_first_not_of(" ", Space);
|
|
|
|
if (ChannelStart == std::string::npos) {
|
|
std::string Message = "Incorrect syntax: /chat password <new password> <channel name>";
|
|
GeneralChannelMessage(Message);
|
|
return;
|
|
}
|
|
|
|
std::string ChannelName = ChannelPassword.substr(ChannelStart);
|
|
|
|
if ((ChannelName.length() > 0) && isdigit(ChannelName[0]))
|
|
ChannelName = ChannelSlotName(Strings::ToInt(ChannelName));
|
|
|
|
std::string Message;
|
|
|
|
if (!strcasecmp(Password.c_str(), "remove")) {
|
|
Password.clear();
|
|
Message = "Password REMOVED on channel " + ChannelName;
|
|
}
|
|
else
|
|
Message = "Password change on channel " + ChannelName;
|
|
|
|
LogInfo("Set password of channel [[{}]] to [[{}]] by [{}]", ChannelName.c_str(), Password.c_str(), GetName().c_str());
|
|
|
|
ChatChannel *RequiredChannel = ChannelList->FindChannel(ChannelName);
|
|
|
|
if (!RequiredChannel) {
|
|
std::string Message = "Channel not found.";
|
|
GeneralChannelMessage(Message);
|
|
return;
|
|
}
|
|
|
|
if (!RequiredChannel->IsOwner(GetName()) && !RequiredChannel->IsModerator(GetName()) && !IsChannelAdmin()) {
|
|
std::string Message = "You do not own or have moderator rights on channel " + ChannelName;
|
|
GeneralChannelMessage(Message);
|
|
return;
|
|
}
|
|
|
|
RequiredChannel->SetPassword(Password);
|
|
database.SaveChatChannel(RequiredChannel->GetName(), RequiredChannel->GetOwnerName(), Password, RequiredChannel->GetMinStatus()); // Update DB with new password
|
|
|
|
GeneralChannelMessage(Message);
|
|
|
|
}
|
|
|
|
void Client::SetChannelOwner(std::string CommandString) {
|
|
|
|
std::string::size_type PlayerStart = CommandString.find_first_not_of(" ");
|
|
|
|
if (PlayerStart == std::string::npos) {
|
|
std::string Message = "Incorrect syntax: /chat setowner <player> <channel>";
|
|
GeneralChannelMessage(Message);
|
|
return;
|
|
}
|
|
|
|
std::string::size_type Space = CommandString.find_first_of(" ", PlayerStart);
|
|
|
|
if (Space == std::string::npos) {
|
|
std::string Message = "Incorrect syntax: /chat setowner <player> <channel>";
|
|
GeneralChannelMessage(Message);
|
|
return;
|
|
}
|
|
|
|
std::string NewOwner = CapitaliseName(CommandString.substr(PlayerStart, Space - PlayerStart));
|
|
|
|
std::string::size_type ChannelStart = CommandString.find_first_not_of(" ", Space);
|
|
|
|
if (ChannelStart == std::string::npos) {
|
|
std::string Message = "Incorrect syntax: /chat setowner <player> <channel>";
|
|
GeneralChannelMessage(Message);
|
|
return;
|
|
}
|
|
|
|
std::string ChannelName = CapitaliseName(CommandString.substr(ChannelStart));
|
|
|
|
if ((ChannelName.length() > 0) && isdigit(ChannelName[0]))
|
|
ChannelName = ChannelSlotName(Strings::ToInt(ChannelName));
|
|
|
|
LogInfo("Set owner of channel [[{}]] to [[{}]]", ChannelName.c_str(), NewOwner.c_str());
|
|
|
|
ChatChannel *RequiredChannel = ChannelList->FindChannel(ChannelName);
|
|
|
|
if (!RequiredChannel) {
|
|
GeneralChannelMessage("Channel " + ChannelName + " not found.");
|
|
return;
|
|
}
|
|
|
|
if (!RequiredChannel->IsOwner(GetName()) && !IsChannelAdmin()) {
|
|
std::string Message = "You do not own channel " + ChannelName;
|
|
GeneralChannelMessage(Message);
|
|
return;
|
|
}
|
|
|
|
if (database.FindCharacter(NewOwner.c_str()) < 0) {
|
|
|
|
GeneralChannelMessage("Player " + NewOwner + " does not exist.");
|
|
return;
|
|
}
|
|
|
|
RequiredChannel->SetOwner(NewOwner);
|
|
database.SaveChatChannel(RequiredChannel->GetName(), NewOwner, RequiredChannel->GetPassword(), RequiredChannel->GetMinStatus()); // Update DB with new owner
|
|
|
|
if (RequiredChannel->IsModerator(NewOwner))
|
|
RequiredChannel->RemoveModerator(NewOwner);
|
|
|
|
GeneralChannelMessage("Channel owner changed.");
|
|
|
|
}
|
|
|
|
void Client::OPList(std::string CommandString) {
|
|
|
|
std::string::size_type ChannelStart = CommandString.find_first_not_of(" ");
|
|
|
|
if (ChannelStart == std::string::npos) {
|
|
std::string Message = "Incorrect syntax: /chat oplist <channel>";
|
|
GeneralChannelMessage(Message);
|
|
return;
|
|
}
|
|
|
|
std::string ChannelName = CapitaliseName(CommandString.substr(ChannelStart));
|
|
|
|
if ((ChannelName.length() > 0) && isdigit(ChannelName[0]))
|
|
ChannelName = ChannelSlotName(Strings::ToInt(ChannelName));
|
|
|
|
ChatChannel *RequiredChannel = ChannelList->FindChannel(ChannelName);
|
|
|
|
if (!RequiredChannel) {
|
|
GeneralChannelMessage("Channel " + ChannelName + " not found.");
|
|
return;
|
|
}
|
|
|
|
RequiredChannel->SendOPList(this);
|
|
}
|
|
|
|
void Client::ChannelInvite(std::string CommandString) {
|
|
|
|
std::string::size_type PlayerStart = CommandString.find_first_not_of(" ");
|
|
|
|
if (PlayerStart == std::string::npos) {
|
|
std::string Message = "Incorrect syntax: /chat invite <player> <channel>";
|
|
GeneralChannelMessage(Message);
|
|
return;
|
|
}
|
|
|
|
std::string::size_type Space = CommandString.find_first_of(" ", PlayerStart);
|
|
|
|
if (Space == std::string::npos) {
|
|
std::string Message = "Incorrect syntax: /chat invite <player> <channel>";
|
|
GeneralChannelMessage(Message);
|
|
return;
|
|
}
|
|
|
|
std::string Invitee = CapitaliseName(CommandString.substr(PlayerStart, Space - PlayerStart));
|
|
|
|
std::string::size_type ChannelStart = CommandString.find_first_not_of(" ", Space);
|
|
|
|
if (ChannelStart == std::string::npos) {
|
|
std::string Message = "Incorrect syntax: /chat invite <player> <channel>";
|
|
GeneralChannelMessage(Message);
|
|
return;
|
|
}
|
|
|
|
std::string ChannelName = CapitaliseName(CommandString.substr(ChannelStart));
|
|
|
|
if ((ChannelName.length() > 0) && isdigit(ChannelName[0]))
|
|
ChannelName = ChannelSlotName(Strings::ToInt(ChannelName));
|
|
|
|
LogInfo("[[{}]] invites [[{}]] to channel [[{}]]", GetName().c_str(), Invitee.c_str(), ChannelName.c_str());
|
|
|
|
Client *RequiredClient = g_Clientlist->FindCharacter(Invitee);
|
|
|
|
if (!RequiredClient) {
|
|
|
|
GeneralChannelMessage(Invitee + " is not online.");
|
|
return;
|
|
}
|
|
|
|
if (RequiredClient == this) {
|
|
|
|
GeneralChannelMessage("You cannot invite yourself to a channel.");
|
|
return;
|
|
}
|
|
|
|
if (!RequiredClient->InvitesAllowed()) {
|
|
|
|
GeneralChannelMessage("That player is not currently accepting channel invitations.");
|
|
return;
|
|
}
|
|
|
|
ChatChannel *RequiredChannel = ChannelList->FindChannel(ChannelName);
|
|
|
|
if (!RequiredChannel) {
|
|
|
|
GeneralChannelMessage("Channel " + ChannelName + " not found.");
|
|
return;
|
|
}
|
|
|
|
if (!RequiredChannel->IsOwner(GetName()) && !RequiredChannel->IsModerator(GetName())) {
|
|
|
|
std::string Message = "You do not own or have moderator rights to channel " + ChannelName;
|
|
|
|
GeneralChannelMessage(Message);
|
|
return;
|
|
}
|
|
|
|
if (RequiredChannel->IsClientInChannel(RequiredClient)) {
|
|
|
|
GeneralChannelMessage(Invitee + " is already in that channel");
|
|
|
|
return;
|
|
}
|
|
|
|
RequiredChannel->AddInvitee(Invitee);
|
|
|
|
RequiredClient->GeneralChannelMessage(GetName() + " has invited you to join channel " + ChannelName);
|
|
|
|
GeneralChannelMessage("Invitation sent to " + Invitee + " to join channel " + ChannelName);
|
|
|
|
}
|
|
|
|
void Client::ChannelModerate(std::string CommandString) {
|
|
|
|
std::string::size_type ChannelStart = CommandString.find_first_not_of(" ");
|
|
|
|
if (ChannelStart == std::string::npos) {
|
|
|
|
std::string Message = "Incorrect syntax: /chat moderate <channel>";
|
|
|
|
GeneralChannelMessage(Message);
|
|
return;
|
|
}
|
|
|
|
std::string ChannelName = CapitaliseName(CommandString.substr(ChannelStart));
|
|
|
|
if ((ChannelName.length() > 0) && isdigit(ChannelName[0]))
|
|
ChannelName = ChannelSlotName(Strings::ToInt(ChannelName));
|
|
|
|
ChatChannel *RequiredChannel = ChannelList->FindChannel(ChannelName);
|
|
|
|
if (!RequiredChannel) {
|
|
|
|
GeneralChannelMessage("Channel " + ChannelName + " not found.");
|
|
return;
|
|
}
|
|
|
|
if (!RequiredChannel->IsOwner(GetName()) && !RequiredChannel->IsModerator(GetName()) && !IsChannelAdmin()) {
|
|
|
|
GeneralChannelMessage("You do not own or have moderator rights to channel " + ChannelName);
|
|
return;
|
|
}
|
|
|
|
RequiredChannel->SetModerated(!RequiredChannel->IsModerated());
|
|
|
|
if (!RequiredChannel->IsClientInChannel(this)) {
|
|
if (RequiredChannel->IsModerated())
|
|
GeneralChannelMessage("Channel " + ChannelName + " is now moderated.");
|
|
else
|
|
GeneralChannelMessage("Channel " + ChannelName + " is no longer moderated.");
|
|
}
|
|
|
|
}
|
|
|
|
void Client::ChannelGrantModerator(std::string CommandString) {
|
|
|
|
std::string::size_type PlayerStart = CommandString.find_first_not_of(" ");
|
|
|
|
if (PlayerStart == std::string::npos) {
|
|
|
|
GeneralChannelMessage("Incorrect syntax: /chat grant <player> <channel>");
|
|
return;
|
|
}
|
|
|
|
std::string::size_type Space = CommandString.find_first_of(" ", PlayerStart);
|
|
|
|
if (Space == std::string::npos) {
|
|
|
|
GeneralChannelMessage("Incorrect syntax: /chat grant <player> <channel>");
|
|
return;
|
|
}
|
|
|
|
std::string Moderator = CapitaliseName(CommandString.substr(PlayerStart, Space - PlayerStart));
|
|
|
|
std::string::size_type ChannelStart = CommandString.find_first_not_of(" ", Space);
|
|
|
|
if (ChannelStart == std::string::npos) {
|
|
|
|
GeneralChannelMessage("Incorrect syntax: /chat grant <player> <channel>");
|
|
return;
|
|
}
|
|
|
|
std::string ChannelName = CapitaliseName(CommandString.substr(ChannelStart));
|
|
|
|
if ((ChannelName.length() > 0) && isdigit(ChannelName[0]))
|
|
ChannelName = ChannelSlotName(Strings::ToInt(ChannelName));
|
|
|
|
LogInfo("[[{}]] gives [[{}]] moderator rights to channel [[{}]]", GetName().c_str(), Moderator.c_str(), ChannelName.c_str());
|
|
|
|
Client *RequiredClient = g_Clientlist->FindCharacter(Moderator);
|
|
|
|
if (!RequiredClient && (database.FindCharacter(Moderator.c_str()) < 0)) {
|
|
|
|
GeneralChannelMessage("Player " + Moderator + " does not exist.");
|
|
return;
|
|
}
|
|
|
|
if (RequiredClient == this) {
|
|
|
|
GeneralChannelMessage("You cannot grant yourself moderator rights to a channel.");
|
|
return;
|
|
}
|
|
|
|
ChatChannel *RequiredChannel = ChannelList->FindChannel(ChannelName);
|
|
|
|
if (!RequiredChannel) {
|
|
|
|
GeneralChannelMessage("Channel " + ChannelName + " not found.");
|
|
return;
|
|
}
|
|
|
|
if (!RequiredChannel->IsOwner(GetName()) && !IsChannelAdmin()) {
|
|
|
|
GeneralChannelMessage("You do not own channel " + ChannelName);
|
|
return;
|
|
}
|
|
|
|
if (RequiredChannel->IsModerator(Moderator)) {
|
|
|
|
RequiredChannel->RemoveModerator(Moderator);
|
|
|
|
if (RequiredClient)
|
|
RequiredClient->GeneralChannelMessage(GetName() + " has removed your moderator rights to channel " + ChannelName);
|
|
|
|
GeneralChannelMessage("Removing moderator rights from " + Moderator + " to channel " + ChannelName);
|
|
}
|
|
else {
|
|
RequiredChannel->AddModerator(Moderator);
|
|
|
|
if (RequiredClient)
|
|
RequiredClient->GeneralChannelMessage(GetName() + " has made you a moderator of channel " + ChannelName);
|
|
|
|
GeneralChannelMessage(Moderator + " is now a moderator on channel " + ChannelName);
|
|
}
|
|
|
|
}
|
|
|
|
void Client::ChannelGrantVoice(std::string CommandString) {
|
|
|
|
std::string::size_type PlayerStart = CommandString.find_first_not_of(" ");
|
|
|
|
if (PlayerStart == std::string::npos) {
|
|
|
|
GeneralChannelMessage("Incorrect syntax: /chat voice <player> <channel>");
|
|
return;
|
|
}
|
|
|
|
std::string::size_type Space = CommandString.find_first_of(" ", PlayerStart);
|
|
|
|
if (Space == std::string::npos) {
|
|
GeneralChannelMessage("Incorrect syntax: /chat voice <player> <channel>");
|
|
return;
|
|
}
|
|
|
|
std::string Voicee = CapitaliseName(CommandString.substr(PlayerStart, Space - PlayerStart));
|
|
|
|
std::string::size_type ChannelStart = CommandString.find_first_not_of(" ", Space);
|
|
|
|
if (ChannelStart == std::string::npos) {
|
|
GeneralChannelMessage("Incorrect syntax: /chat voice <player> <channel>");
|
|
return;
|
|
}
|
|
|
|
std::string ChannelName = CapitaliseName(CommandString.substr(ChannelStart));
|
|
|
|
if ((ChannelName.length() > 0) && isdigit(ChannelName[0]))
|
|
ChannelName = ChannelSlotName(Strings::ToInt(ChannelName));
|
|
|
|
LogInfo("[[{}]] gives [[{}]] voice to channel [[{}]]", GetName().c_str(), Voicee.c_str(), ChannelName.c_str());
|
|
|
|
Client *RequiredClient = g_Clientlist->FindCharacter(Voicee);
|
|
|
|
if (!RequiredClient && (database.FindCharacter(Voicee.c_str()) < 0)) {
|
|
|
|
GeneralChannelMessage("Player " + Voicee + " does not exist.");
|
|
return;
|
|
}
|
|
|
|
if (RequiredClient == this) {
|
|
|
|
GeneralChannelMessage("You cannot grant yourself voice to a channel.");
|
|
return;
|
|
}
|
|
|
|
ChatChannel *RequiredChannel = ChannelList->FindChannel(ChannelName);
|
|
|
|
if (!RequiredChannel) {
|
|
|
|
GeneralChannelMessage("Channel " + ChannelName + " not found.");
|
|
return;
|
|
}
|
|
|
|
if (!RequiredChannel->IsOwner(GetName()) && !RequiredChannel->IsModerator(GetName()) && !IsChannelAdmin()) {
|
|
|
|
GeneralChannelMessage("You do not own or have moderator rights to channel " + ChannelName);
|
|
return;
|
|
}
|
|
|
|
if (RequiredClient && (RequiredChannel->IsOwner(RequiredClient->GetName()) || RequiredChannel->IsModerator(RequiredClient->GetName()))) {
|
|
|
|
GeneralChannelMessage("The channel owner and moderators automatically have voice.");
|
|
return;
|
|
}
|
|
|
|
if (RequiredChannel->HasVoice(Voicee)) {
|
|
|
|
RequiredChannel->RemoveVoice(Voicee);
|
|
|
|
if (RequiredClient)
|
|
RequiredClient->GeneralChannelMessage(GetName() + " has removed your voice rights to channel " + ChannelName);
|
|
|
|
GeneralChannelMessage("Removing voice from " + Voicee + " in channel " + ChannelName);
|
|
}
|
|
else {
|
|
RequiredChannel->AddVoice(Voicee);
|
|
|
|
if (RequiredClient)
|
|
RequiredClient->GeneralChannelMessage(GetName() + " has given you voice in channel " + ChannelName);
|
|
|
|
GeneralChannelMessage(Voicee + " now has voice in channel " + ChannelName);
|
|
}
|
|
|
|
}
|
|
|
|
void Client::ChannelKick(std::string CommandString) {
|
|
|
|
std::string::size_type PlayerStart = CommandString.find_first_not_of(" ");
|
|
|
|
if (PlayerStart == std::string::npos) {
|
|
|
|
GeneralChannelMessage("Incorrect syntax: /chat kick <player> <channel>");
|
|
return;
|
|
}
|
|
|
|
std::string::size_type Space = CommandString.find_first_of(" ", PlayerStart);
|
|
|
|
if (Space == std::string::npos) {
|
|
|
|
GeneralChannelMessage("Incorrect syntax: /chat kick <player> <channel>");
|
|
return;
|
|
}
|
|
|
|
std::string Kickee = CapitaliseName(CommandString.substr(PlayerStart, Space - PlayerStart));
|
|
|
|
std::string::size_type ChannelStart = CommandString.find_first_not_of(" ", Space);
|
|
|
|
if (ChannelStart == std::string::npos) {
|
|
|
|
GeneralChannelMessage("Incorrect syntax: /chat kick <player> <channel>");
|
|
return;
|
|
}
|
|
std::string ChannelName = CapitaliseName(CommandString.substr(ChannelStart));
|
|
|
|
if ((ChannelName.length() > 0) && isdigit(ChannelName[0]))
|
|
ChannelName = ChannelSlotName(Strings::ToInt(ChannelName));
|
|
|
|
LogInfo("[[{}]] kicks [[{}]] from channel [[{}]]", GetName().c_str(), Kickee.c_str(), ChannelName.c_str());
|
|
|
|
Client *RequiredClient = g_Clientlist->FindCharacter(Kickee);
|
|
|
|
if (!RequiredClient) {
|
|
|
|
GeneralChannelMessage("Player " + Kickee + " is not online.");
|
|
return;
|
|
}
|
|
|
|
if (RequiredClient == this) {
|
|
|
|
GeneralChannelMessage("You cannot kick yourself out of a channel.");
|
|
return;
|
|
}
|
|
|
|
ChatChannel *RequiredChannel = ChannelList->FindChannel(ChannelName);
|
|
|
|
if (!RequiredChannel) {
|
|
|
|
GeneralChannelMessage("Channel " + ChannelName + " not found.");
|
|
return;
|
|
}
|
|
|
|
if (!RequiredChannel->IsOwner(GetName()) && !RequiredChannel->IsModerator(GetName()) && !IsChannelAdmin()) {
|
|
|
|
GeneralChannelMessage("You do not own or have moderator rights to channel " + ChannelName);
|
|
return;
|
|
}
|
|
|
|
if (RequiredChannel->IsOwner(RequiredClient->GetName())) {
|
|
|
|
GeneralChannelMessage("You cannot kick the owner out of the channel.");
|
|
return;
|
|
}
|
|
|
|
if (RequiredChannel->IsModerator(Kickee) && !RequiredChannel->IsOwner(GetName())) {
|
|
|
|
GeneralChannelMessage("Only the channel owner can kick a moderator out of the channel.");
|
|
return;
|
|
}
|
|
|
|
if (RequiredChannel->IsModerator(Kickee)) {
|
|
|
|
RequiredChannel->RemoveModerator(Kickee);
|
|
|
|
RequiredClient->GeneralChannelMessage(GetName() + " has removed your moderator rights to channel " + ChannelName);
|
|
|
|
GeneralChannelMessage("Removing moderator rights from " + Kickee + " to channel " + ChannelName);
|
|
}
|
|
|
|
RequiredClient->GeneralChannelMessage(GetName() + " has kicked you from channel " + ChannelName);
|
|
|
|
GeneralChannelMessage("Kicked " + Kickee + " from channel " + ChannelName);
|
|
|
|
RequiredClient->LeaveChannels(ChannelName, false);
|
|
}
|
|
|
|
void Client::ToggleInvites() {
|
|
|
|
AllowInvites = !AllowInvites;
|
|
|
|
if (AllowInvites)
|
|
GeneralChannelMessage("You will now receive channel invitations.");
|
|
else
|
|
GeneralChannelMessage("You will no longer receive channel invitations.");
|
|
|
|
}
|
|
|
|
std::string Client::ChannelSlotName(int SlotNumber) {
|
|
|
|
if ((SlotNumber < 1) || (SlotNumber > MAX_JOINED_CHANNELS))
|
|
return "";
|
|
|
|
if (JoinedChannels[SlotNumber - 1] == nullptr)
|
|
return "";
|
|
|
|
return JoinedChannels[SlotNumber - 1]->GetName();
|
|
|
|
}
|
|
|
|
void Client::SendHelp() {
|
|
|
|
GeneralChannelMessage("Chat Channel Commands:");
|
|
|
|
GeneralChannelMessage("/join, /leave, /leaveall, /list, /announce, /autojoin, ;set");
|
|
GeneralChannelMessage(";oplist, ;grant, ;invite, ;kick, ;moderate, ;password, ;voice");
|
|
GeneralChannelMessage(";setowner, ;toggleinvites");
|
|
}
|
|
|
|
void Client::AccountUpdate()
|
|
{
|
|
if (AccountGrabUpdateTimer)
|
|
{
|
|
if (AccountGrabUpdateTimer->Check(false))
|
|
{
|
|
AccountGrabUpdateTimer->Start(60000);
|
|
database.GetAccountStatus(this);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Client::SetConnectionType(char c) {
|
|
|
|
switch (c)
|
|
{
|
|
case EQ::versions::ucsTitaniumChat:
|
|
{
|
|
TypeOfConnection = ConnectionTypeChat;
|
|
ClientVersion_ = EQ::versions::ClientVersion::Titanium;
|
|
LogInfo("Connection type is Chat (Titanium)");
|
|
break;
|
|
}
|
|
case EQ::versions::ucsTitaniumMail:
|
|
{
|
|
TypeOfConnection = ConnectionTypeMail;
|
|
ClientVersion_ = EQ::versions::ClientVersion::Titanium;
|
|
LogInfo("Connection type is Mail (Titanium)");
|
|
break;
|
|
}
|
|
case EQ::versions::ucsSoFCombined:
|
|
{
|
|
TypeOfConnection = ConnectionTypeCombined;
|
|
ClientVersion_ = EQ::versions::ClientVersion::SoF;
|
|
LogInfo("Connection type is Combined (SoF)");
|
|
break;
|
|
}
|
|
case EQ::versions::ucsSoDCombined:
|
|
{
|
|
TypeOfConnection = ConnectionTypeCombined;
|
|
ClientVersion_ = EQ::versions::ClientVersion::SoD;
|
|
LogInfo("Connection type is Combined (SoD)");
|
|
break;
|
|
}
|
|
case EQ::versions::ucsUFCombined:
|
|
{
|
|
TypeOfConnection = ConnectionTypeCombined;
|
|
ClientVersion_ = EQ::versions::ClientVersion::UF;
|
|
UnderfootOrLater = true;
|
|
LogInfo("Connection type is Combined (Underfoot)");
|
|
break;
|
|
}
|
|
case EQ::versions::ucsRoFCombined:
|
|
{
|
|
TypeOfConnection = ConnectionTypeCombined;
|
|
ClientVersion_ = EQ::versions::ClientVersion::RoF;
|
|
UnderfootOrLater = true;
|
|
LogInfo("Connection type is Combined (RoF)");
|
|
break;
|
|
}
|
|
case EQ::versions::ucsRoF2Combined:
|
|
{
|
|
TypeOfConnection = ConnectionTypeCombined;
|
|
ClientVersion_ = EQ::versions::ClientVersion::RoF2;
|
|
UnderfootOrLater = true;
|
|
LogInfo("Connection type is Combined (RoF2)");
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
TypeOfConnection = ConnectionTypeUnknown;
|
|
ClientVersion_ = EQ::versions::ClientVersion::Unknown;
|
|
LogInfo("Connection type is unknown");
|
|
}
|
|
}
|
|
}
|
|
|
|
Client *Clientlist::IsCharacterOnline(const std::string& CharacterName) {
|
|
|
|
// This method is used to determine if the character we are a sending an email to is connected to the mailserver,
|
|
// so we can send them a new email notification.
|
|
//
|
|
// The way live works is that it sends a notification if a player receives an email for their 'primary' mailbox,
|
|
// i.e. for the character they are logged in as, or for the character whose mailbox they have selected in the
|
|
// mail window.
|
|
//
|
|
std::list<Client*>::iterator Iterator;
|
|
|
|
for (Iterator = ClientChatConnections.begin(); Iterator != ClientChatConnections.end(); ++Iterator) {
|
|
|
|
if (!(*Iterator)->IsMailConnection())
|
|
continue;
|
|
|
|
int MailBoxNumber = (*Iterator)->GetMailBoxNumber(CharacterName);
|
|
|
|
// If the mail is destined for the primary mailbox for this character, or the one they have selected
|
|
//
|
|
if ((MailBoxNumber == 0) || (MailBoxNumber == (*Iterator)->GetMailBoxNumber()))
|
|
return (*Iterator);
|
|
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
int Client::GetMailBoxNumber(const std::string& CharacterName) {
|
|
|
|
for (unsigned int i = 0; i < Characters.size(); i++)
|
|
if (Characters[i].Name == CharacterName)
|
|
return i;
|
|
|
|
return -1;
|
|
}
|
|
|
|
void Client::SendNotification(int MailBoxNumber, const std::string& Subject, const std::string& From, int MessageID) {
|
|
|
|
char TimeStamp[100];
|
|
|
|
char sMessageID[100];
|
|
|
|
char Sequence[100];
|
|
|
|
sprintf(TimeStamp, "%i", (int)time(nullptr));
|
|
|
|
sprintf(sMessageID, "%i", MessageID);
|
|
|
|
sprintf(Sequence, "%i", 1);
|
|
|
|
int PacketLength = 8 + strlen(sMessageID) + strlen(TimeStamp) + From.length() + Subject.length();
|
|
|
|
auto outapp = new EQApplicationPacket(OP_MailNew, PacketLength);
|
|
|
|
char *PacketBuffer = (char *)outapp->pBuffer;
|
|
|
|
VARSTRUCT_ENCODE_INTSTRING(PacketBuffer, MailBoxNumber);
|
|
VARSTRUCT_ENCODE_STRING(PacketBuffer, sMessageID);
|
|
VARSTRUCT_ENCODE_STRING(PacketBuffer, TimeStamp);
|
|
VARSTRUCT_ENCODE_STRING(PacketBuffer, "1");
|
|
VARSTRUCT_ENCODE_STRING(PacketBuffer, From.c_str());
|
|
VARSTRUCT_ENCODE_STRING(PacketBuffer, Subject.c_str());
|
|
|
|
|
|
QueuePacket(outapp);
|
|
|
|
safe_delete(outapp);
|
|
}
|
|
|
|
void Client::ChangeMailBox(int NewMailBox)
|
|
{
|
|
LogInfo("[{}] Change to mailbox [{}]", MailBoxName().c_str(), NewMailBox);
|
|
|
|
SetMailBox(NewMailBox);
|
|
auto id = std::to_string(NewMailBox);
|
|
|
|
LogInfo("New mailbox is [{}]", MailBoxName().c_str());
|
|
|
|
auto outapp = new EQApplicationPacket(OP_MailboxChange, id.length() + 1);
|
|
|
|
char *buf = (char *)outapp->pBuffer;
|
|
|
|
VARSTRUCT_ENCODE_STRING(buf, id.c_str());
|
|
|
|
QueuePacket(outapp);
|
|
safe_delete(outapp);
|
|
}
|
|
|
|
void Client::SendFriends() {
|
|
|
|
std::vector<std::string> Friends, Ignorees;
|
|
|
|
database.GetFriendsAndIgnore(GetCharID(), Friends, Ignorees);
|
|
|
|
EQApplicationPacket *outapp;
|
|
|
|
std::vector<std::string>::iterator Iterator;
|
|
|
|
Iterator = Friends.begin();
|
|
|
|
while (Iterator != Friends.end()) {
|
|
|
|
outapp = new EQApplicationPacket(OP_Buddy, (*Iterator).length() + 2);
|
|
|
|
char *PacketBuffer = (char *)outapp->pBuffer;
|
|
|
|
VARSTRUCT_ENCODE_TYPE(uint8, PacketBuffer, 1);
|
|
|
|
VARSTRUCT_ENCODE_STRING(PacketBuffer, (*Iterator).c_str());
|
|
|
|
|
|
QueuePacket(outapp);
|
|
|
|
safe_delete(outapp);
|
|
|
|
++Iterator;
|
|
}
|
|
|
|
Iterator = Ignorees.begin();
|
|
|
|
while (Iterator != Ignorees.end()) {
|
|
|
|
std::string Ignoree = "SOE.EQ." + WorldShortName + "." + (*Iterator);
|
|
|
|
outapp = new EQApplicationPacket(OP_Ignore, Ignoree.length() + 2);
|
|
|
|
char *PacketBuffer = (char *)outapp->pBuffer;
|
|
|
|
VARSTRUCT_ENCODE_TYPE(uint8, PacketBuffer, 0);
|
|
|
|
VARSTRUCT_ENCODE_STRING(PacketBuffer, Ignoree.c_str());
|
|
|
|
|
|
QueuePacket(outapp);
|
|
|
|
safe_delete(outapp);
|
|
|
|
++Iterator;
|
|
}
|
|
}
|
|
|
|
std::string Client::MailBoxName()
|
|
{
|
|
if ((Characters.empty()) || (CurrentMailBox > (Characters.size() - 1))) {
|
|
LogDebug("MailBoxName() called with CurrentMailBox set to [{}] and Characters.size() is [{}]",
|
|
CurrentMailBox, Characters.size());
|
|
|
|
return std::string();
|
|
}
|
|
|
|
LogDebug("MailBoxName() called with CurrentMailBox set to [{}] and Characters.size() is [{}]",
|
|
CurrentMailBox, Characters.size());
|
|
|
|
return Characters[CurrentMailBox].Name;
|
|
|
|
}
|
|
|
|
int Client::GetCharID() {
|
|
|
|
if (Characters.empty())
|
|
return 0;
|
|
|
|
return Characters[0].CharID;
|
|
}
|