const std = @import("std"); const Api = @import("artifacts-api"); const Allocator = std.mem.Allocator; const TaskGraph = @This(); const CharacterTask = @import("./task.zig").Task; const TaskNodeId = u16; const TaskNode = struct { const Dependencies = std.BoundedArray(TaskNodeId, 8); const MissingItems = Api.BoundedSlotsArray(8); task: CharacterTask, dependencies: Dependencies = Dependencies.init(0) catch unreachable, missing_items: MissingItems = MissingItems.init(), }; const Nodes = std.ArrayList(TaskNode); nodes: Nodes, pub fn init(allocator: Allocator) TaskGraph { return TaskGraph{ .nodes = Nodes.init(allocator) }; } pub fn deinit(self: TaskGraph) void { self.nodes.deinit(); } fn get(self: *TaskGraph, id: TaskNodeId) *TaskNode { return &self.nodes.items[id]; } fn addTask(self: *TaskGraph, node: TaskNode) !TaskNodeId { try self.nodes.append(node); return @intCast(self.nodes.items.len-1); } fn addFightTask(self: *TaskGraph, api: *Api.Server, item_id: Api.CodeId, quantity: u64) !TaskNodeId { const item_code = api.store.getCode(item_id) orelse return error.ItemNotFound; const monsters = try api.getMonsters(.{ .drop = item_code }); defer monsters.deinit(); if (monsters.items.len == 0) return error.ResourceNotFound; if (monsters.items.len > 1) std.log.warn("Multiple monsters exist for target item", .{}); const monster_code = monsters.items[0].code; const resource_maps = try self.api.getMaps(.{ .code = monster_code }); defer resource_maps.deinit(); // This monster currently doesn't exist on the map. Probably only spawns in certain situations. if (resource_maps.items.len == 0) return error.MapNotFound; if (resource_maps.items.len > 1) std.log.warn("Multiple map locations exist for monster", .{}); const resource_map = resource_maps.items[0]; return try self.addTask(TaskNode{ .task = .{ .fight = .{ .at = resource_map.position, .until = .{ .item = Api.ItemQuantity.init(item_id, quantity) } } } }); } fn addGatherTask(self: *TaskGraph, api: *Api.Server, item_id: Api.CodeId, quantity: u64) !TaskNodeId { const item_code = api.store.getCode(item_id) orelse return error.ItemNotFound; const resources = try api.getResources(.{ .drop = item_code }); defer resources.deinit(); if (resources.items.len == 0) return error.ResourceNotFound; if (resources.items.len > 1) std.log.warn("Multiple resources exist for target item", .{}); const resource_code = resources.items[0].code; const resource_maps = try self.api.getMaps(.{ .code = resource_code }); defer resource_maps.deinit(); if (resource_maps.items.len == 0) return error.MapNotFound; if (resource_maps.items.len > 1) std.log.warn("Multiple map locations exist for resource", .{}); const resource_map = resource_maps.items[0]; return try self.addTask(TaskNode{ .task = .{ .gather = .{ .at = resource_map.position, .until = .{ .item = Api.ItemQuantity.init(item_id, quantity) } } } }); } fn addCraftTaskShallow(self: *TaskGraph, api: *Api.Server, item_id: Api.CodeId, quantity: u64) !TaskNodeId { const item = (try api.getItemById(item_id)) orelse return error.ItemNotFound; const recipe = item.craft orelse return error.RecipeNotFound; const skill_str = Api.Server.SkillUtils.toString(recipe.skill); const workshop_maps = try self.api.getMaps(.{ .code = skill_str, .type = .workshop }); defer workshop_maps.deinit(); if (workshop_maps.items.len == 0) return error.WorkshopNotFound; if (workshop_maps.items.len > 1) std.log.warn("Multiple workshop locations exist", .{}); return try self.addTask(TaskNode{ .task = .{ .craft = .{ .at = workshop_maps.items[0].position, .target = Api.ItemQuantity.init(item_id, quantity) } } }); } fn addCraftTask(self: *TaskGraph, api: *Api.Server, item_id: Api.CodeId, quantity: u64) !TaskNodeId { const node_id = try self.addCraftTaskShallow(api, item_id, quantity); var node = self.get(node_id); const item = (try api.getItemById(item_id)) orelse return error.ItemNotFound; const recipe = item.craft orelse return error.RecipeNotFound; const craft_count = recipe.quantity; for (recipe.items.slots.constSlice()) |material| { const needed_quantity = material.quantity * craft_count; if (try self.addAutoTask(api, material.id, needed_quantity)) |dependency_id| { try node.dependencies.append(dependency_id); } else { try node.missing_items.add(material.id, needed_quantity); } } return node_id; } // TODO: Remove `anyerror` from function declaration fn addAutoTask(self: *TaskGraph, api: *Api.Server, item_id: Api.CodeId, quantity: u64) anyerror!?TaskNodeId { const item = (try self.api.getItemById(item_id)) orelse return error.ItemNotFound; if (item.craft != null) { return try self.addCraftTask(api, item_id, quantity); } else if (item.type == .resource) { const eql = std.mem.eql; if (eql(u8, item.subtype, "mining") or eql(u8, item.subtype, "fishing") or eql(u8, item.subtype, "woodcutting")) { return try self.addGatherTask(api, item_id, quantity); } else if (eql(u8, item.subtype, "mob")) { return try self.addFightTask(api, item_id, quantity); } } return null; } fn printTask(self: *TaskGraph, api: *Api.Server, node_id: TaskNodeId) void { self.printTaskLevel(api, node_id, 0); } fn writeIdentation(level: u32) void { const mutex = std.debug.getStderrMutex(); mutex.lock(); defer mutex.unlock(); const stderr = std.io.getStdErr().writer(); stderr.writeBytesNTimes(" ", level) catch return; } fn printTaskLevel(self: *TaskGraph, api: *Api.Server, node_id: TaskNodeId, level: u32) void { const node = self.get(node_id); const print = std.debug.print; writeIdentation(level); switch (node.task) { .fight => |args| { const target_item = args.until.item; const item = api.store.getCode(target_item.id).?; print("Task{{ .fight = {{ \"{s}\" x {} }}, .at = {} }}\n", .{item, target_item.quantity, args.at}); }, .gather => |args| { const target_item = args.until.item; const item = api.store.getCode(target_item.id).?; print("Task{{ .gather = {{ \"{s}\" x {} }}, .at = {} }}\n", .{item, target_item.quantity, args.at}); }, .craft => |args| { const item = api.store.getCode(args.target.id).?; print("Task{{ .craft = {{ \"{s}\" x {} }}, .at = {} }}\n", .{item, args.target.quantity, args.at}); }, } for (node.dependencies.constSlice()) |dependency| { self.printTaskLevel(dependency, level + 1); } for (node.missing_items.slots.constSlice()) |slot| { const item_code = api.getItemCode(slot.id).?; writeIdentation(level+1); print("+ {{ \"{s}\" x {} }}\n", .{item_code, slot.quantity}); } }