add automatic gathering of materials for recipe
This commit is contained in:
parent
7d572e7064
commit
64c4d9ff47
@ -1088,7 +1088,7 @@ fn fetch(self: *Server, options: FetchOptions) APIError!ArtifactsFetchResult {
|
|||||||
|
|
||||||
if (options.paginated) {
|
if (options.paginated) {
|
||||||
const total_pages_i64 = json_utils.getInteger(parsed.object, "pages") orelse return APIError.ParseFailed;
|
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);
|
total_pages = @intCast(total_pages_i64);
|
||||||
|
|
||||||
const page_results = json_utils.getArray(parsed.object, "data") orelse return APIError.ParseFailed;
|
const page_results = json_utils.getArray(parsed.object, "data") orelse return APIError.ParseFailed;
|
||||||
|
@ -13,7 +13,7 @@ pub fn BoundedSlotsArray(comptime slot_count: u32) type {
|
|||||||
return struct {
|
return struct {
|
||||||
slots: Slots,
|
slots: Slots,
|
||||||
|
|
||||||
fn init() @This() {
|
pub fn init() @This() {
|
||||||
return @This(){
|
return @This(){
|
||||||
.slots = Slots.init(0) catch unreachable
|
.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| {
|
if (self.findSlot(id)) |slot| {
|
||||||
slot.quantity += quantity;
|
slot.quantity += quantity;
|
||||||
} else {
|
} else {
|
||||||
var empty_slot: ?*Slot = null;
|
try self.slots.append(Slot{ .id = id, .quantity = quantity });
|
||||||
for (&self.slots) |*slot| {
|
|
||||||
if (slot.id == null) {
|
|
||||||
empty_slot = slot;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(empty_slot != null);
|
|
||||||
empty_slot.?.id = id;
|
|
||||||
empty_slot.?.quantity = quantity;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,7 +132,7 @@ pub const CharacterTask = union(enum) {
|
|||||||
|
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
action_queue: std.ArrayList(QueuedAction),
|
action_queue: std.ArrayList(QueuedAction),
|
||||||
task: ?CharacterTask = null,
|
task: ?*CharacterTask = null,
|
||||||
|
|
||||||
pub fn init(allocator: Allocator, name: []const u8) !CharacterBrain {
|
pub fn init(allocator: Allocator, name: []const u8) !CharacterBrain {
|
||||||
return CharacterBrain{
|
return CharacterBrain{
|
||||||
@ -391,7 +391,7 @@ pub fn performNextAction(self: *CharacterBrain, api: *Server) !void {
|
|||||||
fn onActionCompleted(self: *CharacterBrain, result: QueuedActionResult) void {
|
fn onActionCompleted(self: *CharacterBrain, result: QueuedActionResult) void {
|
||||||
if (self.task == null) return;
|
if (self.task == null) return;
|
||||||
|
|
||||||
switch (self.task.?) {
|
switch (self.task.?.*) {
|
||||||
.fight => |*args| {
|
.fight => |*args| {
|
||||||
if (result.get(.fight)) |r| {
|
if (result.get(.fight)) |r| {
|
||||||
const fight_result: Server.FightResult = r;
|
const fight_result: Server.FightResult = r;
|
||||||
@ -433,7 +433,7 @@ pub fn performTask(self: *CharacterBrain, api: *Server) !void {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (self.task.?) {
|
switch (self.task.?.*) {
|
||||||
.fight => |args| {
|
.fight => |args| {
|
||||||
try self.fightRoutine(api, args.at);
|
try self.fightRoutine(api, args.at);
|
||||||
},
|
},
|
||||||
|
238
src/main.zig
238
src/main.zig
@ -5,6 +5,7 @@ const assert = std.debug.assert;
|
|||||||
const Position = @import("./api/position.zig");
|
const Position = @import("./api/position.zig");
|
||||||
const Server = @import("./api/server.zig");
|
const Server = @import("./api/server.zig");
|
||||||
const CharacterBrain = @import("./character_brain.zig");
|
const CharacterBrain = @import("./character_brain.zig");
|
||||||
|
const BoundedSlotsArray = @import("./api/slot_array.zig").BoundedSlotsArray;
|
||||||
|
|
||||||
// pub const std_options = .{ .log_level = .debug };
|
// pub const std_options = .{ .log_level = .debug };
|
||||||
|
|
||||||
@ -90,6 +91,7 @@ const TaskTree = struct {
|
|||||||
const TaskTreeNode = struct {
|
const TaskTreeNode = struct {
|
||||||
parent: ?usize,
|
parent: ?usize,
|
||||||
character_task: CharacterBrain.CharacterTask,
|
character_task: CharacterBrain.CharacterTask,
|
||||||
|
additional_items: BoundedSlotsArray(8) = BoundedSlotsArray(8).init(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const Nodes = std.ArrayList(TaskTreeNode);
|
const Nodes = std.ArrayList(TaskTreeNode);
|
||||||
@ -115,8 +117,8 @@ const TaskTree = struct {
|
|||||||
return self.nodes.items.len-1;
|
return self.nodes.items.len-1;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn appendSubTree(self: *TaskTree, code: []const u8, quantity: u64, parent: ?usize) !void {
|
fn appendSubTree(self: *TaskTree, code: []const u8, quantity: u64, parent: ?usize) !?usize {
|
||||||
if (quantity == 0) return;
|
if (quantity == 0) return null;
|
||||||
|
|
||||||
const item = (try self.api.getItem(code)).?;
|
const item = (try self.api.getItem(code)).?;
|
||||||
const item_id = try self.api.getItemId(code);
|
const item_id = try self.api.getItemId(code);
|
||||||
@ -144,62 +146,72 @@ const TaskTree = struct {
|
|||||||
|
|
||||||
for (recipe.items.slots.constSlice()) |recipe_item| {
|
for (recipe.items.slots.constSlice()) |recipe_item| {
|
||||||
const recipe_item_code = self.api.getItemCode(recipe_item.id).?;
|
const recipe_item_code = self.api.getItemCode(recipe_item.id).?;
|
||||||
try self.appendSubTree(recipe_item_code, recipe_item.quantity * craft_count, node_id);
|
const needed_quantity = recipe_item.quantity * craft_count;
|
||||||
}
|
const subtree_id = try self.appendSubTree(recipe_item_code, needed_quantity, node_id);
|
||||||
} else {
|
|
||||||
if (item.type == .resource) {
|
|
||||||
if (eql(u8, item.subtype, "mining")) {
|
|
||||||
const resources = try self.api.getResources(.{ .drop = code });
|
|
||||||
defer resources.deinit();
|
|
||||||
|
|
||||||
if (resources.items.len == 0) return error.ResourceNotFound;
|
// The target item can not be currently produced.
|
||||||
if (resources.items.len > 1) std.log.warn("Multiple resources exist for target item", .{});
|
if (subtree_id == null) {
|
||||||
const resource_code = resources.items[0].code;
|
var node = &self.nodes.items[node_id];
|
||||||
|
try node.additional_items.add(recipe_item.id, needed_quantity);
|
||||||
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 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} 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) {
|
fn listByParent(self: *const TaskTree, parent: ?usize) !std.ArrayList(usize) {
|
||||||
@ -212,6 +224,31 @@ const TaskTree = struct {
|
|||||||
return found_nodes;
|
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(
|
pub fn format(
|
||||||
self: TaskTree,
|
self: TaskTree,
|
||||||
comptime fmt: []const u8,
|
comptime fmt: []const u8,
|
||||||
@ -253,6 +290,11 @@ const TaskTree = struct {
|
|||||||
for (child_nodes.items) |child_node| {
|
for (child_nodes.items) |child_node| {
|
||||||
try self.formatNode(child_node, level + 1, writer);
|
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);
|
var api = try Server.init(allocator);
|
||||||
defer api.deinit();
|
defer api.deinit();
|
||||||
|
|
||||||
try api.prefetch();
|
|
||||||
|
|
||||||
const token = (try getAPITokenFromArgs(allocator)) orelse return error.MissingToken;
|
const token = (try getAPITokenFromArgs(allocator)) orelse return error.MissingToken;
|
||||||
defer allocator.free(token);
|
defer allocator.free(token);
|
||||||
|
|
||||||
try api.setToken(token);
|
try api.setToken(token);
|
||||||
|
|
||||||
|
std.log.info("prefetching static data", .{});
|
||||||
|
try api.prefetch();
|
||||||
|
|
||||||
var goal_manager = GoalManager.init(&api, allocator);
|
var goal_manager = GoalManager.init(&api, allocator);
|
||||||
defer goal_manager.deinit();
|
defer goal_manager.deinit();
|
||||||
|
|
||||||
@ -281,69 +324,17 @@ pub fn main() !void {
|
|||||||
try goal_manager.addCharacter(char.name);
|
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);
|
var task_tree = TaskTree.init(allocator, &api);
|
||||||
defer task_tree.deinit();
|
defer task_tree.deinit();
|
||||||
|
|
||||||
try task_tree.appendSubTree("sticky_dagger", 5, null);
|
// _ = try task_tree.appendSubTree("wooden_staff", 5, null);
|
||||||
// try task_tree.appendSubTree("copper_boots" , 5, null);
|
_ = try task_tree.appendSubTree("wooden_shield", 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);
|
|
||||||
|
|
||||||
std.debug.print("{}", .{task_tree});
|
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", .{});
|
std.log.info("Starting main loop", .{});
|
||||||
while (true) {
|
while (true) {
|
||||||
goal_manager.runNextAction() catch |err| switch (err) {
|
goal_manager.runNextAction() catch |err| switch (err) {
|
||||||
@ -360,15 +351,24 @@ pub fn main() !void {
|
|||||||
for (goal_manager.characters.items) |*brain| {
|
for (goal_manager.characters.items) |*brain| {
|
||||||
if (brain.action_queue.items.len > 0) continue;
|
if (brain.action_queue.items.len > 0) continue;
|
||||||
|
|
||||||
if (brain.isRoutineFinished()) {
|
if (brain.isTaskFinished()) {
|
||||||
if (!try brain.depositItemsToBank(&api)) {
|
if (try brain.depositItemsToBank(&api)) {
|
||||||
brain.routine = null;
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user