summary refs log tree commit diff
path: root/src/config.zig
blob: 4bde82ce3fb933f9f4396cfe8f255f6c14c0cb4f (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
115
116
117
// 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 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;
}