diff --git a/MSM.rpy b/MSM.rpy new file mode 100644 index 0000000..5a05206 --- /dev/null +++ b/MSM.rpy @@ -0,0 +1,379 @@ +init -1 python: + import 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 ModScreenManagerConfig: + """Конфигурация параметров мода.""" + # Основные параметры + MOD_NAME = u"Мой мод" # Название вашего мода + MOD_SAVE_IDENTIFIER = "MyMod" # Идентификатор в названии сохранения + RENPY_MIN_VERSION = "7.0" # Минимальная совместимая версия Ren'Py + + # Пути к ресурсам + 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" + 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 = 20 # INFO + + class ModScreenManager: + """ + Менеджер для управления заменой экранов в Ren'Py. + + Обеспечивает безопасную замену экранов с резервным копированием, + обработкой ошибок и поддержкой частичной замены. + """ + + def __init__(self, config=ModScreenManagerConfig): + """ + Инициализация менеджера экранов. + + Args: + config: Класс конфигурации с параметрами мода + """ + self.config = config + self.original_config = {} + self.active_screens = set() + self.is_active = False + + self.activate_screens_after_load() + + # логгер + logger_name = u'ModScreenManager [{}]'.format(self.config.MOD_NAME) + 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): + """ + Проверка совместимости с текущей версией 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( + '.'.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)) + 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, + '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'] + renpy.config.mouse_displayable = None + config.main_menu_music = self.original_config['main_menu_music'] + else: + config.window_title = self.config.ORIGINAL_TITLE + renpy.config.mouse_displayable = None + 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 + 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: + 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 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: + self.logger.debug(u"Все экраны уже были сохранены ранее") + return True # Возвращаем True если нет ошибок, даже если ничего не сохранили + + 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: + 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): + 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) + } + + def after_load_callback(self): + """ + Автоматическая активация мода после загрузки сохранения. + Активируется если в имени сохранения есть идентификатор мода. + """ + try: + global 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: + # 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) diff --git a/demo.rpy b/demo.rpy deleted file mode 100644 index 7843ba6..0000000 --- a/demo.rpy +++ /dev/null @@ -1,244 +0,0 @@ -init: - $ mods["interface_replace"] = "Замена интерфейса" - -label interface_replace: - "Оригинальный интерфейс." - - $ my_mod_screen_save() - - "Сохранение оригинального интерфейса." - - $ 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/example.rpy b/example.rpy new file mode 100644 index 0000000..f2fed50 --- /dev/null +++ b/example.rpy @@ -0,0 +1,368 @@ +init python: + # Кастомный конфиг на основе шаблона + class CustomConfigForModScreenManager(ModScreenManagerConfig): + # Переопределяем параметры + MOD_NAME = u"Менеджер экранов" + MOD_SAVE_IDENTIFIER = "Тест замены интерфейса с помощью менеджера экранов" + + # Кастомные пути + MOD_CURSOR_PATH = "ESModScreenManager/images/1.png" + MOD_MENU_MUSIC = "ESModScreenManager/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 = False +init: + $ mods["mod_screen_manager_init"] = u"Менеджер экранов" + # Создаем глобальный экземпляр менеджера с нашим кастомным шаблоном + $ my_mod_screen_manager = ModScreenManager(CustomConfigForModScreenManager) + +default persistent.timeofday = 'day' + +label mod_screen_manager_init: + # Даём имя сохранению, чтобы при загрузке сейва из нашего мода у нас автоматически включался наш интерфейс. + # Важно: проверяем, что в нашем имени сохранения будет присутствовать MOD_SAVE_IDENTIFIER или MOD_NAME из конфига мода, по нему мы проверяем мод, сейв которого мы грузим. + # Пример: MOD_SAVE_IDENTIFIER = "Мой мод" или MOD_NAME = "Мой мод", значит save_name = "Мой мод. День 1.", "Мой мод начало" или "Старт Мой мод", т.е. чтобы в имени сохранения всегда присутстваол наш индентификатор мода или имя мода в том регистре, в котором он написан + + $ 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 + +label mod_screen_manager_test: + scene bg black + + menu mod_screen_manager_test_main_menu: + "{size=+10}{b}МЕНЕДЖЕР ЭКРАНОВ{/b}{/size}\n\nВыберите режим тестирования:" + + "1. Управление модом (вкл/выкл)": + jump mod_screen_manager_test_mod_control + + "2. Частичная замена экранов": + jump mod_screen_manager_test_partial_replacement + + "3. Тест отдельных экранов": + jump mod_screen_manager_test_individual_screens + + "4. Быстрый тест всех экранов": + jump mod_screen_manager_quick_test_all_screens + + "5. Диагностика системы": + jump mod_screen_manager_test_diagnostics + + "6. Демонстрация интерфейса": + jump mod_screen_manager_demo_interface + + "Выход": + jump mod_screen_manager_exit + +label mod_screen_manager_exit: + $ my_mod_screen_manager.deactivate_screens() # Обязательно отключаем наши экраны при выходе из мода, будь то просто добавив функцию на кнопку выхода или при завершении мода через вызов функции + return + +label mod_screen_manager_test_mod_control: + scene bg black + + python: + status = my_mod_screen_manager.get_status() + status_text = u"Активен" if status['is_active'] else u"Неактивен" + + menu mod_screen_manager_mod_control_menu: + "{b}УПРАВЛЕНИЕ МОДОМ{/b}\n\nТекущий статус: [status_text]" + + "Активировать мод (все экраны)": + $ result = my_mod_screen_manager.activate_screens() + if result: + "Мод успешно активирован!" + "Все экраны заменены на кастомные." + else: + "Ошибка активации мода!" + jump mod_screen_manager_test_mod_control + + "Деактивировать мод (вернуть оригинал)": + $ result = my_mod_screen_manager.deactivate_screens() + if result: + "Мод деактивирован!" + "Восстановлены оригинальные экраны." + else: + "Ошибка деактивации!" + jump mod_screen_manager_test_mod_control + + "Проверить совместимость": + $ compat = my_mod_screen_manager.check_compatibility() + if compat: + "Версия Ren'Py совместима с модом!" + else: + "ВНИМАНИЕ: Возможны проблемы совместимости!" + jump mod_screen_manager_test_mod_control + + "Назад": + jump mod_screen_manager_test_main_menu + +label mod_screen_manager_test_partial_replacement: + scene bg black + + menu mod_screen_manager_partial_menu: + "{b}ЧАСТИЧНАЯ ЗАМЕНА ЭКРАНОВ{/b}\n\nВыберите группу экранов:" + + "Только диалоговые (say, nvl, choice)": + $ screens = ["say", "nvl", "choice"] + $ result = my_mod_screen_manager.activate_screens(screens, partial=True) + if result: + "Активированы диалоговые экраны!" + "Тестируем диалог..." + "Это тестовое сообщение в кастомном окне." + jump mod_screen_manager_partial_menu + + "Только меню (main_menu, game_menu_selector)": + $ screens = ["main_menu", "game_menu_selector", "quit"] + $ result = my_mod_screen_manager.activate_screens(screens, partial=True) + if result: + "Активированы экраны меню!" + jump mod_screen_manager_partial_menu + + "Только сохранение/загрузка": + $ screens = ["save", "load"] + $ result = my_mod_screen_manager.activate_screens(screens, partial=True) + if result: + "Активированы экраны сохранения!" + call screen my_mod_save + jump mod_screen_manager_partial_menu + + "Переключить экран 'say'": + $ is_active = my_mod_screen_manager.toggle_screen("say") + if is_active: + "Экран 'say' активирован!" + else: + "Экран 'say' деактивирован!" + jump mod_screen_manager_partial_menu + + "Деактивировать все": + $ my_mod_screen_manager.deactivate_screens() + "Все экраны деактивированы!" + jump mod_screen_manager_partial_menu + + "Назад": + jump mod_screen_manager_test_main_menu + +label mod_screen_manager_test_individual_screens: + scene bg black + + menu mod_screen_manager_individual_screens_menu: + "{b}ТЕСТ ОТДЕЛЬНЫХ ЭКРАНОВ{/b}" + + "Главное меню": + call screen my_mod_main_menu + jump mod_screen_manager_individual_screens_menu + + "Игровое меню": + call screen my_mod_game_menu_selector + jump mod_screen_manager_individual_screens_menu + + "Сохранение": + call screen my_mod_save + jump mod_screen_manager_individual_screens_menu + + "Загрузка": + call screen my_mod_load + jump mod_screen_manager_individual_screens_menu + + "Настройки": + call screen my_mod_preferences + jump mod_screen_manager_individual_screens_menu + + "История текста": + call screen my_mod_text_history_screen + jump mod_screen_manager_individual_screens_menu + + "Помощь": + call screen my_mod_help + jump mod_screen_manager_individual_screens_menu + + "Галерея": + call screen my_mod_gallery + jump mod_screen_manager_individual_screens_menu + + "Музыкальная комната": + call screen my_mod_music_room + jump mod_screen_manager_individual_screens_menu + + "Тест диалога": + jump mod_screen_manager_test_dialogue + + "Тест выбора": + jump mod_screen_manager_test_choice + + "Назад": + jump mod_screen_manager_test_main_menu + +label mod_screen_manager_test_dialogue: + scene bg black + "Это тест диалогового окна." + "Обратите внимание на дизайн." + me "Привет! Это сообщение от персонажа." + "Можно открыть историю (H) или сохранить (S)." + jump mod_screen_manager_individual_screens_menu + +label mod_screen_manager_test_choice: + scene bg black + menu: + "Выберите вариант:" + "Вариант 1": + "Выбран вариант 1" + "Вариант 2": + "Выбран вариант 2" + "Длинный вариант текста для проверки переноса строк": + "Выбран длинный вариант" + jump mod_screen_manager_individual_screens_menu + +label mod_screen_manager_quick_test_all_screens: + scene bg black + + "Начинаем быстрое тестирование..." + $ my_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 mod_screen_manager_test_main_menu + +label mod_screen_manager_test_diagnostics: + scene bg black + + python: + manager = my_mod_screen_manager + status = manager.get_status() + + missing_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) + + 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) + + $ manager.logger.info(u"\n" + report) + + "Результат диагностики экспортирован в консоль Ren'Py!" + + jump mod_screen_manager_test_main_menu + +label mod_screen_manager_demo_interface: + scene bg black + $ my_mod_screen_manager.activate_screens() + + menu mod_screen_manager_demo_menu: + "{b}ДЕМОНСТРАЦИЯ{/b}" + + "Тест диалога": + "Демонстрация диалогового окна." + me "Привет! Тестовое сообщение." + "Используйте H для истории, S для сохранения." + jump mod_screen_manager_demo_menu + + "Тест выбора": + menu: + "Выберите:" + "Простой": + "Выбран простой" + "С форматированием {b}жирный{/b}": + "Форматирование работает!" + jump mod_screen_manager_demo_menu + + "Цветовые схемы": + jump mod_screen_manager_demo_colors + + "Назад": + jump mod_screen_manager_test_main_menu + +label mod_screen_manager_demo_colors: + menu mod_screen_manager_color_menu: + "Выберите время суток:" + + "День": + $ persistent.timeofday = 'day' + "Дневная схема установлена." + jump mod_screen_manager_color_menu + + "Вечер": + $ persistent.timeofday = 'sunset' + "Вечерняя схема установлена." + jump mod_screen_manager_color_menu + + "Ночь": + $ persistent.timeofday = 'night' + "Ночная схема установлена." + jump mod_screen_manager_color_menu + "Пролог": + $ persistent.timeofday = 'prologue' + "Прологовая схема установлена." + jump mod_screen_manager_color_menu + + "Назад": + jump mod_screen_manager_demo_menu \ No newline at end of file diff --git a/images/1.png b/images/1.png new file mode 100644 index 0000000..df1ae7a Binary files /dev/null and b/images/1.png differ diff --git a/interface.rpy b/interface.rpy deleted file mode 100644 index 65fce41..0000000 --- a/interface.rpy +++ /dev/null @@ -1,72 +0,0 @@ -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() - - # Функция для автоматического включения кастомного интерфейса при загрузке сохранения с названием Вашего мода - def my_mod_activate_after_load(): - global save_name - if "MyMod" in save_name: - my_mod_screen_save() - my_mod_screen_act() - - # Добавляем функцию в 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() diff --git a/logger.rpy b/logger.rpy new file mode 100644 index 0000000..587718f --- /dev/null +++ b/logger.rpy @@ -0,0 +1,86 @@ +init -1 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 diff --git a/screens.rpy b/screens.rpy index e6089b6..03d1030 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(my_mod_screens_diact), 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") @@ -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 @@ -1293,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") @@ -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: @@ -1447,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