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


import pygame
from pygame.locals import *
from OpenGL.GL import *
from OpenGL.GLU import *
import math
import random
import sys

# --- ВЕКТОРНАЯ МАТЕМАТИКА ---
def v_len(v):
    return math.sqrt(v[0]*v[0] + v[1]*v[1] + v[2]*v[2])

def v_norm(v):
    l = v_len(v)
    return [0.0, 0.0, 0.0] if l < 1e-6 else [v[0]/l, v[1]/l, v[2]/l]

def v_dist(a, b):
    return math.sqrt((a[0]-b[0])**2 + (a[1]-b[1])**2 + (a[2]-b[2])**2)

# --- ГРАФИЧЕСКИЕ ПРИМИТИВЫ ---
def draw_box(size, color, offset=(0, 0, 0)):
    sx, sy, sz = size[0] / 2.0, size[1] / 2.0, size[2] / 2.0
    ox, oy, oz = offset[0], offset[1], offset[2]

    glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, [*color, 1.0])
    glBegin(GL_QUADS)
    glNormal3f(0, 0, 1)
    glVertex3f(ox-sx, oy-sy, oz+sz); glVertex3f(ox+sx, oy-sy, oz+sz)
    glVertex3f(ox+sx, oy+sy, oz+sz); glVertex3f(ox-sx, oy+sy, oz+sz)
    glNormal3f(0, 0, -1)
    glVertex3f(ox-sx, oy-sy, oz-sz); glVertex3f(ox-sx, oy+sy, oz-sz)
    glVertex3f(ox+sx, oy+sy, oz-sz); glVertex3f(ox+sx, oy-sy, oz-sz)
    glNormal3f(0, 1, 0)
    glVertex3f(ox-sx, oy+sy, oz-sz); glVertex3f(ox-sx, oy+sy, oz+sz)
    glVertex3f(ox+sx, oy+sy, oz+sz); glVertex3f(ox+sx, oy+sy, oz-sz)
    glNormal3f(0, -1, 0)
    glVertex3f(ox-sx, oy-sy, oz-sz); glVertex3f(ox+sx, oy-sy, oz-sz)
    glVertex3f(ox+sx, oy-sy, oz+sz); glVertex3f(ox-sx, oy-sy, oz+sz)
    glNormal3f(1, 0, 0)
    glVertex3f(ox+sx, oy-sy, oz-sz); glVertex3f(ox+sx, oy+sy, oz-sz)
    glVertex3f(ox+sx, oy+sy, oz+sz); glVertex3f(ox+sx, oy-sy, oz+sz)
    glNormal3f(-1, 0, 0)
    glVertex3f(ox-sx, oy-sy, oz-sz); glVertex3f(ox-sx, oy-sy, oz+sz)
    glVertex3f(ox-sx, oy+sy, oz+sz); glVertex3f(ox-sx, oy+sy, oz-sz)
    glEnd()

def draw_cylinder(radius, length, segments=12, color=(0.2, 0.2, 0.2)):
    glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, [*color, 1.0])
    glBegin(GL_QUAD_STRIP)
    for i in range(segments + 1):
        angle = 2.0 * math.pi * i / segments
        x = radius * math.cos(angle)
        y = radius * math.sin(angle)
        glNormal3f(math.cos(angle), math.sin(angle), 0.0)
        glVertex3f(x, y, 0.0)
        glVertex3f(x, y, length)
    glEnd()

# --- СИСТЕМА ЧАСТИЦ ---
class ParticleSystem:
    def __init__(self):
        self.particles = []

    def emit(self, pos, count=16, color=(1.0, 0.5, 0.1), speed=9.0, size=0.28):
        for _ in range(count):
            rx, ry, rz = random.uniform(-1, 1), random.uniform(0.5, 2.2), random.uniform(-1, 1)
            norm = v_norm([rx, ry, rz])
            sp = random.uniform(speed * 0.3, speed)
            self.particles.append({
                'x': pos[0], 'y': pos[1], 'z': pos[2],
                'vx': norm[0] * sp, 'vy': norm[1] * sp, 'vz': norm[2] * sp,
                'life': 1.0, 'decay': random.uniform(1.2, 2.5),
                'color': color, 'size': size
            })

    def update(self, dt):
        survivors = []
        for p in self.particles:
            p['x'] += p['vx'] * dt
            p['y'] += p['vy'] * dt
            p['z'] += p['vz'] * dt
            p['vy'] -= 9.8 * dt * 0.6
            p['life'] -= p['decay'] * dt
            if p['life'] > 0:
                survivors.append(p)
        self.particles = survivors

    def draw(self):
        glDisable(GL_LIGHTING)
        for p in self.particles:
            s = p['size'] * p['life']
            glPushMatrix()
            glTranslatef(p['x'], p['y'], p['z'])
            draw_box((s, s, s), p['color'])
            glPopMatrix()
        glEnable(GL_LIGHTING)

# --- АПТЕЧКИ ---
class DropItem:
    def __init__(self, x, z):
        self.pos = [float(x), 0.8, float(z)]
        self.rot = 0.0
        self.alive = True

    def update(self, dt):
        self.rot += 120.0 * dt

    def draw(self):
        if not self.alive:
            return
        glPushMatrix()
        glTranslatef(self.pos[0], self.pos[1], self.pos[2])
        glRotatef(self.rot, 0, 1, 0)
        draw_box((1.2, 1.2, 1.2), (0.1, 0.8, 0.2))
        draw_box((0.4, 1.25, 1.25), (1.0, 1.0, 1.0))
        draw_box((1.25, 1.25, 0.4), (1.0, 1.0, 1.0))
        glPopMatrix()

# --- СНАРЯД ---
class Shell:
    def __init__(self, pos, direction, is_player=True, dmg=1):
        self.pos = list(pos)
        self.dir = v_norm(direction)
        self.speed = 85.0
        self.is_player = is_player
        self.dmg = dmg
        self.active = True
        self.life = 3.0

    def update(self, dt):
        self.pos[0] += self.dir[0] * self.speed * dt
        self.pos[1] += self.dir[1] * self.speed * dt
        self.pos[2] += self.dir[2] * self.speed * dt
        self.life -= dt
        if self.life <= 0:
            self.active = False

    def draw(self):
        glPushMatrix()
        glTranslatef(self.pos[0], self.pos[1], self.pos[2])
        col = (0.2, 1.0, 0.9) if self.is_player else (1.0, 0.1, 0.05)
        draw_box((0.3, 0.3, 0.8), col)
        glPopMatrix()

# --- ДЕТАЛЬНЫЕ ОБЪЕКТЫ КАРТЫ ---
class ComplexObstacle:
    def __init__(self, x, z, obs_type):
        self.pos = [float(x), 0.0, float(z)]
        self.obs_type = obs_type
        self.hp = 5 if obs_type == "bunker" else (3 if obs_type == "tower" else 2)
        self.alive = True

        if obs_type == "bunker":
            self.radius = 4.5
        elif obs_type == "tower":
            self.radius = 2.5
        else: # hedgehog (еж)
            self.radius = 1.8

    def check_point_collision(self, px, pz, radius=1.8):
        if not self.alive:
            return False
        return v_dist([px, 0, pz], self.pos) < (self.radius + radius)

    def draw(self):
        if not self.alive:
            return

        glPushMatrix()
        glTranslatef(self.pos[0], 0.0, self.pos[2])

        if self.obs_type == "bunker":
            # Бетонный блокпост с амбразурами и мешками с песком
            draw_box((7.0, 3.5, 7.0), (0.42, 0.40, 0.38), offset=(0, 1.75, 0))
            draw_box((7.4, 0.6, 7.4), (0.35, 0.33, 0.30), offset=(0, 3.8, 0)) # Крыша
            draw_box((3.0, 0.5, 7.2), (0.1, 0.1, 0.1), offset=(0, 2.2, 0))   # Амбразура
            # Мешки с песком перед ним
            draw_box((7.2, 0.9, 1.2), (0.68, 0.58, 0.40), offset=(0, 0.45, 4.0))

        elif self.obs_type == "tower":
            # Сторожевая вышка на опорах
            for ox, oz in [(-1.5, -1.5), (1.5, -1.5), (-1.5, 1.5), (1.5, 1.5)]:
                draw_box((0.4, 6.0, 0.4), (0.28, 0.24, 0.20), offset=(ox, 3.0, oz))
            draw_box((3.6, 1.8, 3.6), (0.45, 0.38, 0.30), offset=(0, 6.9, 0))
            draw_box((4.0, 0.4, 4.0), (0.2, 0.2, 0.2), offset=(0, 7.9, 0))
            # Прожектор
            draw_box((0.6, 0.6, 0.8), (0.9, 0.9, 0.3), offset=(0, 6.2, 1.9))

        else: # hedgehog
            # Противотанковый стальной еж (3 перекрещенные балки)
            col = (0.25, 0.26, 0.28)
            draw_box((0.4, 0.4, 3.2), col, offset=(0, 1.1, 0))
            draw_box((3.2, 0.4, 0.4), col, offset=(0, 1.1, 0))
            draw_box((0.4, 2.6, 0.4), col, offset=(0, 1.1, 0))

        glPopMatrix()

# --- ДЕТАЛЬНЫЙ ТАНК (ИГРОК / ВРАГ) ---
class DetailedTank:
    def __init__(self, x, z, is_player=False, max_hp=5, speed=10.0, enemy_class="assault"):
        self.pos = [float(x), 0.0, float(z)]
        self.rotation_y = 0.0
        self.turret_angle = 0.0
        self.recoil = 0.0
        self.is_player = is_player
        self.enemy_class = enemy_class

        self.max_hp = max_hp
        self.hp = max_hp
        self.speed = speed
        self.turn_speed = 95.0
        self.shoot_cd = 0.0
        self.max_shoot_cd = 0.45 if is_player else 2.5
        self.alive = True

        # Цветовые палитры
        if is_player:
            self.c_hull = (0.16, 0.36, 0.18)      # Военно-зеленый оливковый
            self.c_detail = (0.24, 0.48, 0.26)    # Светлые грани
            self.c_metal = (0.15, 0.15, 0.16)     # Вороненый металл траков
            self.c_decor = (0.8, 0.7, 0.2)        # Золотистый герб/полосы
        else:
            # Вражеский стиль (багровый / ярко-красный / угольный)
            self.c_hull = (0.75, 0.12, 0.12)      # Насыщенный красный
            self.c_detail = (0.95, 0.22, 0.15)    # Предупреждающий алый
            self.c_metal = (0.18, 0.12, 0.12)     # Черно-красная резина
            self.c_decor = (1.0, 0.0, 0.0)        # Агрессивные красные диоды

    def get_forward_vector(self):
        rad = math.radians(self.rotation_y)
        return [math.sin(rad), 0.0, math.cos(rad)]

    def get_turret_forward(self):
        rad = math.radians(self.rotation_y + self.turret_angle)
        return [math.sin(rad), 0.0, math.cos(rad)]

    def update(self, dt):
        if self.shoot_cd > 0:
            self.shoot_cd -= dt
        if self.recoil > 0:
            self.recoil = max(0.0, self.recoil - dt * 4.0)

    def draw(self):
        if not self.alive:
            return

        glPushMatrix()
        glTranslatef(self.pos[0], self.pos[1], self.pos[2])
        glRotatef(self.rotation_y, 0, 1, 0)

        # 1. ШАССИ И ГУСЕНИЦЫ С ФАЛЬШБОРТАМИ
        draw_box((0.85, 0.75, 4.8), self.c_metal, offset=(-1.45, 0.38, 0.0))
        draw_box((0.85, 0.75, 4.8), self.c_metal, offset=(1.45, 0.38, 0.0))

        # Навесные бронещитки над гусеницами
        draw_box((0.9, 0.2, 5.0), self.c_detail, offset=(-1.45, 0.8, 0.0))
        draw_box((0.9, 0.2, 5.0), self.c_detail, offset=(1.45, 0.8, 0.0))

        # Катки
        for side in (-1.45, 1.45):
            for z_off in (-1.8, -0.9, 0.0, 0.9, 1.8):
                draw_box((0.88, 0.5, 0.5), (0.28, 0.28, 0.28), offset=(side, 0.3, z_off))

        # 2. МАССИВНЫЙ КОРПУС С НАКЛОННОЙ БРОНЕЙ
        draw_box((2.5, 0.7, 4.3), self.c_hull, offset=(0.0, 0.65, 0.0))
        draw_box((2.2, 0.4, 3.8), self.c_detail, offset=(0.0, 1.0, -0.1))
        # Наклонный лобовой бронелист
        draw_box((2.1, 0.3, 1.2), self.c_hull, offset=(0.0, 0.75, 1.8))
        # Топливные баки на корме
        draw_box((0.7, 0.4, 0.5), self.c_metal, offset=(-0.65, 0.85, -2.1))
        draw_box((0.7, 0.4, 0.5), self.c_metal, offset=(0.65, 0.85, -2.1))

        # 3. БАШНЯ
        glPushMatrix()
        glTranslatef(0.0, 1.3, 0.0)
        glRotatef(self.turret_angle, 0, 1, 0)

        # Купол башни с усеченными углами
        draw_box((2.1, 0.75, 2.6), self.c_hull, offset=(0.0, 0.0, -0.2))
        draw_box((1.7, 0.25, 2.2), self.c_detail, offset=(0.0, 0.42, -0.2))

        # Маска орудия
        draw_box((1.0, 0.5, 0.8), self.c_metal, offset=(0.0, 0.0, 1.1))

        # Командирская башенка и спаренный пулемет
        draw_box((0.6, 0.35, 0.6), self.c_metal, offset=(0.55, 0.5, -0.4))
        draw_box((0.1, 0.1, 0.9), (0.05, 0.05, 0.05), offset=(-0.45, 0.45, 0.8))

        # Орудие с массивным дульным тормозом
        glPushMatrix()
        glTranslatef(0.0, 0.0, 1.4 - self.recoil)
        draw_cylinder(0.15, 3.2, segments=14, color=(0.12, 0.12, 0.12))
        draw_box((0.45, 0.45, 0.65), self.c_metal, offset=(0.0, 0.0, 3.2))
        glPopMatrix()

        # Антенна
        draw_cylinder(0.03, 1.8, segments=6, color=self.c_decor)

        glPopMatrix()
        glPopMatrix()

        if not self.is_player and self.alive:
            self.draw_enemy_hp()

    def draw_enemy_hp(self):
        glDisable(GL_LIGHTING)
        glPushMatrix()
        glTranslatef(self.pos[0], 3.6, self.pos[2])
        mat = glGetFloatv(GL_MODELVIEW_MATRIX)
        for i in range(3):
            for j in range(3):
                mat[i][j] = 1.0 if i == j else 0.0
        glLoadMatrixf(mat)

        pct = max(0.0, self.hp / self.max_hp)
        draw_box((2.2, 0.22, 0.02), (0.1, 0.1, 0.1))
        # Яркая шкала над врагом
        col = (1.0, 0.2, 0.2) if pct < 0.4 else (1.0, 0.6, 0.1)
        draw_box((2.16 * pct, 0.18, 0.04), col, offset=(-1.08 * (1.0 - pct), 0.0, 0.01))
        glPopMatrix()
        glEnable(GL_LIGHTING)

# --- ИГРОВОЙ ДВИЖОК ---
class TankWarGame:
    def __init__(self):
        pygame.init()
        self.res = (1280, 720)
        pygame.display.set_mode(self.res, DOUBLEBUF | OPENGL)
        pygame.display.set_caption("Iron Vanguard: Battle City 3D")
        pygame.mouse.set_visible(False)
        pygame.event.set_grab(True)

        self.clock = pygame.time.Clock()
        self.particles = ParticleSystem()
        self.map_size = 280.0
        self.score = 0
        self.wave = 1
        self.game_over = False

        self.init_gl()
        self.init_world()

    def init_gl(self):
        glEnable(GL_DEPTH_TEST)
        glEnable(GL_LIGHTING)
        glEnable(GL_LIGHT0)
        glEnable(GL_COLOR_MATERIAL)
        glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE)

        # Контрастное солнце для рельефности деталей
        glLightfv(GL_LIGHT0, GL_POSITION, [100.0, 150.0, 100.0, 1.0])
        glLightfv(GL_LIGHT0, GL_DIFFUSE, [1.0, 0.98, 0.94, 1.0])
        glLightfv(GL_LIGHT0, GL_AMBIENT, [0.38, 0.38, 0.42, 1.0])

        glClearColor(0.42, 0.64, 0.84, 1.0) # Атмосферное небо

        glMatrixMode(GL_PROJECTION)
        glLoadIdentity()
        gluPerspective(60, (self.res[0] / self.res[1]), 0.5, 450.0)
        glMatrixMode(GL_MODELVIEW)

    def init_world(self):
        self.player = DetailedTank(0, -50, is_player=True, max_hp=15, speed=15.0)
        self.enemies = []
        self.bullets = []
        self.obstacles = []
        self.drops = []

        # Генерация объектов карты
        random.seed(2026)
        types = ["bunker", "tower", "hedgehog"]
        for _ in range(55):
            ox = random.uniform(-self.map_size / 2 + 25, self.map_size / 2 - 25)
            oz = random.uniform(-self.map_size / 2 + 25, self.map_size / 2 - 25)
            if abs(ox) > 16 or abs(oz) > 16:
                chosen = random.choices(types, weights=[40, 30, 30])[0]
                self.obstacles.append(ComplexObstacle(ox, oz, chosen))

        self.spawn_wave()
        self.cam_yaw = 0.0
        self.cam_pitch = 22.0
        self.cam_dist = 17.0

    def spawn_wave(self):
        count = 4 + self.wave * 2
        for _ in range(count):
            ex = random.uniform(-self.map_size / 2 + 30, self.map_size / 2 - 30)
            ez = random.uniform(10, self.map_size / 2 - 30)

            t_roll = random.random()
            if t_roll < 0.35:
                e = DetailedTank(ex, ez, is_player=False, max_hp=2, speed=12.0, enemy_class="scout")
                e.max_shoot_cd = 1.8
            elif t_roll < 0.8:
                e = DetailedTank(ex, ez, is_player=False, max_hp=4, speed=8.0, enemy_class="assault")
                e.max_shoot_cd = 2.4
            else:
                e = DetailedTank(ex, ez, is_player=False, max_hp=8, speed=5.5, enemy_class="heavy")
                e.max_shoot_cd = 3.2
            self.enemies.append(e)

    def handle_input(self, dt):
        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit()
            if event.type == KEYDOWN:
                if event.key == K_ESCAPE:
                    pygame.quit()
                    sys.exit()
                if event.key == K_r and self.game_over:
                    self.score = 0
                    self.wave = 1
                    self.game_over = False
                    self.init_world()
                    return

            if event.type == MOUSEMOTION:
                dx, dy = event.rel
                self.cam_yaw -= dx * 0.22
                self.cam_pitch = max(8.0, min(55.0, self.cam_pitch + dy * 0.16))

            if event.type == MOUSEBUTTONDOWN and event.button == 1:
                if self.player.alive and self.player.shoot_cd <= 0:
                    self.fire_tank(self.player)

        if not self.player.alive:
            return

        keys = pygame.key.get_pressed()
        if keys[K_a]:
            self.player.rotation_y += self.player.turn_speed * dt
        if keys[K_d]:
            self.player.rotation_y -= self.player.turn_speed * dt

        move_dir = 0.0
        if keys[K_w]:
            move_dir += 1.0
        if keys[K_s]:
            move_dir -= 0.65

        if move_dir != 0:
            fwd = self.player.get_forward_vector()
            step_x = fwd[0] * (move_dir * self.player.speed * dt)
            step_z = fwd[2] * (move_dir * self.player.speed * dt)
            new_x = self.player.pos[0] + step_x
            new_z = self.player.pos[2] + step_z

            limit = self.map_size / 2.0 - 5.0
            if abs(new_x) < limit and abs(new_z) < limit:
                blocked = False
                for obs in self.obstacles:
                    if obs.check_point_collision(new_x, new_z, radius=2.2):
                        blocked = True
                        break
                if not blocked:
                    for e in self.enemies:
                        if e.alive and v_dist([new_x, 0, new_z], e.pos) < 3.8:
                            blocked = True
                            break
                if not blocked:
                    self.player.pos[0] = new_x
                    self.player.pos[2] = new_z

        target_turret = (self.cam_yaw - self.player.rotation_y) % 360
        if target_turret > 180:
            target_turret -= 360
        self.player.turret_angle = target_turret

    def fire_tank(self, tank):
        tank.shoot_cd = tank.max_shoot_cd
        tank.recoil = 0.5
        fwd = tank.get_turret_forward()
        spawn_pos = [
            tank.pos[0] + fwd[0] * 4.2,
            tank.pos[1] + 1.3,
            tank.pos[2] + fwd[2] * 4.2
        ]
        dmg = 2 if tank.enemy_class == "heavy" else 1
        self.bullets.append(Shell(spawn_pos, fwd, is_player=tank.is_player, dmg=dmg))
        self.particles.emit(spawn_pos, count=16, color=(1.0, 0.75, 0.2), speed=7.0, size=0.25)

    def update_ai(self, dt):
        for e in self.enemies:
            if not e.alive:
                continue

            dist_to_p = v_dist(e.pos, self.player.pos)
            to_p = [self.player.pos[0] - e.pos[0], 0, self.player.pos[2] - e.pos[2]]

            target_yaw = math.degrees(math.atan2(to_p[0], to_p[2]))
            rel_yaw = (target_yaw - e.rotation_y) % 360
            if rel_yaw > 180:
                rel_yaw -= 360
            e.turret_angle = rel_yaw

            sign = 1.0 if rel_yaw > 0 else (-1.0 if rel_yaw < 0 else 0.0)
            e.rotation_y += sign * e.turn_speed * 0.45 * dt

            if dist_to_p > 22.0:
                fwd = e.get_forward_vector()
                nx = e.pos[0] + fwd[0] * e.speed * dt
                nz = e.pos[2] + fwd[2] * e.speed * dt

                hit_wall = any(obs.check_point_collision(nx, nz, radius=2.2) for obs in self.obstacles)
                if not hit_wall:
                    e.pos[0] = nx
                    e.pos[2] = nz
                else:
                    e.rotation_y += 60.0 * dt

            if dist_to_p < 80.0 and e.shoot_cd <= 0 and self.player.alive:
                self.fire_tank(e)

            e.update(dt)

    def update_physics(self, dt):
        self.player.update(dt)
        self.update_ai(dt)
        self.particles.update(dt)

        for d in self.drops:
            d.update(dt)
            if d.alive and v_dist(self.player.pos, d.pos) < 3.2:
                d.alive = False
                self.player.hp = min(self.player.max_hp, self.player.hp + 5)
                self.particles.emit(d.pos, count=25, color=(0.2, 1.0, 0.4), speed=8.0)

        alive_shells = []
        for b in self.bullets:
            b.update(dt)
            if not b.active:
                continue

            hit = False
            for obs in self.obstacles:
                if obs.check_point_collision(b.pos[0], b.pos[2], radius=0.6):
                    obs.hp -= b.dmg
                    self.particles.emit(b.pos, count=12, color=(0.6, 0.5, 0.4), speed=6.0)
                    if obs.hp <= 0:
                        obs.alive = False
                        self.particles.emit(obs.pos, count=50, color=(0.4, 0.4, 0.4), speed=12.0, size=0.6)
                        if random.random() < 0.35:
                            self.drops.append(DropItem(obs.pos[0], obs.pos[2]))
                    hit = True
                    break
            if hit:
                continue

            if b.is_player:
                for e in self.enemies:
                    if e.alive and v_dist(b.pos, [e.pos[0], e.pos[1] + 1.1, e.pos[2]]) < 2.5:
                        e.hp -= b.dmg
                        self.particles.emit(b.pos, count=25, color=(1.0, 0.1, 0.1), speed=12.0)
                        if e.hp <= 0:
                            e.alive = False
                            self.score += 150
                            self.particles.emit([e.pos[0], 1.2, e.pos[2]], count=70, color=(1.0, 0.2, 0.1), speed=16.0, size=0.8)
                            if random.random() < 0.5:
                                self.drops.append(DropItem(e.pos[0], e.pos[2]))
                        hit = True
                        break
            else:
                if self.player.alive and v_dist(b.pos, [self.player.pos[0], 1.1, self.player.pos[2]]) < 2.5:
                    self.player.hp -= b.dmg
                    self.particles.emit(b.pos, count=30, color=(0.2, 0.8, 0.3), speed=14.0)
                    if self.player.hp <= 0:
                        self.player.alive = False
                        self.game_over = True
                        self.particles.emit(self.player.pos, count=90, color=(1.0, 0.3, 0.1), speed=18.0, size=0.9)
                    hit = True

            if not hit:
                alive_shells.append(b)

        self.bullets = alive_shells

        if self.player.alive and len(self.enemies) > 0 and all(not e.alive for e in self.enemies):
            self.wave += 1
            self.score += 500
            self.spawn_wave()

    def render_ground(self):
        glDisable(GL_LIGHTING)
        step = 14
        half = int(self.map_size / 2)
        glBegin(GL_QUADS)
        for x in range(-half, half, step):
            for z in range(-half, half, step):
                # Песчано-грунтовая поверхность поля боя
                if (x // step + z // step) % 2 == 0:
                    glColor3f(0.52, 0.48, 0.38)
                else:
                    glColor3f(0.56, 0.52, 0.42)
                glVertex3f(x, 0.0, z)
                glVertex3f(x + step, 0.0, z)
                glVertex3f(x + step, 0.0, z + step)
                glVertex3f(x, 0.0, z + step)
        glEnd()
        glEnable(GL_LIGHTING)

    def draw_hud(self):
        glMatrixMode(GL_PROJECTION)
        glPushMatrix()
        glLoadIdentity()
        glOrtho(0, self.res[0], self.res[1], 0, -1, 1)
        glMatrixMode(GL_MODELVIEW)
        glPushMatrix()
        glLoadIdentity()
        glDisable(GL_LIGHTING)
        glDisable(GL_DEPTH_TEST)
        glEnable(GL_BLEND)
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)

        # 1. ПРИЦЕЛ
        cx, cy = self.res[0] // 2, self.res[1] // 2
        r = 18
        cd_ratio = max(0.0, self.player.shoot_cd / self.player.max_shoot_cd)
        cross_col = (1.0, 0.2, 0.2) if cd_ratio > 0 else (0.2, 1.0, 0.4)
        glColor3f(*cross_col)
        glLineWidth(2.0)
        glBegin(GL_LINES)
        glVertex2f(cx - r, cy); glVertex2f(cx - 6, cy)
        glVertex2f(cx + 6, cy); glVertex2f(cx + r, cy)
        glVertex2f(cx, cy - r); glVertex2f(cx, cy - 6)
        glVertex2f(cx, cy + 6); glVertex2f(cx, cy + r)
        glEnd()

        # 2. НИЖНЯЯ ПАНЕЛЬ HP
        panel_w, panel_h = 420, 55
        px = self.res[0] // 2 - panel_w // 2
        py = self.res[1] - 75

        glColor4f(0.08, 0.08, 0.1, 0.85)
        glBegin(GL_QUADS)
        glVertex2f(px, py); glVertex2f(px + panel_w, py)
        glVertex2f(px + panel_w, py + panel_h); glVertex2f(px, py + panel_h)
        glEnd()

        glColor3f(0.3, 0.35, 0.4)
        glBegin(GL_LINE_LOOP)
        glVertex2f(px, py); glVertex2f(px + panel_w, py)
        glVertex2f(px + panel_w, py + panel_h); glVertex2f(px, py + panel_h)
        glEnd()

        bar_x, bar_y = px + 15, py + 18
        max_bw = panel_w - 30
        hp_pct = max(0.0, self.player.hp / self.player.max_hp)
        cur_bw = max_bw * hp_pct

        hp_color = (0.2, 0.9, 0.3) if hp_pct > 0.5 else ((0.95, 0.6, 0.1) if hp_pct > 0.25 else (0.9, 0.15, 0.15))
        glColor3f(*hp_color)
        glBegin(GL_QUADS)
        glVertex2f(bar_x, bar_y); glVertex2f(bar_x + cur_bw, bar_y)
        glVertex2f(bar_x + cur_bw, bar_y + 18); glVertex2f(bar_x, bar_y + 18)
        glEnd()

        # 3. МИНИ-КАРТА (РАДАР)
        radar_size = 140
        rx = self.res[0] - radar_size - 25
        ry = 25
        glColor4f(0.05, 0.08, 0.05, 0.85)
        glBegin(GL_QUADS)
        glVertex2f(rx, ry); glVertex2f(rx + radar_size, ry)
        glVertex2f(rx + radar_size, ry + radar_size); glVertex2f(rx, ry + radar_size)
        glEnd()

        glColor3f(0.2, 0.6, 0.3)
        glBegin(GL_LINE_LOOP)
        glVertex2f(rx, ry); glVertex2f(rx + radar_size, ry)
        glVertex2f(rx + radar_size, ry + radar_size); glVertex2f(rx, ry + radar_size)
        glEnd()

        def world_to_radar(wx, wz):
            nx = (wx / (self.map_size / 2.0)) * (radar_size / 2.0)
            nz = (wz / (self.map_size / 2.0)) * (radar_size / 2.0)
            return rx + radar_size / 2.0 + nx, ry + radar_size / 2.0 + nz

        glPointSize(4.0)
        glBegin(GL_POINTS)
        # Препятствия
        glColor3f(0.5, 0.5, 0.5)
        for obs in self.obstacles:
            if obs.alive:
                mx, my = world_to_radar(obs.pos[0], obs.pos[2])
                glVertex2f(mx, my)

        # Враги (ярко-красные точки)
        glColor3f(1.0, 0.1, 0.1)
        for e in self.enemies:
            if e.alive:
                mx, my = world_to_radar(e.pos[0], e.pos[2])
                glVertex2f(mx, my)

        # Игрок (зеленая точка)
        glColor3f(0.2, 1.0, 0.3)
        px_r, py_r = world_to_radar(self.player.pos[0], self.player.pos[2])
        glVertex2f(px_r, py_r)
        glEnd()

        # ТЕКСТ
        font = pygame.font.SysFont("impact", 24)
        hud_text = f"WAVE: {self.wave}   SCORE: {self.score}   HOSTILES: {sum(1 for e in self.enemies if e.alive)}"
        surf = font.render(hud_text, True, (240, 240, 240))
        data = pygame.image.tostring(surf, "RGBA", True)
        glRasterPos2i(30, 45)
        glDrawPixels(surf.get_width(), surf.get_height(), GL_RGBA, GL_UNSIGNED_BYTE, data)

        hp_text = f"HULL INTEGRITY: {self.player.hp} / {self.player.max_hp}"
        hp_surf = pygame.font.SysFont("consolas", 13, bold=True).render(hp_text, True, (255, 255, 255))
        hp_data = pygame.image.tostring(hp_surf, "RGBA", True)
        glRasterPos2i(bar_x + 8, py + 14)
        glDrawPixels(hp_surf.get_width(), hp_surf.get_height(), GL_RGBA, GL_UNSIGNED_BYTE, hp_data)

        if self.game_over:
            m_font = pygame.font.SysFont("impact", 54)
            m_surf = m_font.render("VEHICLE DESTROYED!", True, (255, 40, 40))
            m_data = pygame.image.tostring(m_surf, "RGBA", True)
            glRasterPos2i(self.res[0] // 2 - m_surf.get_width() // 2, self.res[1] // 2 - 40)
            glDrawPixels(m_surf.get_width(), m_surf.get_height(), GL_RGBA, GL_UNSIGNED_BYTE, m_data)

            sub_font = pygame.font.SysFont("consolas", 20, bold=True)
            s_surf = sub_font.render("Press R to Deploy New Tank | ESC to Exit", True, (255, 255, 255))
            s_data = pygame.image.tostring(s_surf, "RGBA", True)
            glRasterPos2i(self.res[0] // 2 - s_surf.get_width() // 2, self.res[1] // 2 + 20)
            glDrawPixels(s_surf.get_width(), s_surf.get_height(), GL_RGBA, GL_UNSIGNED_BYTE, s_data)

        glDisable(GL_BLEND)
        glEnable(GL_DEPTH_TEST)
        glEnable(GL_LIGHTING)
        glPopMatrix()
        glMatrixMode(GL_PROJECTION)
        glPopMatrix()
        glMatrixMode(GL_MODELVIEW)

    def run(self):
        while True:
            dt = self.clock.tick(60) / 1000.0
            self.handle_input(dt)
            if not self.game_over:
                self.update_physics(dt)

            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
            glLoadIdentity()

            rad_yaw = math.radians(self.cam_yaw)
            rad_pitch = math.radians(self.cam_pitch)

            cam_x = self.player.pos[0] - self.cam_dist * math.sin(rad_yaw) * math.cos(rad_pitch)
            cam_y = self.player.pos[1] + self.cam_dist * math.sin(rad_pitch) + 2.4
            cam_z = self.player.pos[2] - self.cam_dist * math.cos(rad_yaw) * math.cos(rad_pitch)

            target_y = self.player.pos[1] + 1.2
            gluLookAt(cam_x, cam_y, cam_z, self.player.pos[0], target_y, self.player.pos[2], 0, 1, 0)

            self.render_ground()
            for obs in self.obstacles:
                obs.draw()
            for d in self.drops:
                d.draw()
            self.player.draw()
            for e in self.enemies:
                e.draw()
            for b in self.bullets:
                b.draw()
            self.particles.draw()

            self.draw_hud()
            pygame.display.flip()

if __name__ == "__main__":
    game = TankWarGame()
    game.run()