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


from ursina import *
import math

app = Ursina(title="The Witcher 3D", borderless=False)
window.color = color.rgb(135, 175, 210) # Чистое небо вместо серого тумана

# Освещение
DirectionalLight(y=15, z=-10, shadows=True, rotation=(45, -45, 0))
AmbientLight(color=color.rgba(160, 160, 170, 200))

# Земля и трава
ground = Entity(
    model='plane',
    scale=(120, 1, 120),
    color=color.rgb(65, 95, 55),
    texture='white_cube',
    texture_scale=(60, 60),
    collider='box'
)

# Деревня: Изба и забор
house = Entity(model='cube', color=color.rgb(90, 60, 40), scale=(8, 5, 8), position=(10, 2.5, 12), collider='box')
roof = Entity(model='cone', color=color.rgb(120, 90, 50), scale=(10, 3.5, 10), position=(10, 6, 12))

# 3D Ведьмак
player = Entity(position=(0, 1, 0))
# Корпус
p_body = Entity(parent=player, model='cube', scale=(0.8, 1.4, 0.4), color=color.rgb(40, 35, 30))
# Голова и волосы
p_head = Entity(parent=player, model='sphere', scale=0.5, color=color.rgb(220, 190, 160), position=(0, 0.95, 0))
p_hair = Entity(parent=player, model='sphere', scale=(0.54, 0.3, 0.54), color=color.rgb(240, 240, 245), position=(0, 1.1, -0.05))
# Мечи за спиной
Entity(parent=p_body, model='cube', scale=(0.06, 1.6, 0.06), color=color.gray, position=(-0.2, 0.1, -0.25), rotation=(0, 0, -25))
Entity(parent=p_body, model='cube', scale=(0.06, 1.6, 0.06), color=color.cyan, position=(0.2, 0.1, -0.25), rotation=(0, 0, 25))

# Ручной меч для удара
sword_mesh = Entity(parent=player, model='cube', scale=(0.06, 1.4, 0.06), color=color.white, position=(0.6, 0.2, 0.5), rotation=(80, 0, 0), enabled=False)

# Щит Квен
quen_shield = Entity(parent=player, model='sphere', scale=2.5, color=color.rgba(255, 215, 0, 80), enabled=False)

# Характеристики
player_stats = {
    "hp": 200,
    "max_hp": 200,
    "stamina": 100,
    "speed": 7,
    "attacking": False,
    "quen": False
}

# Враг: Утопец
drowner = Entity(model='cube', scale=(1, 1.6, 0.8), color=color.rgb(40, 120, 130), position=(15, 1, 20), collider='box')
Entity(parent=drowner, model='sphere', scale=0.15, color=color.red, position=(-0.25, 0.4, 0.45))
Entity(parent=drowner, model='sphere', scale=0.15, color=color.red, position=(0.25, 0.4, 0.45))
drowner_hp = 80

# NPC: Староста
elder = Entity(model='cube', scale=(0.8, 1.4, 0.4), color=color.rgb(160, 140, 110), position=(8, 1, 8), collider='box')
Entity(parent=elder, model='sphere', scale=0.5, color=color.rgb(210, 180, 150), position=(0, 0.9, 0))
quest_icon = Entity(parent=elder, model='sphere', scale=0.25, color=color.yellow, position=(0, 1.6, 0))

# Камера 3-го лица
cam_yaw = 0
cam_pitch = 20
cam_dist = 6.5
mouse.locked = True

# Интерфейс
hud = Text(text="HP: 200 | Энергия: 100 | [ЛКМ] Меч  [1] Игни  [2] Квен", position=(-0.85, 0.45), scale=1.2, color=color.white)
msg_text = Text(text="", origin=(0, 0), position=(0, 0.25), scale=1.8, color=color.yellow)

def show_msg(txt):
    msg_text.text = txt
    invoke(setattr, msg_text, 'text', "", delay=2.0)

def update():
    global cam_yaw, cam_pitch, drowner_hp

    # Энергия
    if player_stats["stamina"] < 100:
        player_stats["stamina"] = min(100, player_stats["stamina"] + 25 * time.dt)

    # Вращение камеры мышью
    cam_yaw += mouse.velocity[0] * 120
    cam_pitch = clamp(cam_pitch - mouse.velocity[1] * 80, -5, 50)

    rad_y = math.radians(cam_yaw)
    rad_p = math.radians(cam_pitch)

    cx = player.x - cam_dist * math.sin(rad_y) * math.cos(rad_p)
    cz = player.z - cam_dist * math.cos(rad_y) * math.cos(rad_p)
    cy = player.y + 2.2 + cam_dist * math.sin(rad_p)

    camera.position = Vec3(cx, cy, cz)
    camera.look_at(player.position + Vec3(0, 1.4, 0))

    # Движение
    move = Vec3(
        (held_keys['d'] - held_keys['a']),
        0,
        (held_keys['w'] - held_keys['s'])
    ).normalized()

    if move.length() > 0:
        fwd = Vec3(math.sin(rad_y), 0, math.cos(rad_y))
        rgt = Vec3(math.cos(rad_y), 0, -math.sin(rad_y))
        wdir = fwd * move.z + rgt * move.x
        player.position += wdir * player_stats["speed"] * time.dt
        player.rotation_y = math.degrees(math.atan2(wdir.x, wdir.z))

    # ИИ Утопца
    if drowner_hp > 0:
        dist = distance(player.position, drowner.position)
        if 1.8 < dist < 22:
            drowner.look_at_2d(player.position)
            drowner.position += drowner.forward * 3.2 * time.dt
        elif dist <= 1.8:
            if player_stats["quen"]:
                player_stats["quen"] = False
                quen_shield.enabled = False
                show_msg("КВЕН ПОГЛОТИЛ УДАР!")
            else:
                player_stats["hp"] -= 15 * time.dt
                if player_stats["hp"] <= 0:
                    show_msg("ВЫ ПОГИБЛИ")

    # Обновление HUD
    hud.text = f"HP: {int(player_stats['hp'])} | Энергия: {int(player_stats['stamina'])}"

def input(key):
    global drowner_hp

    # ЛКМ — Атака
    if key == 'left mouse down' and player_stats["stamina"] >= 15:
        player_stats["stamina"] -= 15
        sword_mesh.enabled = True
        sword_mesh.animate_rotation((10, 60, -40), duration=0.12)
        invoke(setattr, sword_mesh, 'enabled', False, delay=0.2)

        if distance(player.position, drowner.position) < 3.2 and drowner_hp > 0:
            drowner_hp -= 30
            show_msg("УДАР МЕЧОМ: -30 HP!")
            if drowner_hp <= 0:
                drowner.rotation_x = 90
                drowner.y = 0.4
                show_msg("УТОПЕЦ ПОВЕРЖЕН!")

    # 1 — Игни
    if key == '1' and player_stats["stamina"] >= 35:
        player_stats["stamina"] -= 35
        fire = Entity(model='sphere', color=color.orange, scale=0.6, position=player.position + Vec3(0, 1, 0) + player.forward * 1.5)
        fire.animate_position(fire.position + player.forward * 8, duration=0.4)
        destroy(fire, delay=0.45)
        if distance(player.position, drowner.position) < 7 and drowner_hp > 0:
            drowner_hp -= 25
            show_msg("ЗНАК ИГНИ: -25 HP!")

    # 2 — Квен
    if key == '2' and player_stats["stamina"] >= 35:
        player_stats["stamina"] -= 35
        player_stats["quen"] = True
        quen_shield.enabled = True
        show_msg("ЗНАК КВЕН АКТИВЕН")

    # Взаимодействие с NPC
    if key == 'e':
        if distance(player.position, elder.position) < 3.5:
            show_msg("Староста: 'Ведьмак, убей утопца на болоте!'")

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

app.run()