refactor api to reduce memory allocations. Implement limits
This commit is contained in:
parent
5188d778be
commit
bbdf0511b9
@ -1,15 +1,15 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
|
|
||||||
pub fn EnumStringUtils(TargetEnum: anytype, str_to_tag_mapping: anytype) type {
|
// TODO: use `EnumFieldStruct` for `str_to_tag_mapping`
|
||||||
|
pub fn EnumStringUtils(comptime TargetEnum: type, str_to_tag_mapping: anytype) type {
|
||||||
if (str_to_tag_mapping.len != @typeInfo(TargetEnum).Enum.fields.len) {
|
if (str_to_tag_mapping.len != @typeInfo(TargetEnum).Enum.fields.len) {
|
||||||
@compileLog("Mapping is not exhaustive");
|
@compileLog("Mapping is not exhaustive");
|
||||||
}
|
}
|
||||||
|
|
||||||
const EnumMapping = std.StaticStringMap(TargetEnum).initComptime(str_to_tag_mapping);
|
|
||||||
|
|
||||||
return struct {
|
return struct {
|
||||||
pub fn fromString(str: []const u8) ?TargetEnum {
|
pub fn fromString(str: []const u8) ?TargetEnum {
|
||||||
|
const EnumMapping = std.StaticStringMap(TargetEnum).initComptime(str_to_tag_mapping);
|
||||||
return EnumMapping.get(str);
|
return EnumMapping.get(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
216
api/errors.zig
216
api/errors.zig
@ -5,10 +5,7 @@ const ErrorDefinition = struct {
|
|||||||
code: ?u10,
|
code: ?u10,
|
||||||
|
|
||||||
fn init(name: [:0]const u8, code: ?u10) ErrorDefinition {
|
fn init(name: [:0]const u8, code: ?u10) ErrorDefinition {
|
||||||
return ErrorDefinition{
|
return ErrorDefinition{ .name = name, .code = code };
|
||||||
.name = name,
|
|
||||||
.code = code
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -34,38 +31,85 @@ fn ErrorDefinitionList(errors: []const ErrorDefinition) type {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const ServerUnavailable = ErrorDefinition.init("ServerUnavailable", 503);
|
// zig fmt: off
|
||||||
const RequestFailed = ErrorDefinition.init("RequestFailed", null);
|
|
||||||
const ParseFailed = ErrorDefinition.init("ParseFailed", null);
|
|
||||||
const OutOfMemory = ErrorDefinition.init("OutOfMemory", null);
|
|
||||||
|
|
||||||
const MapNotFound = ErrorDefinition.init("MapNotFound", 404);
|
pub const NotAuthenticated = ErrorDefinition.init("NotAuthenticated", 403);
|
||||||
const ItemNotFound = ErrorDefinition.init("ItemNotFound", 404);
|
pub const ServerUnavailable = ErrorDefinition.init("ServerUnavailable", 503);
|
||||||
const RecipeNotFound = ErrorDefinition.init("RecipeNotFound", 404);
|
pub const RequestFailed = ErrorDefinition.init("RequestFailed", null);
|
||||||
|
pub const ParseFailed = ErrorDefinition.init("ParseFailed", null);
|
||||||
|
pub const OutOfMemory = ErrorDefinition.init("OutOfMemory", null);
|
||||||
|
|
||||||
const BankIsBusy = ErrorDefinition.init("BankIsBusy", 461);
|
// List of error codes gotten from https://docs.artifactsmmo.com/api_guide/response_codes
|
||||||
const NotEnoughItems = ErrorDefinition.init("NotEnoughItems", 478);
|
// General
|
||||||
const SlotIsFull = ErrorDefinition.init("SlotIsFull", 485);
|
pub const InvalidPayload = ErrorDefinition.init("InvalidPayload", 422);
|
||||||
const CharacterIsBusy = ErrorDefinition.init("CharacterIsBusy", 486);
|
pub const TooManyRequests = ErrorDefinition.init("TooManyRequests", 429);
|
||||||
const AlreadyHasTask = ErrorDefinition.init("AlreadyHasTask", 486);
|
pub const NotFound = ErrorDefinition.init("NotFound", 404);
|
||||||
const HasNoTask = ErrorDefinition.init("HasNoTask", 487);
|
pub const FatalError = ErrorDefinition.init("FatalError", 500);
|
||||||
const TaskNotCompleted = ErrorDefinition.init("TaskNotCompleted", 488);
|
|
||||||
|
|
||||||
const CharacterAtDestination = ErrorDefinition.init("CharacterAtDestination", 490);
|
// Account Error Codes
|
||||||
const SlotIsEmpty = ErrorDefinition.init("SlotIsEmpty", 491);
|
const TokenInvalid = ErrorDefinition.init("TokenInvalid", 452);
|
||||||
const NotEnoughGold = ErrorDefinition.init("NotEnoughGold", 492);
|
const TokenExpired = ErrorDefinition.init("TokenExpired", 453);
|
||||||
const NotEnoughSkill = ErrorDefinition.init("NotEnoughSkill", 493);
|
const TokenMissing = ErrorDefinition.init("TokenMissing", 454);
|
||||||
const CharacterIsFull = ErrorDefinition.init("CharacterIsFull", 497);
|
const TokenGenerationFail = ErrorDefinition.init("TokenGenerationFail", 455);
|
||||||
const CharacterNotFound = ErrorDefinition.init("CharacterNotFound", 498);
|
const UsernameAlreadyUsed = ErrorDefinition.init("UsernameAlreadyUsed", 456);
|
||||||
const CharacterInCooldown = ErrorDefinition.init("CharacterInCooldown", 499);
|
const EmailAlreadyUsed = ErrorDefinition.init("EmailAlreadyUsed", 457);
|
||||||
|
const SamePassword = ErrorDefinition.init("SamePassword", 458);
|
||||||
|
const CurrentPasswordInvalid = ErrorDefinition.init("CurrentPasswordInvalid", 459);
|
||||||
|
|
||||||
const BankNotFound = ErrorDefinition.init("BankNotFound", 598);
|
// Character Error Codes
|
||||||
const MonsterNotFound = ErrorDefinition.init("MonsterNotFound", 598);
|
const CharacterNotEnoughHp = ErrorDefinition.init("CharacterNotEnoughHp", 483);
|
||||||
const ResourceNotFound = ErrorDefinition.init("ResourceNotFound", 598);
|
const CharacterMaximumUtilitesEquiped = ErrorDefinition.init("CharacterMaximumUtilitesEquiped", 484);
|
||||||
const WorkshopNotFound = ErrorDefinition.init("WorkshopNotFound", 598);
|
const CharacterItemAlreadyEquiped = ErrorDefinition.init("CharacterItemAlreadyEquiped", 485);
|
||||||
const TaskMasterNotFound = ErrorDefinition.init("TaskMasterNotFound", 598);
|
const CharacterLocked = ErrorDefinition.init("CharacterLocked", 486);
|
||||||
|
const CharacterNotThisTask = ErrorDefinition.init("CharacterNotThisTask", 474);
|
||||||
|
const CharacterTooManyItemsTask = ErrorDefinition.init("CharacterTooManyItemsTask", 475);
|
||||||
|
const CharacterNoTask = ErrorDefinition.init("CharacterNoTask", 487);
|
||||||
|
const CharacterTaskNotCompleted = ErrorDefinition.init("CharacterTaskNotCompleted", 488);
|
||||||
|
const CharacterAlreadyTask = ErrorDefinition.init("CharacterAlreadyTask", 489);
|
||||||
|
const CharacterAlreadyMap = ErrorDefinition.init("CharacterAlreadyMap", 490);
|
||||||
|
const CharacterSlotEquipmentError = ErrorDefinition.init("CharacterSlotEquipmentError", 491);
|
||||||
|
const CharacterGoldInsufficient = ErrorDefinition.init("CharacterGoldInsufficient", 492);
|
||||||
|
const CharacterNotSkillLevelRequired = ErrorDefinition.init("CharacterNotSkillLevelRequired", 493);
|
||||||
|
const CharacterNameAlreadyUsed = ErrorDefinition.init("CharacterNameAlreadyUsed", 494);
|
||||||
|
const MaxCharactersReached = ErrorDefinition.init("MaxCharactersReached", 495);
|
||||||
|
const CharacterNotLevelRequired = ErrorDefinition.init("CharacterNotLevelRequired", 496);
|
||||||
|
const CharacterInventoryFull = ErrorDefinition.init("CharacterInventoryFull", 497);
|
||||||
|
const CharacterNotFound = ErrorDefinition.init("CharacterNotFound", 498);
|
||||||
|
const CharacterInCooldown = ErrorDefinition.init("CharacterInCooldown", 499);
|
||||||
|
|
||||||
|
// Item Error Codes
|
||||||
|
const ItemInsufficientQuantity = ErrorDefinition.init("ItemInsufficientQuantity", 471);
|
||||||
|
const ItemInvalidEquipment = ErrorDefinition.init("ItemInvalidEquipment", 472);
|
||||||
|
const ItemRecyclingInvalidItem = ErrorDefinition.init("ItemRecyclingInvalidItem", 473);
|
||||||
|
const ItemInvalidConsumable = ErrorDefinition.init("ItemInvalidConsumable", 476);
|
||||||
|
const MissingItem = ErrorDefinition.init("MissingItem", 478);
|
||||||
|
|
||||||
|
// Grand Exchange Error Codes
|
||||||
|
const GeMaxQuantity = ErrorDefinition.init("GeMaxQuantity", 479);
|
||||||
|
const GeNotInStock = ErrorDefinition.init("GeNotInStock", 480);
|
||||||
|
const GeNotThePrice = ErrorDefinition.init("GeNotThePrice", 482);
|
||||||
|
const GeTransactionInProgress = ErrorDefinition.init("GeTransactionInProgress", 436);
|
||||||
|
const GeNoOrders = ErrorDefinition.init("GeNoOrders", 431);
|
||||||
|
const GeMaxOrders = ErrorDefinition.init("GeMaxOrders", 433);
|
||||||
|
const GeTooManyItems = ErrorDefinition.init("GeTooManyItems", 434);
|
||||||
|
const GeSameAccount = ErrorDefinition.init("GeSameAccount", 435);
|
||||||
|
const GeInvalidItem = ErrorDefinition.init("GeInvalidItem", 437);
|
||||||
|
const GeNotYourOrder = ErrorDefinition.init("GeNotYourOrder", 438);
|
||||||
|
|
||||||
|
// Bank Error Codes
|
||||||
|
const BankInsufficientGold = ErrorDefinition.init("BankInsufficientGold", 460);
|
||||||
|
const BankTransactionInProgress = ErrorDefinition.init("BankTransactionInProgress", 461);
|
||||||
|
const BankFull = ErrorDefinition.init("BankFull", 462);
|
||||||
|
|
||||||
|
// Maps Error Codes
|
||||||
|
const MapNotFound = ErrorDefinition.init("MapNotFound", 597);
|
||||||
|
const MapContentNotFound = ErrorDefinition.init("MapContentNotFound", 598);
|
||||||
|
|
||||||
pub const FetchError = ErrorDefinitionList(&[_]ErrorDefinition{
|
pub const FetchError = ErrorDefinitionList(&[_]ErrorDefinition{
|
||||||
|
NotAuthenticated,
|
||||||
|
TooManyRequests,
|
||||||
|
FatalError,
|
||||||
|
InvalidPayload,
|
||||||
ServerUnavailable,
|
ServerUnavailable,
|
||||||
RequestFailed,
|
RequestFailed,
|
||||||
ParseFailed,
|
ParseFailed,
|
||||||
@ -74,8 +118,8 @@ pub const FetchError = ErrorDefinitionList(&[_]ErrorDefinition{
|
|||||||
|
|
||||||
const MoveErrorDef = ErrorDefinitionList(&[_]ErrorDefinition{
|
const MoveErrorDef = ErrorDefinitionList(&[_]ErrorDefinition{
|
||||||
MapNotFound,
|
MapNotFound,
|
||||||
CharacterIsBusy,
|
CharacterLocked,
|
||||||
CharacterAtDestination,
|
CharacterAlreadyMap,
|
||||||
CharacterNotFound,
|
CharacterNotFound,
|
||||||
CharacterInCooldown
|
CharacterInCooldown
|
||||||
});
|
});
|
||||||
@ -83,91 +127,91 @@ pub const MoveError = FetchError || MoveErrorDef.ErrorSet;
|
|||||||
pub const parseMoveError = MoveErrorDef.parse;
|
pub const parseMoveError = MoveErrorDef.parse;
|
||||||
|
|
||||||
const FightErrorDef = ErrorDefinitionList(&[_]ErrorDefinition{
|
const FightErrorDef = ErrorDefinitionList(&[_]ErrorDefinition{
|
||||||
CharacterIsBusy,
|
CharacterLocked,
|
||||||
CharacterIsFull,
|
CharacterInventoryFull,
|
||||||
CharacterNotFound,
|
CharacterNotFound,
|
||||||
CharacterInCooldown,
|
CharacterInCooldown,
|
||||||
MonsterNotFound,
|
MapContentNotFound,
|
||||||
});
|
});
|
||||||
pub const FightError = FetchError || FightErrorDef.ErrorSet;
|
pub const FightError = FetchError || FightErrorDef.ErrorSet;
|
||||||
pub const parseFightError = FightErrorDef.parse;
|
pub const parseFightError = FightErrorDef.parse;
|
||||||
|
|
||||||
const GatherErrorDef = ErrorDefinitionList(&[_]ErrorDefinition{
|
const GatherErrorDef = ErrorDefinitionList(&[_]ErrorDefinition{
|
||||||
CharacterIsBusy,
|
CharacterLocked,
|
||||||
NotEnoughSkill,
|
CharacterNotSkillLevelRequired,
|
||||||
CharacterIsFull,
|
CharacterInventoryFull,
|
||||||
CharacterNotFound,
|
CharacterNotFound,
|
||||||
CharacterInCooldown,
|
CharacterInCooldown,
|
||||||
ResourceNotFound
|
MapContentNotFound
|
||||||
});
|
});
|
||||||
pub const GatherError = FetchError || GatherErrorDef.ErrorSet;
|
pub const GatherError = FetchError || GatherErrorDef.ErrorSet;
|
||||||
pub const parseGatherError = GatherErrorDef.parse;
|
pub const parseGatherError = GatherErrorDef.parse;
|
||||||
|
|
||||||
const BankDepositItemErrorDef = ErrorDefinitionList(&[_]ErrorDefinition{
|
const BankDepositItemErrorDef = ErrorDefinitionList(&[_]ErrorDefinition{
|
||||||
ItemNotFound,
|
NotFound,
|
||||||
BankIsBusy,
|
BankTransactionInProgress,
|
||||||
NotEnoughItems,
|
MissingItem,
|
||||||
CharacterIsBusy,
|
CharacterLocked,
|
||||||
CharacterNotFound,
|
CharacterNotFound,
|
||||||
CharacterInCooldown,
|
CharacterInCooldown,
|
||||||
BankNotFound
|
MapContentNotFound
|
||||||
});
|
});
|
||||||
pub const BankDepositItemError = FetchError || BankDepositItemErrorDef.ErrorSet;
|
pub const BankDepositItemError = FetchError || BankDepositItemErrorDef.ErrorSet;
|
||||||
pub const parseBankDepositItemError = BankDepositItemErrorDef.parse;
|
pub const parseBankDepositItemError = BankDepositItemErrorDef.parse;
|
||||||
|
|
||||||
const BankDepositGoldErrorDef = ErrorDefinitionList(&[_]ErrorDefinition{
|
const BankDepositGoldErrorDef = ErrorDefinitionList(&[_]ErrorDefinition{
|
||||||
BankIsBusy,
|
BankTransactionInProgress,
|
||||||
NotEnoughGold,
|
BankInsufficientGold,
|
||||||
CharacterIsBusy,
|
CharacterLocked,
|
||||||
CharacterNotFound,
|
CharacterNotFound,
|
||||||
CharacterInCooldown,
|
CharacterInCooldown,
|
||||||
BankNotFound
|
MapContentNotFound
|
||||||
});
|
});
|
||||||
pub const BankDepositGoldError = FetchError || BankDepositGoldErrorDef.ErrorSet;
|
pub const BankDepositGoldError = FetchError || BankDepositGoldErrorDef.ErrorSet;
|
||||||
pub const parseBankDepositGoldError = BankDepositGoldErrorDef.parse;
|
pub const parseBankDepositGoldError = BankDepositGoldErrorDef.parse;
|
||||||
|
|
||||||
const BankWithdrawGoldErrorDef = ErrorDefinitionList(&[_]ErrorDefinition{
|
const BankWithdrawGoldErrorDef = ErrorDefinitionList(&[_]ErrorDefinition{
|
||||||
BankIsBusy,
|
BankTransactionInProgress,
|
||||||
NotEnoughGold,
|
BankInsufficientGold,
|
||||||
CharacterIsBusy,
|
CharacterLocked,
|
||||||
CharacterNotFound,
|
CharacterNotFound,
|
||||||
CharacterInCooldown,
|
CharacterInCooldown,
|
||||||
BankNotFound
|
MapContentNotFound
|
||||||
});
|
});
|
||||||
pub const BankWithdrawGoldError = FetchError || BankWithdrawGoldErrorDef.ErrorSet;
|
pub const BankWithdrawGoldError = FetchError || BankWithdrawGoldErrorDef.ErrorSet;
|
||||||
pub const parseBankWithdrawGoldError = BankWithdrawGoldErrorDef.parse;
|
pub const parseBankWithdrawGoldError = BankWithdrawGoldErrorDef.parse;
|
||||||
|
|
||||||
const BankWithdrawItemErrorDef = ErrorDefinitionList(&[_]ErrorDefinition{
|
const BankWithdrawItemErrorDef = ErrorDefinitionList(&[_]ErrorDefinition{
|
||||||
ItemNotFound,
|
NotFound,
|
||||||
BankIsBusy,
|
BankTransactionInProgress,
|
||||||
NotEnoughItems,
|
MissingItem,
|
||||||
CharacterIsBusy,
|
CharacterLocked,
|
||||||
CharacterIsFull,
|
CharacterInventoryFull,
|
||||||
CharacterNotFound,
|
CharacterNotFound,
|
||||||
CharacterInCooldown,
|
CharacterInCooldown,
|
||||||
BankNotFound
|
MapContentNotFound
|
||||||
});
|
});
|
||||||
pub const BankWithdrawItemError = FetchError || BankWithdrawItemErrorDef.ErrorSet;
|
pub const BankWithdrawItemError = FetchError || BankWithdrawItemErrorDef.ErrorSet;
|
||||||
pub const parseBankWithdrawItemError = BankWithdrawItemErrorDef.parse;
|
pub const parseBankWithdrawItemError = BankWithdrawItemErrorDef.parse;
|
||||||
|
|
||||||
const CraftErrorDef = ErrorDefinitionList(&[_]ErrorDefinition{
|
const CraftErrorDef = ErrorDefinitionList(&[_]ErrorDefinition{
|
||||||
RecipeNotFound,
|
NotFound,
|
||||||
NotEnoughItems,
|
MissingItem,
|
||||||
CharacterIsBusy,
|
CharacterLocked,
|
||||||
NotEnoughSkill,
|
CharacterNotSkillLevelRequired,
|
||||||
CharacterIsFull,
|
CharacterInventoryFull,
|
||||||
CharacterNotFound,
|
CharacterNotFound,
|
||||||
CharacterInCooldown,
|
CharacterInCooldown,
|
||||||
WorkshopNotFound
|
MapContentNotFound
|
||||||
});
|
});
|
||||||
pub const CraftError = FetchError || CraftErrorDef.ErrorSet;
|
pub const CraftError = FetchError || CraftErrorDef.ErrorSet;
|
||||||
pub const parseCraftError = CraftErrorDef.parse;
|
pub const parseCraftError = CraftErrorDef.parse;
|
||||||
|
|
||||||
const UnequipErrorDef = ErrorDefinitionList(&[_]ErrorDefinition{
|
const UnequipErrorDef = ErrorDefinitionList(&[_]ErrorDefinition{
|
||||||
ItemNotFound, // TODO: Can this really occur? maybe a bug in docs
|
NotFound,
|
||||||
CharacterIsBusy,
|
CharacterLocked,
|
||||||
SlotIsEmpty,
|
CharacterSlotEquipmentError,
|
||||||
CharacterIsFull,
|
CharacterInventoryFull,
|
||||||
CharacterNotFound,
|
CharacterNotFound,
|
||||||
CharacterInCooldown,
|
CharacterInCooldown,
|
||||||
});
|
});
|
||||||
@ -175,10 +219,10 @@ pub const UnequipError = FetchError || UnequipErrorDef.ErrorSet;
|
|||||||
pub const parseUnequipError = UnequipErrorDef.parse;
|
pub const parseUnequipError = UnequipErrorDef.parse;
|
||||||
|
|
||||||
const EquipErrorDef = ErrorDefinitionList(&[_]ErrorDefinition{
|
const EquipErrorDef = ErrorDefinitionList(&[_]ErrorDefinition{
|
||||||
ItemNotFound,
|
NotFound,
|
||||||
SlotIsFull,
|
CharacterItemAlreadyEquiped,
|
||||||
CharacterIsBusy,
|
CharacterLocked,
|
||||||
NotEnoughSkill,
|
CharacterNotSkillLevelRequired,
|
||||||
CharacterNotFound,
|
CharacterNotFound,
|
||||||
CharacterInCooldown,
|
CharacterInCooldown,
|
||||||
});
|
});
|
||||||
@ -186,23 +230,35 @@ pub const EquipError = FetchError || EquipErrorDef.ErrorSet;
|
|||||||
pub const parseEquipError = EquipErrorDef.parse;
|
pub const parseEquipError = EquipErrorDef.parse;
|
||||||
|
|
||||||
const AcceptTaskErrorDef = ErrorDefinitionList(&[_]ErrorDefinition{
|
const AcceptTaskErrorDef = ErrorDefinitionList(&[_]ErrorDefinition{
|
||||||
CharacterIsBusy,
|
CharacterLocked,
|
||||||
AlreadyHasTask,
|
|
||||||
CharacterNotFound,
|
CharacterNotFound,
|
||||||
CharacterInCooldown,
|
CharacterInCooldown,
|
||||||
TaskMasterNotFound
|
MapContentNotFound
|
||||||
});
|
});
|
||||||
pub const AcceptTaskError = FetchError || AcceptTaskErrorDef.ErrorSet;
|
pub const AcceptTaskError = FetchError || AcceptTaskErrorDef.ErrorSet;
|
||||||
pub const parseAcceptTaskError = AcceptTaskErrorDef.parse;
|
pub const parseAcceptTaskError = AcceptTaskErrorDef.parse;
|
||||||
|
|
||||||
const TaskCompleteErrorDef = ErrorDefinitionList(&[_]ErrorDefinition{
|
const TaskCompleteErrorDef = ErrorDefinitionList(&[_]ErrorDefinition{
|
||||||
CharacterIsBusy,
|
CharacterLocked,
|
||||||
HasNoTask,
|
CharacterNoTask,
|
||||||
TaskNotCompleted,
|
CharacterTaskNotCompleted,
|
||||||
CharacterIsFull,
|
CharacterInventoryFull,
|
||||||
CharacterNotFound,
|
CharacterNotFound,
|
||||||
CharacterInCooldown,
|
CharacterInCooldown,
|
||||||
TaskMasterNotFound
|
MapContentNotFound
|
||||||
});
|
});
|
||||||
pub const TaskCompleteError = FetchError || TaskCompleteErrorDef.ErrorSet;
|
pub const TaskCompleteError = FetchError || TaskCompleteErrorDef.ErrorSet;
|
||||||
pub const parseTaskCompleteError = TaskCompleteErrorDef.parse;
|
pub const parseTaskCompleteError = TaskCompleteErrorDef.parse;
|
||||||
|
|
||||||
|
const CreateCharacterErrorDef = ErrorDefinitionList(&[_]ErrorDefinition{
|
||||||
|
CharacterNameAlreadyUsed,
|
||||||
|
MaxCharactersReached
|
||||||
|
});
|
||||||
|
pub const CreateCharacterError = FetchError || CreateCharacterErrorDef.ErrorSet;
|
||||||
|
pub const parseCreateCharacterError = CreateCharacterErrorDef.parse;
|
||||||
|
|
||||||
|
const DeleteCharacterErrorDef = ErrorDefinitionList(&[_]ErrorDefinition{
|
||||||
|
CharacterNotFound,
|
||||||
|
});
|
||||||
|
pub const DeleteCharacterError = FetchError || DeleteCharacterErrorDef.ErrorSet;
|
||||||
|
pub const parseDeleteCharacterError = DeleteCharacterErrorDef.parse;
|
||||||
|
@ -19,6 +19,15 @@ pub fn getIntegerRequired(object: json.ObjectMap, name: []const u8) !i64 {
|
|||||||
return getInteger(object, name) orelse return error.MissingProperty;
|
return getInteger(object, name) orelse return error.MissingProperty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn getPositiveIntegerRequired(object: json.ObjectMap, name: []const u8) !u64 {
|
||||||
|
const value = try getIntegerRequired(object, name);
|
||||||
|
if (value < 0) {
|
||||||
|
return error.InvalidInteger;
|
||||||
|
}
|
||||||
|
|
||||||
|
return @intCast(value);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn asObject(value: json.Value) ?json.ObjectMap {
|
pub fn asObject(value: json.Value) ?json.ObjectMap {
|
||||||
if (value != json.Value.object) {
|
if (value != json.Value.object) {
|
||||||
return null;
|
return null;
|
||||||
@ -70,11 +79,6 @@ pub fn getStringRequired(object: json.ObjectMap, name: []const u8) ![]const u8 {
|
|||||||
return getString(object, name) orelse return error.MissingProperty;
|
return getString(object, name) orelse return error.MissingProperty;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn dupeString(allocator: Allocator, object: json.ObjectMap, name: []const u8) !?[]u8 {
|
pub fn getArrayRequired(object: json.ObjectMap, name: []const u8) !json.Array {
|
||||||
const str = getString(object, name) orelse return null;
|
return getArray(object, name) orelse return error.MissingProperty;
|
||||||
return try allocator.dupe(u8, str);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn dupeStringRequired(allocator: Allocator, object: json.ObjectMap, name: []const u8) ![]u8 {
|
|
||||||
return (try dupeString(allocator, object, name)) orelse return error.MissingProperty;
|
|
||||||
}
|
}
|
||||||
|
29
api/root.zig
29
api/root.zig
@ -1,17 +1,32 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub const max_auth_token_size = 256;
|
||||||
|
pub const AuthToken = std.BoundedArray(u8, max_auth_token_size);
|
||||||
|
|
||||||
|
pub const images_url = "https://artifactsmmo.com";
|
||||||
|
pub const images_uri = std.Uri.parse(images_url) catch @compileError("Images server URL is invalid");
|
||||||
|
|
||||||
|
// Specification URL: https://api.artifactsmmo.com/docs
|
||||||
|
pub const api_url = "https://api.artifactsmmo.com";
|
||||||
|
pub const docs_url = api_url ++ "/openapi.json";
|
||||||
|
|
||||||
pub const parseDateTime = @import("./date_time/parse.zig").parseDateTime;
|
pub const parseDateTime = @import("./date_time/parse.zig").parseDateTime;
|
||||||
|
|
||||||
pub const Server = @import("server.zig");
|
pub const Server = @import("server.zig");
|
||||||
pub const Store = @import("store.zig");
|
pub const Store = @import("store.zig");
|
||||||
pub const Character = @import("./schemas/character.zig");
|
pub const Item = @import("./schemas/item.zig");
|
||||||
pub const ServerStatus = @import("./schemas/status.zig");
|
pub const Status = @import("./schemas/status.zig");
|
||||||
|
pub const Position = @import("./schemas/position.zig");
|
||||||
pub const Map = @import("./schemas/map.zig");
|
pub const Map = @import("./schemas/map.zig");
|
||||||
pub const Position = @import("position.zig");
|
// pub const Character = @import("./schemas/character.zig");
|
||||||
pub const BoundedSlotsArray = @import("schemas/slot_array.zig").BoundedSlotsArray;
|
// pub const ServerStatus = @import("./schemas/status.zig");
|
||||||
|
// pub const Map = @import("./schemas/map.zig");
|
||||||
|
// pub const Position = @import("position.zig");
|
||||||
|
// pub const BoundedSlotsArray = @import("schemas/slot_array.zig").BoundedSlotsArray;
|
||||||
|
|
||||||
pub const Slot = Server.Slot;
|
// pub const Slot = Server.Slot;
|
||||||
pub const CodeId = Store.CodeId;
|
// pub const CodeId = Store.Id;
|
||||||
pub const ItemQuantity = @import("./schemas/item_quantity.zig");
|
// pub const ItemQuantity = @import("./schemas/item_quantity.zig");
|
||||||
|
|
||||||
const errors = @import("errors.zig");
|
const errors = @import("errors.zig");
|
||||||
pub const FetchError = errors.FetchError;
|
pub const FetchError = errors.FetchError;
|
||||||
|
@ -1,23 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const Store = @import("../store.zig");
|
|
||||||
const json_utils = @import("../json_utils.zig");
|
|
||||||
const json = std.json;
|
|
||||||
const Allocator = std.mem.Allocator;
|
|
||||||
|
|
||||||
const Cooldown = @import("./cooldown.zig");
|
|
||||||
const Character = @import("./character.zig");
|
|
||||||
|
|
||||||
const BankGoldTransaction = @This();
|
|
||||||
|
|
||||||
cooldown: Cooldown,
|
|
||||||
character: Character,
|
|
||||||
|
|
||||||
pub fn parse(store: *Store, obj: json.ObjectMap, allocator: Allocator) !BankGoldTransaction {
|
|
||||||
const cooldown = json_utils.getObject(obj, "cooldown") orelse return error.MissingProperty;
|
|
||||||
const character = json_utils.getObject(obj, "character") orelse return error.MissingProperty;
|
|
||||||
|
|
||||||
return BankGoldTransaction{
|
|
||||||
.cooldown = try Cooldown.parse(cooldown),
|
|
||||||
.character = try Character.parse(store, character, allocator)
|
|
||||||
};
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const Store = @import("../store.zig");
|
|
||||||
const json_utils = @import("../json_utils.zig");
|
|
||||||
const json = std.json;
|
|
||||||
const Allocator = std.mem.Allocator;
|
|
||||||
|
|
||||||
const Cooldown = @import("./cooldown.zig");
|
|
||||||
const Character = @import("./character.zig");
|
|
||||||
|
|
||||||
const BankItemTransaction = @This();
|
|
||||||
|
|
||||||
cooldown: Cooldown,
|
|
||||||
character: Character,
|
|
||||||
|
|
||||||
pub fn parse(store: *Store, obj: json.ObjectMap, allocator: Allocator) !BankItemTransaction {
|
|
||||||
const cooldown = json_utils.getObject(obj, "cooldown") orelse return error.MissingProperty;
|
|
||||||
const character = json_utils.getObject(obj, "character") orelse return error.MissingProperty;
|
|
||||||
|
|
||||||
return BankItemTransaction{
|
|
||||||
.cooldown = try Cooldown.parse(cooldown),
|
|
||||||
.character = try Character.parse(store, character, allocator)
|
|
||||||
};
|
|
||||||
}
|
|
@ -1,117 +1,158 @@
|
|||||||
|
// zig fmt: off
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const json_utils = @import("../json_utils.zig");
|
|
||||||
const Store = @import("../store.zig");
|
const Store = @import("../store.zig");
|
||||||
const Position = @import("../position.zig");
|
const EnumStringUtils = @import("../enum_string_utils.zig").EnumStringUtils;
|
||||||
const parseDateTime = @import("../date_time/parse.zig").parseDateTime;
|
const json_utils = @import("../json_utils.zig");
|
||||||
const Allocator = std.mem.Allocator;
|
pub const Equipment = @import("./equipment.zig");
|
||||||
const json = std.json;
|
|
||||||
const assert = std.debug.assert;
|
|
||||||
|
|
||||||
const SkillStats = @import("./skill_stats.zig");
|
|
||||||
const CombatStats = @import("./combat_stats.zig");
|
|
||||||
const Equipment = @import("./equipment.zig");
|
|
||||||
const Task = @import("./task.zig");
|
const Task = @import("./task.zig");
|
||||||
const BoundedSlotsArray = @import("./slot_array.zig").BoundedSlotsArray;
|
const SimpleItem = @import("./simple_item.zig");
|
||||||
|
|
||||||
const Inventory = BoundedSlotsArray(20);
|
|
||||||
|
|
||||||
const Character = @This();
|
const Character = @This();
|
||||||
|
|
||||||
const TaskMasterTask = struct {
|
pub const Skin = enum {
|
||||||
target_id: Store.CodeId,
|
men1,
|
||||||
type: Task.Type,
|
men2,
|
||||||
progress: u64,
|
men3,
|
||||||
total: u64,
|
women1,
|
||||||
|
women2,
|
||||||
|
women3,
|
||||||
|
|
||||||
fn parse(store: *Store, obj: json.ObjectMap) !TaskMasterTask {
|
const Utils = EnumStringUtils(Skin, .{
|
||||||
const task_target = try json_utils.getStringRequired(obj, "task");
|
.{ "men1" , Skin.men1 },
|
||||||
const task_type = try json_utils.getStringRequired(obj, "task_type");
|
.{ "men2" , Skin.men2 },
|
||||||
|
.{ "men3" , Skin.men3 },
|
||||||
|
.{ "women1", Skin.women1 },
|
||||||
|
.{ "women2", Skin.women2 },
|
||||||
|
.{ "women3", Skin.women3 },
|
||||||
|
});
|
||||||
|
|
||||||
const progress = try json_utils.getIntegerRequired(obj, "task_progress");
|
pub const fromString = Utils.fromString;
|
||||||
if (progress < 0) {
|
pub const toString = Utils.toString;
|
||||||
return error.InvalidTaskProgress;
|
};
|
||||||
}
|
|
||||||
|
|
||||||
const total = try json_utils.getIntegerRequired(obj, "task_total");
|
pub const Skill = enum {
|
||||||
if (total < 0) {
|
combat,
|
||||||
return error.InvalidTaskTotal;
|
fishing,
|
||||||
}
|
weaponcrafting,
|
||||||
|
gearcrafting,
|
||||||
|
jewelrycrafting,
|
||||||
|
cooking,
|
||||||
|
woodcutting,
|
||||||
|
mining,
|
||||||
|
alchemy,
|
||||||
|
|
||||||
return TaskMasterTask{
|
const Utils = EnumStringUtils(Skill, .{
|
||||||
.target_id = try store.getCodeId(task_target),
|
.{ "combat" , Skill.combat },
|
||||||
.type = Task.TypeUtils.fromString(task_type) orelse return error.InvalidTaskType,
|
.{ "fishing" , Skill.fishing },
|
||||||
.total = @intCast(total),
|
.{ "weaponcrafting" , Skill.weaponcrafting },
|
||||||
.progress = @intCast(progress),
|
.{ "gearcrafting" , Skill.gearcrafting },
|
||||||
|
.{ "jewelrycrafting", Skill.jewelrycrafting },
|
||||||
|
.{ "cooking" , Skill.cooking },
|
||||||
|
.{ "woodcutting" , Skill.woodcutting },
|
||||||
|
.{ "mining" , Skill.mining },
|
||||||
|
.{ "alchemy" , Skill.alchemy },
|
||||||
|
});
|
||||||
|
|
||||||
|
pub const toString = Utils.toString;
|
||||||
|
pub const fromString = Utils.fromString;
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const SkillStats = struct {
|
||||||
|
level: u64 = 0,
|
||||||
|
xp: u64 = 0,
|
||||||
|
max_xp: u64 = 0,
|
||||||
|
|
||||||
|
pub fn parse(object: std.json.ObjectMap, level: []const u8, xp: []const u8, max_xp: []const u8) !SkillStats {
|
||||||
|
return SkillStats{
|
||||||
|
.level = try json_utils.getPositiveIntegerRequired(object, level),
|
||||||
|
.xp = try json_utils.getPositiveIntegerRequired(object, xp),
|
||||||
|
.max_xp = try json_utils.getPositiveIntegerRequired(object, max_xp),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
allocator: Allocator,
|
pub const Element = enum {
|
||||||
|
water,
|
||||||
|
fire,
|
||||||
|
earth,
|
||||||
|
air,
|
||||||
|
};
|
||||||
|
|
||||||
name: []u8,
|
pub const ElementalStats = struct {
|
||||||
skin: []u8,
|
attack: i64,
|
||||||
account: ?[]u8,
|
damage: i64,
|
||||||
gold: i64,
|
resistance: i64,
|
||||||
hp: i64,
|
|
||||||
haste: i64,
|
|
||||||
position: Position,
|
|
||||||
cooldown_expiration: f64,
|
|
||||||
|
|
||||||
combat: SkillStats,
|
pub fn parse(object: std.json.ObjectMap, attack: []const u8, damage: []const u8, resistance: []const u8) !ElementalStats {
|
||||||
mining: SkillStats,
|
return ElementalStats{
|
||||||
woodcutting: SkillStats,
|
.attack = try json_utils.getIntegerRequired(object, attack),
|
||||||
fishing: SkillStats,
|
.damage = try json_utils.getIntegerRequired(object, damage),
|
||||||
weaponcrafting: SkillStats,
|
.resistance = try json_utils.getIntegerRequired(object, resistance),
|
||||||
gearcrafting: SkillStats,
|
};
|
||||||
jewelrycrafting: SkillStats,
|
}
|
||||||
cooking: SkillStats,
|
};
|
||||||
|
|
||||||
water: CombatStats,
|
const TaskMasterTask = struct {
|
||||||
fire: CombatStats,
|
target: Store.Id,
|
||||||
earth: CombatStats,
|
type: Task.Type,
|
||||||
air: CombatStats,
|
progress: u64,
|
||||||
|
total: u64,
|
||||||
|
|
||||||
|
fn parse(store: *Store, obj: std.json.ObjectMap) !?TaskMasterTask {
|
||||||
|
const task_target = try json_utils.getStringRequired(obj, "task");
|
||||||
|
if (task_target.len == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const task_type = try json_utils.getStringRequired(obj, "task_type");
|
||||||
|
const progress = try json_utils.getPositiveIntegerRequired(obj, "task_progress");
|
||||||
|
const total = try json_utils.getPositiveIntegerRequired(obj, "task_total");
|
||||||
|
|
||||||
|
return TaskMasterTask{
|
||||||
|
.target = try store.tasks.getOrReserveId(task_target),
|
||||||
|
.type = Task.Type.fromString(task_type) orelse return error.InvalidTaskType,
|
||||||
|
.total = total,
|
||||||
|
.progress = progress,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const ElementalStatsArray = std.EnumArray(Element, ElementalStats);
|
||||||
|
|
||||||
|
pub const Skills = std.EnumArray(Skill, SkillStats);
|
||||||
|
|
||||||
|
pub const max_name_size = 12;
|
||||||
|
pub const Name = std.BoundedArray(u8, max_name_size);
|
||||||
|
|
||||||
|
pub const max_account_size = 32;
|
||||||
|
pub const Account = std.BoundedArray(u8, max_account_size);
|
||||||
|
|
||||||
|
pub const Inventory = SimpleItem.BoundedArray(20);
|
||||||
|
|
||||||
|
name: Name,
|
||||||
|
account: Account,
|
||||||
|
skin: Skin,
|
||||||
|
gold: u64,
|
||||||
|
skills: Skills,
|
||||||
|
elemental_stats: ElementalStatsArray,
|
||||||
equipment: Equipment,
|
equipment: Equipment,
|
||||||
|
task: ?TaskMasterTask,
|
||||||
inventory_max_items: u64,
|
inventory_max_items: u64,
|
||||||
inventory: Inventory,
|
inventory: Inventory,
|
||||||
|
|
||||||
task: ?TaskMasterTask,
|
pub fn parse(store: *Store, obj: std.json.ObjectMap) !Character {
|
||||||
|
const name = try json_utils.getStringRequired(obj, "name");
|
||||||
pub fn parse(store: *Store, obj: json.ObjectMap, allocator: Allocator) !Character {
|
|
||||||
const inventory = json_utils.getArray(obj, "inventory") orelse return error.MissingProperty;
|
|
||||||
const cooldown_expiration = json_utils.getString(obj, "cooldown_expiration") orelse return error.MissingProperty;
|
|
||||||
|
|
||||||
const x = try json_utils.getIntegerRequired(obj, "x");
|
|
||||||
const y = try json_utils.getIntegerRequired(obj, "y");
|
|
||||||
const name = (try json_utils.dupeString(allocator, obj, "name")) orelse return error.MissingProperty;
|
|
||||||
if (name.len == 0) {
|
if (name.len == 0) {
|
||||||
return error.InvalidName;
|
return error.InvalidName;
|
||||||
}
|
}
|
||||||
|
|
||||||
const inventory_max_items = json_utils.getInteger(obj, "inventory_max_items") orelse return error.MissingProperty;
|
const inventory = try json_utils.getArrayRequired(obj, "inventory");
|
||||||
if (inventory_max_items < 0) {
|
const account = try json_utils.getStringRequired(obj, "name");
|
||||||
return error.InvalidInventoryMaxItems;
|
const skin = try json_utils.getStringRequired(obj, "skin");
|
||||||
}
|
const gold = try json_utils.getPositiveIntegerRequired(obj, "gold");
|
||||||
|
const inventory_max_items = try json_utils.getPositiveIntegerRequired(obj, "inventory_max_items");
|
||||||
var task: ?TaskMasterTask = null;
|
|
||||||
const task_target = try json_utils.getStringRequired(obj, "task");
|
|
||||||
if (task_target.len > 0) {
|
|
||||||
task = try TaskMasterTask.parse(store, obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Character{
|
|
||||||
.allocator = allocator,
|
|
||||||
.account = try json_utils.dupeString(allocator, obj, "account"),
|
|
||||||
.name = name,
|
|
||||||
.skin = (try json_utils.dupeString(allocator, obj, "skin")) orelse return error.MissingProperty,
|
|
||||||
|
|
||||||
.gold = try json_utils.getIntegerRequired(obj, "gold"),
|
|
||||||
.hp = try json_utils.getIntegerRequired(obj, "hp"),
|
|
||||||
.haste = try json_utils.getIntegerRequired(obj, "haste"),
|
|
||||||
.position = Position.init(x, y),
|
|
||||||
.cooldown_expiration = parseDateTime(cooldown_expiration) orelse return error.InvalidDateTime,
|
|
||||||
|
|
||||||
|
const skill_stats = Skills.init(.{
|
||||||
.combat = try SkillStats.parse(obj, "level", "xp", "max_xp"),
|
.combat = try SkillStats.parse(obj, "level", "xp", "max_xp"),
|
||||||
.mining = try SkillStats.parse(obj, "mining_level", "mining_xp", "mining_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"),
|
.woodcutting = try SkillStats.parse(obj, "woodcutting_level", "woodcutting_xp", "woodcutting_max_xp"),
|
||||||
@ -120,43 +161,30 @@ pub fn parse(store: *Store, obj: json.ObjectMap, allocator: Allocator) !Characte
|
|||||||
.gearcrafting = try SkillStats.parse(obj, "gearcrafting_level", "gearcrafting_xp", "gearcrafting_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"),
|
.jewelrycrafting = try SkillStats.parse(obj, "jewelrycrafting_level", "jewelrycrafting_xp", "jewelrycrafting_max_xp"),
|
||||||
.cooking = try SkillStats.parse(obj, "cooking_level", "cooking_xp", "cooking_max_xp"),
|
.cooking = try SkillStats.parse(obj, "cooking_level", "cooking_xp", "cooking_max_xp"),
|
||||||
|
.alchemy = try SkillStats.parse(obj, "alchemy_level", "alchemy_xp", "alchemy_max_xp"),
|
||||||
|
});
|
||||||
|
|
||||||
.water = try CombatStats.parse(obj, "attack_water", "dmg_water", "res_water"),
|
const elemental_stats = ElementalStatsArray.init(.{
|
||||||
.fire = try CombatStats.parse(obj, "attack_fire", "dmg_fire", "res_fire"),
|
.water = try ElementalStats.parse(obj, "attack_water", "dmg_water", "res_water"),
|
||||||
.earth = try CombatStats.parse(obj, "attack_earth", "dmg_earth", "res_earth"),
|
.fire = try ElementalStats.parse(obj, "attack_fire", "dmg_fire", "res_fire"),
|
||||||
.air = try CombatStats.parse(obj, "attack_air", "dmg_air", "res_air"),
|
.earth = try ElementalStats.parse(obj, "attack_earth", "dmg_earth", "res_earth"),
|
||||||
|
.air = try ElementalStats.parse(obj, "attack_air", "dmg_air", "res_air"),
|
||||||
|
});
|
||||||
|
|
||||||
|
return Character{
|
||||||
|
.name = try Name.fromSlice(name),
|
||||||
|
.account = try Account.fromSlice(account),
|
||||||
|
.skin = Skin.fromString(skin) orelse return error.SkinNotFound,
|
||||||
|
.gold = gold,
|
||||||
|
.skills = skill_stats,
|
||||||
|
.elemental_stats = elemental_stats,
|
||||||
.equipment = try Equipment.parse(store, obj),
|
.equipment = try Equipment.parse(store, obj),
|
||||||
|
.task = try TaskMasterTask.parse(store, obj),
|
||||||
.inventory_max_items = @intCast(inventory_max_items),
|
.inventory_max_items = inventory_max_items,
|
||||||
.inventory = try Inventory.parse(store, inventory),
|
.inventory = try Inventory.parse(store, inventory)
|
||||||
|
|
||||||
.task = task
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *Character) void {
|
pub fn parseAndAppend(store: *Store, obj: std.json.ObjectMap) !Store.Id {
|
||||||
if (self.account) |str| self.allocator.free(str);
|
return store.characters.appendOrUpdate(try parse(store, obj));
|
||||||
self.allocator.free(self.name);
|
|
||||||
self.allocator.free(self.skin);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn getItemCount(self: *const Character) u64 {
|
|
||||||
return self.inventory.totalQuantity();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn format(
|
|
||||||
self: Character,
|
|
||||||
comptime fmt: []const u8,
|
|
||||||
options: std.fmt.FormatOptions,
|
|
||||||
writer: anytype,
|
|
||||||
) !void {
|
|
||||||
_ = fmt;
|
|
||||||
_ = options;
|
|
||||||
|
|
||||||
try writer.print("{s}{{ .name = \"{s}\", .position = {} ... }}", .{
|
|
||||||
@typeName(Character),
|
|
||||||
self.name,
|
|
||||||
self.position
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
@ -1,27 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const Store = @import("../store.zig");
|
|
||||||
const json_utils = @import("../json_utils.zig");
|
|
||||||
const json = std.json;
|
|
||||||
const Allocator = std.mem.Allocator;
|
|
||||||
|
|
||||||
const Cooldown = @import("./cooldown.zig");
|
|
||||||
const Fight = @import("./fight.zig");
|
|
||||||
const Character = @import("./character.zig");
|
|
||||||
|
|
||||||
const CharacterFight = @This();
|
|
||||||
|
|
||||||
cooldown: Cooldown,
|
|
||||||
fight: Fight,
|
|
||||||
character: Character,
|
|
||||||
|
|
||||||
pub fn parse(store: *Store, obj: json.ObjectMap, allocator: Allocator) !CharacterFight {
|
|
||||||
const cooldown = json_utils.getObject(obj, "cooldown") orelse return error.MissingProperty;
|
|
||||||
const fight = json_utils.getObject(obj, "fight") orelse return error.MissingProperty;
|
|
||||||
const character = json_utils.getObject(obj, "character") orelse return error.MissingProperty;
|
|
||||||
|
|
||||||
return CharacterFight{
|
|
||||||
.cooldown = try Cooldown.parse(cooldown),
|
|
||||||
.fight = try Fight.parse(store, fight),
|
|
||||||
.character = try Character.parse(store, character, allocator)
|
|
||||||
};
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const Store = @import("../store.zig");
|
|
||||||
const json_utils = @import("../json_utils.zig");
|
|
||||||
const json = std.json;
|
|
||||||
const Allocator = std.mem.Allocator;
|
|
||||||
|
|
||||||
const Cooldown = @import("./cooldown.zig");
|
|
||||||
const Character = @import("./character.zig");
|
|
||||||
|
|
||||||
const CharacterMovement = @This();
|
|
||||||
|
|
||||||
cooldown: Cooldown,
|
|
||||||
character: Character,
|
|
||||||
|
|
||||||
pub fn parse(store: *Store, obj: json.ObjectMap, allocator: Allocator) !CharacterMovement {
|
|
||||||
const cooldown = json_utils.getObject(obj, "cooldown") orelse return error.MissingProperty;
|
|
||||||
const character = json_utils.getObject(obj, "character") orelse return error.MissingProperty;
|
|
||||||
|
|
||||||
return CharacterMovement{
|
|
||||||
.cooldown = try Cooldown.parse(cooldown),
|
|
||||||
.character = try Character.parse(store, character, allocator)
|
|
||||||
};
|
|
||||||
}
|
|
@ -1,53 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const json_utils = @import("../json_utils.zig");
|
|
||||||
pub const parseDateTime = @import("../date_time/parse.zig").parseDateTime;
|
|
||||||
const EnumStringUtils = @import("../enum_string_utils.zig").EnumStringUtils;
|
|
||||||
const json = std.json;
|
|
||||||
|
|
||||||
const Cooldown = @This();
|
|
||||||
|
|
||||||
const ReasonUtils = EnumStringUtils(Reason, .{
|
|
||||||
.{ "movement" , Reason.movement },
|
|
||||||
.{ "fight" , Reason.fight },
|
|
||||||
.{ "crafting" , Reason.crafting },
|
|
||||||
.{ "gathering" , Reason.gathering },
|
|
||||||
.{ "buy_ge" , Reason.buy_ge },
|
|
||||||
.{ "sell_ge" , Reason.sell_ge },
|
|
||||||
.{ "delete_item" , Reason.delete_item },
|
|
||||||
.{ "deposit_bank" , Reason.deposit_bank },
|
|
||||||
.{ "withdraw_bank", Reason.withdraw_bank },
|
|
||||||
.{ "equip" , Reason.equip },
|
|
||||||
.{ "unequip" , Reason.unequip },
|
|
||||||
.{ "task" , Reason.task },
|
|
||||||
.{ "recycling" , Reason.recycling },
|
|
||||||
});
|
|
||||||
pub const Reason = enum {
|
|
||||||
movement,
|
|
||||||
fight,
|
|
||||||
crafting,
|
|
||||||
gathering,
|
|
||||||
buy_ge,
|
|
||||||
sell_ge,
|
|
||||||
delete_item,
|
|
||||||
deposit_bank,
|
|
||||||
withdraw_bank,
|
|
||||||
equip,
|
|
||||||
unequip,
|
|
||||||
task,
|
|
||||||
recycling,
|
|
||||||
|
|
||||||
const parse = ReasonUtils.fromString;
|
|
||||||
};
|
|
||||||
|
|
||||||
expiration: f64,
|
|
||||||
reason: Reason,
|
|
||||||
|
|
||||||
pub fn parse(obj: json.ObjectMap) !Cooldown {
|
|
||||||
const reason = try json_utils.getStringRequired(obj, "reason");
|
|
||||||
const expiration = try json_utils.getStringRequired(obj, "expiration");
|
|
||||||
|
|
||||||
return Cooldown{
|
|
||||||
.expiration = parseDateTime(expiration) orelse return error.InvalidDateTime,
|
|
||||||
.reason = Reason.parse(reason) orelse return error.UnknownReason
|
|
||||||
};
|
|
||||||
}
|
|
@ -1,22 +1,43 @@
|
|||||||
const std = @import("std");
|
// zig fmt: off
|
||||||
const Store = @import("../store.zig");
|
const Store = @import("../store.zig");
|
||||||
|
const std = @import("std");
|
||||||
const json_utils = @import("../json_utils.zig");
|
const json_utils = @import("../json_utils.zig");
|
||||||
const BoundedSlotsArray = @import("./slot_array.zig").BoundedSlotsArray;
|
const SimpleItem = @import("./simple_item.zig");
|
||||||
const json = std.json;
|
const EnumStringUtils = @import("../enum_string_utils.zig").EnumStringUtils;
|
||||||
|
|
||||||
const Skill = @import("./skill.zig").Skill;
|
|
||||||
const SkillUtils = @import("./skill.zig").SkillUtils;
|
|
||||||
|
|
||||||
const Items = BoundedSlotsArray(8);
|
|
||||||
|
|
||||||
const Craft = @This();
|
const Craft = @This();
|
||||||
|
|
||||||
|
pub const Items = SimpleItem.BoundedArray(8);
|
||||||
|
|
||||||
|
pub const Skill = enum {
|
||||||
|
weaponcrafting,
|
||||||
|
gearcrafting,
|
||||||
|
jewelrycrafting,
|
||||||
|
cooking,
|
||||||
|
woodcutting,
|
||||||
|
mining,
|
||||||
|
alchemy,
|
||||||
|
|
||||||
|
const Utils = EnumStringUtils(Skill, .{
|
||||||
|
.{ "weaponcrafting" , Skill.weaponcrafting },
|
||||||
|
.{ "gearcrafting" , Skill.gearcrafting },
|
||||||
|
.{ "jewelrycrafting", Skill.jewelrycrafting },
|
||||||
|
.{ "cooking" , Skill.cooking },
|
||||||
|
.{ "woodcutting" , Skill.woodcutting },
|
||||||
|
.{ "mining" , Skill.mining },
|
||||||
|
.{ "alchemy" , Skill.alchemy },
|
||||||
|
});
|
||||||
|
|
||||||
|
pub const toString = Utils.toString;
|
||||||
|
pub const fromString = Utils.fromString;
|
||||||
|
};
|
||||||
|
|
||||||
skill: Skill,
|
skill: Skill,
|
||||||
level: u64,
|
level: u64,
|
||||||
quantity: u64,
|
quantity: u64,
|
||||||
items: Items,
|
items: Items,
|
||||||
|
|
||||||
pub fn parse(store: *Store, obj: json.ObjectMap) !Craft {
|
pub fn parse(store: *Store, obj: std.json.ObjectMap) !Craft {
|
||||||
const skill = json_utils.getString(obj, "skill") orelse return error.MissingProperty;
|
const skill = json_utils.getString(obj, "skill") orelse return error.MissingProperty;
|
||||||
const level = json_utils.getInteger(obj, "level") orelse return error.MissingProperty;
|
const level = json_utils.getInteger(obj, "level") orelse return error.MissingProperty;
|
||||||
if (level < 1) return error.InvalidLevel;
|
if (level < 1) return error.InvalidLevel;
|
||||||
@ -27,7 +48,7 @@ pub fn parse(store: *Store, obj: json.ObjectMap) !Craft {
|
|||||||
const items = json_utils.getArray(obj, "items") orelse return error.MissingProperty;
|
const items = json_utils.getArray(obj, "items") orelse return error.MissingProperty;
|
||||||
|
|
||||||
return Craft{
|
return Craft{
|
||||||
.skill = SkillUtils.fromString(skill) orelse return error.InvalidSkill,
|
.skill = Skill.fromString(skill) orelse return error.InvalidSkill,
|
||||||
.level = @intCast(level),
|
.level = @intCast(level),
|
||||||
.quantity = @intCast(quantity),
|
.quantity = @intCast(quantity),
|
||||||
.items = try Items.parse(store, items)
|
.items = try Items.parse(store, items)
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
|
// zig fmt: off
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Store = @import("../store.zig");
|
const Store = @import("../store.zig");
|
||||||
const json_utils = @import("../json_utils.zig");
|
const json_utils = @import("../json_utils.zig");
|
||||||
const json = std.json;
|
|
||||||
|
|
||||||
const DropRate = @This();
|
const DropRate = @This();
|
||||||
|
|
||||||
item_id: Store.CodeId,
|
item: Store.Id,
|
||||||
rate: u64,
|
rate: u64,
|
||||||
min_quantity: u64,
|
min_quantity: u64,
|
||||||
max_quantity: u64,
|
max_quantity: u64,
|
||||||
|
|
||||||
pub fn parse(store: *Store, obj: json.ObjectMap) !DropRate {
|
pub fn parse(store: *Store, obj: std.json.ObjectMap) !DropRate {
|
||||||
const rate = try json_utils.getIntegerRequired(obj, "rate");
|
const rate = try json_utils.getIntegerRequired(obj, "rate");
|
||||||
if (rate < 1) {
|
if (rate < 1) {
|
||||||
return error.InvalidRate;
|
return error.InvalidRate;
|
||||||
@ -26,33 +26,27 @@ pub fn parse(store: *Store, obj: json.ObjectMap) !DropRate {
|
|||||||
return error.InvalidMinQuantity;
|
return error.InvalidMinQuantity;
|
||||||
}
|
}
|
||||||
|
|
||||||
const code_str = try json_utils.getStringRequired(obj, "code");
|
const code = try json_utils.getStringRequired(obj, "code");
|
||||||
const item_id = try store.getCodeId(code_str);
|
const item_id = try store.items.getOrReserveId(code);
|
||||||
|
|
||||||
return DropRate{
|
return DropRate{
|
||||||
.item_id = item_id,
|
.item = item_id,
|
||||||
.rate = @intCast(rate),
|
.rate = @intCast(rate),
|
||||||
.min_quantity = @intCast(min_quantity),
|
.min_quantity = @intCast(min_quantity),
|
||||||
.max_quantity = @intCast(max_quantity)
|
.max_quantity = @intCast(max_quantity)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const DropRates = std.BoundedArray(DropRate, 8); // TODO: Maybe rename to "List"?
|
pub fn parseDrops(store: *Store, array: std.json.Array, drops: []DropRate) !usize {
|
||||||
|
for (0.., array.items) |i, drop_value| {
|
||||||
pub fn parseList(store: *Store, array: json.Array) !DropRates {
|
|
||||||
var drops = DropRates.init(0) catch unreachable;
|
|
||||||
for (array.items) |drop_value| {
|
|
||||||
const drop_obj = json_utils.asObject(drop_value) orelse return error.InvalidObject;
|
const drop_obj = json_utils.asObject(drop_value) orelse return error.InvalidObject;
|
||||||
try drops.append(try DropRate.parse(store, drop_obj));
|
|
||||||
}
|
|
||||||
return drops;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn doesListContain(drops: *DropRates, item_id: Store.CodeId) bool {
|
if (i >= drops.len) {
|
||||||
for (drops.constSlice()) |drop| {
|
return error.Overflow;
|
||||||
if (drop.item_id == item_id) {
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
drops[i] = try DropRate.parse(store, drop_obj);
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
|
return array.items.len;
|
||||||
}
|
}
|
||||||
|
@ -2,14 +2,23 @@ const std = @import("std");
|
|||||||
const json_utils = @import("../json_utils.zig");
|
const json_utils = @import("../json_utils.zig");
|
||||||
const json = std.json;
|
const json = std.json;
|
||||||
|
|
||||||
const CombatStats = @This();
|
const ElementalStat = @This();
|
||||||
|
|
||||||
|
pub const Element = enum {
|
||||||
|
water,
|
||||||
|
fire,
|
||||||
|
earth,
|
||||||
|
air,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Array = std.EnumArray(ElementalStat.Element, ElementalStat);
|
||||||
|
|
||||||
attack: i64,
|
attack: i64,
|
||||||
damage: i64,
|
damage: i64,
|
||||||
resistance: i64,
|
resistance: i64,
|
||||||
|
|
||||||
pub fn parse(object: json.ObjectMap, attack: []const u8, damage: []const u8, resistance: []const u8) !CombatStats {
|
pub fn parse(object: json.ObjectMap, attack: []const u8, damage: []const u8, resistance: []const u8) !ElementalStat {
|
||||||
return CombatStats{
|
return ElementalStat{
|
||||||
.attack = try json_utils.getIntegerRequired(object, attack),
|
.attack = try json_utils.getIntegerRequired(object, attack),
|
||||||
.damage = try json_utils.getIntegerRequired(object, damage),
|
.damage = try json_utils.getIntegerRequired(object, damage),
|
||||||
.resistance = try json_utils.getIntegerRequired(object, resistance),
|
.resistance = try json_utils.getIntegerRequired(object, resistance),
|
@ -1,28 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const Store = @import("../store.zig");
|
|
||||||
const json_utils = @import("../json_utils.zig");
|
|
||||||
const json = std.json;
|
|
||||||
const ItemId = Store.ItemId;
|
|
||||||
|
|
||||||
const Cooldown = @import("./cooldown.zig");
|
|
||||||
|
|
||||||
const EquipRequest = @This();
|
|
||||||
|
|
||||||
cooldown: Cooldown,
|
|
||||||
item: ItemId,
|
|
||||||
|
|
||||||
pub fn parse(store: *Store, obj: json.ObjectMap) !EquipRequest {
|
|
||||||
const cooldown = json_utils.getObject(obj, "cooldown") orelse return error.MissingProperty;
|
|
||||||
|
|
||||||
const item = json_utils.getObject(obj, "item") orelse return error.MissingProperty;
|
|
||||||
const item_code = json_utils.getString(item, "code") orelse return error.MissingProperty;
|
|
||||||
|
|
||||||
const item_id = try store.getItemId(item_code);
|
|
||||||
|
|
||||||
// TODO: Might as well save information about time, because full details about it are given
|
|
||||||
|
|
||||||
return EquipRequest{
|
|
||||||
.cooldown = try Cooldown.parse(cooldown),
|
|
||||||
.item = item_id
|
|
||||||
};
|
|
||||||
}
|
|
@ -1,20 +1,24 @@
|
|||||||
|
// zig fmt: off
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const json_utils = @import("../json_utils.zig");
|
const json_utils = @import("../json_utils.zig");
|
||||||
const Store = @import("../store.zig");
|
const Store = @import("../store.zig");
|
||||||
const json = std.json;
|
const Item = @import("./item.zig");
|
||||||
|
|
||||||
const Equipment = @This();
|
const Equipment = @This();
|
||||||
|
|
||||||
const CodeId = Store.CodeId;
|
pub const UtilitySlot = struct {
|
||||||
|
id: Store.Id,
|
||||||
|
quantity: u64,
|
||||||
|
|
||||||
pub const Consumable = struct {
|
fn parse(store: *Store, obj: std.json.ObjectMap, name: []const u8, quantity: []const u8) !?UtilitySlot {
|
||||||
code_id: ?CodeId,
|
const item_code = try json_utils.getStringRequired(obj, name);
|
||||||
quantity: i64,
|
if (item_code.len == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
fn parse(store: *Store, obj: json.ObjectMap, name: []const u8, quantity: []const u8) !Consumable {
|
return UtilitySlot{
|
||||||
return Consumable{
|
.id = try store.items.getOrReserveId(item_code),
|
||||||
.code_id = try store.getCodeIdJson(obj, name),
|
.quantity = try json_utils.getPositiveIntegerRequired(obj, quantity),
|
||||||
.quantity = try json_utils.getIntegerRequired(obj, quantity),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -31,61 +35,74 @@ pub const Slot = enum {
|
|||||||
amulet,
|
amulet,
|
||||||
artifact1,
|
artifact1,
|
||||||
artifact2,
|
artifact2,
|
||||||
consumable1,
|
utility1,
|
||||||
consumable2,
|
utility2,
|
||||||
|
|
||||||
fn name(self: Slot) []const u8 {
|
fn name(self: Slot) []const u8 {
|
||||||
return switch (self) {
|
return switch (self) {
|
||||||
.weapon => "weapon",
|
.weapon => "weapon",
|
||||||
.shield => "shield",
|
.shield => "shield",
|
||||||
.helmet => "helmet",
|
.helmet => "helmet",
|
||||||
.body_armor => "body_armor",
|
.body_armor => "body_armor",
|
||||||
.leg_armor => "leg_armor",
|
.leg_armor => "leg_armor",
|
||||||
.boots => "boots",
|
.boots => "boots",
|
||||||
.ring1 => "ring1",
|
.ring1 => "ring1",
|
||||||
.ring2 => "ring2",
|
.ring2 => "ring2",
|
||||||
.amulet => "amulet",
|
.amulet => "amulet",
|
||||||
.artifact1 => "artifact1",
|
.artifact1 => "artifact1",
|
||||||
.artifact2 => "artifact2",
|
.artifact2 => "artifact2",
|
||||||
.consumable1 => "consumable1",
|
.utility1 => "utility1",
|
||||||
.consumable2 => "consumable2",
|
.utility2 => "utility2",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
weapon: ?CodeId,
|
weapon: ?Store.Id,
|
||||||
shield: ?CodeId,
|
shield: ?Store.Id,
|
||||||
helmet: ?CodeId,
|
helmet: ?Store.Id,
|
||||||
body_armor: ?CodeId,
|
body_armor: ?Store.Id,
|
||||||
leg_armor: ?CodeId,
|
leg_armor: ?Store.Id,
|
||||||
boots: ?CodeId,
|
boots: ?Store.Id,
|
||||||
|
|
||||||
ring1: ?CodeId,
|
ring1: ?Store.Id,
|
||||||
ring2: ?CodeId,
|
ring2: ?Store.Id,
|
||||||
amulet: ?CodeId,
|
amulet: ?Store.Id,
|
||||||
|
|
||||||
artifact1: ?CodeId,
|
artifact1: ?Store.Id,
|
||||||
artifact2: ?CodeId,
|
artifact2: ?Store.Id,
|
||||||
artifact3: ?CodeId,
|
artifact3: ?Store.Id,
|
||||||
|
|
||||||
consumable1: Consumable,
|
utility1: ?UtilitySlot,
|
||||||
consumable2: Consumable,
|
utility2: ?UtilitySlot,
|
||||||
|
|
||||||
|
pub fn parse(store: *Store, obj: std.json.ObjectMap) !Equipment {
|
||||||
|
const weapon = try json_utils.getStringRequired(obj, "weapon_slot");
|
||||||
|
const shield = try json_utils.getStringRequired(obj, "shield_slot");
|
||||||
|
const helmet = try json_utils.getStringRequired(obj, "helmet_slot");
|
||||||
|
const body_armor = try json_utils.getStringRequired(obj, "body_armor_slot");
|
||||||
|
const leg_armor = try json_utils.getStringRequired(obj, "leg_armor_slot");
|
||||||
|
const boots = try json_utils.getStringRequired(obj, "boots_slot");
|
||||||
|
const ring1 = try json_utils.getStringRequired(obj, "ring1_slot");
|
||||||
|
const ring2 = try json_utils.getStringRequired(obj, "ring2_slot");
|
||||||
|
const amulet = try json_utils.getStringRequired(obj, "amulet_slot");
|
||||||
|
const artifact1 = try json_utils.getStringRequired(obj, "artifact1_slot");
|
||||||
|
const artifact2 = try json_utils.getStringRequired(obj, "artifact2_slot");
|
||||||
|
const artifact3 = try json_utils.getStringRequired(obj, "artifact3_slot");
|
||||||
|
|
||||||
pub fn parse(store: *Store, obj: json.ObjectMap) !Equipment {
|
|
||||||
return Equipment{
|
return Equipment{
|
||||||
.weapon = try store.getCodeIdJson(obj, "weapon_slot"),
|
.weapon = try store.items.getOrReserveId(weapon),
|
||||||
.shield = try store.getCodeIdJson(obj, "shield_slot"),
|
.shield = try store.items.getOrReserveId(shield),
|
||||||
.helmet = try store.getCodeIdJson(obj, "helmet_slot"),
|
.helmet = try store.items.getOrReserveId(helmet),
|
||||||
.body_armor = try store.getCodeIdJson(obj, "body_armor_slot"),
|
.body_armor = try store.items.getOrReserveId(body_armor),
|
||||||
.leg_armor = try store.getCodeIdJson(obj, "leg_armor_slot"),
|
.leg_armor = try store.items.getOrReserveId(leg_armor),
|
||||||
.boots = try store.getCodeIdJson(obj, "boots_slot"),
|
.boots = try store.items.getOrReserveId(boots),
|
||||||
.ring1 = try store.getCodeIdJson(obj, "ring1_slot"),
|
.ring1 = try store.items.getOrReserveId(ring1),
|
||||||
.ring2 = try store.getCodeIdJson(obj, "ring2_slot"),
|
.ring2 = try store.items.getOrReserveId(ring2),
|
||||||
.amulet = try store.getCodeIdJson(obj, "amulet_slot"),
|
.amulet = try store.items.getOrReserveId(amulet),
|
||||||
.artifact1 = try store.getCodeIdJson(obj, "artifact1_slot"),
|
.artifact1 = try store.items.getOrReserveId(artifact1),
|
||||||
.artifact2 = try store.getCodeIdJson(obj, "artifact2_slot"),
|
.artifact2 = try store.items.getOrReserveId(artifact2),
|
||||||
.artifact3 = try store.getCodeIdJson(obj, "artifact3_slot"),
|
.artifact3 = try store.items.getOrReserveId(artifact3),
|
||||||
.consumable1 = try Consumable.parse(store, obj, "consumable1_slot", "consumable1_slot_quantity"),
|
.utility1 = try UtilitySlot.parse(store, obj, "utility1_slot", "utility1_slot_quantity"),
|
||||||
.consumable2 = try Consumable.parse(store, obj, "consumable2_slot", "consumable2_slot_quantity"),
|
.utility2 = try UtilitySlot.parse(store, obj, "utility2_slot", "utility2_slot_quantity"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,46 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const Store = @import("../store.zig");
|
|
||||||
const BoundedSlotsArray = @import("./slot_array.zig").BoundedSlotsArray;
|
|
||||||
const json_utils = @import("../json_utils.zig");
|
|
||||||
const json = std.json;
|
|
||||||
|
|
||||||
const Fight = @This();
|
|
||||||
|
|
||||||
pub const Drops = BoundedSlotsArray(8);
|
|
||||||
|
|
||||||
xp: u64,
|
|
||||||
gold: u64,
|
|
||||||
drops: Drops,
|
|
||||||
won: bool,
|
|
||||||
|
|
||||||
pub fn parse(store: *Store, obj: json.ObjectMap) !Fight {
|
|
||||||
const result = try json_utils.getStringRequired(obj, "result");
|
|
||||||
|
|
||||||
var won = false;
|
|
||||||
if (std.mem.eql(u8, result, "win")) {
|
|
||||||
won = true;
|
|
||||||
} else if (std.mem.eql(u8, result, "lose")) {
|
|
||||||
won = false;
|
|
||||||
} else {
|
|
||||||
return error.InvalidProperty;
|
|
||||||
}
|
|
||||||
|
|
||||||
const drops_obj = json_utils.getArray(obj, "drops") orelse return error.MissingProperty;
|
|
||||||
|
|
||||||
const xp = try json_utils.getIntegerRequired(obj, "xp");
|
|
||||||
if (xp < 0) {
|
|
||||||
return error.InvalidXp;
|
|
||||||
}
|
|
||||||
|
|
||||||
const gold = try json_utils.getIntegerRequired(obj, "gold");
|
|
||||||
if (gold < 0) {
|
|
||||||
return error.InvalidGold;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Fight{
|
|
||||||
.xp = @intCast(xp),
|
|
||||||
.gold = @intCast(gold),
|
|
||||||
.drops = try Drops.parse(store, drops_obj),
|
|
||||||
.won = won,
|
|
||||||
};
|
|
||||||
}
|
|
@ -1,15 +1,14 @@
|
|||||||
|
// zig fmt: off
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Store = @import("../store.zig");
|
|
||||||
const EnumStringUtils = @import("../enum_string_utils.zig").EnumStringUtils;
|
const EnumStringUtils = @import("../enum_string_utils.zig").EnumStringUtils;
|
||||||
const json = std.json;
|
const Store = @import("../store.zig");
|
||||||
const Allocator = std.mem.Allocator;
|
|
||||||
const json_utils = @import("../json_utils.zig");
|
const json_utils = @import("../json_utils.zig");
|
||||||
|
|
||||||
const Craft = @import("./craft.zig");
|
const Craft = @import("./craft.zig");
|
||||||
|
|
||||||
const Item = @This();
|
const Item = @This();
|
||||||
|
|
||||||
pub const Type = enum {
|
pub const Type = enum {
|
||||||
|
utility,
|
||||||
consumable,
|
consumable,
|
||||||
body_armor,
|
body_armor,
|
||||||
weapon,
|
weapon,
|
||||||
@ -22,51 +21,87 @@ pub const Type = enum {
|
|||||||
ring,
|
ring,
|
||||||
artifact,
|
artifact,
|
||||||
currency,
|
currency,
|
||||||
};
|
|
||||||
pub const TypeUtils = EnumStringUtils(Type, .{
|
|
||||||
.{ "consumable", .consumable },
|
|
||||||
.{ "body_armor", .body_armor },
|
|
||||||
.{ "weapon" , .weapon },
|
|
||||||
.{ "resource" , .resource },
|
|
||||||
.{ "leg_armor" , .leg_armor },
|
|
||||||
.{ "helmet" , .helmet },
|
|
||||||
.{ "boots" , .boots },
|
|
||||||
.{ "shield" , .shield },
|
|
||||||
.{ "amulet" , .amulet },
|
|
||||||
.{ "ring" , .ring },
|
|
||||||
.{ "artifact" , .artifact },
|
|
||||||
.{ "currency" , .currency },
|
|
||||||
});
|
|
||||||
|
|
||||||
name: []u8,
|
const Utils = EnumStringUtils(Type, .{
|
||||||
code_id: Store.CodeId,
|
.{ "utility" , .consumable },
|
||||||
|
.{ "consumable", .consumable },
|
||||||
|
.{ "body_armor", .body_armor },
|
||||||
|
.{ "weapon" , .weapon },
|
||||||
|
.{ "resource" , .resource },
|
||||||
|
.{ "leg_armor" , .leg_armor },
|
||||||
|
.{ "helmet" , .helmet },
|
||||||
|
.{ "boots" , .boots },
|
||||||
|
.{ "shield" , .shield },
|
||||||
|
.{ "amulet" , .amulet },
|
||||||
|
.{ "ring" , .ring },
|
||||||
|
.{ "artifact" , .artifact },
|
||||||
|
.{ "currency" , .currency },
|
||||||
|
});
|
||||||
|
|
||||||
|
pub const toString = Utils.toString;
|
||||||
|
pub const fromString = Utils.fromString;
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const max_code_size = 32;
|
||||||
|
pub const Name = std.BoundedArray(u8, 32);
|
||||||
|
pub const Code = std.BoundedArray(u8, max_code_size);
|
||||||
|
pub const Subtype = std.BoundedArray(u8, 32);
|
||||||
|
pub const Description = std.BoundedArray(u8, 128);
|
||||||
|
|
||||||
|
name: Name,
|
||||||
|
code: Code,
|
||||||
level: u64,
|
level: u64,
|
||||||
type: Type,
|
type: Type,
|
||||||
subtype: []u8,
|
subtype: Subtype,
|
||||||
description: []u8,
|
description: Description,
|
||||||
craft: ?Craft,
|
craft: ?Craft,
|
||||||
// TODO: effects
|
|
||||||
|
|
||||||
pub fn parse(store: *Store, obj: json.ObjectMap, allocator: Allocator) !Item {
|
pub fn parse(store: *Store, obj: std.json.ObjectMap) !Item {
|
||||||
const level = json_utils.getInteger(obj, "level") orelse return error.MissingProperty;
|
const level = json_utils.getInteger(obj, "level") orelse return error.MissingProperty;
|
||||||
if (level < 1) return error.InvalidLevel;
|
if (level < 1) return error.InvalidLevel;
|
||||||
|
|
||||||
const craft = json_utils.getObject(obj, "craft");
|
const code = try json_utils.getStringRequired(obj, "code");
|
||||||
const item_type_str = try json_utils.getStringRequired(obj, "type");
|
const name = try json_utils.getStringRequired(obj, "name");
|
||||||
|
const subtype = try json_utils.getStringRequired(obj, "subtype");
|
||||||
|
const description = try json_utils.getStringRequired(obj, "description");
|
||||||
|
const item_type = try json_utils.getStringRequired(obj, "type");
|
||||||
|
const craft = json_utils.getObject(obj, "craft");
|
||||||
|
|
||||||
return Item{
|
return Item{
|
||||||
.name = (try json_utils.dupeString(allocator, obj, "name")) orelse return error.MissingProperty,
|
.name = try Name.fromSlice(name),
|
||||||
.code_id = (try store.getCodeIdJson(obj, "code")) orelse return error.MissingProperty,
|
.code = try Code.fromSlice(code),
|
||||||
.level = @intCast(level),
|
.level = @intCast(level),
|
||||||
.type = TypeUtils.fromString(item_type_str) orelse return error.InvalidType,
|
.type = Type.fromString(item_type) orelse return error.InvalidType,
|
||||||
.subtype = (try json_utils.dupeString(allocator, obj, "subtype")) orelse return error.MissingProperty,
|
.subtype = try Subtype.fromSlice(subtype),
|
||||||
.description = (try json_utils.dupeString(allocator, obj, "description")) orelse return error.MissingProperty,
|
.description = try Description.fromSlice(description),
|
||||||
.craft = if (craft != null) try Craft.parse(store, craft.?) else null
|
.craft = if (craft != null) try Craft.parse(store, craft.?) else null
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: Item, allocator: Allocator) void {
|
pub fn parseAndAppend(store: *Store, obj: std.json.ObjectMap) !Store.Id {
|
||||||
allocator.free(self.name);
|
return try store.items.appendOrUpdate(try Item.parse(store, obj));
|
||||||
allocator.free(self.subtype);
|
}
|
||||||
allocator.free(self.description);
|
|
||||||
|
pub fn format(
|
||||||
|
self: Item,
|
||||||
|
comptime fmt: []const u8,
|
||||||
|
options: std.fmt.FormatOptions,
|
||||||
|
writer: anytype,
|
||||||
|
) !void {
|
||||||
|
_ = fmt;
|
||||||
|
_ = options;
|
||||||
|
|
||||||
|
try writer.print("{s}{{ ", .{ @typeName(Item) });
|
||||||
|
|
||||||
|
try writer.print(".name = \"{s}\", ", .{ self.name.slice() });
|
||||||
|
try writer.print(".code = \"{s}\", ", .{ self.code.slice() });
|
||||||
|
try writer.print(".level = {}, ", .{ self.level });
|
||||||
|
try writer.print(".type = {}, ", .{ self.type });
|
||||||
|
try writer.print(".subtype = \"{s}\", ", .{ self.subtype.slice() });
|
||||||
|
try writer.print(".description = \"{s}\", ", .{ self.description.slice() });
|
||||||
|
if (self.craft) |craft| {
|
||||||
|
try writer.print(".craft = {}, ", .{ craft });
|
||||||
|
}
|
||||||
|
|
||||||
|
try writer.writeAll("}");
|
||||||
}
|
}
|
||||||
|
@ -1,29 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const json_utils = @import("../json_utils.zig");
|
|
||||||
const Store = @import("../store.zig");
|
|
||||||
const json = std.json;
|
|
||||||
|
|
||||||
const ItemQuantity = @This();
|
|
||||||
|
|
||||||
id: Store.CodeId,
|
|
||||||
quantity: u64,
|
|
||||||
|
|
||||||
pub fn init(id: Store.CodeId, quantity: u64) ItemQuantity {
|
|
||||||
return ItemQuantity{
|
|
||||||
.id = id,
|
|
||||||
.quantity = quantity
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse(store: *Store, slot_obj: json.ObjectMap) !?ItemQuantity {
|
|
||||||
const code = try json_utils.getStringRequired(slot_obj, "code");
|
|
||||||
if (code.len == 0) return null;
|
|
||||||
|
|
||||||
const quantity = try json_utils.getIntegerRequired(slot_obj, "quantity");
|
|
||||||
if (quantity < 0) return error.InvalidQuantity;
|
|
||||||
|
|
||||||
return ItemQuantity{
|
|
||||||
.id = try store.getCodeId(code),
|
|
||||||
.quantity = @intCast(quantity),
|
|
||||||
};
|
|
||||||
}
|
|
@ -1,34 +1,98 @@
|
|||||||
|
// zig fmt: off
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Store = @import("../store.zig");
|
const Store = @import("../store.zig");
|
||||||
const Position = @import("../position.zig");
|
const Position = @import("./position.zig");
|
||||||
const json_utils = @import("../json_utils.zig");
|
const json_utils = @import("../json_utils.zig");
|
||||||
const json = std.json;
|
const Monster = @import("./monster.zig");
|
||||||
const Allocator = std.mem.Allocator;
|
const Resource = @import("./resource.zig");
|
||||||
|
const Craft = @import("./craft.zig");
|
||||||
|
const EnumStringUtils = @import("../enum_string_utils.zig").EnumStringUtils;
|
||||||
|
|
||||||
const Map = @This();
|
const Map = @This();
|
||||||
|
|
||||||
const MapContent = @import("./map_content.zig");
|
pub const Name = std.BoundedArray(u8, 16);
|
||||||
|
|
||||||
name: []u8,
|
pub const max_skin_size = 32;
|
||||||
skin: []u8,
|
pub const Skin = std.BoundedArray(u8, max_skin_size);
|
||||||
|
|
||||||
|
pub const Content = struct {
|
||||||
|
pub const Type = enum {
|
||||||
|
monster,
|
||||||
|
resource,
|
||||||
|
workshop,
|
||||||
|
bank,
|
||||||
|
grand_exchange,
|
||||||
|
tasks_master,
|
||||||
|
santa_claus,
|
||||||
|
|
||||||
|
const Utils = EnumStringUtils(Type, .{
|
||||||
|
.{ "monster" , Type.monster },
|
||||||
|
.{ "resource" , Type.resource },
|
||||||
|
.{ "workshop" , Type.workshop },
|
||||||
|
.{ "bank" , Type.bank },
|
||||||
|
.{ "grand_exchange", Type.grand_exchange },
|
||||||
|
.{ "tasks_master" , Type.tasks_master },
|
||||||
|
.{ "santa_claus" , Type.santa_claus },
|
||||||
|
});
|
||||||
|
|
||||||
|
pub const fromString = Utils.fromString;
|
||||||
|
pub const toString = Utils.toString;
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const max_code_size = size: {
|
||||||
|
var max: usize = 0;
|
||||||
|
max = @max(max, Monster.max_code_size);
|
||||||
|
max = @max(max, Resource.max_code_size);
|
||||||
|
for (std.meta.fields(Craft.Skill)) |field| {
|
||||||
|
max = @max(max, Craft.Skill.toString(@enumFromInt(field.value)).len);
|
||||||
|
}
|
||||||
|
max = @max(max, "bank".len);
|
||||||
|
max = @max(max, "grand_exchange".len);
|
||||||
|
// TODO: max type 'tasks_master'
|
||||||
|
break :size max;
|
||||||
|
};
|
||||||
|
pub const Code = std.BoundedArray(u8, max_code_size);
|
||||||
|
|
||||||
|
type: Type,
|
||||||
|
code: Code,
|
||||||
|
};
|
||||||
|
|
||||||
|
name: Name,
|
||||||
|
skin: Skin,
|
||||||
position: Position,
|
position: Position,
|
||||||
content: ?MapContent,
|
content: ?Content,
|
||||||
|
|
||||||
pub fn parse(store: *Store, obj: json.ObjectMap, allocator: Allocator) !Map {
|
pub fn parse(store: *Store, obj: std.json.ObjectMap) !Map {
|
||||||
const content = json_utils.getObject(obj, "content");
|
_ = store;
|
||||||
|
|
||||||
const x = json_utils.getInteger(obj, "x") orelse return error.MissingProperty;
|
const name = try json_utils.getStringRequired(obj, "name");
|
||||||
const y = json_utils.getInteger(obj, "y") orelse return error.MissingProperty;
|
const skin = try json_utils.getStringRequired(obj, "skin");
|
||||||
|
const x = try json_utils.getIntegerRequired(obj, "x");
|
||||||
|
const y = try json_utils.getIntegerRequired(obj, "y");
|
||||||
|
|
||||||
|
var content: ?Content = null;
|
||||||
|
if (json_utils.getObject(obj, "content")) |content_obj| {
|
||||||
|
const content_code = try json_utils.getStringRequired(content_obj, "code");
|
||||||
|
const content_type = try json_utils.getStringRequired(content_obj, "type");
|
||||||
|
content = Content{
|
||||||
|
.code = try Content.Code.fromSlice(content_code),
|
||||||
|
.type = Content.Type.fromString(content_type) orelse return error.InvalidContentType
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return Map{
|
return Map{
|
||||||
.name = (try json_utils.dupeString(allocator, obj, "name")) orelse return error.MissingProperty,
|
.name = try Name.fromSlice(name),
|
||||||
.skin = (try json_utils.dupeString(allocator, obj, "skin")) orelse return error.MissingProperty,
|
.skin = try Skin.fromSlice(skin),
|
||||||
.position = Position.init(x, y),
|
.position = Position.init(x, y),
|
||||||
.content = if (content) |c| try MapContent.parse(store, c) else null
|
.content = content
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: Map, allocator: Allocator) void {
|
pub fn parseAndAppend(store: *Store, obj: std.json.ObjectMap) !Position {
|
||||||
allocator.free(self.name);
|
return store.appendOrUpdateMap(try parse(store, obj));
|
||||||
allocator.free(self.skin);
|
}
|
||||||
|
|
||||||
|
pub fn parseAndAppendObject(store: *Store, obj: std.json.ObjectMap) !Map {
|
||||||
|
const position = try parseAndAppend(store, obj);
|
||||||
|
return store.getMap(position).?.*;
|
||||||
}
|
}
|
||||||
|
@ -1,37 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const Store = @import("../store.zig");
|
|
||||||
const json_utils = @import("../json_utils.zig");
|
|
||||||
const json = std.json;
|
|
||||||
const Allocator = std.mem.Allocator;
|
|
||||||
const EnumStringUtils = @import("../enum_string_utils.zig").EnumStringUtils;
|
|
||||||
|
|
||||||
const MapContent = @This();
|
|
||||||
|
|
||||||
pub const Type = enum {
|
|
||||||
monster,
|
|
||||||
resource,
|
|
||||||
workshop,
|
|
||||||
bank,
|
|
||||||
grand_exchange,
|
|
||||||
tasks_master,
|
|
||||||
};
|
|
||||||
pub const TypeUtils = EnumStringUtils(Type, .{
|
|
||||||
.{ "monster" , Type.monster },
|
|
||||||
.{ "resource" , Type.resource },
|
|
||||||
.{ "workshop" , Type.workshop },
|
|
||||||
.{ "bank" , Type.bank },
|
|
||||||
.{ "grand_exchange", Type.grand_exchange },
|
|
||||||
.{ "tasks_master" , Type.tasks_master },
|
|
||||||
});
|
|
||||||
|
|
||||||
type: Type,
|
|
||||||
code_id: Store.CodeId,
|
|
||||||
|
|
||||||
pub fn parse(store: *Store, obj: json.ObjectMap) !MapContent {
|
|
||||||
const content_type = json_utils.getString(obj, "type") orelse return error.MissingProperty;
|
|
||||||
|
|
||||||
return MapContent{
|
|
||||||
.type = TypeUtils.fromString(content_type) orelse return error.InvalidContentType,
|
|
||||||
.code_id = (try store.getCodeIdJson(obj, "code")) orelse return error.MissingProperty
|
|
||||||
};
|
|
||||||
}
|
|
@ -1,19 +1,24 @@
|
|||||||
|
// zig fmt: off
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Store = @import("../store.zig");
|
const Store = @import("../store.zig");
|
||||||
const json_utils = @import("../json_utils.zig");
|
const json_utils = @import("../json_utils.zig");
|
||||||
const json = std.json;
|
const Character = @import("./character.zig");
|
||||||
const Allocator = std.mem.Allocator;
|
|
||||||
|
|
||||||
const DropRate = @import("./drop_rate.zig");
|
const DropRate = @import("./drop_rate.zig");
|
||||||
const DropRates = DropRate.DropRates;
|
|
||||||
|
const Element = Character.Element;
|
||||||
|
|
||||||
const Monster = @This();
|
const Monster = @This();
|
||||||
|
|
||||||
|
pub const Name = std.BoundedArray(u8, 16);
|
||||||
|
|
||||||
|
pub const max_code_size = 16;
|
||||||
|
pub const Code = std.BoundedArray(u8, max_code_size);
|
||||||
|
|
||||||
pub const ElementalStats = struct {
|
pub const ElementalStats = struct {
|
||||||
attack: i64,
|
attack: i64,
|
||||||
resistance: i64,
|
resistance: i64,
|
||||||
|
|
||||||
pub fn parse(object: json.ObjectMap, attack: []const u8, resistance: []const u8) !ElementalStats {
|
pub fn parse(object: std.json.ObjectMap, attack: []const u8, resistance: []const u8) !ElementalStats {
|
||||||
return ElementalStats{
|
return ElementalStats{
|
||||||
.attack = try json_utils.getIntegerRequired(object, attack),
|
.attack = try json_utils.getIntegerRequired(object, attack),
|
||||||
.resistance = try json_utils.getIntegerRequired(object, resistance),
|
.resistance = try json_utils.getIntegerRequired(object, resistance),
|
||||||
@ -21,60 +26,50 @@ pub const ElementalStats = struct {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
name: []u8,
|
pub const ElementalStatsArray = std.EnumArray(Element, ElementalStats);
|
||||||
code_id: Store.CodeId,
|
|
||||||
|
pub const Drops = std.BoundedArray(DropRate, 16);
|
||||||
|
|
||||||
|
name: Name,
|
||||||
|
code: Code,
|
||||||
level: u64,
|
level: u64,
|
||||||
hp: u64,
|
hp: u64,
|
||||||
|
elemental_stats: ElementalStatsArray,
|
||||||
min_gold: u64,
|
min_gold: u64,
|
||||||
max_gold: u64,
|
max_gold: u64,
|
||||||
|
drops: Drops,
|
||||||
|
|
||||||
fire: ElementalStats,
|
pub fn parse(store: *Store, obj: std.json.ObjectMap) !Monster {
|
||||||
earth: ElementalStats,
|
const name = try json_utils.getStringRequired(obj, "name");
|
||||||
water: ElementalStats,
|
const code = try json_utils.getStringRequired(obj, "code");
|
||||||
air: ElementalStats,
|
const level = try json_utils.getPositiveIntegerRequired(obj, "level");
|
||||||
|
const hp = try json_utils.getPositiveIntegerRequired(obj, "hp");
|
||||||
|
const min_gold = try json_utils.getPositiveIntegerRequired(obj, "min_gold");
|
||||||
|
const max_gold = try json_utils.getPositiveIntegerRequired(obj, "max_gold");
|
||||||
|
|
||||||
drops: DropRates,
|
const elemental_stats = ElementalStatsArray.init(.{
|
||||||
|
.water = try ElementalStats.parse(obj, "attack_water", "res_water"),
|
||||||
|
.fire = try ElementalStats.parse(obj, "attack_fire", "res_fire"),
|
||||||
|
.earth = try ElementalStats.parse(obj, "attack_earth", "res_earth"),
|
||||||
|
.air = try ElementalStats.parse(obj, "attack_air", "res_air"),
|
||||||
|
});
|
||||||
|
|
||||||
pub fn parse(store: *Store, obj: json.ObjectMap, allocator: Allocator) !Monster {
|
const drops_array = try json_utils.getArrayRequired(obj, "drops");
|
||||||
const drops_array = json_utils.getArray(obj, "drops") orelse return error.MissingProperty;
|
var drops = Drops.init(0) catch unreachable;
|
||||||
|
drops.len = @intCast(try DropRate.parseDrops(store, drops_array, &drops.buffer));
|
||||||
const min_gold = try json_utils.getIntegerRequired(obj, "min_gold");
|
|
||||||
if (min_gold < 0) {
|
|
||||||
return error.InvalidMinGold;
|
|
||||||
}
|
|
||||||
|
|
||||||
const max_gold = try json_utils.getIntegerRequired(obj, "max_gold");
|
|
||||||
if (max_gold < 0) {
|
|
||||||
return error.InvalidMaxGold;
|
|
||||||
}
|
|
||||||
|
|
||||||
const level = try json_utils.getIntegerRequired(obj, "level");
|
|
||||||
if (level < 0) {
|
|
||||||
return error.InvalidLevel;
|
|
||||||
}
|
|
||||||
|
|
||||||
const hp = try json_utils.getIntegerRequired(obj, "hp");
|
|
||||||
if (hp < 0) {
|
|
||||||
return error.InvalidHp;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Monster{
|
return Monster{
|
||||||
.name = try json_utils.dupeStringRequired(allocator, obj, "name"),
|
.name = try Name.fromSlice(name),
|
||||||
.code_id = (try store.getCodeIdJson(obj, "code")) orelse return error.MissingProperty,
|
.code = try Code.fromSlice(code),
|
||||||
.level = @intCast(level),
|
.level = level,
|
||||||
.hp = @intCast(hp),
|
.hp = hp,
|
||||||
|
.elemental_stats = elemental_stats,
|
||||||
.fire = try ElementalStats.parse(obj, "attack_fire" , "res_fire" ),
|
.min_gold = min_gold,
|
||||||
.earth = try ElementalStats.parse(obj, "attack_earth", "res_earth"),
|
.max_gold = max_gold,
|
||||||
.water = try ElementalStats.parse(obj, "attack_water", "res_water"),
|
.drops = drops
|
||||||
.air = try ElementalStats.parse(obj, "attack_air" , "res_air" ),
|
|
||||||
|
|
||||||
.min_gold = @intCast(min_gold),
|
|
||||||
.max_gold = @intCast(max_gold),
|
|
||||||
.drops = try DropRate.parseList(store, drops_array)
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: Monster, allocator: Allocator) void {
|
pub fn parseAndAppend(store: *Store, obj: std.json.ObjectMap) !Store.Id {
|
||||||
allocator.free(self.name);
|
return store.monsters.appendOrUpdate(try parse(store, obj));
|
||||||
}
|
}
|
||||||
|
@ -5,16 +5,21 @@ x: i64,
|
|||||||
y: i64,
|
y: i64,
|
||||||
|
|
||||||
pub fn init(x: i64, y: i64) Position {
|
pub fn init(x: i64, y: i64) Position {
|
||||||
return Position{
|
return Position{ .x = x, .y = y };
|
||||||
.x = x,
|
}
|
||||||
.y = y
|
|
||||||
};
|
pub fn zero() Position {
|
||||||
|
return init(0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn eql(self: Position, other: Position) bool {
|
pub fn eql(self: Position, other: Position) bool {
|
||||||
return self.x == other.x and self.y == other.y;
|
return self.x == other.x and self.y == other.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn subtract(self: Position, other: Position) Position {
|
||||||
|
return init(self.x - other.x, self.y - other.y);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn format(
|
pub fn format(
|
||||||
self: Position,
|
self: Position,
|
||||||
comptime fmt: []const u8,
|
comptime fmt: []const u8,
|
||||||
@ -24,5 +29,5 @@ pub fn format(
|
|||||||
_ = fmt;
|
_ = fmt;
|
||||||
_ = options;
|
_ = options;
|
||||||
|
|
||||||
try writer.print("{{ {}, {} }}", .{self.x, self.y});
|
try writer.print("Position{{ {}, {} }}", .{ self.x, self.y });
|
||||||
}
|
}
|
@ -1,12 +1,9 @@
|
|||||||
|
// zig fmt: off
|
||||||
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 json_utils = @import("../json_utils.zig");
|
const json_utils = @import("../json_utils.zig");
|
||||||
const json = std.json;
|
|
||||||
const Allocator = std.mem.Allocator;
|
|
||||||
|
|
||||||
const DropRate = @import("./drop_rate.zig");
|
const DropRate = @import("./drop_rate.zig");
|
||||||
const DropRates = DropRate.DropRates;
|
const EnumStringUtils = @import("../enum_string_utils.zig").EnumStringUtils;
|
||||||
|
|
||||||
const Resource = @This();
|
const Resource = @This();
|
||||||
|
|
||||||
@ -14,38 +11,51 @@ pub const Skill = enum {
|
|||||||
mining,
|
mining,
|
||||||
woodcutting,
|
woodcutting,
|
||||||
fishing,
|
fishing,
|
||||||
};
|
alchemy,
|
||||||
pub const SkillUtils = EnumStringUtils(Skill, .{
|
|
||||||
.{ "mining" , .mining },
|
|
||||||
.{ "woodcutting", .woodcutting },
|
|
||||||
.{ "fishing" , .fishing },
|
|
||||||
});
|
|
||||||
|
|
||||||
name: []u8,
|
const Utils = EnumStringUtils(Skill, .{
|
||||||
code_id: Store.CodeId,
|
.{ "mining" , Skill.mining },
|
||||||
|
.{ "woodcutting", Skill.woodcutting },
|
||||||
|
.{ "fishing" , Skill.fishing },
|
||||||
|
.{ "alchemy" , Skill.alchemy },
|
||||||
|
});
|
||||||
|
|
||||||
|
pub const toString = Utils.toString;
|
||||||
|
pub const fromString = Utils.fromString;
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Name = std.BoundedArray(u8, 32);
|
||||||
|
|
||||||
|
pub const max_code_size = 32;
|
||||||
|
pub const Code = std.BoundedArray(u8, max_code_size);
|
||||||
|
|
||||||
|
pub const Drops = std.BoundedArray(DropRate, 16);
|
||||||
|
|
||||||
|
name: Name,
|
||||||
|
code: Code,
|
||||||
skill: Skill,
|
skill: Skill,
|
||||||
level: u64,
|
level: u64,
|
||||||
drops: DropRates,
|
drops: Drops,
|
||||||
|
|
||||||
pub fn parse(store: *Store, obj: json.ObjectMap, allocator: Allocator) !Resource {
|
pub fn parse(store: *Store, obj: std.json.ObjectMap) !Resource {
|
||||||
const drops_array = json_utils.getArray(obj, "drops") orelse return error.MissingProperty;
|
const name = try json_utils.getStringRequired(obj, "name");
|
||||||
|
const code = try json_utils.getStringRequired(obj, "code");
|
||||||
|
const level = try json_utils.getPositiveIntegerRequired(obj, "level");
|
||||||
|
const skill = try json_utils.getStringRequired(obj, "skill");
|
||||||
|
|
||||||
const level = try json_utils.getIntegerRequired(obj, "level");
|
const drops_array = try json_utils.getArrayRequired(obj, "drops");
|
||||||
if (level < 0) {
|
var drops = Drops.init(0) catch unreachable;
|
||||||
return error.InvalidLevel;
|
drops.len = @intCast(try DropRate.parseDrops(store, drops_array, &drops.buffer));
|
||||||
}
|
|
||||||
|
|
||||||
const skill_str = try json_utils.getStringRequired(obj, "skill");
|
|
||||||
|
|
||||||
return Resource{
|
return Resource{
|
||||||
.name = try json_utils.dupeStringRequired(allocator, obj, "name"),
|
.name = try Name.fromSlice(name),
|
||||||
.code_id = (try store.getCodeIdJson(obj, "code")) orelse return error.MissingProperty,
|
.code = try Code.fromSlice(code),
|
||||||
.level = @intCast(level),
|
.level = level,
|
||||||
.skill = SkillUtils.fromString(skill_str) orelse return error.InvalidSkill,
|
.skill = Skill.fromString(skill) orelse return error.InvalidSkill,
|
||||||
.drops = try DropRate.parseList(store, drops_array)
|
.drops = drops
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: Resource, allocator: Allocator) void {
|
pub fn parseAndAppend(store: *Store, obj: std.json.ObjectMap) !Store.Id {
|
||||||
allocator.free(self.name);
|
return store.resources.appendOrUpdate(try parse(store, obj));
|
||||||
}
|
}
|
||||||
|
142
api/schemas/simple_item.zig
Normal file
142
api/schemas/simple_item.zig
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const json_utils = @import("../json_utils.zig");
|
||||||
|
const Store = @import("../store.zig");
|
||||||
|
|
||||||
|
const assert = std.debug.assert;
|
||||||
|
|
||||||
|
const SimpleItem = @This();
|
||||||
|
|
||||||
|
id: Store.Id,
|
||||||
|
quantity: u64,
|
||||||
|
|
||||||
|
pub fn init(id: Store.Id, quantity: u64) SimpleItem {
|
||||||
|
return SimpleItem{ .id = id, .quantity = quantity };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse(store: *Store, slot_obj: std.json.ObjectMap) !?SimpleItem {
|
||||||
|
const code = try json_utils.getStringRequired(slot_obj, "code");
|
||||||
|
if (code.len == 0) return null;
|
||||||
|
|
||||||
|
const quantity = try json_utils.getIntegerRequired(slot_obj, "quantity");
|
||||||
|
if (quantity < 0) return error.InvalidQuantity;
|
||||||
|
|
||||||
|
return SimpleItem{
|
||||||
|
.id = try store.items.getOrReserveId(code),
|
||||||
|
.quantity = @intCast(quantity),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn BoundedArray(comptime slot_count: u32) type {
|
||||||
|
const Items = std.BoundedArray(SimpleItem, slot_count);
|
||||||
|
|
||||||
|
return struct {
|
||||||
|
items: Items,
|
||||||
|
|
||||||
|
pub fn init() @This() {
|
||||||
|
return @This(){ .items = Items.init(0) catch unreachable };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse(store: *Store, slots_array: std.json.Array) !@This() {
|
||||||
|
var slots = Items.init(0) catch unreachable;
|
||||||
|
|
||||||
|
for (slots_array.items) |slot_value| {
|
||||||
|
const slot_obj = json_utils.asObject(slot_value) orelse return error.InvalidType;
|
||||||
|
|
||||||
|
if (try SimpleItem.parse(store, slot_obj)) |slot| {
|
||||||
|
try slots.append(slot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return @This(){ .items = slots };
|
||||||
|
}
|
||||||
|
|
||||||
|
fn findSlotIndex(self: *const @This(), id: Store.Id) ?usize {
|
||||||
|
for (0.., self.items.slice()) |i, *slot| {
|
||||||
|
if (slot.id == id) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn findSlot(self: *@This(), id: Store.Id) ?*SimpleItem {
|
||||||
|
if (self.findSlotIndex(id)) |index| {
|
||||||
|
return &self.items.buffer[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove(self: *@This(), id: Store.Id, quantity: u64) void {
|
||||||
|
const slot_index = self.findSlotIndex(id) orelse unreachable;
|
||||||
|
const slot = self.items.get(slot_index);
|
||||||
|
assert(slot.quantity >= quantity);
|
||||||
|
|
||||||
|
slot.quantity -= quantity;
|
||||||
|
if (slot.quantity == 0) {
|
||||||
|
self.items.swapRemove(slot_index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add(self: *@This(), id: Store.Id, quantity: u64) !void {
|
||||||
|
if (quantity == 0) return;
|
||||||
|
|
||||||
|
if (self.findSlot(id)) |slot| {
|
||||||
|
slot.quantity += quantity;
|
||||||
|
} else {
|
||||||
|
try self.items.append(SimpleItem.init(id, quantity));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn addSlice(self: *@This(), items: []const SimpleItem) void {
|
||||||
|
for (items) |item| {
|
||||||
|
self.add(item.id, item.quantity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn removeSlice(self: *@This(), items: []const SimpleItem) void {
|
||||||
|
for (items) |item| {
|
||||||
|
self.remove(item.id, item.quantity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getQuantity(self: *const @This(), id: Store.Id) u64 {
|
||||||
|
if (self.findSlotIndex(id)) |index| {
|
||||||
|
return self.items.get(index).quantity;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn totalQuantity(self: *const @This()) u64 {
|
||||||
|
var count: u64 = 0;
|
||||||
|
for (self.items.constSlice()) |slot| {
|
||||||
|
count += slot.quantity;
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn slice(self: *@This()) []SimpleItem {
|
||||||
|
return self.items.slice();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn format(
|
||||||
|
self: @This(),
|
||||||
|
comptime fmt: []const u8,
|
||||||
|
options: std.fmt.FormatOptions,
|
||||||
|
writer: anytype,
|
||||||
|
) !void {
|
||||||
|
_ = fmt;
|
||||||
|
_ = options;
|
||||||
|
|
||||||
|
try writer.print("{s}{{ ", .{@typeName(@This())});
|
||||||
|
|
||||||
|
for (self.items.slice()) |item| {
|
||||||
|
try writer.print("{}, ", .{item});
|
||||||
|
}
|
||||||
|
|
||||||
|
try writer.writeAll("}");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
@ -1,22 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const Store = @import("../store.zig");
|
|
||||||
const json_utils = @import("../json_utils.zig");
|
|
||||||
const json = std.json;
|
|
||||||
const Allocator = std.mem.Allocator;
|
|
||||||
|
|
||||||
const Item = @import("./item.zig");
|
|
||||||
|
|
||||||
const SingleItem = @This();
|
|
||||||
|
|
||||||
item: Item,
|
|
||||||
// TODO: Grand exchange
|
|
||||||
|
|
||||||
pub fn parse(store: *Store, obj: json.ObjectMap, allocator: Allocator) !SingleItem {
|
|
||||||
const item_obj = json_utils.getObject(obj, "item") orelse return error.MissingProperty;
|
|
||||||
const ge_obj = json_utils.getObject(obj, "ge") orelse return error.MissingProperty;
|
|
||||||
_ = ge_obj;
|
|
||||||
|
|
||||||
return SingleItem{
|
|
||||||
.item = try Item.parse(store, item_obj, allocator),
|
|
||||||
};
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
const EnumStringUtils = @import("../enum_string_utils.zig").EnumStringUtils;
|
|
||||||
|
|
||||||
pub const Skill = enum {
|
|
||||||
weaponcrafting,
|
|
||||||
gearcrafting,
|
|
||||||
jewelrycrafting,
|
|
||||||
cooking,
|
|
||||||
woodcutting,
|
|
||||||
mining,
|
|
||||||
};
|
|
||||||
pub const SkillUtils = EnumStringUtils(Skill, .{
|
|
||||||
.{ "weaponcrafting" , Skill.weaponcrafting },
|
|
||||||
.{ "gearcrafting" , Skill.gearcrafting },
|
|
||||||
.{ "jewelrycrafting", Skill.jewelrycrafting },
|
|
||||||
.{ "cooking" , Skill.cooking },
|
|
||||||
.{ "woodcutting" , Skill.woodcutting },
|
|
||||||
.{ "mining" , Skill.mining },
|
|
||||||
});
|
|
@ -1,27 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const Store = @import("../store.zig");
|
|
||||||
const json_utils = @import("../json_utils.zig");
|
|
||||||
const json = std.json;
|
|
||||||
const Allocator = std.mem.Allocator;
|
|
||||||
|
|
||||||
const Cooldown = @import("./cooldown.zig");
|
|
||||||
const Character = @import("./character.zig");
|
|
||||||
const SkillInfo = @import("./skill_info.zig");
|
|
||||||
|
|
||||||
const SkillData = @This();
|
|
||||||
|
|
||||||
cooldown: Cooldown,
|
|
||||||
details: SkillInfo,
|
|
||||||
character: Character,
|
|
||||||
|
|
||||||
pub fn parse(store: *Store, obj: json.ObjectMap, allocator: Allocator) !SkillData {
|
|
||||||
const cooldown = json_utils.getObject(obj, "cooldown") orelse return error.MissingProperty;
|
|
||||||
const details = json_utils.getObject(obj, "details") orelse return error.MissingProperty;
|
|
||||||
const character = json_utils.getObject(obj, "character") orelse return error.MissingProperty;
|
|
||||||
|
|
||||||
return SkillData{
|
|
||||||
.cooldown = try Cooldown.parse(cooldown),
|
|
||||||
.details = try SkillInfo.parse(store, details),
|
|
||||||
.character = try Character.parse(store, character, allocator)
|
|
||||||
};
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const Store = @import("../store.zig");
|
|
||||||
const BoundedSlotsArray = @import("./slot_array.zig").BoundedSlotsArray;
|
|
||||||
const json_utils = @import("../json_utils.zig");
|
|
||||||
const json = std.json;
|
|
||||||
|
|
||||||
const Items = BoundedSlotsArray(8);
|
|
||||||
|
|
||||||
const SkillInfo = @This();
|
|
||||||
|
|
||||||
xp: u64,
|
|
||||||
items: Items,
|
|
||||||
|
|
||||||
pub fn parse(store: *Store, obj: json.ObjectMap) !SkillInfo {
|
|
||||||
const items = json_utils.getArray(obj, "items") orelse return error.MissingProperty;
|
|
||||||
const xp = try json_utils.getIntegerRequired(obj, "xp");
|
|
||||||
if (xp < 0) {
|
|
||||||
return error.InvalidXp;
|
|
||||||
}
|
|
||||||
|
|
||||||
return SkillInfo{
|
|
||||||
.xp = @intCast(xp),
|
|
||||||
.items = try Items.parse(store, items),
|
|
||||||
};
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
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,108 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const json_utils = @import("../json_utils.zig");
|
|
||||||
const Store = @import("../store.zig");
|
|
||||||
const assert = std.debug.assert;
|
|
||||||
const json = std.json;
|
|
||||||
|
|
||||||
const ItemQuantity = @import("./item_quantity.zig");
|
|
||||||
|
|
||||||
pub fn BoundedSlotsArray(comptime slot_count: u32) type {
|
|
||||||
const Slots = std.BoundedArray(ItemQuantity, slot_count);
|
|
||||||
const CodeId = Store.CodeId;
|
|
||||||
|
|
||||||
return struct {
|
|
||||||
slots: Slots,
|
|
||||||
|
|
||||||
pub fn init() @This() {
|
|
||||||
return @This(){
|
|
||||||
.slots = Slots.init(0) catch unreachable
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse(api: *Store, slots_array: json.Array) !@This() {
|
|
||||||
var slots = Slots.init(0) catch unreachable;
|
|
||||||
|
|
||||||
for (slots_array.items) |slot_value| {
|
|
||||||
const slot_obj = json_utils.asObject(slot_value) orelse return error.InvalidType;
|
|
||||||
|
|
||||||
if (try ItemQuantity.parse(api, slot_obj)) |slot| {
|
|
||||||
try slots.append(slot);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return @This(){ .slots = slots };
|
|
||||||
}
|
|
||||||
|
|
||||||
fn findSlotIndex(self: *const @This(), id: CodeId) ?usize {
|
|
||||||
for (0.., self.slots.slice()) |i, *slot| {
|
|
||||||
if (slot.id == id) {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn findSlot(self: *@This(), id: CodeId) ?*ItemQuantity {
|
|
||||||
if (self.findSlotIndex(id)) |index| {
|
|
||||||
return &self.slots.buffer[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn remove(self: *@This(), id: CodeId, quantity: u64) void {
|
|
||||||
const slot_index = self.findSlotIndex(id) orelse unreachable;
|
|
||||||
const slot = self.slots.get(slot_index);
|
|
||||||
assert(slot.quantity >= quantity);
|
|
||||||
|
|
||||||
slot.quantity -= quantity;
|
|
||||||
if (slot.quantity == 0) {
|
|
||||||
self.slots.swapRemove(slot_index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add(self: *@This(), id: CodeId, quantity: u64) !void {
|
|
||||||
if (quantity == 0) return;
|
|
||||||
|
|
||||||
if (self.findSlot(id)) |slot| {
|
|
||||||
slot.quantity += quantity;
|
|
||||||
} else {
|
|
||||||
try self.slots.append(ItemQuantity.init(id, quantity));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn addSlice(self: *@This(), items: []const ItemQuantity) void {
|
|
||||||
for (items) |item| {
|
|
||||||
self.add(item.id, item.quantity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn removeSlice(self: *@This(), items: []const ItemQuantity) void {
|
|
||||||
for (items) |item| {
|
|
||||||
self.remove(item.id, item.quantity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn getQuantity(self: *const @This(), id: CodeId) u64 {
|
|
||||||
if (self.findSlotIndex(id)) |index| {
|
|
||||||
return self.slots.get(index).quantity;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn totalQuantity(self: *const @This()) u64 {
|
|
||||||
var count: u64 = 0;
|
|
||||||
for (self.slots.constSlice()) |slot| {
|
|
||||||
count += slot.quantity;
|
|
||||||
}
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn slice(self: *@This()) []ItemQuantity {
|
|
||||||
return self.slots.slice();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,32 +1,102 @@
|
|||||||
|
// zig fmt: off
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Store = @import("../store.zig");
|
const Store = @import("../store.zig");
|
||||||
const json_utils = @import("../json_utils.zig");
|
const json_utils = @import("../json_utils.zig");
|
||||||
|
const parseDateTime = @import("../date_time/parse.zig").parseDateTime;
|
||||||
const json = std.json;
|
const json = std.json;
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
const ServerStatus = @This();
|
const ServerStatus = @This();
|
||||||
|
|
||||||
allocator: Allocator,
|
pub const Status = std.BoundedArray(u8, 32);
|
||||||
status: []const u8,
|
pub const Version = std.BoundedArray(u8, 16);
|
||||||
version: []const u8,
|
pub const Date = std.BoundedArray(u8, 10);
|
||||||
characters_online: u64,
|
pub const Announcement = struct {
|
||||||
|
pub const Message = std.BoundedArray(u8, 64);
|
||||||
|
|
||||||
pub fn parse(store: *Store, object: json.ObjectMap, allocator: Allocator) !ServerStatus {
|
message: Message,
|
||||||
|
created_at: f64
|
||||||
|
};
|
||||||
|
pub const Announcements = std.BoundedArray(Announcement, 16);
|
||||||
|
|
||||||
|
status: Status,
|
||||||
|
version: Version,
|
||||||
|
characters_online: u64,
|
||||||
|
max_level: u64,
|
||||||
|
server_time: f64,
|
||||||
|
last_wipe: Date,
|
||||||
|
next_wipe: Date,
|
||||||
|
announcements: Announcements,
|
||||||
|
|
||||||
|
fn parseAnnouncements(array: json.Array) !Announcements {
|
||||||
|
var announcements = Announcements.init(0) catch unreachable;
|
||||||
|
|
||||||
|
for (array.items) |item| {
|
||||||
|
const obj = json_utils.asObject(item) orelse return error.InvalidAnnouncement;
|
||||||
|
const message = try json_utils.getStringRequired(obj, "message");
|
||||||
|
const created_at = try json_utils.getStringRequired(obj, "created_at");
|
||||||
|
|
||||||
|
try announcements.append(Announcement{
|
||||||
|
.message = try Announcement.Message.fromSlice(message),
|
||||||
|
.created_at = parseDateTime(created_at) orelse return error.InvalidDataTime,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return announcements;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse(store: *Store, obj: json.ObjectMap) !ServerStatus {
|
||||||
_ = store;
|
_ = store;
|
||||||
const characters_online = json_utils.getInteger(object, "characters_online") orelse return error.MissingProperty;
|
|
||||||
|
const characters_online = json_utils.getInteger(obj, "characters_online") orelse return error.MissingProperty;
|
||||||
if (characters_online < 0) {
|
if (characters_online < 0) {
|
||||||
return error.InvalidCharactersOnline;
|
return error.InvalidCharactersOnline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const max_level = try json_utils.getIntegerRequired(obj, "max_level");
|
||||||
|
if (max_level < 0) {
|
||||||
|
return error.InvalidMaxLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
const status = try json_utils.getStringRequired(obj, "status");
|
||||||
|
const version = try json_utils.getStringRequired(obj, "version");
|
||||||
|
const last_wipe = try json_utils.getStringRequired(obj, "last_wipe");
|
||||||
|
const next_wipe = try json_utils.getStringRequired(obj, "next_wipe");
|
||||||
|
const server_time = try json_utils.getStringRequired(obj, "server_time");
|
||||||
|
|
||||||
|
const announcements = json_utils.getArray(obj, "announcements") orelse return error.MissingProperty;
|
||||||
|
|
||||||
return ServerStatus{
|
return ServerStatus{
|
||||||
.allocator = allocator,
|
|
||||||
.characters_online = @intCast(characters_online),
|
.characters_online = @intCast(characters_online),
|
||||||
.status = (try json_utils.dupeString(allocator, object, "status")) orelse return error.MissingStatus,
|
.status = try Status.fromSlice(status),
|
||||||
.version = (try json_utils.dupeString(allocator, object, "version")) orelse return error.MissingVersion
|
.version = try Version.fromSlice(version),
|
||||||
|
.next_wipe = try Date.fromSlice(next_wipe),
|
||||||
|
.last_wipe = try Date.fromSlice(last_wipe),
|
||||||
|
.server_time = parseDateTime(server_time) orelse return error.InvalidaDateTime,
|
||||||
|
.announcements = try parseAnnouncements(announcements),
|
||||||
|
.max_level = @intCast(max_level)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: ServerStatus) void {
|
pub fn format(
|
||||||
self.allocator.free(self.status);
|
self: ServerStatus,
|
||||||
self.allocator.free(self.version);
|
comptime fmt: []const u8,
|
||||||
|
options: std.fmt.FormatOptions,
|
||||||
|
writer: anytype,
|
||||||
|
) !void {
|
||||||
|
_ = fmt;
|
||||||
|
_ = options;
|
||||||
|
|
||||||
|
try writer.print("{s}{{ ", .{ @typeName(ServerStatus) });
|
||||||
|
|
||||||
|
try writer.print(".status = \"{s}\", ", .{ self.status.slice() });
|
||||||
|
try writer.print(".version = \"{s}\", ", .{ self.version.slice() });
|
||||||
|
try writer.print(".characters_online = {}, ", .{ self.characters_online });
|
||||||
|
try writer.print(".max_level = {}, ", .{ self.max_level });
|
||||||
|
try writer.print(".server_time = {}, ", .{ self.server_time });
|
||||||
|
try writer.print(".last_wipe = \"{s}\", ", .{ self.last_wipe.slice() });
|
||||||
|
try writer.print(".next_wipe = \"{s}\", ", .{ self.next_wipe.slice() });
|
||||||
|
try writer.writeAll(".announcements = .{ ... }, ");
|
||||||
|
|
||||||
|
try writer.writeAll("}");
|
||||||
}
|
}
|
||||||
|
@ -1,36 +1,45 @@
|
|||||||
|
// zig fmt: off
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Store = @import("../store.zig");
|
const Store = @import("../store.zig");
|
||||||
const json_utils = @import("../json_utils.zig");
|
const json_utils = @import("../json_utils.zig");
|
||||||
const EnumStringUtils = @import("../enum_string_utils.zig").EnumStringUtils;
|
const EnumStringUtils = @import("../enum_string_utils.zig").EnumStringUtils;
|
||||||
const json = std.json;
|
|
||||||
|
|
||||||
const Task = @This();
|
const Task = @This();
|
||||||
|
|
||||||
pub const Type = enum {
|
pub const Type = enum {
|
||||||
monsters,
|
monsters,
|
||||||
resources,
|
resources,
|
||||||
crafts
|
crafts,
|
||||||
};
|
|
||||||
pub const TypeUtils = EnumStringUtils(Type, .{
|
|
||||||
.{ "monsters" , Type.monsters },
|
|
||||||
.{ "resources", Type.resources },
|
|
||||||
.{ "crafts" , Type.crafts },
|
|
||||||
});
|
|
||||||
|
|
||||||
code_id: Store.CodeId,
|
const Utils = EnumStringUtils(Type, .{
|
||||||
|
.{ "monsters" , Type.monsters },
|
||||||
|
.{ "resources", Type.resources },
|
||||||
|
.{ "crafts" , Type.crafts },
|
||||||
|
});
|
||||||
|
pub const fromString = Utils.fromString;
|
||||||
|
pub const toString = Utils.toString;
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Code = std.BoundedArray(u8, 32);
|
||||||
|
|
||||||
|
code: Code,
|
||||||
type: Type,
|
type: Type,
|
||||||
total: u64,
|
total: u64,
|
||||||
|
|
||||||
pub fn parse(store: *Store, obj: json.ObjectMap) !Task {
|
pub fn parse(store: *Store, obj: std.json.ObjectMap) !Task {
|
||||||
|
_ = store;
|
||||||
|
const code = try json_utils.getStringRequired(obj, "code");
|
||||||
const task_type = try json_utils.getStringRequired(obj, "type");
|
const task_type = try json_utils.getStringRequired(obj, "type");
|
||||||
const total = try json_utils.getIntegerRequired(obj, "total");
|
const total = try json_utils.getPositiveIntegerRequired(obj, "total");
|
||||||
if (total < 0) {
|
|
||||||
return error.InvalidTaskTotal;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Task{
|
return Task{
|
||||||
.code_id = (try store.getCodeIdJson(obj, "code")) orelse return error.MissingProperty,
|
.code = try Code.fromSlice(code),
|
||||||
.type = TypeUtils.fromString(task_type) orelse return error.InvalidTaskType,
|
.type = Type.fromString(task_type) orelse return error.InvalidTaskType,
|
||||||
.total = @intCast(total)
|
.total = total
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn parseAndAppend(store: *Store, obj: std.json.ObjectMap) !Store.Id {
|
||||||
|
return store.tasks.appendOrUpdate(try parse(store, obj));
|
||||||
|
}
|
||||||
|
@ -1,27 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const Store = @import("../store.zig");
|
|
||||||
const json_utils = @import("../json_utils.zig");
|
|
||||||
const json = std.json;
|
|
||||||
const Allocator = std.mem.Allocator;
|
|
||||||
|
|
||||||
const Cooldown = @import("./cooldown.zig");
|
|
||||||
const Character = @import("./character.zig");
|
|
||||||
const Task = @import("./task.zig");
|
|
||||||
|
|
||||||
const TaskData = @This();
|
|
||||||
|
|
||||||
cooldown: Cooldown,
|
|
||||||
character: Character,
|
|
||||||
task: Task,
|
|
||||||
|
|
||||||
pub fn parse(store: *Store, obj: json.ObjectMap, allocator: Allocator) !TaskData {
|
|
||||||
const cooldown = json_utils.getObject(obj, "cooldown") orelse return error.MissingProperty;
|
|
||||||
const character = json_utils.getObject(obj, "character") orelse return error.MissingProperty;
|
|
||||||
const task = json_utils.getObject(obj, "task") orelse return error.MissingProperty;
|
|
||||||
|
|
||||||
return TaskData{
|
|
||||||
.cooldown = try Cooldown.parse(cooldown),
|
|
||||||
.character = try Character.parse(store, character, allocator),
|
|
||||||
.task = try Task.parse(store, task)
|
|
||||||
};
|
|
||||||
}
|
|
@ -1,27 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const Store = @import("../store.zig");
|
|
||||||
const json_utils = @import("../json_utils.zig");
|
|
||||||
const json = std.json;
|
|
||||||
const Allocator = std.mem.Allocator;
|
|
||||||
|
|
||||||
const Cooldown = @import("./cooldown.zig");
|
|
||||||
const Character = @import("./character.zig");
|
|
||||||
const ItemQuantity = @import("./item_quantity.zig");
|
|
||||||
|
|
||||||
const TaskRewardData = @This();
|
|
||||||
|
|
||||||
cooldown: Cooldown,
|
|
||||||
character: Character,
|
|
||||||
reward: ItemQuantity,
|
|
||||||
|
|
||||||
pub fn parse(store: *Store, obj: json.ObjectMap, allocator: Allocator) !TaskRewardData {
|
|
||||||
const cooldown = json_utils.getObject(obj, "cooldown") orelse return error.MissingProperty;
|
|
||||||
const character = json_utils.getObject(obj, "character") orelse return error.MissingProperty;
|
|
||||||
const task = json_utils.getObject(obj, "task") orelse return error.MissingProperty;
|
|
||||||
|
|
||||||
return TaskRewardData{
|
|
||||||
.cooldown = try Cooldown.parse(cooldown),
|
|
||||||
.character = try Character.parse(store, character, allocator),
|
|
||||||
.reward = (try ItemQuantity.parse(store, task)) orelse return error.MissinReward
|
|
||||||
};
|
|
||||||
}
|
|
1423
api/server.zig
1423
api/server.zig
File diff suppressed because it is too large
Load Diff
28
api/stb_image/root.zig
Normal file
28
api/stb_image/root.zig
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const assert = std.debug.assert;
|
||||||
|
|
||||||
|
extern fn zig_stbi_load_from_memory(buffer: [*]const u8, len: i32, x: ?*i32, y: ?*i32, comp: ?*i32, req_comp: i32) callconv(.C) ?[*]u8;
|
||||||
|
extern fn zig_stbi_image_free(buffer: ?*anyopaque) callconv(.C) void;
|
||||||
|
|
||||||
|
pub const Image = struct {
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
rgba: []u8,
|
||||||
|
|
||||||
|
pub fn deinit(self: Image) void {
|
||||||
|
zig_stbi_image_free(self.rgba.ptr);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn load(buffer: []const u8) !Image {
|
||||||
|
var width: i32 = 0;
|
||||||
|
var height: i32 = 0;
|
||||||
|
const image_rgba = zig_stbi_load_from_memory(buffer.ptr, @intCast(buffer.len), &width, &height, null, 4);
|
||||||
|
if (image_rgba == null) {
|
||||||
|
return error.PNGDecode;
|
||||||
|
}
|
||||||
|
errdefer zig_stbi_image_free(image_rgba);
|
||||||
|
|
||||||
|
const byte_count: u32 = @intCast(width * height * 4);
|
||||||
|
return Image{ .width = @intCast(width), .height = @intCast(height), .rgba = image_rgba.?[0..byte_count] };
|
||||||
|
}
|
23
api/stb_image/stb_image.c
Normal file
23
api/stb_image/stb_image.c
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
extern void *stb_image_zig_malloc(uint32_t amount);
|
||||||
|
extern void *stb_image_zig_realloc(void *mem, uint32_t amount);
|
||||||
|
extern void stb_image_zig_free(void *mem);
|
||||||
|
|
||||||
|
#define STB_IMAGE_IMPLEMENTATION
|
||||||
|
#define STBI_NO_STDIO
|
||||||
|
#define STBI_ONLY_PNG
|
||||||
|
#define STB_IMAGE_STATIC
|
||||||
|
#include "stb_image.h"
|
||||||
|
|
||||||
|
void zig_stbi_image_free(void *retval_from_stbi_load)
|
||||||
|
{
|
||||||
|
return stbi_image_free(retval_from_stbi_load);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
stbi_uc *zig_stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp)
|
||||||
|
{
|
||||||
|
return stbi_load_from_memory(buffer, len, x, y, comp, req_comp);
|
||||||
|
}
|
7988
api/stb_image/stb_image.h
Normal file
7988
api/stb_image/stb_image.h
Normal file
File diff suppressed because it is too large
Load Diff
818
api/store.zig
818
api/store.zig
@ -1,422 +1,538 @@
|
|||||||
|
// zig fmt: off
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const json_utils = @import("json_utils.zig");
|
|
||||||
const Server = @import("./server.zig");
|
|
||||||
const s2s = @import("s2s");
|
const s2s = @import("s2s");
|
||||||
const json = std.json;
|
const Item = @import("./schemas/item.zig");
|
||||||
const Allocator = std.mem.Allocator;
|
const Character = @import("./schemas/character.zig");
|
||||||
const assert = std.debug.assert;
|
const Task = @import("./schemas/task.zig");
|
||||||
|
const Monster = @import("./schemas/monster.zig");
|
||||||
|
const Resource = @import("./schemas/resource.zig");
|
||||||
|
const Map = @import("./schemas/map.zig");
|
||||||
|
const Position = @import("./schemas/position.zig");
|
||||||
|
|
||||||
|
const Skin = Character.Skin;
|
||||||
|
|
||||||
|
pub const Id = usize;
|
||||||
|
|
||||||
const Store = @This();
|
const Store = @This();
|
||||||
|
|
||||||
const Character = @import("./schemas/character.zig");
|
const max_character_images = std.meta.fields(Skin).len;
|
||||||
const Item = @import("./schemas/item.zig");
|
const max_character_image_code = size: {
|
||||||
const Position = @import("./position.zig");
|
var size = Skin.toString(.men1).len;
|
||||||
const Map = @import("./schemas/map.zig");
|
for (std.meta.fields(Skin)) |field| {
|
||||||
const Resource = @import("./schemas/resource.zig");
|
size = @max(size, Skin.toString(@enumFromInt(field.value)).len);
|
||||||
const Monster = @import("./schemas/monster.zig");
|
|
||||||
const DropRate = @import("./schemas/drop_rate.zig");
|
|
||||||
|
|
||||||
pub const CodeId = u16;
|
|
||||||
|
|
||||||
const Characters = std.ArrayList(Character);
|
|
||||||
const ItemsMap = std.StringHashMap(Item);
|
|
||||||
const MapsMap = std.AutoHashMap(Position, Map);
|
|
||||||
const ResourcesMap = std.StringHashMap(Resource);
|
|
||||||
const MonstersMap = std.StringHashMap(Monster);
|
|
||||||
|
|
||||||
allocator: Allocator,
|
|
||||||
codes: std.ArrayList([]u8),
|
|
||||||
characters: Characters,
|
|
||||||
items: ItemsMap,
|
|
||||||
maps: MapsMap,
|
|
||||||
resources: ResourcesMap,
|
|
||||||
monsters: MonstersMap,
|
|
||||||
// TODO: bank
|
|
||||||
|
|
||||||
pub fn init(allocator: Allocator) Store {
|
|
||||||
return Store{
|
|
||||||
.allocator = allocator,
|
|
||||||
.codes = std.ArrayList([]u8).init(allocator),
|
|
||||||
.characters = Characters.init(allocator),
|
|
||||||
.items = ItemsMap.init(allocator),
|
|
||||||
.maps = MapsMap.init(allocator),
|
|
||||||
.resources = ResourcesMap.init(allocator),
|
|
||||||
.monsters = MonstersMap.init(allocator),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: *Store) void {
|
|
||||||
self.clearCodes();
|
|
||||||
|
|
||||||
for (self.characters.items) |*char| {
|
|
||||||
char.deinit();
|
|
||||||
}
|
}
|
||||||
self.characters.deinit();
|
|
||||||
|
|
||||||
self.clearItems();
|
break :size size;
|
||||||
|
};
|
||||||
|
|
||||||
self.clearMaps();
|
pub const Image = struct {
|
||||||
|
pub const Category = enum {
|
||||||
|
character,
|
||||||
|
item,
|
||||||
|
monster,
|
||||||
|
map,
|
||||||
|
resource,
|
||||||
|
effect
|
||||||
|
};
|
||||||
|
|
||||||
self.clearResources();
|
pub const max_code_size = size: {
|
||||||
|
var size: usize = 0;
|
||||||
|
|
||||||
self.clearMonsters();
|
size = @max(size, Item.max_code_size);
|
||||||
}
|
size = @max(size, max_character_image_code);
|
||||||
|
size = @max(size, Monster.max_code_size);
|
||||||
|
size = @max(size, Resource.max_code_size);
|
||||||
|
size = @max(size, Map.max_skin_size);
|
||||||
|
// TODO: effect code size
|
||||||
|
|
||||||
pub fn getCodeId(self: *Store, code: []const u8) !CodeId {
|
break :size size;
|
||||||
assert(code.len != 0);
|
};
|
||||||
|
pub const Code = std.BoundedArray(u8, max_code_size);
|
||||||
|
|
||||||
for (0.., self.codes.items) |i, item_code| {
|
code: Code,
|
||||||
if (std.mem.eql(u8, code, item_code)) {
|
width: u32,
|
||||||
return @intCast(i);
|
height: u32,
|
||||||
|
rgba_offset: u32,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Images = struct {
|
||||||
|
const ImagesArray = std.ArrayListUnmanaged(Image);
|
||||||
|
const IdArray = std.ArrayListUnmanaged(Id);
|
||||||
|
const CategoryMap = std.EnumArray(Image.Category, IdArray);
|
||||||
|
|
||||||
|
const Options = struct {
|
||||||
|
max_rgba_data: u32,
|
||||||
|
|
||||||
|
max_items: u32,
|
||||||
|
max_monsters: u32,
|
||||||
|
max_maps: u32,
|
||||||
|
max_resources: u32,
|
||||||
|
max_effects: u32,
|
||||||
|
};
|
||||||
|
|
||||||
|
rgba_data: []u8,
|
||||||
|
rgba_fba: std.heap.FixedBufferAllocator,
|
||||||
|
category_mapping: CategoryMap,
|
||||||
|
images: ImagesArray,
|
||||||
|
|
||||||
|
pub fn initCapacity(allocator: std.mem.Allocator, opts: Options) !Images {
|
||||||
|
const max_characters = std.meta.fields(Character.Skin).len;
|
||||||
|
|
||||||
|
var max_images: u32 = 0;
|
||||||
|
max_images += @intCast(max_characters);
|
||||||
|
max_images += opts.max_items;
|
||||||
|
max_images += opts.max_monsters;
|
||||||
|
max_images += opts.max_maps;
|
||||||
|
max_images += opts.max_resources;
|
||||||
|
max_images += opts.max_effects;
|
||||||
|
|
||||||
|
const rgba_data = try allocator.alloc(u8, opts.max_rgba_data);
|
||||||
|
errdefer allocator.free(rgba_data);
|
||||||
|
|
||||||
|
var images = try ImagesArray.initCapacity(allocator, max_images);
|
||||||
|
errdefer images.deinit(allocator);
|
||||||
|
|
||||||
|
var character_images = try IdArray.initCapacity(allocator, max_characters);
|
||||||
|
errdefer character_images.deinit(allocator);
|
||||||
|
|
||||||
|
var item_images = try IdArray.initCapacity(allocator, opts.max_items);
|
||||||
|
errdefer item_images.deinit(allocator);
|
||||||
|
|
||||||
|
var monster_images = try IdArray.initCapacity(allocator, opts.max_monsters);
|
||||||
|
errdefer monster_images.deinit(allocator);
|
||||||
|
|
||||||
|
var map_images = try IdArray.initCapacity(allocator, opts.max_maps);
|
||||||
|
errdefer map_images.deinit(allocator);
|
||||||
|
|
||||||
|
var resource_images = try IdArray.initCapacity(allocator, opts.max_resources);
|
||||||
|
errdefer resource_images.deinit(allocator);
|
||||||
|
|
||||||
|
var effect_images = try IdArray.initCapacity(allocator, opts.max_effects);
|
||||||
|
errdefer effect_images.deinit(allocator);
|
||||||
|
|
||||||
|
const category_mapping = CategoryMap.init(.{
|
||||||
|
.character = character_images,
|
||||||
|
.item = item_images,
|
||||||
|
.monster = monster_images,
|
||||||
|
.resource = resource_images,
|
||||||
|
.effect = effect_images,
|
||||||
|
.map = map_images
|
||||||
|
});
|
||||||
|
|
||||||
|
return Images{
|
||||||
|
.rgba_data = rgba_data,
|
||||||
|
.rgba_fba = std.heap.FixedBufferAllocator.init(rgba_data),
|
||||||
|
.category_mapping = category_mapping,
|
||||||
|
.images = images
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Images, allocator: std.mem.Allocator) void {
|
||||||
|
allocator.free(self.rgba_data);
|
||||||
|
self.images.deinit(allocator);
|
||||||
|
self.rgba_fba.reset();
|
||||||
|
|
||||||
|
var iter = self.category_mapping.iterator();
|
||||||
|
while (iter.next()) |id_array| {
|
||||||
|
id_array.value.deinit(allocator);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const code_dupe = try self.allocator.dupe(u8, code);
|
pub fn get(self: *Images, id: Id) ?*Image {
|
||||||
errdefer self.allocator.free(code_dupe);
|
if (id < self.images.items.len) {
|
||||||
try self.codes.append(code_dupe);
|
return &self.images.items[id];
|
||||||
|
}
|
||||||
|
|
||||||
return @intCast(self.codes.items.len - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn getCode(self: *const Store, id: CodeId) ?[]const u8 {
|
|
||||||
if (id >= self.codes.items.len) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return self.codes.items[id];
|
pub fn getId(self: *Images, category: Image.Category, code: []const u8) ?Id {
|
||||||
}
|
const id_array = self.category_mapping.get(category);
|
||||||
|
for (id_array.items) |id| {
|
||||||
|
const image = self.images.items[id];
|
||||||
|
const image_code = image.code.slice();
|
||||||
|
if (std.mem.eql(u8, image_code, code)) {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn getCodeIdJson(self: *Store, object: json.ObjectMap, name: []const u8) !?CodeId {
|
|
||||||
const code = try json_utils.getStringRequired(object, name);
|
|
||||||
if (code.len == 0) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return try self.getCodeId(code);
|
pub fn getRGBA(self: *Images, id: Id) ?[]u8 {
|
||||||
}
|
const image = self.get(id) orelse return null;
|
||||||
|
const rgba_start = image.rgba_offset;
|
||||||
|
const rgba_stop = image.rgba_offset + image.width * image.height * 4;
|
||||||
|
|
||||||
fn clearCodes(self: *Store) void {
|
return self.rgba_data[rgba_start..rgba_stop];
|
||||||
for (self.codes.items) |code| {
|
|
||||||
self.allocator.free(code);
|
|
||||||
}
|
}
|
||||||
self.codes.clearAndFree();
|
|
||||||
|
pub fn append(self: *Images, category: Image.Category, code: []const u8, width: u32, height: u32) !Id {
|
||||||
|
if (self.images.unusedCapacitySlice().len == 0) {
|
||||||
|
return error.OutOfMemory;
|
||||||
|
}
|
||||||
|
|
||||||
|
var image_ids = self.category_mapping.getPtr(category);
|
||||||
|
if (image_ids.unusedCapacitySlice().len == 0) {
|
||||||
|
return error.OutOfMemory;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rgba_allocator = self.rgba_fba.allocator();
|
||||||
|
const rgba = try rgba_allocator.alloc(u8, width * height * 4);
|
||||||
|
errdefer rgba_allocator.free(rgba);
|
||||||
|
|
||||||
|
const image_id = self.images.items.len;
|
||||||
|
self.images.appendAssumeCapacity(Image{
|
||||||
|
.rgba_offset = @intCast(@intFromPtr(rgba.ptr) - @intFromPtr(self.rgba_data.ptr)),
|
||||||
|
.width = width,
|
||||||
|
.height = height,
|
||||||
|
.code = try Image.Code.fromSlice(code)
|
||||||
|
});
|
||||||
|
errdefer _ = self.images.pop();
|
||||||
|
|
||||||
|
image_ids.appendAssumeCapacity(image_id);
|
||||||
|
|
||||||
|
return image_id;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fn Repository(comptime Object: type, comptime name_field: []const u8) type {
|
||||||
|
const dummy: Object = undefined;
|
||||||
|
const Name: type = @TypeOf(@field(dummy, name_field));
|
||||||
|
|
||||||
|
return struct {
|
||||||
|
pub const OptionalObject = union(enum) {
|
||||||
|
reserved: Name,
|
||||||
|
object: Object
|
||||||
|
};
|
||||||
|
|
||||||
|
const OptionalObjects = std.ArrayListUnmanaged(OptionalObject);
|
||||||
|
objects: OptionalObjects,
|
||||||
|
|
||||||
|
pub fn initCapacity(allocator: std.mem.Allocator, capacity: usize) !@This() {
|
||||||
|
return @This(){
|
||||||
|
.objects = try OptionalObjects.initCapacity(allocator, capacity)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *@This(), allocator: std.mem.Allocator) void {
|
||||||
|
self.objects.deinit(allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(self: *@This(), id: Id) ?*Object {
|
||||||
|
if (id < self.objects.items.len) {
|
||||||
|
const item = &self.objects.items[id];
|
||||||
|
if (item.* == .object) {
|
||||||
|
return &item.object;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove(self: *@This(), id: Id) bool {
|
||||||
|
if (id < self.objects.items.len) {
|
||||||
|
const item = &self.objects.items[id];
|
||||||
|
if (item.* == .object) {
|
||||||
|
item.* = .{ .reserved = @field(item.object, name_field) };
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getId(self: *@This(), name: []const u8) ?Id {
|
||||||
|
for (0.., self.objects.items) |id, object| {
|
||||||
|
const object_name = switch (object) {
|
||||||
|
.object => |obj| @field(obj, name_field).slice(),
|
||||||
|
.reserved => |object_name| object_name.slice(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (std.mem.eql(u8, name, object_name)) {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getName(self: *@This(), id: Id) ?[]const u8 {
|
||||||
|
if (id < self.objects.items.len) {
|
||||||
|
return switch (self.objects.items[id]) {
|
||||||
|
.object => |obj| @field(obj, name_field).slice(),
|
||||||
|
.reserve => |name| name.slice(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOrReserveId(self: *@This(), name: []const u8) !Id {
|
||||||
|
if (self.getId(name)) |id| {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.objects.unusedCapacitySlice().len == 0) {
|
||||||
|
return error.OutOfMemory;
|
||||||
|
}
|
||||||
|
|
||||||
|
const owned_name = try Name.fromSlice(name);
|
||||||
|
self.objects.appendAssumeCapacity(.{ .reserved = owned_name });
|
||||||
|
|
||||||
|
return self.objects.items.len - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn appendOrUpdate(self: *@This(), item: Object) !Id {
|
||||||
|
if (self.getId(@field(item, name_field).slice())) |id| {
|
||||||
|
self.objects.items[id] = .{ .object = item };
|
||||||
|
return id;
|
||||||
|
} else {
|
||||||
|
if (self.objects.unusedCapacitySlice().len == 0) {
|
||||||
|
return error.OutOfMemory;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.objects.appendAssumeCapacity(.{ .object = item });
|
||||||
|
|
||||||
|
return self.objects.items.len - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------- Storing to file ------------------------------
|
const Items = Repository(Item, "code");
|
||||||
|
const Characters = Repository(Character, "name");
|
||||||
|
const Tasks = Repository(Task, "code");
|
||||||
|
const Monsters = Repository(Monster, "code");
|
||||||
|
const Resources = Repository(Resource, "code");
|
||||||
|
const Maps = std.ArrayListUnmanaged(Map);
|
||||||
|
|
||||||
|
items: Items,
|
||||||
|
characters: Characters,
|
||||||
|
tasks: Tasks,
|
||||||
|
monsters: Monsters,
|
||||||
|
resources: Resources,
|
||||||
|
images: Images,
|
||||||
|
maps: Maps,
|
||||||
|
|
||||||
|
pub fn init(allocator: std.mem.Allocator) !Store {
|
||||||
|
const max_items = 512;
|
||||||
|
const max_characters = 10;
|
||||||
|
const max_tasks = 32;
|
||||||
|
const max_monsters = 64;
|
||||||
|
const max_resources = 32;
|
||||||
|
const max_maps = 512;
|
||||||
|
|
||||||
|
var items = try Items.initCapacity(allocator, max_items);
|
||||||
|
errdefer items.deinit(allocator);
|
||||||
|
|
||||||
|
var characters = try Characters.initCapacity(allocator, max_characters);
|
||||||
|
errdefer characters.deinit(allocator);
|
||||||
|
|
||||||
|
var tasks = try Tasks.initCapacity(allocator, max_tasks);
|
||||||
|
errdefer tasks.deinit(allocator);
|
||||||
|
|
||||||
|
var monsters = try Monsters.initCapacity(allocator, max_monsters);
|
||||||
|
errdefer monsters.deinit(allocator);
|
||||||
|
|
||||||
|
var resources = try Resources.initCapacity(allocator, max_resources);
|
||||||
|
errdefer resources.deinit(allocator);
|
||||||
|
|
||||||
|
var images = try Images.initCapacity(allocator, .{
|
||||||
|
.max_rgba_data = 1024 * 1024 * 32,
|
||||||
|
.max_items = max_items,
|
||||||
|
.max_effects = 16,
|
||||||
|
.max_resources = max_resources,
|
||||||
|
.max_maps = max_maps,
|
||||||
|
.max_monsters = max_monsters,
|
||||||
|
});
|
||||||
|
errdefer images.deinit(allocator);
|
||||||
|
|
||||||
|
var maps = try Maps.initCapacity(allocator, max_maps);
|
||||||
|
errdefer maps.deinit(allocator);
|
||||||
|
|
||||||
|
return Store{
|
||||||
|
.items = items,
|
||||||
|
.characters = characters,
|
||||||
|
.tasks = tasks,
|
||||||
|
.monsters = monsters,
|
||||||
|
.resources = resources,
|
||||||
|
.maps = maps,
|
||||||
|
.images = images,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Store, allocator: std.mem.Allocator) void {
|
||||||
|
self.items.deinit(allocator);
|
||||||
|
self.characters.deinit(allocator);
|
||||||
|
self.tasks.deinit(allocator);
|
||||||
|
self.monsters.deinit(allocator);
|
||||||
|
self.resources.deinit(allocator);
|
||||||
|
self.maps.deinit(allocator);
|
||||||
|
self.images.deinit(allocator);
|
||||||
|
}
|
||||||
|
|
||||||
const SaveData = struct {
|
const SaveData = struct {
|
||||||
api_version: []const u8,
|
api_version: []const u8,
|
||||||
|
|
||||||
codes: [][]u8,
|
items: []Items.OptionalObject,
|
||||||
items: []Item,
|
characters: []Characters.OptionalObject,
|
||||||
|
tasks: []Tasks.OptionalObject,
|
||||||
|
monsters: []Monsters.OptionalObject,
|
||||||
|
resources: []Resources.OptionalObject,
|
||||||
maps: []Map,
|
maps: []Map,
|
||||||
resources: []Resource,
|
|
||||||
monsters: []Monster,
|
images: struct {
|
||||||
|
rgba_data: []u8,
|
||||||
|
images: []Image,
|
||||||
|
|
||||||
|
// These next fields are just `category_mapping`
|
||||||
|
character_mapping: []Id,
|
||||||
|
item_mapping: []Id,
|
||||||
|
monster_mapping: []Id,
|
||||||
|
map_mapping: []Id,
|
||||||
|
resource_mapping: []Id,
|
||||||
|
effect_mapping: []Id,
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
fn allocHashMapValues(Value: type, allocator: Allocator, hashmap: anytype) ![]Value {
|
// TODO: Create a better serialized that doesn't write zeros or BoundedArray types
|
||||||
var values = try allocator.alloc(Value, hashmap.count());
|
|
||||||
errdefer allocator.free(values);
|
|
||||||
|
|
||||||
var valueIter = hashmap.valueIterator();
|
|
||||||
var index: usize = 0;
|
|
||||||
while (valueIter.next()) |value| {
|
|
||||||
values[index] = value.*;
|
|
||||||
index += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return values;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn save(self: *Store, api_version: []const u8, writer: anytype) !void {
|
pub fn save(self: *Store, api_version: []const u8, writer: anytype) !void {
|
||||||
const items = try allocHashMapValues(Item, self.allocator, self.items);
|
const rgba_end_index = self.images.rgba_fba.end_index;
|
||||||
defer self.allocator.free(items);
|
|
||||||
|
|
||||||
const maps = try allocHashMapValues(Map, self.allocator, self.maps);
|
|
||||||
defer self.allocator.free(maps);
|
|
||||||
|
|
||||||
const resources = try allocHashMapValues(Resource, self.allocator, self.resources);
|
|
||||||
defer self.allocator.free(resources);
|
|
||||||
|
|
||||||
const monsters = try allocHashMapValues(Monster, self.allocator, self.monsters);
|
|
||||||
defer self.allocator.free(monsters);
|
|
||||||
|
|
||||||
const data = SaveData{
|
const data = SaveData{
|
||||||
.api_version = api_version,
|
.api_version = api_version,
|
||||||
.codes = self.codes.items,
|
.items = self.items.objects.items,
|
||||||
.items = items,
|
.characters = self.characters.objects.items,
|
||||||
.maps = maps,
|
.tasks = self.tasks.objects.items,
|
||||||
.resources = resources,
|
.resources = self.resources.objects.items,
|
||||||
.monsters = monsters
|
.monsters = self.monsters.objects.items,
|
||||||
|
.maps = self.maps.items,
|
||||||
|
.images = .{
|
||||||
|
.rgba_data = self.images.rgba_data[0..rgba_end_index],
|
||||||
|
.images = self.images.images.items,
|
||||||
|
|
||||||
|
.character_mapping = self.images.category_mapping.get(.character).items,
|
||||||
|
.item_mapping = self.images.category_mapping.get(.item).items,
|
||||||
|
.monster_mapping = self.images.category_mapping.get(.monster).items,
|
||||||
|
.map_mapping = self.images.category_mapping.get(.map).items,
|
||||||
|
.resource_mapping = self.images.category_mapping.get(.resource).items,
|
||||||
|
.effect_mapping = self.images.category_mapping.get(.effect).items,
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
try s2s.serialize(writer, SaveData, data);
|
try s2s.serialize(writer, SaveData, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load(self: *Store, api_version: []const u8, reader: anytype) !void {
|
pub fn load(self: *Store, allocator: std.mem.Allocator, api_version: []const u8, reader: anytype) !void {
|
||||||
var data = try s2s.deserializeAlloc(reader, SaveData, self.allocator);
|
var data: SaveData = try s2s.deserializeAlloc(reader, SaveData, allocator);
|
||||||
|
defer s2s.free(allocator, SaveData, &data);
|
||||||
|
|
||||||
|
// TODO: Add better version checking.
|
||||||
|
// * Hash the layout of `SaveData` and save that hash into the file. To see if layout changed.
|
||||||
|
// * Hash the openapi documentation file to see if the saved data is out-of-date and should be ignored.
|
||||||
if (!std.mem.eql(u8, data.api_version, api_version)) {
|
if (!std.mem.eql(u8, data.api_version, api_version)) {
|
||||||
s2s.free(self.allocator, SaveData, &data);
|
|
||||||
return error.InvalidVersion;
|
return error.InvalidVersion;
|
||||||
}
|
}
|
||||||
defer self.allocator.free(data.api_version);
|
|
||||||
|
|
||||||
self.clearCodes();
|
const repositories = .{
|
||||||
try self.codes.appendSlice(data.codes);
|
.{ &self.items, data.items },
|
||||||
defer self.allocator.free(data.codes);
|
.{ &self.characters, data.characters },
|
||||||
|
.{ &self.tasks, data.tasks },
|
||||||
|
.{ &self.resources, data.resources },
|
||||||
|
.{ &self.monsters, data.monsters },
|
||||||
|
};
|
||||||
|
|
||||||
self.clearItems();
|
const image_category_mapping = &self.images.category_mapping;
|
||||||
for (data.items) |item| {
|
const image_categories = .{
|
||||||
try self.putItem(item);
|
.{ image_category_mapping.getPtr(.character), data.images.character_mapping },
|
||||||
}
|
.{ image_category_mapping.getPtr(.item), data.images.item_mapping },
|
||||||
defer self.allocator.free(data.items);
|
.{ image_category_mapping.getPtr(.monster), data.images.monster_mapping },
|
||||||
|
.{ image_category_mapping.getPtr(.map), data.images.map_mapping },
|
||||||
|
.{ image_category_mapping.getPtr(.resource), data.images.resource_mapping },
|
||||||
|
.{ image_category_mapping.getPtr(.effect), data.images.effect_mapping },
|
||||||
|
};
|
||||||
|
|
||||||
self.clearMaps();
|
// Check if there is enough space
|
||||||
for (data.maps) |map| {
|
{
|
||||||
try self.putMap(map);
|
inline for (repositories) |pair| {
|
||||||
}
|
const repository = pair[0];
|
||||||
defer self.allocator.free(data.maps);
|
const saved_objects = pair[1];
|
||||||
|
if (saved_objects.len > repository.objects.capacity) {
|
||||||
|
return error.OutOfMemory;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.clearResources();
|
if (data.maps.len > self.maps.capacity) {
|
||||||
for (data.resources) |resource| {
|
return error.OutOfMemory;
|
||||||
try self.putResource(resource);
|
}
|
||||||
}
|
|
||||||
defer self.allocator.free(data.resources);
|
|
||||||
|
|
||||||
self.clearMonsters();
|
if (data.images.rgba_data.len > self.images.rgba_data.len) {
|
||||||
for (data.monsters) |monster| {
|
return error.OutOfMemory;
|
||||||
try self.putMonster(monster);
|
}
|
||||||
}
|
|
||||||
defer self.allocator.free(data.monsters);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------- Character ------------------------------
|
if (data.images.images.len > self.images.images.capacity) {
|
||||||
|
return error.OutOfMemory;
|
||||||
|
}
|
||||||
|
|
||||||
fn getCharacterIndex(self: *const Store, name: []const u8) ?usize {
|
inline for (image_categories) |pair| {
|
||||||
for (0.., self.characters.items) |i, character| {
|
const self_ids = pair[0];
|
||||||
if (std.mem.eql(u8, character.name, name)) {
|
const saved_ids = pair[1];
|
||||||
return i;
|
if (saved_ids.len > self_ids.capacity) {
|
||||||
|
return error.OutOfMemory;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
// Move loaded data from file to this store
|
||||||
}
|
{
|
||||||
|
inline for (repositories) |pair| {
|
||||||
|
const repository = pair[0];
|
||||||
|
const saved_objects = pair[1];
|
||||||
|
|
||||||
pub fn getCharacter(self: *Store, name: []const u8) ?Character {
|
repository.objects.clearRetainingCapacity();
|
||||||
if (self.getCharacterIndex(name)) |index| {
|
repository.objects.appendSliceAssumeCapacity(saved_objects);
|
||||||
return self.characters.items[index];
|
}
|
||||||
|
|
||||||
|
self.maps.clearRetainingCapacity();
|
||||||
|
self.maps.appendSliceAssumeCapacity(data.maps);
|
||||||
|
|
||||||
|
@memcpy(self.images.rgba_data[0..data.images.rgba_data.len], data.images.rgba_data);
|
||||||
|
self.images.rgba_fba.end_index = data.images.rgba_data.len;
|
||||||
|
|
||||||
|
self.images.images.clearRetainingCapacity();
|
||||||
|
self.images.images.appendSliceAssumeCapacity(data.images.images);
|
||||||
|
|
||||||
|
inline for (image_categories) |pair| {
|
||||||
|
const self_ids = pair[0];
|
||||||
|
const saved_ids = pair[1];
|
||||||
|
|
||||||
|
self_ids.clearRetainingCapacity();
|
||||||
|
self_ids.appendSliceAssumeCapacity(saved_ids);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn putCharacter(self: *Store, character: Character) !void {
|
pub fn appendOrUpdateMap(self: *Store, map: Map) !Position {
|
||||||
if (self.getCharacterIndex(character.name)) |index| {
|
if (self.getMap(map.position)) |existing_map| {
|
||||||
self.characters.items[index].deinit();
|
existing_map.* = map;
|
||||||
self.characters.items[index] = character;
|
|
||||||
} else {
|
} else {
|
||||||
try self.characters.append(character);
|
if (self.maps.unusedCapacitySlice().len == 0) {
|
||||||
}
|
return error.OutOfMemory;
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------- Map ------------------------------
|
|
||||||
|
|
||||||
pub fn getMap(self: *Store, x: i64, y: i64) ?Map {
|
|
||||||
const pos = Position.init(x, y);
|
|
||||||
return self.maps.get(pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn getMaps(self: *Store, opts: Server.MapOptions) !std.ArrayList(Map) {
|
|
||||||
var found = std.ArrayList(Map).init(self.allocator);
|
|
||||||
errdefer found.deinit();
|
|
||||||
|
|
||||||
var mapIter = self.maps.valueIterator();
|
|
||||||
while (mapIter.next()) |map| {
|
|
||||||
if (opts.type) |content_type| {
|
|
||||||
if (map.content == null) continue;
|
|
||||||
if (map.content.?.type != content_type) continue;
|
|
||||||
}
|
|
||||||
if (opts.code) |content_code| {
|
|
||||||
if (map.content == null) continue;
|
|
||||||
|
|
||||||
const map_content_code = self.getCode(map.content.?.code_id).?;
|
|
||||||
if (!std.mem.eql(u8, map_content_code, content_code)) continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try found.append(map.*);
|
self.maps.appendAssumeCapacity(map);
|
||||||
}
|
}
|
||||||
|
|
||||||
return found;
|
return map.position;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn putMap(self: *Store, map: Map) !void {
|
pub fn getMap(self: *Store, position: Position) ?*Map {
|
||||||
var entry = try self.maps.getOrPut(map.position);
|
for (self.maps.items) |*map| {
|
||||||
if (entry.found_existing) {
|
if (map.position.eql(position)) {
|
||||||
entry.value_ptr.deinit(self.allocator);
|
return map;
|
||||||
}
|
|
||||||
entry.value_ptr.* = map;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn clearMaps(self: *Store) void {
|
|
||||||
var mapsIter = self.maps.valueIterator();
|
|
||||||
while (mapsIter.next()) |map| {
|
|
||||||
map.deinit(self.allocator);
|
|
||||||
}
|
|
||||||
self.maps.clearAndFree();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------- Item ------------------------------
|
|
||||||
|
|
||||||
pub fn getItem(self: *Store, code: []const u8) ?Item {
|
|
||||||
return self.items.get(code);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn getItems(self: *Store, opts: Server.ItemOptions) !std.ArrayList(Item) {
|
|
||||||
var found = std.ArrayList(Item).init(self.allocator);
|
|
||||||
errdefer found.deinit();
|
|
||||||
|
|
||||||
var itemIter = self.items.valueIterator();
|
|
||||||
while (itemIter.next()) |item| {
|
|
||||||
if (opts.craft_skill) |craft_skill| {
|
|
||||||
if (item.craft == null) continue;
|
|
||||||
if (item.craft.?.skill != craft_skill) continue;
|
|
||||||
}
|
}
|
||||||
if (opts.craft_material) |craft_material| {
|
|
||||||
if (item.craft == null) continue;
|
|
||||||
const recipe = item.craft.?;
|
|
||||||
|
|
||||||
const craft_material_id = try self.getCodeId(craft_material);
|
|
||||||
const material_quantity = recipe.items.getQuantity(craft_material_id);
|
|
||||||
if (material_quantity == 0) continue;
|
|
||||||
}
|
|
||||||
if (opts.min_level) |min_level| {
|
|
||||||
if (item.level < min_level) continue;
|
|
||||||
}
|
|
||||||
if (opts.max_level) |max_level| {
|
|
||||||
if (item.level > max_level) continue;
|
|
||||||
}
|
|
||||||
if (opts.type) |item_type| {
|
|
||||||
if (item.type != item_type) continue;
|
|
||||||
}
|
|
||||||
if (opts.name) |name| {
|
|
||||||
if (std.mem.indexOf(u8, item.name, name) == null) continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
try found.append(item.*);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return found;
|
return null;
|
||||||
}
|
|
||||||
|
|
||||||
pub fn putItem(self: *Store, item: Item) !void {
|
|
||||||
const code = self.getCode(item.code_id).?;
|
|
||||||
var entry = try self.items.getOrPut(code);
|
|
||||||
if (entry.found_existing) {
|
|
||||||
entry.value_ptr.deinit(self.allocator);
|
|
||||||
}
|
|
||||||
entry.value_ptr.* = item;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn clearItems(self: *Store) void {
|
|
||||||
var itemsIter = self.items.valueIterator();
|
|
||||||
while (itemsIter.next()) |item| {
|
|
||||||
item.deinit(self.allocator);
|
|
||||||
}
|
|
||||||
self.items.clearAndFree();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------- Monster ------------------------------
|
|
||||||
|
|
||||||
pub fn getMonster(self: *Store, code: []const u8) ?Monster {
|
|
||||||
return self.monsters.get(code);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn getMonsters(self: *Store, opts: Server.MonsterOptions) !std.ArrayList(Monster) {
|
|
||||||
var found = std.ArrayList(Monster).init(self.allocator);
|
|
||||||
errdefer found.deinit();
|
|
||||||
|
|
||||||
var monsterIter = self.monsters.valueIterator();
|
|
||||||
while (monsterIter.next()) |monster| {
|
|
||||||
if (opts.min_level) |min_level| {
|
|
||||||
if (monster.level < min_level) continue;
|
|
||||||
}
|
|
||||||
if (opts.max_level) |max_level| {
|
|
||||||
if (monster.level > max_level) continue;
|
|
||||||
}
|
|
||||||
if (opts.drop) |drop| {
|
|
||||||
const item_id = try self.getCodeId(drop);
|
|
||||||
if (!DropRate.doesListContain(&monster.drops, item_id)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try found.append(monster.*);
|
|
||||||
}
|
|
||||||
|
|
||||||
return found;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn putMonster(self: *Store, monster: Monster) !void {
|
|
||||||
const code = self.getCode(monster.code_id).?;
|
|
||||||
var entry = try self.monsters.getOrPut(code);
|
|
||||||
if (entry.found_existing) {
|
|
||||||
entry.value_ptr.deinit(self.allocator);
|
|
||||||
}
|
|
||||||
entry.value_ptr.* = monster;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn clearMonsters(self: *Store) void {
|
|
||||||
var monstersIter = self.monsters.valueIterator();
|
|
||||||
while (monstersIter.next()) |monster| {
|
|
||||||
monster.deinit(self.allocator);
|
|
||||||
}
|
|
||||||
self.monsters.clearAndFree();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------- Resource ------------------------------
|
|
||||||
|
|
||||||
pub fn getResource(self: *Store, code: []const u8) ?Resource {
|
|
||||||
return self.resources.get(code);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn getResources(self: *Store, opts: Server.ResourceOptions) !std.ArrayList(Resource) {
|
|
||||||
var found = std.ArrayList(Resource).init(self.allocator);
|
|
||||||
errdefer found.deinit();
|
|
||||||
|
|
||||||
var resourceIter = self.resources.valueIterator();
|
|
||||||
while (resourceIter.next()) |resource| {
|
|
||||||
if (opts.min_level) |min_level| {
|
|
||||||
if (resource.level < min_level) continue;
|
|
||||||
}
|
|
||||||
if (opts.max_level) |max_level| {
|
|
||||||
if (resource.level > max_level) continue;
|
|
||||||
}
|
|
||||||
if (opts.drop) |drop| {
|
|
||||||
const item_id = try self.getCodeId(drop);
|
|
||||||
if (!DropRate.doesListContain(&resource.drops, item_id)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try found.append(resource.*);
|
|
||||||
}
|
|
||||||
|
|
||||||
return found;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn putResource(self: *Store, resource: Resource) !void {
|
|
||||||
const code = self.getCode(resource.code_id).?;
|
|
||||||
var entry = try self.resources.getOrPut(code);
|
|
||||||
if (entry.found_existing) {
|
|
||||||
entry.value_ptr.deinit(self.allocator);
|
|
||||||
}
|
|
||||||
entry.value_ptr.* = resource;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn clearResources(self: *Store) void {
|
|
||||||
var resourcesIter = self.resources.valueIterator();
|
|
||||||
while (resourcesIter.next()) |resource| {
|
|
||||||
resource.deinit(self.allocator);
|
|
||||||
}
|
|
||||||
self.resources.clearAndFree();
|
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,10 @@ pub fn build(b: *std.Build) void {
|
|||||||
});
|
});
|
||||||
api.addIncludePath(b.path("api/date_time"));
|
api.addIncludePath(b.path("api/date_time"));
|
||||||
api.addCSourceFile(.{ .file = b.path("api/date_time/timegm.c") });
|
api.addCSourceFile(.{ .file = b.path("api/date_time/timegm.c") });
|
||||||
|
|
||||||
|
api.addIncludePath(b.path("api/stb_image"));
|
||||||
|
api.addCSourceFile(.{ .file = b.path("api/stb_image/stb_image.c") });
|
||||||
|
|
||||||
api.addImport("s2s", s2s_dep.module("s2s"));
|
api.addImport("s2s", s2s_dep.module("s2s"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,8 +86,10 @@ pub fn build(b: *std.Build) void {
|
|||||||
.target = target,
|
.target = target,
|
||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
});
|
});
|
||||||
gui.root_module.addImport("artificer", lib);
|
|
||||||
gui.linkLibrary(raylib_dep.artifact("raylib"));
|
gui.linkLibrary(raylib_dep.artifact("raylib"));
|
||||||
|
|
||||||
|
gui.root_module.addImport("artifacts-api", api);
|
||||||
|
gui.root_module.addImport("artificer", lib);
|
||||||
gui.root_module.addImport("raylib", raylib_dep.module("raylib"));
|
gui.root_module.addImport("raylib", raylib_dep.module("raylib"));
|
||||||
|
|
||||||
const run_cmd = b.addRunArtifact(gui);
|
const run_cmd = b.addRunArtifact(gui);
|
||||||
|
61
cli/main.zig
61
cli/main.zig
@ -5,6 +5,14 @@ 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 = .{
|
||||||
|
.log_scope_levels = &[_]std.log.ScopeLevel{
|
||||||
|
.{ .scope = .api, .level = .info },
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// 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);
|
||||||
defer std.process.argsFree(allocator, args);
|
defer std.process.argsFree(allocator, args);
|
||||||
@ -18,7 +26,7 @@ fn getAPITokenFromArgs(allocator: Allocator) !?[]u8 {
|
|||||||
var token_buffer: [256]u8 = undefined;
|
var token_buffer: [256]u8 = undefined;
|
||||||
const token = try cwd.readFile(filename, &token_buffer);
|
const token = try cwd.readFile(filename, &token_buffer);
|
||||||
|
|
||||||
return try allocator.dupe(u8, std.mem.trim(u8,token,"\n\t "));
|
return try allocator.dupe(u8, std.mem.trim(u8, token, "\n\t "));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn main() !void {
|
pub fn main() !void {
|
||||||
@ -29,25 +37,44 @@ 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);
|
// var artificer = try Artificer.init(allocator, token);
|
||||||
defer artificer.deinit();
|
// defer artificer.deinit();
|
||||||
|
|
||||||
const cache_path = try std.fs.cwd().realpathAlloc(allocator, "api-store.bin");
|
var store = try Api.Store.init(allocator);
|
||||||
defer allocator.free(cache_path);
|
defer store.deinit(allocator);
|
||||||
|
|
||||||
std.log.info("Prefetching server data", .{});
|
var server = try Api.Server.init(allocator, &store);
|
||||||
try artificer.server.prefetchCached(cache_path);
|
defer server.deinit();
|
||||||
|
|
||||||
if (false) {
|
try server.setToken(token);
|
||||||
std.log.info("Starting main loop", .{});
|
|
||||||
while (true) {
|
|
||||||
const waitUntil = artificer.nextStepAt();
|
|
||||||
const duration = waitUntil - std.time.milliTimestamp();
|
|
||||||
if (duration > 0) {
|
|
||||||
std.time.sleep(@intCast(duration));
|
|
||||||
}
|
|
||||||
|
|
||||||
try artificer.step();
|
const resources = try server.getResources(allocator, .{});
|
||||||
}
|
resources.deinit();
|
||||||
|
|
||||||
|
// var artificer = try Artificer.init(allocator, token);
|
||||||
|
// defer artificer.deinit();
|
||||||
|
|
||||||
|
{
|
||||||
|
const status: Api.Status = try server.getStatus();
|
||||||
|
|
||||||
|
const file = try std.fs.cwd().createFile("api-store.bin", .{});
|
||||||
|
defer file.close();
|
||||||
|
try store.save(status.version.slice(), file.writer());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// std.log.info("Prefetching server data", .{});
|
||||||
|
// try artificer.server.prefetchCached(cache_path);
|
||||||
|
|
||||||
|
// if (false) {
|
||||||
|
// std.log.info("Starting main loop", .{});
|
||||||
|
// while (true) {
|
||||||
|
// const waitUntil = artificer.nextStepAt();
|
||||||
|
// const duration = waitUntil - std.time.milliTimestamp();
|
||||||
|
// if (duration > 0) {
|
||||||
|
// std.time.sleep(@intCast(duration));
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// try artificer.step();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
222
lib/root.zig
222
lib/root.zig
@ -1,3 +1,4 @@
|
|||||||
|
// zig fmt: off
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Api = @import("artifacts-api");
|
const Api = @import("artifacts-api");
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
@ -10,127 +11,126 @@ const Artificer = @This();
|
|||||||
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
|
||||||
|
|
||||||
server: Api.Server,
|
server: *Api.Server,
|
||||||
characters: std.ArrayList(Brain),
|
// characters: std.ArrayList(Brain),
|
||||||
task_graph: TaskGraph,
|
// task_graph: TaskGraph,
|
||||||
|
|
||||||
paused_until: ?i64 = null, // ms
|
// paused_until: ?i64 = null, // ms
|
||||||
|
|
||||||
pub fn init(allocator: Allocator, token: []const u8) !Artificer {
|
pub fn init(allocator: Allocator, server: *Api.Server) !Artificer {
|
||||||
var server = try Api.Server.init(allocator);
|
// var characters = std.ArrayList(Brain).init(allocator);
|
||||||
errdefer server.deinit();
|
// errdefer characters.deinit(); // TODO: Add character deinit
|
||||||
|
//
|
||||||
try server.setToken(token);
|
// const chars = try server.listMyCharacters();
|
||||||
|
// defer chars.deinit();
|
||||||
var characters = std.ArrayList(Brain).init(allocator);
|
//
|
||||||
errdefer characters.deinit(); // TODO: Add character deinit
|
// for (chars.items) |char| {
|
||||||
|
// try characters.append(try Brain.init(allocator, char.name));
|
||||||
const chars = try server.listMyCharacters();
|
// }
|
||||||
defer chars.deinit();
|
|
||||||
|
|
||||||
for (chars.items) |char| {
|
|
||||||
try characters.append(try Brain.init(allocator, char.name));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
_ = allocator;
|
||||||
return Artificer{
|
return Artificer{
|
||||||
.server = server,
|
.server = server,
|
||||||
.characters = characters,
|
// .characters = characters,
|
||||||
.task_graph = TaskGraph.init(allocator)
|
// .task_graph = TaskGraph.init(allocator),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *Artificer) void {
|
pub fn deinit(self: *Artificer) void {
|
||||||
for (self.characters.items) |brain| {
|
_ = self;
|
||||||
brain.deinit();
|
// for (self.characters.items) |brain| {
|
||||||
}
|
// brain.deinit();
|
||||||
self.characters.deinit();
|
// }
|
||||||
self.server.deinit();
|
// self.characters.deinit();
|
||||||
|
// self.server.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn step(self: *Artificer) !void {
|
pub fn tick(self: *Artificer) !void {
|
||||||
if (self.paused_until) |paused_until| {
|
_ = self;
|
||||||
if (std.time.milliTimestamp() < paused_until) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
self.paused_until = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
runNextActions(self.characters.items, &self.server) catch |err| switch (err) {
|
|
||||||
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;
|
|
||||||
},
|
|
||||||
else => return err
|
|
||||||
};
|
|
||||||
|
|
||||||
for (self.characters.items) |*brain| {
|
|
||||||
if (brain.task != null) {
|
|
||||||
try brain.step(&self.server);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
// pub fn step(self: *Artificer) !void {
|
||||||
if (self.paused_until) |paused_until| {
|
// if (self.paused_until) |paused_until| {
|
||||||
return paused_until;
|
// if (std.time.milliTimestamp() < paused_until) {
|
||||||
}
|
// return;
|
||||||
|
// }
|
||||||
return earliestCooldown(self.characters.items, &self.server) orelse 0;
|
// self.paused_until = null;
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
fn earliestCooldown(characters: []Brain, api: *Api.Server) ?i64 {
|
// runNextActions(self.characters.items, &self.server) catch |err| switch (err) {
|
||||||
var earliest_cooldown: ?i64 = null;
|
// Api.FetchError.ServerUnavailable => {
|
||||||
for (characters) |*brain| {
|
// self.paused_until = std.time.milliTimestamp() + std.time.ms_per_min * server_down_retry_interval;
|
||||||
if (brain.action_queue.items.len == 0) continue;
|
// std.log.warn("Server is down, retrying in {}min", .{server_down_retry_interval});
|
||||||
|
// return;
|
||||||
const cooldown = brain.cooldown(api);
|
// },
|
||||||
if (earliest_cooldown == null or earliest_cooldown.? > cooldown) {
|
// else => return err,
|
||||||
earliest_cooldown = cooldown;
|
// };
|
||||||
}
|
//
|
||||||
}
|
// for (self.characters.items) |*brain| {
|
||||||
|
// if (brain.task != null) {
|
||||||
return earliest_cooldown;
|
// try brain.step(&self.server);
|
||||||
}
|
// continue;
|
||||||
|
// }
|
||||||
fn runNextActions(characters: []Brain, api: *Api.Server) !void {
|
//
|
||||||
for (characters) |*brain| {
|
// const character = self.server.store.getCharacter(brain.name).?;
|
||||||
if (brain.action_queue.items.len == 0) continue;
|
// if (character.task) |taskmaster_task| {
|
||||||
|
// if (taskmaster_task.total > taskmaster_task.progress) {
|
||||||
const cooldown = brain.cooldown(api);
|
// switch (taskmaster_task.type) {
|
||||||
if (std.time.milliTimestamp() >= cooldown) {
|
// .monsters => {
|
||||||
try brain.performNextAction(api);
|
// 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);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
Loading…
Reference in New Issue
Block a user