using System; using System.Collections.Generic; using System.Text; using UnityEngine; public partial class UniQuake: MonoBehaviour { private const int DefaultMemSize = 0x8000000; // 128 MB of heap space public int memorySize = DefaultMemSize; private QuakeParms quakeParms; private SystemModule systemModule; private RenderModule renderModule; private GameModule gameModule; private bool initialized = false; private double startTime; /// /// Camera and viewport that this instance of Quake will be rendering to /// public Camera Camera { get; set; } public MissionPack BaseGame { get; set; } public string ModDirectory { get; set; } public string[] AdditionalArguments { get; set; } /// /// Time since startup for this particular instance of Quake /// public double CurrentTime => Time.realtimeSinceStartupAsDouble - startTime; public GameAssets GameAssets { get; private set; } public GameState GameState { get; private set; } private Action logHandler; private readonly StringBuilder logBuffer = new StringBuilder(); void Start() { logHandler = PrintLog; // Send each log statement to Unity immediately during startup systemModule = new SystemModule(this); renderModule = new RenderModule(this); gameModule = new GameModule(this); GameAssets = new GameAssets(this); GameState = new GameState(this); LoadLibrary(); List arguments = new List { "", //"-fullscreen", "-width", "1920", "-height", "1080", "-window", "-width", "1440", "-height", "1080", //"-window", "-width", "2240", "-height", "1260", }; switch (BaseGame) { case MissionPack.Hipnotic: arguments.Add("-hipnotic"); break; case MissionPack.Rogue: arguments.Add("-rogue"); break; } if (!string.IsNullOrEmpty(ModDirectory)) { arguments.AddRange(new[] { "-game", ModDirectory }); } if (Debug.isDebugBuild) { arguments.AddRange(new[] { "+developer", "1" }); } if (AdditionalArguments != null) arguments.AddRange(AdditionalArguments); quakeParms = new QuakeParms { baseDir = Application.persistentDataPath, userDir = null, // TODO set this if user prefs need to be stored somewhere specific, otherwise this will be the same as baseDir numCpus = SystemInfo.processorCount, errState = 0, }; quakeParms.SetArguments(arguments.ToArray()); quakeParms.AllocateMemory(memorySize); startTime = Time.realtimeSinceStartupAsDouble; try { UniQuake_SetFmodSystem(AudioManager.Instance.FmodSystem.handle); UniQuake_Init(quakeParms.ToNativePtr(), systemModule.CallbacksPtr, renderModule.CallbacksPtr); initialized = true; } catch (QuakeException ex) { if (ex.ExitCode == 0) Debug.Log(ex.Message); else Debug.LogException(ex); Shutdown(); } catch (Exception ex) { Debug.LogException(ex); Shutdown(); } } void Update() { if (!initialized) return; logHandler = CollectLog; // Collect and dump logs to Unity once per frame try { UniQuake_Update(Time.deltaTime); } catch (QuakeException ex) { if (ex.ExitCode == 0) Debug.Log(ex.Message); else Debug.LogException(ex); Shutdown(); } catch (Exception ex) { Debug.LogException(ex); Shutdown(); } FlushLog(); } public void Shutdown() { initialized = false; UniQuake_Shutdown(); Destroy(this); } private void OnApplicationQuit() { Shutdown(); } private void OnDestroy() { GameState.Destroy(); GameAssets.Destroy(); gameModule.Destroy(); renderModule.Destroy(); systemModule.Destroy(); if (quakeParms != null) { quakeParms.Destroy(); quakeParms = null; } FreeLibrary(); } public void AddLog(string log) { logHandler?.Invoke(log); } private void PrintLog(string log) { Debug.Log(log); } private void CollectLog(string log) { logBuffer.Append(log); } private void FlushLog() { if (logBuffer.Length > 0) { Debug.Log(logBuffer.ToString()); logBuffer.Clear(); } } } public enum MissionPack { Quake, // Vanilla Quake Hipnotic, // Scourge of Armagon Rogue, // Dissolution of Eternity } public class QuakeException: Exception { public int ExitCode { get; } public QuakeException(string message, int exitCode = 0) : base(message) { ExitCode = exitCode; } }