Merge pull request #510 from misternebula/eote-ghost-ai-multiple-players

Ghost AI
This commit is contained in:
_nebula 2022-04-21 14:57:38 +01:00 committed by GitHub
commit 1d17a96500
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
72 changed files with 5482 additions and 162 deletions

View File

@ -28,7 +28,7 @@ public class QSBAngler : LinkedWorldObject<AnglerfishController, AnglerTransform
return;
}
TargetVelocity = TargetTransform.position - _lastTargetPosition;
TargetVelocity = (TargetTransform.position - _lastTargetPosition) / Time.fixedDeltaTime;
_lastTargetPosition = TargetTransform.position;
}
}

Binary file not shown.

View File

@ -1,7 +0,0 @@
namespace QSB.EchoesOfTheEye.DreamLantern;
public enum DreamLanternActionType
{
FOCUS,
CONCEAL
}

View File

@ -0,0 +1,15 @@
using Cysharp.Threading.Tasks;
using QSB.EchoesOfTheEye.DreamLantern.WorldObjects;
using QSB.WorldSync;
using System.Threading;
namespace QSB.EchoesOfTheEye.DreamLantern;
public class DreamLanternManager : WorldObjectManager
{
public override WorldObjectScene WorldObjectScene => WorldObjectScene.SolarSystem;
public override bool DlcOnly => true;
public override async UniTask BuildWorldObjects(OWScene scene, CancellationToken ct) =>
QSBWorldSync.Init<QSBDreamLantern, DreamLanternController>();
}

View File

@ -1,35 +0,0 @@
using QSB.ItemSync.WorldObjects.Items;
using QSB.Messaging;
using QSB.Player;
using QSB.Utility;
namespace QSB.EchoesOfTheEye.DreamLantern.Messages;
internal class DreamLanternStateMessage : QSBMessage<(DreamLanternActionType Type, bool BoolValue, float FloatValue)>
{
public DreamLanternStateMessage(DreamLanternActionType actionType, bool boolValue = false, float floatValue = 0f)
: base((actionType, boolValue, floatValue)) { }
public override void OnReceiveRemote()
{
var heldItem = QSBPlayerManager.GetPlayer(From).HeldItem;
if (heldItem is not QSBDreamLanternItem lantern)
{
DebugLog.ToConsole($"Error - Got DreamLanternStateMessage from player {From}, but they are not holding a QSBDreamLanternItem!");
return;
}
var controller = lantern.AttachedObject._lanternController;
switch (Data.Type)
{
case DreamLanternActionType.CONCEAL:
controller.SetConcealed(Data.BoolValue);
break;
case DreamLanternActionType.FOCUS:
controller.SetFocus(Data.FloatValue);
break;
}
}
}

View File

@ -0,0 +1,13 @@
using QSB.EchoesOfTheEye.DreamLantern.WorldObjects;
using QSB.Messaging;
using QSB.Patches;
namespace QSB.EchoesOfTheEye.DreamLantern.Messages;
internal class SetConcealedMessage : QSBWorldObjectMessage<QSBDreamLantern, bool>
{
public SetConcealedMessage(bool concealed) : base(concealed) { }
public override void OnReceiveRemote()
=> QSBPatch.RemoteCall(() => WorldObject.AttachedObject.SetConcealed(Data));
}

View File

@ -0,0 +1,13 @@
using QSB.EchoesOfTheEye.DreamLantern.WorldObjects;
using QSB.Messaging;
using QSB.Patches;
namespace QSB.EchoesOfTheEye.DreamLantern.Messages;
internal class SetFocusMessage : QSBWorldObjectMessage<QSBDreamLantern, float>
{
public SetFocusMessage(float focus) : base(focus) { }
public override void OnReceiveRemote()
=> QSBPatch.RemoteCall(() => WorldObject.AttachedObject.SetFocus(Data));
}

View File

@ -1,12 +1,12 @@
using QSB.ItemSync.WorldObjects.Items;
using QSB.EchoesOfTheEye.DreamLantern.WorldObjects;
using QSB.Messaging;
using QSB.Patches;
namespace QSB.EchoesOfTheEye.DreamLantern.Messages;
internal class DreamLanternLitMessage : QSBWorldObjectMessage<QSBDreamLanternItem, bool>
internal class SetLitMessage : QSBWorldObjectMessage<QSBDreamLantern, bool>
{
public DreamLanternLitMessage(bool lit) : base(lit) { }
public SetLitMessage(bool lit) : base(lit) { }
public override void OnReceiveRemote()
=> QSBPatch.RemoteCall(() => WorldObject.AttachedObject.SetLit(Data));

View File

@ -0,0 +1,13 @@
using QSB.EchoesOfTheEye.DreamLantern.WorldObjects;
using QSB.Messaging;
using QSB.Patches;
namespace QSB.EchoesOfTheEye.DreamLantern.Messages;
internal class SetRangeMessage : QSBWorldObjectMessage<QSBDreamLantern, (float minRange, float maxRange)>
{
public SetRangeMessage(float minRange, float maxRange) : base((minRange, maxRange)) { }
public override void OnReceiveRemote()
=> QSBPatch.RemoteCall(() => WorldObject.AttachedObject.SetRange(Data.minRange, Data.maxRange));
}

View File

@ -1,6 +1,6 @@
using HarmonyLib;
using QSB.EchoesOfTheEye.DreamLantern.Messages;
using QSB.ItemSync.WorldObjects.Items;
using QSB.EchoesOfTheEye.DreamLantern.WorldObjects;
using QSB.Messaging;
using QSB.Patches;
using QSB.WorldSync;
@ -13,73 +13,15 @@ internal class DreamLanternPatches : QSBPatch
public override QSBPatchTypes Type => QSBPatchTypes.OnClientConnect;
[HarmonyPrefix]
[HarmonyPatch(typeof(DreamLanternItem), nameof(DreamLanternItem.Update))]
public static bool UpdateReplacement(DreamLanternItem __instance)
{
var isHoldingItem = Locator.GetToolModeSwapper().IsInToolMode(ToolMode.Item);
__instance._wasFocusing = __instance._focusing;
__instance._focusing = OWInput.IsPressed(InputLibrary.toolActionPrimary, InputMode.Character) && Time.time > __instance._forceUnfocusTime + 1f && isHoldingItem;
var concealActionPressed = OWInput.IsPressed(InputLibrary.toolActionSecondary, InputMode.Character) && isHoldingItem;
if (concealActionPressed && !__instance._lanternController.IsConcealed())
{
Locator.GetPlayerAudioController().OnArtifactConceal();
__instance._lanternController.SetConcealed(true);
new DreamLanternStateMessage(DreamLanternActionType.CONCEAL, true).Send();
}
else if (!concealActionPressed && __instance._lanternController.IsConcealed())
{
Locator.GetPlayerAudioController().OnArtifactUnconceal();
__instance._lanternController.SetConcealed(false);
new DreamLanternStateMessage(DreamLanternActionType.CONCEAL).Send();
}
if (__instance._focusing != __instance._wasFocusing)
{
if (__instance._focusing)
{
Locator.GetPlayerAudioController().OnArtifactFocus();
}
else
{
Locator.GetPlayerAudioController().OnArtifactUnfocus();
}
}
__instance.UpdateFocus();
return false;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(DreamLanternController), nameof(DreamLanternController.MoveTowardFocus))]
public static bool UpdateFocusReplacement(DreamLanternController __instance, float targetFocus, float rate)
{
var value = Mathf.MoveTowards(__instance._focus, targetFocus, rate * Time.deltaTime);
if (__instance._focus == value)
{
__instance.SetFocus(value);
return false;
}
__instance.SetFocus(value);
new DreamLanternStateMessage(DreamLanternActionType.FOCUS, floatValue: value).Send();
return false;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(DreamLanternItem), nameof(DreamLanternItem.SetLit))]
public static void SetLit(DreamLanternItem __instance, bool lit)
[HarmonyPatch(typeof(DreamLanternController), nameof(DreamLanternController.SetLit))]
public static void SetLit(DreamLanternController __instance, bool lit)
{
if (Remote)
{
return;
}
if (__instance._lanternController.IsLit() == lit)
if (__instance._lit == lit)
{
return;
}
@ -89,6 +31,73 @@ internal class DreamLanternPatches : QSBPatch
return;
}
__instance.GetWorldObject<QSBDreamLanternItem>().SendMessage(new DreamLanternLitMessage(lit));
__instance.GetWorldObject<QSBDreamLantern>().SendMessage(new SetLitMessage(lit));
}
[HarmonyPrefix]
[HarmonyPatch(typeof(DreamLanternController), nameof(DreamLanternController.SetConcealed))]
public static void SetConcealed(DreamLanternController __instance, bool concealed)
{
if (Remote)
{
return;
}
if (__instance._concealed == concealed)
{
return;
}
if (!QSBWorldSync.AllObjectsReady)
{
return;
}
__instance.GetWorldObject<QSBDreamLantern>().SendMessage(new SetConcealedMessage(concealed));
}
[HarmonyPrefix]
[HarmonyPatch(typeof(DreamLanternController), nameof(DreamLanternController.SetFocus))]
public static void SetFocus(DreamLanternController __instance, float focus)
{
if (Remote)
{
return;
}
focus = Mathf.Clamp01(focus);
if (OWMath.ApproxEquals(__instance._focus, focus))
{
return;
}
if (!QSBWorldSync.AllObjectsReady)
{
return;
}
__instance.GetWorldObject<QSBDreamLantern>().SendMessage(new SetFocusMessage(focus));
}
[HarmonyPrefix]
[HarmonyPatch(typeof(DreamLanternController), nameof(DreamLanternController.SetRange))]
public static void SetRange(DreamLanternController __instance, float minRange, float maxRange)
{
if (Remote)
{
return;
}
if (OWMath.ApproxEquals(__instance._minRange, minRange) && OWMath.ApproxEquals(__instance._maxRange, maxRange))
{
return;
}
if (!QSBWorldSync.AllObjectsReady)
{
return;
}
__instance.GetWorldObject<QSBDreamLantern>().SendMessage(new SetRangeMessage(minRange, maxRange));
}
}

View File

@ -0,0 +1,16 @@
using QSB.EchoesOfTheEye.DreamLantern.Messages;
using QSB.Messaging;
using QSB.WorldSync;
namespace QSB.EchoesOfTheEye.DreamLantern.WorldObjects;
public class QSBDreamLantern : WorldObject<DreamLanternController>
{
public override void SendInitialState(uint to)
{
this.SendMessage(new SetLitMessage(AttachedObject._lit) { To = to });
this.SendMessage(new SetConcealedMessage(AttachedObject._concealed) { To = to });
this.SendMessage(new SetFocusMessage(AttachedObject._focus) { To = to });
this.SendMessage(new SetRangeMessage(AttachedObject._minRange, AttachedObject._maxRange) { To = to });
}
}

View File

@ -0,0 +1,79 @@
using GhostEnums;
using QSB.EchoesOfTheEye.Ghosts.Messages;
using QSB.EchoesOfTheEye.Ghosts.WorldObjects;
using QSB.Messaging;
using QSB.WorldSync;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace QSB.EchoesOfTheEye.Ghosts.Actions;
internal class QSBChaseAction : QSBGhostAction
{
private float _lastScreamTime;
public override GhostAction.Name GetName()
{
return GhostAction.Name.Chase;
}
public override float CalculateUtility()
{
if (_data.interestedPlayer == null)
{
return -100f;
}
if (_data.threatAwareness < GhostData.ThreatAwareness.IntruderConfirmed || (PlayerData.GetReducedFrights() && !_data.reducedFrights_allowChase))
{
return -100f;
}
var canSeePlayer = _data.interestedPlayer.sensor.isPlayerVisible
|| _data.interestedPlayer.sensor.isPlayerHeldLanternVisible
|| _data.interestedPlayer.sensor.inContactWithPlayer;
if ((_running
&& _data.interestedPlayer.timeSincePlayerLocationKnown < 5f)
|| (canSeePlayer && _data.interestedPlayer.playerLocation.distance < _data.interestedPlayer.playerMinLanternRange + 0.5f))
{
return 95f;
}
return -100f;
}
protected override void OnEnterAction()
{
_controller.SetLanternConcealed(false, true);
_effects.SetMovementStyle(GhostEffects.MovementStyle.Chase);
if (Time.time > _lastScreamTime + 10f && !PlayerData.GetReducedFrights())
{
_effects.PlayVoiceAudioNear(global::AudioType.Ghost_Chase, 1f);
_lastScreamTime = Time.time;
}
}
public override bool Update_Action()
{
if (_data.interestedPlayer.playerLocation.distance > 10f
&& !_controller.AttachedObject.GetNodeMap().CheckLocalPointInBounds(_data.interestedPlayer.lastKnownPlayerLocation.localPosition))
{
return false;
}
if (!QSBCore.IsHost)
{
return true;
}
var worldPos = _controller.AttachedObject.LocalToWorldPosition(_data.interestedPlayer.lastKnownPlayerLocation.localPosition);
_controller.PathfindToLocalPosition(_data.interestedPlayer.lastKnownPlayerLocation.localPosition, MoveType.CHASE);
_controller.FaceLocalPosition(_data.interestedPlayer.lastKnownPlayerLocation.localPosition, TurnSpeed.FAST);
return true;
}
}

View File

@ -0,0 +1,91 @@
using GhostEnums;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace QSB.EchoesOfTheEye.Ghosts.Actions;
public class QSBElevatorWalkAction : QSBGhostAction
{
private bool _reachedEndOfPath;
private bool _calledToElevator;
private bool _hasUsedElevator;
private GhostNode _elevatorNode;
public bool reachedEndOfPath
{
get
{
return this._reachedEndOfPath;
}
}
public override GhostAction.Name GetName()
{
return GhostAction.Name.ElevatorWalk;
}
public override float CalculateUtility()
{
if (this._calledToElevator && !this._hasUsedElevator && (_data.interestedPlayer == null || !this._data.interestedPlayer.isPlayerLocationKnown))
{
return 100f;
}
if (this._calledToElevator && !this._hasUsedElevator)
{
return 70f;
}
return -100f;
}
public void UseElevator()
{
this._hasUsedElevator = true;
}
public void CallToUseElevator()
{
this._calledToElevator = true;
if (this._controller.AttachedObject.GetNodeMap().GetPathNodes().Length > 1)
{
this._elevatorNode = this._controller.AttachedObject.GetNodeMap().GetPathNodes()[1];
this._controller.PathfindToNode(this._elevatorNode, MoveType.PATROL);
return;
}
Debug.LogError("MissingElevatorNode");
}
protected override void OnEnterAction()
{
this._controller.SetLanternConcealed(true, true);
this._controller.FaceVelocity();
this._effects.PlayDefaultAnimation();
this._effects.SetMovementStyle(GhostEffects.MovementStyle.Normal);
if (this._elevatorNode != null)
{
this._controller.PathfindToNode(this._elevatorNode, MoveType.PATROL);
}
}
protected override void OnExitAction()
{
}
public override bool Update_Action()
{
return true;
}
public override void OnArriveAtPosition()
{
this._reachedEndOfPath = true;
}
}

View File

@ -0,0 +1,48 @@
using System;
namespace QSB.EchoesOfTheEye.Ghosts.Actions;
public class QSBGhostActionStub : QSBGhostAction
{
public GhostAction.Name Name;
public override GhostAction.Name GetName()
{
return Name;
}
public override float CalculateUtility()
{
throw new NotImplementedException();
}
public override bool IsInterruptible()
{
throw new NotImplementedException();
}
protected override void OnEnterAction()
{
throw new NotImplementedException();
}
protected override void OnExitAction()
{
throw new NotImplementedException();
}
public override bool Update_Action()
{
throw new NotImplementedException();
}
public override void FixedUpdate_Action()
{
throw new NotImplementedException();
}
public override void OnArriveAtPosition()
{
throw new NotImplementedException();
}
}

View File

@ -0,0 +1,107 @@
using GhostEnums;
using QSB.Utility;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace QSB.EchoesOfTheEye.Ghosts.Actions;
internal class QSBGrabAction : QSBGhostAction
{
private bool _playerIsGrabbed;
private bool _grabAnimComplete;
public override GhostAction.Name GetName()
{
return GhostAction.Name.Grab;
}
public override float CalculateUtility()
{
if (_data.interestedPlayer == null)
{
return -100f;
}
if (PlayerState.IsAttached() || !_data.interestedPlayer.sensor.inContactWithPlayer)
{
return -100f;
}
return 100f;
}
public override bool IsInterruptible()
{
return false;
}
protected override void OnEnterAction()
{
_effects.SetMovementStyle(GhostEffects.MovementStyle.Chase);
_effects.PlayGrabAnimation();
_effects.AttachedObject.OnGrabComplete += OnGrabComplete;
_controller.SetLanternConcealed(false, true);
_controller.ChangeLanternFocus(0f, 2f);
if (_data.previousAction != GhostAction.Name.Chase)
{
_effects.PlayVoiceAudioNear((_data.interestedPlayer.sensor.isPlayerVisible || PlayerData.GetReducedFrights()) ? AudioType.Ghost_Grab_Shout : AudioType.Ghost_Grab_Scream, 1f);
}
}
protected override void OnExitAction()
{
_effects.PlayDefaultAnimation();
_playerIsGrabbed = false;
_grabAnimComplete = false;
_effects.AttachedObject.OnGrabComplete -= OnGrabComplete;
}
public override bool Update_Action()
{
if (_playerIsGrabbed)
{
return true;
}
if (_data.interestedPlayer.playerLocation.distanceXZ > 1.7f)
{
_controller.MoveToLocalPosition(_data.interestedPlayer.playerLocation.localPosition, MoveType.GRAB);
}
_controller.FaceLocalPosition(_data.interestedPlayer.playerLocation.localPosition, TurnSpeed.FASTEST);
if (_sensors.CanGrabPlayer(_data.interestedPlayer))
{
DebugLog.DebugWrite($"Grab player {_data.interestedPlayer.player.PlayerId}!");
GrabPlayer();
}
else
{
DebugLog.DebugWrite($"can't grab player {_data.interestedPlayer.player.PlayerId}");
}
return !_grabAnimComplete;
}
private void GrabPlayer()
{
_playerIsGrabbed = true;
_controller.StopMovingInstantly();
_controller.StopFacing();
_controller.SetLanternConcealed(true, false);
_controller.AttachedObject.GetGrabController().GrabPlayer(1f);
}
private void OnGrabComplete()
{
_grabAnimComplete = true;
}
public bool isPlayerGrabbed()
{
return _playerIsGrabbed;
}
}

View File

@ -0,0 +1,159 @@
using GhostEnums;
using QSB.EchoesOfTheEye.Ghosts;
using System.Linq;
using UnityEngine;
/// <summary>
///
/// </summary>
public class QSBGuardAction : QSBGhostAction
{
private GhostNode _targetSearchNode;
private bool _searchingAtNode;
private bool _watchingPlayer;
private float _searchStartTime;
private float _lastSawPlayer;
private GhostNode[] _searchNodes;
private bool _hasReachedAnySearchNode;
public override GhostAction.Name GetName()
{
return GhostAction.Name.Guard;
}
public override float CalculateUtility()
{
if (!_controller.AttachedObject.GetNodeMap().HasSearchNodes(_controller.AttachedObject.GetNodeLayer()))
{
return -100f;
}
if (_data.threatAwareness < GhostData.ThreatAwareness.SomeoneIsInHere)
{
return -100f;
}
if (_data.reduceGuardUtility)
{
return 60f;
}
return 90f;
}
protected override void OnEnterAction()
{
_controller.SetLanternConcealed(true, true);
_sensors.AttachedObject.SetContactEdgeCatcherWidth(5f);
_effects.SetMovementStyle(GhostEffects.MovementStyle.Normal);
ContinueSearch();
}
protected override void OnExitAction()
{
if (_searchingAtNode)
{
_controller.FaceVelocity();
}
_sensors.AttachedObject.ResetContactEdgeCatcherWidth();
_targetSearchNode = null;
_searchingAtNode = false;
_watchingPlayer = false;
}
public override bool Update_Action()
{
if (_searchingAtNode && Time.time > _searchStartTime + 4f)
{
_controller.FaceVelocity();
_targetSearchNode.searchData.lastSearchTime = Time.time;
ContinueSearch();
}
var anyPlayerVisible = _data.players.Values.Any(x => x.sensor.isPlayerVisible);
var anyPlayerLanternVisible = _data.players.Values.Any(x => x.sensor.isPlayerHeldLanternVisible);
var flag = _hasReachedAnySearchNode && (anyPlayerVisible || anyPlayerLanternVisible);
if (flag)
{
_lastSawPlayer = Time.time;
}
if (!_watchingPlayer && flag)
{
_watchingPlayer = true;
_searchingAtNode = false;
_controller.StopMoving();
_controller.FacePlayer(_data.interestedPlayer.player, TurnSpeed.MEDIUM);
}
else if (_watchingPlayer && !flag && Time.time - _lastSawPlayer > 1f)
{
_watchingPlayer = false;
ContinueSearch();
}
return true;
}
public override void OnArriveAtPosition()
{
if (_searchNodes != null && _searchNodes.Length == 1)
{
_controller.FaceLocalPosition(_targetSearchNode.localPosition + (_targetSearchNode.localForward * 10f), TurnSpeed.MEDIUM);
}
else
{
_controller.AttachedObject.Spin(TurnSpeed.MEDIUM);
}
_searchingAtNode = true;
_hasReachedAnySearchNode = true;
_searchStartTime = Time.time;
}
private void ContinueSearch()
{
_searchingAtNode = false;
_targetSearchNode = GetHighestPriorityNodeToSearch();
if (_targetSearchNode == null)
{
Debug.LogError("Failed to find any nodes to search! Did we exhaust our existing options?", _controller.AttachedObject);
Debug.Break();
}
_controller.PathfindToNode(_targetSearchNode, MoveType.SEARCH);
_controller.FaceVelocity();
}
private GhostNode GetHighestPriorityNodeToSearch()
{
_searchNodes = _controller.AttachedObject.GetNodeMap().GetSearchNodesOnLayer(_controller.AttachedObject.GetNodeLayer());
var num = 0f;
var time = Time.time;
for (var i = 0; i < _searchNodes.Length; i++)
{
var num2 = time - _searchNodes[i].searchData.lastSearchTime;
num += num2;
}
num /= (float)_searchNodes.Length;
GhostNode ghostNode = null;
for (var j = 0; j < 5; j++)
{
ghostNode = _searchNodes[Random.Range(0, _searchNodes.Length)];
if (time - ghostNode.searchData.lastSearchTime > num)
{
break;
}
}
return ghostNode;
}
}

View File

@ -0,0 +1,299 @@
using System;
using System.Collections.Generic;
using GhostEnums;
using QSB.EchoesOfTheEye.Ghosts;
using QSB.EchoesOfTheEye.Ghosts.WorldObjects;
using QSB.Utility;
using UnityEngine;
public class QSBHuntAction : QSBGhostAction
{
private int _numNodesToSearch;
private GhostNodeMap.NodeSearchData[] _nodesToSearch;
private int _currentNodeIndex;
private GhostNode _closestNode;
private bool _startAtClosestNode;
private bool _huntStarted;
private float _huntStartTime;
private bool _huntFailed;
private float _huntFailTime;
private List<int> _spotlightIndexList = new List<int>(16);
private int _spotlightIndex = -1;
public override void Initialize(QSBGhostBrain brain)
{
base.Initialize(brain);
_numNodesToSearch = 0;
_nodesToSearch = new GhostNodeMap.NodeSearchData[_controller.AttachedObject.GetNodeMap().GetNodeCount()];
_currentNodeIndex = 0;
_huntStarted = false;
_huntStartTime = 0f;
_huntFailed = false;
_huntFailTime = 0f;
_controller.AttachedObject.OnNodeMapChanged += new OWEvent.OWCallback(OnNodeMapChanged);
}
private void OnNodeMapChanged()
{
if (_running)
{
Debug.LogError("Changing node maps while the Hunt action is running is almost definitely not supported!");
_huntFailed = true;
}
_numNodesToSearch = 0;
_nodesToSearch = new GhostNodeMap.NodeSearchData[_controller.AttachedObject.GetNodeMap().GetNodeCount()];
_currentNodeIndex = 0;
}
public override GhostAction.Name GetName()
{
return GhostAction.Name.Hunt;
}
public override float CalculateUtility()
{
if (_data.interestedPlayer == null)
{
return -100f;
}
if (_data.threatAwareness < GhostData.ThreatAwareness.IntruderConfirmed)
{
return -100f;
}
if (_huntFailed && _huntFailTime > _data.interestedPlayer.timeLastSawPlayer)
{
return -100f;
}
if (_running || _data.interestedPlayer.timeSincePlayerLocationKnown < 60f)
{
return 80f;
}
return -100f;
}
protected override void OnEnterAction()
{
_controller.SetLanternConcealed(true, true);
_controller.FaceVelocity();
_effects.SetMovementStyle(GhostEffects.MovementStyle.Normal);
if (!_huntStarted || _data.interestedPlayer.timeLastSawPlayer > _huntStartTime)
{
var knownPlayerVelocity = _data.interestedPlayer.lastKnownSensor.knowsPlayerVelocity ? _data.interestedPlayer.lastKnownPlayerLocation.localVelocity : Vector3.zero;
_numNodesToSearch = _controller.AttachedObject.GetNodeMap().FindPossiblePlayerNodes(_data.interestedPlayer.lastKnownPlayerLocation.localPosition, knownPlayerVelocity, 30f, _nodesToSearch, true, null, null, null);
_currentNodeIndex = 0;
_startAtClosestNode = false;
_closestNode = null;
_huntStarted = true;
_huntStartTime = Time.time;
_huntFailed = false;
if (_numNodesToSearch == 0)
{
DebugLog.DebugWrite($"{_brain.AttachedObject._name} : Failed to find nodes to hunt player.", OWML.Common.MessageType.Error);
_huntFailed = true;
_huntFailTime = Time.time;
}
}
if (!_huntFailed)
{
_closestNode = _controller.AttachedObject.GetNodeMap().FindClosestNode(_controller.AttachedObject.GetLocalFeetPosition());
for (var i = 0; i < _closestNode.visibleNodes.Count; i++)
{
for (var j = 0; j < _numNodesToSearch; j++)
{
if (_closestNode.visibleNodes[i] == _nodesToSearch[j].node.index)
{
_startAtClosestNode = true;
break;
}
}
}
if (_startAtClosestNode)
{
_controller.PathfindToNode(_closestNode, MoveType.SEARCH);
}
else
{
_controller.PathfindToNode(_nodesToSearch[_currentNodeIndex].node, MoveType.SEARCH);
}
_effects.PlayVoiceAudioNear(AudioType.Ghost_Hunt, 1f);
}
}
protected override void OnExitAction()
{
if (_huntFailed && !_data.interestedPlayer.isPlayerLocationKnown)
{
DebugLog.DebugWrite($"{_brain.AttachedObject._name} : Hunt failed. :(");
_effects.PlayVoiceAudioNear(AudioType.Ghost_HuntFail, 1f);
}
}
public override bool Update_Action()
{
return !_huntFailed && !_data.interestedPlayer.isPlayerLocationKnown;
}
public override void FixedUpdate_Action()
{
if (_huntStarted && !_huntFailed && _spotlightIndexList.Count > 0 && !_controller.AttachedObject.GetDreamLanternController().IsConcealed())
{
for (var i = 0; i < _spotlightIndexList.Count; i++)
{
if (!_nodesToSearch[_spotlightIndexList[i]].searched)
{
var from = _nodesToSearch[_spotlightIndexList[i]].node.localPosition - _controller.AttachedObject.GetLocalFeetPosition();
var light = _controller.AttachedObject.GetDreamLanternController().GetLight();
var to = _controller.AttachedObject.WorldToLocalDirection(light.transform.forward);
if (Vector3.Angle(from, to) < (light.GetLight().spotAngle * 0.5f) - 5f && from.sqrMagnitude < light.range * light.range)
{
_nodesToSearch[_spotlightIndexList[i]].searched = true;
}
}
}
}
}
public override void OnTraversePathNode(GhostNode node)
{
for (var i = 0; i < _numNodesToSearch; i++)
{
if (node == _nodesToSearch[i].node)
{
_nodesToSearch[i].searched = true;
}
}
}
public override void OnArriveAtPosition()
{
GhostNode node;
if (_startAtClosestNode)
{
_startAtClosestNode = false;
node = _closestNode;
for (var i = 0; i < _numNodesToSearch; i++)
{
if (_closestNode == _nodesToSearch[i].node)
{
_nodesToSearch[i].searched = true;
break;
}
}
}
else
{
node = _nodesToSearch[_currentNodeIndex].node;
_nodesToSearch[_currentNodeIndex].searched = true;
}
GenerateSpotlightList(node);
if (_spotlightIndexList.Count > 0)
{
_controller.SetLanternConcealed(false, true);
SpotlightNextNode();
return;
}
TryContinueSearch();
}
public override void OnFaceNode(GhostNode node)
{
var num = _spotlightIndexList[_spotlightIndex];
if (node != _nodesToSearch[num].node)
{
Debug.LogError("Why are we facing this node??? " + node.name);
Debug.Break();
return;
}
_nodesToSearch[num].searched = true;
for (var i = _spotlightIndexList.Count - 1; i >= 0; i--)
{
if (_nodesToSearch[_spotlightIndexList[i]].searched)
{
_spotlightIndexList.RemoveAt(i);
}
}
if (_spotlightIndexList.Count > 0)
{
SpotlightNextNode();
return;
}
_controller.SetLanternConcealed(true, true);
_controller.FaceVelocity();
TryContinueSearch();
}
private void SpotlightNextNode()
{
_spotlightIndex = 0;
var num = _spotlightIndexList[_spotlightIndex];
_controller.FaceNode(_nodesToSearch[num].node, TurnSpeed.MEDIUM, 1f, true);
}
private void TryContinueSearch()
{
if (Time.time > _enterTime + 60f)
{
_huntFailed = true;
_huntFailTime = Time.time;
return;
}
while (_nodesToSearch[_currentNodeIndex].searched && _currentNodeIndex < _numNodesToSearch)
{
_currentNodeIndex++;
}
if (_currentNodeIndex < _numNodesToSearch)
{
DebugLog.DebugWrite($"{_brain.AttachedObject._name} : Moving to hunt at new node.");
_controller.PathfindToNode(_nodesToSearch[_currentNodeIndex].node, MoveType.SEARCH);
return;
}
_huntFailed = true;
_huntFailTime = Time.time;
}
private void GenerateSpotlightList(GhostNode node)
{
_spotlightIndexList.Clear();
for (var i = 0; i < node.visibleNodes.Count; i++)
{
for (var j = 0; j < _numNodesToSearch; j++)
{
if (!_nodesToSearch[j].searched && node.visibleNodes[i] == _nodesToSearch[j].node.index)
{
_spotlightIndexList.Add(j);
}
}
}
}
public override void DrawGizmos(bool isGhostSelected)
{
if (isGhostSelected)
{
for (var i = 0; i < _numNodesToSearch; i++)
{
var t = Mathf.Abs(_nodesToSearch[i].score) / 180f;
Popcron.Gizmos.Sphere(
_controller.AttachedObject.LocalToWorldPosition(_nodesToSearch[i].node.localPosition),
(i < _currentNodeIndex) ? 0.5f : 2f,
_nodesToSearch[i].searched ? Color.black : Color.HSVToRGB(Mathf.Lerp(0.5f, 0f, t), 1f, 1f));
}
}
}
}

View File

@ -0,0 +1,298 @@
using System;
using GhostEnums;
using QSB;
using QSB.EchoesOfTheEye.Ghosts;
using QSB.EchoesOfTheEye.Ghosts.Messages;
using QSB.EchoesOfTheEye.Ghosts.WorldObjects;
using QSB.Messaging;
using QSB.Utility;
using QSB.WorldSync;
using UnityEngine;
public class QSBIdentifyIntruderAction : QSBGhostAction
{
private const int AGGRO_THRESHOLD = 2;
private static GhostNode[] s_nodesToSpotlight = new GhostNode[8];
private int _numTimesIlluminatedByPlayer;
private bool _allowFocusBeam;
private bool _sawPlayerOccluded;
private bool _movingToSearchLocation;
private bool _arrivedAtTargetSearchPosition;
private bool _searchNodesNearTarget;
private bool _searchNodesComplete;
private bool _checkingTargetLocation;
private Vector3 _searchStartPosition;
private Vector3 _searchPosition;
private GhostNode _searchNode;
private float _checkTimer;
public override GhostAction.Name GetName()
{
return GhostAction.Name.IdentifyIntruder;
}
public override float CalculateUtility()
{
if (_data.interestedPlayer == null)
{
return -100f;
}
if (_data.threatAwareness >= GhostData.ThreatAwareness.IntruderConfirmed)
{
return -100f;
}
if (_running || (_data.interestedPlayer.sensor.isPlayerHeldLanternVisible && (_data.threatAwareness > GhostData.ThreatAwareness.EverythingIsNormal || _data.interestedPlayer.playerLocation.distance < 20f)) || _data.interestedPlayer.sensor.isIlluminatedByPlayer)
{
return 80f;
}
return -100f;
}
public override float GetActionDelay()
{
if (_data.interestedPlayer.playerLocation.distance < 8f)
{
return 0.1f;
}
return 0.5f;
}
public override void OnSetAsPending()
{
if (_data.interestedPlayer.sensor.isIlluminatedByPlayer)
{
_numTimesIlluminatedByPlayer++;
_allowFocusBeam = true;
}
}
protected override void OnEnterAction()
{
_sawPlayerOccluded = false;
_movingToSearchLocation = false;
_arrivedAtTargetSearchPosition = false;
_searchNodesNearTarget = false;
_searchNodesComplete = false;
_checkingTargetLocation = false;
_checkTimer = 0f;
_effects.PlayVoiceAudioNear((_numTimesIlluminatedByPlayer <= 2) ? AudioType.Ghost_Identify_Curious : AudioType.Ghost_Identify_Irritated, 1f);
}
protected override void OnExitAction()
{
_controller.FaceVelocity();
_allowFocusBeam = false;
}
public override bool Update_Action()
{
if (_checkingTargetLocation && !_data.interestedPlayer.isPlayerLocationKnown && _controller.AttachedObject.GetSpeed() < 0.1f)
{
_checkTimer += Time.deltaTime;
}
else
{
_checkTimer = 0f;
}
if ((_searchNodesNearTarget && _checkTimer > 1f) || _checkTimer > 3f)
{
DebugLog.DebugWrite($"{_brain.AttachedObject._name} : Couldn't identify target. :(");
_effects.PlayVoiceAudioNear(AudioType.Ghost_Identify_Fail, 1f);
return false;
}
return true;
}
public override void FixedUpdate_Action()
{
_checkingTargetLocation = false;
if (!_data.interestedPlayer.wasPlayerLocationKnown && _data.interestedPlayer.isPlayerLocationKnown && _data.interestedPlayer.sensor.isIlluminatedByPlayer)
{
_numTimesIlluminatedByPlayer++;
}
if (!_allowFocusBeam && _data.interestedPlayer.sensor.isIlluminatedByPlayer)
{
_allowFocusBeam = true;
}
var flag = !_data.interestedPlayer.lastKnownSensor.isPlayerVisible && _data.interestedPlayer.lastKnownSensor.isIlluminatedByPlayer && _numTimesIlluminatedByPlayer > 2;
if (_data.interestedPlayer.isPlayerLocationKnown)
{
_sawPlayerOccluded = false;
_movingToSearchLocation = false;
_arrivedAtTargetSearchPosition = false;
_searchNodesNearTarget = false;
_searchNodesComplete = false;
}
else if (!_movingToSearchLocation && (_data.interestedPlayer.lostPlayerDueToOcclusion || flag))
{
_movingToSearchLocation = true;
_sawPlayerOccluded = _data.interestedPlayer.lostPlayerDueToOcclusion;
_controller.ChangeLanternFocus(0f, 2f);
_controller.SetLanternConcealed(true, true);
if (_allowFocusBeam)
{
_searchNodesNearTarget = true;
_searchStartPosition = _controller.AttachedObject.GetLocalFeetPosition();
_searchNode = _controller.AttachedObject.GetNodeMap().FindClosestNode(_data.interestedPlayer.lastKnownPlayerLocation.localPosition);
_controller.PathfindToLocalPosition(_searchNode.localPosition, MoveType.INVESTIGATE);
}
else
{
_searchNodesNearTarget = false;
_controller.PathfindToLocalPosition(_data.interestedPlayer.lastKnownPlayerLocation.localPosition, MoveType.INVESTIGATE);
}
_controller.FaceVelocity();
}
if (_movingToSearchLocation)
{
if (_arrivedAtTargetSearchPosition)
{
if (_searchNodesNearTarget)
{
_checkingTargetLocation = _searchNodesComplete;
return;
}
if (_controller.AttachedObject.GetAngleToLocalPosition(_searchPosition) < 5f)
{
_controller.SetLanternConcealed(false, true);
_checkingTargetLocation = true;
return;
}
}
}
else
{
var playerLocationToCheck = _data.interestedPlayer.lastKnownPlayerLocation.localPosition + new Vector3(0f, 1.8f, 0f);
var canSeePlayerCheckLocation = _sensors.AttachedObject.CheckPositionOccluded(_controller.AttachedObject.LocalToWorldPosition(playerLocationToCheck));
var lanternRange = _allowFocusBeam
? (_controller.AttachedObject.GetFocusedLanternRange() - 3f)
: (_controller.AttachedObject.GetUnfocusedLanternRange() - 1f);
var isLastKnownLocationInRange = _data.interestedPlayer.lastKnownPlayerLocation.distance < _controller.AttachedObject.GetUnfocusedLanternRange();
if (_data.interestedPlayer.sensor.isPlayerIlluminatedByUs)
{
_allowFocusBeam = true;
_controller.FaceLocalPosition(_data.interestedPlayer.lastKnownPlayerLocation.localPosition, TurnSpeed.MEDIUM);
if (isLastKnownLocationInRange == _controller.AttachedObject.IsLanternFocused())
{
_controller.ChangeLanternFocus(isLastKnownLocationInRange ? 0f : 1f, 2f);
return;
}
}
else if (_data.interestedPlayer.lastKnownPlayerLocation.distance < lanternRange && !canSeePlayerCheckLocation)
{
if (_allowFocusBeam || !_data.interestedPlayer.isPlayerLocationKnown)
{
_controller.StopMoving();
}
if (_data.interestedPlayer.lastKnownPlayerLocation.degreesToPositionXZ < 5f && (isLastKnownLocationInRange || _controller.AttachedObject.IsLanternFocused()))
{
_checkingTargetLocation = true;
}
if (isLastKnownLocationInRange)
{
_controller.FaceLocalPosition(_data.interestedPlayer.lastKnownPlayerLocation.localPosition, TurnSpeed.FASTEST);
_controller.SetLanternConcealed(false, true);
_controller.ChangeLanternFocus(0f, 2f);
return;
}
_controller.FaceLocalPosition(_data.interestedPlayer.lastKnownPlayerLocation.localPosition, TurnSpeed.MEDIUM);
if (_data.interestedPlayer.lastKnownPlayerLocation.degreesToPositionXZ < 5f)
{
_controller.ChangeLanternFocus(1f, 2f);
return;
}
}
else
{
_controller.ChangeLanternFocus(0f, 2f);
_controller.SetLanternConcealed(true, true);
if (!QSBCore.IsHost)
{
return;
}
_controller.PathfindToLocalPosition(_data.interestedPlayer.lastKnownPlayerLocation.localPosition, MoveType.INVESTIGATE);
_controller.FaceLocalPosition(_data.interestedPlayer.lastKnownPlayerLocation.localPosition, TurnSpeed.MEDIUM);
}
}
}
public override void OnArriveAtPosition()
{
if (_movingToSearchLocation)
{
_arrivedAtTargetSearchPosition = true;
if (_searchNodesNearTarget)
{
var num = GenerateSpotlightList(_searchNode, _searchStartPosition - _searchNode.localPosition);
if (num > 0)
{
_controller.SetLanternConcealed(false, true);
_controller.ChangeLanternFocus(1f, 2f);
_controller.FaceNodeList(IdentifyIntruderAction.s_nodesToSpotlight, num, TurnSpeed.MEDIUM, 1f, false);
return;
}
}
else
{
if (_sawPlayerOccluded)
{
_searchPosition = _data.interestedPlayer.lastKnownPlayerLocation.localPosition + _data.interestedPlayer.lastKnownPlayerLocation.localVelocity;
_controller.FaceLocalPosition(_searchPosition, TurnSpeed.MEDIUM);
return;
}
}
}
}
public override void OnFinishFaceNodeList()
{
_searchNodesComplete = true;
}
private int GenerateSpotlightList(GhostNode node, Vector3 ignoreDirection)
{
var num = 0;
var localPosition = node.localPosition;
for (var i = 0; i < node.neighbors.Count; i++)
{
if (Vector3.Angle(node.neighbors[i].localPosition - localPosition, ignoreDirection) >= 45f)
{
IdentifyIntruderAction.s_nodesToSpotlight[num] = node.neighbors[i];
num++;
if (num == IdentifyIntruderAction.s_nodesToSpotlight.Length)
{
break;
}
}
}
return num;
}
}

View File

@ -0,0 +1,115 @@
using System;
using GhostEnums;
using QSB.EchoesOfTheEye.Ghosts;
using QSB.EchoesOfTheEye.Ghosts.WorldObjects;
using UnityEngine;
public class QSBPartyHouseAction : QSBGhostAction
{
private Vector3 _initialLocalPosition;
private Vector3 _initialLocalDirection;
private bool _allowChasePlayer;
private bool _waitingToLookAtPlayer;
private bool _lookingAtPlayer;
private float _lookAtPlayerTime;
private TurnSpeed _lookSpeed;
public override GhostAction.Name GetName()
{
return GhostAction.Name.PartyHouse;
}
public override float CalculateUtility()
{
if (!this._allowChasePlayer)
{
return 99f;
}
return 94f;
}
public override bool IsInterruptible()
{
return true;
}
public void ResetAllowChasePlayer()
{
this._allowChasePlayer = false;
}
public void AllowChasePlayer()
{
this._allowChasePlayer = true;
this._controller.SetLanternConcealed(true, true);
this._controller.FacePlayer(_data.interestedPlayer.player, TurnSpeed.MEDIUM);
this._effects.SetMovementStyle(GhostEffects.MovementStyle.Stalk);
}
public void LookAtPlayer(float delay, TurnSpeed lookSpeed = TurnSpeed.SLOWEST)
{
this._waitingToLookAtPlayer = true;
this._lookAtPlayerTime = Time.time + delay;
this._lookSpeed = lookSpeed;
}
public override void Initialize(QSBGhostBrain brain)
{
base.Initialize(brain);
this._initialLocalPosition = this._controller.AttachedObject.GetLocalFeetPosition();
this._initialLocalDirection = this._controller.AttachedObject.GetLocalForward();
}
protected override void OnEnterAction()
{
this._controller.MoveToLocalPosition(this._initialLocalPosition, MoveType.PATROL);
this._controller.FaceLocalPosition(this._initialLocalPosition + this._initialLocalDirection, TurnSpeed.MEDIUM);
this._controller.SetLanternConcealed(true, true);
this._effects.SetMovementStyle(GhostEffects.MovementStyle.Normal);
this._waitingToLookAtPlayer = false;
this._lookingAtPlayer = false;
}
protected override void OnExitAction()
{
this._allowChasePlayer = true;
}
public override bool Update_Action()
{
if (!this._lookingAtPlayer)
{
bool isIlluminatedByPlayer = this._data.IsIlluminatedByAnyPlayer;
if ((this._waitingToLookAtPlayer && Time.time > this._lookAtPlayerTime) || isIlluminatedByPlayer)
{
this._controller.FacePlayer(_data.interestedPlayer.player, isIlluminatedByPlayer ? TurnSpeed.SLOW : this._lookSpeed);
this._waitingToLookAtPlayer = false;
this._lookingAtPlayer = true;
}
}
return true;
}
public override void FixedUpdate_Action()
{
if (_data.interestedPlayer == null)
{
return;
}
if (this._allowChasePlayer)
{
if (this._controller.AttachedObject.GetNodeMap().CheckLocalPointInBounds(this._data.interestedPlayer.playerLocation.localPosition))
{
this._controller.PathfindToLocalPosition(this._data.interestedPlayer.playerLocation.localPosition, MoveType.SEARCH);
}
this._controller.FaceLocalPosition(this._data.interestedPlayer.playerLocation.localPosition, TurnSpeed.MEDIUM);
}
}
}

View File

@ -0,0 +1,136 @@
using System;
using GhostEnums;
using QSB.EchoesOfTheEye.Ghosts;
using UnityEngine;
public class QSBPartyPathAction : QSBGhostAction
{
private int _pathIndex;
private bool _allowFollowPath;
private bool _reachedEndOfPath;
private bool _isMovingToFinalPosition;
private Vector3 _finalPosition;
public int currentPathIndex
{
get
{
return this._pathIndex;
}
}
public bool hasReachedEndOfPath
{
get
{
return this._reachedEndOfPath;
}
}
public bool isMovingToFinalPosition
{
get
{
return this._isMovingToFinalPosition;
}
}
public bool allowHearGhostCall
{
get
{
return this._allowFollowPath && !this._isMovingToFinalPosition;
}
}
public override GhostAction.Name GetName()
{
return GhostAction.Name.PartyPath;
}
public override float CalculateUtility()
{
if (this._controller.AttachedObject.GetNodeMap().GetPathNodes().Length == 0)
{
return -100f;
}
return 10f;
}
public void ResetPath()
{
this._pathIndex = 0;
this._allowFollowPath = false;
this._reachedEndOfPath = false;
this._isMovingToFinalPosition = false;
this._controller.StopMoving();
this._controller.SetLanternConcealed(true, false);
}
public void StartFollowPath()
{
this._allowFollowPath = true;
this._controller.PathfindToNode(this._controller.AttachedObject.GetNodeMap().GetPathNodes()[this._pathIndex], MoveType.PATROL);
this._controller.SetLanternConcealed(false, false);
}
public void MoveToFinalPosition(Vector3 worldPosition)
{
this._isMovingToFinalPosition = true;
this._finalPosition = this._controller.AttachedObject.WorldToLocalPosition(worldPosition);
this._controller.PathfindToLocalPosition(this._finalPosition, MoveType.PATROL);
this._controller.SetLanternConcealed(true, true);
}
protected override void OnEnterAction()
{
this._controller.FaceVelocity();
this._effects.PlayDefaultAnimation();
this._effects.SetMovementStyle(GhostEffects.MovementStyle.Normal);
if (this._allowFollowPath)
{
if (this._isMovingToFinalPosition)
{
this._controller.PathfindToLocalPosition(this._finalPosition, MoveType.PATROL);
}
else
{
this._controller.PathfindToNode(this._controller.AttachedObject.GetNodeMap().GetPathNodes()[this._pathIndex], MoveType.PATROL);
}
this._controller.SetLanternConcealed(this._isMovingToFinalPosition, true);
this._controller.ChangeLanternFocus(0f, 2f);
return;
}
this._controller.SetLanternConcealed(true, false);
}
protected override void OnExitAction()
{
this._reachedEndOfPath = false;
}
public override bool Update_Action()
{
return true;
}
public override void OnArriveAtPosition()
{
if (this._isMovingToFinalPosition)
{
return;
}
GhostNode[] pathNodes = this._controller.AttachedObject.GetNodeMap().GetPathNodes();
if (this._pathIndex + 1 > pathNodes.Length || pathNodes[this._pathIndex].pathData.isEndOfPath)
{
this._reachedEndOfPath = true;
return;
}
this._pathIndex++;
this._controller.PathfindToNode(pathNodes[this._pathIndex], MoveType.PATROL);
}
}

View File

@ -0,0 +1,109 @@
using GhostEnums;
using UnityEngine;
namespace QSB.EchoesOfTheEye.Ghosts.Actions;
public class QSBSearchAction : QSBGhostAction
{
private GhostNode _targetSearchNode;
private bool _searchingAtNode;
private float _searchStartTime;
public override GhostAction.Name GetName()
{
return GhostAction.Name.SearchForIntruder;
}
public override float CalculateUtility()
{
if (!_controller.AttachedObject.GetNodeMap().HasSearchNodes(_controller.AttachedObject.GetNodeLayer()))
{
return -100f;
}
if (_data.threatAwareness >= GhostData.ThreatAwareness.SomeoneIsInHere)
{
return 50f;
}
return -100f;
}
protected override void OnEnterAction()
{
_controller.SetLanternConcealed(true, true);
_effects.SetMovementStyle(GhostEffects.MovementStyle.Normal);
ContinueSearch();
}
protected override void OnExitAction()
{
if (_searchingAtNode)
{
_controller.FaceVelocity();
}
_targetSearchNode = null;
_searchingAtNode = false;
}
public override bool Update_Action()
{
if (_searchingAtNode && Time.time > _searchStartTime + 4f)
{
_controller.FaceVelocity();
_targetSearchNode.searchData.lastSearchTime = Time.time;
ContinueSearch();
}
return true;
}
public override void OnArriveAtPosition()
{
_controller.AttachedObject.Spin(TurnSpeed.MEDIUM);
_searchingAtNode = true;
_searchStartTime = Time.time;
}
private void ContinueSearch()
{
_searchingAtNode = false;
_targetSearchNode = GetHighestPriorityNodeToSearch();
if (_targetSearchNode == null)
{
Debug.LogError("Failed to find any nodes to search! Did we exhaust our existing options?", _controller.AttachedObject);
Debug.Break();
}
_controller.PathfindToNode(_targetSearchNode, MoveType.SEARCH);
_controller.FaceVelocity();
}
private GhostNode GetHighestPriorityNodeToSearch()
{
var searchNodesOnLayer = _controller.AttachedObject.GetNodeMap().GetSearchNodesOnLayer(_controller.AttachedObject.GetNodeLayer());
var num = 0f;
var time = Time.time;
for (var i = 0; i < searchNodesOnLayer.Length; i++)
{
var num2 = time - searchNodesOnLayer[i].searchData.lastSearchTime;
num += num2;
}
num /= (float)searchNodesOnLayer.Length;
GhostNode ghostNode = null;
for (var j = 0; j < 5; j++)
{
ghostNode = searchNodesOnLayer[Random.Range(0, searchNodesOnLayer.Length)];
if (time - ghostNode.searchData.lastSearchTime > num)
{
break;
}
}
return ghostNode;
}
}

View File

@ -0,0 +1,80 @@
using GhostEnums;
using QSB.EchoesOfTheEye.Ghosts;
using QSB.Utility;
/// <summary>
///
/// </summary>
public class QSBSentryAction : QSBGhostAction
{
private GhostNode _targetSentryNode;
private bool _spotlighting;
public override GhostAction.Name GetName()
{
return GhostAction.Name.Sentry;
}
public override float CalculateUtility()
{
if (_data.threatAwareness >= GhostData.ThreatAwareness.SomeoneIsInHere)
{
return 50f;
}
return -100f;
}
protected override void OnEnterAction()
{
DebugLog.DebugWrite($"ON ENTER ACTION");
_spotlighting = false;
_controller.SetLanternConcealed(true, true);
_effects.SetMovementStyle(GhostEffects.MovementStyle.Stalk);
var searchNodesOnLayer = _controller.AttachedObject.GetNodeMap().GetSearchNodesOnLayer(_controller.AttachedObject.GetNodeLayer());
_targetSentryNode = searchNodesOnLayer[0];
_controller.PathfindToNode(_targetSentryNode, MoveType.PATROL);
_controller.FaceVelocity();
}
public override bool Update_Action()
{
return true;
}
public override void FixedUpdate_Action()
{
if (_data.interestedPlayer == null)
{
return;
}
if (_data.interestedPlayer.isPlayerLocationKnown && !_spotlighting)
{
DebugLog.DebugWrite($"Spotlighting player...");
_spotlighting = true;
_controller.ChangeLanternFocus(1f, 2f);
}
if (_spotlighting)
{
if (_data.interestedPlayer.timeSincePlayerLocationKnown > 3f)
{
DebugLog.DebugWrite($"Give up on spotlighting player");
_spotlighting = false;
_controller.SetLanternConcealed(true, true);
_controller.FaceLocalPosition(_targetSentryNode.localPosition + _targetSentryNode.localForward * 10f, TurnSpeed.MEDIUM);
return;
}
DebugLog.DebugWrite($"Facing last known position...");
_controller.FaceLocalPosition(_data.interestedPlayer.lastKnownPlayerLocation.localPosition, TurnSpeed.FAST);
}
}
public override void OnArriveAtPosition()
{
_controller.FaceLocalPosition(_targetSentryNode.localPosition + _targetSentryNode.localForward * 10f, TurnSpeed.MEDIUM);
}
}

View File

@ -0,0 +1,56 @@
using QSB.Utility;
namespace QSB.EchoesOfTheEye.Ghosts.Actions;
public class QSBSleepAction : QSBGhostAction
{
private SleepAction.WakeState _state;
public override GhostAction.Name GetName()
=> GhostAction.Name.Sleep;
public override float CalculateUtility()
=> !_data.hasWokenUp
? 100f
: -100f;
public override bool IsInterruptible()
=> false;
protected override void OnEnterAction()
=> EnterSleepState();
protected override void OnExitAction() { }
public override bool Update_Action()
{
if (_state == SleepAction.WakeState.Sleeping)
{
if (_data.hasWokenUp || _data.IsIlluminatedByAnyPlayer)
{
DebugLog.DebugWrite($"{_brain.AttachedObject._name} : Who dares awaken me?");
_state = SleepAction.WakeState.Awake;
_effects.PlayDefaultAnimation();
}
}
else if (_state is not SleepAction.WakeState.WakingUp and SleepAction.WakeState.Awake)
{
return false;
}
return true;
}
private void EnterSleepState()
{
_controller.SetLanternConcealed(true, true);
_effects.PlaySleepAnimation();
_state = SleepAction.WakeState.Sleeping;
}
private enum WakeState
{
Sleeping,
Awake
}
}

View File

@ -0,0 +1,31 @@
using GhostEnums;
using QSB.EchoesOfTheEye.Ghosts;
public class QSBSleepwalkAction : QSBGhostAction
{
public override GhostAction.Name GetName() => GhostAction.Name.Sleepwalk;
public override float CalculateUtility()
=> !_data.hasWokenUp
? 100f
: -100f;
protected override void OnEnterAction()
{
MoveToRandomPatrolNode();
_controller.SetLanternConcealed(false, true);
_effects.SetMovementStyle(GhostEffects.MovementStyle.Normal);
}
public override bool Update_Action()
=> true;
public override void OnArriveAtPosition()
=> MoveToRandomPatrolNode();
private void MoveToRandomPatrolNode()
{
_controller.PathfindToNode(_controller.AttachedObject.GetNodeMap().GetRandomPatrolNode(), MoveType.PATROL);
_controller.FaceVelocity();
}
}

View File

@ -0,0 +1,111 @@
using GhostEnums;
using QSB.EchoesOfTheEye.Ghosts;
using QSB.Utility;
using UnityEngine;
public class QSBStalkAction : QSBGhostAction
{
private bool _wasPlayerLanternConcealed;
private bool _isFocusingLight;
private bool _shouldFocusLightOnPlayer;
private float _changeFocusTime;
public override GhostAction.Name GetName()
=> GhostAction.Name.Stalk;
public override float CalculateUtility()
{
if (_data.interestedPlayer == null)
{
return -100f;
}
if (_data.threatAwareness < GhostData.ThreatAwareness.IntruderConfirmed)
{
return -100f;
}
if ((_running && _data.interestedPlayer.timeSincePlayerLocationKnown < 4f) || _data.interestedPlayer.isPlayerLocationKnown)
{
return 85f;
}
return -100f;
}
protected override void OnEnterAction()
{
var flag = Locator.GetDreamWorldController().GetPlayerLantern().GetLanternController().IsConcealed();
_wasPlayerLanternConcealed = flag;
_isFocusingLight = flag;
_shouldFocusLightOnPlayer = flag;
_changeFocusTime = 0f;
_controller.ChangeLanternFocus(_isFocusingLight ? 1f : 0f, 2f);
_controller.SetLanternConcealed(!_isFocusingLight, true);
_controller.FaceVelocity();
_effects.SetMovementStyle(GhostEffects.MovementStyle.Stalk);
_effects.PlayVoiceAudioNear(_data.fastStalkUnlocked ? AudioType.Ghost_Stalk_Fast : AudioType.Ghost_Stalk, 1f);
}
public override bool Update_Action()
{
if (!_data.fastStalkUnlocked && _data.illuminatedByPlayerMeter > 4f)
{
DebugLog.DebugWrite($"{_brain.AttachedObject._name} Fast stalk unlocked.");
_data.fastStalkUnlocked = true;
_effects.PlayVoiceAudioNear(AudioType.Ghost_Stalk_Fast, 1f);
}
return true;
}
public override void FixedUpdate_Action()
{
var stalkSpeed = GhostConstants.GetMoveSpeed(MoveType.SEARCH);
if (_data.fastStalkUnlocked)
{
stalkSpeed += 1.5f;
}
if (_controller.AttachedObject.GetNodeMap().CheckLocalPointInBounds(_data.interestedPlayer.lastKnownPlayerLocation.localPosition))
{
_controller.PathfindToLocalPosition(_data.interestedPlayer.lastKnownPlayerLocation.localPosition, stalkSpeed, GhostConstants.GetMoveAcceleration(MoveType.SEARCH));
}
_controller.FaceLocalPosition(_data.interestedPlayer.lastKnownPlayerLocation.localPosition, TurnSpeed.MEDIUM);
var isPlayerLanternConcealed = Locator.GetDreamWorldController().GetPlayerLantern().GetLanternController().IsConcealed();
var sawPlayerLanternConceal = !_wasPlayerLanternConcealed
&& isPlayerLanternConcealed
&& _data.interestedPlayer.wasPlayerLocationKnown;
_wasPlayerLanternConcealed = isPlayerLanternConcealed;
if (sawPlayerLanternConceal && !_shouldFocusLightOnPlayer)
{
_shouldFocusLightOnPlayer = true;
_changeFocusTime = Time.time + 1f;
}
else if (_data.interestedPlayer.sensor.isPlayerHeldLanternVisible && _shouldFocusLightOnPlayer)
{
_shouldFocusLightOnPlayer = false;
_changeFocusTime = Time.time + 1f;
}
if (_isFocusingLight != _shouldFocusLightOnPlayer && Time.time > _changeFocusTime)
{
if (_shouldFocusLightOnPlayer)
{
DebugLog.DebugWrite($"{_brain.AttachedObject._name} : Un-concealing lantern and focusing on player.");
_controller.SetLanternConcealed(false, true);
_controller.ChangeLanternFocus(1f, 2f);
}
else
{
DebugLog.DebugWrite($"{_brain.AttachedObject._name} : Concealing lantern.");
_controller.SetLanternConcealed(true, true);
}
_isFocusingLight = _shouldFocusLightOnPlayer;
}
}
}

View File

@ -0,0 +1,58 @@
using GhostEnums;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace QSB.EchoesOfTheEye.Ghosts.Actions;
public class QSBWaitAction : QSBGhostAction
{
public override GhostAction.Name GetName()
{
return GhostAction.Name.Wait;
}
public override float CalculateUtility()
{
if (_data.interestedPlayer == null)
{
return 0f;
}
if (PlayerState.IsGrabbedByGhost() && (_running || _data.interestedPlayer.timeSincePlayerLocationKnown < 4f))
{
return 666f;
}
return 0f;
}
protected override void OnEnterAction()
{
if (!PlayerState.IsGrabbedByGhost())
{
_controller.StopMoving();
_controller.StopFacing();
return;
}
_effects.SetMovementStyle(GhostEffects.MovementStyle.Stalk);
_controller.FacePlayer(_data.interestedPlayer.player, TurnSpeed.MEDIUM);
if (_data.interestedPlayer.playerLocation.distanceXZ < 3f)
{
Vector3 toPositionXZ = _data.interestedPlayer.playerLocation.toPositionXZ;
_controller.MoveToLocalPosition(_controller.AttachedObject.GetLocalFeetPosition() - toPositionXZ * 3f, MoveType.SEARCH);
return;
}
_controller.StopMoving();
}
public override bool Update_Action()
{
return true;
}
}

View File

@ -0,0 +1,11 @@
namespace QSB.EchoesOfTheEye.Ghosts;
public enum GhostAnimationType
{
Sleep,
Default,
Grab,
BlowOutLanternNormal,
BlowOutLanternFast,
SnapNeck
}

View File

@ -0,0 +1,106 @@
using Cysharp.Threading.Tasks;
using QSB.EchoesOfTheEye.Ghosts.WorldObjects;
using QSB.Utility;
using QSB.WorldSync;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;
namespace QSB.EchoesOfTheEye.Ghosts;
internal class GhostManager : WorldObjectManager
{
public override WorldObjectScene WorldObjectScene => WorldObjectScene.SolarSystem;
public override bool DlcOnly => true;
private static GhostHotelDirector _hotelDirector;
private static GhostPartyPathDirector _partyPathDirector;
private static GhostZone2Director _zone2Director;
public override async UniTask BuildWorldObjects(OWScene scene, CancellationToken ct)
{
QSBWorldSync.Init<QSBGhostController, GhostController>();
QSBWorldSync.Init<QSBGhostEffects, GhostEffects>();
QSBWorldSync.Init<QSBGhostSensors, GhostSensors>();
QSBWorldSync.Init<QSBGhostNodeMap, GhostNodeMap>();
QSBWorldSync.Init<QSBGhostBrain, GhostBrain>(QSBWorldSync.GetUnityObjects<GhostBrain>().Where(x => x.gameObject.activeSelf).SortDeterministic());
_hotelDirector = QSBWorldSync.GetUnityObjects<GhostHotelDirector>().First();
_partyPathDirector = QSBWorldSync.GetUnityObjects<GhostPartyPathDirector>().First();
_zone2Director = QSBWorldSync.GetUnityObjects<GhostZone2Director>().First();
for (int i = 0; i < _hotelDirector._hotelDepthsGhosts.Length; i++)
{
_hotelDirector._hotelDepthsGhosts[i].OnIdentifyIntruder -= _hotelDirector.OnHotelDepthsGhostsIdentifiedIntruder;
_hotelDirector._hotelDepthsGhosts[i].GetWorldObject<QSBGhostBrain>().OnIdentifyIntruder += CustomOnHotelDepthsGhostsIdentifiedIntruder;
}
for (var j = 0; j < _partyPathDirector._directedGhosts.Length; j++)
{
_partyPathDirector._directedGhosts[j].OnIdentifyIntruder -= _partyPathDirector.OnGhostIdentifyIntruder;
_partyPathDirector._directedGhosts[j].GetWorldObject<QSBGhostBrain>().OnIdentifyIntruder += CustomOnGhostIdentifyIntruder;
}
for (int i = 0; i < _zone2Director._cityGhosts.Length; i++)
{
_zone2Director._cityGhosts[i].OnIdentifyIntruder -= _zone2Director.OnCityGhostsIdentifiedIntruder;
_zone2Director._cityGhosts[i].GetWorldObject<QSBGhostBrain>().OnIdentifyIntruder += CustomOnCityGhostsIdentifiedIntruder;
}
}
public static void CustomOnHotelDepthsGhostsIdentifiedIntruder(GhostBrain ghostBrain, QSBGhostData ghostData)
{
if (_hotelDirector._playerIdentifiedInDepths)
{
return;
}
var num = Random.Range(2f, 3f);
for (var i = 0; i < _hotelDirector._hotelDepthsGhosts.Length; i++)
{
if (!(_hotelDirector._hotelDepthsGhosts[i] == ghostBrain) && _hotelDirector._hotelDepthsGhosts[i].HearGhostCall(ghostData.interestedPlayer.playerLocation.localPosition, num, false))
{
num += Random.Range(2f, 3f);
}
}
}
public static void CustomOnGhostIdentifyIntruder(GhostBrain ghostBrain, QSBGhostData ghostData)
{
float num = Random.Range(2f, 3f);
for (int i = 0; i < _partyPathDirector._directedGhosts.Length; i++)
{
if (!(_partyPathDirector._directedGhosts[i] == ghostBrain))
{
bool flag = _partyPathDirector._directedGhosts[i].GetCurrentActionName() != GhostAction.Name.PartyPath || ((QSBPartyPathAction)_partyPathDirector._directedGhosts[i].GetWorldObject<QSBGhostBrain>().GetCurrentAction()).allowHearGhostCall;
float num2 = Vector3.Distance(ghostBrain.transform.position, _partyPathDirector._directedGhosts[i].transform.position);
if (flag && num2 < 50f && _partyPathDirector._directedGhosts[i].HearGhostCall(ghostData.interestedPlayer.playerLocation.localPosition, num, true))
{
_partyPathDirector._directedGhosts[i].GetWorldObject<QSBGhostBrain>().HintPlayerLocation(ghostData.interestedPlayer.player);
num += Random.Range(2f, 3f);
}
}
}
}
public static void CustomOnCityGhostsIdentifiedIntruder(GhostBrain ghostBrain, QSBGhostData ghostData)
{
if (_zone2Director._playerIdentifiedInCity)
{
return;
}
_zone2Director._playerIdentifiedInCity = true;
float num = Random.Range(2f, 3f);
for (int i = 0; i < _zone2Director._cityGhosts.Length; i++)
{
if (!(_zone2Director._cityGhosts[i] == ghostBrain) && _zone2Director._cityGhosts[i].HearGhostCall(ghostData.interestedPlayer.playerLocation.localPosition, num, false))
{
num += Random.Range(2f, 3f);
}
}
}
}

View File

@ -0,0 +1,27 @@
using QSB.Player;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace QSB.EchoesOfTheEye.Ghosts;
public class GhostPlayer
{
public PlayerInfo player;
public QSBGhostSensorData sensor = new();
public GhostLocationData playerLocation = new();
public GhostLocationData lastKnownPlayerLocation = new();
public QSBGhostSensorData lastKnownSensor = new();
public QSBGhostSensorData firstUnknownSensor = new();
public bool isPlayerLocationKnown;
public bool wasPlayerLocationKnown;
public float timeLastSawPlayer;
public float timeSincePlayerLocationKnown = float.PositiveInfinity;
public float playerMinLanternRange;
public bool lostPlayerDueToOcclusion
=> !isPlayerLocationKnown
&& !lastKnownSensor.isPlayerOccluded
&& firstUnknownSensor.isPlayerOccluded;
}

View File

@ -0,0 +1,22 @@
using QSB.EchoesOfTheEye.Ghosts.WorldObjects;
using QSB.Messaging;
using QSB.Utility;
namespace QSB.EchoesOfTheEye.Ghosts.Messages;
internal class ChangeActionMessage : QSBWorldObjectMessage<QSBGhostBrain, GhostAction.Name>
{
public ChangeActionMessage(GhostAction.Name name) : base(name) { }
public override void OnReceiveRemote()
{
if (QSBCore.IsHost)
{
DebugLog.ToConsole("Error - Received ChangeActionMessage on host. Something has gone horribly wrong!", OWML.Common.MessageType.Error);
return;
}
DebugLog.DebugWrite($"{WorldObject.AttachedObject._name} Change action to {Data}");
WorldObject.ChangeAction(WorldObject.GetAction(Data), true);
}
}

View File

@ -0,0 +1,17 @@
using QSB.EchoesOfTheEye.Ghosts.WorldObjects;
using QSB.Messaging;
using QSB.Player;
using QSB.Utility;
namespace QSB.EchoesOfTheEye.Ghosts.Messages;
internal class ChangeInterestedPlayerMessage : QSBWorldObjectMessage<QSBGhostSensors, uint>
{
public ChangeInterestedPlayerMessage(uint playerId) : base(playerId) { }
public override void OnReceiveRemote()
{
DebugLog.DebugWrite($"{WorldObject.AttachedObject.name} Set interested player {Data}");
WorldObject._data.interestedPlayer = WorldObject._data.players[QSBPlayerManager.GetPlayer(Data)];
}
}

View File

@ -0,0 +1,22 @@
using QSB.EchoesOfTheEye.Ghosts.WorldObjects;
using QSB.Messaging;
using QSB.Utility;
using UnityEngine;
namespace QSB.EchoesOfTheEye.Ghosts.Messages;
internal class FaceLocalDirectionMessage : QSBWorldObjectMessage<QSBGhostController, (Vector3 localDirection, float degreesPerSecond, float turnAcceleration)>
{
public FaceLocalDirectionMessage(Vector3 localDirection, float degreesPerSecond, float turnAcceleration) : base((localDirection, degreesPerSecond, turnAcceleration)) { }
public override void OnReceiveRemote()
{
if (QSBCore.IsHost)
{
DebugLog.ToConsole("Error - Received FaceLocalDirectionMessage on host. Something has gone horribly wrong!", OWML.Common.MessageType.Error);
return;
}
WorldObject.FaceLocalDirection(Data.localDirection, Data.degreesPerSecond, Data.turnAcceleration, true);
}
}

View File

@ -0,0 +1,22 @@
using QSB.EchoesOfTheEye.Ghosts.WorldObjects;
using QSB.Messaging;
using QSB.Utility;
using UnityEngine;
namespace QSB.EchoesOfTheEye.Ghosts.Messages;
internal class FaceLocalPositionMessage : QSBWorldObjectMessage<QSBGhostController, (Vector3 localPosition, float degreesPerSecond, float turnAcceleration)>
{
public FaceLocalPositionMessage(Vector3 localPos, float degPerSecond, float turnAccel) : base((localPos, degPerSecond, turnAccel)) { }
public override void OnReceiveRemote()
{
if (QSBCore.IsHost)
{
DebugLog.ToConsole("Error - Received FaceLocalPositionMessage on host. Something has gone horribly wrong!", OWML.Common.MessageType.Error);
return;
}
WorldObject.FaceLocalPosition(Data.localPosition, Data.degreesPerSecond, Data.turnAcceleration, true);
}
}

View File

@ -0,0 +1,72 @@
using GhostEnums;
using QSB.EchoesOfTheEye.Ghosts.WorldObjects;
using QSB.Messaging;
using QSB.Utility;
using QSB.WorldSync;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace QSB.EchoesOfTheEye.Ghosts.Messages;
internal class FaceNodeListMessage : QSBWorldObjectMessage<QSBGhostController, (int mapId, int[] nodeIndexes, int numNodes, TurnSpeed turnSpeed, float nodeDelay, bool autoFocusLantern)>
{
public FaceNodeListMessage(GhostNode[] nodeList, int numNodes, TurnSpeed turnSpeed, float nodeDelay, bool autoFocus) : base(Process(nodeList, numNodes, turnSpeed, nodeDelay, autoFocus)) { }
private static (int mapId, int[] nodeIndexes, int numNodes, TurnSpeed turnSpeed, float nodeDelay, bool autoFocusLantern) Process(GhostNode[] nodeList, int numNodes, TurnSpeed turnSpeed, float nodeDelay, bool autoFocusLantern)
{
(int mapId, int[] nodeIndexes, int numNodes, TurnSpeed turnSpeed, float nodeDelay, bool autoFocusLantern) ret = new();
ret.numNodes = numNodes;
ret.turnSpeed = turnSpeed;
ret.nodeDelay = nodeDelay;
ret.autoFocusLantern = autoFocusLantern;
if (numNodes == 0)
{
ret.mapId = -1;
ret.nodeIndexes = new int[numNodes];
return ret;
}
var nodeMaps = QSBWorldSync.GetWorldObjects<QSBGhostNodeMap>();
var owner = nodeMaps.First(x => x.AttachedObject._nodes.Contains(nodeList[0]));
var hasAll = nodeList.All(owner.AttachedObject._nodes.Contains);
if (!hasAll)
{
DebugLog.ToConsole($"Warning - owner.nodes does not contain all of nodelist! Trying to find a correct owner...", OWML.Common.MessageType.Warning);
owner = nodeMaps.FirstOrDefault(x => nodeList.All(x.AttachedObject._nodes.Contains));
if (owner == default)
{
DebugLog.ToConsole($"Error - Failed to find correct owner for nodelist.", OWML.Common.MessageType.Error);
ret.mapId = -1;
ret.nodeIndexes = new int[numNodes];
}
}
ret.mapId = owner.ObjectId;
ret.nodeIndexes = nodeList.Select(x => Array.IndexOf(owner.AttachedObject._nodes, x)).ToArray();
return ret;
}
public override void OnReceiveRemote()
{
if (QSBCore.IsHost)
{
DebugLog.ToConsole("Error - Received FaceNodeListMessage on host. Something has gone horribly wrong!", OWML.Common.MessageType.Error);
return;
}
var map = Data.mapId.GetWorldObject<QSBGhostNodeMap>();
var nodeList = Data.nodeIndexes.Select(x => map.AttachedObject._nodes[x]).ToArray();
WorldObject.FaceNodeList(nodeList, Data.numNodes, Data.turnSpeed, Data.nodeDelay, Data.autoFocusLantern, true);
}
}

View File

@ -0,0 +1,45 @@
using GhostEnums;
using QSB.EchoesOfTheEye.Ghosts.WorldObjects;
using QSB.Messaging;
using QSB.Utility;
using QSB.WorldSync;
using System;
using System.Linq;
namespace QSB.EchoesOfTheEye.Ghosts.Messages;
internal class FaceNodeMessage : QSBWorldObjectMessage<QSBGhostController, (int mapId, int nodeIndex, TurnSpeed turnSpeed, float nodeDelay, bool autoFocusLantern)>
{
public FaceNodeMessage(GhostNode node, TurnSpeed turnSpeed, float nodeDelay, bool autoFocusLantern) : base(Process(node, turnSpeed, nodeDelay, autoFocusLantern)) { }
private static (int mapId, int nodeIndex, TurnSpeed turnSpeed, float nodeDelay, bool autoFocusLantern) Process(GhostNode node, TurnSpeed turnSpeed, float nodeDelay, bool autoFocusLantern)
{
(int mapId, int nodeIndex, TurnSpeed turnSpeed, float nodeDelay, bool autoFocusLantern) ret = new();
ret.turnSpeed = turnSpeed;
ret.nodeDelay = nodeDelay;
ret.autoFocusLantern = autoFocusLantern;
var nodeMaps = QSBWorldSync.GetWorldObjects<QSBGhostNodeMap>();
var owner = nodeMaps.First(x => x.AttachedObject._nodes.Contains(node));
ret.mapId = owner.ObjectId;
ret.nodeIndex = Array.IndexOf(owner.AttachedObject._nodes, node);
return ret;
}
public override void OnReceiveRemote()
{
if (QSBCore.IsHost)
{
DebugLog.ToConsole("Error - Received FaceNodeMessage on host. Something has gone horribly wrong!", OWML.Common.MessageType.Error);
return;
}
var map = Data.mapId.GetWorldObject<QSBGhostNodeMap>();
var node = map.AttachedObject._nodes[Data.nodeIndex];
WorldObject.FaceNode(node, Data.turnSpeed, Data.nodeIndex, Data.autoFocusLantern, true);
}
}

View File

@ -0,0 +1,28 @@
using GhostEnums;
using QSB.EchoesOfTheEye.Ghosts.WorldObjects;
using QSB.Messaging;
using QSB.Player;
using QSB.Utility;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace QSB.EchoesOfTheEye.Ghosts.Messages;
internal class FacePlayerMessage : QSBWorldObjectMessage<QSBGhostController, (uint playerId, TurnSpeed turnSpeed)>
{
public FacePlayerMessage(uint playerId, TurnSpeed turnSpeed) : base((playerId, turnSpeed)) { }
public override void OnReceiveRemote()
{
if (QSBCore.IsHost)
{
DebugLog.ToConsole("Error - Received FacePlayerMessage on host. Something has gone horribly wrong!", OWML.Common.MessageType.Error);
return;
}
WorldObject.FacePlayer(QSBPlayerManager.GetPlayer(Data.playerId), Data.turnSpeed, true);
}
}

View File

@ -0,0 +1,19 @@
using QSB.EchoesOfTheEye.Ghosts.WorldObjects;
using QSB.Messaging;
using QSB.Utility;
namespace QSB.EchoesOfTheEye.Ghosts.Messages;
internal class FaceVelocityMessage : QSBWorldObjectMessage<QSBGhostController>
{
public override void OnReceiveRemote()
{
if (QSBCore.IsHost)
{
DebugLog.ToConsole("Error - Received FaceVelocityMessage on host. Something has gone horribly wrong!", OWML.Common.MessageType.Error);
return;
}
WorldObject.FaceVelocity(true);
}
}

View File

@ -0,0 +1,43 @@
using QSB.EchoesOfTheEye.Ghosts.WorldObjects;
using QSB.Messaging;
using QSB.Utility;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace QSB.EchoesOfTheEye.Ghosts.Messages;
internal class GhostAnimationTriggerMessage : QSBWorldObjectMessage<QSBGhostEffects, GhostAnimationType>
{
public GhostAnimationTriggerMessage(GhostAnimationType type) : base(type) { }
public override void OnReceiveRemote()
{
switch (Data)
{
case GhostAnimationType.Sleep:
WorldObject.PlaySleepAnimation(true);
break;
case GhostAnimationType.Default:
WorldObject.PlayDefaultAnimation(true);
break;
case GhostAnimationType.Grab:
WorldObject.PlayGrabAnimation(true);
break;
case GhostAnimationType.BlowOutLanternNormal:
WorldObject.PlayBlowOutLanternAnimation(false, true);
break;
case GhostAnimationType.BlowOutLanternFast:
WorldObject.PlayBlowOutLanternAnimation(true, true);
break;
case GhostAnimationType.SnapNeck:
WorldObject.PlaySnapNeckAnimation(true);
break;
default:
DebugLog.ToConsole($"Warning - Received unknown animation type of {Data} for QSBGhostEffects {WorldObject.ObjectId}", OWML.Common.MessageType.Warning);
break;
}
}
}

View File

@ -0,0 +1,27 @@
using QSB.EchoesOfTheEye.Ghosts.WorldObjects;
using QSB.Messaging;
using QSB.Utility;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace QSB.EchoesOfTheEye.Ghosts.Messages;
internal class MoveToLocalPositionMessage : QSBWorldObjectMessage<QSBGhostController, (Vector3 localPosition, float speed, float acceleration)>
{
public MoveToLocalPositionMessage(Vector3 localPos, float speed, float accel) : base((localPos, speed, accel)) { }
public override void OnReceiveRemote()
{
if (QSBCore.IsHost)
{
DebugLog.ToConsole("Error - Received MoveToLocalPosition on host. Something has gone horribly wrong!", OWML.Common.MessageType.Error);
return;
}
WorldObject.MoveToLocalPosition(Data.localPosition, Data.speed, Data.acceleration, true);
}
}

View File

@ -0,0 +1,24 @@
using QSB.EchoesOfTheEye.Ghosts.WorldObjects;
using QSB.Messaging;
using QSB.Utility;
using UnityEngine;
namespace QSB.EchoesOfTheEye.Ghosts.Messages;
internal class PathfindLocalPositionMessage : QSBWorldObjectMessage<QSBGhostController,
(Vector3 localPosition, float speed, float acceleration)>
{
public PathfindLocalPositionMessage(Vector3 localPosition, float speed, float acceleration) :
base((localPosition, speed, acceleration)) { }
public override void OnReceiveRemote()
{
if (QSBCore.IsHost)
{
DebugLog.ToConsole("Error - Received PathfindLocalPositionMessage on host. Something has gone horribly wrong!", OWML.Common.MessageType.Error);
return;
}
WorldObject.PathfindToLocalPosition(Data.localPosition, Data.speed, Data.acceleration, true);
}
}

View File

@ -0,0 +1,43 @@
using QSB.EchoesOfTheEye.Ghosts.WorldObjects;
using QSB.Messaging;
using QSB.Utility;
using QSB.WorldSync;
using System;
using System.Linq;
namespace QSB.EchoesOfTheEye.Ghosts.Messages;
internal class PathfindNodeMessage : QSBWorldObjectMessage<QSBGhostController, (int mapId, int nodeIndex, float speed, float acceleration)>
{
public PathfindNodeMessage(GhostNode node, float speed, float acceleration) : base(Process(node, speed, acceleration)) { }
private static (int mapId, int nodeIndex, float speed, float acceleration) Process(GhostNode node, float speed, float acceleration)
{
(int mapId, int nodeId, float speed, float acceleration) ret = new();
ret.speed = speed;
ret.acceleration = acceleration;
var nodeMaps = QSBWorldSync.GetWorldObjects<QSBGhostNodeMap>();
var owner = nodeMaps.First(x => x.AttachedObject._nodes.Contains(node));
ret.mapId = owner.ObjectId;
ret.nodeId = Array.IndexOf(owner.AttachedObject._nodes, node);
return ret;
}
public override void OnReceiveRemote()
{
if (QSBCore.IsHost)
{
DebugLog.ToConsole("Error - Received PathfindNodeMessage on host. Something has gone horribly wrong!", OWML.Common.MessageType.Error);
return;
}
var map = Data.mapId.GetWorldObject<QSBGhostNodeMap>();
var node = map.AttachedObject._nodes[Data.nodeIndex];
WorldObject.PathfindToNode(node, Data.speed, Data.acceleration, true);
}
}

View File

@ -0,0 +1,20 @@
using QSB.EchoesOfTheEye.Ghosts.WorldObjects;
using QSB.Messaging;
namespace QSB.EchoesOfTheEye.Ghosts.Messages;
internal class PlayVoiceAudioMessage : QSBWorldObjectMessage<QSBGhostEffects, (AudioType audioType, float volumeScale, bool near)>
{
public PlayVoiceAudioMessage(AudioType audioType, float volumeScale, bool near) : base((audioType, volumeScale, near)) { }
public override void OnReceiveRemote()
{
if (Data.near)
{
WorldObject.PlayVoiceAudioNear(Data.audioType, Data.volumeScale, true);
return;
}
WorldObject.PlayVoiceAudioFar(Data.audioType, Data.volumeScale, true);
}
}

View File

@ -0,0 +1,12 @@
using QSB.EchoesOfTheEye.Ghosts.WorldObjects;
using QSB.Messaging;
namespace QSB.EchoesOfTheEye.Ghosts.Messages;
internal class SetMovementStyleMessage : QSBWorldObjectMessage<QSBGhostEffects, GhostEffects.MovementStyle>
{
public SetMovementStyleMessage(GhostEffects.MovementStyle style) : base(style) { }
public override void OnReceiveRemote()
=> WorldObject.SetMovementStyle(Data, true);
}

View File

@ -0,0 +1,17 @@
using QSB.EchoesOfTheEye.Ghosts.WorldObjects;
using QSB.Messaging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace QSB.EchoesOfTheEye.Ghosts.Messages;
internal class StopFacingMessage : QSBWorldObjectMessage<QSBGhostController>
{
public override void OnReceiveRemote()
{
WorldObject.StopFacing(true);
}
}

View File

@ -0,0 +1,36 @@
using QSB.EchoesOfTheEye.Ghosts.WorldObjects;
using QSB.Messaging;
using QSB.Utility;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace QSB.EchoesOfTheEye.Ghosts.Messages;
internal class StopMovingMessage : QSBWorldObjectMessage<QSBGhostController, bool>
{
public StopMovingMessage(bool instant) : base(instant) { }
public override void OnReceiveRemote()
{
if (QSBCore.IsHost)
{
DebugLog.ToConsole("Error - Received StopMovingMessage on host. Something has gone horribly wrong!", OWML.Common.MessageType.Error);
return;
}
if (Data)
{
WorldObject.AttachedObject._velocity = Vector3.zero;
// rest of code gets handled by the second StopMovingMessage
return;
}
WorldObject.AttachedObject._moveToTargetPosition = false;
WorldObject.AttachedObject._followNodePath = false;
WorldObject.AttachedObject._hasFinalPathPosition = false;
}
}

View File

@ -0,0 +1,436 @@
using HarmonyLib;
using QSB.EchoesOfTheEye.Ghosts.WorldObjects;
using QSB.Patches;
using QSB.Utility;
using QSB.WorldSync;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace QSB.EchoesOfTheEye.Ghosts.Patches;
[HarmonyPatch(typeof(GhostBrain))]
internal class GhostBrainPatches : QSBPatch
{
public override QSBPatchTypes Type => QSBPatchTypes.OnClientConnect;
[HarmonyPrefix]
[HarmonyPatch(nameof(GhostBrain.GetCurrentActionName))]
public static bool GetCurrentActionName(GhostBrain __instance, ref GhostAction.Name __result)
{
if (!QSBWorldSync.AllObjectsReady)
{
return true;
}
__result = __instance.GetWorldObject<QSBGhostBrain>().GetCurrentActionName();
return false;
}
[HarmonyPrefix]
[HarmonyPatch(nameof(GhostBrain.GetCurrentAction))]
public static bool GetCurrentAction(GhostBrain __instance)
{
if (!QSBWorldSync.AllObjectsReady)
{
return true;
}
DebugLog.ToConsole($"Error - {MethodBase.GetCurrentMethod().Name} not supported!", OWML.Common.MessageType.Error);
return false;
}
[HarmonyPrefix]
[HarmonyPatch(nameof(GhostBrain.GetAction))]
public static bool GetAction(GhostBrain __instance, GhostAction.Name actionName)
{
if (!QSBWorldSync.AllObjectsReady)
{
return true;
}
DebugLog.ToConsole($"Error - {MethodBase.GetCurrentMethod().Name} not supported!", OWML.Common.MessageType.Error);
return false;
}
[HarmonyPrefix]
[HarmonyPatch(nameof(GhostBrain.GetThreatAwareness))]
public static bool GetThreatAwareness(GhostBrain __instance, ref GhostData.ThreatAwareness __result)
{
if (!QSBWorldSync.AllObjectsReady)
{
return true;
}
__result = __instance.GetWorldObject<QSBGhostBrain>().GetThreatAwareness();
return false;
}
[HarmonyPrefix]
[HarmonyPatch(nameof(GhostBrain.GetEffects))]
public static bool GetEffects(GhostBrain __instance)
{
if (!QSBWorldSync.AllObjectsReady)
{
return true;
}
DebugLog.ToConsole($"Error - {MethodBase.GetCurrentMethod().Name} not supported!", OWML.Common.MessageType.Error);
return false;
}
[HarmonyPrefix]
[HarmonyPatch(nameof(GhostBrain.CheckDreadAudioConditions))]
public static bool CheckDreadAudioConditions(GhostBrain __instance, ref bool __result)
{
if (!QSBWorldSync.AllObjectsReady)
{
return true;
}
__result = __instance.GetWorldObject<QSBGhostBrain>().CheckDreadAudioConditions();
return false;
}
[HarmonyPrefix]
[HarmonyPatch(nameof(GhostBrain.CheckFearAudioConditions))]
public static bool CheckFearAudioConditions(GhostBrain __instance, bool fearAudioAlreadyPlaying, ref bool __result)
{
if (!QSBWorldSync.AllObjectsReady)
{
return true;
}
__result = __instance.GetWorldObject<QSBGhostBrain>().CheckFearAudioConditions(fearAudioAlreadyPlaying);
return false;
}
[HarmonyPrefix]
[HarmonyPatch(nameof(GhostBrain.Awake))]
public static bool Awake(GhostBrain __instance)
{
if (!QSBWorldSync.AllObjectsReady)
{
return true;
}
__instance.GetWorldObject<QSBGhostBrain>().Awake();
return false;
}
[HarmonyPrefix]
[HarmonyPatch(nameof(GhostBrain.Start))]
public static bool Start(GhostBrain __instance)
{
if (!QSBWorldSync.AllObjectsReady)
{
return true;
}
__instance.GetWorldObject<QSBGhostBrain>().Start();
return false;
}
[HarmonyPrefix]
[HarmonyPatch(nameof(GhostBrain.OnDestroy))]
public static bool OnDestroy(GhostBrain __instance)
{
if (!QSBWorldSync.AllObjectsReady)
{
return true;
}
__instance.GetWorldObject<QSBGhostBrain>().OnDestroy();
return false;
}
[HarmonyPrefix]
[HarmonyPatch(nameof(GhostBrain.TabulaRasa))]
public static bool TabulaRasa(GhostBrain __instance)
{
if (!QSBWorldSync.AllObjectsReady)
{
return true;
}
__instance.GetWorldObject<QSBGhostBrain>().TabulaRasa();
return false;
}
[HarmonyPrefix]
[HarmonyPatch(nameof(GhostBrain.Die))]
public static bool Die(GhostBrain __instance)
{
if (!QSBWorldSync.AllObjectsReady)
{
return true;
}
__instance.GetWorldObject<QSBGhostBrain>().Die();
return false;
}
[HarmonyPrefix]
[HarmonyPatch(nameof(GhostBrain.EscalateThreatAwareness))]
public static bool EscalateThreatAwareness(GhostBrain __instance, GhostData.ThreatAwareness newThreatAwareness)
{
if (!QSBWorldSync.AllObjectsReady)
{
return true;
}
__instance.GetWorldObject<QSBGhostBrain>().EscalateThreatAwareness(newThreatAwareness);
return false;
}
[HarmonyPrefix]
[HarmonyPatch(nameof(GhostBrain.WakeUp))]
public static bool WakeUp(GhostBrain __instance)
{
if (!QSBWorldSync.AllObjectsReady)
{
return true;
}
__instance.GetWorldObject<QSBGhostBrain>().WakeUp();
return false;
}
[HarmonyPrefix]
[HarmonyPatch(nameof(GhostBrain.HearGhostCall))]
public static bool HearGhostCall(GhostBrain __instance, Vector3 playerLocalPosition, float reactDelay, bool playResponseAudio, ref bool __result)
{
if (!QSBWorldSync.AllObjectsReady)
{
return true;
}
__result = __instance.GetWorldObject<QSBGhostBrain>().HearGhostCall(playerLocalPosition, reactDelay, playResponseAudio);
return false;
}
[HarmonyPrefix]
[HarmonyPatch(nameof(GhostBrain.HearCallForHelp))]
public static bool HearCallForHelp(GhostBrain __instance, Vector3 playerLocalPosition, float reactDelay, ref bool __result)
{
if (!QSBWorldSync.AllObjectsReady)
{
return true;
}
DebugLog.ToConsole($"Error - {MethodBase.GetCurrentMethod().Name} not supported!", OWML.Common.MessageType.Error);
return false;
}
[HarmonyPrefix]
[HarmonyPatch(nameof(GhostBrain.HintPlayerLocation), new Type[] { })]
public static bool HintPlayerLocation(GhostBrain __instance)
{
if (!QSBWorldSync.AllObjectsReady)
{
return true;
}
DebugLog.ToConsole($"Error - {MethodBase.GetCurrentMethod().Name} not supported!", OWML.Common.MessageType.Error);
return false;
}
[HarmonyPrefix]
[HarmonyPatch(nameof(GhostBrain.HintPlayerLocation), typeof(Vector3), typeof(float))]
public static bool HintPlayerLocation(GhostBrain __instance, Vector3 localPosition, float informationTime)
{
if (!QSBWorldSync.AllObjectsReady)
{
return true;
}
DebugLog.ToConsole($"Error - {MethodBase.GetCurrentMethod().Name} not supported!", OWML.Common.MessageType.Error);
return false;
}
[HarmonyPrefix]
[HarmonyPatch(nameof(GhostBrain.FixedUpdate))]
public static bool FixedUpdate(GhostBrain __instance)
{
if (!QSBWorldSync.AllObjectsReady)
{
return true;
}
__instance.GetWorldObject<QSBGhostBrain>().FixedUpdate();
return false;
}
[HarmonyPrefix]
[HarmonyPatch(nameof(GhostBrain.Update))]
public static bool Update(GhostBrain __instance)
{
if (!QSBWorldSync.AllObjectsReady)
{
return true;
}
__instance.GetWorldObject<QSBGhostBrain>().Update();
return false;
}
[HarmonyPrefix]
[HarmonyPatch(nameof(GhostBrain.FixedUpdate_ThreatAwareness))]
public static bool FixedUpdate_ThreatAwareness(GhostBrain __instance)
{
if (!QSBWorldSync.AllObjectsReady)
{
return true;
}
__instance.GetWorldObject<QSBGhostBrain>().FixedUpdate_ThreatAwareness();
return false;
}
[HarmonyPrefix]
[HarmonyPatch(nameof(GhostBrain.EvaluateActions))]
public static bool EvaluateActions(GhostBrain __instance)
{
if (!QSBWorldSync.AllObjectsReady)
{
return true;
}
__instance.GetWorldObject<QSBGhostBrain>().EvaluateActions();
return false;
}
[HarmonyPrefix]
[HarmonyPatch(nameof(GhostBrain.ChangeAction), typeof(GhostAction.Name))]
public static bool ChangeAction(GhostBrain __instance, GhostAction.Name actionName)
{
if (!QSBWorldSync.AllObjectsReady)
{
return true;
}
DebugLog.ToConsole($"Error - {MethodBase.GetCurrentMethod().Name} not supported!", OWML.Common.MessageType.Error);
return false;
}
[HarmonyPrefix]
[HarmonyPatch(nameof(GhostBrain.ChangeAction), typeof(GhostAction))]
public static bool ChangeAction(GhostBrain __instance, GhostAction action)
{
if (!QSBWorldSync.AllObjectsReady)
{
return true;
}
DebugLog.ToConsole($"Error - {MethodBase.GetCurrentMethod().Name} not supported!", OWML.Common.MessageType.Error);
return false;
}
[HarmonyPrefix]
[HarmonyPatch(nameof(GhostBrain.ClearPendingAction))]
public static bool ClearPendingAction(GhostBrain __instance)
{
if (!QSBWorldSync.AllObjectsReady)
{
return true;
}
__instance.GetWorldObject<QSBGhostBrain>().ClearPendingAction();
return false;
}
[HarmonyPrefix]
[HarmonyPatch(nameof(GhostBrain.OnArriveAtPosition))]
public static bool OnArriveAtPosition(GhostBrain __instance)
{
if (!QSBWorldSync.AllObjectsReady)
{
return true;
}
__instance.GetWorldObject<QSBGhostBrain>().OnArriveAtPosition();
return false;
}
[HarmonyPrefix]
[HarmonyPatch(nameof(GhostBrain.OnTraversePathNode))]
public static bool OnTraversePathNode(GhostBrain __instance, GhostNode node)
{
if (!QSBWorldSync.AllObjectsReady)
{
return true;
}
__instance.GetWorldObject<QSBGhostBrain>().OnTraversePathNode(node);
return false;
}
[HarmonyPrefix]
[HarmonyPatch(nameof(GhostBrain.OnFaceNode))]
public static bool OnFaceNode(GhostBrain __instance, GhostNode node)
{
if (!QSBWorldSync.AllObjectsReady)
{
return true;
}
__instance.GetWorldObject<QSBGhostBrain>().OnFaceNode(node);
return false;
}
[HarmonyPrefix]
[HarmonyPatch(nameof(GhostBrain.OnFinishFaceNodeList))]
public static bool OnFinishFaceNodeList(GhostBrain __instance)
{
if (!QSBWorldSync.AllObjectsReady)
{
return true;
}
__instance.GetWorldObject<QSBGhostBrain>().OnFinishFaceNodeList();
return false;
}
[HarmonyPrefix]
[HarmonyPatch(nameof(GhostBrain.OnCallForHelp))]
public static bool OnCallForHelp(GhostBrain __instance)
{
if (!QSBWorldSync.AllObjectsReady)
{
return true;
}
__instance.GetWorldObject<QSBGhostBrain>().OnCallForHelp();
return false;
}
[HarmonyPrefix]
[HarmonyPatch(nameof(GhostBrain.OnEnterDreamWorld))]
public static bool OnEnterDreamWorld(GhostBrain __instance)
{
if (!QSBWorldSync.AllObjectsReady)
{
return true;
}
__instance.GetWorldObject<QSBGhostBrain>().OnEnterDreamWorld();
return false;
}
[HarmonyPrefix]
[HarmonyPatch(nameof(GhostBrain.OnExitDreamWorld))]
public static bool OnExitDreamWorld(GhostBrain __instance)
{
if (!QSBWorldSync.AllObjectsReady)
{
return true;
}
__instance.GetWorldObject<QSBGhostBrain>().OnExitDreamWorld();
return false;
}
}

View File

@ -0,0 +1,72 @@
using GhostEnums;
using HarmonyLib;
using QSB.EchoesOfTheEye.Ghosts.WorldObjects;
using QSB.Patches;
using QSB.Utility;
using QSB.WorldSync;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace QSB.EchoesOfTheEye.Ghosts.Patches;
[HarmonyPatch(typeof(GhostController))]
internal class GhostControllerPatches : QSBPatch
{
public override QSBPatchTypes Type => QSBPatchTypes.OnClientConnect;
[HarmonyPrefix]
[HarmonyPatch(nameof(GhostController.Initialize))]
public static bool Initialize(GhostController __instance)
{
if (!QSBWorldSync.AllObjectsReady)
{
return true;
}
DebugLog.ToConsole($"Error - {MethodBase.GetCurrentMethod().Name} not supported!", OWML.Common.MessageType.Error);
return false;
}
[HarmonyPrefix]
[HarmonyPatch(nameof(GhostController.SetLanternConcealed))]
public static bool SetLanternConcealed(GhostController __instance, bool concealed, bool playAudio)
{
if (!QSBWorldSync.AllObjectsReady)
{
return true;
}
__instance.GetWorldObject<QSBGhostController>().SetLanternConcealed(concealed, playAudio);
return false;
}
[HarmonyPrefix]
[HarmonyPatch(nameof(GhostController.ChangeLanternFocus))]
public static bool ChangeLanternFocus(GhostController __instance, float focus, float focusRate)
{
if (!QSBWorldSync.AllObjectsReady)
{
return true;
}
__instance.GetWorldObject<QSBGhostController>().ChangeLanternFocus(focus, focusRate);
return false;
}
[HarmonyPrefix]
[HarmonyPatch(nameof(GhostController.FacePlayer))]
public static bool FacePlayer(GhostController __instance, TurnSpeed turnSpeed)
{
if (!QSBWorldSync.AllObjectsReady)
{
return true;
}
DebugLog.ToConsole($"Error - {MethodBase.GetCurrentMethod().Name} not supported!", OWML.Common.MessageType.Error);
return false;
}
}

View File

@ -0,0 +1,72 @@
using HarmonyLib;
using QSB.EchoesOfTheEye.Ghosts.WorldObjects;
using QSB.Patches;
using QSB.Utility;
using QSB.WorldSync;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace QSB.EchoesOfTheEye.Ghosts.Patches;
[HarmonyPatch(typeof(GhostEffects))]
internal class GhostEffectsPatches : QSBPatch
{
public override QSBPatchTypes Type => QSBPatchTypes.OnClientConnect;
[HarmonyPrefix]
[HarmonyPatch(nameof(GhostEffects.Initialize))]
public static bool Initialize(GhostEffects __instance, Transform nodeRoot, GhostController controller, GhostData data)
{
if (!QSBWorldSync.AllObjectsReady)
{
return true;
}
DebugLog.ToConsole($"Error - {MethodBase.GetCurrentMethod().Name} not supported!", OWML.Common.MessageType.Error);
return false;
}
[HarmonyPrefix]
[HarmonyPatch(nameof(GhostEffects.AllowFootstepAudio))]
public static bool AllowFootstepAudio(GhostEffects __instance, bool usingTimer, ref bool __result)
{
if (!QSBWorldSync.AllObjectsReady)
{
return true;
}
__result = __instance.GetWorldObject<QSBGhostEffects>().AllowFootstepAudio(usingTimer);
return false;
}
[HarmonyPrefix]
[HarmonyPatch(nameof(GhostEffects.PlayLanternAudio))]
public static bool PlayLanternAudio(GhostEffects __instance, AudioType audioType)
{
if (!QSBWorldSync.AllObjectsReady)
{
return true;
}
__instance.GetWorldObject<QSBGhostEffects>().PlayLanternAudio(audioType);
return false;
}
[HarmonyPrefix]
[HarmonyPatch(nameof(GhostEffects.Update_Effects))]
public static bool Update_Effects(GhostEffects __instance)
{
if (!QSBWorldSync.AllObjectsReady)
{
return true;
}
__instance.GetWorldObject<QSBGhostEffects>().Update_Effects();
return false;
}
}

View File

@ -0,0 +1,52 @@
using HarmonyLib;
using QSB.EchoesOfTheEye.Ghosts.WorldObjects;
using QSB.Patches;
using QSB.Utility;
using QSB.WorldSync;
namespace QSB.EchoesOfTheEye.Ghosts.Patches;
[HarmonyPatch(typeof(GhostHotelDirector))]
internal class GhostHotelDirectorPatches : QSBPatch
{
public override QSBPatchTypes Type => QSBPatchTypes.OnClientConnect;
[HarmonyReversePatch]
[HarmonyPatch(typeof(GhostDirector), nameof(GhostDirector.OnDestroy))]
public static void GhostDirector_OnDestroy_Stub(object instance) { }
[HarmonyPrefix]
[HarmonyPatch(nameof(GhostHotelDirector.OnDestroy))]
public static bool OnDestroy(GhostHotelDirector __instance)
{
if (!QSBWorldSync.AllObjectsReady)
{
return true;
}
GhostDirector_OnDestroy_Stub(__instance);
__instance._hotelProjector.OnProjectorExtinguished -= __instance.OnHotelProjectorExtinguished;
__instance._bridgeProjector.OnProjectorLit -= __instance.OnBridgeProjectorLit;
__instance._depthsVolume.OnEntry -= __instance.OnEnterDepths;
__instance._depthsVolume.OnExit -= __instance.OnExitDepths;
for (var i = 0; i < __instance._hotelDepthsGhosts.Length; i++)
{
__instance._hotelDepthsGhosts[i].GetWorldObject<QSBGhostBrain>().OnIdentifyIntruder -= GhostManager.CustomOnHotelDepthsGhostsIdentifiedIntruder;
}
return false;
}
/*
* I have no idea why, but for some reason unknown to the damned souls that walk this mortal plane,
* this method only runs when this patch is here. What the absolute fuck.
*/
[HarmonyPrefix]
[HarmonyPatch(typeof(GhostDirector), nameof(GhostDirector.WakeGhosts))]
public static bool WakeGhosts()
{
return true;
}
}

View File

@ -0,0 +1,109 @@
using GhostEnums;
using HarmonyLib;
using QSB.EchoesOfTheEye.Ghosts.WorldObjects;
using QSB.Patches;
using QSB.Utility;
using QSB.WorldSync;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace QSB.EchoesOfTheEye.Ghosts.Patches;
[HarmonyPatch(typeof(GhostPartyDirector))]
internal class GhostPartyDirectorPatches : QSBPatch
{
public override QSBPatchTypes Type => QSBPatchTypes.OnClientConnect;
[HarmonyPrefix]
[HarmonyPatch(nameof(GhostPartyDirector.UnlockGhostForAmbush))]
public static bool UnlockGhostForAmbush(GhostPartyDirector __instance, bool firstAmbush)
{
if (!QSBWorldSync.AllObjectsReady)
{
return true;
}
if (__instance._ghostsWaitingToAmbush.Count == 0)
{
return false;
}
var index = Random.Range(0, __instance._ghostsWaitingToAmbush.Count);
DebugLog.DebugWrite($"Unlocking ghost {index} for ambush.");
var ghost = __instance._ghostsWaitingToAmbush[index].GetWorldObject<QSBGhostBrain>();
(ghost.GetAction(GhostAction.Name.PartyHouse) as QSBPartyHouseAction).AllowChasePlayer();
ghost.HintPlayerLocation(ghost._data.players.MinBy(x => x.Value.playerLocation.distance).Key);
if (firstAmbush)
{
ghost.GetEffects().GetWorldObject<QSBGhostEffects>().PlayVoiceAudioNear(global::AudioType.Ghost_Stalk, 1f);
}
__instance._ghostsWaitingToAmbush.QuickRemoveAt(index);
return false;
}
[HarmonyPrefix]
[HarmonyPatch(nameof(GhostPartyDirector.OnEnterAmbushTrigger))]
public static bool OnEnterAmbushTrigger(GhostPartyDirector __instance, GameObject hitObj)
{
if (!QSBWorldSync.AllObjectsReady)
{
return true;
}
if (__instance._ambushTriggered)
{
return false;
}
DebugLog.DebugWrite($"OnEnterAmbushTrigger");
if (hitObj.CompareTag("PlayerDetector"))
{
__instance._ambushTriggeredThisLoop = true;
__instance._ambushTriggered = true;
__instance._waitingToAmbushInitial = true;
__instance._ambushTriggerTime = Time.time + (__instance._ambushTriggeredThisLoop ? __instance._secondaryAmbushDelay : __instance._initialAmbushDelay);
(__instance._fireplaceGhost.GetWorldObject<QSBGhostBrain>().GetAction(GhostAction.Name.PartyHouse) as QSBPartyHouseAction).LookAtPlayer(0f, TurnSpeed.MEDIUM);
for (int i = 0; i < __instance._ambushGhosts.Length; i++)
{
float delay = (float)i;
(__instance._ambushGhosts[i].GetWorldObject<QSBGhostBrain>().GetAction(GhostAction.Name.PartyHouse) as QSBPartyHouseAction).LookAtPlayer(delay, TurnSpeed.SLOWEST);
}
}
return false;
}
[HarmonyPrefix]
[HarmonyPatch(nameof(GhostPartyDirector.OnEnterSector))]
public static bool OnEnterSector(GhostPartyDirector __instance, SectorDetector detector)
{
if (!QSBWorldSync.AllObjectsReady)
{
return true;
}
if (__instance._connectedDreamCampfire != null && __instance._connectedDreamCampfire.GetState() == Campfire.State.UNLIT)
{
return false;
}
if (detector.GetOccupantType() == DynamicOccupant.Player)
{
__instance._partyMusicController.FadeIn(3f);
__instance._ghostsWaitingToAmbush.Clear();
__instance._ghostsWaitingToAmbush.AddRange(__instance._ambushGhosts);
for (int i = 0; i < __instance._directedGhosts.Length; i++)
{
(__instance._directedGhosts[i].GetWorldObject<QSBGhostBrain>().GetAction(GhostAction.Name.PartyHouse) as QSBPartyHouseAction).ResetAllowChasePlayer();
}
}
return false;
}
}

View File

@ -0,0 +1,135 @@
using HarmonyLib;
using QSB.EchoesOfTheEye.Ghosts.WorldObjects;
using QSB.Patches;
using QSB.Utility;
using QSB.WorldSync;
using System.Reflection;
using UnityEngine;
namespace QSB.EchoesOfTheEye.Ghosts.Patches;
[HarmonyPatch(typeof(GhostPartyPathDirector))]
internal class GhostPartyPathDirectorPatches : QSBPatch
{
public override QSBPatchTypes Type => QSBPatchTypes.OnClientConnect;
[HarmonyReversePatch]
[HarmonyPatch(typeof(GhostDirector), nameof(GhostDirector.OnDestroy))]
public static void GhostDirector_OnDestroy_Stub(object instance) { }
[HarmonyPrefix]
[HarmonyPatch(nameof(GhostPartyPathDirector.OnDestroy))]
public static bool OnDestroy(GhostPartyPathDirector __instance)
{
if (!QSBWorldSync.AllObjectsReady)
{
return true;
}
GhostDirector_OnDestroy_Stub(__instance);
for (var i = 0; i < __instance._directedGhosts.Length; i++)
{
__instance._directedGhosts[i].GetWorldObject<QSBGhostBrain>().OnIdentifyIntruder -= GhostManager.CustomOnGhostIdentifyIntruder;
}
return false;
}
[HarmonyPrefix]
[HarmonyPatch(nameof(GhostPartyPathDirector.Update))]
public static bool Update(GhostPartyPathDirector __instance)
{
if (!QSBWorldSync.AllObjectsReady)
{
return true;
}
if (!QSBCore.IsHost)
{
return false;
}
if (__instance._connectedCampfireExtinguished)
{
return false;
}
for (var i = __instance._dispatchedGhosts.Count - 1; i >= 0; i--)
{
var ghostBrain = __instance._dispatchedGhosts[i].GetWorldObject<QSBGhostBrain>();
if (ghostBrain.GetCurrentActionName() == GhostAction.Name.PartyPath)
{
var partyPathAction = ghostBrain.GetCurrentAction() as QSBPartyPathAction;
if (partyPathAction.hasReachedEndOfPath)
{
if (!partyPathAction.isMovingToFinalPosition)
{
var transform = __instance._numArrivedGhosts < __instance._ghostFinalDestinations.Length
? __instance._ghostFinalDestinations[__instance._numArrivedGhosts].destinationTransform
: __instance._ghostOverflowFinalDestinations[__instance._numArrivedGhosts % __instance._ghostOverflowFinalDestinations.Length].transform;
partyPathAction.MoveToFinalPosition(transform.position);
__instance._numArrivedGhosts++;
}
if (!__instance._respawnBlockTrigger.IsTrackingObject(Locator.GetPlayerDetector()))
{
__instance._dispatchedGhosts.QuickRemoveAt(i);
ghostBrain.AttachedObject.transform.position = __instance._ghostSpawns[Random.Range(0, __instance._ghostSpawns.Length)].spawnTransform.position;
ghostBrain.AttachedObject.transform.eulerAngles = Vector3.up * __instance._ghostSpawns[Random.Range(0, __instance._ghostSpawns.Length)].spawnTransform.eulerAngles.y;
ghostBrain.TabulaRasa();
partyPathAction.ResetPath();
if (__instance._numEnabledGhostProxies < __instance._ghostFinalDestinations.Length && __instance._ghostFinalDestinations[__instance._numEnabledGhostProxies].proxyGhost != null)
{
__instance._ghostFinalDestinations[__instance._numEnabledGhostProxies].proxyGhost.gameObject.SetActive(true);
}
__instance._numEnabledGhostProxies++;
__instance._waitingGhosts.Add(ghostBrain.AttachedObject);
}
}
}
}
if (__instance._waitingGhosts.Count > 0
&& __instance._waitingGhosts[0].GetCurrentActionName() == GhostAction.Name.PartyPath
&& (__instance._dispatchedGhosts.Count == 0 || Time.timeSinceLevelLoad > __instance._nextGhostDispatchTime))
{
DebugLog.DebugWrite($"Dispatch new ghost!");
var ghostBrain2 = __instance._waitingGhosts[0].GetWorldObject<QSBGhostBrain>();
var num = Random.Range(0, __instance._ghostSpawns.Length);
ghostBrain2.AttachedObject.transform.position = __instance._ghostSpawns[num].spawnTransform.position;
ghostBrain2.AttachedObject.transform.eulerAngles = Vector3.up * __instance._ghostSpawns[num].spawnTransform.eulerAngles.y;
(ghostBrain2.GetCurrentAction() as QSBPartyPathAction).StartFollowPath();
__instance._ghostSpawns[num].spawnDoor.Open();
__instance._ghostSpawns[num].spawnDoorTimer = Time.timeSinceLevelLoad + 4f;
__instance._waitingGhosts.RemoveAt(0);
__instance._lastDispatchedGhost = ghostBrain2.AttachedObject;
__instance._dispatchedGhosts.Add(ghostBrain2.AttachedObject);
__instance._nextGhostDispatchTime = Time.timeSinceLevelLoad + Random.Range(__instance._minGhostDispatchDelay, __instance._maxGhostDispatchDelay);
}
for (var j = 0; j < __instance._ghostSpawns.Length; j++)
{
if (__instance._ghostSpawns[j].spawnDoor.IsOpen() && Time.timeSinceLevelLoad > __instance._ghostSpawns[j].spawnDoorTimer)
{
__instance._ghostSpawns[j].spawnDoor.Close();
}
}
return false;
}
[HarmonyPrefix]
[HarmonyPatch(nameof(GhostPartyPathDirector.OnGhostIdentifyIntruder))]
public static bool OnGhostIdentifyIntruder(GhostPartyPathDirector __instance, GhostBrain ghostBrain, GhostData ghostData)
{
if (!QSBWorldSync.AllObjectsReady)
{
return true;
}
DebugLog.ToConsole($"Error - {MethodBase.GetCurrentMethod().Name} not supported!", OWML.Common.MessageType.Error);
return false;
}
}

View File

@ -0,0 +1,85 @@
using HarmonyLib;
using QSB.EchoesOfTheEye.Ghosts.WorldObjects;
using QSB.Patches;
using QSB.Utility;
using QSB.WorldSync;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace QSB.EchoesOfTheEye.Ghosts.Patches;
[HarmonyPatch(typeof(GhostSensors))]
internal class GhostSensorsPatches : QSBPatch
{
public override QSBPatchTypes Type => QSBPatchTypes.OnClientConnect;
[HarmonyPrefix]
[HarmonyPatch(nameof(GhostSensors.Initialize))]
public static bool Initialize(GhostSensors __instance, GhostData data, OWTriggerVolume guardVolume)
{
if (!QSBWorldSync.AllObjectsReady)
{
return true;
}
DebugLog.ToConsole($"Error - {MethodBase.GetCurrentMethod().Name} not supported!", OWML.Common.MessageType.Error);
return false;
}
[HarmonyPrefix]
[HarmonyPatch(nameof(GhostSensors.CanGrabPlayer))]
public static bool CanGrabPlayer(GhostSensors __instance, ref bool __result)
{
if (!QSBWorldSync.AllObjectsReady)
{
return true;
}
DebugLog.ToConsole($"Error - {MethodBase.GetCurrentMethod().Name} not supported!", OWML.Common.MessageType.Error);
return false;
}
[HarmonyPrefix]
[HarmonyPatch(nameof(GhostSensors.FixedUpdate_Sensors))]
public static bool FixedUpdate_Sensors(GhostSensors __instance)
{
if (!QSBWorldSync.AllObjectsReady)
{
return true;
}
__instance.GetWorldObject<QSBGhostSensors>().FixedUpdate_Sensors();
return false;
}
[HarmonyPrefix]
[HarmonyPatch(nameof(GhostSensors.OnEnterContactTrigger))]
public static bool OnEnterContactTrigger(GhostSensors __instance, GameObject hitObj)
{
if (!QSBWorldSync.AllObjectsReady)
{
return true;
}
__instance.GetWorldObject<QSBGhostSensors>().OnEnterContactTrigger(hitObj);
return false;
}
[HarmonyPrefix]
[HarmonyPatch(nameof(GhostSensors.OnExitContactTrigger))]
public static bool OnExitContactTrigger(GhostSensors __instance, GameObject hitObj)
{
if (!QSBWorldSync.AllObjectsReady)
{
return true;
}
__instance.GetWorldObject<QSBGhostSensors>().OnExitContactTrigger(hitObj);
return false;
}
}

View File

@ -0,0 +1,181 @@
using HarmonyLib;
using QSB.EchoesOfTheEye.Ghosts.Actions;
using QSB.EchoesOfTheEye.Ghosts.WorldObjects;
using QSB.Patches;
using QSB.Utility;
using QSB.WorldSync;
using UnityEngine;
namespace QSB.EchoesOfTheEye.Ghosts.Patches;
internal class GhostZone2DirectorPatches : QSBPatch
{
public override QSBPatchTypes Type => QSBPatchTypes.OnClientConnect;
[HarmonyReversePatch]
[HarmonyPatch(typeof(GhostDirector), nameof(GhostDirector.Awake))]
public static void GhostDirector_Awake_Stub(object instance) { }
[HarmonyPrefix]
[HarmonyPatch(typeof(GhostZone2Director), nameof(GhostZone2Director.Awake))]
public static bool Awake(GhostZone2Director __instance)
{
if (!QSBWorldSync.AllObjectsReady)
{
return true;
}
GhostDirector_Awake_Stub(__instance);
QSBGhostZone2Director.ElevatorsStatus = new QSBGhostZone2Director.ElevatorStatus[__instance._elevators.Length];
for (var j = 0; j < __instance._elevators.Length; j++)
{
QSBGhostZone2Director.ElevatorsStatus[j].elevatorPair = __instance._elevators[j];
QSBGhostZone2Director.ElevatorsStatus[j].activated = false;
QSBGhostZone2Director.ElevatorsStatus[j].deactivated = false;
QSBGhostZone2Director.ElevatorsStatus[j].lightsDeactivated = false;
}
return false;
}
[HarmonyReversePatch]
[HarmonyPatch(typeof(GhostDirector), nameof(GhostDirector.OnDestroy))]
public static void GhostDirector_OnDestroy_Stub(object instance) { }
[HarmonyPrefix]
[HarmonyPatch(typeof(GhostZone2Director), nameof(GhostZone2Director.OnDestroy))]
public static bool OnDestroy(GhostZone2Director __instance)
{
if (!QSBWorldSync.AllObjectsReady)
{
return true;
}
GhostDirector_OnDestroy_Stub(__instance);
__instance._lightsProjector.OnProjectorExtinguished -= __instance.OnLightsExtinguished;
__instance._undergroundVolume.OnEntry -= __instance.OnEnterUnderground;
__instance._undergroundVolume.OnExit -= __instance.OnExitUnderground;
__instance._finalTotem.OnRinging -= __instance.OnAlarmRinging;
for (int i = 0; i < __instance._cityGhosts.Length; i++)
{
__instance._cityGhosts[i].GetWorldObject<QSBGhostBrain>().OnIdentifyIntruder -= GhostManager.CustomOnCityGhostsIdentifiedIntruder;
}
__instance._ghostTutorialArrival.OnEntry -= __instance.OnStartGhostTutorial;
return false;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(GhostZone2Director), nameof(GhostZone2Director.Update))]
public static bool Update(GhostZone2Director __instance)
{
if (!QSBWorldSync.AllObjectsReady)
{
return true;
}
if (__instance._lightsProjectorExtinguished)
{
if (__instance._ghostsAreAwake && !__instance._ghostsAlerted && Time.time >= __instance._ghostAlertTime)
{
__instance._ghostHowlAudioSource.PlayOneShot(global::AudioType.Ghost_SomeoneIsInHereHowl, 1f);
__instance._ghostsAlerted = true;
}
for (var i = 0; i < QSBGhostZone2Director.ElevatorsStatus.Length; i++)
{
if (!QSBGhostZone2Director.ElevatorsStatus[i].activated && QSBGhostZone2Director.ElevatorsStatus[i].elevatorAction.reachedEndOfPath)
{
QSBGhostZone2Director.ElevatorsStatus[i].ghostController.SetNodeMap(QSBGhostZone2Director.ElevatorsStatus[i].elevatorPair.nodeMap);
QSBGhostZone2Director.ElevatorsStatus[i].elevatorPair.elevator.topLight.FadeTo(1f, 0.2f);
QSBGhostZone2Director.ElevatorsStatus[i].elevatorPair.elevator.GoToDestination(0);
QSBGhostZone2Director.ElevatorsStatus[i].activated = true;
}
if (!QSBGhostZone2Director.ElevatorsStatus[i].lightsDeactivated && QSBGhostZone2Director.ElevatorsStatus[i].activated && QSBGhostZone2Director.ElevatorsStatus[i].elevatorPair.elevator.isAtBottom)
{
QSBGhostZone2Director.ElevatorsStatus[i].lightsDeactivated = true;
QSBGhostZone2Director.ElevatorsStatus[i].elevatorPair.elevator.topLight.FadeTo(0f, 0.2f);
if (QSBGhostZone2Director.ElevatorsStatus[i].elevatorPair.cityDestination)
{
QSBGhostZone2Director.ElevatorsStatus[i].ghostController.SetNodeMap(__instance._cityNodeMap);
}
else
{
QSBGhostZone2Director.ElevatorsStatus[i].ghostController.SetNodeMap(__instance._undercityNodeMap);
}
if (i == 1)
{
QSBGhostZone2Director.ElevatorsStatus[i].ghostController.gameObject.GetComponent<Transform>().position = __instance._teleportNode.position;
}
QSBGhostZone2Director.ElevatorsStatus[i].elevatorAction.UseElevator();
QSBGhostZone2Director.ElevatorsStatus[i].timeSinceArrival = Time.time;
}
if (QSBGhostZone2Director.ElevatorsStatus[i].lightsDeactivated && QSBGhostZone2Director.ElevatorsStatus[i].activated && !QSBGhostZone2Director.ElevatorsStatus[i].deactivated && Time.time >= QSBGhostZone2Director.ElevatorsStatus[i].timeSinceArrival + 2f)
{
QSBGhostZone2Director.ElevatorsStatus[i].elevatorPair.elevator.GoToDestination(1);
QSBGhostZone2Director.ElevatorsStatus[i].deactivated = true;
}
}
}
return false;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(GhostZone2Director), nameof(GhostZone2Director.OnLightsExtinguished))]
public static bool OnLightsExtinguished(GhostZone2Director __instance)
{
if (!QSBWorldSync.AllObjectsReady)
{
return true;
}
DebugLog.DebugWrite($"LIGHTS EXTINGUISHED");
__instance._lightsProjectorExtinguished = true;
__instance.WakeGhosts();
if (QSBGhostZone2Director.ElevatorsStatus == null)
{
QSBGhostZone2Director.ElevatorsStatus = new QSBGhostZone2Director.ElevatorStatus[__instance._elevators.Length];
for (var j = 0; j < __instance._elevators.Length; j++)
{
QSBGhostZone2Director.ElevatorsStatus[j].elevatorPair = __instance._elevators[j];
QSBGhostZone2Director.ElevatorsStatus[j].activated = false;
QSBGhostZone2Director.ElevatorsStatus[j].deactivated = false;
QSBGhostZone2Director.ElevatorsStatus[j].lightsDeactivated = false;
}
}
DebugLog.DebugWrite($"ESCALATE THREAT AWARENESS");
for (var i = 0; i < __instance._directedGhosts.Length; i++)
{
__instance._directedGhosts[i].EscalateThreatAwareness(GhostData.ThreatAwareness.SomeoneIsInHere);
__instance._directedGhosts[i].GetWorldObject<QSBGhostBrain>().GetEffects().CancelStompyFootsteps();
}
DebugLog.DebugWrite($"SETUP ELEVATOR STATUS");
for (var j = 0; j < QSBGhostZone2Director.ElevatorsStatus.Length; j++)
{
DebugLog.DebugWrite($"[{j}]");
DebugLog.DebugWrite($"- fade light down");
QSBGhostZone2Director.ElevatorsStatus[j].elevatorPair.elevator.topLight.FadeTo(0f, 0.2f);
DebugLog.DebugWrite($"- get action");
QSBGhostZone2Director.ElevatorsStatus[j].elevatorAction = __instance._elevators[j].ghost.GetWorldObject<QSBGhostBrain>().GetAction(GhostAction.Name.ElevatorWalk) as QSBElevatorWalkAction;
DebugLog.DebugWrite($"- CallToUseElevator on action");
QSBGhostZone2Director.ElevatorsStatus[j].elevatorAction.CallToUseElevator();
DebugLog.DebugWrite($"- get ghost controller");
QSBGhostZone2Director.ElevatorsStatus[j].ghostController = QSBGhostZone2Director.ElevatorsStatus[j].elevatorPair.ghost.GetComponent<GhostController>();
}
__instance._ghostAlertTime = Time.time + 2f;
return false;
}
}

View File

@ -0,0 +1,176 @@
using QSB.EchoesOfTheEye.Ghosts.Actions;
using QSB.EchoesOfTheEye.Ghosts.WorldObjects;
using QSB.WorldSync;
using UnityEngine;
namespace QSB.EchoesOfTheEye.Ghosts;
public abstract class QSBGhostAction
{
protected QSBGhostBrain _brain;
protected QSBGhostData _data => _brain._data;
protected QSBGhostController _controller => _brain.AttachedObject._controller.GetWorldObject<QSBGhostController>();
protected QSBGhostSensors _sensors => _brain.AttachedObject._sensors.GetWorldObject<QSBGhostSensors>();
protected QSBGhostEffects _effects => _brain.AttachedObject._effects.GetWorldObject<QSBGhostEffects>();
protected Transform _transform;
protected bool _running;
protected float _enterTime;
public static QSBGhostAction CreateAction(GhostAction.Name name)
{
if (!QSBCore.IsHost)
{
return new QSBGhostActionStub { Name = name };
}
QSBGhostAction ghostAction;
switch (name)
{
case GhostAction.Name.Wait:
ghostAction = new QSBWaitAction();
break;
case GhostAction.Name.Sleep:
ghostAction = new QSBSleepAction();
break;
case GhostAction.Name.Sleepwalk:
ghostAction = new QSBSleepwalkAction();
break;
case GhostAction.Name.PartyPath:
ghostAction = new QSBPartyPathAction();
break;
case GhostAction.Name.PartyHouse:
ghostAction = new QSBPartyHouseAction();
break;
case GhostAction.Name.ElevatorWalk:
ghostAction = new QSBElevatorWalkAction();
break;
case GhostAction.Name.Sentry:
ghostAction = new QSBSentryAction();
break;
case GhostAction.Name.Guard:
ghostAction = new QSBGuardAction();
break;
case GhostAction.Name.IdentifyIntruder:
ghostAction = new QSBIdentifyIntruderAction();
break;
case GhostAction.Name.Chase:
ghostAction = new QSBChaseAction();
break;
case GhostAction.Name.Hunt:
ghostAction = new QSBHuntAction();
break;
case GhostAction.Name.Stalk:
ghostAction = new QSBStalkAction();
break;
case GhostAction.Name.Grab:
ghostAction = new QSBGrabAction();
break;
case GhostAction.Name.SearchForIntruder:
ghostAction = new QSBSearchAction();
break;
default:
Debug.LogError("Failed to create action from name " + name);
return null;
}
if (ghostAction.GetName() != name)
{
Debug.LogError("New action name " + ghostAction.GetName() + "does not match supplied name " + name);
Debug.Break();
}
return ghostAction;
}
public virtual void Initialize(QSBGhostBrain brain)
{
_brain = brain;
this._transform = this._controller.AttachedObject.transform;
}
public void EnterAction()
{
this._running = true;
this._enterTime = Time.time;
if (!QSBCore.IsHost)
{
return;
}
this.OnEnterAction();
}
public void ExitAction()
{
this._running = false;
if (!QSBCore.IsHost)
{
return;
}
this.OnExitAction();
}
public abstract GhostAction.Name GetName();
public abstract float CalculateUtility();
public abstract bool Update_Action();
public virtual bool IsInterruptible()
{
return true;
}
public virtual float GetActionDelay()
{
return 0f;
}
public virtual void FixedUpdate_Action()
{
}
public virtual void OnArriveAtPosition()
{
}
public virtual void OnTraversePathNode(GhostNode node)
{
}
public virtual void OnFaceNode(GhostNode node)
{
}
public virtual void OnFinishFaceNodeList()
{
}
public virtual void DrawGizmos(bool isGhostSelected)
{
}
public virtual void OnSetAsPending()
{
}
protected virtual void OnEnterAction()
{
}
protected virtual void OnExitAction()
{
}
protected float GetActionTimeElapsed()
{
if (!this._running)
{
return -1f;
}
return Time.time - this._enterTime;
}
}

View File

@ -0,0 +1,125 @@
using QSB.EchoesOfTheEye.Ghosts.WorldObjects;
using QSB.Player;
using QSB.Utility;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace QSB.EchoesOfTheEye.Ghosts;
public class QSBGhostData
{
public GhostData.ThreatAwareness threatAwareness;
public GhostAction.Name currentAction = GhostAction.Name.None;
public GhostAction.Name previousAction = GhostAction.Name.None;
public bool isAlive = true;
public bool hasWokenUp;
public bool reduceGuardUtility;
public bool fastStalkUnlocked;
public float illuminatedByPlayerMeter;
public bool reducedFrights_allowChase;
public bool isIlluminated;
public bool IsIlluminatedByAnyPlayer => players.Values.Any(x => x.sensor.isIlluminatedByPlayer);
public Dictionary<PlayerInfo, GhostPlayer> players = new();
public GhostPlayer localPlayer => players[QSBPlayerManager.LocalPlayer];
public GhostPlayer interestedPlayer;
public void TabulaRasa()
{
threatAwareness = GhostData.ThreatAwareness.EverythingIsNormal;
reduceGuardUtility = false;
fastStalkUnlocked = false;
illuminatedByPlayerMeter = 0f;
foreach (var player in players.Values)
{
player.isPlayerLocationKnown = false;
player.wasPlayerLocationKnown = false;
player.timeLastSawPlayer = 0f;
player.timeSincePlayerLocationKnown = float.PositiveInfinity;
player.playerMinLanternRange = 0f;
}
}
public void OnPlayerExitDreamWorld()
{
localPlayer.isPlayerLocationKnown = false;
localPlayer.wasPlayerLocationKnown = false;
reduceGuardUtility = false;
fastStalkUnlocked = false;
localPlayer.timeSincePlayerLocationKnown = float.PositiveInfinity;
}
public void OnEnterAction(GhostAction.Name actionName)
{
if (actionName == GhostAction.Name.IdentifyIntruder || actionName - GhostAction.Name.Chase <= 2)
{
reduceGuardUtility = true;
}
}
public void FixedUpdate_Data(GhostController controller, GhostSensors sensors)
{
foreach (var player in QSBPlayerManager.PlayerList)
{
if (!players.ContainsKey(player))
{
var newPlayer = new GhostPlayer
{
player = player
};
players.Add(player, newPlayer);
}
}
foreach (var pair in players)
{
var player = pair.Value;
if (!player.player.InDreamWorld)
{
continue;
}
player.wasPlayerLocationKnown = player.isPlayerLocationKnown;
player.isPlayerLocationKnown = player.sensor.isPlayerVisible
|| player.sensor.isPlayerHeldLanternVisible
|| player.sensor.isIlluminatedByPlayer
|| player.sensor.inContactWithPlayer;
if (!reduceGuardUtility && player.sensor.isIlluminatedByPlayer)
{
reduceGuardUtility = true;
}
var worldPosition = pair.Key.Body.transform.position - pair.Key.Body.transform.up;
var worldVelocity = pair.Key.Velocity - controller.GetNodeMap().GetOWRigidbody().GetVelocity();
player.playerLocation.Update(worldPosition, worldVelocity, controller);
player.playerMinLanternRange = pair.Key.AssignedSimulationLantern.AttachedObject.GetLanternController().GetMinRange();
if (player.isPlayerLocationKnown)
{
player.lastKnownPlayerLocation.CopyFromOther(player.playerLocation);
player.lastKnownSensor.CopyFromOther(player.sensor);
player.timeLastSawPlayer = Time.time;
player.timeSincePlayerLocationKnown = 0f;
}
else
{
if (player.wasPlayerLocationKnown)
{
player.firstUnknownSensor.CopyFromOther(player.sensor);
}
player.lastKnownPlayerLocation.Update(controller);
player.timeSincePlayerLocationKnown += Time.deltaTime;
}
}
if (threatAwareness >= GhostData.ThreatAwareness.IntruderConfirmed && IsIlluminatedByAnyPlayer && !PlayerData.GetReducedFrights())
{
illuminatedByPlayerMeter += Time.deltaTime;
return;
}
illuminatedByPlayerMeter = Mathf.Max(0f, illuminatedByPlayerMeter - (Time.deltaTime * 0.5f));
}
}

View File

@ -0,0 +1,31 @@
namespace QSB.EchoesOfTheEye.Ghosts;
public class QSBGhostSensorData
{
public bool isPlayerVisible;
public bool isPlayerHeldLanternVisible;
public bool isPlayerDroppedLanternVisible;
public bool isPlayerHoldingLantern;
public bool isIlluminatedByPlayer;
public bool inContactWithPlayer;
public bool isPlayerOccluded;
public bool isPlayerIlluminated;
public bool isPlayerIlluminatedByUs;
public bool isPlayerInGuardVolume;
public bool knowsPlayerVelocity => isPlayerVisible || isPlayerHeldLanternVisible;
public void CopyFromOther(QSBGhostSensorData other)
{
isPlayerVisible = other.isPlayerVisible;
isPlayerHeldLanternVisible = other.isPlayerHeldLanternVisible;
isPlayerDroppedLanternVisible = other.isPlayerDroppedLanternVisible;
isPlayerHoldingLantern = other.isPlayerHoldingLantern;
isIlluminatedByPlayer = other.isIlluminatedByPlayer;
inContactWithPlayer = other.inContactWithPlayer;
isPlayerOccluded = other.isPlayerOccluded;
isPlayerIlluminated = other.isPlayerIlluminated;
isPlayerIlluminatedByUs = other.isPlayerIlluminatedByUs;
isPlayerInGuardVolume = other.isPlayerInGuardVolume;
}
}

View File

@ -0,0 +1,24 @@
using QSB.EchoesOfTheEye.Ghosts.Actions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace QSB.EchoesOfTheEye.Ghosts;
public static class QSBGhostZone2Director
{
public static ElevatorStatus[] ElevatorsStatus;
public struct ElevatorStatus
{
public GhostZone2Director.ElevatorPair elevatorPair;
public bool activated;
public bool lightsDeactivated;
public bool deactivated;
public float timeSinceArrival;
public QSBElevatorWalkAction elevatorAction;
public GhostController ghostController;
}
}

View File

@ -0,0 +1,5 @@
using QSB.WorldSync;
namespace QSB.EchoesOfTheEye.Ghosts.WorldObjects;
public interface IGhostObject : IWorldObject { }

View File

@ -0,0 +1,589 @@
using Cysharp.Threading.Tasks;
using GhostEnums;
using QSB.EchoesOfTheEye.Ghosts.Messages;
using QSB.Messaging;
using QSB.Player;
using QSB.Utility;
using QSB.WorldSync;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;
namespace QSB.EchoesOfTheEye.Ghosts.WorldObjects;
public class QSBGhostBrain : WorldObject<GhostBrain>, IGhostObject
{
#region World Object Stuff
public override void SendInitialState(uint to)
{
}
public override async UniTask Init(CancellationToken ct)
{
Awake();
Start();
}
public override bool ShouldDisplayDebug()
=> base.ShouldDisplayDebug()
&& QSBCore.DebugSettings.DrawGhostAI;
public override string ReturnLabel()
{
var label = $"Name:{AttachedObject.ghostName}" +
$"\r\nAwareness:{AttachedObject.GetThreatAwareness()}" +
$"\r\nCurrent action:{AttachedObject.GetCurrentActionName()}" +
$"\r\nIllumination meter:{_data.illuminatedByPlayerMeter}";
if (QSBCore.IsHost)
{
foreach (var action in _actionLibrary.OrderByDescending(x => x.CalculateUtility()))
{
label += $"\r\n{action.GetName()}:{action.CalculateUtility()}";
}
}
return label;
}
public override void DisplayLines()
{
ControllerLines(AttachedObject._controller);
DataLines(_data, AttachedObject._controller);
if (_currentAction != null)
{
_currentAction.DrawGizmos(true);
}
}
private void ControllerLines(GhostController controller)
{
Popcron.Gizmos.Sphere(controller.transform.position, 2f, Color.white);
if (controller._followNodePath)
{
for (var i = controller._nodePath.Count - 1; i >= 0; i--)
{
Popcron.Gizmos.Sphere(controller.LocalToWorldPosition(controller._nodePath[i].localPosition), 0.25f, Color.cyan, 3);
var hasVisited = controller._pathIndex < i;
var color = hasVisited ? Color.white : Color.cyan;
if (i != 0)
{
Popcron.Gizmos.Line(controller.LocalToWorldPosition(controller._nodePath[i].localPosition), controller.LocalToWorldPosition(controller._nodePath[i - 1].localPosition), color);
}
}
if (controller._hasFinalPathPosition)
{
Popcron.Gizmos.Sphere(controller.LocalToWorldPosition(controller._finalPathPosition), 0.3f, Color.red, 8);
}
}
}
private void DataLines(QSBGhostData data, GhostController controller)
{
foreach (var player in data.players.Values)
{
if (player.timeSincePlayerLocationKnown != float.PositiveInfinity)
{
Popcron.Gizmos.Line(controller.transform.position, controller.LocalToWorldPosition(player.lastKnownPlayerLocation.localPosition), Color.magenta);
Popcron.Gizmos.Sphere(controller.LocalToWorldPosition(player.lastKnownPlayerLocation.localPosition), 1f, Color.magenta);
}
}
}
#endregion
internal QSBGhostData _data;
private List<QSBGhostAction> _actionLibrary = new();
private QSBGhostAction _currentAction;
private QSBGhostAction _pendingAction;
public OWEvent<GhostBrain, QSBGhostData> OnIdentifyIntruder = new(4);
public GhostAction.Name GetCurrentActionName()
{
if (_currentAction == null)
{
return GhostAction.Name.None;
}
return _currentAction.GetName();
}
public QSBGhostAction GetCurrentAction()
{
return _currentAction;
}
public QSBGhostAction GetAction(GhostAction.Name actionName)
{
for (int i = 0; i < _actionLibrary.Count; i++)
{
if (_actionLibrary[i].GetName() == actionName)
{
return _actionLibrary[i];
}
}
return null;
}
public GhostData.ThreatAwareness GetThreatAwareness()
{
return _data.threatAwareness;
}
public GhostEffects GetEffects()
{
return AttachedObject._effects;
}
public bool CheckDreadAudioConditions()
{
return _currentAction != null
&& _data.localPlayer.playerLocation.distance < 10f
&& _currentAction.GetName() != GhostAction.Name.Sentry
&& _currentAction.GetName() != GhostAction.Name.Grab;
}
public bool CheckFearAudioConditions(bool fearAudioAlreadyPlaying)
{
if (_currentAction == null)
{
return false;
}
if (fearAudioAlreadyPlaying)
{
return _currentAction.GetName() is GhostAction.Name.Chase or GhostAction.Name.Grab;
}
return _currentAction.GetName() == GhostAction.Name.Chase;
}
public void Awake()
{
AttachedObject._controller = AttachedObject.GetComponent<GhostController>();
AttachedObject._sensors = AttachedObject.GetComponent<GhostSensors>();
_data = new();
if (AttachedObject._data != null)
{
_data.threatAwareness = AttachedObject._data.threatAwareness;
}
}
public void Start()
{
AttachedObject.enabled = false;
AttachedObject._controller.GetDreamLanternController().enabled = false;
AttachedObject._controller.GetWorldObject<QSBGhostController>().Initialize(AttachedObject._nodeLayer, AttachedObject._effects.GetWorldObject<QSBGhostEffects>());
AttachedObject._sensors.GetWorldObject<QSBGhostSensors>().Initialize(_data, AttachedObject._guardVolume);
AttachedObject._effects.GetWorldObject<QSBGhostEffects>().Initialize(AttachedObject._controller.GetNodeRoot(), AttachedObject._controller, _data);
AttachedObject._effects.OnCallForHelp += AttachedObject.OnCallForHelp;
_data.reducedFrights_allowChase = AttachedObject._reducedFrights_allowChase;
AttachedObject._controller.SetLanternConcealed(AttachedObject._startWithLanternConcealed, false);
AttachedObject._intruderConfirmedBySelf = false;
AttachedObject._intruderConfirmPending = false;
AttachedObject._intruderConfirmTime = 0f;
DebugLog.DebugWrite($"{AttachedObject._name} setting up actions :");
for (var i = 0; i < AttachedObject._actions.Length; i++)
{
DebugLog.DebugWrite($"- {AttachedObject._actions[i]}");
var ghostAction = QSBGhostAction.CreateAction(AttachedObject._actions[i]);
ghostAction.Initialize(this);
_actionLibrary.Add(ghostAction);
}
ClearPendingAction();
}
public void OnDestroy()
{
AttachedObject._sensors.RemoveEventListeners();
AttachedObject._controller.OnArriveAtPosition -= AttachedObject.OnArriveAtPosition;
AttachedObject._controller.OnTraversePathNode -= AttachedObject.OnTraversePathNode;
AttachedObject._controller.OnFaceNode -= AttachedObject.OnFaceNode;
AttachedObject._controller.OnFinishFaceNodeList -= AttachedObject.OnFinishFaceNodeList;
AttachedObject._effects.OnCallForHelp -= AttachedObject.OnCallForHelp;
GlobalMessenger.RemoveListener("EnterDreamWorld", new Callback(AttachedObject.OnEnterDreamWorld));
GlobalMessenger.RemoveListener("ExitDreamWorld", new Callback(AttachedObject.OnExitDreamWorld));
}
public void TabulaRasa()
{
AttachedObject._intruderConfirmedBySelf = false;
AttachedObject._intruderConfirmPending = false;
AttachedObject._intruderConfirmTime = 0f;
AttachedObject._playResponseAudio = false;
_data.TabulaRasa();
}
public void Die()
{
if (!_data.isAlive)
{
return;
}
_data.isAlive = false;
AttachedObject._controller.GetWorldObject<QSBGhostController>().StopMoving();
AttachedObject._controller.GetWorldObject<QSBGhostController>().StopFacing();
AttachedObject._controller.ExtinguishLantern();
AttachedObject._controller.GetCollider().GetComponent<OWCollider>().SetActivation(false);
AttachedObject._controller.GetGrabController().ReleasePlayer();
_pendingAction = null;
_currentAction = null;
_data.currentAction = GhostAction.Name.None;
AttachedObject._effects.PlayDeathAnimation();
AttachedObject._effects.PlayDeathEffects();
}
public void EscalateThreatAwareness(GhostData.ThreatAwareness newThreatAwareness)
{
DebugLog.DebugWrite($"{AttachedObject._name} Escalate threat awareness to {newThreatAwareness}");
if (_data.threatAwareness < newThreatAwareness)
{
_data.threatAwareness = newThreatAwareness;
if (_data.isAlive && _data.threatAwareness == GhostData.ThreatAwareness.IntruderConfirmed)
{
if (AttachedObject._intruderConfirmedBySelf)
{
AttachedObject._effects.GetWorldObject<QSBGhostEffects>().PlayVoiceAudioFar(global::AudioType.Ghost_IntruderConfirmed, 1f);
return;
}
if (AttachedObject._playResponseAudio)
{
AttachedObject._effects.GetWorldObject<QSBGhostEffects>().PlayVoiceAudioFar(global::AudioType.Ghost_IntruderConfirmedResponse, 1f);
AttachedObject._playResponseAudio = false;
}
}
}
}
public void WakeUp()
{
DebugLog.DebugWrite($"Wake up!");
_data.hasWokenUp = true;
}
public bool HearGhostCall(Vector3 playerLocalPosition, float reactDelay, bool playResponseAudio = false)
{
if (_data.isAlive && !_data.hasWokenUp)
{
return false;
}
if (_data.threatAwareness < GhostData.ThreatAwareness.IntruderConfirmed && !AttachedObject._intruderConfirmPending)
{
AttachedObject._intruderConfirmedBySelf = false;
AttachedObject._intruderConfirmPending = true;
AttachedObject._intruderConfirmTime = Time.time + reactDelay;
AttachedObject._playResponseAudio = playResponseAudio;
return true;
}
return false;
}
public bool HearCallForHelp(Vector3 playerLocalPosition, float reactDelay, GhostPlayer player)
{
if (_data.isAlive && !_data.hasWokenUp)
{
return false;
}
DebugLog.DebugWrite($"{AttachedObject._name} Hear call for help!");
if (_data.threatAwareness < GhostData.ThreatAwareness.IntruderConfirmed)
{
_data.threatAwareness = GhostData.ThreatAwareness.IntruderConfirmed;
AttachedObject._intruderConfirmPending = false;
}
AttachedObject._effects.PlayRespondToHelpCallAudio(reactDelay);
_data.reduceGuardUtility = true;
player.lastKnownPlayerLocation.UpdateLocalPosition(playerLocalPosition, AttachedObject._controller);
player.wasPlayerLocationKnown = true;
player.timeSincePlayerLocationKnown = 0f;
return true;
}
public void HintPlayerLocation(PlayerInfo player)
{
var ghostPlayer = _data.players[player];
HintPlayerLocation(ghostPlayer.playerLocation.localPosition, Time.time, ghostPlayer);
}
public void HintPlayerLocation(Vector3 localPosition, float informationTime, GhostPlayer player)
{
if (!_data.hasWokenUp || player.isPlayerLocationKnown)
{
return;
}
if (informationTime > player.timeLastSawPlayer)
{
player.lastKnownPlayerLocation.UpdateLocalPosition(localPosition, AttachedObject._controller);
player.wasPlayerLocationKnown = true;
player.timeSincePlayerLocationKnown = 0f;
}
}
public void FixedUpdate()
{
if (!AttachedObject.enabled)
{
DebugLog.DebugWrite($"attached object is not enabled!");
return;
}
AttachedObject._controller.FixedUpdate_Controller();
AttachedObject._sensors.FixedUpdate_Sensors();
_data.FixedUpdate_Data(AttachedObject._controller, AttachedObject._sensors);
if (!QSBCore.IsHost)
{
return;
}
AttachedObject.FixedUpdate_ThreatAwareness();
if (_currentAction != null)
{
_currentAction.FixedUpdate_Action();
}
}
public void Update()
{
if (!AttachedObject.enabled)
{
return;
}
AttachedObject._controller.Update_Controller();
AttachedObject._sensors.Update_Sensors();
AttachedObject._effects.Update_Effects();
if (!QSBCore.IsHost)
{
return;
}
var flag = false;
if (_currentAction != null)
{
flag = _currentAction.Update_Action();
}
if (!flag && _currentAction != null)
{
_currentAction.ExitAction();
_data.previousAction = _currentAction.GetName();
_currentAction = null;
_data.currentAction = GhostAction.Name.None;
}
if (_data.isAlive && !Locator.GetDreamWorldController().IsExitingDream())
{
AttachedObject.EvaluateActions();
}
}
public void FixedUpdate_ThreatAwareness()
{
if (_data.threatAwareness == GhostData.ThreatAwareness.IntruderConfirmed)
{
return;
}
if (!AttachedObject._intruderConfirmPending
&& (_data.threatAwareness > GhostData.ThreatAwareness.EverythingIsNormal || _data.players.Values.Any(x => x.playerLocation.distance < 20f) || _data.players.Values.Any(x => x.sensor.isPlayerIlluminatedByUs))
&& (_data.players.Values.Any(x => x.sensor.isPlayerVisible) || _data.players.Values.Any(x => x.sensor.inContactWithPlayer)))
{
DebugLog.DebugWrite($"INTRUDER CONFIRMED BY SELF");
AttachedObject._intruderConfirmedBySelf = true;
AttachedObject._intruderConfirmPending = true;
var closestPlayer = _data.players.Values.MinBy(x => x.playerLocation.distance);
var num = Mathf.Lerp(0.1f, 1.5f, Mathf.InverseLerp(5f, 25f, closestPlayer.playerLocation.distance));
AttachedObject._intruderConfirmTime = Time.time + num;
}
if (AttachedObject._intruderConfirmPending && Time.time > AttachedObject._intruderConfirmTime)
{
AttachedObject.EscalateThreatAwareness(GhostData.ThreatAwareness.IntruderConfirmed);
OnIdentifyIntruder.Invoke(AttachedObject, _data);
}
}
public void EvaluateActions()
{
if (_currentAction != null && !_currentAction.IsInterruptible())
{
return;
}
var num = float.NegativeInfinity;
QSBGhostAction actionWithHighestUtility = null;
for (var i = 0; i < _actionLibrary.Count; i++)
{
var num2 = _actionLibrary[i].CalculateUtility();
if (num2 > num)
{
num = num2;
actionWithHighestUtility = _actionLibrary[i];
}
}
if (actionWithHighestUtility == null)
{
DebugLog.ToConsole($"Error - Couldn't find action with highest utility for {AttachedObject._name}?!", OWML.Common.MessageType.Error);
return;
}
var flag = false;
if (_pendingAction == null || (actionWithHighestUtility.GetName() != _pendingAction.GetName() && num > AttachedObject._pendingActionUtility))
{
_pendingAction = actionWithHighestUtility;
AttachedObject._pendingActionUtility = num;
AttachedObject._pendingActionTimer = _pendingAction.GetActionDelay();
flag = true;
}
if (_pendingAction != null && _currentAction != null && _pendingAction.GetName() == _currentAction.GetName())
{
ClearPendingAction();
flag = false;
}
if (flag)
{
_pendingAction.OnSetAsPending();
}
if (_pendingAction != null && AttachedObject._pendingActionTimer <= 0f)
{
ChangeAction(_pendingAction);
}
if (_pendingAction != null)
{
AttachedObject._pendingActionTimer -= Time.deltaTime;
}
}
public void ChangeAction(QSBGhostAction action, bool remote = false)
{
if (!remote)
{
if (!QSBCore.IsHost)
{
return;
}
this.SendMessage(new ChangeActionMessage(action?.GetName() ?? GhostAction.Name.None));
}
DebugLog.DebugWrite($"{AttachedObject._name} Change action to {action?.GetName()}");
if (_currentAction != null)
{
_currentAction.ExitAction();
_data.previousAction = _currentAction.GetName();
}
else
{
_data.previousAction = GhostAction.Name.None;
}
_currentAction = action;
_data.currentAction = (action != null) ? action.GetName() : GhostAction.Name.None;
if (_currentAction != null)
{
_currentAction.EnterAction();
_data.OnEnterAction(_currentAction.GetName());
}
ClearPendingAction();
}
public void ClearPendingAction()
{
_pendingAction = null;
AttachedObject._pendingActionUtility = -100f;
AttachedObject._pendingActionTimer = 0f;
}
public void OnArriveAtPosition()
{
if (!QSBCore.IsHost)
{
return;
}
if (_currentAction != null)
{
_currentAction.OnArriveAtPosition();
}
}
public void OnTraversePathNode(GhostNode node)
{
if (_currentAction != null)
{
_currentAction.OnTraversePathNode(node);
}
}
public void OnFaceNode(GhostNode node)
{
if (_currentAction != null)
{
_currentAction.OnFaceNode(node);
}
}
public void OnFinishFaceNodeList()
{
if (_currentAction != null)
{
_currentAction.OnFinishFaceNodeList();
}
}
public void OnCallForHelp()
{
DebugLog.DebugWrite($"{AttachedObject._name} - iterating through helper list for callforhelp");
if (AttachedObject._helperGhosts != null)
{
for (var i = 0; i < AttachedObject._helperGhosts.Length; i++)
{
AttachedObject._helperGhosts[i].GetWorldObject<QSBGhostBrain>().HearCallForHelp(_data.interestedPlayer.playerLocation.localPosition, 3f, _data.interestedPlayer);
}
}
}
public void OnEnterDreamWorld()
{
AttachedObject.enabled = true;
AttachedObject._controller.GetDreamLanternController().enabled = true;
}
public void OnExitDreamWorld()
{
AttachedObject.enabled = false;
AttachedObject._controller.GetDreamLanternController().enabled = false;
//ChangeAction(null);
_data.OnPlayerExitDreamWorld();
}
}

View File

@ -0,0 +1,270 @@
using GhostEnums;
using QSB.EchoesOfTheEye.Ghosts.Messages;
using QSB.Messaging;
using QSB.Player;
using QSB.WorldSync;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace QSB.EchoesOfTheEye.Ghosts.WorldObjects;
public class QSBGhostController : WorldObject<GhostController>, IGhostObject
{
public override void SendInitialState(uint to)
{
}
public QSBGhostEffects _effects;
public void Initialize(GhostNode.NodeLayer layer, QSBGhostEffects effects)
{
_effects = effects;
AttachedObject._nodeRoot = AttachedObject.transform.parent = AttachedObject._nodeMap.transform;
AttachedObject._nodeLayer = layer;
AttachedObject._grabController.Initialize(effects.AttachedObject);
AttachedObject._lantern.SetLit(true);
AttachedObject.MoveLanternToCarrySocket(false, 0.1f);
AttachedObject._playerCollider = Locator.GetPlayerBody().GetComponent<CapsuleCollider>();
}
public void SetLanternConcealed(bool concealed, bool playAudio = true)
{
if (playAudio && AttachedObject._lantern.IsConcealed() != concealed)
{
_effects.PlayLanternAudio(concealed ? global::AudioType.Artifact_Conceal : global::AudioType.Artifact_Unconceal);
}
AttachedObject._lantern.SetConcealed(concealed);
if (concealed)
{
AttachedObject._lantern.SetFocus(0f);
AttachedObject._updateLantern = false;
}
}
public void ChangeLanternFocus(float focus, float focusRate = 2f)
{
if (focus > 0f)
{
AttachedObject._lantern.SetConcealed(false);
}
if (focus > AttachedObject._targetLanternFocus)
{
_effects.PlayLanternAudio(global::AudioType.Artifact_Focus);
}
else if (focus < AttachedObject._targetLanternFocus)
{
_effects.PlayLanternAudio(global::AudioType.Artifact_Unfocus);
}
AttachedObject._updateLantern = true;
AttachedObject._targetLanternFocus = focus;
AttachedObject._lanternFocusRate = focusRate;
}
public void FacePlayer(PlayerInfo player, TurnSpeed turnSpeed, bool remote = false)
{
if (!remote)
{
if (!QSBCore.IsHost)
{
return;
}
this.SendMessage(new FacePlayerMessage(player.PlayerId, turnSpeed));
}
AttachedObject._facingState = GhostController.FacingState.FaceTransform;
AttachedObject._faceTransform = player.Camera.transform;
AttachedObject._targetDegreesPerSecond = GhostConstants.GetTurnSpeed(turnSpeed);
AttachedObject._angularAcceleration = GhostConstants.GetTurnAcceleration(turnSpeed);
}
public void FaceLocalPosition(Vector3 localPosition, TurnSpeed turnSpeed)
{
FaceLocalPosition(localPosition, GhostConstants.GetTurnSpeed(turnSpeed), GhostConstants.GetTurnAcceleration(turnSpeed));
}
public void FaceLocalPosition(Vector3 localPosition, float degreesPerSecond, float turnAcceleration = 360f, bool remote = false)
{
if (!remote)
{
if (!QSBCore.IsHost)
{
return;
}
this.SendMessage(new FaceLocalPositionMessage(localPosition, degreesPerSecond, turnAcceleration));
}
AttachedObject.FaceLocalPosition(localPosition, degreesPerSecond, turnAcceleration);
}
public void FaceNodeList(GhostNode[] nodeList, int numNodes, TurnSpeed turnSpeed, float nodeDelay, bool autoFocusLantern = false, bool remote = false)
{
if (!remote)
{
if (!QSBCore.IsHost)
{
return;
}
this.SendMessage(new FaceNodeListMessage(nodeList, numNodes, turnSpeed, nodeDelay, autoFocusLantern));
}
AttachedObject.FaceNodeList(nodeList, numNodes, turnSpeed, nodeDelay, autoFocusLantern);
}
public void FaceVelocity(bool remote = false)
{
if (!remote)
{
if (!QSBCore.IsHost)
{
return;
}
this.SendMessage(new FaceVelocityMessage());
}
AttachedObject.FaceVelocity();
}
public void MoveToLocalPosition(Vector3 localPosition, MoveType moveType)
{
MoveToLocalPosition(localPosition, GhostConstants.GetMoveSpeed(moveType), GhostConstants.GetMoveAcceleration(moveType));
}
public void MoveToLocalPosition(Vector3 localPosition, float speed, float acceleration = 10f, bool remote = false)
{
if (!remote)
{
if (!QSBCore.IsHost)
{
return;
}
this.SendMessage(new MoveToLocalPositionMessage(localPosition, speed, acceleration));
}
AttachedObject.MoveToLocalPosition(localPosition, speed, acceleration);
}
public void PathfindToLocalPosition(Vector3 localPosition, MoveType moveType)
{
PathfindToLocalPosition(localPosition, GhostConstants.GetMoveSpeed(moveType), GhostConstants.GetMoveAcceleration(moveType));
}
public void PathfindToLocalPosition(Vector3 localPosition, float speed, float acceleration = 10f, bool remote = false)
{
if (!remote)
{
if (!QSBCore.IsHost)
{
return;
}
this.SendMessage(new PathfindLocalPositionMessage(localPosition, speed, acceleration));
}
AttachedObject.PathfindToLocalPosition(localPosition, speed, acceleration);
}
public void PathfindToNode(GhostNode node, MoveType moveType)
{
PathfindToNode(node, GhostConstants.GetMoveSpeed(moveType), GhostConstants.GetMoveAcceleration(moveType));
}
public void PathfindToNode(GhostNode node, float speed, float acceleration = 10f, bool remote = false)
{
if (!remote)
{
if (!QSBCore.IsHost)
{
return;
}
this.SendMessage(new PathfindNodeMessage(node, speed, acceleration));
}
AttachedObject.PathfindToNode(node, speed, acceleration);
}
public void FaceNode(GhostNode node, TurnSpeed turnSpeed, float nodeDelay, bool autoFocusLantern = false, bool remote = false)
{
if (!remote)
{
if (!QSBCore.IsHost)
{
return;
}
this.SendMessage(new FaceNodeMessage(node, turnSpeed, nodeDelay, autoFocusLantern));
}
AttachedObject.FaceNode(node, turnSpeed, nodeDelay, autoFocusLantern);
}
public void FaceLocalDirection(Vector3 localDirection, TurnSpeed turnSpeed)
{
FaceLocalDirection(localDirection, GhostConstants.GetTurnSpeed(turnSpeed), GhostConstants.GetTurnAcceleration(turnSpeed));
}
public void FaceLocalDirection(Vector3 localDirection, float degreesPerSecond, float turnAcceleration = 360f, bool remote = false)
{
if (!remote)
{
if (!QSBCore.IsHost)
{
return;
}
this.SendMessage(new FaceLocalDirectionMessage(localDirection, degreesPerSecond, turnAcceleration));
}
AttachedObject.FaceLocalDirection(localDirection, degreesPerSecond, turnAcceleration);
}
public void StopMoving()
{
if (!QSBCore.IsHost)
{
return;
}
this.SendMessage(new StopMovingMessage(false));
AttachedObject.StopMoving();
}
public void StopMovingInstantly()
{
if (!QSBCore.IsHost)
{
return;
}
this.SendMessage(new StopMovingMessage(true));
AttachedObject.StopMovingInstantly();
}
public void StopFacing(bool remote = false)
{
if (!remote)
{
if (!QSBCore.IsHost)
{
return;
}
this.SendMessage(new StopFacingMessage());
}
AttachedObject.StopFacing();
}
}

View File

@ -0,0 +1,246 @@
using QSB.EchoesOfTheEye.Ghosts.Messages;
using QSB.Messaging;
using QSB.Utility;
using QSB.WorldSync;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace QSB.EchoesOfTheEye.Ghosts.WorldObjects;
public class QSBGhostEffects : WorldObject<GhostEffects>, IGhostObject
{
public override void SendInitialState(uint to)
{
}
public override bool ShouldDisplayDebug() => false;
private QSBGhostData _data;
public void Initialize(Transform nodeRoot, GhostController controller, QSBGhostData data)
{
AttachedObject._animator = AttachedObject.GetComponent<Animator>();
AttachedObject._controller = controller;
_data = data;
if (AttachedObject._feetAudioSourceFar != null)
{
AttachedObject._stompyFootsteps = true;
}
AttachedObject.SetEyeGlow(AttachedObject._eyeGlow);
}
public bool AllowFootstepAudio(bool usingTimer)
{
var flag = AttachedObject._stompyFootsteps || _data.currentAction == GhostAction.Name.Chase || _data.currentAction == GhostAction.Name.Grab;
if (usingTimer != flag)
{
return false;
}
var num = AttachedObject._stompyFootsteps ? AttachedObject._feetAudioSourceFar.maxDistance : AttachedObject._feetAudioSourceNear.maxDistance;
return _data.localPlayer.playerLocation.distance < num + 5f;
}
public void PlayLanternAudio(AudioType audioType)
{
if (_data.localPlayer.playerLocation.distance < AttachedObject._lanternAudioSource.GetAudioSource().maxDistance + 5f)
{
AttachedObject._lanternAudioSource.PlayOneShot(audioType, 1f);
}
}
public void Update_Effects()
{
if (_data == null)
{
return;
}
if (AttachedObject._waitToRespondToHelpCall && Time.time >= AttachedObject._respondToHelpCallTime)
{
AttachedObject._waitToRespondToHelpCall = false;
PlayVoiceAudioFar(AudioType.Ghost_CallForHelpResponse, 1f);
}
if (AttachedObject._waitForGrabWindow && AttachedObject._animator.GetFloat(GhostEffects.AnimatorKeys.AnimCurve_GrabWindow) > 0.5f)
{
AttachedObject._waitForGrabWindow = false;
AttachedObject.PlayGrabAudio(AudioType.Ghost_Grab_Swish);
}
AttachedObject.Update_Footsteps();
var relativeVelocity = AttachedObject._controller.GetRelativeVelocity();
var num = (AttachedObject._movementStyle == GhostEffects.MovementStyle.Chase) ? 8f : 2f;
var targetValue = new Vector2(relativeVelocity.x / num, relativeVelocity.z / num);
AttachedObject._smoothedMoveSpeed = AttachedObject._moveSpeedSpring.Update(AttachedObject._smoothedMoveSpeed, targetValue, Time.deltaTime);
AttachedObject._animator.SetFloat(GhostEffects.AnimatorKeys.Float_MoveDirectionX, AttachedObject._smoothedMoveSpeed.x);
AttachedObject._animator.SetFloat(GhostEffects.AnimatorKeys.Float_MoveDirectionY, AttachedObject._smoothedMoveSpeed.y);
AttachedObject._smoothedTurnSpeed = AttachedObject._turnSpeedSpring.Update(AttachedObject._smoothedTurnSpeed, AttachedObject._controller.GetAngularVelocity() / 90f, Time.deltaTime);
AttachedObject._animator.SetFloat(GhostEffects.AnimatorKeys.Float_TurnSpeed, AttachedObject._smoothedTurnSpeed);
var target = _data.isIlluminated ? 1f : 0f;
var num2 = _data.isIlluminated ? 8f : 0.8f;
AttachedObject._eyeGlow = Mathf.MoveTowards(AttachedObject._eyeGlow, target, Time.deltaTime * num2);
var num3 = (Locator.GetDreamWorldController().GetPlayerLantern().GetLanternController().GetLight().GetFlickerScale() - 1f + 0.07f) / 0.14f;
num3 = Mathf.Lerp(0.7f, 1f, num3);
AttachedObject.SetEyeGlow(AttachedObject._eyeGlow * num3);
if (AttachedObject._playingDeathSequence)
{
var @float = AttachedObject._animator.GetFloat(GhostEffects.AnimatorKeys.AnimCurve_DeathFade);
for (var i = 0; i < AttachedObject._dissolveRenderers.Length; i++)
{
AttachedObject._dissolveRenderers[i].SetMaterialProperty(AttachedObject._propID_DissolveProgress, @float);
}
for (var j = 0; j < AttachedObject._ditherRenderers.Length; j++)
{
AttachedObject._ditherRenderers[j].SetDitherFade(@float);
}
if (AttachedObject._deathAnimComplete && (AttachedObject._deathParticleSystem == null || !AttachedObject._deathParticleSystem.isPlaying))
{
AttachedObject._playingDeathSequence = false;
AttachedObject._controller.gameObject.SetActive(false);
AttachedObject.OnGhostDeathComplete.Invoke();
for (var k = 0; k < AttachedObject._dissolveRenderers.Length; k++)
{
AttachedObject._dissolveRenderers[k].SetMaterialProperty(AttachedObject._propID_DissolveProgress, 0f);
}
for (var l = 0; l < AttachedObject._ditherRenderers.Length; l++)
{
AttachedObject._ditherRenderers[l].SetDitherFade(0f);
}
}
}
}
public void SetMovementStyle(GhostEffects.MovementStyle style, bool remote = false)
{
if (!remote)
{
if (!QSBCore.IsHost)
{
return;
}
this.SendMessage(new SetMovementStyleMessage(style));
}
AttachedObject.SetMovementStyle(style);
}
public void PlayVoiceAudioNear(AudioType audioType, float volumeScale, bool remote = false)
{
if (!remote)
{
if (!QSBCore.IsHost)
{
return;
}
this.SendMessage(new PlayVoiceAudioMessage(audioType, volumeScale, true));
}
AttachedObject.PlayVoiceAudioNear(audioType, volumeScale);
}
public void PlayVoiceAudioFar(AudioType audioType, float volumeScale, bool remote = false)
{
if (!remote)
{
if (!QSBCore.IsHost)
{
return;
}
this.SendMessage(new PlayVoiceAudioMessage(audioType, volumeScale, false));
}
AttachedObject.PlayVoiceAudioFar(audioType, volumeScale);
}
public void PlaySleepAnimation(bool remote = false)
{
if (!remote)
{
if (!QSBCore.IsHost)
{
return;
}
this.SendMessage(new GhostAnimationTriggerMessage(GhostAnimationType.Sleep));
}
AttachedObject.PlaySleepAnimation();
}
public void PlayDefaultAnimation(bool remote = false)
{
if (!remote)
{
if (!QSBCore.IsHost)
{
return;
}
this.SendMessage(new GhostAnimationTriggerMessage(GhostAnimationType.Default));
}
AttachedObject.PlayDefaultAnimation();
}
public void PlayGrabAnimation(bool remote = false)
{
if (!remote)
{
if (!QSBCore.IsHost)
{
return;
}
this.SendMessage(new GhostAnimationTriggerMessage(GhostAnimationType.Grab));
}
AttachedObject.PlayGrabAnimation();
}
public void PlayBlowOutLanternAnimation(bool fast = false, bool remote = false)
{
if (!remote)
{
if (!QSBCore.IsHost)
{
return;
}
this.SendMessage(new GhostAnimationTriggerMessage(fast ? GhostAnimationType.BlowOutLanternFast : GhostAnimationType.BlowOutLanternNormal));
}
AttachedObject.PlayBlowOutLanternAnimation(fast);
}
public void PlaySnapNeckAnimation(bool remote = false)
{
if (!remote)
{
if (!QSBCore.IsHost)
{
return;
}
this.SendMessage(new GhostAnimationTriggerMessage(GhostAnimationType.SnapNeck));
}
AttachedObject.PlaySnapNeckAnimation();
}
}

View File

@ -0,0 +1,16 @@
using QSB.WorldSync;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace QSB.EchoesOfTheEye.Ghosts.WorldObjects;
internal class QSBGhostNodeMap : WorldObject<GhostNodeMap>
{
public override void SendInitialState(uint to)
{
}
}

View File

@ -0,0 +1,131 @@
using QSB.EchoesOfTheEye.Ghosts.Messages;
using QSB.Messaging;
using QSB.Utility;
using QSB.WorldSync;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace QSB.EchoesOfTheEye.Ghosts.WorldObjects;
public class QSBGhostSensors : WorldObject<GhostSensors>, IGhostObject
{
public override void SendInitialState(uint to)
{
}
public override bool ShouldDisplayDebug() => false;
public QSBGhostData _data;
public void Initialize(QSBGhostData data, OWTriggerVolume guardVolume = null)
{
_data = data;
AttachedObject._origEdgeCatcherSize = AttachedObject._contactEdgeCatcherShape.size;
AttachedObject._guardVolume = guardVolume;
}
public bool CanGrabPlayer(GhostPlayer player)
=> !PlayerState.IsAttached()
&& player.playerLocation.distanceXZ < 2f + AttachedObject._grabDistanceBuff
&& player.playerLocation.degreesToPositionXZ < 20f + AttachedObject._grabAngleBuff
&& AttachedObject._animator.GetFloat("GrabWindow") > 0.5f;
public void FixedUpdate_Sensors()
{
if (_data == null)
{
return;
}
foreach (var pair in _data.players)
{
var player = pair.Value;
if (player.player.AssignedSimulationLantern == null)
{
continue;
}
var lanternController = player.player.AssignedSimulationLantern.AttachedObject.GetLanternController();
var playerLightSensor = player.player.LightSensor;
player.sensor.isPlayerHoldingLantern = lanternController.IsHeldByPlayer();
_data.isIlluminated = AttachedObject._lightSensor.IsIlluminated();
player.sensor.isIlluminatedByPlayer = (lanternController.IsHeldByPlayer() && AttachedObject._lightSensor.IsIlluminatedByLantern(lanternController));
player.sensor.isPlayerIlluminatedByUs = playerLightSensor.IsIlluminatedByLantern(AttachedObject._lantern);
player.sensor.isPlayerIlluminated = playerLightSensor.IsIlluminated();
player.sensor.isPlayerVisible = false;
player.sensor.isPlayerHeldLanternVisible = false;
player.sensor.isPlayerDroppedLanternVisible = false;
player.sensor.isPlayerOccluded = false;
if ((lanternController.IsHeldByPlayer() && !lanternController.IsConcealed()) || playerLightSensor.IsIlluminated())
{
var position = pair.Key.Camera.transform.position;
if (AttachedObject.CheckPointInVisionCone(position))
{
if (AttachedObject.CheckLineOccluded(AttachedObject._sightOrigin.position, position))
{
player.sensor.isPlayerOccluded = true;
}
else
{
player.sensor.isPlayerVisible = playerLightSensor.IsIlluminated();
player.sensor.isPlayerHeldLanternVisible = (lanternController.IsHeldByPlayer() && !lanternController.IsConcealed());
}
}
}
if (!lanternController.IsHeldByPlayer() && AttachedObject.CheckPointInVisionCone(lanternController.GetLightPosition()) && !AttachedObject.CheckLineOccluded(AttachedObject._sightOrigin.position, lanternController.GetLightPosition()))
{
player.sensor.isPlayerDroppedLanternVisible = true;
}
}
if (!QSBCore.IsHost)
{
return;
}
var visiblePlayers = _data.players.Values.Where(x => x.sensor.isPlayerVisible || x.sensor.isPlayerHeldLanternVisible || x.sensor.inContactWithPlayer || x.sensor.isPlayerIlluminatedByUs);
if (visiblePlayers.Count() == 0) // no players visible
{
visiblePlayers = _data.players.Values.Where(x => x.sensor.isIlluminatedByPlayer);
}
if (visiblePlayers.Count() == 0) // no players lighting us
{
return;
}
var closest = visiblePlayers.MinBy(x => x.playerLocation.distance);
if (_data.interestedPlayer != closest)
{
DebugLog.DebugWrite($"CHANGE INTERESTED PLAYER!");
_data.interestedPlayer = closest;
this.SendMessage(new ChangeInterestedPlayerMessage(closest.player.PlayerId));
}
}
public void OnEnterContactTrigger(GameObject hitObj)
{
if (hitObj.CompareTag("PlayerDetector"))
{
_data.localPlayer.sensor.inContactWithPlayer = true;
}
}
public void OnExitContactTrigger(GameObject hitObj)
{
if (hitObj.CompareTag("PlayerDetector"))
{
_data.localPlayer.sensor.inContactWithPlayer = false;
}
}
}

View File

@ -1,21 +1,26 @@
using Cysharp.Threading.Tasks;
using QSB.EchoesOfTheEye.LightSensorSync.WorldObjects;
using QSB.Utility;
using QSB.WorldSync;
using System.Linq;
using System.Threading;
namespace QSB.EchoesOfTheEye.LightSensorSync;
internal class LightSensorManager : WorldObjectManager
{
/// <summary>
/// light sensor apparently shows up in eye
/// </summary>
public override WorldObjectScene WorldObjectScene => WorldObjectScene.Both;
/// <summary>
/// light sensor patches like to run even with no dlc
/// </summary>
public override bool DlcOnly => false;
public override WorldObjectScene WorldObjectScene => WorldObjectScene.SolarSystem;
public override bool DlcOnly => true;
public override async UniTask BuildWorldObjects(OWScene scene, CancellationToken ct) =>
QSBWorldSync.Init<QSBLightSensor, SingleLightSensor>();
public static bool ShouldIgnore(LightSensor lightSensor) =>
lightSensor.name is "CameraDetector" or "REMOTE_CameraDetector";
public override async UniTask BuildWorldObjects(OWScene scene, CancellationToken ct)
{
// ignore player light sensors
var list = QSBWorldSync.GetUnityObjects<SingleLightSensor>()
.Where(x => !ShouldIgnore(x))
.SortDeterministic();
QSBWorldSync.Init<QSBLightSensor, SingleLightSensor>(list);
}
}

View File

@ -25,6 +25,11 @@ internal class LightSensorPatches : QSBPatch
return true;
}
if (LightSensorManager.ShouldIgnore(__instance))
{
return true;
}
var flag = __instance._sector.ContainsAnyOccupants(DynamicOccupant.Player | DynamicOccupant.Probe);
if (flag && !__instance.enabled)
{
@ -64,6 +69,11 @@ internal class LightSensorPatches : QSBPatch
return true;
}
if (LightSensorManager.ShouldIgnore(__instance))
{
return true;
}
var qsbLightSensor = __instance.GetWorldObject<QSBLightSensor>();
var illuminatedByLocal = qsbLightSensor.IlluminatedByLocal;
qsbLightSensor.IlluminatedByLocal = false;

View File

@ -1,17 +1,3 @@
using QSB.EchoesOfTheEye.DreamLantern.Messages;
using QSB.Messaging;
namespace QSB.ItemSync.WorldObjects.Items;
namespace QSB.ItemSync.WorldObjects.Items;
public class QSBDreamLanternItem : QSBItem<DreamLanternItem>
{
public override void SendInitialState(uint to)
{
base.SendInitialState(to);
if (AttachedObject._lanternController != null)
{
this.SendMessage(new DreamLanternLitMessage(AttachedObject._lanternController.IsLit()) { To = to });
}
}
}
public class QSBDreamLanternItem : QSBItem<DreamLanternItem> { }

View File

@ -53,4 +53,42 @@ public partial class PlayerInfo
}
}
private GameObject _body;
public LightSensor LightSensor
{
get
{
if (IsLocalPlayer)
{
return Locator.GetPlayerLightSensor();
}
if (CameraBody == null)
{
DebugLog.ToConsole($"Error - Can't get LightSensor for {PlayerId}, because CameraBody is null.", MessageType.Error);
return null;
}
return CameraBody.transform.Find("REMOTE_CameraDetector").GetComponent<LightSensor>();
}
}
public Vector3 Velocity
{
get
{
if (IsLocalPlayer)
{
return Locator.GetPlayerBody().GetVelocity();
}
if (Body == null)
{
DebugLog.ToConsole($"Error - Can't get velocity for {PlayerId}, because Body is null.", MessageType.Error);
return Vector3.zero;
}
return Body.GetComponent<RemotePlayerVelocity>().Velocity;
}
}
}

View File

@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace QSB.Player;
public class RemotePlayerVelocity : MonoBehaviour
{
private Vector3 _prevPosition;
public Vector3 Velocity { get; private set; }
public void FixedUpdate()
{
Velocity = (transform.position - _prevPosition) / Time.fixedDeltaTime;
_prevPosition = transform.position;
}
}

View File

@ -1,4 +1,5 @@
using QSB.ClientServerStateSync;
using QSB.EchoesOfTheEye.Ghosts.WorldObjects;
using QSB.Player;
using QSB.QuantumSync.WorldObjects;
using QSB.ShipSync;
@ -149,9 +150,18 @@ internal class DebugGUI : MonoBehaviour, IAddComponentOnStart
WriteLine(2, $"Visible : {player.Visible}");
WriteLine(2, $"Ready : {player.IsReady}");
WriteLine(2, $"Suited Up : {player.SuitedUp}");
WriteLine(2, $"InDreamWorld : {player.InDreamWorld}");
if (player.IsReady && QSBWorldSync.AllObjectsReady)
{
WriteLine(2, $"Illuminated : {player.LightSensor.IsIlluminated()}");
var singleLightSensor = player.LightSensor as SingleLightSensor;
foreach (var item in singleLightSensor._lightSources)
{
WriteLine(2, $"- {item.GetLightSourceType()}");
}
var networkTransform = player.TransformSync;
var referenceSector = networkTransform.ReferenceSector;
var referenceTransform = networkTransform.ReferenceTransform;
@ -222,6 +232,37 @@ internal class DebugGUI : MonoBehaviour, IAddComponentOnStart
#endregion
if (QSBWorldSync.AllObjectsReady)
{
var ghost = QSBWorldSync.GetWorldObjects<QSBGhostBrain>().First(x => x.AttachedObject._name == "Kamaji");
WriteLine(4, ghost.AttachedObject._name);
WriteLine(4, $"Action:{ghost.GetCurrentActionName()}");
WriteLine(4, $"Threat Awareness:{ghost.GetThreatAwareness()}");
var interestedPlayer = ghost._data.interestedPlayer;
WriteLine(4, $"InterestedPlayer:{(interestedPlayer == null ? "NULL" : interestedPlayer.player.PlayerId)}");
foreach (var player in ghost._data.players.Values)
{
WriteLine(4, $"{player.player.PlayerId}");
WriteLine(4, $"- isPlayerVisible:{player.sensor.isPlayerVisible}");
WriteLine(4, $"- isPlayerHeldLanternVisible:{player.sensor.isPlayerHeldLanternVisible}");
WriteLine(4, $"- isIlluminatedByPlayer:{player.sensor.isIlluminatedByPlayer}");
WriteLine(4, $"- isPlayerLocationKnown:{player.isPlayerLocationKnown}");
WriteLine(4, $"- timeSincePlayerLocationKnown:{player.timeSincePlayerLocationKnown}");
var lantern = player.player.AssignedSimulationLantern;
WriteLine(4, $"- IsHeldByPlayer:{lantern.AttachedObject.GetLanternController().IsHeldByPlayer()}");
WriteLine(4, $"- Concealed:{lantern.AttachedObject.GetLanternController().IsConcealed()}");
var position = player.player.Camera.transform.position;
WriteLine(4, $"- Camera in vision cone:{ghost.AttachedObject._sensors.CheckPointInVisionCone(position)}");
WriteLine(4, $"- CheckLineOccluded:{ghost.AttachedObject._sensors.CheckLineOccluded(ghost.AttachedObject._sensors._sightOrigin.position, position)}");
}
WriteLine(4, $"First check:{!ghost.AttachedObject._intruderConfirmPending}");
WriteLine(4, $"Second check:{ghost._data.threatAwareness > GhostData.ThreatAwareness.EverythingIsNormal || ghost._data.players.Values.Any(x => x.playerLocation.distance < 20f) || ghost._data.players.Values.Any(x => x.sensor.isPlayerIlluminatedByUs)}");
WriteLine(4, $"Third check:{ghost._data.players.Values.Any(x => x.sensor.isPlayerVisible) || ghost._data.players.Values.Any(x => x.sensor.inContactWithPlayer)}");
}
/*
#region Column4 - Quantum Object Possesion
foreach (var player in QSBPlayerManager.PlayerList)
@ -260,20 +301,29 @@ internal class DebugGUI : MonoBehaviour, IAddComponentOnStart
}
#endregion
*/
}
private static void DrawWorldObjectLabels()
{
if (!QSBCore.DebugSettings.DrawLabels)
if (QSBCore.DebugSettings.DrawLabels)
{
return;
}
foreach (var obj in QSBWorldSync.GetWorldObjects())
{
if (obj.ShouldDisplayDebug())
foreach (var obj in QSBWorldSync.GetWorldObjects())
{
DrawLabel(obj.AttachedObject.transform, obj.ReturnLabel());
if (obj.ShouldDisplayDebug())
{
DrawLabel(obj.AttachedObject.transform, obj.ReturnLabel());
}
}
}
else if (QSBCore.DebugSettings.DrawGhostAI)
{
foreach (var obj in QSBWorldSync.GetWorldObjects<IGhostObject>())
{
if (obj.ShouldDisplayDebug())
{
DrawLabel(obj.AttachedObject.transform, obj.ReturnLabel());
}
}
}
}
@ -282,16 +332,24 @@ internal class DebugGUI : MonoBehaviour, IAddComponentOnStart
private static void DrawWorldObjectLines()
{
if (!QSBCore.DebugSettings.DrawLines)
if (QSBCore.DebugSettings.DrawLines)
{
return;
}
foreach (var obj in QSBWorldSync.GetWorldObjects())
{
if (obj.ShouldDisplayDebug())
foreach (var obj in QSBWorldSync.GetWorldObjects())
{
obj.DisplayLines();
if (obj.ShouldDisplayDebug())
{
obj.DisplayLines();
}
}
}
else if (QSBCore.DebugSettings.DrawGhostAI)
{
foreach (var obj in QSBWorldSync.GetWorldObjects<IGhostObject>())
{
if (obj.ShouldDisplayDebug())
{
obj.DisplayLines();
}
}
}
}

View File

@ -46,4 +46,8 @@ public class DebugSettings
[JsonProperty("greySkybox")]
private bool _greySkybox;
public bool GreySkybox => DebugMode && _greySkybox;
[JsonProperty("drawGhostAI")]
private bool _drawGhostAI;
public bool DrawGhostAI => DebugMode && _drawGhostAI;
}

View File

@ -53,18 +53,11 @@ public static class QSBWorldSync
foreach (var manager in Managers)
{
if (manager.DlcOnly && !QSBCore.DLCInstalled)
if (ShouldIgnoreManager(manager))
{
continue;
}
switch (manager.WorldObjectScene)
{
case WorldObjectScene.SolarSystem when QSBSceneManager.CurrentScene != OWScene.SolarSystem:
case WorldObjectScene.Eye when QSBSceneManager.CurrentScene != OWScene.EyeOfTheUniverse:
continue;
}
var task = UniTask.Create(async () =>
{
await manager.Try("building world objects", async () =>
@ -150,10 +143,32 @@ public static class QSBWorldSync
foreach (var manager in Managers)
{
if (ShouldIgnoreManager(manager))
{
continue;
}
manager.Try("unbuilding world objects", manager.UnbuildWorldObjects);
}
}
private static bool ShouldIgnoreManager(WorldObjectManager manager)
{
if (manager.DlcOnly && !QSBCore.DLCInstalled)
{
return true;
}
switch (manager.WorldObjectScene)
{
case WorldObjectScene.SolarSystem when QSBSceneManager.CurrentScene != OWScene.SolarSystem:
case WorldObjectScene.Eye when QSBSceneManager.CurrentScene != OWScene.EyeOfTheUniverse:
return true;
}
return false;
}
// =======================================================================================================
public static readonly List<CharacterDialogueTree> OldDialogueTrees = new();
@ -176,7 +191,7 @@ public static class QSBWorldSync
private static void GameInit()
{
DebugLog.DebugWrite($"GameInit QSBWorldSync", MessageType.Info);
DebugLog.DebugWrite("GameInit QSBWorldSync", MessageType.Info);
OldDialogueTrees.Clear();
OldDialogueTrees.AddRange(GetUnityObjects<CharacterDialogueTree>().SortDeterministic());
@ -195,7 +210,7 @@ public static class QSBWorldSync
private static void GameReset()
{
DebugLog.DebugWrite($"GameReset QSBWorldSync", MessageType.Info);
DebugLog.DebugWrite("GameReset QSBWorldSync", MessageType.Info);
OldDialogueTrees.Clear();
DialogueConditions.Clear();

View File

@ -10,5 +10,6 @@
"drawLabels": false,
"drawQuantumVisibilityObjects": false,
"skipTitleScreen": false,
"greySkybox": false
"greySkybox": false,
"drawGhostAI": false
}