diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..bc2923d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,30 @@
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
+__pypackages__/
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# VS and VSC
+.vs/*
+.vscode/*
+
+# Data
+[Dd]ata/*
+Content/*
+
+# Texture
+Sprites/
+
+# PyQT6
+*_ui.py
diff --git a/Code/app_client_main.py b/Code/app_client_main.py
new file mode 100644
index 0000000..56e8533
--- /dev/null
+++ b/Code/app_client_main.py
@@ -0,0 +1,10 @@
+import sys
+
+from gui import LoginWindow
+from PyQt6.QtWidgets import QApplication
+
+if __name__ == "__main__":
+ app = QApplication(sys.argv)
+ window: LoginWindow = LoginWindow.get_instance()
+ window.show()
+ sys.exit(app.exec())
diff --git a/Code/gui/__init__.py b/Code/gui/__init__.py
new file mode 100644
index 0000000..b0df4df
--- /dev/null
+++ b/Code/gui/__init__.py
@@ -0,0 +1 @@
+from gui.login_window import LoginWindow
diff --git a/Code/gui/login_window.py b/Code/gui/login_window.py
new file mode 100644
index 0000000..71a7032
--- /dev/null
+++ b/Code/gui/login_window.py
@@ -0,0 +1,217 @@
+import os
+
+from gui.main_window import MainApplicationWindow
+from PyQt6.QtGui import QPixmap
+from PyQt6.QtWidgets import (QDialog, QLabel, QLineEdit, QMainWindow,
+ QMessageBox, QProgressBar, QPushButton)
+from PyQt6.uic import loadUi
+from root_path import ROOT_PATH
+from systems.decorators import global_class
+from systems.network import ClientUnit
+from PyQt6.QtCore import QThread, pyqtSignal
+
+@global_class
+class LoginWindow(QMainWindow):
+ def __init__(self):
+ super().__init__()
+ loadUi(os.path.join(ROOT_PATH, 'GUI', 'windows', 'LoginWindow.ui'), self)
+
+ # Привязка виджетов
+ self.ip_line = self.findChild(QLineEdit, 'IPLine')
+ self.port_line = self.findChild(QLineEdit, 'PortLine')
+ self.server_check_button = self.findChild(QPushButton, 'ServerCheckButton')
+ self.server_check_label = self.findChild(QLabel, 'ServerCheckLabel')
+
+ self.login_button = self.findChild(QPushButton, 'LoginButton')
+ self.register_button = self.findChild(QPushButton, 'RegisterButton')
+
+ self.set_server_check_label("failure")
+
+ # Привязка функций к кнопкам
+ self.server_check_button.clicked.connect(self.check_server)
+ self.login_button.clicked.connect(self.open_auth_window)
+ self.register_button.clicked.connect(self.open_registration_window)
+
+ # Начальное состояние кнопок
+ self.login_button.setEnabled(False)
+ self.register_button.setEnabled(False)
+
+ def set_server_check_label(self, state: str):
+ self.server_check_label.setPixmap(QPixmap(os.path.join(ROOT_PATH, 'GUI', 'images', 'status', f'{state}_64_64.png')))
+
+ def check_server(self):
+ ip = self.ip_line.text()
+ port = self.port_line.text()
+
+ if not ip or not port:
+ QMessageBox.warning(self, "Ошибка", "Все поля должны быть заполнены!")
+ self.set_server_check_label("failure")
+ self.set_buttons(False)
+ return
+
+ if not self.is_valid_ip(ip):
+ QMessageBox.warning(self, "Ошибка", "IP неверно указан. Такого IP не может существовать.")
+ self.set_server_check_label("failure")
+ self.set_buttons(False)
+ return
+
+ if not self.is_valid_port(port):
+ QMessageBox.warning(self, "Ошибка", "Порт неверно указан. Такого порта не может существовать.")
+ self.set_server_check_label("failure")
+ self.set_buttons(False)
+ return
+
+ cl_unit: ClientUnit = ClientUnit.get_instance()
+ try:
+ cl_unit.check_server(ip, int(port))
+ self.set_server_check_label("success")
+ self.set_buttons(True)
+
+ except Exception as e:
+ QMessageBox.warning(self, "Ошибка", f"Невозможно подключиться к серверу: {str(e)}")
+ self.set_server_check_label("failure")
+ self.set_buttons(False)
+ return
+
+ def open_auth_window(self):
+ auth_window = AuthDialog(self)
+ auth_window.exec()
+
+ def open_registration_window(self):
+ registration_window = RegistrationDialog(self)
+ registration_window.exec()
+
+ def set_buttons(self, value: bool):
+ self.login_button.setEnabled(value)
+ self.register_button.setEnabled(value)
+
+ def is_valid_ip(self, ip: str) -> bool:
+ parts = ip.split('.')
+ if len(parts) != 4:
+ return False
+
+ for part in parts:
+ if not part.isdigit():
+ return False
+
+ if not 0 <= int(part) <= 255:
+ return False
+
+ return True
+
+ def is_valid_port(self, port: str) -> bool:
+ if not port.isdigit():
+ return False
+
+ port_num = int(port)
+ return 1 <= port_num <= 65535
+
+ def run_download_server_content(self) -> None:
+ download_dialog = ServerContentDownloadDialog(self)
+ download_dialog.exec()
+
+ def run_main_app(self) -> None:
+ self.close()
+ self.main_window: MainApplicationWindow = MainApplicationWindow.get_instance()
+ self.main_window.show()
+
+class AuthDialog(QDialog):
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ loadUi(os.path.join(ROOT_PATH, 'GUI', 'windows', 'AuthDialog.ui'), self)
+
+ self.username_line = self.findChild(QLineEdit, 'UsernameLine')
+ self.password_line = self.findChild(QLineEdit, 'PasswordLine')
+ self.login_button = self.findChild(QPushButton, 'LoginButton')
+
+ self.login_button.clicked.connect(self.authenticate)
+
+ def authenticate(self):
+ username = self.username_line.text()
+ password = self.password_line.text()
+
+ if not username or not password:
+ QMessageBox.warning(self, "Ошибка", "Все поля должны быть заполнены!")
+ return
+
+ cl_unit: ClientUnit = ClientUnit.get_instance()
+ try:
+ cl_unit.login(username, password)
+ self.close_current_and_open_main_window()
+
+ except Exception as e:
+ QMessageBox.warning(self, "Ошибка", f"Невозможно выполнить аутентификацию: {str(e)}")
+
+ def close_current_and_open_main_window(self):
+ self.close()
+ self.parent().run_download_server_content()
+
+class RegistrationDialog(QDialog):
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ loadUi(os.path.join(ROOT_PATH, 'GUI', 'windows', 'RegistrationDialog.ui'), self)
+
+ self.username_line = self.findChild(QLineEdit, 'UsernameLine')
+ self.password_line = self.findChild(QLineEdit, 'PasswordLine')
+ self.password_line_2 = self.findChild(QLineEdit, 'PasswordLine_2')
+ self.register_button = self.findChild(QPushButton, 'RegisterButton')
+
+ self.register_button.clicked.connect(self.register)
+
+ def register(self):
+ username = self.username_line.text()
+ password = self.password_line.text()
+ password_2 = self.password_line_2.text()
+
+ if not username or not password or not password_2:
+ QMessageBox.warning(self, "Ошибка", "Все поля должны быть заполнены!")
+ return
+
+ if password != password_2:
+ QMessageBox.warning(self, "Ошибка", "Пароли должны совпадать!")
+ return
+
+ cl_unit: ClientUnit = ClientUnit.get_instance()
+ try:
+ cl_unit.register(username, password)
+ self.close_current_and_open_main_window()
+ except Exception as err:
+ QMessageBox.warning(self, "Ошибка", f"Логин который вы указали уже занят. Подробная ошибка: {err}")
+
+ def close_current_and_open_main_window(self):
+ self.close()
+ self.parent().run_download_server_content()
+
+class DownloadThread(QThread):
+ progress_updated = pyqtSignal(int, int)
+
+ def __init__(self):
+ super().__init__()
+
+ def run(self):
+ cl_unit: ClientUnit = ClientUnit.get_instance()
+ cl_unit.download_server_content(self.report_progress)
+
+ def report_progress(self, downloaded_size, total_size):
+ self.progress_updated.emit(downloaded_size, total_size)
+
+class ServerContentDownloadDialog(QDialog):
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ loadUi(os.path.join(ROOT_PATH, 'GUI', 'windows', 'ServerContentDownloadDialog.ui'), self)
+
+ self.progress_bar = self.findChild(QProgressBar, 'progressBar')
+
+ self.download_thread = DownloadThread()
+ self.download_thread.progress_updated.connect(self.update_progress)
+ self.download_thread.start()
+
+ def update_progress(self, downloaded_size, total_size):
+ self.progress_bar.setMaximum(total_size)
+ self.progress_bar.setValue(downloaded_size)
+
+ if downloaded_size >= total_size:
+ self.download_thread.quit()
+ self.download_thread.wait()
+ self.close()
+ self.parent().run_main_app()
diff --git a/Code/gui/main_window.py b/Code/gui/main_window.py
new file mode 100644
index 0000000..988d488
--- /dev/null
+++ b/Code/gui/main_window.py
@@ -0,0 +1,16 @@
+import os
+
+from PyQt6.QtGui import QPixmap
+from PyQt6.QtWidgets import (QDialog, QLabel, QLineEdit, QMainWindow,
+ QMessageBox, QPushButton)
+from PyQt6.uic import loadUi
+from root_path import ROOT_PATH
+from systems.decorators import global_class
+from systems.network import ClientUnit
+
+
+@global_class
+class MainApplicationWindow(QMainWindow):
+ def __init__(self):
+ super().__init__()
+ loadUi(os.path.join(ROOT_PATH, 'GUI', 'windows', 'MainWindow.ui'), self)
diff --git a/Code/root_path.py b/Code/root_path.py
new file mode 100644
index 0000000..d5fd67f
--- /dev/null
+++ b/Code/root_path.py
@@ -0,0 +1,3 @@
+import os
+
+ROOT_PATH = os.path.abspath(os.path.join(__file__, os.pardir, os.pardir))
diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/tests/Texture/__init__.py" b/Code/systems/__init__.py
similarity index 100%
rename from "\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/tests/Texture/__init__.py"
rename to Code/systems/__init__.py
diff --git a/Code/systems/bin_system/__init__.py b/Code/systems/bin_system/__init__.py
new file mode 100644
index 0000000..2bd8183
--- /dev/null
+++ b/Code/systems/bin_system/__init__.py
@@ -0,0 +1 @@
+from systems.bin_system.bin_system import BinaryFileSystem
diff --git a/Code/systems/bin_system/bin_system.py b/Code/systems/bin_system/bin_system.py
new file mode 100644
index 0000000..6e51d51
--- /dev/null
+++ b/Code/systems/bin_system/bin_system.py
@@ -0,0 +1,76 @@
+import atexit
+import os
+import pickle
+from typing import Any, Dict, Optional
+
+from root_path import ROOT_PATH
+
+
+class BinaryFileSystem:
+ __slots__ = ['_data', '_file_path']
+
+ def __init__(self, file_path: Optional[str], file_name: str) -> None:
+ """Инициализирует объект BinaryFileSystem.
+ На текущий момент данный класс используется как сохранение настроек
+
+ Args:
+ file_path (Optional[str]): Путь до файла с данными
+ file_name (str): Имя файла
+ """
+ if not file_path:
+ file_path = ""
+
+ full_file_path = os.path.join(ROOT_PATH, "data", full_file_path)
+
+ if not os.path.exists(full_file_path):
+ os.makedirs(full_file_path)
+
+ self._file_path = os.path.join(full_file_path, f"{file_name}.bin")
+
+ self._data = {}
+ try:
+ self._read_file()
+
+ except Exception:
+ self._write_file()
+
+ atexit.register(self._write_file)
+
+ def _write_file(self) -> None:
+ """Записывает данные в файл.
+ """
+ with open(self._file_path, 'wb') as file:
+ pickle.dump(self._data, file)
+
+ def _read_file(self) -> Dict[str, Any]:
+ """Читает данные из файла
+
+ Returns:
+ Dict[str, Any]: Словарь данных
+ """
+ with open(self._file_path, 'rb') as file:
+ self._data = pickle.load(file)
+
+ def __getitem__(self, key) -> Any:
+ """Возвращает значение по ключу.
+
+ Args:
+ key (str): Ключ для доступа к значению.
+
+ Returns:
+ Any: Значение, соответствующее ключу.
+ """
+ return self._data.get(key, None)
+
+ def __setitem__(self, key: str, value: Any) -> None:
+ """Устанавливает значение по ключу и сохраняет изменения в файл, если значение отличается от текущего.
+
+ Args:
+ key (str): Ключ для установки значения.
+ value (Any): Устанавливаемое значение.
+ """
+ if key in self._data and self._data.get(key, None) == value:
+ return
+
+ self._data[key] = value
+ self._write_file()
diff --git a/Code/systems/decorators.py b/Code/systems/decorators.py
new file mode 100644
index 0000000..0e3ede8
--- /dev/null
+++ b/Code/systems/decorators.py
@@ -0,0 +1,28 @@
+def global_class(cls):
+ """Декоратор для обеспечения того, что класс следует паттерну Singleton, то есть создается и используется только один экземпляр класса во всем приложении.
+
+ Args:
+ cls (type): Класс, к которому применяется декоратор.
+
+ Returns:
+ type: Класс с добавленным методом для получения единственного экземпляра.
+ """
+ instances = {}
+
+ def get_instance(*args, **kwargs):
+ """Возвращает единственный экземпляр класса, создавая его, если он еще не существует.
+
+ Args:
+ *args: Позиционные аргументы для передачи в конструктор класса.
+ **kwargs: Именованные аргументы для передачи в конструктор класса.
+
+ Returns:
+ object: Единственный экземпляр класса.
+ """
+ if cls not in instances:
+ instances[cls] = cls(*args, **kwargs)
+
+ return instances[cls]
+
+ cls.get_instance = get_instance
+ return cls
diff --git a/Code/systems/events_system/__init__.py b/Code/systems/events_system/__init__.py
new file mode 100644
index 0000000..14dc7a6
--- /dev/null
+++ b/Code/systems/events_system/__init__.py
@@ -0,0 +1,4 @@
+from systems.events_system.event_manager import EventManager
+from systems.events_system.register import register_ev
+
+__all__ = ['EventManager', 'register_ev']
diff --git a/Code/systems/events_system/event_manager.py b/Code/systems/events_system/event_manager.py
new file mode 100644
index 0000000..b3da01e
--- /dev/null
+++ b/Code/systems/events_system/event_manager.py
@@ -0,0 +1,30 @@
+import asyncio
+from inspect import signature
+from typing import Any, Callable, Dict, List
+
+from systems.decorators import global_class
+
+
+@global_class
+class EventManager:
+ __slots__ = ['_register_defs']
+
+ def __init__(self) -> None:
+ self._register_defs: Dict[str, List[Callable[..., Any]]] = {}
+
+ def register_event(self, event_name: str, func: Callable[..., Any]):
+ if event_name not in self._register_defs:
+ self._register_defs[event_name] = []
+ self._register_defs[event_name].append(func)
+
+ async def call_event(self, event_name: str, *args, **kwargs):
+ handlers = self._register_defs.get(event_name, [])
+ for handler in handlers:
+ handler_signature = signature(handler)
+ handler_kwargs = {k: v for k, v in kwargs.items() if k in handler_signature.parameters}
+
+ if asyncio.iscoroutinefunction(handler):
+ await handler(*args, **handler_kwargs)
+
+ else:
+ handler(*args, **handler_kwargs)
diff --git a/Code/systems/events_system/events/__init__.py b/Code/systems/events_system/events/__init__.py
new file mode 100644
index 0000000..a8aa4e0
--- /dev/null
+++ b/Code/systems/events_system/events/__init__.py
@@ -0,0 +1,3 @@
+from systems.events_system.events.test import test_echo
+
+__all__ = ['test_echo']
diff --git a/Code/systems/events_system/events/test.py b/Code/systems/events_system/events/test.py
new file mode 100644
index 0000000..9c839ef
--- /dev/null
+++ b/Code/systems/events_system/events/test.py
@@ -0,0 +1,10 @@
+import logging
+
+logger = logging.getLogger("Test ev")
+
+#TODO: Удалить после добавления первого нормального ивента
+
+def test_echo(data): # Ивенты работают только по именованным аргументам! Если сервер отправил data, то и в функцию прийдёт только data. socket_user, socket_access: UserAccess всегда можно поставить в функцию
+ logger.info(f"Echo event called with data: {data}")
+
+test_echo.event_name = "echo" # Указание типа ивента
diff --git a/Code/systems/events_system/register.py b/Code/systems/events_system/register.py
new file mode 100644
index 0000000..e05414e
--- /dev/null
+++ b/Code/systems/events_system/register.py
@@ -0,0 +1,20 @@
+import logging
+
+from systems.events_system import events
+from systems.events_system.event_manager import EventManager
+
+logger = logging.getLogger("Event register")
+
+def register_ev():
+ ev_manager = EventManager.get_instance()
+
+ event_names = events.__all__
+
+ for name in event_names:
+ handler = getattr(events, name)
+
+ if callable(handler) and hasattr(handler, 'event_name'):
+ event_name = handler.event_name
+ ev_manager.register_event(event_name, handler)
+ logger.info(f"Registered event '{event_name}' with handler {handler.__name__}")
+
diff --git a/Code/systems/network/__init__.py b/Code/systems/network/__init__.py
new file mode 100644
index 0000000..965c4fa
--- /dev/null
+++ b/Code/systems/network/__init__.py
@@ -0,0 +1 @@
+from systems.network.client_unit import ClientUnit
diff --git a/Code/systems/network/client_unit.py b/Code/systems/network/client_unit.py
new file mode 100755
index 0000000..d590da2
--- /dev/null
+++ b/Code/systems/network/client_unit.py
@@ -0,0 +1,171 @@
+import asyncio
+import atexit
+import os
+import shutil
+import socket
+import threading
+import zipfile
+from typing import Any, Dict, Optional, Tuple
+
+import msgpack
+import requests
+from root_path import ROOT_PATH
+from systems.decorators import global_class
+from systems.events_system import EventManager
+
+
+@global_class
+class ClientUnit:
+ __slots__ = ['_http_url', '_socket_url', '_session', '_token', '_socket', '_bg_processing', '_bg_thread']
+ SOCKET_CHUNK_SIZE: int = 8192
+ DEFAULT_DOWNLOAD_CHUNK_SIZE: int = 8192
+
+ def __init__(self) -> None:
+ self._http_url: str = ""
+ self._socket_url: Tuple[str, int] = ("", 0)
+ self._session: requests.Session = requests.Session()
+ self._token: Optional[str] = None
+ self._socket: Optional[socket.socket] = None
+ self._bg_processing: bool = False
+ self._bg_thread: Optional[threading.Thread] = None
+
+ atexit.register(self._shutdown)
+
+ # --- Net data --- #
+ @staticmethod
+ def pack_data(data: Any) -> bytes:
+ return msgpack.packb(data)
+
+ @staticmethod
+ def unpack_data(data: bytes) -> Any:
+ return msgpack.unpackb(data, raw=False)
+
+ # --- Server API --- #
+ def check_server(self, ip: str, port: int = 5000) -> None:
+ temp_http_url = f"http://{ip}:{port}"
+ response = self._session.get(f"{temp_http_url}/server/check_status", timeout=5)
+ response.raise_for_status()
+
+ response_data = response.json()
+ if response_data.get("message") == "Server is online":
+ server_info = response_data.get("server_info", {})
+ self._http_url = temp_http_url
+ self._socket_url = (ip, server_info.get("socket_port"))
+
+ def download_server_content(self, progress_callback=None) -> None:
+ response = self._session.get(f"{self._http_url}/server/download_server_content", stream=True)
+
+ archive_path = "content.zip"
+ content_dir = os.path.join(ROOT_PATH, 'Content')
+
+ total_size = int(response.headers.get('content-length', 0))
+ downloaded_size = 0
+
+ try:
+ with open(archive_path, 'wb') as file:
+ for chunk in response.iter_content(chunk_size=self.DEFAULT_DOWNLOAD_CHUNK_SIZE):
+ file.write(chunk)
+ downloaded_size += len(chunk)
+ if progress_callback:
+ progress_callback(downloaded_size, total_size)
+
+ except IOError as e:
+ raise IOError(f"Error saving file: {e}")
+
+ if os.path.exists(content_dir):
+ shutil.rmtree(content_dir)
+
+ with zipfile.ZipFile(archive_path, 'r') as zip_ref:
+ zip_ref.extractall(content_dir)
+
+ os.remove(archive_path)
+
+ # --- Auth API --- #
+ def register(self, login: str, password: str) -> None:
+ self._auth_request("register", login, password)
+ self.login(login, password)
+
+ def login(self, login: str, password: str) -> None:
+ token = self._auth_request("login", login, password)["token"]
+ self._session.headers.update({"Authorization": token})
+ self._token = token
+
+ def logout(self) -> None:
+ if self._token:
+ response = self._session.post(f"{self._http_url}/auth/logout")
+ self._session.headers.update({"Authorization": None})
+ response.raise_for_status()
+
+ def _auth_request(self, endpoint: str, login: str, password: str) -> Dict[str, Any]:
+ response = self._session.post(f"{self._http_url}/auth/{endpoint}", json={'login': login, 'password': password})
+ response.raise_for_status()
+ return response.json()
+
+ # --- Socket work --- #
+ def connect(self) -> None:
+ if not self._token:
+ raise ConnectionError("Token is not available. Please log in first.")
+
+ self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ self._socket.connect(self._socket_url)
+ self._socket.sendall(self._token.encode('utf-8'))
+
+ # Wait for token confirmation
+ response = self._socket.recv(self.SOCKET_CHUNK_SIZE)
+ if response != b"Token accepted":
+ raise ConnectionError("Token was not accepted by the server")
+
+ def disconnect(self) -> None:
+ self.stop_bg_processing()
+ if self._socket:
+ self._socket.close()
+ self._socket = None
+
+ def send_data(self, data: Dict[str, Any]) -> None:
+ if not self._socket:
+ raise ConnectionError("Socket connection is not established")
+ packed_data = self.pack_data(data)
+ self._socket.sendall(packed_data)
+
+ def recv_data(self) -> Dict[str, Any]:
+ if not self._socket:
+ raise ConnectionError("Socket connection is not established")
+ response = self._socket.recv(self.SOCKET_CHUNK_SIZE)
+ return self.unpack_data(response)
+
+ # --- Background Processing --- #
+ def start_bg_processing(self) -> None:
+ if not self._socket:
+ raise ConnectionError("Socket connection is not established")
+ if self._bg_processing:
+ raise ValueError("Background processing is already running")
+
+ self._bg_processing = True
+ loop = asyncio.new_event_loop()
+ self._bg_thread = threading.Thread(target=self._start_event_loop, args=(loop,), daemon=True)
+ self._bg_thread.start()
+ asyncio.run_coroutine_threadsafe(self._bg_receive(), loop)
+
+ def stop_bg_processing(self) -> None:
+ if self._bg_processing:
+ self._bg_processing = False
+ if self._bg_thread:
+ loop = asyncio.get_event_loop()
+ loop.call_soon_threadsafe(loop.stop)
+ self._bg_thread.join()
+ self._bg_thread = None
+
+ async def _bg_receive(self) -> None:
+ ev_manager: EventManager = EventManager.get_instance()
+ while self._bg_processing:
+ data: dict = await asyncio.get_event_loop().run_in_executor(None, self.recv_data)
+ await ev_manager.call_event(event_name=data.get('ev_type', None), **data)
+
+ def _start_event_loop(self, loop: asyncio.AbstractEventLoop) -> None:
+ asyncio.set_event_loop(loop)
+ loop.run_forever()
+
+ # --- Cleanup --- #
+ def _shutdown(self) -> None:
+ self.logout()
+ self.disconnect()
diff --git a/Code/systems/texture_system/__init__.py b/Code/systems/texture_system/__init__.py
new file mode 100644
index 0000000..76df2f1
--- /dev/null
+++ b/Code/systems/texture_system/__init__.py
@@ -0,0 +1 @@
+from systems.texture_system.texture_system import TextureSystem
diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/texture_manager/texture_system.py" b/Code/systems/texture_system/texture_system.py
similarity index 100%
rename from "\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/texture_manager/texture_system.py"
rename to Code/systems/texture_system/texture_system.py
diff --git a/GUI/images/status/failure_16_16.png b/GUI/images/status/failure_16_16.png
new file mode 100644
index 0000000..cfd9918
Binary files /dev/null and b/GUI/images/status/failure_16_16.png differ
diff --git a/GUI/images/status/failure_64_64.png b/GUI/images/status/failure_64_64.png
new file mode 100644
index 0000000..ede13fd
Binary files /dev/null and b/GUI/images/status/failure_64_64.png differ
diff --git a/GUI/windows/AuthDialog.ui b/GUI/windows/AuthDialog.ui
new file mode 100644
index 0000000..f4a2346
--- /dev/null
+++ b/GUI/windows/AuthDialog.ui
@@ -0,0 +1,82 @@
+
+
+ AuthDialog
+
+
+
+ 0
+ 0
+ 400
+ 300
+
+
+
+
+ 400
+ 300
+
+
+
+
+ 400
+ 300
+
+
+
+ Авторизация
+
+
+
+
+ 20
+ 20
+ 360
+ 260
+
+
+
+ -
+
+
+
+ 64
+ 64
+
+
+
+ Введите свой логин
+
+
+
+ -
+
+
+
+ 64
+ 64
+
+
+
+ Введите свой пароль
+
+
+
+ -
+
+
+
+ 64
+ 64
+
+
+
+ Войти
+
+
+
+
+
+
+
+
+
diff --git a/GUI/windows/LoginWindow.ui b/GUI/windows/LoginWindow.ui
new file mode 100644
index 0000000..6a531e9
--- /dev/null
+++ b/GUI/windows/LoginWindow.ui
@@ -0,0 +1,167 @@
+
+
+ LoginWindow
+
+
+ true
+
+
+
+ 0
+ 0
+ 640
+ 640
+
+
+
+
+ 640
+ 640
+
+
+
+
+ 640
+ 640
+
+
+
+
+ 0
+ 0
+
+
+
+ DM-Bot / Login...
+
+
+
+
+ true
+
+
+
+ 410
+ 20
+ 64
+ 64
+
+
+
+
+ 64
+ 64
+
+
+
+
+ 64
+ 64
+
+
+
+ Qt::LeftToRight
+
+
+ Иконка старута
+
+
+ -4
+
+
+
+
+
+ 290
+ 20
+ 110
+ 70
+
+
+
+ Проверить
+
+
+
+
+
+ 20
+ 20
+ 260
+ 70
+
+
+
+ -
+
+
+ false
+
+
+ Введите IP сервера (127.0.0.1)
+
+
+
+ -
+
+
+ Введите порт сервера (5000)
+
+
+
+
+
+
+
+
+ 20
+ 110
+ 590
+ 80
+
+
+
+ -
+
+
+
+ 0
+ 64
+
+
+
+ Войти в аккаунт
+
+
+
+ -
+
+
+
+ 0
+ 64
+
+
+
+ Создать аккаунт
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/GUI/windows/MainWindow.ui b/GUI/windows/MainWindow.ui
new file mode 100644
index 0000000..c6854ca
--- /dev/null
+++ b/GUI/windows/MainWindow.ui
@@ -0,0 +1,31 @@
+
+
+ MainWindow
+
+
+
+ 0
+ 0
+ 800
+ 600
+
+
+
+ MainWindow
+
+
+
+
+
+
+
+
diff --git a/GUI/windows/RegistrationDialog.ui b/GUI/windows/RegistrationDialog.ui
new file mode 100644
index 0000000..ee3ee2c
--- /dev/null
+++ b/GUI/windows/RegistrationDialog.ui
@@ -0,0 +1,86 @@
+
+
+ RegistrationDialog
+
+
+
+ 0
+ 0
+ 400
+ 300
+
+
+
+ Регистрация
+
+
+
+
+ 20
+ 20
+ 360
+ 260
+
+
+
+ -
+
+
+
+ 0
+ 56
+
+
+
+ Введите свой логин
+
+
+
+ -
+
+
+
+ 0
+ 56
+
+
+
+
+
+
+ Введите пароль
+
+
+
+ -
+
+
+
+ 0
+ 56
+
+
+
+ Введите пароль повторно
+
+
+
+ -
+
+
+
+ 0
+ 56
+
+
+
+ Зарегестрироваться
+
+
+
+
+
+
+
+
+
diff --git a/GUI/windows/ServerContentDownloadDialog.ui b/GUI/windows/ServerContentDownloadDialog.ui
new file mode 100644
index 0000000..31ff26a
--- /dev/null
+++ b/GUI/windows/ServerContentDownloadDialog.ui
@@ -0,0 +1,60 @@
+
+
+ ServerContentDownloadDialog
+
+
+
+ 0
+ 0
+ 400
+ 150
+
+
+
+
+ 400
+ 150
+
+
+
+
+ 400
+ 150
+
+
+
+ Загрузка контента сервера
+
+
+
+
+ 20
+ 20
+ 360
+ 80
+
+
+
+ -
+
+
+ false
+
+
+ Загрузка контента сервера...
+
+
+
+ -
+
+
+ 24
+
+
+
+
+
+
+
+
+
diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/tests/Texture/TextureSystem.py" b/Tests/Texture/TextureSystem.py
similarity index 99%
rename from "\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/tests/Texture/TextureSystem.py"
rename to Tests/Texture/TextureSystem.py
index 59d8e08..e9e54c2 100644
--- "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/tests/Texture/TextureSystem.py"
+++ b/Tests/Texture/TextureSystem.py
@@ -7,7 +7,7 @@
import yaml
from PIL import Image
-from Code.texture_manager import TextureSystem
+from Code.texture_system import TextureSystem
class TestTextureSystem(unittest.TestCase):
diff --git a/Tests/Texture/__init__.py b/Tests/Texture/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..2a91214
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,5 @@
+msgpack
+Pillow
+PyQt6
+PyYAML
+requests
diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/Sprites/dev/Test.dms/info.yml" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/Sprites/dev/Test.dms/info.yml"
deleted file mode 100644
index c02bfe7..0000000
--- "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/Sprites/dev/Test.dms/info.yml"
+++ /dev/null
@@ -1,19 +0,0 @@
-Author: "themanyfaceddemon"
-
-License: "NONE. IT IS TEST FOLD"
-
-Sprites:
- - name: "sprite1"
- size: {x: 32, y: 32}
- is_mask: false
- frames: 0
-
- - name: "sprite2"
- size: {x: 32, y: 32}
- is_mask: false
- frames: 0
-
- - name: "sprite3"
- size: {x: 32, y: 32}
- is_mask: false
- frames: 0
diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/Sprites/dev/Test.dms/sprite1.png" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/Sprites/dev/Test.dms/sprite1.png"
deleted file mode 100644
index f9b3bf2..0000000
Binary files "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/Sprites/dev/Test.dms/sprite1.png" and /dev/null differ
diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/Sprites/dev/Test.dms/sprite2.png" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/Sprites/dev/Test.dms/sprite2.png"
deleted file mode 100644
index f9b3bf2..0000000
Binary files "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/Sprites/dev/Test.dms/sprite2.png" and /dev/null differ
diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/Sprites/dev/Test.dms/sprite3.png" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/Sprites/dev/Test.dms/sprite3.png"
deleted file mode 100644
index f9b3bf2..0000000
Binary files "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/Sprites/dev/Test.dms/sprite3.png" and /dev/null differ
diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/Sprites/dev/error.dms/info.yml" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/Sprites/dev/error.dms/info.yml"
deleted file mode 100644
index 24df215..0000000
--- "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/Sprites/dev/error.dms/info.yml"
+++ /dev/null
@@ -1,9 +0,0 @@
-Author: "themanyfaceddemon"
-
-License: "NONE. IT IS ERROR FOLD"
-
-Sprites:
- - name: "not_found"
- size: {x: 64, y: 64}
- is_mask: false
- frames: 0
diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/Sprites/dev/error.dms/not_found.png" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/Sprites/dev/error.dms/not_found.png"
deleted file mode 100644
index 08b9eb9..0000000
Binary files "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/Sprites/dev/error.dms/not_found.png" and /dev/null differ
diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/Sprites/icons/exe-updater-icon.png" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/Sprites/icons/exe-updater-icon.png"
deleted file mode 100644
index d5a831e..0000000
Binary files "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/Sprites/icons/exe-updater-icon.png" and /dev/null differ
diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/Sprites/in_process_banner.png" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/Sprites/in_process_banner.png"
deleted file mode 100644
index 3050efe..0000000
Binary files "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/Sprites/in_process_banner.png" and /dev/null differ
diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/Sprites/in_process_banner2.png" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/Sprites/in_process_banner2.png"
deleted file mode 100644
index 35f5817..0000000
Binary files "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/Sprites/in_process_banner2.png" and /dev/null differ
diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/handle_show_popup.py" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/handle_show_popup.py"
deleted file mode 100644
index 973cec6..0000000
--- "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/handle_show_popup.py"
+++ /dev/null
@@ -1,7 +0,0 @@
-from flask_socketio import emit
-from html_code.socketio_regester import socketio
-
-
-@socketio.on('show_popup')
-def handle_show_popup(data):
- emit('popup_notification', {'message': data['message'], 'type': data['type']}, broadcast=True)
diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/pages/__init__.py" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/pages/__init__.py"
deleted file mode 100644
index c276058..0000000
--- "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/pages/__init__.py"
+++ /dev/null
@@ -1,2 +0,0 @@
-from html_code.pages.changelog import render_changelog_main_page
-from html_code.pages.settings_main import render_settings_main_page
diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/pages/changelog.py" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/pages/changelog.py"
deleted file mode 100644
index 18f3c21..0000000
--- "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/pages/changelog.py"
+++ /dev/null
@@ -1,34 +0,0 @@
-import os
-
-import yaml
-from flask import render_template, request, url_for
-
-
-def render_changelog_main_page():
- base_dir = os.path.dirname(os.path.abspath(__file__))
- changelog_path = os.path.join(base_dir, '..', '..', '..', 'changelog.yml')
-
- with open(changelog_path, 'r', encoding='utf-8') as file:
- data = yaml.safe_load(file)
- changelog = data.get('changelog', [])
-
- # Параметры пагинации
- page = request.args.get('page', 1, type=int)
- per_page = 5
- total = len(changelog)
- pages = (total // per_page) + (1 if total % per_page else 0)
-
- # Срез данных для текущей страницы
- start = (page - 1) * per_page
- end = start + per_page
- changelog_paginated = changelog[start:end]
-
- # Формируем данные для пагинации
- pagination = {
- 'prev': url_for('main.changelog_page', page=page-1) if page > 1 else None,
- 'next': url_for('main.changelog_page', page=page+1) if page < pages else None,
- 'pages': [(p, url_for('main.changelog_page', page=p)) for p in range(1, pages+1)],
- 'current_page': page
- }
-
- return render_template('changelog.html', changelog=changelog_paginated, pagination=pagination)
diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/pages/settings_main.py" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/pages/settings_main.py"
deleted file mode 100644
index 9016a4e..0000000
--- "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/pages/settings_main.py"
+++ /dev/null
@@ -1,42 +0,0 @@
-import asyncio
-
-from auto_updater import needs_update
-from db_work import SettingsManager
-from flask import render_template
-from html_code.socketio_regester import socketio
-
-
-def render_settings_main_page():
- return render_template('settings/settings_main.html')
-
-# Работа с автоматическим обновлением
-@socketio.on('settingSetUpAutoUpdate')
-def get_auto_update(data) -> None:
- flag: bool
-
- try:
- asyncio.run(SettingsManager().set_setting("app.auto_update", data))
- flag = True
-
- except Exception:
- flag = False
-
- socketio.emit("settingAutoUpdateStatusPopup", flag)
- handle_check_auto_update_status()
-
-@socketio.on('settingCheckAutoUpdate')
-def handle_check_auto_update_status():
- auto_update = asyncio.run(SettingsManager().get_setting("app.auto_update"))
- socketio.emit('settingAutoUpdateStatusUpdate', auto_update)
-
-# Получение информации о версиях
-@socketio.on('getVersionInfo')
-def handle_get_version_info():
- _, current_version, latest_version = needs_update()
-
- version_info = {
- "currentVersion": current_version,
- "latestVersion": latest_version
- }
-
- socketio.emit('versionInfo', version_info)
diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/tests/Texture/DMSValidator.py" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/tests/Texture/DMSValidator.py"
deleted file mode 100644
index 1c3363d..0000000
--- "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/tests/Texture/DMSValidator.py"
+++ /dev/null
@@ -1,130 +0,0 @@
-import os
-import unittest
-
-from Code.texture_manager import (DMSValidator, InvalidSpriteError,
- SpriteValidationError)
-
-
-class TestDMSValidator(unittest.TestCase):
- def setUp(self):
- self.base_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
- self.test_dir = os.path.join(self.base_path, 'test_sprites')
- self.dms_dir = os.path.join(self.test_dir, 'test.dms')
- self.info_yml_path = os.path.join(self.dms_dir, 'info.yml')
-
- os.makedirs(self.dms_dir, exist_ok=True)
-
- with open(self.info_yml_path, 'w') as f:
- f.write("""
- Author: "themanyfaceddemon"
- License: "NONE. IT IS TEST"
- Sprites:
- - name: "sprite1"
- size: {x: 10, y: 20}
- is_mask: false
- frames: 5
- - name: "sprite2"
- size: {x: 15, y: 25}
- is_mask: true
- frames: 10
- """)
-
- for sprite in ['sprite1', 'sprite2']:
- open(os.path.join(self.dms_dir, f"{sprite}.png"), 'a').close()
-
- def tearDown(self):
- for root, dirs, files in os.walk(self.test_dir, topdown=False):
- for name in files:
- os.remove(os.path.join(root, name))
- for name in dirs:
- os.rmdir(os.path.join(root, name))
-
- os.rmdir(self.test_dir)
-
- def test_validate_dms(self):
- self.assertTrue(DMSValidator.validate_dms(self.test_dir, 'test.dms'))
-
- def test_validate_all_dms(self):
- self.assertTrue(DMSValidator.validate_all_dms(self.test_dir))
-
- def test_missing_info_yml(self):
- os.remove(self.info_yml_path)
- with self.assertRaises(SpriteValidationError) as context:
- DMSValidator.validate_dms(self.test_dir, 'test.dms')
-
- self.assertTrue("info.yml not found" in str(context.exception))
-
- def test_invalid_sprite_format(self):
- with open(self.info_yml_path, 'w') as f:
- f.write("""
- Author: Test Author
- License: Test License
- Sprites:
- - name: sprite1
- size: {x: 10}
- is_mask: false
- frames: 5
- """)
-
- with self.assertRaises(InvalidSpriteError) as context:
- DMSValidator.validate_dms(self.test_dir, 'test.dms')
-
- self.assertTrue("Each sprite 'size' must be a dictionary with 'x' and 'y' fields" in str(context.exception))
-
- def test_missing_sprite_file(self):
- os.remove(os.path.join(self.dms_dir, 'sprite1.png'))
- with self.assertRaises(InvalidSpriteError) as context:
- DMSValidator.validate_dms(self.test_dir, 'test.dms')
-
- self.assertTrue("Missing files" in str(context.exception))
-
- def test_check_forbidden_files(self):
- forbidden_file_path = os.path.join(self.dms_dir, '_compiled_file.txt')
- with open(forbidden_file_path, 'w') as f:
- f.write("This is a forbidden file.")
-
- with self.assertRaises(InvalidSpriteError) as context:
- DMSValidator.validate_dms(self.test_dir, 'test.dms')
-
- self.assertTrue("Forbidden file or directory found" in str(context.exception))
-
- def test_sprite_name_contains_forbidden_pattern(self):
- with open(self.info_yml_path, 'w') as f:
- f.write("""
- Author: "themanyfaceddemon"
- License: "NONE. IT IS TEST"
- Sprites:
- - name: "_compiled_sprite1"
- size: {x: 10, y: 20}
- is_mask: false
- frames: 5
- - name: "sprite2"
- size: {x: 15, y: 25}
- is_mask: true
- frames: 10
- """)
-
- with self.assertRaises(InvalidSpriteError) as context:
- DMSValidator.validate_dms(self.test_dir, 'test.dms')
-
- self.assertTrue("contains forbidden pattern" in str(context.exception))
-
- def test_non_existent_directory(self):
- non_existent_dir = os.path.join(self.test_dir, 'non_existent.dms')
- with self.assertRaises(SpriteValidationError) as context:
- DMSValidator.validate_dms(self.test_dir, 'non_existent.dms')
-
- self.assertTrue("DMS does not exist" in str(context.exception))
-
- def test_not_a_directory(self):
- not_a_directory_path = os.path.join(self.test_dir, 'not_a_directory.dms')
- with open(not_a_directory_path, 'w') as f:
- f.write("This is not a directory.")
-
- with self.assertRaises(SpriteValidationError) as context:
- DMSValidator.validate_dms(self.test_dir, 'not_a_directory.dms')
-
- self.assertTrue("DMS is not a directory" in str(context.exception))
-
-if __name__ == '__main__':
- unittest.main()
diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/tests/Texture/ValidateTexture.py" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/tests/Texture/ValidateTexture.py"
deleted file mode 100644
index 5a77a88..0000000
--- "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/tests/Texture/ValidateTexture.py"
+++ /dev/null
@@ -1,28 +0,0 @@
-import os
-import unittest
-
-from Code.texture_manager import (DMSValidator, InvalidSpriteError,
- SpriteValidationError)
-
-
-class TestTextureFolders(unittest.TestCase):
- def setUp(self):
- self.base_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'Sprites'))
-
- def test_validate_all_dms_folders(self):
- try:
- result = DMSValidator.validate_all_dms(self.base_path)
- self.assertTrue(result)
-
- except (SpriteValidationError, InvalidSpriteError) as e:
- error_message = f"validate_all_dms raised an exception: {e.message}, Path: {e.path}"
- if isinstance(e, InvalidSpriteError):
- if e.missing_files:
- error_message += f", Missing Files: {e.missing_files}"
- if e.missing_field:
- error_message += f", Missing Field: {e.missing_field}"
-
- self.fail(error_message)
-
-if __name__ == '__main__':
- unittest.main()
diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/texture_manager/__init__.py" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/texture_manager/__init__.py"
deleted file mode 100644
index 488c3b3..0000000
--- "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/texture_manager/__init__.py"
+++ /dev/null
@@ -1,4 +0,0 @@
-from texture_manager.texture_errors import (InvalidSpriteError,
- SpriteValidationError)
-from texture_manager.texture_system import TextureSystem
-from texture_manager.texture_validator import DMSValidator
diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/texture_manager/texture_errors.py" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/texture_manager/texture_errors.py"
deleted file mode 100644
index db9aac9..0000000
--- "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/texture_manager/texture_errors.py"
+++ /dev/null
@@ -1,32 +0,0 @@
-from typing import List
-
-
-class SpriteValidationError(Exception):
- """Базовый класс для исключений, связанных с валидацией спрайтов.
-
- Этот класс расширяет стандартный класс исключений и добавляет дополнительную информацию о пути к файлу, где произошла ошибка.
-
- Args:
- message (str): Сообщение об ошибке, описывающее, что пошло не так.
- path (str): Путь к файлу, в котором произошла ошибка.
- """
- def __init__(self, message: str, path: str):
- super().__init__(message)
- self.message = message
- self.path = path
-
-class InvalidSpriteError(SpriteValidationError):
- """Исключение для случаев, когда файл info.yml отсутствует или содержит неверные данные.
-
- Этот класс расширяет SpriteValidationError и добавляет информацию о недостающих файлах или полях.
-
- Args:
- message (str): Сообщение об ошибке, описывающее, что пошло не так.
- path (str): Путь к файлу, в котором произошла ошибка.
- missing_files (List[str], optional): Список недостающих файлов. По умолчанию None.
- missing_field (str, optional): Отсутствующее поле в файле info.yml. По умолчанию None.
- """
- def __init__(self, message: str, path: str, missing_files: List[str] = None, missing_field: str = None):
- super().__init__(message, path)
- self.missing_files = missing_files
- self.missing_field = missing_field
diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/texture_manager/texture_validator.py" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/texture_manager/texture_validator.py"
deleted file mode 100644
index bcd0a3e..0000000
--- "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/texture_manager/texture_validator.py"
+++ /dev/null
@@ -1,194 +0,0 @@
-import os
-from typing import Dict, List, Union
-
-import yaml
-from texture_manager.texture_errors import (InvalidSpriteError,
- SpriteValidationError)
-
-
-class DMSValidator:
- __slots__ = []
- INFO_REQUIRED_FIELDS: List[str] = ['Author', 'License', 'Sprites']
- SPRITE_REQUIRED_FIELDS: List[str] = ['name', 'size', 'is_mask', 'frames']
-
- COMPILED_PATTERNS = ['_compiled', '_compiled_']
-
- @staticmethod
- def _raise_dms_file(path: str) -> None:
- """Проверяет существование директории DMS и является ли она директорией.
-
- Args:
- path (str): Путь к директории.
-
- Raises:
- SpriteValidationError: Если DMS не существует или не является директорией.
- """
- if not os.path.exists(path):
- raise SpriteValidationError("DMS does not exist", path)
-
- if not os.path.isdir(path):
- raise SpriteValidationError("DMS is not a directory", path)
-
- @staticmethod
- def _load_dms_info(path: str) -> Dict:
- """Загружает информацию из файла info.yml.
-
- Args:
- path (str): Путь к директории DMS.
-
- Raises:
- SpriteValidationError: Если файл info.yml не найден или отсутствуют обязательные поля.
-
- Returns:
- Dict: Содержимое info.yml.
- """
- yml_path = os.path.join(path, "info.yml")
- if not os.path.isfile(yml_path):
- raise SpriteValidationError("info.yml not found", path)
-
- with open(yml_path, 'r', encoding='utf-8') as file:
- info_yml = yaml.safe_load(file)
-
- for field in DMSValidator.INFO_REQUIRED_FIELDS:
- if field not in info_yml:
- raise SpriteValidationError(f"Missing required field: {field}", yml_path)
-
- return info_yml
-
- @staticmethod
- def _validate_sprites_format(sprites: List[Dict], info_yml_path: str) -> None:
- """Проверяет формат спрайтов в info.yml.
-
- Args:
- sprites (List[Dict]): Список спрайтов.
- info_yml_path (str): Путь к info.yml.
-
- Raises:
- InvalidSpriteError: Если формат спрайтов некорректен или отсутствуют обязательные поля.
- """
- if not isinstance(sprites, list) or not all(isinstance(item, dict) for item in sprites):
- raise InvalidSpriteError(f"Field 'Sprites' must be a list of dictionaries", info_yml_path)
-
- for sprite in sprites:
- for field in DMSValidator.SPRITE_REQUIRED_FIELDS:
- if field not in sprite:
- raise InvalidSpriteError(f"Missing required field in sprite: {field}", info_yml_path)
-
- if not isinstance(sprite['size'], dict) or not all(k in sprite['size'] for k in ['x', 'y']):
- raise InvalidSpriteError("Each sprite 'size' must be a dictionary with 'x' and 'y' fields", info_yml_path)
-
- frames = sprite['frames']
- if not isinstance(frames, int) or frames < 0:
- raise InvalidSpriteError(f"Frame count must be a non-negative integer", info_yml_path)
-
- # Проверка на имя спрайта
- sprite_name = sprite['name']
- for pattern in DMSValidator.COMPILED_PATTERNS:
- if pattern in sprite_name:
- raise InvalidSpriteError(f"Sprite name '{sprite_name}' contains forbidden pattern: {pattern}", info_yml_path)
-
- @staticmethod
- def _check_files_exist(folder_path: str, sprites: List[Dict[str, Union[str, Dict[str, int], bool]]]) -> None:
- """Проверяет наличие файлов спрайтов в директории.
-
- Args:
- folder_path (str): Путь к директории.
- sprites (List[Dict[str, Union[str, Dict[str, int], bool]]]): Список спрайтов.
-
- Raises:
- InvalidSpriteError: Если один или несколько файлов спрайтов отсутствуют.
- """
- missing_files = []
-
- for sprite in sprites:
- file_name = sprite['name']
- file_path = os.path.join(folder_path, f"{file_name}.png")
- if not os.path.isfile(file_path):
- missing_files.append(f"{file_name}.png")
-
- if missing_files:
- raise InvalidSpriteError("Missing files", folder_path, missing_files=missing_files)
-
- @staticmethod
- def _check_forbidden_files(folder_path: str) -> None:
- """Проверяет наличие запрещенных файлов или директорий.
-
- Args:
- folder_path (str): Путь к директории.
-
- Raises:
- InvalidSpriteError: Если найдены запрещенные файлы или директории.
- """
- for root, dirs, files in os.walk(folder_path):
- for pattern in DMSValidator.COMPILED_PATTERNS:
- for name in files + dirs:
- if pattern in name:
- raise InvalidSpriteError(f"Forbidden file or directory found: {name}", folder_path)
-
- @staticmethod
- def validate_dms_dirrect(dms_path: str) -> bool:
- """Валидирует директорию DMS.
-
- Args:
- dms_path (str): Путь к директории DMS.
-
- Returns:
- bool: True, если валидация прошла успешно.
-
- Raises:
- SpriteValidationError: Если директория не существует, не является директорией или некорректна структура.
- """
- DMSValidator._raise_dms_file(dms_path)
-
- info_yml = DMSValidator._load_dms_info(dms_path)
- DMSValidator._validate_sprites_format(info_yml['Sprites'], dms_path)
- DMSValidator._check_files_exist(dms_path, info_yml['Sprites'])
- DMSValidator._check_forbidden_files(dms_path)
-
- return True
-
- @staticmethod
- def validate_dms(base_path: str, dms_path: str) -> bool:
- """Валидирует конкретную директорию DMS относительно базового пути.
-
- Args:
- base_path (str): Базовый путь к директории с текстурами.
- dms_path (str): Путь к директории DMS относительно базового пути.
-
- Returns:
- bool: True, если валидация прошла успешно.
-
- Raises:
- SpriteValidationError: Если директория не существует, не является директорией или некорректна структура.
- """
- full_dms_path = os.path.join(base_path, dms_path.replace('/', os.sep))
-
- DMSValidator._raise_dms_file(full_dms_path)
-
- info_yml = DMSValidator._load_dms_info(full_dms_path)
- DMSValidator._validate_sprites_format(info_yml['Sprites'], full_dms_path)
- DMSValidator._check_files_exist(full_dms_path, info_yml['Sprites'])
- DMSValidator._check_forbidden_files(full_dms_path)
-
- return True
-
- @staticmethod
- def validate_all_dms(base_path: str) -> bool:
- """Валидирует все директории DMS в базовой директории.
-
- Args:
- base_path (str): Базовый путь к директории с текстурами.
-
- Returns:
- bool: True, если валидация всех директорий прошла успешно.
-
- Raises:
- SpriteValidationError: Если хотя бы одна директория некорректна.
- """
- for item in os.listdir(base_path):
- item_path = os.path.join(base_path, item)
-
- if os.path.isdir(item_path) and item.endswith('.dms'):
- DMSValidator.validate_dms(base_path, item)
-
- return True
diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/css/collapsible.css" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/css/collapsible.css"
deleted file mode 100644
index 814ab9b..0000000
--- "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/css/collapsible.css"
+++ /dev/null
@@ -1,28 +0,0 @@
-.collapsible {
- background-color: #333;
- color: white;
- cursor: pointer;
- padding: 15px;
- width: 100%;
- border: none;
- text-align: left;
- outline: none;
- font-size: 15px;
- margin-top: 10px; /* Верхний отступ */
- margin-bottom: 5px; /* Нижний отступ */
- border-radius: 4px; /* Скругленные углы */
-}
-
-.active, .collapsible:hover {
- background-color: #444;
-}
-
-.collapsible-content {
- padding: 15px;
- display: none;
- overflow: hidden;
- background-color: #111;
- border: 1px solid #e0e0e0; /* Добавляем границу */
- border-radius: 4px; /* Скругленные углы */
- margin-bottom: 10px; /* Нижний отступ */
-}
diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/css/footer.css" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/css/footer.css"
deleted file mode 100644
index fafdcf0..0000000
--- "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/css/footer.css"
+++ /dev/null
@@ -1,11 +0,0 @@
-/* Стили для нижней панели */
-.footer {
- background-color: #333; /* Тёмно-серый фон */
- color: white; /* Белый цвет текста */
- text-align: center; /* Выравнивание текста по центру */
- position: fixed; /* Фиксированное положение */
- bottom: 5px; /* Отступ снизу */
- left: 5px; /* Отступ слева */
- right: 5px; /* Отступ справа */
- width: auto; /* Ширина по содержимому */
-}
diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/css/main.css" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/css/main.css"
deleted file mode 100644
index 2dcd067..0000000
--- "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/css/main.css"
+++ /dev/null
@@ -1,197 +0,0 @@
-/*
-Общая цветовая палитка сайта: черный, белый, оранжевый.
-Индикаторные цвета: ярко красный и зелёный.
-*/
-body {
- background-color: #111;
- color: white;
- font-family: Arial, sans-serif;
- margin: 5px;
- padding: 5px;
-}
-
-label {
- display: block;
- margin-bottom: 10px;
-}
-
-input[type="checkbox"] {
- width: 20px;
- height: 20px;
- margin-right: 10px;
- vertical-align: middle;
- background-color: #333;
- border: 2px solid #ffa500;
- border-radius: 4px;
- cursor: pointer;
- appearance: none;
- position: relative;
-}
-
-input[type="checkbox"]:checked {
- background-color: #ffa500;
-}
-
-input[type="checkbox"]:checked::before {
- content: '';
- position: absolute;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- width: 12px;
- height: 12px;
- background-color: #000;
- clip-path: polygon(14% 44%, 0% 64%, 44% 100%, 100% 0%, 77% 0%, 39% 86%);
-}
-
-input[type="text"] {
- padding: 10px;
- margin-top: 5px;
- margin-bottom: 20px;
- background-color: #333;
- color: white;
- border: 1px solid #ffa500;
- border-radius: 4px;
- box-sizing: border-box;
-}
-
-input[type="submit"] {
- background-color: #333;
- color: white;
- padding: 10px 20px;
- border: none;
- border-radius: 4px;
- cursor: pointer;
- transition: background-color 0.3s ease;
- display: inline-block;
- text-align: center;
-}
-
-input[type="submit"]:hover {
- background-color: #222;
-}
-
-/* Стили для ссылок */
-a {
- color: #ff7e00;
- text-decoration: none;
-}
-
-a:visited {
- color: #ffa500;
-}
-
-a:hover {
- text-decoration: underline;
-}
-
-/* Стили для кнопок */
-button {
- background-color: #333;
- color: white;
- padding: 10px 20px;
- border: none;
- border-radius: 4px;
- cursor: pointer;
- transition: background-color 0.3s ease;
-}
-
-button:hover {
- background-color: #222;
-}
-
-/* Стили для шапки */
-.header {
- background-color: #f2f2f2;
- padding: 20px;
- text-align: center;
-}
-
-/* Стили для карточек */
-.card {
- border: 1px solid #e0e0e0;
- border-radius: 8px;
- overflow: hidden;
- margin: 20px;
- box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
-}
-
-.card-content {
- padding: 15px;
-}
-
-/* Добавление стилей для заголовков внутри карточек */
-.card-content h1,
-.card-content h2,
-.card-content h3,
-.card-content h4,
-.card-content h5,
-.card-content h6 {
- margin-top: 0;
- margin-bottom: 15px;
-}
-
-/* Стили для таблицы */
-table {
- width: 100%;
- border-collapse: collapse;
- margin-top: 20px;
- margin-bottom: 20px;
-}
-
-/* Стили для заголовков таблицы */
-th {
- background-color: #333;
- color: white;
- padding: 10px 15px;
- text-align: left;
- border: 1px solid #555;
-}
-
-/* Стили для ячеек таблицы */
-td {
- padding: 8px 15px;
- border: 1px solid #e0e0e0;
-}
-
-/* Стили для полосы прокрутки */
-::-webkit-scrollbar {
- width: 12px;
- background-color: #111;
-}
-
-::-webkit-scrollbar-thumb {
- background-color: #ffa500;
- border-radius: 6px;
- border: 3px solid #111;
-}
-
-::-webkit-scrollbar-thumb:hover {
- background-color: #ff7e00;
-}
-
-.image-center {
- display: block;
- margin: 20px auto;
- max-width: 100%;
- height: auto;
-}
-
-/* Стили для блока pre */
-pre {
- background-color: #333;
- color: white;
- padding: 10px;
- border-radius: 4px;
- overflow: auto;
- margin: 20px 0;
- border: 1px solid #ffa500;
-}
-
-/* Стили для блока code */
-code {
- background-color: #ffffff00;
- color: #ff7e00;
- padding: 2px 4px;
- border-radius: 4px;
-}
diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/css/nav_menu.css" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/css/nav_menu.css"
deleted file mode 100644
index 80b94f6..0000000
--- "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/css/nav_menu.css"
+++ /dev/null
@@ -1,48 +0,0 @@
-/* Стили для навигационного меню */
-.nav-menu {
- list-style-type: none; /* Убираем маркеры списка */
- margin: 0; /* Убираем внешние отступы */
- padding: 0; /* Убираем внутренние отступы */
- overflow: hidden; /* Прячем содержимое, которое не помещается */
- background-color: #333; /* Тёмно-серый фон */
- display: flex;
- justify-content: space-between; /* Распределяем пространство между элементами */
- align-items: center; /* Выравниваем элементы по центру по вертикали */
-}
-
-.nav-item {
- float: left; /* Элементы списка располагаются горизонтально */
-}
-
-.nav-item a {
- display: block; /* Блочный элемент */
- text-align: center; /* Выравнивание текста по центру */
- padding: 14px 16px; /* Внутренние отступы */
- text-decoration: none; /* Убираем подчеркивание */
- color: white; /* Белый текст */
- transition: background-color 0.3s ease; /* Плавное изменение цвета фона */
-}
-
-.nav-item a:hover {
- background-color: #222; /* Темно-серый фон при наведении */
-}
-
-/* Кнопка выключения меню */
-.shutdown-button {
- background-color: transparent; /* Прозрачный фон */
- border: none;
- color: #ff7e00; /* Оранжевый цвет */
- font-size: 20px; /* Размер иконки */
- cursor: pointer;
- transition: color 0.3s ease;
-}
-
-.shutdown-button:hover {
- color: #e69500; /* Темно-оранжевый цвет при наведении */
-}
-
-.shutdown-icon {
- width: 20px;
- height: 20px;
- fill: currentColor;
-}
diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/css/popup.css" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/css/popup.css"
deleted file mode 100644
index 6996a25..0000000
--- "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/css/popup.css"
+++ /dev/null
@@ -1,36 +0,0 @@
-.popup {
- display: block;
- position: fixed;
- right: 20px;
- padding: 10px;
- margin-top: 10px;
- z-index: 1000;
- width: 200px;
- box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
- cursor: pointer;
- border-radius: 4px; /* Скругленные углы */
-}
-
-.info {
- background-color: #333; /* Тёмно-серый фон */
- color: white; /* Белый текст */
- border: 1px solid #ffa500; /* Оранжевая граница */
-}
-
-.success {
- background-color: #333; /* Тёмно-серый фон */
- color: rgb(0, 255, 0); /* Зелёный текст */
- border: 1px solid rgb(0, 255, 0); /* Зелёная граница */
-}
-
-.warning {
- background-color: #333; /* Тёмно-серый фон */
- color: yellow; /* Жёлтый текст */
- border: 1px solid yellow; /* Жёлтая граница */
-}
-
-.error {
- background-color: #333; /* Тёмно-серый фон */
- color: red; /* Красный текст */
- border: 1px solid red; /* Красная граница */
-}
\ No newline at end of file
diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/icons/shutdown_icon.png" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/icons/shutdown_icon.png"
deleted file mode 100644
index 7e7fe06..0000000
Binary files "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/icons/shutdown_icon.png" and /dev/null differ
diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/icons/site_icon.png" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/icons/site_icon.png"
deleted file mode 100644
index 90faa60..0000000
Binary files "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/icons/site_icon.png" and /dev/null differ
diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/js/base.js" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/js/base.js"
deleted file mode 100644
index 1a5e1d7..0000000
--- "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/js/base.js"
+++ /dev/null
@@ -1 +0,0 @@
-var socket = io.connect('http://' + document.domain + ':' + location.port);
diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/js/collapsible.js" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/js/collapsible.js"
deleted file mode 100644
index 044e660..0000000
--- "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/js/collapsible.js"
+++ /dev/null
@@ -1,12 +0,0 @@
-var coll = document.getElementsByClassName("collapsible");
-for (var i = 0; i < coll.length; i++) {
- coll[i].addEventListener("click", function() {
- this.classList.toggle("active");
- var content = this.nextElementSibling;
- if (content.style.display === "block") {
- content.style.display = "none";
- } else {
- content.style.display = "block";
- }
- });
-}
diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/js/player.js" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/js/player.js"
deleted file mode 100644
index d94173f..0000000
--- "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/js/player.js"
+++ /dev/null
@@ -1,63 +0,0 @@
-var socket = io.connect('http://' + document.domain + ':' + location.port);
-
-// Отправка запроса на получение списка игроков
-socket.emit('getAllPlayers');
-
-// Обработка ответа и отображение списка игроков
-socket.on('allPlayers', function(players) {
- const playerButtons = document.getElementById('playerButtons');
- playerButtons.innerHTML = ''; // Очищаем кнопки
-
- players.forEach(function(player) {
- const button = document.createElement('button');
- button.textContent = player.name;
- button.setAttribute('data-id', player.id);
- button.classList.add('player-button'); // Добавляем класс для стилизации
-
- // Добавляем обработчик клика для каждой кнопки
- button.addEventListener('click', function() {
- showPlayerInfo(player);
- });
-
- playerButtons.appendChild(button);
- });
-});
-
-// Функция для отображения информации о выбранном игроке
-function showPlayerInfo(player) {
- const playerInfoDiv = document.getElementById('playerInfo');
- let playerBaseData = '';
-
- // Базовая информация об игроке
- playerBaseData = `
-
- Discord параметры Значение
- Discord name ${player.name}
- Discord ID ${player.id}
-
- `
-
- // Формируем HTML код информации об игроке
- playerInfoDiv.innerHTML = `
- ${playerBaseData}
- `;
-}
-
-// Поиск игроков по ID или имени
-document.getElementById('searchInput').addEventListener('input', function() {
- const searchText = this.value.toLowerCase();
-
- const playerButtons = document.getElementById('playerButtons');
- const buttons = playerButtons.getElementsByTagName('button');
-
- Array.from(buttons).forEach(function(button) {
- const text = button.textContent.toLowerCase();
- const id = button.getAttribute('data-id').toLowerCase();
-
- if (text.includes(searchText) || id.includes(searchText)) {
- button.style.display = 'block';
- } else {
- button.style.display = 'none';
- }
- });
-});
diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/js/popup.js" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/js/popup.js"
deleted file mode 100644
index 0e55e98..0000000
--- "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/js/popup.js"
+++ /dev/null
@@ -1,34 +0,0 @@
-var socket = io();
-var popups = [];
-
-socket.on('popup_notification', function(data) {
- var popup = document.createElement('div');
- popup.className = 'popup ' + data.type;
- popup.innerHTML = '' + data.message + ' ';
- popup.onclick = function() { closePopup(popup); };
- document.body.appendChild(popup);
- popups.push(popup);
- adjustPopupPositions();
- setTimeout(function() { closePopup(popup); }, 5000); // Закрыть через 5 секунд
-});
-
-function showPopup(type, message) {
- socket.emit('show_popup', {'type': type, 'message': message});
-}
-
-function closePopup(element) {
- element.style.display = 'none';
- popups = popups.filter(popup => popup !== element);
- element.remove();
- adjustPopupPositions();
-}
-
-function adjustPopupPositions() {
- var topOffset = 20;
- popups.forEach(function(popup) {
- popup.style.top = topOffset + 'px';
- topOffset += popup.offsetHeight + 10;
- });
-}
-
-window.showPopup = showPopup;
diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/js/settings/main.js" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/js/settings/main.js"
deleted file mode 100644
index 8e8446e..0000000
--- "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/js/settings/main.js"
+++ /dev/null
@@ -1,108 +0,0 @@
-var socket = io.connect('http://' + document.domain + ':' + location.port);
-
-// Функции загрузки данных
-document.addEventListener("DOMContentLoaded", function() {
- checkTokenStatus();
- checkAutoStartStatus();
- checkAutoUpdateStatus();
- getVersionInfo();
-});
-
-// Работа с токеном
-function saveTokenSettings(event) {
- event.preventDefault();
-
- var botToken = document.getElementById("token").value;
- socket.emit('settingSetUpToken', botToken);
-}
-
-socket.on('settingTokenStatusPopup', function(data) {
- if (data) {
- showPopup('success', 'Настройка токена сохранена');
- } else {
- showPopup('error', 'Введён неверный токен бота');
- }
-});
-
-function checkTokenStatus() {
- socket.emit('settingCheckToken');
-}
-
-socket.on('settingTokenStatusUpdate', function(data) {
- if (data) {
- document.getElementById("tokenStatus").textContent = "Токен существует";
- } else {
- document.getElementById("tokenStatus").textContent = "Токен не существует";
- }
-});
-
-// Работа с автозапуском
-function saveAutoStartSetting() {
- var autoStart = document.getElementById("autoStart").checked;
- socket.emit('settingSetUpAutoStart', autoStart);
-}
-
-socket.on('settingAutoStartStatusPopup', function(data) {
- if (data) {
- showPopup('success', 'Настройка автозапуска сохранена');
- } else {
- showPopup('error', 'Ошибка сохранения настройки автозапуска');
- }
-});
-
-function checkAutoStartStatus() {
- socket.emit('settingCheckAutoStart');
-}
-
-socket.on('settingAutoStartStatusUpdate', function(data) {
- document.getElementById("autoStart").checked = data;
-});
-
-// Функция для запуска бота
-function startBot() {
- socket.emit('settingsStartBot');
-}
-
-socket.on('settingsBotStartStatus', function(data) {
- if (data.status === 'success') {
- showPopup('success', 'Бот успешно запущен!');
- } else {
- showPopup('error', 'Ошибка при запуске бота: ' + data.message);
- }
-});
-
-// Функция для сохранения настройки автоматического обновления
-function saveAutoUpdateSetting() {
- var autoUpdate = document.getElementById("autoUpdateVersion").checked;
- socket.emit('settingSetUpAutoUpdate', autoUpdate);
-}
-
-// Обработка события для отображения сообщения о сохранении настройки
-socket.on('settingAutoUpdateStatusPopup', function(data) {
- if (data) {
- showPopup('success', 'Настройка автоматического обновления сохранена');
- } else {
- showPopup('error', 'Ошибка сохранения настройки автоматического обновления');
- }
-});
-
-// Функция для проверки состояния автоматического обновления
-function checkAutoUpdateStatus() {
- socket.emit('settingCheckAutoUpdate');
-}
-
-// Обработка события для обновления состояния чекбокса
-socket.on('settingAutoUpdateStatusUpdate', function(data) {
- document.getElementById("autoUpdateVersion").checked = data;
-});
-
-// Функция для получения текущей и последней версии приложения
-function getVersionInfo() {
- socket.emit('getVersionInfo');
-}
-
-// Обработка события для обновления информации о версиях
-socket.on('versionInfo', function(data) {
- document.getElementById("appCurentVersion").textContent = data.currentVersion;
- document.getElementById("appLatestVersion").textContent = data.latestVersion;
-});
diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/templates/base_modules/footer.html" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/templates/base_modules/footer.html"
deleted file mode 100644
index 24966e1..0000000
--- "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/templates/base_modules/footer.html"
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/templates/base_modules/head_ru.html" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/templates/base_modules/head_ru.html"
deleted file mode 100644
index 92c35cd..0000000
--- "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/templates/base_modules/head_ru.html"
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/templates/base_modules/navigation.html" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/templates/base_modules/navigation.html"
deleted file mode 100644
index 9bb9e73..0000000
--- "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/templates/base_modules/navigation.html"
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
-
-
-
diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/templates/changelog.html" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/templates/changelog.html"
deleted file mode 100644
index b267a20..0000000
--- "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/templates/changelog.html"
+++ /dev/null
@@ -1,75 +0,0 @@
-
-
-
-
- {% include 'base_modules/head_ru.html' %}
- Ченджлог
-
-
-
-
- {% include 'base_modules/navigation.html' %}
-
-
-
- {% for entry in changelog %}
-
-
- Версия: {{ entry.version }}
- Автор: {{ entry.author }}
- Дата: {{ entry.date }}
-
- {% for change in entry.changes %}
-
- {{ change }}
- {% endfor %}
-
-
-
- {% endfor %}
-
-
-
-
-
- {% include 'base_modules/footer.html' %}
-
-
-
diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/templates/error_pages/401.html" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/templates/error_pages/401.html"
deleted file mode 100644
index 066cbfe..0000000
--- "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/templates/error_pages/401.html"
+++ /dev/null
@@ -1 +0,0 @@
-401
diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/templates/error_pages/404.html" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/templates/error_pages/404.html"
deleted file mode 100644
index f1b1cb3..0000000
--- "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/templates/error_pages/404.html"
+++ /dev/null
@@ -1 +0,0 @@
-404
diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/templates/error_pages/500.html" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/templates/error_pages/500.html"
deleted file mode 100644
index f573e99..0000000
--- "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/templates/error_pages/500.html"
+++ /dev/null
@@ -1 +0,0 @@
-505
diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/templates/index.html" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/templates/index.html"
deleted file mode 100644
index b62c249..0000000
--- "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/templates/index.html"
+++ /dev/null
@@ -1,33 +0,0 @@
-
-
-
-
- {% include 'base_modules/head_ru.html' %}
- Main
-
-
-
- {% include 'base_modules/navigation.html' %}
-
-
О программе DM-Bot
-
-
Введение
-
Приветствую вас на странице разработки утилиты DM-Bot организации A&D (angels and demons).
-
-
Что такое DM-Bot?
-
DM-Bot — это утилита для проведения текстовых ролевых игр на платформе Discord. Наша цель — создать максимально глубокий и проработанный игровой опыт для всех участников.
-
-
Техническая часть
-
DM-Bot написан на Python. Исходный код открыт и его можно найти на Github . Утилита будет поддерживать гибкую систему настроек и добавления собственного контента через yml файлы.
-
-
Ссылки
-
Discord
-
Boosty
-
Github
-
-
-
- {% include 'base_modules/footer.html' %}
-
-
-
diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/templates/settings/settings_main.html" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/templates/settings/settings_main.html"
deleted file mode 100644
index d526fc2..0000000
--- "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/templates/settings/settings_main.html"
+++ /dev/null
@@ -1,32 +0,0 @@
-
-
-
-
- {% include 'base_modules/head_ru.html' %}
- Настройки
-
-
-
-
-
-
- {% include 'base_modules/navigation.html' %}
-
- Автоматические обновления
-
- Текущая версия приложения: ???
- Версия приложения на репозитории: ???
-
-
-
- Автоматические обновления программы при запуске (необходимо перезапустить приложение)
-
-
-
-
-
-
- {% include 'base_modules/footer.html' %}
-
-
-