move character brain to separate ile
This commit is contained in:
parent
8cd5fb44c8
commit
ee563e6ba6
@ -10,6 +10,7 @@ const json_utils = @import("json_utils.zig");
|
||||
pub const Character = @import("character.zig");
|
||||
const BoundedSlotsArray = @import("./slot_array.zig").BoundedSlotsArray;
|
||||
pub const Slot = @import("./slot.zig");
|
||||
pub const Position = @import("./position.zig");
|
||||
|
||||
// Specification: https://api.artifactsmmo.com/docs
|
||||
|
||||
|
612
src/character_brain.zig
Normal file
612
src/character_brain.zig
Normal file
@ -0,0 +1,612 @@
|
||||
const std = @import("std");
|
||||
const Server = @import("./api/server.zig");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const Position = Server.Position;
|
||||
const assert = std.debug.assert;
|
||||
|
||||
const CharacterBrain = @This();
|
||||
|
||||
const bank_position: Position = .{ .x = 4, .y = 1 }; // TODO: Figure this out dynamically
|
||||
|
||||
pub const QueuedAction = union(enum) {
|
||||
move: Position,
|
||||
fight,
|
||||
gather,
|
||||
deposit_gold: u64,
|
||||
deposit_item: Server.ItemIdQuantity,
|
||||
withdraw_item: Server.ItemIdQuantity,
|
||||
craft_item: Server.ItemIdQuantity,
|
||||
|
||||
pub fn perform(self: QueuedAction, api: *Server, name: []const u8, ) !QueuedActionResult {
|
||||
const log = std.log.default;
|
||||
|
||||
switch (self) {
|
||||
.fight => {
|
||||
log.debug("[{s}] attack", .{name});
|
||||
return .{
|
||||
.fight = api.actionFight(name)
|
||||
};
|
||||
},
|
||||
.move => |pos| {
|
||||
log.debug("[{s}] move to ({}, {})", .{name, pos.x, pos.y});
|
||||
return .{
|
||||
.move = api.actionMove(name, pos.x, pos.y)
|
||||
};
|
||||
},
|
||||
.deposit_gold => |quantity| {
|
||||
log.debug("[{s}] deposit {} gold", .{name, quantity});
|
||||
return .{
|
||||
.deposit_gold = api.actionBankDepositGold(name, quantity)
|
||||
};
|
||||
},
|
||||
.deposit_item => |item| {
|
||||
const code = api.getItemCode(item.id) orelse return error.ItemNotFound;
|
||||
log.debug("[{s}] deposit {s} (x{})", .{name, code, item.quantity});
|
||||
return .{
|
||||
.deposit_item = api.actionBankDepositItem(name, code, item.quantity)
|
||||
};
|
||||
},
|
||||
.withdraw_item => |item| {
|
||||
const code = api.getItemCode(item.id) orelse return error.ItemNotFound;
|
||||
log.debug("[{s}] withdraw {s} (x{})", .{name, code, item.quantity});
|
||||
return .{
|
||||
.withdraw_item = api.actionBankWithdrawItem(name, code, item.quantity)
|
||||
};
|
||||
},
|
||||
.gather => {
|
||||
log.debug("[{s}] gather", .{name});
|
||||
return .{
|
||||
.gather = api.actionGather(name)
|
||||
};
|
||||
},
|
||||
.craft_item => |item| {
|
||||
const code = api.getItemCode(item.id) orelse return error.ItemNotFound;
|
||||
log.debug("[{s}] craft {s} (x{})", .{name, code, item.quantity});
|
||||
return .{
|
||||
.craft_item = api.actionCraft(name, code, item.quantity)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const QueuedActionResult = union(enum) {
|
||||
move: Server.MoveError!Server.MoveResult,
|
||||
fight: Server.FightError!Server.FightResult,
|
||||
gather: Server.GatherError!Server.GatherResult,
|
||||
deposit_gold: Server.BankDepositGoldError!Server.GoldTransactionResult,
|
||||
deposit_item: Server.BankDepositItemError!Server.ItemTransactionResult,
|
||||
withdraw_item: Server.BankWithdrawItemError!Server.ItemTransactionResult,
|
||||
craft_item: Server.CraftError!Server.CraftResult,
|
||||
|
||||
const Tag = @typeInfo(QueuedActionResult).Union.tag_type.?;
|
||||
|
||||
fn fieldType(comptime kind: Tag) type {
|
||||
const field_type = std.meta.fields(QueuedActionResult)[@intFromEnum(kind)].type;
|
||||
return @typeInfo(field_type).ErrorUnion.payload;
|
||||
}
|
||||
|
||||
pub fn get(self: QueuedActionResult, comptime kind: Tag) ?fieldType(kind) {
|
||||
return switch (self) {
|
||||
kind => |v| v catch null,
|
||||
else => null
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
comptime {
|
||||
assert(@typeInfo(QueuedAction).Union.fields.len == @typeInfo(QueuedActionResult).Union.fields.len);
|
||||
}
|
||||
|
||||
name: []const u8,
|
||||
routine: union (enum) {
|
||||
idle,
|
||||
|
||||
fight: struct {
|
||||
at: Position,
|
||||
target: Server.ItemIdQuantity,
|
||||
progress: u64 = 0,
|
||||
},
|
||||
gather: struct {
|
||||
at: Position,
|
||||
target: Server.ItemIdQuantity,
|
||||
progress: u64 = 0,
|
||||
},
|
||||
craft: struct {
|
||||
target: Server.ItemIdQuantity,
|
||||
progress: u64 = 0,
|
||||
},
|
||||
},
|
||||
action_queue: std.ArrayList(QueuedAction),
|
||||
|
||||
pub fn init(allocator: Allocator, name: []const u8) !CharacterBrain {
|
||||
return CharacterBrain{
|
||||
.name = try allocator.dupe(u8, name),
|
||||
.routine = .idle,
|
||||
.action_queue = std.ArrayList(QueuedAction).init(allocator),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: CharacterBrain) void {
|
||||
const allocator = self.action_queue.allocator;
|
||||
allocator.free(self.name);
|
||||
self.action_queue.deinit();
|
||||
}
|
||||
|
||||
fn currentTime() f64 {
|
||||
const timestamp: f64 = @floatFromInt(std.time.milliTimestamp());
|
||||
return timestamp / std.time.ms_per_s;
|
||||
}
|
||||
|
||||
pub fn performNextAction(self: *CharacterBrain, api: *Server) !void {
|
||||
const log = std.log.default;
|
||||
assert(self.action_queue.items.len > 0);
|
||||
|
||||
const APIError = Server.APIError;
|
||||
|
||||
const retry_delay = 0.5; // 500ms
|
||||
var character = api.findCharacterPtr(self.name).?;
|
||||
|
||||
const next_action = self.action_queue.items[0];
|
||||
const action_result = try next_action.perform(api, self.name);
|
||||
switch (action_result) {
|
||||
.fight => |result| {
|
||||
const FightError = Server.FightError;
|
||||
_ = result catch |err| switch (err) {
|
||||
FightError.CharacterInCooldown,
|
||||
FightError.CharacterIsBusy => {
|
||||
// A bit too eager, retry action
|
||||
character.cooldown_expiration = currentTime() + retry_delay;
|
||||
log.warn("[{s}] retry fighting", .{self.name});
|
||||
return;
|
||||
},
|
||||
|
||||
FightError.CharacterIsFull,
|
||||
FightError.MonsterNotFound => {
|
||||
// Re-evaluate what the character should do, something is not right.
|
||||
log.warn("[{s}] clear action queue", .{self.name});
|
||||
self.action_queue.clearAndFree();
|
||||
return;
|
||||
},
|
||||
|
||||
FightError.CharacterNotFound,
|
||||
APIError.ServerUnavailable,
|
||||
APIError.RequestFailed,
|
||||
APIError.ParseFailed,
|
||||
APIError.OutOfMemory => {
|
||||
// Welp... Abondon ship. Bail. Bail
|
||||
return err;
|
||||
}
|
||||
};
|
||||
},
|
||||
.move => |result| {
|
||||
const MoveError = Server.MoveError;
|
||||
_ = result catch |err| switch (err) {
|
||||
MoveError.CharacterIsBusy,
|
||||
MoveError.CharacterInCooldown => {
|
||||
// A bit too eager, retry action
|
||||
character.cooldown_expiration = currentTime() + retry_delay;
|
||||
log.warn("[{s}] retry moving", .{self.name});
|
||||
return;
|
||||
},
|
||||
|
||||
MoveError.CharacterAtDestination => {
|
||||
// Not great, but I guess the goal achieved? The character is at the desired location.
|
||||
log.warn("[{s}] tried to move, but already at destination", .{self.name});
|
||||
},
|
||||
|
||||
MoveError.MapNotFound => {
|
||||
// Re-evaluate what the character should do, something is not right.
|
||||
log.warn("[{s}] clear action queue", .{self.name});
|
||||
self.action_queue.clearAndFree();
|
||||
return;
|
||||
},
|
||||
|
||||
MoveError.CharacterNotFound,
|
||||
APIError.ServerUnavailable,
|
||||
APIError.RequestFailed,
|
||||
APIError.ParseFailed,
|
||||
APIError.OutOfMemory => {
|
||||
// Welp... Abondon ship. Bail. Bail
|
||||
return err;
|
||||
}
|
||||
};
|
||||
},
|
||||
.deposit_gold => |result| {
|
||||
const BankDepositGoldError = Server.BankDepositGoldError;
|
||||
_ = result catch |err| switch (err) {
|
||||
BankDepositGoldError.BankIsBusy,
|
||||
BankDepositGoldError.CharacterIsBusy,
|
||||
BankDepositGoldError.CharacterInCooldown => {
|
||||
// A bit too eager, retry action
|
||||
character.cooldown_expiration = currentTime() + retry_delay;
|
||||
log.warn("[{s}] retry depositing gold", .{self.name});
|
||||
return;
|
||||
},
|
||||
|
||||
BankDepositGoldError.NotEnoughGold,
|
||||
BankDepositGoldError.BankNotFound => {
|
||||
// Re-evaluate what the character should do, something is not right.
|
||||
log.warn("[{s}] clear action queue", .{self.name});
|
||||
self.action_queue.clearAndFree();
|
||||
return;
|
||||
},
|
||||
|
||||
BankDepositGoldError.CharacterNotFound,
|
||||
APIError.ServerUnavailable,
|
||||
APIError.RequestFailed,
|
||||
APIError.ParseFailed,
|
||||
APIError.OutOfMemory => {
|
||||
// Welp... Abondon ship. Bail. Bail
|
||||
return err;
|
||||
}
|
||||
};
|
||||
},
|
||||
.deposit_item => |result| {
|
||||
const BankDepositItemError = Server.BankDepositItemError;
|
||||
_ = result catch |err| switch (err) {
|
||||
BankDepositItemError.BankIsBusy,
|
||||
BankDepositItemError.CharacterIsBusy,
|
||||
BankDepositItemError.CharacterInCooldown => {
|
||||
// A bit too eager, retry action
|
||||
character.cooldown_expiration = currentTime() + retry_delay;
|
||||
log.warn("[{s}] retry depositing item", .{self.name});
|
||||
return;
|
||||
},
|
||||
|
||||
BankDepositItemError.ItemNotFound,
|
||||
BankDepositItemError.NotEnoughItems,
|
||||
BankDepositItemError.BankNotFound => {
|
||||
// Re-evaluate what the character should do, something is not right.
|
||||
log.warn("[{s}] clear action queue", .{self.name});
|
||||
self.action_queue.clearAndFree();
|
||||
return;
|
||||
},
|
||||
|
||||
BankDepositItemError.CharacterNotFound,
|
||||
APIError.ServerUnavailable,
|
||||
APIError.RequestFailed,
|
||||
APIError.ParseFailed,
|
||||
APIError.OutOfMemory => {
|
||||
// Welp... Abondon ship. Bail. Bail
|
||||
return err;
|
||||
}
|
||||
};
|
||||
},
|
||||
.withdraw_item => |result| {
|
||||
const BankWithdrawItemError = Server.BankWithdrawItemError;
|
||||
_ = result catch |err| switch (err) {
|
||||
BankWithdrawItemError.CharacterIsBusy,
|
||||
BankWithdrawItemError.CharacterInCooldown,
|
||||
BankWithdrawItemError.BankIsBusy => {
|
||||
// A bit too eager, retry action
|
||||
character.cooldown_expiration = currentTime() + retry_delay;
|
||||
log.warn("[{s}] retry withdrawing item", .{self.name});
|
||||
return;
|
||||
},
|
||||
|
||||
BankWithdrawItemError.ItemNotFound,
|
||||
BankWithdrawItemError.NotEnoughItems,
|
||||
BankWithdrawItemError.CharacterIsFull,
|
||||
BankWithdrawItemError.BankNotFound => {
|
||||
// Re-evaluate what the character should do, something is not right.
|
||||
log.warn("[{s}] clear action queue", .{self.name});
|
||||
self.action_queue.clearAndFree();
|
||||
return;
|
||||
},
|
||||
|
||||
BankWithdrawItemError.CharacterNotFound,
|
||||
APIError.ServerUnavailable,
|
||||
APIError.RequestFailed,
|
||||
APIError.ParseFailed,
|
||||
APIError.OutOfMemory => {
|
||||
// Welp... Abondon ship. Bail. Bail
|
||||
return err;
|
||||
}
|
||||
};
|
||||
},
|
||||
.gather => |result| {
|
||||
const GatherError = Server.GatherError;
|
||||
_ = result catch |err| switch (err) {
|
||||
GatherError.CharacterInCooldown,
|
||||
GatherError.CharacterIsBusy => {
|
||||
// A bit too eager, retry action
|
||||
character.cooldown_expiration = currentTime() + retry_delay;
|
||||
log.warn("[{s}] retry withdrawing item", .{self.name});
|
||||
return;
|
||||
},
|
||||
|
||||
GatherError.NotEnoughSkill,
|
||||
GatherError.CharacterIsFull,
|
||||
GatherError.ResourceNotFound => {
|
||||
// Re-evaluate what the character should do, something is not right.
|
||||
log.warn("[{s}] clear action queue", .{self.name});
|
||||
self.action_queue.clearAndFree();
|
||||
return;
|
||||
},
|
||||
|
||||
GatherError.CharacterNotFound,
|
||||
APIError.ServerUnavailable,
|
||||
APIError.RequestFailed,
|
||||
APIError.ParseFailed,
|
||||
APIError.OutOfMemory => {
|
||||
// Welp... Abondon ship. Bail. Bail
|
||||
return err;
|
||||
}
|
||||
};
|
||||
},
|
||||
.craft_item => |result| {
|
||||
const CraftError = Server.CraftError;
|
||||
_ = result catch |err| switch (err) {
|
||||
CraftError.CharacterInCooldown,
|
||||
CraftError.CharacterIsBusy => {
|
||||
// A bit too eager, retry action
|
||||
character.cooldown_expiration = currentTime() + retry_delay;
|
||||
log.warn("[{s}] retry withdrawing item", .{self.name});
|
||||
return;
|
||||
},
|
||||
|
||||
CraftError.RecipeNotFound,
|
||||
CraftError.NotEnoughItems,
|
||||
CraftError.NotEnoughSkill,
|
||||
CraftError.CharacterIsFull,
|
||||
CraftError.WorkshopNotFound => {
|
||||
// Re-evaluate what the character should do, something is not right.
|
||||
log.warn("[{s}] clear action queue", .{self.name});
|
||||
self.action_queue.clearAndFree();
|
||||
return;
|
||||
},
|
||||
|
||||
CraftError.CharacterNotFound,
|
||||
APIError.ServerUnavailable,
|
||||
APIError.RequestFailed,
|
||||
APIError.ParseFailed,
|
||||
APIError.OutOfMemory => {
|
||||
// Welp... Abondon ship. Bail. Bail
|
||||
return err;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
_ = self.action_queue.orderedRemove(0);
|
||||
|
||||
self.onActionCompleted(action_result);
|
||||
}
|
||||
|
||||
fn onActionCompleted(self: *CharacterBrain, result: QueuedActionResult) void {
|
||||
switch (self.routine) {
|
||||
.idle => {},
|
||||
|
||||
.fight => |*args| {
|
||||
if (result.get(.fight)) |r| {
|
||||
const fight_result: Server.FightResult = r;
|
||||
const drops = fight_result.fight.drops;
|
||||
|
||||
args.progress += drops.getQuantity(args.target.id);
|
||||
}
|
||||
},
|
||||
.gather => |*args| {
|
||||
if (result.get(.gather)) |r| {
|
||||
const gather_resutl: Server.GatherResult = r;
|
||||
const items = gather_resutl.details.items;
|
||||
|
||||
args.progress += items.getQuantity(args.target.id);
|
||||
}
|
||||
},
|
||||
.craft => |*args| {
|
||||
if (result.get(.craft_item)) |r| {
|
||||
const craft_result: Server.CraftResult = r;
|
||||
const items = craft_result.details.items;
|
||||
|
||||
args.progress += items.getQuantity(args.target.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn isRoutineFinished(self: *CharacterBrain) bool {
|
||||
return switch (self.routine) {
|
||||
.idle => false,
|
||||
|
||||
.fight => |args| {
|
||||
return args.progress >= args.target.quantity;
|
||||
},
|
||||
.gather => |args| {
|
||||
return args.progress >= args.target.quantity;
|
||||
},
|
||||
.craft => |args| {
|
||||
return args.progress >= args.target.quantity;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn performRoutine(self: *CharacterBrain, api: *Server) !void {
|
||||
switch (self.routine) {
|
||||
.idle => {
|
||||
std.log.debug("[{s}] idle", .{self.name});
|
||||
},
|
||||
.fight => |args| {
|
||||
try self.fightRoutine(api, args.at);
|
||||
},
|
||||
.gather => |args| {
|
||||
try self.gatherRoutine(api, args.at);
|
||||
},
|
||||
.craft => |args| {
|
||||
try self.craftRoutine(api, args.target.id, args.target.quantity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn moveIfNeeded(self: *CharacterBrain, api: *Server, pos: Position) !bool {
|
||||
const character = api.findCharacter(self.name).?;
|
||||
|
||||
if (character.position.eql(pos)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try self.action_queue.append(.{ .move = pos });
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
pub fn depositItemsToBank(self: *CharacterBrain, api: *Server) !bool {
|
||||
var character = api.findCharacter(self.name).?;
|
||||
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: *CharacterBrain, api: *Server) !bool {
|
||||
const character = api.findCharacter(self.name).?;
|
||||
if (character.getItemCount() < character.inventory_max_items) {
|
||||
return false;
|
||||
}
|
||||
|
||||
_ = try self.depositItemsToBank(api);
|
||||
|
||||
if (character.gold > 0) {
|
||||
try self.action_queue.append(.{ .deposit_gold = @intCast(character.gold) });
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
fn fightRoutine(self: *CharacterBrain, api: *Server, enemy_position: Position) !void {
|
||||
if (try self.depositIfFull(api)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (try self.moveIfNeeded(api, enemy_position)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try self.action_queue.append(.{ .fight = {} });
|
||||
}
|
||||
|
||||
fn gatherRoutine(self: *CharacterBrain, api: *Server, resource_position: Position) !void {
|
||||
if (try self.depositIfFull(api)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (try self.moveIfNeeded(api, resource_position)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try self.action_queue.append(.{ .gather = {} });
|
||||
}
|
||||
|
||||
fn withdrawFromBank(self: *CharacterBrain, api: *Server, items: []const Server.Slot) !bool {
|
||||
var character = api.findCharacter(self.name).?;
|
||||
|
||||
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(api, 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: *CharacterBrain, api: *Server, id: Server.ItemId, quantity: u64) !bool {
|
||||
var character = api.findCharacter(self.name).?;
|
||||
|
||||
const inventory_quantity = character.inventory.getQuantity(id);
|
||||
if (inventory_quantity >= quantity) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const code = api.getItemCode(id) orelse return error.InvalidItemId;
|
||||
const item = try api.getItem(code) orelse return error.ItemNotFound;
|
||||
if (item.craft == null) {
|
||||
return error.NotCraftable;
|
||||
}
|
||||
|
||||
const recipe = item.craft.?;
|
||||
|
||||
// TODO: Figure this out dynamically
|
||||
const workstation = switch (recipe.skill) {
|
||||
.weaponcrafting => Position{ .x = 2, .y = 1 },
|
||||
.gearcrafting => Position{ .x = 3, .y = 1 },
|
||||
.jewelrycrafting => Position{ .x = 1, .y = 3 },
|
||||
.cooking => Position{ .x = 1, .y = 1 },
|
||||
.woodcutting => Position{ .x = -2, .y = -3 },
|
||||
.mining => Position{ .x = 1, .y = 5 },
|
||||
};
|
||||
|
||||
if (try self.moveIfNeeded(api, workstation)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try self.action_queue.append(.{ .craft_item = .{
|
||||
.id = id,
|
||||
.quantity = quantity - inventory_quantity
|
||||
}});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
fn craftRoutine(self: *CharacterBrain, api: *Server, id: Server.ItemId, quantity: u64) !void {
|
||||
var character = api.findCharacter(self.name).?;
|
||||
const inventory_quantity = character.inventory.getQuantity(id);
|
||||
if (inventory_quantity >= quantity) {
|
||||
if (try self.depositItemsToBank(api)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const code = api.getItemCode(id) orelse return error.InvalidItemId;
|
||||
const target_item = try api.getItem(code) orelse return error.ItemNotFound;
|
||||
if (target_item.craft == null) {
|
||||
return error.NotCraftable;
|
||||
}
|
||||
|
||||
const recipe = target_item.craft.?;
|
||||
assert(recipe.quantity == 1); // TODO: Add support for recipe which produce multiple items
|
||||
|
||||
var needed_items = recipe.items;
|
||||
for (needed_items.slice()) |*needed_item| {
|
||||
needed_item.quantity *= quantity;
|
||||
}
|
||||
|
||||
if (try self.withdrawFromBank(api, needed_items.slice())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (try self.craftItem(api, id, quantity)) {
|
||||
return;
|
||||
}
|
||||
}
|
605
src/main.zig
605
src/main.zig
@ -4,421 +4,10 @@ const assert = std.debug.assert;
|
||||
|
||||
const Position = @import("./api/position.zig");
|
||||
const Server = @import("./api/server.zig");
|
||||
const CharacterBrain = @import("./character_brain.zig");
|
||||
|
||||
// pub const std_options = .{ .log_level = .debug };
|
||||
|
||||
const QueuedAction = union(enum) {
|
||||
move: Position,
|
||||
fight,
|
||||
gather,
|
||||
deposit_gold: u64,
|
||||
deposit_item: Server.ItemIdQuantity,
|
||||
withdraw_item: Server.ItemIdQuantity,
|
||||
craft_item: Server.ItemIdQuantity,
|
||||
};
|
||||
|
||||
const QueuedActionResult = union(enum) {
|
||||
move: Server.MoveError!Server.MoveResult,
|
||||
fight: Server.FightError!Server.FightResult,
|
||||
gather: Server.GatherError!Server.GatherResult,
|
||||
deposit_gold: Server.BankDepositGoldError!Server.GoldTransactionResult,
|
||||
deposit_item: Server.BankDepositItemError!Server.ItemTransactionResult,
|
||||
withdraw_item: Server.BankWithdrawItemError!Server.ItemTransactionResult,
|
||||
craft_item: Server.CraftError!Server.CraftResult,
|
||||
|
||||
const Tag = @typeInfo(QueuedActionResult).Union.tag_type.?;
|
||||
|
||||
fn fieldType(comptime kind: Tag) type {
|
||||
const field_type = std.meta.fields(QueuedActionResult)[@intFromEnum(kind)].type;
|
||||
return @typeInfo(field_type).ErrorUnion.payload;
|
||||
}
|
||||
|
||||
pub fn get(self: QueuedActionResult, comptime kind: Tag) ?fieldType(kind) {
|
||||
return switch (self) {
|
||||
kind => |v| v catch null,
|
||||
else => null
|
||||
};
|
||||
}
|
||||
|
||||
fn getMove() Server.MoveResult {
|
||||
}
|
||||
};
|
||||
|
||||
comptime {
|
||||
assert(@typeInfo(QueuedAction).Union.fields.len == @typeInfo(QueuedActionResult).Union.fields.len);
|
||||
}
|
||||
|
||||
fn performAction(api: *Server, name: []const u8, action: QueuedAction) !QueuedActionResult {
|
||||
const log = std.log.default;
|
||||
|
||||
switch (action) {
|
||||
.fight => {
|
||||
log.debug("[{s}] attack", .{name});
|
||||
return .{
|
||||
.fight = api.actionFight(name)
|
||||
};
|
||||
},
|
||||
.move => |pos| {
|
||||
log.debug("[{s}] move to ({}, {})", .{name, pos.x, pos.y});
|
||||
return .{
|
||||
.move = api.actionMove(name, pos.x, pos.y)
|
||||
};
|
||||
},
|
||||
.deposit_gold => |quantity| {
|
||||
log.debug("[{s}] deposit {} gold", .{name, quantity});
|
||||
return .{
|
||||
.deposit_gold = api.actionBankDepositGold(name, quantity)
|
||||
};
|
||||
},
|
||||
.deposit_item => |item| {
|
||||
const code = api.getItemCode(item.id) orelse return error.ItemNotFound;
|
||||
log.debug("[{s}] deposit {s} (x{})", .{name, code, item.quantity});
|
||||
return .{
|
||||
.deposit_item = api.actionBankDepositItem(name, code, item.quantity)
|
||||
};
|
||||
},
|
||||
.withdraw_item => |item| {
|
||||
const code = api.getItemCode(item.id) orelse return error.ItemNotFound;
|
||||
log.debug("[{s}] withdraw {s} (x{})", .{name, code, item.quantity});
|
||||
return .{
|
||||
.withdraw_item = api.actionBankWithdrawItem(name, code, item.quantity)
|
||||
};
|
||||
},
|
||||
.gather => {
|
||||
log.debug("[{s}] gather", .{name});
|
||||
return .{
|
||||
.gather = api.actionGather(name)
|
||||
};
|
||||
},
|
||||
.craft_item => |item| {
|
||||
const code = api.getItemCode(item.id) orelse return error.ItemNotFound;
|
||||
log.debug("[{s}] craft {s} (x{})", .{name, code, item.quantity});
|
||||
return .{
|
||||
.craft_item = api.actionCraft(name, code, item.quantity)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const CharacterBrain = struct {
|
||||
name: []const u8,
|
||||
routine: union (enum) {
|
||||
idle,
|
||||
|
||||
fight: struct {
|
||||
at: Position,
|
||||
target: Server.ItemIdQuantity,
|
||||
progress: u64 = 0,
|
||||
},
|
||||
gather: struct {
|
||||
at: Position,
|
||||
target: Server.ItemIdQuantity,
|
||||
progress: u64 = 0,
|
||||
},
|
||||
craft: struct {
|
||||
target: Server.ItemIdQuantity,
|
||||
progress: u64 = 0,
|
||||
},
|
||||
},
|
||||
action_queue: std.ArrayList(QueuedAction),
|
||||
|
||||
fn init(allocator: Allocator, name: []const u8) !CharacterBrain {
|
||||
return CharacterBrain{
|
||||
.name = try allocator.dupe(u8, name),
|
||||
.routine = .idle,
|
||||
.action_queue = std.ArrayList(QueuedAction).init(allocator),
|
||||
};
|
||||
}
|
||||
|
||||
fn deinit(self: CharacterBrain) void {
|
||||
const allocator = self.action_queue.allocator;
|
||||
allocator.free(self.name);
|
||||
self.action_queue.deinit();
|
||||
}
|
||||
|
||||
fn performNextAction(self: *CharacterBrain, api: *Server) !void {
|
||||
const log = std.log.default;
|
||||
assert(self.action_queue.items.len > 0);
|
||||
|
||||
const APIError = Server.APIError;
|
||||
|
||||
const retry_delay = 0.5; // 500ms
|
||||
var character = api.findCharacterPtr(self.name).?;
|
||||
|
||||
const action_result = try performAction(api, self.name, self.action_queue.items[0]);
|
||||
switch (action_result) {
|
||||
.fight => |result| {
|
||||
const FightError = Server.FightError;
|
||||
_ = result catch |err| switch (err) {
|
||||
FightError.CharacterInCooldown,
|
||||
FightError.CharacterIsBusy => {
|
||||
// A bit too eager, retry action
|
||||
character.cooldown_expiration = currentTime() + retry_delay;
|
||||
log.warn("[{s}] retry fighting", .{self.name});
|
||||
return;
|
||||
},
|
||||
|
||||
FightError.CharacterIsFull,
|
||||
FightError.MonsterNotFound => {
|
||||
// Re-evaluate what the character should do, something is not right.
|
||||
log.warn("[{s}] clear action queue", .{self.name});
|
||||
self.action_queue.clearAndFree();
|
||||
return;
|
||||
},
|
||||
|
||||
FightError.CharacterNotFound,
|
||||
APIError.ServerUnavailable,
|
||||
APIError.RequestFailed,
|
||||
APIError.ParseFailed,
|
||||
APIError.OutOfMemory => {
|
||||
// Welp... Abondon ship. Bail. Bail
|
||||
return err;
|
||||
}
|
||||
};
|
||||
},
|
||||
.move => |result| {
|
||||
const MoveError = Server.MoveError;
|
||||
_ = result catch |err| switch (err) {
|
||||
MoveError.CharacterIsBusy,
|
||||
MoveError.CharacterInCooldown => {
|
||||
// A bit too eager, retry action
|
||||
character.cooldown_expiration = currentTime() + retry_delay;
|
||||
log.warn("[{s}] retry moving", .{self.name});
|
||||
return;
|
||||
},
|
||||
|
||||
MoveError.CharacterAtDestination => {
|
||||
// Not great, but I guess the goal achieved? The character is at the desired location.
|
||||
log.warn("[{s}] tried to move, but already at destination", .{self.name});
|
||||
},
|
||||
|
||||
MoveError.MapNotFound => {
|
||||
// Re-evaluate what the character should do, something is not right.
|
||||
log.warn("[{s}] clear action queue", .{self.name});
|
||||
self.action_queue.clearAndFree();
|
||||
return;
|
||||
},
|
||||
|
||||
MoveError.CharacterNotFound,
|
||||
APIError.ServerUnavailable,
|
||||
APIError.RequestFailed,
|
||||
APIError.ParseFailed,
|
||||
APIError.OutOfMemory => {
|
||||
// Welp... Abondon ship. Bail. Bail
|
||||
return err;
|
||||
}
|
||||
};
|
||||
},
|
||||
.deposit_gold => |result| {
|
||||
const BankDepositGoldError = Server.BankDepositGoldError;
|
||||
_ = result catch |err| switch (err) {
|
||||
BankDepositGoldError.BankIsBusy,
|
||||
BankDepositGoldError.CharacterIsBusy,
|
||||
BankDepositGoldError.CharacterInCooldown => {
|
||||
// A bit too eager, retry action
|
||||
character.cooldown_expiration = currentTime() + retry_delay;
|
||||
log.warn("[{s}] retry depositing gold", .{self.name});
|
||||
return;
|
||||
},
|
||||
|
||||
BankDepositGoldError.NotEnoughGold,
|
||||
BankDepositGoldError.BankNotFound => {
|
||||
// Re-evaluate what the character should do, something is not right.
|
||||
log.warn("[{s}] clear action queue", .{self.name});
|
||||
self.action_queue.clearAndFree();
|
||||
return;
|
||||
},
|
||||
|
||||
BankDepositGoldError.CharacterNotFound,
|
||||
APIError.ServerUnavailable,
|
||||
APIError.RequestFailed,
|
||||
APIError.ParseFailed,
|
||||
APIError.OutOfMemory => {
|
||||
// Welp... Abondon ship. Bail. Bail
|
||||
return err;
|
||||
}
|
||||
};
|
||||
},
|
||||
.deposit_item => |result| {
|
||||
const BankDepositItemError = Server.BankDepositItemError;
|
||||
_ = result catch |err| switch (err) {
|
||||
BankDepositItemError.BankIsBusy,
|
||||
BankDepositItemError.CharacterIsBusy,
|
||||
BankDepositItemError.CharacterInCooldown => {
|
||||
// A bit too eager, retry action
|
||||
character.cooldown_expiration = currentTime() + retry_delay;
|
||||
log.warn("[{s}] retry depositing item", .{self.name});
|
||||
return;
|
||||
},
|
||||
|
||||
BankDepositItemError.ItemNotFound,
|
||||
BankDepositItemError.NotEnoughItems,
|
||||
BankDepositItemError.BankNotFound => {
|
||||
// Re-evaluate what the character should do, something is not right.
|
||||
log.warn("[{s}] clear action queue", .{self.name});
|
||||
self.action_queue.clearAndFree();
|
||||
return;
|
||||
},
|
||||
|
||||
BankDepositItemError.CharacterNotFound,
|
||||
APIError.ServerUnavailable,
|
||||
APIError.RequestFailed,
|
||||
APIError.ParseFailed,
|
||||
APIError.OutOfMemory => {
|
||||
// Welp... Abondon ship. Bail. Bail
|
||||
return err;
|
||||
}
|
||||
};
|
||||
},
|
||||
.withdraw_item => |result| {
|
||||
const BankWithdrawItemError = Server.BankWithdrawItemError;
|
||||
_ = result catch |err| switch (err) {
|
||||
BankWithdrawItemError.CharacterIsBusy,
|
||||
BankWithdrawItemError.CharacterInCooldown,
|
||||
BankWithdrawItemError.BankIsBusy => {
|
||||
// A bit too eager, retry action
|
||||
character.cooldown_expiration = currentTime() + retry_delay;
|
||||
log.warn("[{s}] retry withdrawing item", .{self.name});
|
||||
return;
|
||||
},
|
||||
|
||||
BankWithdrawItemError.ItemNotFound,
|
||||
BankWithdrawItemError.NotEnoughItems,
|
||||
BankWithdrawItemError.CharacterIsFull,
|
||||
BankWithdrawItemError.BankNotFound => {
|
||||
// Re-evaluate what the character should do, something is not right.
|
||||
log.warn("[{s}] clear action queue", .{self.name});
|
||||
self.action_queue.clearAndFree();
|
||||
return;
|
||||
},
|
||||
|
||||
BankWithdrawItemError.CharacterNotFound,
|
||||
APIError.ServerUnavailable,
|
||||
APIError.RequestFailed,
|
||||
APIError.ParseFailed,
|
||||
APIError.OutOfMemory => {
|
||||
// Welp... Abondon ship. Bail. Bail
|
||||
return err;
|
||||
}
|
||||
};
|
||||
},
|
||||
.gather => |result| {
|
||||
const GatherError = Server.GatherError;
|
||||
_ = result catch |err| switch (err) {
|
||||
GatherError.CharacterInCooldown,
|
||||
GatherError.CharacterIsBusy => {
|
||||
// A bit too eager, retry action
|
||||
character.cooldown_expiration = currentTime() + retry_delay;
|
||||
log.warn("[{s}] retry withdrawing item", .{self.name});
|
||||
return;
|
||||
},
|
||||
|
||||
GatherError.NotEnoughSkill,
|
||||
GatherError.CharacterIsFull,
|
||||
GatherError.ResourceNotFound => {
|
||||
// Re-evaluate what the character should do, something is not right.
|
||||
log.warn("[{s}] clear action queue", .{self.name});
|
||||
self.action_queue.clearAndFree();
|
||||
return;
|
||||
},
|
||||
|
||||
GatherError.CharacterNotFound,
|
||||
APIError.ServerUnavailable,
|
||||
APIError.RequestFailed,
|
||||
APIError.ParseFailed,
|
||||
APIError.OutOfMemory => {
|
||||
// Welp... Abondon ship. Bail. Bail
|
||||
return err;
|
||||
}
|
||||
};
|
||||
},
|
||||
.craft_item => |result| {
|
||||
const CraftError = Server.CraftError;
|
||||
_ = result catch |err| switch (err) {
|
||||
CraftError.CharacterInCooldown,
|
||||
CraftError.CharacterIsBusy => {
|
||||
// A bit too eager, retry action
|
||||
character.cooldown_expiration = currentTime() + retry_delay;
|
||||
log.warn("[{s}] retry withdrawing item", .{self.name});
|
||||
return;
|
||||
},
|
||||
|
||||
CraftError.RecipeNotFound,
|
||||
CraftError.NotEnoughItems,
|
||||
CraftError.NotEnoughSkill,
|
||||
CraftError.CharacterIsFull,
|
||||
CraftError.WorkshopNotFound => {
|
||||
// Re-evaluate what the character should do, something is not right.
|
||||
log.warn("[{s}] clear action queue", .{self.name});
|
||||
self.action_queue.clearAndFree();
|
||||
return;
|
||||
},
|
||||
|
||||
CraftError.CharacterNotFound,
|
||||
APIError.ServerUnavailable,
|
||||
APIError.RequestFailed,
|
||||
APIError.ParseFailed,
|
||||
APIError.OutOfMemory => {
|
||||
// Welp... Abondon ship. Bail. Bail
|
||||
return err;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
_ = self.action_queue.orderedRemove(0);
|
||||
|
||||
self.onActionCompleted(action_result);
|
||||
}
|
||||
|
||||
fn onActionCompleted(self: *CharacterBrain, result: QueuedActionResult) void {
|
||||
switch (self.routine) {
|
||||
.idle => {},
|
||||
|
||||
.fight => |*args| {
|
||||
if (result.get(.fight)) |r| {
|
||||
const fight_result: Server.FightResult = r;
|
||||
const drops = fight_result.fight.drops;
|
||||
|
||||
args.progress += drops.getQuantity(args.target.id);
|
||||
}
|
||||
},
|
||||
.gather => |*args| {
|
||||
if (result.get(.gather)) |r| {
|
||||
const gather_resutl: Server.GatherResult = r;
|
||||
const items = gather_resutl.details.items;
|
||||
|
||||
args.progress += items.getQuantity(args.target.id);
|
||||
}
|
||||
},
|
||||
.craft => |*args| {
|
||||
if (result.get(.craft_item)) |r| {
|
||||
const craft_result: Server.CraftResult = r;
|
||||
const items = craft_result.details.items;
|
||||
|
||||
args.progress += items.getQuantity(args.target.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn isRoutineFinished(self: *CharacterBrain) bool {
|
||||
return switch (self.routine) {
|
||||
.idle => false,
|
||||
|
||||
.fight => |args| {
|
||||
return args.progress >= args.target.quantity;
|
||||
},
|
||||
.gather => |args| {
|
||||
return args.progress >= args.target.quantity;
|
||||
},
|
||||
.craft => |args| {
|
||||
return args.progress >= args.target.quantity;
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
fn currentTime() f64 {
|
||||
const timestamp: f64 = @floatFromInt(std.time.milliTimestamp());
|
||||
return timestamp / std.time.ms_per_s;
|
||||
@ -477,8 +66,6 @@ const GoalManager = struct {
|
||||
}
|
||||
};
|
||||
|
||||
const bank_position: Position = .{ .x = 4, .y = 1 }; // TODO: Figure this out dynamically
|
||||
|
||||
fn getAPITokenFromArgs(allocator: Allocator) !?[]u8 {
|
||||
const args = try std.process.argsAlloc(allocator);
|
||||
defer std.process.argsFree(allocator, args);
|
||||
@ -495,179 +82,6 @@ fn getAPITokenFromArgs(allocator: Allocator) !?[]u8 {
|
||||
return try allocator.dupe(u8, std.mem.trim(u8,token,"\n\t "));
|
||||
}
|
||||
|
||||
fn moveIfNeeded(api: *Server, brain: *CharacterBrain, pos: Position) !bool {
|
||||
const character = api.findCharacter(brain.name).?;
|
||||
|
||||
if (character.position.eql(pos)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try brain.action_queue.append(.{ .move = pos });
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
fn depositItemsToBank(api: *Server, brain: *CharacterBrain) !bool {
|
||||
var character = api.findCharacter(brain.name).?;
|
||||
const action_queue = &brain.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(api: *Server, brain: *CharacterBrain) !bool {
|
||||
const character = api.findCharacter(brain.name).?;
|
||||
if (character.getItemCount() < character.inventory_max_items) {
|
||||
return false;
|
||||
}
|
||||
|
||||
_ = try depositItemsToBank(api, brain);
|
||||
|
||||
if (character.gold > 0) {
|
||||
try brain.action_queue.append(.{ .deposit_gold = @intCast(character.gold) });
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
fn fightRoutine(api: *Server, brain: *CharacterBrain, enemy_position: Position) !void {
|
||||
if (try depositIfFull(api, brain)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (try moveIfNeeded(api, brain, enemy_position)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try brain.action_queue.append(.{ .fight = {} });
|
||||
}
|
||||
|
||||
fn gatherRoutine(api: *Server, brain: *CharacterBrain, resource_position: Position) !void {
|
||||
if (try depositIfFull(api, brain)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (try moveIfNeeded(api, brain, resource_position)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try brain.action_queue.append(.{ .gather = {} });
|
||||
}
|
||||
|
||||
fn withdrawFromBank(api: *Server, brain: *CharacterBrain, items: []const Server.Slot) !bool {
|
||||
var character = api.findCharacter(brain.name).?;
|
||||
|
||||
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 moveIfNeeded(api, brain, bank_position)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (items) |item_quantity| {
|
||||
const inventory_quantity = character.inventory.getQuantity(item_quantity.id);
|
||||
if(inventory_quantity < item_quantity.quantity) {
|
||||
try brain.action_queue.append(.{ .withdraw_item = .{
|
||||
.id = item_quantity.id,
|
||||
.quantity = item_quantity.quantity - inventory_quantity,
|
||||
}});
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
fn craftItem(api: *Server, brain: *CharacterBrain, id: Server.ItemId, quantity: u64) !bool {
|
||||
var character = api.findCharacter(brain.name).?;
|
||||
|
||||
const inventory_quantity = character.inventory.getQuantity(id);
|
||||
if (inventory_quantity >= quantity) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const code = api.getItemCode(id) orelse return error.InvalidItemId;
|
||||
const item = try api.getItem(code) orelse return error.ItemNotFound;
|
||||
if (item.craft == null) {
|
||||
return error.NotCraftable;
|
||||
}
|
||||
|
||||
const recipe = item.craft.?;
|
||||
|
||||
// TODO: Figure this out dynamically
|
||||
const workstation = switch (recipe.skill) {
|
||||
.weaponcrafting => Position{ .x = 2, .y = 1 },
|
||||
.gearcrafting => Position{ .x = 3, .y = 1 },
|
||||
.jewelrycrafting => Position{ .x = 1, .y = 3 },
|
||||
.cooking => Position{ .x = 1, .y = 1 },
|
||||
.woodcutting => Position{ .x = -2, .y = -3 },
|
||||
.mining => Position{ .x = 1, .y = 5 },
|
||||
};
|
||||
|
||||
if (try moveIfNeeded(api, brain, workstation)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try brain.action_queue.append(.{ .craft_item = .{
|
||||
.id = id,
|
||||
.quantity = quantity - inventory_quantity
|
||||
}});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
fn craftRoutine(api: *Server, brain: *CharacterBrain, id: Server.ItemId, quantity: u64) !void {
|
||||
var character = api.findCharacter(brain.name).?;
|
||||
const inventory_quantity = character.inventory.getQuantity(id);
|
||||
if (inventory_quantity >= quantity) {
|
||||
if (try depositItemsToBank(api, brain)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const code = api.getItemCode(id) orelse return error.InvalidItemId;
|
||||
const target_item = try api.getItem(code) orelse return error.ItemNotFound;
|
||||
if (target_item.craft == null) {
|
||||
return error.NotCraftable;
|
||||
}
|
||||
|
||||
const recipe = target_item.craft.?;
|
||||
assert(recipe.quantity == 1); // TODO: Add support for recipe which produce multiple items
|
||||
|
||||
var needed_items = recipe.items;
|
||||
for (needed_items.slice()) |*needed_item| {
|
||||
needed_item.quantity *= quantity;
|
||||
}
|
||||
|
||||
if (try withdrawFromBank(api, brain, needed_items.slice())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (try craftItem(api, brain, id, quantity)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn main() !void {
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
defer _ = gpa.deinit();
|
||||
@ -759,26 +173,13 @@ pub fn main() !void {
|
||||
if (brain.action_queue.items.len > 0) continue;
|
||||
|
||||
if (brain.isRoutineFinished()) {
|
||||
if (!try depositItemsToBank(&api, brain)) {
|
||||
if (!try brain.depositItemsToBank(&api)) {
|
||||
brain.routine = .idle;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (brain.routine) {
|
||||
.idle => {
|
||||
std.log.debug("[{s}] idle", .{brain.name});
|
||||
},
|
||||
.fight => |args| {
|
||||
try fightRoutine(&api, brain, args.at);
|
||||
},
|
||||
.gather => |args| {
|
||||
try gatherRoutine(&api, brain, args.at);
|
||||
},
|
||||
.craft => |args| {
|
||||
try craftRoutine(&api, brain, args.target.id, args.target.quantity);
|
||||
}
|
||||
}
|
||||
try brain.performRoutine(&api);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user