const std = @import("std"); const Api = @import("artifacts-api"); const Allocator = std.mem.Allocator; pub const Brain = @import("./brain.zig"); pub const TaskGraph = @import("./task_graph.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(Brain), task_graph: TaskGraph, paused_until: ?i64 = null, // ms 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(Brain).init(allocator); errdefer characters.deinit(); // TODO: Add character deinit const chars = try server.listMyCharacters(); defer chars.deinit(); for (chars.items) |char| { try characters.append(try Brain.init(allocator, char.name)); } return Artificer{ .server = server, .characters = characters, .task_graph = TaskGraph.init(allocator) }; } pub fn deinit(self: *Artificer) void { for (self.characters.items) |brain| { brain.deinit(); } self.characters.deinit(); self.server.deinit(); } pub fn step(self: *Artificer) !void { if (self.paused_until) |paused_until| { if (std.time.milliTimestamp() < paused_until) { return; } self.paused_until = null; } runNextActions(self.characters.items, &self.server) catch |err| switch (err) { Api.FetchError.ServerUnavailable => { self.paused_until = std.time.milliTimestamp() + std.time.ms_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.task != null) { try brain.step(&self.server); continue; } const character = self.server.store.getCharacter(brain.name).?; if (character.task) |taskmaster_task| { if (taskmaster_task.total > taskmaster_task.progress) { switch (taskmaster_task.type) { .monsters => { const monster_code = self.server.store.getCode(taskmaster_task.target_id).?; const maps = try self.server.getMaps(.{ .code = monster_code }); defer maps.deinit(); if (maps.items.len > 0) { const resource_map: Api.Map = maps.items[0]; std.debug.print("fight at {}\n", .{resource_map.position}); brain.task = .{ .fight = .{ .at = resource_map.position, .until = .{ .quantity = taskmaster_task.total - taskmaster_task.progress }, } }; } }, .crafts => {}, .resources => {}, } } } else { brain.task = .{ .accept_task = .{} }; } } } 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: []Brain, api: *Api.Server) ?i64 { var earliest_cooldown: ?i64 = null; for (characters) |*brain| { if (brain.action_queue.items.len == 0) continue; const cooldown = brain.cooldown(api); if (earliest_cooldown == null or earliest_cooldown.? > cooldown) { earliest_cooldown = cooldown; } } return earliest_cooldown; } fn runNextActions(characters: []Brain, api: *Api.Server) !void { for (characters) |*brain| { if (brain.action_queue.items.len == 0) continue; const cooldown = brain.cooldown(api); if (std.time.milliTimestamp() >= cooldown) { try brain.performNextAction(api); } } }