pub export fn shutdown_module() void {} pub export fn startup_module(p_allocator: *anyopaque, p_a: ?*anyopaque) bool { const allocator = core.modulePreamble(p_allocator, p_a) catch return false; _ = allocator; imgui.setupFromModule(); platform.setupFromModule(); sys.setupFromModule(); rend.setupFromModule(); backlog.physics.setupFromModule(); core.engine_logs("creating externgame"); start_module(core.startup_getArgs(p_a.?)) catch return false; return true; } pub export fn add(a: i32, b: i32) i32 { return a + b; } pub export fn subtract(a: i32, b: i32) i32 { return a + b; } var gAllocator: std.mem.Allocator = undefined; pub fn start_module(args: core.ModuleLoaderArgs) !void { core.PatchOrCreateObject(extras.GameList, .{}); core.PatchOrCreateObject(extras.ModuleLoader, .{}); core.PatchOrCreateObject(extras.ObjectSystemSpawner, .{}); core.PatchOrCreateObject(net.EnetTransport, .{}); core.PatchOrCreateObject(ExternGameObject, .{}); core.PatchOrCreateObject(imgui.utils.ConsoleWindow, .{}); core.PatchOrCreateObject(@import("fpgame/fpgame.zig"), .{}); core.engine_log("this change definitely happened :^) ", .{}); _ = args; core.logDisplay("externGame", "accessing old object at {x} same object? {d}", .{ @intFromPtr(core.EngineObject(ExternGameObject).get()), @sizeOf(ExternGameObject) }); } pub const BoxObject = struct { playing: bool = false, pub fn create(allocator: std.mem.Allocator, entity: core.Entity, params: core.SpawnParameters) !*@This() { const position = params.posRot.position; const box2 = entity; const scene = box2.addComponent(core.Scene).?; const mesh = box2.addComponent(rend.MeshComponent).?; mesh.setMesh("m_crate"); mesh.setTexture("t_crate"); scene.setPosition(position); scene.setScaleV(params.posRot.scale); scene.setMobility(.moveable); const collider = box2.addComponent(physics.PhysicsCollider).?; try collider.setupByShapeName(core.MakeName("ph_small_box"), .{ .motion_type = .dynamic, .object_layer = physics.ObjectLayers.moving, .friction = 0.8, .mass_properties_override = .{ .mass = 12 }, .override_mass_properties = .calc_inertia, }); return try allocator.create(@This()); } pub fn createSmoky(allocator: std.mem.Allocator, entity: core.Entity, params: core.SpawnParameters) !*@This() { const rv = try create(allocator, entity, params); const particle = entity.addComponent(rend.ParticleEmitter).?; particle.start(); particle.gravity = .{ .y = 1.0 }; particle.particleVelocity = .{ .min = 0.6, .max = 1.2 }; particle.particleLife = .{ .min = 5, .max = 6 }; var name = core.MakeName("t_pixelSmoke"); particle.texture = rend.getTexture(&name).?; return rv; } pub fn destroy(self: *@This(), alloc: std.mem.Allocator) void { alloc.destroy(self); } }; pub const ExternGameArgs = struct { clientTarget: []const u8 = "127.0.0.1", clientIndex: u32 = 0, asClient: bool = false, }; pub const ExternGameObject = struct { pub var NeonObjectTable = core.EngineObjectVTable.from(@This(), "game.ExternGameObject"); pub const Slack = core.SlackStruct(@This(), 1024); allocator: std.mem.Allocator = undefined, tbMap: ?*bsp.maploader.TBMap = null, physicsObjectsWindow: *extras.PhysicsObjectList = undefined, fireTraceFilter: physics.IgnoreFixedBodiesFilter = .{}, particleDebugger: extras.ParticleDebugger.ParticleDebugger = undefined, session: ?*net.Session = null, clientLink: ?*net.Link = null, launchAsClient: ?[]const u8 = null, recompiling: bool = false, firstTick: bool = true, fpcamera: *extras.FpCamera = undefined, fireInput: ?*core.ActionBinding = null, altFireInput: ?*core.ActionBinding = null, reloadInput: ?*core.ActionBinding = null, openSpawner: ?*core.ActionBinding = null, reloadMapInput: ?*core.ActionBinding = null, volume: f32 = 100.0, volume2: f32 = 100.0, a: core.Vector2f = .{ .x = 1, .y = 4 }, b: core.Vector2f = .{ .x = 4, .y = 1 }, Anormal: core.Vector2f = .{ .x = -1, .y = 3 }, Bnormal: core.Vector2f = .{ .x = -1, .y = -3 }, spawnedEntities: std.ArrayListUnmanaged(core.Entity) = .{}, clientIndex: u32 = 0, autoAddClients: bool = true, clientsToAdd: u32 = 1, displayVectorsTest: bool = false, pub fn beginPlay(p: *anyopaque) void { const self = core.cast(*@This(), p); ui.context().screenContext.drawDebug = true; self._beginPlay() catch { core.engine_errs("unable to beginplay"); }; } pub fn _beginPlay(self: *@This()) !void { try self.setupInputs(); if (self.firstTick) { self.loadMap2() catch unreachable; self.firstTick = false; } // change this into a parallel for for (0..400) |i| { _ = try core.get(core.GameObjectSystem).spawnObject(BoxObject, "Box", .{ .posRot = .{ .position = .{ .x = core.itof32((i % 100)) * 2, .z = 15.0, .y = core.itof32(@divFloor(i, 100) * 2) }, }, }); } physics.optimizeBroadPhase(); } pub fn onMapReload(ctx: ?*anyopaque, _: core.ActionEvent) void { const self = core.cast(*@This(), ctx.?); self.loadMap2() catch {}; } pub fn setupInputs(self: *@This()) !void { if (self.fireInput) |fireInput| { fireInput.deactivate(); } if (self.altFireInput) |altFireInput| { altFireInput.deactivate(); } if (self.reloadInput) |input| { input.deactivate(); } if (self.openSpawner) |input| { input.deactivate(); self.openSpawner = null; } if (self.reloadMapInput) |input| { input.deactivate(); self.reloadMapInput = null; } if (self.reloadMapInput == null) { self.reloadMapInput = try core.ActionBinding.create(core.MakeName("input")); self.reloadMapInput.?.addKey(.@"7", .keyDown); _ = self.reloadMapInput.?.data.addListener(@This(), "onMapReload", self); } self.reloadMapInput.?.activate(); if (self.reloadInput == null) { self.reloadInput = try core.ActionBinding.create(core.MakeName("reloadInput")); self.reloadInput.?.addKey(.@"9", .keyDown); _ = self.reloadInput.?.data.addListener(@This(), "onRequestReload", self); } self.reloadInput.?.activate(); if (self.fireInput == null) { self.fireInput = try core.ActionBinding.create(core.MakeName("fire")); self.fireInput.?.addKey(.Mouse1, .keyDown); _ = self.fireInput.?.data.addListener(@This(), "onFire", self); } self.fireInput.?.activate(); if (self.altFireInput == null) { self.altFireInput = try core.ActionBinding.create(core.MakeName("altFire")); self.altFireInput.?.addKey(.Mouse3, .keyDown); _ = self.altFireInput.?.data.addListener(@This(), "onAltFire", self); } self.altFireInput.?.activate(); if (self.openSpawner == null) { self.openSpawner = try core.ActionBinding.create(core.MakeName("openSpawner")); self.openSpawner.?.addKey(.@"8", .keyDown); _ = self.openSpawner.?.data.addListener(@This(), "onOpenSpawner", self); } self.openSpawner.?.activate(); core.engine_log("inputs bound for externGame", .{}); } pub fn objectReload(self: *@This()) void { core.engine_logs("extern game object reload"); self.setupInputs() catch { core.engine_errs("Unable to setup inputs"); }; } pub fn endPlay(p: *anyopaque) void { const self = core.cast(*@This(), p); self.fireInput.?.deactivate(); self.altFireInput.?.deactivate(); } pub fn loadMap2(self: *@This()) !void { if (self.tbMap) |tbMap| { core.engine_log("killing the map", .{}); tbMap.destroy(); } self.tbMap = try bsp.maploader.LoadTrenchbroomMap(self.allocator, .{ .rotation = core.Rotation.eulerX(core.radians(-90.0)), .mapName = "bsp/testmap.map", }); core.engine_log("map loaded", .{}); } pub fn create(allocator: std.mem.Allocator) !*@This() { const self = try Slack.create(allocator); self.* = .{ .allocator = allocator, .particleDebugger = extras.ParticleDebugger.ParticleDebugger.init(), }; // core.get(imgui.utils.TopBar).addMenuObject(self, "Input Stack Viewer", extras.inputDebugger.windowOpen); self.physicsObjectsWindow = try extras.PhysicsObjectList.create(self.allocator); const args = try core.ParseArgs(ExternGameArgs); if (args.asClient) { self.launchAsClient = args.clientTarget; self.clientIndex = args.clientIndex; core.engine_log("starting up as client connecting to {s}", .{args.clientTarget}); platform.context().pushRequest(.{ .setWindowSize = .{ .size = .{ .x = 1000, .y = 562 } } }); platform.context().pushRequest(.{ .setWindowPosition = .{ .pos = .{ .x = 1000 * args.clientIndex, .y = 30 } } }); const ctx = ui.getScreen(); const text = try ctx.addText(.{}, "client"); ctx.get(text).pos = .{ .x = 0, .y = 10 }; ctx.get(text).size = .{ .x = 400, .y = 300 }; ui.context().screenContext.debugFontSize = 20; self.connectClient(); } try core.registerObject(BoxObject, "Box"); try core.registerObjectAdvanced(BoxObject, "BoxSmoky", "createSmoky"); const settings: physics.ShapeSettings = .{ .box = try physics.BoxShapeSettings.create(.{ 1.0, 1.0, 1.0 }) }; defer settings.release(); try physics.addShape("ph_small_box", settings); if (false) { const ctx = ui.getScreen(); const panel2 = try ctx.addPanel(.{}); ctx.drawDebug = true; { ctx.getPanel(panel2).hasTitle = true; //ctx.get(panel2).anchor = .TopRight; // ctx.get(panel2).fill = .FillY; todo, this is a bug. why. ctx.get(panel2).pos = .{ .x = 900, .y = 200 }; ctx.get(panel2).size = .{ .x = 300, .y = 500 }; ctx.get(panel2).style.backgroundColor = ui.ModernStyle.GreyDark; ctx.getPanel(panel2).titleColor = ui.ModernStyle.GreyDark; ctx.getPanel(panel2).layoutMode = .Vertical; // add some text to this panel const text = try ctx.addText(panel2, "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."); ctx.get(text).pos = .{ .x = 2, .y = 2 }; ctx.get(text).size = .{ .x = 296, .y = 300 }; const btn = try ctx.addButton(panel2, "select file..."); _ = btn; } } try extras.addGame("ExternGame", self, @This()); extras.beginGame("ExternGame"); return self; } fn editVector2(comptime Name: []const u8, v: *core.Vector2f) void { const ItemWidth = 120; ig.textFmt(Name, .{}) catch {}; ig.sameLine(0, 10); ig.setNextItemWidth(ItemWidth); _ = ig.inputFloat("X##particleEd" ++ Name, &v.x, 1.0, 5.0, null, .{}); ig.sameLine(0, 10); ig.setNextItemWidth(ItemWidth); _ = ig.inputFloat("Y##particleEd" ++ Name, &v.y, 1.0, 5.0, null, .{}); } fn v2ToV3(v: core.Vector2f) core.Vectorf { return core.Vectorf{ .x = v.x, .z = v.y, .y = 0 }; } pub fn intersectLine(p1: core.Vector2f, v1: core.Vector2f, p2: core.Vector2f, v2: core.Vector2f) ?core.Vector2f { const denom = v1.x * v2.y - v1.y * v2.x; if (@abs(denom) < 0.00001) { return null; } const t = ((p2.x - p1.x) * v2.y - (p2.y - p1.y) * v2.x) / denom; return .{ .x = p1.x + t * v1.x, .y = p1.y + t * v1.y, }; } pub fn drawVector2Test(self: *@This()) void { if (ig.begin("vector2Test", null, .{})) { editVector2("a", &self.a); editVector2("Anormal", &self.Anormal); ig.separator(); editVector2("b", &self.b); editVector2("Bnormal", &self.Bnormal); } ig.end(); const a = core.Vectorf{ .x = self.a.x, .z = self.a.y, .y = 0 }; const b = core.Vectorf{ .x = self.b.x, .z = self.b.y, .y = 0 }; const A = core.Vectorf{ .x = self.Anormal.x, .z = self.Anormal.y, .y = 0 }; const B = core.Vectorf{ .x = self.Bnormal.x, .z = self.Bnormal.y, .y = 0 }; core.debugSphere(a, 0.1, .{}); core.debugSphere(b, 0.1, .{}); core.debugLine(a, a.add(A), .{ .color = .{ .y = 0, .x = 1.0 } }); core.debugLine(b, b.add(B), .{ .color = .{ .y = 0, .x = 1.0 } }); const Vab = self.b.sub(self.a); const Vba = Vab.negate(); core.debugLine(a, a.add(v2ToV3(Vab)), .{ .color = .{ .x = 1.0, .z = 1.0 } }); // const ADwall = Vab.dot(self.Anormal.removeComponent(Vab.normalize()).normalize()); // const BDwall = Vba.dot(self.Anormal.removeComponent(Vba.normalize()).normalize()); //const Awall = Vab.removeComponent(self.Anormal.normalize()).normalize().fmul(ADwall).normalize(); const Awall = Vab.removeComponent(self.Anormal.normalize()).normalize(); const Bwall = Vba.removeComponent(self.Bnormal.normalize()).normalize(); //ig.textFmt("Awall: {d} {d}", .{ Awall.x, Awall.y }) catch {}; core.debugLine(a.sub(v2ToV3(Awall).fmul(10)), a.add(v2ToV3(Awall).fmul(10)), .{ .color = .{ .y = 1.0, .z = 1.0 } }); // ig.textFmt("Bwall: {d} {d}", .{ Bwall.x, Bwall.y }) catch {}; core.debugLine(b.sub(v2ToV3(Bwall).fmul(10)), b.add(v2ToV3(Bwall).fmul(10)), .{ .color = .{ .y = 1.0, .z = 1.0 } }); const vp = intersectLine(self.a, Awall.normalize(), self.b, Bwall.normalize()) orelse { return; }; // ig.textFmt("p, y: {d} {d}", .{ vp.x, vp.y }) catch {}; core.debugSphere(v2ToV3(vp), 0.1, .{ .color = .{ .y = 1.0, .z = 1.0, .x = 1.0 } }); } pub fn recompileComplete(ctx: ?*anyopaque) void { const self = core.cast(*@This(), ctx.?); self.recompiling = false; } pub fn onOpenSpawner(ctx: ?*anyopaque, _: core.ActionEvent) void { core.engine_logs("spawner opened"); _ = ctx; if (core.getEngineObject(imgui.utils.TopBar)) |topbar| { topbar.setEntryOpen("New Object Spawner", null); } } pub fn onRequestReload(ctx: ?*anyopaque, _: core.ActionEvent) void { const self = core.cast(*@This(), ctx.?); if (!self.recompiling) { self.recompiling = true; const x = core.EngineObject(sys.SystemRunner).get(); x.commandQueue.pushLocked(.{ .subprocess = .{ .context = self, .onComplete = recompileComplete, .task = sys.SubprocessTask.runCommand( x.allocator, &.{ "zig", "build", "install" }, ".", ) catch { self.recompiling = false; return; }, }, }) catch { self.recompiling = false; return; }; } } pub fn onAltFire(ctx: ?*anyopaque, _: core.ActionEvent) void { const self: *@This() = @ptrCast(@alignCast(ctx)); if (platform.context().isCursorEnabled()) { core.engine_logs("moving light"); core.debugTextLog("moving light", 3.5, .{ .color = core.colors.Color.Green }); const ray = rend.context().activeCamera.?.getNormalRayFromScreen(platform.getCursorPosition()); if (physics.traceLine(ray.start, ray.dir.fmul(10000), .{ .bodyFilter = &self.fireTraceFilter, .getNormalsSlow = true })) |r| { if (r.normal) |n| { const p = r.point.add(n.fmul(0.2)); core.debugSphere(p, 0.2, .{ .color = .{ .y = 1.0 }, .duration = 10.0 }); rend.context().lightPosition = p; } physics.applyForce(r.body, ray.dir.fmul(400), r.point); // core.debugLine(ray.start, ray.start.add(ray.dir.fmul(1000)), .{ .duration = 2.5 }); // physics.applyForce(r.body, ray.dir.fmul(400), r.point); } } } pub fn onFire(ctx: ?*anyopaque, _: core.ActionEvent) void { const self: *@This() = @ptrCast(@alignCast(ctx)); if (platform.context().isCursorEnabled()) { core.engine_logs("moving light"); const ray = rend.context().activeCamera.?.getNormalRayFromScreen(platform.getCursorPosition()); if (physics.traceLine(ray.start, ray.dir.fmul(10000), .{ .bodyFilter = &self.fireTraceFilter, .getNormalsSlow = true })) |r| { if (r.normal) |n| { const p: core.Vectorf = r.point.add(n.fmul(0.2)); const posRot = core.ScenePosRot{ .position = p, }; self.spawnedEntities.append(self.allocator, core.get(extras.ObjectSystemSpawner).spawnObjectAt(posRot) orelse return) catch unreachable; } } } } pub fn tick(self: *@This(), dt: f64) void { const rctx = rend.context(); _ = rctx; _ = dt; //self.time += @floatCast(dt); //core.debugSphere(.{ .y = 10 + std.math.sin(self.time) }, 5, .{}); if (core.getEngineObject(imgui.utils.TopBar)) |topbar| { topbar.menuOpen = platform.context().isCursorEnabled(); } if (self.displayVectorsTest) { self.drawVector2Test(); } if (self.recompiling) { if (ig.begin("recompiling", null, .{})) { ig.textf("recompiling...", .{}); } ig.end(); } if (platform.context().isCursorEnabled()) { imgui.utils.structDebugWindow(self); // imgui.utils.structDebugWindow(core.get(rend.renderer.Renderer)); if (ig.begin("meh", null, .{})) { // if (ig.checkbox("move lights ", null)) {} ig.textFmt("info: - WASD to move, mouse to look,\n- Q and E to go up and down lol", .{}) catch return; ig.textFmt("- shift to slow down camera speed", .{}) catch return; ig.textFmt("- T to enable/disable mouse cursor", .{}) catch return; ig.textFmt("- f2 to route all inputs to the doom player", .{}) catch return; ig.textFmt("- movingLight checkbox makes the light stop moving with you", .{}) catch return; ig.textf("lmao", .{}); if (ig.smallButton("800x450")) { platform.context().pushRequest(.{ .setWindowSize = .{ .size = .{ .x = 800, .y = 450 } } }); } if (ig.smallButton("1600x900")) { platform.context().pushRequest(.{ .setWindowSize = .{ .size = .{ .x = 1600, .y = 900 } } }); } if (ig.smallButton("maximize")) { platform.context().pushRequest(.{ .maximizeWindow = .{} }); } if (ig.smallButton("setMousePos")) { platform.context().pushRequest(.{ .setMousePosition = .{ .pos = .{ .x = 800, .y = 450 } } }); } if (ig.smallButton("reload map")) { self.loadMap2() catch unreachable; } if (ig.smallButton("destroy map")) { if (self.tbMap) |tbMap| { core.engine_log("killing the map", .{}); tbMap.destroy(); self.tbMap = null; } } if (ig.smallButton("dumpTimeline")) { core.MemoryTracker.dumpTimeline("timeline.txt") catch unreachable; } if (ig.checkbox("vectors test", &self.displayVectorsTest)) {} if (ig.checkbox("showdebug", &ui.context().screenContext.drawDebug)) {} if (ig.smallButton("click for fun")) { // var soundname = core.MakeName("s_macintosh"); // audio.context().playSound(&soundname) catch {}; } if (ig.sliderFloat("sliderFloat", &self.volume, 0, 100, null, .{})) { audio.context().setVolume(self.volume / 100); } _ = ig.checkbox("addClients", &self.autoAddClients); ig.sameLine(0, 10); if (self.autoAddClients) { _ = ig.inputInt("clientCount", @ptrCast(&self.clientsToAdd), 1, 1, .{}); } if (ig.smallButton("host")) { self.session = blk: { const s = net.hostSession(&.{"7777"}) catch { core.engine_err("host server failed", .{}); break :blk null; }; break :blk s; }; if (self.session) |session| { _ = session; var fmt: [256]u8 = undefined; var fmt2: [256]u8 = undefined; for (0..self.clientsToAdd) |i| { const ci = std.fmt.bufPrint(&fmt, "--clientIndex={d}", .{i}) catch { core.engine_err("unable to create client {d} out of memory?", .{i}); break; }; const igIni = std.fmt.bufPrint(&fmt2, "--imguiIni=client{d}.ini", .{i}) catch { core.engine_err("unable to create client {d} out of memory?", .{i}); break; }; _ = sys.SubprocessTask.runCommand( self.allocator, &.{ "zig-out/bin/sampleGame.exe", "--asClient=True", ci, igIni }, ".", ) catch { core.engine_err("unable to create client {d}", .{i}); break; }; } } } if (ig.smallButton("connect")) { self.connectClient(); } if (self.clientLink) |link| { if (ig.smallButton("sendMessageToServer")) { link.sendMessage("hello from client", true) catch {}; link.sendMessage("hello from client (unreliable)", false) catch {}; const linkData = net.EnetTransport.getLinkData(link); linkData.sendPacketChannelDebug("debug: unreliable over channel 0", false, 0); linkData.sendPacketChannelDebug("debug: reliable over channel 1", true, 1); } } } ig.end(); if (platform.context().isCursorEnabled()) { if (rend.context().activeCamera) |cam| { const ray = cam.getNormalRayFromScreen(platform.getCursorPosition()); if (physics.traceLine(ray.start, ray.dir.fmul(10000), .{ .bodyFilter = &self.fireTraceFilter, .getNormalsSlow = false })) |r| { core.debugSphere(r.point, 0.2, .{ .color = .{ .x = 1.0 }, .duration = 0 }); } } } self.particleDebugger.particleDebug(); } if (self.session) |session| { if (ig.begin("host session", null, .{})) { for (session.links.items) |link| { const linkData = net.EnetTransport.getLinkData(link); ig.textf("{s} {d}ms", .{ link.idString, linkData.peer.*.roundTripTime }); } } ig.end(); for (session.links.items) |link| { const linkData = net.EnetTransport.getLinkData(link); if (rend.context().activeCamera) |camera| { linkData.testLinkPosition = camera.entity.fetch(core.Scene).?.getPosition(); } } } if (self.clientLink) |link| { const linkData = net.EnetTransport.getLinkData(link); if (self.spawnedEntities.items.len > 0) { self.spawnedEntities.items[0].fetch(core.Scene).?.setPosition(linkData.testLinkPosition); } core.debugSphere(linkData.testLinkPosition, 1.0, .{ .color = .{ .x = 1.0 } }); } } fn connectClient(self: *@This()) void { self.clientLink = net.connect("127.0.0.1:7777") catch unreachable; var fmt: [256]u8 = undefined; if (self.clientLink) |link| { const nameMsg = std.fmt.bufPrint(&fmt, "cname:client{d}", .{self.clientIndex}) catch { core.engine_err("unable to create client message, out of memory?", .{}); return; }; link.sendMessage(nameMsg, true) catch { core.engine_err("unable to send message?", .{}); }; } } pub fn destroy(self: *@This()) void { if (self.clientLink) |link| { link.destroy(); } if (self.session) |session| { session.destroy(); } if (self.tbMap) |map| { map.destroy(); } for (self.spawnedEntities.items) |*entity| { entity.destroy(); } self.spawnedEntities.deinit(self.allocator); self.physicsObjectsWindow.destroy(); self.allocator.destroy(Slack.fromPtr(self)); } }; const std = @import("std"); const backlog = @import("backlog"); const core = backlog.core; const imgui = backlog.imgui; const physics = backlog.physics; const rend = backlog.rend; const ig = imgui.api; const sys = backlog.sys; const ui = backlog.ui; const net = backlog.net; const audio = backlog.audio; const platform = backlog.platform; const extras = @import("gameExtras"); const bsp = @import("bsp");