Загрузка данных


import pygame
import random
import math
import sys

pygame.init()

SCREEN_W, SCREEN_H = 1000, 650
WORLD_W, WORLD_H = 2400, 1800
FPS = 60

screen = pygame.display.set_mode((SCREEN_W, SCREEN_H))
pygame.display.set_caption("The Witcher 2D - Velen Woods")
clock = pygame.time.Clock()
font = pygame.font.SysFont("consolas", 14, bold=True)
dmg_font = pygame.font.SysFont("consolas", 18, bold=True)

# Цвета
C_GROUND = (38, 50, 40)
C_GRASS = (48, 64, 48)
C_TREE_TRUNK = (60, 42, 30)
C_TREE_LEAVES = (28, 48, 28)
C_WITCHER = (220, 220, 230)
C_QUEN = (255, 215, 0)
C_DROWN = (45, 110, 115)
C_HP = (220, 50, 50)
C_STAMINA = (230, 180, 30)
C_FIRE = (255, 90, 20)

class Camera:
    def __init__(self):
        self.x = 0
        self.y = 0

    def follow(self, target_rect):
        self.x = target_rect.centerx - SCREEN_W // 2
        self.y = target_rect.centery - SCREEN_H // 2
        self.x = max(0, min(self.x, WORLD_W - SCREEN_W))
        self.y = max(0, min(self.y, WORLD_H - SCREEN_H))

    def apply(self, rect):
        return rect.move(-self.x, -self.y)

    def apply_pos(self, pos):
        return (pos[0] - self.x, pos[1] - self.y)

camera = Camera()

class DamageText:
    def __init__(self, x, y, text, color):
        self.x, self.y = x, y
        self.text = str(text)
        self.color = color
        self.lifetime = 35

    def update(self):
        self.y -= 1
        self.lifetime -= 1

    def draw(self, surf):
        render = dmg_font.render(self.text, True, self.color)
        screen_pos = camera.apply_pos((self.x, self.y))
        surf.blit(render, screen_pos)

class Witcher:
    def __init__(self, x, y):
        self.rect = pygame.Rect(x, y, 32, 44)
        self.hp = 120
        self.max_hp = 120
        self.stamina = 100
        self.max_stamina = 100
        self.speed = 4.5
        self.facing = pygame.Vector2(0, 1)

        self.attacking = False
        self.atk_timer = 0
        self.attack_hitbox = pygame.Rect(0, 0, 0, 0)

        self.quen_active = False
        self.dodge_timer = 0

    def update(self, obstacles):
        if self.stamina < self.max_stamina:
            self.stamina = min(self.max_stamina, self.stamina + 0.35)

        # Передвижение
        keys = pygame.key.get_pressed()
        move = pygame.Vector2(0, 0)
        if keys[pygame.K_w]: move.y -= 1
        if keys[pygame.K_s]: move.y += 1
        if keys[pygame.K_a]: move.x -= 1
        if keys[pygame.K_d]: move.x += 1

        if move.length() > 0:
            move = move.normalize()
            self.facing = move

        spd = self.speed * 2.0 if self.dodge_timer > 0 else self.speed

        # Движение с проверкой препятствий X
        self.rect.x += int(move.x * spd)
        for obs in obstacles:
            if self.rect.colliderect(obs):
                if move.x > 0: self.rect.right = obs.left
                if move.x < 0: self.rect.left = obs.right

        # Движение с проверкой препятствий Y
        self.rect.y += int(move.y * spd)
        for obs in obstacles:
            if self.rect.colliderect(obs):
                if move.y > 0: self.rect.bottom = obs.top
                if move.y < 0: self.rect.top = obs.bottom

        self.rect.clamp_ip(pygame.Rect(0, 0, WORLD_W, WORLD_H))

        if self.dodge_timer > 0:
            self.dodge_timer -= 1

        # Атака мечом
        if self.attacking:
            self.atk_timer -= 1
            reach = 36
            ax = self.rect.centerx + int(self.facing.x * reach) - 16
            ay = self.rect.centery + int(self.facing.y * reach) - 16
            self.attack_hitbox = pygame.Rect(ax, ay, 32, 32)
            if self.atk_timer <= 0:
                self.attacking = False
                self.attack_hitbox = pygame.Rect(0, 0, 0, 0)

    def draw(self, surf):
        r = camera.apply(self.rect)
        # Тело Ведьмака
        pygame.draw.rect(surf, (80, 70, 60), r, border_radius=4)
        # Волосы (белые)
        pygame.draw.circle(surf, C_WITCHER, (r.centerx, r.top + 6), 9)

        # Щит Квен
        if self.quen_active:
            pygame.draw.circle(surf, C_QUEN, r.center, 30, 2)

        # Взмах меча
        if self.attacking:
            pygame.draw.arc(surf, (220, 240, 255), camera.apply(self.attack_hitbox), 0, 3.14, 4)

class Drowner:
    def __init__(self, x, y):
        self.rect = pygame.Rect(x, y, 28, 38)
        self.hp = 60
        self.max_hp = 60
        self.speed = 2.4
        self.hit_cd = 0

    def update(self, player, dmg_texts):
        self.hit_cd = max(0, self.hit_cd - 1)
        dist = pygame.Vector2(player.rect.center) - pygame.Vector2(self.rect.center)
        length = dist.length()

        # Агро-радиус
        if 18 < length < 450:
            direction = dist.normalize()
            self.rect.x += int(direction.x * self.speed)
            self.rect.y += int(direction.y * self.speed)

        # Нанесение урона Ведьмаку
        if self.rect.colliderect(player.rect) and self.hit_cd == 0 and player.dodge_timer == 0:
            self.hit_cd = 40
            dmg = random.randint(12, 18)
            if player.quen_active:
                player.quen_active = False
                dmg_texts.append(DamageText(player.rect.centerx, player.rect.top, "ABSORBED", C_QUEN))
            else:
                player.hp = max(0, player.hp - dmg)
                dmg_texts.append(DamageText(player.rect.centerx, player.rect.top, f"-{dmg}", C_HP))

    def draw(self, surf):
        r = camera.apply(self.rect)
        pygame.draw.rect(surf, C_DROWN, r, border_radius=3)
        # ХП бар врага
        hp_pct = max(0, self.hp / self.max_hp)
        pygame.draw.rect(surf, (30, 30, 30), (r.x, r.y - 8, self.rect.w, 4))
        pygame.draw.rect(surf, C_HP, (r.x, r.y - 8, int(self.rect.w * hp_pct), 4))

class Particle:
    def __init__(self, x, y, dx, dy, life, color):
        self.x, self.y = x, y
        self.dx, self.dy = dx, dy
        self.life = life
        self.color = color

    def update(self):
        self.x += self.dx
        self.y += self.dy
        self.life -= 1

    def draw(self, surf):
        pos = camera.apply_pos((self.x, self.y))
        pygame.draw.circle(surf, self.color, pos, 3)

# Генерация мира
trees = [pygame.Rect(random.randint(50, WORLD_W - 100), random.randint(50, WORLD_H - 100), 50, 50) for _ in range(35)]
grass_patches = [(random.randint(0, WORLD_W), random.randint(0, WORLD_H), random.randint(2, 4)) for _ in range(250)]
drowners = [Drowner(random.randint(400, WORLD_W - 100), random.randint(400, WORLD_H - 100)) for _ in range(8)]
particles = []
dmg_texts = []

witcher = Witcher(SCREEN_W // 2, SCREEN_H // 2)
running = True

while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

        if event.type == pygame.KEYDOWN:
            # Атака мечом (SPACE)
            if event.key == pygame.K_SPACE and not witcher.attacking and witcher.stamina >= 10:
                witcher.attacking = True
                witcher.atk_timer = 12
                witcher.stamina -= 10

            # Кувырок / Рывок (LSHIFT)
            if event.key == pygame.K_LSHIFT and witcher.dodge_timer == 0 and witcher.stamina >= 25:
                witcher.dodge_timer = 14
                witcher.stamina -= 25

            # Знак Игни (1 или Q)
            if event.key in (pygame.K_1, pygame.K_q) and witcher.stamina >= 35:
                witcher.stamina -= 35
                for _ in range(25):
                    spread = (random.random() - 0.5) * 0.8
                    dir_vec = witcher.facing.rotate_rad(spread) * random.uniform(5, 9)
                    particles.append(Particle(witcher.rect.centerx, witcher.rect.centery, dir_vec.x, dir_vec.y, random.randint(15, 25), C_FIRE))

            # Знак Квен (2 или E)
            if event.key in (pygame.K_2, pygame.K_e) and witcher.stamina >= 40:
                witcher.stamina -= 40
                witcher.quen_active = True

    # Обновление сущностей
    witcher.update(trees)
    camera.follow(witcher.rect)

    # Проверка попаданий меча
    if witcher.attacking and witcher.attack_hitbox.width > 0:
        for d in drowners:
            if witcher.attack_hitbox.colliderect(d.rect):
                dmg = random.randint(22, 32)
                d.hp -= dmg
                dmg_texts.append(DamageText(d.rect.centerx, d.rect.top, f"-{dmg}", (255, 255, 255)))
                witcher.attack_hitbox = pygame.Rect(0, 0, 0, 0) # Один удар - одно попадание
                break

    # Обновление частиц огня (Игни) и урон монстрам
    for p in particles[:]:
        p.update()
        for d in drowners:
            if math.hypot(p.x - d.rect.centerx, p.y - d.rect.centery) < 22:
                d.hp -= 2
                dmg_texts.append(DamageText(d.rect.centerx, d.rect.top, "BURN", C_FIRE))
                p.life = 0
                break
        if p.life <= 0:
            particles.remove(p)

    # Обновление монстров
    for d in drowners[:]:
        d.update(witcher, dmg_texts)
        if d.hp <= 0:
            drowners.remove(d)

    # Обновление плавающего текста
    for dt in dmg_texts[:]:
        dt.update()
        if dt.lifetime <= 0:
            dmg_texts.remove(dt)

    # Отрисовка
    screen.fill(C_GROUND)

    # Трава
    for gx, gy, sz in grass_patches:
        pos = camera.apply_pos((gx, gy))
        if -10 < pos[0] < SCREEN_W + 10 and -10 < pos[1] < SCREEN_H + 10:
            pygame.draw.circle(screen, C_GRASS, pos, sz)

    # Деревья
    for t in trees:
        tr = camera.apply(t)
        pygame.draw.circle(screen, C_TREE_LEAVES, (tr.centerx, tr.centery), 38)
        pygame.draw.rect(screen, C_TREE_TRUNK, (tr.centerx - 8, tr.centery - 8, 16, 22))

    # Сущности
    for d in drowners:
        d.draw(screen)

    witcher.draw(screen)

    for p in particles:
        p.draw(screen)

    for dt in dmg_texts:
        dt.draw(screen)

    # HUD (Интерфейс)
    # Здоровье
    pygame.draw.rect(screen, (30, 30, 30), (20, 20, 200, 16), border_radius=3)
    pygame.draw.rect(screen, C_HP, (20, 20, int(200 * (witcher.hp / witcher.max_hp)), 16), border_radius=3)
    # Энергия
    pygame.draw.rect(screen, (30, 30, 30), (20, 40, 200, 10), border_radius=3)
    pygame.draw.rect(screen, C_STAMINA, (20, 40, int(200 * (witcher.stamina / witcher.max_stamina)), 10), border_radius=3)

    hint = font.render("[SPACE] Меч  |  [SHIFT] Кувырок  |  [Q/1] Игни  |  [E/2] Квен", True, (200, 200, 200))
    screen.blit(hint, (20, 60))

    pygame.display.flip()
    clock.tick(FPS)

pygame.quit()
sys.exit()