add gathering of resources
This commit is contained in:
parent
d97b26a586
commit
0ffe638691
@ -27,10 +27,13 @@ pub fn build(b: *std.Build) void {
|
|||||||
run_step.dependOn(&run_cmd.step);
|
run_step.dependOn(&run_cmd.step);
|
||||||
|
|
||||||
const exe_unit_tests = b.addTest(.{
|
const exe_unit_tests = b.addTest(.{
|
||||||
.root_source_file = b.path("src/main.zig"),
|
.root_source_file = b.path("src/artifacts.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 run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);
|
||||||
const test_step = b.step("test", "Run unit tests");
|
const test_step = b.step("test", "Run unit tests");
|
||||||
|
@ -208,23 +208,60 @@ pub const FightResult = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
cooldown: Cooldown,
|
cooldown: Cooldown,
|
||||||
character: Character,
|
|
||||||
fight: Details,
|
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 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;
|
const fight = json_utils.getObject(obj, "fight") orelse return error.MissingProperty;
|
||||||
|
|
||||||
return FightResult{
|
return FightResult{
|
||||||
.cooldown = try Cooldown.parse(cooldown),
|
.cooldown = try Cooldown.parse(cooldown),
|
||||||
.character = try Character.parse(character, allocator, api),
|
|
||||||
.fight = try Details.parse(api, fight)
|
.fight = try Details.parse(api, fight)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
pub fn deinit(self: *FightResult) void {
|
pub const GatheringResult = struct {
|
||||||
self.character.deinit();
|
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 {
|
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;
|
var uri = self.server_uri;
|
||||||
uri.path = .{ .raw = path };
|
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 result = try self.client.fetch(opts);
|
||||||
const response_body = response_storage.items;
|
const response_body = response_storage.items;
|
||||||
|
|
||||||
|
std.log.debug("fetch result {}", .{result.status});
|
||||||
|
|
||||||
if (result.status != .ok) {
|
if (result.status != .ok) {
|
||||||
return ArtifactsFetchResult{
|
return ArtifactsFetchResult{
|
||||||
.arena = arena,
|
.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});
|
const path = try std.fmt.allocPrint(self.allocator, "/my/{s}/action/fight", .{name});
|
||||||
defer self.allocator.free(path);
|
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 {
|
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, .{});
|
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").?);
|
||||||
|
}
|
||||||
|
141
src/main.zig
141
src/main.zig
@ -3,6 +3,8 @@ const assert = std.debug.assert;
|
|||||||
const ArtifactsAPI = @import("artifacts.zig");
|
const ArtifactsAPI = @import("artifacts.zig");
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
|
// pub const std_options = .{ .log_level = .debug };
|
||||||
|
|
||||||
const Position = struct {
|
const Position = struct {
|
||||||
x: i64,
|
x: i64,
|
||||||
y: i64,
|
y: i64,
|
||||||
@ -22,13 +24,16 @@ const Position = struct {
|
|||||||
const QueuedAction = union(enum) {
|
const QueuedAction = union(enum) {
|
||||||
move: Position,
|
move: Position,
|
||||||
attack,
|
attack,
|
||||||
|
gathering,
|
||||||
depositGold: u64,
|
depositGold: u64,
|
||||||
depositItem: struct { id: ArtifactsAPI.ItemId, quantity: u64 },
|
depositItem: struct { id: ArtifactsAPI.ItemId, quantity: u64 },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const ActionQueue = std.ArrayList(QueuedAction);
|
||||||
|
|
||||||
const ManagedCharacter = struct {
|
const ManagedCharacter = struct {
|
||||||
character: ArtifactsAPI.Character,
|
character: ArtifactsAPI.Character,
|
||||||
action_queue: std.ArrayList(QueuedAction),
|
action_queue: ActionQueue,
|
||||||
cooldown_expires_at: f64,
|
cooldown_expires_at: f64,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -72,12 +77,14 @@ const Manager = struct {
|
|||||||
const duration_s = earliest_expiration - now;
|
const duration_s = earliest_expiration - now;
|
||||||
const duration_ms: u64 = @intFromFloat(@trunc(duration_s * std.time.ms_per_s));
|
const duration_ms: u64 = @intFromFloat(@trunc(duration_s * std.time.ms_per_s));
|
||||||
std.log.debug("waiting for {d:.3}s", .{duration_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();
|
now = currentTime();
|
||||||
for (self.characters.items) |*managed_character| {
|
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;
|
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 {
|
pub fn main() !void {
|
||||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||||
defer _ = gpa.deinit();
|
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("Server status: {s} v{s}", .{ status.status, status.version });
|
||||||
std.log.info("Characters online: {}", .{ status.characters_online });
|
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", .{});
|
std.log.info("Starting main loop", .{});
|
||||||
while (manager.poll()) |char| {
|
while (manager.poll()) |char| {
|
||||||
if (char.action_queue.items.len > 0) {
|
if (char.action_queue.items.len > 0) {
|
||||||
@ -146,7 +225,6 @@ pub fn main() !void {
|
|||||||
.attack => {
|
.attack => {
|
||||||
std.log.debug("{s} attacks", .{char.character.name});
|
std.log.debug("{s} attacks", .{char.character.name});
|
||||||
var result = try api.actionFight(char.character.name);
|
var result = try api.actionFight(char.character.name);
|
||||||
defer result.deinit();
|
|
||||||
|
|
||||||
cooldown = result.cooldown;
|
cooldown = result.cooldown;
|
||||||
char.character.gold += result.fight.gold;
|
char.character.gold += result.fight.gold;
|
||||||
@ -180,6 +258,15 @@ pub fn main() !void {
|
|||||||
|
|
||||||
cooldown = result.cooldown;
|
cooldown = result.cooldown;
|
||||||
char.character.inventory.removeItem(item.id, item.quantity);
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var character_pos = Position.init(char.character.x, char.character.y);
|
if (std.mem.eql(u8, char.character.name, "Devin")) {
|
||||||
|
try gatherResourceRoutine(char, .{ .x = -1, .y = 0 }); // Ash trees
|
||||||
// Deposit items and gold to bank if full
|
} else if (std.mem.eql(u8, char.character.name, "Dawn")) {
|
||||||
if (char.character.getItemCount() == char.character.inventory_max_items) {
|
try gatherResourceRoutine(char, .{ .x = 2, .y = 0 }); // Copper ore
|
||||||
if (!character_pos.eql(bank_pos)) {
|
} else if (std.mem.eql(u8, char.character.name, "Diana")) {
|
||||||
try char.action_queue.append(.{ .move = bank_pos });
|
try gatherResourceRoutine(char, .{ .x = 4, .y = 2 }); // Gudgeon fish
|
||||||
}
|
} else {
|
||||||
|
try attackChickenRoutine(char);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 = {} });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user