const std = @import("std"); const Api = @import("artifacts-api"); const Position = Api.Position; const Action = @import("./action.zig").Action; const ActionResult = @import("./action.zig").ActionResult; const CodeId = Api.CodeId; const ItemQuantity = Api.ItemQuantity; const bank_position: Position = .{ .x = 4, .y = 1 }; // TODO: Figure this out dynamically pub const UntilCondition = union(enum) { xp: u64, item: Api.ItemQuantity, }; 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.ItemQuantity, progress: u64 = 0, }, accept_task: struct { done: bool = false }, pub fn isComplete(self: Task) bool { return switch (self) { .fight => |args| args.progress >= args.until.item.quantity, .gather => |args| args.progress >= args.until.item.quantity, .craft => |args| args.progress >= args.target.quantity, .accept_task => |args| args.done }; } pub fn onActionCompleted(self: *Task, result: ActionResult) void { switch (self.*) { .fight => |*args| { if (result.get(.fight)) |r| { const fight_result: Api.Server.FightResult = r; const drops = fight_result.fight.drops; args.progress += drops.getQuantity(args.until.item.id); } }, .gather => |*args| { if (result.get(.gather)) |r| { const gather_resutl: Api.Server.GatherResult = r; const items = gather_resutl.details.items; args.progress += items.getQuantity(args.until.item.id); } }, .craft => |*args| { if (result.get(.craft_item)) |r| { const craft_result: Api.Server.CraftResult = r; const items = craft_result.details.items; args.progress += items.getQuantity(args.target.id); } }, .accept_task => { // TODO: } } } pub fn queueActions(self: Task, api: *Api.Server, name: []const u8, action_queue: *std.ArrayList(Action)) !void { const ctx = TaskContext{ .api = api, .name = name, .action_queue = action_queue }; switch (self) { .fight => |args| { try ctx.fightRoutine(args.at); }, .gather => |args| { try ctx.gatherRoutine(args.at); }, .craft => |args| { try ctx.craftRoutine(args.at, args.target.id, args.target.quantity); }, .accept_task => { // TODO: } } } }; const TaskContext = struct { api: *Api.Server, name: []const u8, action_queue: *std.ArrayList(Action), fn getCharacter(self: TaskContext) Api.Character { return self.api.store.getCharacter(self.name).?; } fn moveIfNeeded(self: TaskContext, pos: Position) !bool { const character = self.getCharacter(); if (character.position.eql(pos)) { return false; } try self.action_queue.append(.{ .move = pos }); return true; } pub fn depositItemsToBank(self: TaskContext) !bool { var character = self.getCharacter(); const action_queue = self.action_queue; // Deposit items and gold to bank if full if (character.getItemCount() == 0) { return false; } if (!character.position.eql(bank_position)) { try action_queue.append(.{ .move = bank_position }); } for (character.inventory.slice()) |slot| { try action_queue.append(.{ .deposit_item = .{ .id = slot.id, .quantity = slot.quantity } }); } return true; } fn depositIfFull(self: TaskContext) !bool { const character = self.getCharacter(); if (character.getItemCount() < character.inventory_max_items) { return false; } _ = try depositItemsToBank(self); if (character.gold > 0) { try self.action_queue.append(.{ .deposit_gold = @intCast(character.gold) }); } return true; } fn fightRoutine(self: TaskContext, enemy_position: Position) !void { if (try self.depositIfFull()) { return; } if (try self.moveIfNeeded(enemy_position)) { return; } try self.action_queue.append(.{ .fight = {} }); } fn gatherRoutine(self: TaskContext, resource_position: Position) !void { if (try self.depositIfFull()) { return; } if (try self.moveIfNeeded(resource_position)) { return; } try self.action_queue.append(.{ .gather = {} }); } fn withdrawFromBank(self: TaskContext, items: []const ItemQuantity) !bool { const character = self.getCharacter(); var has_all_items = true; for (items) |item_quantity| { const inventory_quantity = character.inventory.getQuantity(item_quantity.id); if(inventory_quantity < item_quantity.quantity) { has_all_items = false; break; } } if (has_all_items) return false; if (try self.moveIfNeeded(bank_position)) { return true; } for (items) |item_quantity| { const inventory_quantity = character.inventory.getQuantity(item_quantity.id); if(inventory_quantity < item_quantity.quantity) { try self.action_queue.append(.{ .withdraw_item = .{ .id = item_quantity.id, .quantity = item_quantity.quantity - inventory_quantity, }}); } } return true; } fn craftItem(self: TaskContext, workstation: Position, id: CodeId, quantity: u64) !bool { var character = self.getCharacter(); const inventory_quantity = character.inventory.getQuantity(id); if (inventory_quantity >= quantity) { return false; } if (try self.moveIfNeeded(workstation)) { return true; } try self.action_queue.append(.{ .craft_item = .{ .id = id, .quantity = quantity - inventory_quantity }}); return true; } fn craftRoutine(self: TaskContext, workstation: Position, id: CodeId, quantity: u64) !void { var character = self.getCharacter(); const inventory_quantity = character.inventory.getQuantity(id); if (inventory_quantity >= quantity) { if (try self.depositItemsToBank()) { return; } } const code = self.api.store.getCode(id) orelse return error.InvalidItemId; const target_item = try self.api.getItem(code) orelse return error.ItemNotFound; if (target_item.craft == null) { return error.NotCraftable; } const recipe = target_item.craft.?; var needed_items = recipe.items; for (needed_items.slice()) |*needed_item| { needed_item.quantity *= quantity; } if (try self.withdrawFromBank(needed_items.slice())) { return; } if (try self.craftItem(workstation, id, quantity)) { return; } } };