diff --git a/Source/HoloDeck.cs b/Source/HoloDeck.cs new file mode 100644 index 0000000..3568120 --- /dev/null +++ b/Source/HoloDeck.cs @@ -0,0 +1,215 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2015 Alexander Taylor + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Reflection; + +using UnityEngine; + +using HoloDeck.SceneModules; + +namespace HoloDeck +{ + // We want to load in all relevant game scenes, and be applied to all games. + [KSPScenario(ScenarioCreationOptions.AddToAllGames, new GameScenes[] { GameScenes.SPACECENTER, GameScenes.EDITOR, GameScenes.FLIGHT, GameScenes.TRACKSTATION })] + class HoloDeck : ScenarioModule + { + // Tag for marking debug logs with + private const string TAG = "HoloDeck.HoloDeck"; + + // This class is a singleton, as much as Unity will allow. + // There is probably a better way to do this in a Unity-like way, + // But I don't know it. + public static HoloDeck instance; + + // This is a flag for marking a save as 'dirty'. Any flag with this flag + // that enters SPACECENTER, EDITOR, or TRACKSTATION will be immediately reset + [KSPField(isPersistant = true)] + public bool SimulationActive = false; + + // This is our entry point + void Start() + { + // update the singleton + instance = this; + + // Reload to pre-sim if we are in the wrong scene. + if (HighLogic.LoadedScene != GameScenes.FLIGHT) + { + HoloDeck.Deactivate(GameScenes.SPACECENTER); + } + + // Deploy scene-specific modules, for GUI hijacking and similar logic + switch (HighLogic.LoadedScene) + { + case GameScenes.FLIGHT: + gameObject.AddComponent(); + break; + case GameScenes.EDITOR: + gameObject.AddComponent(); + break; + } + + // If the sim is active, display the tell-tale + SimulationNotification(SimulationActive); + } + + // This is called when the script is destroyed. + // This is honestly probably not necessary, but better safe than sorry + void Destroy() + { + SimulationNotification(false); + instance = null; + } + + // Activates the Simulation. Returns the success of the activation. + public static bool Activate() + { + // for recording save status, not sure what this string actually is tbh + string save = null; + + // Make sure the instance actually exists. I can't imagine this ever failing, but NREs are bad. + if (instance != null) + { + // We create the pre-sim save. + save = GamePersistence.SaveGame("HoloDeckRevert", HighLogic.SaveFolder, SaveMode.OVERWRITE); + + // Mark the existing save as dirty. + HoloDeck.instance.SimulationActive = true; + + // Record the scene we are coming from + HoloDeckShelter.lastScene = HighLogic.LoadedScene; + + if (HoloDeckShelter.lastScene == GameScenes.EDITOR) + { + HoloDeck.OnLeavingEditor(EditorDriver.editorFacility, EditorLogic.fetch.launchSiteName); + } + + // Start the tell-tale + HoloDeck.instance.SimulationNotification(true); + } + + return save != null ? true : false; + } + + // Deactivates the simulation. Success is destructive to the plugin state, + // so... no return value + public static void Deactivate(GameScenes targetScene) + { + // This method only does something if the sim is active. + if (HoloDeck.instance.SimulationActive) + { + // Weird bug can be intorduced by how KSP keeps around KSPAddons until it decides to destroy + // them. We need to preempt this so extraneous behavior isn't observed + FlightModule[] flightModules = GameObject.FindObjectsOfType(typeof(FlightModule)) as FlightModule[]; + EditorModule[] editorModules = GameObject.FindObjectsOfType(typeof(EditorModule)) as EditorModule[]; + + foreach (FlightModule flightModule in flightModules) + { + DestroyImmediate(flightModule); + } + + foreach (EditorModule editorModule in editorModules) + { + DestroyImmediate(editorModule); + } + + // Ok, here is where this is tricky. We can't just directly load the save, we need to + // load the save into a new Game object, re-save that object into the default persistence, + // and then change the scene. Weird shit, right? This is actually how the vanilla quickload + // works! + Game newGame = GamePersistence.LoadGame("HoloDeckRevert", HighLogic.SaveFolder, true, false); + GamePersistence.SaveGame(newGame, "persistent", HighLogic.SaveFolder, SaveMode.OVERWRITE); + newGame.startScene = targetScene; + + // This has to be before... newGame.Start() + if (targetScene == GameScenes.EDITOR) + { + newGame.editorFacility = HoloDeckShelter.lastEditor; + } + + newGame.Start(); + HoloDeck.instance.SimulationActive = false; + + // ... And this has to be after. <3 KSP + if (targetScene == GameScenes.EDITOR) + { + EditorDriver.StartupBehaviour = EditorDriver.StartupBehaviours.LOAD_FROM_CACHE; + ShipConstruction.ShipConfig = HoloDeckShelter.lastShip; + } + } + } + + // This method should be called before activating the simulation directly from an editor, and allows + // QOL improvements (like returning to that editor correctly, and automatically clearing the launch site) + // Either of these values can be null, if you want to do that for some reason + public static void OnLeavingEditor(EditorFacility facility, string launchSiteName) + { + // clear the launchpad. + if (launchSiteName != null) + { + List junkAtLaunchSite = ShipConstruction.FindVesselsLandedAt(HighLogic.CurrentGame.flightState, launchSiteName); + + foreach (ProtoVessel pv in junkAtLaunchSite) + { + HighLogic.CurrentGame.flightState.protoVessels.Remove(pv); + } + } + + if (facility != EditorFacility.None) + { + HoloDeckShelter.lastEditor = facility; + } + + HoloDeckShelter.lastShip = ShipConstruction.ShipConfig; + } + + // This is in here instead of GUI, because this isn't an 'implementation detail' + // If some other mod uses sim mode for some reason, I still want this displayed. + private void SimulationNotification(bool state) + { + if (HighLogic.LoadedScene == GameScenes.FLIGHT) + { + switch (state) + { + case true: + InvokeRepeating("DoSimulationNotification", 0.1f, 1.0f); + break; + + case false: + CancelInvoke("DoSimulationNotification"); + break; + } + } + } + + private void DoSimulationNotification() + { + ScreenMessages.PostScreenMessage("Simulation Active", 1.0f, ScreenMessageStyle.LOWER_CENTER); + } + } +} diff --git a/Source/HoloDeck.csproj b/Source/HoloDeck.csproj new file mode 100644 index 0000000..da1df3d --- /dev/null +++ b/Source/HoloDeck.csproj @@ -0,0 +1,65 @@ + + + + + Debug + AnyCPU + {8C5539DD-396B-4084-892E-8F1901E51584} + Library + Properties + HoloDeck + HoloDeck + v3.5 + 512 + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + ..\..\..\..\..\..\..\Games\Kerbal Space Program\KSP_Data\Managed\Assembly-CSharp.dll + + + ..\..\..\..\..\..\..\Games\Kerbal Space Program\KSP_Data\Managed\Assembly-CSharp-firstpass.dll + + + ..\..\..\..\..\..\..\Games\Kerbal Space Program\KSP_Data\Managed\UnityEngine.dll + + + + + + + + + + + xcopy /Y $(TargetPath) $(TargetDir)..\..\..\GameData\Fingerboxes\HoloDeck\Plugins\ + + + \ No newline at end of file diff --git a/Source/HoloDeck.sln b/Source/HoloDeck.sln new file mode 100644 index 0000000..3c45cb7 --- /dev/null +++ b/Source/HoloDeck.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.31101.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HoloDeck", "HoloDeck.csproj", "{8C5539DD-396B-4084-892E-8F1901E51584}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FingerboxLib", "..\..\FingerboxLib\Source\FingerboxLib.csproj", "{6AE0BA91-9473-43E6-980D-C7B04CCAEB45}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {8C5539DD-396B-4084-892E-8F1901E51584}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8C5539DD-396B-4084-892E-8F1901E51584}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8C5539DD-396B-4084-892E-8F1901E51584}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8C5539DD-396B-4084-892E-8F1901E51584}.Release|Any CPU.Build.0 = Release|Any CPU + {6AE0BA91-9473-43E6-980D-C7B04CCAEB45}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6AE0BA91-9473-43E6-980D-C7B04CCAEB45}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6AE0BA91-9473-43E6-980D-C7B04CCAEB45}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6AE0BA91-9473-43E6-980D-C7B04CCAEB45}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Source/HoloDeckShelter.cs b/Source/HoloDeckShelter.cs new file mode 100644 index 0000000..8d1b522 --- /dev/null +++ b/Source/HoloDeckShelter.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; + +namespace HoloDeck.SceneModules +{ + // This class is a place to stick data that will not be destroyed by HoloDeck.Deactivate(). + [KSPAddon(KSPAddon.Startup.Instantly, true)] + class HoloDeckShelter : MonoBehaviour + { + // Editor stuff + public static EditorFacility lastEditor = EditorFacility.None; + public static ConfigNode lastShip = null; + + public static GameScenes lastScene; + + void Awake() + { + DontDestroyOnLoad(this); + } + } +} diff --git a/Source/SceneModules/EditorModule.cs b/Source/SceneModules/EditorModule.cs new file mode 100644 index 0000000..c9b4520 --- /dev/null +++ b/Source/SceneModules/EditorModule.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using System.Text; +using UnityEngine; + +namespace HoloDeck.SceneModules +{ + class EditorModule : MonoBehaviour + { + private const string TAG = "HoloDeck.EditorModule"; + + // Grab the KSP base skin, for future reference + GUISkin Skin = HighLogic.Skin; + + // We need to check if this is being displayed, later + PopupDialog LaunchDialog = null; + + void Start() + { + // Hijack launch button + EditorLogic.fetch.launchBtn.scriptWithMethodToInvoke = this; + EditorLogic.fetch.launchBtn.methodToInvoke = "LaunchButton"; + } + + void Destroy() + { + } + + void Update() + { + // If the LaunchDialog isn't being displayed, but the Controls are locked + // The reason we are doing this instead of just releasing the locks directly is so that the user doesn't + // 'click-through' to any parts underneath the popup window + if (LaunchDialog == null && InputLockManager.GetControlLock("KCT_EDITOR_LAUNCH_B") != ControlTypes.None) + { + // Release the locks + ReleaseControlLock(); + } + } + + void LaunchButton() + { + List LaunchOptionsList = new List(); + + LaunchOptionsList.Add(new DialogOption("Proceed to Launch", InvokeLaunch)); + LaunchOptionsList.Add( new DialogOption("Simulate this Vessel", InvokeSimulation)); + LaunchOptionsList.Add(new DialogOption("Cancel Launch", null)); + + // This is how you hide tooltips. + EditorTooltip.Instance.HideToolTip(); + GameEvents.onTooltipDestroyRequested.Fire(); + + // Lock inputs + EditorLogic.fetch.Lock(true, true, true, "KCT_EDITOR_LAUNCH_A"); + InputLockManager.SetControlLock(ControlTypes.EDITOR_SOFT_LOCK, "KCT_EDITOR_LAUNCH_B"); + + // Display the new launch prompt + LaunchDialog = PopupDialog.SpawnPopupDialog(new MultiOptionDialog(null, null, Skin, LaunchOptionsList.ToArray()), false, Skin); + } + + void InvokeLaunch() + { + ReleaseControlLock(); + EditorLogic.fetch.launchVessel(); + } + + void InvokeSimulation() + { + ReleaseControlLock(); + + HoloDeck.Activate(); + + EditorLogic.fetch.launchVessel(); + } + + void InvokeBuild() + { + + } + + void ReleaseControlLock() + { + EditorLogic.fetch.Unlock("KCT_EDITOR_LAUNCH_A"); + InputLockManager.RemoveControlLock("KCT_EDITOR_LAUNCH_B"); + } + } +} diff --git a/Source/SceneModules/FlightModule.cs b/Source/SceneModules/FlightModule.cs new file mode 100644 index 0000000..b9319ee --- /dev/null +++ b/Source/SceneModules/FlightModule.cs @@ -0,0 +1,262 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Reflection; + +using UnityEngine; + +namespace HoloDeck.SceneModules +{ + /* + * This class contains all of the code for manipulating the GUI, and interacting with the game + * for HoloDeck in GameScenes.FLIGHT. + */ + + class FlightModule : MonoBehaviour + { + private const string TAG = "HoloDeck.FlightModule"; + + private SimulationPauseMenu menu; + + // Entry Point + void Start() + { + menu = new SimulationPauseMenu(); + } + + /* + * The basic idea here is that if we are Simming, destroy the vanilla pause menu as soon + * as it appears, and replace it with out own. + * + * If the pause menu is open, close it. + * + * If the pause key is being pressed, check if our menu is active. + * if our menu is not active, make it active. + * if our menu is not active, tell it to reduce by one ui layer. + */ + void Update() + { + // We don't want to do anything if we aren't simming + if (HoloDeck.instance.SimulationActive) + { + // Hide the vanilla pause menu. + if (PauseMenu.isOpen) + { + PauseMenu.Close(); + } + + // Check for pause keypress + if (GameSettings.PAUSE.GetKeyDown()) + { + switch (menu.isOpen) + { + case false: + menu.Display(); + break; + case true: + menu.Close(); + break; + } + } + } + } + + /* + * When this module is destroyed, make sure to clean up our menu. + * It probably won't leak, but I'd rather not tempt fate. + */ + void Destroy() + { + menu.Destroy(); + } + } + + /* + * This menu is very similar to the squad PauseMenu. + * + * isOpen - returns true if this menu is in an active state + * + * Display() - makes this menu active + * Close() - closes one ui layer. + */ + class SimulationPauseMenu + { + private const float SPACER = 5.0f; + private const string TAG = "HoloDeck.FlightModule.SimulationPauseMenu"; + + private Rect _windowRect; + private bool _display; + private Color _backgroundColor; + private GUISkin _guiSkin; + private PopupDialog _activePopup; + private MiniSettings _miniSettings; + + public bool isOpen; + + public SimulationPauseMenu() + { + PauseMenu originalMenu = GameObject.FindObjectOfType(typeof(PauseMenu)) as PauseMenu; + + _backgroundColor = originalMenu.backgroundColor; + _guiSkin = originalMenu.guiSkin; + + RenderingManager.AddToPostDrawQueue(3, new Callback(DrawGUI)); + _windowRect = new Rect((float)(Screen.width / 2.0 - 125.0), (float)(Screen.height / 2.0 - 70.0), 250f, 130f); + } + + ~SimulationPauseMenu() + { + Destroy(); + } + + public void Display() + { + isOpen = true; + _display = true; + InputLockManager.SetControlLock(ControlTypes.PAUSE, "SimulationPauseMenu"); + FlightDriver.SetPause(true); + } + + public void Close() + { + if (_activePopup != null) + { + _activePopup.Dismiss(); + _activePopup = null; + Unhide(); + } + else if (_miniSettings != null) + { + // WTF SQUAD? + _miniSettings.GetType().GetMethod("Dismiss", BindingFlags.NonPublic | BindingFlags.Instance); + } + else + { + isOpen = false; + _display = false; + InputLockManager.RemoveControlLock("SimulationPauseMenu"); + FlightDriver.SetPause(false); + } + } + + public void Destroy() + { + RenderingManager.RemoveFromPostDrawQueue(3, new Callback(DrawGUI)); + } + + // Screw you, MiniSettings + private void Hide() + { + _display = false; + } + + private void Unhide() + { + _display = true; + } + + private void DrawGUI() + { + if (_display) + { + GUI.skin = _guiSkin; + GUI.backgroundColor = _backgroundColor; + _windowRect = GUILayout.Window(0, _windowRect, new GUI.WindowFunction(draw), "Game Paused", new GUILayoutOption[0]); + } + } + + private void draw(int id) + { + if (GUILayout.Button("Resume Simulation", _guiSkin.button)) + { + Close(); + } + + GUILayout.Space(SPACER); + + if (GUILayout.Button("Abort Simulation", _guiSkin.button)) + { + _activePopup = PopupDialog.SpawnPopupDialog(new MultiOptionDialog(null, new Callback(drawAbortWarning), "Aborting Simulation", HighLogic.Skin, new DialogOption[0]), false, HighLogic.Skin); + Hide(); + } + + if (FlightDriver.CanRevertToPostInit) + { + if (GUILayout.Button("Restart Simulation", _guiSkin.button)) + { + _activePopup = PopupDialog.SpawnPopupDialog(new MultiOptionDialog(null, new Callback(drawRevertWarning), "Reverting Simulation", HighLogic.Skin, new DialogOption[0]), false, HighLogic.Skin); + Hide(); + } + } + + GUILayout.Space(SPACER); + + if (GUILayout.Button("Settings", _guiSkin.button)) + { + Hide(); + _miniSettings = MiniSettings.Create(Unhide); + } + } + + private void drawRevertWarning() + { + GUILayout.Label("Reverting will set the game back to an earlier state. Are you sure you want to continue?"); + if (GUILayout.Button("Revert to Launch (" + KSPUtil.PrintTime((int)(Planetarium.GetUniversalTime() - FlightDriver.PostInitState.UniversalTime), 3, false) + " ago)")) + { + Close(); + Close(); + FlightDriver.RevertToLaunch(); + } + if (GUILayout.Button("Cancel")) + { + Close(); + } + } + + private void drawAbortWarning() + { + string revertTarget; + + GUILayout.Label("Reverting will set the game back to an earlier state. Are you sure you want to continue?"); + + switch (HoloDeckShelter.lastScene) + { + case GameScenes.EDITOR: + switch (HoloDeckShelter.lastEditor) + { + case EditorFacility.SPH: + revertTarget = "Spaceplane Hangar"; + break; + case EditorFacility.VAB: + revertTarget = "Vehicle Assembly Building"; + break; + // This should never happen. If it does, just go to the SC + default: + revertTarget = "Space Center"; + HoloDeckShelter.lastScene = GameScenes.SPACECENTER; + break; + } + break; + case GameScenes.SPACECENTER: + revertTarget = "Space Center"; + break; + default: + revertTarget = "Pre-Simulation"; + break; + } + + if (GUILayout.Button("Revert to " + revertTarget + " (" + KSPUtil.PrintTime((int)(Planetarium.GetUniversalTime() - FlightDriver.PostInitState.UniversalTime), 3, false) + " ago)")) + { + Close(); + Close(); + HoloDeck.Deactivate(HoloDeckShelter.lastScene); + } + + if (GUILayout.Button("Cancel")) + { + Close(); + } + } + } +}