/* GNU General Public License version 3 notice Copyright (C) 2012 Mihawk . All rights reserved. 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, 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see < http://www.gnu.org/licenses/ >. */ #include "Client.h" #include #include #include #include #include #include #include #include "App.h" #include "Settings.h" static QRegExp rxColoredPattern("&c[0-9a-fA-F]{3}"); static QRegExp rxBrackets("\\{[^}]*\\}"); // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Client::Client(App *app, ActiveClient* ac): QWClient(), myActiveClient(ac), myApp(app), myOnServerFlag(false), mySpamMutedFlag(false), myQWMutedFlag(false), myKeepNickTimer(new QTimer()), myFloodTimer(new QTimer()), myBroadcastFloodTimer(new QTimer()), mySPDetectionTimer(new QTimer()), mySPSupport(false), mySPAutoDetect(true), myMaxClients(0), myCmdScheduledTimer(new QTimer()) { rxBrackets.setMinimal(true); myKeepNickTimer->setSingleShot(true); myFloodTimer->setSingleShot(true); myBroadcastFloodTimer->setSingleShot(true); mySPDetectionTimer->setSingleShot(true); myCmdScheduledTimer->setSingleShot(true); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Client::~Client() { delete myKeepNickTimer; delete myFloodTimer; delete myBroadcastFloodTimer; delete mySPDetectionTimer; delete myCmdScheduledTimer; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void Client::connect(const char *host, quint16 port) { setPing(13); QWClient::connect(host, port); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void Client::say(const QString &msg, const QString &nickName) { QString cmd("say "); if(!nickName.isEmpty() && mySPSupport) cmd.append("s-p \"" + nickName + "\" "); cmd.append(msg); sendCmd(cmd.toLatin1().data()); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void Client::setTeam(const QString &msg) { sendCmd(QString("setinfo \"team\" \"" + msg + "\"").toLatin1().data()); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void Client::disconnect() { QWClient::disconnect(); myOnServerFlag = false; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void Client::print(const QString &msg) { QString str; str = "[" + QTime::currentTime().toString(Qt::ISODate) + "] " + QString(host()) + ":" + QString::number(port()) + "> " + msg; QByteArray b = str.toLatin1(); Client::stripColor(b.data()); str = QString::fromLatin1(b.data()); myApp->print(str); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void Client::setPlayerList(PlayerList &playerList) { myPlayerList = playerList; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void Client::onDisconnect() { print("Disconnected..\n"); myOnServerFlag = false; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool Client::isQWMuted() const { return myQWMutedFlag; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool Client::isSpamMuted() const { return mySpamMutedFlag; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void Client::parsePrintedLine() { // Deletes invalid map for redownloading next retry if(!myOnServerFlag) { QRegExp rx("^Map model file does not match \\((.+)\\),"); if(rx.indexIn(myPrintLine) != -1) { print("Invalid map, deleting this map for redownload on next retry.\n"); QFile::remove(quakeDir() + "/" + gameDir() + "/" + rx.cap(1)); return; } } // Detects whether s-p command is supported if(mySPDetectionTimer->isActive()) { if(myPrintLine.startsWith(Settings::globalInstance()->botName() + ": s-p")) { mySPSupport = false; mySPDetectionTimer->stop(); setAutoDetectSP(false); // Don't try detecting again on next map } else if(myPrintLine.startsWith("usage: s-p id/name txt")) { mySPSupport = true; mySPDetectionTimer->stop(); setAutoDetectSP(false); // Don't try detecting again on next map } } // Find the player that printed this line of text Player player, bestPlayer; quint16 lastMatchSize = 0; foreach(player, myPlayerList) { if(player.spectator && myPrintLine.startsWith("[SPEC] " + player.name)) { if(lastMatchSize < (player.name.size() + 7)) { lastMatchSize = (player.name.size() + 7); bestPlayer = player; } continue; } if(myPrintLine.startsWith(player.name)) { if(lastMatchSize < player.name.size()) { lastMatchSize = player.name.size(); bestPlayer = player; } continue; } } if(!lastMatchSize) return; QString nick(bestPlayer.name); if(bestPlayer.spectator) nick.prepend("(spec) "); QString message(myPrintLine.right(myPrintLine.size() - lastMatchSize)); QRegExp regex("^:\\s+\\.(spam|qw|help|qw_mute|qw_unmute|spam_mute|spam_unmute|lm|about|qw_local)\\s*(.+)$"); if(regex.indexIn(message) == -1) return; /* Flood prot */ QTime currentTime = QTime::currentTime(); int floodProtTime = Settings::globalInstance()->floodProtTime(); if(myFloodTimer->isActive()) { if(!myFloodMsgPrinted) { say("FloodProt: Not so fast, wait " + QString::number(floodProtTime + currentTime.secsTo(myFloodTimerStart)) + " sec(s).", bestPlayer.name); myFloodMsgPrinted = true; } return; } myFloodTimerStart = currentTime; myFloodTimer->start(floodProtTime*1000); myFloodMsgPrinted = false; QString command = regex.capturedTexts().at(1); QString args = regex.capturedTexts().at(2); if(command == "help") { say("Broadcast a message: .qw or .spam ", bestPlayer.name); say("Broadcast locally (only to the game servers this bot is connected to): .qw_local ", bestPlayer.name); say("(Un)Mute: .qw_mute .qw_unmute or .spam_mute .spam_unmute", bestPlayer.name); say("Last 5 messages: .lm", bestPlayer.name); say("About this bot: .about", bestPlayer.name); return; } if(command == "about") { say("ServeMe specbots by mihawk & Haudraufwienix, find us on #qwnet @ irc.quakenet.org", bestPlayer.name); return; } if(command == "qw" || command == "spam" || command == "qw_local") { if(!args.trimmed().size()) { say("The format is ." + command + " .", bestPlayer.name); return; } /* Floodprot for broadcasting commands */ if(command == "qw" || command == "spam" || command == "qw_local") { if(myBroadcastFloodTimer->isActive()) { say("FloodProt: Wait " + QString::number(myBroadcastFloodTimer->interval()/1000 + currentTime.secsTo(myBroadcastFloodTimerStart)) + " sec(s) before issuing a new " + command, bestPlayer.name); return; } int time = 0; if(command == "qw") time = Settings::globalInstance()->qwFloodProtTime(); else if(command == "spam") time = Settings::globalInstance()->spamFloodProtTime(); else if(command == "qw_local") time = 30; myBroadcastFloodTimerStart = currentTime; myBroadcastFloodTimer->start(time*1000); } // Prepare all strings to be broadcasted QString server(QString(host()) + ":" + QString::number(port())); // Tries to find the hostname for this ip in the cache QString resolvedServer(myApp->serverHostName(server)); if(!resolvedServer.isEmpty()) resolvedServer.append(":" + QString::number(port())); else resolvedServer = server; // Internal message to be broadcast within our network of bots (don't need to have namefun parsed) // The internal message uses the resolvedServer, because this message doesn't pass tru central, thus // the server ip isn't replaced by a hostname automatically QString internalMessage("-" + command + "- " + nick + " - " + resolvedServer + " " + QString::number(playerCount()) + "/" + QString::number(myMaxClients) + " : " + args.trimmed()); myApp->broadcast(internalMessage, myActiveClient); // If this is a local message don't broadcast externally if (command == "qw_local") { say("Broadcasting within qw servers only..." , bestPlayer.name); return; } // Message that will be broadcast to other networks (needs namefun parsing) // this message passes tru central, thus the server ip will be replaced by a hostname // automatically nick = parseNameFun(nick); QString parsedMsg = parseNameFun(args.trimmed()); server.append(" " + QString::number(playerCount()) + "/" + QString::number(myMaxClients)); // append player count to the server (central needs another parm for this!) if(!Settings::globalInstance()->developerMode()) myApp->requestBroadcast(command, nick, server, parsedMsg); else myApp->requestBroadcast("dev", nick, server, parsedMsg); say("Broadcasting...", bestPlayer.name); return; } if(command == "qw_mute") { say("Qw frequency muted.", bestPlayer.name); myQWMutedFlag = true; return; } if(command == "qw_unmute") { say("Qw frequency unmuted.", bestPlayer.name); myQWMutedFlag = false; return; } if(command == "spam_mute") { say("Spam frequency muted.", bestPlayer.name); mySpamMutedFlag = true; return; } if(command == "spam_unmute") { say("Spam frequency unmuted.", bestPlayer.name); mySpamMutedFlag = false; return; } if(command == "lm") { QStringList messages = myApp->lastMessages(); if(!messages.size()) { say("None", bestPlayer.name); return; } QString msg; int i = 0; foreach(msg, messages) { if(++i > 5) break; say(msg, bestPlayer.name); } return; } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - int Client::playerCount() const { Player c; int pc = 0; foreach(c, myPlayerList) { if(c.spectator) continue; pc++; } return pc; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void Client::setMaxClients(int maxClients) { myMaxClients = maxClients; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - int Client::maxClients() const { return myMaxClients; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void Client::onPrint(int, const char *msg) { if(!strlen(msg)) return; QString text(msg); if(text.endsWith('\n')) { myPrintLine.append(text); parsePrintedLine(); myPrintLine.clear(); } else { myPrintLine.append(text); } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool Client::isOnServer() const { return myOnServerFlag; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void Client::onError(const char *description) { QString desc(description); if(desc == "Client Timed Out.") { print("Error (" + QString(description) + ")\n"); } else { print("Error (" + QString(description) + ")\n"); } myOnServerFlag = false; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void Client::onLevelChanged(int, const char *levelName, float, float, float, float, float, float, float, float, float, float) { print(QString(levelName) + "\n"); myDownloadProgressPrintedFlag = false; mySpamMutedFlag = false; myQWMutedFlag = false; setPing(13); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void Client::onChallenge() { print("challenge\n"); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void Client::onConnection() { print("connection\n"); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void Client::onConnected() { print("connected\n"); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void Client::onDownloadStarted(const char *fileName) { print("Download started " + QString(fileName) + "\n"); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void Client::run() { // Keep nick... Simply set name again after 30 secs if(!myKeepNickTimer->isActive()) { setName(Settings::globalInstance()->botName().toLatin1().data()); myKeepNickTimer->start(30000); } // Scheduled commands if(!myCmdScheduled.isEmpty() && !myCmdScheduledTimer->isActive()) { sendCmd(myCmdScheduled.toLatin1().data()); myCmdScheduled.clear(); } QWClient::run(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void Client::onOOBPrint(const char *msg) { print(QString(msg)); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void Client::onStuffedCmd(const char *cmd) { QString strCmd(cmd); if(strCmd == "skins") //connection sequence complete { myOnServerFlag = true; setPing(Settings::globalInstance()->botPing()); if(mySPAutoDetect) spDetection(); } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void Client::onDownloadProgress(int percent) { if(!(percent % 10)) { if(!myDownloadProgressPrintedFlag) { print("Download " + QString::number(percent) + "%\n"); myDownloadProgressPrintedFlag = true; } } else { myDownloadProgressPrintedFlag = false; } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void Client::onDownloadFinished() { print("Download 100% finished.\n"); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void Client::setAutoDetectSP(bool autoDetect) { mySPAutoDetect = autoDetect; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void Client::spDetection() { say("s-p"); mySPDetectionTimer->start(2000); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - QString Client::parseNameFun(const QString &string) { QByteArray b(string.toLatin1()); // Remove normal QW colored text QWClient::stripColor(b.data()); // Remove new QW colored text QString text = QString::fromLatin1(b); int pos = 0; int index_adj; while ((pos = rxBrackets.indexIn(text, pos)) != -1) { QString match = rxBrackets.cap(0); QString newtext = rxBrackets.cap(0).replace(rxColoredPattern, ""); newtext = newtext.mid(1, newtext.size()-2); index_adj = match.length() - newtext.length(); text.replace(match, newtext); pos += rxBrackets.matchedLength() - index_adj; } return text; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void Client::scheduleCmd(const QString &cmd, int time) { myCmdScheduled = cmd; myCmdScheduledTimer->start(time); }