summary refs log tree commit diff
path: root/src/config.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/config.zig')
-rw-r--r--src/config.zig138
1 files changed, 119 insertions, 19 deletions
diff --git a/src/config.zig b/src/config.zig
index dbe0f7d..4a1840d 100644
--- a/src/config.zig
+++ b/src/config.zig
@@ -16,19 +16,116 @@
 // 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 Dir = std.fs.Dir;
+const File = std.fs.File;
 const allocator = std.heap.c_allocator;
-const createFile = std.fs.createFileAbsolute;
+const c = @import("main.zig").c;
 const cwd = std.fs.cwd;
 const data_dir = @import("build_options").data_dir;
 const eql = std.mem.eql;
 const ini = @import("ini");
 const join = std.fs.path.join;
+const maxInt = std.math.maxInt;
 const mkdir = std.os.mkdir;
-const openFile = std.fs.openFileAbsolute;
 const parseBool = @import("misc.zig").parseBool;
 const parseFloat = std.fmt.parseFloat;
 const parseInt = std.fmt.parseInt;
 const std = @import("std");
+const tokenize = std.mem.tokenize;
+
+const default_levels_len = 13;
+
+/// Open the given file for reading,
+/// on error copy it from the game's data and try again.
+fn openData(dir: Dir, filename: []const u8) !File {
+    return dir.openFile(filename, .{}) catch {
+        var data = try cwd().makeOpenPath(data_dir, .{});
+        defer data.close();
+        try data.copyFile(filename, dir, filename, .{});
+        return try dir.openFile(filename, .{});
+    };
+}
+
+const Level = extern struct {
+    environment: c_int,
+    evil_weapons: u8,
+    evil_rarity: u8,
+    guard_weapon: u8,
+    guard_reloads: u8,
+    time: c_int,
+    difficulty: f32,
+};
+
+const Weapons = packed struct {
+    // TODO: remove nogun and sort
+    jaw: bool = false,
+    sniper_rifle: bool = false,
+    assault_rifle: bool = false,
+    magnum: bool = false,
+    handgun: bool = false,
+    grenade: bool = false,
+    knife: bool = false,
+    shotgun: bool = false,
+};
+
+fn parseLevels(dir_path: []const u8, length: usize) ![*]Level {
+    var dir = try cwd().makeOpenPath(dir_path, .{});
+    defer dir.close();
+    const input = try openData(dir, "levels.ini");
+    defer input.close();
+    const levels = try allocator.alloc(Level, length);
+    errdefer allocator.free(levels);
+    var parser = ini.parse(allocator, input.reader());
+    defer parser.deinit();
+
+    var i: usize = maxInt(usize);
+    while (try parser.next()) |record|
+        switch (record) {
+            .section => i +%= 1,
+            .property => |kv| if (eql(u8, kv.key, "environment")) {
+                levels[i].environment = if (eql(u8, kv.value, "sunny"))
+                    c.sunny_environment
+                else if (eql(u8, kv.value, "foggy"))
+                    c.foggy_environment
+                else if (eql(u8, kv.value, "snowy"))
+                    c.snowy_environment
+                else if (eql(u8, kv.value, "rainy"))
+                    c.rainy_environment
+                else if (eql(u8, kv.value, "firey"))
+                    c.firey_environment
+                else if (eql(u8, kv.value, "night"))
+                    c.night_environment
+                else return error.InvalidData;
+            } else if (eql(u8, kv.key, "evil weapons")) {
+                var weapons = Weapons{};
+                var enums = tokenize(kv.value, " ");
+                while (enums.next()) |weapon|
+                    switch (try parseInt(u3, weapon, 10)) {
+                        c.nogun => weapons.jaw = true,
+                        c.sniperrifle => weapons.sniper_rifle = true,
+                        c.assaultrifle => weapons.assault_rifle = true,
+                        c.handgun1 => weapons.magnum = true,
+                        c.handgun2 => weapons.handgun = true,
+                        c.grenade => weapons.grenade = true,
+                        c.knife => weapons.knife = true,
+                        c.shotgun => weapons.shotgun = true,
+                    };
+                levels[i].evil_weapons = @bitCast(u8, weapons);
+            } else if (eql(u8, kv.key, "evil rarity")) {
+                levels[i].evil_rarity = try parseInt(u8, kv.value, 10);
+            } else if (eql(u8, kv.key, "guard weapon")) {
+                levels[i].guard_weapon = try parseInt(u3, kv.value, 10);
+            } else if (eql(u8, kv.key, "guard reloads")) {
+                levels[i].guard_reloads = try parseInt(u8, kv.value, 10);
+            } else if (eql(u8, kv.key, "time")) {
+                levels[i].time = try parseInt(c_int, kv.value, 10);
+            } else if (eql(u8, kv.key, "difficulty")) {
+                levels[i].difficulty = try parseFloat(f32, kv.value);
+            } else return error.InvalidData,
+            else => return error.InvalidData,
+        };
+    return levels.ptr;
+}
 
 /// Game configuration.
 pub const Config = extern struct {
@@ -42,7 +139,10 @@ pub const Config = extern struct {
 
     mouse_sensitivity: f32 = 1.0,
 
-    custom_levels: bool = false,
+    levels: extern struct {
+        ptr: [*]Level,
+        len: usize,
+    } = .{ .ptr = undefined, .len = 0 },
     debug: bool = false,
 };
 
@@ -52,19 +152,12 @@ pub fn parse(base_dir: []const u8) !Config {
     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 blk: {
-        var source = try cwd().makeOpenPath(data_dir, .{});
-        defer source.close();
-        try source.copyFile("config.ini", dir, "config.ini", .{});
-        break :blk try dir.openFile("config.ini", .{});
-    };
+    const input = try openData(dir, "config.ini");
     defer input.close();
-
     var parser = ini.parse(allocator, input.reader());
     defer parser.deinit();
 
+    var config = Config{};
     var section: []u8 = "";
     defer allocator.free(section);
     while (try parser.next()) |record|
@@ -84,23 +177,30 @@ pub fn parse(base_dir: []const u8) !Config {
                     config.blur = try parseBool(kv.value)
                 else if (eql(u8, kv.key, "blood"))
                     config.blood = try parseBool(kv.value)
-                else unreachable;
+                else return error.InvalidData;
             } else if (eql(u8, section, "audio")) {
                 if (eql(u8, kv.key, "music"))
                     config.music = try parseBool(kv.value)
-                else unreachable;
+                else return error.InvalidData;
             } else if (eql(u8, section, "input")) {
                 if (eql(u8, kv.key, "mouse sensitivity"))
                     config.mouse_sensitivity = try parseFloat(f32, kv.value)
-                else unreachable;
+                else return error.InvalidData;
             } else if (eql(u8, section, "misc")) {
                 if (eql(u8, kv.key, "custom levels"))
-                    config.custom_levels = try parseBool(kv.value)
+                    config.levels.len = try parseInt(usize, kv.value, 10)
                 else if (eql(u8, kv.key, "debug"))
                     config.debug = try parseBool(kv.value)
-                else unreachable;
-            } else unreachable,
-            else => unreachable,
+                else return error.InvalidData;
+            } else return error.InvalidData,
+            else => return error.InvalidData,
         };
+
+    if (config.levels.len > 0) {
+        config.levels.ptr = try parseLevels(config_dir, config.levels.len);
+    } else {
+        config.levels.len = default_levels_len;
+        config.levels.ptr = try parseLevels(data_dir, config.levels.len);
+    }
     return config;
 }