const std = @import("std"); const json_utils = @import("../json_utils.zig"); const Store = @import("../store.zig"); const Position = @import("../position.zig"); const parseDateTime = @import("../date_time/parse.zig").parseDateTime; const Allocator = std.mem.Allocator; const json = std.json; const assert = std.debug.assert; const SkillStats = @import("./skill_stats.zig"); const CombatStats = @import("./combat_stats.zig"); const Equipment = @import("./equipment.zig"); const Task = @import("./task.zig"); const BoundedSlotsArray = @import("./slot_array.zig").BoundedSlotsArray; const Inventory = BoundedSlotsArray(20); const Character = @This(); const TaskMasterTask = struct { target_id: Store.CodeId, type: Task.Type, progress: u64, total: u64, fn parse(store: *Store, obj: json.ObjectMap) !TaskMasterTask { const task_target = try json_utils.getStringRequired(obj, "task"); const task_type = try json_utils.getStringRequired(obj, "task_type"); const progress = try json_utils.getIntegerRequired(obj, "task_progress"); if (progress < 0) { return error.InvalidTaskProgress; } const total = try json_utils.getIntegerRequired(obj, "task_total"); if (total < 0) { return error.InvalidTaskTotal; } return TaskMasterTask{ .target_id = try store.getCodeId(task_target), .type = Task.TypeUtils.fromString(task_type) orelse return error.InvalidTaskType, .total = @intCast(total), .progress = @intCast(progress), }; } }; allocator: Allocator, name: []u8, skin: []u8, account: ?[]u8, gold: i64, hp: i64, haste: i64, position: Position, cooldown_expiration: f64, combat: SkillStats, mining: SkillStats, woodcutting: SkillStats, fishing: SkillStats, weaponcrafting: SkillStats, gearcrafting: SkillStats, jewelrycrafting: SkillStats, cooking: SkillStats, water: CombatStats, fire: CombatStats, earth: CombatStats, air: CombatStats, equipment: Equipment, inventory_max_items: u64, inventory: Inventory, task: ?TaskMasterTask, pub fn parse(store: *Store, obj: json.ObjectMap, allocator: Allocator) !Character { const inventory = json_utils.getArray(obj, "inventory") orelse return error.MissingProperty; const cooldown_expiration = json_utils.getString(obj, "cooldown_expiration") orelse return error.MissingProperty; const x = try json_utils.getIntegerRequired(obj, "x"); const y = try json_utils.getIntegerRequired(obj, "y"); const name = (try json_utils.dupeString(allocator, obj, "name")) orelse return error.MissingProperty; if (name.len == 0) { return error.InvalidName; } const inventory_max_items = json_utils.getInteger(obj, "inventory_max_items") orelse return error.MissingProperty; if (inventory_max_items < 0) { return error.InvalidInventoryMaxItems; } var task: ?TaskMasterTask = null; const task_target = try json_utils.getStringRequired(obj, "task"); if (task_target.len > 0) { task = try TaskMasterTask.parse(store, obj); } return Character{ .allocator = allocator, .account = try json_utils.dupeString(allocator, obj, "account"), .name = name, .skin = (try json_utils.dupeString(allocator, obj, "skin")) orelse return error.MissingProperty, .gold = try json_utils.getIntegerRequired(obj, "gold"), .hp = try json_utils.getIntegerRequired(obj, "hp"), .haste = try json_utils.getIntegerRequired(obj, "haste"), .position = Position.init(x, y), .cooldown_expiration = parseDateTime(cooldown_expiration) orelse return error.InvalidDateTime, .combat = try SkillStats.parse(obj, "level", "xp", "max_xp"), .mining = try SkillStats.parse(obj, "mining_level", "mining_xp", "mining_max_xp"), .woodcutting = try SkillStats.parse(obj, "woodcutting_level", "woodcutting_xp", "woodcutting_max_xp"), .fishing = try SkillStats.parse(obj, "fishing_level", "fishing_xp", "fishing_max_xp"), .weaponcrafting = try SkillStats.parse(obj, "weaponcrafting_level", "weaponcrafting_xp", "weaponcrafting_max_xp"), .gearcrafting = try SkillStats.parse(obj, "gearcrafting_level", "gearcrafting_xp", "gearcrafting_max_xp"), .jewelrycrafting = try SkillStats.parse(obj, "jewelrycrafting_level", "jewelrycrafting_xp", "jewelrycrafting_max_xp"), .cooking = try SkillStats.parse(obj, "cooking_level", "cooking_xp", "cooking_max_xp"), .water = try CombatStats.parse(obj, "attack_water", "dmg_water", "res_water"), .fire = try CombatStats.parse(obj, "attack_fire", "dmg_fire", "res_fire"), .earth = try CombatStats.parse(obj, "attack_earth", "dmg_earth", "res_earth"), .air = try CombatStats.parse(obj, "attack_air", "dmg_air", "res_air"), .equipment = try Equipment.parse(store, obj), .inventory_max_items = @intCast(inventory_max_items), .inventory = try Inventory.parse(store, inventory), .task = task }; } pub fn deinit(self: *Character) void { if (self.account) |str| self.allocator.free(str); self.allocator.free(self.name); self.allocator.free(self.skin); } pub fn getItemCount(self: *const Character) u64 { return self.inventory.totalQuantity(); } pub fn format( self: Character, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype, ) !void { _ = fmt; _ = options; try writer.print("{s}{{ .name = \"{s}\", .position = {} ... }}", .{ @typeName(Character), self.name, self.position }); }