diff --git a/NewHorizons/Builder/ShipLog/MapModeBuilder.cs b/NewHorizons/Builder/ShipLog/MapModeBuilder.cs index de4116aa..1be8cc7c 100644 --- a/NewHorizons/Builder/ShipLog/MapModeBuilder.cs +++ b/NewHorizons/Builder/ShipLog/MapModeBuilder.cs @@ -459,11 +459,6 @@ namespace NewHorizons.Builder.ShipLog private static MapModeObject ConstructPrimaryNode(List bodies) { - float DistanceFromPrimary(NewHorizonsBody body) - { - return Mathf.Max(body.Config.Orbit.semiMajorAxis, body.Config.Orbit.staticPosition?.Length() ?? 0f); - } - foreach (NewHorizonsBody body in bodies.Where(b => b.Config.Base.centerOfSolarSystem)) { bodies.Sort((b, o) => b.Config.Orbit.semiMajorAxis.CompareTo(o.Config.Orbit.semiMajorAxis)); diff --git a/NewHorizons/Builder/Volumes/RepairVolumeBuilder.cs b/NewHorizons/Builder/Volumes/RepairVolumeBuilder.cs new file mode 100644 index 00000000..7f51dc2c --- /dev/null +++ b/NewHorizons/Builder/Volumes/RepairVolumeBuilder.cs @@ -0,0 +1,52 @@ +using NewHorizons.Builder.Props; +using NewHorizons.Components.Volumes; +using NewHorizons.External.Modules.Props; +using NewHorizons.External.Modules.Volumes.VolumeInfos; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace NewHorizons.Builder.Volumes +{ + public static class RepairVolumeBuilder + { + public static NHRepairReceiver Make(GameObject planetGO, Sector sector, RepairVolumeInfo info) + { + // Repair receivers aren't technically volumes (no OWTriggerVolume) so we don't use the VolumeBuilder + + var go = GeneralPropBuilder.MakeNew("RepairVolume", planetGO, sector, info); + + if (info.shape != null) + { + ShapeBuilder.AddCollider(go, info.shape); + } + else + { + var shapeInfo = new ShapeInfo() + { + type = ShapeType.Sphere, + useShape = false, + hasCollision = true, + radius = info.radius, + }; + ShapeBuilder.AddCollider(go, shapeInfo); + } + + var repairReceiver = go.AddComponent(); + repairReceiver.displayName = info.name ?? info.rename ?? go.name; + repairReceiver.repairFraction = info.repairFraction; + repairReceiver.repairTime = info.repairTime; + repairReceiver.repairDistance = info.repairDistance; + repairReceiver.damagedCondition = info.damagedCondition; + repairReceiver.repairedCondition = info.repairedCondition; + repairReceiver.revealFact = info.revealFact; + + go.SetActive(true); + + return repairReceiver; + } + } +} diff --git a/NewHorizons/Builder/Volumes/VolumesBuildManager.cs b/NewHorizons/Builder/Volumes/VolumesBuildManager.cs index 161f1d6a..40303a0c 100644 --- a/NewHorizons/Builder/Volumes/VolumesBuildManager.cs +++ b/NewHorizons/Builder/Volumes/VolumesBuildManager.cs @@ -222,6 +222,13 @@ namespace NewHorizons.Builder.Volumes VolumeBuilder.MakeAndEnable(go, sector, referenceFrameBlockerVolume); } } + if (config.Volumes.repairVolumes != null) + { + foreach (var repairVolume in config.Volumes.repairVolumes) + { + RepairVolumeBuilder.Make(go, sector, repairVolume); + } + } if (config.Volumes.speedTrapVolumes != null) { foreach (var speedTrapVolume in config.Volumes.speedTrapVolumes) diff --git a/NewHorizons/Components/Volumes/NHRepairReceiver.cs b/NewHorizons/Components/Volumes/NHRepairReceiver.cs new file mode 100644 index 00000000..130d8486 --- /dev/null +++ b/NewHorizons/Components/Volumes/NHRepairReceiver.cs @@ -0,0 +1,103 @@ +using NewHorizons.Handlers; +using OWML.Utils; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using UnityEngine.Events; + +namespace NewHorizons.Components.Volumes +{ + // RepairReceiver isn't set up for proper subclassing but a subclass is necessary for the first-person manipulator to detect it. + public class NHRepairReceiver : RepairReceiver + { + public static Type RepairReceiverType = EnumUtils.Create("NewHorizons"); + + public class RepairEvent : UnityEvent { } + + public RepairEvent OnRepaired = new(); + public RepairEvent OnDamaged = new(); + + public string displayName; + public float repairTime; + public string damagedCondition; + public string repairedCondition; + public string revealFact; + + float _repairFraction = 0f; + UITextType _uiTextType = UITextType.None; + + public float repairFraction + { + get => _repairFraction; + set + { + var prevValue = _repairFraction; + _repairFraction = Mathf.Clamp01(value); + if (prevValue < 1f && _repairFraction >= 1f) + { + Repair(); + } + else if (prevValue >= 1f && _repairFraction < 1f) + { + Damage(); + } + } + } + + public new virtual bool IsRepairable() => IsDamaged(); + public new virtual bool IsDamaged() => _repairFraction < 1f; + public new virtual float GetRepairFraction() => _repairFraction; + + protected new void Awake() + { + base.Awake(); + _type = RepairReceiverType; + if (IsDamaged()) Damage(); + else Repair(); + } + + public new virtual void RepairTick() + { + if (!IsRepairable()) return; + repairFraction += Time.deltaTime / repairTime; + } + + public new virtual UITextType GetRepairableName() + { + if (_uiTextType != UITextType.None) return _uiTextType; + var value = TranslationHandler.GetTranslation(displayName, TranslationHandler.TextType.UI); + _uiTextType = (UITextType)TranslationHandler.AddUI(value, false); + return _uiTextType; + } + + void Damage() + { + if (!string.IsNullOrEmpty(damagedCondition)) + { + DialogueConditionManager.SharedInstance.SetConditionState(damagedCondition, true); + } + if (!string.IsNullOrEmpty(repairedCondition)) + { + DialogueConditionManager.SharedInstance.SetConditionState(repairedCondition, false); + } + OnDamaged.Invoke(this); + } + + void Repair() + { + if (!string.IsNullOrEmpty(damagedCondition)) + { + DialogueConditionManager.SharedInstance.SetConditionState(damagedCondition, false); + } + if (!string.IsNullOrEmpty(repairedCondition)) + { + DialogueConditionManager.SharedInstance.SetConditionState(repairedCondition, true); + } + if (!string.IsNullOrEmpty(revealFact)) + { + Locator.GetShipLogManager().RevealFact(revealFact); + } + OnRepaired.Invoke(this); + } + } +} diff --git a/NewHorizons/External/Modules/Volumes/VolumeInfos/RepairVolumeInfo.cs b/NewHorizons/External/Modules/Volumes/VolumeInfos/RepairVolumeInfo.cs new file mode 100644 index 00000000..550e2a3d --- /dev/null +++ b/NewHorizons/External/Modules/Volumes/VolumeInfos/RepairVolumeInfo.cs @@ -0,0 +1,49 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NewHorizons.External.Modules.Volumes.VolumeInfos +{ + [JsonObject] + public class RepairVolumeInfo : VolumeInfo + { + /// + /// The name displayed in the UI when the player is repairing this object. If not set, the name of the object will be used. + /// + public string name; + + /// + /// How much of the object is initially repaired. 0 = not repaired, 1 = fully repaired. + /// + [DefaultValue(0f)] public float repairFraction = 0f; + + /// + /// The time it takes to repair the object. Defaults to 3 seconds. + /// + [DefaultValue(3f)] public float repairTime = 3f; + + /// + /// The distance from the object that the player can be to repair it. Defaults to 3 meters. + /// + [DefaultValue(3f)] public float repairDistance = 3f; + + /// + /// A dialogue condition that will be set while the object is damaged. It will be unset when the object is repaired. + /// + public string damagedCondition; + + /// + /// A dialogue condition that will be set when the object is repaired. It will be unset if the object is damaged again. + /// + public string repairedCondition; + + /// + /// A ship log fact that will be revealed when the object is repaired. + /// + public string revealFact; + } +} diff --git a/NewHorizons/External/Modules/Volumes/VolumesModule.cs b/NewHorizons/External/Modules/Volumes/VolumesModule.cs index 34795d15..800e94bd 100644 --- a/NewHorizons/External/Modules/Volumes/VolumesModule.cs +++ b/NewHorizons/External/Modules/Volumes/VolumesModule.cs @@ -85,6 +85,11 @@ namespace NewHorizons.External.Modules.Volumes /// public VolumeInfo[] referenceFrameBlockerVolumes; + /// + /// Add repair volumes to this planet. + /// + public RepairVolumeInfo[] repairVolumes; + /// /// Add triggers that reveal parts of the ship log on this planet. /// diff --git a/NewHorizons/Patches/VolumePatches/RepairReceiverPatches.cs b/NewHorizons/Patches/VolumePatches/RepairReceiverPatches.cs new file mode 100644 index 00000000..a21e6b79 --- /dev/null +++ b/NewHorizons/Patches/VolumePatches/RepairReceiverPatches.cs @@ -0,0 +1,62 @@ +using HarmonyLib; +using NewHorizons.Components.Volumes; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NewHorizons.Patches.VolumePatches +{ + + [HarmonyPatch(typeof(RepairReceiver))] + public static class RepairReceiverPatches + { + // We can't actually override these methods so we patch the base class methods to invoke the subclass methods dynamically + + [HarmonyPostfix, HarmonyPatch(nameof(RepairReceiver.IsRepairable))] + public static void IsRepairable(RepairReceiver __instance, ref bool __result) + { + if (__instance is NHRepairReceiver r) + { + __result = r.IsRepairable(); + } + } + + [HarmonyPostfix, HarmonyPatch(nameof(RepairReceiver.RepairTick))] + public static void RepairTick(RepairReceiver __instance) + { + if (__instance is NHRepairReceiver r) + { + r.RepairTick(); + } + } + + [HarmonyPostfix, HarmonyPatch(nameof(RepairReceiver.IsDamaged))] + public static void IsDamaged(RepairReceiver __instance, ref bool __result) + { + if (__instance is NHRepairReceiver r) + { + __result = r.IsDamaged(); + } + } + + [HarmonyPostfix, HarmonyPatch(nameof(RepairReceiver.GetRepairableName))] + public static void GetRepairableName(RepairReceiver __instance, ref UITextType __result) + { + if (__instance is NHRepairReceiver r) + { + __result = r.GetRepairableName(); + } + } + + [HarmonyPostfix, HarmonyPatch(nameof(RepairReceiver.GetRepairFraction))] + public static void GetRepairFraction(RepairReceiver __instance, ref float __result) + { + if (__instance is NHRepairReceiver r) + { + __result = r.GetRepairFraction(); + } + } + } +} diff --git a/NewHorizons/Schemas/body_schema.json b/NewHorizons/Schemas/body_schema.json index 1f242deb..b525e4fe 100644 --- a/NewHorizons/Schemas/body_schema.json +++ b/NewHorizons/Schemas/body_schema.json @@ -5450,6 +5450,13 @@ "$ref": "#/definitions/VolumeInfo" } }, + "repairVolumes": { + "type": "array", + "description": "Add repair volumes to this planet.", + "items": { + "$ref": "#/definitions/RepairVolumeInfo" + } + }, "revealVolumes": { "type": "array", "description": "Add triggers that reveal parts of the ship log on this planet.", @@ -6722,6 +6729,83 @@ } } }, + "RepairVolumeInfo": { + "type": "object", + "additionalProperties": false, + "properties": { + "radius": { + "type": "number", + "description": "The radius of this volume, if a shape is not specified.", + "format": "float", + "default": 1.0 + }, + "shape": { + "description": "The shape of this volume. Defaults to a sphere with a radius of `radius` if not specified.", + "$ref": "#/definitions/ShapeInfo" + }, + "rotation": { + "description": "Rotation of the object", + "$ref": "#/definitions/MVector3" + }, + "alignRadial": { + "type": [ + "boolean", + "null" + ], + "description": "Do we try to automatically align this object to stand upright relative to the body's center? Stacks with rotation.\nDefaults to true for geysers, tornados, and volcanoes, and false for everything else." + }, + "position": { + "description": "Position of the object", + "$ref": "#/definitions/MVector3" + }, + "isRelativeToParent": { + "type": "boolean", + "description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object." + }, + "parentPath": { + "type": "string", + "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)." + }, + "rename": { + "type": "string", + "description": "An optional rename of this object" + }, + "name": { + "type": "string", + "description": "The name displayed in the UI when the player is repairing this object. If not set, the name of the object will be used." + }, + "repairFraction": { + "type": "number", + "description": "How much of the object is initially repaired. 0 = not repaired, 1 = fully repaired.", + "format": "float", + "default": 0.0 + }, + "repairTime": { + "type": "number", + "description": "The time it takes to repair the object. Defaults to 3 seconds.", + "format": "float", + "default": 3.0 + }, + "repairDistance": { + "type": "number", + "description": "The distance from the object that the player can be to repair it. Defaults to 3 meters.", + "format": "float", + "default": 3.0 + }, + "damagedCondition": { + "type": "string", + "description": "A dialogue condition that will be set while the object is damaged. It will be unset when the object is repaired." + }, + "repairedCondition": { + "type": "string", + "description": "A dialogue condition that will be set when the object is repaired. It will be unset if the object is damaged again." + }, + "revealFact": { + "type": "string", + "description": "A ship log fact that will be revealed when the object is repaired." + } + } + }, "RevealVolumeInfo": { "type": "object", "additionalProperties": false,