diff --git a/api/date_time/parse.zig b/api/date_time/parse.zig index 3f6e79a..5829bd8 100644 --- a/api/date_time/parse.zig +++ b/api/date_time/parse.zig @@ -1,5 +1,7 @@ const std = @import("std"); +// TODO: Maybe it would be good to move date time parsing to separate module + pub fn parseDateTime(datetime: []const u8) ?f64 { const time_h = @cImport({ @cDefine("_XOPEN_SOURCE", "700"); diff --git a/api/root.zig b/api/root.zig index 1865025..fcd4016 100644 --- a/api/root.zig +++ b/api/root.zig @@ -1,7 +1,9 @@ +pub const parseDateTime = @import("./date_time/parse.zig").parseDateTime; + pub const Server = @import("server.zig"); pub const Position = @import("position.zig"); -pub const BoundedSlotsArray = @import("slot_array.zig").BoundedSlotsArray; +pub const BoundedSlotsArray = @import("schemas/slot_array.zig").BoundedSlotsArray; pub const Slot = Server.Slot; pub const ItemId = Server.ItemId; diff --git a/api/schemas/bank_gold_transaction.zig b/api/schemas/bank_gold_transaction.zig new file mode 100644 index 0000000..75a3f09 --- /dev/null +++ b/api/schemas/bank_gold_transaction.zig @@ -0,0 +1,23 @@ +const std = @import("std"); +const Server = @import("../server.zig"); +const json_utils = @import("../json_utils.zig"); +const json = std.json; +const Allocator = std.mem.Allocator; + +const Cooldown = @import("./cooldown.zig"); +const Character = @import("./character.zig"); + +const BankGoldTransaction = @This(); + +cooldown: Cooldown, +character: Character, + +pub fn parse(api: *Server, obj: json.ObjectMap, allocator: Allocator) !BankGoldTransaction { + const cooldown = json_utils.getObject(obj, "cooldown") orelse return error.MissingProperty; + const character = json_utils.getObject(obj, "character") orelse return error.MissingProperty; + + return BankGoldTransaction{ + .cooldown = try Cooldown.parse(cooldown), + .character = try Character.parse(api, character, allocator) + }; +} diff --git a/api/schemas/bank_item_transaction.zig b/api/schemas/bank_item_transaction.zig new file mode 100644 index 0000000..c2f00de --- /dev/null +++ b/api/schemas/bank_item_transaction.zig @@ -0,0 +1,23 @@ +const std = @import("std"); +const Server = @import("../server.zig"); +const json_utils = @import("../json_utils.zig"); +const json = std.json; +const Allocator = std.mem.Allocator; + +const Cooldown = @import("./cooldown.zig"); +const Character = @import("./character.zig"); + +const BankItemTransaction = @This(); + +cooldown: Cooldown, +character: Character, + +pub fn parse(api: *Server, obj: json.ObjectMap, allocator: Allocator) !BankItemTransaction { + const cooldown = json_utils.getObject(obj, "cooldown") orelse return error.MissingProperty; + const character = json_utils.getObject(obj, "character") orelse return error.MissingProperty; + + return BankItemTransaction{ + .cooldown = try Cooldown.parse(cooldown), + .character = try Character.parse(api, character, allocator) + }; +} diff --git a/api/character.zig b/api/schemas/character.zig similarity index 96% rename from api/character.zig rename to api/schemas/character.zig index ca77e8b..4aa6607 100644 --- a/api/character.zig +++ b/api/schemas/character.zig @@ -1,8 +1,8 @@ const std = @import("std"); -const json_utils = @import("json_utils.zig"); -const Server = @import("./server.zig"); -const Position = @import("./position.zig"); -const parseDateTime = Server.parseDateTime; +const json_utils = @import("../json_utils.zig"); +const Server = @import("../server.zig"); +const Position = @import("../position.zig"); +const parseDateTime = @import("../date_time/parse.zig").parseDateTime; const ItemId = Server.ItemId; const Allocator = std.mem.Allocator; const json = std.json; diff --git a/api/schemas/character_fight.zig b/api/schemas/character_fight.zig new file mode 100644 index 0000000..93acc7a --- /dev/null +++ b/api/schemas/character_fight.zig @@ -0,0 +1,27 @@ +const std = @import("std"); +const Server = @import("../server.zig"); +const json_utils = @import("../json_utils.zig"); +const json = std.json; +const Allocator = std.mem.Allocator; + +const Cooldown = @import("./cooldown.zig"); +const Fight = @import("./fight.zig"); +const Character = @import("./character.zig"); + +const CharacterFight = @This(); + +cooldown: Cooldown, +fight: Fight, +character: Character, + +pub fn parse(api: *Server, obj: json.ObjectMap, allocator: Allocator) !CharacterFight { + const cooldown = json_utils.getObject(obj, "cooldown") orelse return error.MissingProperty; + const fight = json_utils.getObject(obj, "fight") orelse return error.MissingProperty; + const character = json_utils.getObject(obj, "character") orelse return error.MissingProperty; + + return CharacterFight{ + .cooldown = try Cooldown.parse(cooldown), + .fight = try Fight.parse(api, fight), + .character = try Character.parse(api, character, allocator) + }; +} diff --git a/api/schemas/character_movement.zig b/api/schemas/character_movement.zig new file mode 100644 index 0000000..50d1476 --- /dev/null +++ b/api/schemas/character_movement.zig @@ -0,0 +1,23 @@ +const std = @import("std"); +const Server = @import("../server.zig"); +const json_utils = @import("../json_utils.zig"); +const json = std.json; +const Allocator = std.mem.Allocator; + +const Cooldown = @import("./cooldown.zig"); +const Character = @import("./character.zig"); + +const CharacterMovement = @This(); + +cooldown: Cooldown, +character: Character, + +pub fn parse(api: *Server, obj: json.ObjectMap, allocator: Allocator) !CharacterMovement { + const cooldown = json_utils.getObject(obj, "cooldown") orelse return error.MissingProperty; + const character = json_utils.getObject(obj, "character") orelse return error.MissingProperty; + + return CharacterMovement{ + .cooldown = try Cooldown.parse(cooldown), + .character = try Character.parse(api, character, allocator) + }; +} diff --git a/api/combat_stats.zig b/api/schemas/combat_stats.zig similarity index 90% rename from api/combat_stats.zig rename to api/schemas/combat_stats.zig index 22d7a9f..e3985a4 100644 --- a/api/combat_stats.zig +++ b/api/schemas/combat_stats.zig @@ -1,5 +1,5 @@ const std = @import("std"); -const json_utils = @import("json_utils.zig"); +const json_utils = @import("../json_utils.zig"); const json = std.json; const CombatStats = @This(); diff --git a/api/schemas/cooldown.zig b/api/schemas/cooldown.zig new file mode 100644 index 0000000..2580d4b --- /dev/null +++ b/api/schemas/cooldown.zig @@ -0,0 +1,53 @@ +const std = @import("std"); +const json_utils = @import("../json_utils.zig"); +pub const parseDateTime = @import("../date_time/parse.zig").parseDateTime; +const EnumStringUtils = @import("../enum_string_utils.zig").EnumStringUtils; +const json = std.json; + +const Cooldown = @This(); + +const ReasonUtils = EnumStringUtils(Reason, .{ + .{ "movement" , Reason.movement }, + .{ "fight" , Reason.fight }, + .{ "crafting" , Reason.crafting }, + .{ "gathering" , Reason.gathering }, + .{ "buy_ge" , Reason.buy_ge }, + .{ "sell_ge" , Reason.sell_ge }, + .{ "delete_item" , Reason.delete_item }, + .{ "deposit_bank" , Reason.deposit_bank }, + .{ "withdraw_bank", Reason.withdraw_bank }, + .{ "equip" , Reason.equip }, + .{ "unequip" , Reason.unequip }, + .{ "task" , Reason.task }, + .{ "recycling" , Reason.recycling }, +}); +pub const Reason = enum { + movement, + fight, + crafting, + gathering, + buy_ge, + sell_ge, + delete_item, + deposit_bank, + withdraw_bank, + equip, + unequip, + task, + recycling, + + const parse = ReasonUtils.fromString; +}; + +expiration: f64, +reason: Reason, + +pub fn parse(obj: json.ObjectMap) !Cooldown { + const reason = try json_utils.getStringRequired(obj, "reason"); + const expiration = try json_utils.getStringRequired(obj, "expiration"); + + return Cooldown{ + .expiration = parseDateTime(expiration) orelse return error.InvalidDateTime, + .reason = Reason.parse(reason) orelse return error.UnknownReason + }; +} diff --git a/api/schemas/craft.zig b/api/schemas/craft.zig new file mode 100644 index 0000000..c5fdce9 --- /dev/null +++ b/api/schemas/craft.zig @@ -0,0 +1,35 @@ +const std = @import("std"); +const Server = @import("../server.zig"); +const json_utils = @import("../json_utils.zig"); +const BoundedSlotsArray = @import("./slot_array.zig").BoundedSlotsArray; +const json = std.json; + +const Skill = @import("./skill.zig").Skill; +const SkillUtils = @import("./skill.zig").SkillUtils; + +const Items = BoundedSlotsArray(8); + +const Craft = @This(); + +skill: Skill, +level: u64, +quantity: u64, +items: Items, + +pub fn parse(api: *Server, obj: json.ObjectMap) !Craft { + 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 Craft{ + .skill = SkillUtils.fromString(skill) orelse return error.InvalidSkill, + .level = @intCast(level), + .quantity = @intCast(quantity), + .items = try Items.parse(api, items) + }; +} diff --git a/api/schemas/drop_rate.zig b/api/schemas/drop_rate.zig new file mode 100644 index 0000000..0557f00 --- /dev/null +++ b/api/schemas/drop_rate.zig @@ -0,0 +1,59 @@ +const std = @import("std"); +const Server = @import("../server.zig"); +const json_utils = @import("../json_utils.zig"); +const ItemId = Server.ItemId; +const json = std.json; + +const DropRate = @This(); + +item_id: ItemId, +rate: u64, +min_quantity: u64, +max_quantity: u64, + +pub fn parse(api: *Server, obj: json.ObjectMap) !DropRate { + const rate = try json_utils.getIntegerRequired(obj, "rate"); + if (rate < 1) { + return error.InvalidRate; + } + + const min_quantity = try json_utils.getIntegerRequired(obj, "min_quantity"); + if (min_quantity < 1) { + return error.InvalidMinQuantity; + } + + const max_quantity = try json_utils.getIntegerRequired(obj, "max_quantity"); + if (max_quantity < 1) { + return error.InvalidMinQuantity; + } + + const code_str = try json_utils.getStringRequired(obj, "code"); + const item_id = try api.getItemId(code_str); + + return DropRate{ + .item_id = item_id, + .rate = @intCast(rate), + .min_quantity = @intCast(min_quantity), + .max_quantity = @intCast(max_quantity) + }; +} + +pub const DropRates = std.BoundedArray(DropRate, 8); // TODO: Maybe rename to "List"? + +pub fn parseList(api: *Server, array: json.Array) !DropRates { + var drops = DropRates.init(0) catch unreachable; + for (array.items) |drop_value| { + const drop_obj = json_utils.asObject(drop_value) orelse return error.InvalidObject; + try drops.append(try DropRate.parse(api, drop_obj)); + } + return drops; +} + +pub fn doesListContain(drops: *DropRates, item_id: ItemId) bool { + for (drops.constSlice()) |drop| { + if (drop.item_id == item_id) { + return true; + } + } + return false; +} diff --git a/api/schemas/equip_request.zig b/api/schemas/equip_request.zig new file mode 100644 index 0000000..274e591 --- /dev/null +++ b/api/schemas/equip_request.zig @@ -0,0 +1,28 @@ +const std = @import("std"); +const Server = @import("../server.zig"); +const json_utils = @import("../json_utils.zig"); +const json = std.json; +const ItemId = Server.ItemId; + +const Cooldown = @import("./cooldown.zig"); + +const EquipRequest = @This(); + +cooldown: Cooldown, +item: ItemId, + +pub fn parse(api: *Server, obj: json.ObjectMap) !EquipRequest { + 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 EquipRequest{ + .cooldown = try Cooldown.parse(cooldown), + .item = item_id + }; +} diff --git a/api/equipment.zig b/api/schemas/equipment.zig similarity index 96% rename from api/equipment.zig rename to api/schemas/equipment.zig index 19f9aa4..81b2340 100644 --- a/api/equipment.zig +++ b/api/schemas/equipment.zig @@ -1,6 +1,6 @@ const std = @import("std"); -const json_utils = @import("json_utils.zig"); -const Server = @import("./server.zig"); +const json_utils = @import("../json_utils.zig"); +const Server = @import("../server.zig"); const ItemId = Server.ItemId; const json = std.json; diff --git a/api/schemas/fight.zig b/api/schemas/fight.zig new file mode 100644 index 0000000..8775a3a --- /dev/null +++ b/api/schemas/fight.zig @@ -0,0 +1,46 @@ +const std = @import("std"); +const Server = @import("../server.zig"); +const BoundedSlotsArray = @import("./slot_array.zig").BoundedSlotsArray; +const json_utils = @import("../json_utils.zig"); +const json = std.json; + +const Fight = @This(); + +pub const Drops = BoundedSlotsArray(8); + +xp: u64, +gold: u64, +drops: Drops, +won: bool, + +pub fn parse(api: *Server, obj: json.ObjectMap) !Fight { + const result = try json_utils.getStringRequired(obj, "result"); + + var won = false; + if (std.mem.eql(u8, result, "win")) { + won = true; + } else if (std.mem.eql(u8, result, "lose")) { + won = false; + } else { + return error.InvalidProperty; + } + + const drops_obj = json_utils.getArray(obj, "drops") orelse return error.MissingProperty; + + const xp = try json_utils.getIntegerRequired(obj, "xp"); + if (xp < 0) { + return error.InvalidXp; + } + + const gold = try json_utils.getIntegerRequired(obj, "gold"); + if (gold < 0) { + return error.InvalidGold; + } + + return Fight{ + .xp = @intCast(xp), + .gold = @intCast(gold), + .drops = try Drops.parse(api, drops_obj), + .won = won, + }; +} diff --git a/api/schemas/item.zig b/api/schemas/item.zig new file mode 100644 index 0000000..af0ddf9 --- /dev/null +++ b/api/schemas/item.zig @@ -0,0 +1,75 @@ +const std = @import("std"); +const Server = @import("../server.zig"); +const EnumStringUtils = @import("../enum_string_utils.zig").EnumStringUtils; +const json = std.json; +const Allocator = std.mem.Allocator; +const json_utils = @import("../json_utils.zig"); + +const Craft = @import("./craft.zig"); + +const Item = @This(); + +pub const Type = enum { + consumable, + body_armor, + weapon, + resource, + leg_armor, + helmet, + boots, + shield, + amulet, + ring, + artifact, + currency, +}; +pub const TypeUtils = EnumStringUtils(Type, .{ + .{ "consumable", .consumable }, + .{ "body_armor", .body_armor }, + .{ "weapon" , .weapon }, + .{ "resource" , .resource }, + .{ "leg_armor" , .leg_armor }, + .{ "helmet" , .helmet }, + .{ "boots" , .boots }, + .{ "shield" , .shield }, + .{ "amulet" , .amulet }, + .{ "ring" , .ring }, + .{ "artifact" , .artifact }, + .{ "currency" , .currency }, +}); + +allocator: Allocator, +name: []u8, +code: []u8, +level: u64, +type: Type, +subtype: []u8, +description: []u8, +craft: ?Craft, +// TODO: effects + +pub fn parse(api: *Server, obj: json.ObjectMap, allocator: Allocator) !Item { + const level = json_utils.getInteger(obj, "level") orelse return error.MissingProperty; + if (level < 1) return error.InvalidLevel; + + const craft = json_utils.getObject(obj, "craft"); + const item_type_str = try json_utils.getStringRequired(obj, "type"); + + return Item{ + .allocator = allocator, + .name = (try json_utils.dupeString(allocator, obj, "name")) orelse return error.MissingProperty, + .code = (try json_utils.dupeString(allocator, obj, "code")) orelse return error.MissingProperty, + .level = @intCast(level), + .type = TypeUtils.fromString(item_type_str) orelse return error.InvalidType, + .subtype = (try json_utils.dupeString(allocator, obj, "subtype")) orelse return error.MissingProperty, + .description = (try json_utils.dupeString(allocator, obj, "description")) orelse return error.MissingProperty, + .craft = if (craft != null) try Craft.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.subtype); + self.allocator.free(self.description); +} diff --git a/api/schemas/map.zig b/api/schemas/map.zig new file mode 100644 index 0000000..6e3b0c9 --- /dev/null +++ b/api/schemas/map.zig @@ -0,0 +1,37 @@ +const std = @import("std"); +const Server = @import("../server.zig"); +const Position = @import("../position.zig"); +const json_utils = @import("../json_utils.zig"); +const json = std.json; +const Allocator = std.mem.Allocator; + +const Map = @This(); + +const MapContent = @import("./map_content.zig"); + +name: []u8, +skin: []u8, +position: Position, +content: ?MapContent, + +pub fn parse(api: *Server, obj: json.ObjectMap, allocator: Allocator) !Map { + const content = json_utils.getObject(obj, "content"); + + const x = json_utils.getInteger(obj, "x") orelse return error.MissingProperty; + const y = json_utils.getInteger(obj, "y") orelse return error.MissingProperty; + + return Map{ + .name = (try json_utils.dupeString(allocator, obj, "name")) orelse return error.MissingProperty, + .skin = (try json_utils.dupeString(allocator, obj, "skin")) orelse return error.MissingProperty, + .position = Position.init(x, y), + .content = try MapContent.parse(api, content, allocator) + }; +} + +pub fn deinit(self: Map, allocator: Allocator) void { + allocator.free(self.name); + allocator.free(self.skin); + if (self.content) |content| { + content.deinit(allocator); + } +} diff --git a/api/schemas/map_content.zig b/api/schemas/map_content.zig new file mode 100644 index 0000000..c5c82f2 --- /dev/null +++ b/api/schemas/map_content.zig @@ -0,0 +1,43 @@ +const std = @import("std"); +const Server = @import("../server.zig"); +const json_utils = @import("../json_utils.zig"); +const json = std.json; +const Allocator = std.mem.Allocator; +const EnumStringUtils = @import("../enum_string_utils.zig").EnumStringUtils; + +const MapContent = @This(); + +pub const Type = enum { + monster, + resource, + workshop, + bank, + grand_exchange, + tasks_master, +}; +pub const TypeUtils = EnumStringUtils(Type, .{ + .{ "monster" , Type.monster }, + .{ "resource" , Type.resource }, + .{ "workshop" , Type.workshop }, + .{ "bank" , Type.bank }, + .{ "grand_exchange", Type.grand_exchange }, + .{ "tasks_master" , Type.tasks_master }, +}); + +type: Type, +code: []u8, + +pub fn parse(api: *Server, obj: json.ObjectMap, allocator: Allocator) MapContent { + _ = api; + + const content_type = json_utils.getString(obj, "type") orelse return error.MissingProperty; + + return MapContent{ + .type = TypeUtils.fromString(content_type) orelse return error.InvalidContentType, + .code = (try json_utils.dupeString(allocator, obj, "code")) orelse return error.MissingProperty, + }; +} + +pub fn deinit(self: MapContent, allocator: Allocator) void { + allocator.free(self.code); +} diff --git a/api/schemas/monster.zig b/api/schemas/monster.zig new file mode 100644 index 0000000..18828ee --- /dev/null +++ b/api/schemas/monster.zig @@ -0,0 +1,81 @@ +const std = @import("std"); +const Server = @import("../server.zig"); +const json_utils = @import("../json_utils.zig"); +const json = std.json; +const Allocator = std.mem.Allocator; + +const DropRate = @import("./drop_rate.zig"); +const DropRates = DropRate.DropRates; + +const Monster = @This(); + +pub const ElementalStats = struct { + attack: i64, + resistance: i64, + + pub fn parse(object: json.ObjectMap, attack: []const u8, resistance: []const u8) !ElementalStats { + return ElementalStats{ + .attack = try json_utils.getIntegerRequired(object, attack), + .resistance = try json_utils.getIntegerRequired(object, resistance), + }; + } +}; + +name: []u8, +code: []u8, +level: u64, +hp: u64, +min_gold: u64, +max_gold: u64, + +fire: ElementalStats, +earth: ElementalStats, +water: ElementalStats, +air: ElementalStats, + +drops: DropRates, + +pub fn parse(api: *Server, obj: json.ObjectMap, allocator: Allocator) !Monster { + const drops_array = json_utils.getArray(obj, "drops") orelse return error.MissingProperty; + + const min_gold = try json_utils.getIntegerRequired(obj, "min_gold"); + if (min_gold < 0) { + return error.InvalidMinGold; + } + + const max_gold = try json_utils.getIntegerRequired(obj, "max_gold"); + if (max_gold < 0) { + return error.InvalidMaxGold; + } + + const level = try json_utils.getIntegerRequired(obj, "level"); + if (level < 0) { + return error.InvalidLevel; + } + + const hp = try json_utils.getIntegerRequired(obj, "hp"); + if (hp < 0) { + return error.InvalidHp; + } + + return Monster{ + .name = try json_utils.dupeStringRequired(allocator, obj, "name"), + .code = try json_utils.dupeStringRequired(allocator, obj, "code"), + .level = @intCast(level), + .hp = @intCast(hp), + + .fire = try ElementalStats.parse(obj, "attack_fire" , "res_fire" ), + .earth = try ElementalStats.parse(obj, "attack_earth", "res_earth"), + .water = try ElementalStats.parse(obj, "attack_water", "res_water"), + .air = try ElementalStats.parse(obj, "attack_air" , "res_air" ), + + .min_gold = @intCast(min_gold), + .max_gold = @intCast(max_gold), + .drops = try DropRate.parseList(api, drops_array) + }; +} + +pub fn deinit(self: Monster, allocator: Allocator) void { + allocator.free(self.name); + allocator.free(self.code); +} diff --git a/api/schemas/resource.zig b/api/schemas/resource.zig new file mode 100644 index 0000000..9a9a5a2 --- /dev/null +++ b/api/schemas/resource.zig @@ -0,0 +1,52 @@ +const std = @import("std"); +const Server = @import("../server.zig"); +const EnumStringUtils = @import("../enum_string_utils.zig").EnumStringUtils; +const json_utils = @import("../json_utils.zig"); +const json = std.json; +const Allocator = std.mem.Allocator; + +const DropRate = @import("./drop_rate.zig"); +const DropRates = DropRate.DropRates; + +const Resource = @This(); + +pub const Skill = enum { + mining, + woodcutting, + fishing, +}; +pub const SkillUtils = EnumStringUtils(Skill, .{ + .{ "mining" , .mining }, + .{ "woodcutting", .woodcutting }, + .{ "fishing" , .fishing }, +}); + +name: []u8, +code: []u8, +skill: Skill, +level: u64, +drops: DropRates, + +pub fn parse(api: *Server, obj: json.ObjectMap, allocator: Allocator) !Resource { + const drops_array = json_utils.getArray(obj, "drops") orelse return error.MissingProperty; + + const level = try json_utils.getIntegerRequired(obj, "level"); + if (level < 0) { + return error.InvalidLevel; + } + + const skill_str = try json_utils.getStringRequired(obj, "skill"); + + return Resource{ + .name = try json_utils.dupeStringRequired(allocator, obj, "name"), + .code = try json_utils.dupeStringRequired(allocator, obj, "code"), + .level = @intCast(level), + .skill = SkillUtils.fromString(skill_str) orelse return error.InvalidSkill, + .drops = try DropRate.parseList(api, drops_array) + }; +} + +pub fn deinit(self: Resource, allocator: Allocator) void { + allocator.free(self.name); + allocator.free(self.code); +} diff --git a/api/schemas/single_item.zig b/api/schemas/single_item.zig new file mode 100644 index 0000000..18a211c --- /dev/null +++ b/api/schemas/single_item.zig @@ -0,0 +1,22 @@ +const std = @import("std"); +const Server = @import("../server.zig"); +const json_utils = @import("../json_utils.zig"); +const json = std.json; +const Allocator = std.mem.Allocator; + +const Item = @import("./item.zig"); + +const SingleItem = @This(); + +item: Item, +// TODO: Grand exchange + +pub fn parse(api: *Server, obj: json.ObjectMap, allocator: Allocator) !SingleItem { + const item_obj = json_utils.getObject(obj, "item") orelse return error.MissingProperty; + const ge_obj = json_utils.getObject(obj, "ge") orelse return error.MissingProperty; + _ = ge_obj; + + return SingleItem{ + .item = try Item.parse(api, item_obj, allocator), + }; +} diff --git a/api/schemas/skill.zig b/api/schemas/skill.zig new file mode 100644 index 0000000..c8598b1 --- /dev/null +++ b/api/schemas/skill.zig @@ -0,0 +1,18 @@ +const EnumStringUtils = @import("../enum_string_utils.zig").EnumStringUtils; + +pub const Skill = enum { + weaponcrafting, + gearcrafting, + jewelrycrafting, + cooking, + woodcutting, + mining, +}; +pub const SkillUtils = EnumStringUtils(Skill, .{ + .{ "weaponcrafting" , Skill.weaponcrafting }, + .{ "gearcrafting" , Skill.gearcrafting }, + .{ "jewelrycrafting", Skill.jewelrycrafting }, + .{ "cooking" , Skill.cooking }, + .{ "woodcutting" , Skill.woodcutting }, + .{ "mining" , Skill.mining }, +}); diff --git a/api/schemas/skill_data.zig b/api/schemas/skill_data.zig new file mode 100644 index 0000000..73da878 --- /dev/null +++ b/api/schemas/skill_data.zig @@ -0,0 +1,27 @@ +const std = @import("std"); +const Server = @import("../server.zig"); +const json_utils = @import("../json_utils.zig"); +const json = std.json; +const Allocator = std.mem.Allocator; + +const Cooldown = @import("./cooldown.zig"); +const Character = @import("./character.zig"); +const SkillInfo = @import("./skill_info.zig"); + +const SkillData = @This(); + +cooldown: Cooldown, +details: SkillInfo, +character: Character, + +pub fn parse(api: *Server, obj: json.ObjectMap, allocator: Allocator) !SkillData { + const cooldown = json_utils.getObject(obj, "cooldown") orelse return error.MissingProperty; + const details = json_utils.getObject(obj, "details") orelse return error.MissingProperty; + const character = json_utils.getObject(obj, "character") orelse return error.MissingProperty; + + return SkillData{ + .cooldown = try Cooldown.parse(cooldown), + .details = try SkillInfo.parse(api, details), + .character = try Character.parse(api, character, allocator) + }; +} diff --git a/api/schemas/skill_info.zig b/api/schemas/skill_info.zig new file mode 100644 index 0000000..09256fd --- /dev/null +++ b/api/schemas/skill_info.zig @@ -0,0 +1,21 @@ +const std = @import("std"); +const Server = @import("../server.zig"); +const BoundedSlotsArray = @import("./slot_array.zig").BoundedSlotsArray; +const json_utils = @import("../json_utils.zig"); +const json = std.json; + +const Items = BoundedSlotsArray(8); + +const SkillInfo = @This(); + +xp: i64, +items: Items, + +pub fn parse(api: *Server, obj: json.ObjectMap) !SkillInfo { + const items = json_utils.getArray(obj, "items") orelse return error.MissingProperty; + + return SkillInfo{ + .xp = try json_utils.getIntegerRequired(obj, "xp"), + .items = try Items.parse(api, items), + }; +} diff --git a/api/skill_stats.zig b/api/schemas/skill_stats.zig similarity index 90% rename from api/skill_stats.zig rename to api/schemas/skill_stats.zig index 4ecdb59..8de35a3 100644 --- a/api/skill_stats.zig +++ b/api/schemas/skill_stats.zig @@ -1,5 +1,5 @@ const std = @import("std"); -const json_utils = @import("./json_utils.zig"); +const json_utils = @import("../json_utils.zig"); const json = std.json; const SkillStats = @This(); diff --git a/api/slot.zig b/api/schemas/slot.zig similarity index 85% rename from api/slot.zig rename to api/schemas/slot.zig index 1f2dea5..eb547c5 100644 --- a/api/slot.zig +++ b/api/schemas/slot.zig @@ -1,6 +1,6 @@ const std = @import("std"); -const json_utils = @import("json_utils.zig"); -const Server = @import("./server.zig"); +const json_utils = @import("../json_utils.zig"); +const Server = @import("../server.zig"); const ItemId = Server.ItemId; const json = std.json; diff --git a/api/slot_array.zig b/api/schemas/slot_array.zig similarity index 97% rename from api/slot_array.zig rename to api/schemas/slot_array.zig index fbb35ae..10bd278 100644 --- a/api/slot_array.zig +++ b/api/schemas/slot_array.zig @@ -1,6 +1,6 @@ const std = @import("std"); -const json_utils = @import("json_utils.zig"); -const Server = @import("./server.zig"); +const json_utils = @import("../json_utils.zig"); +const Server = @import("../server.zig"); const ItemId = Server.ItemId; const assert = std.debug.assert; const json = std.json; diff --git a/api/schemas/status.zig b/api/schemas/status.zig new file mode 100644 index 0000000..5f5af55 --- /dev/null +++ b/api/schemas/status.zig @@ -0,0 +1,32 @@ +const std = @import("std"); +const Server = @import("../server.zig"); +const json_utils = @import("../json_utils.zig"); +const json = std.json; +const Allocator = std.mem.Allocator; + +const ServerStatus = @This(); + +allocator: Allocator, +status: []const u8, +version: []const u8, +characters_online: u64, + +pub fn parse(api: *Server, object: json.ObjectMap, allocator: Allocator) !ServerStatus { + _ = api; + const characters_online = json_utils.getInteger(object, "characters_online") orelse return error.MissingProperty; + if (characters_online < 0) { + return error.InvalidCharactersOnline; + } + + return ServerStatus{ + .allocator = allocator, + .characters_online = @intCast(characters_online), + .status = (try json_utils.dupeString(allocator, object, "status")) orelse return error.MissingStatus, + .version = (try json_utils.dupeString(allocator, object, "version")) orelse return error.MissingVersion + }; +} + +pub fn deinit(self: ServerStatus) void { + self.allocator.free(self.status); + self.allocator.free(self.version); +} diff --git a/api/schemas/task.zig b/api/schemas/task.zig new file mode 100644 index 0000000..4e3575e --- /dev/null +++ b/api/schemas/task.zig @@ -0,0 +1,40 @@ +const std = @import("std"); +const Server = @import("../server.zig"); +const json_utils = @import("../json_utils.zig"); +const EnumStringUtils = @import("../enum_string_utils.zig").EnumStringUtils; +const json = std.json; +const ItemId = Server.ItemId; + +const Task = @This(); + +pub const Type = enum { + monsters, + resources, + crafts +}; +pub const TypeUtils = EnumStringUtils(Type, .{ + .{ "monsters" , Type.monsters }, + .{ "resources", Type.resources }, + .{ "crafts" , Type.crafts }, +}); + +id: ItemId, // TODO: Refactor `ItemId` to include other object types +type: Type, +total: u64, + +pub fn parse(api: *Server, obj: json.ObjectMap) !Task { + const task_type = try json_utils.getStringRequired(obj, "type"); + const total = try json_utils.getIntegerRequired(obj, "total"); + if (total < 0) { + return error.InvalidTaskTotal; + } + + const code = json_utils.getStringRequired(obj, "code"); + const id = try api.getItemId(code); + + return Task{ + .id = id, + .type = TypeUtils.fromString(task_type) orelse return error.InvalidTaskType, + .total = @intCast(total) + }; +} diff --git a/api/schemas/task_data.zig b/api/schemas/task_data.zig new file mode 100644 index 0000000..e099cb5 --- /dev/null +++ b/api/schemas/task_data.zig @@ -0,0 +1,27 @@ +const std = @import("std"); +const Server = @import("../server.zig"); +const json_utils = @import("../json_utils.zig"); +const json = std.json; +const Allocator = std.mem.Allocator; + +const Cooldown = @import("./cooldown.zig"); +const Character = @import("./character.zig"); +const Task = @import("./task.zig"); + +const TaskData = @This(); + +cooldown: Cooldown, +character: Character, +task: Task, + +pub fn parse(api: *Server, obj: json.ObjectMap, allocator: Allocator) !TaskData { + const cooldown = json_utils.getObject(obj, "cooldown") orelse return error.MissingProperty; + const character = json_utils.getObject(obj, "character") orelse return error.MissingProperty; + const task = json_utils.getObject(obj, "task") orelse return error.MissingProperty; + + return TaskData{ + .cooldown = try Cooldown.parse(cooldown), + .character = try Character.parse(api, character, allocator), + .task = try Task.parse(api, task) + }; +} diff --git a/api/server.zig b/api/server.zig index c9309e4..e3282aa 100644 --- a/api/server.zig +++ b/api/server.zig @@ -3,14 +3,9 @@ const assert = std.debug.assert; const json = std.json; const Allocator = std.mem.Allocator; -// TODO: Maybe it would be good to move date time parsing to separate module -pub const parseDateTime = @import("./date_time/parse.zig").parseDateTime; - const json_utils = @import("json_utils.zig"); -pub const Character = @import("character.zig"); -const BoundedSlotsArray = @import("./slot_array.zig").BoundedSlotsArray; +pub const Character = @import("./schemas/character.zig"); const EnumStringUtils = @import("./enum_string_utils.zig").EnumStringUtils; -pub const Slot = @import("./slot.zig"); pub const Position = @import("./position.zig"); const errors = @import("./errors.zig"); @@ -46,147 +41,28 @@ prefetched_items: bool = false, // ------------------------- API result structs ------------------------ -pub const EquipmentSlot = @import("./equipment.zig").Slot; - -pub const Skill = enum { - weaponcrafting, - gearcrafting, - jewelrycrafting, - cooking, - woodcutting, - mining, -}; -pub const SkillUtils = EnumStringUtils(Skill, .{ - .{ "weaponcrafting" , Skill.weaponcrafting }, - .{ "gearcrafting" , Skill.gearcrafting }, - .{ "jewelrycrafting", Skill.jewelrycrafting }, - .{ "cooking" , Skill.cooking }, - .{ "woodcutting" , Skill.woodcutting }, - .{ "mining" , Skill.mining }, -}); - -const ServerStatus = struct { - allocator: Allocator, - status: []const u8, - version: []const u8, - characters_online: i64, - - pub fn parse(api: *Server, object: json.ObjectMap, allocator: Allocator) !ServerStatus { - _ = api; - - return ServerStatus{ - .allocator = allocator, - .characters_online = json_utils.getInteger(object, "characters_online") orelse return error.MissingProperty, - .status = (try json_utils.dupeString(allocator, object, "status")) orelse return error.MissingStatus, - .version = (try json_utils.dupeString(allocator, object, "version")) orelse return error.MissingVersion - }; - } - - pub fn deinit(self: ServerStatus) void { - self.allocator.free(self.status); - self.allocator.free(self.version); - } -}; - -pub const Cooldown = struct { - pub const Reason = enum { - movement, - fight, - crafting, - gathering, - buy_ge, - sell_ge, - delete_item, - deposit_bank, - withdraw_bank, - equip, - unequip, - task, - recycling, - - fn parse(str: []const u8) ?Reason { - const Mapping = std.ComptimeStringMap(Reason, .{ - .{ "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 }, - }); - - return Mapping.get(str); - } - }; - - expiration: f64, - reason: Reason, - - pub fn parse(obj: json.ObjectMap) !Cooldown { - const reason = try json_utils.getStringRequired(obj, "reason"); - const expiration = try json_utils.getStringRequired(obj, "expiration"); - - return Cooldown{ - .expiration = parseDateTime(expiration) orelse return error.InvalidDateTime, - .reason = Reason.parse(reason) orelse return error.UnknownReason - }; - } -}; - -pub const FightResult = struct { - const Details = struct { - const Result = enum { win, lose }; - const Drops = BoundedSlotsArray(8); - - xp: i64, - gold: i64, - drops: Drops, - result: Result, - - fn parse(api: *Server, obj: json.ObjectMap) !Details { - const result = try json_utils.getStringRequired(obj, "result"); - var result_enum: Result = undefined; - if (std.mem.eql(u8, result, "win")) { - result_enum = .win; - } else if (std.mem.eql(u8, result, "win")) { - result_enum = .lose; - } else { - return error.InvalidProperty; - } - - const drops_obj = json_utils.getArray(obj, "drops") orelse return error.MissingProperty; - - return Details{ - .xp = try json_utils.getIntegerRequired(obj, "xp"), - .gold = try json_utils.getIntegerRequired(obj, "gold"), - .drops = try Drops.parse(api, drops_obj), - .result = result_enum, - }; - } - }; - - cooldown: Cooldown, - fight: Details, - character: Character, - - pub fn parse(api: *Server, obj: json.ObjectMap, allocator: Allocator) !FightResult { - const cooldown = json_utils.getObject(obj, "cooldown") orelse return error.MissingProperty; - const fight = json_utils.getObject(obj, "fight") orelse return error.MissingProperty; - const character = json_utils.getObject(obj, "character") orelse return error.MissingProperty; - - return FightResult{ - .cooldown = try Cooldown.parse(cooldown), - .fight = try Details.parse(api, fight), - .character = try Character.parse(api, character, allocator) - }; - } -}; +pub const Slot = @import("./schemas/slot.zig"); +const BoundedSlotsArray = @import("./schemas/slot_array.zig").BoundedSlotsArray; +pub const EquipmentSlot = @import("./schemas/equipment.zig").Slot; +pub const ServerStatus = @import("./schemas/status.zig"); +pub const Cooldown = @import("./schemas/cooldown.zig"); +pub const FightResult = @import("./schemas/character_fight.zig"); +pub const Skill = @import("./schemas/skill.zig").Skill; +pub const GatherResult = @import("./schemas/skill_data.zig"); +pub const MoveResult = @import("./schemas/character_movement.zig"); +pub const GoldTransactionResult = @import("./schemas/bank_gold_transaction.zig"); +pub const ItemTransactionResult = @import("./schemas/bank_item_transaction.zig"); +pub const CraftResult = @import("./schemas/skill_data.zig"); +pub const UnequipResult = @import("./schemas/equip_request.zig"); +pub const EquipResult = @import("./schemas/equip_request.zig"); +const DropRate = @import("./schemas/drop_rate.zig"); +pub const AcceptTaskResult = @import("./schemas/task_data.zig"); +pub const MapContent = @import("./schemas/map_content.zig"); +pub const MapTile = @import("./schemas/map.zig"); +pub const Item = @import("./schemas/item.zig"); +pub const ItemWithGE = @import("./schemas/single_item.zig"); +pub const Resource = @import("./schemas/resource.zig"); +pub const Monster = @import("./schemas/monster.zig"); // TODO: Replace this with ItemSlot struct pub const ItemIdQuantity = struct { @@ -212,530 +88,6 @@ pub const ItemIdQuantity = struct { } }; -pub const SkillResultDetails = struct { - const Items = BoundedSlotsArray(8); - - xp: i64, - items: Items, - - fn parse(api: *Server, obj: json.ObjectMap) !SkillResultDetails { - const items = json_utils.getArray(obj, "items") orelse return error.MissingProperty; - - return SkillResultDetails{ - .xp = try json_utils.getIntegerRequired(obj, "xp"), - .items = try Items.parse(api, items), - }; - } -}; - -pub const GatherResult = struct { - cooldown: Cooldown, - details: SkillResultDetails, - character: Character, - - pub fn parse(api: *Server, obj: json.ObjectMap, allocator: Allocator) !GatherResult { - const cooldown = json_utils.getObject(obj, "cooldown") orelse return error.MissingProperty; - const details = json_utils.getObject(obj, "details") orelse return error.MissingProperty; - const character = json_utils.getObject(obj, "character") orelse return error.MissingProperty; - - return GatherResult{ - .cooldown = try Cooldown.parse(cooldown), - .details = try SkillResultDetails.parse(api, details), - .character = try Character.parse(api, character, allocator) - }; - } -}; - -pub const MoveResult = struct { - cooldown: Cooldown, - character: Character, - - pub fn parse(api: *Server, obj: json.ObjectMap, allocator: Allocator) !MoveResult { - const cooldown = json_utils.getObject(obj, "cooldown") orelse return error.MissingProperty; - const character = json_utils.getObject(obj, "character") orelse return error.MissingProperty; - - return MoveResult{ - .cooldown = try Cooldown.parse(cooldown), - .character = try Character.parse(api, character, allocator) - }; - } -}; - -pub const GoldTransactionResult = struct { - cooldown: Cooldown, - character: Character, - - pub fn parse(api: *Server, obj: json.ObjectMap, allocator: Allocator) !GoldTransactionResult { - const cooldown = json_utils.getObject(obj, "cooldown") orelse return error.MissingProperty; - const character = json_utils.getObject(obj, "character") orelse return error.MissingProperty; - - return GoldTransactionResult{ - .cooldown = try Cooldown.parse(cooldown), - .character = try Character.parse(api, character, allocator) - }; - } -}; - -pub const ItemTransactionResult = struct { - cooldown: Cooldown, - character: Character, - - pub fn parse(api: *Server, obj: json.ObjectMap, allocator: Allocator) !ItemTransactionResult { - const cooldown = json_utils.getObject(obj, "cooldown") orelse return error.MissingProperty; - const character = json_utils.getObject(obj, "character") orelse return error.MissingProperty; - - return ItemTransactionResult{ - .cooldown = try Cooldown.parse(cooldown), - .character = try Character.parse(api, character, allocator) - }; - } -}; - -pub const CraftResult = struct { - cooldown: Cooldown, - details: SkillResultDetails, - character: Character, - - pub fn parse(api: *Server, obj: json.ObjectMap, allocator: Allocator) !CraftResult { - const cooldown = json_utils.getObject(obj, "cooldown") orelse return error.MissingProperty; - const details = json_utils.getObject(obj, "details") orelse return error.MissingProperty; - const character = json_utils.getObject(obj, "character") orelse return error.MissingProperty; - - return CraftResult{ - .cooldown = try Cooldown.parse(cooldown), - .details = try SkillResultDetails.parse(api, details), - .character = try Character.parse(api, character, allocator) - }; - } -}; - -pub const UnequipResult = struct { - cooldown: Cooldown, - item: ItemId, - - pub fn parse(api: *Server, 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 const EquipResult = struct { - cooldown: Cooldown, - - pub fn parse(api: *Server, 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 const TaskType = enum { - monsters, - resources, - crafts -}; -pub const TaskTypeUtils = EnumStringUtils(TaskType, .{ - .{ "monsters" , TaskType.monsters }, - .{ "resources", TaskType.resources }, - .{ "crafts" , TaskType.crafts }, -}); - -pub const Task = struct { - id: ItemId, // TODO: Refactor `ItemId` to include other object types - type: TaskType, - total: u64, - - pub fn parse(api: *Server, obj: json.ObjectMap) !Task { - const task_type = try json_utils.getStringRequired(obj, "type"); - const total = try json_utils.getIntegerRequired(obj, "total"); - if (total < 0) { - return error.InvalidTaskTotal; - } - - const code = json_utils.getStringRequired(obj, "code"); - const id = try api.getItemId(code); - - return Task{ - .id = id, - .type = TaskTypeUtils.fromString(task_type) orelse return error.InvalidTaskType, - .total = @intCast(total) - }; - } -}; - -pub const AcceptTaskResult = struct { - cooldown: Cooldown, - character: Character, - task: Task, - - pub fn parse(api: *Server, obj: json.ObjectMap, allocator: Allocator) !AcceptTaskResult { - const cooldown = json_utils.getObject(obj, "cooldown") orelse return error.MissingProperty; - const character = json_utils.getObject(obj, "character") orelse return error.MissingProperty; - const task = json_utils.getObject(obj, "task") orelse return error.MissingProperty; - - return EquipResult{ - .cooldown = try Cooldown.parse(cooldown), - .character = try Character.parse(api, character, allocator), - .task = try Task.parse(api, task) - }; - } -}; - -pub const MapContentType = enum { - monster, - resource, - workshop, - bank, - grand_exchange, - tasks_master, -}; -pub const MapContentTypeUtils = EnumStringUtils(MapContentType, .{ - .{ "monster" , MapContentType.monster }, - .{ "resource" , MapContentType.resource }, - .{ "workshop" , MapContentType.workshop }, - .{ "bank" , MapContentType.bank }, - .{ "grand_exchange", MapContentType.grand_exchange }, - .{ "tasks_master" , MapContentType.tasks_master }, -}); - -pub const MapTile = struct { - pub const MapContent = struct { - type: MapContentType, - code: []u8, - }; - - name: []u8, - skin: []u8, - position: Position, - content: ?MapContent, - - pub fn parse(api: *Server, obj: json.ObjectMap, allocator: Allocator) !MapTile { - _ = api; - - var content: ?MapContent = null; - if (json_utils.getObject(obj, "content")) |content_obj| { - const content_type = json_utils.getString(content_obj, "type") orelse return error.MissingProperty; - - content = MapContent{ - .type = MapContentTypeUtils.fromString(content_type) orelse return error.InvalidContentType, - .code = (try json_utils.dupeString(allocator, content_obj, "code")) orelse return error.MissingProperty, - }; - } - - const x = json_utils.getInteger(obj, "x") orelse return error.MissingProperty; - const y = json_utils.getInteger(obj, "y") orelse return error.MissingProperty; - - return MapTile{ - .name = (try json_utils.dupeString(allocator, obj, "name")) orelse return error.MissingProperty, - .skin = (try json_utils.dupeString(allocator, obj, "skin")) orelse return error.MissingProperty, - .position = Position.init(x, y), - .content = content - }; - } - - pub fn deinit(self: MapTile, allocator: Allocator) void { - allocator.free(self.name); - allocator.free(self.skin); - if (self.content) |content| { - allocator.free(content.code); - } - } -}; - -pub const ItemType = enum { - consumable, - body_armor, - weapon, - resource, - leg_armor, - helmet, - boots, - shield, - amulet, - ring, - artifact, - currency, -}; -const ItemTypeUtils = EnumStringUtils(ItemType, .{ - .{ "consumable", .consumable }, - .{ "body_armor", .body_armor }, - .{ "weapon" , .weapon }, - .{ "resource" , .resource }, - .{ "leg_armor" , .leg_armor }, - .{ "helmet" , .helmet }, - .{ "boots" , .boots }, - .{ "shield" , .shield }, - .{ "amulet" , .amulet }, - .{ "ring" , .ring }, - .{ "artifact" , .artifact }, - .{ "currency" , .currency }, -}); - -pub const Item = struct { - pub const Recipe = struct { - const Items = BoundedSlotsArray(8); - - skill: Skill, - level: u64, - quantity: u64, - items: Items, - - pub fn parse(api: *Server, 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 = SkillUtils.fromString(skill) orelse return error.InvalidSkill, - .level = @intCast(level), - .quantity = @intCast(quantity), - .items = try Items.parse(api, items) - }; - } - }; - - allocator: Allocator, - name: []u8, - code: []u8, - level: u64, - type: ItemType, - subtype: []u8, - description: []u8, - craft: ?Recipe, - // TODO: effects - - pub fn parse(api: *Server, obj: json.ObjectMap, allocator: Allocator) !Item { - const level = json_utils.getInteger(obj, "level") orelse return error.MissingProperty; - if (level < 1) return error.InvalidLevel; - - const craft = json_utils.getObject(obj, "craft"); - const item_type_str = try json_utils.getStringRequired(obj, "type"); - - return Item{ - .allocator = allocator, - .name = (try json_utils.dupeString(allocator, obj, "name")) orelse return error.MissingProperty, - .code = (try json_utils.dupeString(allocator, obj, "code")) orelse return error.MissingProperty, - .level = @intCast(level), - .type = ItemTypeUtils.fromString(item_type_str) orelse return error.InvalidType, - .subtype = (try json_utils.dupeString(allocator, obj, "subtype")) orelse return error.MissingProperty, - .description = (try json_utils.dupeString(allocator, 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.subtype); - self.allocator.free(self.description); - } -}; - -pub const ItemWithGE = struct { - item: Item, - // TODO: Grand exchange - - pub fn parse(api: *Server, obj: json.ObjectMap, allocator: Allocator) !ItemWithGE { - const item_obj = json_utils.getObject(obj, "item") orelse return error.MissingProperty; - const ge_obj = json_utils.getObject(obj, "ge") orelse return error.MissingProperty; - _ = ge_obj; - - return ItemWithGE{ - .item = try Item.parse(api, item_obj, allocator), - }; - } -}; - -pub const ResourceSkill = enum { - mining, - woodcutting, - fishing, -}; -const ResourceSkillUtils = EnumStringUtils(ResourceSkill, .{ - .{ "mining" , .mining }, - .{ "woodcutting", .woodcutting }, - .{ "fishing" , .fishing }, -}); - -const DropRates = std.BoundedArray(DropRate, 8); -const DropRate = struct { - item_id: ItemId, - rate: u64, - min_quantity: u64, - max_quantity: u64, - - fn parse(api: *Server, obj: json.ObjectMap) !DropRate { - const rate = try json_utils.getIntegerRequired(obj, "rate"); - if (rate < 1) { - return error.InvalidRate; - } - - const min_quantity = try json_utils.getIntegerRequired(obj, "min_quantity"); - if (min_quantity < 1) { - return error.InvalidMinQuantity; - } - - const max_quantity = try json_utils.getIntegerRequired(obj, "max_quantity"); - if (max_quantity < 1) { - return error.InvalidMinQuantity; - } - - const code_str = try json_utils.getStringRequired(obj, "code"); - const item_id = try api.getItemId(code_str); - - return DropRate{ - .item_id = item_id, - .rate = @intCast(rate), - .min_quantity = @intCast(min_quantity), - .max_quantity = @intCast(max_quantity) - }; - } - - fn parseList(api: *Server, array: json.Array) !DropRates { - var drops = DropRates.init(0) catch unreachable; - for (array.items) |drop_value| { - const drop_obj = json_utils.asObject(drop_value) orelse return error.InvalidObject; - try drops.append(try DropRate.parse(api, drop_obj)); - } - return drops; - } - - fn doesListContain(drops: *DropRates, item_id: ItemId) bool { - for (drops.constSlice()) |drop| { - if (drop.item_id == item_id) { - return true; - } - } - return false; - } -}; - -pub const Resource = struct { - name: []u8, - code: []u8, - skill: ResourceSkill, - level: u64, - drops: DropRates, - - fn parse(api: *Server, obj: json.ObjectMap, allocator: Allocator) !Resource { - const drops_array = json_utils.getArray(obj, "drops") orelse return error.MissingProperty; - - const level = try json_utils.getIntegerRequired(obj, "level"); - if (level < 0) { - return error.InvalidLevel; - } - - const skill_str = try json_utils.getStringRequired(obj, "skill"); - - return Resource{ - .name = try json_utils.dupeStringRequired(allocator, obj, "name"), - .code = try json_utils.dupeStringRequired(allocator, obj, "code"), - .level = @intCast(level), - .skill = ResourceSkillUtils.fromString(skill_str) orelse return error.InvalidSkill, - .drops = try DropRate.parseList(api, drops_array) - }; - } - - fn deinit(self: Resource, allocator: Allocator) void { - allocator.free(self.name); - allocator.free(self.code); - } -}; - -pub const Monster = struct { - const ElementalStats = struct { - attack: i64, - resistance: i64, - - pub fn parse(object: json.ObjectMap, attack: []const u8, resistance: []const u8) !ElementalStats { - return ElementalStats{ - .attack = try json_utils.getIntegerRequired(object, attack), - .resistance = try json_utils.getIntegerRequired(object, resistance), - }; - } - }; - - name: []u8, - code: []u8, - level: u64, - hp: u64, - min_gold: u64, - max_gold: u64, - - fire: ElementalStats, - earth: ElementalStats, - water: ElementalStats, - air: ElementalStats, - - drops: DropRates, - - fn parse(api: *Server, obj: json.ObjectMap, allocator: Allocator) !Monster { - const drops_array = json_utils.getArray(obj, "drops") orelse return error.MissingProperty; - - const min_gold = try json_utils.getIntegerRequired(obj, "min_gold"); - if (min_gold < 0) { - return error.InvalidMinGold; - } - - const max_gold = try json_utils.getIntegerRequired(obj, "max_gold"); - if (max_gold < 0) { - return error.InvalidMaxGold; - } - - const level = try json_utils.getIntegerRequired(obj, "level"); - if (level < 0) { - return error.InvalidLevel; - } - - const hp = try json_utils.getIntegerRequired(obj, "hp"); - if (hp < 0) { - return error.InvalidHp; - } - - return Monster{ - .name = try json_utils.dupeStringRequired(allocator, obj, "name"), - .code = try json_utils.dupeStringRequired(allocator, obj, "code"), - .level = @intCast(level), - .hp = @intCast(hp), - - .fire = try ElementalStats.parse(obj, "attack_fire" , "res_fire" ), - .earth = try ElementalStats.parse(obj, "attack_earth", "res_earth"), - .water = try ElementalStats.parse(obj, "attack_water", "res_water"), - .air = try ElementalStats.parse(obj, "attack_air" , "res_air" ), - - .min_gold = @intCast(min_gold), - .max_gold = @intCast(max_gold), - .drops = try DropRate.parseList(api, drops_array) - }; - } - - fn deinit(self: Monster, allocator: Allocator) void { - allocator.free(self.name); - allocator.free(self.code); - } -}; - pub const ArtifactsFetchResult = struct { arena: std.heap.ArenaAllocator, status: std.http.Status, @@ -746,16 +98,6 @@ pub const ArtifactsFetchResult = struct { } }; -fn appendQueryParam(query: *std.ArrayList(u8), key: []const u8, value: []const u8) !void { - if (query.items.len > 0) { - try query.appendSlice("&"); - } - - try query.appendSlice(key); - try query.appendSlice("="); - try query.appendSlice(value); -} - // ------------------------- General API methods ------------------------ pub fn init(allocator: Allocator) !Server { @@ -829,6 +171,16 @@ const FetchOptions = struct { paginated: bool = false }; +fn appendQueryParam(query: *std.ArrayList(u8), key: []const u8, value: []const u8) !void { + if (query.items.len > 0) { + try query.appendSlice("&"); + } + + try query.appendSlice(key); + try query.appendSlice("="); + try query.appendSlice(value); +} + fn allocPaginationParams(allocator: Allocator, page: u64, page_size: ?u64) ![]u8 { if (page_size) |size| { return try std.fmt.allocPrint(allocator, "page={}&size={}", .{page, size}); @@ -1609,7 +961,7 @@ pub fn getMap(self: *Server, x: i64, y: i64) FetchError!?MapTile { pub const MapOptions = struct { code: ?[]const u8 = null, - type: ?MapContentType = null, + type: ?MapContent.Type = null, }; pub fn getMaps(self: *Server, opts: MapOptions) FetchError!std.ArrayList(MapTile) { @@ -1638,7 +990,7 @@ pub fn getMaps(self: *Server, opts: MapOptions) FetchError!std.ArrayList(MapTile try appendQueryParam(&query, "content_code", code); } if (opts.type) |map_type| { - try appendQueryParam(&query, "content_type", MapContentTypeUtils.toString(map_type)); + try appendQueryParam(&query, "content_type", MapContent.TypeUtils.toString(map_type)); } const result = try self.fetchArray( @@ -1697,7 +1049,7 @@ pub const ItemOptions = struct { min_level: ?u64 = null, max_level: ?u64 = null, name: ?[]const u8 = null, - type: ?ItemType = null, + type: ?Item.Type = null, }; pub fn getItems(self: *Server, opts: ItemOptions) FetchError!std.ArrayList(Item) { @@ -1756,7 +1108,7 @@ pub fn getItems(self: *Server, opts: ItemOptions) FetchError!std.ArrayList(Item) try appendQueryParam(&query, "name", name); } if (opts.type) |item_type| { - try appendQueryParam(&query, "type", ItemTypeUtils.toString(item_type)); + try appendQueryParam(&query, "type", Item.TypeUtils.toString(item_type)); } const result = try self.fetchArray( @@ -1807,7 +1159,7 @@ pub const ResourceOptions = struct { drop: ?[]const u8 = null, max_level: ?u64 = null, min_level: ?u64 = null, - skill: ?ResourceSkill = null, + skill: ?Resource.Skill = null, }; pub fn getResources(self: *Server, opts: ResourceOptions) FetchError!std.ArrayList(Resource) { @@ -1851,7 +1203,7 @@ pub fn getResources(self: *Server, opts: ResourceOptions) FetchError!std.ArrayLi try appendQueryParam(&query, "max_level", max_level_str); } if (opts.skill) |skill| { - try appendQueryParam(&query, "skill", ResourceSkillUtils.toString(skill)); + try appendQueryParam(&query, "skill", Resource.SkillUtils.toString(skill)); } const result = try self.fetchArray(