add crafting goal
This commit is contained in:
parent
d6a141d098
commit
df56eceab6
10
api/root.zig
10
api/root.zig
@ -18,7 +18,15 @@ pub const Item = @import("./schemas/item.zig");
|
|||||||
pub const Status = @import("./schemas/status.zig");
|
pub const Status = @import("./schemas/status.zig");
|
||||||
pub const Position = @import("./schemas/position.zig");
|
pub const Position = @import("./schemas/position.zig");
|
||||||
pub const Map = @import("./schemas/map.zig");
|
pub const Map = @import("./schemas/map.zig");
|
||||||
// pub const Character = @import("./schemas/character.zig");
|
pub const Character = @import("./schemas/character.zig");
|
||||||
|
pub const Equipment = @import("./schemas/equipment.zig");
|
||||||
|
pub const Craft = @import("./schemas/craft.zig");
|
||||||
|
pub const Resource = @import("./schemas/resource.zig");
|
||||||
|
pub const MoveResult = @import("./schemas/move_result.zig");
|
||||||
|
const SkillUsageResult = @import("./schemas/skill_usage_result.zig");
|
||||||
|
pub const GatherResult = SkillUsageResult;
|
||||||
|
pub const CraftResult = SkillUsageResult;
|
||||||
|
|
||||||
// pub const ServerStatus = @import("./schemas/status.zig");
|
// pub const ServerStatus = @import("./schemas/status.zig");
|
||||||
// pub const Map = @import("./schemas/map.zig");
|
// pub const Map = @import("./schemas/map.zig");
|
||||||
// pub const Position = @import("position.zig");
|
// pub const Position = @import("position.zig");
|
||||||
|
|||||||
@ -2,10 +2,12 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Store = @import("../store.zig");
|
const Store = @import("../store.zig");
|
||||||
const EnumStringUtils = @import("../enum_string_utils.zig").EnumStringUtils;
|
const EnumStringUtils = @import("../enum_string_utils.zig").EnumStringUtils;
|
||||||
|
const parseDateTime = @import("../date_time/parse.zig").parseDateTime;
|
||||||
const json_utils = @import("../json_utils.zig");
|
const json_utils = @import("../json_utils.zig");
|
||||||
pub const Equipment = @import("./equipment.zig");
|
pub const Equipment = @import("./equipment.zig");
|
||||||
const Task = @import("./task.zig");
|
const Task = @import("./task.zig");
|
||||||
const SimpleItem = @import("./simple_item.zig");
|
const SimpleItem = @import("./simple_item.zig");
|
||||||
|
const Position = @import("./position.zig");
|
||||||
|
|
||||||
const Character = @This();
|
const Character = @This();
|
||||||
|
|
||||||
@ -139,6 +141,8 @@ equipment: Equipment,
|
|||||||
task: ?TaskMasterTask,
|
task: ?TaskMasterTask,
|
||||||
inventory_max_items: u64,
|
inventory_max_items: u64,
|
||||||
inventory: Inventory,
|
inventory: Inventory,
|
||||||
|
position: Position,
|
||||||
|
cooldown_expiration: ?f64,
|
||||||
|
|
||||||
pub fn parse(store: *Store, obj: std.json.ObjectMap) !Character {
|
pub fn parse(store: *Store, obj: std.json.ObjectMap) !Character {
|
||||||
const name = try json_utils.getStringRequired(obj, "name");
|
const name = try json_utils.getStringRequired(obj, "name");
|
||||||
@ -171,6 +175,14 @@ pub fn parse(store: *Store, obj: std.json.ObjectMap) !Character {
|
|||||||
.air = try ElementalStats.parse(obj, "attack_air", "dmg_air", "res_air"),
|
.air = try ElementalStats.parse(obj, "attack_air", "dmg_air", "res_air"),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const x = try json_utils.getIntegerRequired(obj, "x");
|
||||||
|
const y = try json_utils.getIntegerRequired(obj, "y");
|
||||||
|
|
||||||
|
var cooldown_expiration: ?f64 = null;
|
||||||
|
if (json_utils.getString(obj, "cooldown_expiration")) |date_time| {
|
||||||
|
cooldown_expiration = parseDateTime(date_time) orelse return error.FailedToParseCooldownExpiration;
|
||||||
|
}
|
||||||
|
|
||||||
return Character{
|
return Character{
|
||||||
.name = try Name.fromSlice(name),
|
.name = try Name.fromSlice(name),
|
||||||
.account = try Account.fromSlice(account),
|
.account = try Account.fromSlice(account),
|
||||||
@ -181,7 +193,9 @@ pub fn parse(store: *Store, obj: std.json.ObjectMap) !Character {
|
|||||||
.equipment = try Equipment.parse(store, obj),
|
.equipment = try Equipment.parse(store, obj),
|
||||||
.task = try TaskMasterTask.parse(store, obj),
|
.task = try TaskMasterTask.parse(store, obj),
|
||||||
.inventory_max_items = inventory_max_items,
|
.inventory_max_items = inventory_max_items,
|
||||||
.inventory = try Inventory.parse(store, inventory)
|
.inventory = try Inventory.parse(store, inventory),
|
||||||
|
.position = Position.init(x, y),
|
||||||
|
.cooldown_expiration = cooldown_expiration
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
74
api/schemas/cooldown.zig
Normal file
74
api/schemas/cooldown.zig
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
// zig fmt: off
|
||||||
|
const std = @import("std");
|
||||||
|
const Store = @import("../store.zig");
|
||||||
|
const json_utils = @import("../json_utils.zig");
|
||||||
|
const parseDateTime = @import("../date_time/parse.zig").parseDateTime;
|
||||||
|
const EnumStringUtils = @import("../enum_string_utils.zig").EnumStringUtils;
|
||||||
|
|
||||||
|
const Cooldown = @This();
|
||||||
|
|
||||||
|
pub const Reason = enum {
|
||||||
|
movement,
|
||||||
|
fight,
|
||||||
|
crafting,
|
||||||
|
gathering,
|
||||||
|
buy_ge,
|
||||||
|
sell_ge,
|
||||||
|
cancel_ge,
|
||||||
|
delete_item,
|
||||||
|
deposit,
|
||||||
|
withdraw,
|
||||||
|
deposit_gold,
|
||||||
|
withdraw_gold,
|
||||||
|
equip,
|
||||||
|
unequip,
|
||||||
|
task,
|
||||||
|
christmas_exchange,
|
||||||
|
recycling,
|
||||||
|
rest,
|
||||||
|
use,
|
||||||
|
buy_bank_expansion,
|
||||||
|
|
||||||
|
const Utils = EnumStringUtils(Reason, .{
|
||||||
|
.{ "movement" , Reason.movement },
|
||||||
|
.{ "fight" , Reason.fight },
|
||||||
|
.{ "crafting" , Reason.crafting },
|
||||||
|
.{ "gathering" , Reason.gathering },
|
||||||
|
.{ "buy_ge" , Reason.buy_ge },
|
||||||
|
.{ "sell_ge" , Reason.sell_ge },
|
||||||
|
.{ "cancel_ge" , Reason.cancel_ge },
|
||||||
|
.{ "delete_item" , Reason.delete_item },
|
||||||
|
.{ "deposit" , Reason.deposit },
|
||||||
|
.{ "withdraw" , Reason.withdraw },
|
||||||
|
.{ "deposit_gold" , Reason.deposit_gold },
|
||||||
|
.{ "withdraw_gold" , Reason.withdraw_gold },
|
||||||
|
.{ "equip" , Reason.equip },
|
||||||
|
.{ "unequip" , Reason.unequip },
|
||||||
|
.{ "task" , Reason.task },
|
||||||
|
.{ "christmas_exchange", Reason.christmas_exchange },
|
||||||
|
.{ "recycling" , Reason.recycling },
|
||||||
|
.{ "rest" , Reason.rest },
|
||||||
|
.{ "use" , Reason.use },
|
||||||
|
.{ "buy_bank_expansion", Reason.buy_bank_expansion },
|
||||||
|
});
|
||||||
|
|
||||||
|
pub const fromString = Utils.fromString;
|
||||||
|
pub const toString = Utils.toString;
|
||||||
|
};
|
||||||
|
|
||||||
|
started_at: f64,
|
||||||
|
expiration: f64,
|
||||||
|
reason: Reason,
|
||||||
|
|
||||||
|
pub fn parse(store: *Store, obj: std.json.ObjectMap) !Cooldown {
|
||||||
|
_ = store;
|
||||||
|
const started_at = try json_utils.getStringRequired(obj, "started_at");
|
||||||
|
const expiration = try json_utils.getStringRequired(obj, "expiration");
|
||||||
|
const reason = try json_utils.getStringRequired(obj, "reason");
|
||||||
|
|
||||||
|
return Cooldown{
|
||||||
|
.started_at = parseDateTime(started_at) orelse return error.InvalidStartedAt,
|
||||||
|
.expiration = parseDateTime(expiration) orelse return error.InvalidExpiration,
|
||||||
|
.reason = Reason.fromString(reason) orelse return error.InvalidReason
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -3,11 +3,13 @@ const Store = @import("../store.zig");
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const json_utils = @import("../json_utils.zig");
|
const json_utils = @import("../json_utils.zig");
|
||||||
const SimpleItem = @import("./simple_item.zig");
|
const SimpleItem = @import("./simple_item.zig");
|
||||||
|
const Character = @import("./character.zig");
|
||||||
const EnumStringUtils = @import("../enum_string_utils.zig").EnumStringUtils;
|
const EnumStringUtils = @import("../enum_string_utils.zig").EnumStringUtils;
|
||||||
|
|
||||||
const Craft = @This();
|
const Craft = @This();
|
||||||
|
|
||||||
pub const Items = SimpleItem.BoundedArray(8);
|
pub const max_items = 8;
|
||||||
|
pub const Items = SimpleItem.BoundedArray(max_items);
|
||||||
|
|
||||||
pub const Skill = enum {
|
pub const Skill = enum {
|
||||||
weaponcrafting,
|
weaponcrafting,
|
||||||
@ -30,6 +32,18 @@ pub const Skill = enum {
|
|||||||
|
|
||||||
pub const toString = Utils.toString;
|
pub const toString = Utils.toString;
|
||||||
pub const fromString = Utils.fromString;
|
pub const fromString = Utils.fromString;
|
||||||
|
|
||||||
|
pub fn toCharacterSkill(self: Skill) Character.Skill {
|
||||||
|
return switch (self) {
|
||||||
|
.weaponcrafting => Character.Skill.weaponcrafting,
|
||||||
|
.gearcrafting => Character.Skill.gearcrafting,
|
||||||
|
.jewelrycrafting => Character.Skill.jewelrycrafting,
|
||||||
|
.cooking => Character.Skill.cooking,
|
||||||
|
.woodcutting => Character.Skill.woodcutting,
|
||||||
|
.mining => Character.Skill.mining,
|
||||||
|
.alchemy => Character.Skill.alchemy,
|
||||||
|
};
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
skill: Skill,
|
skill: Skill,
|
||||||
|
|||||||
@ -106,3 +106,21 @@ pub fn parse(store: *Store, obj: std.json.ObjectMap) !Equipment {
|
|||||||
.utility2 = try UtilitySlot.parse(store, obj, "utility2_slot", "utility2_slot_quantity"),
|
.utility2 = try UtilitySlot.parse(store, obj, "utility2_slot", "utility2_slot_quantity"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn getSlot(self: Equipment, slot: Slot) ?Store.Id {
|
||||||
|
return switch (slot) {
|
||||||
|
.weapon => self.weapon,
|
||||||
|
.shield => self.shield,
|
||||||
|
.helmet => self.helmet,
|
||||||
|
.body_armor => self.body_armor,
|
||||||
|
.leg_armor => self.leg_armor,
|
||||||
|
.boots => self.boots,
|
||||||
|
.ring1 => self.ring1,
|
||||||
|
.ring2 => self.ring2,
|
||||||
|
.amulet => self.amulet,
|
||||||
|
.artifact1 => self.artifact1,
|
||||||
|
.artifact2 => self.artifact2,
|
||||||
|
.utility1 => if (self.utility1) |util| util.id else null,
|
||||||
|
.utility2 => if (self.utility2) |util| util.id else null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
43
api/schemas/ge_order.zig
Normal file
43
api/schemas/ge_order.zig
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
// zig fmt: off
|
||||||
|
const std = @import("std");
|
||||||
|
const Character = @import("./character.zig");
|
||||||
|
const json_utils = @import("../json_utils.zig");
|
||||||
|
const parseDateTime = @import("../date_time/parse.zig").parseDateTime;
|
||||||
|
const Store = @import("../store.zig");
|
||||||
|
const Item = @import("./item.zig");
|
||||||
|
|
||||||
|
const GEOrder = @This();
|
||||||
|
|
||||||
|
pub const max_id_size = 32;
|
||||||
|
pub const Id = std.BoundedArray(u8, max_id_size);
|
||||||
|
pub const Account = Character.Account;
|
||||||
|
pub const Code = Item.Code;
|
||||||
|
|
||||||
|
id: Id,
|
||||||
|
seller: Account,
|
||||||
|
item_id: Store.Id,
|
||||||
|
quantity: u64,
|
||||||
|
price: u64,
|
||||||
|
created_at: f64,
|
||||||
|
|
||||||
|
pub fn parse(store: *Store, obj: std.json.ObjectMap) !GEOrder {
|
||||||
|
const id = try json_utils.getStringRequired(obj, "id");
|
||||||
|
const seller = try json_utils.getStringRequired(obj, "seller");
|
||||||
|
const code = try json_utils.getStringRequired(obj, "code");
|
||||||
|
const quantity = try json_utils.getPositiveIntegerRequired(obj, "quantity");
|
||||||
|
const price = try json_utils.getPositiveIntegerRequired(obj, "price");
|
||||||
|
const created_at = try json_utils.getStringRequired(obj, "created_at");
|
||||||
|
|
||||||
|
return GEOrder{
|
||||||
|
.id = try Id.fromSlice(id),
|
||||||
|
.seller = try Account.fromSlice(seller),
|
||||||
|
.item_id = try store.items.getOrReserveId(code),
|
||||||
|
.quantity = quantity,
|
||||||
|
.price = price,
|
||||||
|
.created_at = parseDateTime(created_at) orelse return error.InvalidDataTime,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parseAndAppend(store: *Store, obj: std.json.ObjectMap) !Store.Id {
|
||||||
|
return try store.ge_orders.appendOrUpdate(try parse(store, obj));
|
||||||
|
}
|
||||||
31
api/schemas/move_result.zig
Normal file
31
api/schemas/move_result.zig
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
// zig fmt: off
|
||||||
|
const std = @import("std");
|
||||||
|
const Store = @import("../store.zig");
|
||||||
|
const json_utils = @import("../json_utils.zig");
|
||||||
|
const Character = @import("./character.zig");
|
||||||
|
const Cooldown = @import("./cooldown.zig");
|
||||||
|
const Map = @import("./map.zig");
|
||||||
|
|
||||||
|
const MoveResult = @This();
|
||||||
|
|
||||||
|
cooldown: Cooldown,
|
||||||
|
destination: Map,
|
||||||
|
character: Character,
|
||||||
|
|
||||||
|
fn parse(store: *Store, obj: std.json.ObjectMap) !MoveResult {
|
||||||
|
const cooldown = json_utils.getObject(obj, "cooldown") orelse return error.MissingProperty;
|
||||||
|
const character = json_utils.getObject(obj, "character") orelse return error.MissingProperty;
|
||||||
|
const destination = json_utils.getObject(obj, "destination") orelse return error.MissingProperty;
|
||||||
|
|
||||||
|
return MoveResult{
|
||||||
|
.character = try Character.parse(store, character),
|
||||||
|
.destination = try Map.parse(store, destination),
|
||||||
|
.cooldown = try Cooldown.parse(store, cooldown)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parseAndUpdate(store: *Store, obj: std.json.ObjectMap) !MoveResult {
|
||||||
|
const result = try parse(store, obj);
|
||||||
|
_ = try store.characters.appendOrUpdate(result.character);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
@ -20,6 +20,12 @@ pub fn subtract(self: Position, other: Position) Position {
|
|||||||
return init(self.x - other.x, self.y - other.y);
|
return init(self.x - other.x, self.y - other.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn distance(self: Position, other: Position) f32 {
|
||||||
|
const dx: f32 = @floatFromInt(self.x - other.x);
|
||||||
|
const dy: f32 = @floatFromInt(self.y - other.y);
|
||||||
|
return @sqrt(dx * dx + dy * dy);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn format(
|
pub fn format(
|
||||||
self: Position,
|
self: Position,
|
||||||
comptime fmt: []const u8,
|
comptime fmt: []const u8,
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
// zig fmt: off
|
// zig fmt: off
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Store = @import("../store.zig");
|
const Store = @import("../store.zig");
|
||||||
|
const Character = @import("./character.zig");
|
||||||
const json_utils = @import("../json_utils.zig");
|
const json_utils = @import("../json_utils.zig");
|
||||||
const DropRate = @import("./drop_rate.zig");
|
const DropRate = @import("./drop_rate.zig");
|
||||||
const EnumStringUtils = @import("../enum_string_utils.zig").EnumStringUtils;
|
const EnumStringUtils = @import("../enum_string_utils.zig").EnumStringUtils;
|
||||||
@ -22,6 +23,15 @@ pub const Skill = enum {
|
|||||||
|
|
||||||
pub const toString = Utils.toString;
|
pub const toString = Utils.toString;
|
||||||
pub const fromString = Utils.fromString;
|
pub const fromString = Utils.fromString;
|
||||||
|
|
||||||
|
pub fn toCharacterSkill(self: Skill) Character.Skill {
|
||||||
|
return switch (self) {
|
||||||
|
.mining => Character.Skill.mining,
|
||||||
|
.woodcutting => Character.Skill.woodcutting,
|
||||||
|
.fishing => Character.Skill.fishing,
|
||||||
|
.alchemy => Character.Skill.alchemy,
|
||||||
|
};
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Name = std.BoundedArray(u8, 32);
|
pub const Name = std.BoundedArray(u8, 32);
|
||||||
@ -29,7 +39,9 @@ pub const Name = std.BoundedArray(u8, 32);
|
|||||||
pub const max_code_size = 32;
|
pub const max_code_size = 32;
|
||||||
pub const Code = std.BoundedArray(u8, max_code_size);
|
pub const Code = std.BoundedArray(u8, max_code_size);
|
||||||
|
|
||||||
pub const Drops = std.BoundedArray(DropRate, 16);
|
pub const max_drops = 16;
|
||||||
|
pub const Drop = DropRate;
|
||||||
|
pub const Drops = std.BoundedArray(DropRate, max_drops);
|
||||||
|
|
||||||
name: Name,
|
name: Name,
|
||||||
code: Code,
|
code: Code,
|
||||||
|
|||||||
23
api/schemas/skill_info_details.zig
Normal file
23
api/schemas/skill_info_details.zig
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
// zig fmt: off
|
||||||
|
const std = @import("std");
|
||||||
|
const json_utils = @import("../json_utils.zig");
|
||||||
|
const Resource = @import("./resource.zig");
|
||||||
|
const Store = @import("../store.zig");
|
||||||
|
const SimpleItem = @import("./simple_item.zig");
|
||||||
|
|
||||||
|
const SkillInfoDetails = @This();
|
||||||
|
|
||||||
|
pub const Items = SimpleItem.BoundedArray(Resource.max_drops);
|
||||||
|
|
||||||
|
xp: u64,
|
||||||
|
items: Items,
|
||||||
|
|
||||||
|
pub fn parse(store: *Store, obj: std.json.ObjectMap) !SkillInfoDetails {
|
||||||
|
const xp = try json_utils.getPositiveIntegerRequired(obj, "xp");
|
||||||
|
const items = json_utils.getArray(obj, "items") orelse return error.MissingProperty;
|
||||||
|
|
||||||
|
return SkillInfoDetails{
|
||||||
|
.xp = xp,
|
||||||
|
.items = try Items.parse(store, items)
|
||||||
|
};
|
||||||
|
}
|
||||||
31
api/schemas/skill_usage_result.zig
Normal file
31
api/schemas/skill_usage_result.zig
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
// zig fmt: off
|
||||||
|
const std = @import("std");
|
||||||
|
const Store = @import("../store.zig");
|
||||||
|
const json_utils = @import("../json_utils.zig");
|
||||||
|
const Character = @import("./character.zig");
|
||||||
|
const Cooldown = @import("./cooldown.zig");
|
||||||
|
const SkillInfoDetails = @import("./skill_info_details.zig");
|
||||||
|
|
||||||
|
const SkillUsageResult = @This();
|
||||||
|
|
||||||
|
cooldown: Cooldown,
|
||||||
|
details: SkillInfoDetails,
|
||||||
|
character: Character,
|
||||||
|
|
||||||
|
fn parse(store: *Store, obj: std.json.ObjectMap) !SkillUsageResult {
|
||||||
|
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 SkillUsageResult{
|
||||||
|
.character = try Character.parse(store, character),
|
||||||
|
.cooldown = try Cooldown.parse(store, cooldown),
|
||||||
|
.details = try SkillInfoDetails.parse(store, details)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parseAndUpdate(store: *Store, obj: std.json.ObjectMap) !SkillUsageResult {
|
||||||
|
const result = try parse(store, obj);
|
||||||
|
_ = try store.characters.appendOrUpdate(result.character);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
100
api/server.zig
100
api/server.zig
@ -22,6 +22,11 @@ const Monster = @import("./schemas/monster.zig");
|
|||||||
const Resource = @import("./schemas/resource.zig");
|
const Resource = @import("./schemas/resource.zig");
|
||||||
const Position = @import("./schemas/position.zig");
|
const Position = @import("./schemas/position.zig");
|
||||||
const Map = @import("./schemas/map.zig");
|
const Map = @import("./schemas/map.zig");
|
||||||
|
const GEOrder = @import("./schemas/ge_order.zig");
|
||||||
|
const MoveResult = @import("./schemas/move_result.zig");
|
||||||
|
const SkillUsageResult = @import("./schemas/skill_usage_result.zig");
|
||||||
|
pub const GatherResult = SkillUsageResult;
|
||||||
|
pub const CraftResult = SkillUsageResult;
|
||||||
const Image = Store.Image;
|
const Image = Store.Image;
|
||||||
|
|
||||||
const Server = @This();
|
const Server = @This();
|
||||||
@ -946,6 +951,101 @@ pub fn getImage(self: *Server, category: Image.Category, code: []const u8) Fetch
|
|||||||
return image_id;
|
return image_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://api.artifactsmmo.com/docs/#/operations/get_ge_sell_order_grandexchange_orders__id__get
|
||||||
|
pub fn getGEOrder(self: *Server, id: []const u8) FetchError!?Store.Id {
|
||||||
|
const path_buff_size = comptime blk: {
|
||||||
|
var count = 0;
|
||||||
|
count += 22; // "/grandexchange/orders/"
|
||||||
|
count += GEOrder.max_id_size;
|
||||||
|
break :blk count;
|
||||||
|
};
|
||||||
|
|
||||||
|
var path_buff: [path_buff_size]u8 = undefined;
|
||||||
|
const path = std.fmt.bufPrint(&path_buff, "/grandexchange/orders/{s}", .{ id }) catch return FetchError.InvalidPayload;
|
||||||
|
|
||||||
|
return try self.fetchOptionalObject(
|
||||||
|
FetchError,
|
||||||
|
null,
|
||||||
|
Store.Id,
|
||||||
|
GEOrder.parseAndAppend,
|
||||||
|
.{ .method = .GET, .path = path, .ratelimit = .data }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://api.artifactsmmo.com/docs/#/operations/action_move_my__name__action_move_post
|
||||||
|
pub fn move(self: *Server, character: []const u8, position: Position) errors.MoveError!MoveResult {
|
||||||
|
const path_buff_size = comptime blk: {
|
||||||
|
var count = 0;
|
||||||
|
count += 4; // "/my/"
|
||||||
|
count += Character.max_name_size;
|
||||||
|
count += 12; // "/action/move"
|
||||||
|
break :blk count;
|
||||||
|
};
|
||||||
|
|
||||||
|
var path_buff: [path_buff_size]u8 = undefined;
|
||||||
|
const path = std.fmt.bufPrint(&path_buff, "/my/{s}/action/move", .{ character }) catch return FetchError.InvalidPayload;
|
||||||
|
|
||||||
|
var payload_buffer: [64]u8 = undefined;
|
||||||
|
const payload = std.fmt.bufPrint(&payload_buffer, "{{ \"x\":{}, \"y\":{} }}", .{ position.x, position.y }) catch return FetchError.InvalidPayload;
|
||||||
|
|
||||||
|
const result = try self.fetchObject(
|
||||||
|
errors.MoveError,
|
||||||
|
errors.parseMoveError,
|
||||||
|
MoveResult,
|
||||||
|
MoveResult.parseAndUpdate,
|
||||||
|
.{ .method = .POST, .path = path, .ratelimit = .actions, .payload = payload }
|
||||||
|
);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://api.artifactsmmo.com/docs/#/operations/action_gathering_my__name__action_gathering_post
|
||||||
|
pub fn gather(self: *Server, character: []const u8) errors.GatherError!GatherResult {
|
||||||
|
const path_buff_size = comptime blk: {
|
||||||
|
var count = 0;
|
||||||
|
count += 4; // "/my/"
|
||||||
|
count += Character.max_name_size;
|
||||||
|
count += 17; // "/action/gathering"
|
||||||
|
break :blk count;
|
||||||
|
};
|
||||||
|
|
||||||
|
var path_buff: [path_buff_size]u8 = undefined;
|
||||||
|
const path = std.fmt.bufPrint(&path_buff, "/my/{s}/action/gathering", .{ character }) catch return FetchError.InvalidPayload;
|
||||||
|
|
||||||
|
return try self.fetchObject(
|
||||||
|
errors.GatherError,
|
||||||
|
errors.parseGatherError,
|
||||||
|
GatherResult,
|
||||||
|
GatherResult.parseAndUpdate,
|
||||||
|
.{ .method = .POST, .path = path, .ratelimit = .actions }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://api.artifactsmmo.com/docs/#/operations/action_crafting_my__name__action_crafting_post
|
||||||
|
pub fn craft(self: *Server, character: []const u8, item: []const u8, quantity: u64) errors.CraftError!CraftResult {
|
||||||
|
const path_buff_size = comptime blk: {
|
||||||
|
var count = 0;
|
||||||
|
count += 4; // "/my/"
|
||||||
|
count += Character.max_name_size;
|
||||||
|
count += 16; // "/action/crafting"
|
||||||
|
break :blk count;
|
||||||
|
};
|
||||||
|
|
||||||
|
var path_buff: [path_buff_size]u8 = undefined;
|
||||||
|
const path = std.fmt.bufPrint(&path_buff, "/my/{s}/action/crafting", .{ character }) catch return FetchError.InvalidPayload;
|
||||||
|
|
||||||
|
var payload_buff: [256]u8 = undefined;
|
||||||
|
const payload = std.fmt.bufPrint(&payload_buff, "{{ \"code\":\"{s}\", \"quantity\":{} }}", .{ item, quantity }) catch return FetchError.InvalidPayload;
|
||||||
|
|
||||||
|
return try self.fetchObject(
|
||||||
|
errors.CraftError,
|
||||||
|
errors.parseCraftError,
|
||||||
|
CraftResult,
|
||||||
|
CraftResult.parseAndUpdate,
|
||||||
|
.{ .method = .POST, .path = path, .payload = payload, .ratelimit = .actions }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// https://api.artifactsmmo.com/docs/#/operations/generate_token_token_post
|
// https://api.artifactsmmo.com/docs/#/operations/generate_token_token_post
|
||||||
pub fn generateToken(self: *Server, username: []const u8, password: []const u8) !AuthToken {
|
pub fn generateToken(self: *Server, username: []const u8, password: []const u8) !AuthToken {
|
||||||
const base64_encoder = std.base64.standard.Encoder;
|
const base64_encoder = std.base64.standard.Encoder;
|
||||||
|
|||||||
@ -2,11 +2,13 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const s2s = @import("s2s");
|
const s2s = @import("s2s");
|
||||||
const Item = @import("./schemas/item.zig");
|
const Item = @import("./schemas/item.zig");
|
||||||
|
const SimpleItem = @import("./schemas/simple_item.zig");
|
||||||
const Character = @import("./schemas/character.zig");
|
const Character = @import("./schemas/character.zig");
|
||||||
const Task = @import("./schemas/task.zig");
|
const Task = @import("./schemas/task.zig");
|
||||||
const Monster = @import("./schemas/monster.zig");
|
const Monster = @import("./schemas/monster.zig");
|
||||||
const Resource = @import("./schemas/resource.zig");
|
const Resource = @import("./schemas/resource.zig");
|
||||||
const Map = @import("./schemas/map.zig");
|
const Map = @import("./schemas/map.zig");
|
||||||
|
const GEOrder = @import("./schemas/ge_order.zig");
|
||||||
const Position = @import("./schemas/position.zig");
|
const Position = @import("./schemas/position.zig");
|
||||||
|
|
||||||
const Skin = Character.Skin;
|
const Skin = Character.Skin;
|
||||||
@ -261,7 +263,7 @@ fn Repository(comptime Object: type, comptime name_field: []const u8) type {
|
|||||||
if (id < self.objects.items.len) {
|
if (id < self.objects.items.len) {
|
||||||
return switch (self.objects.items[id]) {
|
return switch (self.objects.items[id]) {
|
||||||
.object => |obj| @field(obj, name_field).slice(),
|
.object => |obj| @field(obj, name_field).slice(),
|
||||||
.reserve => |name| name.slice(),
|
.reserved => |name| name.slice(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -305,7 +307,9 @@ const Characters = Repository(Character, "name");
|
|||||||
const Tasks = Repository(Task, "code");
|
const Tasks = Repository(Task, "code");
|
||||||
const Monsters = Repository(Monster, "code");
|
const Monsters = Repository(Monster, "code");
|
||||||
const Resources = Repository(Resource, "code");
|
const Resources = Repository(Resource, "code");
|
||||||
|
const GEOrders = Repository(GEOrder, "id");
|
||||||
const Maps = std.ArrayListUnmanaged(Map);
|
const Maps = std.ArrayListUnmanaged(Map);
|
||||||
|
const Bank = SimpleItem.BoundedArray(64);
|
||||||
|
|
||||||
items: Items,
|
items: Items,
|
||||||
characters: Characters,
|
characters: Characters,
|
||||||
@ -314,6 +318,8 @@ monsters: Monsters,
|
|||||||
resources: Resources,
|
resources: Resources,
|
||||||
images: Images,
|
images: Images,
|
||||||
maps: Maps,
|
maps: Maps,
|
||||||
|
bank: Bank,
|
||||||
|
ge_orders: GEOrders,
|
||||||
|
|
||||||
pub fn init(allocator: std.mem.Allocator) !Store {
|
pub fn init(allocator: std.mem.Allocator) !Store {
|
||||||
const max_items = 512;
|
const max_items = 512;
|
||||||
@ -322,6 +328,7 @@ pub fn init(allocator: std.mem.Allocator) !Store {
|
|||||||
const max_monsters = 64;
|
const max_monsters = 64;
|
||||||
const max_resources = 32;
|
const max_resources = 32;
|
||||||
const max_maps = 512;
|
const max_maps = 512;
|
||||||
|
const max_ge_orders = 128;
|
||||||
|
|
||||||
var items = try Items.initCapacity(allocator, max_items);
|
var items = try Items.initCapacity(allocator, max_items);
|
||||||
errdefer items.deinit(allocator);
|
errdefer items.deinit(allocator);
|
||||||
@ -351,6 +358,11 @@ pub fn init(allocator: std.mem.Allocator) !Store {
|
|||||||
var maps = try Maps.initCapacity(allocator, max_maps);
|
var maps = try Maps.initCapacity(allocator, max_maps);
|
||||||
errdefer maps.deinit(allocator);
|
errdefer maps.deinit(allocator);
|
||||||
|
|
||||||
|
var ge_orders = try GEOrders.initCapacity(allocator, max_ge_orders);
|
||||||
|
errdefer ge_orders.deinit(allocator);
|
||||||
|
|
||||||
|
const bank = Bank.init();
|
||||||
|
|
||||||
return Store{
|
return Store{
|
||||||
.items = items,
|
.items = items,
|
||||||
.characters = characters,
|
.characters = characters,
|
||||||
@ -359,6 +371,8 @@ pub fn init(allocator: std.mem.Allocator) !Store {
|
|||||||
.resources = resources,
|
.resources = resources,
|
||||||
.maps = maps,
|
.maps = maps,
|
||||||
.images = images,
|
.images = images,
|
||||||
|
.bank = bank,
|
||||||
|
.ge_orders = ge_orders
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -370,6 +384,7 @@ pub fn deinit(self: *Store, allocator: std.mem.Allocator) void {
|
|||||||
self.resources.deinit(allocator);
|
self.resources.deinit(allocator);
|
||||||
self.maps.deinit(allocator);
|
self.maps.deinit(allocator);
|
||||||
self.images.deinit(allocator);
|
self.images.deinit(allocator);
|
||||||
|
self.ge_orders.deinit(allocator);
|
||||||
}
|
}
|
||||||
|
|
||||||
const SaveData = struct {
|
const SaveData = struct {
|
||||||
|
|||||||
57
cli/main.zig
57
cli/main.zig
@ -1,3 +1,4 @@
|
|||||||
|
// zig fmt: off
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
@ -5,13 +6,12 @@ const Allocator = std.mem.Allocator;
|
|||||||
const Artificer = @import("artificer");
|
const Artificer = @import("artificer");
|
||||||
const Api = @import("artifacts-api");
|
const Api = @import("artifacts-api");
|
||||||
|
|
||||||
// zig fmt: off
|
|
||||||
pub const std_options = .{
|
pub const std_options = .{
|
||||||
.log_scope_levels = &[_]std.log.ScopeLevel{
|
.log_scope_levels = &[_]std.log.ScopeLevel{
|
||||||
.{ .scope = .api, .level = .info },
|
.{ .scope = .api, .level = .info },
|
||||||
|
.{ .scope = .artificer, .level = .debug },
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// zig fmt: on
|
|
||||||
|
|
||||||
fn getAPITokenFromArgs(allocator: Allocator) !?[]u8 {
|
fn getAPITokenFromArgs(allocator: Allocator) !?[]u8 {
|
||||||
const args = try std.process.argsAlloc(allocator);
|
const args = try std.process.argsAlloc(allocator);
|
||||||
@ -37,9 +37,6 @@ pub fn main() !void {
|
|||||||
const token = (try getAPITokenFromArgs(allocator)) orelse return error.MissingToken;
|
const token = (try getAPITokenFromArgs(allocator)) orelse return error.MissingToken;
|
||||||
defer allocator.free(token);
|
defer allocator.free(token);
|
||||||
|
|
||||||
// var artificer = try Artificer.init(allocator, token);
|
|
||||||
// defer artificer.deinit();
|
|
||||||
|
|
||||||
var store = try Api.Store.init(allocator);
|
var store = try Api.Store.init(allocator);
|
||||||
defer store.deinit(allocator);
|
defer store.deinit(allocator);
|
||||||
|
|
||||||
@ -48,33 +45,37 @@ pub fn main() !void {
|
|||||||
|
|
||||||
try server.setToken(token);
|
try server.setToken(token);
|
||||||
|
|
||||||
const resources = try server.getResources(allocator, .{});
|
std.log.info("Prefetching server data", .{});
|
||||||
resources.deinit();
|
|
||||||
|
|
||||||
// var artificer = try Artificer.init(allocator, token);
|
|
||||||
// defer artificer.deinit();
|
|
||||||
|
|
||||||
{
|
{
|
||||||
const status: Api.Status = try server.getStatus();
|
const cwd_path = try std.fs.cwd().realpathAlloc(allocator, ".");
|
||||||
|
defer allocator.free(cwd_path);
|
||||||
|
|
||||||
const file = try std.fs.cwd().createFile("api-store.bin", .{});
|
const cache_path = try std.fs.path.resolve(allocator, &.{ cwd_path, "./api-store.bin" });
|
||||||
defer file.close();
|
defer allocator.free(cache_path);
|
||||||
try store.save(status.version.slice(), file.writer());
|
|
||||||
|
// TODO: Don't prefetch images
|
||||||
|
try server.prefetchCached(allocator, cache_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
// std.log.info("Prefetching server data", .{});
|
const character_id = (try server.getCharacter("Blondie")).?;
|
||||||
// try artificer.server.prefetchCached(cache_path);
|
|
||||||
|
|
||||||
// if (false) {
|
var artificer = try Artificer.init(allocator, &server, character_id);
|
||||||
// std.log.info("Starting main loop", .{});
|
defer artificer.deinit(allocator);
|
||||||
// while (true) {
|
|
||||||
// const waitUntil = artificer.nextStepAt();
|
// _ = try artificer.appendGoal(Artificer.Goal{
|
||||||
// const duration = waitUntil - std.time.milliTimestamp();
|
// .gather = .{
|
||||||
// if (duration > 0) {
|
// .item = store.items.getId("sap").?,
|
||||||
// std.time.sleep(@intCast(duration));
|
// .quantity = 1
|
||||||
// }
|
|
||||||
//
|
|
||||||
// try artificer.step();
|
|
||||||
// }
|
// }
|
||||||
// }
|
// });
|
||||||
|
|
||||||
|
_ = try artificer.appendGoal(Artificer.Goal{
|
||||||
|
.craft = .{
|
||||||
|
.item = store.items.getId("copper").?,
|
||||||
|
.quantity = 3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
std.log.info("Starting main loop", .{});
|
||||||
|
try artificer.runUntilGoalsComplete();
|
||||||
}
|
}
|
||||||
|
|||||||
183
lib/action.zig
183
lib/action.zig
@ -1,183 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const Api = @import("artifacts-api");
|
|
||||||
const Position = Api.Position;
|
|
||||||
const Server = Api.Server;
|
|
||||||
const assert = std.debug.assert;
|
|
||||||
|
|
||||||
pub const Action = union(enum) {
|
|
||||||
move: Position,
|
|
||||||
fight,
|
|
||||||
gather,
|
|
||||||
deposit_gold: u64,
|
|
||||||
deposit_item: Api.ItemQuantity,
|
|
||||||
withdraw_item: Api.ItemQuantity,
|
|
||||||
craft_item: Api.ItemQuantity,
|
|
||||||
accept_task,
|
|
||||||
|
|
||||||
pub fn perform(self: Action, api: *Server, name: []const u8) !ActionResult {
|
|
||||||
const log = std.log.default;
|
|
||||||
|
|
||||||
switch (self) {
|
|
||||||
.fight => {
|
|
||||||
log.debug("[{s}] attack", .{name});
|
|
||||||
return .{
|
|
||||||
.fight = api.actionFight(name)
|
|
||||||
};
|
|
||||||
},
|
|
||||||
.move => |pos| {
|
|
||||||
log.debug("[{s}] move to ({}, {})", .{name, pos.x, pos.y});
|
|
||||||
return .{
|
|
||||||
.move = api.actionMove(name, pos.x, pos.y)
|
|
||||||
};
|
|
||||||
},
|
|
||||||
.deposit_gold => |quantity| {
|
|
||||||
log.debug("[{s}] deposit {} gold", .{name, quantity});
|
|
||||||
return .{
|
|
||||||
.deposit_gold = api.actionBankDepositGold(name, quantity)
|
|
||||||
};
|
|
||||||
},
|
|
||||||
.deposit_item => |item| {
|
|
||||||
const code = api.store.getCode(item.id) orelse return error.ItemNotFound;
|
|
||||||
log.debug("[{s}] deposit {s} (x{})", .{name, code, item.quantity});
|
|
||||||
return .{
|
|
||||||
.deposit_item = api.actionBankDepositItem(name, code, item.quantity)
|
|
||||||
};
|
|
||||||
},
|
|
||||||
.withdraw_item => |item| {
|
|
||||||
const code = api.store.getCode(item.id) orelse return error.ItemNotFound;
|
|
||||||
log.debug("[{s}] withdraw {s} (x{})", .{name, code, item.quantity});
|
|
||||||
return .{
|
|
||||||
.withdraw_item = api.actionBankWithdrawItem(name, code, item.quantity)
|
|
||||||
};
|
|
||||||
},
|
|
||||||
.gather => {
|
|
||||||
log.debug("[{s}] gather", .{name});
|
|
||||||
return .{
|
|
||||||
.gather = api.actionGather(name)
|
|
||||||
};
|
|
||||||
},
|
|
||||||
.craft_item => |item| {
|
|
||||||
const code = api.store.getCode(item.id) orelse return error.ItemNotFound;
|
|
||||||
log.debug("[{s}] craft {s} (x{})", .{name, code, item.quantity});
|
|
||||||
return .{
|
|
||||||
.craft_item = api.actionCraft(name, code, item.quantity)
|
|
||||||
};
|
|
||||||
},
|
|
||||||
.accept_task => {
|
|
||||||
log.debug("[{s}] accept task", .{name});
|
|
||||||
return .{
|
|
||||||
.accept_task = api.acceptTask(name)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const ErrorResponse = enum {
|
|
||||||
/// Something went wrong, and you probably can't reasonbly recover from it. Bail, bail!
|
|
||||||
abort,
|
|
||||||
|
|
||||||
/// You probably were trying to an action a bit too early, just try again a bit later.
|
|
||||||
retry,
|
|
||||||
|
|
||||||
/// Something in your logic went wrong, re-evaluate your state and do something different.
|
|
||||||
restart,
|
|
||||||
|
|
||||||
/// The error can be safe ignored, continue doing the next action that you wanted.
|
|
||||||
ignore
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const ActionResult = union(enum) {
|
|
||||||
move: Api.MoveError!Server.MoveResult,
|
|
||||||
fight: Api.FightError!Server.FightResult,
|
|
||||||
gather: Api.GatherError!Server.GatherResult,
|
|
||||||
deposit_gold: Api.BankDepositGoldError!Server.GoldTransactionResult,
|
|
||||||
deposit_item: Api.BankDepositItemError!Server.ItemTransactionResult,
|
|
||||||
withdraw_item: Api.BankWithdrawItemError!Server.ItemTransactionResult,
|
|
||||||
craft_item: Api.CraftError!Server.CraftResult,
|
|
||||||
accept_task: Api.AcceptTaskError!Server.AcceptTaskResult,
|
|
||||||
|
|
||||||
const AnyError = Server.MoveError;
|
|
||||||
|
|
||||||
const Tag = @typeInfo(ActionResult).Union.tag_type.?;
|
|
||||||
|
|
||||||
fn fieldType(comptime kind: Tag) type {
|
|
||||||
const field_type = std.meta.fields(ActionResult)[@intFromEnum(kind)].type;
|
|
||||||
return @typeInfo(field_type).ErrorUnion.payload;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get(self: ActionResult, comptime kind: Tag) ?fieldType(kind) {
|
|
||||||
return switch (self) {
|
|
||||||
kind => |v| v catch null,
|
|
||||||
else => null
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn getError(self: ActionResult) !void {
|
|
||||||
switch (self) {
|
|
||||||
.fight => |result| {
|
|
||||||
_ = try result;
|
|
||||||
},
|
|
||||||
.move => |result| {
|
|
||||||
_ = try result;
|
|
||||||
},
|
|
||||||
.deposit_gold => |result| {
|
|
||||||
_ = try result;
|
|
||||||
},
|
|
||||||
.deposit_item => |result| {
|
|
||||||
_ = try result;
|
|
||||||
},
|
|
||||||
.withdraw_item => |result| {
|
|
||||||
_ = try result;
|
|
||||||
},
|
|
||||||
.gather => |result| {
|
|
||||||
_ = try result;
|
|
||||||
},
|
|
||||||
.craft_item => |result| {
|
|
||||||
_ = try result;
|
|
||||||
},
|
|
||||||
.accept_task => |result| {
|
|
||||||
_ = try result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn getErrorResponse(self: ActionResult) ?ErrorResponse {
|
|
||||||
self.getError() catch |err| switch (err) {
|
|
||||||
error.CharacterIsBusy,
|
|
||||||
error.CharacterInCooldown,
|
|
||||||
error.BankIsBusy => return ErrorResponse.retry,
|
|
||||||
|
|
||||||
error.CharacterAtDestination => return ErrorResponse.ignore,
|
|
||||||
|
|
||||||
error.MapNotFound,
|
|
||||||
error.CharacterIsFull,
|
|
||||||
error.MonsterNotFound,
|
|
||||||
error.NotEnoughSkill,
|
|
||||||
error.ResourceNotFound,
|
|
||||||
error.NotEnoughGold,
|
|
||||||
error.BankNotFound,
|
|
||||||
error.ItemNotFound,
|
|
||||||
error.NotEnoughItems,
|
|
||||||
error.RecipeNotFound,
|
|
||||||
error.AlreadyHasTask,
|
|
||||||
error.TaskMasterNotFound,
|
|
||||||
error.WorkshopNotFound => return ErrorResponse.restart,
|
|
||||||
|
|
||||||
error.CharacterNotFound,
|
|
||||||
error.ServerUnavailable,
|
|
||||||
error.RequestFailed,
|
|
||||||
error.ParseFailed,
|
|
||||||
error.OutOfMemory => return ErrorResponse.abort
|
|
||||||
};
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
comptime {
|
|
||||||
const ActionTag = @typeInfo(Action).Union.tag_type.?;
|
|
||||||
const ResultTag = @typeInfo(ActionResult).Union.tag_type.?;
|
|
||||||
|
|
||||||
assert(std.meta.fields(ActionTag).len == std.meta.fields(ResultTag).len);
|
|
||||||
}
|
|
||||||
104
lib/brain.zig
104
lib/brain.zig
@ -1,104 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const Api = @import("artifacts-api");
|
|
||||||
const Server = Api.Server;
|
|
||||||
const Position = Api.Position;
|
|
||||||
const Allocator = std.mem.Allocator;
|
|
||||||
const assert = std.debug.assert;
|
|
||||||
|
|
||||||
const CharacterTask = @import("./task.zig").Task;
|
|
||||||
const QueuedAction = @import("./action.zig").Action;
|
|
||||||
const QueuedActionResult = @import("./action.zig").ActionResult;
|
|
||||||
|
|
||||||
const Brain = @This();
|
|
||||||
|
|
||||||
name: []const u8,
|
|
||||||
action_queue: std.ArrayList(QueuedAction),
|
|
||||||
task: ?CharacterTask = null,
|
|
||||||
paused_until: ?i64 = null, // ms
|
|
||||||
|
|
||||||
pub fn init(allocator: Allocator, name: []const u8) !Brain {
|
|
||||||
return Brain{
|
|
||||||
.name = try allocator.dupe(u8, name),
|
|
||||||
.action_queue = std.ArrayList(QueuedAction).init(allocator),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: Brain) void {
|
|
||||||
const allocator = self.action_queue.allocator;
|
|
||||||
allocator.free(self.name);
|
|
||||||
self.action_queue.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn performNextAction(self: *Brain, api: *Server) !void {
|
|
||||||
const log = std.log.default;
|
|
||||||
assert(self.action_queue.items.len > 0);
|
|
||||||
|
|
||||||
const retry_delay = 500; // 500ms
|
|
||||||
|
|
||||||
const next_action = self.action_queue.items[0];
|
|
||||||
const action_result = try next_action.perform(api, self.name);
|
|
||||||
|
|
||||||
if (action_result.getErrorResponse()) |error_response| {
|
|
||||||
switch (error_response) {
|
|
||||||
.retry => {
|
|
||||||
self.paused_until = std.time.milliTimestamp() + retry_delay;
|
|
||||||
log.warn("[{s}] retry action", .{self.name});
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
.restart => {
|
|
||||||
log.warn("[{s}] clear action queue", .{self.name});
|
|
||||||
self.action_queue.clearAndFree();
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
.abort => {
|
|
||||||
log.warn("[{s}] abort action {s}", .{ self.name, @tagName(next_action) });
|
|
||||||
try action_result.getError();
|
|
||||||
|
|
||||||
// The error above should always return
|
|
||||||
unreachable;
|
|
||||||
},
|
|
||||||
.ignore => { },
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = self.action_queue.orderedRemove(0);
|
|
||||||
|
|
||||||
if (self.task) |*task| {
|
|
||||||
task.onActionCompleted(action_result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn step(self: *Brain, api: *Api.Server) !void {
|
|
||||||
if (self.paused_until) |paused_until| {
|
|
||||||
if (std.time.milliTimestamp() < paused_until) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
self.paused_until = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self.action_queue.items.len > 0) return;
|
|
||||||
|
|
||||||
if (self.task) |task| {
|
|
||||||
if (task.isComplete()) {
|
|
||||||
// if (try brain.depositItemsToBank(&self.server)) {
|
|
||||||
// continue;
|
|
||||||
// }
|
|
||||||
self.task = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self.task) |task| {
|
|
||||||
try task.queueActions(api, self.name, &self.action_queue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn cooldown(self: *Brain, api: *Server) i64 {
|
|
||||||
const character = api.store.getCharacter(self.name).?;
|
|
||||||
const cooldown_expiration: i64 = @intFromFloat(character.cooldown_expiration * std.time.ms_per_s);
|
|
||||||
|
|
||||||
if (self.paused_until) |pause_until| {
|
|
||||||
return @max(cooldown_expiration, pause_until);
|
|
||||||
} else {
|
|
||||||
return cooldown_expiration;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
67
lib/craft_goal.zig
Normal file
67
lib/craft_goal.zig
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
// zig fmt: off
|
||||||
|
const Api = @import("artifacts-api");
|
||||||
|
const Artificer = @import("./root.zig");
|
||||||
|
|
||||||
|
const Goal = @This();
|
||||||
|
|
||||||
|
item: Api.Store.Id,
|
||||||
|
quantity: u64,
|
||||||
|
|
||||||
|
pub fn tick(self: *Goal, goal_id: Artificer.GoalId, artificer: *Artificer) !void {
|
||||||
|
const store = artificer.server.store;
|
||||||
|
const character = store.characters.get(artificer.character).?;
|
||||||
|
|
||||||
|
if (self.quantity == 0) {
|
||||||
|
artificer.removeGoal(goal_id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const item = store.items.get(self.item).?;
|
||||||
|
const craft = item.craft.?;
|
||||||
|
|
||||||
|
const skill = craft.skill.toCharacterSkill();
|
||||||
|
if (character.skills.get(skill).level < craft.level) {
|
||||||
|
return error.SkillTooLow;
|
||||||
|
}
|
||||||
|
|
||||||
|
const craft_multiples: u64 = @intFromFloat(@ceil(
|
||||||
|
@as(f32, @floatFromInt(self.quantity)) /
|
||||||
|
@as(f32, @floatFromInt(craft.quantity))
|
||||||
|
));
|
||||||
|
|
||||||
|
for (craft.items.items.slice()) |craft_item| {
|
||||||
|
const inventory_item_quantity = character.inventory.getQuantity(craft_item.id);
|
||||||
|
if (inventory_item_quantity < craft_item.quantity * craft_multiples) {
|
||||||
|
return error.NotEnoughItems;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const workshop_position = artificer.findNearestWorkstation(craft.skill).?;
|
||||||
|
if (!workshop_position.eql(character.position)) {
|
||||||
|
artificer.queued_actions.appendAssumeCapacity(.{
|
||||||
|
.goal = goal_id,
|
||||||
|
.action = .{ .move = workshop_position }
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
artificer.queued_actions.appendAssumeCapacity(.{
|
||||||
|
.goal = goal_id,
|
||||||
|
.action = .{ .craft = .{ .item = self.item, .quantity = self.quantity } }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn onActionCompleted(self: *Goal, goal_id: Artificer.GoalId, result: Artificer.ActionResult) void {
|
||||||
|
_ = goal_id;
|
||||||
|
|
||||||
|
if (result == .craft) {
|
||||||
|
const craft_result = result.craft;
|
||||||
|
const craft_quantity = craft_result.details.items.getQuantity(self.item);
|
||||||
|
|
||||||
|
if (self.quantity > craft_quantity) {
|
||||||
|
self.quantity -= craft_quantity;
|
||||||
|
} else {
|
||||||
|
self.quantity = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
51
lib/gather_goal.zig
Normal file
51
lib/gather_goal.zig
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
// zig fmt: off
|
||||||
|
const Api = @import("artifacts-api");
|
||||||
|
const Artificer = @import("./root.zig");
|
||||||
|
|
||||||
|
const Goal = @This();
|
||||||
|
|
||||||
|
item: Api.Store.Id,
|
||||||
|
quantity: u64,
|
||||||
|
|
||||||
|
pub fn tick(self: *Goal, goal_id: Artificer.GoalId, artificer: *Artificer) void {
|
||||||
|
const store = artificer.server.store;
|
||||||
|
const character = store.characters.get(artificer.character).?;
|
||||||
|
|
||||||
|
if (self.quantity == 0) {
|
||||||
|
artificer.removeGoal(goal_id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const resource_id = artificer.findBestResourceWithItem(self.item) orelse @panic("Failed to find resource with item");
|
||||||
|
|
||||||
|
const map_position: Api.Position = artificer.findNearestMapWithResource(resource_id) orelse @panic("Map with resource not found");
|
||||||
|
|
||||||
|
if (!map_position.eql(character.position)) {
|
||||||
|
artificer.queued_actions.appendAssumeCapacity(.{
|
||||||
|
.goal = goal_id,
|
||||||
|
.action = .{ .move = map_position }
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
artificer.queued_actions.appendAssumeCapacity(.{
|
||||||
|
.goal = goal_id,
|
||||||
|
.action = .{ .gather = {} }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn onActionCompleted(self: *Goal, goal_id: Artificer.GoalId, result: Artificer.ActionResult) void {
|
||||||
|
_ = goal_id;
|
||||||
|
|
||||||
|
if (result == .gather) {
|
||||||
|
const gather_result = result.gather;
|
||||||
|
const gather_quantity = gather_result.details.items.getQuantity(self.item);
|
||||||
|
|
||||||
|
if (self.quantity > gather_quantity) {
|
||||||
|
self.quantity -= gather_quantity;
|
||||||
|
} else {
|
||||||
|
self.quantity = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
414
lib/root.zig
414
lib/root.zig
@ -3,134 +3,326 @@ const std = @import("std");
|
|||||||
const Api = @import("artifacts-api");
|
const Api = @import("artifacts-api");
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
pub const Brain = @import("./brain.zig");
|
const GatherGoal = @import("gather_goal.zig");
|
||||||
pub const TaskGraph = @import("./task_graph.zig");
|
const CraftGoal = @import("craft_goal.zig");
|
||||||
|
|
||||||
|
const assert = std.debug.assert;
|
||||||
|
const log = std.log.scoped(.artificer);
|
||||||
|
|
||||||
const Artificer = @This();
|
const Artificer = @This();
|
||||||
|
|
||||||
|
pub const GoalId = packed struct {
|
||||||
|
const Generation = u5;
|
||||||
|
const Index = u11;
|
||||||
|
|
||||||
|
generation: Generation,
|
||||||
|
index: Index
|
||||||
|
};
|
||||||
|
|
||||||
|
const max_goals = std.math.maxInt(GoalId.Index);
|
||||||
|
|
||||||
const expiration_margin: u64 = 100 * std.time.ns_per_ms; // 100ms
|
const expiration_margin: u64 = 100 * std.time.ns_per_ms; // 100ms
|
||||||
const server_down_retry_interval = 5; // minutes
|
const server_down_retry_interval = 5; // minutes
|
||||||
|
|
||||||
|
pub const Goal = union(enum) {
|
||||||
|
gather: GatherGoal,
|
||||||
|
craft: CraftGoal,
|
||||||
|
|
||||||
|
pub fn tick(self: *Goal, goal_id: Artificer.GoalId, artificer: *Artificer) !void {
|
||||||
|
switch (self.*) {
|
||||||
|
.gather => |*gather| gather.tick(goal_id, artificer),
|
||||||
|
.craft => |*craft| try craft.tick(goal_id, artificer),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn onActionCompleted(self: *Goal, goal_id: Artificer.GoalId, result: Artificer.ActionResult) void {
|
||||||
|
switch (self.*) {
|
||||||
|
.gather => |*gather| gather.onActionCompleted(goal_id, result),
|
||||||
|
.craft => |*craft| craft.onActionCompleted(goal_id, result),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const GoalSlot = struct {
|
||||||
|
generation: GoalId.Generation = 0,
|
||||||
|
goal: ?Goal = null,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Action = union(enum) {
|
||||||
|
move: Api.Position,
|
||||||
|
gather,
|
||||||
|
craft: struct {
|
||||||
|
item: Api.Store.Id,
|
||||||
|
quantity: u64
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const ActionResult = union(enum) {
|
||||||
|
move: Api.MoveResult,
|
||||||
|
gather: Api.GatherResult,
|
||||||
|
craft: Api.CraftResult,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ActionSlot = struct {
|
||||||
|
goal: GoalId,
|
||||||
|
action: Action,
|
||||||
|
};
|
||||||
|
|
||||||
|
const QueuedActions = std.ArrayListUnmanaged(ActionSlot);
|
||||||
|
|
||||||
server: *Api.Server,
|
server: *Api.Server,
|
||||||
// characters: std.ArrayList(Brain),
|
character: Api.Store.Id,
|
||||||
// task_graph: TaskGraph,
|
goal_slots: []GoalSlot,
|
||||||
|
queued_actions: QueuedActions,
|
||||||
|
|
||||||
// paused_until: ?i64 = null, // ms
|
pub fn init(allocator: Allocator, server: *Api.Server, character: Api.Store.Id) !Artificer {
|
||||||
|
const max_queued_actions = 16;
|
||||||
|
|
||||||
pub fn init(allocator: Allocator, server: *Api.Server) !Artificer {
|
const goal_slots = try allocator.alloc(GoalSlot, max_goals);
|
||||||
// var characters = std.ArrayList(Brain).init(allocator);
|
errdefer allocator.free(goal_slots);
|
||||||
// errdefer characters.deinit(); // TODO: Add character deinit
|
@memset(goal_slots, .{});
|
||||||
//
|
|
||||||
// const chars = try server.listMyCharacters();
|
var queued_actions = try QueuedActions.initCapacity(allocator, max_queued_actions);
|
||||||
// defer chars.deinit();
|
errdefer queued_actions.deinit(allocator);
|
||||||
//
|
|
||||||
// for (chars.items) |char| {
|
|
||||||
// try characters.append(try Brain.init(allocator, char.name));
|
|
||||||
// }
|
|
||||||
|
|
||||||
_ = allocator;
|
|
||||||
return Artificer{
|
return Artificer{
|
||||||
.server = server,
|
.server = server,
|
||||||
// .characters = characters,
|
.goal_slots = goal_slots,
|
||||||
// .task_graph = TaskGraph.init(allocator),
|
.character = character,
|
||||||
|
.queued_actions = queued_actions
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *Artificer) void {
|
pub fn deinit(self: *Artificer, allocator: Allocator) void {
|
||||||
_ = self;
|
allocator.free(self.goal_slots);
|
||||||
// for (self.characters.items) |brain| {
|
self.queued_actions.deinit(allocator);
|
||||||
// brain.deinit();
|
}
|
||||||
// }
|
|
||||||
// self.characters.deinit();
|
pub fn appendGoal(self: *Artificer, goal: Goal) !GoalId {
|
||||||
// self.server.deinit();
|
for (0.., self.goal_slots) |index, *goal_slot| {
|
||||||
|
if (goal_slot.goal != null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (goal_slot.generation == std.math.maxInt(GoalId.Generation)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
goal_slot.goal = goal;
|
||||||
|
return GoalId{
|
||||||
|
.index = @intCast(index),
|
||||||
|
.generation = goal_slot.generation
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return error.OutOfMemory;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn removeGoal(self: *Artificer, id: GoalId) void {
|
||||||
|
if (self.getGoal(id)) |goal_slot| {
|
||||||
|
goal_slot.* = .{
|
||||||
|
.generation = goal_slot.generation + 1
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getGoal(self: *Artificer, id: GoalId) ?*GoalSlot {
|
||||||
|
const slot = &self.goal_slots[id.index];
|
||||||
|
|
||||||
|
if (slot.generation != id.generation) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (slot.goal == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return slot;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn findBestResourceWithItem(self: *Artificer, item: Api.Store.Id) ?Api.Store.Id {
|
||||||
|
const store = self.server.store;
|
||||||
|
const character = store.characters.get(self.character).?;
|
||||||
|
|
||||||
|
var best_resource: ?Api.Store.Id = null;
|
||||||
|
var best_rate: u64 = 0;
|
||||||
|
|
||||||
|
for (0.., store.resources.objects.items) |resource_id, optional_resource| {
|
||||||
|
if (optional_resource != .object) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const resource = optional_resource.object;
|
||||||
|
|
||||||
|
const skill = resource.skill.toCharacterSkill();
|
||||||
|
const character_skill_level = character.skills.get(skill).level;
|
||||||
|
if (character_skill_level < resource.level) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (resource.drops.slice()) |_drop| {
|
||||||
|
const drop: Api.Resource.Drop = _drop;
|
||||||
|
if (drop.item != item) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The lower the `drop.rate` the better
|
||||||
|
if (best_resource == null or best_rate > drop.rate) {
|
||||||
|
best_resource = resource_id;
|
||||||
|
best_rate = drop.rate;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return best_resource;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn findNearestMapWithResource(self: *Artificer, resource: Api.Store.Id) ?Api.Position {
|
||||||
|
const store = self.server.store;
|
||||||
|
const character = store.characters.get(self.character).?;
|
||||||
|
const resource_code = store.resources.get(resource).?.code.slice();
|
||||||
|
|
||||||
|
var nearest_position: ?Api.Position = null;
|
||||||
|
for (store.maps.items) |map| {
|
||||||
|
const content = map.content orelse continue;
|
||||||
|
|
||||||
|
if (content.type != .resource) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!std.mem.eql(u8, resource_code, content.code.slice())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nearest_position == null or map.position.distance(character.position) < map.position.distance(nearest_position.?)) {
|
||||||
|
nearest_position = map.position;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nearest_position;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn findNearestWorkstation(self: *Artificer, skill: Api.Craft.Skill) ?Api.Position {
|
||||||
|
const store = self.server.store;
|
||||||
|
const character = store.characters.get(self.character).?;
|
||||||
|
const skill_name = skill.toString();
|
||||||
|
|
||||||
|
var nearest_position: ?Api.Position = null;
|
||||||
|
for (store.maps.items) |map| {
|
||||||
|
const content = map.content orelse continue;
|
||||||
|
|
||||||
|
if (content.type != .workshop) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!std.mem.eql(u8, skill_name, content.code.slice())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nearest_position == null or map.position.distance(character.position) < map.position.distance(nearest_position.?)) {
|
||||||
|
nearest_position = map.position;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nearest_position;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn timeUntilCooldownExpires(self: *Artificer) u64 {
|
||||||
|
const store = self.server.store;
|
||||||
|
|
||||||
|
const character = store.characters.get(self.character).?;
|
||||||
|
if (character.cooldown_expiration) |cooldown_expiration| {
|
||||||
|
const cooldown_expiration_ns: i64 = @intFromFloat(cooldown_expiration * std.time.ns_per_s);
|
||||||
|
const now = std.time.nanoTimestamp();
|
||||||
|
if (cooldown_expiration_ns > now) {
|
||||||
|
return @intCast(@as(i128, @intCast(cooldown_expiration_ns)) - now);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn getGoalCount(self: *Artificer) u32 {
|
||||||
|
var count: u32 = 0;
|
||||||
|
|
||||||
|
for (self.goal_slots) |goal_slot| {
|
||||||
|
if (goal_slot.goal == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tick(self: *Artificer) !void {
|
pub fn tick(self: *Artificer) !void {
|
||||||
_ = self;
|
const store = self.server.store;
|
||||||
|
|
||||||
|
if (self.queued_actions.items.len > 0) {
|
||||||
|
const expires_in = self.timeUntilCooldownExpires();
|
||||||
|
if (expires_in > 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const character = store.characters.get(self.character).?;
|
||||||
|
const action_slot = self.queued_actions.orderedRemove(0);
|
||||||
|
const action_result = switch (action_slot.action) {
|
||||||
|
.move => |position| ActionResult{
|
||||||
|
.move = try self.server.move(character.name.slice(), position)
|
||||||
|
},
|
||||||
|
.gather => ActionResult{
|
||||||
|
.gather = try self.server.gather(character.name.slice())
|
||||||
|
},
|
||||||
|
.craft => |craft| ActionResult{
|
||||||
|
.craft = try self.server.craft(character.name.slice(), store.items.getName(craft.item).?, craft.quantity)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (self.getGoal(action_slot.goal)) |goal_slot| {
|
||||||
|
const goal = &goal_slot.goal.?;
|
||||||
|
goal.onActionCompleted(action_slot.goal, action_result);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
for (0.., self.goal_slots) |index, *goal_slot| {
|
||||||
|
if (goal_slot.goal == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const goal = &(goal_slot.*.goal orelse continue);
|
||||||
|
|
||||||
|
const goal_id = GoalId{
|
||||||
|
.index = @intCast(index),
|
||||||
|
.generation = goal_slot.generation
|
||||||
|
};
|
||||||
|
|
||||||
|
try goal.tick(goal_id, self);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// pub fn step(self: *Artificer) !void {
|
pub fn runUntilGoalsComplete(self: *Artificer) !void {
|
||||||
// if (self.paused_until) |paused_until| {
|
while (self.getGoalCount() > 0) {
|
||||||
// if (std.time.milliTimestamp() < paused_until) {
|
const expires_in = self.timeUntilCooldownExpires();
|
||||||
// return;
|
if (expires_in > 0) {
|
||||||
// }
|
log.debug("Sleeping for {d:.3}s", .{ @as(f64, @floatFromInt(expires_in)) / std.time.ns_per_s });
|
||||||
// self.paused_until = null;
|
std.time.sleep(expires_in);
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
// runNextActions(self.characters.items, &self.server) catch |err| switch (err) {
|
try self.tick();
|
||||||
// Api.FetchError.ServerUnavailable => {
|
}
|
||||||
// self.paused_until = std.time.milliTimestamp() + std.time.ms_per_min * server_down_retry_interval;
|
}
|
||||||
// std.log.warn("Server is down, retrying in {}min", .{server_down_retry_interval});
|
|
||||||
// return;
|
pub fn runForever(self: *Artificer) !void {
|
||||||
// },
|
while (true) {
|
||||||
// else => return err,
|
const expires_in = self.timeUntilCooldownExpires();
|
||||||
// };
|
if (expires_in > 0) {
|
||||||
//
|
log.debug("Sleeping for {d:.3}s", .{ @as(f64, @floatFromInt(expires_in)) / std.time.ns_per_s });
|
||||||
// for (self.characters.items) |*brain| {
|
std.time.sleep(expires_in);
|
||||||
// if (brain.task != null) {
|
log.debug("Finished sleeping", .{});
|
||||||
// try brain.step(&self.server);
|
}
|
||||||
// continue;
|
|
||||||
// }
|
try self.tick();
|
||||||
//
|
}
|
||||||
// const character = self.server.store.getCharacter(brain.name).?;
|
}
|
||||||
// if (character.task) |taskmaster_task| {
|
|
||||||
// if (taskmaster_task.total > taskmaster_task.progress) {
|
|
||||||
// switch (taskmaster_task.type) {
|
|
||||||
// .monsters => {
|
|
||||||
// const monster_code = self.server.store.getCode(taskmaster_task.target_id).?;
|
|
||||||
//
|
|
||||||
// const maps = try self.server.getMaps(.{ .code = monster_code });
|
|
||||||
// defer maps.deinit();
|
|
||||||
//
|
|
||||||
// if (maps.items.len > 0) {
|
|
||||||
// const resource_map: Api.Map = maps.items[0];
|
|
||||||
// std.debug.print("fight at {}\n", .{resource_map.position});
|
|
||||||
//
|
|
||||||
// brain.task = .{ .fight = .{
|
|
||||||
// .at = resource_map.position,
|
|
||||||
// .until = .{ .quantity = taskmaster_task.total - taskmaster_task.progress },
|
|
||||||
// } };
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
// .crafts => {},
|
|
||||||
// .resources => {},
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// brain.task = .{ .accept_task = .{} };
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// pub fn nextStepAt(self: *Artificer) i64 {
|
|
||||||
// if (self.paused_until) |paused_until| {
|
|
||||||
// return paused_until;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// return earliestCooldown(self.characters.items, &self.server) orelse 0;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// fn earliestCooldown(characters: []Brain, api: *Api.Server) ?i64 {
|
|
||||||
// var earliest_cooldown: ?i64 = null;
|
|
||||||
// for (characters) |*brain| {
|
|
||||||
// if (brain.action_queue.items.len == 0) continue;
|
|
||||||
//
|
|
||||||
// const cooldown = brain.cooldown(api);
|
|
||||||
// if (earliest_cooldown == null or earliest_cooldown.? > cooldown) {
|
|
||||||
// earliest_cooldown = cooldown;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// return earliest_cooldown;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// fn runNextActions(characters: []Brain, api: *Api.Server) !void {
|
|
||||||
// for (characters) |*brain| {
|
|
||||||
// if (brain.action_queue.items.len == 0) continue;
|
|
||||||
//
|
|
||||||
// const cooldown = brain.cooldown(api);
|
|
||||||
// if (std.time.milliTimestamp() >= cooldown) {
|
|
||||||
// try brain.performNextAction(api);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|||||||
302
lib/task.zig
302
lib/task.zig
@ -1,302 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const Api = @import("artifacts-api");
|
|
||||||
const Position = Api.Position;
|
|
||||||
|
|
||||||
const Action = @import("./action.zig").Action;
|
|
||||||
const ActionResult = @import("./action.zig").ActionResult;
|
|
||||||
const CodeId = Api.CodeId;
|
|
||||||
const ItemQuantity = Api.ItemQuantity;
|
|
||||||
|
|
||||||
const bank_position = Position{ .x = 4, .y = 1 }; // TODO: Figure this out dynamically
|
|
||||||
|
|
||||||
const task_master_position = Position{ .x = 1, .y = 2 }; // TODO: Figure this out dynamically
|
|
||||||
|
|
||||||
pub const UntilCondition = union(enum) {
|
|
||||||
xp: u64,
|
|
||||||
item: Api.ItemQuantity,
|
|
||||||
quantity: u64,
|
|
||||||
|
|
||||||
fn isComplete(self: UntilCondition, progress: u64) bool {
|
|
||||||
return switch (self) {
|
|
||||||
.xp => |xp| progress >= xp,
|
|
||||||
.item => |item| progress >= item.quantity,
|
|
||||||
.quantity => |quantity| progress >= quantity,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Task = union(enum) {
|
|
||||||
fight: struct {
|
|
||||||
at: Position,
|
|
||||||
until: UntilCondition,
|
|
||||||
progress: u64 = 0,
|
|
||||||
},
|
|
||||||
gather: struct {
|
|
||||||
at: Position,
|
|
||||||
until: UntilCondition,
|
|
||||||
progress: u64 = 0,
|
|
||||||
},
|
|
||||||
craft: struct {
|
|
||||||
at: Position,
|
|
||||||
target: Api.ItemQuantity,
|
|
||||||
progress: u64 = 0,
|
|
||||||
},
|
|
||||||
accept_task: struct {
|
|
||||||
done: bool = false
|
|
||||||
},
|
|
||||||
|
|
||||||
pub fn isComplete(self: Task) bool {
|
|
||||||
return switch (self) {
|
|
||||||
.fight => |args| args.until.isComplete(args.progress),
|
|
||||||
.gather => |args| args.until.isComplete(args.progress),
|
|
||||||
.craft => |args| args.progress >= args.target.quantity,
|
|
||||||
.accept_task => |args| args.done
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn onActionCompleted(self: *Task, result: ActionResult) void {
|
|
||||||
switch (self.*) {
|
|
||||||
.fight => |*args| {
|
|
||||||
if (result.get(.fight)) |r| {
|
|
||||||
const fight_result: Api.Server.FightResult = r;
|
|
||||||
|
|
||||||
switch (args.until) {
|
|
||||||
.xp => {
|
|
||||||
args.progress += fight_result.fight.xp;
|
|
||||||
},
|
|
||||||
.item => {
|
|
||||||
const drops = fight_result.fight.drops;
|
|
||||||
args.progress += drops.getQuantity(args.until.item.id);
|
|
||||||
},
|
|
||||||
.quantity => {
|
|
||||||
args.progress += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
.gather => |*args| {
|
|
||||||
if (result.get(.gather)) |r| {
|
|
||||||
const gather_result: Api.Server.GatherResult = r;
|
|
||||||
|
|
||||||
switch (args.until) {
|
|
||||||
.xp => {
|
|
||||||
args.progress += gather_result.details.xp;
|
|
||||||
},
|
|
||||||
.item => {
|
|
||||||
const items = gather_result.details.items;
|
|
||||||
args.progress += items.getQuantity(args.until.item.id);
|
|
||||||
},
|
|
||||||
.quantity => {
|
|
||||||
args.progress += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
.craft => |*args| {
|
|
||||||
if (result.get(.craft_item)) |r| {
|
|
||||||
const craft_result: Api.Server.CraftResult = r;
|
|
||||||
const items = craft_result.details.items;
|
|
||||||
|
|
||||||
args.progress += items.getQuantity(args.target.id);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
.accept_task => |*args| {
|
|
||||||
if (result.get(.accept_task)) |_| {
|
|
||||||
args.done = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn queueActions(self: Task, api: *Api.Server, name: []const u8, action_queue: *std.ArrayList(Action)) !void {
|
|
||||||
const ctx = TaskContext{
|
|
||||||
.api = api,
|
|
||||||
.name = name,
|
|
||||||
.action_queue = action_queue
|
|
||||||
};
|
|
||||||
|
|
||||||
switch (self) {
|
|
||||||
.fight => |args| {
|
|
||||||
try ctx.fightRoutine(args.at);
|
|
||||||
},
|
|
||||||
.gather => |args| {
|
|
||||||
try ctx.gatherRoutine(args.at);
|
|
||||||
},
|
|
||||||
.craft => |args| {
|
|
||||||
try ctx.craftRoutine(args.at, args.target.id, args.target.quantity);
|
|
||||||
},
|
|
||||||
.accept_task => {
|
|
||||||
if (try ctx.moveIfNeeded(task_master_position)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try ctx.action_queue.append(.{ .accept_task = {} });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const TaskContext = struct {
|
|
||||||
api: *Api.Server,
|
|
||||||
name: []const u8,
|
|
||||||
action_queue: *std.ArrayList(Action),
|
|
||||||
|
|
||||||
fn getCharacter(self: TaskContext) Api.Character {
|
|
||||||
return self.api.store.getCharacter(self.name).?;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn moveIfNeeded(self: TaskContext, pos: Position) !bool {
|
|
||||||
const character = self.getCharacter();
|
|
||||||
|
|
||||||
if (character.position.eql(pos)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
try self.action_queue.append(.{ .move = pos });
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn depositItemsToBank(self: TaskContext) !bool {
|
|
||||||
var character = self.getCharacter();
|
|
||||||
const action_queue = self.action_queue;
|
|
||||||
|
|
||||||
// Deposit items and gold to bank if full
|
|
||||||
if (character.getItemCount() == 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!character.position.eql(bank_position)) {
|
|
||||||
try action_queue.append(.{ .move = bank_position });
|
|
||||||
}
|
|
||||||
|
|
||||||
for (character.inventory.slice()) |slot| {
|
|
||||||
try action_queue.append(.{
|
|
||||||
.deposit_item = .{ .id = slot.id, .quantity = slot.quantity }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn depositIfFull(self: TaskContext) !bool {
|
|
||||||
const character = self.getCharacter();
|
|
||||||
if (character.getItemCount() < character.inventory_max_items) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = try depositItemsToBank(self);
|
|
||||||
|
|
||||||
if (character.gold > 0) {
|
|
||||||
try self.action_queue.append(.{ .deposit_gold = @intCast(character.gold) });
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fightRoutine(self: TaskContext, enemy_position: Position) !void {
|
|
||||||
if (try self.depositIfFull()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (try self.moveIfNeeded(enemy_position)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try self.action_queue.append(.{ .fight = {} });
|
|
||||||
}
|
|
||||||
|
|
||||||
fn gatherRoutine(self: TaskContext, resource_position: Position) !void {
|
|
||||||
if (try self.depositIfFull()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (try self.moveIfNeeded(resource_position)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try self.action_queue.append(.{ .gather = {} });
|
|
||||||
}
|
|
||||||
|
|
||||||
fn withdrawFromBank(self: TaskContext, items: []const ItemQuantity) !bool {
|
|
||||||
const character = self.getCharacter();
|
|
||||||
|
|
||||||
var has_all_items = true;
|
|
||||||
for (items) |item_quantity| {
|
|
||||||
const inventory_quantity = character.inventory.getQuantity(item_quantity.id);
|
|
||||||
if(inventory_quantity < item_quantity.quantity) {
|
|
||||||
has_all_items = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (has_all_items) return false;
|
|
||||||
|
|
||||||
if (try self.moveIfNeeded(bank_position)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (items) |item_quantity| {
|
|
||||||
const inventory_quantity = character.inventory.getQuantity(item_quantity.id);
|
|
||||||
if(inventory_quantity < item_quantity.quantity) {
|
|
||||||
try self.action_queue.append(.{ .withdraw_item = .{
|
|
||||||
.id = item_quantity.id,
|
|
||||||
.quantity = item_quantity.quantity - inventory_quantity,
|
|
||||||
}});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn craftItem(self: TaskContext, workstation: Position, id: CodeId, quantity: u64) !bool {
|
|
||||||
var character = self.getCharacter();
|
|
||||||
|
|
||||||
const inventory_quantity = character.inventory.getQuantity(id);
|
|
||||||
if (inventory_quantity >= quantity) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (try self.moveIfNeeded(workstation)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
try self.action_queue.append(.{ .craft_item = .{
|
|
||||||
.id = id,
|
|
||||||
.quantity = quantity - inventory_quantity
|
|
||||||
}});
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn craftRoutine(self: TaskContext, workstation: Position, id: CodeId, quantity: u64) !void {
|
|
||||||
var character = self.getCharacter();
|
|
||||||
const inventory_quantity = character.inventory.getQuantity(id);
|
|
||||||
if (inventory_quantity >= quantity) {
|
|
||||||
if (try self.depositItemsToBank()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const code = self.api.store.getCode(id) orelse return error.InvalidItemId;
|
|
||||||
const target_item = try self.api.getItem(code) orelse return error.ItemNotFound;
|
|
||||||
if (target_item.craft == null) {
|
|
||||||
return error.NotCraftable;
|
|
||||||
}
|
|
||||||
|
|
||||||
const recipe = target_item.craft.?;
|
|
||||||
|
|
||||||
var needed_items = recipe.items;
|
|
||||||
for (needed_items.slice()) |*needed_item| {
|
|
||||||
needed_item.quantity *= quantity;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (try self.withdrawFromBank(needed_items.slice())) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (try self.craftItem(workstation, id, quantity)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
@ -1,197 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const Api = @import("artifacts-api");
|
|
||||||
const Allocator = std.mem.Allocator;
|
|
||||||
|
|
||||||
const TaskGraph = @This();
|
|
||||||
const CharacterTask = @import("./task.zig").Task;
|
|
||||||
|
|
||||||
const TaskNodeId = u16;
|
|
||||||
const TaskNode = struct {
|
|
||||||
const Dependencies = std.BoundedArray(TaskNodeId, 8);
|
|
||||||
const MissingItems = Api.BoundedSlotsArray(8);
|
|
||||||
|
|
||||||
task: CharacterTask,
|
|
||||||
dependencies: Dependencies = Dependencies.init(0) catch unreachable,
|
|
||||||
missing_items: MissingItems = MissingItems.init(),
|
|
||||||
};
|
|
||||||
|
|
||||||
const Nodes = std.ArrayList(TaskNode);
|
|
||||||
|
|
||||||
nodes: Nodes,
|
|
||||||
|
|
||||||
pub fn init(allocator: Allocator) TaskGraph {
|
|
||||||
return TaskGraph{ .nodes = Nodes.init(allocator) };
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: TaskGraph) void {
|
|
||||||
self.nodes.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get(self: *TaskGraph, id: TaskNodeId) *TaskNode {
|
|
||||||
return &self.nodes.items[id];
|
|
||||||
}
|
|
||||||
|
|
||||||
fn addTask(self: *TaskGraph, node: TaskNode) !TaskNodeId {
|
|
||||||
try self.nodes.append(node);
|
|
||||||
return @intCast(self.nodes.items.len-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn addFightTask(self: *TaskGraph, api: *Api.Server, item_id: Api.CodeId, quantity: u64) !TaskNodeId {
|
|
||||||
const item_code = api.store.getCode(item_id) orelse return error.ItemNotFound;
|
|
||||||
const monsters = try api.getMonsters(.{ .drop = item_code });
|
|
||||||
defer monsters.deinit();
|
|
||||||
|
|
||||||
if (monsters.items.len == 0) return error.ResourceNotFound;
|
|
||||||
if (monsters.items.len > 1) std.log.warn("Multiple monsters exist for target item", .{});
|
|
||||||
const monster_code = monsters.items[0].code;
|
|
||||||
|
|
||||||
const resource_maps = try self.api.getMaps(.{ .code = monster_code });
|
|
||||||
defer resource_maps.deinit();
|
|
||||||
|
|
||||||
// This monster currently doesn't exist on the map. Probably only spawns in certain situations.
|
|
||||||
if (resource_maps.items.len == 0) return error.MapNotFound;
|
|
||||||
|
|
||||||
if (resource_maps.items.len > 1) std.log.warn("Multiple map locations exist for monster", .{});
|
|
||||||
const resource_map = resource_maps.items[0];
|
|
||||||
|
|
||||||
return try self.addTask(TaskNode{
|
|
||||||
.task = .{
|
|
||||||
.fight = .{
|
|
||||||
.at = resource_map.position,
|
|
||||||
.until = .{ .item = Api.ItemQuantity.init(item_id, quantity) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn addGatherTask(self: *TaskGraph, api: *Api.Server, item_id: Api.CodeId, quantity: u64) !TaskNodeId {
|
|
||||||
const item_code = api.store.getCode(item_id) orelse return error.ItemNotFound;
|
|
||||||
const resources = try api.getResources(.{ .drop = item_code });
|
|
||||||
defer resources.deinit();
|
|
||||||
|
|
||||||
if (resources.items.len == 0) return error.ResourceNotFound;
|
|
||||||
if (resources.items.len > 1) std.log.warn("Multiple resources exist for target item", .{});
|
|
||||||
const resource_code = resources.items[0].code;
|
|
||||||
|
|
||||||
const resource_maps = try self.api.getMaps(.{ .code = resource_code });
|
|
||||||
defer resource_maps.deinit();
|
|
||||||
|
|
||||||
if (resource_maps.items.len == 0) return error.MapNotFound;
|
|
||||||
if (resource_maps.items.len > 1) std.log.warn("Multiple map locations exist for resource", .{});
|
|
||||||
const resource_map = resource_maps.items[0];
|
|
||||||
|
|
||||||
return try self.addTask(TaskNode{
|
|
||||||
.task = .{
|
|
||||||
.gather = .{
|
|
||||||
.at = resource_map.position,
|
|
||||||
.until = .{ .item = Api.ItemQuantity.init(item_id, quantity) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn addCraftTaskShallow(self: *TaskGraph, api: *Api.Server, item_id: Api.CodeId, quantity: u64) !TaskNodeId {
|
|
||||||
const item = (try api.getItemById(item_id)) orelse return error.ItemNotFound;
|
|
||||||
const recipe = item.craft orelse return error.RecipeNotFound;
|
|
||||||
|
|
||||||
const skill_str = Api.Server.SkillUtils.toString(recipe.skill);
|
|
||||||
const workshop_maps = try self.api.getMaps(.{ .code = skill_str, .type = .workshop });
|
|
||||||
defer workshop_maps.deinit();
|
|
||||||
|
|
||||||
if (workshop_maps.items.len == 0) return error.WorkshopNotFound;
|
|
||||||
if (workshop_maps.items.len > 1) std.log.warn("Multiple workshop locations exist", .{});
|
|
||||||
|
|
||||||
return try self.addTask(TaskNode{
|
|
||||||
.task = .{
|
|
||||||
.craft = .{
|
|
||||||
.at = workshop_maps.items[0].position,
|
|
||||||
.target = Api.ItemQuantity.init(item_id, quantity)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn addCraftTask(self: *TaskGraph, api: *Api.Server, item_id: Api.CodeId, quantity: u64) !TaskNodeId {
|
|
||||||
const node_id = try self.addCraftTaskShallow(api, item_id, quantity);
|
|
||||||
var node = self.get(node_id);
|
|
||||||
|
|
||||||
const item = (try api.getItemById(item_id)) orelse return error.ItemNotFound;
|
|
||||||
const recipe = item.craft orelse return error.RecipeNotFound;
|
|
||||||
|
|
||||||
const craft_count = recipe.quantity;
|
|
||||||
|
|
||||||
for (recipe.items.slots.constSlice()) |material| {
|
|
||||||
const needed_quantity = material.quantity * craft_count;
|
|
||||||
|
|
||||||
if (try self.addAutoTask(api, material.id, needed_quantity)) |dependency_id| {
|
|
||||||
try node.dependencies.append(dependency_id);
|
|
||||||
} else {
|
|
||||||
try node.missing_items.add(material.id, needed_quantity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return node_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Remove `anyerror` from function declaration
|
|
||||||
fn addAutoTask(self: *TaskGraph, api: *Api.Server, item_id: Api.CodeId, quantity: u64) anyerror!?TaskNodeId {
|
|
||||||
const item = (try self.api.getItemById(item_id)) orelse return error.ItemNotFound;
|
|
||||||
|
|
||||||
if (item.craft != null) {
|
|
||||||
return try self.addCraftTask(api, item_id, quantity);
|
|
||||||
} else if (item.type == .resource) {
|
|
||||||
const eql = std.mem.eql;
|
|
||||||
if (eql(u8, item.subtype, "mining") or eql(u8, item.subtype, "fishing") or eql(u8, item.subtype, "woodcutting")) {
|
|
||||||
return try self.addGatherTask(api, item_id, quantity);
|
|
||||||
} else if (eql(u8, item.subtype, "mob")) {
|
|
||||||
return try self.addFightTask(api, item_id, quantity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn printTask(self: *TaskGraph, api: *Api.Server, node_id: TaskNodeId) void {
|
|
||||||
self.printTaskLevel(api, node_id, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn writeIdentation(level: u32) void {
|
|
||||||
const mutex = std.debug.getStderrMutex();
|
|
||||||
mutex.lock();
|
|
||||||
defer mutex.unlock();
|
|
||||||
|
|
||||||
const stderr = std.io.getStdErr().writer();
|
|
||||||
stderr.writeBytesNTimes(" ", level) catch return;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn printTaskLevel(self: *TaskGraph, api: *Api.Server, node_id: TaskNodeId, level: u32) void {
|
|
||||||
const node = self.get(node_id);
|
|
||||||
const print = std.debug.print;
|
|
||||||
|
|
||||||
writeIdentation(level);
|
|
||||||
switch (node.task) {
|
|
||||||
.fight => |args| {
|
|
||||||
const target_item = args.until.item;
|
|
||||||
const item = api.store.getCode(target_item.id).?;
|
|
||||||
print("Task{{ .fight = {{ \"{s}\" x {} }}, .at = {} }}\n", .{item, target_item.quantity, args.at});
|
|
||||||
},
|
|
||||||
.gather => |args| {
|
|
||||||
const target_item = args.until.item;
|
|
||||||
const item = api.store.getCode(target_item.id).?;
|
|
||||||
print("Task{{ .gather = {{ \"{s}\" x {} }}, .at = {} }}\n", .{item, target_item.quantity, args.at});
|
|
||||||
},
|
|
||||||
.craft => |args| {
|
|
||||||
const item = api.store.getCode(args.target.id).?;
|
|
||||||
print("Task{{ .craft = {{ \"{s}\" x {} }}, .at = {} }}\n", .{item, args.target.quantity, args.at});
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for (node.dependencies.constSlice()) |dependency| {
|
|
||||||
self.printTaskLevel(dependency, level + 1);
|
|
||||||
}
|
|
||||||
for (node.missing_items.slots.constSlice()) |slot| {
|
|
||||||
const item_code = api.getItemCode(slot.id).?;
|
|
||||||
writeIdentation(level+1);
|
|
||||||
print("+ {{ \"{s}\" x {} }}\n", .{item_code, slot.quantity});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue
Block a user