add item crating
This commit is contained in:
parent
7a1b7971a9
commit
b33754efb8
@ -160,6 +160,38 @@ pub const EquipmentSlot = enum {
|
||||
}
|
||||
};
|
||||
|
||||
pub const Skill = enum {
|
||||
weaponcrafting,
|
||||
gearcrafting,
|
||||
jewelrycrafting,
|
||||
cooking,
|
||||
woodcutting,
|
||||
mining,
|
||||
|
||||
fn parse(str: []const u8) ?Skill {
|
||||
const eql = std.mem.eql;
|
||||
const mapping = .{
|
||||
.{ "weaponcrafting" , .weaponcrafting },
|
||||
.{ "gearcrafting" , .gearcrafting },
|
||||
.{ "jewelrycrafting", .jewelrycrafting },
|
||||
.{ "cooking" , .cooking },
|
||||
.{ "woodcutting" , .woodcutting },
|
||||
.{ "mining" , .mining },
|
||||
};
|
||||
if (mapping.len != @typeInfo(Skill).Enum.fields.len) {
|
||||
@compileLog("Mapping is not exhaustive");
|
||||
}
|
||||
|
||||
inline for (mapping) |mapping_entry| {
|
||||
if (eql(u8, str, mapping_entry[0])) {
|
||||
return mapping_entry[1];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const ServerStatus = struct {
|
||||
allocator: Allocator,
|
||||
status: []const u8,
|
||||
@ -250,35 +282,32 @@ pub const Cooldown = struct {
|
||||
|
||||
fn parse(str: []const u8) ?Reason {
|
||||
const eql = std.mem.eql;
|
||||
if (eql(u8, str, "movement")) {
|
||||
return .movement;
|
||||
} else if (eql(u8, str, "fight")) {
|
||||
return .fight;
|
||||
} else if (eql(u8, str, "crafting")) {
|
||||
return .crafting;
|
||||
} else if (eql(u8, str, "gathering")) {
|
||||
return .gathering;
|
||||
} else if (eql(u8, str, "buy_ge")) {
|
||||
return .buy_ge;
|
||||
} else if (eql(u8, str, "sell_ge")) {
|
||||
return .sell_ge;
|
||||
} else if (eql(u8, str, "delete_item")) {
|
||||
return .delete_item;
|
||||
} else if (eql(u8, str, "deposit_bank")) {
|
||||
return .deposit_bank;
|
||||
} else if (eql(u8, str, "withdraw_bank")) {
|
||||
return .withdraw_bank;
|
||||
} else if (eql(u8, str, "equip")) {
|
||||
return .equip;
|
||||
} else if (eql(u8, str, "unequip")) {
|
||||
return .unequip;
|
||||
} else if (eql(u8, str, "task")) {
|
||||
return .task;
|
||||
} else if (eql(u8, str, "recycling")) {
|
||||
return .recycling;
|
||||
} else {
|
||||
return null;
|
||||
const mapping = .{
|
||||
.{ "movement" , .movement },
|
||||
.{ "fight" , .fight },
|
||||
.{ "crafting" , .crafting },
|
||||
.{ "gathering" , .gathering },
|
||||
.{ "buy_ge" , .buy_ge },
|
||||
.{ "sell_ge" , .sell_ge },
|
||||
.{ "delete_item" , .delete_item },
|
||||
.{ "deposit_bank" , .deposit_bank },
|
||||
.{ "withdraw_bank", .withdraw_bank },
|
||||
.{ "equip" , .equip },
|
||||
.{ "unequip" , .unequip },
|
||||
.{ "task" , .task },
|
||||
.{ "recycling" , .recycling },
|
||||
};
|
||||
if (mapping.len != @typeInfo(Reason).Enum.fields.len) {
|
||||
@compileLog("Mapping is not exhaustive");
|
||||
}
|
||||
|
||||
inline for (mapping) |mapping_entry| {
|
||||
if (eql(u8, str, mapping_entry[0])) {
|
||||
return mapping_entry[1];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
@ -365,44 +394,52 @@ pub const FightResult = struct {
|
||||
}
|
||||
};
|
||||
|
||||
pub const ItemQuantity = struct {
|
||||
pub const ItemIdQuantity = struct {
|
||||
id: ItemId,
|
||||
quantity: u64,
|
||||
|
||||
pub fn parse(api: *ArtifactsAPI, obj: json.ObjectMap) !ItemQuantity {
|
||||
pub fn parse(api: *ArtifactsAPI, obj: json.ObjectMap) !ItemIdQuantity {
|
||||
const code = try json_utils.getStringRequired(obj, "code");
|
||||
const quantity = try json_utils.getIntegerRequired(obj, "quantity");
|
||||
if (quantity < 1) return error.InvalidQuantity;
|
||||
|
||||
return ItemQuantity{
|
||||
return ItemIdQuantity{
|
||||
.id = try api.getItemId(code),
|
||||
.quantity = @intCast(quantity)
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const SkillDetails = struct {
|
||||
const BoundedItems = std.BoundedArray(ItemIdQuantity, 8);
|
||||
fn parseSimpleItemList(api: *ArtifactsAPI, array: json.Array) !BoundedItems {
|
||||
var items = BoundedItems.init(0) catch unreachable;
|
||||
|
||||
for (array.items) |item_value| {
|
||||
const item_obj = json_utils.asObject(item_value) orelse return error.MissingProperty;
|
||||
|
||||
try items.append(try ItemIdQuantity.parse(api, item_obj));
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
pub const SkillResultDetails = struct {
|
||||
xp: i64,
|
||||
items: std.BoundedArray(ItemQuantity, 8),
|
||||
items: BoundedItems,
|
||||
|
||||
fn parse(api: *ArtifactsAPI, obj: json.ObjectMap) !SkillDetails {
|
||||
var items = std.BoundedArray(ItemQuantity, 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;
|
||||
fn parse(api: *ArtifactsAPI, obj: json.ObjectMap) !SkillResultDetails {
|
||||
const items = json_utils.getArray(obj, "items") orelse return error.MissingProperty;
|
||||
|
||||
try items.append(ItemQuantity.parse(api, item_obj));
|
||||
}
|
||||
|
||||
return SkillDetails{
|
||||
return SkillResultDetails{
|
||||
.xp = try json_utils.getIntegerRequired(obj, "xp"),
|
||||
.items = items,
|
||||
.items = try parseSimpleItemList(api, items),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const GatherResult = struct {
|
||||
cooldown: Cooldown,
|
||||
details: SkillDetails,
|
||||
details: SkillResultDetails,
|
||||
|
||||
pub fn parse(api: *ArtifactsAPI, obj: json.ObjectMap) !GatherResult {
|
||||
const cooldown = json_utils.getObject(obj, "cooldown") orelse return error.MissingProperty;
|
||||
@ -410,7 +447,7 @@ pub const GatherResult = struct {
|
||||
|
||||
return GatherResult{
|
||||
.cooldown = try Cooldown.parse(cooldown),
|
||||
.details = try SkillDetails.parse(api, details)
|
||||
.details = try SkillResultDetails.parse(api, details)
|
||||
};
|
||||
}
|
||||
|
||||
@ -531,15 +568,11 @@ pub const ItemTransactionResult = struct {
|
||||
else => null
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: ItemTransactionResult) void {
|
||||
_ = self;
|
||||
}
|
||||
};
|
||||
|
||||
pub const CraftResult = struct {
|
||||
cooldown: Cooldown,
|
||||
details: SkillDetails,
|
||||
details: SkillResultDetails,
|
||||
|
||||
pub fn parse(api: *ArtifactsAPI, obj: json.ObjectMap) !CraftResult {
|
||||
const cooldown = json_utils.getObject(obj, "cooldown") orelse return error.MissingProperty;
|
||||
@ -547,7 +580,7 @@ pub const CraftResult = struct {
|
||||
|
||||
return CraftResult{
|
||||
.cooldown = try Cooldown.parse(cooldown),
|
||||
.details = try SkillDetails.parse(api, details)
|
||||
.details = try SkillResultDetails.parse(api, details)
|
||||
};
|
||||
}
|
||||
|
||||
@ -670,6 +703,72 @@ pub const MapResult = struct {
|
||||
}
|
||||
};
|
||||
|
||||
pub const Item = struct {
|
||||
pub const Recipe = struct {
|
||||
skill: Skill,
|
||||
level: u64,
|
||||
quantity: u64,
|
||||
items: BoundedItems,
|
||||
|
||||
pub fn parse(api: *ArtifactsAPI, obj: json.ObjectMap) !Recipe {
|
||||
const skill = json_utils.getString(obj, "skill") orelse return error.MissingProperty;
|
||||
const level = json_utils.getInteger(obj, "level") orelse return error.MissingProperty;
|
||||
if (level < 1) return error.InvalidLevel;
|
||||
|
||||
const quantity = json_utils.getInteger(obj, "quantity") orelse return error.MissingProperty;
|
||||
if (quantity < 1) return error.InvalidQuantity;
|
||||
|
||||
const items = json_utils.getArray(obj, "items") orelse return error.MissingProperty;
|
||||
|
||||
return Recipe{
|
||||
.skill = Skill.parse(skill) orelse return error.InvalidSkill,
|
||||
.level = @intCast(level),
|
||||
.quantity = @intCast(quantity),
|
||||
.items = try parseSimpleItemList(api, items)
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
allocator: Allocator,
|
||||
name: []u8,
|
||||
code: []u8,
|
||||
level: u64,
|
||||
type: []u8,
|
||||
subtype: []u8,
|
||||
description: []u8,
|
||||
craft: ?Recipe,
|
||||
// TODO: effects
|
||||
// TODO: Grand exchange
|
||||
|
||||
pub fn parse(api: *ArtifactsAPI, obj: json.ObjectMap, allocator: Allocator) !Item {
|
||||
const item_obj = json_utils.getObject(obj, "item") orelse return error.MissingProperty;
|
||||
|
||||
const level = json_utils.getInteger(item_obj, "level") orelse return error.MissingProperty;
|
||||
if (level < 1) return error.InvalidLevel;
|
||||
|
||||
const craft = json_utils.getObject(item_obj, "craft");
|
||||
|
||||
return Item{
|
||||
.allocator = allocator,
|
||||
.name = (try json_utils.dupeString(allocator, item_obj, "name")) orelse return error.MissingProperty,
|
||||
.code = (try json_utils.dupeString(allocator, item_obj, "code")) orelse return error.MissingProperty,
|
||||
.level = @intCast(level),
|
||||
.type = (try json_utils.dupeString(allocator, item_obj, "type")) orelse return error.MissingProperty,
|
||||
.subtype = (try json_utils.dupeString(allocator, item_obj, "subtype")) orelse return error.MissingProperty,
|
||||
.description = (try json_utils.dupeString(allocator, item_obj, "description")) orelse return error.MissingProperty,
|
||||
.craft = if (craft != null) try Recipe.parse(api, craft.?) else null
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: Item) void {
|
||||
self.allocator.free(self.name);
|
||||
self.allocator.free(self.code);
|
||||
self.allocator.free(self.type);
|
||||
self.allocator.free(self.subtype);
|
||||
self.allocator.free(self.description);
|
||||
}
|
||||
};
|
||||
|
||||
pub const ArtifactsFetchResult = struct {
|
||||
arena: std.heap.ArenaAllocator,
|
||||
status: std.http.Status,
|
||||
@ -711,6 +810,8 @@ const FetchOptions = struct {
|
||||
path: []const u8,
|
||||
payload: ?[]const u8 = null,
|
||||
|
||||
query: ?[]const u8 = null,
|
||||
|
||||
page: ?u64 = null,
|
||||
page_size: ?u64 = null,
|
||||
paginated: bool = false
|
||||
@ -743,12 +844,23 @@ fn fetch(self: *ArtifactsAPI, options: FetchOptions) APIError!ArtifactsFetchResu
|
||||
var fetch_results = std.ArrayList(json.Value).init(arena.allocator());
|
||||
|
||||
while (true) : (current_page += 1) {
|
||||
var query_params: ?[]u8 = null;
|
||||
defer if (query_params) |str| self.allocator.free(str);
|
||||
var pagination_params: ?[]u8 = null;
|
||||
defer if (pagination_params) |str| self.allocator.free(str);
|
||||
|
||||
if (options.paginated) {
|
||||
query_params = try allocPaginationParams(self.allocator, current_page, options.page_size);
|
||||
uri.query = .{ .raw = query_params.? };
|
||||
pagination_params = try allocPaginationParams(self.allocator, current_page, options.page_size);
|
||||
}
|
||||
|
||||
if (options.query != null and pagination_params != null) {
|
||||
const combined = try std.mem.join(self.allocator, "&", &.{ options.query.?, pagination_params.? });
|
||||
self.allocator.free(pagination_params.?);
|
||||
pagination_params = combined;
|
||||
|
||||
uri.query = .{ .raw = combined };
|
||||
} else if (pagination_params != null) {
|
||||
uri.query = .{ .raw = pagination_params.? };
|
||||
} else if (options.query != null) {
|
||||
uri.query = .{ .raw = options.query.? };
|
||||
}
|
||||
|
||||
var response_storage = std.ArrayList(u8).init(arena.allocator());
|
||||
@ -791,6 +903,7 @@ fn fetch(self: *ArtifactsAPI, options: FetchOptions) APIError!ArtifactsFetchResu
|
||||
|
||||
if (options.paginated) {
|
||||
const total_pages_i64 = json_utils.getInteger(parsed.object, "pages") orelse return APIError.ParseFailed;
|
||||
if (total_pages_i64 < 1) return APIError.ParseFailed;
|
||||
total_pages = @intCast(total_pages_i64);
|
||||
|
||||
const page_results = json_utils.getArray(parsed.object, "data") orelse return APIError.ParseFailed;
|
||||
@ -835,13 +948,6 @@ fn handleFetchError(
|
||||
}
|
||||
}
|
||||
|
||||
if (status == .not_found) {
|
||||
return null;
|
||||
}
|
||||
if (status != .ok) {
|
||||
return APIError.RequestFailed;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -864,6 +970,12 @@ fn fetchOptionalObject(
|
||||
if (handleFetchError(result.status, Error, parseError)) |error_value| {
|
||||
return error_value;
|
||||
}
|
||||
if (result.status == .not_found) {
|
||||
return null;
|
||||
}
|
||||
if (result.status != .ok) {
|
||||
return APIError.RequestFailed;
|
||||
}
|
||||
|
||||
if (result.body == null) {
|
||||
return APIError.ParseFailed;
|
||||
@ -886,7 +998,26 @@ fn fetchObject(
|
||||
return result orelse return APIError.RequestFailed;
|
||||
}
|
||||
|
||||
fn fetchArray(
|
||||
fn ObjectList(Object: type) type {
|
||||
return struct {
|
||||
list: std.ArrayList(Object),
|
||||
|
||||
pub fn deinit(self: @This()) void {
|
||||
for (self.list.items) |*item| {
|
||||
if (std.meta.hasMethod(@TypeOf(item), "deinit")) {
|
||||
item.deinit();
|
||||
}
|
||||
}
|
||||
self.deinitList();
|
||||
}
|
||||
|
||||
pub fn deinitList(self: @This()) void {
|
||||
self.list.deinit();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn fetchOptionalArray(
|
||||
self: *ArtifactsAPI,
|
||||
allocator: Allocator,
|
||||
Error: type,
|
||||
@ -895,7 +1026,7 @@ fn fetchArray(
|
||||
parseObject: anytype,
|
||||
parseObjectArgs: anytype,
|
||||
fetchOptions: FetchOptions
|
||||
) Error!std.ArrayList(Object) {
|
||||
) Error!?ObjectList(Object) {
|
||||
if (@typeInfo(@TypeOf(parseObject)) != .Fn) {
|
||||
@compileError("`parseObject` must be a function");
|
||||
}
|
||||
@ -906,23 +1037,56 @@ fn fetchArray(
|
||||
if (handleFetchError(result.status, Error, parseError)) |error_value| {
|
||||
return error_value;
|
||||
}
|
||||
if (result.status == .not_found) {
|
||||
return null;
|
||||
}
|
||||
if (result.status != .ok) {
|
||||
return APIError.RequestFailed;
|
||||
}
|
||||
|
||||
if (result.body == null) {
|
||||
return APIError.ParseFailed;
|
||||
}
|
||||
|
||||
var array = std.ArrayList(Object).init(allocator);
|
||||
errdefer array.deinit();
|
||||
var object_array = ObjectList(Object){
|
||||
.list = std.ArrayList(Object).init(allocator)
|
||||
};
|
||||
errdefer object_array.deinit();
|
||||
// var array = std.ArrayList(Object).init(allocator);
|
||||
// errdefer {
|
||||
// if (std.meta.hasFn(Object, "deinit")) {
|
||||
// for (array.items) |item| {
|
||||
// if (@typeInfo(Object.deinit).Fn.args.len == 1) {
|
||||
// item.deinit();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// array.deinit();
|
||||
// }
|
||||
|
||||
const result_data = json_utils.asArray(result.body.?) orelse return APIError.ParseFailed;
|
||||
for (result_data.items) |result_item| {
|
||||
const item_obj = json_utils.asObject(result_item) orelse return APIError.ParseFailed;
|
||||
|
||||
const parsed_item = @call(.auto, parseObject, .{ self, item_obj } ++ parseObjectArgs) catch return APIError.ParseFailed;
|
||||
array.append(parsed_item) catch return APIError.OutOfMemory;
|
||||
object_array.list.append(parsed_item) catch return APIError.OutOfMemory;
|
||||
}
|
||||
|
||||
return array;
|
||||
return object_array;
|
||||
}
|
||||
|
||||
fn fetchArray(
|
||||
self: *ArtifactsAPI,
|
||||
allocator: Allocator,
|
||||
Error: type,
|
||||
parseError: ?fn (status: std.http.Status) ?Error,
|
||||
Object: type,
|
||||
parseObject: anytype,
|
||||
parseObjectArgs: anytype,
|
||||
fetchOptions: FetchOptions
|
||||
) Error!ObjectList(Object) {
|
||||
const result = try self.fetchOptionalArray(allocator, Error, parseError, Object, parseObject, parseObjectArgs, fetchOptions);
|
||||
return result orelse return APIError.RequestFailed;
|
||||
}
|
||||
|
||||
pub fn setServer(self: *ArtifactsAPI, url: []const u8) !void {
|
||||
@ -995,7 +1159,7 @@ pub fn getCharacter(self: *ArtifactsAPI, allocator: Allocator, name: []const u8)
|
||||
);
|
||||
}
|
||||
|
||||
pub fn listMyCharacters(self: *ArtifactsAPI, allocator: Allocator) APIError!std.ArrayList(Character) {
|
||||
pub fn listMyCharacters(self: *ArtifactsAPI, allocator: Allocator) APIError!ObjectList(Character) {
|
||||
return self.fetchArray(
|
||||
allocator,
|
||||
APIError,
|
||||
@ -1205,20 +1369,48 @@ pub fn getBankGold(self: *ArtifactsAPI) APIError!u64 {
|
||||
|
||||
const data = json_utils.asObject(result.body.?) orelse return APIError.RequestFailed;
|
||||
const quantity = json_utils.getInteger(data, "quantity") orelse return APIError.ParseFailed;
|
||||
if (quantity < 0) return APIError.ParseFailed;
|
||||
|
||||
return @intCast(quantity);
|
||||
}
|
||||
|
||||
pub fn getBankItems(self: *ArtifactsAPI, allocator: Allocator) APIError!std.ArrayList(ItemQuantity) {
|
||||
pub fn getBankItems(self: *ArtifactsAPI, allocator: Allocator) APIError!ObjectList(ItemIdQuantity) {
|
||||
return self.fetchArray(
|
||||
allocator,
|
||||
APIError,
|
||||
null,
|
||||
ItemQuantity,
|
||||
ItemQuantity.parse, .{},
|
||||
ItemIdQuantity,
|
||||
ItemIdQuantity.parse, .{},
|
||||
.{ .method = .GET, .path = "/my/bank/items", .paginated = true }
|
||||
);
|
||||
}
|
||||
|
||||
pub fn getBankItemQuantity(self: *ArtifactsAPI, code: []const u8) APIError!?u64 {
|
||||
const query = try std.fmt.allocPrint(self.allocator, "item_code={s}", .{code});
|
||||
defer self.allocator.free(query);
|
||||
|
||||
const maybe_items = try self.fetchOptionalArray(
|
||||
self.allocator,
|
||||
APIError,
|
||||
null,
|
||||
ItemIdQuantity,
|
||||
ItemIdQuantity.parse, .{},
|
||||
.{ .method = .GET, .path = "/my/bank/items", .query = query, .paginated = true }
|
||||
);
|
||||
if (maybe_items == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const items = maybe_items.?;
|
||||
defer items.deinit();
|
||||
|
||||
const list_items = items.list.items;
|
||||
assert(list_items.len == 1);
|
||||
assert(list_items[0].id == try self.getItemId(code));
|
||||
|
||||
return list_items[0].quantity;
|
||||
}
|
||||
|
||||
pub fn getMap(self: *ArtifactsAPI, allocator: Allocator, x: i64, y: i64) APIError!?MapResult {
|
||||
const path = try std.fmt.allocPrint(self.allocator, "/maps/{}/{}", .{x, y});
|
||||
defer self.allocator.free(path);
|
||||
@ -1243,6 +1435,19 @@ pub fn getMaps(self: *ArtifactsAPI, allocator: Allocator) APIError!ObjectList(Ma
|
||||
);
|
||||
}
|
||||
|
||||
pub fn getItem(self: *ArtifactsAPI, allocator: Allocator, code: []const u8) APIError!?Item {
|
||||
const path = try std.fmt.allocPrint(self.allocator, "/items/{s}", .{code});
|
||||
defer self.allocator.free(path);
|
||||
|
||||
return self.fetchOptionalObject(
|
||||
APIError,
|
||||
null,
|
||||
Item,
|
||||
Item.parse, .{ allocator },
|
||||
.{ .method = .GET, .path = path }
|
||||
);
|
||||
}
|
||||
|
||||
test "parse date time" {
|
||||
try std.testing.expectEqual(1723069394.105, parseDateTime("2024-08-07T22:23:14.105Z").?);
|
||||
}
|
||||
|
@ -102,12 +102,15 @@ pub const Inventory = struct {
|
||||
|
||||
pub const Slot = struct {
|
||||
id: ?ItemId,
|
||||
quantity: i64,
|
||||
quantity: u64,
|
||||
|
||||
fn parse(api: *ArtifactsAPI, slot_obj: json.ObjectMap) !Slot {
|
||||
const quantity = try json_utils.getIntegerRequired(slot_obj, "quantity");
|
||||
if (quantity < 0) return error.InvalidQuantity;
|
||||
|
||||
return Slot{
|
||||
.id = try getItemId(api, slot_obj, "code"),
|
||||
.quantity = try json_utils.getIntegerRequired(slot_obj, "quantity"),
|
||||
.quantity = @intCast(quantity),
|
||||
};
|
||||
}
|
||||
};
|
||||
@ -140,7 +143,7 @@ pub const Inventory = struct {
|
||||
const slot = self.findSlot(id) orelse unreachable;
|
||||
assert(slot.quantity >= quantity);
|
||||
|
||||
slot.quantity -= @intCast(quantity);
|
||||
slot.quantity -= quantity;
|
||||
if (slot.quantity == 0) {
|
||||
slot.id = null;
|
||||
}
|
||||
@ -148,7 +151,7 @@ pub const Inventory = struct {
|
||||
|
||||
pub fn addItem(self: *Inventory, id: ItemId, quantity: u64) void {
|
||||
if (self.findSlot(id)) |slot| {
|
||||
slot.quantity += @intCast(quantity);
|
||||
slot.quantity += quantity;
|
||||
} else {
|
||||
var empty_slot: ?*Slot = null;
|
||||
for (&self.slots) |*slot| {
|
||||
@ -159,9 +162,29 @@ pub const Inventory = struct {
|
||||
|
||||
assert(empty_slot != null);
|
||||
empty_slot.?.id = id;
|
||||
empty_slot.?.quantity = @intCast(quantity);
|
||||
empty_slot.?.quantity = quantity;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn addItems(self: *Inventory, items: []const ArtifactsAPI.ItemIdQuantity) void {
|
||||
for (items) |item| {
|
||||
self.addItem(item.id, item.quantity);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn removeItems(self: *Inventory, items: []const ArtifactsAPI.ItemIdQuantity) void {
|
||||
for (items) |item| {
|
||||
self.removeItem(item.id, item.quantity);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn getItem(self: *Inventory, id: ItemId) u64 {
|
||||
if (self.findSlot(id)) |slot| {
|
||||
return slot.quantity;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
arena: *std.heap.ArenaAllocator,
|
||||
@ -249,10 +272,10 @@ pub fn deinit(self: *Character) void {
|
||||
child_allocator.destroy(self.arena);
|
||||
}
|
||||
|
||||
pub fn getItemCount(self: *const Character) u32 {
|
||||
var count: u32 = 0;
|
||||
pub fn getItemCount(self: *const Character) u64 {
|
||||
var count: u64 = 0;
|
||||
for (self.inventory.slots) |slot| {
|
||||
count += @intCast(slot.quantity);
|
||||
count += slot.quantity;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
269
src/main.zig
269
src/main.zig
@ -25,8 +25,10 @@ const QueuedAction = union(enum) {
|
||||
move: Position,
|
||||
attack,
|
||||
gather,
|
||||
depositGold: u64,
|
||||
depositItem: struct { id: ArtifactsAPI.ItemId, quantity: u64 },
|
||||
deposit_gold: u64,
|
||||
deposit_item: ArtifactsAPI.ItemIdQuantity,
|
||||
withdraw_item: ArtifactsAPI.ItemIdQuantity,
|
||||
craft_item: ArtifactsAPI.ItemIdQuantity,
|
||||
};
|
||||
|
||||
const ActionQueue = std.ArrayList(QueuedAction);
|
||||
@ -35,6 +37,10 @@ const ManagedCharacter = struct {
|
||||
character: ArtifactsAPI.Character,
|
||||
action_queue: ActionQueue,
|
||||
cooldown_expires_at: f64,
|
||||
|
||||
pub fn position(self: *const ManagedCharacter) Position {
|
||||
return Position{ .x = self.character.x, .y = self.character.y };
|
||||
}
|
||||
};
|
||||
|
||||
fn currentTime() f64 {
|
||||
@ -46,11 +52,15 @@ const Manager = struct {
|
||||
allocator: Allocator,
|
||||
characters: std.ArrayList(ManagedCharacter),
|
||||
api: *ArtifactsAPI,
|
||||
known_items: std.StringHashMap(ArtifactsAPI.Item),
|
||||
|
||||
bank_position: Position = .{ .x = 4, .y = 1 },
|
||||
|
||||
fn init(allocator: Allocator, api: *ArtifactsAPI) Manager {
|
||||
return Manager{
|
||||
.allocator = allocator,
|
||||
.api = api,
|
||||
.known_items = std.StringHashMap(ArtifactsAPI.Item).init(allocator),
|
||||
.characters = std.ArrayList(ManagedCharacter).init(allocator),
|
||||
};
|
||||
}
|
||||
@ -92,28 +102,74 @@ const Manager = struct {
|
||||
return null;
|
||||
}
|
||||
|
||||
fn deinit(self: Manager) void {
|
||||
fn getItem(self: *Manager, code: []const u8) !?ArtifactsAPI.Item {
|
||||
if (self.known_items.get(code)) |item| {
|
||||
return item;
|
||||
}
|
||||
|
||||
const maybe_item = try self.api.getItem(self.allocator, code);
|
||||
if (maybe_item == null) {
|
||||
std.log.warn("attempt to get item '{s}' which does not exist", .{code});
|
||||
return null;
|
||||
}
|
||||
|
||||
const item = maybe_item.?;
|
||||
try self.known_items.putNoClobber(item.code, item);
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
fn deinit(self: *Manager) void {
|
||||
for (self.characters.items) |managed_character| {
|
||||
managed_character.action_queue.deinit();
|
||||
}
|
||||
self.characters.deinit();
|
||||
|
||||
var known_items_iter = self.known_items.valueIterator();
|
||||
while (known_items_iter.next()) |item| {
|
||||
item.deinit();
|
||||
}
|
||||
self.known_items.deinit();
|
||||
}
|
||||
|
||||
fn getWorkstation(self: *const Manager, skill: ArtifactsAPI.Skill) Position {
|
||||
_ = self;
|
||||
// TODO: Find workstation using map endpoint
|
||||
|
||||
return switch (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 },
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
fn depositIfFull(managed_char: *ManagedCharacter) !bool {
|
||||
fn moveIfNeeded(char: *ManagedCharacter, pos: Position) !bool {
|
||||
if (char.position().eql(pos)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try char.action_queue.append(.{ .move = pos });
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
fn depositItemsToBank(manager: *Manager, 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) {
|
||||
if (character.getItemCount() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var character_pos = Position.init(character.x, character.y);
|
||||
const bank_pos = Position{ .x = 4, .y = 1 };
|
||||
var character_pos = managed_char.position();
|
||||
|
||||
if (!character_pos.eql(bank_pos)) {
|
||||
try action_queue.append(.{ .move = bank_pos });
|
||||
if (!character_pos.eql(manager.bank_position)) {
|
||||
try action_queue.append(.{ .move = manager.bank_position });
|
||||
}
|
||||
|
||||
for (character.inventory.slots) |slot| {
|
||||
@ -121,58 +177,140 @@ fn depositIfFull(managed_char: *ManagedCharacter) !bool {
|
||||
|
||||
if (slot.id) |item_id| {
|
||||
try action_queue.append(.{
|
||||
.depositItem = .{ .id = item_id, .quantity = @intCast(slot.quantity) }
|
||||
.deposit_item = .{ .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;
|
||||
fn depositIfFull(manager: *Manager, char: *ManagedCharacter) !bool {
|
||||
const character = char.character;
|
||||
if (character.getItemCount() < character.inventory_max_items) {
|
||||
return false;
|
||||
}
|
||||
|
||||
_ = try depositItemsToBank(manager, char);
|
||||
|
||||
if (character.gold > 0) {
|
||||
try char.action_queue.append(.{ .deposit_gold = @intCast(character.gold) });
|
||||
}
|
||||
}
|
||||
|
||||
fn attackChickenRoutine(manager: *Manager, managed_char: *ManagedCharacter) !void {
|
||||
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)) {
|
||||
if (try depositIfFull(manager, managed_char)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Go to chickens
|
||||
if (!character_pos.eql(chicken_pos)) {
|
||||
try action_queue.append(.{ .move = chicken_pos });
|
||||
if (try moveIfNeeded(managed_char, chicken_pos)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Attack chickens
|
||||
try action_queue.append(.{ .attack = {} });
|
||||
try managed_char.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)) {
|
||||
fn gatherResourceRoutine(manager: *Manager, managed_char: *ManagedCharacter, resource_pos: Position) !void {
|
||||
if (try depositIfFull(manager, managed_char)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!character_pos.eql(resource_pos)) {
|
||||
try action_queue.append(.{ .move = resource_pos });
|
||||
if (try moveIfNeeded(managed_char, resource_pos)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try action_queue.append(.{ .gather = {} });
|
||||
try managed_char.action_queue;.append(.{ .gather = {} });
|
||||
}
|
||||
|
||||
fn withdrawFromBank(manager: *Manager, char: *ManagedCharacter, items: []const ArtifactsAPI.ItemIdQuantity) !bool {
|
||||
var has_all_items = true;
|
||||
for (items) |item_quantity| {
|
||||
const inventory_quantity = char.character.inventory.getItem(item_quantity.id);
|
||||
if(inventory_quantity < item_quantity.quantity) {
|
||||
has_all_items = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (has_all_items) return false;
|
||||
|
||||
if (try moveIfNeeded(char, manager.bank_position)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (items) |item_quantity| {
|
||||
const inventory_quantity = char.character.inventory.getItem(item_quantity.id);
|
||||
if(inventory_quantity < item_quantity.quantity) {
|
||||
try char.action_queue.append(.{ .withdraw_item = .{
|
||||
.id = item_quantity.id,
|
||||
.quantity = item_quantity.quantity - inventory_quantity,
|
||||
}});
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
fn craftItem(manager: *Manager, char: *ManagedCharacter, id: ArtifactsAPI.ItemId, quantity: u64) !bool {
|
||||
const inventory_quantity = char.character.inventory.getItem(id);
|
||||
if (inventory_quantity >= quantity) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const code = manager.api.getItemCode(id) orelse return error.InvalidItemId;
|
||||
const item = try manager.getItem(code) orelse return error.ItemNotFound;
|
||||
if (item.craft == null) {
|
||||
return error.NotCraftable;
|
||||
}
|
||||
|
||||
const recipe = item.craft.?;
|
||||
const workstation = manager.getWorkstation(recipe.skill);
|
||||
|
||||
if (try moveIfNeeded(char, workstation)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try char.action_queue.append(.{ .craft_item = .{
|
||||
.id = id,
|
||||
.quantity = quantity - inventory_quantity
|
||||
}});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
fn craftItemFromBank(manager: *Manager, char: *ManagedCharacter, id: ArtifactsAPI.ItemId, quantity: u64) !bool {
|
||||
const inventory_quantity = char.character.inventory.getItem(id);
|
||||
if (inventory_quantity >= quantity) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const code = manager.api.getItemCode(id) orelse return error.InvalidItemId;
|
||||
const target_item = try manager.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(manager, char, needed_items.constSlice())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (try craftItem(manager, char, id, quantity)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
pub fn main() !void {
|
||||
@ -208,14 +346,13 @@ 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 characters = try api.listMyCharacters();
|
||||
const characters = try api.listMyCharacters(allocator);
|
||||
defer characters.deinit();
|
||||
|
||||
for (characters.items) |character| {
|
||||
try manager.addCharacter(character);
|
||||
}
|
||||
|
||||
|
||||
// for (characters.list.items) |character| {
|
||||
// try manager.addCharacter(character);
|
||||
// }
|
||||
try manager.addCharacter(characters.list.items[0]);
|
||||
|
||||
std.log.info("Starting main loop", .{});
|
||||
while (manager.poll()) |char| {
|
||||
@ -243,32 +380,56 @@ pub fn main() !void {
|
||||
char.character.x = pos.x;
|
||||
char.character.y = pos.y;
|
||||
},
|
||||
.depositGold => |quantity| {
|
||||
.deposit_gold => |quantity| {
|
||||
std.log.debug("deposit {} gold from {s}", .{quantity, char.character.name});
|
||||
var result = try api.actionBankDepositGold(char.character.name, quantity);
|
||||
defer result.deinit();
|
||||
const result = try api.actionBankDepositGold(char.character.name, quantity);
|
||||
|
||||
cooldown = result.cooldown;
|
||||
char.character.gold -= @intCast(quantity);
|
||||
assert(char.character.gold >= 0);
|
||||
},
|
||||
.depositItem => |item| {
|
||||
std.log.debug("deposit {s}(x{}) from {s}", .{api.getItemCode(item.id).?, item.quantity, char.character.name});
|
||||
.deposit_item => |item| {
|
||||
const code = api.getItemCode(item.id) orelse return error.ItemNotFound;
|
||||
var result = try api.actionBankDepositItem(char.character.name, code, item.quantity);
|
||||
defer result.deinit();
|
||||
std.log.debug("deposit {s} (x{}) from {s}", .{code, item.quantity, char.character.name});
|
||||
|
||||
const result = try api.actionBankDepositItem(char.character.name, code, item.quantity);
|
||||
|
||||
cooldown = result.cooldown;
|
||||
char.character.inventory.removeItem(item.id, item.quantity);
|
||||
},
|
||||
.withdraw_item => |item| {
|
||||
const code = api.getItemCode(item.id) orelse return error.ItemNotFound;
|
||||
std.log.debug("withdraw {s} (x{}) from {s}", .{code, item.quantity, char.character.name});
|
||||
|
||||
const result = try api.actionBankWithdrawItem(char.character.name, code, item.quantity);
|
||||
|
||||
cooldown = result.cooldown;
|
||||
char.character.inventory.addItem(item.id, item.quantity);
|
||||
},
|
||||
.gather => {
|
||||
std.log.debug("{s} gathers", .{char.character.name});
|
||||
var result = try api.actionGather(char.character.name);
|
||||
|
||||
cooldown = result.cooldown;
|
||||
for (result.details.items.slice()) |item| {
|
||||
char.character.inventory.addItem(item.id, @intCast(item.quantity));
|
||||
char.character.inventory.addItems(result.details.items.slice());
|
||||
},
|
||||
.craft_item => |item| {
|
||||
const code = api.getItemCode(item.id) orelse return error.ItemNotFound;
|
||||
std.log.debug("craft {s} (x{}) from {s}", .{code, item.quantity, char.character.name});
|
||||
|
||||
var result = try api.actionCraft(char.character.name, code, item.quantity);
|
||||
|
||||
cooldown = result.cooldown;
|
||||
|
||||
var inventory = &char.character.inventory;
|
||||
|
||||
const item_details = (try manager.getItem(code)) orelse return error.ItemNotFound;
|
||||
const recipe = item_details.craft orelse return error.RecipeNotFound;
|
||||
for (recipe.items.slice()) |recipe_item| {
|
||||
inventory.removeItem(recipe_item.id, recipe_item.quantity * item.quantity);
|
||||
}
|
||||
|
||||
inventory.addItems(result.details.items.slice());
|
||||
}
|
||||
}
|
||||
|
||||
@ -278,6 +439,16 @@ pub fn main() !void {
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO: Add checking if character state is in sync. Debug mode only
|
||||
|
||||
// if (try craftItemFromBank(&manager, char, try api.getItemId("copper"), 10)) {
|
||||
// continue;
|
||||
// }
|
||||
//
|
||||
// if (try depositItemsToBank(&manager, char)) {
|
||||
// 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")) {
|
||||
|
Loading…
Reference in New Issue
Block a user