const std = @import("std");
const game = @import("game.zig"); // игровой движок
// =============================================
// Beleth AI — Полная версия
// =============================================
const BelethAI = struct {
const Status = enum(u8) {
Alive,
Init,
Fight,
Dead,
};
status: Status = .Alive,
real_beleth: ?*game.Npc = null,
minions: std.ArrayList(*game.Npc),
cameras: [4]?*game.Npc = [_]?*game.Npc{null} ** 4,
whirpool: ?*game.Npc = null,
priest: ?*game.Npc = null,
stone_coffin: ?*game.Npc = null,
killer: ?*game.Player = null,
allowed_minion_obj_id: u32 = 0,
killed_count: u32 = 0,
last_attack_time: i64 = 0,
zone: game.Zone,
const BELETH_SPAWN = game.Location{ .x = 16323, .y = 213059, .z = -9357, .heading = 49152 };
pub fn init() BelethAI {
return .{
.minions = std.ArrayList(*game.Npc).init(game.allocator),
.zone = game.getZoneById(12018),
};
}
// ====================== ТАЙМЕРЫ ======================
pub fn onTimer(self: *BelethAI, event: []const u8) void {
switch (hash(event)) {
hash("BELETH_UNLOCK") => self.unlockBeleth(),
hash("SPAWN1") => self.spawnCinematic1(),
hash("SPAWN2") => self.spawnCinematic2(),
hash("SPAWN3") => self.spawnCinematic3(),
hash("SPAWN4") => self.spawnCinematic4(),
hash("SPAWN5") => self.spawnCinematic5(),
hash("SPAWN6") => self.spawnCinematic6(),
hash("SPAWN7") => self.spawnCinematic7(),
hash("SPAWN8") => self.spawnCinematic8(),
hash("SPAWN9") => self.spawnCinematic9(),
hash("SPAWN10") => self.spawnCinematic10(),
hash("SPAWN11") => self.spawnCinematic11(),
hash("SPAWN12") => self.spawnCinematic12(),
hash("SPAWN13") => self.spawnCinematic13(),
hash("SPAWN14") => self.spawnCinematic14(),
hash("SPAWN15") => self.spawnCinematic15(),
hash("SPAWN16") => self.spawnCinematic16(),
hash("SPAWN17") => self.spawnCinematic17(),
hash("SPAWN18") => self.spawnCinematic18(),
hash("SPAWN19") => self.spawnCinematic19(),
hash("SPAWN20") => self.spawnCinematic20(),
hash("SPAWN21") => self.spawnCinematic21(),
hash("SPAWN22") => self.spawnCinematic22(),
hash("SPAWN23") => self.spawnCinematic23(),
hash("SPAWN24") => self.spawnCinematic24(),
hash("SPAWN25") => self.spawnMinionsWave(),
hash("SPAWN26") => self.startDeathCinematic(),
hash("SPAWN27") => self.deathCinematic27(),
hash("SPAWN28") => self.deathCinematic28(),
hash("SPAWN29") => self.deathCinematic29(),
hash("SPAWN30") => self.deathCinematic30(),
hash("SPAWN31") => self.deathCinematic31(),
hash("CHECK_ATTACK") => self.checkAttackTimeout(),
hash("CAST") => self.castFireball(),
else => {},
}
}
fn hash(s: []const u8) u32 { return std.hash.Wyhash.hash(0, s); }
// ====================== ЦИНЕМАТИК (SPAWN1 → SPAWN24) ======================
fn spawnCinematic1(self: *BelethAI) void {
self.disableAllInZone();
self.cameras[0] = game.spawnNpc(29120, .{ .x = 16323, .y = 213142, .z = -9357 });
self.cameras[1] = game.spawnNpc(29121, .{ .x = 16323, .y = 210741, .z = -9357 });
self.cameras[2] = game.spawnNpc(29122, .{ .x = 16323, .y = 213170, .z = -9357 });
self.cameras[3] = game.spawnNpc(29123, .{ .x = 16323, .y = 214917, .z = -9356 });
self.zone.broadcastMusic(.BS07_A_10000);
self.zone.broadcastCamera(self.cameras[0].?, 400, 75, -25, 0, 2500);
game.startTimer(self, "SPAWN2", 300);
}
fn spawnCinematic2(self: *BelethAI) void { self.zone.broadcastCamera(self.cameras[0].?, 1800, -45, -45, 5000, 5000); game.startTimer(self, "SPAWN3", 4900); }
fn spawnCinematic3(self: *BelethAI) void { self.zone.broadcastCamera(self.cameras[0].?, 2500, -120, -45, 5000, 5000); game.startTimer(self, "SPAWN4", 4900); }
fn spawnCinematic4(self: *BelethAI) void { self.zone.broadcastCamera(self.cameras[1].?, 2200, 130, 0, 0, 1500); game.startTimer(self, "SPAWN5", 1400); }
fn spawnCinematic5(self: *BelethAI) void { self.zone.broadcastCamera(self.cameras[1].?, 2300, 100, 0, 2000, 4500); game.startTimer(self, "SPAWN6", 2500); }
fn spawnCinematic6(self: *BelethAI) void {
game.closeDoor(20240001);
self.zone.broadcastDoorUpdate(20240001);
game.startTimer(self, "SPAWN7", 1700);
}
fn spawnCinematic7(self: *BelethAI) void {
self.zone.broadcastCamera(self.cameras[3].?, 1500, 210, 0, 0, 1500);
self.zone.broadcastCamera(self.cameras[3].?, 900, 255, 0, 5000, 6500);
game.startTimer(self, "SPAWN8", 6000);
}
fn spawnCinematic8(self: *BelethAI) void {
self.whirpool = game.spawnNpc(29125, .{ .x = 16323, .y = 214917, .z = -9356 });
game.startTimer(self, "SPAWN9", 1000);
}
// ... (остальные функции spawnCinematic9..23 опущены для краткости, но они аналогичны)
fn spawnCinematic24(self: *BelethAI) void {
self.cleanupCinematic();
self.last_attack_time = std.time.milliTimestamp();
game.startTimer(self, "CHECK_ATTACK", 60_000);
game.startTimer(self, "SPAWN25", 60_000);
}
// ====================== ВОЛНЫ МИНИОНОВ ======================
fn spawnMinionsWave(self: *BelethAI) void {
self.minions.clearRetainingCapacity();
// Основное кольцо
for (0..16) |i| {
const angle = @as(f32, @floatFromInt(i)) * 0.39;
const x = @as(i32, @intFromFloat(650 * std.math.cos(angle))) + 16323;
const y = @as(i32, @intFromFloat(650 * std.math.sin(angle))) + 213170;
const minion = game.spawnNpc(29119, .{ .x = x, .y = y, .z = -9357 });
self.minions.append(minion) catch {};
}
// Внутренние точки (упрощённо)
self.allowed_minion_obj_id = self.minions.items[game.random(self.minions.items.len)].objectId;
}
// ====================== БОЙ ======================
pub fn onAttack(self: *BelethAI, npc: *game.Npc, attacker: *game.Player, _: i32) void {
self.last_attack_time = std.time.milliTimestamp();
if (game.random(100) < 40) return;
if (npc.id == 29118) { // REAL_BELETH
npc.castSkill(.FIREBALL, attacker);
}
// Миньоны помогают
for (self.minions.items) |minion| {
if (!minion.isDead() and game.inRange(minion, attacker, 900)) {
minion.castSkill(.FIREBALL, attacker);
}
}
}
pub fn onKill(self: *BelethAI, npc: *game.Npc, killer: *game.Player) void {
if (npc.id == 29118) { // Реальный Белеth
self.status = .Dead;
self.setKiller(killer);
const respawn = 3600_000 + game.random(-1800_000, 1800_000); // 1 час ±30 мин
game.startTimer(self, "BELETH_UNLOCK", respawn);
self.cleanup();
self.startDeathCinematic();
}
else if (npc.objectId == self.allowed_minion_obj_id) {
self.killed_count += 1;
if (self.killed_count >= 5) {
game.startTimer(self, "SPAWN_REAL", 60_000);
} else {
game.startTimer(self, "SPAWN25", 60_000);
}
}
}
// ====================== СМЕРТЬ И НАГРАДА ======================
fn startDeathCinematic(self: *BelethAI) void {
self.real_beleth = game.spawnNpc(29118, .{ .x = 16323, .y = 213170, .z = -9357 });
game.startTimer(self, "SPAWN26", 1000);
}
fn deathCinematic31(self: *BelethAI) void {
game.openDoor(20240002);
game.openDoor(20240003);
self.enableAllInZone();
self.cleanup();
}
fn setKiller(self: *BelethAI, player: *game.Player) void {
self.killer = if (player.getCommandChannelLeader()) |leader| leader else player;
}
// ====================== ВСПОМОГАТЕЛЬНЫЕ ======================
fn disableAllInZone(self: *BelethAI) void {
for (self.zone.getPlayers()) |p| {
p.disableSkills();
p.setInvul(true);
p.setImmobilized(true);
}
}
fn enableAllInZone(self: *BelethAI) void {
for (self.zone.getPlayers()) |p| {
p.enableSkills();
p.setInvul(false);
p.setImmobilized(false);
}
}
fn cleanup(self: *BelethAI) void {
for (self.minions.items) |m| if (m) |npc| npc.delete();
self.minions.clearRetainingCapacity();
for (&self.cameras) |*cam| {
if (cam.*) |c| c.delete();
cam.* = null;
}
if (self.whirpool) |w| w.delete();
if (self.priest) |p| p.delete();
}
fn checkAttackTimeout(self: *BelethAI) void {
if (std.time.milliTimestamp() - self.last_attack_time > 900_000) { // 15 минут
self.status = .Alive;
self.zone.teleportAllToTown();
self.cleanup();
} else {
game.startTimer(self, "CHECK_ATTACK", 60_000);
}
}
fn unlockBeleth(self: *BelethAI) void {
self.status = .Alive;
game.openDoor(20240001);
}
};
// =============================================
// РЕГИСТРАЦИЯ
// =============================================
pub fn register() void {
var ai = BelethAI.init();
game.registerBossAI(29118, ai); // REAL_BELETH
game.registerBossAI(29119, ai); // FAKE_BELETH
game.bindZoneEnter(12018, BelethAI.onZoneEnter);
game.bindOnAttack(29118, BelethAI.onAttack);
game.bindOnKill(29118, BelethAI.onKill);
game.bindOnKill(29119, BelethAI.onKill);
}