Skip to content

Instantly share code, notes, and snippets.

@gamingoninsulin
Last active December 19, 2024 21:37
Show Gist options
  • Save gamingoninsulin/61ab85c5695391e2319548e400613fb9 to your computer and use it in GitHub Desktop.
Save gamingoninsulin/61ab85c5695391e2319548e400613fb9 to your computer and use it in GitHub Desktop.
Python Project
################################# ./UnturnedServer.py begin #################################
import sys
from PyQt5.QtWidgets import QApplication, QStackedWidget
from utils.interfaces.DashboardInterface import MainWindow as DashboardInterface
from utils.interfaces.CreateServerInterface import MainWindow as CreateServerInterface
class ApplicationWindow(QStackedWidget):
def __init__(self):
super().__init__()
self.dashboard_interface = DashboardInterface()
self.create_server_interface = CreateServerInterface()
self.addWidget(self.dashboard_interface)
self.addWidget(self.create_server_interface)
self.dashboard_interface.switch_to_create_server_signal.connect(self.show_create_server_interface)
self.create_server_interface.switch_to_dashboard_signal.connect(self.show_dashboard_interface)
def show_create_server_interface(self):
print("Switching to Create Server interface")
self.setCurrentWidget(self.create_server_interface)
def show_dashboard_interface(self):
print("Switching to Dashboard interface")
self.setCurrentWidget(self.dashboard_interface)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = ApplicationWindow()
window.show()
sys.exit(app.exec_())
################################# ./UnturnedServer.py end #################################
################################# ./utils/interfaces/DahsboardInterface.py begin #################################
from PyQt5.QtWidgets import QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QGridLayout, QPushButton, QProgressBar, \
QTextEdit, QMessageBox, QLabel, QMenuBar, QMenu, QAction, QSizePolicy
from PyQt5.QtGui import QTextCursor, QFont
from PyQt5.QtCore import pyqtSignal, QDateTime
from PyQt5.uic.properties import QtWidgets
from rich.console import Console
import utils.interfaces.CreateServerInterface
from utils.logic.DashboardLogic import ServerClient
from utils.handlers.QTextEditHandler import QTextEditHandler
class MainWindow(QMainWindow):
switch_to_create_server_signal = pyqtSignal() # Signal to switch to CreateServer
def __init__(self):
super().__init__()
self.server_process = None
self.console_output = None
self.init_ui()
self.switch_to_create_server_signal.connect(self.show_create_server_interface) # Connect the signal
def init_ui(self):
self.console_output = QTextEdit()
width = 900
height = 900
self.setFixedSize(width, height)
size_policy = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
size_policy.setHorizontalStretch(0)
size_policy.setVerticalStretch(0)
size_policy.setHeightForWidth(MainWindow.sizePolicy(self).hasHeightForWidth())
self.setSizePolicy(size_policy)
self.console_output.setReadOnly(True)
# Set a monospaced font for better alignment
font = QFont("Courier")
self.console_output.setFont(font)
self.server_logic = ServerClient(output_callback=self.update_console_output)
self.setWindowTitle("Unturned Server manager | Dashboard")
# create menu bar
menu_bar = QMenuBar(self)
self.setMenuBar(menu_bar)
# add Dashboard & CreateServer
server_menu = QMenu("menu", self)
menu_bar.addMenu(server_menu)
# Dashboard
self.dashboard_action = QAction("Dashboard", self)
self.dashboard_action.setEnabled(False) # Initially disabled
server_menu.addAction(self.dashboard_action)
# CreateServer
self.create_server_action = QAction("Create Server", self)
server_menu.addAction(self.create_server_action)
self.dashboard_action.triggered.connect(utils.interfaces.CreateServerInterface.MainWindow.switch_to_dashboard)
self.create_server_action.triggered.connect(self.switch_to_create_server)
# Custom handler for console output
self.console_handler = QTextEditHandler(self.console_output)
self.rich_console = Console(file=self.console_handler)
# Create widgets and layout
central_widget = QWidget(self)
layout = QVBoxLayout()
# Top Section (Server Status and Control)
status_layout = QHBoxLayout()
self.server_status_label = QLabel("Server Status: Offline")
status_layout.addWidget(self.server_status_label)
button_layout = QHBoxLayout()
self.start_button = QPushButton("Start Server")
self.start_button.clicked.connect(self.start_server)
self.stop_button = QPushButton("Stop Server")
self.stop_button.setEnabled(False) # Initially disabled
self.stop_button.clicked.connect(self.stop_server)
self.restart_button = QPushButton("Restart Server")
self.restart_button.setEnabled(False) # Initially disabled
self.restart_button.clicked.connect(self.restart_server)
self.kill_button = QPushButton("Kill Server")
self.kill_button.setEnabled(False) # Initially disabled
self.kill_button.clicked.connect(self.kill_server)
button_layout.addWidget(self.start_button)
button_layout.addWidget(self.stop_button)
button_layout.addWidget(self.restart_button)
button_layout.addWidget(self.kill_button)
status_layout.addLayout(button_layout)
layout.addLayout(status_layout)
# Middle Section (Server Information)
info_layout = QGridLayout()
info_layout.addWidget(QLabel("IP:"), 0, 0)
self.server_ip_label = QLabel("127.0.0.1")
info_layout.addWidget(self.server_ip_label, 0, 1)
info_layout.addWidget(QLabel("PORT:"), 1, 0)
self.server_port_label = QLabel("27015")
info_layout.addWidget(self.server_port_label, 1, 1)
info_layout.addWidget(QLabel("Players:"), 3, 0)
self.player_count_label = QLabel("0/20")
info_layout.addWidget(self.player_count_label, 3, 1)
info_layout.addWidget(QLabel("CPU Usage:"), 5, 0)
self.cpu_bar = QProgressBar()
info_layout.addWidget(self.cpu_bar, 5, 1)
info_layout.addWidget(QLabel("Memory Usage:"), 6, 0)
self.memory_bar = QProgressBar()
info_layout.addWidget(self.memory_bar, 6, 1)
layout.addLayout(info_layout)
# Bottom Section (Console Output)
layout.addWidget(self.console_output)
central_widget.setLayout(layout)
self.setCentralWidget(central_widget)
def start_server(self):
self.server_logic.start_server()
# enable and disable buttons
self.start_button.setEnabled(False)
self.stop_button.setEnabled(True)
self.restart_button.setEnabled(True)
self.kill_button.setEnabled(True)
self.server_status_label.setText("Server Status: Online")
def stop_server(self):
self.server_logic.stop_server()
# enable and disable buttons
self.start_button.setEnabled(True)
self.stop_button.setEnabled(False)
self.restart_button.setEnabled(False)
self.kill_button.setEnabled(True)
self.server_status_label.setText("Server Status: Offline")
def restart_server(self):
self.server_logic.restart_server()
# enable and disable buttons
self.start_button.setEnabled(False)
self.stop_button.setEnabled(True)
self.restart_button.setEnabled(True)
self.kill_button.setEnabled(True)
self.server_status_label.setText("Server Status: Online")
def kill_server(self):
self.server_logic.kill_server()
# enable and disable buttons
self.start_button.setEnabled(False)
self.stop_button.setEnabled(True)
self.restart_button.setEnabled(True)
self.kill_button.setEnabled(True)
self.server_status_label.setText("Server Status: Online")
def update_console_output(self, text):
current_time = QDateTime.currentDateTime().toString("yyyy-MM-dd hh:mm:ss")
formatted_text = f"[{current_time}] {text.strip()}" # Ensure no extra spaces
# Use Rich console to format text
self.rich_console.print(formatted_text)
# Ensure auto-scrolling to the bottom
self.console_output.moveCursor(QTextCursor.End)
def closeEvent(self, event):
reply = QMessageBox.question(self, 'Message',
"Are you sure you want to quit?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
if reply == QMessageBox.Yes:
self.server_logic.stop_server()
event.accept()
else:
event.ignore()
def resizeEvent(self, event):
self.setFixedSize(self.size())
super().resizeEvent(event)
def switch_to_create_server(self):
self.dashboard_action.setEnabled(False)
self.create_server_action.setEnabled(True)
self.switch_to_create_server_signal.emit() # Emit signal to switch to dashboard
def show_create_server_interface(self):
print("Switching to Create Server interface")
parent_widget = self.parentWidget()
if parent_widget:
parent_widget.setCurrentWidget(parent_widget.create_server_interface)
################################# ./utils/interfaces/DahsboardInterface.py end #################################
################################# ./utils/interfaces/CreateServerInterface.py begin #################################
# CreateServerInterface.py
from PyQt5.QtWidgets import QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, \
QTextEdit, QLabel, QMenuBar, QMenu, QAction, QLineEdit, QComboBox, QSizePolicy
from PyQt5.QtGui import QFont
from PyQt5.QtCore import pyqtSignal
from PyQt5.uic.properties import QtWidgets
import utils.interfaces.DashboardInterface
class MainWindow(QMainWindow):
switch_to_dashboard_signal = pyqtSignal() # Signal to switch to Dashboard
def __init__(self):
super().__init__()
self.init_ui()
def init_ui(self):
width = 900
height = 900
self.setMinimumSize(width, height)
size_policy = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
size_policy.setHorizontalStretch(0)
size_policy.setVerticalStretch(0)
size_policy.setHeightForWidth(MainWindow.sizePolicy(self).hasHeightForWidth())
self.setSizePolicy(size_policy)
self.console_output = QTextEdit()
self.console_output.setReadOnly(True)
self.console_output.setFont(QFont("Courier"))
self.setWindowTitle("Unturned Server manager | Create Server")
# create menu bar
menu_bar = QMenuBar(self)
self.setMenuBar(menu_bar)
# add Dashboard & CreateServer
server_menu = QMenu("menu", self)
menu_bar.addMenu(server_menu)
# Dashboard
self.dashboard_action = QAction("Dashboard", self)
server_menu.addAction(self.dashboard_action)
# CreateServer
self.create_server_action = QAction("Create Server", self)
self.create_server_action.setEnabled(False) # Initially disabled
server_menu.addAction(self.create_server_action)
self.dashboard_action.triggered.connect(self.switch_to_dashboard)
self.create_server_action.triggered.connect(utils.interfaces.DashboardInterface.MainWindow.switch_to_create_server)
# Create widgets and layout
central_widget = QWidget(self)
layout = QVBoxLayout()
# COMMANDS.DAT Section
commands_layout = QVBoxLayout()
commands_label = QLabel("COMMANDS.DAT")
commands_layout.addWidget(commands_label)
self.server_name_input = QLineEdit("My Local Unturned Server")
self.game_mode_dropdown = QComboBox()
self.game_mode_dropdown.addItems(["PVP", "PVE"])
self.server_map_dropdown = QComboBox()
self.server_map_dropdown.addItems(["Washington", "Russia", "Germany", "PEI", "Yukon"])
self.difficulty_dropdown = QComboBox()
self.difficulty_dropdown.addItems(["Easy", "Normal", "Hard"])
self.difficulty_dropdown.setCurrentIndex(1)
self.perspective_dropdown = QComboBox()
self.perspective_dropdown.addItems(["First Person", "Third Person", "Both"])
self.perspective_dropdown.setCurrentIndex(0)
self.owner_id_input = QLineEdit("YOUR_STEAM64ID_HERE")
self.password_input = QLineEdit()
self.welcome_message_input = QLineEdit("Welcome! This Unturned Server!")
commands_layout.addWidget(QLabel("Server Name:"))
commands_layout.addWidget(self.server_name_input)
commands_layout.addWidget(QLabel("Game Mode:"))
commands_layout.addWidget(self.game_mode_dropdown)
commands_layout.addWidget(QLabel("Server Map:"))
commands_layout.addWidget(self.server_map_dropdown)
commands_layout.addWidget(QLabel("Difficulty:"))
commands_layout.addWidget(self.difficulty_dropdown)
commands_layout.addWidget(QLabel("Perspective:"))
commands_layout.addWidget(self.perspective_dropdown)
commands_layout.addWidget(QLabel("Set Owner ID:"))
commands_layout.addWidget(self.owner_id_input)
commands_layout.addWidget(QLabel("Set Password:"))
commands_layout.addWidget(self.password_input)
commands_layout.addWidget(QLabel("Welcome Message:"))
commands_layout.addWidget(self.welcome_message_input)
self.save_commands_button = QPushButton("Save Commands.dat")
commands_layout.addWidget(self.save_commands_button)
layout.addLayout(commands_layout)
# CONFIG.JSON Section
config_layout = QVBoxLayout()
config_label = QLabel("CONFIG.JSON")
config_layout.addWidget(config_label)
self.icon_input = QLineEdit("https://imgur.com/Gb1pZUW")
self.thumbnail_input = QLineEdit("https://imgur.com/Gb1pZUW")
self.login_token_input = QLineEdit()
self.description_hint_input = QLineEdit("<color=#fff>This Server is <color=#b4300>Local Hosted</color></color>")
self.description_server_list_input = QLineEdit("<color=#b4300>This is a Local Server</color>")
self.description_full_input = QTextEdit("<color=#fff>This Server is Local Hosted <color=#b4300>This is a Local Server</color></color>")
config_layout.addWidget(QLabel("Icon:"))
config_layout.addWidget(self.icon_input)
config_layout.addWidget(QLabel("Thumbnail:"))
config_layout.addWidget(self.thumbnail_input)
config_layout.addWidget(QLabel("Login Token:"))
config_layout.addWidget(self.login_token_input)
config_layout.addWidget(QLabel("Description Hint:"))
config_layout.addWidget(self.description_hint_input)
config_layout.addWidget(QLabel("Description Server List:"))
config_layout.addWidget(self.description_server_list_input)
config_layout.addWidget(QLabel("Description Full:"))
config_layout.addWidget(self.description_full_input)
self.save_config_button = QPushButton("Save Config.json")
config_layout.addWidget(self.save_config_button)
layout.addLayout(config_layout)
# Quick Actions Section
quick_actions_layout = QHBoxLayout()
self.wipe_map_button = QPushButton("Wipe Map")
self.wipe_players_button = QPushButton("Wipe Players")
self.clear_logs_button = QPushButton("Clear Logs")
quick_actions_layout.addWidget(self.wipe_map_button)
quick_actions_layout.addWidget(self.wipe_players_button)
quick_actions_layout.addWidget(self.clear_logs_button)
layout.addLayout(quick_actions_layout)
# Console Output
layout.addWidget(self.console_output)
central_widget.setLayout(layout)
self.setCentralWidget(central_widget)
# Connect signals to slots
self.save_commands_button.clicked.connect(self.save_commands)
self.save_config_button.clicked.connect(self.save_config)
self.wipe_map_button.clicked.connect(self.wipe_map)
self.wipe_players_button.clicked.connect(self.wipe_players)
self.clear_logs_button.clicked.connect(self.clear_logs)
def save_commands(self):
pass
def save_config(self):
pass
def wipe_map(self):
pass
def wipe_players(self):
pass
def clear_logs(self):
pass
def update_console_output(self, text):
pass # Logic will be implemented in CreateServerLogic.py
def closeEvent(self, event):
pass # Logic will be implemented in CreateServerLogic.py
def switch_to_dashboard(self):
self.dashboard_action.setEnabled(True)
self.create_server_action.setEnabled(False)
self.switch_to_dashboard_signal.emit() # Emit signal to switch to dashboard
def resizeEvent(self, event):
self.setFixedSize(self.size())
super().resizeEvent(event)
def show_dashboard_interface(self):
print("Switching to Dashboard interface")
parent_widget = self.parentWidget()
if parent_widget:
parent_widget.setCurrentWidget(parent_widget.dashboard_interface)
################################# ./utils/interfaces/CreateServerInterface.py end #################################
################################# ./utils/logic/DashboardLogic.py begin #################################
import os
import signal
import subprocess
import threading
import time
import platform
import codecs
class ServerClient:
def __init__(self, output_callback=None):
self.server_process = None
# Path Prefixes
self.PathPrefix = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../"))
self.ServersPath = os.path.abspath(os.path.join(os.path.dirname(__file__), self.PathPrefix + "/Servers"))
self.ServersFolderName = os.path.abspath(os.path.join(os.path.dirname(__file__), self.PathPrefix + self.ServersPath + "/Default")) # or any name settable in create server window!
self.ServerDatsFolder = os.path.abspath(os.path.join(os.path.dirname(__file__), self.PathPrefix + self.ServersPath +"/server"))
# Unturned.exe
self.serverExePath = self.PathPrefix + "/Unturned.exe"
# os.path.abspath(os.path.join(os.path.dirname(__file__)
print(self.serverExePath)
# .Dat files
self.WhitelistDat = os.path.abspath(os.path.join(os.path.dirname(__file__), self.ServerDatsFolder + "/Whitelist.dat"))
self.BlacklistDat = os.path.abspath(os.path.join(os.path.dirname(__file__), self.ServerDatsFolder + "/Blacklist.dat"))
self.AdminlistDat = os.path.abspath(os.path.join(os.path.dirname(__file__), self.ServerDatsFolder + "/Adminlist.dat"))
self.CommandsDat = os.path.abspath(os.path.join(os.path.dirname(__file__), self.ServerDatsFolder + "/Commands.dat")) # for ip and such
self.stdout_thread = None
self.stderr_thread = None
self.output_callback = output_callback
def _read_output(self, stream):
"""Reads output from the given stream and prints it to the console."""
# Uses codecs to handle UTF-8 decoding
reader = codecs.getreader("UTF-8")(stream)
for line in iter(stream.readline, ''):
if self.output_callback:
self.output_callback(line.strip()) # send the line to the callback function
# print(line.strip()) # Print each line as it becomes available
def start_server(self):
if self.server_process:
raise Exception("Server is already running.")
if not os.path.exists(self.serverExePath):
raise FileNotFoundError(f"Server executable not found: {self.serverExePath}")
try:
if platform.system() == "Windows":
# Use STARTUPINFO to ensure the window is hidden
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
startupinfo.wShowWindow = subprocess.SW_HIDE
self.server_process = subprocess.Popen(
[self.serverExePath, "-nographics"],
creationflags=subprocess.CREATE_NO_WINDOW,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
encoding='utf-8', # Ensure UTF-8 encoding
startupinfo = startupinfo
)
elif platform.system() == "Linux":
command = ["nohup", self.serverExePath, "-nographics"]
self.server_process = subprocess.Popen(
command,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
preexec_fn=os.setsid,
text=True,
encoding='utf-8' # Ensure UTF-8 encoding
)
else:
raise OSError("Unsupported OS")
# Start threads to read stdout and stderr asynchronously
self.stdout_thread = threading.Thread(target=self._read_output, args=(self.server_process.stdout,))
self.stderr_thread = threading.Thread(target=self._read_output, args=(self.server_process.stderr,))
self.stdout_thread.daemon = True # Allow main program to exit even if threads are running
self.stderr_thread.daemon = True
self.stdout_thread.start()
self.stderr_thread.start()
self.output_callback(f"Server started (PID: {self.server_process.pid})")
except OSError as e:
raise OSError(f"OS Error starting server: {e}")
except Exception as e:
raise Exception(f"Error starting server: {e}")
def stop_server(self):
"""Stops the Unturned server with a save command."""
if not self.server_process:
raise Exception("Server is not running.")
try:
# Send the "save" command to the server's standard input
if platform.system() == "Windows":
self.server_process.stdin.write("save\n")
self.server_process.stdin.write("shutdown\n")
self.server_process.stdin.flush() # Important to flush the input buffer
elif platform.system() == "Linux":
self.server_process.stdin.write("save\n")
self.server_process.stdin.write("shutdown\n")
self.server_process.stdin.flush()
else:
raise OSError("Unsupported OS")
time.sleep(2) # Give the server some time to save
self.server_process = None
self.output_callback("Server Stopped!")
except Exception as e:
self.output_callback(f"Error stopping server: {e}")
def restart_server(self):
try:
self.stop_server()
self.start_server()
except Exception as e:
self.output_callback(f"Error restarting server: {e}")
def kill_server(self):
try:
# Now terminate the server process
if platform.system() == "Windows":
self.server_process.terminate()
try:
self.server_process.wait(timeout=5)
except subprocess.TimeoutExpired:
self.server_process.kill()
elif platform.system() == "Linux":
os.killpg(os.getpgid(self.server_process.pid), signal.SIGTERM)
try:
self.server_process.wait(timeout=5)
except subprocess.TimeoutExpired:
os.killpg(os.getpgid(self.server_process.pid), signal.SIGKILL)
else:
raise OSError("Unsupported OS")
except Exception as e:
self.output_callback(f"Error Killing server: {e}")
################################# ./utils/logic/DashboardLogic.py end #################################
################################# ./utils/logic/CreateServerLogic.py begin #################################
from PyQt5.QtCore import QDateTime
from PyQt5.QtGui import QTextCursor
from PyQt5.QtWidgets import QMessageBox
from rich.console import Console
from utils.handlers.QTextEditHandler import QTextEditHandler
from utils.interfaces.CreateServerInterface import MainWindow
from utils.logic.DashboardLogic import ServerClient
class MainWindowLogic(MainWindow):
def __init__(self):
super().__init__()
# Create server logic with console output callback
self.server_logic = ServerClient(output_callback=self.update_console_output)
self.console_handler = QTextEditHandler(self.console_output)
self.rich_console = Console(file=self.console_handler)
def start_server(self):
self.server_logic.start_server()
# Enable and disable buttons
self.start_button.setEnabled(False)
self.stop_button.setEnabled(True)
self.restart_button.setEnabled(True)
self.server_status_label.setText("Server Status: Online")
def stop_server(self):
self.server_logic.stop_server()
# Print to the console
self.update_console_output("Server stopped.")
# Enable and disable buttons
self.start_button.setEnabled(True)
self.stop_button.setEnabled(False)
self.restart_button.setEnabled(False)
self.server_status_label.setText("Server Status: Offline")
def restart_server(self):
self.server_logic.restart_server()
# Enable and disable buttons
self.start_button.setEnabled(False)
self.stop_button.setEnabled(True)
self.restart_button.setEnabled(True)
self.server_status_label.setText("Server Status: Online")
def save_commands(self):
# Save COMMANDS.DAT settings
pass
def save_config(self):
# Save CONFIG.JSON settings
pass
def wipe_map(self):
# Logic for wiping map
self.update_console_output("Map wiped.")
def wipe_players(self):
# Logic for wiping players
self.update_console_output("Players wiped.")
def clear_logs(self):
# Logic for clearing logs
self.update_console_output("Logs cleared.")
def update_console_output(self, text):
current_time = QDateTime.currentDateTime().toString("yyyy-MM-dd hh:mm:ss")
formatted_text = f"[{current_time}] {text.strip()}" # Ensure no extra spaces
# Use Rich console to format text
self.rich_console.print(formatted_text)
# Ensure auto-scrolling to the bottom
self.console_output.moveCursor(QTextCursor.End)
def closeEvent(self, event):
reply = QMessageBox.question(self, 'Message',
"Are you sure you want to quit?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
if reply == QMessageBox.Yes:
self.server_logic.stop_server()
event.accept()
else:
event.ignore()
################################# ./utils/logic/CreateServerLogic.py end #################################
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment