artificer/lib/task.zig

303 lines
8.9 KiB
Zig

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
const task_master_position = Position{ .x = 1, .y = 2 }; // TODO: Figure this out dynamically
pub const UntilCondition = union(enum) {
xp: u64,
item: Api.ItemQuantity,
quantity: u64,
fn isComplete(self: UntilCondition, progress: u64) bool {
return switch (self) {
.xp => |xp| progress >= xp,
.item => |item| progress >= item.quantity,
.quantity => |quantity| progress >= quantity,
};
}
};
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.until.isComplete(args.progress),
.gather => |args| args.until.isComplete(args.progress),
.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;
switch (args.until) {
.xp => {
args.progress += fight_result.fight.xp;
},
.item => {
const drops = fight_result.fight.drops;
args.progress += drops.getQuantity(args.until.item.id);
},
.quantity => {
args.progress += 1;
}
}
}
},
.gather => |*args| {
if (result.get(.gather)) |r| {
const gather_result: Api.Server.GatherResult = r;
switch (args.until) {
.xp => {
args.progress += gather_result.details.xp;
},
.item => {
const items = gather_result.details.items;
args.progress += items.getQuantity(args.until.item.id);
},
.quantity => {
args.progress += 1;
}
}
}
},
.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 => |*args| {
if (result.get(.accept_task)) |_| {
args.done = true;
}
}
}
}
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 => {
if (try ctx.moveIfNeeded(task_master_position)) {
return;
}
try ctx.action_queue.append(.{ .accept_task = {} });
}
}
}
};
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;
}
}
};