add automatic gathering of materials for recipe

This commit is contained in:
Rokas Puzonas 2024-08-31 17:53:52 +03:00
parent 7d572e7064
commit 64c4d9ff47
4 changed files with 128 additions and 135 deletions

View File

@ -1088,7 +1088,7 @@ fn fetch(self: *Server, options: FetchOptions) APIError!ArtifactsFetchResult {
if (options.paginated) {
const total_pages_i64 = json_utils.getInteger(parsed.object, "pages") orelse return APIError.ParseFailed;
if (total_pages_i64 < 1) return APIError.ParseFailed;
if (total_pages_i64 < 0) return APIError.ParseFailed;
total_pages = @intCast(total_pages_i64);
const page_results = json_utils.getArray(parsed.object, "data") orelse return APIError.ParseFailed;

View File

@ -13,7 +13,7 @@ pub fn BoundedSlotsArray(comptime slot_count: u32) type {
return struct {
slots: Slots,
fn init() @This() {
pub fn init() @This() {
return @This(){
.slots = Slots.init(0) catch unreachable
};
@ -62,20 +62,13 @@ pub fn BoundedSlotsArray(comptime slot_count: u32) type {
}
}
pub fn add(self: *@This(), id: ItemId, quantity: u64) void {
pub fn add(self: *@This(), id: ItemId, quantity: u64) !void {
if (quantity == 0) return;
if (self.findSlot(id)) |slot| {
slot.quantity += quantity;
} else {
var empty_slot: ?*Slot = null;
for (&self.slots) |*slot| {
if (slot.id == null) {
empty_slot = slot;
}
}
assert(empty_slot != null);
empty_slot.?.id = id;
empty_slot.?.quantity = quantity;
try self.slots.append(Slot{ .id = id, .quantity = quantity });
}
}

View File

@ -132,7 +132,7 @@ pub const CharacterTask = union(enum) {
name: []const u8,
action_queue: std.ArrayList(QueuedAction),
task: ?CharacterTask = null,
task: ?*CharacterTask = null,
pub fn init(allocator: Allocator, name: []const u8) !CharacterBrain {
return CharacterBrain{
@ -391,7 +391,7 @@ pub fn performNextAction(self: *CharacterBrain, api: *Server) !void {
fn onActionCompleted(self: *CharacterBrain, result: QueuedActionResult) void {
if (self.task == null) return;
switch (self.task.?) {
switch (self.task.?.*) {
.fight => |*args| {
if (result.get(.fight)) |r| {
const fight_result: Server.FightResult = r;
@ -433,7 +433,7 @@ pub fn performTask(self: *CharacterBrain, api: *Server) !void {
return;
}
switch (self.task.?) {
switch (self.task.?.*) {
.fight => |args| {
try self.fightRoutine(api, args.at);
},

View File

@ -5,6 +5,7 @@ const assert = std.debug.assert;
const Position = @import("./api/position.zig");
const Server = @import("./api/server.zig");
const CharacterBrain = @import("./character_brain.zig");
const BoundedSlotsArray = @import("./api/slot_array.zig").BoundedSlotsArray;
// pub const std_options = .{ .log_level = .debug };
@ -90,6 +91,7 @@ const TaskTree = struct {
const TaskTreeNode = struct {
parent: ?usize,
character_task: CharacterBrain.CharacterTask,
additional_items: BoundedSlotsArray(8) = BoundedSlotsArray(8).init(),
};
const Nodes = std.ArrayList(TaskTreeNode);
@ -115,8 +117,8 @@ const TaskTree = struct {
return self.nodes.items.len-1;
}
fn appendSubTree(self: *TaskTree, code: []const u8, quantity: u64, parent: ?usize) !void {
if (quantity == 0) return;
fn appendSubTree(self: *TaskTree, code: []const u8, quantity: u64, parent: ?usize) !?usize {
if (quantity == 0) return null;
const item = (try self.api.getItem(code)).?;
const item_id = try self.api.getItemId(code);
@ -144,62 +146,72 @@ const TaskTree = struct {
for (recipe.items.slots.constSlice()) |recipe_item| {
const recipe_item_code = self.api.getItemCode(recipe_item.id).?;
try self.appendSubTree(recipe_item_code, recipe_item.quantity * craft_count, node_id);
}
} else {
if (item.type == .resource) {
if (eql(u8, item.subtype, "mining")) {
const resources = try self.api.getResources(.{ .drop = code });
defer resources.deinit();
const needed_quantity = recipe_item.quantity * craft_count;
const subtree_id = try self.appendSubTree(recipe_item_code, needed_quantity, node_id);
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];
_ = try self.appendNode(TaskTreeNode{
.parent = parent,
.character_task = .{
.gather = .{
.at = resource_map.position,
.target = .{ .id = item_id, .quantity = quantity }
}
}
});
} else if (eql(u8, item.subtype, "mob")) {
const monsters = try self.api.getMonsters(.{ .drop = 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();
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];
_ = try self.appendNode(TaskTreeNode{
.parent = parent,
.character_task = .{
.fight = .{
.at = resource_map.position,
.target = .{ .id = item_id, .quantity = quantity }
}
}
});
// The target item can not be currently produced.
if (subtree_id == null) {
var node = &self.nodes.items[node_id];
try node.additional_items.add(recipe_item.id, needed_quantity);
}
} else {
}
return node_id;
} else if (item.type == .resource) {
if (eql(u8, item.subtype, "mining") or eql(u8, item.subtype, "fishing") or eql(u8, item.subtype, "woodcutting")) {
const resources = try self.api.getResources(.{ .drop = 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.appendNode(TaskTreeNode{
.parent = parent,
.character_task = .{
.gather = .{
.at = resource_map.position,
.target = .{ .id = item_id, .quantity = quantity }
}
}
});
} else if (eql(u8, item.subtype, "mob")) {
const monsters = try self.api.getMonsters(.{ .drop = 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 null;
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.appendNode(TaskTreeNode{
.parent = parent,
.character_task = .{
.fight = .{
.at = resource_map.position,
.target = .{ .id = item_id, .quantity = quantity }
}
}
});
}
}
return null;
}
fn listByParent(self: *const TaskTree, parent: ?usize) !std.ArrayList(usize) {
@ -212,6 +224,31 @@ const TaskTree = struct {
return found_nodes;
}
fn listNextTasks(self: *const TaskTree) !std.ArrayList(*CharacterBrain.CharacterTask) {
var next_tasks = std.ArrayList(*CharacterBrain.CharacterTask).init(self.allocator);
errdefer next_tasks.deinit();
for (0.., self.nodes.items) |i, *node| {
var child_nodes = try self.listByParent(i);
defer child_nodes.deinit();
var can_be_started = true;
for (child_nodes.items) |child_node_id| {
var child_node = self.nodes.items[child_node_id];
if (!child_node.character_task.isComplete()) {
can_be_started = false;
break;
}
}
if (can_be_started) {
try next_tasks.append(&node.character_task);
}
}
return next_tasks;
}
pub fn format(
self: TaskTree,
comptime fmt: []const u8,
@ -253,6 +290,11 @@ const TaskTree = struct {
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});
}
}
};
@ -264,13 +306,14 @@ pub fn main() !void {
var api = try Server.init(allocator);
defer api.deinit();
try api.prefetch();
const token = (try getAPITokenFromArgs(allocator)) orelse return error.MissingToken;
defer allocator.free(token);
try api.setToken(token);
std.log.info("prefetching static data", .{});
try api.prefetch();
var goal_manager = GoalManager.init(&api, allocator);
defer goal_manager.deinit();
@ -281,69 +324,17 @@ pub fn main() !void {
try goal_manager.addCharacter(char.name);
}
goal_manager.characters.items[0].task = .{
.fight = .{
.at = Position.init(0, 1),
.target = .{
.id = try api.getItemId("egg"),
.quantity = 1
}
},
};
goal_manager.characters.items[1].task = .{
.gather = .{
.at = Position.init(-1, 0),
.target = .{
.id = try api.getItemId("ash_wood"),
.quantity = 3
}
}
};
goal_manager.characters.items[2].task = .{
.gather = .{
.at = Position.init(2, 0),
.target = .{
.id = try api.getItemId("copper_ore"),
.quantity = 3
}
}
};
goal_manager.characters.items[3].task = .{
.gather = .{
.at = Position.init(4, 2),
.target = .{
.id = try api.getItemId("gudgeon"),
.quantity = 3
}
}
};
goal_manager.characters.items[4].task = .{
.fight = .{
.at = Position.init(0, 1),
.target = .{
.id = try api.getItemId("raw_chicken"),
.quantity = 1
}
},
};
var task_tree = TaskTree.init(allocator, &api);
defer task_tree.deinit();
try task_tree.appendSubTree("sticky_dagger", 5, null);
// try task_tree.appendSubTree("copper_boots" , 5, null);
// try task_tree.appendSubTree("copper_helmet", 5, null);
// try task_tree.appendSubTree("copper_legs_armor", 5, null);
// try task_tree.appendSubTree("copper_armor", 5, null);
// try task_tree.appendSubTree("copper_ring", 5, null);
// _ = try task_tree.appendSubTree("wooden_staff", 5, null);
_ = try task_tree.appendSubTree("wooden_shield", 5, null);
std.debug.print("{}", .{task_tree});
if (false) {
var default_prng = std.rand.DefaultPrng.init(@bitCast(std.time.timestamp()));
var random = default_prng.random();
std.log.info("Starting main loop", .{});
while (true) {
goal_manager.runNextAction() catch |err| switch (err) {
@ -360,15 +351,24 @@ pub fn main() !void {
for (goal_manager.characters.items) |*brain| {
if (brain.action_queue.items.len > 0) continue;
if (brain.isRoutineFinished()) {
if (!try brain.depositItemsToBank(&api)) {
brain.routine = null;
if (brain.isTaskFinished()) {
if (try brain.depositItemsToBank(&api)) {
continue;
}
continue;
brain.task = null;
}
try brain.performRoutine(&api);
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(&api);
}
}
}
}