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);
|
||||
|
||||
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");
|
||||
|
@ -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").?);
|
||||
}
|
||||
|
141
src/main.zig
141
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 = {} });
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user