refactor main loop
This commit is contained in:
parent
b33754efb8
commit
805e2dcabc
@ -11,8 +11,8 @@ pub fn build(b: *std.Build) void {
|
||||
.optimize = optimize,
|
||||
});
|
||||
exe.linkLibC();
|
||||
exe.addIncludePath(b.path("src"));
|
||||
exe.addCSourceFile(.{ .file = b.path("src/timegm.c") });
|
||||
exe.addIncludePath(b.path("src/date_time"));
|
||||
exe.addCSourceFile(.{ .file = b.path("src/date_time/timegm.c") });
|
||||
|
||||
b.installArtifact(exe);
|
||||
|
||||
|
118
src/api/character.zig
Normal file
118
src/api/character.zig
Normal file
@ -0,0 +1,118 @@
|
||||
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 ItemId = Server.ItemId;
|
||||
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 Inventory = @import("./inventory.zig");
|
||||
|
||||
const Character = @This();
|
||||
|
||||
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: i64,
|
||||
inventory: Inventory,
|
||||
|
||||
pub fn parse(api: *Server, 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;
|
||||
assert(name.len > 0);
|
||||
|
||||
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(api, obj),
|
||||
|
||||
.inventory_max_items = json_utils.getInteger(obj, "inventory_max_items") orelse return error.MissingProperty,
|
||||
.inventory = try Inventory.parse(api, inventory)
|
||||
};
|
||||
}
|
||||
|
||||
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 {
|
||||
var count: u64 = 0;
|
||||
for (self.inventory.slots) |slot| {
|
||||
count += slot.quantity;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
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
|
||||
});
|
||||
}
|
17
src/api/combat_stats.zig
Normal file
17
src/api/combat_stats.zig
Normal file
@ -0,0 +1,17 @@
|
||||
const std = @import("std");
|
||||
const json_utils = @import("json_utils.zig");
|
||||
const json = std.json;
|
||||
|
||||
const CombatStats = @This();
|
||||
|
||||
attack: i64,
|
||||
damage: i64,
|
||||
resistance: i64,
|
||||
|
||||
pub fn parse(object: json.ObjectMap, attack: []const u8, damage: []const u8, resistance: []const u8) !CombatStats {
|
||||
return CombatStats{
|
||||
.attack = try json_utils.getIntegerRequired(object, attack),
|
||||
.damage = try json_utils.getIntegerRequired(object, damage),
|
||||
.resistance = try json_utils.getIntegerRequired(object, resistance),
|
||||
};
|
||||
}
|
90
src/api/equipment.zig
Normal file
90
src/api/equipment.zig
Normal file
@ -0,0 +1,90 @@
|
||||
const std = @import("std");
|
||||
const json_utils = @import("json_utils.zig");
|
||||
const Server = @import("./server.zig");
|
||||
const ItemId = Server.ItemId;
|
||||
const json = std.json;
|
||||
|
||||
const Equipment = @This();
|
||||
|
||||
pub const Consumable = struct {
|
||||
id: ?ItemId,
|
||||
quantity: i64,
|
||||
|
||||
fn parse(api: *Server, obj: json.ObjectMap, name: []const u8, quantity: []const u8) !Consumable {
|
||||
return Consumable{
|
||||
.id = try api.getItemIdJson(obj, name),
|
||||
.quantity = try json_utils.getIntegerRequired(obj, quantity),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const Slot = enum {
|
||||
weapon,
|
||||
shield,
|
||||
helmet,
|
||||
body_armor,
|
||||
leg_armor,
|
||||
boots,
|
||||
ring1,
|
||||
ring2,
|
||||
amulet,
|
||||
artifact1,
|
||||
artifact2,
|
||||
consumable1,
|
||||
consumable2,
|
||||
|
||||
fn name(self: Slot) []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",
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
weapon: ?ItemId,
|
||||
shield: ?ItemId,
|
||||
helmet: ?ItemId,
|
||||
body_armor: ?ItemId,
|
||||
leg_armor: ?ItemId,
|
||||
boots: ?ItemId,
|
||||
|
||||
ring1: ?ItemId,
|
||||
ring2: ?ItemId,
|
||||
amulet: ?ItemId,
|
||||
|
||||
artifact1: ?ItemId,
|
||||
artifact2: ?ItemId,
|
||||
artifact3: ?ItemId,
|
||||
|
||||
consumable1: Consumable,
|
||||
consumable2: Consumable,
|
||||
|
||||
pub fn parse(api: *Server, obj: json.ObjectMap) !Equipment {
|
||||
return Equipment{
|
||||
.weapon = try api.getItemIdJson(obj, "weapon_slot"),
|
||||
.shield = try api.getItemIdJson(obj, "shield_slot"),
|
||||
.helmet = try api.getItemIdJson(obj, "helmet_slot"),
|
||||
.body_armor = try api.getItemIdJson(obj, "body_armor_slot"),
|
||||
.leg_armor = try api.getItemIdJson(obj, "leg_armor_slot"),
|
||||
.boots = try api.getItemIdJson(obj, "boots_slot"),
|
||||
.ring1 = try api.getItemIdJson(obj, "ring1_slot"),
|
||||
.ring2 = try api.getItemIdJson(obj, "ring2_slot"),
|
||||
.amulet = try api.getItemIdJson(obj, "amulet_slot"),
|
||||
.artifact1 = try api.getItemIdJson(obj, "artifact1_slot"),
|
||||
.artifact2 = try api.getItemIdJson(obj, "artifact2_slot"),
|
||||
.artifact3 = try api.getItemIdJson(obj, "artifact3_slot"),
|
||||
.consumable1 = try Consumable.parse(api, obj, "consumable1_slot", "consumable1_slot_quantity"),
|
||||
.consumable2 = try Consumable.parse(api, obj, "consumable2_slot", "consumable2_slot_quantity"),
|
||||
};
|
||||
}
|
96
src/api/inventory.zig
Normal file
96
src/api/inventory.zig
Normal file
@ -0,0 +1,96 @@
|
||||
const std = @import("std");
|
||||
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;
|
||||
|
||||
const Inventory = @This();
|
||||
|
||||
const slot_count = 20;
|
||||
|
||||
pub const Slot = struct {
|
||||
id: ?ItemId,
|
||||
quantity: u64,
|
||||
|
||||
fn parse(api: *Server, slot_obj: json.ObjectMap) !Slot {
|
||||
const quantity = try json_utils.getIntegerRequired(slot_obj, "quantity");
|
||||
if (quantity < 0) return error.InvalidQuantity;
|
||||
|
||||
return Slot{
|
||||
.id = try api.getItemIdJson(slot_obj, "code"),
|
||||
.quantity = @intCast(quantity),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
slots: [slot_count]Slot,
|
||||
|
||||
pub fn parse(api: *Server, slots_array: json.Array) !Inventory {
|
||||
assert(slots_array.items.len == Inventory.slot_count);
|
||||
|
||||
var inventory: Inventory = undefined;
|
||||
|
||||
for (0.., slots_array.items) |i, slot_value| {
|
||||
const slot_obj = json_utils.asObject(slot_value) orelse return error.InvalidType;
|
||||
inventory.slots[i] = try Slot.parse(api, slot_obj);
|
||||
}
|
||||
|
||||
return inventory;
|
||||
}
|
||||
|
||||
fn findSlot(self: *Inventory, id: ItemId) ?*Slot {
|
||||
for (&self.slots) |*slot| {
|
||||
if (slot.id == id) {
|
||||
return slot;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn removeItem(self: *Inventory, id: ItemId, quantity: u64) void {
|
||||
const slot = self.findSlot(id) orelse unreachable;
|
||||
assert(slot.quantity >= quantity);
|
||||
|
||||
slot.quantity -= quantity;
|
||||
if (slot.quantity == 0) {
|
||||
slot.id = null;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn addItem(self: *Inventory, id: ItemId, quantity: u64) void {
|
||||
if (self.findSlot(id)) |slot| {
|
||||
slot.quantity += quantity;
|
||||
} else {
|
||||
var empty_slot: ?*Slot = null;
|
||||
for (&self.slots) |*slot| {
|
||||
if (slot.id == null) {
|
||||
empty_slot = slot;
|
||||
}
|
||||
}
|
||||
|
||||
assert(empty_slot != null);
|
||||
empty_slot.?.id = id;
|
||||
empty_slot.?.quantity = quantity;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn addItems(self: *Inventory, items: []const Server.ItemIdQuantity) void {
|
||||
for (items) |item| {
|
||||
self.addItem(item.id, item.quantity);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn removeItems(self: *Inventory, items: []const Server.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;
|
||||
}
|
15
src/api/position.zig
Normal file
15
src/api/position.zig
Normal file
@ -0,0 +1,15 @@
|
||||
const Position = @This();
|
||||
|
||||
x: i64,
|
||||
y: i64,
|
||||
|
||||
pub fn init(x: i64, y: i64) Position {
|
||||
return Position{
|
||||
.x = x,
|
||||
.y = y
|
||||
};
|
||||
}
|
||||
|
||||
pub fn eql(self: Position, other: Position) bool {
|
||||
return self.x == other.x and self.y == other.y;
|
||||
}
|
@ -3,13 +3,17 @@ 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");
|
||||
|
||||
// Specification: https://api.artifactsmmo.com/docs
|
||||
|
||||
const ArtifactsAPI = @This();
|
||||
const Server = @This();
|
||||
|
||||
const log = std.log.scoped(.api);
|
||||
pub const ItemId = u32;
|
||||
|
||||
allocator: Allocator,
|
||||
@ -21,6 +25,8 @@ server_uri: std.Uri,
|
||||
token: ?[]u8 = null,
|
||||
|
||||
item_codes: std.ArrayList([]u8),
|
||||
characters: std.ArrayList(Character),
|
||||
items: std.StringHashMap(Item),
|
||||
|
||||
// ------------------------- API errors ------------------------
|
||||
|
||||
@ -126,39 +132,7 @@ pub const EquipError = APIError || error {
|
||||
|
||||
// ------------------------- API result structs ------------------------
|
||||
|
||||
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",
|
||||
};
|
||||
}
|
||||
};
|
||||
pub const EquipmentSlot = @import("./equipment.zig").Slot;
|
||||
|
||||
pub const Skill = enum {
|
||||
weaponcrafting,
|
||||
@ -198,7 +172,7 @@ const ServerStatus = struct {
|
||||
version: []const u8,
|
||||
characters_online: i64,
|
||||
|
||||
pub fn parse(api: *ArtifactsAPI, object: json.ObjectMap, allocator: Allocator) !ServerStatus {
|
||||
pub fn parse(api: *Server, object: json.ObjectMap, allocator: Allocator) !ServerStatus {
|
||||
_ = api;
|
||||
|
||||
return ServerStatus{
|
||||
@ -215,55 +189,6 @@ const ServerStatus = struct {
|
||||
}
|
||||
};
|
||||
|
||||
pub fn parseDateTime(datetime: []const u8) ?f64 {
|
||||
const time_h = @cImport({
|
||||
@cDefine("_XOPEN_SOURCE", "700");
|
||||
@cInclude("stddef.h");
|
||||
@cInclude("stdio.h");
|
||||
@cInclude("time.h");
|
||||
@cInclude("timegm.h");
|
||||
});
|
||||
|
||||
var buffer: [256]u8 = undefined;
|
||||
var fba = std.heap.FixedBufferAllocator.init(&buffer);
|
||||
var allocator = fba.allocator();
|
||||
|
||||
const datetime_z = allocator.dupeZ(u8, datetime) catch return null;
|
||||
|
||||
var tm: time_h.tm = .{};
|
||||
const s = time_h.strptime(datetime_z, "%Y-%m-%dT%H:%M:%S.", &tm);
|
||||
if (s == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const s_len = std.mem.len(s);
|
||||
if (s[s_len-1] != 'Z') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const milliseconds_str = s[0..(s_len-1)];
|
||||
var milliseconds: f64 = @floatFromInt(std.fmt.parseUnsigned(u32, milliseconds_str, 10) catch return null);
|
||||
while (milliseconds >= 1) {
|
||||
milliseconds /= 10;
|
||||
}
|
||||
|
||||
const seconds: f64 = @floatFromInt(time_h.my_timegm(&tm));
|
||||
|
||||
return seconds + milliseconds;
|
||||
}
|
||||
|
||||
pub const CharacterList = struct {
|
||||
allocator: Allocator,
|
||||
items: []Character,
|
||||
|
||||
pub fn deinit(self: CharacterList) void {
|
||||
for (self.items) |*char| {
|
||||
char.deinit();
|
||||
}
|
||||
self.allocator.free(self.items);
|
||||
}
|
||||
};
|
||||
|
||||
pub const Cooldown = struct {
|
||||
pub const Reason = enum {
|
||||
movement,
|
||||
@ -339,7 +264,7 @@ pub const FightResult = struct {
|
||||
drops: std.BoundedArray(DroppedItem, 8),
|
||||
result: Result,
|
||||
|
||||
fn parse(api: *ArtifactsAPI, obj: json.ObjectMap) !Details {
|
||||
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")) {
|
||||
@ -371,14 +296,17 @@ pub const FightResult = struct {
|
||||
|
||||
cooldown: Cooldown,
|
||||
fight: Details,
|
||||
character: Character,
|
||||
|
||||
pub fn parse(api: *ArtifactsAPI, obj: json.ObjectMap) !FightResult {
|
||||
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)
|
||||
.fight = try Details.parse(api, fight),
|
||||
.character = try Character.parse(api, character, allocator)
|
||||
};
|
||||
}
|
||||
|
||||
@ -398,7 +326,7 @@ pub const ItemIdQuantity = struct {
|
||||
id: ItemId,
|
||||
quantity: u64,
|
||||
|
||||
pub fn parse(api: *ArtifactsAPI, obj: json.ObjectMap) !ItemIdQuantity {
|
||||
pub fn parse(api: *Server, 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;
|
||||
@ -411,7 +339,7 @@ pub const ItemIdQuantity = struct {
|
||||
};
|
||||
|
||||
const BoundedItems = std.BoundedArray(ItemIdQuantity, 8);
|
||||
fn parseSimpleItemList(api: *ArtifactsAPI, array: json.Array) !BoundedItems {
|
||||
fn parseSimpleItemList(api: *Server, array: json.Array) !BoundedItems {
|
||||
var items = BoundedItems.init(0) catch unreachable;
|
||||
|
||||
for (array.items) |item_value| {
|
||||
@ -427,7 +355,7 @@ pub const SkillResultDetails = struct {
|
||||
xp: i64,
|
||||
items: BoundedItems,
|
||||
|
||||
fn parse(api: *ArtifactsAPI, obj: json.ObjectMap) !SkillResultDetails {
|
||||
fn parse(api: *Server, obj: json.ObjectMap) !SkillResultDetails {
|
||||
const items = json_utils.getArray(obj, "items") orelse return error.MissingProperty;
|
||||
|
||||
return SkillResultDetails{
|
||||
@ -440,14 +368,17 @@ pub const SkillResultDetails = struct {
|
||||
pub const GatherResult = struct {
|
||||
cooldown: Cooldown,
|
||||
details: SkillResultDetails,
|
||||
character: Character,
|
||||
|
||||
pub fn parse(api: *ArtifactsAPI, obj: json.ObjectMap) !GatherResult {
|
||||
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)
|
||||
.details = try SkillResultDetails.parse(api, details),
|
||||
.character = try Character.parse(api, character, allocator)
|
||||
};
|
||||
}
|
||||
|
||||
@ -466,14 +397,15 @@ pub const GatherResult = struct {
|
||||
|
||||
pub const MoveResult = struct {
|
||||
cooldown: Cooldown,
|
||||
character: Character,
|
||||
|
||||
pub fn parse(api: *ArtifactsAPI, obj: json.ObjectMap) !MoveResult {
|
||||
_ = api;
|
||||
|
||||
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)
|
||||
.cooldown = try Cooldown.parse(cooldown),
|
||||
.character = try Character.parse(api, character, allocator)
|
||||
};
|
||||
}
|
||||
|
||||
@ -487,21 +419,19 @@ pub const MoveResult = struct {
|
||||
else => null
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: MoveResult) void {
|
||||
_ = self;
|
||||
}
|
||||
};
|
||||
|
||||
pub const GoldTransactionResult = struct {
|
||||
cooldown: Cooldown,
|
||||
character: Character,
|
||||
|
||||
pub fn parse(api: *ArtifactsAPI, obj: json.ObjectMap) !GoldTransactionResult {
|
||||
_ = api;
|
||||
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)
|
||||
.cooldown = try Cooldown.parse(cooldown),
|
||||
.character = try Character.parse(api, character, allocator)
|
||||
};
|
||||
}
|
||||
|
||||
@ -532,13 +462,15 @@ pub const GoldTransactionResult = struct {
|
||||
|
||||
pub const ItemTransactionResult = struct {
|
||||
cooldown: Cooldown,
|
||||
character: Character,
|
||||
|
||||
pub fn parse(api: *ArtifactsAPI, obj: json.ObjectMap) !ItemTransactionResult {
|
||||
_ = api;
|
||||
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)
|
||||
.cooldown = try Cooldown.parse(cooldown),
|
||||
.character = try Character.parse(api, character, allocator)
|
||||
};
|
||||
}
|
||||
|
||||
@ -573,14 +505,17 @@ pub const ItemTransactionResult = struct {
|
||||
pub const CraftResult = struct {
|
||||
cooldown: Cooldown,
|
||||
details: SkillResultDetails,
|
||||
character: Character,
|
||||
|
||||
pub fn parse(api: *ArtifactsAPI, obj: json.ObjectMap) !CraftResult {
|
||||
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)
|
||||
.details = try SkillResultDetails.parse(api, details),
|
||||
.character = try Character.parse(api, character, allocator)
|
||||
};
|
||||
}
|
||||
|
||||
@ -603,7 +538,7 @@ pub const UnequipResult = struct {
|
||||
cooldown: Cooldown,
|
||||
item: ItemId,
|
||||
|
||||
pub fn parse(api: *ArtifactsAPI, obj: json.ObjectMap) !UnequipResult {
|
||||
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;
|
||||
@ -635,7 +570,7 @@ pub const UnequipResult = struct {
|
||||
pub const EquipResult = struct {
|
||||
cooldown: Cooldown,
|
||||
|
||||
pub fn parse(api: *ArtifactsAPI, obj: json.ObjectMap) !EquipResult {
|
||||
pub fn parse(api: *Server, obj: json.ObjectMap) !EquipResult {
|
||||
_ = api;
|
||||
const cooldown = json_utils.getObject(obj, "cooldown") orelse return error.MissingProperty;
|
||||
|
||||
@ -673,7 +608,7 @@ pub const MapResult = struct {
|
||||
y: i64,
|
||||
content: ?MapContent,
|
||||
|
||||
pub fn parse(api: *ArtifactsAPI, obj: json.ObjectMap, allocator: Allocator) !MapResult {
|
||||
pub fn parse(api: *Server, obj: json.ObjectMap, allocator: Allocator) !MapResult {
|
||||
_ = api;
|
||||
|
||||
var content: ?MapContent = null;
|
||||
@ -710,7 +645,7 @@ pub const Item = struct {
|
||||
quantity: u64,
|
||||
items: BoundedItems,
|
||||
|
||||
pub fn parse(api: *ArtifactsAPI, obj: json.ObjectMap) !Recipe {
|
||||
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;
|
||||
@ -740,7 +675,7 @@ pub const Item = struct {
|
||||
// TODO: effects
|
||||
// TODO: Grand exchange
|
||||
|
||||
pub fn parse(api: *ArtifactsAPI, obj: json.ObjectMap, allocator: Allocator) !Item {
|
||||
pub fn parse(api: *Server, 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;
|
||||
@ -781,20 +716,23 @@ pub const ArtifactsFetchResult = struct {
|
||||
|
||||
// ------------------------- General API methods ------------------------
|
||||
|
||||
pub fn init(allocator: Allocator) !ArtifactsAPI {
|
||||
const server = try allocator.dupe(u8, "https://api.artifactsmmo.com");
|
||||
const server_uri = std.Uri.parse(server) catch unreachable;
|
||||
pub fn init(allocator: Allocator) !Server {
|
||||
const url = try allocator.dupe(u8, "https://api.artifactsmmo.com");
|
||||
const uri = std.Uri.parse(url) catch unreachable;
|
||||
|
||||
return ArtifactsAPI{
|
||||
return Server{
|
||||
.allocator = allocator,
|
||||
.item_codes = std.ArrayList([]u8).init(allocator),
|
||||
.client = .{ .allocator = allocator },
|
||||
.server = server,
|
||||
.server_uri = server_uri
|
||||
.server = url,
|
||||
.server_uri = uri,
|
||||
|
||||
.item_codes = std.ArrayList([]u8).init(allocator),
|
||||
.characters = std.ArrayList(Character).init(allocator),
|
||||
.items = std.StringHashMap(Item).init(allocator),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *ArtifactsAPI) void {
|
||||
pub fn deinit(self: *Server) void {
|
||||
self.client.deinit();
|
||||
self.allocator.free(self.server);
|
||||
if (self.token) |str| self.allocator.free(str);
|
||||
@ -803,6 +741,17 @@ pub fn deinit(self: *ArtifactsAPI) void {
|
||||
self.allocator.free(code);
|
||||
}
|
||||
self.item_codes.deinit();
|
||||
|
||||
for (self.characters.items) |*char| {
|
||||
char.deinit();
|
||||
}
|
||||
self.characters.deinit();
|
||||
|
||||
var itemsIter = self.items.valueIterator();
|
||||
while (itemsIter.next()) |item| {
|
||||
item.deinit();
|
||||
}
|
||||
self.items.deinit();
|
||||
}
|
||||
|
||||
const FetchOptions = struct {
|
||||
@ -825,7 +774,7 @@ fn allocPaginationParams(allocator: Allocator, page: u64, page_size: ?u64) ![]u8
|
||||
}
|
||||
}
|
||||
|
||||
fn fetch(self: *ArtifactsAPI, options: FetchOptions) APIError!ArtifactsFetchResult {
|
||||
fn fetch(self: *Server, options: FetchOptions) APIError!ArtifactsFetchResult {
|
||||
const method = options.method;
|
||||
const path = options.path;
|
||||
const payload = options.payload;
|
||||
@ -879,11 +828,11 @@ fn fetch(self: *ArtifactsAPI, options: FetchOptions) APIError!ArtifactsFetchResu
|
||||
opts.headers.authorization = .{ .override = authorization_header.? };
|
||||
}
|
||||
|
||||
std.log.debug("fetch {} {s}", .{method, path});
|
||||
log.debug("fetch {} {s}", .{method, path});
|
||||
const result = self.client.fetch(opts) catch return APIError.RequestFailed;
|
||||
const response_body = response_storage.items;
|
||||
|
||||
std.log.debug("fetch result {}", .{result.status});
|
||||
log.debug("fetch result {}", .{result.status});
|
||||
|
||||
if (result.status == .service_unavailable) {
|
||||
return APIError.ServerUnavailable;
|
||||
@ -952,7 +901,7 @@ fn handleFetchError(
|
||||
}
|
||||
|
||||
fn fetchOptionalObject(
|
||||
self: *ArtifactsAPI,
|
||||
self: *Server,
|
||||
Error: type,
|
||||
parseError: ?fn (status: std.http.Status) ?Error,
|
||||
Object: type,
|
||||
@ -986,7 +935,7 @@ fn fetchOptionalObject(
|
||||
}
|
||||
|
||||
fn fetchObject(
|
||||
self: *ArtifactsAPI,
|
||||
self: *Server,
|
||||
Error: type,
|
||||
parseError: ?fn (status: std.http.Status) ?Error,
|
||||
Object: type,
|
||||
@ -998,27 +947,8 @@ fn fetchObject(
|
||||
return result orelse return APIError.RequestFailed;
|
||||
}
|
||||
|
||||
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,
|
||||
self: *Server,
|
||||
allocator: Allocator,
|
||||
Error: type,
|
||||
parseError: ?fn (status: std.http.Status) ?Error,
|
||||
@ -1026,7 +956,7 @@ fn fetchOptionalArray(
|
||||
parseObject: anytype,
|
||||
parseObjectArgs: anytype,
|
||||
fetchOptions: FetchOptions
|
||||
) Error!?ObjectList(Object) {
|
||||
) Error!?std.ArrayList(Object) {
|
||||
if (@typeInfo(@TypeOf(parseObject)) != .Fn) {
|
||||
@compileError("`parseObject` must be a function");
|
||||
}
|
||||
@ -1048,35 +978,29 @@ fn fetchOptionalArray(
|
||||
return APIError.ParseFailed;
|
||||
}
|
||||
|
||||
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();
|
||||
// }
|
||||
var array = std.ArrayList(Object).init(allocator);
|
||||
errdefer {
|
||||
if (std.meta.hasFn(Object, "deinit")) {
|
||||
for (array.items) |*item| {
|
||||
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;
|
||||
object_array.list.append(parsed_item) catch return APIError.OutOfMemory;
|
||||
array.append(parsed_item) catch return APIError.OutOfMemory;
|
||||
}
|
||||
|
||||
return object_array;
|
||||
return array;
|
||||
}
|
||||
|
||||
fn fetchArray(
|
||||
self: *ArtifactsAPI,
|
||||
self: *Server,
|
||||
allocator: Allocator,
|
||||
Error: type,
|
||||
parseError: ?fn (status: std.http.Status) ?Error,
|
||||
@ -1084,12 +1008,12 @@ fn fetchArray(
|
||||
parseObject: anytype,
|
||||
parseObjectArgs: anytype,
|
||||
fetchOptions: FetchOptions
|
||||
) Error!ObjectList(Object) {
|
||||
) Error!std.ArrayList(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 {
|
||||
pub fn setURL(self: *Server, url: []const u8) !void {
|
||||
const url_dupe = self.allocator.dupe(u8, url);
|
||||
errdefer self.allocator.free(url_dupe);
|
||||
|
||||
@ -1100,7 +1024,7 @@ pub fn setServer(self: *ArtifactsAPI, url: []const u8) !void {
|
||||
self.server_uri = uri;
|
||||
}
|
||||
|
||||
pub fn setToken(self: *ArtifactsAPI, token: ?[]const u8) !void {
|
||||
pub fn setToken(self: *Server, token: ?[]const u8) !void {
|
||||
var new_token: ?[]u8 = null;
|
||||
if (token != null) {
|
||||
new_token = try self.allocator.dupe(u8, token.?);
|
||||
@ -1110,7 +1034,7 @@ pub fn setToken(self: *ArtifactsAPI, token: ?[]const u8) !void {
|
||||
self.token = new_token;
|
||||
}
|
||||
|
||||
pub fn getItemId(self: *ArtifactsAPI, code: []const u8) !ItemId {
|
||||
pub fn getItemId(self: *Server, code: []const u8) !ItemId {
|
||||
assert(code.len != 0);
|
||||
|
||||
for (0.., self.item_codes.items) |i, item_code| {
|
||||
@ -1126,7 +1050,7 @@ pub fn getItemId(self: *ArtifactsAPI, code: []const u8) !ItemId {
|
||||
return @intCast(self.item_codes.items.len - 1);
|
||||
}
|
||||
|
||||
pub fn getItemCode(self: *const ArtifactsAPI, id: ItemId) ?[]const u8 {
|
||||
pub fn getItemCode(self: *const Server, id: ItemId) ?[]const u8 {
|
||||
if (id >= self.item_codes.items.len) {
|
||||
return null;
|
||||
}
|
||||
@ -1134,9 +1058,49 @@ pub fn getItemCode(self: *const ArtifactsAPI, id: ItemId) ?[]const u8 {
|
||||
return self.item_codes.items[id];
|
||||
}
|
||||
|
||||
pub fn getItemIdJson(self: *Server, object: json.ObjectMap, name: []const u8) !?ItemId {
|
||||
const code = try json_utils.getStringRequired(object, name);
|
||||
if (code.len == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return try self.getItemId(code);
|
||||
}
|
||||
|
||||
fn findCharacterIndex(self: *const Server, name: []const u8) ?usize {
|
||||
for (0.., self.characters.items) |i, character| {
|
||||
if (std.mem.eql(u8, character.name, name)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
fn addOrUpdateCharacter(self: *Server, character: Character) !void {
|
||||
if (self.findCharacterIndex(character.name)) |found| {
|
||||
self.characters.items[found].deinit();
|
||||
self.characters.items[found] = character;
|
||||
} else {
|
||||
try self.characters.append(character);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn findCharacter(self: *const Server, name: []const u8) ?Character {
|
||||
if (self.findCharacterIndex(name)) |index| {
|
||||
return self.characters.items[index];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn findItem(self: *const Server, name: []const u8) ?Item {
|
||||
return self.items.get(name);
|
||||
}
|
||||
|
||||
// ------------------------- Endpoints ------------------------
|
||||
|
||||
pub fn getServerStatus(self: *ArtifactsAPI) !ServerStatus {
|
||||
pub fn getServerStatus(self: *Server) !ServerStatus {
|
||||
return try self.fetchObject(
|
||||
APIError,
|
||||
null,
|
||||
@ -1146,74 +1110,97 @@ pub fn getServerStatus(self: *ArtifactsAPI) !ServerStatus {
|
||||
);
|
||||
}
|
||||
|
||||
pub fn getCharacter(self: *ArtifactsAPI, allocator: Allocator, name: []const u8) APIError!?Character {
|
||||
pub fn getCharacter(self: *Server, name: []const u8) APIError!?Character {
|
||||
const path = try std.fmt.allocPrint(self.allocator, "/characters/{s}", .{name});
|
||||
defer self.allocator.free(path);
|
||||
|
||||
return try self.fetchOptionalObject(
|
||||
var maybe_character = try self.fetchOptionalObject(
|
||||
APIError,
|
||||
null,
|
||||
Character,
|
||||
Character.parse, .{ allocator },
|
||||
Character.parse, .{ self.allocator },
|
||||
.{ .method = .GET, .path = path }
|
||||
);
|
||||
|
||||
if (maybe_character) |*character| {
|
||||
errdefer character.deinit();
|
||||
try self.addOrUpdateCharacter(character.*);
|
||||
return character.*;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn listMyCharacters(self: *ArtifactsAPI, allocator: Allocator) APIError!ObjectList(Character) {
|
||||
return self.fetchArray(
|
||||
allocator,
|
||||
pub fn listMyCharacters(self: *Server) APIError!std.ArrayList(Character) {
|
||||
const characters = try self.fetchArray(
|
||||
self.allocator,
|
||||
APIError,
|
||||
null,
|
||||
Character,
|
||||
Character.parse, .{ allocator },
|
||||
Character.parse, .{ self.allocator },
|
||||
.{ .method = .GET, .path = "/my/characters" }
|
||||
);
|
||||
errdefer characters.deinit();
|
||||
for (characters.items) |character| {
|
||||
try self.addOrUpdateCharacter(character);
|
||||
}
|
||||
|
||||
return characters;
|
||||
}
|
||||
|
||||
pub fn actionFight(self: *ArtifactsAPI, name: []const u8) FightError!FightResult {
|
||||
pub fn actionFight(self: *Server, name: []const u8) FightError!FightResult {
|
||||
const path = try std.fmt.allocPrint(self.allocator, "/my/{s}/action/fight", .{name});
|
||||
defer self.allocator.free(path);
|
||||
|
||||
return try self.fetchObject(
|
||||
const result = try self.fetchObject(
|
||||
FightError,
|
||||
FightResult.parseError,
|
||||
FightResult,
|
||||
FightResult.parse, .{ },
|
||||
FightResult.parse, .{ self.allocator },
|
||||
.{ .method = .POST, .path = path }
|
||||
);
|
||||
try self.addOrUpdateCharacter(result.character);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
pub fn actionGather(self: *ArtifactsAPI, name: []const u8) GatherError!GatherResult {
|
||||
pub fn actionGather(self: *Server, name: []const u8) GatherError!GatherResult {
|
||||
const path = try std.fmt.allocPrint(self.allocator, "/my/{s}/action/gathering", .{name});
|
||||
defer self.allocator.free(path);
|
||||
|
||||
return try self.fetchObject(
|
||||
const result = try self.fetchObject(
|
||||
GatherError,
|
||||
GatherResult.parseError,
|
||||
GatherResult,
|
||||
GatherResult.parse, .{ },
|
||||
GatherResult.parse, .{ self.allocator },
|
||||
.{ .method = .POST, .path = path }
|
||||
);
|
||||
try self.addOrUpdateCharacter(result.character);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
pub fn actionMove(self: *ArtifactsAPI, name: []const u8, x: i64, y: i64) MoveError!MoveResult {
|
||||
pub fn actionMove(self: *Server, name: []const u8, x: i64, y: i64) MoveError!MoveResult {
|
||||
const path = try std.fmt.allocPrint(self.allocator, "/my/{s}/action/move", .{name});
|
||||
defer self.allocator.free(path);
|
||||
|
||||
const payload = try std.fmt.allocPrint(self.allocator, "{{ \"x\":{},\"y\":{} }}", .{x, y});
|
||||
defer self.allocator.free(payload);
|
||||
|
||||
return try self.fetchObject(
|
||||
const result = try self.fetchObject(
|
||||
MoveError,
|
||||
MoveResult.parseError,
|
||||
MoveResult,
|
||||
MoveResult.parse, .{ },
|
||||
MoveResult.parse, .{ self.allocator },
|
||||
.{ .method = .POST, .path = path, .payload = payload }
|
||||
);
|
||||
try self.addOrUpdateCharacter(result.character);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
pub fn actionBankDepositGold(
|
||||
self: *ArtifactsAPI,
|
||||
self: *Server,
|
||||
name: []const u8,
|
||||
quantity: u64
|
||||
) BankDepositGoldError!GoldTransactionResult {
|
||||
@ -1223,17 +1210,20 @@ pub fn actionBankDepositGold(
|
||||
const payload = try std.fmt.allocPrint(self.allocator, "{{ \"quantity\":{} }}", .{quantity});
|
||||
defer self.allocator.free(payload);
|
||||
|
||||
return try self.fetchObject(
|
||||
const result = try self.fetchObject(
|
||||
BankDepositGoldError,
|
||||
GoldTransactionResult.parseDepositError,
|
||||
GoldTransactionResult,
|
||||
GoldTransactionResult.parse, .{ },
|
||||
GoldTransactionResult.parse, .{ self.allocator },
|
||||
.{ .method = .POST, .path = path, .payload = payload }
|
||||
);
|
||||
try self.addOrUpdateCharacter(result.character);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
pub fn actionBankDepositItem(
|
||||
self: *ArtifactsAPI,
|
||||
self: *Server,
|
||||
name: []const u8,
|
||||
code: []const u8,
|
||||
quantity: u64
|
||||
@ -1244,17 +1234,20 @@ pub fn actionBankDepositItem(
|
||||
const payload = try std.fmt.allocPrint(self.allocator, "{{ \"code\":\"{s}\",\"quantity\":{} }}", .{code, quantity});
|
||||
defer self.allocator.free(payload);
|
||||
|
||||
return try self.fetchObject(
|
||||
const result = try self.fetchObject(
|
||||
BankDepositItemError,
|
||||
ItemTransactionResult.parseDepositError,
|
||||
ItemTransactionResult,
|
||||
ItemTransactionResult.parse, .{ },
|
||||
ItemTransactionResult.parse, .{ self.allocator },
|
||||
.{ .method = .POST, .path = path, .payload = payload }
|
||||
);
|
||||
try self.addOrUpdateCharacter(result.character);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
pub fn actionBankWithdrawGold(
|
||||
self: *ArtifactsAPI,
|
||||
self: *Server,
|
||||
name: []const u8,
|
||||
quantity: u64
|
||||
) BankDepositGoldError!GoldTransactionResult {
|
||||
@ -1264,17 +1257,20 @@ pub fn actionBankWithdrawGold(
|
||||
const payload = try std.fmt.allocPrint(self.allocator, "{{ \"quantity\":{} }}", .{quantity});
|
||||
defer self.allocator.free(payload);
|
||||
|
||||
return try self.fetchObject(
|
||||
const result = try self.fetchObject(
|
||||
BankWithdrawGoldError,
|
||||
GoldTransactionResult.parseWithdrawError,
|
||||
GoldTransactionResult,
|
||||
GoldTransactionResult.parse, .{ },
|
||||
GoldTransactionResult.parse, .{ self.allocator },
|
||||
.{ .method = .POST, .path = path, .payload = payload }
|
||||
);
|
||||
try self.addOrUpdateCharacter(result.character);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
pub fn actionBankWithdrawItem(
|
||||
self: *ArtifactsAPI,
|
||||
self: *Server,
|
||||
name: []const u8,
|
||||
code: []const u8,
|
||||
quantity: u64
|
||||
@ -1285,17 +1281,20 @@ pub fn actionBankWithdrawItem(
|
||||
const payload = try std.fmt.allocPrint(self.allocator, "{{ \"code\":\"{s}\",\"quantity\":{} }}", .{code, quantity});
|
||||
defer self.allocator.free(payload);
|
||||
|
||||
return try self.fetchObject(
|
||||
const result = try self.fetchObject(
|
||||
BankWithdrawItemError,
|
||||
ItemTransactionResult.parseWithdrawError,
|
||||
ItemTransactionResult,
|
||||
ItemTransactionResult.parse, .{ },
|
||||
ItemTransactionResult.parse, .{ self.allocator },
|
||||
.{ .method = .POST, .path = path, .payload = payload }
|
||||
);
|
||||
try self.addOrUpdateCharacter(result.character);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
pub fn actionCraft(
|
||||
self: *ArtifactsAPI,
|
||||
self: *Server,
|
||||
name: []const u8,
|
||||
code: []const u8,
|
||||
quantity: u64
|
||||
@ -1306,17 +1305,20 @@ pub fn actionCraft(
|
||||
const payload = try std.fmt.allocPrint(self.allocator, "{{ \"code\":\"{s}\",\"quantity\":{} }}", .{code, quantity});
|
||||
defer self.allocator.free(payload);
|
||||
|
||||
return try self.fetchObject(
|
||||
const result = try self.fetchObject(
|
||||
CraftError,
|
||||
CraftResult.parseError,
|
||||
CraftResult,
|
||||
CraftResult.parse, .{ },
|
||||
CraftResult.parse, .{ self.allocator },
|
||||
.{ .method = .POST, .path = path, .payload = payload }
|
||||
);
|
||||
try self.addOrUpdateCharacter(result.character);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
pub fn actionUnequip(
|
||||
self: *ArtifactsAPI,
|
||||
self: *Server,
|
||||
name: []const u8,
|
||||
slot: EquipmentSlot
|
||||
) UnequipError!UnequipResult {
|
||||
@ -1326,17 +1328,20 @@ pub fn actionUnequip(
|
||||
const payload = try std.fmt.allocPrint(self.allocator, "{{ \"slot\":\"{s}\" }}", .{slot.name()});
|
||||
defer self.allocator.free(payload);
|
||||
|
||||
return try self.fetchObject(
|
||||
const result = try self.fetchObject(
|
||||
UnequipError,
|
||||
UnequipResult.parseError,
|
||||
UnequipResult,
|
||||
UnequipResult.parse, .{ },
|
||||
UnequipResult.parse, .{ self.allocator },
|
||||
.{ .method = .POST, .path = path, .payload = payload }
|
||||
);
|
||||
try self.addOrUpdateCharacter(result.character);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
pub fn actionEquip(
|
||||
self: *ArtifactsAPI,
|
||||
self: *Server,
|
||||
name: []const u8,
|
||||
slot: EquipmentSlot,
|
||||
code: []const u8
|
||||
@ -1347,16 +1352,19 @@ pub fn actionEquip(
|
||||
const payload = try std.fmt.allocPrint(self.allocator, "{{ \"slot\":\"{s}\",\"code\":\"{s}\" }}", .{slot.name(), code});
|
||||
defer self.allocator.free(payload);
|
||||
|
||||
return try self.fetchObject(
|
||||
const result = try self.fetchObject(
|
||||
EquipError,
|
||||
EquipResult.parseError,
|
||||
EquipResult,
|
||||
EquipResult.parse, .{ },
|
||||
EquipResult.parse, .{ self.allocator },
|
||||
.{ .method = .POST, .path = path, .payload = payload }
|
||||
);
|
||||
try self.addOrUpdateCharacter(result.character);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
pub fn getBankGold(self: *ArtifactsAPI) APIError!u64 {
|
||||
pub fn getBankGold(self: *Server) APIError!u64 {
|
||||
const result = try self.fetch(.{ .method = .GET, .path = "/my/bank/gold" });
|
||||
defer result.deinit();
|
||||
|
||||
@ -1374,7 +1382,7 @@ pub fn getBankGold(self: *ArtifactsAPI) APIError!u64 {
|
||||
return @intCast(quantity);
|
||||
}
|
||||
|
||||
pub fn getBankItems(self: *ArtifactsAPI, allocator: Allocator) APIError!ObjectList(ItemIdQuantity) {
|
||||
pub fn getBankItems(self: *Server, allocator: Allocator) APIError!std.ArrayList(ItemIdQuantity) {
|
||||
return self.fetchArray(
|
||||
allocator,
|
||||
APIError,
|
||||
@ -1385,7 +1393,7 @@ pub fn getBankItems(self: *ArtifactsAPI, allocator: Allocator) APIError!ObjectLi
|
||||
);
|
||||
}
|
||||
|
||||
pub fn getBankItemQuantity(self: *ArtifactsAPI, code: []const u8) APIError!?u64 {
|
||||
pub fn getBankItemQuantity(self: *Server, code: []const u8) APIError!?u64 {
|
||||
const query = try std.fmt.allocPrint(self.allocator, "item_code={s}", .{code});
|
||||
defer self.allocator.free(query);
|
||||
|
||||
@ -1411,7 +1419,7 @@ pub fn getBankItemQuantity(self: *ArtifactsAPI, code: []const u8) APIError!?u64
|
||||
return list_items[0].quantity;
|
||||
}
|
||||
|
||||
pub fn getMap(self: *ArtifactsAPI, allocator: Allocator, x: i64, y: i64) APIError!?MapResult {
|
||||
pub fn getMap(self: *Server, allocator: Allocator, x: i64, y: i64) APIError!?MapResult {
|
||||
const path = try std.fmt.allocPrint(self.allocator, "/maps/{}/{}", .{x, y});
|
||||
defer self.allocator.free(path);
|
||||
|
||||
@ -1424,7 +1432,7 @@ pub fn getMap(self: *ArtifactsAPI, allocator: Allocator, x: i64, y: i64) APIErro
|
||||
);
|
||||
}
|
||||
|
||||
pub fn getMaps(self: *ArtifactsAPI, allocator: Allocator) APIError!ObjectList(MapResult) {
|
||||
pub fn getMaps(self: *Server, allocator: Allocator) APIError!std.ArrayList(MapResult) {
|
||||
return self.fetchArray(
|
||||
allocator,
|
||||
APIError,
|
||||
@ -1435,19 +1443,32 @@ pub fn getMaps(self: *ArtifactsAPI, allocator: Allocator) APIError!ObjectList(Ma
|
||||
);
|
||||
}
|
||||
|
||||
pub fn getItem(self: *ArtifactsAPI, allocator: Allocator, code: []const u8) APIError!?Item {
|
||||
pub fn getItem(self: *Server, code: []const u8) APIError!?Item {
|
||||
if (self.findItem(code)) |item| {
|
||||
return item;
|
||||
}
|
||||
|
||||
const path = try std.fmt.allocPrint(self.allocator, "/items/{s}", .{code});
|
||||
defer self.allocator.free(path);
|
||||
|
||||
return self.fetchOptionalObject(
|
||||
const result = try self.fetchOptionalObject(
|
||||
APIError,
|
||||
null,
|
||||
Item,
|
||||
Item.parse, .{ allocator },
|
||||
Item.parse, .{ self.allocator },
|
||||
.{ .method = .GET, .path = path }
|
||||
);
|
||||
}
|
||||
|
||||
test "parse date time" {
|
||||
try std.testing.expectEqual(1723069394.105, parseDateTime("2024-08-07T22:23:14.105Z").?);
|
||||
if (result) |item| {
|
||||
const item_id = try self.getItemId(code);
|
||||
const code_owned = self.getItemCode(item_id).?;
|
||||
|
||||
var entry = try self.items.getOrPut(code_owned);
|
||||
if (entry.found_existing) {
|
||||
entry.value_ptr.deinit();
|
||||
}
|
||||
entry.value_ptr.* = item;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
17
src/api/skill_stats.zig
Normal file
17
src/api/skill_stats.zig
Normal file
@ -0,0 +1,17 @@
|
||||
const std = @import("std");
|
||||
const json_utils = @import("./json_utils.zig");
|
||||
const json = std.json;
|
||||
|
||||
const SkillStats = @This();
|
||||
|
||||
level: i64,
|
||||
xp: i64,
|
||||
max_xp: i64,
|
||||
|
||||
pub fn parse(object: json.ObjectMap, level: []const u8, xp: []const u8, max_xp: []const u8) !SkillStats {
|
||||
return SkillStats{
|
||||
.level = try json_utils.getIntegerRequired(object, level),
|
||||
.xp = try json_utils.getIntegerRequired(object, xp),
|
||||
.max_xp = try json_utils.getIntegerRequired(object, max_xp),
|
||||
};
|
||||
}
|
@ -1,281 +0,0 @@
|
||||
const std = @import("std");
|
||||
const json_utils = @import("json_utils.zig");
|
||||
const ArtifactsAPI = @import("artifacts.zig");
|
||||
const parseDateTime = ArtifactsAPI.parseDateTime;
|
||||
const ItemId = ArtifactsAPI.ItemId;
|
||||
const assert = std.debug.assert;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const json = std.json;
|
||||
|
||||
const Character = @This();
|
||||
|
||||
fn getItemId(api: *ArtifactsAPI, object: json.ObjectMap, name: []const u8) !?ItemId {
|
||||
const code = try json_utils.getStringRequired(object, name);
|
||||
if (code.len == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return try api.getItemId(code);
|
||||
}
|
||||
|
||||
pub const SkillStats = struct {
|
||||
level: i64,
|
||||
xp: i64,
|
||||
max_xp: i64,
|
||||
|
||||
fn parse(object: json.ObjectMap, level: []const u8, xp: []const u8, max_xp: []const u8) !SkillStats {
|
||||
return SkillStats{
|
||||
.level = try json_utils.getIntegerRequired(object, level),
|
||||
.xp = try json_utils.getIntegerRequired(object, xp),
|
||||
.max_xp = try json_utils.getIntegerRequired(object, max_xp),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const CombatStats = struct {
|
||||
attack: i64,
|
||||
damage: i64,
|
||||
resistance: i64,
|
||||
|
||||
fn parse(object: json.ObjectMap, attack: []const u8, damage: []const u8, resistance: []const u8) !CombatStats {
|
||||
return CombatStats{
|
||||
.attack = try json_utils.getIntegerRequired(object, attack),
|
||||
.damage = try json_utils.getIntegerRequired(object, damage),
|
||||
.resistance = try json_utils.getIntegerRequired(object, resistance),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const Equipment = struct {
|
||||
pub const Consumable = struct {
|
||||
id: ?ItemId,
|
||||
quantity: i64,
|
||||
|
||||
fn parse(api: *ArtifactsAPI, obj: json.ObjectMap, name: []const u8, quantity: []const u8) !Consumable {
|
||||
return Consumable{
|
||||
.id = try getItemId(api, obj, name),
|
||||
.quantity = try json_utils.getIntegerRequired(obj, quantity),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
weapon: ?ItemId,
|
||||
shield: ?ItemId,
|
||||
helmet: ?ItemId,
|
||||
body_armor: ?ItemId,
|
||||
leg_armor: ?ItemId,
|
||||
boots: ?ItemId,
|
||||
|
||||
ring1: ?ItemId,
|
||||
ring2: ?ItemId,
|
||||
amulet: ?ItemId,
|
||||
|
||||
artifact1: ?ItemId,
|
||||
artifact2: ?ItemId,
|
||||
artifact3: ?ItemId,
|
||||
|
||||
consumable1: Consumable,
|
||||
consumable2: Consumable,
|
||||
|
||||
fn parse(api: *ArtifactsAPI, obj: json.ObjectMap) !Equipment {
|
||||
return Equipment{
|
||||
.weapon = try getItemId(api, obj, "weapon_slot"),
|
||||
.shield = try getItemId(api, obj, "shield_slot"),
|
||||
.helmet = try getItemId(api, obj, "helmet_slot"),
|
||||
.body_armor = try getItemId(api, obj, "body_armor_slot"),
|
||||
.leg_armor = try getItemId(api, obj, "leg_armor_slot"),
|
||||
.boots = try getItemId(api, obj, "boots_slot"),
|
||||
.ring1 = try getItemId(api, obj, "ring1_slot"),
|
||||
.ring2 = try getItemId(api, obj, "ring2_slot"),
|
||||
.amulet = try getItemId(api, obj, "amulet_slot"),
|
||||
.artifact1 = try getItemId(api, obj, "artifact1_slot"),
|
||||
.artifact2 = try getItemId(api, obj, "artifact2_slot"),
|
||||
.artifact3 = try getItemId(api, obj, "artifact3_slot"),
|
||||
.consumable1 = try Consumable.parse(api, obj, "consumable1_slot", "consumable1_slot_quantity"),
|
||||
.consumable2 = try Consumable.parse(api, obj, "consumable2_slot", "consumable2_slot_quantity"),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const Inventory = struct {
|
||||
const slot_count = 20;
|
||||
|
||||
pub const Slot = struct {
|
||||
id: ?ItemId,
|
||||
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 = @intCast(quantity),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
slots: [slot_count]Slot,
|
||||
|
||||
fn parse(api: *ArtifactsAPI, slots_array: json.Array) !Inventory {
|
||||
assert(slots_array.items.len == Inventory.slot_count);
|
||||
|
||||
var inventory: Inventory = undefined;
|
||||
|
||||
for (0.., slots_array.items) |i, slot_value| {
|
||||
const slot_obj = json_utils.asObject(slot_value) orelse return error.InvalidType;
|
||||
inventory.slots[i] = try Slot.parse(api, slot_obj);
|
||||
}
|
||||
|
||||
return inventory;
|
||||
}
|
||||
|
||||
fn findSlot(self: *Inventory, id: ItemId) ?*Slot {
|
||||
for (&self.slots) |*slot| {
|
||||
if (slot.id == id) {
|
||||
return slot;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn removeItem(self: *Inventory, id: ItemId, quantity: u64) void {
|
||||
const slot = self.findSlot(id) orelse unreachable;
|
||||
assert(slot.quantity >= quantity);
|
||||
|
||||
slot.quantity -= quantity;
|
||||
if (slot.quantity == 0) {
|
||||
slot.id = null;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn addItem(self: *Inventory, id: ItemId, quantity: u64) void {
|
||||
if (self.findSlot(id)) |slot| {
|
||||
slot.quantity += quantity;
|
||||
} else {
|
||||
var empty_slot: ?*Slot = null;
|
||||
for (&self.slots) |*slot| {
|
||||
if (slot.id == null) {
|
||||
empty_slot = slot;
|
||||
}
|
||||
}
|
||||
|
||||
assert(empty_slot != null);
|
||||
empty_slot.?.id = id;
|
||||
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,
|
||||
|
||||
name: []u8,
|
||||
skin: []u8,
|
||||
account: ?[]u8,
|
||||
total_xp: i64,
|
||||
gold: i64,
|
||||
hp: i64,
|
||||
haste: i64,
|
||||
x: i64,
|
||||
y: i64,
|
||||
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: i64,
|
||||
inventory: Inventory,
|
||||
|
||||
pub fn parse(api: *ArtifactsAPI, obj: json.ObjectMap, child_allocator: Allocator) !Character {
|
||||
var arena = try child_allocator.create(std.heap.ArenaAllocator);
|
||||
errdefer child_allocator.destroy(arena);
|
||||
|
||||
arena.* = std.heap.ArenaAllocator.init(child_allocator);
|
||||
errdefer arena.deinit();
|
||||
|
||||
const allocator = arena.allocator();
|
||||
|
||||
const inventory = json_utils.getArray(obj, "inventory") orelse return error.MissingProperty;
|
||||
const cooldown_expiration = json_utils.getString(obj, "cooldown_expiration") orelse return error.MissingProperty;
|
||||
|
||||
return Character{
|
||||
.arena = arena,
|
||||
.account = try json_utils.dupeString(allocator, obj, "account"),
|
||||
.name = (try json_utils.dupeString(allocator, obj, "name")) orelse return error.MissingProperty,
|
||||
.skin = (try json_utils.dupeString(allocator, obj, "skin")) orelse return error.MissingProperty,
|
||||
|
||||
.total_xp = try json_utils.getIntegerRequired(obj, "total_xp"),
|
||||
.gold = try json_utils.getIntegerRequired(obj, "gold"),
|
||||
.hp = try json_utils.getIntegerRequired(obj, "hp"),
|
||||
.haste = try json_utils.getIntegerRequired(obj, "haste"),
|
||||
.x = try json_utils.getIntegerRequired(obj, "x"),
|
||||
.y = try json_utils.getIntegerRequired(obj, "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(api, obj),
|
||||
|
||||
.inventory_max_items = json_utils.getInteger(obj, "inventory_max_items") orelse return error.MissingProperty,
|
||||
.inventory = try Inventory.parse(api, inventory)
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Character) void {
|
||||
var child_allocator = self.arena.child_allocator;
|
||||
self.arena.deinit();
|
||||
child_allocator.destroy(self.arena);
|
||||
}
|
||||
|
||||
pub fn getItemCount(self: *const Character) u64 {
|
||||
var count: u64 = 0;
|
||||
for (self.inventory.slots) |slot| {
|
||||
count += slot.quantity;
|
||||
}
|
||||
return count;
|
||||
}
|
42
src/date_time/parse.zig
Normal file
42
src/date_time/parse.zig
Normal file
@ -0,0 +1,42 @@
|
||||
const std = @import("std");
|
||||
|
||||
pub fn parseDateTime(datetime: []const u8) ?f64 {
|
||||
const time_h = @cImport({
|
||||
@cDefine("_XOPEN_SOURCE", "700");
|
||||
@cInclude("stddef.h");
|
||||
@cInclude("stdio.h");
|
||||
@cInclude("time.h");
|
||||
@cInclude("timegm.h");
|
||||
});
|
||||
|
||||
var buffer: [256]u8 = undefined;
|
||||
var fba = std.heap.FixedBufferAllocator.init(&buffer);
|
||||
var allocator = fba.allocator();
|
||||
|
||||
const datetime_z = allocator.dupeZ(u8, datetime) catch return null;
|
||||
|
||||
var tm: time_h.tm = .{};
|
||||
const s = time_h.strptime(datetime_z, "%Y-%m-%dT%H:%M:%S.", &tm);
|
||||
if (s == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const s_len = std.mem.len(s);
|
||||
if (s[s_len-1] != 'Z') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const milliseconds_str = s[0..(s_len-1)];
|
||||
var milliseconds: f64 = @floatFromInt(std.fmt.parseUnsigned(u32, milliseconds_str, 10) catch return null);
|
||||
while (milliseconds >= 1) {
|
||||
milliseconds /= 10;
|
||||
}
|
||||
|
||||
const seconds: f64 = @floatFromInt(time_h.my_timegm(&tm));
|
||||
|
||||
return seconds + milliseconds;
|
||||
}
|
||||
|
||||
test "parse date time" {
|
||||
try std.testing.expectEqual(1723069394.105, parseDateTime("2024-08-07T22:23:14.105Z").?);
|
||||
}
|
532
src/main.zig
532
src/main.zig
@ -1,45 +1,98 @@
|
||||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
const ArtifactsAPI = @import("artifacts.zig");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const assert = std.debug.assert;
|
||||
|
||||
const Position = @import("./api/position.zig");
|
||||
const Server = @import("./api/server.zig");
|
||||
|
||||
// pub const std_options = .{ .log_level = .debug };
|
||||
|
||||
const Position = struct {
|
||||
x: i64,
|
||||
y: i64,
|
||||
const QueuedAction = union(enum) {
|
||||
move: Position,
|
||||
fight,
|
||||
gather,
|
||||
deposit_gold: u64,
|
||||
deposit_item: Server.ItemIdQuantity,
|
||||
withdraw_item: Server.ItemIdQuantity,
|
||||
craft_item: Server.ItemIdQuantity,
|
||||
};
|
||||
|
||||
fn init(x: i64, y: i64) Position {
|
||||
return Position{
|
||||
.x = x,
|
||||
.y = y
|
||||
const CharacterBrain = struct {
|
||||
name: []const u8,
|
||||
routine: union (enum) {
|
||||
idle,
|
||||
|
||||
fight: struct {
|
||||
at: Position,
|
||||
target: Server.ItemIdQuantity,
|
||||
progress: u64 = 0,
|
||||
},
|
||||
gather: struct {
|
||||
at: Position,
|
||||
target: Server.ItemIdQuantity,
|
||||
progress: u64 = 0,
|
||||
},
|
||||
craft: struct {
|
||||
target: Server.ItemIdQuantity,
|
||||
progress: u64 = 0,
|
||||
},
|
||||
},
|
||||
action_queue: std.ArrayList(QueuedAction),
|
||||
|
||||
fn init(allocator: Allocator, name: []const u8) !CharacterBrain {
|
||||
return CharacterBrain{
|
||||
.name = try allocator.dupe(u8, name),
|
||||
.routine = .idle,
|
||||
.action_queue = std.ArrayList(QueuedAction).init(allocator),
|
||||
};
|
||||
}
|
||||
|
||||
fn eql(self: Position, other: Position) bool {
|
||||
return self.x == other.x and self.y == other.y;
|
||||
fn deinit(self: CharacterBrain) void {
|
||||
const allocator = self.action_queue.allocator;
|
||||
allocator.free(self.name);
|
||||
self.action_queue.deinit();
|
||||
}
|
||||
};
|
||||
|
||||
const QueuedAction = union(enum) {
|
||||
move: Position,
|
||||
attack,
|
||||
gather,
|
||||
deposit_gold: u64,
|
||||
deposit_item: ArtifactsAPI.ItemIdQuantity,
|
||||
withdraw_item: ArtifactsAPI.ItemIdQuantity,
|
||||
craft_item: ArtifactsAPI.ItemIdQuantity,
|
||||
};
|
||||
fn performNextAction(self: *CharacterBrain, api: *Server) !void {
|
||||
assert(self.action_queue.items.len > 0);
|
||||
|
||||
const ActionQueue = std.ArrayList(QueuedAction);
|
||||
const log = std.log.default;
|
||||
|
||||
const ManagedCharacter = struct {
|
||||
character: ArtifactsAPI.Character,
|
||||
action_queue: ActionQueue,
|
||||
cooldown_expires_at: f64,
|
||||
switch (self.action_queue.items[0]) {
|
||||
.fight => {
|
||||
log.debug("{s} attacks", .{self.name});
|
||||
_ = try api.actionFight(self.name);
|
||||
},
|
||||
.move => |pos| {
|
||||
log.debug("move {s} to ({}, {})", .{self.name, pos.x, pos.y});
|
||||
_ = try api.actionMove(self.name, pos.x, pos.y);
|
||||
},
|
||||
.deposit_gold => |quantity| {
|
||||
log.debug("deposit {} gold from {s}", .{quantity, self.name});
|
||||
_ = try api.actionBankDepositGold(self.name, quantity);
|
||||
},
|
||||
.deposit_item => |item| {
|
||||
const code = api.getItemCode(item.id) orelse return error.ItemNotFound;
|
||||
log.debug("deposit {s} (x{}) from {s}", .{code, item.quantity, self.name});
|
||||
_ = try api.actionBankDepositItem(self.name, code, item.quantity);
|
||||
},
|
||||
.withdraw_item => |item| {
|
||||
const code = api.getItemCode(item.id) orelse return error.ItemNotFound;
|
||||
log.debug("withdraw {s} (x{}) from {s}", .{code, item.quantity, self.name});
|
||||
_ = try api.actionBankWithdrawItem(self.name, code, item.quantity);
|
||||
},
|
||||
.gather => {
|
||||
log.debug("{s} gathers", .{self.name});
|
||||
_ = try api.actionGather(self.name);
|
||||
},
|
||||
.craft_item => |item| {
|
||||
const code = api.getItemCode(item.id) orelse return error.ItemNotFound;
|
||||
log.debug("craft {s} (x{}) from {s}", .{code, item.quantity, self.name});
|
||||
_ = try api.actionCraft(self.name, code, item.quantity);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn position(self: *const ManagedCharacter) Position {
|
||||
return Position{ .x = self.character.x, .y = self.character.y };
|
||||
_ = self.action_queue.orderedRemove(0);
|
||||
}
|
||||
};
|
||||
|
||||
@ -48,128 +101,100 @@ fn currentTime() f64 {
|
||||
return timestamp / std.time.ms_per_s;
|
||||
}
|
||||
|
||||
const Manager = struct {
|
||||
const GoalManager = struct {
|
||||
api: *Server,
|
||||
allocator: Allocator,
|
||||
characters: std.ArrayList(ManagedCharacter),
|
||||
api: *ArtifactsAPI,
|
||||
known_items: std.StringHashMap(ArtifactsAPI.Item),
|
||||
characters: std.ArrayList(CharacterBrain),
|
||||
expiration_margin: f64 = 0.1, // 100ms
|
||||
|
||||
bank_position: Position = .{ .x = 4, .y = 1 },
|
||||
|
||||
fn init(allocator: Allocator, api: *ArtifactsAPI) Manager {
|
||||
return Manager{
|
||||
.allocator = allocator,
|
||||
fn init(api: *Server, allocator: Allocator) GoalManager {
|
||||
return GoalManager{
|
||||
.api = api,
|
||||
.known_items = std.StringHashMap(ArtifactsAPI.Item).init(allocator),
|
||||
.characters = std.ArrayList(ManagedCharacter).init(allocator),
|
||||
.allocator = allocator,
|
||||
.characters = std.ArrayList(CharacterBrain).init(allocator)
|
||||
};
|
||||
}
|
||||
|
||||
fn addCharacter(self: *Manager, character: ArtifactsAPI.Character) !void {
|
||||
try self.characters.append(ManagedCharacter{
|
||||
.character = character,
|
||||
.action_queue = std.ArrayList(QueuedAction).init(self.allocator),
|
||||
.cooldown_expires_at = character.cooldown_expiration
|
||||
});
|
||||
fn addCharacter(self: *GoalManager, name: []const u8) !void {
|
||||
const character = try CharacterBrain.init(self.allocator, name);
|
||||
try self.characters.append(character);
|
||||
}
|
||||
|
||||
fn poll(self: *Manager) ?*ManagedCharacter {
|
||||
if (self.characters.items.len == 0) return null;
|
||||
fn deinit(self: GoalManager) void {
|
||||
for (self.characters.items) |brain| {
|
||||
brain.deinit();
|
||||
}
|
||||
self.characters.deinit();
|
||||
}
|
||||
|
||||
var earliest_expiration = self.characters.items[0].cooldown_expires_at;
|
||||
fn runNextAction(self: *GoalManager) !void {
|
||||
var earliest_cooldown: f64 = 0;
|
||||
var earliest_character: ?*CharacterBrain = null;
|
||||
for (self.characters.items) |*brain| {
|
||||
if (brain.action_queue.items.len == 0) continue;
|
||||
|
||||
var now = currentTime();
|
||||
for (self.characters.items) |managed_character| {
|
||||
earliest_expiration = @min(earliest_expiration, managed_character.cooldown_expires_at);
|
||||
const character = self.api.findCharacter(brain.name).?;
|
||||
if (earliest_character == null or earliest_cooldown > character.cooldown_expiration) {
|
||||
earliest_character = brain;
|
||||
earliest_cooldown = character.cooldown_expiration;
|
||||
}
|
||||
}
|
||||
|
||||
if (earliest_expiration > now) {
|
||||
const duration_s = earliest_expiration - now;
|
||||
if (earliest_character == null) return;
|
||||
|
||||
const now = currentTime();
|
||||
if (earliest_cooldown > now) {
|
||||
const duration_s = earliest_cooldown - now + self.expiration_margin;
|
||||
const duration_ms: u64 = @intFromFloat(@trunc(duration_s * std.time.ms_per_s));
|
||||
std.log.debug("waiting for {d:.3}s", .{duration_s});
|
||||
std.time.sleep(std.time.ns_per_ms * duration_ms);
|
||||
}
|
||||
|
||||
const cooldown_margin = 0.1; // 100ms
|
||||
|
||||
now = currentTime();
|
||||
for (self.characters.items) |*managed_character| {
|
||||
if (now - managed_character.cooldown_expires_at >= -cooldown_margin) {
|
||||
return managed_character;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
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 },
|
||||
};
|
||||
try earliest_character.?.performNextAction(self.api);
|
||||
}
|
||||
};
|
||||
|
||||
fn moveIfNeeded(char: *ManagedCharacter, pos: Position) !bool {
|
||||
if (char.position().eql(pos)) {
|
||||
const bank_position: Position = .{ .x = 4, .y = 1 }; // TODO: Figure this out dynamically
|
||||
|
||||
fn getAPITokenFromArgs(allocator: Allocator) !?[]u8 {
|
||||
const args = try std.process.argsAlloc(allocator);
|
||||
defer std.process.argsFree(allocator, args);
|
||||
|
||||
if (args.len < 2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const filename = args[1];
|
||||
const cwd = std.fs.cwd();
|
||||
var token_buffer: [256]u8 = undefined;
|
||||
const token = try cwd.readFile(filename, &token_buffer);
|
||||
|
||||
return try allocator.dupe(u8, std.mem.trim(u8,token,"\n\t "));
|
||||
}
|
||||
|
||||
fn moveIfNeeded(api: *Server, brain: *CharacterBrain, pos: Position) !bool {
|
||||
const character = api.findCharacter(brain.name).?;
|
||||
|
||||
if (character.position.eql(pos)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try char.action_queue.append(.{ .move = pos });
|
||||
try brain.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;
|
||||
fn depositItemsToBank(api: *Server, brain: *CharacterBrain) !bool {
|
||||
const character = api.findCharacter(brain.name).?;
|
||||
const action_queue = &brain.action_queue;
|
||||
|
||||
// Deposit items and gold to bank if full
|
||||
if (character.getItemCount() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var character_pos = managed_char.position();
|
||||
|
||||
if (!character_pos.eql(manager.bank_position)) {
|
||||
try action_queue.append(.{ .move = manager.bank_position });
|
||||
if (!character.position.eql(bank_position)) {
|
||||
try action_queue.append(.{ .move = bank_position });
|
||||
}
|
||||
|
||||
for (character.inventory.slots) |slot| {
|
||||
@ -185,52 +210,51 @@ fn depositItemsToBank(manager: *Manager, managed_char: *ManagedCharacter) !bool
|
||||
return true;
|
||||
}
|
||||
|
||||
fn depositIfFull(manager: *Manager, char: *ManagedCharacter) !bool {
|
||||
const character = char.character;
|
||||
fn depositIfFull(api: *Server, brain: *CharacterBrain) !bool {
|
||||
const character = api.findCharacter(brain.name).?;
|
||||
if (character.getItemCount() < character.inventory_max_items) {
|
||||
return false;
|
||||
}
|
||||
|
||||
_ = try depositItemsToBank(manager, char);
|
||||
_ = try depositItemsToBank(api, brain);
|
||||
|
||||
if (character.gold > 0) {
|
||||
try char.action_queue.append(.{ .deposit_gold = @intCast(character.gold) });
|
||||
try brain.action_queue.append(.{ .deposit_gold = @intCast(character.gold) });
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
fn attackChickenRoutine(manager: *Manager, managed_char: *ManagedCharacter) !void {
|
||||
const chicken_pos = Position{ .x = 0, .y = 1 };
|
||||
|
||||
// Deposit items and gold to bank if full
|
||||
if (try depositIfFull(manager, managed_char)) {
|
||||
fn fightRoutine(api: *Server, brain: *CharacterBrain, enemy_position: Position) !void {
|
||||
if (try depositIfFull(api, brain)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Go to chickens
|
||||
if (try moveIfNeeded(managed_char, chicken_pos)) {
|
||||
if (try moveIfNeeded(api, brain, enemy_position)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Attack chickens
|
||||
try managed_char.action_queue.append(.{ .attack = {} });
|
||||
try brain.action_queue.append(.{ .fight = {} });
|
||||
}
|
||||
|
||||
fn gatherResourceRoutine(manager: *Manager, managed_char: *ManagedCharacter, resource_pos: Position) !void {
|
||||
if (try depositIfFull(manager, managed_char)) {
|
||||
fn gatherRoutine(api: *Server, brain: *CharacterBrain, resource_position: Position) !void {
|
||||
if (try depositIfFull(api, brain)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (try moveIfNeeded(managed_char, resource_pos)) {
|
||||
if (try moveIfNeeded(api, brain, resource_position)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try managed_char.action_queue;.append(.{ .gather = {} });
|
||||
try brain.action_queue.append(.{ .gather = {} });
|
||||
}
|
||||
|
||||
fn withdrawFromBank(manager: *Manager, char: *ManagedCharacter, items: []const ArtifactsAPI.ItemIdQuantity) !bool {
|
||||
fn withdrawFromBank(api: *Server, brain: *CharacterBrain, items: []const Server.ItemIdQuantity) !bool {
|
||||
var character = api.findCharacter(brain.name).?;
|
||||
|
||||
var has_all_items = true;
|
||||
for (items) |item_quantity| {
|
||||
const inventory_quantity = char.character.inventory.getItem(item_quantity.id);
|
||||
const inventory_quantity = character.inventory.getItem(item_quantity.id);
|
||||
if(inventory_quantity < item_quantity.quantity) {
|
||||
has_all_items = false;
|
||||
break;
|
||||
@ -238,14 +262,14 @@ fn withdrawFromBank(manager: *Manager, char: *ManagedCharacter, items: []const A
|
||||
}
|
||||
if (has_all_items) return false;
|
||||
|
||||
if (try moveIfNeeded(char, manager.bank_position)) {
|
||||
if (try moveIfNeeded(api, brain, bank_position)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (items) |item_quantity| {
|
||||
const inventory_quantity = char.character.inventory.getItem(item_quantity.id);
|
||||
const inventory_quantity = character.inventory.getItem(item_quantity.id);
|
||||
if(inventory_quantity < item_quantity.quantity) {
|
||||
try char.action_queue.append(.{ .withdraw_item = .{
|
||||
try brain.action_queue.append(.{ .withdraw_item = .{
|
||||
.id = item_quantity.id,
|
||||
.quantity = item_quantity.quantity - inventory_quantity,
|
||||
}});
|
||||
@ -255,26 +279,37 @@ fn withdrawFromBank(manager: *Manager, char: *ManagedCharacter, items: []const A
|
||||
return true;
|
||||
}
|
||||
|
||||
fn craftItem(manager: *Manager, char: *ManagedCharacter, id: ArtifactsAPI.ItemId, quantity: u64) !bool {
|
||||
const inventory_quantity = char.character.inventory.getItem(id);
|
||||
fn craftItem(api: *Server, brain: *CharacterBrain, id: Server.ItemId, quantity: u64) !bool {
|
||||
var character = api.findCharacter(brain.name).?;
|
||||
|
||||
const inventory_quantity = 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;
|
||||
const code = api.getItemCode(id) orelse return error.InvalidItemId;
|
||||
const item = try api.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)) {
|
||||
// TODO: Figure this out dynamically
|
||||
const workstation = switch (recipe.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 },
|
||||
};
|
||||
|
||||
if (try moveIfNeeded(api, brain, workstation)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try char.action_queue.append(.{ .craft_item = .{
|
||||
try brain.action_queue.append(.{ .craft_item = .{
|
||||
.id = id,
|
||||
.quantity = quantity - inventory_quantity
|
||||
}});
|
||||
@ -282,14 +317,17 @@ fn craftItem(manager: *Manager, char: *ManagedCharacter, id: ArtifactsAPI.ItemId
|
||||
return true;
|
||||
}
|
||||
|
||||
fn craftItemFromBank(manager: *Manager, char: *ManagedCharacter, id: ArtifactsAPI.ItemId, quantity: u64) !bool {
|
||||
const inventory_quantity = char.character.inventory.getItem(id);
|
||||
fn craftRoutine(api: *Server, brain: *CharacterBrain, id: Server.ItemId, quantity: u64) !void {
|
||||
var character = api.findCharacter(brain.name).?;
|
||||
const inventory_quantity = character.inventory.getItem(id);
|
||||
if (inventory_quantity >= quantity) {
|
||||
return false;
|
||||
if (try depositItemsToBank(api, brain)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const code = manager.api.getItemCode(id) orelse return error.InvalidItemId;
|
||||
const target_item = try manager.getItem(code) orelse return error.ItemNotFound;
|
||||
const code = api.getItemCode(id) orelse return error.InvalidItemId;
|
||||
const target_item = try api.getItem(code) orelse return error.ItemNotFound;
|
||||
if (target_item.craft == null) {
|
||||
return error.NotCraftable;
|
||||
}
|
||||
@ -302,15 +340,13 @@ fn craftItemFromBank(manager: *Manager, char: *ManagedCharacter, id: ArtifactsAP
|
||||
needed_item.quantity *= quantity;
|
||||
}
|
||||
|
||||
if (try withdrawFromBank(manager, char, needed_items.constSlice())) {
|
||||
return true;
|
||||
if (try withdrawFromBank(api, brain, needed_items.constSlice())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (try craftItem(manager, char, id, quantity)) {
|
||||
return true;
|
||||
if (try craftItem(api, brain, id, quantity)) {
|
||||
return;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
pub fn main() !void {
|
||||
@ -318,145 +354,87 @@ pub fn main() !void {
|
||||
defer _ = gpa.deinit();
|
||||
const allocator = gpa.allocator();
|
||||
|
||||
var api = try ArtifactsAPI.init(allocator);
|
||||
var api = try Server.init(allocator);
|
||||
defer api.deinit();
|
||||
|
||||
const args = try std.process.argsAlloc(allocator);
|
||||
defer std.process.argsFree(allocator, args);
|
||||
const token = (try getAPITokenFromArgs(allocator)) orelse return error.MissingToken;
|
||||
defer allocator.free(token);
|
||||
|
||||
if (args.len >= 2) {
|
||||
const filename = args[1];
|
||||
const cwd = std.fs.cwd();
|
||||
try api.setToken(token);
|
||||
|
||||
var token_buffer: [256]u8 = undefined;
|
||||
const token = try cwd.readFile(filename, &token_buffer);
|
||||
try api.setToken(std.mem.trim(u8,token,"\n\t "));
|
||||
var goal_manager = GoalManager.init(&api, allocator);
|
||||
defer goal_manager.deinit();
|
||||
|
||||
const chars = try api.listMyCharacters();
|
||||
defer chars.deinit();
|
||||
|
||||
for (chars.items) |char| {
|
||||
try goal_manager.addCharacter(char.name);
|
||||
}
|
||||
|
||||
if (api.token == null) {
|
||||
return error.MissingToken;
|
||||
}
|
||||
goal_manager.characters.items[0].routine = .{
|
||||
.fight = .{
|
||||
.at = Position.init(0, 1),
|
||||
.target = undefined
|
||||
},
|
||||
};
|
||||
|
||||
var manager = Manager.init(allocator, &api);
|
||||
defer manager.deinit();
|
||||
goal_manager.characters.items[1].routine = .{
|
||||
.gather = .{
|
||||
.at = Position.init(-1, 0),
|
||||
.target = undefined
|
||||
}
|
||||
};
|
||||
|
||||
const status = try api.getServerStatus();
|
||||
defer status.deinit();
|
||||
goal_manager.characters.items[2].routine = .{
|
||||
.gather = .{
|
||||
.at = Position.init(2, 0),
|
||||
.target = undefined
|
||||
}
|
||||
};
|
||||
|
||||
std.log.info("Server status: {s} v{s}", .{ status.status, status.version });
|
||||
std.log.info("Characters online: {}", .{ status.characters_online });
|
||||
goal_manager.characters.items[3].routine = .{
|
||||
.gather = .{
|
||||
.at = Position.init(4, 2),
|
||||
.target = undefined
|
||||
}
|
||||
};
|
||||
|
||||
const characters = try api.listMyCharacters(allocator);
|
||||
defer characters.deinit();
|
||||
goal_manager.characters.items[4].routine = .{
|
||||
.fight = .{
|
||||
.at = Position.init(0, 1),
|
||||
.target = undefined
|
||||
},
|
||||
};
|
||||
|
||||
// for (characters.list.items) |character| {
|
||||
// try manager.addCharacter(character);
|
||||
// }
|
||||
try manager.addCharacter(characters.list.items[0]);
|
||||
// goal_manager.characters.items[2].routine = .{
|
||||
// .craft = .{
|
||||
// .target = .{
|
||||
// .quantity = 3,
|
||||
// .id = try api.getItemId("copper"),
|
||||
// }
|
||||
// }
|
||||
// };
|
||||
|
||||
std.log.info("Starting main loop", .{});
|
||||
while (manager.poll()) |char| {
|
||||
if (char.action_queue.items.len > 0) {
|
||||
const action = char.action_queue.items[0];
|
||||
while (true) {
|
||||
try goal_manager.runNextAction();
|
||||
|
||||
var cooldown: ArtifactsAPI.Cooldown = undefined;
|
||||
switch (action) {
|
||||
.attack => {
|
||||
std.log.debug("{s} attacks", .{char.character.name});
|
||||
var result = try api.actionFight(char.character.name);
|
||||
for (goal_manager.characters.items) |*character| {
|
||||
if (character.action_queue.items.len > 0) continue;
|
||||
|
||||
cooldown = result.cooldown;
|
||||
char.character.gold += result.fight.gold;
|
||||
for (result.fight.drops.slice()) |item| {
|
||||
char.character.inventory.addItem(item.id, @intCast(item.quantity));
|
||||
}
|
||||
switch (character.routine) {
|
||||
.idle => {},
|
||||
.fight => |args| {
|
||||
try fightRoutine(&api, character, args.at);
|
||||
},
|
||||
.move => |pos| {
|
||||
std.log.debug("move {s} to ({}, {})", .{char.character.name, pos.x, pos.y});
|
||||
var result = try api.actionMove(char.character.name, pos.x, pos.y);
|
||||
defer result.deinit();
|
||||
|
||||
cooldown = result.cooldown;
|
||||
char.character.x = pos.x;
|
||||
char.character.y = pos.y;
|
||||
.gather => |args| {
|
||||
try gatherRoutine(&api, character, args.at);
|
||||
},
|
||||
.deposit_gold => |quantity| {
|
||||
std.log.debug("deposit {} gold from {s}", .{quantity, char.character.name});
|
||||
const result = try api.actionBankDepositGold(char.character.name, quantity);
|
||||
|
||||
cooldown = result.cooldown;
|
||||
char.character.gold -= @intCast(quantity);
|
||||
assert(char.character.gold >= 0);
|
||||
},
|
||||
.deposit_item => |item| {
|
||||
const code = api.getItemCode(item.id) orelse return error.ItemNotFound;
|
||||
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;
|
||||
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());
|
||||
.craft => |args| {
|
||||
try craftRoutine(&api, character, args.target.id, args.target.quantity);
|
||||
}
|
||||
}
|
||||
|
||||
char.cooldown_expires_at = cooldown.expiration;
|
||||
|
||||
_ = char.action_queue.orderedRemove(0);
|
||||
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")) {
|
||||
try gatherResourceRoutine(char, .{ .x = 2, .y = 0 }); // Copper ore
|
||||
} else if (std.mem.eql(u8, char.character.name, "Diana")) {
|
||||
try gatherResourceRoutine(char, .{ .x = 4, .y = 2 }); // Gudgeon fish
|
||||
} else {
|
||||
try attackChickenRoutine(char);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user