From 0ffe638691b6f91f1c00e6d123d07bda57b595ad Mon Sep 17 00:00:00 2001 From: Rokas Puzonas Date: Thu, 8 Aug 2024 01:57:56 +0300 Subject: [PATCH] add gathering of resources --- build.zig | 5 +- src/artifacts.zig | 65 ++++++++++++++++++--- src/main.zig | 141 +++++++++++++++++++++++++++++++++------------- 3 files changed, 164 insertions(+), 47 deletions(-) diff --git a/build.zig b/build.zig index bd55c76..e46a5e7 100644 --- a/build.zig +++ b/build.zig @@ -27,10 +27,13 @@ pub fn build(b: *std.Build) void { run_step.dependOn(&run_cmd.step); const exe_unit_tests = b.addTest(.{ - .root_source_file = b.path("src/main.zig"), + .root_source_file = b.path("src/artifacts.zig"), .target = target, .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 test_step = b.step("test", "Run unit tests"); diff --git a/src/artifacts.zig b/src/artifacts.zig index 04710f4..7666d28 100644 --- a/src/artifacts.zig +++ b/src/artifacts.zig @@ -208,23 +208,60 @@ pub const FightResult = struct { }; cooldown: Cooldown, - character: Character, fight: Details, - fn parse(api: *ArtifactsAPI, obj: json.ObjectMap, allocator: Allocator) !FightResult { + fn parse(api: *ArtifactsAPI, obj: json.ObjectMap) !FightResult { const cooldown = json_utils.getObject(obj, "cooldown") orelse return error.MissingProperty; - const character = json_utils.getObject(obj, "character") orelse return error.MissingProperty; const fight = json_utils.getObject(obj, "fight") orelse return error.MissingProperty; return FightResult{ .cooldown = try Cooldown.parse(cooldown), - .character = try Character.parse(character, allocator, api), .fight = try Details.parse(api, fight) }; } +}; - pub fn deinit(self: *FightResult) void { - self.character.deinit(); +pub const GatheringResult = struct { + const Details = struct { + const Item = struct { + id: ItemId, + quantity: i64 + }; + + xp: i64, + items: std.BoundedArray(Item, 8), + + fn parse(api: *ArtifactsAPI, obj: json.ObjectMap) !Details { + var items = std.BoundedArray(Item, 8).init(0) catch unreachable; + const items_obj = json_utils.getArray(obj, "items") orelse return error.MissingProperty; + for (items_obj.items) |item_value| { + const item_obj = json_utils.asObject(item_value) orelse return error.MissingProperty; + const code = try json_utils.getStringRequired(item_obj, "code"); + + try items.append(Item{ + .id = try api.getItemId(code), + .quantity = try json_utils.getIntegerRequired(item_obj, "quantity") + }); + } + + return Details{ + .xp = try json_utils.getIntegerRequired(obj, "xp"), + .items = items, + }; + } + }; + + cooldown: Cooldown, + details: Details, + + fn parse(api: *ArtifactsAPI, obj: json.ObjectMap) !GatheringResult { + const cooldown = json_utils.getObject(obj, "cooldown") orelse return error.MissingProperty; + const details = json_utils.getObject(obj, "details") orelse return error.MissingProperty; + + return GatheringResult{ + .cooldown = try Cooldown.parse(cooldown), + .details = try Details.parse(api, details) + }; } }; @@ -315,6 +352,7 @@ pub fn deinit(self: *ArtifactsAPI) void { } fn fetch(self: *ArtifactsAPI, method: std.http.Method, path: []const u8, payload: ?[]const u8) !ArtifactsFetchResult { + std.log.debug("fetch {} {s}", .{method, path}); var uri = self.server_uri; uri.path = .{ .raw = path }; @@ -341,6 +379,8 @@ fn fetch(self: *ArtifactsAPI, method: std.http.Method, path: []const u8, payload const result = try self.client.fetch(opts); const response_body = response_storage.items; + std.log.debug("fetch result {}", .{result.status}); + if (result.status != .ok) { return ArtifactsFetchResult{ .arena = arena, @@ -476,7 +516,14 @@ pub fn actionFight(self: *ArtifactsAPI, name: []const u8) !FightResult { const path = try std.fmt.allocPrint(self.allocator, "/my/{s}/action/fight", .{name}); defer self.allocator.free(path); - return try self.fetchAndParseObject(FightResult, .POST, path, null, .{ self.allocator }); + return try self.fetchAndParseObject(FightResult, .POST, path, null, .{ }); +} + +pub fn actionGathering(self: *ArtifactsAPI, name: []const u8) !GatheringResult { + const path = try std.fmt.allocPrint(self.allocator, "/my/{s}/action/gathering", .{name}); + defer self.allocator.free(path); + + return try self.fetchAndParseObject(GatheringResult, .POST, path, null, .{ }); } pub fn actionMove(self: *ArtifactsAPI, name: []const u8, x: i64, y: i64) !MoveResult { @@ -508,3 +555,7 @@ pub fn actionBankDepositItem(self: *ArtifactsAPI, name: []const u8, code: []cons return try self.fetchAndParseObject(ItemTransactionResult, .POST, path, payload, .{}); } + +test "parse date time" { + try std.testing.expectEqual(1723069394.105, parseDateTime("2024-08-07T22:23:14.105Z").?); +} diff --git a/src/main.zig b/src/main.zig index 3212b9c..f718707 100644 --- a/src/main.zig +++ b/src/main.zig @@ -3,6 +3,8 @@ const assert = std.debug.assert; const ArtifactsAPI = @import("artifacts.zig"); const Allocator = std.mem.Allocator; +// pub const std_options = .{ .log_level = .debug }; + const Position = struct { x: i64, y: i64, @@ -22,13 +24,16 @@ const Position = struct { const QueuedAction = union(enum) { move: Position, attack, + gathering, depositGold: u64, depositItem: struct { id: ArtifactsAPI.ItemId, quantity: u64 }, }; +const ActionQueue = std.ArrayList(QueuedAction); + const ManagedCharacter = struct { character: ArtifactsAPI.Character, - action_queue: std.ArrayList(QueuedAction), + action_queue: ActionQueue, cooldown_expires_at: f64, }; @@ -72,12 +77,14 @@ const Manager = struct { const duration_s = earliest_expiration - now; 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 + 100)); + std.time.sleep(std.time.ns_per_ms * duration_ms); } + const cooldown_margin = 0.1; // 100ms + now = currentTime(); for (self.characters.items) |*managed_character| { - if (now >= managed_character.cooldown_expires_at) { + if (now - managed_character.cooldown_expires_at >= -cooldown_margin) { return managed_character; } } @@ -93,6 +100,81 @@ const Manager = struct { } }; +fn depositIfFull(managed_char: *ManagedCharacter) !bool { + const character = managed_char.character; + const action_queue = &managed_char.action_queue; + + // Deposit items and gold to bank if full + if (character.getItemCount() < character.inventory_max_items) { + return false; + } + + var character_pos = Position.init(character.x, character.y); + const bank_pos = Position{ .x = 4, .y = 1 }; + + if (!character_pos.eql(bank_pos)) { + try action_queue.append(.{ .move = bank_pos }); + } + + for (character.inventory.slots) |slot| { + if (slot.quantity == 0) continue; + + if (slot.id) |item_id| { + try action_queue.append(.{ + .depositItem = .{ .id = item_id, .quantity = @intCast(slot.quantity) } + }); + } + } + + if (character.gold > 0) { + try action_queue.append(.{ .depositGold = @intCast(character.gold) }); + } + + return true; +} + +fn attackChickenRoutine(managed_char: *ManagedCharacter) !void { + const character = managed_char.character; + const action_queue = &managed_char.action_queue; + + const chicken_pos = Position{ .x = 0, .y = 1 }; + + var character_pos = Position.init(character.x, character.y); + + // Deposit items and gold to bank if full + if (try depositIfFull(managed_char)) { + return; + } + + // Go to chickens + if (!character_pos.eql(chicken_pos)) { + try action_queue.append(.{ .move = chicken_pos }); + return; + } + + // Attack chickens + try action_queue.append(.{ .attack = {} }); +} + +fn gatherResourceRoutine(managed_char: *ManagedCharacter, resource_pos: Position) !void { + const character = managed_char.character; + const action_queue = &managed_char.action_queue; + + var character_pos = Position.init(character.x, character.y); + + // Deposit items and gold to bank if full + if (try depositIfFull(managed_char)) { + return; + } + + if (!character_pos.eql(resource_pos)) { + try action_queue.append(.{ .move = resource_pos }); + return; + } + + try action_queue.append(.{ .gathering = {} }); +} + pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer _ = gpa.deinit(); @@ -133,9 +215,6 @@ pub fn main() !void { std.log.info("Server status: {s} v{s}", .{ status.status, status.version }); std.log.info("Characters online: {}", .{ status.characters_online }); - const chicken_pos = Position{ .x = 0, .y = 1 }; - const bank_pos = Position{ .x = 4, .y = 1 }; - std.log.info("Starting main loop", .{}); while (manager.poll()) |char| { if (char.action_queue.items.len > 0) { @@ -146,7 +225,6 @@ pub fn main() !void { .attack => { std.log.debug("{s} attacks", .{char.character.name}); var result = try api.actionFight(char.character.name); - defer result.deinit(); cooldown = result.cooldown; char.character.gold += result.fight.gold; @@ -180,6 +258,15 @@ pub fn main() !void { cooldown = result.cooldown; char.character.inventory.removeItem(item.id, item.quantity); + }, + .gathering => { + std.log.debug("{s} gathers", .{char.character.name}); + var result = try api.actionGathering(char.character.name); + + cooldown = result.cooldown; + for (result.details.items.slice()) |item| { + char.character.inventory.addItem(item.id, @intCast(item.quantity)); + } } } @@ -189,38 +276,14 @@ pub fn main() !void { continue; } - var character_pos = Position.init(char.character.x, char.character.y); - - // Deposit items and gold to bank if full - if (char.character.getItemCount() == char.character.inventory_max_items) { - if (!character_pos.eql(bank_pos)) { - try char.action_queue.append(.{ .move = bank_pos }); - } - - for (char.character.inventory.slots) |slot| { - if (slot.quantity == 0) continue; - - if (slot.id) |item_id| { - try char.action_queue.append(.{ - .depositItem = .{ .id = item_id, .quantity = @intCast(slot.quantity) } - }); - } - } - - if (char.character.gold > 0) { - try char.action_queue.append(.{ .depositGold = @intCast(char.character.gold) }); - } - - continue; + if (std.mem.eql(u8, char.character.name, "Devin")) { + try gatherResourceRoutine(char, .{ .x = -1, .y = 0 }); // Ash trees + } else if (std.mem.eql(u8, char.character.name, "Dawn")) { + try gatherResourceRoutine(char, .{ .x = 2, .y = 0 }); // Copper ore + } else if (std.mem.eql(u8, char.character.name, "Diana")) { + try gatherResourceRoutine(char, .{ .x = 4, .y = 2 }); // Gudgeon fish + } else { + try attackChickenRoutine(char); } - - // Go to chickens - if (!character_pos.eql(chicken_pos)) { - try char.action_queue.append(.{ .move = chicken_pos }); - continue; - } - - // Attack chickens - try char.action_queue.append(.{ .attack = {} }); } }