summary refs log tree commit diff
path: root/src/config.zig
blob: 380a8660e757317591e5f2569d6f8c047ee7746a (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
// 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 <https://www.gnu.org/licenses/>.

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