added doomplayer
This commit is contained in:
parent
8c12242304
commit
98a2f6577f
|
|
@ -0,0 +1,25 @@
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub fn build(b: *std.Build) void {
|
||||||
|
const target = b.standardTargetOptions(.{});
|
||||||
|
const optimize = b.standardOptimizeOption(.{});
|
||||||
|
|
||||||
|
const mod = b.addModule("doomplayer", .{
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
.root_source_file = b.path("src/doom.zig"),
|
||||||
|
});
|
||||||
|
|
||||||
|
mod.addIncludePath(b.path("src/"));
|
||||||
|
mod.addCSourceFile(.{
|
||||||
|
.file = b.path("src/impl.c"),
|
||||||
|
.flags = &.{
|
||||||
|
"-fno-sanitize=undefined",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
{
|
||||||
|
const dep = b.dependency("Backlog", .{ .target = target, .optimize = optimize });
|
||||||
|
mod.addImport("Backlog", dep.module("Backlog"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
.{
|
||||||
|
.name = .gameExtras,
|
||||||
|
.version = "0.0.0",
|
||||||
|
.dependencies = .{
|
||||||
|
.Backlog = .{ .path = "../../" },
|
||||||
|
},
|
||||||
|
.paths = .{
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
.fingerprint = 0x993cf5f4ca155aa6,
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,3 @@
|
||||||
|
pub const c = @cImport({
|
||||||
|
@cInclude("pureDOOM.h");
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,420 @@
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
|
||||||
|
doomSampler: *gpu.GPUTexture = undefined,
|
||||||
|
|
||||||
|
gpuTransferBuffer: *gpu.GPUTransferBuffer = undefined,
|
||||||
|
texture: *gpu.GPUTexture = undefined,
|
||||||
|
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
|
||||||
|
fn log(comptime fmt: []const u8, args: anytype) void {
|
||||||
|
core.engine_log("[Doomplayer] " ++ fmt, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create(allocator: std.mem.Allocator) !*@This() {
|
||||||
|
const self = try allocator.create(@This());
|
||||||
|
self.* = .{
|
||||||
|
.allocator = allocator,
|
||||||
|
.width = 320,
|
||||||
|
.height = 200,
|
||||||
|
};
|
||||||
|
|
||||||
|
log("initialized", .{});
|
||||||
|
|
||||||
|
try self.setup();
|
||||||
|
|
||||||
|
gDoom = self;
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setup(self: *@This()) !void {
|
||||||
|
try self.createTextures();
|
||||||
|
try backlog.platform.context().addSDLProcessFunction(processFunc);
|
||||||
|
c.doom_set_resolution(320, 200);
|
||||||
|
c.doom_set_default_int("key_up", c.DOOM_KEY_W);
|
||||||
|
c.doom_set_default_int("key_down", c.DOOM_KEY_S);
|
||||||
|
c.doom_set_default_int("key_strafeleft", c.DOOM_KEY_A);
|
||||||
|
c.doom_set_default_int("key_straferight", c.DOOM_KEY_D);
|
||||||
|
c.doom_set_default_int("key_use", c.DOOM_KEY_E);
|
||||||
|
c.doom_set_default_int("mouse_move", 0); // Mouse will not move forward
|
||||||
|
|
||||||
|
c.doom_init(1, @constCast(@ptrCast(@alignCast(&.{"doom"}))), 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sdlToDoomScancode(sdlScancode: c_uint) c_int {
|
||||||
|
switch (sdlScancode) {
|
||||||
|
sdl3_c.SDL_SCANCODE_TAB => {
|
||||||
|
return c.DOOM_KEY_TAB;
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_SCANCODE_RETURN => {
|
||||||
|
return c.DOOM_KEY_ENTER;
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_SCANCODE_ESCAPE => {
|
||||||
|
return c.DOOM_KEY_ESCAPE;
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_SCANCODE_SPACE => {
|
||||||
|
return c.DOOM_KEY_SPACE;
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_SCANCODE_APOSTROPHE => {
|
||||||
|
return c.DOOM_KEY_APOSTROPHE;
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_SCANCODE_KP_MULTIPLY => {
|
||||||
|
return c.DOOM_KEY_MULTIPLY;
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_SCANCODE_COMMA => {
|
||||||
|
return c.DOOM_KEY_COMMA;
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_SCANCODE_MINUS => {
|
||||||
|
return c.DOOM_KEY_MINUS;
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_SCANCODE_PERIOD => {
|
||||||
|
return c.DOOM_KEY_PERIOD;
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_SCANCODE_SLASH => {
|
||||||
|
return c.DOOM_KEY_SLASH;
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_SCANCODE_0 => {
|
||||||
|
return c.DOOM_KEY_0;
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_SCANCODE_1 => {
|
||||||
|
return c.DOOM_KEY_1;
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_SCANCODE_2 => {
|
||||||
|
return c.DOOM_KEY_2;
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_SCANCODE_3 => {
|
||||||
|
return c.DOOM_KEY_3;
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_SCANCODE_4 => {
|
||||||
|
return c.DOOM_KEY_4;
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_SCANCODE_5 => {
|
||||||
|
return c.DOOM_KEY_5;
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_SCANCODE_6 => {
|
||||||
|
return c.DOOM_KEY_6;
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_SCANCODE_7 => {
|
||||||
|
return c.DOOM_KEY_7;
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_SCANCODE_8 => {
|
||||||
|
return c.DOOM_KEY_8;
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_SCANCODE_9 => {
|
||||||
|
return c.DOOM_KEY_9;
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_SCANCODE_SEMICOLON => {
|
||||||
|
return c.DOOM_KEY_SEMICOLON;
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_SCANCODE_EQUALS => {
|
||||||
|
return c.DOOM_KEY_EQUALS;
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_SCANCODE_LEFTBRACKET => {
|
||||||
|
return c.DOOM_KEY_LEFT_BRACKET;
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_SCANCODE_RIGHTBRACKET => {
|
||||||
|
return c.DOOM_KEY_RIGHT_BRACKET;
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_SCANCODE_A => {
|
||||||
|
return c.DOOM_KEY_A;
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_SCANCODE_B => {
|
||||||
|
return c.DOOM_KEY_B;
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_SCANCODE_C => {
|
||||||
|
return c.DOOM_KEY_C;
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_SCANCODE_D => {
|
||||||
|
return c.DOOM_KEY_D;
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_SCANCODE_E => {
|
||||||
|
return c.DOOM_KEY_E;
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_SCANCODE_F => {
|
||||||
|
return c.DOOM_KEY_F;
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_SCANCODE_G => {
|
||||||
|
return c.DOOM_KEY_G;
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_SCANCODE_H => {
|
||||||
|
return c.DOOM_KEY_H;
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_SCANCODE_I => {
|
||||||
|
return c.DOOM_KEY_I;
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_SCANCODE_J => {
|
||||||
|
return c.DOOM_KEY_J;
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_SCANCODE_K => {
|
||||||
|
return c.DOOM_KEY_K;
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_SCANCODE_L => {
|
||||||
|
return c.DOOM_KEY_L;
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_SCANCODE_M => {
|
||||||
|
return c.DOOM_KEY_M;
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_SCANCODE_N => {
|
||||||
|
return c.DOOM_KEY_N;
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_SCANCODE_O => {
|
||||||
|
return c.DOOM_KEY_O;
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_SCANCODE_P => {
|
||||||
|
return c.DOOM_KEY_P;
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_SCANCODE_Q => {
|
||||||
|
return c.DOOM_KEY_Q;
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_SCANCODE_R => {
|
||||||
|
return c.DOOM_KEY_R;
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_SCANCODE_S => {
|
||||||
|
return c.DOOM_KEY_S;
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_SCANCODE_T => {
|
||||||
|
return c.DOOM_KEY_T;
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_SCANCODE_U => {
|
||||||
|
return c.DOOM_KEY_U;
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_SCANCODE_V => {
|
||||||
|
return c.DOOM_KEY_V;
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_SCANCODE_W => {
|
||||||
|
return c.DOOM_KEY_W;
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_SCANCODE_X => {
|
||||||
|
return c.DOOM_KEY_X;
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_SCANCODE_Y => {
|
||||||
|
return c.DOOM_KEY_Y;
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_SCANCODE_Z => {
|
||||||
|
return c.DOOM_KEY_Z;
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_SCANCODE_BACKSPACE => {
|
||||||
|
return c.DOOM_KEY_BACKSPACE;
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_SCANCODE_LCTRL => {},
|
||||||
|
sdl3_c.SDL_SCANCODE_RCTRL => {
|
||||||
|
return c.DOOM_KEY_CTRL;
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_SCANCODE_LEFT => {
|
||||||
|
return c.DOOM_KEY_LEFT_ARROW;
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_SCANCODE_UP => {
|
||||||
|
return c.DOOM_KEY_UP_ARROW;
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_SCANCODE_RIGHT => {
|
||||||
|
return c.DOOM_KEY_RIGHT_ARROW;
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_SCANCODE_DOWN => {
|
||||||
|
return c.DOOM_KEY_DOWN_ARROW;
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_SCANCODE_LSHIFT, sdl3_c.SDL_SCANCODE_RSHIFT => {
|
||||||
|
return c.DOOM_KEY_SHIFT;
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_SCANCODE_LALT => {},
|
||||||
|
sdl3_c.SDL_SCANCODE_RALT => {
|
||||||
|
return c.DOOM_KEY_ALT;
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_SCANCODE_F1 => {
|
||||||
|
return c.DOOM_KEY_F1;
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_SCANCODE_F2 => {
|
||||||
|
return c.DOOM_KEY_F2;
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_SCANCODE_F3 => {
|
||||||
|
return c.DOOM_KEY_F3;
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_SCANCODE_F4 => {
|
||||||
|
return c.DOOM_KEY_F4;
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_SCANCODE_F5 => {
|
||||||
|
return c.DOOM_KEY_F5;
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_SCANCODE_F6 => {
|
||||||
|
return c.DOOM_KEY_F6;
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_SCANCODE_F7 => {
|
||||||
|
return c.DOOM_KEY_F7;
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_SCANCODE_F8 => {
|
||||||
|
return c.DOOM_KEY_F8;
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_SCANCODE_F9 => {
|
||||||
|
return c.DOOM_KEY_F9;
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_SCANCODE_F10 => {
|
||||||
|
return c.DOOM_KEY_F10;
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_SCANCODE_F11 => {
|
||||||
|
return c.DOOM_KEY_F11;
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_SCANCODE_F12 => {
|
||||||
|
return c.DOOM_KEY_F12;
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_SCANCODE_PAUSE => {
|
||||||
|
return c.DOOM_KEY_PAUSE;
|
||||||
|
},
|
||||||
|
else => {
|
||||||
|
return c.DOOM_KEY_UNKNOWN;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.DOOM_KEY_UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
var gDoom: *@This() = undefined;
|
||||||
|
|
||||||
|
pub fn processFunc(e: *sdl3.Event) void {
|
||||||
|
switch (e.type) {
|
||||||
|
else => {},
|
||||||
|
sdl3_c.SDL_EVENT_MOUSE_MOTION => {
|
||||||
|
c.doom_mouse_move(@intFromFloat(e.motion.xrel * 4), @intFromFloat(e.motion.yrel * 4));
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_EVENT_MOUSE_BUTTON_DOWN => {
|
||||||
|
c.doom_key_down(sdlToDoomScancode(e.button.button));
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_EVENT_MOUSE_BUTTON_UP => {
|
||||||
|
c.doom_key_down(sdlToDoomScancode(e.button.button));
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_EVENT_KEY_DOWN => {
|
||||||
|
if (!e.key.repeat) {
|
||||||
|
c.doom_key_down(sdlToDoomScancode(e.key.scancode));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
sdl3_c.SDL_EVENT_KEY_UP => {
|
||||||
|
if (!e.key.repeat) {
|
||||||
|
c.doom_key_up(sdlToDoomScancode(e.key.scancode));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn getFrameLength(self: @This()) c_int {
|
||||||
|
return self.width * self.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn acquireVideoFrameBuffer(self: *@This()) []u8 {
|
||||||
|
var slice: []u8 = undefined;
|
||||||
|
slice.ptr = rend.context().device.mapGPUTransferBuffer(self.gpuTransferBuffer, false);
|
||||||
|
slice.len = @intCast(self.width * self.width * 4);
|
||||||
|
return slice;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn releaseAndUploadFrameBuffer(self: *@This(), cmd: *gpu.GPUCommandBuffer) void {
|
||||||
|
const copyPass = cmd.beginGPUCopyPass();
|
||||||
|
|
||||||
|
copyPass.uploadToGPUTexture(
|
||||||
|
&.{
|
||||||
|
.transfer_buffer = self.gpuTransferBuffer,
|
||||||
|
.offset = 0,
|
||||||
|
.pixels_per_row = self.width,
|
||||||
|
.rows_per_layer = self.height,
|
||||||
|
},
|
||||||
|
&std.mem.zeroInit(gpu.GPUTextureRegion, .{
|
||||||
|
.texture = self.texture,
|
||||||
|
.w = self.width,
|
||||||
|
.h = self.height,
|
||||||
|
.d = 1,
|
||||||
|
}),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
|
copyPass.endGPUCopyPass();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn createTextureInner(width: u32, height: u32) !*gpu.GPUTexture {
|
||||||
|
const ctx = rend.context();
|
||||||
|
|
||||||
|
const gpuTexture = ctx.device.createGPUTexture(&std.mem.zeroInit(gpu.GPUTextureCreateInfo, .{
|
||||||
|
.type = .texturetype2d,
|
||||||
|
.format = .textureformatR8g8b8a8Unorm,
|
||||||
|
.usage = .{ .textureusageSampler = true },
|
||||||
|
.width = @as(u32, @intCast(width)),
|
||||||
|
.height = @as(u32, @intCast(height)),
|
||||||
|
.layer_count_or_depth = 1,
|
||||||
|
.num_levels = 1,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return gpuTexture;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn createTextures(self: *@This()) !void {
|
||||||
|
self.texture = try createTextureInner(self.width, self.height);
|
||||||
|
|
||||||
|
// create transfer Buffer
|
||||||
|
self.gpuTransferBuffer = rend.context().device.createGPUTransferBuffer(&.{
|
||||||
|
.usage = .transferbufferusageUpload,
|
||||||
|
.size = @as(u32, @intCast(self.width * self.height * 4)),
|
||||||
|
.props = 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tick(self: *@This(), dt: f64) void {
|
||||||
|
_ = dt;
|
||||||
|
c.doom_update();
|
||||||
|
|
||||||
|
const frame = self.acquireVideoFrameBuffer();
|
||||||
|
|
||||||
|
const doomFb = c.doom_get_framebuffer(4);
|
||||||
|
|
||||||
|
var i: usize = 0;
|
||||||
|
while (i < self.width * self.height * 4) : (i += 1) {
|
||||||
|
frame[i] = doomFb[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
rend.context().device.unmapGPUTransferBuffer(self.gpuTransferBuffer);
|
||||||
|
|
||||||
|
const cmd = rend.context().device.acquireGPUCommandBuffer();
|
||||||
|
self.releaseAndUploadFrameBuffer(cmd);
|
||||||
|
_ = cmd.submitGPUCommandBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bindVideoTextures(p: ?*anyopaque) void {
|
||||||
|
const self: *@This() = @ptrCast(@alignCast(p));
|
||||||
|
const ctx = rend.context();
|
||||||
|
const renderpass: *gpu.GPURenderPass = ctx.state.pass.?;
|
||||||
|
renderpass.bindGPUFragmentSamplers(0, &.{ .sampler = ctx.blockySampler, .texture = self.texture }, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// creates a video player entity
|
||||||
|
pub fn createEntity(self: *@This()) !core.Entity {
|
||||||
|
const entity = try core.createEntity();
|
||||||
|
const scene = entity.addComponent(core.Scene).?;
|
||||||
|
const size = 5.0;
|
||||||
|
const h: f32 = @floatFromInt(self.height);
|
||||||
|
const w: f32 = @floatFromInt(self.width);
|
||||||
|
|
||||||
|
scene.setScale(size, size, size * (h / w));
|
||||||
|
scene.setRotation(core.Rotation.eulerX(core.radians(90.0)));
|
||||||
|
scene.setPosition(.{ .z = 30 });
|
||||||
|
|
||||||
|
if (entity.addComponent(rend.MeshComponent)) |mesh| {
|
||||||
|
mesh.cmrf = bindVideoTextures;
|
||||||
|
mesh.cmrf_ctx = self;
|
||||||
|
mesh.textureMode = 0x1;
|
||||||
|
mesh.setMesh("m_plane");
|
||||||
|
}
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn destroy(self: *@This()) void {
|
||||||
|
core.engine_logs("[Videoplayer] destroying player");
|
||||||
|
|
||||||
|
self.allocator.destroy(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
const backlog = @import("Backlog");
|
||||||
|
const core = backlog.core;
|
||||||
|
const rend = backlog.rend;
|
||||||
|
const sdl3 = backlog.platform.windowing.sdl3;
|
||||||
|
const sdl3_c = sdl3.c;
|
||||||
|
const c = @import("c.zig").c;
|
||||||
|
const gpu = rend.renderer.gpu;
|
||||||
|
const ig = backlog.imgui.api;
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
|
||||||
|
#define DOOM_IMPLEMENTATION
|
||||||
|
#define DOOM_IMPLEMENT_MALLOC
|
||||||
|
#define DOOM_IMPLEMENT_PRINT
|
||||||
|
#define DOOM_IMPLEMENT_MALLOC
|
||||||
|
#define DOOM_IMPLEMENT_FILE_IO
|
||||||
|
#define DOOM_IMPLEMENT_SOCKETS
|
||||||
|
#define DOOM_IMPLEMENT_GETTIME
|
||||||
|
#define DOOM_IMPLEMENT_GETENV
|
||||||
|
|
||||||
|
#include "PureDOOM.h"
|
||||||
|
|
||||||
Loading…
Reference in New Issue