add equip goal

This commit is contained in:
Rokas Puzonas 2025-01-05 18:44:31 +02:00
parent 87c09f1e27
commit cdd11147d3
9 changed files with 308 additions and 128 deletions

View File

@ -222,6 +222,7 @@ const EquipErrorDef = ErrorDefinitionList(&[_]ErrorDefinition{
NotFound, NotFound,
CharacterItemAlreadyEquiped, CharacterItemAlreadyEquiped,
CharacterLocked, CharacterLocked,
CharacterSlotEquipmentError,
CharacterNotSkillLevelRequired, CharacterNotSkillLevelRequired,
CharacterNotFound, CharacterNotFound,
CharacterInCooldown, CharacterInCooldown,

View File

@ -24,6 +24,8 @@ pub const Craft = @import("./schemas/craft.zig");
pub const Resource = @import("./schemas/resource.zig"); pub const Resource = @import("./schemas/resource.zig");
pub const MoveResult = @import("./schemas/move_result.zig"); pub const MoveResult = @import("./schemas/move_result.zig");
pub const SimpleItem = @import("./schemas/simple_item.zig"); pub const SimpleItem = @import("./schemas/simple_item.zig");
pub const EquipResult = @import("./schemas/equip_result.zig");
pub const UnequipResult = EquipResult;
const SkillUsageResult = @import("./schemas/skill_usage_result.zig"); const SkillUsageResult = @import("./schemas/skill_usage_result.zig");
pub const GatherResult = SkillUsageResult; pub const GatherResult = SkillUsageResult;
pub const CraftResult = SkillUsageResult; pub const CraftResult = SkillUsageResult;

View File

@ -0,0 +1,37 @@
// zig fmt: off
const std = @import("std");
const json_utils = @import("../json_utils.zig");
const Store = @import("../store.zig");
const Character = @import("./character.zig");
const Cooldown = @import("./cooldown.zig");
const Item = @import("./item.zig");
const EquipResult = @This();
pub const Slot = Character.Equipment.SlotId;
cooldown: Cooldown,
slot: Slot,
item: Item,
character: Character,
fn parse(store: *Store, obj: std.json.ObjectMap) !EquipResult {
const cooldown = json_utils.getObject(obj, "cooldown") orelse return error.MissingProperty;
const item = json_utils.getObject(obj, "item") orelse return error.MissingProperty;
const character = json_utils.getObject(obj, "character") orelse return error.MissingProperty;
const slot = try json_utils.getStringRequired(obj, "slot");
return EquipResult{
.character = try Character.parse(store, character),
.cooldown = try Cooldown.parse(store, cooldown),
.item = try Item.parse(store, item),
.slot = Slot.fromString(slot) orelse return error.InvalidSlot
};
}
pub fn parseAndUpdate(store: *Store, obj: std.json.ObjectMap) !EquipResult {
const result = try parse(store, obj);
_ = try store.characters.appendOrUpdate(result.character);
_ = try store.items.appendOrUpdate(result.item);
return result;
}

View File

@ -3,27 +3,37 @@ const std = @import("std");
const json_utils = @import("../json_utils.zig"); const json_utils = @import("../json_utils.zig");
const Store = @import("../store.zig"); const Store = @import("../store.zig");
const Item = @import("./item.zig"); const Item = @import("./item.zig");
const EnumStringUtils = @import("../enum_string_utils.zig").EnumStringUtils;
const Equipment = @This(); const Equipment = @This();
pub const UtilitySlot = struct { pub const Slot = struct {
id: Store.Id, item: ?Store.Id = null,
quantity: u64, quantity: u64 = 0,
fn parse(store: *Store, obj: std.json.ObjectMap, name: []const u8, quantity: []const u8) !?UtilitySlot { fn parse(store: *Store, obj: std.json.ObjectMap, name: []const u8) !Slot {
const item_code = try json_utils.getStringRequired(obj, name); const item_code = try json_utils.getStringRequired(obj, name);
if (item_code.len == 0) { if (item_code.len == 0) {
return null; return Slot{};
} }
return UtilitySlot{ return Slot{
.id = try store.items.getOrReserveId(item_code), .item = try store.items.getOrReserveId(item_code),
.quantity = try json_utils.getPositiveIntegerRequired(obj, quantity), .quantity = 1
}; };
} }
fn parseWithQuantity(store: *Store, obj: std.json.ObjectMap, name: []const u8, quantity: []const u8) !Slot {
var slot = try Slot.parse(store, obj, name);
if (slot.item != null) {
slot.quantity = try json_utils.getPositiveIntegerRequired(obj, quantity);
}
return slot;
}
}; };
pub const Slot = enum { pub const SlotId = enum {
weapon, weapon,
shield, shield,
helmet, helmet,
@ -35,92 +45,56 @@ pub const Slot = enum {
amulet, amulet,
artifact1, artifact1,
artifact2, artifact2,
artifact3,
utility1, utility1,
utility2, utility2,
fn name(self: Slot) []const u8 { const Utils = EnumStringUtils(SlotId, .{
return switch (self) { .{ "weapon" , SlotId.weapon },
.weapon => "weapon", .{ "shield" , SlotId.shield },
.shield => "shield", .{ "helmet" , SlotId.helmet },
.helmet => "helmet", .{ "body_armor", SlotId.body_armor },
.body_armor => "body_armor", .{ "leg_armor" , SlotId.leg_armor },
.leg_armor => "leg_armor", .{ "boots" , SlotId.boots },
.boots => "boots", .{ "ring1" , SlotId.ring1 },
.ring1 => "ring1", .{ "ring2" , SlotId.ring2 },
.ring2 => "ring2", .{ "amulet" , SlotId.amulet },
.amulet => "amulet", .{ "artifact1" , SlotId.artifact1 },
.artifact1 => "artifact1", .{ "artifact2" , SlotId.artifact2 },
.artifact2 => "artifact2", .{ "artifact3" , SlotId.artifact3 },
.utility1 => "utility1", .{ "utility1" , SlotId.utility1 },
.utility2 => "utility2", .{ "utility2" , SlotId.utility2 },
}; });
pub const toString = Utils.toString;
pub const fromString = Utils.fromString;
pub fn canHoldManyItems(self: SlotId) bool {
return self == .utility1 or self == .utility2;
} }
}; };
weapon: ?Store.Id, pub const Slots = std.EnumArray(SlotId, Slot);
shield: ?Store.Id,
helmet: ?Store.Id,
body_armor: ?Store.Id,
leg_armor: ?Store.Id,
boots: ?Store.Id,
ring1: ?Store.Id, slots: Slots,
ring2: ?Store.Id,
amulet: ?Store.Id,
artifact1: ?Store.Id,
artifact2: ?Store.Id,
artifact3: ?Store.Id,
utility1: ?UtilitySlot,
utility2: ?UtilitySlot,
pub fn parse(store: *Store, obj: std.json.ObjectMap) !Equipment { pub fn parse(store: *Store, obj: std.json.ObjectMap) !Equipment {
const weapon = try json_utils.getStringRequired(obj, "weapon_slot");
const shield = try json_utils.getStringRequired(obj, "shield_slot");
const helmet = try json_utils.getStringRequired(obj, "helmet_slot");
const body_armor = try json_utils.getStringRequired(obj, "body_armor_slot");
const leg_armor = try json_utils.getStringRequired(obj, "leg_armor_slot");
const boots = try json_utils.getStringRequired(obj, "boots_slot");
const ring1 = try json_utils.getStringRequired(obj, "ring1_slot");
const ring2 = try json_utils.getStringRequired(obj, "ring2_slot");
const amulet = try json_utils.getStringRequired(obj, "amulet_slot");
const artifact1 = try json_utils.getStringRequired(obj, "artifact1_slot");
const artifact2 = try json_utils.getStringRequired(obj, "artifact2_slot");
const artifact3 = try json_utils.getStringRequired(obj, "artifact3_slot");
return Equipment{ return Equipment{
.weapon = try store.items.getOrReserveId(weapon), .slots = Slots.init(.{
.shield = try store.items.getOrReserveId(shield), .weapon = try Slot.parse(store, obj, "weapon_slot"),
.helmet = try store.items.getOrReserveId(helmet), .shield = try Slot.parse(store, obj, "shield_slot"),
.body_armor = try store.items.getOrReserveId(body_armor), .helmet = try Slot.parse(store, obj, "helmet_slot"),
.leg_armor = try store.items.getOrReserveId(leg_armor), .body_armor = try Slot.parse(store, obj, "body_armor_slot"),
.boots = try store.items.getOrReserveId(boots), .leg_armor = try Slot.parse(store, obj, "leg_armor_slot"),
.ring1 = try store.items.getOrReserveId(ring1), .boots = try Slot.parse(store, obj, "boots_slot"),
.ring2 = try store.items.getOrReserveId(ring2), .ring1 = try Slot.parse(store, obj, "ring1_slot"),
.amulet = try store.items.getOrReserveId(amulet), .ring2 = try Slot.parse(store, obj, "ring2_slot"),
.artifact1 = try store.items.getOrReserveId(artifact1), .amulet = try Slot.parse(store, obj, "amulet_slot"),
.artifact2 = try store.items.getOrReserveId(artifact2), .artifact1 = try Slot.parse(store, obj, "artifact1_slot"),
.artifact3 = try store.items.getOrReserveId(artifact3), .artifact2 = try Slot.parse(store, obj, "artifact2_slot"),
.utility1 = try UtilitySlot.parse(store, obj, "utility1_slot", "utility1_slot_quantity"), .artifact3 = try Slot.parse(store, obj, "artifact3_slot"),
.utility2 = try UtilitySlot.parse(store, obj, "utility2_slot", "utility2_slot_quantity"), .utility1 = try Slot.parseWithQuantity(store, obj, "utility1_slot", "utility1_slot_quantity"),
}; .utility2 = try Slot.parseWithQuantity(store, obj, "utility2_slot", "utility2_slot_quantity"),
} })
pub fn getSlot(self: Equipment, slot: Slot) ?Store.Id {
return switch (slot) {
.weapon => self.weapon,
.shield => self.shield,
.helmet => self.helmet,
.body_armor => self.body_armor,
.leg_armor => self.leg_armor,
.boots => self.boots,
.ring1 => self.ring1,
.ring2 => self.ring2,
.amulet => self.amulet,
.artifact1 => self.artifact1,
.artifact2 => self.artifact2,
.utility1 => if (self.utility1) |util| util.id else null,
.utility2 => if (self.utility2) |util| util.id else null,
}; };
} }

View File

@ -14,6 +14,7 @@ const AuthToken = Root.AuthToken;
const log = std.log.scoped(.api); const log = std.log.scoped(.api);
const ServerURL = std.BoundedArray(u8, 256); const ServerURL = std.BoundedArray(u8, 256);
const Equipment = @import("./schemas/equipment.zig");
const Item = @import("./schemas/item.zig"); const Item = @import("./schemas/item.zig");
const Craft = @import("./schemas/craft.zig"); const Craft = @import("./schemas/craft.zig");
const Status = @import("./schemas/status.zig"); const Status = @import("./schemas/status.zig");
@ -25,6 +26,8 @@ const Map = @import("./schemas/map.zig");
const GEOrder = @import("./schemas/ge_order.zig"); const GEOrder = @import("./schemas/ge_order.zig");
const MoveResult = @import("./schemas/move_result.zig"); const MoveResult = @import("./schemas/move_result.zig");
const SkillUsageResult = @import("./schemas/skill_usage_result.zig"); const SkillUsageResult = @import("./schemas/skill_usage_result.zig");
const EquipResult = @import("./schemas/equip_result.zig");
const UnequipResult = EquipResult;
pub const GatherResult = SkillUsageResult; pub const GatherResult = SkillUsageResult;
pub const CraftResult = SkillUsageResult; pub const CraftResult = SkillUsageResult;
const Image = Store.Image; const Image = Store.Image;
@ -502,32 +505,50 @@ fn fetchArray(
return result orelse return FetchError.RequestFailed; return result orelse return FetchError.RequestFailed;
} }
fn prefetch(self: *Server, allocator: std.mem.Allocator) !void { pub const PrefetchOptions = struct {
resources: bool = true,
maps: bool = true,
monsters: bool = true,
items: bool = true,
images: bool = false,
};
pub fn prefetch(self: *Server, allocator: std.mem.Allocator, opts: PrefetchOptions) !void {
// TODO: Create a version of `getResources`, `getMonsters`, `getItems`, etc.. // TODO: Create a version of `getResources`, `getMonsters`, `getItems`, etc..
// which don't need an allocator to be passed. // which don't need an allocator to be passed.
// This is for cases when you only care that everything will be saved into the store. // This is for cases when you only care that everything will be saved into the store.
const resources = try self.getResources(allocator, .{}); if (opts.resources) {
defer resources.deinit(); const resources = try self.getResources(allocator, .{});
defer resources.deinit();
}
const maps: std.ArrayList(Map) = try self.getMaps(allocator, .{}); if (opts.maps) {
defer maps.deinit(); const maps: std.ArrayList(Map) = try self.getMaps(allocator, .{});
defer maps.deinit();
}
const monsters = try self.getMonsters(allocator, .{}); if (opts.monsters) {
defer monsters.deinit(); const monsters = try self.getMonsters(allocator, .{});
defer monsters.deinit();
}
const items = try self.getItems(allocator, .{}); if (opts.items) {
defer items.deinit(); const items = try self.getItems(allocator, .{});
defer items.deinit();
}
for (maps.items) |map| { if (opts.images) {
const skin: []const u8 = map.skin.slice(); for (self.store.maps.items) |map| {
if (self.store.images.getId(.map, skin) == null) { const skin: []const u8 = map.skin.slice();
_ = try self.getImage(.map, skin); if (self.store.images.getId(.map, skin) == null) {
_ = try self.getImage(.map, skin);
}
} }
} }
} }
pub fn prefetchCached(self: *Server, allocator: std.mem.Allocator, absolute_cache_path: []const u8) !void { pub fn prefetchCached(self: *Server, allocator: std.mem.Allocator, absolute_cache_path: []const u8, opts: PrefetchOptions) !void {
const status: Status = try self.getStatus(); const status: Status = try self.getStatus();
const version = status.version.slice(); const version = status.version.slice();
@ -539,7 +560,7 @@ pub fn prefetchCached(self: *Server, allocator: std.mem.Allocator, absolute_cach
} else |_| {} } else |_| {}
} else |_| {} } else |_| {}
try self.prefetch(allocator); try self.prefetch(allocator, opts);
const file = try std.fs.createFileAbsolute(absolute_cache_path, .{}); const file = try std.fs.createFileAbsolute(absolute_cache_path, .{});
defer file.close(); defer file.close();
@ -1046,6 +1067,66 @@ pub fn craft(self: *Server, character: []const u8, item: []const u8, quantity: u
); );
} }
pub fn equip(self: *Server, character: []const u8, slot: Equipment.SlotId, item: []const u8, quantity: u64) errors.EquipError!EquipResult {
const path_buff_size = comptime blk: {
var count = 0;
count += 4; // "/my/"
count += Character.max_name_size;
count += 13; // "/action/equip"
break :blk count;
};
var path_buff: [path_buff_size]u8 = undefined;
const path = std.fmt.bufPrint(
&path_buff,
"/my/{s}/action/equip",.{ character }
) catch return FetchError.InvalidPayload;
var payload_buff: [256]u8 = undefined;
const payload = std.fmt.bufPrint(
&payload_buff,
"{{ \"slot\":\"{s}\", \"code\":\"{s}\", \"quantity\":{} }}", .{ slot.toString(), item, quantity }
) catch return FetchError.InvalidPayload;
return try self.fetchObject(
errors.EquipError,
errors.parseEquipError,
EquipResult,
EquipResult.parseAndUpdate,
.{ .method = .POST, .path = path, .payload = payload, .ratelimit = .actions }
);
}
pub fn unequip(self: *Server, character: []const u8, slot: Equipment.SlotId, quantity: u64) errors.UnequipError!UnequipResult {
const path_buff_size = comptime blk: {
var count = 0;
count += 4; // "/my/"
count += Character.max_name_size;
count += 15; // "/action/unequip"
break :blk count;
};
var path_buff: [path_buff_size]u8 = undefined;
const path = std.fmt.bufPrint(
&path_buff,
"/my/{s}/action/unequip",.{ character }
) catch return FetchError.InvalidPayload;
var payload_buff: [256]u8 = undefined;
const payload = std.fmt.bufPrint(
&payload_buff,
"{{ \"slot\":\"{s}\", \"quantity\":{} }}", .{ slot.toString(), quantity }
) catch return FetchError.InvalidPayload;
return try self.fetchObject(
errors.UnequipError,
errors.parseUnequipError,
UnequipResult,
UnequipResult.parseAndUpdate,
.{ .method = .POST, .path = path, .payload = payload, .ratelimit = .actions }
);
}
// https://api.artifactsmmo.com/docs/#/operations/generate_token_token_post // https://api.artifactsmmo.com/docs/#/operations/generate_token_token_post
pub fn generateToken(self: *Server, username: []const u8, password: []const u8) !AuthToken { pub fn generateToken(self: *Server, username: []const u8, password: []const u8) !AuthToken {
const base64_encoder = std.base64.standard.Encoder; const base64_encoder = std.base64.standard.Encoder;

View File

@ -50,11 +50,10 @@ pub fn main() !void {
const cwd_path = try std.fs.cwd().realpathAlloc(allocator, "."); const cwd_path = try std.fs.cwd().realpathAlloc(allocator, ".");
defer allocator.free(cwd_path); defer allocator.free(cwd_path);
const cache_path = try std.fs.path.resolve(allocator, &.{ cwd_path, "./api-store.bin" }); const cache_path = try std.fs.path.resolve(allocator, &.{ cwd_path, "./api-store-cli.bin" });
defer allocator.free(cache_path); defer allocator.free(cache_path);
// TODO: Don't prefetch images try server.prefetchCached(allocator, cache_path, .{ .images = false });
try server.prefetchCached(allocator, cache_path);
} }
const character_id = (try server.getCharacter("Blondie")).?; const character_id = (try server.getCharacter("Blondie")).?;
@ -62,17 +61,10 @@ pub fn main() !void {
var artificer = try Artificer.init(allocator, &server, character_id); var artificer = try Artificer.init(allocator, &server, character_id);
defer artificer.deinit(allocator); defer artificer.deinit(allocator);
// _ = try artificer.appendGoal(Artificer.Goal{
// .gather = .{
// .item = store.items.getId("sap").?,
// .quantity = 1
// }
// });
_ = try artificer.appendGoal(Artificer.Goal{ _ = try artificer.appendGoal(Artificer.Goal{
.craft = .{ .equip = .{
.item = store.items.getId("copper").?, .slot = .weapon,
.quantity = 2 .item = store.items.getId("copper_dagger").?
} }
}); });

View File

@ -102,10 +102,10 @@ pub fn main() anyerror!void {
const cwd_path = try std.fs.cwd().realpathAlloc(allocator, "."); const cwd_path = try std.fs.cwd().realpathAlloc(allocator, ".");
defer allocator.free(cwd_path); defer allocator.free(cwd_path);
const cache_path = try std.fs.path.resolve(allocator, &.{ cwd_path, "./api-store.bin" }); const cache_path = try std.fs.path.resolve(allocator, &.{ cwd_path, "./api-store-gui.bin" });
defer allocator.free(cache_path); defer allocator.free(cache_path);
try server.prefetchCached(allocator, cache_path); try server.prefetchCached(allocator, cache_path, .{ .images = true });
} }
rl.initWindow(800, 450, "Artificer"); rl.initWindow(800, 450, "Artificer");

61
lib/equip_goal.zig Normal file
View File

@ -0,0 +1,61 @@
// zig fmt: off
const std = @import("std");
const Api = @import("artifacts-api");
const Artificer = @import("./root.zig");
const Goal = @This();
slot: Api.Character.Equipment.SlotId,
item: Api.Store.Id,
quantity: u64 = 1,
pub fn tick(self: *Goal, goal_id: Artificer.GoalId, artificer: *Artificer) void {
const store = artificer.server.store;
const character = store.characters.get(artificer.character).?;
const equipment_slot = character.equipment.slots.get(self.slot);
if (equipment_slot.item) |equiped_item|{
if (equiped_item == self.item and !self.slot.canHoldManyItems()) {
artificer.removeGoal(goal_id);
} else {
artificer.queued_actions.appendAssumeCapacity(.{
.goal = goal_id,
.action = .{
.unequip = .{
.slot = self.slot,
.quantity = self.quantity
}
}
});
}
return;
}
artificer.queued_actions.appendAssumeCapacity(.{
.goal = goal_id,
.action = .{
.equip = .{
.slot = self.slot,
.item = self.item,
.quantity = self.quantity
}
}
});
}
pub fn requirements(self: Goal, artificer: *Artificer) Artificer.Requirements {
_ = artificer;
var reqs: Artificer.Requirements = .{};
reqs.items.addAssumeCapacity(self.item, self.quantity);
return reqs;
}
pub fn onActionCompleted(self: *Goal, goal_id: Artificer.GoalId, artificer: *Artificer, result: Artificer.ActionResult) void {
_ = self;
if (result == .equip) {
artificer.removeGoal(goal_id);
}
}

View File

@ -5,6 +5,7 @@ const Allocator = std.mem.Allocator;
const GatherGoal = @import("gather_goal.zig"); const GatherGoal = @import("gather_goal.zig");
const CraftGoal = @import("craft_goal.zig"); const CraftGoal = @import("craft_goal.zig");
const EquipGoal = @import("equip_goal.zig");
const assert = std.debug.assert; const assert = std.debug.assert;
const log = std.log.scoped(.artificer); const log = std.log.scoped(.artificer);
@ -31,11 +32,13 @@ const server_down_retry_interval = 5; // minutes
pub const Goal = union(enum) { pub const Goal = union(enum) {
gather: GatherGoal, gather: GatherGoal,
craft: CraftGoal, craft: CraftGoal,
equip: EquipGoal,
pub fn tick(self: *Goal, goal_id: Artificer.GoalId, artificer: *Artificer) !void { pub fn tick(self: *Goal, goal_id: Artificer.GoalId, artificer: *Artificer) !void {
switch (self.*) { switch (self.*) {
.gather => |*gather| gather.tick(goal_id, artificer), .gather => |*gather| gather.tick(goal_id, artificer),
.craft => |*craft| try craft.tick(goal_id, artificer), .craft => |*craft| try craft.tick(goal_id, artificer),
.equip => |*equip| equip.tick(goal_id, artificer),
} }
} }
@ -43,13 +46,15 @@ pub const Goal = union(enum) {
return switch (self) { return switch (self) {
.gather => |gather| gather.requirements(artificer), .gather => |gather| gather.requirements(artificer),
.craft => |craft| craft.requirements(artificer), .craft => |craft| craft.requirements(artificer),
.equip => |equip| equip.requirements(artificer),
}; };
} }
pub fn onActionCompleted(self: *Goal, goal_id: Artificer.GoalId, result: Artificer.ActionResult) void { pub fn onActionCompleted(self: *Goal, goal_id: Artificer.GoalId, artificer: *Artificer, result: Artificer.ActionResult) void {
switch (self.*) { switch (self.*) {
.gather => |*gather| gather.onActionCompleted(goal_id, result), .gather => |*gather| gather.onActionCompleted(goal_id, result),
.craft => |*craft| craft.onActionCompleted(goal_id, result), .craft => |*craft| craft.onActionCompleted(goal_id, result),
.equip => |*equip| equip.onActionCompleted(goal_id, artificer, result),
} }
} }
}; };
@ -66,13 +71,24 @@ pub const Action = union(enum) {
craft: struct { craft: struct {
item: Api.Store.Id, item: Api.Store.Id,
quantity: u64 quantity: u64
} },
unequip: struct {
slot: Api.Equipment.SlotId,
quantity: u64
},
equip: struct {
slot: Api.Equipment.SlotId,
item: Api.Store.Id,
quantity: u64
},
}; };
pub const ActionResult = union(enum) { pub const ActionResult = union(enum) {
move: Api.MoveResult, move: Api.MoveResult,
gather: Api.GatherResult, gather: Api.GatherResult,
craft: Api.CraftResult, craft: Api.CraftResult,
equip: Api.EquipResult,
unequip: Api.UnequipResult,
}; };
const ActionSlot = struct { const ActionSlot = struct {
@ -81,11 +97,7 @@ const ActionSlot = struct {
}; };
pub const Requirements = struct { pub const Requirements = struct {
pub const Items = Api.SimpleItem.BoundedArray(blk: { pub const Items = Api.SimpleItem.BoundedArray(Api.Craft.max_items);
var max: usize = 0;
max = @max(max, Api.Craft.max_items);
break :blk max;
});
items: Items = .{} items: Items = .{}
}; };
@ -299,21 +311,28 @@ pub fn tick(self: *Artificer) !void {
} }
const action_slot = self.queued_actions.orderedRemove(0); const action_slot = self.queued_actions.orderedRemove(0);
const character_name = character.name.slice();
const action_result = switch (action_slot.action) { const action_result = switch (action_slot.action) {
.move => |position| ActionResult{ .move => |position| ActionResult{
.move = try self.server.move(character.name.slice(), position) .move = try self.server.move(character_name, position)
}, },
.gather => ActionResult{ .gather => ActionResult{
.gather = try self.server.gather(character.name.slice()) .gather = try self.server.gather(character_name)
}, },
.craft => |craft| ActionResult{ .craft => |craft| ActionResult{
.craft = try self.server.craft(character.name.slice(), store.items.getName(craft.item).?, craft.quantity) .craft = try self.server.craft(character_name, store.items.getName(craft.item).?, craft.quantity)
},
.equip => |equip| ActionResult{
.equip = try self.server.equip(character_name, equip.slot, store.items.getName(equip.item).?, equip.quantity)
},
.unequip => |unequip| ActionResult{
.unequip = try self.server.unequip(character_name, unequip.slot, unequip.quantity)
} }
}; };
if (self.getGoal(action_slot.goal)) |goal_slot| { if (self.getGoal(action_slot.goal)) |goal_slot| {
const goal = &goal_slot.goal.?; const goal = &goal_slot.goal.?;
goal.onActionCompleted(action_slot.goal, action_result); goal.onActionCompleted(action_slot.goal, self, action_result);
} }
} else { } else {
@ -337,11 +356,24 @@ pub fn tick(self: *Artificer) !void {
for (reqs.items.slice()) |req_item| { for (reqs.items.slice()) |req_item| {
const inventory_quantity = character.inventory.getQuantity(req_item.id); const inventory_quantity = character.inventory.getQuantity(req_item.id);
if (inventory_quantity < req_item.quantity) { if (inventory_quantity < req_item.quantity) {
const missing_quantity = req_item.quantity - inventory_quantity;
const item = store.items.get(req_item.id).?;
if (self.findBestResourceWithItem(req_item.id) != null) { if (self.findBestResourceWithItem(req_item.id) != null) {
const subgoal_id = try self.appendGoal(.{ const subgoal_id = try self.appendGoal(.{
.gather = .{ .gather = .{
.item = req_item.id, .item = req_item.id,
.quantity = req_item.quantity - inventory_quantity, .quantity = missing_quantity
}
});
const subgoal = self.getGoal(subgoal_id).?;
subgoal.parent_goal = goal_id;
} else if (item.craft != null) {
const subgoal_id = try self.appendGoal(.{
.craft = .{
.item = req_item.id,
.quantity = missing_quantity
} }
}); });