From 56915df9acdd924426426465441104af6c20bf99 Mon Sep 17 00:00:00 2001 From: Rokas Puzonas Date: Fri, 9 Aug 2024 01:30:19 +0300 Subject: [PATCH] add equiping and unequiping actions --- src/artifacts.zig | 284 ++++++++++++++++++++++++++++++++++++---------- src/character.zig | 1 - src/main.zig | 10 +- 3 files changed, 227 insertions(+), 68 deletions(-) diff --git a/src/artifacts.zig b/src/artifacts.zig index 4b15b9c..ff0e194 100644 --- a/src/artifacts.zig +++ b/src/artifacts.zig @@ -23,6 +23,7 @@ token: ?[]u8 = null, item_codes: std.ArrayList([]u8), pub const APIError = error { + ServerUnavailable, RequestFailed, ParseFailed, OutOfMemory @@ -103,6 +104,58 @@ pub const CraftError = APIError || error { WorkshopNotFound }; +pub const UnequipError = APIError || error { + ItemNotFound, // TODO: Can this really occur? maybe a bug in docs + CharacterIsBusy, + SlotIsEmpty, + CharacterIsFull, + CharacterNotFound, + CharacterInCooldown, +}; + +pub const EquipError = APIError || error { + ItemNotFound, + SlotIsFull, + CharacterIsBusy, + NotEnoughSkill, + CharacterNotFound, + CharacterInCooldown, +}; + +pub const EquipmentSlot = enum { + weapon, + shield, + helmet, + body_armor, + leg_armor, + boots, + ring1, + ring2, + amulet, + artifact1, + artifact2, + consumable1, + consumable2, + + fn name(self: EquipmentSlot) []const u8 { + return switch (self) { + .weapon => "weapon", + .shield => "shield", + .helmet => "helmet", + .body_armor => "body_armor", + .leg_armor => "leg_armor", + .boots => "boots", + .ring1 => "ring1", + .ring2 => "ring2", + .amulet => "amulet", + .artifact1 => "artifact1", + .artifact2 => "artifact2", + .consumable1 => "consumable1", + .consumable2 => "consumable2", + }; + } +}; + const ServerStatus = struct { allocator: Allocator, status: []const u8, @@ -298,12 +351,12 @@ pub const FightResult = struct { pub fn parseError(status: std.http.Status) ?FightError { return switch (@intFromEnum(status)) { - 486 => return FightError.CharacterIsBusy, - 497 => return FightError.CharacterIsFull, - 498 => return FightError.CharacterNotFound, - 499 => return FightError.CharacterInCooldown, - 598 => return FightError.MonsterNotFound, - else => return null + 486 => FightError.CharacterIsBusy, + 497 => FightError.CharacterIsFull, + 498 => FightError.CharacterNotFound, + 499 => FightError.CharacterInCooldown, + 598 => FightError.MonsterNotFound, + else => null }; } }; @@ -353,12 +406,12 @@ pub const GatherResult = struct { pub fn parseError(status: std.http.Status) ?GatherError { return switch (@intFromEnum(status)) { - 486 => return GatherError.CharacterIsBusy, - 493 => return GatherError.NotEnoughSkill, - 497 => return GatherError.CharacterIsFull, - 498 => return GatherError.CharacterNotFound, - 499 => return GatherError.CharacterInCooldown, - 598 => return GatherError.ResourceNotFound, + 486 => GatherError.CharacterIsBusy, + 493 => GatherError.NotEnoughSkill, + 497 => GatherError.CharacterIsFull, + 498 => GatherError.CharacterNotFound, + 499 => GatherError.CharacterInCooldown, + 598 => GatherError.ResourceNotFound, else => null }; } @@ -379,11 +432,11 @@ pub const MoveResult = struct { pub fn parseError(status: std.http.Status) ?MoveError { return switch (@intFromEnum(status)) { - 404 => return MoveError.MapNotFound, - 486 => return MoveError.CharacterIsBusy, - 490 => return MoveError.CharacterAtDestination, - 498 => return MoveError.CharacterNotFound, - 499 => return MoveError.CharacterInCooldown, + 404 => MoveError.MapNotFound, + 486 => MoveError.CharacterIsBusy, + 490 => MoveError.CharacterAtDestination, + 498 => MoveError.CharacterNotFound, + 499 => MoveError.CharacterInCooldown, else => null }; } @@ -407,26 +460,26 @@ pub const GoldTransactionResult = struct { pub fn parseDepositError(status: std.http.Status) ?BankDepositGoldError { return switch (@intFromEnum(status)) { - 461 => return BankDepositGoldError.BankIsBusy, - 478 => return BankDepositGoldError.NotEnoughGold, // TODO: This should maybe be removed - 486 => return BankDepositGoldError.CharacterIsBusy, - 492 => return BankDepositGoldError.NotEnoughGold, - 498 => return BankDepositGoldError.CharacterNotFound, - 499 => return BankDepositGoldError.CharacterInCooldown, - 598 => return BankDepositGoldError.BankNotFound, - else => return null + 461 => BankDepositGoldError.BankIsBusy, + 478 => BankDepositGoldError.NotEnoughGold, // TODO: This should maybe be removed + 486 => BankDepositGoldError.CharacterIsBusy, + 492 => BankDepositGoldError.NotEnoughGold, + 498 => BankDepositGoldError.CharacterNotFound, + 499 => BankDepositGoldError.CharacterInCooldown, + 598 => BankDepositGoldError.BankNotFound, + else => null }; } pub fn parseWithdrawError(status: std.http.Status) ?BankWithdrawGoldError { return switch (@intFromEnum(status)) { - 460 => return BankWithdrawGoldError.NotEnoughGold, - 461 => return BankWithdrawGoldError.BankIsBusy, - 486 => return BankWithdrawGoldError.CharacterIsBusy, - 498 => return BankWithdrawGoldError.CharacterNotFound, - 499 => return BankWithdrawGoldError.CharacterInCooldown, - 598 => return BankWithdrawGoldError.BankNotFound, - else => return null + 460 => BankWithdrawGoldError.NotEnoughGold, + 461 => BankWithdrawGoldError.BankIsBusy, + 486 => BankWithdrawGoldError.CharacterIsBusy, + 498 => BankWithdrawGoldError.CharacterNotFound, + 499 => BankWithdrawGoldError.CharacterInCooldown, + 598 => BankWithdrawGoldError.BankNotFound, + else => null }; } @@ -449,28 +502,28 @@ pub const ItemTransactionResult = struct { pub fn parseDepositError(status: std.http.Status) ?BankDepositItemError { return switch (@intFromEnum(status)) { - 404 => return BankDepositItemError.ItemNotFound, - 461 => return BankDepositItemError.BankIsBusy, - 478 => return BankDepositItemError.NotEnoughItems, - 486 => return BankDepositItemError.CharacterIsBusy, - 498 => return BankDepositItemError.CharacterNotFound, - 499 => return BankDepositItemError.CharacterInCooldown, - 598 => return BankDepositItemError.BankNotFound, - else => return null + 404 => BankDepositItemError.ItemNotFound, + 461 => BankDepositItemError.BankIsBusy, + 478 => BankDepositItemError.NotEnoughItems, + 486 => BankDepositItemError.CharacterIsBusy, + 498 => BankDepositItemError.CharacterNotFound, + 499 => BankDepositItemError.CharacterInCooldown, + 598 => BankDepositItemError.BankNotFound, + else => null }; } pub fn parseWithdrawError(status: std.http.Status) ?BankWithdrawItemError { return switch (@intFromEnum(status)) { - 404 => return BankWithdrawItemError.ItemNotFound, - 461 => return BankWithdrawItemError.BankIsBusy, - 478 => return BankWithdrawItemError.NotEnoughItems, - 486 => return BankWithdrawItemError.CharacterIsBusy, - 497 => return BankWithdrawItemError.CharacterIsFull, - 498 => return BankWithdrawItemError.CharacterNotFound, - 499 => return BankWithdrawItemError.CharacterInCooldown, - 598 => return BankWithdrawItemError.BankNotFound, - else => return null + 404 => BankWithdrawItemError.ItemNotFound, + 461 => BankWithdrawItemError.BankIsBusy, + 478 => BankWithdrawItemError.NotEnoughItems, + 486 => BankWithdrawItemError.CharacterIsBusy, + 497 => BankWithdrawItemError.CharacterIsFull, + 498 => BankWithdrawItemError.CharacterNotFound, + 499 => BankWithdrawItemError.CharacterInCooldown, + 598 => BankWithdrawItemError.BankNotFound, + else => null }; } @@ -495,14 +548,76 @@ pub const CraftResult = struct { pub fn parseError(status: std.http.Status) ?CraftError { return switch (@intFromEnum(status)) { - 404 => return CraftError.RecipeNotFound, - 478 => return CraftError.NotEnoughItems, - 486 => return CraftError.CharacterIsBusy, - 493 => return CraftError.NotEnoughSkill, - 497 => return CraftError.CharacterIsFull, - 498 => return CraftError.CharacterNotFound, - 499 => return CraftError.CharacterInCooldown, - 598 => return CraftError.WorkshopNotFound, + 404 => CraftError.RecipeNotFound, + 478 => CraftError.NotEnoughItems, + 486 => CraftError.CharacterIsBusy, + 493 => CraftError.NotEnoughSkill, + 497 => CraftError.CharacterIsFull, + 498 => CraftError.CharacterNotFound, + 499 => CraftError.CharacterInCooldown, + 598 => CraftError.WorkshopNotFound, + else => null + }; + } +}; + +pub const UnequipResult = struct { + cooldown: Cooldown, + item: ItemId, + + pub fn parse(api: *ArtifactsAPI, obj: json.ObjectMap) !UnequipResult { + const cooldown = json_utils.getObject(obj, "cooldown") orelse return error.MissingProperty; + + const item = json_utils.getObject(obj, "item") orelse return error.MissingProperty; + const item_code = json_utils.getString(item, "code") orelse return error.MissingProperty; + + const item_id = try api.getItemId(item_code); + + // TODO: Might as well save information about time, because full details about it are given + + return UnequipResult{ + .cooldown = try Cooldown.parse(cooldown), + .item = item_id + }; + } + + pub fn parseError(status: std.http.Status) ?UnequipError { + return switch (@intFromEnum(status)) { + 404 => UnequipError.ItemNotFound, + 486 => UnequipError.CharacterIsBusy, + 491 => UnequipError.SlotIsEmpty, + 497 => UnequipError.CharacterIsFull, + 498 => UnequipError.CharacterNotFound, + 499 => UnequipError.CharacterInCooldown, + else => null + }; + } +}; + +pub const EquipResult = struct { + cooldown: Cooldown, + + pub fn parse(api: *ArtifactsAPI, obj: json.ObjectMap) !EquipResult { + _ = api; + const cooldown = json_utils.getObject(obj, "cooldown") orelse return error.MissingProperty; + + // TODO: Might as well save information about time, because full details about it are given + + return EquipResult{ + .cooldown = try Cooldown.parse(cooldown) + }; + } + + pub fn parseError(status: std.http.Status) ?EquipError { + return switch (@intFromEnum(status)) { + 404 => EquipError.ItemNotFound, + 478 => EquipError.ItemNotFound, // TODO: What is the difference between 404 and 478? + 485 => EquipError.SlotIsFull, + 486 => EquipError.CharacterIsBusy, + 491 => EquipError.SlotIsFull, // TODO: What is the difference between 485 and 491? + 496 => EquipError.NotEnoughSkill, + 498 => EquipError.CharacterNotFound, + 499 => EquipError.CharacterInCooldown, else => null }; } @@ -548,7 +663,7 @@ const FetchOptions = struct { payload: ?[]const u8 = null }; -fn fetch(self: *ArtifactsAPI, options: FetchOptions) !ArtifactsFetchResult { +fn fetch(self: *ArtifactsAPI, options: FetchOptions) APIError!ArtifactsFetchResult { const method = options.method; const path = options.path; const payload = options.payload; @@ -573,23 +688,25 @@ fn fetch(self: *ArtifactsAPI, options: FetchOptions) !ArtifactsFetchResult { defer if (authorization_header) |str| self.allocator.free(str); if (self.token) |token| { - authorization_header = try std.fmt.allocPrint(self.allocator, "Bearer {s}", .{token}); + authorization_header = std.fmt.allocPrint(self.allocator, "Bearer {s}", .{token}) catch return APIError.OutOfMemory; opts.headers.authorization = .{ .override = authorization_header.? }; } - const result = try self.client.fetch(opts); + const result = self.client.fetch(opts) catch return APIError.RequestFailed; const response_body = response_storage.items; std.log.debug("fetch result {}", .{result.status}); - if (result.status != .ok) { + if (result.status == .service_unavailable) { + return APIError.ServerUnavailable; + } else if (result.status != .ok) { return ArtifactsFetchResult{ .arena = arena, .status = result.status }; } - const parsed = try json.parseFromSliceLeaky(json.Value, arena.allocator(), response_body, .{ .allocate = .alloc_if_needed }); + const parsed = json.parseFromSliceLeaky(json.Value, arena.allocator(), response_body, .{ .allocate = .alloc_if_needed }) catch return APIError.ParseFailed; if (parsed != json.Value.object) { return APIError.ParseFailed; } @@ -614,7 +731,7 @@ fn fetchOptionalObject( @compileError("`parseObject` must be a function"); } - const result = self.fetch(fetchOptions) catch return APIError.RequestFailed; + const result = try self.fetch(fetchOptions); defer result.deinit(); if (result.status != .ok) { @@ -913,6 +1030,47 @@ pub fn actionCraft( ); } +pub fn actionUnequip( + self: *ArtifactsAPI, + name: []const u8, + slot: EquipmentSlot +) UnequipError!UnequipResult { + const path = try std.fmt.allocPrint(self.allocator, "/my/{s}/action/unequip", .{name}); + defer self.allocator.free(path); + + const payload = try std.fmt.allocPrint(self.allocator, "{{ \"slot\":\"{s}\" }}", .{slot.name()}); + defer self.allocator.free(payload); + + return try self.fetchObject( + UnequipError, + UnequipResult.parseError, + UnequipResult, + UnequipResult.parse, .{ }, + .{ .method = .POST, .path = path, .payload = payload } + ); +} + +pub fn actionEquip( + self: *ArtifactsAPI, + name: []const u8, + slot: EquipmentSlot, + code: []const u8 +) EquipError!EquipResult { + const path = try std.fmt.allocPrint(self.allocator, "/my/{s}/action/equip", .{name}); + defer self.allocator.free(path); + + const payload = try std.fmt.allocPrint(self.allocator, "{{ \"slot\":\"{s}\",\"code\":\"{s}\" }}", .{slot.name(), code}); + defer self.allocator.free(payload); + + return try self.fetchObject( + EquipError, + EquipResult.parseError, + EquipResult, + EquipResult.parse, .{ }, + .{ .method = .POST, .path = path, .payload = payload } + ); +} + test "parse date time" { try std.testing.expectEqual(1723069394.105, parseDateTime("2024-08-07T22:23:14.105Z").?); } diff --git a/src/character.zig b/src/character.zig index 0b46b5b..af2f41e 100644 --- a/src/character.zig +++ b/src/character.zig @@ -18,7 +18,6 @@ fn getItemId(api: *ArtifactsAPI, object: json.ObjectMap, name: []const u8) !?Ite return try api.getItemId(code); } - pub const SkillStats = struct { level: i64, xp: i64, diff --git a/src/main.zig b/src/main.zig index a76055a..28c2268 100644 --- a/src/main.zig +++ b/src/main.zig @@ -202,6 +202,12 @@ pub fn main() !void { var manager = Manager.init(allocator, &api); defer manager.deinit(); + const status = try api.getServerStatus(); + defer status.deinit(); + + 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(); defer characters.deinit(); @@ -209,11 +215,7 @@ pub fn main() !void { try manager.addCharacter(character); } - const status = try api.getServerStatus(); - defer status.deinit(); - std.log.info("Server status: {s} v{s}", .{ status.status, status.version }); - std.log.info("Characters online: {}", .{ status.characters_online }); std.log.info("Starting main loop", .{}); while (manager.poll()) |char| {