// 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 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 { screen_width: c_int = 640, screen_height: c_int = 480, mouse_sensitivity: f32 = 0.7, debug: bool = false, vsync: bool = true, blood: bool = true, blur: bool = false, menu: bool = true, custom_levels: bool = false, music: bool = true, azerty: 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 settings.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); const path = try join(allocator, &.{ config_dir, "settings.ini" }); defer allocator.free(path); var config = Config{}; const input = openFile(path, .{}) catch { mkdir(config_dir, 0o644) catch |err| switch (err) { error.PathAlreadyExists => {}, else => return err, }; const output = try createFile(path, .{}); defer output.close(); const writer = output.writer(); try writer.print("screen width = {}\n", .{ config.screen_width }); try writer.print("screen height = {}\n", .{ config.screen_height }); try writer.print("mouse sensitivity = {d:.1}\n", .{ config.mouse_sensitivity }); try writer.print("debug = {}\n", .{ config.debug }); try writer.print("vsync = {}\n", .{ config.vsync }); try writer.print("blood = {}\n", .{ config.blood }); try writer.print("blur = {}\n", .{ config.blur }); try writer.print("menu = {}\n", .{ config.menu }); try writer.print("custom levels = {}\n", .{ config.custom_levels }); try writer.print("music = {}\n", .{ config.music }); try writer.print("azerty = {}\n", .{ config.azerty }); return config; }; defer input.close(); var parser = ini.parse(allocator, input.reader()); defer parser.deinit(); while (try parser.next()) |record| switch (record) { .property => |kv| if (eql(u8, kv.key, "screen width")) { config.screen_width = try parseInt(c_int, kv.value, 10); } else if (eql(u8, kv.key, "screen height")) { config.screen_height = try parseInt(c_int, kv.value, 10); } else if (eql(u8, kv.key, "mouse_sensitivity")) { config.mouse_sensitivity = try parseFloat(f32, kv.value); } else if (eql(u8, kv.key, "debug")) { config.debug = try parseBool(kv.value); } else if (eql(u8, kv.key, "vsync")) { config.vsync = try parseBool(kv.value); } else if (eql(u8, kv.key, "blood")) { config.blood = try parseBool(kv.value); } else if (eql(u8, kv.key, "blur")) { config.blur = try parseBool(kv.value); } else 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, "music")) { config.music = try parseBool(kv.value); } else if (eql(u8, kv.key, "azerty")) { config.azerty = try parseBool(kv.value); }, else => unreachable, }; return config; }