const std = @import("std"); const Api = @import("artifacts-api"); const Allocator = std.mem.Allocator; const CharacterBrain = @import("./brain.zig"); const Artificer = @This(); const expiration_margin: u64 = 100 * std.time.ns_per_ms; // 100ms const server_down_retry_interval = 5; // minutes server: Api.Server, characters: std.ArrayList(CharacterBrain), paused_until: ?i64 = null, pub fn init(allocator: Allocator, token: []const u8) !Artificer { var server = try Api.Server.init(allocator); errdefer server.deinit(); try server.setToken(token); var characters = std.ArrayList(CharacterBrain).init(allocator); defer characters.deinit(); // TODO: Add character deinit // const chars = try api.listMyCharacters(); // defer chars.deinit(); // for (chars.items) |char| { // try scheduler.addCharacter(char.name); // } return Artificer{ .server = server, .characters = characters }; } pub fn deinit(self: Artificer) void { for (self.characters.items) |brain| { brain.deinit(); } self.characters.deinit(); } pub fn step(self: *Artificer) !void { if (self.paused_until) |paused_until| { if (std.time.timestamp() < paused_until) { return; } self.paused_until = null; } runNextActions(self.characters.items, &self.server) catch |err| switch (err) { Api.Error.ServerUnavailable => { self.paused_until = std.time.timestamp() + std.time.ns_per_min * server_down_retry_interval; std.log.warn("Server is down, retrying in {}min", .{ server_down_retry_interval }); return; }, else => return err }; for (self.characters.items) |*brain| { if (brain.action_queue.items.len > 0) continue; if (brain.isTaskFinished()) { if (try brain.depositItemsToBank(&self.server)) { continue; } brain.task = null; } if (brain.task == null) { // var next_tasks = try task_tree.listNextTasks(); // defer next_tasks.deinit(); // // if (next_tasks.items.len > 0) { // const next_task_index = random.intRangeLessThan(usize, 0, next_tasks.items.len); // brain.task = next_tasks.items[next_task_index]; // } } try brain.performTask(&self.server); } } pub fn nextStepAt(self: *Artificer) i64 { if (self.paused_until) |paused_until| { return paused_until; } return earliestCooldown(self.characters.items, &self.server) orelse 0; } fn earliestCooldown(characters: []CharacterBrain, api: *Api.Server) ?i64 { var earliest_cooldown: ?i64 = null; for (characters) |*brain| { if (brain.action_queue.items.len == 0) continue; const character = api.findCharacter(brain.name).?; const cooldown: i64 = @intFromFloat(character.cooldown_expiration * std.time.ns_per_s); if (earliest_cooldown == null or earliest_cooldown.? > cooldown) { earliest_cooldown = cooldown; } } return earliest_cooldown; } fn runNextActions(characters: []CharacterBrain, api: *Api.Server) !void { const maybe_earliest_cooldown = earliestCooldown(characters, api); if (maybe_earliest_cooldown == null) return; const earliest_cooldown = maybe_earliest_cooldown.?; if (earliest_cooldown < std.time.timestamp()) return; for (characters) |*brain| { if (brain.action_queue.items.len == 0) continue; const character = api.findCharacter(brain.name).?; const cooldown: u64 = @intFromFloat(character.cooldown_expiration * std.time.ns_per_s); if (earliest_cooldown > cooldown) { try brain.performNextAction(api); } } } fn currentTime() f64 { const timestamp: f64 = @floatFromInt(std.time.milliTimestamp()); return timestamp / std.time.ms_per_s; }