split up into multiple modules
This commit is contained in:
parent
64c4d9ff47
commit
45e32424cb
28
api/position.zig
Normal file
28
api/position.zig
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Position = @This();
|
||||||
|
|
||||||
|
x: i64,
|
||||||
|
y: i64,
|
||||||
|
|
||||||
|
pub fn init(x: i64, y: i64) Position {
|
||||||
|
return Position{
|
||||||
|
.x = x,
|
||||||
|
.y = y
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn eql(self: Position, other: Position) bool {
|
||||||
|
return self.x == other.x and self.y == other.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn format(
|
||||||
|
self: Position,
|
||||||
|
comptime fmt: []const u8,
|
||||||
|
options: std.fmt.FormatOptions,
|
||||||
|
writer: anytype,
|
||||||
|
) !void {
|
||||||
|
_ = fmt;
|
||||||
|
_ = options;
|
||||||
|
|
||||||
|
try writer.print("{{ {}, {} }}", .{self.x, self.y});
|
||||||
|
}
|
8
api/root.zig
Normal file
8
api/root.zig
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
|
||||||
|
pub const Server = @import("server.zig");
|
||||||
|
pub const Position = @import("position.zig");
|
||||||
|
pub const BoundedSlotsArray = @import("slot_array.zig").BoundedSlotsArray;
|
||||||
|
|
||||||
|
pub const Error = Server.APIError;
|
||||||
|
pub const ItemId = Server.ItemId;
|
||||||
|
pub const ItemIdQuantity = Server.ItemIdQuantity;
|
@ -4,7 +4,7 @@ const json = std.json;
|
|||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
// TODO: Maybe it would be good to move date time parsing to separate module
|
// TODO: Maybe it would be good to move date time parsing to separate module
|
||||||
pub const parseDateTime = @import("../date_time/parse.zig").parseDateTime;
|
pub const parseDateTime = @import("./date_time/parse.zig").parseDateTime;
|
||||||
|
|
||||||
const json_utils = @import("json_utils.zig");
|
const json_utils = @import("json_utils.zig");
|
||||||
pub const Character = @import("character.zig");
|
pub const Character = @import("character.zig");
|
||||||
@ -303,6 +303,13 @@ pub const ItemIdQuantity = struct {
|
|||||||
id: ItemId,
|
id: ItemId,
|
||||||
quantity: u64,
|
quantity: u64,
|
||||||
|
|
||||||
|
pub fn init(id: ItemId, quantity: u64) ItemIdQuantity {
|
||||||
|
return ItemIdQuantity{
|
||||||
|
.id = id,
|
||||||
|
.quantity = quantity
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
pub fn parse(api: *Server, obj: json.ObjectMap) !ItemIdQuantity {
|
pub fn parse(api: *Server, obj: json.ObjectMap) !ItemIdQuantity {
|
||||||
const code = try json_utils.getStringRequired(obj, "code");
|
const code = try json_utils.getStringRequired(obj, "code");
|
||||||
const quantity = try json_utils.getIntegerRequired(obj, "quantity");
|
const quantity = try json_utils.getIntegerRequired(obj, "quantity");
|
||||||
@ -1851,6 +1858,11 @@ pub fn getItem(self: *Server, code: []const u8) APIError!?Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn getItemById(self: *Server, id: ItemId) APIError!?Item {
|
||||||
|
const code = self.getItemCode(id) orelse return null;
|
||||||
|
return self.getItem(code);
|
||||||
|
}
|
||||||
|
|
||||||
pub const ItemOptions = struct {
|
pub const ItemOptions = struct {
|
||||||
craft_material: ?[]const u8 = null,
|
craft_material: ?[]const u8 = null,
|
||||||
craft_skill: ?Skill = null,
|
craft_skill: ?Skill = null,
|
84
build.zig
84
build.zig
@ -1,41 +1,91 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const ResolvedTarget = std.Build.ResolvedTarget;
|
||||||
|
const OptimizeMode = std.Build.OptimizeMode;
|
||||||
|
const Module = std.Build.Module;
|
||||||
|
|
||||||
pub fn build(b: *std.Build) void {
|
pub fn build(b: *std.Build) void {
|
||||||
const target = b.standardTargetOptions(.{});
|
const target = b.standardTargetOptions(.{});
|
||||||
const optimize = b.standardOptimizeOption(.{});
|
const optimize = b.standardOptimizeOption(.{});
|
||||||
|
|
||||||
const exe = b.addExecutable(.{
|
var api: *Module = undefined;
|
||||||
.name = "artificer",
|
{
|
||||||
.root_source_file = b.path("src/main.zig"),
|
api = b.createModule(.{
|
||||||
|
.root_source_file = b.path("api/root.zig"),
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
.link_libc = true,
|
||||||
|
});
|
||||||
|
api.addIncludePath(b.path("api/date_time"));
|
||||||
|
api.addCSourceFile(.{ .file = b.path("api/date_time/timegm.c") });
|
||||||
|
}
|
||||||
|
|
||||||
|
var lib: *Module = undefined;
|
||||||
|
{
|
||||||
|
lib = b.createModule(.{
|
||||||
|
.root_source_file = b.path("lib/root.zig"),
|
||||||
.target = target,
|
.target = target,
|
||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
});
|
});
|
||||||
exe.linkLibC();
|
lib.addImport("artifacts-api", api);
|
||||||
exe.addIncludePath(b.path("src/date_time"));
|
|
||||||
exe.addCSourceFile(.{ .file = b.path("src/date_time/timegm.c") });
|
|
||||||
|
|
||||||
b.installArtifact(exe);
|
const unit_tests = b.addTest(.{
|
||||||
|
.root_source_file = b.path("lib/root.zig"),
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
});
|
||||||
|
|
||||||
const run_cmd = b.addRunArtifact(exe);
|
const run_unit_tests = b.addRunArtifact(unit_tests);
|
||||||
|
const test_step = b.step("test-lib", "Run lib unit tests");
|
||||||
|
test_step.dependOn(&run_unit_tests.step);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const cli = b.addExecutable(.{
|
||||||
|
.name = "artificer",
|
||||||
|
.root_source_file = b.path("cli/main.zig"),
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
});
|
||||||
|
cli.root_module.addImport("artificer", lib);
|
||||||
|
b.installArtifact(cli);
|
||||||
|
|
||||||
|
const run_cmd = b.addRunArtifact(cli);
|
||||||
run_cmd.step.dependOn(b.getInstallStep());
|
run_cmd.step.dependOn(b.getInstallStep());
|
||||||
|
|
||||||
if (b.args) |args| {
|
if (b.args) |args| {
|
||||||
run_cmd.addArgs(args);
|
run_cmd.addArgs(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
const run_step = b.step("run", "Run the app");
|
const run_step = b.step("run-cli", "Run the CLI");
|
||||||
run_step.dependOn(&run_cmd.step);
|
run_step.dependOn(&run_cmd.step);
|
||||||
|
}
|
||||||
|
|
||||||
const exe_unit_tests = b.addTest(.{
|
{
|
||||||
.root_source_file = b.path("src/artifacts.zig"),
|
const raylib_dep = b.dependency("raylib-zig", .{
|
||||||
.target = target,
|
.target = target,
|
||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
});
|
});
|
||||||
exe_unit_tests.linkLibC();
|
|
||||||
exe_unit_tests.addIncludePath(b.path("src"));
|
|
||||||
exe_unit_tests.addCSourceFile(.{ .file = b.path("src/timegm.c") });
|
|
||||||
|
|
||||||
const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);
|
const gui = b.addExecutable(.{
|
||||||
const test_step = b.step("test", "Run unit tests");
|
.name = "artificer-gui",
|
||||||
test_step.dependOn(&run_exe_unit_tests.step);
|
.root_source_file = b.path("gui/main.zig"),
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
});
|
||||||
|
gui.root_module.addImport("artificer", lib);
|
||||||
|
gui.linkLibrary(raylib_dep.artifact("raylib"));
|
||||||
|
gui.root_module.addImport("raylib", raylib_dep.module("raylib"));
|
||||||
|
|
||||||
|
b.installArtifact(gui);
|
||||||
|
|
||||||
|
const run_cmd = b.addRunArtifact(gui);
|
||||||
|
run_cmd.step.dependOn(b.getInstallStep());
|
||||||
|
|
||||||
|
if (b.args) |args| {
|
||||||
|
run_cmd.addArgs(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
const run_step = b.step("run-gui", "Run the GUI");
|
||||||
|
run_step.dependOn(&run_cmd.step);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,11 @@
|
|||||||
.name = "artificer",
|
.name = "artificer",
|
||||||
.version = "0.1.0",
|
.version = "0.1.0",
|
||||||
.minimum_zig_version = "0.12.0",
|
.minimum_zig_version = "0.12.0",
|
||||||
.dependencies = .{ },
|
.dependencies = .{
|
||||||
|
.@"raylib-zig" = .{
|
||||||
|
.url = "https://github.com/Not-Nik/raylib-zig/archive/43d15b05c2b97cab30103fa2b46cff26e91619ec.tar.gz",
|
||||||
|
.hash = "12204a223b19043e17b79300413d02f60fc8004c0d9629b8d8072831e352a78bf212"
|
||||||
|
},
|
||||||
|
},
|
||||||
.paths = .{ "" },
|
.paths = .{ "" },
|
||||||
}
|
}
|
||||||
|
42
cli/main.zig
Normal file
42
cli/main.zig
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Artificer = @import("artificer");
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
|
fn getAPITokenFromArgs(allocator: Allocator) !?[]u8 {
|
||||||
|
const args = try std.process.argsAlloc(allocator);
|
||||||
|
defer std.process.argsFree(allocator, args);
|
||||||
|
|
||||||
|
if (args.len < 2) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const filename = args[1];
|
||||||
|
const cwd = std.fs.cwd();
|
||||||
|
var token_buffer: [256]u8 = undefined;
|
||||||
|
const token = try cwd.readFile(filename, &token_buffer);
|
||||||
|
|
||||||
|
return try allocator.dupe(u8, std.mem.trim(u8,token,"\n\t "));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn main() !void {
|
||||||
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||||
|
defer _ = gpa.deinit();
|
||||||
|
const allocator = gpa.allocator();
|
||||||
|
|
||||||
|
const token = (try getAPITokenFromArgs(allocator)) orelse return error.MissingToken;
|
||||||
|
defer allocator.free(token);
|
||||||
|
|
||||||
|
var artificer = try Artificer.init(allocator, token);
|
||||||
|
defer artificer.deinit();
|
||||||
|
|
||||||
|
std.log.info("Starting main loop", .{});
|
||||||
|
while (true) {
|
||||||
|
const waitUntil = artificer.nextStepAt();
|
||||||
|
const duration = waitUntil - std.time.timestamp();
|
||||||
|
if (duration > 0) {
|
||||||
|
std.time.sleep(@intCast(duration));
|
||||||
|
}
|
||||||
|
|
||||||
|
try artificer.step();
|
||||||
|
}
|
||||||
|
}
|
18
gui/main.zig
Normal file
18
gui/main.zig
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
const rl = @import("raylib");
|
||||||
|
|
||||||
|
pub fn main() anyerror!void {
|
||||||
|
rl.initWindow(800, 450, "Artificer");
|
||||||
|
defer rl.closeWindow();
|
||||||
|
|
||||||
|
rl.setTargetFPS(60);
|
||||||
|
|
||||||
|
while (!rl.windowShouldClose()) {
|
||||||
|
|
||||||
|
rl.beginDrawing();
|
||||||
|
defer rl.endDrawing();
|
||||||
|
|
||||||
|
rl.clearBackground(rl.Color.white);
|
||||||
|
|
||||||
|
rl.drawText("Congrats! You created your first window!", 190, 200, 20, rl.Color.light_gray);
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,12 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Server = @import("./api/server.zig");
|
const ArtifactsAPI = @import("artifacts-api");
|
||||||
|
const Server = ArtifactsAPI.Server;
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const Position = Server.Position;
|
const Position = Server.Position;
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
|
|
||||||
|
const CharacterTask = @import("./task.zig").Task;
|
||||||
|
|
||||||
const CharacterBrain = @This();
|
const CharacterBrain = @This();
|
||||||
|
|
||||||
const bank_position: Position = .{ .x = 4, .y = 1 }; // TODO: Figure this out dynamically
|
const bank_position: Position = .{ .x = 4, .y = 1 }; // TODO: Figure this out dynamically
|
||||||
@ -98,38 +101,6 @@ comptime {
|
|||||||
assert(@typeInfo(QueuedAction).Union.fields.len == @typeInfo(QueuedActionResult).Union.fields.len);
|
assert(@typeInfo(QueuedAction).Union.fields.len == @typeInfo(QueuedActionResult).Union.fields.len);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const CharacterTask = union(enum) {
|
|
||||||
fight: struct {
|
|
||||||
at: Position,
|
|
||||||
target: Server.ItemIdQuantity,
|
|
||||||
progress: u64 = 0,
|
|
||||||
},
|
|
||||||
gather: struct {
|
|
||||||
at: Position,
|
|
||||||
target: Server.ItemIdQuantity,
|
|
||||||
progress: u64 = 0,
|
|
||||||
},
|
|
||||||
craft: struct {
|
|
||||||
at: Position,
|
|
||||||
target: Server.ItemIdQuantity,
|
|
||||||
progress: u64 = 0,
|
|
||||||
},
|
|
||||||
|
|
||||||
pub fn isComplete(self: CharacterTask) bool {
|
|
||||||
return switch (self) {
|
|
||||||
.fight => |args| {
|
|
||||||
return args.progress >= args.target.quantity;
|
|
||||||
},
|
|
||||||
.gather => |args| {
|
|
||||||
return args.progress >= args.target.quantity;
|
|
||||||
},
|
|
||||||
.craft => |args| {
|
|
||||||
return args.progress >= args.target.quantity;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
action_queue: std.ArrayList(QueuedAction),
|
action_queue: std.ArrayList(QueuedAction),
|
||||||
task: ?*CharacterTask = null,
|
task: ?*CharacterTask = null,
|
||||||
@ -397,7 +368,7 @@ fn onActionCompleted(self: *CharacterBrain, result: QueuedActionResult) void {
|
|||||||
const fight_result: Server.FightResult = r;
|
const fight_result: Server.FightResult = r;
|
||||||
const drops = fight_result.fight.drops;
|
const drops = fight_result.fight.drops;
|
||||||
|
|
||||||
args.progress += drops.getQuantity(args.target.id);
|
args.progress += drops.getQuantity(args.until.item.id);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
.gather => |*args| {
|
.gather => |*args| {
|
||||||
@ -405,7 +376,7 @@ fn onActionCompleted(self: *CharacterBrain, result: QueuedActionResult) void {
|
|||||||
const gather_resutl: Server.GatherResult = r;
|
const gather_resutl: Server.GatherResult = r;
|
||||||
const items = gather_resutl.details.items;
|
const items = gather_resutl.details.items;
|
||||||
|
|
||||||
args.progress += items.getQuantity(args.target.id);
|
args.progress += items.getQuantity(args.until.item.id);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
.craft => |*args| {
|
.craft => |*args| {
|
134
lib/root.zig
Normal file
134
lib/root.zig
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Api = @import("artifacts-api");
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
|
const CharacterBrain = @import("./brain.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(CharacterBrain),
|
||||||
|
|
||||||
|
paused_until: ?i64 = null,
|
||||||
|
|
||||||
|
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(CharacterBrain).init(allocator);
|
||||||
|
defer characters.deinit();
|
||||||
|
// TODO: Add character deinit
|
||||||
|
|
||||||
|
// const chars = try api.listMyCharacters();
|
||||||
|
// defer chars.deinit();
|
||||||
|
|
||||||
|
// for (chars.items) |char| {
|
||||||
|
// try scheduler.addCharacter(char.name);
|
||||||
|
// }
|
||||||
|
|
||||||
|
return Artificer{
|
||||||
|
.server = server,
|
||||||
|
.characters = characters
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: Artificer) void {
|
||||||
|
for (self.characters.items) |brain| {
|
||||||
|
brain.deinit();
|
||||||
|
}
|
||||||
|
self.characters.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn step(self: *Artificer) !void {
|
||||||
|
if (self.paused_until) |paused_until| {
|
||||||
|
if (std.time.timestamp() < paused_until) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.paused_until = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
runNextActions(self.characters.items, &self.server) catch |err| switch (err) {
|
||||||
|
Api.Error.ServerUnavailable => {
|
||||||
|
self.paused_until = std.time.timestamp() + std.time.ns_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.action_queue.items.len > 0) continue;
|
||||||
|
|
||||||
|
if (brain.isTaskFinished()) {
|
||||||
|
if (try brain.depositItemsToBank(&self.server)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
brain.task = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
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(&self.server);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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: []CharacterBrain, api: *Api.Server) ?i64 {
|
||||||
|
var earliest_cooldown: ?i64 = null;
|
||||||
|
for (characters) |*brain| {
|
||||||
|
if (brain.action_queue.items.len == 0) continue;
|
||||||
|
|
||||||
|
const character = api.findCharacter(brain.name).?;
|
||||||
|
const cooldown: i64 = @intFromFloat(character.cooldown_expiration * std.time.ns_per_s);
|
||||||
|
|
||||||
|
if (earliest_cooldown == null or earliest_cooldown.? > cooldown) {
|
||||||
|
earliest_cooldown = cooldown;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return earliest_cooldown;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn runNextActions(characters: []CharacterBrain, api: *Api.Server) !void {
|
||||||
|
const maybe_earliest_cooldown = earliestCooldown(characters, api);
|
||||||
|
if (maybe_earliest_cooldown == null) return;
|
||||||
|
|
||||||
|
const earliest_cooldown = maybe_earliest_cooldown.?;
|
||||||
|
if (earliest_cooldown < std.time.timestamp()) return;
|
||||||
|
|
||||||
|
for (characters) |*brain| {
|
||||||
|
if (brain.action_queue.items.len == 0) continue;
|
||||||
|
|
||||||
|
const character = api.findCharacter(brain.name).?;
|
||||||
|
const cooldown: u64 = @intFromFloat(character.cooldown_expiration * std.time.ns_per_s);
|
||||||
|
|
||||||
|
if (earliest_cooldown > cooldown) {
|
||||||
|
try brain.performNextAction(api);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn currentTime() f64 {
|
||||||
|
const timestamp: f64 = @floatFromInt(std.time.milliTimestamp());
|
||||||
|
return timestamp / std.time.ms_per_s;
|
||||||
|
}
|
39
lib/task.zig
Normal file
39
lib/task.zig
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
const Api = @import("artifacts-api");
|
||||||
|
const Position = Api.Position;
|
||||||
|
|
||||||
|
pub const UntilCondition = union(enum) {
|
||||||
|
xp: u64,
|
||||||
|
item: Api.ItemIdQuantity,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Task = union(enum) {
|
||||||
|
fight: struct {
|
||||||
|
at: Position,
|
||||||
|
until: UntilCondition,
|
||||||
|
progress: u64 = 0,
|
||||||
|
},
|
||||||
|
gather: struct {
|
||||||
|
at: Position,
|
||||||
|
until: UntilCondition,
|
||||||
|
progress: u64 = 0,
|
||||||
|
},
|
||||||
|
craft: struct {
|
||||||
|
at: Position,
|
||||||
|
target: Api.ItemIdQuantity,
|
||||||
|
progress: u64 = 0,
|
||||||
|
},
|
||||||
|
|
||||||
|
pub fn isComplete(self: Task) bool {
|
||||||
|
return switch (self) {
|
||||||
|
.fight => |args| {
|
||||||
|
return args.progress >= args.until.item.quantity;
|
||||||
|
},
|
||||||
|
.gather => |args| {
|
||||||
|
return args.progress >= args.until.item.quantity;
|
||||||
|
},
|
||||||
|
.craft => |args| {
|
||||||
|
return args.progress >= args.target.quantity;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
234
lib/task_graph.zig
Normal file
234
lib/task_graph.zig
Normal file
@ -0,0 +1,234 @@
|
|||||||
|
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});
|
||||||
|
}
|
||||||
|
}
|
@ -1,15 +0,0 @@
|
|||||||
const Position = @This();
|
|
||||||
|
|
||||||
x: i64,
|
|
||||||
y: i64,
|
|
||||||
|
|
||||||
pub fn init(x: i64, y: i64) Position {
|
|
||||||
return Position{
|
|
||||||
.x = x,
|
|
||||||
.y = y
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn eql(self: Position, other: Position) bool {
|
|
||||||
return self.x == other.x and self.y == other.y;
|
|
||||||
}
|
|
374
src/main.zig
374
src/main.zig
@ -1,374 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const Allocator = std.mem.Allocator;
|
|
||||||
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 };
|
|
||||||
|
|
||||||
fn todo() void {
|
|
||||||
unreachable;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn currentTime() f64 {
|
|
||||||
const timestamp: f64 = @floatFromInt(std.time.milliTimestamp());
|
|
||||||
return timestamp / std.time.ms_per_s;
|
|
||||||
}
|
|
||||||
|
|
||||||
const GoalManager = struct {
|
|
||||||
api: *Server,
|
|
||||||
allocator: Allocator,
|
|
||||||
characters: std.ArrayList(CharacterBrain),
|
|
||||||
expiration_margin: f64 = 0.1, // 100ms
|
|
||||||
|
|
||||||
fn init(api: *Server, allocator: Allocator) GoalManager {
|
|
||||||
return GoalManager{
|
|
||||||
.api = api,
|
|
||||||
.allocator = allocator,
|
|
||||||
.characters = std.ArrayList(CharacterBrain).init(allocator)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn addCharacter(self: *GoalManager, name: []const u8) !void {
|
|
||||||
const character = try CharacterBrain.init(self.allocator, name);
|
|
||||||
try self.characters.append(character);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deinit(self: GoalManager) void {
|
|
||||||
for (self.characters.items) |brain| {
|
|
||||||
brain.deinit();
|
|
||||||
}
|
|
||||||
self.characters.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn runNextAction(self: *GoalManager) !void {
|
|
||||||
var earliest_cooldown: f64 = 0;
|
|
||||||
var earliest_character: ?*CharacterBrain = null;
|
|
||||||
for (self.characters.items) |*brain| {
|
|
||||||
if (brain.action_queue.items.len == 0) continue;
|
|
||||||
|
|
||||||
const character = self.api.findCharacter(brain.name).?;
|
|
||||||
if (earliest_character == null or earliest_cooldown > character.cooldown_expiration) {
|
|
||||||
earliest_character = brain;
|
|
||||||
earliest_cooldown = character.cooldown_expiration;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (earliest_character == null) return;
|
|
||||||
|
|
||||||
const now = currentTime();
|
|
||||||
if (earliest_cooldown > now) {
|
|
||||||
const duration_s = earliest_cooldown - now + self.expiration_margin;
|
|
||||||
const duration_ms: u64 = @intFromFloat(@trunc(duration_s * std.time.ms_per_s));
|
|
||||||
std.log.debug("waiting for {d:.3}s", .{duration_s});
|
|
||||||
std.time.sleep(std.time.ns_per_ms * duration_ms);
|
|
||||||
}
|
|
||||||
|
|
||||||
try earliest_character.?.performNextAction(self.api);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
fn getAPITokenFromArgs(allocator: Allocator) !?[]u8 {
|
|
||||||
const args = try std.process.argsAlloc(allocator);
|
|
||||||
defer std.process.argsFree(allocator, args);
|
|
||||||
|
|
||||||
if (args.len < 2) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const filename = args[1];
|
|
||||||
const cwd = std.fs.cwd();
|
|
||||||
var token_buffer: [256]u8 = undefined;
|
|
||||||
const token = try cwd.readFile(filename, &token_buffer);
|
|
||||||
|
|
||||||
return try allocator.dupe(u8, std.mem.trim(u8,token,"\n\t "));
|
|
||||||
}
|
|
||||||
|
|
||||||
const TaskTree = struct {
|
|
||||||
const TaskTreeNode = struct {
|
|
||||||
parent: ?usize,
|
|
||||||
character_task: CharacterBrain.CharacterTask,
|
|
||||||
additional_items: BoundedSlotsArray(8) = BoundedSlotsArray(8).init(),
|
|
||||||
};
|
|
||||||
|
|
||||||
const Nodes = std.ArrayList(TaskTreeNode);
|
|
||||||
|
|
||||||
allocator: Allocator,
|
|
||||||
nodes: Nodes,
|
|
||||||
api: *Server,
|
|
||||||
|
|
||||||
pub fn init(allocator: Allocator, api: *Server) TaskTree {
|
|
||||||
return TaskTree{
|
|
||||||
.allocator = allocator,
|
|
||||||
.nodes = Nodes.init(allocator),
|
|
||||||
.api = api
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: TaskTree) void {
|
|
||||||
self.nodes.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn appendNode(self: *TaskTree, node: TaskTreeNode) !usize {
|
|
||||||
try self.nodes.append(node);
|
|
||||||
return self.nodes.items.len-1;
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
const eql = std.mem.eql;
|
|
||||||
if (item.craft) |recipe| {
|
|
||||||
const craft_count = std.math.divCeil(u64, quantity, recipe.quantity) catch unreachable;
|
|
||||||
|
|
||||||
const skill_str = 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", .{});
|
|
||||||
|
|
||||||
const node_id = try self.appendNode(TaskTreeNode{
|
|
||||||
.parent = parent,
|
|
||||||
.character_task = .{
|
|
||||||
.craft = .{
|
|
||||||
.at = workshop_maps.items[0].position,
|
|
||||||
.target = .{ .id = item_id, .quantity = craft_count }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
for (recipe.items.slots.constSlice()) |recipe_item| {
|
|
||||||
const recipe_item_code = self.api.getItemCode(recipe_item.id).?;
|
|
||||||
const needed_quantity = recipe_item.quantity * craft_count;
|
|
||||||
const subtree_id = try self.appendSubTree(recipe_item_code, needed_quantity, node_id);
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
var found_nodes = std.ArrayList(usize).init(self.allocator);
|
|
||||||
for (0.., self.nodes.items) |i, node| {
|
|
||||||
if (node.parent == parent) {
|
|
||||||
try found_nodes.append(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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,
|
|
||||||
options: std.fmt.FormatOptions,
|
|
||||||
writer: anytype,
|
|
||||||
) !void {
|
|
||||||
_ = fmt;
|
|
||||||
_ = options;
|
|
||||||
|
|
||||||
var root_nodes = try self.listByParent(null);
|
|
||||||
defer root_nodes.deinit();
|
|
||||||
|
|
||||||
for (root_nodes.items) |root_node| {
|
|
||||||
try self.formatNode(root_node, 0, writer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn formatNode(self: TaskTree, node_id: usize, level: u32, writer: anytype) !void {
|
|
||||||
const node = self.nodes.items[node_id];
|
|
||||||
try writer.writeBytesNTimes(" ", level);
|
|
||||||
switch (node.character_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});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn main() !void {
|
|
||||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
|
||||||
defer _ = gpa.deinit();
|
|
||||||
const allocator = gpa.allocator();
|
|
||||||
|
|
||||||
var api = try Server.init(allocator);
|
|
||||||
defer api.deinit();
|
|
||||||
|
|
||||||
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();
|
|
||||||
|
|
||||||
const chars = try api.listMyCharacters();
|
|
||||||
defer chars.deinit();
|
|
||||||
|
|
||||||
for (chars.items) |char| {
|
|
||||||
try goal_manager.addCharacter(char.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
var task_tree = TaskTree.init(allocator, &api);
|
|
||||||
defer task_tree.deinit();
|
|
||||||
|
|
||||||
// _ = try task_tree.appendSubTree("wooden_staff", 5, null);
|
|
||||||
_ = try task_tree.appendSubTree("wooden_shield", 5, null);
|
|
||||||
|
|
||||||
std.debug.print("{}", .{task_tree});
|
|
||||||
|
|
||||||
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) {
|
|
||||||
Server.APIError.ServerUnavailable => {
|
|
||||||
// If the server is down, wait for a moment and try again.
|
|
||||||
std.time.sleep(std.time.ns_per_min * 5);
|
|
||||||
continue;
|
|
||||||
},
|
|
||||||
|
|
||||||
// TODO: Log all other error to a file or something. So it could be review later on.
|
|
||||||
else => return err
|
|
||||||
};
|
|
||||||
|
|
||||||
for (goal_manager.characters.items) |*brain| {
|
|
||||||
if (brain.action_queue.items.len > 0) continue;
|
|
||||||
|
|
||||||
if (brain.isTaskFinished()) {
|
|
||||||
if (try brain.depositItemsToBank(&api)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
brain.task = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
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