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


from ursina import *
import math
import random

app = Ursina()

# Настройка окна и фона (делаем темно-песочным, чтобы не было ослепляющего белого)
window.title = "Savannah Rider: Wild Hunt 3D"
window.borderless = False
window.fullscreen = False
window.color = color.rgb(80, 50, 20)
window.fps_counter.enabled = True
mouse.locked = True

# --- ОСВЕЩЕНИЕ (Исправленное, без пересвета) ---
# Используем мягкий рассеянный свет вместо слепящих направленных источников
ambient_light = AmbientLight(color=color.rgb(200, 180, 140))
sun = DirectionalLight()
sun.color = color.rgb(255, 240, 200)
sun.rotation = (45, -45, 0)

# --- ЗЕМЛЯ САВАННЫ ---
ground = Entity(
    model='plane',
    scale=(400, 1, 400),
    color=color.rgb(150, 120, 60),
    texture='white_cube',
    texture_scale=(100, 100),
    collider='box'
)

# Декорации саванны: деревья и камни
for _ in range(60):
    x = random.uniform(-180, 180)
    z = random.uniform(-180, 180)
    # Ствол дерева
    Entity(model='cylinder', color=color.rgb(90, 60, 30), scale=(1, 8, 1), position=(x, 4, z), collider='box')
    # Крона дерева
    Entity(model='sphere', color=color.rgb(50, 110, 30), scale=(6, 3, 6), position=(x, 8, z))

for _ in range(40):
    Entity(
        model='cube',
        color=color.rgb(120, 110, 100),
        scale=(random.uniform(2, 6), random.uniform(1, 3), random.uniform(2, 6)),
        position=(random.uniform(-180, 180), 0.5, random.uniform(-180, 180)),
        collider='box'
    )

# --- ЕЗДОВОЙ ЗВЕРЬ (НОСОРОГ) И ИГРОК ---
mount = Entity(position=(0, 0, 0))

# Тело носорога
body = Entity(model='cube', color=color.rgb(110, 105, 95), scale=(2.2, 2.0, 4.5), position=(0, 1.2, 0), parent=mount, collider='box')
head = Entity(model='cube', color=color.rgb(95, 90, 80), scale=(1.3, 1.3, 1.8), position=(0, 1.7, 2.2), parent=mount)
horn = Entity(model='cone', color=color.rgb(230, 230, 220), scale=(0.4, 2.0, 0.4), position=(0, 2.4, 2.8), rotation=(20, 0, 0), parent=mount)

# Ноги носорога
for px, pz in [(-0.9, 1.5), (0.9, 1.5), (-0.9, -1.5), (0.9, -1.5)]:
    Entity(model='cylinder', color=color.rgb(80, 75, 65), scale=(0.6, 1.5, 0.6), position=(px, 0.75, pz), parent=mount)

# Всадник (игрок) на спине
rider = Entity(model='cube', color=color.rgb(190, 130, 80), scale=(0.8, 1.4, 0.8), position=(0, 2.5, -0.2), parent=mount)
rider_head = Entity(model='sphere', color=color.rgb(220, 170, 130), scale=0.6, position=(0, 1.0, 0), parent=rider)

# Камера от третьего лица (стабильная подвеска позади зверя)
camera.parent = mount
camera.position = (0, 5, -10)
camera.rotation = (20, 0, 0)

# --- ИНВЕНТАРЬ И ОРУЖИЕ ---
weapons = ['Копье', 'Лук', 'Топор', 'Арбалет', 'Огненный Шар']
current_weapon_idx = 0

# Текстовый интерфейс на экране
weapon_display = Text(text=f'Оружие: {weapons[current_weapon_idx]}', position=(-0.85, 0.44), scale=1.4, color=color.rgb(255, 230, 150))
hp_display = Text(text='Здоровье зверя: 100%', position=(-0.85, 0.38), scale=1.2, color=color.green)
Text(
    text="Управление:\nWASD - Движение\nМышь - Обзор\nКолесико мыши - Смена оружия\nЛКМ - Атака / Выстрел\nESC - Курсор",
    position=(-0.85, 0.30), scale=1.0, color=color.rgb(220, 220, 220)
)

# Модель оружия в руке
weapon_model = Entity(model='cube', color=color.rgb(139, 90, 43), scale=(0.15, 0.15, 2.6), position=(0.8, 0.8, 0.5), parent=mount)

def update_weapon_visual():
    w = weapons[current_weapon_idx]
    weapon_display.text = f'Оружие: {w}'
    if w == 'Копье':
        weapon_model.model = 'cube'
        weapon_model.scale = (0.15, 0.15, 2.8)
        weapon_model.color = color.rgb(139, 90, 43)
    elif w == 'Лук':
        weapon_model.model = 'cube'
        weapon_model.scale = (0.2, 1.5, 0.2)
        weapon_model.color = color.rgb(100, 60, 20)
    elif w == 'Топор':
        weapon_model.model = 'cube'
        weapon_model.scale = (0.4, 0.9, 0.25)
        weapon_model.color = color.rgb(180, 180, 180)
    elif w == 'Арбалет':
        weapon_model.model = 'cube'
        weapon_model.scale = (0.7, 0.3, 1.3)
        weapon_model.color = color.rgb(60, 60, 60)
    elif w == 'Огненный Шар':
        weapon_model.model = 'sphere'
        weapon_model.scale = (0.6, 0.6, 0.6)
        weapon_model.color = color.rgb(255, 120, 0)

# --- СНАРЯДЫ И МАГИЯ ---
projectiles = []

class Projectile(Entity):
    def __init__(self, position, rotation, p_type):
        super().__init__(
            model='sphere' if p_type == 'Огненный Шар' else 'cube',
            color=color.rgb(255, 120, 0) if p_type == 'Огненный Шар' else color.rgb(70, 40, 10),
            scale=0.7 if p_type == 'Огненный Шар' else (0.2, 0.2, 1.2),
            position=position,
            rotation=rotation
        )
        self.p_type = p_type
        self.speed = 40 if p_type == 'Огненный Шар' else 50
        projectiles.append(self)

    def update(self):
        self.position += self.forward * self.speed * time.dt
        
        # Эффект искр для огненного шара
        if self.p_type == 'Огненный Шар' and random.random() < 0.6:
            Particle(position=self.position, color=color.rgb(255, 200, 50), scale=0.4)

        if distance(self.position, mount.position) > 150:
            projectiles.remove(self)
            destroy(self)
            return

        # Попадания по врагам
        for enemy in enemies:
            if distance(self, enemy) < 2.5:
                enemy.hp -= 70 if self.p_type == 'Огненный Шар' else 40
                enemy.blink(color.red, duration=0.15)
                projectiles.remove(self)
                destroy(self)
                if enemy.hp <= 0:
                    enemies.remove(enemy)
                    destroy(enemy)
                break

class Particle(Entity):
    def __init__(self, position, color, scale):
        super().__init__(model='sphere', color=color, scale=scale, position=position)
        self.animate_scale(0, duration=0.3, curve=curve.out_expo)
        invoke(destroy, self, delay=0.3)

# --- ВРАГИ (ХИЩНИКИ САВАННЫ) ---
enemies = []

class Predator(Entity):
    def __init__(self, position):
        super().__init__(
            model='cube',
            color=color.rgb(200, 120, 40),
            scale=(1.6, 1.2, 2.8),
            position=position,
            collider='box'
        )
        self.hp = 80
        self.speed = random.uniform(4, 6)
        enemies.append(self)

    def update(self):
        if distance(self, mount) > 2.5:
            self.look_at(mount)
            self.position += self.forward * self.speed * time.dt
            # Анимация бега зверя
            self.y = 0.6 + math.sin(time.time() * 22 + self.x) * 0.2

for _ in range(15):
    Predator(position=(random.uniform(-150, 150), 0.6, random.uniform(-150, 150)))

# --- УПРАВЛЕНИЕ ---
def input(key):
    global current_weapon_idx
    if key == 'scroll up':
        current_weapon_idx = (current_weapon_idx + 1) % len(weapons)
        update_weapon_visual()
    elif key == 'scroll down':
        current_weapon_idx = (current_weapon_idx - 1) % len(weapons)
        update_weapon_visual()
    
    elif key == 'left mouse down':
        w = weapons[current_weapon_idx]
        # Анимация атаки оружием
        weapon_model.animate_rotation_y(weapon_model.rotation_y + 50, duration=0.08)
        weapon_model.animate_rotation_y(weapon_model.rotation_y, duration=0.08, delay=0.08)

        if w in ['Лук', 'Арбалет', 'Огненный Шар']:
            Projectile(position=weapon_model.world_position, rotation=mount.world_rotation, p_type=w)
        else:
            # Ближний бой
            for enemy in enemies:
                if distance(mount, enemy) < 5.0:
                    enemy.hp -= 50
                    enemy.blink(color.red, duration=0.15)
                    if enemy.hp <= 0:
                        enemies.remove(enemy)
                        destroy(enemy)
                    break

    elif key == 'escape':
        mouse.locked = not mouse.locked

# --- ИГРОВОЙ ЦИКЛ ---
def update():
    move_speed = 14
    mount.rotation_y += mouse.velocity[0] * 45

    move_dir = Vec3(0, 0, 0)
    is_moving = False

    if held_keys['w']:
        move_dir += mount.forward
        is_moving = True
    if held_keys['s']:
        move_dir -= mount.forward
        is_moving = True
    if held_keys['a']:
        move_dir -= mount.right
        is_moving = True
    if held_keys['d']:
        move_dir += mount.right
        is_moving = True

    if is_moving:
        move_dir = move_dir.normalized()
        mount.position += move_dir * move_speed * time.dt
        # Покачивание при ходьбе на носороге
        mount.y = math.sin(time.time() * 20) * 0.25
        body.rotation_z = math.sin(time.time() * 10) * 4
    else:
        mount.y = 0
        body.rotation_z = 0

    # Границы мира
    mount.x = clamp(mount.x, -180, 180)
    mount.z = clamp(mount.z, -180, 180)

app.run()