dsa-4th-edition/zig/foundry.zig

165 lines
5.1 KiB
Zig

const std = @import("std");
const leveldb = @import("leveldb");
const system = @import("system.zig");
const String = []const u8;
const CORE_VERSION = "12.331";
const SYSTEM_NAME = system.SYSTEM_NAME;
const SYSTEM_VERSION = system.SYSTEM_VERSION;
// Entry must be a tagged union with one tag being
// Folder: struct {
// name: String,
// entries: []const Entry,
// },
// and the other tags being the respective Foundry type name
pub fn Compendium(base_type: BaseType, Entry: type) type {
if (std.meta.activeTag(@typeInfo(Entry)) != .@"union") @compileError("Entry must be a tagged union.");
if (@typeInfo(Entry).@"union".tag_type == null) @compileError("Entry must be a tagged union.");
return struct {
entries: []const Entry = &.{},
pub fn serialize(self: @This(), path: [:0]const u8) !void {
try leveldb.destroy(.{ .path = path });
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
var diagnostic: leveldb.Diagnostic = null;
const db = leveldb.open(.{ .path = path, .diagnostic = &diagnostic, .options = .{ .create_if_missing = true, .compression = .Snappy } }) catch |err| {
std.log.err("leveldb.open failed: {s}", .{ diagnostic.? });
return err;
};
defer db.close();
for (self.entries) |entry| {
defer _ = arena.reset(.retain_capacity);
try serialize_entry(arena.allocator(), db, entry, null);
}
}
fn serialize_entry(allocator: std.mem.Allocator, db: leveldb, entry: Entry, folder: ?String) !void{
switch (entry) {
.Folder => |_folder| {
const foundry_folder: Folder(base_type) = .{
.name = _folder.name,
._id = &random_id(),
.folder = folder,
};
const key = try std.fmt.allocPrintZ(allocator, "!folders!{s}", .{ foundry_folder._id.? });
const value = try std.json.stringifyAlloc(allocator, foundry_folder, .{});
try db.put(.{ .key = key, .value = value });
for (_folder.entries) |folder_entry| {
try serialize_entry(allocator, db, folder_entry, foundry_folder._id);
}
},
inline else => |item| {
std.debug.assert(item.folder == null);
var foundry_item = item;
foundry_item.folder = folder;
if (foundry_item._id == null)
foundry_item._id = &random_id();
const key = try std.fmt.allocPrintZ(allocator, "!{s}!{s}", .{ base_type.to_compendium_type(), foundry_item._id.? });
const value = try std.json.stringifyAlloc(allocator, foundry_item, .{});
try db.put(.{ .key = key, .value = value });
}
}
}
};
}
const BaseType = enum {
Item,
Actor,
fn to_compendium_type(self: @This()) String {
return switch (self) {
.Item => "items",
.Actor => "actors",
};
}
};
fn Folder(base_type: BaseType) type {
return struct {
_id: ?String = null,
name: String,
@"type": BaseType = base_type,
description: String = "",
folder: ?String = null,
sorting: enum { a, m } = .a,
sort: u64 = 0,
color: ?String = null,
flags: struct {} = .{},
_stats: DocumentStats = .{},
};
}
pub fn Item(comptime typename: String, comptime T: type) type {
return struct {
const Type: BaseType = .Item;
_id: ?String = null,
name: String,
@"type": String = typename,
img: String = "icons/svg/item-bag.svg",
system: T,
effects: []const u8 = &.{}, // TODO
folder: ?String = null,
sort: u64 = 0,
ownership: struct {
default: u8 = 0,
} = .{},
flags: struct {} = .{},
_stats: DocumentStats = .{},
};
}
pub const DocumentStats = struct {
coreVersion: String = CORE_VERSION,
systemId: String = SYSTEM_NAME,
systemVersion: String = SYSTEM_VERSION,
createdTime: ?u64 = null,
modifiedTime: ?u64 = null,
lastModifiedBy: ?String = null,
compendiumSource: ?String = null,
duplicateSource: ?String = null,
};
inline fn random_char(alphabet: []const u8) u8 {
return alphabet[std.crypto.random.uintLessThan(u8, alphabet.len)];
}
var id_set = std.BufSet.init(std.heap.c_allocator);
fn random_id() [16]u8 {
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
var result: [16]u8 = undefined;
inline for (&result) |*c| c.* = random_char(alphabet);
if (id_set.contains(&result)) {
return random_id();
}
id_set.insert(&result) catch unreachable;
return result;
}