New Dream World Props (#973)

## Major features
- New dream world related prop types: alarm totems, grapple totems,
projection totems, portholes/peepholes, and dream candles. Where
possible, these have been modified to work with the regular flashlight
as well as the artifact for use outside of the dream world.
This commit is contained in:
Joshua Thome 2024-10-16 21:28:26 -05:00 committed by GitHub
commit 0f9a75f82c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 1255 additions and 12 deletions

View File

@ -340,6 +340,16 @@ namespace NewHorizons.Builder.Props
{
remoteCameraPlatform._visualSector = sector;
}
else if(component is SingleLightSensor singleLightSensor && !existingSectors.Contains(singleLightSensor._sector))
{
if (singleLightSensor._sector != null)
{
singleLightSensor._sector.OnSectorOccupantsUpdated -= singleLightSensor.OnSectorOccupantsUpdated;
}
singleLightSensor._sector = sector;
singleLightSensor._sector.OnSectorOccupantsUpdated += singleLightSensor.OnSectorOccupantsUpdated;
}
}
/// <summary>

View File

@ -0,0 +1,60 @@
using NewHorizons.External.Modules.Props;
using NewHorizons.External.Modules.Props.EchoesOfTheEye;
using NewHorizons.Handlers;
using NewHorizons.Utility;
using NewHorizons.Utility.OWML;
using OWML.Common;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace NewHorizons.Builder.Props.EchoesOfTheEye
{
public static class AlarmTotemBuilder
{
private static GameObject _prefab;
internal static void InitPrefab()
{
if (_prefab == null)
{
_prefab = SearchUtilities.Find("DreamWorld_Body/Sector_DreamWorld/Sector_Underground/IslandsRoot/IslandPivot_C/Island_C/Interactibles_Island_C/Prefab_IP_AlarmTotem").InstantiateInactive().Rename("Prefab_AlarmTotem").DontDestroyOnLoad();
if (_prefab == null)
{
NHLogger.LogWarning($"Tried to make a grapple totem but couldn't. Do you have the DLC installed?");
return;
}
else
{
_prefab.AddComponent<DestroyOnDLC>()._destroyOnDLCNotOwned = true;
var alarmTotem = _prefab.GetComponent<AlarmTotem>();
alarmTotem._sector = null;
}
}
}
public static GameObject Make(GameObject planetGO, Sector sector, AlarmTotemInfo info, IModBehaviour mod)
{
InitPrefab();
if (_prefab == null || sector == null) return null;
var totemObj = DetailBuilder.Make(planetGO, sector, mod, _prefab, new DetailInfo(info));
var alarmTotem = totemObj.GetComponent<AlarmTotem>();
alarmTotem._sightAngle = info.sightAngle;
alarmTotem._sightDistance = info.sightDistance;
if (info.stretchVisionCone != null)
{
var visionCone = totemObj.transform.Find("Effects_IP_SIM_AlarmTotem/AlarmTotemVisionCone");
visionCone.localScale = Vector3.Scale(visionCone.localScale, info.stretchVisionCone);
}
return totemObj;
}
}
}

View File

@ -0,0 +1,76 @@
using NewHorizons.External.Modules.Props;
using NewHorizons.External.Modules.Props.EchoesOfTheEye;
using NewHorizons.Handlers;
using NewHorizons.Utility;
using NewHorizons.Utility.OWML;
using OWML.Common;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace NewHorizons.Builder.Props.EchoesOfTheEye
{
public static class DreamCandleBuilder
{
private static Dictionary<DreamCandleType, GameObject> _prefabs = new();
internal static void InitPrefabs()
{
InitPrefab(DreamCandleType.Ground, "DreamWorld_Body/Sector_DreamWorld/Sector_DreamZone_1/Interactibles_DreamZone_1/DreamHouseIsland/Prefab_IP_DreamCandle");
InitPrefab(DreamCandleType.GroundSmall, "DreamWorld_Body/Sector_DreamWorld/Sector_DreamZone_2/Structure_DreamZone_2/City/StartingAreaLanterns/Prefab_IP_DreamCandle_Ground_Small");
InitPrefab(DreamCandleType.GroundLarge, "DreamWorld_Body/Sector_DreamWorld/Sector_DreamZone_1/Interactibles_DreamZone_1/DreamHouseIsland/Prefab_IP_DreamCandle_Ground_Large");
InitPrefab(DreamCandleType.GroundSingle, "DreamWorld_Body/Sector_DreamWorld/Sector_DreamZone_1/Sector_PartyHouse/Interactables_PartyHouse/Prefab_IP_DreamCandle_Ground_Single");
InitPrefab(DreamCandleType.Wall, "DreamWorld_Body/Sector_DreamWorld/Sector_DreamZone_2/Structure_DreamZone_2/City/ParkLanterns/Prefab_IP_DreamCandle_Wall");
InitPrefab(DreamCandleType.WallLargeFlame, "DreamWorld_Body/Sector_DreamWorld/Sector_DreamZone_2/Structure_DreamZone_2/City/FalseKnightHouse/CandleDoor/FirstDoorLanterns/Prefab_IP_DreamCandle_LargeFlame_Wall");
InitPrefab(DreamCandleType.WallBigWick, "DreamWorld_Body/Sector_DreamWorld/Sector_DreamZone_2/Structure_DreamZone_2/DreamFireHouse_2/Interactibles_DreamFireHouse_2/Pivot_SlideReelRoom/CandleController/CandlePivot_0/Prefab_IP_DreamCandle_BigWick_Wall");
InitPrefab(DreamCandleType.Standing, "DreamWorld_Body/Sector_DreamWorld/Sector_DreamZone_2/Structure_DreamZone_2/City/ElevatorHouse/CandleDoor/DoorTutorial/Prefab_IP_DreamCandle_LargeFlame_Standing");
InitPrefab(DreamCandleType.Pile, "DreamWorld_Body/Sector_DreamWorld/Sector_DreamZone_3/Sector_Hotel/Gallery/Interactibles_Gallery/DreamCandles/Prefab_IP_DreamCandle_Pile");
}
private static void InitPrefab(DreamCandleType type, string path)
{
var prefab = _prefabs.ContainsKey(type) ? _prefabs[type] : null;
if (prefab == null)
{
prefab = SearchUtilities.Find(path).InstantiateInactive().Rename($"Prefab_DreamCandle_{type}").DontDestroyOnLoad();
if (prefab == null)
{
NHLogger.LogWarning($"Tried to make a dream candle but couldn't. Do you have the DLC installed?");
return;
}
else
{
prefab.AddComponent<DestroyOnDLC>()._destroyOnDLCNotOwned = true;
var sensor = prefab.GetComponentInChildren<SingleLightSensor>();
sensor._sector = null;
}
_prefabs.Add(type, prefab);
}
}
public static GameObject Make(GameObject planetGO, Sector sector, DreamCandleInfo info, IModBehaviour mod)
{
InitPrefabs();
var prefab = _prefabs.ContainsKey(info.type) ? _prefabs[info.type] : null;
if (prefab == null || sector == null) return null;
var candleObj = DetailBuilder.Make(planetGO, sector, mod, prefab, new DetailInfo(info));
var dreamCandle = candleObj.GetComponent<DreamCandle>();
var sensor = candleObj.GetComponentInChildren<SingleLightSensor>();
sensor._detectFlashlight = true;
sensor._lightSourceMask |= LightSourceType.FLASHLIGHT;
dreamCandle._startLit = info.startLit;
dreamCandle.SetLit(info.startLit, false, true);
return candleObj;
}
}
}

View File

@ -0,0 +1,63 @@
using NewHorizons.External.Modules.Props;
using NewHorizons.External.Modules.Props.EchoesOfTheEye;
using NewHorizons.Handlers;
using NewHorizons.Utility;
using NewHorizons.Utility.OWML;
using OWML.Common;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace NewHorizons.Builder.Props.EchoesOfTheEye
{
public static class GrappleTotemBuilder
{
private static GameObject _prefab;
internal static void InitPrefab()
{
if (_prefab == null)
{
_prefab = SearchUtilities.Find("DreamWorld_Body/Sector_DreamWorld/Sector_DreamZone_4/Interactibles_DreamZone_4_Upper/Prefab_IP_GrappleTotem").InstantiateInactive().Rename("Prefab_GrappleTotem").DontDestroyOnLoad();
if (_prefab == null)
{
NHLogger.LogWarning($"Tried to make a grapple totem but couldn't. Do you have the DLC installed?");
return;
}
else
{
_prefab.AddComponent<DestroyOnDLC>()._destroyOnDLCNotOwned = true;
var zoomPoint = _prefab.GetComponentInChildren<LanternZoomPoint>();
zoomPoint._sector = null;
var sensor = _prefab.GetComponentInChildren<SingleLightSensor>();
sensor._sector = null;
}
}
}
public static GameObject Make(GameObject planetGO, Sector sector, GrappleTotemInfo info, IModBehaviour mod)
{
InitPrefab();
if (_prefab == null || sector == null) return null;
var totemObj = DetailBuilder.Make(planetGO, sector, mod, _prefab, new DetailInfo(info));
var zoomPoint = totemObj.GetComponentInChildren<LanternZoomPoint>();
zoomPoint._minActivationDistance = info.minDistance;
zoomPoint._arrivalDistance = info.arrivalDistance;
var sensor = totemObj.GetComponentInChildren<SingleLightSensor>();
sensor._detectionAngle = info.maxAngle;
sensor._maxDistance = info.maxDistance;
sensor._detectFlashlight = true;
sensor._lightSourceMask |= LightSourceType.FLASHLIGHT;
return totemObj;
}
}
}

View File

@ -0,0 +1,94 @@
using NewHorizons.External.Modules.Props;
using NewHorizons.External.Modules.Props.EchoesOfTheEye;
using NewHorizons.Handlers;
using NewHorizons.Utility;
using NewHorizons.Utility.OWML;
using OWML.Common;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace NewHorizons.Builder.Props.EchoesOfTheEye
{
public static class PortholeBuilder
{
private static GameObject _mainPrefab;
private static GameObject _simPrefab;
internal static void InitPrefabs()
{
if (_mainPrefab == null)
{
_mainPrefab = SearchUtilities.Find("DreamWorld_Body/Sector_DreamWorld/Sector_DreamZone_4/Interactibles_DreamZone_4_Upper/Props_IP_Peephole_Prison").InstantiateInactive().Rename("Prefab_Porthole").DontDestroyOnLoad();
if (_mainPrefab == null)
{
NHLogger.LogWarning($"Tried to make a grapple totem but couldn't. Do you have the DLC installed?");
return;
}
else
{
_mainPrefab.AddComponent<DestroyOnDLC>()._destroyOnDLCNotOwned = true;
var peephole = _mainPrefab.GetComponentInChildren<Peephole>();
peephole._factIDs = new string[0];
peephole._viewingSector = null;
}
}
if (_simPrefab == null)
{
_simPrefab = SearchUtilities.Find("DreamWorld_Body/Sector_DreamWorld/Sector_DreamZone_4/Simulation_DreamZone_4/Geo_DreamZone_4_Upper/Effects_IP_SIM_Porthole").InstantiateInactive().Rename("Prefab_SIM_Porthole").DontDestroyOnLoad();
if (_simPrefab == null)
{
NHLogger.LogWarning($"Tried to make a grapple totem but couldn't. Do you have the DLC installed?");
return;
}
else
{
_simPrefab.AddComponent<DestroyOnDLC>()._destroyOnDLCNotOwned = true;
}
}
}
public static GameObject Make(GameObject planetGO, Sector sector, PortholeInfo info, IModBehaviour mod)
{
InitPrefabs();
if (_mainPrefab == null || _simPrefab == null || sector == null) return null;
var portholeObj = DetailBuilder.Make(planetGO, sector, mod, _mainPrefab, new DetailInfo(info));
portholeObj.name = "Prefab_Porthole";
var simObj = DetailBuilder.Make(planetGO, sector, mod, _simPrefab, new DetailInfo(info));
simObj.transform.parent = portholeObj.transform;
var parentObj = GeneralPropBuilder.MakeNew("Porthole", planetGO, sector, info);
parentObj.SetActive(true);
portholeObj.transform.SetParent(parentObj.transform, true);
portholeObj.transform.localPosition = new Vector3(0f, -4f, 8f);
portholeObj.transform.localEulerAngles = new Vector3(0f, 315f, 0f);
var peephole = portholeObj.GetComponentInChildren<Peephole>();
if (info.revealFacts != null)
{
peephole._factIDs = info.revealFacts;
}
peephole._peepholeCamera.farClipPlane = 4000f;
peephole._peepholeCamera.fieldOfView = info.fieldOfView;
// Reposition the peephole camera later, after all planets are built, in case the target point is on a different astro body.
Delay.FireInNUpdates(() =>
{
var cameraObj = GeneralPropBuilder.MakeFromExisting(peephole._peepholeCamera.gameObject, planetGO, sector, info.target);
cameraObj.transform.Rotate(Vector3.up, 180f, Space.Self);
cameraObj.transform.position += cameraObj.transform.up;
var viewingSector = cameraObj.GetComponentInParent<Sector>();
peephole._viewingSector = viewingSector;
}, 2);
return portholeObj;
}
}
}

View File

@ -0,0 +1,129 @@
using NewHorizons.External.Modules.Props;
using NewHorizons.External.Modules.Props.EchoesOfTheEye;
using NewHorizons.Handlers;
using NewHorizons.Utility;
using NewHorizons.Utility.OWML;
using OWML.Common;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.InputSystem;
namespace NewHorizons.Builder.Props.EchoesOfTheEye
{
public static class ProjectionTotemBuilder
{
private static GameObject _prefab;
internal static void InitPrefab()
{
if (_prefab == null)
{
_prefab = SearchUtilities.Find("DreamWorld_Body/Sector_DreamWorld/Sector_DreamZone_3/Interactibles_DreamZone_3/Prefab_IP_DreamObjectProjector_Bridge").InstantiateInactive().Rename("Prefab_ProjectionTotem").DontDestroyOnLoad();
if (_prefab == null)
{
NHLogger.LogWarning($"Tried to make a grapple totem but couldn't. Do you have the DLC installed?");
return;
}
else
{
_prefab.AddComponent<DestroyOnDLC>()._destroyOnDLCNotOwned = true;
var projector = _prefab.GetComponent<DreamObjectProjector>();
projector._projections = new DreamObjectProjection[0];
}
}
}
public static GameObject Make(GameObject planetGO, Sector sector, ProjectionTotemInfo info, IModBehaviour mod)
{
InitPrefab();
if (_prefab == null || sector == null) return null;
var totemObj = DetailBuilder.Make(planetGO, sector, mod, _prefab, new DetailInfo(info));
var projector = totemObj.GetComponent<DreamObjectProjector>();
if (!string.IsNullOrEmpty(info.pathToAlarmTotem))
{
var alarmTotemObj = planetGO.transform.Find(info.pathToAlarmTotem);
if (alarmTotemObj != null)
{
var alarmTotem = alarmTotemObj.GetComponentInChildren<AlarmTotem>();
if (alarmTotem != null)
{
projector._alarmTotem = alarmTotem;
}
}
}
if (info.pathsToDreamCandles != null)
{
var dreamCandles = new List<DreamCandle>();
foreach (var pathToDreamCandles in info.pathsToDreamCandles)
{
if (string.IsNullOrEmpty(pathToDreamCandles)) continue;
var dreamCandleObj = planetGO.transform.Find(pathToDreamCandles);
if (dreamCandleObj != null)
{
dreamCandles.AddRange(dreamCandleObj.GetComponentsInChildren<DreamCandle>());
}
}
projector._dreamCandles = dreamCandles.ToArray();
}
if (info.pathsToProjectionTotems != null)
{
var projectionTotems = new List<DreamObjectProjector>();
foreach (var pathToProjectionTotems in info.pathsToProjectionTotems)
{
if (string.IsNullOrEmpty(pathToProjectionTotems)) continue;
var projectionTotemObj = planetGO.transform.Find(pathToProjectionTotems);
if (projectionTotemObj != null)
{
projectionTotems.AddRange(projectionTotemObj.GetComponentsInChildren<DreamObjectProjector>());
}
}
projector._extinguishedProjectors = projectionTotems.ToArray();
}
if (info.pathsToProjectedObjects != null)
{
var projections = new List<DreamObjectProjection>();
foreach (var pathToProjectedObject in info.pathsToProjectedObjects)
{
if (string.IsNullOrEmpty(pathToProjectedObject)) continue;
var projectionObj = planetGO.transform.Find(pathToProjectedObject);
if (projectionObj != null)
{
projectionObj.gameObject.AddComponent<DitheringAnimator>();
var projection = projectionObj.gameObject.AddComponent<DreamObjectProjection>();
projection._setActive = info.toggleProjectedObjectsActive;
projection.Awake();
projections.Add(projection);
}
}
projector._projections = projections.ToArray();
}
var sensor = projector._lightSensor as SingleLightSensor;
sensor._detectFlashlight = true;
sensor._lightSourceMask |= LightSourceType.FLASHLIGHT;
projector._lit = info.startLit;
projector._startLit = info.startLit;
projector._extinguishOnly = info.extinguishOnly;
/*
Delay.FireOnNextUpdate(() =>
{
projector.Start();
});
*/
return totemObj;
}
}
}

View File

@ -102,13 +102,20 @@ namespace NewHorizons.Builder.Props
// If a prop has set its parentPath and the parent cannot be found, add it to the next pass and try again later
nextPass = new List<Action>();
if (Main.HasDLC) MakeGeneralProps(go, config.Props.dreamCampfires, (campfire) => DreamCampfireBuilder.Make(go, sector, campfire, mod), (campfire) => campfire.id);
if (Main.HasDLC) MakeGeneralProps(go, config.Props.dreamArrivalPoints, (point) => DreamArrivalPointBuilder.Make(go, sector, point, mod), (point) => point.id);
MakeGeneralProps(go, config.Props.gravityCannons, (cannon) => GravityCannonBuilder.Make(go, sector, cannon, mod), (cannon) => cannon.shuttleID);
MakeGeneralProps(go, config.Props.shuttles, (shuttle) => ShuttleBuilder.Make(go, sector, mod, shuttle), (shuttle) => shuttle.id);
MakeGeneralProps(go, config.Props.details, (detail) => DetailBuilder.Make(go, sector, mod, detail), (detail) => detail.path);
MakeGeneralProps(go, config.Props.geysers, (geyser) => GeyserBuilder.Make(go, sector, geyser));
if (Main.HasDLC) MakeGeneralProps(go, config.Props.rafts, (raft) => RaftBuilder.Make(go, sector, raft, planetBody));
if (Main.HasDLC)
{
MakeGeneralProps(go, config.Props.dreamCandles, (candle) => DreamCandleBuilder.Make(go, sector, candle, mod));
MakeGeneralProps(go, config.Props.portholes, (porthole) => PortholeBuilder.Make(go, sector, porthole, mod));
MakeGeneralProps(go, config.Props.alarmTotems, (totem) => AlarmTotemBuilder.Make(go, sector, totem, mod));
MakeGeneralProps(go, config.Props.grappleTotems, (totem) => GrappleTotemBuilder.Make(go, sector, totem, mod));
MakeGeneralProps(go, config.Props.dreamCampfires, (campfire) => DreamCampfireBuilder.Make(go, sector, campfire, mod), (campfire) => campfire.id);
MakeGeneralProps(go, config.Props.dreamArrivalPoints, (point) => DreamArrivalPointBuilder.Make(go, sector, point, mod), (point) => point.id);
MakeGeneralProps(go, config.Props.rafts, (raft) => RaftBuilder.Make(go, sector, raft, planetBody));
}
MakeGeneralProps(go, config.Props.tornados, (tornado) => TornadoBuilder.Make(go, sector, tornado, config.Atmosphere?.clouds != null));
MakeGeneralProps(go, config.Props.volcanoes, (volcano) => VolcanoBuilder.Make(go, sector, volcano));
MakeGeneralProps(go, config.Props.dialogue, (dialogueInfo) =>
@ -132,6 +139,7 @@ namespace NewHorizons.Builder.Props
MakeGeneralProps(go, config.Props.warpTransmitters, (warpTransmitter) => WarpPadBuilder.Make(go, sector, mod, warpTransmitter), (warpTransmitter) => warpTransmitter.frequency);
MakeGeneralProps(go, config.Props.audioSources, (audioSource) => AudioSourceBuilder.Make(go, sector, audioSource, mod), (audioSource) => audioSource.audio);
RemoteBuilder.MakeGeneralProps(go, sector, config.Props.remotes, nhBody);
if (Main.HasDLC) MakeGeneralProps(go, config.Props.projectionTotems, (totem) => ProjectionTotemBuilder.Make(go, sector, totem, mod));
RunMultiPass();

View File

@ -54,7 +54,7 @@ namespace NewHorizons.External.Modules
public DetailInfo[] proxyDetails;
/// <summary>
/// Add rafts to this planet
/// Add rafts to this planet (requires Echoes of the Eye DLC)
/// </summary>
public RaftInfo[] rafts;
@ -64,7 +64,7 @@ namespace NewHorizons.External.Modules
public ScatterInfo[] scatter;
/// <summary>
/// Add slideshows (from the DLC) to the planet
/// Add slideshows to the planet (requires Echoes of the Eye DLC)
/// </summary>
public ProjectionInfo[] slideShows;
@ -124,15 +124,40 @@ namespace NewHorizons.External.Modules
public ShuttleInfo[] shuttles;
/// <summary>
/// Add campfires that allow you to enter the dream world/simulation. Must be paired with a dream arrival point, which can be placed on this planet or elsewhere.
/// Add campfires that allow you to enter the dream world/simulation (requires Echoes of the Eye DLC). Must be paired with a dream arrival point, which can be placed on this planet or elsewhere.
/// </summary>
public DreamCampfireInfo[] dreamCampfires;
/// <summary>
/// Add the points you will arrive at when entering the dream world/simulation from a paired dream campfire, which can be placed on this planet or elsewhere. The planet with the arrival point should be statically positioned to avoid issues with the simulation view materials.
/// Add the points you will arrive at when entering the dream world/simulation from a paired dream campfire (requires Echoes of the Eye DLC). The planet with the arrival point should be statically positioned to avoid issues with the simulation view materials.
/// </summary>
public DreamArrivalPointInfo[] dreamArrivalPoints;
/// <summary>
/// Adds dream world grapple totems to this planet (requires Echoes of the Eye DLC).
/// </summary>
public GrappleTotemInfo[] grappleTotems;
/// <summary>
/// Adds dream world alarm totems to this planet (requires Echoes of the Eye DLC).
/// </summary>
public AlarmTotemInfo[] alarmTotems;
/// <summary>
/// Adds portholes (the windows you can peek through in the Stranger) to this planet (requires Echoes of the Eye DLC).
/// </summary>
public PortholeInfo[] portholes;
/// <summary>
/// Adds dream world candles to this planet (requires Echoes of the Eye DLC).
/// </summary>
public DreamCandleInfo[] dreamCandles;
/// <summary>
/// Adds dream world projection totems (requires Echoes of the Eye DLC).
/// </summary>
public ProjectionTotemInfo[] projectionTotems;
[Obsolete("reveal is deprecated. Use Volumes->revealVolumes instead.")] public RevealVolumeInfo[] reveal;
[Obsolete("audioVolumes is deprecated. Use Volumes->audioVolumes instead.")] public AudioVolumeInfo[] audioVolumes;

View File

@ -0,0 +1,30 @@
using NewHorizons.External.SerializableData;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NewHorizons.External.Modules.Props.EchoesOfTheEye
{
[JsonObject]
public class AlarmTotemInfo : GeneralPropInfo
{
/// <summary>
/// The maximum distance of the alarm's vision cone.
/// </summary>
[DefaultValue(45f)] public float sightDistance = 45;
/// <summary>
/// The width of the alarm's vision cone in degrees.
/// </summary>
[DefaultValue(60f)] public float sightAngle = 60f;
/// <summary>
/// Scales the visible vision cone in the simulation view (does not affect the actual vision cone detection).
/// </summary>
public MVector3 stretchVisionCone;
}
}

View File

@ -0,0 +1,24 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NewHorizons.External.Modules.Props.EchoesOfTheEye
{
[JsonObject]
public class DreamCandleInfo : GeneralPropInfo
{
/// <summary>
/// The type of dream candle this is.
/// </summary>
[DefaultValue(DreamCandleType.Ground)] public DreamCandleType type = DreamCandleType.Ground;
/// <summary>
/// Whether the candle should start lit or extinguished.
/// </summary>
public bool startLit;
}
}

View File

@ -0,0 +1,33 @@
using Newtonsoft.Json.Converters;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Threading.Tasks;
namespace NewHorizons.External.Modules.Props.EchoesOfTheEye
{
[JsonConverter(typeof(StringEnumConverter))]
public enum DreamCandleType
{
[EnumMember(Value = @"ground")] Ground,
[EnumMember(Value = @"groundSmall")] GroundSmall,
[EnumMember(Value = @"groundLarge")] GroundLarge,
[EnumMember(Value = @"groundSingle")] GroundSingle,
[EnumMember(Value = @"wall")] Wall,
[EnumMember(Value = @"wallLargeFlame")] WallLargeFlame,
[EnumMember(Value = @"wallBigWick")] WallBigWick,
[EnumMember(Value = @"standing")] Standing,
[EnumMember(Value = @"pile")] Pile,
}
}

View File

@ -0,0 +1,34 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NewHorizons.External.Modules.Props.EchoesOfTheEye
{
[JsonObject]
public class GrappleTotemInfo : GeneralPropInfo
{
/// <summary>
/// The minimum distance that the player must be from the grapple totem for it to activate.
/// </summary>
[DefaultValue(10f)] public float minDistance = 10f;
/// <summary>
/// The distance from the grapple totem that the player will stop at when it activates.
/// </summary>
[DefaultValue(4f)] public float arrivalDistance = 4f;
/// <summary>
/// The maximum angle in degrees allowed between the grapple totem's face and the player's lantern in order to activate the totem.
/// </summary>
[DefaultValue(45f)] public float maxAngle = 45f;
/// <summary>
/// The maximum distance allowed between the grapple totem's face and the player's lantern in order to activate the totem.
/// </summary>
[DefaultValue(29f)] public float maxDistance = 29f;
}
}

View File

@ -0,0 +1,35 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NewHorizons.External.Modules.Props.EchoesOfTheEye
{
[JsonObject]
public class PortholeInfo : GeneralPropInfo
{
/// <summary>
/// Fact IDs to reveal when peeking through the porthole.
/// </summary>
public string[] revealFacts;
/// <summary>
/// The field of view of the porthole camera.
/// </summary>
[DefaultValue(90f)] public float fieldOfView = 90f;
/// <summary>
/// The location of the camera when the player peeks through the porthole. Can be placed on a different planet.
/// </summary>
public PortholeTargetInfo target;
}
[JsonObject]
public class PortholeTargetInfo : GeneralSolarSystemPropInfo
{
}
}

View File

@ -0,0 +1,49 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NewHorizons.External.Modules.Props.EchoesOfTheEye
{
[JsonObject]
public class ProjectionTotemInfo : GeneralPropInfo
{
/// <summary>
/// Whether the totem should start lit or extinguished.
/// </summary>
public bool startLit;
/// <summary>
/// Whether the projection totem should be able to extinguished but not be able to be lit again with the artifact. Mainly useful if `startLit` is set to true.
/// </summary>
public bool extinguishOnly;
/// <summary>
/// A relative path from this planet to an alarm totem that will be activated or deactivated based on whether this totem is lit.
/// </summary>
public string pathToAlarmTotem;
/// <summary>
/// Relative paths from this planet to objects containing dream candles that will be activated or deactivated based on whether this totem is lit. All dream candles in the selected objects will be connected to this totem, so they do not need to be specified individually if a parent object is specified.
/// </summary>
public string[] pathsToDreamCandles;
/// <summary>
/// Relative paths from this planet to projection totems that will be deactivated if this totem is extinguished. All projection totems in the selected objects will be connected to this totem, so they do not need to be specified individually if a parent object is specified.
/// </summary>
public string[] pathsToProjectionTotems;
/// <summary>
/// Relative paths from this planet to objects that will appear or disappear when this totem is lit or extinguished. Some types of objects and effects are not supported and will remain visible and active.
/// </summary>
public string[] pathsToProjectedObjects;
/// <summary>
/// If set, projected objects will be set to fully active or fully disabled instantly instead of smoothly fading lights/renderers/colliders. Use this if the normal behavior is insufficient for the objects you're using.
/// </summary>
public bool toggleProjectedObjectsActive;
}
}

View File

@ -0,0 +1,21 @@
using HarmonyLib;
using NewHorizons.Components.EOTE;
using System.Collections.Generic;
using System.Reflection.Emit;
using UnityEngine;
namespace NewHorizons.Patches.EchoesOfTheEyePatches
{
[HarmonyPatch(typeof(AlarmTotem))]
public static class AlarmTotemPatches
{
[HarmonyPostfix]
[HarmonyPatch(nameof(AlarmTotem.SetFaceOpen))]
public static void AlarmTotem_SetFaceOpen(AlarmTotem __instance, bool open)
{
// This method is unused in the base game and sets the rotations incorrectly (-90f instead of 90f); this corrects that
__instance._rightFaceCover.localEulerAngles = Vector3.up * (open ? 90f : 0f);
__instance._leftFaceCover.localEulerAngles = Vector3.up * (open ? -90f : 0f);
}
}
}

View File

@ -0,0 +1,117 @@
using HarmonyLib;
using NewHorizons.Components.EOTE;
using System.Collections.Generic;
using System.Reflection.Emit;
using UnityEngine;
namespace NewHorizons.Patches.EchoesOfTheEyePatches
{
[HarmonyPatch(typeof(LanternZoomPoint))]
public static class LanternZoomPointPatches
{
// Patching all methods that assume the player is holding an artifact (_playerLantern) to add null checks so they'll work outside of the dream world
[HarmonyPrefix]
[HarmonyPatch(nameof(LanternZoomPoint.Update))]
public static bool LanternZoomPoint_Update(LanternZoomPoint __instance)
{
if (PlayerState.InDreamWorld()) return true;
if (!__instance.enabled)
{
return false;
}
if (__instance._state != LanternZoomPoint.State.RetroZoom)
{
if (__instance._playerLantern != null)
{
__instance._playerLantern.GetLanternController().MoveTowardFocus(1f, 2f);
}
}
if (__instance._state == LanternZoomPoint.State.LookAt && Time.time > __instance._stateChangeTime + 0.4f)
{
__instance.ChangeState(LanternZoomPoint.State.ZoomIn);
__instance.StartZoomIn();
}
else if (__instance._state == LanternZoomPoint.State.ZoomIn)
{
__instance.UpdateZoomIn();
}
if (__instance._state == LanternZoomPoint.State.RetroZoom)
{
__instance.UpdateRetroZoom();
}
return false;
}
[HarmonyPrefix]
[HarmonyPatch(nameof(LanternZoomPoint.UpdateRetroZoom))]
public static bool LanternZoomPoint_UpdateRetroZoom(LanternZoomPoint __instance)
{
if (PlayerState.InDreamWorld()) return true;
float num = Mathf.InverseLerp(__instance._stateChangeTime, __instance._stateChangeTime + 1.2f, Time.time);
float focus = Mathf.Pow(Mathf.SmoothStep(0f, 1f, 1f - num), 0.2f);
if (__instance._playerLantern != null)
{
__instance._playerLantern.GetLanternController().SetFocus(focus);
}
float t = __instance._retroZoomCurve.Evaluate(num);
float targetFieldOfView = Mathf.Lerp(__instance._startFOV, Locator.GetPlayerCameraController().GetOrigFieldOfView(), t);
Locator.GetPlayerCameraController().SetTargetFieldOfView(targetFieldOfView);
float d = __instance._imageHalfWidth / Mathf.Tan(Locator.GetPlayerCamera().fieldOfView * 0.017453292f * 0.5f);
Vector3 vector = __instance._startLocalPos - __instance._endLocalPos;
__instance._attachPoint.transform.localPosition = __instance._endLocalPos + vector.normalized * d;
if (num >= 1f)
{
__instance.FinishRetroZoom();
}
return false;
}
[HarmonyPrefix]
[HarmonyPatch(nameof(LanternZoomPoint.FinishRetroZoom))]
public static bool LanternZoomPoint_FinishRetroZoom(LanternZoomPoint __instance)
{
if (PlayerState.InDreamWorld()) return true;
__instance.ChangeState(LanternZoomPoint.State.Idle);
__instance.enabled = false;
__instance._attachPoint.DetachPlayer();
GlobalMessenger.FireEvent("PlayerRepositioned");
if (__instance._playerLantern != null)
{
__instance._playerLantern.ForceUnfocus();
__instance._playerLantern.enabled = true;
__instance._playerLantern = null;
}
OWInput.ChangeInputMode(InputMode.Character);
__instance._lightController.FadeTo(0f, 1f);
Locator.GetPlayerController().SetColliderActivation(true);
Locator.GetPlayerTransform().GetComponent<PlayerLockOnTargeting>().BreakLock();
Locator.GetDreamWorldController().SetActiveZoomPoint(null);
return false;
}
[HarmonyPrefix]
[HarmonyPatch(nameof(LanternZoomPoint.OnDetectLight))]
public static bool LanternZoomPoint_OnDetectLight(LanternZoomPoint __instance)
{
if (PlayerState.InDreamWorld()) return true;
if (__instance._state == LanternZoomPoint.State.Idle && !PlayerState.IsAttached() && Time.time > __instance._stateChangeTime + 1f && Vector3.Distance(__instance.transform.position, Locator.GetPlayerCamera().transform.position) > __instance._minActivationDistance)
{
__instance._playerLantern = Locator.GetToolModeSwapper().GetItemCarryTool().GetHeldItem() as DreamLanternItem;
Locator.GetDreamWorldController().SetActiveZoomPoint(__instance);
__instance._attachPoint.transform.position = Locator.GetPlayerTransform().position;
__instance._attachPoint.transform.rotation = Locator.GetPlayerTransform().rotation;
__instance._attachPoint.AttachPlayer();
Locator.GetPlayerTransform().GetComponent<PlayerLockOnTargeting>().LockOn(__instance.transform, 5f, false, 1f);
OWInput.ChangeInputMode(InputMode.None);
if (__instance._playerLantern != null)
{
__instance._playerLantern.enabled = false;
}
__instance.ChangeState(LanternZoomPoint.State.LookAt);
__instance.enabled = true;
}
return false;
}
}
}

View File

@ -0,0 +1,69 @@
using HarmonyLib;
using NewHorizons.Components.EOTE;
using System.Collections.Generic;
using System.Reflection.Emit;
using UnityEngine;
namespace NewHorizons.Patches.EchoesOfTheEyePatches
{
[HarmonyPatch(typeof(Peephole))]
public static class PeepholePatches
{
static List<Sector> _previousSectors = new List<Sector>();
[HarmonyPrefix]
[HarmonyPatch(nameof(Peephole.SwitchToPeepholeCamera))]
public static void Peephole_SwitchToPeepholeCamera_Prefix()
{
_previousSectors.Clear();
_previousSectors.AddRange(Locator.GetPlayerSectorDetector()._sectorList);
}
[HarmonyPostfix]
[HarmonyPatch(nameof(Peephole.SwitchToPeepholeCamera))]
public static void Peephole_SwitchToPeepholeCamera(Peephole __instance)
{
if (__instance._viewingSector)
{
var sector = __instance._viewingSector;
while (sector._parentSector != null)
{
sector = sector._parentSector;
if (!_previousSectors.Contains(sector))
{
sector.AddOccupant(Locator.GetPlayerSectorDetector());
}
}
}
}
[HarmonyPostfix]
[HarmonyPatch(nameof(Peephole.SwitchToPlayerCamera))]
public static void Peephole_SwitchToPlayerCamera(Peephole __instance)
{
if (__instance._viewingSector)
{
var sector = __instance._viewingSector;
if (_previousSectors.Contains(sector))
{
sector.AddOccupant(Locator.GetPlayerSectorDetector());
}
while (sector._parentSector != null)
{
sector = sector._parentSector;
if (!_previousSectors.Contains(sector))
{
sector.RemoveOccupant(Locator.GetPlayerSectorDetector());
}
}
}
_previousSectors.Clear();
}
}
}

View File

@ -1217,7 +1217,7 @@
},
"rafts": {
"type": "array",
"description": "Add rafts to this planet",
"description": "Add rafts to this planet (requires Echoes of the Eye DLC)",
"items": {
"$ref": "#/definitions/RaftInfo"
}
@ -1231,7 +1231,7 @@
},
"slideShows": {
"type": "array",
"description": "Add slideshows (from the DLC) to the planet",
"description": "Add slideshows to the planet (requires Echoes of the Eye DLC)",
"items": {
"$ref": "#/definitions/ProjectionInfo"
}
@ -1315,17 +1315,52 @@
},
"dreamCampfires": {
"type": "array",
"description": "Add campfires that allow you to enter the dream world/simulation. Must be paired with a dream arrival point, which can be placed on this planet or elsewhere.",
"description": "Add campfires that allow you to enter the dream world/simulation (requires Echoes of the Eye DLC). Must be paired with a dream arrival point, which can be placed on this planet or elsewhere.",
"items": {
"$ref": "#/definitions/DreamCampfireInfo"
}
},
"dreamArrivalPoints": {
"type": "array",
"description": "Add the points you will arrive at when entering the dream world/simulation from a paired dream campfire, which can be placed on this planet or elsewhere. The planet with the arrival point should be statically positioned to avoid issues with the simulation view materials.",
"description": "Add the points you will arrive at when entering the dream world/simulation from a paired dream campfire (requires Echoes of the Eye DLC). The planet with the arrival point should be statically positioned to avoid issues with the simulation view materials.",
"items": {
"$ref": "#/definitions/DreamArrivalPointInfo"
}
},
"grappleTotems": {
"type": "array",
"description": "Adds dream world grapple totems to this planet (requires Echoes of the Eye DLC).",
"items": {
"$ref": "#/definitions/GrappleTotemInfo"
}
},
"alarmTotems": {
"type": "array",
"description": "Adds dream world alarm totems to this planet (requires Echoes of the Eye DLC).",
"items": {
"$ref": "#/definitions/AlarmTotemInfo"
}
},
"portholes": {
"type": "array",
"description": "Adds portholes (the windows you can peek through in the Stranger) to this planet (requires Echoes of the Eye DLC).",
"items": {
"$ref": "#/definitions/PortholeInfo"
}
},
"dreamCandles": {
"type": "array",
"description": "Adds dream world candles to this planet (requires Echoes of the Eye DLC).",
"items": {
"$ref": "#/definitions/DreamCandleInfo"
}
},
"projectionTotems": {
"type": "array",
"description": "Adds dream world projection totems (requires Echoes of the Eye DLC).",
"items": {
"$ref": "#/definitions/ProjectionTotemInfo"
}
}
}
},
@ -3481,6 +3516,337 @@
}
}
},
"GrappleTotemInfo": {
"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"
},
"isRelativeToParent": {
"type": "boolean",
"description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object."
},
"parentPath": {
"type": "string",
"description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
},
"rename": {
"type": "string",
"description": "An optional rename of this object"
},
"minDistance": {
"type": "number",
"description": "The minimum distance that the player must be from the grapple totem for it to activate.",
"format": "float",
"default": 10.0
},
"arrivalDistance": {
"type": "number",
"description": "The distance from the grapple totem that the player will stop at when it activates.",
"format": "float",
"default": 4.0
},
"maxAngle": {
"type": "number",
"description": "The maximum angle in degrees allowed between the grapple totem's face and the player's lantern in order to activate the totem.",
"format": "float",
"default": 45.0
},
"maxDistance": {
"type": "number",
"description": "The maximum distance allowed between the grapple totem's face and the player's lantern in order to activate the totem.",
"format": "float",
"default": 29.0
}
}
},
"AlarmTotemInfo": {
"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"
},
"isRelativeToParent": {
"type": "boolean",
"description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object."
},
"parentPath": {
"type": "string",
"description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
},
"rename": {
"type": "string",
"description": "An optional rename of this object"
},
"sightDistance": {
"type": "number",
"description": "The maximum distance of the alarm's vision cone.",
"format": "float",
"default": 45.0
},
"sightAngle": {
"type": "number",
"description": "The width of the alarm's vision cone in degrees.",
"format": "float",
"default": 60.0
},
"stretchVisionCone": {
"description": "Scales the visible vision cone in the simulation view (does not affect the actual vision cone detection).",
"$ref": "#/definitions/MVector3"
}
}
},
"PortholeInfo": {
"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"
},
"isRelativeToParent": {
"type": "boolean",
"description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object."
},
"parentPath": {
"type": "string",
"description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
},
"rename": {
"type": "string",
"description": "An optional rename of this object"
},
"revealFacts": {
"type": "array",
"description": "Fact IDs to reveal when peeking through the porthole.",
"items": {
"type": "string"
}
},
"fieldOfView": {
"type": "number",
"description": "The field of view of the porthole camera.",
"format": "float",
"default": 90.0
},
"target": {
"description": "The location of the camera when the player peeks through the porthole. Can be placed on a different planet.",
"$ref": "#/definitions/PortholeTargetInfo"
}
}
},
"PortholeTargetInfo": {
"type": "object",
"additionalProperties": false,
"properties": {
"parentBody": {
"type": "string",
"description": "The name of the planet that will be used with `parentPath`. Must be set if `parentPath` is set."
},
"rotation": {
"description": "Rotation of the object",
"$ref": "#/definitions/MVector3"
},
"alignRadial": {
"type": [
"boolean",
"null"
],
"description": "Do we try to automatically align this object to stand upright relative to the body's center? Stacks with rotation.\nDefaults to true for geysers, tornados, and volcanoes, and false for everything else."
},
"position": {
"description": "Position of the object",
"$ref": "#/definitions/MVector3"
},
"isRelativeToParent": {
"type": "boolean",
"description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object."
},
"parentPath": {
"type": "string",
"description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
},
"rename": {
"type": "string",
"description": "An optional rename of this object"
}
}
},
"DreamCandleInfo": {
"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"
},
"isRelativeToParent": {
"type": "boolean",
"description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object."
},
"parentPath": {
"type": "string",
"description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
},
"rename": {
"type": "string",
"description": "An optional rename of this object"
},
"type": {
"description": "The type of dream candle this is.",
"default": "Ground",
"$ref": "#/definitions/DreamCandleType"
},
"startLit": {
"type": "boolean",
"description": "Whether the candle should start lit or extinguished."
}
}
},
"DreamCandleType": {
"type": "string",
"description": "",
"x-enumNames": [
"Ground",
"GroundSmall",
"GroundLarge",
"GroundSingle",
"Wall",
"WallLargeFlame",
"WallBigWick",
"Standing",
"Pile"
],
"enum": [
"ground",
"groundSmall",
"groundLarge",
"groundSingle",
"wall",
"wallLargeFlame",
"wallBigWick",
"standing",
"pile"
]
},
"ProjectionTotemInfo": {
"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"
},
"isRelativeToParent": {
"type": "boolean",
"description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object."
},
"parentPath": {
"type": "string",
"description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
},
"rename": {
"type": "string",
"description": "An optional rename of this object"
},
"startLit": {
"type": "boolean",
"description": "Whether the totem should start lit or extinguished."
},
"extinguishOnly": {
"type": "boolean",
"description": "Whether the projection totem should be able to extinguished but not be able to be lit again with the artifact. Mainly useful if `startLit` is set to true."
},
"pathToAlarmTotem": {
"type": "string",
"description": "A relative path from this planet to an alarm totem that will be activated or deactivated based on whether this totem is lit."
},
"pathsToDreamCandles": {
"type": "array",
"description": "Relative paths from this planet to objects containing dream candles that will be activated or deactivated based on whether this totem is lit. All dream candles in the selected objects will be connected to this totem, so they do not need to be specified individually if a parent object is specified.",
"items": {
"type": "string"
}
},
"pathsToProjectionTotems": {
"type": "array",
"description": "Relative paths from this planet to projection totems that will be deactivated if this totem is extinguished. All projection totems in the selected objects will be connected to this totem, so they do not need to be specified individually if a parent object is specified.",
"items": {
"type": "string"
}
},
"pathsToProjectedObjects": {
"type": "array",
"description": "Relative paths from this planet to objects that will appear or disappear when this totem is lit or extinguished. Some types of objects and effects are not supported and will remain visible and active.",
"items": {
"type": "string"
}
},
"toggleProjectedObjectsActive": {
"type": "boolean",
"description": "If set, projected objects will be set to fully active or fully disabled instantly instead of smoothly fading lights/renderers/colliders. Use this if the normal behavior is insufficient for the objects you're using."
}
}
},
"ReferenceFrameModule": {
"type": "object",
"additionalProperties": false,