From 20ce09c9d40cbb8a4bc5ed568e428015616ab81a Mon Sep 17 00:00:00 2001 From: Tecquo Date: Tue, 21 Oct 2025 13:41:03 +0300 Subject: [PATCH 01/10] =?UTF-8?q?=D0=92=D0=B0=D0=B9=D0=B1=D0=BA=D0=BE?= =?UTF-8?q?=D0=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- interface.rpy | 483 +++++++++++++++++++++++++++++++++++------ interface_examples.rpy | 246 +++++++++++++++++++++ 2 files changed, 660 insertions(+), 69 deletions(-) create mode 100644 interface_examples.rpy diff --git a/interface.rpy b/interface.rpy index 65fce41..6f67a75 100644 --- a/interface.rpy +++ b/interface.rpy @@ -1,72 +1,417 @@ init python: - # Уберите из списка ненужные названия экранов, если не хотите их заменять. - MY_MOD_SCREENS = [ - "main_menu", - "game_menu_selector", - "quit", - "say", - "preferences", - "save", - "load", - "nvl", - "choice", - "text_history_screen", - "yesno_prompt", - "skip_indicator", - "history", - "help", - ] - - def my_mod_screen_save(): # Функция сохранения экранов из оригинала. - for name in MY_MOD_SCREENS: - renpy.display.screen.screens[ - ("my_mod_old_" + name, None) - ] = renpy.display.screen.screens[(name, None)] - - - def my_mod_screen_act(): # Функция замены экранов из оригинала на собственные. - config.window_title = u"Мой мод" # Здесь вводите название Вашего мода. - for ( - name - ) in ( - MY_MOD_SCREENS - ): - renpy.display.screen.screens[(name, None)] = renpy.display.screen.screens[ - ("my_mod_" + name, None) - ] - config.mouse["default"] = [ ("images/misc/mouse/1.png", 0, 0) ] - default_mouse = "default" - # Две строчки сверху - замена курсора - config.main_menu_music = ( - "interface/music/main_menu.mp3" # Вставьте ваш путь до музыки в главном меню. - ) - - - def my_mod_screens_diact(): # Функция обратной замены. - # Пытаемся заменить экраны. - try: - config.window_title = u"Бесконечное лето" - for name in MY_MOD_SCREENS: - renpy.display.screen.screens[(name, None)] = renpy.display.screen.screens[ - ("my_mod_old_" + name, None) - ] - config.mouse["default"] = [ ("images/misc/mouse/1.png", 0, 0) ] - default_mouse = "default" - config.main_menu_music = "sound/music/blow_with_the_fires.ogg" - except: # Если возникают ошибки, то мы выходим из игры, чтобы избежать Traceback - renpy.quit() - - # Функция для автоматического включения кастомного интерфейса при загрузке сохранения с названием Вашего мода + import logging + from copy import deepcopy + + # ======================== CONFIGURATION ======================== + # Централизованная конфигурация мода для легкой настройки + + class ModConfig: + """Конфигурация параметров мода.""" + # Основные параметры + MOD_NAME = u"Мой мод" # Название вашего мода + MOD_SAVE_IDENTIFIER = "MyMod" # Идентификатор в названии сохранения + MOD_VERSION = "1.0.0" # Версия мода + GAME_MIN_VERSION = "7.0" # Минимальная совместимая версия Ren'Py + + # Пути к ресурсам + MOD_CURSOR_PATH = "images/misc/mouse/1.png" + MOD_MENU_MUSIC = "interface/music/main_menu.mp3" + + # Оригинальные настройки игры + ORIGINAL_TITLE = u"Бесконечное лето" + ORIGINAL_CURSOR_PATH = "images/misc/mouse/1.png" + ORIGINAL_MENU_MUSIC = "sound/music/blow_with_the_fires.ogg" + + # Экраны для замены (удалите ненужные) + DEFAULT_SCREENS = [ + "main_menu", + "game_menu_selector", + "quit", + "say", + "preferences", + "save", + "load", + "nvl", + "choice", + "text_history_screen", + "yesno_prompt", + "skip_indicator", + "history", + "help", + ] + + # Настройки логирования + ENABLE_LOGGING = True + LOG_LEVEL = logging.INFO + + # ======================== LOGGING SETUP ======================== + + class ModScreenManager: + """ + Менеджер для управления заменой экранов в Ren'Py. + + Обеспечивает безопасную замену экранов с резервным копированием, + обработкой ошибок и поддержкой частичной замены. + """ + + def __init__(self, config=ModConfig): + """ + Инициализация менеджера экранов. + + Args: + config: Класс конфигурации с параметрами мода + """ + self.config = config + self.original_screens = {} + self.original_config = {} + self.active_screens = set() + self.is_active = False + + # Настройка логгера + self.logger = logging.getLogger('ModScreenManager') + if self.config.ENABLE_LOGGING: + self.logger.setLevel(self.config.LOG_LEVEL) + handler = logging.StreamHandler() + formatter = logging.Formatter( + '[%(levelname)s] %(name)s: %(message)s' + ) + handler.setFormatter(formatter) + self.logger.addHandler(handler) + + def check_compatibility(self): + """ + Проверка совместимости с текущей версией Ren'Py. + + Returns: + bool: True если версия совместима, False иначе + """ + try: + current_version = renpy.version_tuple[:2] + min_version = tuple(map(int, self.config.GAME_MIN_VERSION.split('.'))) + + if current_version < min_version: + self.logger.warning( + u"Версия Ren'Py {} может быть несовместима с модом (требуется {}+)".format( + '.'.join(map(str, current_version)), + self.config.GAME_MIN_VERSION + ) + ) + return False + return True + except Exception as e: + self.logger.error(u"Ошибка проверки совместимости: {}".format(e)) + return False + + def _screen_exists(self, screen_name): + """ + Проверка существования экрана. + + Args: + screen_name: Имя экрана для проверки + + Returns: + bool: True если экран существует + """ + return (screen_name, None) in renpy.display.screen.screens + + def _backup_config(self): + """ + Создание резервной копии конфигурации игры. + """ + try: + self.original_config = { + 'window_title': config.window_title, + 'mouse': deepcopy(config.mouse.get('default', [])), + 'main_menu_music': config.main_menu_music + } + self.logger.debug("Конфигурация сохранена") + except Exception as e: + self.logger.error(u"Ошибка сохранения конфигурации: {}".format(e)) + + def _restore_config(self): + """ + Восстановление оригинальной конфигурации игры. + """ + try: + if self.original_config: + config.window_title = self.original_config['window_title'] + config.mouse['default'] = self.original_config['mouse'] + config.main_menu_music = self.original_config['main_menu_music'] + else: + # Fallback на дефолтные значения + config.window_title = self.config.ORIGINAL_TITLE + config.mouse['default'] = [(self.config.ORIGINAL_CURSOR_PATH, 0, 0)] + config.main_menu_music = self.config.ORIGINAL_MENU_MUSIC + + self.logger.debug("Конфигурация восстановлена") + except Exception as e: + self.logger.error(u"Ошибка восстановления конфигурации: {}".format(e)) + + def _apply_mod_config(self): + """ + Применение конфигурации мода. + """ + try: + config.window_title = self.config.MOD_NAME + config.mouse['default'] = [(self.config.MOD_CURSOR_PATH, 0, 0)] + config.main_menu_music = self.config.MOD_MENU_MUSIC + self.logger.debug("Конфигурация мода применена") + except Exception as e: + self.logger.error(u"Ошибка применения конфигурации мода: {}".format(e)) + + def save_screens(self, screen_names=None): + """ + Сохранение оригинальных экранов. + + Args: + screen_names: Список имен экранов для сохранения. + Если None, сохраняются все экраны из конфигурации. + + Returns: + bool: True если сохранение успешно + """ + if screen_names is None: + screen_names = self.config.DEFAULT_SCREENS + + saved_count = 0 + for name in screen_names: + try: + if self._screen_exists(name): + original_key = (name, None) + backup_key = ("mod_backup_{}".format(name), None) + + # Сохраняем только если еще не сохранен + if original_key not in self.original_screens: + self.original_screens[original_key] = renpy.display.screen.screens[original_key] + renpy.display.screen.screens[backup_key] = renpy.display.screen.screens[original_key] + saved_count += 1 + self.logger.debug(u"Экран '{}' сохранен".format(name)) + else: + self.logger.warning(u"Экран '{}' не найден".format(name)) + + except (KeyError, AttributeError) as e: + self.logger.error(u"Ошибка сохранения экрана '{}': {}".format(name, e)) + + self.logger.info(u"Сохранено {} экранов".format(saved_count)) + return saved_count > 0 + + def activate_screens(self, screen_names=None, partial=False): + """ + Активация модифицированных экранов. + + Args: + screen_names: Список имен экранов для замены. + Если None, заменяются все экраны из конфигурации. + partial: Если True, добавляет экраны к уже активным. + Если False, заменяет все активные экраны. + + Returns: + bool: True если активация успешна + """ + if not self.check_compatibility(): + self.logger.warning("Проверка совместимости не пройдена") + + if screen_names is None: + screen_names = self.config.DEFAULT_SCREENS + + # Если не частичная замена, сначала деактивируем все + if not partial and self.is_active: + self.deactivate_screens() + + # Сохраняем экраны перед заменой + if not self.save_screens(screen_names): + self.logger.error("Не удалось сохранить экраны") + return False + + # Сохраняем конфигурацию перед первой активацией + if not self.is_active: + self._backup_config() + + activated_count = 0 + for name in screen_names: + try: + mod_screen_name = "my_mod_{}".format(name) + + if self._screen_exists(mod_screen_name): + original_key = (name, None) + mod_key = (mod_screen_name, None) + + renpy.display.screen.screens[original_key] = renpy.display.screen.screens[mod_key] + self.active_screens.add(name) + activated_count += 1 + self.logger.debug(u"Экран '{}' активирован".format(name)) + else: + self.logger.warning(u"Модифицированный экран '{}' не найден".format(mod_screen_name)) + + except (KeyError, AttributeError) as e: + self.logger.error(u"Ошибка активации экрана '{}': {}".format(name, e)) + + if activated_count > 0: + self._apply_mod_config() + self.is_active = True + self.logger.info(u"Активировано {} экранов".format(activated_count)) + + return activated_count > 0 + + def deactivate_screens(self, screen_names=None): + """ + Деактивация модифицированных экранов и восстановление оригиналов. + + Args: + screen_names: Список имен экранов для восстановления. + Если None, восстанавливаются все активные экраны. + + Returns: + bool: True если деактивация успешна + """ + if screen_names is None: + screen_names = list(self.active_screens) + + restored_count = 0 + for name in screen_names: + try: + original_key = (name, None) + backup_key = ("mod_backup_{}".format(name), None) + + if backup_key in renpy.display.screen.screens: + renpy.display.screen.screens[original_key] = renpy.display.screen.screens[backup_key] + self.active_screens.discard(name) + restored_count += 1 + self.logger.debug(u"Экран '{}' восстановлен".format(name)) + else: + self.logger.warning(u"Резервная копия экрана '{}' не найдена".format(name)) + + except (KeyError, AttributeError) as e: + self.logger.error(u"Ошибка восстановления экрана '{}': {}".format(name, e)) + + # Восстанавливаем конфигурацию если все экраны деактивированы + if not self.active_screens: + self._restore_config() + self.is_active = False + + self.logger.info(u"Восстановлено {} экранов".format(restored_count)) + return restored_count > 0 + + def toggle_screen(self, screen_name): + """ + Переключение состояния отдельного экрана. + + Args: + screen_name: Имя экрана для переключения + + Returns: + bool: True если экран теперь активен, False если деактивирован + """ + if screen_name in self.active_screens: + self.deactivate_screens([screen_name]) + return False + else: + self.activate_screens([screen_name], partial=True) + return True + + def get_status(self): + """ + Получение текущего статуса менеджера. + + Returns: + dict: Словарь с информацией о состоянии + """ + return { + 'is_active': self.is_active, + 'active_screens': list(self.active_screens), + 'total_screens': len(self.config.DEFAULT_SCREENS), + 'mod_version': self.config.MOD_VERSION + } + + # ======================== GLOBAL INSTANCE ======================== + + # Создаем глобальный экземпляр менеджера + mod_screen_manager = ModScreenManager() + + # ======================== COMPATIBILITY FUNCTIONS ======================== + # Функции для обратной совместимости со старым кодом + + def my_mod_screen_save(): + """ + Устаревшая функция сохранения экранов. + Оставлена для обратной совместимости. + """ + return mod_screen_manager.save_screens() + + def my_mod_screen_act(): + """ + Устаревшая функция активации экранов. + Оставлена для обратной совместимости. + """ + return mod_screen_manager.activate_screens() + + def my_mod_screens_diact(): + """ + Устаревшая функция деактивации экранов. + Оставлена для обратной совместимости. + """ + return mod_screen_manager.deactivate_screens() + + def my_mod_screens_save_act(): + """ + Устаревшая функция сохранения и активации экранов. + Оставлена для обратной совместимости. + """ + return mod_screen_manager.activate_screens() + def my_mod_activate_after_load(): - global save_name - if "MyMod" in save_name: - my_mod_screen_save() - my_mod_screen_act() - - # Добавляем функцию в Callback + """ + Автоматическая активация мода после загрузки сохранения. + Активируется если в имени сохранения есть идентификатор мода. + """ + try: + global save_name + if ModConfig.MOD_SAVE_IDENTIFIER in save_name: + mod_screen_manager.activate_screens() + mod_screen_manager.logger.info("Мод активирован после загрузки") + except NameError: + # save_name может быть не определен + mod_screen_manager.logger.debug("save_name не определен") + except Exception as e: + mod_screen_manager.logger.error(u"Ошибка автоактивации: {}".format(e)) + + # Регистрируем callback для автоматической активации config.after_load_callbacks.append(my_mod_activate_after_load) - - # Объединяем функцию сохранения экранов и замены в одну. - def my_mod_screens_save_act(): - my_mod_screen_save() - my_mod_screen_act() + + # ======================== UTILITY FUNCTIONS ======================== + + def mod_activate_partial(screen_names): + """ + Активация только определенных экранов. + + Args: + screen_names: Список имен экранов для активации + """ + return mod_screen_manager.activate_screens(screen_names, partial=True) + + def mod_deactivate_partial(screen_names): + """ + Деактивация только определенных экранов. + + Args: + screen_names: Список имен экранов для деактивации + """ + return mod_screen_manager.deactivate_screens(screen_names) + + def mod_toggle_screen(screen_name): + """ + Переключение отдельного экрана. + + Args: + screen_name: Имя экрана для переключения + """ + return mod_screen_manager.toggle_screen(screen_name) + + def mod_get_status(): + """ + Получение статуса мода. + + Returns: + dict: Информация о текущем состоянии + """ + return mod_screen_manager.get_status() diff --git a/interface_examples.rpy b/interface_examples.rpy new file mode 100644 index 0000000..2f894dc --- /dev/null +++ b/interface_examples.rpy @@ -0,0 +1,246 @@ +## Примеры использования улучшенной системы замены экранов + +# ======================== PERSISTENT VARIABLES ======================== +# Определение persistent переменных для примеров + +default persistent.use_custom_interface = False +default persistent.use_custom_menus = False +default persistent.developer_mode = False + +# ======================== БАЗОВОЕ ИСПОЛЬЗОВАНИЕ ======================== + +label mod_start: + # Проверка совместимости перед активацией + $ status = mod_screen_manager.check_compatibility() + if not status: + "Внимание: версия игры может быть несовместима с модом." + + # Активация всех экранов мода + $ mod_screen_manager.activate_screens() + "Мод активирован!" + return + +label mod_stop: + # Деактивация всех экранов мода + $ mod_screen_manager.deactivate_screens() + "Мод деактивирован!" + return + +# ======================== ЧАСТИЧНАЯ ЗАМЕНА ЭКРАНОВ ======================== + +label activate_custom_menu: + # Активация только главного меню и настроек + $ mod_activate_partial(["main_menu", "preferences"]) + "Активировано кастомное меню!" + return + +label toggle_custom_save_screen: + # Переключение экрана сохранения + $ is_active = mod_toggle_screen("save") + if is_active: + "Экран сохранения активирован!" + else: + "Экран сохранения деактивирован!" + return + +# ======================== ПОЛУЧЕНИЕ СТАТУСА ======================== + +label check_mod_status: + $ status = mod_get_status() + "Статус мода:" + "Активен: [status[is_active]]" + "Активные экраны: [status[active_screens]]" + "Версия мода: [status[mod_version]]" + return + +# ======================== НАСТРОЙКА КОНФИГУРАЦИИ ======================== + +init python: + # Создание кастомной конфигурации + class MyCustomConfig(ModConfig): + # Переопределяем параметры + MOD_NAME = u"Мой Супер Мод" + MOD_SAVE_IDENTIFIER = "SuperMod" + MOD_VERSION = "2.0.0" + + # Кастомные пути + MOD_CURSOR_PATH = "mods/supermod/cursor.png" + MOD_MENU_MUSIC = "mods/supermod/music/menu.mp3" + + # Выбираем только нужные экраны + DEFAULT_SCREENS = [ + "main_menu", + "save", + "load", + "preferences" + ] + + # Включаем подробное логирование для отладки + ENABLE_LOGGING = True + LOG_LEVEL = logging.DEBUG + + # Создаем менеджер с кастомной конфигурацией + custom_mod_manager = ModScreenManager(MyCustomConfig) + +# ======================== ПРОДВИНУТОЕ ИСПОЛЬЗОВАНИЕ ======================== + +init python: + def smart_mod_activation(): + """ + Умная активация мода с проверками и обработкой ошибок. + """ + manager = mod_screen_manager + + # Проверяем совместимость + if not manager.check_compatibility(): + renpy.notify("Предупреждение: возможна несовместимость с версией игры") + + # Пытаемся активировать экраны + try: + # Сначала активируем критичные экраны + critical_screens = ["main_menu", "say", "choice"] + if not manager.activate_screens(critical_screens, partial=False): + renpy.notify("Ошибка активации критичных экранов!") + return False + + # Затем добавляем остальные + optional_screens = ["save", "load", "preferences", "history"] + manager.activate_screens(optional_screens, partial=True) + + renpy.notify("Мод успешно активирован!") + return True + + except Exception as e: + manager.logger.error(u"Ошибка при активации мода: {}".format(e)) + renpy.notify("Произошла ошибка при активации мода") + + # Пытаемся откатить изменения + manager.deactivate_screens() + return False + + def selective_screen_replacement(screen_list): + """ + Замена только выбранных экранов с проверкой их существования. + + Args: + screen_list: список имен экранов для замены + """ + manager = mod_screen_manager + successful = [] + failed = [] + + for screen_name in screen_list: + # Проверяем существование модифицированного экрана + mod_screen = "my_mod_{}".format(screen_name) + if manager._screen_exists(mod_screen): + if manager.activate_screens([screen_name], partial=True): + successful.append(screen_name) + else: + failed.append(screen_name) + else: + manager.logger.warning(u"Модифицированный экран '{}' не найден".format(mod_screen)) + failed.append(screen_name) + + # Отчет о результатах + if successful: + renpy.notify(u"Успешно активировано: {}".format(', '.join(successful))) + if failed: + renpy.notify(u"Не удалось активировать: {}".format(', '.join(failed))) + + return successful, failed + +# ======================== ИНТЕГРАЦИЯ С ИГРОЙ ======================== + +label game_menu_mod_options: + menu: + "Управление модом" + + "Активировать мод": + $ smart_mod_activation() + + "Деактивировать мод": + $ mod_screen_manager.deactivate_screens() + "Мод деактивирован" + + "Частичная активация...": + menu: + "Какие экраны активировать?" + + "Только интерфейс": + $ mod_activate_partial(["say", "nvl", "choice"]) + + "Только меню": + $ mod_activate_partial(["main_menu", "game_menu_selector", "preferences"]) + + "Только сохранения": + $ mod_activate_partial(["save", "load"]) + + "Назад": + pass + + "Статус мода": + call check_mod_status + + "Назад": + return + +# ======================== АВТОМАТИЗАЦИЯ ======================== + +init python: + # Автоматическая активация при определенных условиях + def conditional_mod_activation(): + """ + Активация мода в зависимости от условий. + """ + # Проверяем настройки игрока + if persistent.use_custom_interface: + # Активируем интерфейс + mod_activate_partial(["say", "nvl", "choice", "text_history_screen"]) + + if persistent.use_custom_menus: + # Активируем меню + mod_activate_partial(["main_menu", "preferences", "save", "load"]) + + if persistent.developer_mode: + # В режиме разработчика включаем логирование + mod_screen_manager.logger.setLevel(logging.DEBUG) + + # Регистрируем для автозапуска + config.start_callbacks.append(conditional_mod_activation) + +# ======================== ОТЛАДКА И ДИАГНОСТИКА ======================== + +label debug_mod_system: + python: + manager = mod_screen_manager + + # Получаем полную информацию + status = manager.get_status() + + # Проверяем все экраны + all_screens_ok = True + missing_screens = [] + + for screen_name in ModConfig.DEFAULT_SCREENS: + mod_screen = "my_mod_{}".format(screen_name) + if not manager._screen_exists(mod_screen): + missing_screens.append(mod_screen) + all_screens_ok = False + + # Выводим отчет + renpy.say(None, u"=== ДИАГНОСТИКА СИСТЕМЫ МОДОВ ===") + renpy.say(None, u"Статус: {}".format(u'Активен' if status['is_active'] else u'Неактивен')) + renpy.say(None, u"Версия мода: {}".format(status['mod_version'])) + renpy.say(None, u"Активные экраны: {}/{}".format(len(status['active_screens']), status['total_screens'])) + + if status['active_screens']: + renpy.say(None, u"Список активных: {}".format(', '.join(status['active_screens']))) + + if missing_screens: + renpy.say(None, u"ВНИМАНИЕ! Отсутствующие экраны: {}".format(', '.join(missing_screens))) + else: + renpy.say(None, u"Все необходимые экраны найдены") + + renpy.say(None, u"Совместимость: {}".format(u'ОК' if manager.check_compatibility() else u'Возможны проблемы')) + + return From 2507e57ba366d14bb8cd9084fe41ce52cf154c7d Mon Sep 17 00:00:00 2001 From: Tecquo Date: Tue, 21 Oct 2025 14:27:36 +0300 Subject: [PATCH 02/10] =?UTF-8?q?=D0=9F=D1=80=D0=B0=D0=B2=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- images/1.png | Bin 0 -> 2278 bytes interface.rpy | 15 ++++++------ interface_examples.rpy | 52 +++++++++++++++++++++-------------------- 3 files changed, 34 insertions(+), 33 deletions(-) create mode 100644 images/1.png diff --git a/images/1.png b/images/1.png new file mode 100644 index 0000000000000000000000000000000000000000..df1ae7aca0e6dea3db5847f0fac417d2624e4e96 GIT binary patch literal 2278 zcmVt5uxfxMG*v1L|F60(wnm$%&Q z*FRoT1Bz0s{$|eHd(S=he16|^&hK}BtQ*9looAEF_iVZ~MtP(_HCK)2p6?YLOR(#B zhgjhZ7972p+M^e8#rFbEy6kFdf~2P~P0k4hcN+3WQTU#ZRtkVX z7_ze&9?fCXC#Af;HlMTpU$6mi35-!N#S;)0#D{`>1tOpno(BS0K`=FCfe`b49w9Ke zgH7*)|BV$VVYdx9K-_`GW}&GWQ0W1NPW#{sm;uO)KxTvot}Nx))?%*z#^4Uv)eE+P z?j+RI`y`VZ5kYHE3cOxz3Nj)P&Llr0ix(?Kv*XheCjQUhZBW@4Z2Qm^gQ{vDr8VM% z?osVH;M(bC0a}5jn3xe_$G-6_|92^cXAFKHToVpea-pS7s6Px^E5rlkf@8BY zkz|1z$hpJ~Mkgnd!P3SGtayU{c&s!{=59WV310^`dv9E*07`Tpk5mfk>V3iih=h2{ z^|*8F9_mCyE2U08R5dgMD|_0RDF*Ohx69|todV)OTLSi0aoagtxpKs()B*H`%8C`n zivBt0zEN0Mx@Yp_=$SyFCxHOm16NzH(j*;YLDGAu;L3UMR^N}_9-JL*Jr=vk^W+*= zWRilQmv-I`K(iGJeiF*ce&w1qYhG99o#%Wd_%0~Rf_=JArwP~o3103UvjNO_R6W|% zF2Ay}0-@nIUQFT4vFO7uV3nPZn;J#aN0rnp`G9!i2`p1r=ML;Y?}vvP-}p-P_k%nL zazDs@@O^gD1m&BEq#g_%L<7!Ki%ZG_8tHhP1;>jygyXx2S z>fkI{4Bm>Y$kQTX^qo)lxfh^X03X^Ohg<#&?Pp%#+0OQal~Ho}w#ONB-)ww!9Koh@ z|7NN-Ik@i}0qXEY@FhjjRnz!!^)r;*bb;A-so;213G?QlK&xrC=OrmuaNNKph48wda)G*#?=p$#_#a=K&M7}-jC3l#(aymHV4-ey5k

+wJKBDvt0;QOPbs=l z*mA?Gyt^t!@d!mx)S@8UBm;{}$7b?T#R(k8XG2Aj+z3oM+hA~k#lTz(-L5fbS_B@r z9u=HUZt)h{kHpJQ0s}09qX4Pn$$qW_;b;JYYxlA4EX1612fjqn*yIf#M04$D15Z8GpYOM8yS4+QJV0<| z0HwFi4<9ETJ`TPZWRAFjIUnZI{-bq-BL*rYykFTxaX8GlF`8wo<7}*o z^NVfe=&^GVanqrNMk#~%p%zSK#80dE2M61`Jg9HmQG+5xIz9!#z}uf96jTUsprf0L zTEVmwk?tU~F0ufqe6tpXpu|9_^htAD8##WUgRDqK&1t~^7{-Q%cPe^np7gIn;oxnr z1r%MO6kE2qxPak<@57LAgmFVPJD+Vq^C>6;5R@`#TDgf>3O749bIVtP0|8@2tkd)N z&0mRDk@N&K;BN0mvVA`MI6((6<}#B(PZU9a7cUM|hGr6p2@U^Rk5U?N!Erda@HK!V zV`h|}RGX(a;uL#c>s`+o$nnZY#gS*lZo64JXJpBHWm^7bB76QiKzw;r9C<^WKl@2I3c zo**9axvpY5Wp^&bm0=(b1SS)jyMWL$;|b+Jzea`Ewu$(T1MGZctuIhD@zR-#dR2a{ zy_OwZ*x8kun=`z?n0&1w9!hY|-8bV~6A&>#(uq?kLGaSc>ux8pcp32qhstGf4%8%C zMvpr8j;VWVSDle!fS=s`yL+Q~fyJ={t!(;B3x57A#2ODIQOZx3pUMW6Ojn3FpeGP_ zCbI3RHX4qp#JR--e}Cbgnt%4)*E2GahxEEZ3m{O84oPWSzP0Q%a`udU+@xc~qF07*qoM6N<$g3DJ( AfdBvi literal 0 HcmV?d00001 diff --git a/interface.rpy b/interface.rpy index 6f67a75..5beb4f8 100644 --- a/interface.rpy +++ b/interface.rpy @@ -1,6 +1,7 @@ init python: import logging from copy import deepcopy + import builtins # ======================== CONFIGURATION ======================== # Централизованная конфигурация мода для легкой настройки @@ -10,11 +11,10 @@ init python: # Основные параметры MOD_NAME = u"Мой мод" # Название вашего мода MOD_SAVE_IDENTIFIER = "MyMod" # Идентификатор в названии сохранения - MOD_VERSION = "1.0.0" # Версия мода - GAME_MIN_VERSION = "7.0" # Минимальная совместимая версия Ren'Py + RENPY_MIN_VERSION = "7.0" # Минимальная совместимая версия Ren'Py # Пути к ресурсам - MOD_CURSOR_PATH = "images/misc/mouse/1.png" + MOD_CURSOR_PATH = "interface/images/1.png" MOD_MENU_MUSIC = "interface/music/main_menu.mp3" # Оригинальные настройки игры @@ -87,13 +87,13 @@ init python: """ try: current_version = renpy.version_tuple[:2] - min_version = tuple(map(int, self.config.GAME_MIN_VERSION.split('.'))) + min_version = tuple(builtins.map(int, self.config.RENPY_MIN_VERSION.split('.'))) if current_version < min_version: self.logger.warning( u"Версия Ren'Py {} может быть несовместима с модом (требуется {}+)".format( - '.'.join(map(str, current_version)), - self.config.GAME_MIN_VERSION + '.'.join(builtins.map(str, current_version)), + self.config.RENPY_MIN_VERSION ) ) return False @@ -319,8 +319,7 @@ init python: return { 'is_active': self.is_active, 'active_screens': list(self.active_screens), - 'total_screens': len(self.config.DEFAULT_SCREENS), - 'mod_version': self.config.MOD_VERSION + 'total_screens': len(self.config.DEFAULT_SCREENS) } # ======================== GLOBAL INSTANCE ======================== diff --git a/interface_examples.rpy b/interface_examples.rpy index 2f894dc..a888b04 100644 --- a/interface_examples.rpy +++ b/interface_examples.rpy @@ -8,6 +8,8 @@ default persistent.use_custom_menus = False default persistent.developer_mode = False # ======================== БАЗОВОЕ ИСПОЛЬЗОВАНИЕ ======================== +init: + $ mods["mod_start"] = "Замена интерфейса | ВАЙБКОД" label mod_start: # Проверка совместимости перед активацией @@ -55,32 +57,32 @@ label check_mod_status: # ======================== НАСТРОЙКА КОНФИГУРАЦИИ ======================== -init python: - # Создание кастомной конфигурации - class MyCustomConfig(ModConfig): - # Переопределяем параметры - MOD_NAME = u"Мой Супер Мод" - MOD_SAVE_IDENTIFIER = "SuperMod" - MOD_VERSION = "2.0.0" - - # Кастомные пути - MOD_CURSOR_PATH = "mods/supermod/cursor.png" - MOD_MENU_MUSIC = "mods/supermod/music/menu.mp3" - - # Выбираем только нужные экраны - DEFAULT_SCREENS = [ - "main_menu", - "save", - "load", - "preferences" - ] - - # Включаем подробное логирование для отладки - ENABLE_LOGGING = True - LOG_LEVEL = logging.DEBUG +# init python: +# # Создание кастомной конфигурации +# class MyCustomConfig(ModConfig): +# # Переопределяем параметры +# MOD_NAME = u"Мой Супер Мод" +# MOD_SAVE_IDENTIFIER = "SuperMod" +# MOD_VERSION = "2.0.0" + +# # Кастомные пути +# MOD_CURSOR_PATH = "mods/supermod/cursor.png" +# MOD_MENU_MUSIC = "mods/supermod/music/menu.mp3" + +# # Выбираем только нужные экраны +# DEFAULT_SCREENS = [ +# "main_menu", +# "save", +# "load", +# "preferences" +# ] + +# # Включаем подробное логирование для отладки +# ENABLE_LOGGING = True +# LOG_LEVEL = logging.DEBUG - # Создаем менеджер с кастомной конфигурацией - custom_mod_manager = ModScreenManager(MyCustomConfig) +# # Создаем менеджер с кастомной конфигурацией +# custom_mod_manager = ModScreenManager(MyCustomConfig) # ======================== ПРОДВИНУТОЕ ИСПОЛЬЗОВАНИЕ ======================== From a52dc08172012c418b95586b24f5ee48e6b5835b Mon Sep 17 00:00:00 2001 From: Tecquo Date: Tue, 21 Oct 2025 15:41:16 +0300 Subject: [PATCH 03/10] =?UTF-8?q?=D0=9F=D1=80=D0=B0=D0=B2=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- demo.rpy | 4 ---- interface.rpy | 43 ++++++++++++++++++++++++++++--------------- 2 files changed, 28 insertions(+), 19 deletions(-) diff --git a/demo.rpy b/demo.rpy index 7843ba6..f5ada10 100644 --- a/demo.rpy +++ b/demo.rpy @@ -4,10 +4,6 @@ init: label interface_replace: "Оригинальный интерфейс." - $ my_mod_screen_save() - - "Сохранение оригинального интерфейса." - $ my_mod_screen_act() "Кастомный интерфейс." diff --git a/interface.rpy b/interface.rpy index 5beb4f8..eff284f 100644 --- a/interface.rpy +++ b/interface.rpy @@ -67,16 +67,19 @@ init python: self.active_screens = set() self.is_active = False - # Настройка логгера - self.logger = logging.getLogger('ModScreenManager') + # Настройка логгера с уникальным именем для каждого мода + logger_name = u'ModScreenManager [{}]'.format(self.config.MOD_NAME) + self.logger = logging.getLogger(logger_name) if self.config.ENABLE_LOGGING: self.logger.setLevel(self.config.LOG_LEVEL) - handler = logging.StreamHandler() - formatter = logging.Formatter( - '[%(levelname)s] %(name)s: %(message)s' - ) - handler.setFormatter(formatter) - self.logger.addHandler(handler) + # Добавляем handler только если его еще нет (избегаем дублирования логов) + if not self.logger.handlers: + handler = logging.StreamHandler() + formatter = logging.Formatter( + '[%(levelname)s] %(name)s: %(message)s' + ) + handler.setFormatter(formatter) + self.logger.addHandler(handler) def check_compatibility(self): """ @@ -192,8 +195,12 @@ init python: except (KeyError, AttributeError) as e: self.logger.error(u"Ошибка сохранения экрана '{}': {}".format(name, e)) - self.logger.info(u"Сохранено {} экранов".format(saved_count)) - return saved_count > 0 + # Логируем только если действительно что-то сохранили + if saved_count > 0: + self.logger.info(u"Сохранено {} экранов".format(saved_count)) + else: + self.logger.debug(u"Все экраны уже были сохранены ранее") + return True # Возвращаем True если нет ошибок, даже если ничего не сохранили def activate_screens(self, screen_names=None, partial=False): """ @@ -213,10 +220,16 @@ init python: if screen_names is None: screen_names = self.config.DEFAULT_SCREENS - - # Если не частичная замена, сначала деактивируем все - if not partial and self.is_active: - self.deactivate_screens() + + # Проверяем, не активны ли уже все запрошенные экраны + if not partial: + requested_set = set(screen_names) + if self.is_active and requested_set == self.active_screens: + self.logger.debug(u"Запрошенные экраны уже активны, пропускаем активацию") + return True + # Если не частичная замена и активны другие экраны, деактивируем их + elif self.is_active: + self.deactivate_screens() # Сохраняем экраны перед заменой if not self.save_screens(screen_names): @@ -328,7 +341,7 @@ init python: mod_screen_manager = ModScreenManager() # ======================== COMPATIBILITY FUNCTIONS ======================== - # Функции для обратной совместимости со старым кодом + # Функции для обратной совместимости со старой версией замены интерфейса def my_mod_screen_save(): """ From c96b27e7ff430e3e2f47c73d2d442a2802a2a701 Mon Sep 17 00:00:00 2001 From: Tecquo Date: Tue, 21 Oct 2025 16:58:36 +0300 Subject: [PATCH 04/10] =?UTF-8?q?=D0=9F=D1=80=D0=B0=D0=B2=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- demo.rpy | 240 ------------------------------- interface.rpy | 91 +++--------- interface_examples.rpy | 248 -------------------------------- label.rpy | 316 +++++++++++++++++++++++++++++++++++++++++ screens.rpy | 11 +- 5 files changed, 334 insertions(+), 572 deletions(-) delete mode 100644 demo.rpy delete mode 100644 interface_examples.rpy create mode 100644 label.rpy diff --git a/demo.rpy b/demo.rpy deleted file mode 100644 index f5ada10..0000000 --- a/demo.rpy +++ /dev/null @@ -1,240 +0,0 @@ -init: - $ mods["interface_replace"] = "Замена интерфейса" - -label interface_replace: - "Оригинальный интерфейс." - - $ my_mod_screen_act() - - "Кастомный интерфейс." - - call screen developer_custom_screens_menu() - - return - -label custom_screens_demo: - - scene bg black - - menu demo_menu: - "Что вы хотите посмотреть?" - - "Тест диалогового окна": - jump test_dialogue - - "Тест выборов": - jump test_choices - - "Тест меню и навигации": - jump test_menus - - "Тест сохранения/загрузки": - call screen my_mod_save - jump demo_menu - - "Тест цветовых схем": - jump test_color_schemes - - "Выйти из демо": - return - -label test_dialogue: - - scene bg black - - "Это демонстрация диалогового окна." - "Обратите внимание на кнопки управления в правом нижнем углу." - - me "Привет! Это тестовое сообщение от персонажа." - me "Диалоговое окно адаптируется к времени суток." - - "Вы можете открыть историю диалогов с помощью кнопки 'H'." - "Или сохранить игру кнопкой 'S'." - - jump demo_menu - -label test_choices: - - scene bg black - - "Сейчас будет показан экран выбора с несколькими вариантами." - - menu: - "Это заголовок меню выбора" - - "Первый вариант": - "Вы выбрали первый вариант." - - "Второй вариант": - "Вы выбрали второй вариант." - - "Третий вариант": - "Вы выбрали третий вариант." - - "Очень длинный вариант текста, который демонстрирует, как экран выбора обрабатывает длинные строки": - "Вы выбрали длинный вариант." - - jump demo_menu - -label test_menus: - - scene bg black - - menu: - "Какое меню открыть?" - - "Игровое меню (ESC)": - call screen my_mod_game_menu_selector - - "Настройки": - call screen my_mod_preferences - - "Галерея": - call screen my_mod_gallery - - "Музыкальная комната": - call screen my_mod_music_room - - "Об игре": - call screen my_mod_about # TODO заменить about на help - - "Назад": - jump demo_menu - - jump test_menus - -label test_color_schemes: - - scene bg black - - menu: - "Выберите время суток:" - - "День (Day)": - $ persistent.timeofday = 'day' - "Установлена дневная схема." - - "Закат (Sunset)": - $ persistent.timeofday = 'sunset' - "Установлена вечерняя схема." - - "Ночь (Night)": - $ persistent.timeofday = 'night' - "Установлена ночная схема." - - "Пролог (Prologue)": - $ persistent.timeofday = 'prologue' - "Установлена схема пролога." - - "Вернуться в меню": - jump demo_menu - - jump test_color_schemes - -label quick_test_all_screens: - - "1. Тест главного меню делайте в меню разработчика." - - "2. Тест игрового меню..." - call screen my_mod_game_menu_selector - - "3. Тест сохранения..." - call screen my_mod_save - - "4. Тест загрузки..." - call screen my_mod_load - - "5. Тест настроек..." - call screen my_mod_preferences - - "6. Тест галереи..." - call screen my_mod_gallery - - "7. Тест выбора..." - menu: - "Тестовый выбор?" - "Да": - pass - "Нет": - pass - - "8. Тест уведомления..." - $ renpy.notify("Тестовое уведомление!") - pause 2.0 - - "9. Тест истории текста..." - call screen my_mod_text_history_screen - - "10. Тест подтверждения..." - call screen my_mod_yesno_prompt( - "Это тестовое подтверждение. Продолжить?", - Return(), - Return() - ) - - call screen developer_custom_screens_menu() - - return - -screen developer_custom_screens_menu(): - modal True - - frame: - background Solid("#2F4F4F") - xalign 0.5 - yalign 0.5 - padding (40, 40) - - vbox: - spacing 20 - - text "КАСТОМНЫЕ ЭКРАНЫ": - size 36 - color "#FFD700" - bold True - - null height 20 - - textbutton "Запустить полное демо": - xsize 400 - ysize 50 - background Solid("#1E90FF") - hover_background Solid("#4169E1") - text_color "#FFFFFF" - text_size 24 - text_xalign 0.5 - text_yalign 0.5 - action Jump("custom_screens_demo") - - textbutton "Быстрое тестирование всех экранов": - xsize 400 - ysize 50 - background Solid("#9370DB") - hover_background Solid("#BA55D3") - text_color "#FFFFFF" - text_size 24 - text_xalign 0.5 - text_yalign 0.5 - action Jump("quick_test_all_screens") - - textbutton "Главное меню (кастомное)": - xsize 400 - ysize 50 - background Solid("#228B22") - hover_background Solid("#32CD32") - text_color "#FFFFFF" - text_size 24 - text_xalign 0.5 - text_yalign 0.5 - action ShowMenu("my_mod_main_menu") - - textbutton "Закрыть": - xsize 400 - ysize 50 - background Solid("#696969") - hover_background Solid("#808080") - text_color "#FFFFFF" - text_size 24 - text_xalign 0.5 - text_yalign 0.5 - action Return() diff --git a/interface.rpy b/interface.rpy index eff284f..88bdf35 100644 --- a/interface.rpy +++ b/interface.rpy @@ -85,21 +85,36 @@ init python: """ Проверка совместимости с текущей версией Ren'Py. + Проверяет: + - Минимальную версию + - Максимальную версию (должна быть < 8.0, т.к. Ren'Py 8 использует Python 3 + и не поддерживает прямую замену экранов через renpy.display.screen.screens) + Returns: bool: True если версия совместима, False иначе """ try: current_version = renpy.version_tuple[:2] min_version = tuple(builtins.map(int, self.config.RENPY_MIN_VERSION.split('.'))) + max_version = (7, 99) # Любая версия RenPy 7.x if current_version < min_version: self.logger.warning( - u"Версия Ren'Py {} может быть несовместима с модом (требуется {}+)".format( + u"Версия Ren'Py {} слишком старая (требуется {}+)".format( '.'.join(builtins.map(str, current_version)), self.config.RENPY_MIN_VERSION ) ) return False + + if current_version >= (8, 0): + self.logger.error( + u"Ren'Py {} не поддерживается! Замена экранов работает только на Ren'Py 7.x".format( + '.'.join(builtins.map(str, current_version)) + ) + ) + return False + return True except Exception as e: self.logger.error(u"Ошибка проверки совместимости: {}".format(e)) @@ -141,7 +156,6 @@ init python: config.mouse['default'] = self.original_config['mouse'] config.main_menu_music = self.original_config['main_menu_music'] else: - # Fallback на дефолтные значения config.window_title = self.config.ORIGINAL_TITLE config.mouse['default'] = [(self.config.ORIGINAL_CURSOR_PATH, 0, 0)] config.main_menu_music = self.config.ORIGINAL_MENU_MUSIC @@ -335,42 +349,9 @@ init python: 'total_screens': len(self.config.DEFAULT_SCREENS) } - # ======================== GLOBAL INSTANCE ======================== - # Создаем глобальный экземпляр менеджера mod_screen_manager = ModScreenManager() - # ======================== COMPATIBILITY FUNCTIONS ======================== - # Функции для обратной совместимости со старой версией замены интерфейса - - def my_mod_screen_save(): - """ - Устаревшая функция сохранения экранов. - Оставлена для обратной совместимости. - """ - return mod_screen_manager.save_screens() - - def my_mod_screen_act(): - """ - Устаревшая функция активации экранов. - Оставлена для обратной совместимости. - """ - return mod_screen_manager.activate_screens() - - def my_mod_screens_diact(): - """ - Устаревшая функция деактивации экранов. - Оставлена для обратной совместимости. - """ - return mod_screen_manager.deactivate_screens() - - def my_mod_screens_save_act(): - """ - Устаревшая функция сохранения и активации экранов. - Оставлена для обратной совместимости. - """ - return mod_screen_manager.activate_screens() - def my_mod_activate_after_load(): """ Автоматическая активация мода после загрузки сохранения. @@ -383,47 +364,9 @@ init python: mod_screen_manager.logger.info("Мод активирован после загрузки") except NameError: # save_name может быть не определен - mod_screen_manager.logger.debug("save_name не определен") + mod_screen_manager.logger.error("save_name не определен") except Exception as e: mod_screen_manager.logger.error(u"Ошибка автоактивации: {}".format(e)) # Регистрируем callback для автоматической активации config.after_load_callbacks.append(my_mod_activate_after_load) - - # ======================== UTILITY FUNCTIONS ======================== - - def mod_activate_partial(screen_names): - """ - Активация только определенных экранов. - - Args: - screen_names: Список имен экранов для активации - """ - return mod_screen_manager.activate_screens(screen_names, partial=True) - - def mod_deactivate_partial(screen_names): - """ - Деактивация только определенных экранов. - - Args: - screen_names: Список имен экранов для деактивации - """ - return mod_screen_manager.deactivate_screens(screen_names) - - def mod_toggle_screen(screen_name): - """ - Переключение отдельного экрана. - - Args: - screen_name: Имя экрана для переключения - """ - return mod_screen_manager.toggle_screen(screen_name) - - def mod_get_status(): - """ - Получение статуса мода. - - Returns: - dict: Информация о текущем состоянии - """ - return mod_screen_manager.get_status() diff --git a/interface_examples.rpy b/interface_examples.rpy deleted file mode 100644 index a888b04..0000000 --- a/interface_examples.rpy +++ /dev/null @@ -1,248 +0,0 @@ -## Примеры использования улучшенной системы замены экранов - -# ======================== PERSISTENT VARIABLES ======================== -# Определение persistent переменных для примеров - -default persistent.use_custom_interface = False -default persistent.use_custom_menus = False -default persistent.developer_mode = False - -# ======================== БАЗОВОЕ ИСПОЛЬЗОВАНИЕ ======================== -init: - $ mods["mod_start"] = "Замена интерфейса | ВАЙБКОД" - -label mod_start: - # Проверка совместимости перед активацией - $ status = mod_screen_manager.check_compatibility() - if not status: - "Внимание: версия игры может быть несовместима с модом." - - # Активация всех экранов мода - $ mod_screen_manager.activate_screens() - "Мод активирован!" - return - -label mod_stop: - # Деактивация всех экранов мода - $ mod_screen_manager.deactivate_screens() - "Мод деактивирован!" - return - -# ======================== ЧАСТИЧНАЯ ЗАМЕНА ЭКРАНОВ ======================== - -label activate_custom_menu: - # Активация только главного меню и настроек - $ mod_activate_partial(["main_menu", "preferences"]) - "Активировано кастомное меню!" - return - -label toggle_custom_save_screen: - # Переключение экрана сохранения - $ is_active = mod_toggle_screen("save") - if is_active: - "Экран сохранения активирован!" - else: - "Экран сохранения деактивирован!" - return - -# ======================== ПОЛУЧЕНИЕ СТАТУСА ======================== - -label check_mod_status: - $ status = mod_get_status() - "Статус мода:" - "Активен: [status[is_active]]" - "Активные экраны: [status[active_screens]]" - "Версия мода: [status[mod_version]]" - return - -# ======================== НАСТРОЙКА КОНФИГУРАЦИИ ======================== - -# init python: -# # Создание кастомной конфигурации -# class MyCustomConfig(ModConfig): -# # Переопределяем параметры -# MOD_NAME = u"Мой Супер Мод" -# MOD_SAVE_IDENTIFIER = "SuperMod" -# MOD_VERSION = "2.0.0" - -# # Кастомные пути -# MOD_CURSOR_PATH = "mods/supermod/cursor.png" -# MOD_MENU_MUSIC = "mods/supermod/music/menu.mp3" - -# # Выбираем только нужные экраны -# DEFAULT_SCREENS = [ -# "main_menu", -# "save", -# "load", -# "preferences" -# ] - -# # Включаем подробное логирование для отладки -# ENABLE_LOGGING = True -# LOG_LEVEL = logging.DEBUG - -# # Создаем менеджер с кастомной конфигурацией -# custom_mod_manager = ModScreenManager(MyCustomConfig) - -# ======================== ПРОДВИНУТОЕ ИСПОЛЬЗОВАНИЕ ======================== - -init python: - def smart_mod_activation(): - """ - Умная активация мода с проверками и обработкой ошибок. - """ - manager = mod_screen_manager - - # Проверяем совместимость - if not manager.check_compatibility(): - renpy.notify("Предупреждение: возможна несовместимость с версией игры") - - # Пытаемся активировать экраны - try: - # Сначала активируем критичные экраны - critical_screens = ["main_menu", "say", "choice"] - if not manager.activate_screens(critical_screens, partial=False): - renpy.notify("Ошибка активации критичных экранов!") - return False - - # Затем добавляем остальные - optional_screens = ["save", "load", "preferences", "history"] - manager.activate_screens(optional_screens, partial=True) - - renpy.notify("Мод успешно активирован!") - return True - - except Exception as e: - manager.logger.error(u"Ошибка при активации мода: {}".format(e)) - renpy.notify("Произошла ошибка при активации мода") - - # Пытаемся откатить изменения - manager.deactivate_screens() - return False - - def selective_screen_replacement(screen_list): - """ - Замена только выбранных экранов с проверкой их существования. - - Args: - screen_list: список имен экранов для замены - """ - manager = mod_screen_manager - successful = [] - failed = [] - - for screen_name in screen_list: - # Проверяем существование модифицированного экрана - mod_screen = "my_mod_{}".format(screen_name) - if manager._screen_exists(mod_screen): - if manager.activate_screens([screen_name], partial=True): - successful.append(screen_name) - else: - failed.append(screen_name) - else: - manager.logger.warning(u"Модифицированный экран '{}' не найден".format(mod_screen)) - failed.append(screen_name) - - # Отчет о результатах - if successful: - renpy.notify(u"Успешно активировано: {}".format(', '.join(successful))) - if failed: - renpy.notify(u"Не удалось активировать: {}".format(', '.join(failed))) - - return successful, failed - -# ======================== ИНТЕГРАЦИЯ С ИГРОЙ ======================== - -label game_menu_mod_options: - menu: - "Управление модом" - - "Активировать мод": - $ smart_mod_activation() - - "Деактивировать мод": - $ mod_screen_manager.deactivate_screens() - "Мод деактивирован" - - "Частичная активация...": - menu: - "Какие экраны активировать?" - - "Только интерфейс": - $ mod_activate_partial(["say", "nvl", "choice"]) - - "Только меню": - $ mod_activate_partial(["main_menu", "game_menu_selector", "preferences"]) - - "Только сохранения": - $ mod_activate_partial(["save", "load"]) - - "Назад": - pass - - "Статус мода": - call check_mod_status - - "Назад": - return - -# ======================== АВТОМАТИЗАЦИЯ ======================== - -init python: - # Автоматическая активация при определенных условиях - def conditional_mod_activation(): - """ - Активация мода в зависимости от условий. - """ - # Проверяем настройки игрока - if persistent.use_custom_interface: - # Активируем интерфейс - mod_activate_partial(["say", "nvl", "choice", "text_history_screen"]) - - if persistent.use_custom_menus: - # Активируем меню - mod_activate_partial(["main_menu", "preferences", "save", "load"]) - - if persistent.developer_mode: - # В режиме разработчика включаем логирование - mod_screen_manager.logger.setLevel(logging.DEBUG) - - # Регистрируем для автозапуска - config.start_callbacks.append(conditional_mod_activation) - -# ======================== ОТЛАДКА И ДИАГНОСТИКА ======================== - -label debug_mod_system: - python: - manager = mod_screen_manager - - # Получаем полную информацию - status = manager.get_status() - - # Проверяем все экраны - all_screens_ok = True - missing_screens = [] - - for screen_name in ModConfig.DEFAULT_SCREENS: - mod_screen = "my_mod_{}".format(screen_name) - if not manager._screen_exists(mod_screen): - missing_screens.append(mod_screen) - all_screens_ok = False - - # Выводим отчет - renpy.say(None, u"=== ДИАГНОСТИКА СИСТЕМЫ МОДОВ ===") - renpy.say(None, u"Статус: {}".format(u'Активен' if status['is_active'] else u'Неактивен')) - renpy.say(None, u"Версия мода: {}".format(status['mod_version'])) - renpy.say(None, u"Активные экраны: {}/{}".format(len(status['active_screens']), status['total_screens'])) - - if status['active_screens']: - renpy.say(None, u"Список активных: {}".format(', '.join(status['active_screens']))) - - if missing_screens: - renpy.say(None, u"ВНИМАНИЕ! Отсутствующие экраны: {}".format(', '.join(missing_screens))) - else: - renpy.say(None, u"Все необходимые экраны найдены") - - renpy.say(None, u"Совместимость: {}".format(u'ОК' if manager.check_compatibility() else u'Возможны проблемы')) - - return diff --git a/label.rpy b/label.rpy new file mode 100644 index 0000000..d04f5ec --- /dev/null +++ b/label.rpy @@ -0,0 +1,316 @@ +init: + $ mods["interface_test"] = u"Тест замены интерфейса" + +default persistent.timeofday = 'day' + +label interface_test: + scene bg black + + menu test_main_menu: + "{size=+10}{b}СИСТЕМА ЗАМЕНЫ ЭКРАНОВ{/b}{/size}\n\nВыберите режим тестирования:" + + "1. Управление модом (вкл/выкл)": + jump test_mod_control + + "2. Частичная замена экранов": + jump test_partial_replacement + + "3. Тест отдельных экранов": + jump test_individual_screens + + "4. Быстрый тест всех экранов": + jump quick_test_all_screens + + "5. Диагностика системы": + jump test_diagnostics + + "6. Демонстрация интерфейса": + jump demo_interface + + "Выход": + return + +label test_mod_control: + scene bg black + + python: + status = mod_screen_manager.get_status() + status_text = u"Активен" if status['is_active'] else u"Неактивен" + + menu mod_control_menu: + "{b}УПРАВЛЕНИЕ МОДОМ{/b}\n\nТекущий статус: [status_text]" + + "Активировать мод (все экраны)": + $ result = mod_screen_manager.activate_screens() + if result: + "Мод успешно активирован!" + "Все экраны заменены на кастомные." + else: + "Ошибка активации мода!" + jump test_mod_control + + "Деактивировать мод (вернуть оригинал)": + $ result = mod_screen_manager.deactivate_screens() + if result: + "Мод деактивирован!" + "Восстановлены оригинальные экраны." + else: + "Ошибка деактивации!" + jump test_mod_control + + "Проверить совместимость": + $ compat = mod_screen_manager.check_compatibility() + if compat: + "Версия Ren'Py совместима с модом!" + else: + "ВНИМАНИЕ: Возможны проблемы совместимости!" + jump test_mod_control + + "Назад": + jump test_main_menu + +label test_partial_replacement: + scene bg black + + menu partial_menu: + "{b}ЧАСТИЧНАЯ ЗАМЕНА ЭКРАНОВ{/b}\n\nВыберите группу экранов:" + + "Только диалоговые (say, nvl, choice)": + $ screens = ["say", "nvl", "choice"] + $ result = mod_screen_manager.activate_screens(screens, partial=True) + if result: + "Активированы диалоговые экраны!" + "Тестируем диалог..." + "Это тестовое сообщение в кастомном окне." + jump partial_menu + + "Только меню (main_menu, game_menu_selector)": + $ screens = ["main_menu", "game_menu_selector", "quit"] + $ result = mod_screen_manager.activate_screens(screens, partial=True) + if result: + "Активированы экраны меню!" + jump partial_menu + + "Только сохранение/загрузка": + $ screens = ["save", "load"] + $ result = mod_screen_manager.activate_screens(screens, partial=True) + if result: + "Активированы экраны сохранения!" + call screen my_mod_save + jump partial_menu + + "Переключить экран 'say'": + $ is_active = mod_screen_manager.toggle_screen("say") + if is_active: + "Экран 'say' активирован!" + else: + "Экран 'say' деактивирован!" + jump partial_menu + + "Деактивировать все": + $ mod_screen_manager.deactivate_screens() + "Все экраны деактивированы!" + jump partial_menu + + "Назад": + jump test_main_menu + +label test_individual_screens: + scene bg black + + menu individual_screens_menu: + "{b}ТЕСТ ОТДЕЛЬНЫХ ЭКРАНОВ{/b}" + + "Главное меню": + call screen my_mod_main_menu + jump individual_screens_menu + + "Игровое меню": + call screen my_mod_game_menu_selector + jump individual_screens_menu + + "Сохранение": + call screen my_mod_save + jump individual_screens_menu + + "Загрузка": + call screen my_mod_load + jump individual_screens_menu + + "Настройки": + call screen my_mod_preferences + jump individual_screens_menu + + "История текста": + call screen my_mod_text_history_screen + jump individual_screens_menu + + "Помощь": + call screen my_mod_help + jump individual_screens_menu + + "Галерея": + call screen my_mod_gallery + jump individual_screens_menu + + "Музыкальная комната": + call screen my_mod_music_room + jump individual_screens_menu + + "Тест диалога": + jump test_dialogue + + "Тест выбора": + jump test_choice + + "Назад": + jump test_main_menu + +label test_dialogue: + scene bg black + "Это тест диалогового окна." + "Обратите внимание на дизайн." + me "Привет! Это сообщение от персонажа." + "Можно открыть историю (H) или сохранить (S)." + jump individual_screens_menu + +label test_choice: + scene bg black + menu: + "Выберите вариант:" + "Вариант 1": + "Выбран вариант 1" + "Вариант 2": + "Выбран вариант 2" + "Длинный вариант текста для проверки переноса строк": + "Выбран длинный вариант" + jump individual_screens_menu + +label quick_test_all_screens: + scene bg black + + "Начинаем быстрое тестирование..." + $ mod_screen_manager.activate_screens() + + "1. Игровое меню..." + call screen my_mod_game_menu_selector + + "2. Сохранение..." + call screen my_mod_save + + "3. Настройки..." + call screen my_mod_preferences + + "4. Галерея..." + call screen my_mod_gallery + + "5. Выбор..." + menu: + "Тест?" + "Да": + pass + "Нет": + pass + + "6. Уведомление..." + $ renpy.notify(u"Тест!") + pause 1.0 + + "Быстрое тестирование завершено!" + jump test_main_menu + +label test_diagnostics: + scene bg black + + python: + manager = mod_screen_manager + status = manager.get_status() + + missing_screens = [] + for screen_name in ModConfig.DEFAULT_SCREENS: + mod_screen = "my_mod_{}".format(screen_name) + if not manager._screen_exists(mod_screen): + missing_screens.append(screen_name) + + report = u"=== ДИАГНОСТИКА ===\n\n" + report += u"Активен: {}\n".format(u"Да" if status['is_active'] else u"Нет") + report += u"Активных экранов: {}/{}\n\n".format( + len(status['active_screens']), status['total_screens'] + ) + + compat = manager.check_compatibility() + report += u"Версия Ren'Py: {}\n".format('.'.join(builtins.map(str, renpy.version_tuple[:2]))) + report += u"Совместимость: {}\n\n".format(u"OK" if compat else u"Проблемы") + + if status['active_screens']: + report += u"Активные экраны:\n" + for screen in sorted(status['active_screens']): + report += u" + {}\n".format(screen) + + if missing_screens: + report += u"\nОтсутствуют:\n" + for screen in missing_screens: + report += u" - my_mod_{}\n".format(screen) + + "[report]" + + menu: + "Экспортировать в лог?": + $ manager.logger.info(u"\n" + report) + "Экспортировано!" + "Назад": + jump test_main_menu + +label demo_interface: + scene bg black + $ mod_screen_manager.activate_screens() + + menu demo_menu: + "{b}ДЕМОНСТРАЦИЯ{/b}" + + "Тест диалога": + "Демонстрация диалогового окна." + me "Привет! Тестовое сообщение." + "Используйте H для истории, S для сохранения." + jump demo_menu + + "Тест выбора": + menu: + "Выберите:" + "Простой": + "Выбран простой" + "С форматированием {b}жирный{/b}": + "Форматирование работает!" + jump demo_menu + + "Цветовые схемы": + jump demo_colors + + "Назад": + jump test_main_menu + +label demo_colors: + menu color_menu: + "Выберите время суток:" + + "День": + $ persistent.timeofday = 'day' + "Дневная схема установлена." + jump color_menu + + "Вечер": + $ persistent.timeofday = 'sunset' + "Вечерняя схема установлена." + jump color_menu + + "Ночь": + $ persistent.timeofday = 'night' + "Ночная схема установлена." + jump color_menu + "Пролог": + $ persistent.timeofday = 'prologue' + "Прологовая схема установлена." + jump color_menu + + "Назад": + jump demo_menu \ No newline at end of file diff --git a/screens.rpy b/screens.rpy index e6089b6..b551399 100644 --- a/screens.rpy +++ b/screens.rpy @@ -135,7 +135,7 @@ screen my_mod_main_menu(): ysize 60 background Frame(Solid("#696969"), 10, 10) hover_background Frame(Solid("#808080"), 10, 10) - action [Function(my_mod_screens_diact), Function(renpy.full_restart)] + action [Function(mod_screen_manager.deactivate_screens), Function(renpy.full_restart)] text_size 32 text_color "#FFFFFF" text_hover_color "#FFFF00" @@ -896,17 +896,10 @@ screen my_mod_load(): hbox: xalign 0.5 - spacing 10 - add Solid("#FFD700"): - xsize 20 - ysize 20 text "ЗАГРУЗКА": size 48 color "#FFFFFF" bold True - add Solid("#FFD700"): - xsize 20 - ysize 20 null height 10 @@ -1302,8 +1295,6 @@ screen my_mod_music_room(): background Frame(Solid("#2F4F4F80"), 20, 20) xalign 0.5 yalign 0.5 - xsize 1200 - ysize 800 padding (40, 40) vbox: From 5d536850557556076185b11ab810eb982727d21e Mon Sep 17 00:00:00 2001 From: Tecquo Date: Thu, 23 Oct 2025 13:45:27 +0300 Subject: [PATCH 05/10] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B0=20=D0=BF=D0=BE=D0=B4=D0=B4=D0=B5=D1=80=D0=B6?= =?UTF-8?q?=D0=BA=D0=B0=20=D0=B7=D0=B0=D0=BC=D0=B5=D0=BD=D1=8B=20=D0=BA?= =?UTF-8?q?=D1=83=D1=80=D1=81=D0=BE=D1=80=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- interface.rpy | 53 +++++++++++++++++++++++++++++++++------------------ 1 file changed, 34 insertions(+), 19 deletions(-) diff --git a/interface.rpy b/interface.rpy index 88bdf35..b02c88f 100644 --- a/interface.rpy +++ b/interface.rpy @@ -1,11 +1,29 @@ init python: - import logging - from copy import deepcopy - import builtins - - # ======================== CONFIGURATION ======================== - # Централизованная конфигурация мода для легкой настройки + import logging, builtins + # На будущее, кастомный форматтер вывода логгера в консоль cmd + # Важно: в консоли RenPy цвета фиксированны, т.е. код замены цветов в нём отображается текстом, он не меняет цвет + # class CustomFormatter(logging.Formatter): + + # grey = "\x1b[38;20m" + # yellow = "\x1b[33;20m" + # red = "\x1b[31;20m" + # bold_red = "\x1b[31;1m" + # reset = "\x1b[0m" + # format = "[%(levelname)s] %(name)s: %(message)s" + + # FORMATS = { + # logging.DEBUG: yellow + format + reset, + # logging.INFO: grey + format + reset, + # logging.WARNING: red + format + reset, + # logging.ERROR: bold_red + format + reset, + # } + + # def format(self, record): + # log_fmt = self.FORMATS.get(record.levelno) + # formatter = logging.Formatter(log_fmt) + # return formatter.format(record) + class ModConfig: """Конфигурация параметров мода.""" # Основные параметры @@ -18,6 +36,7 @@ init python: MOD_MENU_MUSIC = "interface/music/main_menu.mp3" # Оригинальные настройки игры + # Захардкодил на случай кодеров, что с помощью своих версий скриптов для замены интерфейса меняют их на свои до отработки этого скрипта для замены интерфейса, чтобы точно заменились на стандартные ORIGINAL_TITLE = u"Бесконечное лето" ORIGINAL_CURSOR_PATH = "images/misc/mouse/1.png" ORIGINAL_MENU_MUSIC = "sound/music/blow_with_the_fires.ogg" @@ -44,8 +63,6 @@ init python: ENABLE_LOGGING = True LOG_LEVEL = logging.INFO - # ======================== LOGGING SETUP ======================== - class ModScreenManager: """ Менеджер для управления заменой экранов в Ren'Py. @@ -67,12 +84,12 @@ init python: self.active_screens = set() self.is_active = False - # Настройка логгера с уникальным именем для каждого мода + # логгер logger_name = u'ModScreenManager [{}]'.format(self.config.MOD_NAME) self.logger = logging.getLogger(logger_name) if self.config.ENABLE_LOGGING: self.logger.setLevel(self.config.LOG_LEVEL) - # Добавляем handler только если его еще нет (избегаем дублирования логов) + # добавляем handler только если его еще нет, чтобы дублирования не было if not self.logger.handlers: handler = logging.StreamHandler() formatter = logging.Formatter( @@ -139,7 +156,6 @@ init python: try: self.original_config = { 'window_title': config.window_title, - 'mouse': deepcopy(config.mouse.get('default', [])), 'main_menu_music': config.main_menu_music } self.logger.debug("Конфигурация сохранена") @@ -153,11 +169,11 @@ init python: try: if self.original_config: config.window_title = self.original_config['window_title'] - config.mouse['default'] = self.original_config['mouse'] + renpy.config.mouse_displayable = None config.main_menu_music = self.original_config['main_menu_music'] else: config.window_title = self.config.ORIGINAL_TITLE - config.mouse['default'] = [(self.config.ORIGINAL_CURSOR_PATH, 0, 0)] + renpy.config.mouse_displayable = None config.main_menu_music = self.config.ORIGINAL_MENU_MUSIC self.logger.debug("Конфигурация восстановлена") @@ -170,7 +186,7 @@ init python: """ try: config.window_title = self.config.MOD_NAME - config.mouse['default'] = [(self.config.MOD_CURSOR_PATH, 0, 0)] + renpy.config.mouse_displayable = MouseDisplayable(self.config.MOD_CURSOR_PATH, 0, 0) config.main_menu_music = self.config.MOD_MENU_MUSIC self.logger.debug("Конфигурация мода применена") except Exception as e: @@ -209,7 +225,6 @@ init python: except (KeyError, AttributeError) as e: self.logger.error(u"Ошибка сохранения экрана '{}': {}".format(name, e)) - # Логируем только если действительно что-то сохранили if saved_count > 0: self.logger.info(u"Сохранено {} экранов".format(saved_count)) else: @@ -235,7 +250,7 @@ init python: if screen_names is None: screen_names = self.config.DEFAULT_SCREENS - # Проверяем, не активны ли уже все запрошенные экраны + # проверяем, не активны ли уже все запрошенные экраны if not partial: requested_set = set(screen_names) if self.is_active and requested_set == self.active_screens: @@ -245,12 +260,12 @@ init python: elif self.is_active: self.deactivate_screens() - # Сохраняем экраны перед заменой + # сохраняем экраны перед заменой, чтобы точно никто не забыл сохранить if not self.save_screens(screen_names): self.logger.error("Не удалось сохранить экраны") return False - # Сохраняем конфигурацию перед первой активацией + # сохраняем конфиг перед первой активацией if not self.is_active: self._backup_config() @@ -311,7 +326,7 @@ init python: except (KeyError, AttributeError) as e: self.logger.error(u"Ошибка восстановления экрана '{}': {}".format(name, e)) - # Восстанавливаем конфигурацию если все экраны деактивированы + # восстанавливаем конфиг если все экраны деактивированы if not self.active_screens: self._restore_config() self.is_active = False From 06652bc587fb3f004c817959a964315e9313ff6d Mon Sep 17 00:00:00 2001 From: Tecquo Date: Thu, 23 Oct 2025 15:29:16 +0300 Subject: [PATCH 06/10] =?UTF-8?q?=D0=9F=D1=80=D0=B0=D0=B2=D0=BA=D0=B8,=20?= =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20?= =?UTF-8?q?=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D1=8B=20=D1=81=20=D0=BA=D0=B0?= =?UTF-8?q?=D1=81=D1=82=D0=BE=D0=BC=D0=BD=D1=8B=D0=BC=20=D0=BA=D0=BE=D0=BD?= =?UTF-8?q?=D1=84=D0=B8=D0=B3=D0=BE=D0=BC=20=D0=B8=20=D0=BE=D1=80=D0=B8?= =?UTF-8?q?=D0=B3=D0=B8=D0=BD=D0=B0=D0=BB=D0=B8=D0=B7=D0=B0=D1=86=D0=B8?= =?UTF-8?q?=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- interface.rpy => MSM.rpy | 50 +++++----- label.rpy => example.rpy | 208 ++++++++++++++++++++++++--------------- screens.rpy | 26 ++--- 3 files changed, 167 insertions(+), 117 deletions(-) rename interface.rpy => MSM.rpy (93%) rename label.rpy => example.rpy (52%) diff --git a/interface.rpy b/MSM.rpy similarity index 93% rename from interface.rpy rename to MSM.rpy index b02c88f..a31e869 100644 --- a/interface.rpy +++ b/MSM.rpy @@ -24,7 +24,7 @@ init python: # formatter = logging.Formatter(log_fmt) # return formatter.format(record) - class ModConfig: + class ModScreenManagerConfig: """Конфигурация параметров мода.""" # Основные параметры MOD_NAME = u"Мой мод" # Название вашего мода @@ -71,7 +71,7 @@ init python: обработкой ошибок и поддержкой частичной замены. """ - def __init__(self, config=ModConfig): + def __init__(self, config=ModScreenManagerConfig): """ Инициализация менеджера экранов. @@ -84,6 +84,8 @@ init python: self.active_screens = set() self.is_active = False + self.activate_screens_after_load() + # логгер logger_name = u'ModScreenManager [{}]'.format(self.config.MOD_NAME) self.logger = logging.getLogger(logger_name) @@ -97,7 +99,7 @@ init python: ) handler.setFormatter(formatter) self.logger.addHandler(handler) - + def check_compatibility(self): """ Проверка совместимости с текущей версией Ren'Py. @@ -363,25 +365,23 @@ init python: 'active_screens': list(self.active_screens), 'total_screens': len(self.config.DEFAULT_SCREENS) } - - # Создаем глобальный экземпляр менеджера - mod_screen_manager = ModScreenManager() - - def my_mod_activate_after_load(): - """ - Автоматическая активация мода после загрузки сохранения. - Активируется если в имени сохранения есть идентификатор мода. - """ - try: - global save_name - if ModConfig.MOD_SAVE_IDENTIFIER in save_name: - mod_screen_manager.activate_screens() - mod_screen_manager.logger.info("Мод активирован после загрузки") - except NameError: - # save_name может быть не определен - mod_screen_manager.logger.error("save_name не определен") - except Exception as e: - mod_screen_manager.logger.error(u"Ошибка автоактивации: {}".format(e)) - - # Регистрируем callback для автоматической активации - config.after_load_callbacks.append(my_mod_activate_after_load) + + def after_load_callback(self): + """ + Автоматическая активация мода после загрузки сохранения. + Активируется если в имени сохранения есть идентификатор мода. + """ + try: + global save_name + if self.config.MOD_SAVE_IDENTIFIER in save_name: + self.activate_screens() + self.logger.info("Мод активирован после загрузки") + except NameError: + # save_name может быть не определен + self.logger.error("save_name не определен") + except Exception as e: + self.logger.error(u"Ошибка автоактивации: {}".format(e)) + + def activate_screens_after_load(self): + """Добавляем написанный нами коллбэк в список коллбэков после загрузки сохранения""" + config.after_load_callbacks.append(self.after_load_callback) \ No newline at end of file diff --git a/label.rpy b/example.rpy similarity index 52% rename from label.rpy rename to example.rpy index d04f5ec..e051060 100644 --- a/label.rpy +++ b/example.rpy @@ -1,180 +1,233 @@ +init python: + # Кастомный конфиг на основе шаблона + class CustomConfigForModScreenManager(ModScreenManagerConfig): + # Переопределяем параметры + MOD_NAME = u"Менеджер экранов" + MOD_SAVE_IDENTIFIER = "Тест замены интерфейса с помощью менеджера экранов" + + # Кастомные пути + MOD_CURSOR_PATH = "interface/images/1.png" + MOD_MENU_MUSIC = "interface/music/main_menu.mp3" + + # Выбираем только нужные экраны + DEFAULT_SCREENS = [ + "main_menu", + "game_menu_selector", + "quit", + "say", + "preferences", + "save", + "load", + "nvl", + "choice", + "text_history_screen", + "yesno_prompt", + "skip_indicator", + "history", + "help", + ] + + # Включаем подробное логирование для отладки + ENABLE_LOGGING = True + LOG_LEVEL = logging.DEBUG init: - $ mods["interface_test"] = u"Тест замены интерфейса" + $ mods["mod_screen_manager_init"] = u"Менеджер экранов" default persistent.timeofday = 'day' -label interface_test: +label mod_screen_manager_init: + # Создаем глобальный экземпляр менеджера с нашим кастомным шаблоном + $ my_mod_screen_manager = ModScreenManager(CustomConfigForModScreenManager) + + # Даём имя сохранению, чтобы при загрузке сейва из нашего мода у нас автоматически включался наш интерфейс. + # Важно: проверяем, что в нашем имени сохранения будет присутствовать MOD_SAVE_IDENTIFIER из конфига мода, по нему он проверяем мод, сейв которого мы грузим. + # Пример: MOD_SAVE_IDENTIFIER = "Мой мод", значит save_name = "Мой мод. День 1.", "Мой мод начало" или "Старт Мой мод", т.е. чтобы в имени сохранения всегда присутстваол наш индентификатор мода в том регистре, в котором он написан + + $ save_name = "Тест замены интерфейса с помощью менеджера экранов" + # или, если Вы боитесь забыть указание идентификатора мода, то можно сделать так: + #$ save_name = MyCustomConfig.MOD_SAVE_IDENTIFIER + # если надо добавить ещё какой-то текст для например обозначения дня, то + #$ save_name = MyCustomConfig.MOD_SAVE_IDENTIFIER + "Любой текст, который вы хотите" + # так он всегда будет в Вашем имени сохранения + + jump mod_screen_manager_test + +label mod_screen_manager_test: scene bg black - menu test_main_menu: - "{size=+10}{b}СИСТЕМА ЗАМЕНЫ ЭКРАНОВ{/b}{/size}\n\nВыберите режим тестирования:" + menu mod_screen_manager_test_main_menu: + "{size=+10}{b}МЕНЕДЖЕР ЭКРАНОВ{/b}{/size}\n\nВыберите режим тестирования:" "1. Управление модом (вкл/выкл)": - jump test_mod_control + jump mod_screen_manager_test_mod_control "2. Частичная замена экранов": - jump test_partial_replacement + jump mod_screen_manager_test_partial_replacement "3. Тест отдельных экранов": - jump test_individual_screens + jump mod_screen_manager_test_individual_screens "4. Быстрый тест всех экранов": - jump quick_test_all_screens + jump mod_screen_manager_quick_test_all_screens "5. Диагностика системы": - jump test_diagnostics + jump mod_screen_manager_test_diagnostics "6. Демонстрация интерфейса": - jump demo_interface + jump mod_screen_manager_demo_interface "Выход": - return + jump mod_screen_manager_exit -label test_mod_control: +label mod_screen_manager_exit: + $ my_mod_screen_manager.deactivate_screens() # Обязательно отключаем наши экраны при выходе из мода, будь то просто добавив функцию на кнопку выхода или при завершении мода через вызов функции + return + +label mod_screen_manager_test_mod_control: scene bg black python: - status = mod_screen_manager.get_status() + status = my_mod_screen_manager.get_status() status_text = u"Активен" if status['is_active'] else u"Неактивен" - menu mod_control_menu: + menu mod_screen_manager_mod_control_menu: "{b}УПРАВЛЕНИЕ МОДОМ{/b}\n\nТекущий статус: [status_text]" "Активировать мод (все экраны)": - $ result = mod_screen_manager.activate_screens() + $ result = my_mod_screen_manager.activate_screens() if result: "Мод успешно активирован!" "Все экраны заменены на кастомные." else: "Ошибка активации мода!" - jump test_mod_control + jump mod_screen_manager_test_mod_control "Деактивировать мод (вернуть оригинал)": - $ result = mod_screen_manager.deactivate_screens() + $ result = my_mod_screen_manager.deactivate_screens() if result: "Мод деактивирован!" "Восстановлены оригинальные экраны." else: "Ошибка деактивации!" - jump test_mod_control + jump mod_screen_manager_test_mod_control "Проверить совместимость": - $ compat = mod_screen_manager.check_compatibility() + $ compat = my_mod_screen_manager.check_compatibility() if compat: "Версия Ren'Py совместима с модом!" else: "ВНИМАНИЕ: Возможны проблемы совместимости!" - jump test_mod_control + jump mod_screen_manager_test_mod_control "Назад": - jump test_main_menu + jump mod_screen_manager_test_main_menu -label test_partial_replacement: +label mod_screen_manager_test_partial_replacement: scene bg black - menu partial_menu: + menu mod_screen_manager_partial_menu: "{b}ЧАСТИЧНАЯ ЗАМЕНА ЭКРАНОВ{/b}\n\nВыберите группу экранов:" "Только диалоговые (say, nvl, choice)": $ screens = ["say", "nvl", "choice"] - $ result = mod_screen_manager.activate_screens(screens, partial=True) + $ result = my_mod_screen_manager.activate_screens(screens, partial=True) if result: "Активированы диалоговые экраны!" "Тестируем диалог..." "Это тестовое сообщение в кастомном окне." - jump partial_menu + jump mod_screen_manager_partial_menu "Только меню (main_menu, game_menu_selector)": $ screens = ["main_menu", "game_menu_selector", "quit"] - $ result = mod_screen_manager.activate_screens(screens, partial=True) + $ result = my_mod_screen_manager.activate_screens(screens, partial=True) if result: "Активированы экраны меню!" - jump partial_menu + jump mod_screen_manager_partial_menu "Только сохранение/загрузка": $ screens = ["save", "load"] - $ result = mod_screen_manager.activate_screens(screens, partial=True) + $ result = my_mod_screen_manager.activate_screens(screens, partial=True) if result: "Активированы экраны сохранения!" call screen my_mod_save - jump partial_menu + jump mod_screen_manager_partial_menu "Переключить экран 'say'": - $ is_active = mod_screen_manager.toggle_screen("say") + $ is_active = my_mod_screen_manager.toggle_screen("say") if is_active: "Экран 'say' активирован!" else: "Экран 'say' деактивирован!" - jump partial_menu + jump mod_screen_manager_partial_menu "Деактивировать все": - $ mod_screen_manager.deactivate_screens() + $ my_mod_screen_manager.deactivate_screens() "Все экраны деактивированы!" - jump partial_menu + jump mod_screen_manager_partial_menu "Назад": - jump test_main_menu + jump mod_screen_manager_test_main_menu -label test_individual_screens: +label mod_screen_manager_test_individual_screens: scene bg black - menu individual_screens_menu: + menu mod_screen_manager_individual_screens_menu: "{b}ТЕСТ ОТДЕЛЬНЫХ ЭКРАНОВ{/b}" "Главное меню": call screen my_mod_main_menu - jump individual_screens_menu + jump mod_screen_manager_individual_screens_menu "Игровое меню": call screen my_mod_game_menu_selector - jump individual_screens_menu + jump mod_screen_manager_individual_screens_menu "Сохранение": call screen my_mod_save - jump individual_screens_menu + jump mod_screen_manager_individual_screens_menu "Загрузка": call screen my_mod_load - jump individual_screens_menu + jump mod_screen_manager_individual_screens_menu "Настройки": call screen my_mod_preferences - jump individual_screens_menu + jump mod_screen_manager_individual_screens_menu "История текста": call screen my_mod_text_history_screen - jump individual_screens_menu + jump mod_screen_manager_individual_screens_menu "Помощь": call screen my_mod_help - jump individual_screens_menu + jump mod_screen_manager_individual_screens_menu "Галерея": call screen my_mod_gallery - jump individual_screens_menu + jump mod_screen_manager_individual_screens_menu "Музыкальная комната": call screen my_mod_music_room - jump individual_screens_menu + jump mod_screen_manager_individual_screens_menu "Тест диалога": - jump test_dialogue + jump mod_screen_manager_test_dialogue "Тест выбора": - jump test_choice + jump mod_screen_manager_test_choice "Назад": - jump test_main_menu + jump mod_screen_manager_test_main_menu -label test_dialogue: +label mod_screen_manager_test_dialogue: scene bg black "Это тест диалогового окна." "Обратите внимание на дизайн." me "Привет! Это сообщение от персонажа." "Можно открыть историю (H) или сохранить (S)." - jump individual_screens_menu + jump mod_screen_manager_individual_screens_menu -label test_choice: +label mod_screen_manager_test_choice: scene bg black menu: "Выберите вариант:" @@ -184,13 +237,13 @@ label test_choice: "Выбран вариант 2" "Длинный вариант текста для проверки переноса строк": "Выбран длинный вариант" - jump individual_screens_menu + jump mod_screen_manager_individual_screens_menu -label quick_test_all_screens: +label mod_screen_manager_quick_test_all_screens: scene bg black "Начинаем быстрое тестирование..." - $ mod_screen_manager.activate_screens() + $ my_mod_screen_manager.activate_screens() "1. Игровое меню..." call screen my_mod_game_menu_selector @@ -217,17 +270,17 @@ label quick_test_all_screens: pause 1.0 "Быстрое тестирование завершено!" - jump test_main_menu + jump mod_screen_manager_test_main_menu -label test_diagnostics: +label mod_screen_manager_test_diagnostics: scene bg black python: - manager = mod_screen_manager + manager = my_mod_screen_manager status = manager.get_status() missing_screens = [] - for screen_name in ModConfig.DEFAULT_SCREENS: + for screen_name in CustomConfigForModScreenManager.DEFAULT_SCREENS: mod_screen = "my_mod_{}".format(screen_name) if not manager._screen_exists(mod_screen): missing_screens.append(screen_name) @@ -252,27 +305,24 @@ label test_diagnostics: for screen in missing_screens: report += u" - my_mod_{}\n".format(screen) - "[report]" + $ manager.logger.info(u"\n" + report) + + "Результат диагностики экспортирован в консоль Ren'Py!" - menu: - "Экспортировать в лог?": - $ manager.logger.info(u"\n" + report) - "Экспортировано!" - "Назад": - jump test_main_menu + jump mod_screen_manager_test_main_menu -label demo_interface: +label mod_screen_manager_demo_interface: scene bg black - $ mod_screen_manager.activate_screens() + $ my_mod_screen_manager.activate_screens() - menu demo_menu: + menu mod_screen_manager_demo_menu: "{b}ДЕМОНСТРАЦИЯ{/b}" "Тест диалога": "Демонстрация диалогового окна." me "Привет! Тестовое сообщение." "Используйте H для истории, S для сохранения." - jump demo_menu + jump mod_screen_manager_demo_menu "Тест выбора": menu: @@ -281,36 +331,36 @@ label demo_interface: "Выбран простой" "С форматированием {b}жирный{/b}": "Форматирование работает!" - jump demo_menu + jump mod_screen_manager_demo_menu "Цветовые схемы": - jump demo_colors + jump mod_screen_manager_demo_colors "Назад": - jump test_main_menu + jump mod_screen_manager_test_main_menu -label demo_colors: - menu color_menu: +label mod_screen_manager_demo_colors: + menu mod_screen_manager_color_menu: "Выберите время суток:" "День": $ persistent.timeofday = 'day' "Дневная схема установлена." - jump color_menu + jump mod_screen_manager_color_menu "Вечер": $ persistent.timeofday = 'sunset' "Вечерняя схема установлена." - jump color_menu + jump mod_screen_manager_color_menu "Ночь": $ persistent.timeofday = 'night' "Ночная схема установлена." - jump color_menu + jump mod_screen_manager_color_menu "Пролог": $ persistent.timeofday = 'prologue' "Прологовая схема установлена." - jump color_menu + jump mod_screen_manager_color_menu "Назад": - jump demo_menu \ No newline at end of file + jump mod_screen_manager_demo_menu \ No newline at end of file diff --git a/screens.rpy b/screens.rpy index b551399..3a5c720 100644 --- a/screens.rpy +++ b/screens.rpy @@ -1,7 +1,7 @@ # Пример кастомных экранов init python: - interface_color_schemes = { + mod_screen_manager_interface_color_schemes = { 'day': { 'bg': '#87CEEB', 'box': '#F0E68C', @@ -36,9 +36,9 @@ init python: } } - def interface_get_color_scheme(): + def mod_screen_manager_interface_get_color_scheme(): timeofday = getattr(persistent, 'timeofday', 'day') - return interface_color_schemes.get(timeofday, interface_color_schemes['day']) + return mod_screen_manager_interface_color_schemes.get(timeofday, mod_screen_manager_interface_color_schemes['day']) screen my_mod_main_menu(): tag menu @@ -135,7 +135,7 @@ screen my_mod_main_menu(): ysize 60 background Frame(Solid("#696969"), 10, 10) hover_background Frame(Solid("#808080"), 10, 10) - action [Function(mod_screen_manager.deactivate_screens), Function(renpy.full_restart)] + action [Function(my_mod_screen_manager.deactivate_screens), Function(renpy.full_restart)] # Если хотите, чтобы по выходу из Вашего мода отключался Ваш иннтерфейс и возвращался оригинальный, не забудьте в Action кнопки для выхода из мода добавить my_mod_screen_manager.deactivate_screens text_size 32 text_color "#FFFFFF" text_hover_color "#FFFF00" @@ -159,7 +159,7 @@ screen my_mod_game_menu_selector(): modal True python: - scheme = interface_get_color_scheme() + scheme = mod_screen_manager_interface_get_color_scheme() button: background Solid("#00000080") @@ -255,7 +255,7 @@ screen my_mod_choice(items): modal True python: - scheme = interface_get_color_scheme() + scheme = mod_screen_manager_interface_get_color_scheme() add Solid("#00000060") @@ -296,7 +296,7 @@ screen my_mod_choice(items): screen my_mod_say(who, what, **kwargs): python: - scheme = interface_get_color_scheme() + scheme = mod_screen_manager_interface_get_color_scheme() window: id "window" @@ -397,7 +397,7 @@ screen my_mod_say(who, what, **kwargs): screen my_mod_nvl(dialogue, items=None): python: - scheme = interface_get_color_scheme() + scheme = mod_screen_manager_interface_get_color_scheme() window: background Frame(Solid(scheme['box'] + "E0"), 30, 30) @@ -507,7 +507,7 @@ screen my_mod_notify(message): zorder 100 python: - scheme = interface_get_color_scheme() + scheme = mod_screen_manager_interface_get_color_scheme() if not config.skipping: frame: @@ -529,7 +529,7 @@ screen my_mod_text_history_screen(): modal True python: - scheme = interface_get_color_scheme() + scheme = mod_screen_manager_interface_get_color_scheme() button: background Solid("#00000080") @@ -605,7 +605,7 @@ screen my_mod_skip_indicator(): zorder 100 python: - scheme = interface_get_color_scheme() + scheme = mod_screen_manager_interface_get_color_scheme() frame: background Frame(Solid(scheme['box']), 10, 10) @@ -641,7 +641,7 @@ screen my_mod_history(): modal True python: - scheme = interface_get_color_scheme() + scheme = mod_screen_manager_interface_get_color_scheme() button: background Solid("#00000080") @@ -1438,7 +1438,7 @@ screen my_mod_quick_menu(): zorder 100 python: - scheme = interface_get_color_scheme() + scheme = mod_screen_manager_interface_get_color_scheme() hbox: xalign 0.5 From 4544027cd79bf2e877afea35f59a1a8e017c2d11 Mon Sep 17 00:00:00 2001 From: Tecquo <46904988+Tecquo@users.noreply.github.com> Date: Tue, 4 Nov 2025 23:10:59 +0300 Subject: [PATCH 07/10] =?UTF-8?q?=D0=9F=D1=80=D0=B0=D0=B2=D0=BA=D0=B0=20?= =?UTF-8?q?=D0=B8=D0=BC=D0=B5=D0=BD=D0=B8=20=D0=BC=D0=BE=D0=B4=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MSM.rpy | 4 ++-- example.rpy | 4 ++-- screens.rpy | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/MSM.rpy b/MSM.rpy index a31e869..884a46a 100644 --- a/MSM.rpy +++ b/MSM.rpy @@ -32,8 +32,8 @@ init python: RENPY_MIN_VERSION = "7.0" # Минимальная совместимая версия Ren'Py # Пути к ресурсам - MOD_CURSOR_PATH = "interface/images/1.png" - MOD_MENU_MUSIC = "interface/music/main_menu.mp3" + MOD_CURSOR_PATH = "ESModScreenManager/images/1.png" + MOD_MENU_MUSIC = "ESModScreenManager/music/main_menu.mp3" # Оригинальные настройки игры # Захардкодил на случай кодеров, что с помощью своих версий скриптов для замены интерфейса меняют их на свои до отработки этого скрипта для замены интерфейса, чтобы точно заменились на стандартные diff --git a/example.rpy b/example.rpy index e051060..fe1928d 100644 --- a/example.rpy +++ b/example.rpy @@ -6,8 +6,8 @@ init python: MOD_SAVE_IDENTIFIER = "Тест замены интерфейса с помощью менеджера экранов" # Кастомные пути - MOD_CURSOR_PATH = "interface/images/1.png" - MOD_MENU_MUSIC = "interface/music/main_menu.mp3" + MOD_CURSOR_PATH = "ESModScreenManager/images/1.png" + MOD_MENU_MUSIC = "ESModScreenManager/music/main_menu.mp3" # Выбираем только нужные экраны DEFAULT_SCREENS = [ diff --git a/screens.rpy b/screens.rpy index 3a5c720..3350a58 100644 --- a/screens.rpy +++ b/screens.rpy @@ -1286,7 +1286,7 @@ screen my_mod_music_room(): python: music_list = [ - ("140 kilograms of sex — Incelthread", "interface/music/main_menu.mp3"), + ("140 kilograms of sex — Incelthread", "ESModScreenManager/music/main_menu.mp3"), ] add Solid("#1C1C1C") From fda2899a3862e63ee19e0b2f292b059c4afe8efe Mon Sep 17 00:00:00 2001 From: Tecquo Date: Thu, 6 Nov 2025 13:32:54 +0300 Subject: [PATCH 08/10] =?UTF-8?q?=D0=9E=D1=82=D0=BA=D0=BB=D1=8E=D1=87?= =?UTF-8?q?=D0=B8=D0=BB=20=D0=BB=D0=BE=D0=B3=D0=B3=D0=B5=D1=80=20=D0=B2=20?= =?UTF-8?q?=D0=BF=D1=80=D0=B8=D0=BC=D0=B5=D1=80=D0=B5=20+=20=D0=BF=D0=BE?= =?UTF-8?q?=D0=B4=D0=B4=D0=B5=D1=80=D0=B6=D0=BA=D0=B0=20=D0=B8=D0=BC=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=20=D0=BC=D0=BE=D0=B4=D0=B0=20=D0=B2=20=D1=81=D0=B5?= =?UTF-8?q?=D0=B9=D0=B5=20=D0=B4=D0=BB=D1=8F=20=D0=B2=D0=BA=D0=BB=D1=8E?= =?UTF-8?q?=D1=87=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B8=D0=BD=D1=82=D0=B5=D1=80?= =?UTF-8?q?=D1=84=D0=B5=D0=B9=D1=81=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MSM.rpy | 8 ++++---- example.rpy | 17 ++++++++++------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/MSM.rpy b/MSM.rpy index 884a46a..80c40b2 100644 --- a/MSM.rpy +++ b/MSM.rpy @@ -35,7 +35,7 @@ init python: MOD_CURSOR_PATH = "ESModScreenManager/images/1.png" MOD_MENU_MUSIC = "ESModScreenManager/music/main_menu.mp3" - # Оригинальные настройки игры + # Оригинальные настройки Летонька # Захардкодил на случай кодеров, что с помощью своих версий скриптов для замены интерфейса меняют их на свои до отработки этого скрипта для замены интерфейса, чтобы точно заменились на стандартные ORIGINAL_TITLE = u"Бесконечное лето" ORIGINAL_CURSOR_PATH = "images/misc/mouse/1.png" @@ -153,7 +153,7 @@ init python: def _backup_config(self): """ - Создание резервной копии конфигурации игры. + Создание резервной копии конфигурации БЛ. """ try: self.original_config = { @@ -166,7 +166,7 @@ init python: def _restore_config(self): """ - Восстановление оригинальной конфигурации игры. + Восстановление оригинальной конфигурации БЛ. """ try: if self.original_config: @@ -373,7 +373,7 @@ init python: """ try: global save_name - if self.config.MOD_SAVE_IDENTIFIER in save_name: + if (self.config.MOD_SAVE_IDENTIFIER in save_name) or (self.config.MOD_NAME in save_name): self.activate_screens() self.logger.info("Мод активирован после загрузки") except NameError: diff --git a/example.rpy b/example.rpy index fe1928d..8742dbd 100644 --- a/example.rpy +++ b/example.rpy @@ -27,9 +27,8 @@ init python: "help", ] - # Включаем подробное логирование для отладки - ENABLE_LOGGING = True - LOG_LEVEL = logging.DEBUG + # логирование + ENABLE_LOGGING = False init: $ mods["mod_screen_manager_init"] = u"Менеджер экранов" @@ -40,14 +39,18 @@ label mod_screen_manager_init: $ my_mod_screen_manager = ModScreenManager(CustomConfigForModScreenManager) # Даём имя сохранению, чтобы при загрузке сейва из нашего мода у нас автоматически включался наш интерфейс. - # Важно: проверяем, что в нашем имени сохранения будет присутствовать MOD_SAVE_IDENTIFIER из конфига мода, по нему он проверяем мод, сейв которого мы грузим. - # Пример: MOD_SAVE_IDENTIFIER = "Мой мод", значит save_name = "Мой мод. День 1.", "Мой мод начало" или "Старт Мой мод", т.е. чтобы в имени сохранения всегда присутстваол наш индентификатор мода в том регистре, в котором он написан + # Важно: проверяем, что в нашем имени сохранения будет присутствовать MOD_SAVE_IDENTIFIER или MOD_NAME из конфига мода, по нему он проверяем мод, сейв которого мы грузим. + # Пример: MOD_SAVE_IDENTIFIER = "Мой мод" или MOD_NAME = "Мой мод", значит save_name = "Мой мод. День 1.", "Мой мод начало" или "Старт Мой мод", т.е. чтобы в имени сохранения всегда присутстваол наш индентификатор мода или имя мода в том регистре, в котором он написан - $ save_name = "Тест замены интерфейса с помощью менеджера экранов" - # или, если Вы боитесь забыть указание идентификатора мода, то можно сделать так: + $ save_name = "Менеджер модов"#"Тест замены интерфейса с помощью менеджера экранов" + # или, если Вы боитесь забыть об указании идентификатора или имени мода, то можно сделать так: #$ save_name = MyCustomConfig.MOD_SAVE_IDENTIFIER + # или + #$ save_name = MyCustomConfig.MOD_NAME # если надо добавить ещё какой-то текст для например обозначения дня, то #$ save_name = MyCustomConfig.MOD_SAVE_IDENTIFIER + "Любой текст, который вы хотите" + # или + #$ save_name = MyCustomConfig.MOD_NAME + "Любой текст, который вы хотите" # так он всегда будет в Вашем имени сохранения jump mod_screen_manager_test From f047c6d3c02bc8a31ee3ec7c08ca637e34832275 Mon Sep 17 00:00:00 2001 From: Tecquo Date: Thu, 6 Nov 2025 14:04:08 +0300 Subject: [PATCH 09/10] =?UTF-8?q?=D0=97=D0=B0=D0=BC=D0=B5=D0=BD=D0=B0=20?= =?UTF-8?q?=D0=BB=D0=BE=D0=B3=D0=B3=D0=B5=D1=80=D0=B0=20=D0=BD=D0=B0=20?= =?UTF-8?q?=D1=81=D0=B0=D0=BC=D0=BE=D0=BF=D0=B8=D1=81=D0=BD=D1=8B=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MSM.rpy | 22 +++++--------- logger.rpy | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+), 14 deletions(-) create mode 100644 logger.rpy diff --git a/MSM.rpy b/MSM.rpy index 80c40b2..c9ca298 100644 --- a/MSM.rpy +++ b/MSM.rpy @@ -1,5 +1,5 @@ init python: - import logging, builtins + import builtins # На будущее, кастомный форматтер вывода логгера в консоль cmd # Важно: в консоли RenPy цвета фиксированны, т.е. код замены цветов в нём отображается текстом, он не меняет цвет @@ -59,9 +59,9 @@ init python: "help", ] - # Настройки логирования + # логгер ENABLE_LOGGING = True - LOG_LEVEL = logging.INFO + LOG_LEVEL = 20 # INFO class ModScreenManager: """ @@ -88,17 +88,11 @@ init python: # логгер logger_name = u'ModScreenManager [{}]'.format(self.config.MOD_NAME) - self.logger = logging.getLogger(logger_name) - if self.config.ENABLE_LOGGING: - self.logger.setLevel(self.config.LOG_LEVEL) - # добавляем handler только если его еще нет, чтобы дублирования не было - if not self.logger.handlers: - handler = logging.StreamHandler() - formatter = logging.Formatter( - '[%(levelname)s] %(name)s: %(message)s' - ) - handler.setFormatter(formatter) - self.logger.addHandler(handler) + self.logger = ModScreenManagerLogger( + name=logger_name, + level=self.config.LOG_LEVEL if self.config.ENABLE_LOGGING else ModScreenManagerLogger.ERROR + 10, + enabled=self.config.ENABLE_LOGGING + ) def check_compatibility(self): """ diff --git a/logger.rpy b/logger.rpy new file mode 100644 index 0000000..2c4f6f6 --- /dev/null +++ b/logger.rpy @@ -0,0 +1,86 @@ +init python: + # Простой логгер для Ren'Py, совместимый с сериализацией + class ModScreenManagerLogger: + """ + Логгер MSM с поддержкой сериализации + """ + + # Уровни логирования + DEBUG = 10 + INFO = 20 + WARNING = 30 + ERROR = 40 + + LEVEL_NAMES = { + DEBUG: "DEBUG", + INFO: "INFO", + WARNING: "WARNING", + ERROR: "ERROR" + } + + def __init__(self, name, level=INFO, enabled=True): + """ + Инициализация логгера. + + Args: + name: Имя логгера + level: Минимальный уровень для вывода сообщений + enabled: Включен ли логгер + """ + self.name = name + self.level = level + self.enabled = enabled + + def _log(self, level, message): + """Внутренний метод для вывода сообщений.""" + if not self.enabled or level < self.level: + return + + level_name = self.LEVEL_NAMES.get(level, "UNKNOWN") + formatted_message = u"[{}] {}: {}".format(level_name, self.name, message) + + renpy.display.log.write(formatted_message) + + # выводим в стандартный вывод для отладки + try: + print(formatted_message.encode('utf-8')) + except: + print(formatted_message) + + def debug(self, message): + """Вывод отладочного сообщения.""" + self._log(self.DEBUG, message) + + def info(self, message): + """Вывод информационного сообщения.""" + self._log(self.INFO, message) + + def warning(self, message): + """Вывод предупреждения.""" + self._log(self.WARNING, message) + + def error(self, message): + """Вывод ошибки.""" + self._log(self.ERROR, message) + + def setLevel(self, level): + """Установка уровня логирования.""" + self.level = level + + def set_enabled(self, enabled): + """Включение/выключение логгера.""" + self.enabled = enabled + + def create_logger(name, level=ModScreenManagerLogger.INFO, enabled=True): + """ + Создание логгера + + Args: + name: Имя логгера + level: Уровень логирования (по умолчанию INFO) + enabled: Включен ли логгер (по умолчанию True) + + Returns: + ModScreenManagerLogger: Экземпляр логгера + """ + return ModScreenManagerLogger(name, level, enabled) \ No newline at end of file From c11275758dd7fabaa1954e350303d03275978019 Mon Sep 17 00:00:00 2001 From: Tecquo Date: Mon, 8 Dec 2025 14:18:39 +0300 Subject: [PATCH 10/10] =?UTF-8?q?=D0=9F=D1=80=D0=B0=D0=B2=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MSM.rpy | 22 ++++++++++------------ example.rpy | 11 +++++------ logger.rpy | 8 ++++---- screens.rpy | 2 +- 4 files changed, 20 insertions(+), 23 deletions(-) diff --git a/MSM.rpy b/MSM.rpy index c9ca298..5a05206 100644 --- a/MSM.rpy +++ b/MSM.rpy @@ -1,4 +1,4 @@ -init python: +init -1 python: import builtins # На будущее, кастомный форматтер вывода логгера в консоль cmd @@ -74,16 +74,15 @@ init python: def __init__(self, config=ModScreenManagerConfig): """ Инициализация менеджера экранов. - + Args: config: Класс конфигурации с параметрами мода """ self.config = config - self.original_screens = {} self.original_config = {} self.active_screens = set() self.is_active = False - + self.activate_screens_after_load() # логгер @@ -182,7 +181,7 @@ init python: """ try: config.window_title = self.config.MOD_NAME - renpy.config.mouse_displayable = MouseDisplayable(self.config.MOD_CURSOR_PATH, 0, 0) + renpy.config.mouse_displayable = MouseDisplayable(self.config.MOD_CURSOR_PATH, 0, 0) config.main_menu_music = self.config.MOD_MENU_MUSIC self.logger.debug("Конфигурация мода применена") except Exception as e: @@ -201,26 +200,25 @@ init python: """ if screen_names is None: screen_names = self.config.DEFAULT_SCREENS - + saved_count = 0 for name in screen_names: try: if self._screen_exists(name): original_key = (name, None) backup_key = ("mod_backup_{}".format(name), None) - + # Сохраняем только если еще не сохранен - if original_key not in self.original_screens: - self.original_screens[original_key] = renpy.display.screen.screens[original_key] + if backup_key not in renpy.display.screen.screens: renpy.display.screen.screens[backup_key] = renpy.display.screen.screens[original_key] saved_count += 1 self.logger.debug(u"Экран '{}' сохранен".format(name)) else: self.logger.warning(u"Экран '{}' не найден".format(name)) - + except (KeyError, AttributeError) as e: self.logger.error(u"Ошибка сохранения экрана '{}': {}".format(name, e)) - + if saved_count > 0: self.logger.info(u"Сохранено {} экранов".format(saved_count)) else: @@ -378,4 +376,4 @@ init python: def activate_screens_after_load(self): """Добавляем написанный нами коллбэк в список коллбэков после загрузки сохранения""" - config.after_load_callbacks.append(self.after_load_callback) \ No newline at end of file + config.after_load_callbacks.append(self.after_load_callback) diff --git a/example.rpy b/example.rpy index 8742dbd..f2fed50 100644 --- a/example.rpy +++ b/example.rpy @@ -31,18 +31,17 @@ init python: ENABLE_LOGGING = False init: $ mods["mod_screen_manager_init"] = u"Менеджер экранов" + # Создаем глобальный экземпляр менеджера с нашим кастомным шаблоном + $ my_mod_screen_manager = ModScreenManager(CustomConfigForModScreenManager) default persistent.timeofday = 'day' -label mod_screen_manager_init: - # Создаем глобальный экземпляр менеджера с нашим кастомным шаблоном - $ my_mod_screen_manager = ModScreenManager(CustomConfigForModScreenManager) - +label mod_screen_manager_init: # Даём имя сохранению, чтобы при загрузке сейва из нашего мода у нас автоматически включался наш интерфейс. - # Важно: проверяем, что в нашем имени сохранения будет присутствовать MOD_SAVE_IDENTIFIER или MOD_NAME из конфига мода, по нему он проверяем мод, сейв которого мы грузим. + # Важно: проверяем, что в нашем имени сохранения будет присутствовать MOD_SAVE_IDENTIFIER или MOD_NAME из конфига мода, по нему мы проверяем мод, сейв которого мы грузим. # Пример: MOD_SAVE_IDENTIFIER = "Мой мод" или MOD_NAME = "Мой мод", значит save_name = "Мой мод. День 1.", "Мой мод начало" или "Старт Мой мод", т.е. чтобы в имени сохранения всегда присутстваол наш индентификатор мода или имя мода в том регистре, в котором он написан - $ save_name = "Менеджер модов"#"Тест замены интерфейса с помощью менеджера экранов" + $ save_name = "Менеджер экранов"#"Тест замены интерфейса с помощью менеджера экранов" # или, если Вы боитесь забыть об указании идентификатора или имени мода, то можно сделать так: #$ save_name = MyCustomConfig.MOD_SAVE_IDENTIFIER # или diff --git a/logger.rpy b/logger.rpy index 2c4f6f6..587718f 100644 --- a/logger.rpy +++ b/logger.rpy @@ -1,4 +1,4 @@ -init python: +init -1 python: # Простой логгер для Ren'Py, совместимый с сериализацией class ModScreenManagerLogger: """ @@ -35,12 +35,12 @@ init python: """Внутренний метод для вывода сообщений.""" if not self.enabled or level < self.level: return - + level_name = self.LEVEL_NAMES.get(level, "UNKNOWN") formatted_message = u"[{}] {}: {}".format(level_name, self.name, message) - + renpy.display.log.write(formatted_message) - + # выводим в стандартный вывод для отладки try: print(formatted_message.encode('utf-8')) diff --git a/screens.rpy b/screens.rpy index 3350a58..03d1030 100644 --- a/screens.rpy +++ b/screens.rpy @@ -135,7 +135,7 @@ screen my_mod_main_menu(): ysize 60 background Frame(Solid("#696969"), 10, 10) hover_background Frame(Solid("#808080"), 10, 10) - action [Function(my_mod_screen_manager.deactivate_screens), Function(renpy.full_restart)] # Если хотите, чтобы по выходу из Вашего мода отключался Ваш иннтерфейс и возвращался оригинальный, не забудьте в Action кнопки для выхода из мода добавить my_mod_screen_manager.deactivate_screens + action [Function(my_mod_screen_manager.deactivate_screens), Function(renpy.full_restart)] # Если хотите, чтобы по выходу из Вашего мода отключался Ваш интерфейс и возвращался оригинальный, не забудьте в Action кнопки для выхода из мода добавить my_mod_screen_manager.deactivate_screens text_size 32 text_color "#FFFFFF" text_hover_color "#FFFF00"