From bd219c14723c7de3a257d68c9947918723c1ba5a Mon Sep 17 00:00:00 2001 From: Flerp Date: Sun, 19 Apr 2026 11:53:30 -0700 Subject: [PATCH] Tons of crap --- .gitignore | 2 + mainwindow.cpp | 793 ++++++++++++++++++++++++++++++++++++++++++++----- mainwindow.h | 75 ++++- 3 files changed, 795 insertions(+), 75 deletions(-) diff --git a/.gitignore b/.gitignore index 567609b..503ba50 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ build/ +build-codex/ +.codex \ No newline at end of file diff --git a/mainwindow.cpp b/mainwindow.cpp index 679dec6..7e69174 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -1,12 +1,29 @@ #include "mainwindow.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include +#include +#include +#include +#include MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), socket(new QTcpSocket(this)) { setupUI(); + updateConnectionStatus(); + loadSavedServers(); statusTimer = new QTimer(this); + lockedTimeTimer = new QTimer(this); connect(statusTimer, &QTimer::timeout, this, &MainWindow::updateConnectionStatus); + connect(lockedTimeTimer, &QTimer::timeout, this, &MainWindow::resendLockedServerTime); statusTimer->start(1000); connect(socket, &QTcpSocket::readyRead, this, &MainWindow::readServerOutput); } @@ -36,6 +53,25 @@ void MainWindow::readServerOutput() { QString text = QString::fromUtf8(response); serverLogTextEdit->append(text); + if (awaitingUsersResponse) { + usersResponseBuffer += text; + if (usersResponseBuffer.contains(QRegularExpression(R"(Total of \d+ in the game)"))) { + parseUsersResponse(usersResponseBuffer); + usersResponseBuffer.clear(); + awaitingUsersResponse = false; + } + } + + if (awaitingBansResponse) { + bansResponseBuffer += text; + if (bansResponseBuffer.contains("Ban list entries:") && + bansResponseBuffer.contains(QRegularExpression(R"(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} - )"))) { + parseBansResponse(bansResponseBuffer); + bansResponseBuffer.clear(); + awaitingBansResponse = false; + } + } + if (text.contains("Please enter password:", Qt::CaseInsensitive)) { socket->write(passwordEdit->text().toUtf8() + "\n"); socket->waitForBytesWritten(1000); @@ -47,26 +83,485 @@ void MainWindow::readServerOutput() { } } -void MainWindow::sendTelnetCommand(const QString &command) { +bool MainWindow::sendTelnetCommand(const QString &command) { if (socket->state() != QAbstractSocket::ConnectedState) { QMessageBox::warning(this, "Not Connected", "Please connect to the server first."); - return; + return false; } if (!isAuthenticated) { QMessageBox::warning(this, "Not Authenticated", "Server is waiting for a password. Please wait or reconnect."); - return; + return false; } socket->write(command.toUtf8() + "\n"); socket->waitForBytesWritten(1000); - historyTextEdit->append(command); + const QString timestamp = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss"); + historyTextEdit->append(QString("[%1] %2").arg(timestamp, command)); + return true; +} + +QString MainWindow::serverConfigFilePath() const { + const QString configRoot = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); + return QDir(configRoot).filePath("7d2d-commandutil/servers.xml"); +} + +void MainWindow::loadSavedServers() { + savedServers.clear(); + savedServersCombo->clear(); + savedServersCombo->addItem("Select Server"); + savedServersCombo->setCurrentIndex(0); + + QFile file(serverConfigFilePath()); + if (!file.exists()) { + return; + } + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + QMessageBox::warning(this, "Load Failed", "Could not open saved servers file."); + return; + } + + QXmlStreamReader xml(&file); + while (!xml.atEnd()) { + xml.readNext(); + if (!xml.isStartElement() || xml.name() != "server") { + continue; + } + + SavedServerEntry entry; + entry.name = xml.attributes().value("name").toString(); + + while (!(xml.isEndElement() && xml.name() == "server") && !xml.atEnd()) { + xml.readNext(); + if (!xml.isStartElement()) { + continue; + } + + if (xml.name() == "host") { + entry.server = xml.readElementText(); + } else if (xml.name() == "port") { + entry.port = xml.readElementText(); + } else if (xml.name() == "password") { + entry.password = xml.readElementText(); + } + } + + if (!entry.name.isEmpty()) { + savedServers.append(entry); + } + } + + if (xml.hasError()) { + QMessageBox::warning(this, "Load Failed", "The saved servers file is not valid XML."); + return; + } + + std::sort(savedServers.begin(), savedServers.end(), [](const SavedServerEntry &left, const SavedServerEntry &right) { + return QString::compare(left.name, right.name, Qt::CaseInsensitive) < 0; + }); + + for (const SavedServerEntry &entry : savedServers) { + savedServersCombo->addItem(entry.name); + } +} + +void MainWindow::parseUsersResponse(const QString &text) { + static const QRegularExpression userLinePattern( + R"(^\d+\.\s+id=([^,]+),\s+([^,]+),\s+pos=\(([^)]*)\),\s+rot=\(([^)]*)\),\s+remote=([^,]+),\s+health=([^,]+),\s+deaths=([^,]+),\s+zombies=([^,]+),\s+players=([^,]+),\s+score=([^,]+),\s+level=([^,]+),\s+pltfmid=([^,]+),\s+crossid=([^,]+),\s+ip=([^,]+),\s+ping=([^,\s]+)\s*$)" + ); + + users.clear(); + usersListWidget->clear(); + clearUserInfo(); + + const QStringList lines = text.split(QRegularExpression(R"(\r?\n)"), Qt::SkipEmptyParts); + for (const QString &line : lines) { + const QRegularExpressionMatch match = userLinePattern.match(line.trimmed()); + if (!match.hasMatch()) { + continue; + } + + UserEntry entry{ + match.captured(1).trimmed(), + match.captured(2).trimmed(), + match.captured(3).trimmed(), + match.captured(4).trimmed(), + match.captured(5).trimmed(), + match.captured(6).trimmed(), + match.captured(7).trimmed(), + match.captured(8).trimmed(), + match.captured(9).trimmed(), + match.captured(10).trimmed(), + match.captured(11).trimmed(), + match.captured(12).trimmed(), + match.captured(13).trimmed(), + match.captured(14).trimmed(), + match.captured(15).trimmed() + }; + users.append(entry); + usersListWidget->addItem(entry.username); + } +} + +void MainWindow::clearUserInfo() { + for (QLabel *label : userInfoValueLabels) { + label->clear(); + } +} + +void MainWindow::clearBanInfo() { + for (QLabel *label : banInfoValueLabels) { + label->clear(); + } +} + +void MainWindow::refreshUsers() { + users.clear(); + usersListWidget->clear(); + usersResponseBuffer.clear(); + clearUserInfo(); + + if (!sendTelnetCommand("lp")) { + return; + } + + awaitingUsersResponse = true; +} + +void MainWindow::displaySelectedUserInfo(int row) { + if (row < 0 || row >= users.size()) { + clearUserInfo(); + return; + } + + const UserEntry &entry = users.at(row); + userInfoValueLabels.value("Player ID")->setText(entry.playerId); + userInfoValueLabels.value("Username")->setText(entry.username); + userInfoValueLabels.value("Position")->setText(entry.position); + userInfoValueLabels.value("Rotation")->setText(entry.rotation); + userInfoValueLabels.value("Remote")->setText(entry.remoteState); + userInfoValueLabels.value("Health")->setText(entry.health); + userInfoValueLabels.value("Deaths")->setText(entry.deaths); + userInfoValueLabels.value("Zombies")->setText(entry.zombies); + userInfoValueLabels.value("Players")->setText(entry.players); + userInfoValueLabels.value("Score")->setText(entry.score); + userInfoValueLabels.value("Level")->setText(entry.level); + userInfoValueLabels.value("Platform ID")->setText(entry.platformId); + userInfoValueLabels.value("EOS ID")->setText(entry.eosId); + userInfoValueLabels.value("IP Address")->setText(entry.ipAddress); + userInfoValueLabels.value("Ping")->setText(entry.ping); +} + +void MainWindow::parseBansResponse(const QString &text) { + static const QRegularExpression banLinePattern( + R"(^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}) - ([^(]+) \(([^)]*)\) - (.*)$)" + ); + + bans.clear(); + bansListWidget->clear(); + clearBanInfo(); + + const QStringList lines = text.split(QRegularExpression(R"(\r?\n)"), Qt::SkipEmptyParts); + for (const QString &line : lines) { + const QRegularExpressionMatch match = banLinePattern.match(line.trimmed()); + if (!match.hasMatch()) { + continue; + } + + BanEntry entry{ + match.captured(1).trimmed(), + match.captured(2).trimmed(), + match.captured(3).trimmed(), + match.captured(4).trimmed() + }; + bans.append(entry); + bansListWidget->addItem(entry.username); + } +} + +void MainWindow::refreshBans() { + bans.clear(); + bansListWidget->clear(); + bansResponseBuffer.clear(); + clearBanInfo(); + + if (!sendTelnetCommand("ban list")) { + return; + } + + awaitingBansResponse = true; +} + +void MainWindow::displaySelectedBanInfo(int row) { + if (row < 0 || row >= bans.size()) { + clearBanInfo(); + return; + } + + const BanEntry &entry = bans.at(row); + banInfoValueLabels.value("Banned Until")->setText(entry.bannedUntil); + banInfoValueLabels.value("Ban Reason")->setText(entry.reason); +} + +void MainWindow::removeSelectedBan() { + const int row = bansListWidget->currentRow(); + if (row < 0 || row >= bans.size()) { + QMessageBox::warning(this, "No Ban Selected", "Please select a banned user first."); + return; + } + + sendTelnetCommand(QString("ban remove %1").arg(bans.at(row).userId)); +} + +void MainWindow::kickSelectedUser() { + const int row = usersListWidget->currentRow(); + if (row < 0 || row >= users.size()) { + QMessageBox::warning(this, "No User Selected", "Please select a user first."); + return; + } + + bool ok = false; + const QString reason = QInputDialog::getText( + this, + "Kick User", + "Reason?", + QLineEdit::Normal, + QString(), + &ok + ); + + if (!ok) { + return; + } + + const QString trimmedReason = reason.trimmed(); + const QString playerId = users.at(row).playerId; + QString escapedReason = trimmedReason; + const QString command = trimmedReason.isEmpty() + ? QString("kick %1").arg(playerId) + : QString("kick %1 \"%2\"").arg(playerId, escapedReason.replace("\"", "\\\"")); + sendTelnetCommand(command); +} + +void MainWindow::banSelectedUser() { + const int row = usersListWidget->currentRow(); + if (row < 0 || row >= users.size()) { + QMessageBox::warning(this, "No User Selected", "Please select a user first."); + return; + } + + QDialog dialog(this); + dialog.setWindowTitle("Ban User"); + + QVBoxLayout *dialogLayout = new QVBoxLayout(&dialog); + + QHBoxLayout *durationLayout = new QHBoxLayout(); + QLineEdit *durationEdit = new QLineEdit(); + durationEdit->setValidator(new QIntValidator(0, 999999999, durationEdit)); + QComboBox *durationUnitCombo = new QComboBox(); + durationUnitCombo->addItems({"minutes", "hours", "days", "weeks", "months", "years"}); + durationLayout->addWidget(durationEdit); + durationLayout->addWidget(durationUnitCombo); + dialogLayout->addLayout(durationLayout); + + QHBoxLayout *reasonLayout = new QHBoxLayout(); + reasonLayout->addWidget(new QLabel("Reason")); + QLineEdit *reasonEdit = new QLineEdit(); + QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + reasonLayout->addWidget(reasonEdit); + reasonLayout->addWidget(buttonBox); + dialogLayout->addLayout(reasonLayout); + + connect(buttonBox, &QDialogButtonBox::accepted, &dialog, &QDialog::accept); + connect(buttonBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); + + if (dialog.exec() != QDialog::Accepted) { + return; + } + + const QString duration = durationEdit->text().trimmed(); + if (duration.isEmpty()) { + QMessageBox::warning(this, "Invalid Duration", "Please enter a duration."); + return; + } + + const QString playerId = users.at(row).playerId; + QString escapedReason = reasonEdit->text().trimmed(); + const QString command = QString("ban add %1 %2 %3 \"%4\"") + .arg(playerId, duration, durationUnitCombo->currentText(), escapedReason.replace("\"", "\\\"")); + sendTelnetCommand(command); +} + +bool MainWindow::writeSavedServers() const { + const QString configFile = serverConfigFilePath(); + QDir configDir = QFileInfo(configFile).dir(); + if (!configDir.exists() && !configDir.mkpath(".")) { + return false; + } + + QFile file(configFile); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) { + return false; + } + + QXmlStreamWriter xml(&file); + xml.setAutoFormatting(true); + xml.writeStartDocument(); + xml.writeStartElement("servers"); + + for (const SavedServerEntry &entry : savedServers) { + xml.writeStartElement("server"); + xml.writeAttribute("name", entry.name); + xml.writeTextElement("host", entry.server); + xml.writeTextElement("port", entry.port); + xml.writeTextElement("password", entry.password); + xml.writeEndElement(); + } + + xml.writeEndElement(); + xml.writeEndDocument(); + return !xml.hasError(); +} + +void MainWindow::saveServerEntry() { + const QString server = serverEdit->text().trimmed(); + const QString port = portEdit->text().trimmed(); + + if (server.isEmpty() || port.isEmpty()) { + QMessageBox::warning(this, "Missing Information", "Server and port must be filled in before saving."); + return; + } + + bool ok = false; + const QString name = QInputDialog::getText( + this, + "Save Server", + "Server name:", + QLineEdit::Normal, + QString(), + &ok + ).trimmed(); + + if (!ok || name.isEmpty()) { + return; + } + + SavedServerEntry entry{name, server, port, passwordEdit->text()}; + bool replaced = false; + for (SavedServerEntry &savedEntry : savedServers) { + if (savedEntry.name == name) { + savedEntry = entry; + replaced = true; + break; + } + } + + if (!replaced) { + savedServers.append(entry); + } + + if (!writeSavedServers()) { + QMessageBox::warning(this, "Save Failed", "Could not write the saved servers file."); + return; + } + + loadSavedServers(); +} + +void MainWindow::loadSelectedServer() { + const int index = savedServersCombo->currentIndex() - 1; + if (index < 0 || index >= savedServers.size()) { + QMessageBox::warning(this, "Load Failed", "Please select a saved server first."); + return; + } + + const SavedServerEntry &entry = savedServers.at(index); + serverEdit->setText(entry.server); + portEdit->setText(entry.port); + passwordEdit->setText(entry.password); +} + +void MainWindow::deleteSelectedServer() { + const int index = savedServersCombo->currentIndex() - 1; + if (index < 0 || index >= savedServers.size()) { + QMessageBox::warning(this, "Delete Failed", "Please select a saved server first."); + return; + } + + savedServers.removeAt(index); + if (!writeSavedServers()) { + QMessageBox::warning(this, "Delete Failed", "Could not update the saved servers file."); + return; + } + + loadSavedServers(); +} + +bool MainWindow::sendValidatedTimeCommand() { + bool dayOk = false; + bool hourOk = false; + bool minuteOk = false; + const int day = dayEdit->text().toInt(&dayOk); + const int hour = hourEdit->text().toInt(&hourOk); + const int minute = minuteEdit->text().toInt(&minuteOk); + + if (!dayOk || !hourOk || !minuteOk || day < 0 || day > 9999 || hour < 0 || hour > 23 || minute < 0 || minute > 59) { + QMessageBox::warning(this, "Invalid Time", "Invalid Time"); + return false; + } + + QString cmd = QString("st %1 %2 %3") + .arg(day) + .arg(hour) + .arg(minute); + return sendTelnetCommand(cmd); } void MainWindow::sendTimeCommand() { - QString cmd = QString("st %1 %2 %3") - .arg(dayEdit->text()) - .arg(hourEdit->text()) - .arg(minuteEdit->text()); - sendTelnetCommand(cmd); + sendValidatedTimeCommand(); +} + +void MainWindow::updateServerTimeLockState(bool locked) { + isServerTimeLocked = locked; + dayEdit->setEnabled(!locked); + hourEdit->setEnabled(!locked); + minuteEdit->setEnabled(!locked); + + QPalette palette = serverTimeStatusLabel->palette(); + if (locked) { + serverTimeStatusLabel->setText("Server Time Locked"); + serverTimeLockButton->setText("Unlock Server Time"); + palette.setColor(QPalette::WindowText, Qt::red); + } else { + serverTimeStatusLabel->setText("Server Time Unlocked"); + serverTimeLockButton->setText("Lock Server Time"); + palette.setColor(QPalette::WindowText, Qt::darkGreen); + } + serverTimeStatusLabel->setPalette(palette); +} + +void MainWindow::lockServerTime() { + if (!sendValidatedTimeCommand()) { + return; + } + + lockedTimeTimer->start(5000); + updateServerTimeLockState(true); +} + +void MainWindow::unlockServerTime() { + if (!isServerTimeLocked) { + return; + } + + lockedTimeTimer->stop(); + updateServerTimeLockState(false); +} + +void MainWindow::resendLockedServerTime() { + if (!isServerTimeLocked) { + return; + } + + sendValidatedTimeCommand(); } void MainWindow::sendSpeedCommand() { @@ -90,14 +585,18 @@ void MainWindow::sendCustomCommand() { void MainWindow::updateConnectionStatus() { QPalette palette; + QString connectionStatus; if (socket->state() == QAbstractSocket::ConnectedState) { - connectionStatusLabel->setText("Connected"); + connectionStatus = "Connected"; + connectionStatusLabel->setText(connectionStatus); palette.setColor(QPalette::WindowText, Qt::darkGreen); } else { - connectionStatusLabel->setText("Disconnected"); + connectionStatus = "Disconnected"; + connectionStatusLabel->setText(connectionStatus); palette.setColor(QPalette::WindowText, Qt::red); } connectionStatusLabel->setPalette(palette); + setWindowTitle(QString("7D2D-CommandUtil - %1").arg(connectionStatus)); } void MainWindow::disconnectFromServer() { @@ -122,6 +621,186 @@ void MainWindow::setupUI() { QWidget *mainTab = new QWidget; QVBoxLayout *mainLayout = new QVBoxLayout(mainTab); + QIntValidator *dayValidator = new QIntValidator(0, 9999, this); + QIntValidator *hourValidator = new QIntValidator(0, 23, this); + QIntValidator *minuteValidator = new QIntValidator(0, 59, this); + + QGroupBox *timeBox = new QGroupBox("Set Server Time"); + QVBoxLayout *timeBoxLayout = new QVBoxLayout(timeBox); + serverTimeStatusLabel = new QLabel("Server Time Unlocked"); + serverTimeStatusLabel->setAlignment(Qt::AlignCenter); + timeBoxLayout->addWidget(serverTimeStatusLabel); + + QHBoxLayout *timeLayout = new QHBoxLayout(); + dayEdit = new QLineEdit(); dayEdit->setMaxLength(4); dayEdit->setValidator(dayValidator); + hourEdit = new QLineEdit(); hourEdit->setMaxLength(2); hourEdit->setValidator(hourValidator); + minuteEdit = new QLineEdit(); minuteEdit->setMaxLength(2); minuteEdit->setValidator(minuteValidator); + QPushButton *timeButton = new QPushButton("Set Server Time"); + connect(timeButton, &QPushButton::clicked, this, &MainWindow::sendTimeCommand); + timeLayout->addWidget(new QLabel("Day:")); + timeLayout->addWidget(dayEdit); + timeLayout->addWidget(new QLabel("Hour:")); + timeLayout->addWidget(hourEdit); + timeLayout->addWidget(new QLabel("Minute:")); + timeLayout->addWidget(minuteEdit); + timeLayout->addStretch(); + timeLayout->addWidget(timeButton); + timeBoxLayout->addLayout(timeLayout); + + QHBoxLayout *timeLockButtonLayout = new QHBoxLayout(); + serverTimeLockButton = new QPushButton("Lock Server Time"); + connect(serverTimeLockButton, &QPushButton::clicked, this, [this]() { + if (isServerTimeLocked) { + unlockServerTime(); + } else { + lockServerTime(); + } + }); + timeLockButtonLayout->addStretch(); + timeLockButtonLayout->addWidget(serverTimeLockButton); + timeLockButtonLayout->addStretch(); + timeBoxLayout->addLayout(timeLockButtonLayout); + mainLayout->addWidget(timeBox); + updateServerTimeLockState(false); + + QGroupBox *speedBox = new QGroupBox("Set Game Speed"); + QVBoxLayout *speedLayout = new QVBoxLayout(speedBox); + QHBoxLayout *sliderLayout = new QHBoxLayout(); + speedSlider = new QSlider(Qt::Horizontal); + speedSlider->setMinimum(1); + speedSlider->setMaximum(5); + speedSlider->setTickInterval(1); + speedSlider->setTickPosition(QSlider::TicksBelow); + connect(speedSlider, &QSlider::valueChanged, this, &MainWindow::updateSliderLabel); + sliderLayout->addWidget(speedSlider); + QPushButton *speedButton = new QPushButton("Set Time Speed"); + connect(speedButton, &QPushButton::clicked, this, &MainWindow::sendSpeedCommand); + sliderLayout->addWidget(speedButton); + sliderLabel = new QLabel("60"); + sliderLabel->setAlignment(Qt::AlignCenter); + speedLayout->addLayout(sliderLayout); + speedLayout->addWidget(sliderLabel); + mainLayout->addWidget(speedBox); + + tabWidget->addTab(mainTab, "Time"); + + QWidget *usersTab = new QWidget; + QHBoxLayout *usersTabLayout = new QHBoxLayout(usersTab); + QVBoxLayout *usersLeftLayout = new QVBoxLayout(); + + QGroupBox *usersBox = new QGroupBox("Users"); + QVBoxLayout *usersBoxLayout = new QVBoxLayout(usersBox); + usersListWidget = new QListWidget(); + QPushButton *refreshUsersButton = new QPushButton("Refresh"); + connect(usersListWidget, &QListWidget::currentRowChanged, this, &MainWindow::displaySelectedUserInfo); + connect(refreshUsersButton, &QPushButton::clicked, this, &MainWindow::refreshUsers); + usersBoxLayout->addWidget(usersListWidget); + usersBoxLayout->addWidget(refreshUsersButton); + usersLeftLayout->addWidget(usersBox); + + QGroupBox *actionsBox = new QGroupBox("Actions"); + QVBoxLayout *actionsBoxLayout = new QVBoxLayout(actionsBox); + QPushButton *kickUserButton = new QPushButton("Kick"); + QPushButton *banUserButton = new QPushButton("Ban"); + connect(kickUserButton, &QPushButton::clicked, this, &MainWindow::kickSelectedUser); + connect(banUserButton, &QPushButton::clicked, this, &MainWindow::banSelectedUser); + actionsBoxLayout->addWidget(kickUserButton); + actionsBoxLayout->addWidget(banUserButton); + actionsBoxLayout->addStretch(); + usersLeftLayout->addWidget(actionsBox); + + QGroupBox *userInfoBox = new QGroupBox("User Info"); + QFormLayout *userInfoBoxLayout = new QFormLayout(userInfoBox); + const QStringList userInfoFields = { + "Player ID", "Username", "Position", "Rotation", "Remote", + "Health", "Deaths", "Zombies", "Players", "Score", + "Level", "Platform ID", "EOS ID", "IP Address", "Ping" + }; + for (const QString &fieldName : userInfoFields) { + QLabel *valueLabel = new QLabel(); + valueLabel->setWordWrap(true); + valueLabel->setTextInteractionFlags(Qt::TextSelectableByMouse); + valueLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + userInfoValueLabels.insert(fieldName, valueLabel); + userInfoBoxLayout->addRow(fieldName + ":", valueLabel); + } + clearUserInfo(); + + usersTabLayout->addLayout(usersLeftLayout, 1); + usersTabLayout->addWidget(userInfoBox, 1); + tabWidget->addTab(usersTab, "Users"); + + QWidget *bansTab = new QWidget; + QHBoxLayout *bansTabLayout = new QHBoxLayout(bansTab); + QVBoxLayout *bansLeftLayout = new QVBoxLayout(); + + QGroupBox *bansBox = new QGroupBox("Bans"); + QVBoxLayout *bansBoxLayout = new QVBoxLayout(bansBox); + bansListWidget = new QListWidget(); + QPushButton *refreshBansButton = new QPushButton("Refresh"); + connect(bansListWidget, &QListWidget::currentRowChanged, this, &MainWindow::displaySelectedBanInfo); + connect(refreshBansButton, &QPushButton::clicked, this, &MainWindow::refreshBans); + bansBoxLayout->addWidget(bansListWidget); + bansBoxLayout->addWidget(refreshBansButton); + bansLeftLayout->addWidget(bansBox); + + QGroupBox *banActionsBox = new QGroupBox("Actions"); + QVBoxLayout *banActionsBoxLayout = new QVBoxLayout(banActionsBox); + QPushButton *removeBanButton = new QPushButton("Remove"); + connect(removeBanButton, &QPushButton::clicked, this, &MainWindow::removeSelectedBan); + banActionsBoxLayout->addWidget(removeBanButton); + banActionsBoxLayout->addStretch(); + bansLeftLayout->addWidget(banActionsBox); + + QGroupBox *banInfoBox = new QGroupBox("Ban Info"); + QFormLayout *banInfoBoxLayout = new QFormLayout(banInfoBox); + const QStringList banInfoFields = {"Banned Until", "Ban Reason"}; + for (const QString &fieldName : banInfoFields) { + QLabel *valueLabel = new QLabel(); + valueLabel->setWordWrap(true); + valueLabel->setTextInteractionFlags(Qt::TextSelectableByMouse); + valueLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + banInfoValueLabels.insert(fieldName, valueLabel); + banInfoBoxLayout->addRow(fieldName + ":", valueLabel); + } + clearBanInfo(); + + bansTabLayout->addLayout(bansLeftLayout, 1); + bansTabLayout->addWidget(banInfoBox, 1); + tabWidget->addTab(bansTab, "Bans"); + + QWidget *worldTab = new QWidget; + tabWidget->addTab(worldTab, "World"); + + QWidget *historyTab = new QWidget; + QVBoxLayout *historyLayout = new QVBoxLayout(historyTab); + historyTextEdit = new QTextEdit(); + historyTextEdit->setReadOnly(true); + historyTextEdit->setTextInteractionFlags(Qt::TextSelectableByMouse); + historyLayout->addWidget(historyTextEdit); + tabWidget->addTab(historyTab, "History"); + + QWidget *logTab = new QWidget; + QVBoxLayout *logLayout = new QVBoxLayout(logTab); + serverLogTextEdit = new QTextEdit(); + serverLogTextEdit->setReadOnly(true); + logLayout->addWidget(serverLogTextEdit); + + QGroupBox *customBox = new QGroupBox(); + QHBoxLayout *customLayout = new QHBoxLayout(customBox); + customCommandEdit = new QLineEdit(); + QPushButton *customButton = new QPushButton("Send Command"); + connect(customButton, &QPushButton::clicked, this, &MainWindow::sendCustomCommand); + connect(customCommandEdit, &QLineEdit::returnPressed, this, &MainWindow::sendCustomCommand); + customLayout->addWidget(customCommandEdit); + customLayout->addWidget(customButton); + logLayout->addWidget(customBox); + + tabWidget->addTab(logTab, "Console"); + + QWidget *connectionTab = new QWidget; + QVBoxLayout *connectionLayout = new QVBoxLayout(connectionTab); + QGroupBox *serverBox = new QGroupBox("Server Info"); QVBoxLayout *serverVBox = new QVBoxLayout(serverBox); QHBoxLayout *serverLayout = new QHBoxLayout(); @@ -151,71 +830,37 @@ void MainWindow::setupUI() { statusLayout->addWidget(connectButton); statusLayout->addWidget(disconnectButton); serverVBox->addLayout(statusLayout); - mainLayout->addWidget(serverBox); + connectionLayout->addWidget(serverBox); - QGroupBox *timeBox = new QGroupBox("Set Server Time"); - QHBoxLayout *timeLayout = new QHBoxLayout(timeBox); - dayEdit = new QLineEdit(); dayEdit->setMaxLength(2); - hourEdit = new QLineEdit(); hourEdit->setMaxLength(2); - minuteEdit = new QLineEdit(); minuteEdit->setMaxLength(2); - QPushButton *timeButton = new QPushButton("Set Server Time"); - connect(timeButton, &QPushButton::clicked, this, &MainWindow::sendTimeCommand); - timeLayout->addWidget(new QLabel("Day:")); - timeLayout->addWidget(dayEdit); - timeLayout->addWidget(new QLabel("Hour:")); - timeLayout->addWidget(hourEdit); - timeLayout->addWidget(new QLabel("Minute:")); - timeLayout->addWidget(minuteEdit); - timeLayout->addStretch(); - timeLayout->addWidget(timeButton); - mainLayout->addWidget(timeBox); + QGroupBox *savedServersBox = new QGroupBox("Servers"); + QVBoxLayout *savedServersBoxLayout = new QVBoxLayout(savedServersBox); + QHBoxLayout *savedServersLayout = new QHBoxLayout(); + savedServersCombo = new QComboBox(); + savedServersCombo->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + QPushButton *saveServerButton = new QPushButton("Save"); + QPushButton *loadServerButton = new QPushButton("Load"); + QPushButton *deleteServerButton = new QPushButton("Delete"); + deleteServerButton->setStyleSheet("color: red;"); + connect(saveServerButton, &QPushButton::clicked, this, &MainWindow::saveServerEntry); + connect(loadServerButton, &QPushButton::clicked, this, &MainWindow::loadSelectedServer); + connect(deleteServerButton, &QPushButton::clicked, this, &MainWindow::deleteSelectedServer); + savedServersLayout->addWidget(savedServersCombo); + savedServersLayout->addWidget(saveServerButton); + savedServersLayout->addWidget(loadServerButton); + savedServersBoxLayout->addLayout(savedServersLayout); + savedServersBoxLayout->addWidget(deleteServerButton, 0, Qt::AlignHCenter); + connectionLayout->addWidget(savedServersBox); + connectionLayout->addStretch(); - QGroupBox *speedBox = new QGroupBox("Set Game Speed"); - QVBoxLayout *speedLayout = new QVBoxLayout(speedBox); - QHBoxLayout *sliderLayout = new QHBoxLayout(); - speedSlider = new QSlider(Qt::Horizontal); - speedSlider->setMinimum(1); - speedSlider->setMaximum(5); - speedSlider->setTickInterval(1); - speedSlider->setTickPosition(QSlider::TicksBelow); - connect(speedSlider, &QSlider::valueChanged, this, &MainWindow::updateSliderLabel); - sliderLayout->addWidget(speedSlider); - QPushButton *speedButton = new QPushButton("Set Time Speed"); - connect(speedButton, &QPushButton::clicked, this, &MainWindow::sendSpeedCommand); - sliderLayout->addWidget(speedButton); - sliderLabel = new QLabel("60"); - sliderLabel->setAlignment(Qt::AlignCenter); - speedLayout->addLayout(sliderLayout); - speedLayout->addWidget(sliderLabel); - mainLayout->addWidget(speedBox); - - tabWidget->addTab(mainTab, "Time"); - - QWidget *historyTab = new QWidget; - QVBoxLayout *historyLayout = new QVBoxLayout(historyTab); - historyTextEdit = new QTextEdit(); - historyTextEdit->setReadOnly(true); - historyTextEdit->setTextInteractionFlags(Qt::TextSelectableByMouse); - historyLayout->addWidget(historyTextEdit); - tabWidget->addTab(historyTab, "History"); - - QWidget *logTab = new QWidget; - QVBoxLayout *logLayout = new QVBoxLayout(logTab); - serverLogTextEdit = new QTextEdit(); - serverLogTextEdit->setReadOnly(true); - logLayout->addWidget(serverLogTextEdit); - - QGroupBox *customBox = new QGroupBox(); - QHBoxLayout *customLayout = new QHBoxLayout(customBox); - customCommandEdit = new QLineEdit(); - QPushButton *customButton = new QPushButton("Send Command"); - connect(customButton, &QPushButton::clicked, this, &MainWindow::sendCustomCommand); - connect(customCommandEdit, &QLineEdit::returnPressed, this, &MainWindow::sendCustomCommand); - customLayout->addWidget(customCommandEdit); - customLayout->addWidget(customButton); - logLayout->addWidget(customBox); - - tabWidget->addTab(logTab, "Console"); + tabWidget->addTab(connectionTab, "Connection"); + connect(tabWidget, &QTabWidget::currentChanged, this, [this, usersTab, bansTab](int index) { + if (tabWidget->widget(index) == usersTab) { + refreshUsers(); + } else if (tabWidget->widget(index) == bansTab) { + refreshBans(); + } + }); + tabWidget->setCurrentWidget(connectionTab); updateSliderLabel(speedSlider->value()); } diff --git a/mainwindow.h b/mainwindow.h index f6301b4..c947c44 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -3,10 +3,13 @@ #include #include +#include #include #include #include #include +#include +#include #include #include #include @@ -22,6 +25,19 @@ public: ~MainWindow(); private slots: + void saveServerEntry(); + void loadSelectedServer(); + void deleteSelectedServer(); + void refreshUsers(); + void displaySelectedUserInfo(int row); + void kickSelectedUser(); + void banSelectedUser(); + void refreshBans(); + void displaySelectedBanInfo(int row); + void removeSelectedBan(); + void lockServerTime(); + void unlockServerTime(); + void resendLockedServerTime(); void sendTimeCommand(); void sendSpeedCommand(); void updateSliderLabel(int value); @@ -32,22 +48,79 @@ private slots: void readServerOutput(); private: + struct SavedServerEntry { + QString name; + QString server; + QString port; + QString password; + }; + + struct UserEntry { + QString playerId; + QString username; + QString position; + QString rotation; + QString remoteState; + QString health; + QString deaths; + QString zombies; + QString players; + QString score; + QString level; + QString platformId; + QString eosId; + QString ipAddress; + QString ping; + }; + + struct BanEntry { + QString bannedUntil; + QString userId; + QString username; + QString reason; + }; + QLineEdit *serverEdit, *portEdit, *passwordEdit; QLineEdit *dayEdit, *hourEdit, *minuteEdit; QLineEdit *customCommandEdit; + QComboBox *savedServersCombo; + QListWidget *usersListWidget; + QListWidget *bansListWidget; + QMap userInfoValueLabels; + QMap banInfoValueLabels; QSlider *speedSlider; QLabel *sliderLabel; QLabel *connectionStatusLabel; + QLabel *serverTimeStatusLabel; + QPushButton *serverTimeLockButton; QTcpSocket *socket; QPushButton *connectButton; QPushButton *disconnectButton; QTimer *statusTimer; + QTimer *lockedTimeTimer; QTextEdit *historyTextEdit; QTextEdit *serverLogTextEdit; QTabWidget *tabWidget; + QList savedServers; + QList users; + QList bans; bool isAuthenticated = false; + bool isServerTimeLocked = false; + bool awaitingUsersResponse = false; + bool awaitingBansResponse = false; + QString usersResponseBuffer; + QString bansResponseBuffer; - void sendTelnetCommand(const QString &command); + QString serverConfigFilePath() const; + void clearUserInfo(); + void clearBanInfo(); + void loadSavedServers(); + void parseUsersResponse(const QString &text); + void parseBansResponse(const QString &text); + bool writeSavedServers() const; + bool sendValidatedTimeCommand(); + void updateServerTimeLockState(bool locked); + bool sendTelnetCommand(const QString &command); void setupUI(); };