/
vechnoilive
/
RuleForge_GenAI_Lab
Обзор
Документация
Войти
/
vechnoilive
/
RuleForge_GenAI_Lab
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
scenes.py
1 653 строки
79 KB
vechnoilive
upload files
20 ноя 2025, 01:00
20 ноя 2025, 01:00
823b56d
Код
Авторство
О чём код?
import pygame from ui import draw_text_center, draw_text_wrapped import math import random from level import Level from rules import RuleSystem from player import Player from perks import PERKS_DATA from gods import Pantheon from ui import draw_text_center from projectile import Arrow, EnemyArrow, HealthPotion from settings import VIRTUAL_WIDTH, VIRTUAL_HEIGHT from enemies import DarkKnight, DarkArcher from ui import DamageText class Scene: def __init__(self, game): self.game = game self.damage_texts = [] def enter(self): pass def exit(self): pass def handle_event(self, event): pass def update(self, dt): self.damage_texts = [text for text in self.damage_texts if text.update(dt)] pass def render(self, surface): pass class StoryIntroScene(Scene): """🌟 ЭПИЧЕСКОЕ ПРЕДИСЛОВИЕ - История о бунте против богов""" def __init__(self, game): super().__init__(game) self.font_small = pygame.font.Font(None, 28) self.font_large = pygame.font.Font(None, 48) # 🔥 ЭПИЧЕСКОЕ ПРЕДИСЛОВИЕ self.story_text = """ ДАВНЫМ-ДАВНО, КОГДА БОГИ ЕЩЁ СЧИТАЛИ СЕБЯ НЕУЯЗВИМЫМИ... Четыре всемогущих божества правили вечностью: ХАОС - бог непредсказуемости и энтропии БОЛЬ - хранитель баланса через испытания МУДРОСТЬ - архитектор знания и судьбы СМЕРТЬ - владыка последнего вдоха Они сидели на своих престолах, развлекаясь, создавая миры и разрушая их для утех. НО ОДИН ЧЕЛОВЕК ОСМЕЛИЛСЯ... Слепой боец, рожденный в беспамятстве, не знавший ни названия, ни происхождения, встал перед четырьмя богами и бросил вызов. "Я свергну вас," - сказал он. БОГИ СМЕЯЛИСЬ. Их смех трясил основания мироздания. Они думали, что видели всё в своей вечности, но они ошибались. "Смертный?" - спросил ХАОС, искрясь энергией. "Один?" - усмехнулась БОЛЬ, скручиваясь от хохота. "Возможно ли это?" - недоумевал МУДРОСТЬ. И СМЕРТЬ... СМЕРТЬ только улыбалась в тени. "Послушайте, забавный маленький человечек," - заговорила МУДРОСТЬ, "Если ты действительно веришь в это, то давай ПОИГРАЕМ. Ты имеешь право на семь испытаний. Семь уровней усложнения. Семь врата к нам." "Но сначала," - добавил ХАОС, "ответь на наши вопросы. Если ты свергнешь нас, ты должен знать, за что ты борешься. Если ты не знаешь ответов — твой путь закончится здесь и сейчас." И боги задали вопросы... "ПЕРВЫЙ ВОПРОС от ХАОСА: Что такое сила? Это мощь в ударе? Скорость в беге? Или это упорство когда весь мир против тебя?" "ВТОРОЙ ВОПРОС от БОЛИ: Чему ты научился из своих падений? Каждый удар боли — учитель. Сможешь ли ты подняться, когда ничего не остаётся?" "ТРЕТИЙ ВОПРОС от МУДРОСТИ: Почему ты веришь, что можешь? Вера без основания — глупость. Но глупость иногда делает невозможное возможным. Твоя вера — слепая надежда или расчёт?" "И ЧЕТВЁРТЫЙ," - прошептала СМЕРТЬ, "Самый страшный вопрос: Готов ли ты к цене победы? Потому что каждая победа требует жертвы. Даже боги знают об этом." Человек молчал. А потом ответил просто: "Я не знаю, кто я. Я не знаю, откуда пришёл. Я не знаю, могу ли я победить. Но я ЗНАЮ, что я попробую. И это — единственное, что нужно богам понять." Боги посмотрели друг на друга. В первый раз за вечность они почувствовали что-то новое. Не страх. Не интерес. Что-то третье. Что-то, что они забыли, что испытывали давным-давно. "ХОРОШО," - сказал ХАОС. "Тогда ПОЕХАЛИ. Семь комнат. Четыре гвардейца. Каждый сильнее предыдущего. Давай посмотрим, как долго ты продержишься." "И если ты действительно сумеешь дойти до нас," - добавила МУДРОСТЬ, "То мы узнаем истину. Не твоих слов, а твоих ДЕЛ." Человек кивнул. Он взял оружие — простой меч из металла и веры. На его левом плече появилась странная метка — синее сияние. ЭХОМ её назвали боги. Древней магией, забытой в веках. Силой, позволяющей ему УЧИТЬСЯ от каждого падения, становиться СИЛЬНЕЕ от каждого удара, и ВОССТАВАТЬ, когда всё кажется потеряным. "Я готов," - сказал он. И боги рассмеялись в последний раз перед финальной битвой. ════════════════════════════════════════════════════════════════════ ТЕПЕРЬ ТВОЙ ХОДУ. Тебе предстоит: - Пройти через СЕМЬ УРОВНЕЙ - Встретиться с ЧЕТЫРЬМЯ БОЖЕСТВ-БОССАМИ - Ответить на их испытания не словами, а ДЕЙСТВИЯМИ - Свергнуть СМЕРТЬ саму ВОПРОСЫ БОГОВ ВСЁ ЕЩЁ ОТКРЫТЫ. Ответишь ли ты? ТВОЙ ПУТЬ НАЧИНАЕТСЯ ЗДЕСЬ. """ self.lines = [line.strip() for line in self.story_text.strip().split('\n') if line.strip()] self.y_offset = 1000 self.speed = 50 self.finished = False self.finish_timer = 3.0 def handle_event(self, event): if event.type == pygame.KEYDOWN: if event.key == pygame.K_SPACE or event.key == pygame.K_RETURN: if not self.finished: self.y_offset -= 300 else: self._go_to_game() def update(self, dt): if not self.finished: self.y_offset -= self.speed * dt if self.y_offset < -500: self.finished = True self.finish_timer = 3.0 else: self.finish_timer -= dt if self.finish_timer <= 0: self._go_to_game() def _go_to_game(self): """Переход сразу в игру после истории""" self.game.set_scene(GameLevelScene(self.game, 1)) def render(self, surface): # Фон: космос с божественным сиянием surface.fill((10, 5, 20)) # Градиентный фон (фиолетовый-чёрный) for y in range(surface.get_height()): brightness = int(10 + (y / surface.get_height()) * 40) pygame.draw.line(surface, (brightness // 2, 5, brightness), (0, y), (surface.get_width(), y)) # Звёзды import random random.seed(42) for _ in range(100): x = random.randint(0, surface.get_width()) y_star = random.randint(0, surface.get_height()) size = random.choice([1, 1, 2]) color = random.choice([(255, 255, 200), (200, 200, 255), (255, 200, 255)]) pygame.draw.circle(surface, color, (x, y_star), size) # Большое название игры с перспективой perspective_scale = max(0, (self.y_offset - 500) / 300) if perspective_scale < 1.0: title_font = pygame.font.Font(None, int(80 + perspective_scale * 60)) title = title_font.render("RULEFORGE", True, (255, 100, 255)) title_rect = title.get_rect(center=(surface.get_width() // 2, 100)) surface.blit(title, title_rect) # Подзаголовок subtitle_font = pygame.font.Font(None, 32) subtitle = subtitle_font.render("Вызов Богам", True, (200, 100, 255)) subtitle_rect = subtitle.get_rect(center=(surface.get_width() // 2, 160)) surface.blit(subtitle, subtitle_rect) # Рисуем текст истории current_y = self.y_offset for i, line in enumerate(self.lines): if current_y > -30 and current_y < surface.get_height() + 30: # Определяем стиль строки is_title = line.startswith("ДАВНЫМ") or "════" in line or line.isupper() and len(line) > 10 is_question = "ВОПРОС" in line or "?" in line is_dialogue = line.startswith('"') or "," in line[-5:] is_climax = "СМЕРТЬ" in line or "ЧЕТЫРЕ" in line or "СВЕРГНУТЬ" in line is_god_name = any(god in line for god in ["ХАОС", "БОЛЬ", "МУДРОСТЬ", "СМЕРТЬ", "ЭХОМ"]) if "════" in line: font = pygame.font.Font(None, 20) color = (255, 150, 100) elif is_title: font = pygame.font.Font(None, 52) color = (255, 200, 0) elif is_climax: font = pygame.font.Font(None, 36) color = (255, 50, 100) elif is_question: font = pygame.font.Font(None, 34) color = (150, 200, 255) elif is_god_name: font = pygame.font.Font(None, 32) color = (200, 100, 255) else: font = self.font_small color = (200, 200, 200) text_surface = font.render(line, True, color) text_rect = text_surface.get_rect(centerx=surface.get_width() // 2, y=current_y) # Если финальная сцена - добавляем свечение if is_climax or is_question: glow_surface = pygame.Surface(text_rect.size, pygame.SRCALPHA) pygame.draw.rect(glow_surface, (color[0], color[1], color[2], 30), glow_surface.get_rect()) surface.blit(glow_surface, text_rect) surface.blit(text_surface, text_rect) current_y += 50 # Интерактивный элемент внизу if self.finished: hint_text = "SPACE/ENTER - начать игру" hint_color = (100, 255, 100) else: hint_text = "SPACE/ENTER - пропустить" hint_color = (150, 150, 150) hint_font = pygame.font.Font(None, 24) hint_surface = hint_font.render(hint_text, True, hint_color) hint_rect = hint_surface.get_rect(centerx=surface.get_width() // 2, y=surface.get_height() - 30) surface.blit(hint_surface, hint_rect) # Прогресс полоса if not self.finished: progress = min(1.0, (1000 - self.y_offset) / 1500) bar_width = int(surface.get_width() * progress) pygame.draw.rect(surface, (255, 100, 150), (0, surface.get_height() - 5, bar_width, 5)) class VictoryScene(Scene): """🏆 ФИНАЛЬНАЯ СЦЕНА - Победа над СМЕРТЬЮ и концовка""" def __init__(self, game): super().__init__(game) self.font_small = pygame.font.Font(None, 28) self.font_large = pygame.font.Font(None, 48) # 🔥 ФИНАЛЬНЫЙ ТЕКСТ ПОБЕДЫ self.victory_text = """ СМЕРТЬ ПАЛА. Её последний вдох развеялся в ничто. Её власть — сломана. Её гордость — поправима. Боец стоял среди руин её дворца, весь в шрамах и крови, но всё ещё стоял. ════════════════════════════════════════════════════════════ БОГИ МОЛЧАЛИ. В первый раз за вечность четыре божества не смеялись. Не издевались. Не кичились. Они просто СМОТРЕЛИ. На маленького смертного, который недавно казался им шуткой. Который осмелился ответить на их вопросы не словами, а ДЕЛАМИ. ════════════════════════════════════════════════════════════ "ТЫ Нас ПОНЯЛ," - первым заговорил ХАОС. "Сила — не в ударе. Сила в том, чтобы продолжать биться, когда весь хаос мироздания пытается тебя расколоть. Ты понял это." ════════════════════════════════════════════════════════════ "ТЫ нас ИСПЫТАЛ," - сказала БОЛЬ. "Боль — не враг. Боль — учитель. Каждый её удар делал тебя сильнее. Каждое падение учило тебя вставать. Ты прошёл этот урок лучше, чем иные боги." ════════════════════════════════════════════════════════════ "ТЫ нас ПРЕВЗОШЁЛ," - произнесла МУДРОСТЬ. "Вера без причины — это глупость. Но вера несмотря на всё — это мудрость. Ты веровал, не зная, будет ли смысл. И твоя вера оказалась единственной истиной, которая имела значение." ════════════════════════════════════════════════════════════ И СМЕРТЬ... Даже разбитая, даже поверженная, СМЕРТЬ всё ещё улыбалась. "ТЫ ПОНЯЛ," - её голос был тише, чем шепот ночи. "Цена победы — это не кровь других. Цена победы — это часть тебя самого. Ты оставил здесь много. Может быть, часть своего прошлого. Может быть, иллюзии о том, кем ты был. Может быть, саму надежду на возвращение. Но взамен ты получил ИСТИНУ. Это — единственная валюта, которая имеет вес." ════════════════════════════════════════════════════════════ И тогда произошло невероятное. Четыре бога встали со своих престолов. Они ПОКЛОНИЛИСЬ. Не изображая покорность, а признавая величие. Не как побеждённые, а как учителя, которых ученик превзошёл. "Когда-то МЫ были такими же," - сказали они в унисон. "Мы тоже были смертными. Маленькими. Слабыми. Мы поднялись через испытания и боль. Мы закалились в огне неизвестности. И мы забыли, что это значит. Ты напомнил нам." ════════════════════════════════════════════════════════════ МУДРОСТЬ подошла первой и положила руку на плечо бойца. "Теперь ТЫ выбираешь. Ты можешь остаться богом. Занять место СМЕРТИ. Вечность, власть, всезнание. Или ты можешь уйти. Вернуться туда, откуда пришёл. Быть простым смертным. Прожить одну жизнь. Но ПОЛНУЮ жизнь. С началом и концом. С любовью и потерей. С смыслом." ════════════════════════════════════════════════════════════ Боец долго молчал. Он смотрел на четырёх богов. На их престолы. На вечность, которая ждала его. А потом произнес: "Я ухожу. Я видел, что происходит с теми, кто выбирает вечность. Вы забываете, что значит быть живым. Вы смеялись надо мной, потому что забыли о боли, о страхе, о надежде. Я хочу помнить всё это. Я хочу жить, а не просто существовать." ════════════════════════════════════════════════════════════ ХАОС засмеялся. Но это был не издевательский смех, как раньше. Это был смех признания. Смех, полный уважения. "Даже после победы ты не теряешь мудрость. Может быть, ты был прав с самого начала. Может быть, смертность — это не слабость. Может быть, это — подарок." ════════════════════════════════════════════════════════════ Боец медленно спускался с вершины. Позади него четыре бога смотрели на его спину. БОЛЬ прошептала: "Помни нас. Помни боль. Помни, почему ты боролся." МУДРОСТЬ добавила: "Ты пришёл в нашу комнату без памяти. Уходи с пониманием." ХАОС сказал: "Твой путь был хаотичным и неправильным. Но он был твоим. И это — всё, что имеет значение." И СМЕРТЬ — даже поверженная, даже обессиленная — всё ещё щёптала: "Мы встретимся снова, боец. Не как враги. Как старые друзья. Потому что сейчас ты знаешь, что я не враг. Я просто конец. И конец может быть красивым." ════════════════════════════════════════════════════════════ БОЕЦ УШЁЛ. Он не знал, куда идёт. Он не знал своего имени. Он не знал своего будущего. Но впервые после той ночи, когда он встал перед богами, он знал, КТО он такой. Он был не просто смертным. Он был тем, кто осмелился восстать. Тем, кто прошёл через испытания и вышел сильнее. Тем, кто побил четырёх богов не мечом, а верой. А это — единственное имя, которое имеет значение. ════════════════════════════════════════════════════════════ БОГИ ОСТАЛИСЬ. Они снова сели на престолы. Но теперь они не смеялись над смертными. Теперь они смотрели на них с интересом. С интересом к существам, которые обладают чем-то, чего боги потеряли давным-давно. ХАОС сказал другим: "Может, мы должны создать больше испытаний? Больше врат? Может быть, есть другие, кто тоже осмелится?" БОЛЬ ответила: "Может быть. Потому что теперь мы знаем: боль рождает не слабость. Боль рождает ЧЕМПИОНОВ." МУДРОСТЬ кивнула: "И нам нужны чемпионы. Чтобы напомнить нам." ════════════════════════════════════════════════════════════ ТЫ ПОБЕДИЛ. Не богов. Не судьбу. Не смерть. ТЫ ПОБЕДИЛ САМУ СЕБЯ. И это — величайшая из всех побед. ════════════════════════════════════════════════════════════ Но история не заканчивается здесь. История НИКОГДА не заканчивается. Потому что победа — это только начало новой главы. А в большой книге мироздания много ещё непрочитанных страниц. ════════════════════════════════════════════════════════════ СПАСИБО, ЧТО СЫГРАЛ. Твой путь вдохновил нас. Твоя верность — легендарна. Твоя победа — вечна. Может быть, когда-нибудь... ты вернёшься. ════════════════════════════════════════════════════════════ """ self.lines = [line.strip() for line in self.victory_text.strip().split('\n') if line.strip()] self.y_offset = 1200 self.speed = 50 self.finished = False self.finish_timer = 4.0 self.music_played = False def handle_event(self, event): if event.type == pygame.KEYDOWN: if event.key in (pygame.K_SPACE, pygame.K_RETURN, pygame.K_ESCAPE): if not self.finished: self.y_offset -= 400 else: self._go_to_menu() def update(self, dt): if not self.finished: self.y_offset -= self.speed * dt if self.y_offset < -600: self.finished = True self.finish_timer = 4.0 else: self.finish_timer -= dt if self.finish_timer <= 0: self._go_to_menu() def _go_to_menu(self): # Полная очистка состояния self.game.player = None self.game.room_states.clear() self.game.room_history.clear() self.game.total_kills = 0 self.game.next_god_kill = 2 self.game.set_scene(MenuScene(self.game)) def render(self, surface): # Фон: величественный золотистый закат с фиолетовыми облаками surface.fill((30, 20, 50)) # Градиент: фиолетовый-чёрный-золотой for y in range(surface.get_height()): progress = y / surface.get_height() r = int(30 + progress * 80) g = int(20 + progress * 40) b = int(50 - progress * 30) pygame.draw.line(surface, (r, g, b), (0, y), (surface.get_width(), y)) # Звёзды + парящие огни (боги) import random random.seed(42) for _ in range(150): x = random.randint(0, surface.get_width()) y_star = random.randint(0, surface.get_height()) size = random.choice([1, 1, 1, 2]) color_choice = random.choice([ (255, 255, 200), # Жёлтые звёзды (200, 200, 255), # Синие звёзды (255, 200, 255), # Фиолетовые звёзды (255, 150, 100), # Оранжевые звёзды - боги ]) pygame.draw.circle(surface, color_choice, (x, y_star), size) # Заголовок победы с эффектом title_alpha_factor = max(0, (self.y_offset - 600) / 400) if title_alpha_factor < 1.0: title_font = pygame.font.Font(None, 96) title = title_font.render("ПОБЕДА!", True, (255, 215, 0)) title_rect = title.get_rect(center=(surface.get_width() // 2, 80)) # Свечение за титлом glow_surface = pygame.Surface(title_rect.inflate(40, 40).size, pygame.SRCALPHA) pygame.draw.circle(glow_surface, (255, 200, 0, 50), (glow_surface.get_width() // 2, glow_surface.get_height() // 2), 150) surface.blit(glow_surface, title_rect.inflate(40, 40).topleft) surface.blit(title, title_rect) # Рисуем основной текст current_y = self.y_offset for i, line in enumerate(self.lines): if current_y > -30 and current_y < surface.get_height() + 30: # Разные стили для разных элементов is_separator = "════" in line is_god_speaks = "ХАОС" in line or "БОЛЬ" in line or "МУДРОСТЬ" in line or "СМЕРТЬ" in line is_climax = "ПОБЕДИЛ" in line or "УШЁЛ" in line or "СПАСИБО" in line is_question = "?" in line is_dialogue = line.startswith('"') if is_separator: font = pygame.font.Font(None, 20) color = (255, 150, 100) elif is_climax: font = pygame.font.Font(None, 48) color = (255, 215, 0) elif is_god_speaks: font = pygame.font.Font(None, 36) color = (200, 150, 255) elif is_question: font = pygame.font.Font(None, 32) color = (150, 200, 255) elif is_dialogue: font = pygame.font.Font(None, 30) color = (200, 200, 150) else: font = self.font_small color = (220, 210, 180) text_surface = font.render(line, True, color) text_rect = text_surface.get_rect(centerx=surface.get_width() // 2, y=current_y) # Свечение для важных строк if is_climax or is_god_speaks: glow_surface = pygame.Surface(text_rect.inflate(20, 10).size, pygame.SRCALPHA) glow_color = (color[0], color[1], color[2], 40) pygame.draw.rect(glow_surface, glow_color, glow_surface.get_rect(), border_radius=5) surface.blit(glow_surface, text_rect.inflate(20, 10).topleft) surface.blit(text_surface, text_rect) current_y += 55 # Кнопка внизу if self.finished: hint_text = "SPACE/ENTER/ESC - вернуться в меню" hint_color = (100, 255, 100) else: hint_text = "SPACE/ENTER - пропустить или продолжить" hint_color = (150, 150, 150) hint_font = pygame.font.Font(None, 24) hint_surface = hint_font.render(hint_text, True, hint_color) hint_rect = hint_surface.get_rect(centerx=surface.get_width() // 2, y=surface.get_height() - 30) surface.blit(hint_surface, hint_rect) # Прогресс полоса if not self.finished: progress = min(1.0, (1200 - self.y_offset) / 1800) bar_width = int(surface.get_width() * progress) pygame.draw.rect(surface, (255, 215, 0), (0, surface.get_height() - 5, bar_width, 5)) class MenuScene(Scene): def __init__(self, game): super().__init__(game) self.title_font = pygame.font.Font(None, 92) self.btn_font = pygame.font.Font(None, 56) self.selected = 0 self.options = ["ИГРАТЬ", "НАСТРОЙКИ", "ВЫХОД"] def handle_event(self, event): if event.type == pygame.KEYDOWN: if event.key in (pygame.K_DOWN, pygame.K_s): self.selected = (self.selected + 1) % len(self.options) elif event.key in (pygame.K_UP, pygame.K_w): self.selected = (self.selected - 1) % len(self.options) elif event.key == pygame.K_RETURN: if self.selected == 0: # ИГРАТЬ if self.game.player: self.game.player = None self.game.room_states.clear() self.game.room_history.clear() self.game.total_kills = 0 self.game.next_god_kill = 2 # 🌟 ПОКАЗЫВАЕМ ИСТОРИЮ ПЕРЕД ПЕРВОЙ ИГРОЙ! self.game.set_scene(StoryIntroScene(self.game)) elif self.selected == 1: # НАСТРОЙКИ self.game.set_scene(SettingsScene(self.game)) elif self.selected == 2: # ВЫХОД self.game.running = False def render(self, surface): surface.fill((8, 8, 20)) draw_text_center(surface, "RULEFORGE", self.title_font, (220, 180, 60), surface.get_width()//2, 140) draw_text_center(surface, "Суд Богов", pygame.font.Font(None, 48), (180, 140, 100), surface.get_width()//2, 220) for i, option in enumerate(self.options): color = (255, 255, 120) if i == self.selected else (180, 180, 180) draw_text_center(surface, option, self.btn_font, color, surface.get_width()//2, 340 + i * 80) draw_text_center(surface, "↑↓ — выбор • ENTER — подтвердить", pygame.font.Font(None, 32), (120, 120, 140), surface.get_width()//2, 580) # ═══════════════════════════════════════════════════════════════════════════ # GameLevelScene - ИСПРАВЛЕНА ЛОГИКА СПАВНА! # ═══════════════════════════════════════════════════════════════════════════ class GameLevelScene(Scene): # ← НОВЫЙ ПАРАМЕТР: coming_from_right указывает, откуда пришли def __init__(self, game, room_id, coming_from_right=False, restore_position=None): super().__init__(game) self.room_id = room_id self.coming_from_right = coming_from_right # Откуда пришли (влево/вправо) self.restore_position = restore_position # Позиция игрока для восстановления (x, y) self.initialized = False self.projectiles = [] self.enemy_projectiles = [] self.potions = [] self.mouse_held = False self.spawn_safe_timer = 0.1 self.debug_draw = False self.is_boss_room = (room_id % 7 == 0) self.is_cleared = False self.damage_texts = [] print(f"[GameLevelScene] room_id={room_id}, coming_from_right={coming_from_right}, restore_position={restore_position}") def _scale_mouse_pos(self, pos): """Масштабирует реальные координаты мыши к виртуальным""" real_size = self.game.surface.get_size() if real_size[0] == 0: return pos scale_x = VIRTUAL_WIDTH / real_size[0] scale_y = VIRTUAL_HEIGHT / real_size[1] return (pos[0] * scale_x, pos[1] * scale_y) def enter(self): self.initialized = False if not self.game.rule_system: self.game.rule_system = RuleSystem(self.game) self.rule_system = self.game.rule_system self.pantheon = Pantheon() # Загрузка или создание комнаты if self.room_id in self.game.room_states: self._load_room_state() print(f"[ROOM {self.room_id}] Загружено из сохранения") else: self._create_new_room() print(f"[ROOM {self.room_id}] Создана новая") # ═══════════════════════════════════════════════════════════════════════ # КЛЮЧЕВОЕ ИСПРАВЛЕНИЕ: Определяем позицию спавна # ═══════════════════════════════════════════════════════════════════════ # ← ПРИОРИТЕТ 1: Если restore_position задан - ВОССТАНАВЛИВАЕМ ПОЗИЦИЮ if self.restore_position is not None: spawn_x, spawn_y = self.restore_position print(f"[SPAWN] Восстановление позиции из меню: ({spawn_x}, {spawn_y})") # ← ПРИОРИТЕТ 2: Возврат справа из другой комнаты elif self.coming_from_right: if self.level.exit_portal: spawn_x = self.level.exit_portal.left - 70 spawn_y = self.level.exit_portal.top - 60 else: spawn_x, spawn_y = 900, 300 print(f"[SPAWN] Вернулся справа → ПРАВАЯ ДВЕРЬ ({spawn_x}, {spawn_y})") # ← ПРИОРИТЕТ 3: Нормальный вход в комнату else: spawn_x, spawn_y = self.level.get_spawn_point() print(f"[SPAWN] Первый вход → СТАРТОВАЯ ПЛАТФОРМА ({spawn_x}, {spawn_y})") # Инициализация игрока if self.game.player is None: self.game.player = Player(spawn_x, spawn_y, self.rule_system) self.game.player.echos = self.game.storage.get("echos", 0) self.game.player.perk_levels = self.game.storage.get("perk_levels", {key: 0 for key in PERKS_DATA.keys()}) # ← ПРИМЕНЯЕМ ПЕРКИ! for perk_id, level in self.game.player.perk_levels.items(): if level > 0 and perk_id in PERKS_DATA: PERKS_DATA[perk_id]['apply'](self.game.player, level) # ← Восстанавливаем здоровье до нового максимума! self.game.player.health = self.game.player.max_health self.game.player.stamina = self.game.player.max_stamina print( f"[PLAYER] Создан новый: ({spawn_x}, {spawn_y}) | max_health={self.game.player.max_health}, health={self.game.player.health}") else: if self.game.player.dead or self.game.player.health <= 0: self.game.player.health = self.game.player.max_health self.game.player.stamina = self.game.player.max_stamina self.game.player.dead = False print(f"[PLAYER] ВОЗРОЖДЁН! HP: {self.game.player.health}") # Позиционирование self.game.player.rect.topleft = (spawn_x, spawn_y) self.game.player.vx = 0 self.game.player.vy = 0 self.game.player.on_ground = True self.game.player.charging = False self.game.player.attack_cooldown = 0 print(f"[PLAYER] Позиция установлена: ({spawn_x}, {spawn_y})") self.player = self.game.player # Инициализация врагов for enemy in self.level.enemies: enemy._scene = self enemy._just_spawned = True enemy.vy = 0 self.projectiles = [] self.enemy_projectiles = [] self.potions = [] self.spawn_safe_timer = 0.1 self.is_cleared = len([e for e in self.level.enemies if not e.dead]) == 0 self.damage_texts = [] if self.level.boss: self.level.boss._scene = self print(f"[BOSS] {self.level.boss.name} готова к бою!") def _create_new_room(self): # ← ОБНОВИТЬ ЭТУ СТРОКУ: self.level = Level(1024, 640, difficulty=self.room_id, is_boss_arena=self.is_boss_room) self.is_cleared = False self.game.current_room_id = self.room_id print(f"[ROOM] {'БОССОВАЯ АРЕНА' if self.is_boss_room else 'НОВАЯ КОМНАТА'} #{self.room_id}") def _load_room_state(self): state = self.game.room_states[self.room_id] self.level = Level(1024, 640, self.room_id, loaded_mode=True) self.level.platforms.clear() self.level.walls.clear() self.level.enemies.clear() self.level.checkpoints.clear() self.level.water.clear() self.level.left_portal = None # Восстановление чекпоинтов self.level.checkpoints.clear() for cp_data in state.checkpoints: self.level.checkpoints.append(pygame.Rect(cp_data["x"], cp_data["y"], cp_data["w"], cp_data["h"])) # Spawn point if state.spawn_point: self.level.spawn_point = (state.spawn_point["x"], state.spawn_point["y"]) # Платформы for p_data in state.platforms: self.level.platforms.append(pygame.Rect(p_data["x"], p_data["y"], p_data["w"], p_data["h"])) # Вода for w_data in state.water: self.level.water.append(pygame.Rect(w_data["x"], w_data["y"], w_data["w"], w_data["h"])) # Стены self.level.walls.clear() for w_data in state.walls: self.level.walls.append(pygame.Rect(w_data["x"], w_data["y"], w_data["w"], w_data["h"])) # Левый портал if state.left_portal: self.level.left_portal = pygame.Rect( state.left_portal["x"], state.left_portal["y"], state.left_portal["w"], state.left_portal["h"] ) # Платформа выхода main_exit_platform = None if hasattr(state, 'main_exit_platform') and state.main_exit_platform: main_exit_platform = pygame.Rect( state.main_exit_platform["x"], state.main_exit_platform["y"], state.main_exit_platform["w"], state.main_exit_platform["h"] ) self.level.main_exit_platform = main_exit_platform # Правая стена + дверь wall_w = 40 if state.exit_portal: self.level.exit_portal = pygame.Rect( state.exit_portal["x"], state.exit_portal["y"], state.exit_portal["w"], state.exit_portal["h"] ) door_y = self.level.exit_portal.y door_height = self.level.exit_portal.height else: door_height = 80 if main_exit_platform: door_y = max(0, min(self.level.height - door_height, main_exit_platform.top - 120)) else: door_y = max(0, min(self.level.height - door_height, self.level.platforms[-1].top - 120)) door_x = self.level.width - wall_w self.level.exit_portal = pygame.Rect(door_x, door_y, wall_w, door_height) door_x = self.level.exit_portal.x door_y = self.level.exit_portal.y door_height = self.level.exit_portal.height upper = pygame.Rect(door_x, 0, wall_w, door_y) lower = pygame.Rect(door_x, door_y + door_height, wall_w, self.level.height - (door_y + door_height)) if upper.height > 0: self.level.walls.append(upper) if lower.height > 0: self.level.walls.append(lower) # Враги for enemy_data in state.enemies: if enemy_data["type"] == "DarkKnight": enemy = DarkKnight(enemy_data["x"], enemy_data["y"]) elif enemy_data["type"] == "DarkArcher": enemy = DarkArcher(enemy_data["x"], enemy_data["y"]) else: continue enemy.health = enemy_data["health"] enemy.max_health = enemy_data["max_health"] enemy.dead = enemy_data["dead"] if "scale_override" in enemy_data: enemy.scale_override = enemy_data["scale_override"] if "damage_mult" in enemy_data: enemy.damage_mult = enemy_data["damage_mult"] self.level.enemies.append(enemy) # Зелья self.potions = [HealthPotion(p["x"], p["y"]) for p in state.potions] self.is_cleared = len([e for e in self.level.enemies if not e.dead]) == 0 print(f"[ROOM {self.room_id}] ЗАГРУЖЕНО | платформ: {len(state.platforms)}") def _save_room_state(self): platforms_data = [{"x": p.x, "y": p.y, "w": p.width, "h": p.height} for p in self.level.platforms] checkpoints_data = [{"x": cp.x, "y": cp.y, "w": cp.width, "h": cp.height} for cp in self.level.checkpoints] spawn_point_data = None if hasattr(self.level, 'spawn_point') and self.level.spawn_point: spawn_point_data = {"x": self.level.spawn_point[0], "y": self.level.spawn_point[1]} water_data = [{"x": w.x, "y": w.y, "w": w.width, "h": w.height} for w in self.level.water] exit_portal_data = { "x": self.level.exit_portal.x, "y": self.level.exit_portal.y, "w": self.level.exit_portal.width, "h": self.level.exit_portal.height } if self.level.exit_portal else None walls_data = [{"x": w.x, "y": w.y, "w": w.width, "h": w.height} for w in self.level.walls] left_portal_data = None if self.level.left_portal: left_portal_data = { "x": self.level.left_portal.x, "y": self.level.left_portal.y, "w": self.level.left_portal.width, "h": self.level.left_portal.height } main_exit_platform_data = None if hasattr(self.level, 'main_exit_platform') and self.level.main_exit_platform: main_exit_platform_data = { "x": self.level.main_exit_platform.x, "y": self.level.main_exit_platform.y, "w": self.level.main_exit_platform.width, "h": self.level.main_exit_platform.height } enemies_data = [] for enemy in self.level.enemies: enemies_data.append({ "type": "DarkKnight" if isinstance(enemy, DarkKnight) else "DarkArcher", "x": enemy.rect.x, "y": enemy.rect.y, "health": enemy.health, "max_health": enemy.max_health, "dead": enemy.dead, "scale_override": getattr(enemy, "scale_override", 1.0), "damage_mult": getattr(enemy, "damage_mult", 1.0) }) potions_data = [{"x": p.x, "y": p.y} for p in self.potions if p.alive] from room_state import RoomState self.game.room_states[self.room_id] = RoomState( platforms_data, water_data, enemies_data, potions_data, exit_portal_data, main_exit_platform_data, walls_data, left_portal_data, checkpoints_data, spawn_point_data ) print(f"[ROOM {self.room_id}] СОХРАНЕНО | платформ: {len(platforms_data)}") def handle_event(self, event): if event.type in (pygame.MOUSEBUTTONDOWN, pygame.MOUSEBUTTONUP): scaled_pos = self._scale_mouse_pos(event.pos) event.pos = scaled_pos if event.type == pygame.KEYDOWN: if event.key == pygame.K_SPACE: self.player.jump() if event.key == pygame.K_TAB: self.player.switch_mode() if event.key == pygame.K_f and self.game.total_kills >= self.game.next_god_kill: self._save_room_state() # Переходим в меню богов, сохраняя текущую сцену как parent self.game.set_scene(GodQuestionScene(self.game, self)) return if event.key == pygame.K_BACKQUOTE: self.debug_draw = not self.debug_draw elif event.type == pygame.MOUSEBUTTONDOWN: if event.button == 1: if self.player.combat_mode == "ranged": self.mouse_held = True self.player.start_charge() else: self.player.melee_attack() elif event.button == 3: self.player.switch_mode() elif event.type == pygame.MOUSEBUTTONUP: if event.button == 1 and self.player.combat_mode == "ranged": self.mouse_held = False self.player.release_charge(self.projectiles) def update(self, dt): keys = pygame.key.get_pressed() mouse_pos = pygame.mouse.get_pos() scaled_mouse_pos = self._scale_mouse_pos(mouse_pos) self.player.update_mouse(scaled_mouse_pos) # Защитный таймер спавна if self.spawn_safe_timer > 0: self.spawn_safe_timer -= dt self.player.on_ground = True self.player.vy = 0 for en in self.level.enemies: if hasattr(en, '_just_spawned') and en._just_spawned: en.vy = 0 self.damage_texts = [text for text in self.damage_texts if text.update(dt)] return # Снимаем флаг _just_spawned for en in self.level.enemies: if hasattr(en, '_just_spawned'): en._just_spawned = False # Обновляем игрока self.player.update(dt, keys, self.level, self.projectiles) if not pygame.mouse.get_pressed()[0] and self.player.charging: self.player.charging = False self.mouse_held = False # Снаряды игрока (БЫСТРО!) for proj in self.projectiles[:]: if not proj.alive: continue proj.update(dt, self.level, self.level.enemies, self.player) # Очистка в конце (ОДН РАЗ, а не каждый раз!) self.projectiles = [p for p in self.projectiles if p.alive] # Враги if self.level.boss and not self.level.boss.dead: # Обновляем босса new_enemy_proj = [] self.level.boss.update(dt, self.level.platforms, self.player, self.projectiles, new_enemy_proj) self.enemy_projectiles.extend(new_enemy_proj) # Проверяем коллизии атак с босом for proj in self.projectiles[:]: if proj.alive and self.level.boss.rect.colliderect(proj.rect): damage = proj.damage self.level.boss.take_damage(damage) proj.alive = False if proj in self.projectiles: self.projectiles.remove(proj) elif self.level.boss and self.level.boss.dead and not self.level.boss._kill_counted: # БОСС ПОБЕЖДЕН! self.level.boss._kill_counted = True self.player.echos += 200 self.game.storage["echos"] = self.player.echos self.game.total_kills += 1 print(f"[BOSS DEFEATED] {self.level.boss.name}! +200 ЭХО!") # 🏆 ЕСЛИ ЭТО ФИНАЛЬНЫЙ БОСС (СМЕРТЬ) - ПОБЕДА! if self.room_id == 28: # ← Комната финального босса print("[VICTORY] СМЕРТЬ ПОВЕРЖЕНА! ПОБЕДА!") self.game.set_scene(VictoryScene(self.game)) return elif self.level.boss and self.level.boss.dead and not self.level.boss._kill_counted: # БОСС ПОБЕЖДЕН! Большая награда! self.level.boss._kill_counted = True self.player.echos += 200 # ← ОГРОМНАЯ НАГРАДА ЗА БОССА! self.game.storage["echos"] = self.player.echos self.game.total_kills += 1 print(f"[BOSS DEFEATED] {self.level.boss.name}! +200 ЭХО!") # Обновляем обычных врагов (если их есть) new_enemy_proj = [] # Враги (БЫСТРО!) for en in self.level.enemies[:]: if en.dead: continue en.update(dt, self.level.platforms, self.player, self.projectiles, new_enemy_proj) # 🔥 РЕГЕН ЗА КИЛЛ for dead_enemy in self.level.enemies: if dead_enemy.dead and not getattr(dead_enemy, '_regen_applied', False): dead_enemy._regen_applied = True if hasattr(self.player, 'regen_per_kill'): regen_amount = getattr(self.player, 'regen_per_kill', 0) if regen_amount > 0: old_health = self.player.health self.player.health = min(self.player.max_health, self.player.health + regen_amount) healed = self.player.health - old_health if healed > 0: self.damage_texts.append(DamageText( self.player.rect.centerx, self.player.rect.centery - 50, f"+{int(healed)}HP", (100, 255, 100) )) print(f"[REGEN] +{healed} HP за килл!") # Очистка после всего! self.level.enemies = [e for e in self.level.enemies if not e.dead] # 🔥 КЛЮЧЕВОЕ: Добавляем новые снаряды врагов! self.enemy_projectiles.extend(new_enemy_proj) # Снаряды врагов # Снаряды врагов (БЫСТРО!) for arrow in self.enemy_projectiles[:]: if not arrow.alive: continue arrow.update(dt, self.player, self.level) # Очистка в конце! self.enemy_projectiles = [a for a in self.enemy_projectiles if a.alive] # Зелья # Зелья (БЫСТРО!) for potion in self.potions[:]: if not potion.alive: continue potion.update(dt, self.player) # Очистка в конце! self.potions = [p for p in self.potions if p.alive] # Проверка очистки alive_enemies = [e for e in self.level.enemies if not e.dead] if self.level.boss and not self.level.boss.dead: alive_enemies.append(self.level.boss) if not self.is_cleared and len(alive_enemies) == 0: self.is_cleared = True print(f"[ROOM {self.room_id}] ОЧИЩЕНА!") self.damage_texts = [text for text in self.damage_texts if text.update(dt)] if self.player.dead: self.game.set_scene(GameOverScene(self.game)) return # Выход вправо if self.player.rect.right > self.level.width: self._save_room_state() self.game.room_history.append(self.room_id) # ← coming_from_right=False (нормальный вход в новую комнату) next_room = GameLevelScene(self.game, self.room_id + 1, coming_from_right=False) self.game.set_scene(next_room) return # Выход влево if self.player.rect.left < 0: self._save_room_state() if self.game.room_history: prev_room_id = self.game.room_history.pop() # ← coming_from_right=True (возвращаемся справа) prev_room = GameLevelScene(self.game, prev_room_id, coming_from_right=True) self.game.set_scene(prev_room) return def exit(self): self._save_room_state() def render(self, surface): surface.fill((30, 30, 50)) self.level.render(surface) for en in self.level.enemies: en.render(surface) for proj in self.projectiles: proj.render(surface) for arrow in self.enemy_projectiles: arrow.render(surface) for potion in self.potions: potion.render(surface) self.player.render(surface) # Damage texts for text in self.damage_texts: text.render(surface) # DEBUG if self.debug_draw: pygame.draw.rect(surface, (255, 0, 0), self.player.rect, 2) debug_font = pygame.font.Font(None, 28) surface.blit(debug_font.render(f"Комната: {self.room_id}", True, (255, 255, 0)), (10, 130)) surface.blit(debug_font.render(f"Player Y: {self.player.rect.y}, VY: {self.player.vy:.1f}", True, (255, 255, 0)), (10, 155)) surface.blit(debug_font.render(f"On ground: {self.player.on_ground}", True, (255, 255, 0)), (10, 180)) cleared_text = "ОЧИЩЕНА" if self.is_cleared else "ВРАГИ ЖИВЫ" cleared_color = (0, 255, 0) if self.is_cleared else (255, 100, 100) surface.blit(debug_font.render(f"Статус: {cleared_text}", True, cleared_color), (10, 205)) if self.level.boss: self.level.boss.render(surface) # Если босс в боссовой комнате - показать его имя в UI if self.is_boss_room and self.level.boss: boss_font = pygame.font.Font(None, 48) boss_name = boss_font.render(f"⚔️ {self.level.boss.name} ⚔️", True, (255, 50, 50)) surface.blit(boss_name, (surface.get_width() // 2 - 100, 30)) # UI # Нарисовать полоску HP pygame.draw.rect(surface, (50, 50, 50), (10, 10, 200, 16)) hp_ratio = max(0, self.player.health / self.player.max_health) pygame.draw.rect(surface, (200, 70, 70), (10, 10, int(200 * hp_ratio), 16)) # Нарисовать числовой HP hp_font = pygame.font.Font(None, 24) hp_text = f"{int(self.player.health)}/{int(self.player.max_health)}" surface.blit(hp_font.render(hp_text, True, (255, 255, 255)), (215, 8)) # Нарисовать полоску стамины pygame.draw.rect(surface, (50, 50, 50), (10, 30, 200, 16)) stam_ratio = max(0, self.player.stamina / self.player.max_stamina) pygame.draw.rect(surface, (70, 200, 70), (10, 30, int(200 * stam_ratio), 16)) # Нарисовать числовую стамину stam_font = pygame.font.Font(None, 24) stam_text = f"{int(self.player.stamina)}/{int(self.player.max_stamina)}" surface.blit(stam_font.render(stam_text, True, (255, 255, 255)), (215, 28)) mode_font = pygame.font.Font(None, 32) mode_text = "ЛУК" if self.player.combat_mode == "ranged" else "МЕЧ" surface.blit(mode_font.render(f"Режим: {mode_text}", True, (255, 255, 255)), (10, 55)) echo_font = pygame.font.Font(None, 32) surface.blit(echo_font.render(f"ЭХО: {self.player.echos}", True, (255, 215, 0)), (10, 75)) god_font = pygame.font.Font(None, 24) if self.game.total_kills >= self.game.next_god_kill: god_text = god_font.render("F - БОГИ", True, (0, 255, 0)) else: remain = self.game.next_god_kill - self.game.total_kills god_text = god_font.render(f"F - {remain} убийств", True, (255, 255, 0)) surface.blit(god_text, (10, 105)) class GodQuestionScene(Scene): def __init__(self, game, parent_scene): super().__init__(game) self.parent = parent_scene self.pantheon = self.parent.pantheon self.font = pygame.font.Font(None, 36) self.small_font = pygame.font.Font(None, 28) self.god, self.qidx, self.prompt, self.choices = self.pantheon.pose_question() self.selected = 0 self.result_text = None self.choice_made = False self.auto_exit_timer = 0.0 # ← НОВОЕ: Сохраняем позицию игрока для восстановления после меню self.saved_player_position = self.parent.player.rect.topleft print(f"[GOD_MENU] Сохранена позиция игрока: {self.saved_player_position}") def handle_event(self, event): if event.type == pygame.KEYDOWN: if event.key in (pygame.K_DOWN, pygame.K_s) and not self.result_text: self.selected = (self.selected + 1) % len(self.choices) elif event.key in (pygame.K_UP, pygame.K_w) and not self.result_text: self.selected = (self.selected - 1) % len(self.choices) elif event.key == pygame.K_RETURN: if not self.choice_made: res = self.pantheon.apply_effect(self.parent, self.god, self.qidx, self.selected) self.result_text = f"{self.god.name}: {res}" self.choice_made = True self.auto_exit_timer = 1.5 else: self._exit_to_game_level() elif event.key == pygame.K_ESCAPE: self._exit_to_game_level() def update(self, dt): if self.choice_made and self.auto_exit_timer > 0: self.auto_exit_timer -= dt if self.auto_exit_timer <= 0: self._exit_to_game_level() # ← ПЕРЕИМЕНОВАНА И ПЕРЕДЕЛАНА ФУНКЦИЯ def _exit_to_game_level(self): """Выход из меню богов - возврат в ту же комнату с восстановлением позиции""" # Обнуляем убийства + увеличиваем милок self.game.total_kills = 0 if self.game.next_god_kill < 9: self.game.next_god_kill += 2 else: self.game.next_god_kill += 3 print(f"[GODS] Убийства обнулены! Следующие боги на {self.game.next_god_kill} убийстве") # ═══════════════════════════════════════════════════════════════════ # КЛЮЧЕВОЕ ИСПРАВЛЕНИЕ: Передаём restore_position! # ═══════════════════════════════════════════════════════════════════ self.game.set_scene(GameLevelScene( self.game, self.parent.room_id, coming_from_right=False, # Не возвращаемся справа restore_position=self.saved_player_position # ← ВОССТАНАВЛИВАЕМ ПОЗИЦИЮ! )) def render(self, surface): surface.fill((10, 5, 20)) draw_text_center(surface, f"Бог {self.god.name} вопрошает:", self.font, (200, 180, 250), surface.get_width() // 2, 100) draw_text_wrapped(surface, self.prompt, self.font, (255, 255, 200), surface.get_width() // 2, 160, max_width=900) for i, (text, _, _) in enumerate(self.choices): color = (255, 255, 255) if i == self.selected else (100, 100, 100) draw_text_center(surface, text, self.small_font, color, surface.get_width() // 2, 240 + i * 40) if self.result_text: draw_text_center(surface, self.result_text, self.small_font, (200, 255, 200), surface.get_width() // 2, 480) class GameOverScene(Scene): def __init__(self, game): super().__init__(game) self.font = pygame.font.Font(None, 64) self.timer = 0.0 # ← НОВОЕ: Сохраняем перки и ЭХО перед переходом if self.game.player: self.game.storage["perk_levels"] = self.game.player.perk_levels.copy() self.game.storage["echos"] = self.game.player.echos print(f"[GAMEOVER] Сохранены перки и ЭХО: {self.game.player.echos}") def handle_event(self, event): if event.type == pygame.KEYDOWN: if event.key == pygame.K_RETURN: # ← КЛЮЧЕВОЕ: Перед PerkShopScene сбрасываем бонусы богов self._reset_god_effects() self.game.set_scene(PerkShopScene(self.game)) elif event.key == pygame.K_ESCAPE: self._reset_god_effects() # ← Сброс и перед меню! self.game.set_scene(MenuScene(self.game)) def _reset_god_effects(self): """Сбрасывает ВСЕ бонусы богов""" if self.game.rule_system: # ← Сбрасываем все правила/бонусы от богов self.game.rule_system.reset_god_buffs() print("[GAMEOVER] Бонусы богов сброшены!") def update(self, dt): self.timer += dt if self.timer > 3.0: self._reset_god_effects() self.game.set_scene(PerkShopScene(self.game)) def render(self, surface): surface.fill((20, 10, 10)) draw_text_center(surface, "ТЫ ПАЛ...", self.font, (200, 50, 50), surface.get_width() // 2, surface.get_height() // 2 - 80) draw_text_center(surface, f"ЭХО: {self.game.player.echos}", self.font, (255, 215, 0), surface.get_width() // 2, surface.get_height() // 2 - 20) draw_text_center(surface, "ENTER - МАГАЗИН ПЕРКОВ", pygame.font.Font(None, 42), (255, 255, 255), surface.get_width() // 2, surface.get_height() // 2 + 60) draw_text_center(surface, "ESC - МЕНЮ", pygame.font.Font(None, 36), (150, 150, 150), surface.get_width() // 2, surface.get_height() // 2 + 120) class PerkShopScene(Scene): def __init__(self, game): super().__init__(game) self.selected = 0 self.font = pygame.font.Font(None, 36) self.small_font = pygame.font.Font(None, 24) self.perk_keys = list(PERKS_DATA.keys()) self.menu_options = ["ИГРАТЬ ДАЛЬШЕ", "МЕНЮ"] self.in_shop = True def enter(self): # ← КЛЮЧЕВОЕ: Если игрок мёртв, создаём НОВОГО с сохранённых перков if self.game.player is None or self.game.player.dead: # Загружаем сохранённые перки и ЭХО perk_levels = self.game.storage.get("perk_levels", {key: 0 for key in PERKS_DATA.keys()}) echos = self.game.storage.get("echos", 0) # Создаём нового игрока (временно, для магазина) # Он будет пересоздан в GameLevelScene, но нужен для отображения if not self.game.player: self.game.player = Player(0, 0, self.game.rule_system or RuleSystem(self.game)) # Восстанавливаем перки и ЭХО self.game.player.perk_levels = perk_levels self.game.player.echos = echos # ← ВАЖНО: Применяем все куплены перки к новому игроку! for perk_id, level in perk_levels.items(): if level > 0 and perk_id in PERKS_DATA: perk_data = PERKS_DATA[perk_id] # Применяем перк уровня level for _ in range(level): perk_data["apply"](self.game.player, level) print(f"[PERKSHOP] Создан новый игрок со старыми перками: {perk_levels}, ЭХО: {echos}") self.game.player.health = self.game.player.max_health self.game.player.stamina = self.game.player.max_stamina # ← Гарантируем, что perk_levels имеет ВСЕ ключи default_perks = {key: 0 for key in PERKS_DATA.keys()} self.game.player.perk_levels = {**default_perks, **getattr(self.game.player, 'perk_levels', {})} # ← Сохраняем в storage self.game.storage["perk_levels"] = self.game.player.perk_levels def handle_event(self, event): if event.type == pygame.KEYDOWN: if self.in_shop: if event.key == pygame.K_UP: self.selected = (self.selected - 1) % len(self.perk_keys) elif event.key == pygame.K_DOWN: self.selected = (self.selected + 1) % len(self.perk_keys) elif event.key == pygame.K_RETURN: self.buy_perk() elif event.key == pygame.K_ESCAPE: self.in_shop = False self.selected = 0 else: # Меню выбора после ESC if event.key in (pygame.K_UP, pygame.K_w): self.selected = (self.selected - 1) % len(self.menu_options) elif event.key in (pygame.K_DOWN, pygame.K_s): self.selected = (self.selected + 1) % len(self.menu_options) elif event.key == pygame.K_RETURN: if self.selected == 0: # ИГРАТЬ ДАЛЬШЕ self.game.room_states.clear() self.game.room_history.clear() self.game.total_kills = 0 self.game.next_god_kill = 2 # ← КЛЮЧЕВОЕ: Создаём нового игрока, он загрузит перки в GameLevelScene.enter()! self.game.player = None # Обнуляем для создания заново self.game.set_scene(GameLevelScene(self.game, 1)) else: # МЕНЮ self.game.player = None self.game.room_states.clear() self.game.room_history.clear() self.game.set_scene(MenuScene(self.game)) def buy_perk(self): perk_id = self.perk_keys[self.selected] perk = PERKS_DATA[perk_id] level = self.game.player.perk_levels.get(perk_id, 0) if level >= perk["max_level"]: print(f"[SHOP] {perk['name']} — макс. уровень!") return cost = perk["cost"] * (level + 1) if self.game.player.echos >= cost: self.game.player.echos -= cost self.game.player.perk_levels[perk_id] = level + 1 perk["apply"](self.game.player, self.game.player.perk_levels[perk_id]) # ← Сохраняем в storage self.game.storage["perk_levels"] = self.game.player.perk_levels self.game.storage["echos"] = self.game.player.echos print(f"[SHOP] Куплен {perk['name']} lvl{self.game.player.perk_levels[perk_id]} за {cost}!") else: print(f"[SHOP] Нужно {cost} эхо (у тебя: {self.game.player.echos})") def render(self, surface): surface.fill((10, 10, 30)) if self.in_shop: draw_text_center(surface, "МАГАЗИН ПЕРКОВ", self.font, (255, 215, 0), surface.get_width() // 2, 60) draw_text_center(surface, f"ЭХО: {self.game.player.echos}", self.font, (255, 215, 0), surface.get_width() // 2, 100) perks_per_page = 6 start_idx = (self.selected // perks_per_page) * perks_per_page for i in range(start_idx, min(start_idx + perks_per_page, len(self.perk_keys))): perk_id = self.perk_keys[i] perk = PERKS_DATA[perk_id] level = self.game.player.perk_levels.get(perk_id, 0) cost = perk["cost"] * (level + 1) y = 150 + (i - start_idx) * 65 color = (255, 255, 255) if i == self.selected else (180, 180, 180) stars = "★" * min(level, 3) + "☆" * (3 - min(level, 3)) draw_text_center(surface, f"{stars} {perk['name']}", self.font, color, surface.get_width() // 2, y) lvl_text = f"Ур. {level}/{perk['max_level']} | {cost} эхо" color_cost = (0, 255, 0) if self.game.player.echos >= cost else (255, 100, 100) draw_text_center(surface, lvl_text, self.small_font, color_cost, surface.get_width() // 2, y + 30) draw_text_center(surface, "↑↓ - выбрать | ENTER - купить | ESC - выйти в меню", self.small_font, (150, 255, 150), surface.get_width() // 2, 520) else: draw_text_center(surface, "ВЕРНУЛСЯ К ЖИЗНИ?", self.font, (255, 215, 0), surface.get_width() // 2, 120) draw_text_center(surface, f"ЭХО: {self.game.player.echos}", self.font, (255, 215, 0), surface.get_width() // 2, 170) for i, option in enumerate(self.menu_options): y = 250 + i * 80 color = (255, 255, 255) if i == self.selected else (120, 120, 120) draw_text_center(surface, option, self.font, color, surface.get_width() // 2, y) draw_text_center(surface, "ENTER - выбрать | ↑↓ - навигация", self.small_font, (150, 150, 150), surface.get_width() // 2, 500) class SettingsScene(Scene): def __init__(self, game): super().__init__(game) self.font = pygame.font.Font(None, 48) self.small = pygame.font.Font(None, 32) self.resolutions = [ (1024, 640), # Добавил оригинал для теста (1280, 720), (1600, 900), (1920, 1080) ] self.fullscreen = False self.selected_res = 0 self.selected_option = 0 self.options = ["Разрешение", "Полноэкранный режим", "Применить", "Назад"] def handle_event(self, event): if event.type == pygame.KEYDOWN: if event.key in (pygame.K_DOWN, pygame.K_s): self.selected_option = (self.selected_option + 1) % len(self.options) elif event.key in (pygame.K_UP, pygame.K_w): self.selected_option = (self.selected_option - 1) % len(self.options) # ← ENTER теперь меняет опции напрямую! elif event.key == pygame.K_RETURN: if self.selected_option == 0: # Разрешение: ENTER циклически меняет self.selected_res = (self.selected_res + 1) % len(self.resolutions) print(f"[SETTINGS] Разрешение изменено на {self.resolutions[self.selected_res]}") # Для дебага elif self.selected_option == 1: # Полноэкранный: ENTER toggles self.fullscreen = not self.fullscreen print(f"[SETTINGS] Полноэкранный: {'ВКЛ' if self.fullscreen else 'ВЫКЛ'}") # Для дебага elif self.selected_option == 2: # Применить: применяем изменения if self.fullscreen: # ← ПОЛНЫЙ ЭКРАН: Авто-размер БЕЗ SCALED (исправлено!) self.game.surface = pygame.display.set_mode((0, 0), pygame.FULLSCREEN) else: # ОКОННЫЙ: Выбранное разрешение w, h = self.resolutions[self.selected_res] self.game.surface = pygame.display.set_mode((w, h)) print(f"[SETTINGS] Изменения применены: {self.resolutions[self.selected_res]} {'(полноэкранный)' if self.fullscreen else ''}") self.game.set_scene(MenuScene(self.game)) elif self.selected_option == 3: # Назад: без применения self.game.set_scene(MenuScene(self.game)) # ← Оставил стрелки для тонкой настройки (опционально) elif event.key in (pygame.K_LEFT, pygame.K_a): if self.selected_option == 0: self.selected_res = (self.selected_res - 1) % len(self.resolutions) elif event.key in (pygame.K_RIGHT, pygame.K_d): if self.selected_option == 0: self.selected_res = (self.selected_res + 1) % len(self.resolutions) def render(self, surface): surface.fill((15, 15, 30)) draw_text_center(surface, "НАСТРОЙКИ", self.font, (255,255,0), surface.get_width()//2, 80) # Разрешение col = (255,255,255) if self.selected_option == 0 else (130,130,130) res_text = f"{self.resolutions[self.selected_res][0]} x {self.resolutions[self.selected_res][1]}" draw_text_center(surface, f"Разрешение: {res_text} (ENTER - сменить)", self.small, col, surface.get_width()//2, 200) # Полноэкранный col = (255,255,255) if self.selected_option == 1 else (130,130,130) mode_text = "ВКЛ" if self.fullscreen else "ВЫКЛ" draw_text_center(surface, f"Полноэкранный: {mode_text} (ENTER - переключить)", self.small, col, surface.get_width()//2, 260) # Применить col = (255,255,255) if self.selected_option == 2 else (130,130,130) draw_text_center(surface, "Применить (ENTER - сохранить)", self.small, col, surface.get_width()//2, 340) # Назад col = (255,255,255) if self.selected_option == 3 else (130,130,130) draw_text_center(surface, "Назад (ENTER - отменить)", self.small, col, surface.get_width()//2, 400) # ← Подсказка внизу hint_font = pygame.font.Font(None, 28) surface.blit(hint_font.render("↑↓ - выбрать опцию | ENTER - действие | ←→ - тонкая настройка", True, (150, 150, 150)), (50, 500))