235 lines
8.5 KiB
Zig
235 lines
8.5 KiB
Zig
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(),
|
|
|
|
pub fn format(
|
|
self: TaskNode,
|
|
comptime fmt: []const u8,
|
|
options: std.fmt.FormatOptions,
|
|
writer: anytype,
|
|
) !void {
|
|
_ = options;
|
|
_ = fmt;
|
|
|
|
switch (self.task) {
|
|
.fight => |args| {
|
|
const item = self.api.getItemCode(args.target.id).?;
|
|
try writer.print("Task{{ .fight = {{ \"{s}\" x {} }}, .at = {} }}\n", .{item, args.target.quantity, args.at});
|
|
},
|
|
.gather => |args| {
|
|
const item = self.api.getItemCode(args.target.id).?;
|
|
try writer.print("Task{{ .gather = {{ \"{s}\" x {} }}, .at = {} }}\n", .{item, args.target.quantity, args.at});
|
|
},
|
|
.craft => |args| {
|
|
const item = self.api.getItemCode(args.target.id).?;
|
|
try writer.print("Task{{ .craft = {{ \"{s}\" x {} }}, .at = {} }}\n", .{item, args.target.quantity, args.at});
|
|
},
|
|
}
|
|
|
|
// var child_nodes = try self.listByParent(node_id);
|
|
// defer child_nodes.deinit();
|
|
//
|
|
// for (child_nodes.items) |child_node| {
|
|
// try self.formatNode(child_node, level + 1, writer);
|
|
// }
|
|
// for (node.additional_items.slots.constSlice()) |slot| {
|
|
// const item_code = self.api.getItemCode(slot.id).?;
|
|
// try writer.writeBytesNTimes(" ", level+1);
|
|
// try writer.print("+ {{ \"{s}\" x {} }}\n", .{item_code, slot.quantity});
|
|
// }
|
|
}
|
|
};
|
|
|
|
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.ItemId, quantity: u64) !TaskNodeId {
|
|
const item_code = api.getItemCode(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.ItemIdQuantity.init(item_id, quantity) }
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
fn addGatherTask(self: *TaskGraph, api: *Api.Server, item_id: Api.ItemId, quantity: u64) !TaskNodeId {
|
|
const item_code = api.getItemCode(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.ItemIdQuantity.init(item_id, quantity) }
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
fn addCraftTaskShallow(self: *TaskGraph, api: *Api.Server, item_id: Api.ItemId, 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.ItemIdQuantity.init(item_id, quantity)
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
fn addCraftTask(self: *TaskGraph, api: *Api.Server, item_id: Api.ItemId, 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.ItemId, 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.getItemCode(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.getItemCode(target_item.id).?;
|
|
print("Task{{ .gather = {{ \"{s}\" x {} }}, .at = {} }}\n", .{item, target_item.quantity, args.at});
|
|
},
|
|
.craft => |args| {
|
|
const item = api.getItemCode(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});
|
|
}
|
|
}
|