Item and Item Socket Props (#750)

<!-- A new module or something else important -->
## Major features
- Custom items and item sockets, as an extension of the existing
details. Interactivity can be added without code by setting dialogue
conditions with an item's `pickupCondition` or socket's
`insertCondition`, and then turning other details on or off using their
`activationCondition`.

<!-- A new parameter added to a module, or API feature -->
## Minor features
- Added API methods for looking up translated strings for localization.
This commit is contained in:
xen-42 2023-12-17 00:42:28 -05:00 committed by GitHub
commit daf5709b51
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 784 additions and 48 deletions

View File

@ -6,6 +6,7 @@ using NewHorizons.External.Modules;
using NewHorizons.External.Modules.Props;
using NewHorizons.Utility;
using NewHorizons.Utility.OWML;
using OWML.Common;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
@ -80,7 +81,7 @@ namespace NewHorizons.Builder.Body
if (_wallCollision == null) _wallCollision = Main.NHPrivateAssetBundle.LoadAsset<GameObject>("BrambleCollision");
}
public static GameObject Make(NewHorizonsBody body, GameObject go, NHAstroObject ao, Sector sector, OWRigidbody owRigidBody)
public static GameObject Make(NewHorizonsBody body, GameObject go, NHAstroObject ao, Sector sector, IModBehaviour mod, OWRigidbody owRigidBody)
{
InitPrefabs();
@ -102,7 +103,7 @@ namespace NewHorizons.Builder.Body
default: geometryPrefab = _hubGeometry; break;
}
var geometry = DetailBuilder.Make(go, sector, geometryPrefab, new DetailInfo());
var geometry = DetailBuilder.Make(go, sector, mod, geometryPrefab, new DetailInfo());
var exitWarps = _exitWarps.InstantiateInactive();
var repelVolume = _repelVolume.InstantiateInactive();

View File

@ -52,24 +52,16 @@ namespace NewHorizons.Builder.Props
/// <summary>
/// Create a detail using an asset bundle or a path in the scene hierarchy of the item to copy.
/// </summary>
public static GameObject Make(GameObject go, Sector sector, IModBehaviour mod, DetailInfo detail)
public static GameObject Make(GameObject planetGO, Sector sector, IModBehaviour mod, DetailInfo info)
{
if (detail.assetBundle != null)
if (info.assetBundle != null)
{
// Shouldn't happen
if (mod == null) return null;
return Make(go, sector, AssetBundleUtilities.LoadPrefab(detail.assetBundle, detail.path, mod), detail);
}
else
return Make(go, sector, detail);
return Make(planetGO, sector, mod, AssetBundleUtilities.LoadPrefab(info.assetBundle, info.path, mod), info);
}
/// <summary>
/// Create a detail using a path in the scene hierarchy of the item to copy.
/// </summary>
public static GameObject Make(GameObject planetGO, Sector sector, DetailInfo info)
{
if (_emptyPrefab == null) _emptyPrefab = new GameObject("Empty");
// Allow for empty game objects so you can set up conditional activation on them and parent other props to them
@ -82,14 +74,14 @@ namespace NewHorizons.Builder.Props
}
else
{
return Make(planetGO, sector, prefab, info);
return Make(planetGO, sector, mod, prefab, info);
}
}
/// <summary>
/// Create a detail using a prefab.
/// </summary>
public static GameObject Make(GameObject go, Sector sector, GameObject prefab, DetailInfo detail)
public static GameObject Make(GameObject go, Sector sector, IModBehaviour mod, GameObject prefab, DetailInfo detail)
{
if (prefab == null) return null;
@ -163,6 +155,16 @@ namespace NewHorizons.Builder.Props
}
}
if (detail.item != null)
{
ItemBuilder.MakeItem(prop, go, sector, detail.item, mod);
}
if (detail.itemSocket != null)
{
ItemBuilder.MakeSocket(prop, go, sector, detail.itemSocket);
}
// Items should always be kept loaded else they will vanish in your hand as you leave the sector
if (isItem) detail.keepLoaded = true;

View File

@ -66,7 +66,7 @@ namespace NewHorizons.Builder.Props
if (_interfacePrefab == null || planetGO == null || sector == null || _detailedPlatformPrefab == null || _platformPrefab == null) return null;
var detailInfo = new DetailInfo(info.controls) { keepLoaded = true };
var gravityCannonObject = DetailBuilder.Make(planetGO, sector, _interfacePrefab, detailInfo);
var gravityCannonObject = DetailBuilder.Make(planetGO, sector, mod, _interfacePrefab, detailInfo);
gravityCannonObject.SetActive(false);
var gravityCannonController = gravityCannonObject.GetComponent<GravityCannonController>();
@ -77,7 +77,7 @@ namespace NewHorizons.Builder.Props
gravityCannonController._retrieveShipLogFact = info.retrieveReveal ?? string.Empty;
gravityCannonController._launchShipLogFact = info.launchReveal ?? string.Empty;
CreatePlatform(planetGO, sector, gravityCannonController, info);
CreatePlatform(planetGO, sector, mod, gravityCannonController, info);
if (info.computer != null)
{
@ -120,9 +120,9 @@ namespace NewHorizons.Builder.Props
return computer;
}
private static GameObject CreatePlatform(GameObject planetGO, Sector sector, GravityCannonController gravityCannonController, GravityCannonInfo platformInfo)
private static GameObject CreatePlatform(GameObject planetGO, Sector sector, IModBehaviour mod, GravityCannonController gravityCannonController, GravityCannonInfo platformInfo)
{
var platform = DetailBuilder.Make(planetGO, sector, platformInfo.detailed ? _detailedPlatformPrefab : _platformPrefab, new DetailInfo(platformInfo) { keepLoaded = true });
var platform = DetailBuilder.Make(planetGO, sector, mod, platformInfo.detailed ? _detailedPlatformPrefab : _platformPrefab, new DetailInfo(platformInfo) { keepLoaded = true });
gravityCannonController._forceVolume = platform.FindChild("ForceVolume").GetComponent<DirectionalForceVolume>();
gravityCannonController._platformTrigger = platform.FindChild("PlatformTrigger").GetComponent<OWTriggerVolume>();

View File

@ -0,0 +1,188 @@
using NewHorizons.Components.Props;
using NewHorizons.External.Modules.Props.Item;
using NewHorizons.Handlers;
using NewHorizons.Utility.OWML;
using OWML.Common;
using OWML.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace NewHorizons.Builder.Props
{
public static class ItemBuilder
{
private static Dictionary<string, ItemType> _itemTypes;
internal static void Init()
{
if (_itemTypes != null)
{
foreach (var value in _itemTypes.Values)
{
EnumUtils.Remove<ItemType>(value);
}
}
_itemTypes = new Dictionary<string, ItemType>();
}
public static NHItem MakeItem(GameObject go, GameObject planetGO, Sector sector, ItemInfo info, IModBehaviour mod)
{
var itemName = info.name;
if (string.IsNullOrEmpty(itemName))
{
itemName = go.name;
}
var itemTypeName = info.itemType;
if (string.IsNullOrEmpty(itemTypeName))
{
itemTypeName = itemName;
}
var itemType = GetOrCreateItemType(itemTypeName);
var item = go.GetAddComponent<NHItem>();
item._sector = sector;
item._interactable = info.interactRange > 0f;
item._interactRange = info.interactRange;
item._localDropOffset = info.dropOffset ?? Vector3.zero;
item._localDropNormal = info.dropNormal ?? Vector3.up;
item.DisplayName = itemName;
item.ItemType = itemType;
item.Droppable = info.droppable;
if (!string.IsNullOrEmpty(info.pickupAudio))
{
item.PickupAudio = AudioTypeHandler.GetAudioType(info.pickupAudio, mod);
}
if (!string.IsNullOrEmpty(info.dropAudio))
{
item.DropAudio = AudioTypeHandler.GetAudioType(info.dropAudio, mod);
}
if (!string.IsNullOrEmpty(info.socketAudio))
{
item.SocketAudio = AudioTypeHandler.GetAudioType(info.socketAudio, mod);
}
else
{
item.SocketAudio = item.DropAudio;
}
if (!string.IsNullOrEmpty(info.unsocketAudio))
{
item.UnsocketAudio = AudioTypeHandler.GetAudioType(info.unsocketAudio, mod);
}
else
{
item.UnsocketAudio = item.PickupAudio;
}
item.PickupCondition = info.pickupCondition;
item.ClearPickupConditionOnDrop = info.clearPickupConditionOnDrop;
item.PickupFact = info.pickupFact;
Delay.FireOnNextUpdate(() =>
{
if (item != null && !string.IsNullOrEmpty(info.pathToInitialSocket))
{
var socketGO = planetGO.transform.Find(info.pathToInitialSocket);
if (socketGO != null)
{
var socket = socketGO.GetComponent<OWItemSocket>();
if (socket != null)
{
if (socket.PlaceIntoSocket(item))
{
// Successfully socketed
}
else
{
NHLogger.LogError($"Could not insert item {itemName} into socket at path {socketGO}");
}
}
else
{
NHLogger.LogError($"Could not find a socket to parent item {itemName} to at path {socketGO}");
}
}
else
{
NHLogger.LogError($"Could not find a socket to parent item {itemName} to at path {socketGO}");
}
}
});
return item;
}
public static NHItemSocket MakeSocket(GameObject go, GameObject planetGO, Sector sector, ItemSocketInfo info)
{
var itemType = EnumUtils.TryParse(info.itemType, true, out ItemType result) ? result : ItemType.Invalid;
if (itemType == ItemType.Invalid && !string.IsNullOrEmpty(info.itemType))
{
itemType = EnumUtilities.Create<ItemType>(info.itemType);
}
var socket = go.GetAddComponent<NHItemSocket>();
socket._sector = sector;
socket._interactable = info.interactRange > 0f;
socket._interactRange = info.interactRange;
if (!string.IsNullOrEmpty(info.socketPath))
{
socket._socketTransform = go.transform.Find(info.socketPath);
}
if (socket._socketTransform == null)
{
var socketGO = GeneralPropBuilder.MakeNew("Socket", planetGO, sector, info, defaultParent: go.transform);
socketGO.SetActive(true);
socket._socketTransform = socketGO.transform;
}
socket.ItemType = itemType;
socket.UseGiveTakePrompts = info.useGiveTakePrompts;
socket.InsertCondition = info.insertCondition;
socket.ClearInsertConditionOnRemoval = info.clearInsertConditionOnRemoval;
socket.InsertFact = info.insertFact;
socket.RemovalCondition = info.removalCondition;
socket.ClearRemovalConditionOnInsert = info.clearRemovalConditionOnInsert;
socket.RemovalFact = info.removalFact;
Delay.FireInNUpdates(() =>
{
if (socket != null && !socket._socketedItem)
{
socket.TriggerRemovalConditions();
}
}, 2);
return socket;
}
public static ItemType GetOrCreateItemType(string name)
{
var itemType = ItemType.Invalid;
if (_itemTypes.ContainsKey(name))
{
itemType = _itemTypes[name];
}
else if (EnumUtils.TryParse(name, true, out ItemType result))
{
itemType = result;
}
else if (!string.IsNullOrEmpty(name))
{
itemType = EnumUtils.Create<ItemType>(name);
_itemTypes.Add(name, itemType);
}
return itemType;
}
public static bool IsCustomItemType(ItemType type)
{
return _itemTypes.ContainsValue(type);
}
}
}

View File

@ -376,7 +376,7 @@ namespace NewHorizons.Builder.Props
}
case NomaiTextType.PreCrashComputer:
{
var computerObject = DetailBuilder.Make(planetGO, sector, _preCrashComputerPrefab, new DetailInfo(info));
var computerObject = DetailBuilder.Make(planetGO, sector, mod, _preCrashComputerPrefab, new DetailInfo(info));
computerObject.SetActive(false);
var up = computerObject.transform.position - planetGO.transform.position;
@ -493,7 +493,7 @@ namespace NewHorizons.Builder.Props
case NomaiTextType.Recorder:
{
var prefab = (info.type == NomaiTextType.PreCrashRecorder ? _preCrashRecorderPrefab : _recorderPrefab);
var recorderObject = DetailBuilder.Make(planetGO, sector, prefab, new DetailInfo(info));
var recorderObject = DetailBuilder.Make(planetGO, sector, mod, prefab, new DetailInfo(info));
recorderObject.SetActive(false);
if (info.rotation == null)

View File

@ -203,7 +203,7 @@ namespace NewHorizons.Builder.Props
if (_visionTorchDetectorPrefab == null) return null;
// spawn a trigger for the vision torch
var g = DetailBuilder.Make(planetGO, sector, _visionTorchDetectorPrefab, new DetailInfo(info) { scale = 2, rename = !string.IsNullOrEmpty(info.rename) ? info.rename : "VisionStaffDetector" });
var g = DetailBuilder.Make(planetGO, sector, mod, _visionTorchDetectorPrefab, new DetailInfo(info) { scale = 2, rename = !string.IsNullOrEmpty(info.rename) ? info.rename : "VisionStaffDetector" });
if (g == null)
{
@ -240,7 +240,7 @@ namespace NewHorizons.Builder.Props
if (_standingVisionTorchPrefab == null) return null;
// Spawn the torch itself
var standingTorch = DetailBuilder.Make(planetGO, sector, _standingVisionTorchPrefab, new DetailInfo(info));
var standingTorch = DetailBuilder.Make(planetGO, sector, mod, _standingVisionTorchPrefab, new DetailInfo(info));
if (standingTorch == null)
{

View File

@ -39,7 +39,7 @@ namespace NewHorizons.Builder.Props
{
try
{
ShuttleBuilder.Make(go, sector, shuttleInfo);
ShuttleBuilder.Make(go, sector, nhBody.Mod, shuttleInfo);
}
catch (Exception ex)
{
@ -283,7 +283,7 @@ namespace NewHorizons.Builder.Props
{
try
{
WarpPadBuilder.Make(go, sector, warpReceiver);
WarpPadBuilder.Make(go, sector, nhBody.Mod, warpReceiver);
}
catch (Exception ex)
{
@ -297,7 +297,7 @@ namespace NewHorizons.Builder.Props
{
try
{
WarpPadBuilder.Make(go, sector, warpTransmitter);
WarpPadBuilder.Make(go, sector, nhBody.Mod, warpTransmitter);
}
catch (Exception ex)
{

View File

@ -149,7 +149,7 @@ namespace NewHorizons.Builder.Props
{
try
{
MakeWhiteboard(go, sector, id, decal, info.whiteboard, nhBody);
MakeWhiteboard(go, sector, nhBody.Mod, id, decal, info.whiteboard, nhBody);
}
catch (Exception ex)
{
@ -173,9 +173,9 @@ namespace NewHorizons.Builder.Props
}
}
public static void MakeWhiteboard(GameObject go, Sector sector, NomaiRemoteCameraPlatform.ID id, Texture2D decal, RemoteWhiteboardInfo info, NewHorizonsBody nhBody)
public static void MakeWhiteboard(GameObject go, Sector sector, IModBehaviour mod, NomaiRemoteCameraPlatform.ID id, Texture2D decal, RemoteWhiteboardInfo info, NewHorizonsBody nhBody)
{
var whiteboard = DetailBuilder.Make(go, sector, _whiteboardPrefab, new DetailInfo(info));
var whiteboard = DetailBuilder.Make(go, sector, mod, _whiteboardPrefab, new DetailInfo(info));
whiteboard.SetActive(false);
var decalMat = new Material(_decalMaterial);
@ -215,7 +215,7 @@ namespace NewHorizons.Builder.Props
public static void MakePlatform(GameObject go, Sector sector, NomaiRemoteCameraPlatform.ID id, Texture2D decal, PlatformInfo info, IModBehaviour mod)
{
var platform = DetailBuilder.Make(go, sector, _remoteCameraPlatformPrefab, new DetailInfo(info));
var platform = DetailBuilder.Make(go, sector, mod, _remoteCameraPlatformPrefab, new DetailInfo(info));
platform.SetActive(false);
var decalMat = new Material(_decalMaterial);

View File

@ -77,7 +77,7 @@ namespace NewHorizons.Builder.Props
stretch = propInfo.stretch,
keepLoaded = propInfo.keepLoaded
};
var scatterPrefab = DetailBuilder.Make(go, sector, prefab, detailInfo);
var scatterPrefab = DetailBuilder.Make(go, sector, mod, prefab, detailInfo);
for (int i = 0; i < propInfo.count; i++)
{

View File

@ -3,6 +3,7 @@ using NewHorizons.External.Modules.Props;
using NewHorizons.External.Modules.Props.Shuttle;
using NewHorizons.Handlers;
using NewHorizons.Utility;
using OWML.Common;
using System.Collections.Generic;
using UnityEngine;
@ -68,14 +69,14 @@ namespace NewHorizons.Builder.Props
}
}
public static GameObject Make(GameObject planetGO, Sector sector, ShuttleInfo info)
public static GameObject Make(GameObject planetGO, Sector sector, IModBehaviour mod, ShuttleInfo info)
{
InitPrefab();
if (_prefab == null || planetGO == null || sector == null) return null;
var detailInfo = new DetailInfo(info) { keepLoaded = true };
var shuttleObject = DetailBuilder.Make(planetGO, sector, _prefab, detailInfo);
var shuttleObject = DetailBuilder.Make(planetGO, sector, mod, _prefab, detailInfo);
shuttleObject.SetActive(false);
StreamingHandler.SetUpStreaming(shuttleObject, sector);

View File

@ -242,7 +242,7 @@ namespace NewHorizons.Builder.Props.TranslatorText
}
case NomaiTextType.PreCrashComputer:
{
var computerObject = DetailBuilder.Make(planetGO, sector, PreCrashComputerPrefab, new DetailInfo(info));
var computerObject = DetailBuilder.Make(planetGO, sector, nhBody.Mod, PreCrashComputerPrefab, new DetailInfo(info));
computerObject.SetActive(false);
var computer = computerObject.GetComponent<NomaiVesselComputer>();
@ -323,7 +323,7 @@ namespace NewHorizons.Builder.Props.TranslatorText
case NomaiTextType.Recorder:
{
var prefab = (info.type == NomaiTextType.PreCrashRecorder ? _preCrashRecorderPrefab : _recorderPrefab);
var recorderObject = DetailBuilder.Make(planetGO, sector, prefab, new DetailInfo(info));
var recorderObject = DetailBuilder.Make(planetGO, sector, nhBody.Mod, prefab, new DetailInfo(info));
recorderObject.SetActive(false);
var nomaiText = recorderObject.GetComponentInChildren<NomaiText>();
@ -373,7 +373,7 @@ namespace NewHorizons.Builder.Props.TranslatorText
path = "BrittleHollow_Body/Sector_BH/Sector_NorthHemisphere/Sector_NorthPole/Sector_HangingCity/Sector_HangingCity_District2/Interactables_HangingCity_District2/VisibleFrom_HangingCity/Props_NOM_Whiteboard (1)",
rename = info.rename ?? "Props_NOM_Whiteboard",
};
var whiteboardObject = DetailBuilder.Make(planetGO, sector, whiteboardInfo);
var whiteboardObject = DetailBuilder.Make(planetGO, sector, nhBody.Mod, whiteboardInfo);
// Spawn a scroll and insert it into the whiteboard, but only if text is provided
if (!string.IsNullOrEmpty(info.xmlFile))

View File

@ -6,6 +6,7 @@ using NewHorizons.External.Modules.WarpPad;
using NewHorizons.Utility;
using NewHorizons.Utility.OuterWilds;
using NewHorizons.Utility.OWML;
using OWML.Common;
using OWML.Utils;
using UnityEngine;
@ -86,10 +87,10 @@ namespace NewHorizons.Builder.Props
}
}
public static void Make(GameObject planetGO, Sector sector, NomaiWarpReceiverInfo info)
public static void Make(GameObject planetGO, Sector sector, IModBehaviour mod, NomaiWarpReceiverInfo info)
{
var detailInfo = new DetailInfo(info);
var receiverObject = DetailBuilder.Make(planetGO, sector, info.detailed ? _detailedReceiverPrefab : _receiverPrefab, detailInfo);
var receiverObject = DetailBuilder.Make(planetGO, sector, mod, info.detailed ? _detailedReceiverPrefab : _receiverPrefab, detailInfo);
NHLogger.Log($"Position is {detailInfo.position} was {info.position}");
@ -122,13 +123,13 @@ namespace NewHorizons.Builder.Props
if (info.computer != null)
{
CreateComputer(planetGO, sector, info.computer, receiver);
CreateComputer(planetGO, sector, mod, info.computer, receiver);
}
}
public static void Make(GameObject planetGO, Sector sector, NomaiWarpTransmitterInfo info)
public static void Make(GameObject planetGO, Sector sector, IModBehaviour mod, NomaiWarpTransmitterInfo info)
{
var transmitterObject = DetailBuilder.Make(planetGO, sector, _transmitterPrefab, new DetailInfo(info));
var transmitterObject = DetailBuilder.Make(planetGO, sector, mod, _transmitterPrefab, new DetailInfo(info));
var transmitter = transmitterObject.GetComponentInChildren<NomaiWarpTransmitter>();
transmitter._frequency = GetFrequency(info.frequency);
@ -145,9 +146,9 @@ namespace NewHorizons.Builder.Props
transmitterObject.SetActive(true);
}
private static void CreateComputer(GameObject planetGO, Sector sector, GeneralPropInfo computerInfo, NomaiWarpReceiver receiver)
private static void CreateComputer(GameObject planetGO, Sector sector, IModBehaviour mod, GeneralPropInfo computerInfo, NomaiWarpReceiver receiver)
{
var computerObject = DetailBuilder.Make(planetGO, sector, TranslatorTextBuilder.ComputerPrefab, new DetailInfo(computerInfo));
var computerObject = DetailBuilder.Make(planetGO, sector, mod, TranslatorTextBuilder.ComputerPrefab, new DetailInfo(computerInfo));
var computer = computerObject.GetComponentInChildren<NomaiComputer>();
computer.SetSector(sector);

View File

@ -0,0 +1,103 @@
using NewHorizons.Builder.Props;
using NewHorizons.Handlers;
using OWML.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace NewHorizons.Components.Props
{
public class NHItem : OWItem
{
public string DisplayName;
public bool Droppable;
public AudioType PickupAudio;
public AudioType DropAudio;
public AudioType SocketAudio;
public AudioType UnsocketAudio;
public string PickupCondition;
public bool ClearPickupConditionOnDrop;
public string PickupFact;
public ItemType ItemType
{
get => _type;
set => _type = value;
}
public override string GetDisplayName()
{
return TranslationHandler.GetTranslation(DisplayName, TranslationHandler.TextType.UI);
}
public override bool CheckIsDroppable()
{
return Droppable;
}
public override void PickUpItem(Transform holdTranform)
{
base.PickUpItem(holdTranform);
TriggerPickupConditions();
PlayCustomSound(PickupAudio);
}
public override void DropItem(Vector3 position, Vector3 normal, Transform parent, Sector sector, IItemDropTarget customDropTarget)
{
base.DropItem(position, normal, parent, sector, customDropTarget);
TriggerDropConditions();
PlayCustomSound(DropAudio);
}
public override void SocketItem(Transform socketTransform, Sector sector)
{
base.SocketItem(socketTransform, sector);
TriggerDropConditions();
PlayCustomSound(SocketAudio);
}
public override void OnCompleteUnsocket()
{
base.OnCompleteUnsocket();
TriggerPickupConditions();
PlayCustomSound(UnsocketAudio);
}
internal void TriggerPickupConditions()
{
if (!string.IsNullOrEmpty(PickupCondition))
{
DialogueConditionManager.SharedInstance.SetConditionState(PickupCondition, true);
}
if (!string.IsNullOrEmpty(PickupFact))
{
Locator.GetShipLogManager().RevealFact(PickupFact);
}
}
internal void TriggerDropConditions()
{
if (ClearPickupConditionOnDrop && !string.IsNullOrEmpty(PickupCondition))
{
DialogueConditionManager.SharedInstance.SetConditionState(PickupCondition, false);
}
}
void PlayCustomSound(AudioType audioType)
{
if (ItemBuilder.IsCustomItemType(ItemType))
{
Locator.GetPlayerAudioController()._oneShotExternalSource.PlayOneShot(audioType);
}
else
{
// Vanilla items play sounds via hard-coded ItemType switch statements
// in the PlayerAudioController code, so there's no clean way to override them
}
}
}
}

View File

@ -0,0 +1,92 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace NewHorizons.Components.Props
{
public class NHItemSocket : OWItemSocket
{
public bool UseGiveTakePrompts;
public string InsertCondition;
public bool ClearInsertConditionOnRemoval;
public string InsertFact;
public string RemovalCondition;
public bool ClearRemovalConditionOnInsert;
public string RemovalFact;
public ItemType ItemType
{
get => _acceptableType;
set => _acceptableType = value;
}
public override bool UsesGiveTakePrompts()
{
return UseGiveTakePrompts;
}
public override bool AcceptsItem(OWItem item)
{
if (item == null || item._type == ItemType.Invalid)
{
return false;
}
return ItemType == item._type;
}
public override bool PlaceIntoSocket(OWItem item)
{
if (base.PlaceIntoSocket(item))
{
TriggerInsertConditions();
return true;
}
return false;
}
public override OWItem RemoveFromSocket()
{
var removedItem = base.RemoveFromSocket();
if (removedItem != null)
{
TriggerRemovalConditions();
}
return removedItem;
}
internal void TriggerInsertConditions()
{
if (!string.IsNullOrEmpty(InsertCondition))
{
DialogueConditionManager.SharedInstance.SetConditionState(InsertCondition, true);
}
if (ClearRemovalConditionOnInsert && !string.IsNullOrEmpty(RemovalCondition))
{
DialogueConditionManager.SharedInstance.SetConditionState(RemovalCondition, false);
}
if (!string.IsNullOrEmpty(InsertFact))
{
Locator.GetShipLogManager().RevealFact(InsertFact);
}
}
internal void TriggerRemovalConditions()
{
if (!string.IsNullOrEmpty(RemovalCondition))
{
DialogueConditionManager.SharedInstance.SetConditionState(RemovalCondition, true);
}
if (ClearInsertConditionOnRemoval && !string.IsNullOrEmpty(InsertCondition))
{
DialogueConditionManager.SharedInstance.SetConditionState(InsertCondition, false);
}
if (!string.IsNullOrEmpty(RemovalFact))
{
Locator.GetShipLogManager().RevealFact(RemovalFact);
}
}
}
}

View File

@ -1,3 +1,4 @@
using NewHorizons.External.Modules.Props.Item;
using NewHorizons.External.SerializableData;
using Newtonsoft.Json;
using System;
@ -101,6 +102,16 @@ namespace NewHorizons.External.Modules.Props
/// </summary>
[DefaultValue(true)] public bool blinkWhenActiveChanged = true;
/// <summary>
/// Should this detail be treated as an interactible item
/// </summary>
public ItemInfo item;
/// <summary>
/// Should this detail be treated as a socket for an interactible item
/// </summary>
public ItemSocketInfo itemSocket;
[Obsolete("alignToNormal is deprecated. Use alignRadial instead")] public bool alignToNormal;
}

View File

@ -0,0 +1,78 @@
using NewHorizons.External.SerializableData;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Threading.Tasks;
namespace NewHorizons.External.Modules.Props.Item
{
[JsonObject]
public class ItemInfo
{
/// <summary>
/// The name of the item to be displayed in the UI. Defaults to the name of the detail object.
/// </summary>
public string name;
/// <summary>
/// The type of the item, which determines its orientation when held and what sockets it fits into. This can be a custom string, or a vanilla ItemType (Scroll, WarpCode, SharedStone, ConversationStone, Lantern, SlideReel, DreamLantern, or VisionTorch). Defaults to the item name.
/// </summary>
public string itemType;
/// <summary>
/// The furthest distance where the player can interact with this item. Defaults to two meters, same as most vanilla items. Set this to zero to disable all interaction by default.
/// </summary>
[DefaultValue(2f)] public float interactRange = 2f;
/// <summary>
/// Whether the item can be dropped. Defaults to true.
/// </summary>
[DefaultValue(true)] public bool droppable = true;
/// <summary>
/// A relative offset to apply to the item's position when dropping it on the ground.
/// </summary>
public MVector3 dropOffset;
/// <summary>
/// The direction the item will be oriented when dropping it on the ground. Defaults to up (0, 1, 0).
/// </summary>
public MVector3 dropNormal;
/// <summary>
/// The audio to play when this item is picked up. Only applies to custom/non-vanilla item types.
/// Can be a path to a .wav/.ogg/.mp3 file, or taken from the AudioClip list.
/// </summary>
public string pickupAudio;
/// <summary>
/// The audio to play when this item is dropped. Only applies to custom/non-vanilla item types.
/// Can be a path to a .wav/.ogg/.mp3 file, or taken from the AudioClip list.
/// </summary>
public string dropAudio;
/// <summary>
/// The audio to play when this item is inserted into a socket. Only applies to custom/non-vanilla item types.
/// Can be a path to a .wav/.ogg/.mp3 file, or taken from the AudioClip list.
/// </summary>
public string socketAudio;
/// <summary>
/// The audio to play when this item is removed from a socket. Only applies to custom/non-vanilla item types.
/// Can be a path to a .wav/.ogg/.mp3 file, or taken from the AudioClip list.
/// </summary>
public string unsocketAudio;
/// <summary>
/// A dialogue condition to set when picking up this item.
/// </summary>
public string pickupCondition;
/// <summary>
/// Whether the pickup condition should be cleared when dropping the item. Defaults to true.
/// </summary>
[DefaultValue(true)] public bool clearPickupConditionOnDrop = true;
/// <summary>
/// A ship log fact to reveal when picking up this item.
/// </summary>
public string pickupFact;
/// <summary>
/// A relative path from the planet to a socket that this item will be automatically inserted into.
/// </summary>
public string pathToInitialSocket;
}
}

View File

@ -0,0 +1,58 @@
using NewHorizons.External.SerializableData;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Threading.Tasks;
namespace NewHorizons.External.Modules.Props.Item
{
[JsonObject]
public class ItemSocketInfo : GeneralPropInfo
{
/// <summary>
/// The relative path to a child game object of this detail that will act as the socket point for the item. Will be used instead of this socket's positioning info if set.
/// </summary>
public string socketPath;
/// <summary>
/// The type of item allowed in this socket. This can be a custom string, or a vanilla ItemType (Scroll, WarpCode, SharedStone, ConversationStone, Lantern, SlideReel, DreamLantern, or VisionTorch).
/// </summary>
public string itemType;
/// <summary>
/// The furthest distance where the player can interact with this item socket. Defaults to two meters, same as most vanilla item sockets. Set this to zero to disable all interaction by default.
/// </summary>
[DefaultValue(2f)] public float interactRange = 2f;
/// <summary>
/// Whether to use "Give Item" / "Take Item" prompts instead of "Insert Item" / "Remove Item".
/// </summary>
public bool useGiveTakePrompts;
/// <summary>
/// A dialogue condition to set when inserting an item into this socket.
/// </summary>
public string insertCondition;
/// <summary>
/// Whether the insert condition should be cleared when removing the socketed item. Defaults to true.
/// </summary>
[DefaultValue(true)] public bool clearInsertConditionOnRemoval = true;
/// <summary>
/// A ship log fact to reveal when inserting an item into this socket.
/// </summary>
public string insertFact;
/// <summary>
/// A dialogue condition to set when removing an item from this socket, or when the socket is empty.
/// </summary>
public string removalCondition;
/// <summary>
/// Whether the removal condition should be cleared when inserting a socketed item. Defaults to true.
/// </summary>
[DefaultValue(true)] public bool clearRemovalConditionOnInsert = true;
/// <summary>
/// A ship log fact to reveal when removing an item from this socket, or when the socket is empty.
/// </summary>
public string removalFact;
}
}

View File

@ -377,7 +377,7 @@ namespace NewHorizons.Handlers
ao._rootSector = sector;
ao._type = AstroObject.Type.None;
BrambleDimensionBuilder.Make(body, go, ao, sector, owRigidBody);
BrambleDimensionBuilder.Make(body, go, ao, sector, body.Mod, owRigidBody);
go = SharedGenerateBody(body, go, sector, owRigidBody);

View File

@ -16,6 +16,9 @@ namespace NewHorizons
[Obsolete("Create(Dictionary<string, object> config) is deprecated, please use LoadConfigs(IModBehaviour mod) instead")]
void Create(Dictionary<string, object> config, IModBehaviour mod);
[Obsolete("SpawnObject(GameObject planet, Sector sector, string propToCopyPath, Vector3 position, Vector3 eulerAngles, float scale, bool alignRadial) is deprecated, please use SpawnObject(IModBehaviour mod, GameObject planet, Sector sector, string propToCopyPath, Vector3 position, Vector3 eulerAngles, float scale, bool alignRadial) instead")]
GameObject SpawnObject(GameObject planet, Sector sector, string propToCopyPath, Vector3 position, Vector3 eulerAngles, float scale, bool alignWithNormal);
#endregion
/// <summary>
@ -105,7 +108,7 @@ namespace NewHorizons
/// Allows you to spawn a copy of a prop by specifying its path.
/// This is the same as using Props->details in a config, but also returns the spawned gameObject to you.
/// </summary>
GameObject SpawnObject(GameObject planet, Sector sector, string propToCopyPath, Vector3 position, Vector3 eulerAngles,
GameObject SpawnObject(IModBehaviour mod, GameObject planet, Sector sector, string propToCopyPath, Vector3 position, Vector3 eulerAngles,
float scale, bool alignWithNormal);
/// <summary>
@ -173,5 +176,36 @@ namespace NewHorizons
/// <param name="curiousityColours">A dictionary of each curiousity ID to its colour and highlight colour in the ship log. Optional.</param>
void AddShipLogXML(IModBehaviour mod, XElement xml, string planetName, string imageFolder = null, Dictionary<string, Vector2> entryPositions = null, Dictionary<string, (Color colour, Color highlight)> curiousityColours = null);
#endregion
#region Translations
/// <summary>
/// Look up shiplog-related translated text for the given text key.
/// Defaults to English if no translation in the current language is available, and just the key if no English translation is available.
/// </summary>
/// <param name="text">The text key to look up.</param>
/// <returns></returns>
string GetTranslationForShipLog(string text);
/// <summary>
/// Look up dialogue-related translated text for the given text key.
/// Defaults to English if no translation in the current language is available, and just the key if no English translation is available.
/// </summary>
/// <param name="text">The text key to look up.</param>
/// <returns></returns>
string GetTranslationForDialogue(string text);
/// <summary>
/// Look up UI-related translated text for the given text key.
/// Defaults to English if no translation in the current language is available, and just the key if no English translation is available.
/// </summary>
/// <param name="text">The text key to look up.</param>
/// <returns></returns>
string GetTranslationForUI(string text);
/// <summary>
/// Look up miscellaneous translated text for the given text key.
/// Defaults to English if no translation in the current language is available, and just the key if no English translation is available.
/// </summary>
/// <param name="text">The text key to look up.</param>
/// <returns></returns>
string GetTranslationForOtherText(string text);
#endregion
}
}

View File

@ -437,6 +437,7 @@ namespace NewHorizons
// Some builders have to be reset each loop
SignalBuilder.Init();
BrambleDimensionBuilder.Init();
ItemBuilder.Init();
AstroObjectLocator.Init();
StreamingHandler.Init();
AudioTypeHandler.Init();

View File

@ -70,6 +70,13 @@ namespace NewHorizons
}
}
[Obsolete("SpawnObject(GameObject planet, Sector sector, string propToCopyPath, Vector3 position, Vector3 eulerAngles, float scale, bool alignRadial) is deprecated, please use SpawnObject(IModBehaviour mod, GameObject planet, Sector sector, string propToCopyPath, Vector3 position, Vector3 eulerAngles, float scale, bool alignRadial) instead")]
public GameObject SpawnObject(GameObject planet, Sector sector, string propToCopyPath, Vector3 position, Vector3 eulerAngles,
float scale, bool alignRadial)
{
return SpawnObject(null, planet, sector, propToCopyPath, position, eulerAngles, scale, alignRadial);
}
public void LoadConfigs(IModBehaviour mod)
{
Main.Instance.LoadConfigs(mod);
@ -170,7 +177,7 @@ namespace NewHorizons
return default;
}
public GameObject SpawnObject(GameObject planet, Sector sector, string propToCopyPath, Vector3 position, Vector3 eulerAngles,
public GameObject SpawnObject(IModBehaviour mod, GameObject planet, Sector sector, string propToCopyPath, Vector3 position, Vector3 eulerAngles,
float scale, bool alignRadial)
{
var prefab = SearchUtilities.Find(propToCopyPath);
@ -181,7 +188,7 @@ namespace NewHorizons
scale = scale,
alignRadial = alignRadial
};
return DetailBuilder.Make(planet, sector, prefab, detailInfo);
return DetailBuilder.Make(planet, sector, mod, prefab, detailInfo);
}
public AudioSignal SpawnSignal(IModBehaviour mod, GameObject root, string audio, string name, string frequency,
@ -322,5 +329,13 @@ namespace NewHorizons
/// </summary>
/// <param name="builder"></param>
public void RegisterCustomBuilder(Action<GameObject, string> builder) => PlanetCreationHandler.CustomBuilders.Add(builder);
public string GetTranslationForShipLog(string text) => TranslationHandler.GetTranslation(text, TranslationHandler.TextType.SHIPLOG);
public string GetTranslationForDialogue(string text) => TranslationHandler.GetTranslation(text, TranslationHandler.TextType.DIALOGUE);
public string GetTranslationForUI(string text) => TranslationHandler.GetTranslation(text, TranslationHandler.TextType.UI);
public string GetTranslationForOtherText(string text) => TranslationHandler.GetTranslation(text, TranslationHandler.TextType.OTHER);
}
}

View File

@ -1381,6 +1381,157 @@
"type": "boolean",
"description": "Should the player close their eyes while the activation state changes. Only relevant if activationCondition or deactivationCondition are set.",
"default": true
},
"item": {
"description": "Should this detail be treated as an interactible item",
"$ref": "#/definitions/ItemInfo"
},
"itemSocket": {
"description": "Should this detail be treated as a socket for an interactible item",
"$ref": "#/definitions/ItemSocketInfo"
}
}
},
"ItemInfo": {
"type": "object",
"additionalProperties": false,
"properties": {
"name": {
"type": "string",
"description": "The name of the item to be displayed in the UI. Defaults to the name of the detail object."
},
"itemType": {
"type": "string",
"description": "The type of the item, which determines its orientation when held and what sockets it fits into. This can be a custom string, or a vanilla ItemType (Scroll, WarpCode, SharedStone, ConversationStone, Lantern, SlideReel, DreamLantern, or VisionTorch). Defaults to the item name."
},
"interactRange": {
"type": "number",
"description": "The furthest distance where the player can interact with this item. Defaults to two meters, same as most vanilla items. Set this to zero to disable all interaction by default.",
"format": "float",
"default": 2.0
},
"droppable": {
"type": "boolean",
"description": "Whether the item can be dropped. Defaults to true.",
"default": true
},
"dropOffset": {
"description": "A relative offset to apply to the item's position when dropping it on the ground.",
"$ref": "#/definitions/MVector3"
},
"dropNormal": {
"description": "The direction the item will be oriented when dropping it on the ground. Defaults to up (0, 1, 0).",
"$ref": "#/definitions/MVector3"
},
"pickupAudio": {
"type": "string",
"description": "The audio to play when this item is picked up. Only applies to custom/non-vanilla item types.\nCan be a path to a .wav/.ogg/.mp3 file, or taken from the AudioClip list."
},
"dropAudio": {
"type": "string",
"description": "The audio to play when this item is dropped. Only applies to custom/non-vanilla item types.\nCan be a path to a .wav/.ogg/.mp3 file, or taken from the AudioClip list."
},
"socketAudio": {
"type": "string",
"description": "The audio to play when this item is inserted into a socket. Only applies to custom/non-vanilla item types.\nCan be a path to a .wav/.ogg/.mp3 file, or taken from the AudioClip list."
},
"unsocketAudio": {
"type": "string",
"description": "The audio to play when this item is removed from a socket. Only applies to custom/non-vanilla item types.\nCan be a path to a .wav/.ogg/.mp3 file, or taken from the AudioClip list."
},
"pickupCondition": {
"type": "string",
"description": "A dialogue condition to set when picking up this item."
},
"clearPickupConditionOnDrop": {
"type": "boolean",
"description": "Whether the pickup condition should be cleared when dropping the item. Defaults to true.",
"default": true
},
"pickupFact": {
"type": "string",
"description": "A ship log fact to reveal when picking up this item."
},
"pathToInitialSocket": {
"type": "string",
"description": "A relative path from the planet to a socket that this item will be automatically inserted into."
}
}
},
"ItemSocketInfo": {
"type": "object",
"additionalProperties": false,
"properties": {
"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"
},
"parentPath": {
"type": "string",
"description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
},
"isRelativeToParent": {
"type": "boolean",
"description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object."
},
"rename": {
"type": "string",
"description": "An optional rename of this object"
},
"socketPath": {
"type": "string",
"description": "The relative path to a child game object of this detail that will act as the socket point for the item. Will be used instead of this socket's positioning info if set."
},
"itemType": {
"type": "string",
"description": "The type of item allowed in this socket. This can be a custom string, or a vanilla ItemType (Scroll, WarpCode, SharedStone, ConversationStone, Lantern, SlideReel, DreamLantern, or VisionTorch)."
},
"interactRange": {
"type": "number",
"description": "The furthest distance where the player can interact with this item socket. Defaults to two meters, same as most vanilla item sockets. Set this to zero to disable all interaction by default.",
"format": "float",
"default": 2.0
},
"useGiveTakePrompts": {
"type": "boolean",
"description": "Whether to use \"Give Item\" / \"Take Item\" prompts instead of \"Insert Item\" / \"Remove Item\"."
},
"insertCondition": {
"type": "string",
"description": "A dialogue condition to set when inserting an item into this socket."
},
"clearInsertConditionOnRemoval": {
"type": "boolean",
"description": "Whether the insert condition should be cleared when removing the socketed item. Defaults to true.",
"default": true
},
"insertFact": {
"type": "string",
"description": "A ship log fact to reveal when inserting an item into this socket."
},
"removalCondition": {
"type": "string",
"description": "A dialogue condition to set when removing an item from this socket, or when the socket is empty."
},
"clearRemovalConditionOnInsert": {
"type": "boolean",
"description": "Whether the removal condition should be cleared when inserting a socketed item. Defaults to true.",
"default": true
},
"removalFact": {
"type": "string",
"description": "A ship log fact to reveal when removing an item from this socket, or when the socket is empty."
}
}
},

View File

@ -157,7 +157,7 @@ namespace NewHorizons.Utility.DebugTools
position = data.pos,
rotation = data.rot.eulerAngles,
};
var prop = DetailBuilder.Make(planetGO, sector, prefab, detailInfo);
var prop = DetailBuilder.Make(planetGO, sector, null, prefab, detailInfo);
var body = data.hitBodyGameObject.GetComponent<AstroObject>();
if (body != null) RegisterProp(body, prop);