// Configuration parser // Copyright (C) 2021 Nguyễn Gia Phong // // This file is part of Black Shades. // // Black Shades is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published // by the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Black Shades is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Black Shades. If not, see . const Allocator = std.mem.Allocator; const createFile = std.fs.createFileAbsolute; const cwd = std.fs.cwd; const eql = std.mem.eql; const ini = @import("ini"); const join = std.fs.path.join; const mkdir = std.os.mkdir; const openFile = std.fs.openFileAbsolute; const parseFloat = std.fmt.parseFloat; const parseInt = std.fmt.parseInt; const std = @import("std"); /// Game configuration. pub const Config = extern struct { width: c_int = 800, height: c_int = 600, vsync: bool = true, blur: bool = false, blood: bool = true, music: bool = true, mouse_sensitivity: f32 = 0.7, menu: bool = true, custom_levels: bool = false, debug: bool = false, }; /// Parse boolean values. fn parseBool(s: []const u8) !bool { if (eql(u8, s, "false")) return false; if (eql(u8, s, "true")) return true; return error.InvalidCharacter; } /// Parse config.ini in the given base directory. pub fn parse(allocator: *Allocator, base_dir: []const u8) !Config { const config_dir = try join(allocator, &.{ base_dir, "blackshades" }); defer allocator.free(config_dir); var dir = try cwd().makeOpenPath(config_dir, .{}); defer dir.close(); var config = Config{}; const input = dir.openFile("config.ini", .{}) catch { try dir.writeFile("config.ini", @embedFile("config.ini")); return config; }; defer input.close(); var parser = ini.parse(allocator, input.reader()); defer parser.deinit(); var section: []u8 = ""; defer allocator.free(section); while (try parser.next()) |record| switch (record) { .section => |heading| { allocator.free(section); section = try allocator.dupe(u8, heading); }, .property => |kv| if (eql(u8, section, "graphics")) { if (eql(u8, kv.key, "width")) config.width = try parseInt(c_int, kv.value, 10) else if (eql(u8, kv.key, "height")) config.height = try parseInt(c_int, kv.value, 10) else if (eql(u8, kv.key, "vsync")) config.vsync = try parseBool(kv.value) else if (eql(u8, kv.key, "blur")) config.blur = try parseBool(kv.value) else if (eql(u8, kv.key, "blood")) config.blood = try parseBool(kv.value) else unreachable; } else if (eql(u8, section, "audio")) { if (eql(u8, kv.key, "music")) config.music = try parseBool(kv.value) else unreachable; } else if (eql(u8, section, "input")) { if (eql(u8, kv.key, "mouse sensitivity")) config.mouse_sensitivity = try parseFloat(f32, kv.value) else unreachable; } else if (eql(u8, section, "misc")) { if (eql(u8, kv.key, "menu")) config.menu = try parseBool(kv.value) else if (eql(u8, kv.key, "custom levels")) config.custom_levels = try parseBool(kv.value) else if (eql(u8, kv.key, "debug")) config.debug = try parseBool(kv.value) else unreachable; } else unreachable, else => unreachable, }; return config; }