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(); }