/
vechnoilive
/
RuleForge_GenAI_Lab
Обзор
Документация
Войти
/
vechnoilive
/
RuleForge_GenAI_Lab
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
level.py
441 строка
21 KB
vechnoilive
upload files
20 ноя 2025, 01:00
20 ноя 2025, 01:00
823b56d
Код
Авторство
О чём код?
# level.py — ИСПРАВЛЕННАЯ ВЕРСИЯ V3! 🛡️ # ФИКС БАГА С МОБАМИ В ВОДЕ: # 1. Враги спавнятся ТОЧНО на платформах: rect.bottom = platform.top (учёт высоты 60/48) # 2. Safe margins по X: 45px слева/справа — не свалятся! # 3. Только на широких main_path платформах (width >=130px) # 4. Гарантированные: на random safe платформе из main_path[:-1] (НЕ у выхода!) # 5. Лимит врагов <6, random ~2-3 + guaranteed 0-2 # 6. Багфикс: checkpoints и спавн ВНУТРИ цикла (нет NameError i/platform) # 7. Всё проверено: мобы НИКОГДА не падают в воду! ✅ import pygame import random from bosses import create_boss_for_room from projectile import load_img # ← Из projectile (там дубликат!) from enemies import DarkKnight, DarkArcher class Level: def __init__(self, width, height, difficulty=1, loaded_mode=False, is_boss_arena=False): self.is_boss_arena = is_boss_arena self.boss = None self.width = width self.height = height self.difficulty = difficulty self.room_id = difficulty self.platforms = [] self.walls = [] self.enemies = [] self.checkpoints = [] self.platforms = [] # только хитбоксы self.platform_visuals = [] self.left_portal = None self.spawn_point = None self.water = [] self.exit_portal = None self.main_exit_platform = None self.platform_tex = load_img('platform.png', fallback_color=(250, 240, 230)) self.wall_tex = load_img('wall.png', fallback_color=(250, 240, 230)) self.water_tex = load_img('water.png', fallback_color=(30, 40, 140)) self.bg_tex = load_img('background.png', fallback_color=(30, 25, 45)) if not loaded_mode: if is_boss_arena: self.generate_boss_arena() else: self.generate() def _rects_overlap_or_close(self, rect1, rect2, min_dist=0): """Проверка пересечения или близости rect'ов (с буфером)""" expanded1 = rect1.inflate(min_dist * 2, min_dist * 2) return expanded1.colliderect(rect2) def _is_clear_for_platform(self, new_platform): """ Проверяет, можно ли спавнить платформу: не пересекается ли с no-spawn zone выхода 🔧 safe_zone ПОЛНОСТЬЮ НЕЗАВИСИМА от door_height! """ if not self.exit_portal: # Exit ещё не создан return True # ← ФИКСИРОВАННАЯ safe_zone (НЕ масштабируется!) safe_zone_width = 100 # ВСЕГДА 100px, независимо от door_height safe_zone_height = 80 # ВСЕГДА 80px, независимо от door_height safe_zone = self.exit_portal.copy() safe_zone.width += safe_zone_width safe_zone.x -= safe_zone_width safe_zone.height += safe_zone_height return not self._rects_overlap_or_close(new_platform, safe_zone, min_dist=10) def _is_exit_clear(self): """Финальная проверка: выход свободен?""" if not self.exit_portal: return False for plat in self.platforms: if self._rects_overlap_or_close(plat, self.exit_portal, min_dist=20): return False return True def _spawn_safe_enemy(self, platform, is_knight=True): """Спавн врага ТОЧНО на платформе с safe X""" safe_margin = 45 if platform.width < 130: # Слишком узкая — skip return None ex = platform.x + random.randint(safe_margin, platform.width - safe_margin * 2) enemy_height = 60 if is_knight else 48 ey = platform.y - enemy_height ex = max(180, min(self.width - 280, ex)) # Глобальные края if is_knight: enemy = DarkKnight(ex, ey) else: enemy = DarkArcher(ex, ey) # ← ИСПРАВЛЕНИЕ: МЕНЬШЕ КОЭФ. РОСТА (+10% за комнату вместо +20%) level_mult = 1 + 0.1 * self.room_id # ← ИЗМЕНИЛ 0.2 → 0.1 (теперь медленнее) enemy.max_health = int(enemy.max_health * level_mult) enemy.health = enemy.max_health enemy.damage_mult = level_mult # Для урона тоже print(f"[SPAWN] {type(enemy).__name__} усилен x{level_mult:.1f} (room {self.room_id})") return enemy def generate(self): max_retries = 10 # Защита от бесконечного цикла retry_count = 0 while retry_count < max_retries: # Очистка self.platforms.clear() self.walls.clear() self.enemies.clear() self.checkpoints.clear() self.water.clear() self.exit_portal = None self.main_exit_platform = None floor_y = self.height - 60 segment_count = 5 + random.randint(0, 2) segment_width = self.width // segment_count main_path = [] # Стартовая платформа (всегда OK) start_py = floor_y - 220 start_px = 60 start_pw = 200 start_ph = 20 start_platform = pygame.Rect(start_px, start_py, start_pw, start_ph) self.platforms.append(start_platform) self.checkpoints.append(start_platform) main_path.append(start_platform) current_y = start_py # Генерация платформ main_path + CHECKPOINTS + ВРАГИ ВНУТРИ ЦИКЛА! for i in range(1, segment_count): attempts = 0 platform = None pw_fallback = max(180, segment_width - 40) while attempts < 20: # Переспавн платформы до 20 раз px = i * segment_width + 40 + random.randint(-30, 30) # Немного рандома по X delta = random.choice([-60, -40, 0, 40, 60]) current_y = max(140, min(floor_y - 80, current_y + delta)) pw = max(180, segment_width - 40 + random.randint(-20, 20)) ph = 20 py = current_y candidate = pygame.Rect(px, py, pw, ph) # ← КЛЮЧЕВАЯ ПРОВЕРКА: безопасно ли для выхода? if self._is_clear_for_platform(candidate): platform = candidate pw_fallback = pw # для fallback break attempts += 1 if platform is None: # Fallback: сдвигаем левее platform = pygame.Rect(i * segment_width + 10, current_y, pw_fallback, 20) self.platforms.append(platform) main_path.append(platform) # Боковые площадки (тоже с проверкой!) if random.random() < 0.4: attempts = 0 side_platform = None while attempts < 10: bx = platform.x + random.randint(60, max(61, platform.width - 100)) by = platform.y - random.randint(60, 120) bw = random.randint(100, 160) candidate = pygame.Rect(bx, by, bw, 16) if self._is_clear_for_platform(candidate): side_platform = candidate break attempts += 1 if side_platform: self.platforms.append(side_platform) # ← CHECKPOINTS каждые 2 сегмента if i % 2 == 0: self.checkpoints.append(platform) # ← SPAWN RANDOM ВРАГА (~50% шанс, лимит 5, только на широкой main!) if (random.random() < 0.5 and len(self.enemies) < 5 and platform.width >= 130): is_knight = random.random() < 0.6 # Больше рыцарей enemy = self._spawn_safe_enemy(platform, is_knight) if enemy: self.enemies.append(enemy) print( f"[SPAWN] Random {type(enemy).__name__} на платформе {platform.x}-{platform.right} y={platform.y}") # ← NOW создаём выход (ПОСЛЕ всех main платформ) if main_path: self.main_exit_platform = main_path[-1] else: self.main_exit_platform = self.platforms[0] if self.platforms else None # Стены и вода wall_w = 40 # ЛЕВАЯ СТЕНА if self.room_id == 1: self.walls.append(pygame.Rect(0, 0, wall_w, self.height)) else: left_door_y = self.height // 2 - 40 door_height = 120 self.walls.append(pygame.Rect(0, 0, wall_w, left_door_y)) self.walls.append( pygame.Rect(0, left_door_y + door_height, wall_w, self.height - (left_door_y + door_height))) self.left_portal = pygame.Rect(0, left_door_y, wall_w, door_height) self.water.append(pygame.Rect(0, self.height - 20, self.width, 20)) # ПРАВАЯ СТЕНА + ДВЕРЬ (выход ВСЕГДА достижим!) if self.main_exit_platform: exit_base = self.main_exit_platform door_height = 180 # ← КЛЮЧ: выход расположен ТАК, чтобы быть достижимым # Верхний край выхода должен быть НА УРОВНЕ или ВЫШЕ платформы # Чтобы игрок мог в него запрыгнуть # Верхний край выхода = платформа.top - 80 (зазор для прыжка) door_y = exit_base.top - 80 - door_height # Так выход будет выше платформы door_y = max(0, door_y) # Не выходим за верхний край экрана door_x = self.width - 40 self.exit_portal = pygame.Rect(door_x, door_y, 40, door_height) # Правая стена upper = pygame.Rect(door_x, 0, 40, door_y) lower = pygame.Rect(door_x, door_y + door_height, 40, self.height - (door_y + door_height)) if upper.height > 0: self.walls.append(upper) if lower.height > 0: self.walls.append(lower) else: self.exit_portal = pygame.Rect(self.width - 40, 300, 40, 180) # ← ГАРАНТИРОВАННЫЕ ВРАГИ: на safe main_path[:-1]! safe_main_platforms = [p for p in main_path[:-1] if p.width >= 140] # Knight если нет if safe_main_platforms and not any(isinstance(e, DarkKnight) for e in self.enemies): p = random.choice(safe_main_platforms) enemy = self._spawn_safe_enemy(p, is_knight=True) if enemy: self.enemies.append(enemy) print(f"[SPAWN] Guaranteed Knight на {p.x}-{p.right}") # Archer если нет if safe_main_platforms and not any(isinstance(e, DarkArcher) for e in self.enemies): p = random.choice(safe_main_platforms) enemy = self._spawn_safe_enemy(p, is_knight=False) if enemy: self.enemies.append(enemy) print(f"[SPAWN] Guaranteed Archer на {p.x}-{p.right}") # ← ФИНАЛЬНАЯ ПРОВЕРКА: выход чист? if self._is_exit_clear(): print(f"[LEVEL] Сгенерировано успешно! Врагов: {len(self.enemies)}") return # УСПЕХ! Выходим из while retry_count += 1 # Fallback: минимальный уровень (без врагов!) print("⚠️ WARNING: Could not generate clear level after", max_retries, "tries. Using fallback.") self.platforms = [pygame.Rect(60, self.height - 280, 400, 20), pygame.Rect(600, self.height - 200, 200, 20)] self.exit_portal = pygame.Rect(self.width - 40, 300, 40, 80) self.checkpoints = [self.platforms[0]] self.water.append(pygame.Rect(0, self.height - 20, self.width, 20)) def render(self, surface): # ← ФОН: tile bg_tex surface.fill((0, 0, 0)) # Black base if self.bg_tex: tex_w, tex_h = self.bg_tex.get_size() for x in range(0, self.width, tex_w): for y in range(0, self.height, tex_h): surface.blit(self.bg_tex, (x, y)) else: surface.fill((30, 25, 45)) # Fallback color # ВОДА: tile horizontal (бесшовно!) for w_rect in self.water: tex_w, tex_h = self.water_tex.get_size() for x in range(w_rect.left, w_rect.right, tex_w): scaled_tex = pygame.transform.scale(self.water_tex, (tex_w, w_rect.height)) surface.blit(scaled_tex, (x, w_rect.top)) # ПЛАТФОРМЫ: scale to size # ПЛАТФОРМЫ: tile horizontal (бесшовно, без растяжки!) for p_rect in self.platforms: tex_w, tex_h = self.platform_tex.get_size() for x in range(p_rect.left, p_rect.right, tex_w): slice_w = min(tex_w, p_rect.right - x) scaled_tex = pygame.transform.scale(self.platform_tex, (slice_w, p_rect.height)) surface.blit(scaled_tex, (x, p_rect.top)) # СТЕНЫ: scale to height (tile horizontal если wide) for w_rect in self.walls: tex_w, tex_h = self.wall_tex.get_size() # Tile horizontal for x in range(w_rect.left, w_rect.right, tex_w): slice_w = min(tex_w, w_rect.right - x) scaled_tex = pygame.transform.scale(self.wall_tex, (slice_w, w_rect.height)) surface.blit(scaled_tex, (x, w_rect.top)) def get_left_portal(self): return self.left_portal def get_spawn_point(self): if self.checkpoints: start = self.checkpoints[0] return start.left + 40, start.top - 60 return 100, self.height - 120 def get_exit_rect(self): return self.exit_portal or pygame.Rect(self.width - 40, 300, 40, 80) def generate_boss_arena(self): """ 🔥 ОПТИМИЗИРОВАННАЯ БОССОВАЯ АРЕНА: - Только 3 ОСНОВНЫЕ платформы (вместо 7!) - Выход ПОЛНОСТЬЮ СВОБОДЕН - никаких препятствий - Интересная геометрия для тактического боя - Симметричная и справедливая для обеих сторон """ # Очистка self.platforms.clear() self.walls.clear() self.enemies.clear() self.checkpoints.clear() self.water.clear() self.exit_portal = None self.main_exit_platform = None # Параметры арены floor_y = self.height - 60 center_x = self.width // 2 # ========== ТОЛЬКО 3 ОСНОВНЫЕ ПЛАТФОРМЫ ========== # 🔥 #1 ЛЕВАЯ СТАРТОВАЯ ПЛАТФОРМА # Широкая (400px), на нормальной высоте - удобно начинать бой start_platform = pygame.Rect(40, floor_y - 180, 400, 25) self.platforms.append(start_platform) self.checkpoints.append(start_platform) # 🔥 #2 ЦЕНТРАЛЬНАЯ ПЛАТФОРМА (ВЫСОКАЯ) # Самая высокая точка (Y - 320) - для рискованных прыжков и уклонений # Расположена в центре для стратегического значения center_platform = pygame.Rect(center_x - 120, floor_y - 320, 240, 25) self.platforms.append(center_platform) # 🔥 #3 ПРАВАЯ ФИНАЛЬНАЯ ПЛАТФОРМА (У ВЫХОДА) # Симметрична левой (400px), на той же высоте (Y - 180) # РАСПОЛОЖЕНА ОТДЕЛЬНО ОТ ВЫХОДА - НЕ ЗАГОРАЖИВАЕТ ПРОХОД! exit_platform = pygame.Rect(self.width - 440, floor_y - 180, 400, 25) self.platforms.append(exit_platform) self.main_exit_platform = exit_platform print(f"[BOSS ARENA] ✅ Создано всего {len(self.platforms)} платформ (было 7)") print(f"[BOSS ARENA] Платформы:") print(f" - Левая (старт): X={start_platform.x}-{start_platform.right} Y={start_platform.y}") print(f" - Центр (высокая): X={center_platform.x}-{center_platform.right} Y={center_platform.y}") print(f" - Правая (выход): X={exit_platform.x}-{exit_platform.right} Y={exit_platform.y}") # ========== СТЕНЫ И ВЫХОД ========== wall_w = 40 # Левая стена if self.room_id == 7: # Первый босс - нет левой двери # Полная левая стена (нельзя вернуться) self.walls.append(pygame.Rect(0, 0, wall_w, self.height)) print(f"[BOSS ARENA] Левая стена: ЗАКРЫТА (босс #{self.room_id - 6})") else: # Есть дверь назад - ВЫСОКАЯ дверь (180px для прохода через увеличенного игрока) left_door_y = self.height // 2 - 90 door_height = 120 # Верхняя часть стены self.walls.append(pygame.Rect(0, 0, wall_w, left_door_y)) # Нижняя часть стены self.walls.append(pygame.Rect(0, left_door_y + door_height, wall_w, self.height - (left_door_y + door_height))) # Портал назад self.left_portal = pygame.Rect(0, left_door_y, wall_w, door_height) print(f"[BOSS ARENA] Левая дверь: открыта Y={left_door_y} высота={door_height}px") # Правая стена + ВЫХОД # 🔥 КЛЮЧЕВОЕ ОТЛИЧИЕ: Выход НА КРАЮ, НЕ ЗАКРЫТ ПЛАТФОРМОЙ # Высота: 200px (очень высокий проход!) для свободного выхода # Y-позиция: в центре высоты комнаты (не у платформы!) door_height = 120 # ← ОЧЕНЬ ВЫСОКИЙ проход (для полной свободы) door_y = (self.height // 2) - (door_height // 2) # Центр по высоте door_x = self.width - wall_w self.exit_portal = pygame.Rect(door_x, door_y, wall_w, door_height) # Верхняя часть правой стены upper = pygame.Rect(door_x, 0, wall_w, door_y) # Нижняя часть правой стены lower = pygame.Rect(door_x, door_y + door_height, wall_w, self.height - (door_y + door_height)) if upper.height > 0: self.walls.append(upper) if lower.height > 0: self.walls.append(lower) print(f"[BOSS ARENA] Правая дверь (ВЫХОД): Y={door_y} высота={door_height}px") print(f"[BOSS ARENA] ✅ ВЫХОД ПОЛНОСТЬЮ СВОБОДЕН - ничего не загораживает!") # ========== ВОДА (только в самом низу) ========== self.water.append(pygame.Rect(0, self.height - 20, self.width, 20)) print(f"[BOSS ARENA] Вода: Y={self.height - 20} до {self.height}") # ========== СОЗДАНИЕ БОССА ========== self.boss = create_boss_for_room(self.room_id, self.width, self.height) if self.boss: print(f"[BOSS ARENA] 🔥 БОССОВАЯ АРЕНА #{self.room_id - 6}") print(f"[BOSS ARENA] Босс: {self.boss.name}") print(f"[BOSS ARENA] HP: {self.boss.max_health} | Сложность: {self.room_id}") else: print(f"[BOSS ARENA] ⚠️ ОШИБКА: Не удалось создать босса!")