//////////////////////////////////////////////////////////////////////////////////////
// akashi - a server for Attorney Online 2 //
// Copyright (C) 2020 scatterflower //
// //
// This program is free software: you can redistribute it and/or modify //
// it under the terms of the GNU Affero General Public License as //
// published by the Free Software Foundation, either version 3 of the //
// License, or (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU Affero General Public License for more details. //
// //
// You should have received a copy of the GNU Affero General Public License //
// along with this program. If not, see . //
//////////////////////////////////////////////////////////////////////////////////////
#include "aoclient.h"
#include "area_data.h"
#include "command_extension.h"
#include "config_manager.h"
#include "packet/packet_factory.h"
#include "server.h"
const QMap AOClient::COMMANDS{
{"login", {{ACLRole::NONE}, 0, &AOClient::cmdLogin}},
{"getarea", {{ACLRole::NONE}, 0, &AOClient::cmdGetArea}},
{"getareas", {{ACLRole::NONE}, 0, &AOClient::cmdGetAreas}},
{"ban", {{ACLRole::BAN}, 3, &AOClient::cmdBan}},
{"kick", {{ACLRole::KICK}, 2, &AOClient::cmdKick}},
{"changeauth", {{ACLRole::SUPER}, 0, &AOClient::cmdChangeAuth}},
{"rootpass", {{ACLRole::SUPER}, 1, &AOClient::cmdSetRootPass}},
{"background", {{ACLRole::NONE}, 1, &AOClient::cmdSetBackground}},
{"side", {{ACLRole::NONE}, 0, &AOClient::cmdSetSide}},
{"lock_background", {{ACLRole::CM}, 0, &AOClient::cmdBgLock}},
{"unlock_background", {{ACLRole::CM}, 0, &AOClient::cmdBgUnlock}},
{"adduser", {{ACLRole::MODIFY_USERS}, 2, &AOClient::cmdAddUser}},
{"removeuser", {{ACLRole::MODIFY_USERS}, 1, &AOClient::cmdRemoveUser}},
{"listusers", {{ACLRole::MODIFY_USERS}, 0, &AOClient::cmdListUsers}},
{"setperms", {{ACLRole::MODIFY_USERS}, 2, &AOClient::cmdSetPerms}},
{"removeperms", {{ACLRole::MODIFY_USERS}, 1, &AOClient::cmdRemovePerms}},
{"listperms", {{ACLRole::NONE}, 0, &AOClient::cmdListPerms}},
{"logout", {{ACLRole::NONE}, 0, &AOClient::cmdLogout}},
{"pos", {{ACLRole::NONE}, 1, &AOClient::cmdPos}},
{"g", {{ACLRole::NONE}, 1, &AOClient::cmdG}},
{"need", {{ACLRole::NONE}, 1, &AOClient::cmdNeed}},
{"coinflip", {{ACLRole::NONE}, 0, &AOClient::cmdFlip}},
{"roll", {{ACLRole::NONE}, 0, &AOClient::cmdRoll}},
{"rollp", {{ACLRole::NONE}, 0, &AOClient::cmdRollP}},
{"doc", {{ACLRole::NONE}, 0, &AOClient::cmdDoc}},
{"cleardoc", {{ACLRole::NONE}, 0, &AOClient::cmdClearDoc}},
{"cm", {{ACLRole::NONE}, 0, &AOClient::cmdCM}},
{"uncm", {{ACLRole::CM}, 0, &AOClient::cmdUnCM}},
{"invite", {{ACLRole::CM}, 1, &AOClient::cmdInvite}},
{"uninvite", {{ACLRole::CM}, 1, &AOClient::cmdUnInvite}},
{"area_lock", {{ACLRole::CM}, 0, &AOClient::cmdLock}},
{"area_spectate", {{ACLRole::CM}, 0, &AOClient::cmdSpectatable}},
{"area_unlock", {{ACLRole::CM}, 0, &AOClient::cmdUnLock}},
{"timer", {{ACLRole::CM}, 0, &AOClient::cmdTimer}},
{"area", {{ACLRole::NONE}, 1, &AOClient::cmdArea}},
{"play", {{ACLRole::NONE}, 1, &AOClient::cmdPlay}},
{"area_kick", {{ACLRole::CM}, 1, &AOClient::cmdAreaKick}},
{"randomchar", {{ACLRole::NONE}, 0, &AOClient::cmdRandomChar}},
{"switch", {{ACLRole::NONE}, 1, &AOClient::cmdSwitch}},
{"toggleglobal", {{ACLRole::NONE}, 0, &AOClient::cmdToggleGlobal}},
{"mods", {{ACLRole::NONE}, 0, &AOClient::cmdMods}},
{"commands", {{ACLRole::NONE}, 0, &AOClient::cmdCommands}},
{"status", {{ACLRole::NONE}, 1, &AOClient::cmdStatus}},
{"forcepos", {{ACLRole::CM}, 2, &AOClient::cmdForcePos}},
{"currentmusic", {{ACLRole::NONE}, 0, &AOClient::cmdCurrentMusic}},
{"pm", {{ACLRole::NONE}, 2, &AOClient::cmdPM}},
{"evidence_mod", {{ACLRole::EVI_MOD}, 1, &AOClient::cmdEvidenceMod}},
{"motd", {{ACLRole::NONE}, 0, &AOClient::cmdMOTD}},
{"set_motd", {{ACLRole::MOTD}, 1, &AOClient::cmdSetMOTD}},
{"announce", {{ACLRole::ANNOUNCE}, 1, &AOClient::cmdAnnounce}},
{"m", {{ACLRole::MODCHAT}, 1, &AOClient::cmdM}},
{"gm", {{ACLRole::MODCHAT}, 1, &AOClient::cmdGM}},
{"mute", {{ACLRole::MUTE}, 1, &AOClient::cmdMute}},
{"unmute", {{ACLRole::MUTE}, 1, &AOClient::cmdUnMute}},
{"bans", {{ACLRole::BAN}, 0, &AOClient::cmdBans}},
{"unban", {{ACLRole::BAN}, 1, &AOClient::cmdUnBan}},
{"subtheme", {{ACLRole::CM}, 1, &AOClient::cmdSubTheme}},
{"about", {{ACLRole::NONE}, 0, &AOClient::cmdAbout}},
{"evidence_swap", {{ACLRole::CM}, 2, &AOClient::cmdEvidence_Swap}},
{"notecard", {{ACLRole::NONE}, 1, &AOClient::cmdNoteCard}},
{"notecard_reveal", {{ACLRole::CM}, 0, &AOClient::cmdNoteCardReveal}},
{"notecard_clear", {{ACLRole::NONE}, 0, &AOClient::cmdNoteCardClear}},
{"8ball", {{ACLRole::NONE}, 1, &AOClient::cmd8Ball}},
{"lm", {{ACLRole::MODCHAT}, 1, &AOClient::cmdLM}},
{"judgelog", {{ACLRole::CM}, 0, &AOClient::cmdJudgeLog}},
{"allow_blankposting", {{ACLRole::MODCHAT}, 0, &AOClient::cmdAllowBlankposting}},
{"gimp", {{ACLRole::MUTE}, 1, &AOClient::cmdGimp}},
{"ungimp", {{ACLRole::MUTE}, 1, &AOClient::cmdUnGimp}},
{"baninfo", {{ACLRole::BAN}, 1, &AOClient::cmdBanInfo}},
{"testify", {{ACLRole::CM}, 0, &AOClient::cmdTestify}},
{"testimony", {{ACLRole::NONE}, 0, &AOClient::cmdTestimony}},
{"examine", {{ACLRole::CM}, 0, &AOClient::cmdExamine}},
{"pause", {{ACLRole::CM}, 0, &AOClient::cmdPauseTestimony}},
{"delete", {{ACLRole::CM}, 0, &AOClient::cmdDeleteStatement}},
{"update", {{ACLRole::CM}, 0, &AOClient::cmdUpdateStatement}},
{"add", {{ACLRole::CM}, 0, &AOClient::cmdAddStatement}},
{"reload", {{ACLRole::SUPER}, 0, &AOClient::cmdReload}},
{"disemvowel", {{ACLRole::MUTE}, 1, &AOClient::cmdDisemvowel}},
{"undisemvowel", {{ACLRole::MUTE}, 1, &AOClient::cmdUnDisemvowel}},
{"shake", {{ACLRole::MUTE}, 1, &AOClient::cmdShake}},
{"unshake", {{ACLRole::MUTE}, 1, &AOClient::cmdUnShake}},
{"force_noint_pres", {{ACLRole::CM}, 0, &AOClient::cmdForceImmediate}},
{"allow_iniswap", {{ACLRole::CM}, 0, &AOClient::cmdAllowIniswap}},
{"afk", {{ACLRole::NONE}, 0, &AOClient::cmdAfk}},
{"savetestimony", {{ACLRole::NONE}, 1, &AOClient::cmdSaveTestimony}},
{"loadtestimony", {{ACLRole::CM}, 1, &AOClient::cmdLoadTestimony}},
{"permitsaving", {{ACLRole::MODCHAT}, 1, &AOClient::cmdPermitSaving}},
{"saveevidence", {{ACLRole::MODCHAT}, 0, &AOClient::cmdSaveEvidence}},
{"mutepm", {{ACLRole::NONE}, 0, &AOClient::cmdMutePM}},
{"toggleadverts", {{ACLRole::NONE}, 0, &AOClient::cmdToggleAdverts}},
{"ooc_mute", {{ACLRole::MUTE}, 1, &AOClient::cmdOocMute}},
{"ooc_unmute", {{ACLRole::MUTE}, 1, &AOClient::cmdOocUnMute}},
{"block_wtce", {{ACLRole::MUTE}, 1, &AOClient::cmdBlockWtce}},
{"unblock_wtce", {{ACLRole::MUTE}, 1, &AOClient::cmdUnBlockWtce}},
{"block_dj", {{ACLRole::MUTE}, 1, &AOClient::cmdBlockDj}},
{"unblock_dj", {{ACLRole::MUTE}, 1, &AOClient::cmdUnBlockDj}},
{"charcurse", {{ACLRole::MUTE}, 1, &AOClient::cmdCharCurse}},
{"uncharcurse", {{ACLRole::MUTE}, 1, &AOClient::cmdUnCharCurse}},
{"charselect", {{ACLRole::NONE}, 0, &AOClient::cmdCharSelect}},
{"force_charselect", {{ACLRole::FORCE_CHARSELECT}, 1, &AOClient::cmdForceCharSelect}},
{"togglemusic", {{ACLRole::CM}, 0, &AOClient::cmdToggleMusic}},
{"a", {{ACLRole::NONE}, 2, &AOClient::cmdA}},
{"s", {{ACLRole::NONE}, 0, &AOClient::cmdS}},
{"kick_uid", {{ACLRole::KICK}, 2, &AOClient::cmdKickUid}},
{"firstperson", {{ACLRole::NONE}, 0, &AOClient::cmdFirstPerson}},
{"update_ban", {{ACLRole::BAN}, 3, &AOClient::cmdUpdateBan}},
{"changepass", {{ACLRole::NONE}, 1, &AOClient::cmdChangePassword}},
{"ignore_bglist", {{ACLRole::IGNORE_BGLIST}, 0, &AOClient::cmdIgnoreBgList}},
{"notice", {{ACLRole::SEND_NOTICE}, 1, &AOClient::cmdNotice}},
{"noticeg", {{ACLRole::SEND_NOTICE}, 1, &AOClient::cmdNoticeGlobal}},
{"togglejukebox", {{ACLRole::CM, ACLRole::JUKEBOX}, 0, &AOClient::cmdToggleJukebox}},
{"help", {{ACLRole::NONE}, 1, &AOClient::cmdHelp}},
{"clearcm", {{ACLRole::KICK}, 0, &AOClient::cmdClearCM}},
{"togglemessage", {{ACLRole::CM}, 0, &AOClient::cmdToggleAreaMessageOnJoin}},
{"clearmessage", {{ACLRole::CM}, 0, &AOClient::cmdClearAreaMessage}},
{"areamessage", {{ACLRole::CM}, 0, &AOClient::cmdAreaMessage}},
{"webfiles", {{ACLRole::NONE}, 0, &AOClient::cmdWebfiles}},
{"addsong", {{ACLRole::CM}, 1, &AOClient::cmdAddSong}},
{"addcategory", {{ACLRole::CM}, 1, &AOClient::cmdAddCategory}},
{"removeentry", {{ACLRole::CM}, 1, &AOClient::cmdRemoveCategorySong}},
{"toggleroot", {{ACLRole::CM}, 0, &AOClient::cmdToggleRootlist}},
{"clearcustom", {{ACLRole::CM}, 0, &AOClient::cmdClearCustom}},
{"toggle_wtce", {{ACLRole::CM}, 0, &AOClient::cmdToggleWtce}},
{"toggle_shouts", {{ACLRole::CM}, 0, &AOClient::cmdToggleShouts}},
{"kick_other", {{ACLRole::NONE}, 0, &AOClient::cmdKickOther}},
{"jukebox_skip", {{ACLRole::CM}, 0, &AOClient::cmdJukeboxSkip}},
{"play_ambience", {{ACLRole::NONE}, 1, &AOClient::cmdPlayAmbience}}};
void AOClient::clientDisconnected()
{
#ifdef NET_DEBUG
qDebug() << m_remote_ip.toString() << "disconnected";
#endif
if (m_joined) {
server->getAreaById(areaId())
->removeClient(server->getCharID(character()), clientId());
arup(ARUPType::PLAYER_COUNT, true);
}
if (character() != "") {
server->updateCharsTaken(server->getAreaById(areaId()));
}
bool l_updateLocks = false;
const QVector l_areas = server->getAreas();
for (AreaData *l_area : l_areas) {
l_updateLocks = l_updateLocks || l_area->removeOwner(clientId());
}
if (l_updateLocks)
arup(ARUPType::LOCKED, true);
arup(ARUPType::CM, true);
emit clientSuccessfullyDisconnected(clientId());
}
void AOClient::handlePacket(AOPacket *packet)
{
#ifdef NET_DEBUG
qDebug() << "Received packet:" << packet->getPacketInfo().header << ":" << packet->getContent() << "args length:" << packet->getContent().length();
#endif
AreaData *l_area = server->getAreaById(areaId());
if (packet->getContent().join("").size() > 16384) {
return;
}
if (!checkPermission(packet->getPacketInfo().acl_permission)) {
return;
}
if (packet->getPacketInfo().header != "CH" && m_joined) {
if (m_is_afk)
sendServerMessage("You are no longer AFK.");
m_is_afk = false;
if (characterName().endsWith(" [AFK]")) {
setCharacterName(characterName().remove(" [AFK]"));
}
m_afk_timer->start(ConfigManager::afkTimeout() * 1000);
}
if (packet->getContent().length() < packet->getPacketInfo().min_args) {
#ifdef NET_DEBUG
qDebug() << "Invalid packet args length. Minimum is" << packet->getPacketInfo().min_args << "but only" << packet->getContent().length() << "were given.";
#endif
return;
}
packet->handlePacket(l_area, *this);
}
void AOClient::changeArea(int new_area)
{
if (areaId() == new_area) {
sendServerMessage("You are already in area " + server->getAreaName(areaId()));
return;
}
if (server->getAreaById(new_area)->lockStatus() == AreaData::LockStatus::LOCKED && !server->getAreaById(new_area)->invited().contains(clientId()) && !checkPermission(ACLRole::BYPASS_LOCKS)) {
sendServerMessage("Area " + server->getAreaName(new_area) + " is locked.");
return;
}
if (character() != "") {
server->getAreaById(areaId())
->changeCharacter(server->getCharID(character()), -1);
}
server->getAreaById(areaId())->removeClient(m_char_id, clientId());
bool l_character_taken = false;
if (server->getAreaById(new_area)->charactersTaken().contains(
server->getCharID(character()))) {
setCharacter("");
m_char_id = -1;
l_character_taken = true;
}
int old_area_id = areaId();
server->getAreaById(new_area)->addClient(m_char_id, clientId());
setAreaId(new_area);
server->updateCharsTaken(server->getAreaById(old_area_id));
server->updateCharsTaken(server->getAreaById(new_area));
arup(ARUPType::PLAYER_COUNT, true);
sendEvidenceList(server->getAreaById(new_area));
sendPacket("HP", {"1", QString::number(server->getAreaById(new_area)->defHP())});
sendPacket("HP", {"2", QString::number(server->getAreaById(new_area)->proHP())});
sendPacket("BN", {server->getAreaById(new_area)->background(), server->getAreaById(new_area)->side()});
if (l_character_taken) {
sendPacket("DONE");
}
const QList l_timers = server->getAreaById(areaId())->timers();
for (QTimer *l_timer : l_timers) {
int l_timer_id = server->getAreaById(areaId())->timers().indexOf(l_timer) + 1;
if (l_timer->isActive()) {
sendPacket("TI", {QString::number(l_timer_id), "2"});
sendPacket("TI", {QString::number(l_timer_id), "0", QString::number(QTime(0, 0).msecsTo(QTime(0, 0).addMSecs(l_timer->remainingTime())))});
}
else {
sendPacket("TI", {QString::number(l_timer_id), "3"});
}
}
sendServerMessage("You moved to area " + server->getAreaName(areaId()));
if (server->getAreaById(areaId())->sendAreaMessageOnJoin())
sendServerMessage(server->getAreaById(areaId())->areaMessage());
if (server->getAreaById(areaId())->lockStatus() == AreaData::LockStatus::SPECTATABLE)
sendServerMessage("Area " + server->getAreaName(areaId()) + " is spectate-only; to chat IC you will need to be invited by the CM.");
}
bool AOClient::changeCharacter(int char_id)
{
AreaData *l_area = server->getAreaById(areaId());
if (char_id >= server->getCharacterCount())
return false;
if (m_is_charcursed && !m_charcurse_list.contains(char_id)) {
return false;
}
bool l_successfulChange = l_area->changeCharacter(server->getCharID(character()),
char_id);
if (char_id < 0) {
setCharacter("");
m_char_id = char_id;
setSpectator(true);
}
if (l_successfulChange == true) {
QString l_char_selected = server->getCharacterById(char_id);
setCharacter(l_char_selected);
m_pos = "";
server->updateCharsTaken(l_area);
sendPacket("PV", {QString::number(clientId()), "CID", QString::number(char_id)});
return true;
}
return false;
}
void AOClient::changePosition(QString new_pos)
{
m_pos = new_pos;
sendServerMessage("Position changed to " + m_pos + ".");
sendPacket("SP", {m_pos});
}
void AOClient::handleCommand(QString command, int argc, QStringList argv)
{
command = command.toLower();
QString l_target_command = command;
QVector l_permissions;
// check for aliases
const QList l_extensions = server->getCommandExtensionCollection()->getExtensions();
for (const CommandExtension &i_extension : l_extensions) {
if (i_extension.checkCommandNameAndAlias(command)) {
l_target_command = i_extension.getCommandName();
l_permissions = i_extension.getPermissions();
break;
}
}
CommandInfo l_command = COMMANDS.value(l_target_command, {{ACLRole::NONE}, -1, &AOClient::cmdDefault});
if (l_permissions.isEmpty()) {
l_permissions.append(l_command.acl_permissions);
}
bool l_has_permissions = false;
for (const ACLRole::Permission i_permission : qAsConst(l_permissions)) {
if (checkPermission(i_permission)) {
l_has_permissions = true;
break;
}
}
if (!l_has_permissions) {
sendServerMessage("You do not have permission to use that command.");
return;
}
if (argc < l_command.minArgs) {
sendServerMessage("Invalid command syntax.");
sendServerMessage("The expected syntax for this command is: \n" + ConfigManager::commandHelp(command).usage);
return;
}
(this->*(l_command.action))(argc, argv);
}
void AOClient::arup(ARUPType type, bool broadcast)
{
QStringList l_arup_data;
l_arup_data.append(QString::number(type));
const QVector l_areas = server->getAreas();
for (AreaData *l_area : l_areas) {
switch (type) {
case ARUPType::PLAYER_COUNT:
{
l_arup_data.append(QString::number(l_area->playerCount()));
break;
}
case ARUPType::STATUS:
{
QString l_area_status = QVariant::fromValue(l_area->status()).toString().replace("_", "-"); // LOOKING_FOR_PLAYERS to LOOKING-FOR-PLAYERS
l_arup_data.append(l_area_status);
break;
}
case ARUPType::CM:
{
if (l_area->owners().isEmpty())
l_arup_data.append("FREE");
else {
QStringList l_area_owners;
const QList l_owner_ids = l_area->owners();
for (int l_owner_id : l_owner_ids) {
AOClient *l_owner = server->getClientByID(l_owner_id);
l_area_owners.append("[" + QString::number(l_owner->clientId()) + "] " + l_owner->character());
}
l_arup_data.append(l_area_owners.join(", "));
}
break;
}
case ARUPType::LOCKED:
{
QString l_lock_status = QVariant::fromValue(l_area->lockStatus()).toString();
l_arup_data.append(l_lock_status);
break;
}
default:
{
return;
}
}
}
if (broadcast)
server->broadcast(PacketFactory::createPacket("ARUP", l_arup_data));
else
sendPacket("ARUP", l_arup_data);
}
void AOClient::fullArup()
{
arup(ARUPType::PLAYER_COUNT, false);
arup(ARUPType::STATUS, false);
arup(ARUPType::CM, false);
arup(ARUPType::LOCKED, false);
}
void AOClient::sendPacket(AOPacket *packet)
{
m_socket->write(packet);
}
void AOClient::sendPacket(QString header, QStringList contents)
{
sendPacket(PacketFactory::createPacket(header, contents));
}
void AOClient::sendPacket(QString header)
{
sendPacket(PacketFactory::createPacket(header, {}));
}
void AOClient::calculateIpid()
{
// TODO: add support for longer ipids?
// This reduces the (fairly high) chance of
// birthday paradox issues arising. However,
// typing more than 8 characters might be a
// bit cumbersome.
QCryptographicHash hash(QCryptographicHash::Md5); // Don't need security, just hashing for uniqueness
hash.addData(m_remote_ip.toString().toUtf8());
m_ipid = hash.result().toHex().right(8); // Use the last 8 characters (4 bytes)
}
void AOClient::sendServerMessage(QString message)
{
sendPacket("CT", {ConfigManager::serverTag(), message, "1"});
}
void AOClient::sendServerMessageArea(QString message)
{
server->broadcast(PacketFactory::createPacket("CT", {ConfigManager::serverTag(), message, "1"}),
areaId());
}
void AOClient::sendServerBroadcast(QString message)
{
server->broadcast(PacketFactory::createPacket("CT", {ConfigManager::serverTag(), message, "1"}));
}
bool AOClient::checkPermission(ACLRole::Permission f_permission) const
{
if (f_permission == ACLRole::NONE) {
return true;
}
if ((f_permission == ACLRole::CM) && server->getAreaById(areaId())->owners().contains(clientId())) {
return true; // I'm sorry for this hack.
}
if (!isAuthenticated()) {
return false;
}
if (ConfigManager::authType() == DataTypes::AuthType::SIMPLE) {
return true;
}
const ACLRole l_role = server->getACLRolesHandler()->getRoleById(m_acl_role_id);
return l_role.checkPermission(f_permission);
}
QString AOClient::getIpid() const
{
return m_ipid;
}
QString AOClient::getHwid() const
{
return m_hwid;
}
bool AOClient::hasJoined() const
{
return m_joined;
}
bool AOClient::isAuthenticated() const
{
return m_authenticated;
}
Server *AOClient::getServer() { return server; }
int AOClient::clientId() const
{
return m_id;
}
QString AOClient::name() const { return m_ooc_name; }
void AOClient::setName(const QString &f_name)
{
if (f_name != m_ooc_name) {
m_ooc_name = f_name;
Q_EMIT nameChanged(m_ooc_name);
}
}
int AOClient::areaId() const
{
return m_current_area;
}
void AOClient::setAreaId(const int f_area_id)
{
if (f_area_id != m_current_area) {
m_current_area = f_area_id;
Q_EMIT areaIdChanged(m_current_area);
}
}
QString AOClient::character() const
{
return m_current_char;
}
void AOClient::setCharacter(const QString &f_character)
{
if (f_character != m_current_char) {
m_current_char = f_character;
Q_EMIT characterChanged(m_current_char);
}
}
QString AOClient::characterName() const { return m_showname; }
void AOClient::setCharacterName(const QString &f_showname)
{
if (f_showname != m_showname) {
m_showname = f_showname;
Q_EMIT characterNameChanged(m_showname);
}
}
void AOClient::setSpectator(bool f_spectator)
{
m_is_spectator = f_spectator;
}
bool AOClient::isSpectator() const
{
return m_is_spectator;
}
void AOClient::onAfkTimeout()
{
if (!m_is_afk) {
sendServerMessage("You are now AFK.");
setCharacterName(characterName() + " [AFK]");
}
m_is_afk = true;
}
AOClient::AOClient(
Server *p_server, NetworkSocket *socket, QObject *parent, int user_id, MusicManager *p_manager) :
QObject(parent),
m_remote_ip(socket->peerAddress()),
m_password(""),
m_joined(false),
m_socket(socket),
m_music_manager(p_manager),
m_last_wtce_time(0),
m_id(user_id),
m_current_area(0),
m_current_char(""),
server(p_server),
is_partial(false)
{
m_afk_timer = new QTimer;
m_afk_timer->setSingleShot(true);
connect(m_afk_timer, &QTimer::timeout, this, &AOClient::onAfkTimeout);
}
AOClient::~AOClient()
{
clientDisconnected();
m_socket->deleteLater();
}