diff --git a/include/aoclient.h b/include/aoclient.h index a0ec439..d5175f7 100644 --- a/include/aoclient.h +++ b/include/aoclient.h @@ -38,7 +38,7 @@ class Server; class AOClient : public QObject { Q_OBJECT public: - AOClient(Server* p_server, QTcpSocket* p_socket, QObject* parent = nullptr); + AOClient(Server* p_server, QTcpSocket* p_socket, QObject* parent = nullptr, int user_id = 0); ~AOClient(); QString getHwid(); @@ -46,6 +46,8 @@ class AOClient : public QObject { Server* getServer(); void setHwid(QString p_hwid); + int id; + QHostAddress remote_ip; QString password; bool joined; @@ -61,6 +63,7 @@ class AOClient : public QObject { {"BAN", 1ULL << 1}, {"BGLOCK", 1ULL << 2}, {"MODIFY_USERS", 1ULL << 3}, + {"CM", 1ULL << 4}, {"SUPER", ~0ULL} }; @@ -160,30 +163,44 @@ class AOClient : public QObject { {"EE", {ACLFlags.value("NONE"), 4, &AOClient::pktEditEvidence}} }; - // Commands + //// Commands void cmdDefault(int argc, QStringList argv); + // Authentication void cmdLogin(int argc, QStringList argv); - void cmdGetAreas(int argc, QStringList argv); - void cmdGetArea(int argc, QStringList argv); - void cmdBan(int argc, QStringList argv); - void cmdKick(int argc, QStringList argv); void cmdChangeAuth(int argc, QStringList argv); void cmdSetRootPass(int argc, QStringList argv); - void cmdSetBackground(int argc, QStringList argv); - void cmdBgLock(int argc, QStringList argv); - void cmdBgUnlock(int argc, QStringList argv); void cmdAddUser(int argc, QStringList argv); void cmdListPerms(int argc, QStringList argv); void cmdAddPerms(int argc, QStringList argv); void cmdRemovePerms(int argc, QStringList argv); void cmdListUsers(int argc, QStringList argv); void cmdLogout(int argc, QStringList argv); - void cmdPos(int argc, QStringList argv); - void cmdG(int argc, QStringList argv); + // Areas + void cmdCM(int argc, QStringList argv); + void cmdUnCM(int argc, QStringList argv); + void cmdInvite(int argc, QStringList argv); + void cmdUnInvite(int argc, QStringList argv); + void cmdLock(int argc, QStringList argv); + void cmdSpectatable(int argc, QStringList argv); + void cmdUnLock(int argc, QStringList argv); + void cmdGetAreas(int argc, QStringList argv); + void cmdGetArea(int argc, QStringList argv); + void cmdSetBackground(int argc, QStringList argv); + void cmdBgLock(int argc, QStringList argv); + void cmdBgUnlock(int argc, QStringList argv); + // Moderation + void cmdBan(int argc, QStringList argv); + void cmdKick(int argc, QStringList argv); + // Casing/RP void cmdNeed(int argc, QStringList argv); void cmdFlip(int argc, QStringList argv); void cmdRoll(int argc, QStringList argv); void cmdRollP(int argc, QStringList argv); + void cmdDoc(int argc, QStringList argv); + void cmdClearDoc(int argc, QStringList argv); + // Messaging/Client + void cmdPos(int argc, QStringList argv); + void cmdG(int argc, QStringList argv); // Command helper functions QStringList buildAreaList(int area_idx); @@ -222,8 +239,16 @@ class AOClient : public QObject { {"need", {ACLFlags.value("NONE"), 1, &AOClient::cmdNeed}}, {"flip", {ACLFlags.value("NONE"), 0, &AOClient::cmdFlip}}, {"roll", {ACLFlags.value("NONE"), 0, &AOClient::cmdRoll}}, - {"rollp", {ACLFlags.value("NONE"), 0, &AOClient::cmdRollP}} - + {"rollp", {ACLFlags.value("NONE"), 0, &AOClient::cmdRollP}}, + {"doc", {ACLFlags.value("NONE"), 0, &AOClient::cmdDoc}}, + {"cleardoc", {ACLFlags.value("NONE"), 0, &AOClient::cmdClearDoc}}, + {"cm", {ACLFlags.value("NONE"), 0, &AOClient::cmdCM}}, + {"uncm", {ACLFlags.value("CM"), 0, &AOClient::cmdUnCM}}, + {"invite", {ACLFlags.value("CM"), 1, &AOClient::cmdInvite}}, + {"uninvite", {ACLFlags.value("CM"), 1, &AOClient::cmdUnInvite}}, + {"lock", {ACLFlags.value("CM"), 0, &AOClient::cmdLock}}, + {"spectatable", {ACLFlags.value("CM"), 0, &AOClient::cmdSpectatable}}, + {"unlock", {ACLFlags.value("CM"), 0, &AOClient::cmdUnLock}}, }; QString partial_packet; diff --git a/include/area_data.h b/include/area_data.h index 175b595..13d30d7 100644 --- a/include/area_data.h +++ b/include/area_data.h @@ -42,13 +42,20 @@ class AreaData { QList evidence; int player_count; QString status; - QString current_cm; - bool locked; + QList owners; + QList invited; + enum LockStatus { + FREE, + LOCKED, + SPECTATABLE + }; + LockStatus locked; QString background; + bool is_protected; bool showname_allowed; - bool locking_allowed; bool iniswap_allowed; bool bg_locked; + QString document; int def_hp; int pro_hp; Logger* logger; diff --git a/include/server.h b/include/server.h index d70ef34..1aca99f 100644 --- a/include/server.h +++ b/include/server.h @@ -46,6 +46,7 @@ class Server : public QObject { void start(); AOClient* getClient(QString ipid); + AOClient* getClientByID(int id); void updateCharsTaken(AreaData* area); void broadcast(AOPacket packet, int area_index); void broadcast(AOPacket packet); diff --git a/src/aoclient.cpp b/src/aoclient.cpp index 215d539..344eebf 100644 --- a/src/aoclient.cpp +++ b/src/aoclient.cpp @@ -17,11 +17,12 @@ ////////////////////////////////////////////////////////////////////////////////////// #include "include/aoclient.h" -AOClient::AOClient(Server* p_server, QTcpSocket* p_socket, QObject* parent) +AOClient::AOClient(Server* p_server, QTcpSocket* p_socket, QObject* parent, int user_id) : QObject(parent) { socket = p_socket; server = p_server; + id = user_id; joined = false; password = ""; current_area = 0; @@ -67,6 +68,11 @@ void AOClient::clientDisconnected() false; server->updateCharsTaken(server->areas[current_area]); } + for (AreaData* area : server->areas) { + area->owners.removeAll(id); + area->invited.removeAll(id); + } + arup(ARUPType::CM, true); } void AOClient::handlePacket(AOPacket packet) @@ -97,10 +103,11 @@ void AOClient::changeArea(int new_area) sendServerMessage("You are already in area " + server->area_names[current_area]); return; } - if (server->areas[new_area]->locked) { + if (server->areas[new_area]->locked == AreaData::LockStatus::LOCKED && !server->areas[new_area]->invited.contains(id)) { sendServerMessage("Area " + server->area_names[new_area] + " is locked."); return; } + if (current_char != "") { server->areas[current_area]->characters_taken[current_char] = false; @@ -123,7 +130,9 @@ void AOClient::changeArea(int new_area) server->areas[current_area]->characters_taken[current_char] = true; server->updateCharsTaken(server->areas[current_area]); } - sendServerMessage("You have been moved to area " + server->area_names[current_area]); + sendServerMessage("You moved to area " + server->area_names[current_area]); + if (server->areas[current_area]->locked == AreaData::LockStatus::SPECTATABLE) + sendServerMessage("Area " + server->area_names[current_area] + " is spectate-only; to chat IC you will need to be invited by the CM."); } void AOClient::handleCommand(QString command, int argc, QStringList argv) @@ -155,10 +164,33 @@ void AOClient::arup(ARUPType type, bool broadcast) arup_data.append(area->status); } else if (type == ARUPType::CM) { - arup_data.append(area->current_cm); + if (area->owners.isEmpty()) + arup_data.append("FREE"); + else { + QStringList area_owners; + for (int owner_id : area->owners) { + AOClient* owner = server->getClientByID(owner_id); + area_owners.append("[" + QString::number(owner->id) + "] " + owner->current_char); + } + arup_data.append(area_owners.join(", ")); + } } else if (type == ARUPType::LOCKED) { - arup_data.append(area->locked ? "LOCKED" : "FREE"); + QString lock_status; + switch (area->locked) { + case AreaData::LockStatus::FREE: + lock_status = "FREE"; + break; + case AreaData::LockStatus::LOCKED: + lock_status = "LOCKED"; + break; + case AreaData::LockStatus::SPECTATABLE: + lock_status = "SPECTATABLE"; + break; + default: + break; + } + arup_data.append(lock_status); } else return; } @@ -232,10 +264,14 @@ void AOClient::sendServerBroadcast(QString message) bool AOClient::checkAuth(unsigned long long acl_mask) { if (acl_mask != ACLFlags.value("NONE")) { - if (!authenticated) { + if (acl_mask == ACLFlags.value("CM")) { + AreaData* area = server->areas[current_area]; + if (area->owners.contains(id)) + return true; + } + else if (!authenticated) { return false; } - QSettings settings("config/config.ini", QSettings::IniFormat); settings.beginGroup("Options"); QString auth_type = settings.value("auth", "simple").toString(); diff --git a/src/area_data.cpp b/src/area_data.cpp index 63b57aa..a444c0a 100644 --- a/src/area_data.cpp +++ b/src/area_data.cpp @@ -24,20 +24,22 @@ AreaData::AreaData(QStringList characters, QString p_name, int p_index) for (QString cur_char : characters) { characters_taken.insert(cur_char, false); } - QSettings areas_ini("areas.ini", QSettings::IniFormat); + QSettings areas_ini("config/areas.ini", QSettings::IniFormat); areas_ini.beginGroup(p_name); background = areas_ini.value("background", "gs4").toString(); + is_protected = areas_ini.value("protected_area").toBool(); areas_ini.endGroup(); player_count = 0; - current_cm = "FREE"; - locked = false; + locked = FREE; status = "FREE"; def_hp = 10; pro_hp = 10; bg_locked = false; + document = "No document."; QSettings config_ini("config/config.ini", QSettings::IniFormat); config_ini.beginGroup("Options"); int log_size = config_ini.value("logbuffer", 50).toInt(); + config_ini.endGroup(); if (log_size == 0) log_size = 500; logger = new Logger(log_size, this); diff --git a/src/commands.cpp b/src/commands.cpp index adf2c60..253c94c 100644 --- a/src/commands.cpp +++ b/src/commands.cpp @@ -188,7 +188,7 @@ void AOClient::cmdSetBackground(int argc, QStringList argv) if (server->backgrounds.contains(argv[0])) { area->background = argv[0]; server->broadcast(AOPacket("BN", {argv[0]}), current_area); - server->broadcast(AOPacket("CT", {"Server", current_char + " changed the background to " + argv[0], "1"}), current_area); + sendServerMessageArea(current_char + " changed the background to " + argv[0]); } else { sendServerMessage("Invalid background name."); @@ -363,10 +363,6 @@ void AOClient::cmdLogout(int argc, QStringList argv) void AOClient::cmdPos(int argc, QStringList argv) { - if (argv[0] != "def" && argv[0] != "hld" && argv[0] != "pro" && argv[0] != "hlp" && argv[0] != "wit" && argv[0] != "jud" && argv[0] != "jur" && argv[0] != "sea") { - sendServerMessage("Invalid position!"); - return; - } pos = argv[0]; sendServerMessage("Position changed to " + pos + "."); } @@ -390,7 +386,7 @@ void AOClient::cmdNeed(int argc, QStringList argv) void AOClient::cmdFlip(int argc, QStringList argv) { QString sender_name = ooc_name; - QStringList faces = {"head","tails"}; + QStringList faces = {"heads","tails"}; QString face = faces[AOClient::genRand(0,1)]; sendServerMessage(sender_name + " flipped a coin and got " + face + "."); } @@ -402,22 +398,187 @@ void AOClient::cmdRoll(int argc, QStringList argv) void AOClient::cmdRollP(int argc, QStringList argv) { - diceThrower(argc, argv, RollType::ROLLP); } +void AOClient::cmdDoc(int argc, QStringList argv) +{ + QString sender_name = ooc_name; + AreaData* area = server->areas[current_area]; + if (argc == 0) { + sendServerMessage("Document: " + area->document); + } + else { + area->document = argv.join(" "); + sendServerMessageArea(sender_name + " changed the document."); + } +} + +void AOClient::cmdClearDoc(int argc, QStringList argv) +{ + QString sender_name = ooc_name; + AreaData* area = server->areas[current_area]; + area->document = "No document."; + sendServerMessageArea(sender_name + " cleared the document."); +} + +void AOClient::cmdCM(int argc, QStringList argv) +{ + QString sender_name = ooc_name; + AreaData* area = server->areas[current_area]; + if (area->is_protected) { + sendServerMessage("This area is protected, you may not become CM."); + return; + } + else if (area->owners.isEmpty()) { // no one owns this area, and it's not protected + area->owners.append(id); + area->invited.append(id); + sendServerMessageArea(sender_name + " is now CM in this area."); + arup(ARUPType::CM, true); + } + else if (!area->owners.contains(id)) { // there is already a CM, and it isn't us + sendServerMessage("You cannot become a CM in this area."); + } + else if (argc == 1) { // we are CM, and we want to make ID argv[0] also CM + bool ok; + AOClient* owner_candidate = server->getClientByID(argv[0].toInt(&ok)); + if (!ok) { + sendServerMessage("That doesn't look like a valid ID."); + return; + } + if (owner_candidate == nullptr) { + sendServerMessage("Unable to find client with ID " + argv[0] + "."); + return; + } + area->owners.append(owner_candidate->id); + sendServerMessageArea(owner_candidate->ooc_name + " is now CM in this area."); + arup(ARUPType::CM, true); + } + else { + sendServerMessage("You are already a CM in this area."); + } +} + +void AOClient::cmdUnCM(int argc, QStringList argv) +{ + AreaData* area = server->areas[current_area]; + int removed = area->owners.removeAll(id); + area->invited.removeAll(id); + sendServerMessage("You are no longer CM in this area."); + arup(ARUPType::CM, true); + if (area->owners.isEmpty()) + area->invited.clear(); +} + +void AOClient::cmdInvite(int argc, QStringList argv) +{ + AreaData* area = server->areas[current_area]; + bool ok; + int invited_id = argv[0].toInt(&ok); + if (!ok) { + sendServerMessage("That does not look like a valid ID."); + return; + } + else if (area->invited.contains(invited_id)) { + sendServerMessage("That ID is already on the invite list."); + return; + } + area->invited.append(invited_id); + sendServerMessage("You invited ID " + argv[0]); +} + +void AOClient::cmdUnInvite(int argc, QStringList argv) +{ + AreaData* area = server->areas[current_area]; + bool ok; + int uninvited_id = argv[0].toInt(&ok); + if (!ok) { + sendServerMessage("That does not look like a valid ID."); + return; + } + else if (area->owners.contains(uninvited_id)) { + sendServerMessage("You cannot uninvite a CM!"); + return; + } + else if (!area->invited.contains(uninvited_id)) { + sendServerMessage("That ID is not on the invite list."); + return; + } + area->invited.removeAll(uninvited_id); + sendServerMessage("You uninvited ID " + argv[0]); +} + +void AOClient::cmdLock(int argc, QStringList argv) +{ + AreaData* area = server->areas[current_area]; + if (area->locked == AreaData::LockStatus::LOCKED) { + sendServerMessage("This area is already locked."); + return; + } + sendServerMessageArea("This area is now locked."); + area->locked = AreaData::LockStatus::LOCKED; + for (AOClient* client : server->clients) { + if (client->current_area == current_area && client->joined) { + area->invited.append(client->id); + } + } + arup(ARUPType::LOCKED, true); +} + +void AOClient::cmdSpectatable(int argc, QStringList argv) +{ + AreaData* area = server->areas[current_area]; + if (area->locked == AreaData::LockStatus::SPECTATABLE) { + sendServerMessage("This area is already in spectate mode."); + return; + } + sendServerMessageArea("This area is now spectatable."); + area->locked = AreaData::LockStatus::SPECTATABLE; + for (AOClient* client : server->clients) { + if (client->current_area == current_area && client->joined) { + area->invited.append(client->id); + } + } + arup(ARUPType::LOCKED, true); +} + +void AOClient::cmdUnLock(int argc, QStringList argv) +{ + AreaData* area = server->areas[current_area]; + if (area->locked == AreaData::LockStatus::FREE) { + sendServerMessage("This area is not locked."); + return; + } + sendServerMessageArea("This area is now unlocked."); + area->locked = AreaData::LockStatus::FREE; + arup(ARUPType::LOCKED, true); +} + QStringList AOClient::buildAreaList(int area_idx) { QStringList entries; QString area_name = server->area_names[area_idx]; AreaData* area = server->areas[area_idx]; entries.append("=== " + area_name + " ==="); + switch (area->locked) { + case AreaData::LockStatus::LOCKED: + entries.append("[LOCKED]"); + break; + case AreaData::LockStatus::SPECTATABLE: + entries.append("[SPECTATABLE]"); + break; + case AreaData::LockStatus::FREE: + default: + break; + } entries.append("[" + QString::number(area->player_count) + " users][" + area->status + "]"); for (AOClient* client : server->clients) { if (client->current_area == area_idx && client->joined) { - QString char_entry = client->current_char; - if (char_entry == "") - char_entry = "Spectator"; + QString char_entry = "[" + QString::number(client->id) + "] " + client->current_char; + if (client->current_char == "") + char_entry += "Spectator"; + if (area->owners.contains(client->id)) + char_entry.insert(0, "[CM] "); if (authenticated) char_entry += " (" + client->getIpid() + "): " + client->ooc_name; entries.append(char_entry); diff --git a/src/logger.cpp b/src/logger.cpp index 3dd5cf9..9e33240 100644 --- a/src/logger.cpp +++ b/src/logger.cpp @@ -51,7 +51,7 @@ void Logger::logCmd(AOClient *client, AOPacket *packet, QString cmd, QStringList else if (cmd == "rootpass") { addEntry(buildEntry(client, "USERS", "Root password created")); } - else if (cmd == "adduser") { + else if (cmd == "adduser" && !args.isEmpty()) { addEntry(buildEntry(client, "USERS", "Added user " + args[0])); } else diff --git a/src/packets.cpp b/src/packets.cpp index 901952c..3bc7f30 100644 --- a/src/packets.cpp +++ b/src/packets.cpp @@ -141,7 +141,8 @@ void AOClient::pktSelectChar(AreaData* area, int argc, QStringList argv, AOPacke pos = ""; server->updateCharsTaken(area); - sendPacket("PV", {"271828", "CID", argv[1]}); + sendPacket("PV", {QString::number(id), "CID", argv[1]}); + fullArup(); } void AOClient::pktIcChat(AreaData* area, int argc, QStringList argv, AOPacket packet) @@ -317,6 +318,10 @@ AOPacket AOClient::validateIcPacket(AOPacket packet) if (current_char == "" || !joined) // Spectators cannot use IC return invalid; + AreaData* area = server->areas[current_area]; + if (area->locked == AreaData::LockStatus::SPECTATABLE && !area->invited.contains(id)) + // Non-invited players cannot speak in spectatable areas + return invalid; QList incoming_args; for (QString arg : packet.contents) { @@ -405,7 +410,6 @@ AOPacket AOClient::validateIcPacket(AOPacket packet) // evidence int evi_idx = incoming_args[11].toInt(); - AreaData* area = server->areas[current_area]; if (evi_idx > area->evidence.length()) return invalid; args.append(QString::number(evi_idx)); diff --git a/src/server.cpp b/src/server.cpp index b7b4408..e5d728e 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -82,17 +82,24 @@ void Server::start() for (int i = 0; i < area_names.length(); i++) { QString area_name = area_names[i]; areas.insert(i, new AreaData(characters, area_name, i)); - areas_ini.beginGroup(area_name); - // TODO: more area config - areas[i]->background = areas_ini.value("background", "gs4").toString(); - areas_ini.endGroup(); } } void Server::clientConnected() { QTcpSocket* socket = server->nextPendingConnection(); - AOClient* client = new AOClient(this, socket, this); + int user_id; + QList user_ids; + for (AOClient* client : clients) { + user_ids.append(client->id); + } + for (user_id = 0; user_id <= player_count; user_id++) { + if (user_ids.contains(user_id)) + continue; + else + break; + } + AOClient* client = new AOClient(this, socket, this, user_id); if (db_manager->isIPBanned(socket->peerAddress())) { AOPacket ban_reason("BD", {db_manager->getBanReason(socket->peerAddress())}); socket->write(ban_reason.toUtf8()); @@ -174,6 +181,15 @@ AOClient* Server::getClient(QString ipid) return nullptr; } +AOClient* Server::getClientByID(int id) +{ + for (AOClient* client : clients) { + if (client->id == id) + return client; + } + return nullptr; +} + Server::~Server() { for (AOClient* client : clients) {