423 lines
12 KiB
Zig
423 lines
12 KiB
Zig
const std = @import("std");
|
|
const json_utils = @import("json_utils.zig");
|
|
const Server = @import("./server.zig");
|
|
const s2s = @import("s2s");
|
|
const json = std.json;
|
|
const Allocator = std.mem.Allocator;
|
|
const assert = std.debug.assert;
|
|
|
|
const Store = @This();
|
|
|
|
const Character = @import("./schemas/character.zig");
|
|
const Item = @import("./schemas/item.zig");
|
|
const Position = @import("./position.zig");
|
|
const Map = @import("./schemas/map.zig");
|
|
const Resource = @import("./schemas/resource.zig");
|
|
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();
|
|
|
|
self.clearMaps();
|
|
|
|
self.clearResources();
|
|
|
|
self.clearMonsters();
|
|
}
|
|
|
|
pub fn getCodeId(self: *Store, code: []const u8) !CodeId {
|
|
assert(code.len != 0);
|
|
|
|
for (0.., self.codes.items) |i, item_code| {
|
|
if (std.mem.eql(u8, code, item_code)) {
|
|
return @intCast(i);
|
|
}
|
|
}
|
|
|
|
const code_dupe = try self.allocator.dupe(u8, code);
|
|
errdefer self.allocator.free(code_dupe);
|
|
try self.codes.append(code_dupe);
|
|
|
|
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 self.codes.items[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 try self.getCodeId(code);
|
|
}
|
|
|
|
fn clearCodes(self: *Store) void {
|
|
for (self.codes.items) |code| {
|
|
self.allocator.free(code);
|
|
}
|
|
self.codes.clearAndFree();
|
|
}
|
|
|
|
// ----------------------- Storing to file ------------------------------
|
|
|
|
const SaveData = struct {
|
|
api_version: []const u8,
|
|
|
|
codes: [][]u8,
|
|
items: []Item,
|
|
maps: []Map,
|
|
resources: []Resource,
|
|
monsters: []Monster,
|
|
};
|
|
|
|
fn allocHashMapValues(Value: type, allocator: Allocator, hashmap: anytype) ![]Value {
|
|
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 {
|
|
const items = try allocHashMapValues(Item, self.allocator, self.items);
|
|
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{
|
|
.api_version = api_version,
|
|
.codes = self.codes.items,
|
|
.items = items,
|
|
.maps = maps,
|
|
.resources = resources,
|
|
.monsters = monsters
|
|
};
|
|
|
|
try s2s.serialize(writer, SaveData, data);
|
|
}
|
|
|
|
pub fn load(self: *Store, api_version: []const u8, reader: anytype) !void {
|
|
var data = try s2s.deserializeAlloc(reader, SaveData, self.allocator);
|
|
if (!std.mem.eql(u8, data.api_version, api_version)) {
|
|
s2s.free(self.allocator, SaveData, &data);
|
|
return error.InvalidVersion;
|
|
}
|
|
defer self.allocator.free(data.api_version);
|
|
|
|
self.clearCodes();
|
|
try self.codes.appendSlice(data.codes);
|
|
defer self.allocator.free(data.codes);
|
|
|
|
self.clearItems();
|
|
for (data.items) |item| {
|
|
try self.putItem(item);
|
|
}
|
|
defer self.allocator.free(data.items);
|
|
|
|
self.clearMaps();
|
|
for (data.maps) |map| {
|
|
try self.putMap(map);
|
|
}
|
|
defer self.allocator.free(data.maps);
|
|
|
|
self.clearResources();
|
|
for (data.resources) |resource| {
|
|
try self.putResource(resource);
|
|
}
|
|
defer self.allocator.free(data.resources);
|
|
|
|
self.clearMonsters();
|
|
for (data.monsters) |monster| {
|
|
try self.putMonster(monster);
|
|
}
|
|
defer self.allocator.free(data.monsters);
|
|
}
|
|
|
|
// ----------------------- Character ------------------------------
|
|
|
|
fn getCharacterIndex(self: *const Store, name: []const u8) ?usize {
|
|
for (0.., self.characters.items) |i, character| {
|
|
if (std.mem.eql(u8, character.name, name)) {
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
pub fn getCharacter(self: *Store, name: []const u8) ?Character {
|
|
if (self.getCharacterIndex(name)) |index| {
|
|
return self.characters.items[index];
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
pub fn putCharacter(self: *Store, character: Character) !void {
|
|
if (self.getCharacterIndex(character.name)) |index| {
|
|
self.characters.items[index].deinit();
|
|
self.characters.items[index] = character;
|
|
} else {
|
|
try self.characters.append(character);
|
|
}
|
|
}
|
|
|
|
// ----------------------- 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.*);
|
|
}
|
|
|
|
return found;
|
|
}
|
|
|
|
pub fn putMap(self: *Store, map: Map) !void {
|
|
var entry = try self.maps.getOrPut(map.position);
|
|
if (entry.found_existing) {
|
|
entry.value_ptr.deinit(self.allocator);
|
|
}
|
|
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;
|
|
}
|
|
|
|
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();
|
|
}
|