diff --git a/.gitignore b/.gitignore index 204daa57..70f0ab13 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ obj zip *.zip -*/Build/* \ No newline at end of file +*/Build/* +.idea/ diff --git a/NewHorizons/AssetBundle/DefaultMapModNoAtmo.png b/NewHorizons/AssetBundle/DefaultMapModNoAtmo.png new file mode 100644 index 00000000..9a5438bf Binary files /dev/null and b/NewHorizons/AssetBundle/DefaultMapModNoAtmo.png differ diff --git a/NewHorizons/AssetBundle/DefaultMapModePlanet.png b/NewHorizons/AssetBundle/DefaultMapModePlanet.png new file mode 100644 index 00000000..afbd76b9 Binary files /dev/null and b/NewHorizons/AssetBundle/DefaultMapModePlanet.png differ diff --git a/NewHorizons/AssetBundle/DefaultMapModeStar.png b/NewHorizons/AssetBundle/DefaultMapModeStar.png new file mode 100644 index 00000000..7bc0ac1b Binary files /dev/null and b/NewHorizons/AssetBundle/DefaultMapModeStar.png differ diff --git a/NewHorizons/Builder/Atmosphere/EffectsBuilder.cs b/NewHorizons/Builder/Atmosphere/EffectsBuilder.cs index 501148c6..1b15c24a 100644 --- a/NewHorizons/Builder/Atmosphere/EffectsBuilder.cs +++ b/NewHorizons/Builder/Atmosphere/EffectsBuilder.cs @@ -26,12 +26,12 @@ namespace NewHorizons.Atmosphere rainGO.transform.localPosition = Vector3.zero; var pvc = rainGO.GetComponent(); - pvc._densityByHeight = new AnimationCurve(new Keyframe[] - { + pvc._densityByHeight = new AnimationCurve(new Keyframe[] + { new Keyframe(surfaceSize - 0.5f, 0), - new Keyframe(surfaceSize, 10f), - new Keyframe(atmoSize, 0f) - }); + new Keyframe(surfaceSize, 10f), + new Keyframe(atmoSize, 0f) + }); rainGO.GetComponent().SetValue("_activeInSector", sector); rainGO.GetComponent().SetValue("_exclusionSectors", new Sector[] { }); diff --git a/NewHorizons/Builder/Body/WaterBuilder.cs b/NewHorizons/Builder/Body/WaterBuilder.cs index 873b5f67..bb155cf7 100644 --- a/NewHorizons/Builder/Body/WaterBuilder.cs +++ b/NewHorizons/Builder/Body/WaterBuilder.cs @@ -39,7 +39,6 @@ namespace NewHorizons.Builder.Body tempArray[i] = new Material(GDSharedMaterials[i]); if (module.Tint != null) { - tempArray[i].color = module.Tint.ToColor32(); tempArray[i].color = module.Tint.ToColor(); tempArray[i].SetColor("_FogColor", module.Tint.ToColor()); } @@ -82,11 +81,11 @@ namespace NewHorizons.Builder.Body fogGO.name = "OceanFog"; fogGO.transform.localPosition = Vector3.zero; fogGO.transform.localScale = Vector3.one; + if (module.Tint != null) { var adjustedColour = module.Tint.ToColor() / 4f; adjustedColour.a = adjustedColour.a * 4f; - fogGO.GetComponent().material.color = adjustedColour; } diff --git a/NewHorizons/Builder/Props/PropBuildManager.cs b/NewHorizons/Builder/Props/PropBuildManager.cs index 281a2dd0..aecb8ad9 100644 --- a/NewHorizons/Builder/Props/PropBuildManager.cs +++ b/NewHorizons/Builder/Props/PropBuildManager.cs @@ -8,8 +8,10 @@ using UnityEngine; using Random = UnityEngine.Random; using Logger = NewHorizons.Utility.Logger; using System.Reflection; +using NewHorizons.Builder.General; using NewHorizons.Utility; using OWML.Common; +using NewHorizons.Builder.ShipLog; namespace NewHorizons.Builder.Props { @@ -53,6 +55,20 @@ namespace NewHorizons.Builder.Props DialogueBuilder.Make(go, sector, dialogueInfo, mod); } } + if (config.Props.Reveal != null) + { + foreach (var revealInfo in config.Props.Reveal) + { + RevealBuilder.Make(go, sector, revealInfo, mod); + } + } + if (config.Props.EntryLocation != null) + { + foreach (var entryLocationInfo in config.Props.EntryLocation) + { + EntryLocationBuilder.Make(go, sector, entryLocationInfo, mod); + } + } } public static GameObject LoadPrefab(string assetBundle, string path, string uniqueModName, IModAssets assets) diff --git a/NewHorizons/Builder/Props/SignalBuilder.cs b/NewHorizons/Builder/Props/SignalBuilder.cs index 96d7c3af..cc1a66e8 100644 --- a/NewHorizons/Builder/Props/SignalBuilder.cs +++ b/NewHorizons/Builder/Props/SignalBuilder.cs @@ -145,6 +145,7 @@ namespace NewHorizons.Builder.Props } audioSignal._name = name; audioSignal._sourceRadius = info.SourceRadius; + audioSignal._revealFactID = info.Reveals; audioSignal._onlyAudibleToScope = info.OnlyAudibleToScope; audioSignal._identificationDistance = info.IdentificationRadius; audioSignal._canBePickedUpByScope = true; diff --git a/NewHorizons/Builder/ShipLog/EntryLocationBuilder.cs b/NewHorizons/Builder/ShipLog/EntryLocationBuilder.cs new file mode 100644 index 00000000..905b76bd --- /dev/null +++ b/NewHorizons/Builder/ShipLog/EntryLocationBuilder.cs @@ -0,0 +1,37 @@ +using NewHorizons.Components; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Xml.Linq; +using NewHorizons.External; +using NewHorizons.Utility; +using OWML.Common; +using UnityEngine; +using UnityEngine.UI; +using Logger = NewHorizons.Utility.Logger; + +namespace NewHorizons.Builder.ShipLog +{ + public static class EntryLocationBuilder + { + private static readonly List _locationsToInitialize = new List(); + public static void Make(GameObject go, Sector sector, PropModule.EntryLocationInfo info, IModHelper mod) + { + GameObject entryLocationGameObject = new GameObject("Entry Location (" + info.id + ")"); + entryLocationGameObject.SetActive(false); + entryLocationGameObject.transform.parent = sector?.transform ?? go.transform; + entryLocationGameObject.transform.localPosition = info.position; + ShipLogEntryLocation newLocation = entryLocationGameObject.AddComponent(); + newLocation._entryID = info.id; + newLocation._isWithinCloakField = info.cloaked; + _locationsToInitialize.Add(newLocation); + entryLocationGameObject.SetActive(true); + } + + public static void InitializeLocations() + { + _locationsToInitialize.ForEach(l => l.InitEntry()); + _locationsToInitialize.Clear(); + } + } +} diff --git a/NewHorizons/Builder/ShipLog/MapModeBuilder.cs b/NewHorizons/Builder/ShipLog/MapModeBuilder.cs new file mode 100644 index 00000000..1b8fba48 --- /dev/null +++ b/NewHorizons/Builder/ShipLog/MapModeBuilder.cs @@ -0,0 +1,557 @@ +using NewHorizons.Components; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Xml.Linq; +using NewHorizons.External; +using NewHorizons.Utility; +using OWML.Common; +using UnityEngine; +using UnityEngine.UI; +using Logger = NewHorizons.Utility.Logger; +using NewHorizons.Builder.Handlers; +using System; + +namespace NewHorizons.Builder.ShipLog +{ + public static class MapModeBuilder + { + #region General + public static ShipLogAstroObject[][] ConstructMapMode(string systemName, GameObject transformParent, ShipLogAstroObject[][] currentNav, int layer) + { + Material greyScaleMaterial = GameObject.Find(ShipLogHandler.PAN_ROOT_PATH + "/TimberHearth/Sprite").GetComponent().material; + List bodies = Main.BodyDict[systemName].Where( + b => (b.Config.ShipLog?.mapMode?.remove ?? false) == false + ).ToList(); + bool flagManualPositionUsed = systemName == "SolarSystem"; + bool flagAutoPositionUsed = false; + foreach (NewHorizonsBody body in bodies.Where(b => ShipLogHandler.IsVanillaBody(b) == false)) + { + if (body.Config.ShipLog == null) continue; + + if (body.Config.ShipLog?.mapMode?.manualPosition == null) + { + flagAutoPositionUsed = true; + } + else + { + flagManualPositionUsed = true; + if (body.Config.ShipLog?.mapMode?.manualNavigationPosition == null) + { + Logger.LogError("Navigation position is missing for: " + body.Config.Name); + return null; + } + } + } + + if(flagManualPositionUsed) + { + if (flagAutoPositionUsed && flagManualPositionUsed) + Logger.LogWarning("Can't mix manual and automatic layout of ship log map mode, defaulting to manual"); + return ConstructMapModeManual(bodies, transformParent, greyScaleMaterial, currentNav, layer); + } + else if (flagAutoPositionUsed) + { + return ConstructMapModeAuto(bodies, transformParent, greyScaleMaterial, layer); + } + + return null; + } + + public static string GetAstroBodyShipLogName(string id) + { + return ShipLogHandler.GetNameFromAstroID(id) ?? id; + } + + private static GameObject CreateImage(GameObject nodeGO, IModAssets assets, Texture2D texture, string name, int layer) + { + GameObject newImageGO = new GameObject(name); + newImageGO.layer = layer; + newImageGO.transform.SetParent(nodeGO.transform); + + RectTransform transform = newImageGO.AddComponent(); + transform.localPosition = Vector3.zero; + transform.localRotation = Quaternion.identity; + transform.localScale = Vector3.one; + + Image newImage = newImageGO.AddComponent(); + + Rect rect = new Rect(0, 0, texture.width, texture.height); + Vector2 pivot = new Vector2(texture.width / 2, texture.height / 2); + newImage.sprite = Sprite.Create(texture, rect, pivot); + + return newImageGO; + } + + private static GameObject CreateMapModeGameObject(NewHorizonsBody body, GameObject parent, int layer, Vector2 position) + { + GameObject newGameObject = new GameObject(body.Config.Name + "_ShipLog"); + newGameObject.layer = layer; + newGameObject.transform.SetParent(parent.transform); + + RectTransform transform = newGameObject.AddComponent(); + float scale = body.Config.ShipLog?.mapMode?.scale ?? 1f; + transform.localPosition = position; + transform.localRotation = Quaternion.identity; + transform.localScale = Vector3.one * scale; + transform.SetAsFirstSibling(); + return newGameObject; + } + + private static ShipLogAstroObject AddShipLogAstroObject(GameObject gameObject, NewHorizonsBody body, Material greyScaleMaterial, int layer) + { + const float unviewedIconOffset = 15; + + GameObject unviewedReference = GameObject.Find(ShipLogHandler.PAN_ROOT_PATH + "/TimberHearth/UnviewedIcon"); + + ShipLogAstroObject astroObject = gameObject.AddComponent(); + astroObject._id = ShipLogHandler.GetAstroObjectId(body); + + Texture2D image; + Texture2D outline; + + string imagePath = body.Config.ShipLog?.mapMode?.revealedSprite; + string outlinePath = body.Config.ShipLog?.mapMode?.outlineSprite; + + if (imagePath != null) image = body.Mod.Assets.GetTexture(imagePath); + else image = AutoGenerateMapModePicture(body); + + if (outlinePath != null) outline = body.Mod.Assets.GetTexture(outlinePath); + else outline = ImageUtilities.MakeOutline(image, Color.white, 10); + + astroObject._imageObj = CreateImage(gameObject, body.Mod.Assets, image, body.Config.Name + " Revealed", layer); + astroObject._outlineObj = CreateImage(gameObject, body.Mod.Assets, outline, body.Config.Name + " Outline", layer); + if (ShipLogHandler.BodyHasEntries(body)) + { + Image revealedImage = astroObject._imageObj.GetComponent(); + astroObject._greyscaleMaterial = greyScaleMaterial; + revealedImage.material = greyScaleMaterial; + revealedImage.color = Color.white; + astroObject._image = revealedImage; + } + + astroObject._unviewedObj = GameObject.Instantiate(unviewedReference, gameObject.transform, false); + astroObject._invisibleWhenHidden = body.Config.ShipLog?.mapMode?.invisibleWhenHidden ?? false; + + Rect imageRect = astroObject._imageObj.GetComponent().rect; + astroObject._unviewedObj.transform.localPosition = new Vector3(imageRect.width / 2 + unviewedIconOffset, imageRect.height / 2 + unviewedIconOffset, 0); + return astroObject; + } + #endregion + + # region Details + private static void MakeDetail(ShipLogModule.ShipLogDetailInfo info, Transform parent, NewHorizonsBody body, Material greyScaleMaterial) + { + GameObject detailGameObject = new GameObject("Detail"); + detailGameObject.transform.SetParent(parent); + detailGameObject.SetActive(false); + + RectTransform detailTransform = detailGameObject.AddComponent(); + detailTransform.localPosition = (Vector2)(info.position ?? new MVector2(0, 0)); + detailTransform.localRotation = Quaternion.Euler(0f, 0f, info.rotation); + detailTransform.localScale = (Vector2)(info.scale ?? new MVector2(0, 0)); + + Texture2D image; + Texture2D outline; + + string imagePath = info.revealedSprite; + string outlinePath = info.outlineSprite; + + if (imagePath != null) image = body.Mod.Assets.GetTexture(imagePath); + else image = AutoGenerateMapModePicture(body); + + if (outlinePath != null) outline = body.Mod.Assets.GetTexture(outlinePath); + else outline = ImageUtilities.MakeOutline(image, Color.white, 10); + + Image revealedImage = CreateImage(detailGameObject, body.Mod.Assets, image, "Detail Revealed", parent.gameObject.layer).GetComponent(); + Image outlineImage = CreateImage(detailGameObject, body.Mod.Assets, outline, "Detail Outline", parent.gameObject.layer).GetComponent(); + + ShipLogDetail detail = detailGameObject.AddComponent(); + detail.Init(info, revealedImage, outlineImage, greyScaleMaterial); + detailGameObject.SetActive(true); + } + + private static void MakeDetails(NewHorizonsBody body, Transform parent, Material greyScaleMaterial) + { + if (body.Config.ShipLog?.mapMode?.details?.Length > 0) + { + GameObject detailsParent = new GameObject("Details"); + detailsParent.transform.SetParent(parent); + detailsParent.SetActive(false); + + RectTransform detailsTransform = detailsParent.AddComponent(); + detailsTransform.localPosition = Vector3.zero; + detailsTransform.localRotation = Quaternion.identity; + detailsTransform.localScale = Vector3.one; + + foreach (ShipLogModule.ShipLogDetailInfo detailInfo in body.Config.ShipLog.mapMode.details) + { + MakeDetail(detailInfo, detailsTransform, body, greyScaleMaterial); + } + detailsParent.SetActive(true); + } + } + #endregion + + #region Manual Map Mode + private static ShipLogAstroObject[][] ConstructMapModeManual(List bodies, GameObject transformParent, Material greyScaleMaterial, ShipLogAstroObject[][] currentNav, int layer) + { + int maxAmount = bodies.Count + 20; + ShipLogAstroObject[][] navMatrix = new ShipLogAstroObject[maxAmount][]; + for (int i = 0; i < maxAmount; i++) + { + navMatrix[i] = new ShipLogAstroObject[maxAmount]; + } + + Dictionary astroIdToNavIndex = new Dictionary(); + + if (Main.Instance.CurrentStarSystem == "SolarSystem") + { + + for (int y = 0; y < currentNav.Length; y++) + { + for (int x = 0; x < currentNav[y].Length; x++) + { + navMatrix[y][x] = currentNav[y][x]; + astroIdToNavIndex.Add(currentNav[y][x].GetID(), new [] {y, x}); + } + } + } + + foreach(NewHorizonsBody body in bodies) + { + if (body.Config.ShipLog?.mapMode?.manualNavigationPosition == null) continue; + + // Sometimes they got other names idk + var name = body.Config.Name.Replace(" ", ""); + var existingBody = AstroObjectLocator.GetAstroObject(body.Config.Name); + if (existingBody != null) + { + var astroName = existingBody.GetAstroObjectName(); + if (astroName == AstroObject.Name.RingWorld) name = "InvisiblePlanet"; + else if (astroName != AstroObject.Name.CustomString) name = astroName.ToString(); + } + // Should probably also just fix the IsVanilla method + var isVanilla = ShipLogHandler.IsVanillaBody(body); + + if (!isVanilla) + { + GameObject newMapModeGO = CreateMapModeGameObject(body, transformParent, layer, body.Config.ShipLog?.mapMode?.manualPosition); + ShipLogAstroObject newAstroObject = AddShipLogAstroObject(newMapModeGO, body, greyScaleMaterial, layer); + MakeDetails(body, newMapModeGO.transform, greyScaleMaterial); + Vector2 navigationPosition = body.Config.ShipLog?.mapMode?.manualNavigationPosition; + navMatrix[(int)navigationPosition.y][(int)navigationPosition.x] = newAstroObject; + } + else if (Main.Instance.CurrentStarSystem == "SolarSystem") + { + GameObject gameObject = GameObject.Find(ShipLogHandler.PAN_ROOT_PATH + "/" + name); + if (body.Config.Destroy || (body.Config.ShipLog?.mapMode?.remove ?? false)) + { + ShipLogAstroObject astroObject = gameObject.GetComponent(); + if (astroObject != null) + { + int[] navIndex = astroIdToNavIndex[astroObject.GetID()]; + navMatrix[navIndex[0]][navIndex[1]] = null; + if (astroObject.GetID() == "CAVE_TWIN" || astroObject.GetID() == "TOWER_TWIN") + { + GameObject.Find(ShipLogHandler.PAN_ROOT_PATH + "/" + "SandFunnel").SetActive(false); + } + } + else if (name == "SandFunnel") + { + GameObject.Find(ShipLogHandler.PAN_ROOT_PATH + "/" + "SandFunnel").SetActive(false); + } + gameObject.SetActive(false); + } + else + { + if (body.Config.ShipLog?.mapMode?.manualPosition != null) + { + gameObject.transform.localPosition = (Vector2)body.Config.ShipLog.mapMode.manualPosition; + } + if (body.Config.ShipLog?.mapMode?.manualNavigationPosition != null) + { + Vector2 navigationPosition = body.Config.ShipLog?.mapMode?.manualNavigationPosition; + navMatrix[(int)navigationPosition.y][(int)navigationPosition.x] = gameObject.GetComponent(); + } + if (body.Config.ShipLog?.mapMode?.scale != null) + { + gameObject.transform.localScale = Vector3.one * body.Config.ShipLog.mapMode.scale; + } + } + } + } + + navMatrix = navMatrix.Where(a => a.Count(c => c != null && c.gameObject != null) > 0).Prepend(new ShipLogAstroObject[1]).ToArray(); + for (var index = 0; index < navMatrix.Length; index++) + { + navMatrix[index] = navMatrix[index].Where(a => a != null && a.gameObject != null).ToArray(); + } + + return navMatrix; + } + #endregion + + #region Automatic Map Mode + private class MapModeObject + { + public int x; + public int y; + public int branch_width; + public int branch_height; + public int level; + public NewHorizonsBody mainBody; + public ShipLogAstroObject astroObject; + public List children; + public MapModeObject parent; + public MapModeObject lastSibling; + public void Increment_width() + { + branch_width++; + parent?.Increment_width(); + } + public void Increment_height() + { + branch_height++; + parent?.Increment_height(); + } + } + + private static ShipLogAstroObject[][] ConstructMapModeAuto(List bodies, GameObject transformParent, Material greyScaleMaterial, int layer) + { + MapModeObject rootObject = ConstructPrimaryNode(bodies); + if (rootObject.mainBody != null) + { + MakeAllNodes(ref rootObject, transformParent, greyScaleMaterial, layer); + } + + int maxAmount = bodies.Count; + ShipLogAstroObject[][] navMatrix = new ShipLogAstroObject[maxAmount][]; + for (int i = 0; i < maxAmount; i++) + { + navMatrix[i] = new ShipLogAstroObject[maxAmount]; + } + + CreateNavigationMatrix(rootObject, ref navMatrix); + navMatrix = navMatrix.Where(a => a.Count(c => c != null) > 0).Prepend(new ShipLogAstroObject[1]).ToArray(); + for (var index = 0; index < navMatrix.Length; index++) + { + navMatrix[index] = navMatrix[index].Where(a => a != null).ToArray(); + } + return navMatrix; + } + + private static void CreateNavigationMatrix(MapModeObject root, ref ShipLogAstroObject[][] navMatrix) + { + if (root.astroObject != null) + { + navMatrix[root.y][root.x] = root.astroObject; + } + foreach (MapModeObject child in root.children) + { + CreateNavigationMatrix(child, ref navMatrix); + } + } + + private static void MakeAllNodes(ref MapModeObject parentNode, GameObject parent, Material greyScaleMaterial, int layer) + { + MakeNode(ref parentNode, parent, greyScaleMaterial, layer); + for (var i = 0; i < parentNode.children.Count; i++) + { + MapModeObject child = parentNode.children[i]; + MakeAllNodes(ref child, parent, greyScaleMaterial, layer); + parentNode.children[i] = child; + } + } + + private static MapModeObject ConstructPrimaryNode(List bodies) + { + foreach (NewHorizonsBody body in bodies.Where(b => b.Config.Base.CenterOfSolarSystem)) + { + bodies.Sort((b, o) => b.Config.Orbit.SemiMajorAxis.CompareTo(o.Config.Orbit.SemiMajorAxis)); + MapModeObject newNode = new MapModeObject + { + mainBody = body, + level = 0, + x = 0, + y = 0 + }; + newNode.children = ConstructChildrenNodes(newNode, bodies); + return newNode; + } + Logger.LogError("Couldn't find center of system!"); + return new MapModeObject(); + } + + private static List ConstructChildrenNodes(MapModeObject parent, List searchList, string secondaryName = "") + { + List children = new List(); + int newX = parent.x; + int newY = parent.y; + int newLevel = parent.level + 1; + MapModeObject lastSibling = parent; + + foreach (NewHorizonsBody body in searchList.Where(b => b.Config.Orbit.PrimaryBody == parent.mainBody.Config.Name || b.Config.Name == secondaryName)) + { + bool even = newLevel % 2 == 0; + newX = even ? newX : newX + 1; + newY = even ? newY + 1 : newY; + MapModeObject newNode = new MapModeObject() + { + mainBody = body, + level = newLevel, + x = newX, + y = newY, + parent = parent, + lastSibling = lastSibling + }; + string newSecondaryName = ""; + if (body.Config.FocalPoint != null) + { + newNode.mainBody = searchList.Find(b => b.Config.Name == body.Config.FocalPoint.Primary); + newSecondaryName = searchList.Find(b => b.Config.Name == body.Config.FocalPoint.Secondary).Config.Name; + } + + newNode.children = ConstructChildrenNodes(newNode, searchList, newSecondaryName); + if (even) + { + newY += newNode.branch_height; + parent.Increment_height(); + newY += 1; + } + else + { + newX += newNode.branch_width; + parent.Increment_width(); + newX += 1; + } + + lastSibling = newNode; + children.Add(newNode); + } + return children; + } + + private static void ConnectNodeToLastSibling(MapModeObject node, Material greyScaleMaterial) + { + Vector2 fromPosition = node.astroObject.transform.localPosition; + Vector2 toPosition = node.lastSibling.astroObject.transform.localPosition; + + GameObject newLink = new GameObject("Line_ShipLog"); + newLink.layer = node.astroObject.gameObject.layer; + newLink.SetActive(false); + + RectTransform transform = newLink.AddComponent(); + transform.SetParent(node.astroObject.transform.parent); + Vector2 center = toPosition + (fromPosition - toPosition) / 2; + transform.localPosition = new Vector3(center.x, center.y, -1); + transform.localRotation = Quaternion.identity; + transform.localScale = node.level % 2 == 0 ? new Vector3(node.astroObject.transform.localScale.x / 5f, Mathf.Abs(fromPosition.y - toPosition.y) / 100f, 1) : new Vector3(Mathf.Abs(fromPosition.x - toPosition.x) / 100f, node.astroObject.transform.localScale.y / 5f, 1); + Image linkImage = newLink.AddComponent(); + linkImage.color = new Color(0.28f, 0.28f, 0.5f, 0.12f); + + ShipLogModule.ShipLogDetailInfo linkDetailInfo = new ShipLogModule.ShipLogDetailInfo() + { + invisibleWhenHidden = node.mainBody.Config.ShipLog?.mapMode?.invisibleWhenHidden ?? false + }; + + ShipLogDetail linkDetail = newLink.AddComponent(); + linkDetail.Init(linkDetailInfo, linkImage, linkImage, greyScaleMaterial); + + transform.SetParent(node.astroObject.transform); + transform.SetAsFirstSibling(); + newLink.SetActive(true); + } + + private static void MakeNode(ref MapModeObject node, GameObject parent, Material greyScaleMaterial, int layer) + { + const float padding = 50f; + Vector2 position = Vector2.zero; + if (node.lastSibling != null) + { + ShipLogAstroObject lastAstroObject = node.lastSibling.astroObject; + Vector3 lastPosition = lastAstroObject.transform.localPosition; + position = lastPosition; + float extraDistance = (node.mainBody.Config.ShipLog?.mapMode?.offset ?? 0f) * 100; + + if(node.parent != null) + { + var branchDistance = node.parent.children.IndexOf(node); + var goingUp = node.parent.level % 2 != 0; + + if(goingUp && branchDistance == 0) position.y += (int)padding; + if(!goingUp && branchDistance == 0) position.x += (int)padding; + } + + if (node.level % 2 == 0) + { + position.y += padding * (node.y - node.lastSibling.y) + extraDistance; + } + else + { + position.x += padding * (node.x - node.lastSibling.x) + extraDistance; + } + } + GameObject newNodeGO = CreateMapModeGameObject(node.mainBody, parent, layer, position); + ShipLogAstroObject astroObject = AddShipLogAstroObject(newNodeGO, node.mainBody, greyScaleMaterial, layer); + if (node.mainBody.Config.FocalPoint != null) + { + astroObject._imageObj.GetComponent().enabled = false; + astroObject._outlineObj.GetComponent().enabled = false; + astroObject._unviewedObj.GetComponent().enabled = false; + astroObject.transform.localScale = node.lastSibling.astroObject.transform.localScale; + } + node.astroObject = astroObject; + if (node.lastSibling != null) ConnectNodeToLastSibling(node, greyScaleMaterial); + MakeDetails(node.mainBody, newNodeGO.transform, greyScaleMaterial); + } + #endregion + + private static Texture2D AutoGenerateMapModePicture(NewHorizonsBody body) + { + Texture2D texture; + + if(body.Config.Star != null) texture = Main.Instance.ModHelper.Assets.GetTexture("AssetBundle/DefaultMapModeStar.png"); + else if(body.Config.Atmosphere != null) texture = Main.Instance.ModHelper.Assets.GetTexture("AssetBundle/DefaultMapModNoAtmo.png"); + else texture = Main.Instance.ModHelper.Assets.GetTexture("AssetBundle/DefaultMapModePlanet.png"); + + var color = GetDominantPlanetColor(body); + var darkColor = new Color(color.r / 3f, color.g / 3f, color.b / 3f); + + texture = ImageUtilities.LerpGreyscaleImage(texture, color, darkColor); + + return texture; + } + + private static Color GetDominantPlanetColor(NewHorizonsBody body) + { + var starColor = body.Config?.Star?.Tint; + if (starColor != null) return starColor.ToColor(); + + var atmoColor = body.Config.Atmosphere?.AtmosphereTint; + if (body.Config.Atmosphere?.Cloud != null) return atmoColor.ToColor(); + + if (body.Config?.HeightMap?.TextureMap != null) + { + try + { + var texture = body.Mod.Assets.GetTexture(body.Config.HeightMap.TextureMap); + var landColor = ImageUtilities.GetAverageColor(texture); + if (landColor != null) return landColor; + } + catch (Exception) { } + } + + var waterColor = body.Config.Water?.Tint; + if (waterColor != null) return waterColor.ToColor(); + + var lavaColor = body.Config.Lava?.Tint; + if (lavaColor != null) return lavaColor.ToColor(); + + var sandColor = body.Config.Sand?.Tint; + if (sandColor != null) return sandColor.ToColor(); + + return Color.white; + } + } +} diff --git a/NewHorizons/Builder/ShipLog/RevealBuilder.cs b/NewHorizons/Builder/ShipLog/RevealBuilder.cs new file mode 100644 index 00000000..ef8c5aae --- /dev/null +++ b/NewHorizons/Builder/ShipLog/RevealBuilder.cs @@ -0,0 +1,89 @@ +using NewHorizons.Components; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Xml.Linq; +using NewHorizons.External; +using NewHorizons.Utility; +using OWML.Common; +using UnityEngine; +using UnityEngine.UI; +using Logger = NewHorizons.Utility.Logger; + +namespace NewHorizons.Builder.ShipLog +{ + public static class RevealBuilder + { + public static void Make(GameObject go, Sector sector, PropModule.RevealInfo info, IModHelper mod) + { + GameObject newRevealGO = MakeGameObject(go, sector, info, mod); + switch (info.revealOn.ToLower()) + { + case "enter": + MakeTrigger(newRevealGO, sector, info, mod); + break; + case "observe": + MakeObservable(newRevealGO, sector, info, mod); + break; + case "snapshot": + MakeSnapshot(newRevealGO, sector, info, mod); + break; + default: + Logger.LogError("Invalid revealOn: " + info.revealOn); + break; + } + + newRevealGO.SetActive(true); + } + + private static SphereShape MakeShape(GameObject go, PropModule.RevealInfo info, Shape.CollisionMode collisionMode) + { + SphereShape newShape = go.AddComponent(); + newShape.radius = info.radius; + newShape.SetCollisionMode(collisionMode); + return newShape; + } + + private static GameObject MakeGameObject(GameObject go, Sector sector, PropModule.RevealInfo info, IModHelper mod) + { + GameObject revealTriggerVolume = new GameObject("Reveal Volume (" + info.revealOn + ")"); + revealTriggerVolume.SetActive(false); + revealTriggerVolume.transform.parent = sector?.transform ?? go.transform; + revealTriggerVolume.transform.localPosition = info.position; + return revealTriggerVolume; + } + + private static void MakeTrigger(GameObject go, Sector sector, PropModule.RevealInfo info, IModHelper mod) + { + SphereShape newShape = MakeShape(go, info, Shape.CollisionMode.Volume); + OWTriggerVolume newVolume = go.AddComponent(); + newVolume._shape = newShape; + ShipLogFactListTriggerVolume volume = go.AddComponent(); + volume._factIDs = info.reveals; + } + + private static void MakeObservable(GameObject go, Sector sector, PropModule.RevealInfo info, IModHelper mod) + { + go.layer = LayerMask.NameToLayer("Interactible"); + SphereCollider newSphere = go.AddComponent(); + newSphere.radius = info.radius; + OWCollider newCollider = go.AddComponent(); + ShipLogFactObserveTrigger newObserveTrigger = go.AddComponent(); + newObserveTrigger._factIDs = info.reveals; + newObserveTrigger._maxViewDistance = info.maxDistance == -1f ? 2f : info.maxDistance; + newObserveTrigger._maxViewAngle = info.maxAngle; + newObserveTrigger._owCollider = newCollider; + newObserveTrigger._disableColliderOnRevealFact = true; + } + + private static void MakeSnapshot(GameObject go, Sector sector, PropModule.RevealInfo info, IModHelper mod) + { + SphereShape newShape = MakeShape(go, info, Shape.CollisionMode.Manual); + ShapeVisibilityTracker newTracker = go.AddComponent(); + newTracker._shapes = new Shape[] { newShape }; + ShipLogFactSnapshotTrigger newSnapshotTrigger = go.AddComponent(); + newSnapshotTrigger._maxDistance = info.maxDistance == -1f ? 200f : info.maxDistance; + newSnapshotTrigger._factIDs = info.reveals; + } + } +} diff --git a/NewHorizons/Builder/ShipLog/RumorModeBuilder.cs b/NewHorizons/Builder/ShipLog/RumorModeBuilder.cs new file mode 100644 index 00000000..7117fb68 --- /dev/null +++ b/NewHorizons/Builder/ShipLog/RumorModeBuilder.cs @@ -0,0 +1,236 @@ +using System; +using NewHorizons.Components; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Xml.Linq; +using NewHorizons.External; +using NewHorizons.Utility; +using OWML.Common; +using UnityEngine; +using UnityEngine.UI; +using Logger = NewHorizons.Utility.Logger; +using NewHorizons.Builder.Handlers; + +namespace NewHorizons.Builder.ShipLog +{ + public static class RumorModeBuilder + { + private static Dictionary _curiosityColors; + private static Dictionary _curiosityHighlightColors; + private static Dictionary _rawNameToCuriosityName; + private static Dictionary _entryIdToRawName; + + public static void Init() + { + _curiosityColors = new Dictionary(); + _curiosityHighlightColors = new Dictionary(); + _rawNameToCuriosityName = new Dictionary(); + _entryIdToRawName = new Dictionary(); + } + + public static void AddCuriosityColors(ShipLogModule.CuriosityColorInfo[] newColors) + { + foreach (ShipLogModule.CuriosityColorInfo newColor in newColors) + { + if (_rawNameToCuriosityName.ContainsKey(newColor.id) == false) + { + CuriosityName newName = (CuriosityName)8 + _rawNameToCuriosityName.Count; + _rawNameToCuriosityName.Add(newColor.id, newName); + _curiosityColors.Add(newName, newColor.color.ToColor()); + _curiosityHighlightColors.Add(newName, newColor.highlightColor.ToColor()); + } + } + } + + public static Color GetCuriosityColor(CuriosityName curiosityName, bool highlighted, Color defaultColor, Color defaultHighlight) + { + if (_curiosityColors.ContainsKey(curiosityName) && _curiosityHighlightColors.ContainsKey(curiosityName)) + { + return (highlighted ? _curiosityHighlightColors : _curiosityColors)[curiosityName]; + } + else + { + return highlighted ? defaultHighlight : defaultColor; + } + } + + public static void AddBodyToShipLog(ShipLogManager manager, NewHorizonsBody body) + { + string systemName = body.Config.StarSystem; + XElement astroBodyFile = XElement.Load(body.Mod.Manifest.ModFolderPath + "/" + body.Config.ShipLog.xmlFile); + XElement astroBodyId = astroBodyFile.Element("ID"); + if (astroBodyId == null) + { + Logger.LogError("Failed to load ship logs for " + systemName + "!"); + } + else + { + var entryIDs = new List(); + foreach (XElement entryElement in astroBodyFile.DescendantsAndSelf("Entry")) + { + XElement curiosityName = entryElement.Element("Curiosity"); + XElement id = entryElement.Element("ID"); + if (curiosityName != null && id != null && _entryIdToRawName.ContainsKey(id.Value) == false) + { + entryIDs.Add(id.Value); + _entryIdToRawName.Add(id.Value, curiosityName.Value); + } + foreach (XElement childEntryElement in entryElement.Elements("Entry")) + { + XElement childCuriosityName = childEntryElement.Element("Curiosity"); + XElement childId = childEntryElement.Element("ID"); + if (childId != null && _entryIdToRawName.ContainsKey(childId.Value)) + { + if (childCuriosityName == null && curiosityName != null) + { + _entryIdToRawName.Add(childId.Value, curiosityName.Value); + } + else if (childCuriosityName != null) + { + _entryIdToRawName.Add(childId.Value, childCuriosityName.Value); + } + entryIDs.Add(childId.Value); + } + AddTranslation(childEntryElement); + } + AddTranslation(entryElement); + } + TextAsset newAsset = new TextAsset(astroBodyFile.ToString()); + List newBodies = new List(manager._shipLogXmlAssets) { newAsset }; + manager._shipLogXmlAssets = newBodies.ToArray(); + ShipLogHandler.AddConfig(astroBodyId.Value, entryIDs, body); + } + } + + public static void GenerateEntryData(ShipLogManager manager) + { + const int step = 400; + int colAccumulator = 0; + int rowAccumulator = 0; + foreach (ShipLogEntry entry in manager._entryList) + { + if (manager._entryDataDict.ContainsKey(entry._id) == false) + { + NewHorizonsBody body = ShipLogHandler.GetConfigFromEntryID(entry._id); + Vector2? manualEntryPosition = GetManualEntryPosition(entry._id, body.Config.ShipLog); + Vector2 entryPosition; + if (manualEntryPosition == null) + { + entryPosition = new Vector2(colAccumulator, rowAccumulator); + } + else + { + entryPosition = (Vector2)manualEntryPosition; + } + EntryData newData = new EntryData + { + id = entry._id, + cardPosition = entryPosition, + sprite = body.Config.ShipLog.spriteFolder == null ? null : GetEntrySprite(entry._id, body, true), + altSprite = body.Config.ShipLog.spriteFolder == null ? null : GetEntrySprite(entry._id + "_ALT", body, false) + }; + entry.SetSprite(newData.sprite == null ? manager._shipLogLibrary.defaultEntrySprite : newData.sprite); + entry.SetAltSprite(newData.sprite == null ? manager._shipLogLibrary.defaultEntrySprite : newData.altSprite); + manager._entryDataDict.Add(entry._id, newData); + int index = manager._entryList.IndexOf(entry); + if (index < manager._entryList.Count - 2 && manager._entryList[index + 1]._astroObjectID != entry._astroObjectID) + { + rowAccumulator += step; + colAccumulator = 0; + } + else + { + colAccumulator += step; + } + } + } + } + + private static void AddTranslation(XElement entry) + { + Dictionary table = TextTranslation.Get().m_table.theShipLogTable; + XElement nameElement = entry.Element("Name"); + if (nameElement != null) + { + string name = nameElement.Value; + table[name] = name; + foreach (XElement rumorFact in entry.Elements("RumorFact")) + { + AddTranslationForElement(rumorFact, "RumorName", string.Empty, table); + AddTranslationForElement(rumorFact, "Text", name, table); + AddTranslationForAltText(rumorFact, name, table); + } + foreach (XElement exploreFact in entry.Elements("ExploreFact")) + { + AddTranslationForElement(exploreFact, "Text", name, table); + AddTranslationForAltText(exploreFact, name, table); + } + } + } + + private static void AddTranslationForElement(XElement parent, string elementName, string keyName, Dictionary table) + { + XElement element = parent.Element(elementName); + if (element != null) + { + table[keyName + element.Value] = element.Value; + } + } + + private static void AddTranslationForAltText(XElement fact, string keyName, Dictionary table) + { + XElement altText = fact.Element("AltText"); + if (altText != null) + { + AddTranslationForElement(altText, "Text", keyName, table); + } + } + + public static void UpdateEntryCuriosity(ref ShipLogEntry entry) + { + if (_entryIdToRawName.ContainsKey(entry._id)) + { + var raw = _entryIdToRawName[entry._id]; + if (_rawNameToCuriosityName.ContainsKey(raw)) + { + entry._curiosity = _rawNameToCuriosityName[raw]; + } + else + { + Logger.LogError($"Couldn't find {raw}. Did you define the curiosity in a json config? Because you have to."); + } + } + } + + private static Sprite GetEntrySprite(string entryId, NewHorizonsBody body, bool logError) + { + string relativePath = body.Config.ShipLog.spriteFolder + "/" + entryId + ".png"; + try + { + Texture2D newTexture = body.Mod.Assets.GetTexture(relativePath); + Rect rect = new Rect(0, 0, newTexture.width, newTexture.height); + Vector2 pivot = new Vector2(newTexture.width / 2, newTexture.height / 2); + return Sprite.Create(newTexture, rect, pivot); + } + catch(Exception) + { + if(logError) Logger.LogError($"Couldn't load image for {entryId} at {relativePath}"); + return null; + } + } + + private static Vector2? GetManualEntryPosition(string entryId, ShipLogModule config) + { + if (config.entryPositions == null) return null; + foreach (ShipLogModule.EntryPositionInfo position in config.entryPositions) + { + if (position.id == entryId) + { + return position.position; + } + } + return null; + } + } +} diff --git a/NewHorizons/Components/ShipLogDetail.cs b/NewHorizons/Components/ShipLogDetail.cs new file mode 100644 index 00000000..e1936a30 --- /dev/null +++ b/NewHorizons/Components/ShipLogDetail.cs @@ -0,0 +1,60 @@ +using System; +using NewHorizons.External; +using OWML.Common; +using UnityEngine; +using UnityEngine.UI; +using Logger = NewHorizons.Utility.Logger; + +namespace NewHorizons.Components +{ + public class ShipLogDetail : MonoBehaviour + { + private Image _revealedImage; + private Image _outlineImage; + private Material _greyScaleMaterial; + private ShipLogModule.ShipLogDetailInfo _detailInfo; + + public void Init(ShipLogModule.ShipLogDetailInfo info, Image revealed, Image outline, Material greyScale) + { + _detailInfo = info; + _revealedImage = revealed; + _outlineImage = outline; + _greyScaleMaterial = greyScale; + _revealedImage.enabled = false; + _outlineImage.enabled = false; + } + + public void UpdateState(ShipLogEntry.State parentState) + { + switch (parentState) + { + case ShipLogEntry.State.Explored: + _outlineImage.enabled = false; + _revealedImage.enabled = true; + SetGreyScale(false); + break; + case ShipLogEntry.State.Rumored: + _outlineImage.enabled = false; + _revealedImage.enabled = true; + SetGreyScale(true); + break; + case ShipLogEntry.State.Hidden: + _revealedImage.enabled = false; + _outlineImage.enabled = !_detailInfo.invisibleWhenHidden; + break; + case ShipLogEntry.State.None: + _revealedImage.enabled = false; + _outlineImage.enabled = false; + break; + default: + Logger.LogError("Invalid ShipLogEntryState for " + _revealedImage.transform.parent.parent.gameObject.name); + break; + } + } + + private void SetGreyScale(bool greyScale) + { + _revealedImage.material = (greyScale ? _greyScaleMaterial : null); + } + } +} \ No newline at end of file diff --git a/NewHorizons/External/IPlanetConfig.cs b/NewHorizons/External/IPlanetConfig.cs index 56d12c12..0922c4fa 100644 --- a/NewHorizons/External/IPlanetConfig.cs +++ b/NewHorizons/External/IPlanetConfig.cs @@ -20,6 +20,7 @@ namespace NewHorizons.External StarModule Star { get; } FocalPointModule FocalPoint { get; } PropModule Props { get; } + ShipLogModule ShipLog { get; } SpawnModule Spawn { get; } SignalModule Signal { get; } SingularityModule Singularity { get; } diff --git a/NewHorizons/External/PlanetConfig.cs b/NewHorizons/External/PlanetConfig.cs index cf71a576..d23a58ab 100644 --- a/NewHorizons/External/PlanetConfig.cs +++ b/NewHorizons/External/PlanetConfig.cs @@ -23,6 +23,7 @@ namespace NewHorizons.External public StarModule Star { get; set; } public FocalPointModule FocalPoint { get; set; } public PropModule Props { get; set; } + public ShipLogModule ShipLog { get; set; } public SpawnModule Spawn { get; set; } public SignalModule Signal { get; set; } public SingularityModule Singularity { get; set; } @@ -36,6 +37,7 @@ namespace NewHorizons.External // Always have to have a base module Base = new BaseModule(); Orbit = new OrbitModule(); + ShipLog = new ShipLogModule(); if (dict == null) return; diff --git a/NewHorizons/External/PropModule.cs b/NewHorizons/External/PropModule.cs index 2d35bfe3..4119ca66 100644 --- a/NewHorizons/External/PropModule.cs +++ b/NewHorizons/External/PropModule.cs @@ -15,6 +15,8 @@ namespace NewHorizons.External public GeyserInfo[] Geysers; public TornadoInfo[] Tornados; public DialogueInfo[] Dialogue; + public RevealInfo[] Reveal; + public EntryLocationInfo[] EntryLocation; public class ScatterInfo { @@ -67,5 +69,22 @@ namespace NewHorizons.External public MVector3 remoteTriggerPosition; public string blockAfterPersistentCondition; } + + public class RevealInfo + { + public string revealOn = "enter"; + public string[] reveals; + public MVector3 position = new MVector3(0, 0, 0); + public float radius = 1f; + public float maxDistance = -1f; // Snapshot & Observe Only + public float maxAngle = 180f; // Observe Only + } + + public class EntryLocationInfo + { + public string id; + public bool cloaked; + public MVector3 position; + } } } diff --git a/NewHorizons/External/ShipLogModule.cs b/NewHorizons/External/ShipLogModule.cs new file mode 100644 index 00000000..dfd17be1 --- /dev/null +++ b/NewHorizons/External/ShipLogModule.cs @@ -0,0 +1,50 @@ +using NewHorizons.Utility; + +namespace NewHorizons.External +{ + public class ShipLogModule : Module + { + public string xmlFile; + public string spriteFolder; + public string[] initialReveal; + public MapModeInfo mapMode = new MapModeInfo(); + public CuriosityColorInfo[] curiosities; + public EntryPositionInfo[] entryPositions; + + public class MapModeInfo + { + public string revealedSprite; + public string outlineSprite; + public float scale = 1f; + public bool invisibleWhenHidden; + public float offset = 0f; + public MVector2 manualPosition; + public MVector2 manualNavigationPosition; + public bool remove = false; + public ShipLogDetailInfo[] details; + } + + public class ShipLogDetailInfo + { + public string revealedSprite; + public string outlineSprite; + public float rotation = 0f; + public bool invisibleWhenHidden; + public MVector2 position; + public MVector2 scale; + } + + public class CuriosityColorInfo + { + public string id; + public MColor color; + public MColor highlightColor; + } + + public class EntryPositionInfo + { + public string id; + public MVector2 position; + } + } +} \ No newline at end of file diff --git a/NewHorizons/External/SignalModule.cs b/NewHorizons/External/SignalModule.cs index 07a72252..906e3411 100644 --- a/NewHorizons/External/SignalModule.cs +++ b/NewHorizons/External/SignalModule.cs @@ -18,6 +18,7 @@ namespace NewHorizons.External public string Name; public string AudioClip = null; public string AudioFilePath = null; + public string Reveals = ""; public float SourceRadius = 1f; public float DetectionRadius = 0f; public float IdentificationRadius = 10f; diff --git a/NewHorizons/Handlers/ShipLogHandler.cs b/NewHorizons/Handlers/ShipLogHandler.cs new file mode 100644 index 00000000..5975e5f3 --- /dev/null +++ b/NewHorizons/Handlers/ShipLogHandler.cs @@ -0,0 +1,98 @@ +using NewHorizons.Components; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Xml.Linq; +using NewHorizons.External; +using NewHorizons.Utility; +using OWML.Common; +using UnityEngine; +using UnityEngine.UI; +using Logger = NewHorizons.Utility.Logger; + +namespace NewHorizons.Builder.Handlers +{ + public static class ShipLogHandler + { + public static readonly string PAN_ROOT_PATH = "Ship_Body/Module_Cabin/Systems_Cabin/ShipLogPivot/ShipLog/ShipLogPivot/ShipLogCanvas/MapMode/ScaleRoot/PanRoot"; + + // NewHorizonsBody -> EntryIDs + private static Dictionary> _nhBodyToEntryIDs; + //EntryID -> NewHorizonsBody + private static Dictionary _entryIDsToNHBody; + // NewHorizonsBody -> AstroID + private static Dictionary _nhBodyToAstroIDs; + + private static string[] vanillaBodies; + private static string[] vanillaIDs; + + public static void Init() + { + _nhBodyToEntryIDs = new Dictionary>(); + _entryIDsToNHBody = new Dictionary(); + _nhBodyToAstroIDs = new Dictionary(); + + List gameObjects = SearchUtilities.GetAllChildren(GameObject.Find(PAN_ROOT_PATH)); + vanillaBodies = gameObjects.ConvertAll(g => g.name).ToArray(); + vanillaIDs = gameObjects.ConvertAll(g => g.GetComponent()?.GetID()).ToArray(); + } + + public static bool IsVanillaAstroID(string astroId) + { + return vanillaIDs.Contains(astroId); + } + + public static bool IsVanillaBody(NewHorizonsBody body) + { + var existingBody = AstroObjectLocator.GetAstroObject(body.Config.Name); + if (existingBody != null && existingBody.GetAstroObjectName() != AstroObject.Name.CustomString) + return true; + + return vanillaBodies.Contains(body.Config.Name.Replace(" ", "")); + } + + public static string GetNameFromAstroID(string astroID) + { + return CollectionUtilities.KeyByValue(_nhBodyToAstroIDs, astroID)?.Config.Name; + } + + public static NewHorizonsBody GetConfigFromEntryID(string entryID) + { + if (_entryIDsToNHBody.ContainsKey(entryID)) return _entryIDsToNHBody[entryID]; + else + { + Logger.LogError($"Couldn't find NewHorizonsBody that corresponds to {entryID}"); + return null; + } + } + + public static void AddConfig(string astroID, List entryIDs, NewHorizonsBody body) + { + // Nice to be able to just get the AstroID from the body + if (!_nhBodyToEntryIDs.ContainsKey(body)) _nhBodyToEntryIDs.Add(body, entryIDs); + else Logger.LogWarning($"Possible duplicate shiplog entry {body.Config.Name}"); + + // AstroID + if (!_nhBodyToAstroIDs.ContainsKey(body)) _nhBodyToAstroIDs.Add(body, astroID); + else Logger.LogWarning($"Possible duplicate shiplog entry {astroID} for {body.Config.Name}"); + + // EntryID to Body + foreach (var entryID in entryIDs) + { + if (!_entryIDsToNHBody.ContainsKey(entryID)) _entryIDsToNHBody.Add(entryID, body); + else Logger.LogWarning($"Possible duplicate shiplog entry {entryID} for {astroID} from NewHorizonsBody {body.Config.Name}"); + } + } + + public static string GetAstroObjectId(NewHorizonsBody body) + { + if (_nhBodyToAstroIDs.ContainsKey(body)) return _nhBodyToAstroIDs[body]; + else return body.Config.Name; + } + + public static bool BodyHasEntries(NewHorizonsBody body) + { + return _nhBodyToAstroIDs.ContainsKey(body) && _nhBodyToAstroIDs[body].Length > 0; + } + } +} diff --git a/NewHorizons/Main.cs b/NewHorizons/Main.cs index 29d4b03b..9751e6e3 100644 --- a/NewHorizons/Main.cs +++ b/NewHorizons/Main.cs @@ -4,6 +4,7 @@ using NewHorizons.Builder.Body; using NewHorizons.Builder.General; using NewHorizons.Builder.Orbital; using NewHorizons.Builder.Props; +using NewHorizons.Builder.ShipLog; using NewHorizons.Builder.Updater; using NewHorizons.Components; using NewHorizons.External; @@ -14,17 +15,12 @@ using NewHorizons.Utility; using OWML.Common; using OWML.ModHelper; using OWML.Utils; -using PacificEngine.OW_CommonResources.Game.Player; -using PacificEngine.OW_CommonResources.Game.Resource; -using PacificEngine.OW_CommonResources.Game.State; using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Reflection; using UnityEngine; using UnityEngine.SceneManagement; -using UnityEngine.UI; using Logger = NewHorizons.Utility.Logger; namespace NewHorizons @@ -67,6 +63,7 @@ namespace NewHorizons Tools.Patches.Apply(); Tools.WarpDrivePatches.Apply(); Tools.OWCameraFix.Apply(); + Tools.ShipLogPatches.Apply(); Logger.Log("Begin load of config files...", Logger.LogType.Log); @@ -79,7 +76,7 @@ namespace NewHorizons Logger.LogWarning("Couldn't find planets folder"); } - UnityEngine.Random.InitState((int)DateTime.Now.Ticks); + //UnityEngine.Random.InitState((int)DateTime.Now.Ticks); Instance.ModHelper.Events.Unity.FireOnNextUpdate(() => OnSceneLoaded(SceneManager.GetActiveScene(), LoadSceneMode.Single)); } diff --git a/NewHorizons/NewHorizons.csproj.user b/NewHorizons/NewHorizons.csproj.user index 4a7efd53..9aeb566d 100644 --- a/NewHorizons/NewHorizons.csproj.user +++ b/NewHorizons/NewHorizons.csproj.user @@ -2,7 +2,7 @@ ProjectFiles - $(AppData)\OuterWildsModManager\OWML\Mods\xen.NewHorizons - $(AppData)\OuterWildsModManager\OWML\Mods + $(AppData)\OuterWildsModManager\OWML\Mods\xen.NewHorizons + $(AppData)\OuterWildsModManager\OWML\Mods - + \ No newline at end of file diff --git a/NewHorizons/Tools/Patches.cs b/NewHorizons/Tools/Patches.cs index 4e32af97..bd1fdf4c 100644 --- a/NewHorizons/Tools/Patches.cs +++ b/NewHorizons/Tools/Patches.cs @@ -5,11 +5,20 @@ using NewHorizons.External; using OWML.Common; using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; +using System.Xml.Linq; +using Harmony; +using NewHorizons.Utility; +using OWML.Utils; using UnityEngine; using Logger = NewHorizons.Utility.Logger; +using Object = UnityEngine.Object; +using NewHorizons.Handlers; +using NewHorizons.Builder.ShipLog; +using NewHorizons.Builder.Handlers; namespace NewHorizons.Tools { @@ -41,7 +50,7 @@ namespace NewHorizons.Tools var playerDataLearnFrequency = typeof(PlayerData).GetMethod("LearnFrequency"); Main.Instance.ModHelper.HarmonyHelper.AddPrefix(playerDataLearnFrequency, typeof(Patches), nameof(Patches.OnPlayerDataLearnFrequency)); var playerDataKnowsMultipleFrequencies = typeof(PlayerData).GetMethod("KnowsMultipleFrequencies"); - Main.Instance.ModHelper.HarmonyHelper.AddPrefix(playerDataKnowsMultipleFrequencies, typeof(Patches), nameof(Patches.OnPlayerDataKnowsMultipleFrequencies)); + Main.Instance.ModHelper.HarmonyHelper.AddPrefix(playerDataKnowsMultipleFrequencies, typeof(Patches), nameof(Patches.OnPlayerDataKnowsMultipleFrequencies)); var playerDataResetGame = typeof(PlayerData).GetMethod("ResetGame"); Main.Instance.ModHelper.HarmonyHelper.AddPostfix(playerDataResetGame, typeof(Patches), nameof(Patches.OnPlayerDataResetGame)); @@ -307,7 +316,7 @@ namespace NewHorizons.Tools } return true; } - + public static void OnPlayerDataResetGame() { NewHorizonsData.Reset(); diff --git a/NewHorizons/Tools/ShipLogPatches.cs b/NewHorizons/Tools/ShipLogPatches.cs new file mode 100644 index 00000000..2c25b921 --- /dev/null +++ b/NewHorizons/Tools/ShipLogPatches.cs @@ -0,0 +1,215 @@ +using NewHorizons.Components; +using System; +using System.Collections.Generic; +using System.Linq; +using NewHorizons.Utility; +using UnityEngine; +using Logger = NewHorizons.Utility.Logger; +using Object = UnityEngine.Object; +using NewHorizons.Builder.ShipLog; +using NewHorizons.Builder.Handlers; + +namespace NewHorizons.Tools +{ + public static class ShipLogPatches + { + public static void Apply() + { + var playerDataGetNewlyRevealedFactIDs = typeof(PlayerData).GetMethod("GetNewlyRevealedFactIDs"); + Main.Instance.ModHelper.HarmonyHelper.AddPostfix(playerDataGetNewlyRevealedFactIDs, typeof(ShipLogPatches), nameof(ShipLogPatches.OnPlayerDataGetNewlyRevealedFactIDsComplete)); + + Main.Instance.ModHelper.HarmonyHelper.AddPrefix("Awake", typeof(ShipLogPatches), nameof(ShipLogPatches.OnShipLogManagerAwake)); + Main.Instance.ModHelper.HarmonyHelper.AddPrefix("Start", typeof(ShipLogPatches), nameof(ShipLogPatches.OnShipLogManagerStart)); + Main.Instance.ModHelper.HarmonyHelper.AddPrefix("IsFactRevealed", typeof(ShipLogPatches), nameof(ShipLogPatches.OnShipLogManagerIsFactRevealed)); + Main.Instance.ModHelper.HarmonyHelper.AddPrefix("CheckForCompletionAchievement", typeof(ShipLogPatches), nameof(ShipLogPatches.OnShipLogManagerCheckForCompletionAchievement)); + Main.Instance.ModHelper.HarmonyHelper.AddPrefix("GetCuriosityColor", typeof(ShipLogPatches), nameof(ShipLogPatches.OnUIStyleManagerGetCuriosityColor)); + Main.Instance.ModHelper.HarmonyHelper.AddPrefix("Awake", typeof(ShipLogPatches), nameof(ShipLogPatches.DisableShipLogSandFunnel)); + Main.Instance.ModHelper.HarmonyHelper.AddPrefix("UpdateState", typeof(ShipLogPatches), nameof(ShipLogPatches.DisableShipLogSandFunnel)); + Main.Instance.ModHelper.HarmonyHelper.AddPrefix("GetName", typeof(ShipLogPatches), nameof(ShipLogPatches.OnShipLogAstroObjectGetName)); + Main.Instance.ModHelper.HarmonyHelper.AddPostfix("Initialize", typeof(ShipLogPatches), nameof(ShipLogPatches.OnShipLogMapModeInitialize)); + Main.Instance.ModHelper.HarmonyHelper.AddPostfix("Awake", typeof(ShipLogPatches), nameof(ShipLogPatches.OnShipLogManagerAwakeComplete)); + Main.Instance.ModHelper.HarmonyHelper.AddPostfix("UpdateState", typeof(ShipLogPatches), nameof(ShipLogPatches.OnShipLogAstroObjectUpdateState)); + } + + public static void OnShipLogManagerAwake(ShipLogManager __instance) + { + RumorModeBuilder.Init(); + ShipLogHandler.Init(); + Logger.Log("Beginning Ship Log Generation For: " + Main.Instance.CurrentStarSystem, Logger.LogType.Log); + if (Main.Instance.CurrentStarSystem != "SolarSystem") + { + __instance._shipLogXmlAssets = new TextAsset[] { }; + foreach (ShipLogEntryLocation logEntryLocation in GameObject.FindObjectsOfType()) + { + logEntryLocation._initialized = true; + } + } + + foreach (NewHorizonsBody body in Main.BodyDict[Main.Instance.CurrentStarSystem]) + { + if (body.Config.ShipLog?.curiosities != null) + { + RumorModeBuilder.AddCuriosityColors(body.Config.ShipLog.curiosities); + } + } + + foreach (NewHorizonsBody body in Main.BodyDict[Main.Instance.CurrentStarSystem]) + { + if (body.Config.ShipLog?.xmlFile != null) + { + RumorModeBuilder.AddBodyToShipLog(__instance, body); + } + } + } + + public static void OnShipLogManagerAwakeComplete(ShipLogManager __instance) + { + RumorModeBuilder.GenerateEntryData(__instance); + for (var i = 0; i < __instance._entryList.Count; i++) + { + ShipLogEntry logEntry = __instance._entryList[i]; + RumorModeBuilder.UpdateEntryCuriosity(ref logEntry); + } + + Logger.Log("Ship Log Generation Complete For: " + Main.Instance.CurrentStarSystem, Logger.LogType.Log); + } + + public static bool OnShipLogManagerIsFactRevealed(ShipLogManager __instance, ref bool __result, string __0) + { + if (__instance._factDict != null && __instance._factDict.ContainsKey(__0)) + { + __result = __instance._factDict[__0].IsRevealed(); + } + else + { + __result = false; + } + + return false; + } + + public static bool OnShipLogManagerCheckForCompletionAchievement(ShipLogManager __instance) + { + foreach (KeyValuePair keyValuePair in __instance._factDict) + { + if (ShipLogHandler.IsVanillaAstroID(__instance.GetEntry(keyValuePair.Value.GetEntryID()).GetAstroObjectID()) && !keyValuePair.Value.IsRumor() && !keyValuePair.Value.IsRevealed() && !keyValuePair.Key.Equals("TH_VILLAGE_X3") && !keyValuePair.Key.Equals("GD_GABBRO_ISLAND_X1") && __instance.GetEntry(keyValuePair.Value.GetEntryID()).GetCuriosityName() != CuriosityName.InvisiblePlanet) + { + return false; + } + } + Achievements.Earn(Achievements.Type.STUDIOUS); + return false; + } + + public static bool OnShipLogManagerStart(ShipLogManager __instance) + { + foreach (NewHorizonsBody body in Main.BodyDict[Main.Instance.CurrentStarSystem]) + { + foreach (string fact in body.Config.ShipLog?.initialReveal ?? Array.Empty()) + { + __instance.RevealFact(fact, false, false); + } + } + + if (Main.Instance.CurrentStarSystem == "SolarSystem") + { + return true; + } + else + { + EntryLocationBuilder.InitializeLocations(); + return false; + } + } + + public static bool OnUIStyleManagerGetCuriosityColor(UIStyleManager __instance, CuriosityName __0, bool __1, ref Color __result) + { + if ((int) __0 < 7) + { + return true; + } + else + { + __result = RumorModeBuilder.GetCuriosityColor(__0, __1, __instance._neutralColor, __instance._neutralHighlight); + return false; + } + } + + public static void OnShipLogMapModeInitialize(ShipLogMapMode __instance) + { + GameObject panRoot = GameObject.Find(ShipLogHandler.PAN_ROOT_PATH); + GameObject sunObject = GameObject.Find(ShipLogHandler.PAN_ROOT_PATH + "/Sun"); + ShipLogAstroObject[][] navMatrix = MapModeBuilder.ConstructMapMode(Main.Instance.CurrentStarSystem, panRoot, __instance._astroObjects, sunObject.layer); + if (navMatrix == null || navMatrix.Length <= 1) + { + Logger.LogWarning("Skipping Map Mode Generation."); + } + else + { + __instance._astroObjects = navMatrix; + __instance._startingAstroObjectID = navMatrix[1][0].GetID(); + if (Main.Instance.CurrentStarSystem != "SolarSystem") + { + List delete = SearchUtilities.GetAllChildren(panRoot).Where(g => g.name.Contains("_ShipLog") == false).ToList(); + foreach (GameObject gameObject in delete) + { + Object.Destroy(GameObject.Find(ShipLogHandler.PAN_ROOT_PATH + "/" + gameObject.name)); + } + if (GameObject.Find(ShipLogHandler.PAN_ROOT_PATH + "/" + "SandFunnel") == null) + { + __instance._sandFunnel = __instance.gameObject.AddComponent(); + } + } + } + + Logger.Log("Map Mode Construction Complete", Logger.LogType.Log); + } + + public static bool OnShipLogAstroObjectGetName(ShipLogAstroObject __instance, ref string __result) + { + if (ShipLogHandler.IsVanillaAstroID(__instance.GetID())) + { + return true; + } + else + { + __result = MapModeBuilder.GetAstroBodyShipLogName(__instance.GetID()); + return false; + } + } + + public static void OnShipLogAstroObjectUpdateState(ShipLogAstroObject __instance) + { + Transform detailsParent = __instance.transform.Find("Details"); + if (detailsParent != null) + { + foreach (GameObject child in SearchUtilities.GetAllChildren(detailsParent.gameObject)) + { + Component detail; + if (child.TryGetComponent(typeof(ShipLogDetail), out detail)) + { + (detail as ShipLogDetail)?.UpdateState(__instance._state); + } + } + } + + Transform lineObject = __instance.transform.Find("Line_ShipLog"); + if (lineObject != null) + { + ShipLogDetail lineDetail = lineObject.gameObject.GetComponent(); + lineDetail.UpdateState(__instance._state); + } + } + + public static bool DisableShipLogSandFunnel() + { + return Main.Instance.CurrentStarSystem == "SolarSystem"; + } + + public static void OnPlayerDataGetNewlyRevealedFactIDsComplete(ref List __result) + { + ShipLogManager manager = Locator.GetShipLogManager(); + __result = __result.Where(e => manager.GetFact(e) != null).ToList(); + } + } +} \ No newline at end of file diff --git a/NewHorizons/Tools/WarpDrivePatches.cs b/NewHorizons/Tools/WarpDrivePatches.cs index e06ce4c0..4fa7e713 100644 --- a/NewHorizons/Tools/WarpDrivePatches.cs +++ b/NewHorizons/Tools/WarpDrivePatches.cs @@ -14,9 +14,7 @@ namespace NewHorizons.Tools public static void Apply() { Main.Instance.ModHelper.HarmonyHelper.AddPrefix("Update", typeof(WarpDrivePatches), nameof(WarpDrivePatches.OnShipCockpitControllerUpdate)); - Main.Instance.ModHelper.HarmonyHelper.AddPostfix("EnterMode", typeof(WarpDrivePatches), nameof(WarpDrivePatches.OnShipLogMapModeEnterMode)); - Main.Instance.ModHelper.HarmonyHelper.AddPrefix("Update", typeof(WarpDrivePatches), nameof(WarpDrivePatches.OnShipLogControllerUpdate)); } diff --git a/NewHorizons/Utility/CollectionUtilities.cs b/NewHorizons/Utility/CollectionUtilities.cs new file mode 100644 index 00000000..e4b49e6d --- /dev/null +++ b/NewHorizons/Utility/CollectionUtilities.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NewHorizons.Utility +{ + public static class CollectionUtilities + { + public static T KeyByValue(Dictionary dict, W val) + { + T key = default; + foreach (KeyValuePair pair in dict) + { + if (EqualityComparer.Default.Equals(pair.Value, val)) + { + key = pair.Key; + break; + } + } + return key; + } + } +} diff --git a/NewHorizons/Utility/DebugRaycaster.cs b/NewHorizons/Utility/DebugRaycaster.cs index 6dd56a44..fa7d4760 100644 --- a/NewHorizons/Utility/DebugRaycaster.cs +++ b/NewHorizons/Utility/DebugRaycaster.cs @@ -13,6 +13,7 @@ namespace NewHorizons.Utility public class DebugRaycaster : MonoBehaviour { private OWRigidbody _rb; + private void Awake() { _rb = this.GetRequiredComponent(); diff --git a/NewHorizons/Utility/ImageUtilities.cs b/NewHorizons/Utility/ImageUtilities.cs index 7877e218..d66f7469 100644 --- a/NewHorizons/Utility/ImageUtilities.cs +++ b/NewHorizons/Utility/ImageUtilities.cs @@ -1,10 +1,55 @@ -using System.IO; +using System; +using System.IO; using UnityEngine; namespace NewHorizons.Utility { static class ImageUtilities { + public static Texture2D MakeOutline(Texture2D texture, Color color, int thickness) + { + var outline = new Texture2D(texture.width, texture.height, TextureFormat.ARGB32, false); + var outlinePixels = new Color[texture.width * texture.height]; + var pixels = texture.GetPixels(); + + for (int x = 0; x < texture.width; x++) + { + for (int y = 0; y < texture.height; y++) + { + var fillColor = new Color(0, 0, 0, 0); + + if(pixels[x + y * texture.width].a == 1 && CloseToTransparent(pixels, texture.width, texture.height, x, y, thickness)) + { + fillColor = color; + } + outlinePixels[x + y * texture.width] = fillColor; + } + } + + outline.SetPixels(outlinePixels); + outline.Apply(); + + return outline; + } + + private static bool CloseToTransparent(Color[] pixels, int width, int height, int x, int y, int thickness) + { + // Check nearby + var minX = Math.Max(0, x - thickness/2); + var minY = Math.Max(0, y - thickness/2); + var maxX = Math.Min(width, x + thickness/2); + var maxY = Math.Min(height, y + thickness/2); + + for (int i = minX; i < maxX; i++) + { + for (int j = minY; j < maxY; j++) + { + if (pixels[i + j * width].a < 1) return true; + } + } + return false; + } + public static Texture2D TintImage(Texture2D image, Color tint) { var pixels = image.GetPixels(); @@ -74,49 +119,22 @@ namespace NewHorizons.Utility return tex; } - // Thank you PETERSVP - public static Texture2D Scaled(Texture2D src, int width, int height, FilterMode mode = FilterMode.Trilinear) + public static Color GetAverageColor(Texture2D src) { - Rect texR = new Rect(0, 0, width, height); - _gpu_scale(src, width, height, mode); + var pixels = src.GetPixels32(); + var r = 0f; + var g = 0f; + var b = 0f; + var length = pixels.Length; + for(int i = 0; i < pixels.Length; i++) + { + var color = pixels[i]; + r += (float)color.r / length; + g += (float)color.g / length; + b += (float)color.b / length; + } - //Get rendered data back to a new texture - Texture2D result = new Texture2D(width, height, TextureFormat.ARGB32, true); - result.Resize(width, height); - result.ReadPixels(texR, 0, 0, true); - return result; - } - - public static void Scale(Texture2D tex, int width, int height, FilterMode mode = FilterMode.Trilinear) - { - Rect texR = new Rect(0, 0, width, height); - _gpu_scale(tex, width, height, mode); - - // Update new texture - tex.Resize(width, height); - tex.ReadPixels(texR, 0, 0, true); - tex.Apply(true); //Remove this if you hate us applying textures for you :) - } - - // Internal unility that renders the source texture into the RTT - the scaling method itself. - static void _gpu_scale(Texture2D src, int width, int height, FilterMode fmode) - { - //We need the source texture in VRAM because we render with it - src.filterMode = fmode; - src.Apply(true); - - //Using RTT for best quality and performance. Thanks, Unity 5 - RenderTexture rtt = new RenderTexture(width, height, 32); - - //Set the RTT in order to render to it - Graphics.SetRenderTarget(rtt); - - //Setup 2D matrix in range 0..1, so nobody needs to care about sized - GL.LoadPixelMatrix(0, 1, 1, 0); - - //Then clear & draw the texture to fill the entire RTT. - GL.Clear(true, true, new Color(0, 0, 0, 0)); - Graphics.DrawTexture(new Rect(0, 0, 1, 1), src); + return new Color(r / 255, g / 255, b / 255); } } } diff --git a/NewHorizons/Utility/MVector2.cs b/NewHorizons/Utility/MVector2.cs new file mode 100644 index 00000000..4d6ccf84 --- /dev/null +++ b/NewHorizons/Utility/MVector2.cs @@ -0,0 +1,26 @@ +using UnityEngine; + +namespace NewHorizons.Utility +{ + public class MVector2 + { + public MVector2(float x, float y) + { + X = x; + Y = y; + } + + public float X { get; } + public float Y { get; } + + public static implicit operator MVector2(Vector2 vec) + { + return new MVector2(vec.x, vec.y); + } + + public static implicit operator Vector2(MVector2 vec) + { + return new Vector2(vec.X, vec.Y); + } + } +} \ No newline at end of file diff --git a/NewHorizons/Utility/SearchUtilities.cs b/NewHorizons/Utility/SearchUtilities.cs index 953fe211..bd164b7b 100644 --- a/NewHorizons/Utility/SearchUtilities.cs +++ b/NewHorizons/Utility/SearchUtilities.cs @@ -162,5 +162,15 @@ namespace NewHorizons.Utility return null; } } + + public static List GetAllChildren(GameObject parent) + { + List children = new List(); + foreach (Transform child in parent.transform) + { + children.Add(child.gameObject); + } + return children; + } } } diff --git a/NewHorizons/dialogue_schema.xsd b/NewHorizons/dialogue_schema.xsd new file mode 100644 index 00000000..f956f814 --- /dev/null +++ b/NewHorizons/dialogue_schema.xsd @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/NewHorizons/schema.json b/NewHorizons/schema.json index 2b005b04..966be976 100644 --- a/NewHorizons/schema.json +++ b/NewHorizons/schema.json @@ -669,7 +669,7 @@ "generateColliders": { "type": "bool", "default": false, - "description": "For each mesh filter found here should we make a mesh collider?" + "description": "For each mesh filter found here should we make a mesh collider?" } } } @@ -680,9 +680,6 @@ "items": { "type": "object", "properties": { - "count": { - "type": "integer" - }, "path": { "type": "string", "description": "Either the path in the scene hierarchy of the item to copy or the path to the object in the supplied asset bundle" @@ -739,6 +736,10 @@ "default": false, "description": "For each mesh filter found here should we make a mesh collider?" } + }, + "scale": { + "type": "number", + "default": 1 } } } @@ -791,6 +792,94 @@ } } } + }, + "reveal": { + "type": "array", + "description": "A set of volumes that reveal ship log fact", + "items": { + "type": "object", + "properties": { + "revealOn": { + "type": "string", + "description": "'enter', 'observe', or 'snapshot' what needs to be done to the volume to unlock the facts" + }, + "reveals": { + "type": "array", + "description": "A list of facts to reveal", + "items": { + "type": "string" + } + }, + "position": { + "type": "object", + "description": "The position to place the volume at", + "properties": { + "x": { + "type": "number", + "default": 0 + }, + "y": { + "type": "number", + "default": 0 + }, + "z": { + "type": "number", + "default": 0 + } + } + }, + "radius": { + "type": "number", + "description": "The radius of the volume", + "default": 1.0 + }, + "maxDistance": { + "type": "number", + "description": "The max distance the user can be away from the volume to reveal the fact (snapshot and observe only)" + }, + "maxAngle": { + "type": "number", + "description": "The max view angle the player can see the volume with to unlock the fact", + "default": 180.0 + } + } + } + }, + "entryLocation": { + "type": "array", + "description": "A set of locations for ship log entries", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "The ID of the entry this location is for" + }, + "cloaked": { + "type": "bool", + "description": "Whether this entry location is in a cloaking field", + "default": false + }, + "position": { + "type": "object", + "description": "The position of this entry location", + "properties": { + "x": { + "type": "number", + "default": 0 + }, + "y": { + "type": "number", + "default": 0 + }, + "z": { + "type": "number", + "default": 0 + } + } + } + } + } } } }, @@ -886,6 +975,10 @@ "type": "string", "description": "Relative filepath to the .wav file to use as the audio. Mutually exclusive with audioClip" }, + "reveals": { + "type": "string", + "description": "A ship log fact to reveal when the signal is identified" + }, "sourceRadius": { "type": "number", "default": 1, @@ -1154,6 +1247,241 @@ } } } + }, + "ShipLog": { + "type": "object", + "properties": { + "xmlFile": { + "type": "string", + "description": "The xml file to load ship log entries from" + }, + "spriteFolder": { + "type": "string", + "description": "A path to the folder where entry sprites are stored" + }, + "initialReveal": { + "type": "array", + "description": "A list of fact IDs to reveal when the game starts", + "items": { + "type": "string" + } + }, + "entryPositions": { + "type": "array", + "description": "A set of positions to use instead of automatic layout in rumor mode", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "The name of the entry to apply the position" + }, + "position": { + "type": "object", + "properties": { + "x": { + "type": "number", + "default": 0 + }, + "y": { + "type": "number", + "default": 0 + } + } + } + } + } + }, + "MapMode": { + "type": "object", + "properties": { + "revealedSprite": { + "type": "string", + "description": "The path to the sprite to show when the planet is revealed in map mode" + }, + "outlineSprite": { + "type": "string", + "description": "The path to the sprite to show when the planet is unexplored in map mode" + }, + "manualPosition": { + "type": "object", + "description": "Manually place this planet at the specified position", + "properties": { + "x": { + "type": "number", + "default": 0 + }, + "y": { + "type": "number", + "default": 0 + } + } + }, + "manualNavigationPosition": { + "type": "object", + "description": "Specify where this planet is in terms of navigation", + "properties": { + "x": { + "type": "integer", + "default": 0 + }, + "y": { + "type": "integer", + "default": 0 + } + } + }, + "scale": { + "type": "number", + "description": "Scale to apply to the planet in map mode", + "default": 1 + }, + "invisibleWhenHidden": { + "type": "bool", + "description": "Hide the planet completely if unexplored instead of showing an outline", + "default": false + }, + "offset": { + "type": "number", + "description": "Extra distance to apply to this object in map mode", + "default": 0 + }, + "remove": { + "type": "boolean", + "description": "Completely remove this planet (and it's children) from map mode", + "default": false + }, + "details": { + "type": "array", + "description": "Place non-selectable object in map mode (like sand funnels)", + "items": { + "type": "object", + "properties": { + "revealedSprite": { + "type": "string", + "description": "The sprite to show when the parent AstroBody is revealed" + }, + "outlineSprite": { + "type": "string", + "description": "The sprite to show when the parent AstroBody is rumored/unexplored" + }, + "rotation": { + "type": "number", + "description": "The angle in degrees to rotate the detail", + "default": 0 + }, + "invisibleWhenHidden": { + "type": "boolean", + "description": "Whether to completely hide this detail when the parent AstroBody is unexplored", + "default": false + }, + "position": { + "type": "object", + "description": "The position (relative to the parent) to place the detail", + "properties": { + "x": { + "type": "number", + "default": 0 + }, + "y": { + "type": "number", + "default": 0 + } + } + }, + "scale": { + "type": "object", + "description": "The amount to scale the x and y axis of the detail by", + "properties": { + "x": { + "type": "number", + "default": 0 + }, + "y": { + "type": "number", + "default": 0 + } + } + } + } + } + } + } + }, + "Curiosities": { + "type": "array", + "description": "A set of colors to apply to curiosities", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "The ID of the curiosity to apply the color to" + }, + "color": { + "type": "object", + "description": "The color to apply to entries with this curiosity", + "properties": { + "R": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 255 + }, + "G": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 255 + }, + "B": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 255 + }, + "A": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 255 + } + } + }, + "highlightColor": { + "type": "object", + "description": "The color to apply to highlighted entries with this curiosity", + "properties": { + "R": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 255 + }, + "G": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 255 + }, + "B": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 255 + }, + "A": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 255 + } + } + } + } + } + } + } } } } \ No newline at end of file diff --git a/NewHorizons/shiplog_schema.xsd b/NewHorizons/shiplog_schema.xsd new file mode 100644 index 00000000..c6b77898 --- /dev/null +++ b/NewHorizons/shiplog_schema.xsd @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index 824e4178..3ebc2592 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ Check the ship's log for how to use your warp drive to travel between star syste - [Water](#water) - [Lava](#lava) - [Sand](#sand) + - [Ship Log](#ship-log) - [How to destroy existing planets](#how-to-destroy-existing-planets) - [How to update existing planets](#how-to-update-existing-planets) - [How to use New Horizons in other mods](#how-to-use-new-horizons-in-other-mods) @@ -576,6 +577,9 @@ This allows you to make black holes and white holes, and to pair them. - "tint" : (colour) - "curve": (scale curve) +### Ship Log +You can make custom ship logs for your planets. There's a guide [here](https://gist.github.com/Bwc9876/1817f8726e7f1900e57e3b05dd047d86#intro). + ### How to destroy existing planets You do this (but with the appropriate name) as it's own config. @@ -640,9 +644,10 @@ Authors: - [Mister_Nebula](https://github.com/misternebula) ([Marshmallow](https://github.com/misternebula/Marshmallow) v0.1 to v1.1.0) New Horizons was made with help from: -- [jtsalomo](https://github.com/jtsalomo) (Implemented [OW_CommonResources](https://github.com/PacificEngine/OW_CommonResources) support introduced in v0.5.0) -- [Raicuparta](https://github.com/Raicuparta) (Integrated the [New Horizons Template](https://github.com/xen-42/ow-new-horizons-config-template) into the Outer Wilds Mods website) -- [Nageld](https://github.com/Nageld) (Set up xml reading for custom dialogue in v0.8.0) +- [jtsalomo](https://github.com/jtsalomo): Implemented [OW_CommonResources](https://github.com/PacificEngine/OW_CommonResources) support introduced in v0.5.0 +- [Raicuparta](https://github.com/Raicuparta): Integrated the [New Horizons Template](https://github.com/xen-42/ow-new-horizons-config-template) into the Outer Wilds Mods website +- [Nageld](https://github.com/Nageld): Set up xml reading for custom dialogue in v0.8.0 +- [Bwc9876](https://github.com/Bwc9876): Set up ship log entires for planets in v0.9.0 Marshmallow was made with help from: - TAImatem