mirror of
https://github.com/misternebula/quantum-space-buddies.git
synced 2025-01-29 18:32:45 +00:00
commit
b1768e7afb
@ -4,6 +4,6 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="../Mirror/*.dll" />
|
||||
<PackageReference Include="OuterWildsGameLibs" Version="1.1.12.168" IncludeAssets="compile" />
|
||||
<PackageReference Include="OuterWildsGameLibs" Version="1.1.12.201" IncludeAssets="compile" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
@ -3,7 +3,7 @@
|
||||
<OutputType>Exe</OutputType>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="OuterWildsGameLibs" Version="1.1.12.168" IncludeAssets="compile" />
|
||||
<PackageReference Include="OuterWildsGameLibs" Version="1.1.12.201" IncludeAssets="compile" />
|
||||
<PackageReference Include="HarmonyX" Version="2.10.0" IncludeAssets="compile" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
@ -3,7 +3,7 @@
|
||||
<OutputType>Exe</OutputType>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="OuterWildsGameLibs" Version="1.1.12.168" />
|
||||
<PackageReference Include="OuterWildsGameLibs" Version="1.1.12.201" />
|
||||
<PackageReference Include="OWML" Version="2.5.2" />
|
||||
<PackageReference Include="HarmonyX" Version="2.10.0" />
|
||||
<Reference Include="../Mirror/*.dll" />
|
||||
|
@ -7,16 +7,7 @@
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_LOCK/@EntryValue">Required</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_USING/@EntryValue">Required</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_WHILE/@EntryValue">Required</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/NAMESPACE_BODY/@EntryValue">FileScoped</s:String>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALIGN_MULTILINE_BINARY_EXPRESSIONS_CHAIN/@EntryValue">False</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALIGN_MULTILINE_STATEMENT_CONDITIONS/@EntryValue">False</s:Boolean>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALIGNMENT_TAB_FILL_STYLE/@EntryValue">USE_TABS_ONLY</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/CASE_BLOCK_BRACES/@EntryValue">NEXT_LINE_SHIFTED_2</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/INDENT_STYLE/@EntryValue">Tab</s:String>
|
||||
<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_BLANK_LINES_IN_CODE/@EntryValue">1</s:Int64>
|
||||
<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_BLANK_LINES_IN_DECLARATIONS/@EntryValue">1</s:Int64>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/LINE_FEED_AT_FILE_END/@EntryValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_LINES/@EntryValue">False</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpUseContinuousIndentInsideBracesMigration/@EntryIndexedValue">True</s:Boolean>
|
||||
|
@ -9,9 +9,6 @@ internal class CharacterAnimManager : WorldObjectManager
|
||||
{
|
||||
public override WorldObjectScene WorldObjectScene => WorldObjectScene.Both;
|
||||
|
||||
public override async UniTask BuildWorldObjects(OWScene scene, CancellationToken ct)
|
||||
{
|
||||
QSBWorldSync.Init<QSBCharacterAnimController, CharacterAnimController>();
|
||||
public override async UniTask BuildWorldObjects(OWScene scene, CancellationToken ct) =>
|
||||
QSBWorldSync.Init<QSBSolanumAnimController, SolanumAnimController>();
|
||||
}
|
||||
}
|
@ -115,23 +115,4 @@ public class CharacterAnimationPatches : QSBPatch
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(KidRockController), nameof(KidRockController.Update))]
|
||||
public static bool UpdateReplacement(KidRockController __instance)
|
||||
{
|
||||
if (!QSBWorldSync.AllObjectsReady)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var qsbObj = QSBWorldSync.GetWorldObjects<QSBCharacterAnimController>().First(x => x.GetDialogueTree() == __instance._dialogueTree);
|
||||
|
||||
if (!__instance._throwingRock && !qsbObj.InConversation() && Time.time > __instance._nextThrowTime)
|
||||
{
|
||||
__instance.StartRockThrow();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
using QSB.WorldSync;
|
||||
|
||||
namespace QSB.Animation.NPC.WorldObjects;
|
||||
|
||||
internal class QSBCharacterAnimController : WorldObject<CharacterAnimController>
|
||||
{
|
||||
public override void SendInitialState(uint to)
|
||||
{
|
||||
// todo SendInitialState
|
||||
}
|
||||
|
||||
public CharacterDialogueTree GetDialogueTree()
|
||||
=> AttachedObject._dialogueTree;
|
||||
|
||||
public bool InConversation()
|
||||
=> AttachedObject._inConversation;
|
||||
}
|
@ -11,6 +11,4 @@ internal class QSBSolanumAnimController : WorldObject<SolanumAnimController>
|
||||
{
|
||||
private QSBSolanumTrigger _trigger;
|
||||
public QSBSolanumTrigger Trigger => _trigger ??= QSBWorldSync.GetWorldObjects<QSBSolanumTrigger>().First();
|
||||
|
||||
public override void SendInitialState(uint to) { }
|
||||
}
|
@ -6,6 +6,7 @@ using QSB.Animation.Player.Thrusters;
|
||||
using QSB.Messaging;
|
||||
using QSB.Player;
|
||||
using QSB.Utility;
|
||||
using QSB.WorldSync;
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
@ -17,8 +18,6 @@ public class AnimationSync : PlayerSyncObject
|
||||
private AnimatorOverrideController _unsuitedAnimController;
|
||||
private GameObject _suitedGraphics;
|
||||
private GameObject _unsuitedGraphics;
|
||||
private PlayerCharacterController _playerController;
|
||||
private CrouchSync _crouchSync;
|
||||
|
||||
public AnimatorMirror Mirror { get; private set; }
|
||||
public bool InSuitedUpState { get; set; }
|
||||
@ -31,16 +30,26 @@ public class AnimationSync : PlayerSyncObject
|
||||
InvisibleAnimator = gameObject.GetRequiredComponent<Animator>();
|
||||
NetworkAnimator = gameObject.GetRequiredComponent<NetworkAnimator>();
|
||||
NetworkAnimator.enabled = false;
|
||||
RequestInitialStatesMessage.SendInitialState += SendInitialState;
|
||||
}
|
||||
|
||||
protected void OnDestroy() => RequestInitialStatesMessage.SendInitialState -= SendInitialState;
|
||||
|
||||
/// <summary>
|
||||
/// This wipes the NetworkAnimator's fields, so it assumes the parameters have changed.
|
||||
/// Basically just forces it to set all its dirty flags.
|
||||
/// </summary>
|
||||
private void SendInitialState(uint to) => NetworkAnimator.Invoke("Awake");
|
||||
|
||||
public void Reset() => InSuitedUpState = false;
|
||||
|
||||
private void InitCommon(Transform modelRoot)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
if (modelRoot == null)
|
||||
{
|
||||
DebugLog.ToConsole($"Error - Trying to InitCommon with null body!", MessageType.Error);
|
||||
DebugLog.ToConsole("Error - Trying to InitCommon with null body!", MessageType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -48,11 +57,11 @@ public class AnimationSync : PlayerSyncObject
|
||||
Mirror = modelRoot.gameObject.AddComponent<AnimatorMirror>();
|
||||
if (isLocalPlayer)
|
||||
{
|
||||
Mirror.Init(VisibleAnimator, InvisibleAnimator);
|
||||
Mirror.Init(VisibleAnimator, InvisibleAnimator, NetworkAnimator);
|
||||
}
|
||||
else
|
||||
{
|
||||
Mirror.Init(InvisibleAnimator, VisibleAnimator);
|
||||
Mirror.Init(InvisibleAnimator, VisibleAnimator, null);
|
||||
}
|
||||
|
||||
NetworkAnimator.enabled = true;
|
||||
@ -74,10 +83,6 @@ public class AnimationSync : PlayerSyncObject
|
||||
public void InitLocal(Transform body)
|
||||
{
|
||||
InitCommon(body);
|
||||
|
||||
_playerController = body.parent.GetComponent<PlayerCharacterController>();
|
||||
|
||||
InitCrouchSync();
|
||||
InitAccelerationSync();
|
||||
}
|
||||
|
||||
@ -85,7 +90,6 @@ public class AnimationSync : PlayerSyncObject
|
||||
{
|
||||
InitCommon(body);
|
||||
SetSuitState(QSBSceneManager.CurrentScene == OWScene.EyeOfTheUniverse);
|
||||
InitCrouchSync();
|
||||
InitAccelerationSync();
|
||||
ThrusterManager.CreateRemotePlayerVFX(Player);
|
||||
|
||||
@ -100,12 +104,6 @@ public class AnimationSync : PlayerSyncObject
|
||||
Player.JetpackAcceleration.Init(thrusterModel);
|
||||
}
|
||||
|
||||
private void InitCrouchSync()
|
||||
{
|
||||
_crouchSync = this.GetRequiredComponent<CrouchSync>();
|
||||
_crouchSync.Init(_playerController, VisibleAnimator);
|
||||
}
|
||||
|
||||
public void SetSuitState(bool suitedUp)
|
||||
{
|
||||
if (!Player.IsReady)
|
||||
@ -176,12 +174,12 @@ public class AnimationSync : PlayerSyncObject
|
||||
// Avoids "jumping" when putting on suit
|
||||
if (VisibleAnimator != null)
|
||||
{
|
||||
VisibleAnimator.SetTrigger("Grounded");
|
||||
VisibleAnimator.SetBool("Grounded", true);
|
||||
}
|
||||
|
||||
if (InvisibleAnimator != null)
|
||||
{
|
||||
InvisibleAnimator.SetTrigger("Grounded");
|
||||
InvisibleAnimator.SetBool("Grounded", true);
|
||||
}
|
||||
|
||||
if (NetworkAnimator == null)
|
||||
@ -196,4 +194,4 @@ public class AnimationSync : PlayerSyncObject
|
||||
Mirror.RebuildFloatParams();
|
||||
NetworkAnimator.Invoke("Awake");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
using OWML.Common;
|
||||
using Mirror;
|
||||
using OWML.Common;
|
||||
using QSB.Utility;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@ -12,10 +13,17 @@ public class AnimatorMirror : MonoBehaviour
|
||||
|
||||
private Animator _from;
|
||||
private Animator _to;
|
||||
private NetworkAnimator _networkAnimator;
|
||||
|
||||
private readonly Dictionary<string, AnimFloatParam> _floatParams = new();
|
||||
|
||||
public void Init(Animator from, Animator to)
|
||||
/// <summary>
|
||||
/// Initializes the Animator Mirror
|
||||
/// </summary>
|
||||
/// <param name="from">The Animator to take the values from.</param>
|
||||
/// <param name="to">The Animator to set the values on to.</param>
|
||||
/// <param name="netAnimator">The NetworkAnimator to sync triggers through. Set only if you have auth over "<paramref name="from"/>", otherwise set to null.</param>
|
||||
public void Init(Animator from, Animator to, NetworkAnimator netAnimator)
|
||||
{
|
||||
if (from == null)
|
||||
{
|
||||
@ -44,6 +52,8 @@ public class AnimatorMirror : MonoBehaviour
|
||||
_to.runtimeAnimatorController = _from.runtimeAnimatorController;
|
||||
}
|
||||
|
||||
_networkAnimator = netAnimator;
|
||||
|
||||
RebuildFloatParams();
|
||||
}
|
||||
|
||||
@ -61,6 +71,7 @@ public class AnimatorMirror : MonoBehaviour
|
||||
}
|
||||
|
||||
SyncParams();
|
||||
SyncLayerWeights();
|
||||
SmoothFloats();
|
||||
}
|
||||
|
||||
@ -74,13 +85,49 @@ public class AnimatorMirror : MonoBehaviour
|
||||
_floatParams[fromParam.name].Target = _from.GetFloat(fromParam.name);
|
||||
break;
|
||||
|
||||
case AnimatorControllerParameterType.Int:
|
||||
_to.SetInteger(fromParam.name, _from.GetInteger(fromParam.name));
|
||||
break;
|
||||
|
||||
case AnimatorControllerParameterType.Bool:
|
||||
_to.SetBool(fromParam.name, _from.GetBool(fromParam.name));
|
||||
break;
|
||||
|
||||
case AnimatorControllerParameterType.Trigger:
|
||||
if (_from.GetBool(fromParam.name) && !_to.GetBool(fromParam.name))
|
||||
{
|
||||
if (_networkAnimator != null)
|
||||
{
|
||||
_networkAnimator.SetTrigger(fromParam.name);
|
||||
}
|
||||
|
||||
_to.SetTrigger(fromParam.name);
|
||||
}
|
||||
|
||||
if (!_from.GetBool(fromParam.name) && _to.GetBool(fromParam.name))
|
||||
{
|
||||
if (_networkAnimator != null)
|
||||
{
|
||||
_networkAnimator.ResetTrigger(fromParam.name);
|
||||
}
|
||||
|
||||
_to.ResetTrigger(fromParam.name);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SyncLayerWeights()
|
||||
{
|
||||
for (var i = 0; i < _from.layerCount; i++)
|
||||
{
|
||||
var weight = _from.GetLayerWeight(i);
|
||||
_to.SetLayerWeight(i, weight);
|
||||
}
|
||||
}
|
||||
|
||||
private void SmoothFloats()
|
||||
{
|
||||
foreach (var floatParam in _floatParams)
|
||||
@ -98,4 +145,4 @@ public class AnimatorMirror : MonoBehaviour
|
||||
_floatParams.Add(param.name, new AnimFloatParam());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,59 +0,0 @@
|
||||
using Mirror;
|
||||
using QSB.Utility.VariableSync;
|
||||
using UnityEngine;
|
||||
|
||||
namespace QSB.Animation.Player;
|
||||
|
||||
public class CrouchSync : NetworkBehaviour
|
||||
{
|
||||
public AnimFloatParam CrouchParam { get; } = new AnimFloatParam();
|
||||
|
||||
private const float CrouchSmoothTime = 0.05f;
|
||||
public const int CrouchLayerIndex = 1;
|
||||
|
||||
private PlayerCharacterController _playerController;
|
||||
private Animator _bodyAnim;
|
||||
|
||||
public FloatVariableSyncer CrouchVariableSyncer;
|
||||
|
||||
public void Init(PlayerCharacterController playerController, Animator bodyAnim)
|
||||
{
|
||||
_playerController = playerController;
|
||||
_bodyAnim = bodyAnim;
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
if (isLocalPlayer)
|
||||
{
|
||||
SyncLocalCrouch();
|
||||
return;
|
||||
}
|
||||
|
||||
SyncRemoteCrouch();
|
||||
}
|
||||
|
||||
private void SyncLocalCrouch()
|
||||
{
|
||||
if (_playerController == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var jumpChargeFraction = _playerController.GetJumpCrouchFraction();
|
||||
CrouchVariableSyncer.Value = jumpChargeFraction;
|
||||
}
|
||||
|
||||
private void SyncRemoteCrouch()
|
||||
{
|
||||
if (_bodyAnim == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CrouchParam.Target = CrouchVariableSyncer.Value;
|
||||
CrouchParam.Smooth(CrouchSmoothTime);
|
||||
var jumpChargeFraction = CrouchParam.Current;
|
||||
_bodyAnim.SetLayerWeight(CrouchLayerIndex, jumpChargeFraction);
|
||||
}
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
using QSB.Messaging;
|
||||
using QSB.Player;
|
||||
using QSB.WorldSync;
|
||||
|
||||
namespace QSB.Animation.Player.Messages;
|
||||
|
||||
internal class AnimationTriggerMessage : QSBMessage<string>
|
||||
{
|
||||
public AnimationTriggerMessage(string name) : base(name) { }
|
||||
|
||||
public override bool ShouldReceive => QSBWorldSync.AllObjectsReady;
|
||||
|
||||
public override void OnReceiveRemote()
|
||||
{
|
||||
var animationSync = QSBPlayerManager.GetPlayer(From).AnimationSync;
|
||||
if (animationSync == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (animationSync.VisibleAnimator == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
animationSync.VisibleAnimator.SetTrigger(Data);
|
||||
}
|
||||
}
|
@ -1,132 +0,0 @@
|
||||
using HarmonyLib;
|
||||
using QSB.Animation.Player.Messages;
|
||||
using QSB.Messaging;
|
||||
using QSB.Patches;
|
||||
using QSB.Utility;
|
||||
using UnityEngine;
|
||||
|
||||
namespace QSB.Animation.Player.Patches;
|
||||
|
||||
[HarmonyPatch]
|
||||
internal class PlayerAnimationPatches : QSBPatch
|
||||
{
|
||||
public override QSBPatchTypes Type => QSBPatchTypes.OnClientConnect;
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(PlayerAnimController), nameof(PlayerAnimController.LateUpdate))]
|
||||
public static bool LateUpdateReplacement(
|
||||
PlayerAnimController __instance)
|
||||
{
|
||||
var isGrounded = __instance._playerController.IsGrounded();
|
||||
var isAttached = PlayerState.IsAttached();
|
||||
var isInZeroG = PlayerState.InZeroG();
|
||||
var isFlying = __instance._playerJetpack.GetLocalAcceleration().y > 0f;
|
||||
var movementVector = Vector3.zero;
|
||||
if (!isAttached)
|
||||
{
|
||||
movementVector = __instance._playerController.GetRelativeGroundVelocity();
|
||||
}
|
||||
|
||||
if (Mathf.Abs(movementVector.x) < 0.05f)
|
||||
{
|
||||
movementVector.x = 0f;
|
||||
}
|
||||
|
||||
if (Mathf.Abs(movementVector.z) < 0.05f)
|
||||
{
|
||||
movementVector.z = 0f;
|
||||
}
|
||||
|
||||
if (isFlying)
|
||||
{
|
||||
__instance._ungroundedTime = Time.time;
|
||||
}
|
||||
|
||||
var freefallMagnitude = 0f;
|
||||
var timeInFreefall = 0f;
|
||||
var lastGroundBody = __instance._playerController.GetLastGroundBody();
|
||||
if (!isGrounded && !isAttached && !isInZeroG && lastGroundBody != null)
|
||||
{
|
||||
freefallMagnitude = (__instance._playerController.GetAttachedOWRigidbody().GetVelocity() - lastGroundBody.GetPointVelocity(__instance._playerController.transform.position)).magnitude;
|
||||
timeInFreefall = Time.time - __instance._ungroundedTime;
|
||||
}
|
||||
|
||||
__instance._animator.SetFloat("RunSpeedX", movementVector.x / 3f);
|
||||
__instance._animator.SetFloat("RunSpeedY", movementVector.z / 3f);
|
||||
__instance._animator.SetFloat("TurnSpeed", __instance._playerController.GetTurning());
|
||||
__instance._animator.SetBool("Grounded", isGrounded || isAttached || PlayerState.IsRecentlyDetached());
|
||||
__instance._animator.SetLayerWeight(CrouchSync.CrouchLayerIndex, __instance._playerController.GetJumpCrouchFraction());
|
||||
__instance._animator.SetFloat("FreefallSpeed", freefallMagnitude / 15f * (timeInFreefall / 3f));
|
||||
__instance._animator.SetBool("InZeroG", isInZeroG || isFlying);
|
||||
__instance._animator.SetBool("UsingJetpack", isInZeroG && PlayerState.IsWearingSuit());
|
||||
if (__instance._justBecameGrounded)
|
||||
{
|
||||
if (__instance._justTookFallDamage)
|
||||
{
|
||||
__instance._animator.SetTrigger("LandHard");
|
||||
new AnimationTriggerMessage("LandHard").Send();
|
||||
}
|
||||
else
|
||||
{
|
||||
__instance._animator.SetTrigger("Land");
|
||||
new AnimationTriggerMessage("Land").Send();
|
||||
}
|
||||
}
|
||||
|
||||
if (isGrounded)
|
||||
{
|
||||
var leftFootLift = __instance._animator.GetFloat("LeftFootLift");
|
||||
if (!__instance._leftFootGrounded && leftFootLift < 0.333f)
|
||||
{
|
||||
__instance._leftFootGrounded = true;
|
||||
__instance.RaiseEvent(nameof(__instance.OnLeftFootGrounded));
|
||||
}
|
||||
else if (__instance._leftFootGrounded && leftFootLift > 0.666f)
|
||||
{
|
||||
__instance._leftFootGrounded = false;
|
||||
__instance.RaiseEvent(nameof(__instance.OnLeftFootLift));
|
||||
}
|
||||
|
||||
var rightFootLift = __instance._animator.GetFloat("RightFootLift");
|
||||
if (!__instance._rightFootGrounded && rightFootLift < 0.333f)
|
||||
{
|
||||
__instance._rightFootGrounded = true;
|
||||
__instance.RaiseEvent(nameof(__instance.OnRightFootGrounded));
|
||||
}
|
||||
else if (__instance._rightFootGrounded && rightFootLift > 0.666f)
|
||||
{
|
||||
__instance._rightFootGrounded = false;
|
||||
__instance.RaiseEvent(nameof(__instance.OnRightFootLift));
|
||||
}
|
||||
}
|
||||
|
||||
__instance._justBecameGrounded = false;
|
||||
__instance._justTookFallDamage = false;
|
||||
var usingTool = Locator.GetToolModeSwapper().GetToolMode() != ToolMode.None;
|
||||
if ((usingTool && !__instance._rightArmHidden) || (!usingTool && __instance._rightArmHidden))
|
||||
{
|
||||
__instance._rightArmHidden = usingTool;
|
||||
for (var i = 0; i < __instance._rightArmObjects.Length; i++)
|
||||
{
|
||||
__instance._rightArmObjects[i].layer = (!__instance._rightArmHidden) ? __instance._defaultLayer : __instance._probeOnlyLayer;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(PlayerAnimController), nameof(PlayerAnimController.OnPlayerJump))]
|
||||
public static bool OnPlayerJumpReplacement(PlayerAnimController __instance)
|
||||
{
|
||||
__instance._ungroundedTime = Time.time;
|
||||
if (!__instance.isActiveAndEnabled)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
__instance._animator.SetTrigger("Jump");
|
||||
new AnimationTriggerMessage("Jump").Send();
|
||||
return false;
|
||||
}
|
||||
}
|
@ -39,6 +39,7 @@ public class PlayerHeadRotationSync : MonoBehaviour
|
||||
var bone = _attachedAnimator.GetBoneTransform(HumanBodyBones.Head);
|
||||
// Get the camera's local rotation with respect to the player body
|
||||
var lookLocalRotation = Quaternion.Inverse(_attachedAnimator.transform.rotation) * _lookBase.rotation;
|
||||
// no idea why this rotation is like this, but this is the only way it looks right
|
||||
bone.localRotation = Quaternion.Euler(-lookLocalRotation.eulerAngles.y, -lookLocalRotation.eulerAngles.z, lookLocalRotation.eulerAngles.x);
|
||||
}
|
||||
}
|
Binary file not shown.
Binary file not shown.
39
QSB/AuthoritySync/AuthWorldObject.cs
Normal file
39
QSB/AuthoritySync/AuthWorldObject.cs
Normal file
@ -0,0 +1,39 @@
|
||||
using Cysharp.Threading.Tasks;
|
||||
using QSB.Messaging;
|
||||
using QSB.Player;
|
||||
using QSB.WorldSync;
|
||||
using System.Threading;
|
||||
using UnityEngine;
|
||||
|
||||
namespace QSB.AuthoritySync;
|
||||
|
||||
/// <summary>
|
||||
/// helper implementation of the interface
|
||||
/// </summary>
|
||||
public abstract class AuthWorldObject<T> : WorldObject<T>, IAuthWorldObject
|
||||
where T : MonoBehaviour
|
||||
{
|
||||
public uint Owner { get; set; }
|
||||
public abstract bool CanOwn { get; }
|
||||
|
||||
public override void SendInitialState(uint to) =>
|
||||
((IAuthWorldObject)this).SendMessage(new AuthWorldObjectMessage(Owner) { To = to });
|
||||
|
||||
public override async UniTask Init(CancellationToken ct) =>
|
||||
QSBPlayerManager.OnRemovePlayer += OnPlayerLeave;
|
||||
|
||||
public override void OnRemoval() =>
|
||||
QSBPlayerManager.OnRemovePlayer -= OnPlayerLeave;
|
||||
|
||||
private void OnPlayerLeave(PlayerInfo player)
|
||||
{
|
||||
if (!QSBCore.IsHost)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (Owner == player.PlayerId)
|
||||
{
|
||||
((IAuthWorldObject)this).SendMessage(new AuthWorldObjectMessage(CanOwn ? QSBPlayerManager.LocalPlayerId : 0));
|
||||
}
|
||||
}
|
||||
}
|
45
QSB/AuthoritySync/AuthWorldObjectMessage.cs
Normal file
45
QSB/AuthoritySync/AuthWorldObjectMessage.cs
Normal file
@ -0,0 +1,45 @@
|
||||
using QSB.Messaging;
|
||||
using QSB.Player;
|
||||
|
||||
namespace QSB.AuthoritySync;
|
||||
|
||||
/// <summary>
|
||||
/// request or release ownership of a world object
|
||||
/// </summary>
|
||||
public class AuthWorldObjectMessage : QSBWorldObjectMessage<IAuthWorldObject, uint>
|
||||
{
|
||||
public AuthWorldObjectMessage(uint owner) : base(owner) { }
|
||||
|
||||
public override bool ShouldReceive
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!base.ShouldReceive)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Deciding if to change the object's owner
|
||||
// Message
|
||||
// | = 0 | > 0 |
|
||||
// = 0 | No | Yes |
|
||||
// > 0 | Yes | No |
|
||||
// if Obj==Message then No
|
||||
// Obj
|
||||
|
||||
return (WorldObject.Owner == 0 || Data == 0) && WorldObject.Owner != Data;
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnReceiveLocal() => WorldObject.Owner = Data;
|
||||
|
||||
public override void OnReceiveRemote()
|
||||
{
|
||||
WorldObject.Owner = Data;
|
||||
if (WorldObject.Owner == 0 && WorldObject.CanOwn)
|
||||
{
|
||||
// object has no owner, but is still active for this player. request ownership
|
||||
WorldObject.SendMessage(new AuthWorldObjectMessage(QSBPlayerManager.LocalPlayerId));
|
||||
}
|
||||
}
|
||||
}
|
18
QSB/AuthoritySync/IAuthWorldObject.cs
Normal file
18
QSB/AuthoritySync/IAuthWorldObject.cs
Normal file
@ -0,0 +1,18 @@
|
||||
using QSB.WorldSync;
|
||||
|
||||
namespace QSB.AuthoritySync;
|
||||
|
||||
/// <summary>
|
||||
/// a world object that has an owner
|
||||
/// </summary>
|
||||
public interface IAuthWorldObject : IWorldObject
|
||||
{
|
||||
/// <summary>
|
||||
/// 0 = owned by no one
|
||||
/// </summary>
|
||||
public uint Owner { get; set; }
|
||||
/// <summary>
|
||||
/// can the world object have authority
|
||||
/// </summary>
|
||||
public bool CanOwn { get; }
|
||||
}
|
32
QSB/AuthoritySync/IAuthWorldObject_Extensions.cs
Normal file
32
QSB/AuthoritySync/IAuthWorldObject_Extensions.cs
Normal file
@ -0,0 +1,32 @@
|
||||
using QSB.Messaging;
|
||||
using QSB.Player;
|
||||
|
||||
namespace QSB.AuthoritySync;
|
||||
|
||||
public static class IAuthWorldObject_Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// try and gain authority over the object
|
||||
/// </summary>
|
||||
public static void RequestOwnership(this IAuthWorldObject @this)
|
||||
{
|
||||
if (@this.Owner != 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@this.SendMessage(new AuthWorldObjectMessage(QSBPlayerManager.LocalPlayerId));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// release authority over the object,
|
||||
/// potentially to giving it to someone else
|
||||
/// </summary>
|
||||
public static void ReleaseOwnership(this IAuthWorldObject @this)
|
||||
{
|
||||
if (@this.Owner != QSBPlayerManager.LocalPlayerId)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@this.SendMessage(new AuthWorldObjectMessage(0));
|
||||
}
|
||||
}
|
@ -6,13 +6,16 @@ using QSB.WorldSync;
|
||||
|
||||
namespace QSB.CampfireSync.Messages;
|
||||
|
||||
/// <summary>
|
||||
/// TODO: initial state on campfire and item
|
||||
/// </summary>
|
||||
internal class BurnSlideReelMessage : QSBWorldObjectMessage<QSBSlideReelItem, int>
|
||||
{
|
||||
public BurnSlideReelMessage(QSBCampfire campfire) : base(campfire.ObjectId) { }
|
||||
|
||||
public override void OnReceiveRemote()
|
||||
{
|
||||
var campfire = QSBWorldSync.GetWorldObject<QSBCampfire>(Data).AttachedObject;
|
||||
var campfire = Data.GetWorldObject<QSBCampfire>().AttachedObject;
|
||||
var fromPlayer = QSBPlayerManager.GetPlayer(From);
|
||||
WorldObject.DropItem(
|
||||
campfire._burnedSlideReelSocket.position,
|
||||
|
@ -45,7 +45,7 @@ public class ConversationManager : WorldObjectManager
|
||||
QSBWorldSync.Init<QSBCharacterDialogueTree, CharacterDialogueTree>();
|
||||
}
|
||||
|
||||
public uint GetPlayerTalkingToTree(CharacterDialogueTree tree) =>
|
||||
public uint GetPlayerTalkingToTree(CharacterDialogueTree tree) =>
|
||||
QSBPlayerManager.PlayerList.FirstOrDefault(x => x.CurrentCharacterDialogueTree?.AttachedObject == tree)
|
||||
?.PlayerId ?? uint.MaxValue;
|
||||
|
||||
@ -62,7 +62,7 @@ public class ConversationManager : WorldObjectManager
|
||||
new ConversationMessage(ConversationType.CloseCharacter, id).Send();
|
||||
|
||||
public void SendConvState(QSBCharacterDialogueTree tree, bool state)
|
||||
=> tree.SendMessage(new ConversationStartEndMessage(state));
|
||||
=> tree.SendMessage(new ConversationStartEndMessage(QSBPlayerManager.LocalPlayerId, state));
|
||||
|
||||
public void DisplayPlayerConversationBox(uint playerId, string text)
|
||||
{
|
||||
@ -109,4 +109,4 @@ public class ConversationManager : WorldObjectManager
|
||||
newBox.SetActive(true);
|
||||
return newBox;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,21 +5,21 @@ using QSB.Utility;
|
||||
|
||||
namespace QSB.ConversationSync.Messages;
|
||||
|
||||
public class ConversationStartEndMessage : QSBWorldObjectMessage<QSBCharacterDialogueTree, bool>
|
||||
public class ConversationStartEndMessage : QSBWorldObjectMessage<QSBCharacterDialogueTree, (uint playerId, bool start)>
|
||||
{
|
||||
public ConversationStartEndMessage(bool start) : base(start) { }
|
||||
public ConversationStartEndMessage(uint playerId, bool start) : base((playerId, start)) { }
|
||||
|
||||
public override void OnReceiveRemote()
|
||||
{
|
||||
if (Data)
|
||||
if (Data.start)
|
||||
{
|
||||
QSBPlayerManager.GetPlayer(From).CurrentCharacterDialogueTree = WorldObject;
|
||||
QSBPlayerManager.GetPlayer(Data.playerId).CurrentCharacterDialogueTree = WorldObject;
|
||||
WorldObject.AttachedObject.GetInteractVolume().DisableInteraction();
|
||||
WorldObject.AttachedObject.RaiseEvent(nameof(CharacterDialogueTree.OnStartConversation));
|
||||
}
|
||||
else
|
||||
{
|
||||
QSBPlayerManager.GetPlayer(From).CurrentCharacterDialogueTree = null;
|
||||
QSBPlayerManager.GetPlayer(Data.playerId).CurrentCharacterDialogueTree = null;
|
||||
WorldObject.AttachedObject.GetInteractVolume().EnableInteraction();
|
||||
WorldObject.AttachedObject.RaiseEvent(nameof(CharacterDialogueTree.OnEndConversation));
|
||||
}
|
||||
|
@ -1,9 +1,6 @@
|
||||
using QSB.WorldSync;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using QSB.ConversationSync.Messages;
|
||||
using QSB.Messaging;
|
||||
using QSB.WorldSync;
|
||||
|
||||
namespace QSB.ConversationSync.WorldObjects;
|
||||
|
||||
@ -11,6 +8,11 @@ public class QSBCharacterDialogueTree : WorldObject<CharacterDialogueTree>
|
||||
{
|
||||
public override void SendInitialState(uint to)
|
||||
{
|
||||
// todo : implement this
|
||||
var playerId = ConversationManager.Instance.GetPlayerTalkingToTree(AttachedObject);
|
||||
if (playerId != uint.MaxValue)
|
||||
{
|
||||
this.SendMessage(new ConversationStartEndMessage(playerId, true) { To = to });
|
||||
}
|
||||
// TODO: maybe also sync the dialogue box and player box?
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,9 @@
|
||||
using OWML.Common;
|
||||
using QSB.Localization;
|
||||
using QSB.Player;
|
||||
using QSB.Player.TransformSync;
|
||||
using QSB.RespawnSync;
|
||||
using QSB.ShipSync;
|
||||
using QSB.Utility;
|
||||
using QSB.WorldSync;
|
||||
using System.Linq;
|
||||
@ -26,6 +28,7 @@ public class RespawnOnDeath : MonoBehaviour
|
||||
private PlayerSpacesuit _spaceSuit;
|
||||
private SuitPickupVolume[] _suitPickupVolumes;
|
||||
private Vector3 _deathPositionRelative;
|
||||
private GUIStyle _deadTextStyle;
|
||||
|
||||
public Transform DeathClosestAstroObject { get; private set; }
|
||||
public Vector3 DeathPositionWorld
|
||||
@ -47,6 +50,11 @@ public class RespawnOnDeath : MonoBehaviour
|
||||
_suitPickupVolumes = FindObjectsOfType<SuitPickupVolume>();
|
||||
_fluidDetector = Locator.GetPlayerCamera().GetComponentInChildren<FluidDetector>();
|
||||
_playerSpawnPoint = GetSpawnPoint();
|
||||
_deadTextStyle = new();
|
||||
_deadTextStyle.font = (Font)Resources.Load(@"fonts\english - latin\SpaceMono-Regular_Dynamic");
|
||||
_deadTextStyle.alignment = TextAnchor.MiddleCenter;
|
||||
_deadTextStyle.normal.textColor = Color.white;
|
||||
_deadTextStyle.fontSize = 20;
|
||||
}
|
||||
|
||||
public void ResetPlayer()
|
||||
@ -122,4 +130,31 @@ public class RespawnOnDeath : MonoBehaviour
|
||||
spawnPoint.GetSpawnLocation() == SpawnLocation.TimberHearth
|
||||
&& spawnPoint.IsShipSpawn() == false);
|
||||
}
|
||||
|
||||
void OnGUI()
|
||||
{
|
||||
if (PlayerTransformSync.LocalInstance == null || ShipManager.Instance.ShipCockpitUI == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (QSBPlayerManager.LocalPlayer.IsDead)
|
||||
{
|
||||
GUI.contentColor = Color.white;
|
||||
|
||||
var width = 200;
|
||||
var height = 100;
|
||||
|
||||
// it is good day to be not dead
|
||||
|
||||
var secondText = ShipManager.Instance.IsShipWrecked
|
||||
? string.Format(QSBLocalization.Current.WaitingForAllToDie, QSBPlayerManager.PlayerList.Count(x => !x.IsDead))
|
||||
: QSBLocalization.Current.WaitingForRespawn;
|
||||
|
||||
GUI.Label(
|
||||
new Rect((Screen.width / 2) - (width / 2), (Screen.height / 2) - (height / 2) + (height * 2), width, height),
|
||||
$"{QSBLocalization.Current.YouAreDead}\n{secondText}",
|
||||
_deadTextStyle);
|
||||
}
|
||||
}
|
||||
}
|
@ -10,6 +10,9 @@ internal class AirlockManager : WorldObjectManager
|
||||
public override WorldObjectScene WorldObjectScene => WorldObjectScene.SolarSystem;
|
||||
public override bool DlcOnly => true;
|
||||
|
||||
public override async UniTask BuildWorldObjects(OWScene scene, CancellationToken ct) =>
|
||||
public override async UniTask BuildWorldObjects(OWScene scene, CancellationToken ct)
|
||||
{
|
||||
QSBWorldSync.Init<QSBAirlockInterface, AirlockInterface>();
|
||||
}
|
||||
QSBWorldSync.Init<QSBGhostAirlock, GhostAirlock>();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,17 @@
|
||||
using QSB.EchoesOfTheEye.AirlockSync.WorldObjects;
|
||||
using QSB.Messaging;
|
||||
|
||||
namespace QSB.EchoesOfTheEye.AirlockSync.Messages;
|
||||
|
||||
internal class AirlockInitialStateMessage : QSBWorldObjectMessage<QSBGhostAirlock, (bool innerDoorOpen, bool outerDoorOpen, bool pressurized)>
|
||||
{
|
||||
public AirlockInitialStateMessage(bool innerDoorOpen, bool outerDoorOpen, bool pressurized) : base((innerDoorOpen, outerDoorOpen, pressurized)) { }
|
||||
|
||||
public override void OnReceiveRemote()
|
||||
{
|
||||
var airlock = WorldObject.AttachedObject;
|
||||
airlock._innerDoor.SetOpenImmediate(Data.innerDoorOpen);
|
||||
airlock._outerDoor.SetOpenImmediate(Data.outerDoorOpen);
|
||||
airlock.SetPressurization(Data.pressurized);
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
using QSB.EchoesOfTheEye.AirlockSync.VariableSync;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
@ -9,4 +10,14 @@ internal class QSBAirlockInterface : QSBRotatingElements<AirlockInterface, Airlo
|
||||
protected override IEnumerable<SingleLightSensor> LightSensors => AttachedObject._lightSensors;
|
||||
|
||||
protected override GameObject NetworkObjectPrefab => QSBNetworkManager.singleton.AirlockPrefab;
|
||||
|
||||
public override string ReturnLabel()
|
||||
{
|
||||
var baseString = $"{this}{Environment.NewLine}CurrentRotation:{AttachedObject._currentRotation}";
|
||||
foreach (var element in AttachedObject._rotatingElements)
|
||||
{
|
||||
baseString += $"{Environment.NewLine}localRotation:{element.localRotation}";
|
||||
}
|
||||
return baseString;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,17 @@
|
||||
using QSB.EchoesOfTheEye.AirlockSync.Messages;
|
||||
using QSB.Messaging;
|
||||
using QSB.WorldSync;
|
||||
|
||||
namespace QSB.EchoesOfTheEye.AirlockSync.WorldObjects;
|
||||
|
||||
internal class QSBGhostAirlock : WorldObject<GhostAirlock>
|
||||
{
|
||||
public override void SendInitialState(uint to)
|
||||
=> this.SendMessage(
|
||||
new AirlockInitialStateMessage(
|
||||
AttachedObject._innerDoor.IsOpen(),
|
||||
AttachedObject._outerDoor.IsOpen(),
|
||||
AttachedObject._pressurized
|
||||
)
|
||||
);
|
||||
}
|
@ -15,7 +15,7 @@ public class AlarmTotemManager : WorldObjectManager
|
||||
|
||||
public override async UniTask BuildWorldObjects(OWScene scene, CancellationToken ct)
|
||||
{
|
||||
QSBWorldSync.Init<QSBAlarmTotem, AlarmTotem>();
|
||||
// QSBWorldSync.Init<QSBAlarmTotem, AlarmTotem>();
|
||||
QSBWorldSync.Init<QSBAlarmBell, AlarmBell>();
|
||||
|
||||
_qsbAlarmSequenceController = new GameObject(nameof(QSBAlarmSequenceController))
|
||||
|
@ -12,6 +12,7 @@ public class AlarmTotemPatches : QSBPatch
|
||||
{
|
||||
public override QSBPatchTypes Type => QSBPatchTypes.OnClientConnect;
|
||||
|
||||
/*
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(AlarmTotem), nameof(AlarmTotem.OnSectorOccupantAdded))]
|
||||
private static void OnSectorOccupantAdded(AlarmTotem __instance, SectorDetector sectorDetector)
|
||||
@ -80,6 +81,7 @@ public class AlarmTotemPatches : QSBPatch
|
||||
|
||||
return false;
|
||||
}
|
||||
*/
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(AlarmBell), nameof(AlarmBell.OnEntry))]
|
||||
|
@ -2,7 +2,4 @@
|
||||
|
||||
namespace QSB.EchoesOfTheEye.AlarmTotemSync.WorldObjects;
|
||||
|
||||
public class QSBAlarmBell : WorldObject<AlarmBell>
|
||||
{
|
||||
public override void SendInitialState(uint to) { }
|
||||
}
|
||||
public class QSBAlarmBell : WorldObject<AlarmBell> { }
|
||||
|
@ -8,6 +8,9 @@ using System.Threading;
|
||||
|
||||
namespace QSB.EchoesOfTheEye.AlarmTotemSync.WorldObjects;
|
||||
|
||||
/// <summary>
|
||||
/// TODO: make this not NRE (by not doing enable sync) and then readd it back in
|
||||
/// </summary>
|
||||
public class QSBAlarmTotem : WorldObject<AlarmTotem>
|
||||
{
|
||||
public readonly List<uint> VisibleFor = new();
|
||||
|
@ -4,6 +4,9 @@ using QSB.WorldSync;
|
||||
|
||||
namespace QSB.EchoesOfTheEye.DreamLantern.WorldObjects;
|
||||
|
||||
/// <summary>
|
||||
/// TODO: lanterns held by ghosts should only be controlled by the host (to prevent it from visually freaking out)
|
||||
/// </summary>
|
||||
public class QSBDreamLantern : WorldObject<DreamLanternController>
|
||||
{
|
||||
public override void SendInitialState(uint to)
|
||||
|
@ -6,8 +6,6 @@ namespace QSB.EchoesOfTheEye.DreamRafts.WorldObjects;
|
||||
|
||||
public class QSBDreamRaft : LinkedWorldObject<DreamRaftController, RaftTransformSync>
|
||||
{
|
||||
public override void SendInitialState(uint to) { }
|
||||
|
||||
protected override GameObject NetworkObjectPrefab => QSBNetworkManager.singleton.RaftPrefab;
|
||||
protected override bool SpawnWithServerAuthority => false;
|
||||
}
|
||||
|
@ -6,8 +6,6 @@ namespace QSB.EchoesOfTheEye.DreamRafts.WorldObjects;
|
||||
|
||||
public class QSBSealRaft : LinkedWorldObject<SealRaftController, RaftTransformSync>
|
||||
{
|
||||
public override void SendInitialState(uint to) { }
|
||||
|
||||
protected override GameObject NetworkObjectPrefab => QSBNetworkManager.singleton.RaftPrefab;
|
||||
protected override bool SpawnWithServerAuthority => false;
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ public class QSBGhostBrain : WorldObject<GhostBrain>, IGhostObject
|
||||
|
||||
public override void SendInitialState(uint to)
|
||||
{
|
||||
|
||||
// todo SendInitialState
|
||||
}
|
||||
|
||||
public override async UniTask Init(CancellationToken ct)
|
||||
|
@ -17,7 +17,7 @@ public class QSBGhostController : WorldObject<GhostController>, IGhostObject
|
||||
{
|
||||
public override void SendInitialState(uint to)
|
||||
{
|
||||
|
||||
// todo SendInitialState
|
||||
}
|
||||
|
||||
public QSBGhostEffects _effects;
|
||||
|
@ -16,7 +16,7 @@ public class QSBGhostEffects : WorldObject<GhostEffects>, IGhostObject
|
||||
{
|
||||
public override void SendInitialState(uint to)
|
||||
{
|
||||
|
||||
// todo SendInitialState
|
||||
}
|
||||
|
||||
public override bool ShouldDisplayDebug() => false;
|
||||
|
@ -11,7 +11,7 @@ public class QSBGhostGrabController : WorldObject<GhostGrabController>
|
||||
{
|
||||
public override void SendInitialState(uint to)
|
||||
{
|
||||
|
||||
// todo SendInitialState
|
||||
}
|
||||
|
||||
public void GrabPlayer(float speed, GhostPlayer player, bool remote = false)
|
||||
|
@ -11,6 +11,6 @@ internal class QSBGhostNodeMap : WorldObject<GhostNodeMap>
|
||||
{
|
||||
public override void SendInitialState(uint to)
|
||||
{
|
||||
|
||||
// todo SendInitialState??
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ public class QSBGhostSensors : WorldObject<GhostSensors>, IGhostObject
|
||||
{
|
||||
public override void SendInitialState(uint to)
|
||||
{
|
||||
|
||||
// todo SendInitialState
|
||||
}
|
||||
|
||||
public override string ReturnLabel() => "";
|
||||
|
@ -2,7 +2,4 @@
|
||||
|
||||
namespace QSB.EchoesOfTheEye.GrappleTotemSync.WorldObjects;
|
||||
|
||||
public class QSBGrappleTotem : WorldObject<LanternZoomPoint>
|
||||
{
|
||||
public override void SendInitialState(uint to) { }
|
||||
}
|
||||
public class QSBGrappleTotem : WorldObject<LanternZoomPoint> { }
|
||||
|
@ -1,26 +0,0 @@
|
||||
using QSB.EchoesOfTheEye.LightSensorSync.WorldObjects;
|
||||
using QSB.Messaging;
|
||||
using System.Linq;
|
||||
|
||||
namespace QSB.EchoesOfTheEye.LightSensorSync.Messages;
|
||||
|
||||
/// <summary>
|
||||
/// always sent by host
|
||||
/// </summary>
|
||||
internal class IlluminatedByMessage : QSBWorldObjectMessage<QSBLightSensor, uint[]>
|
||||
{
|
||||
public IlluminatedByMessage(uint[] illuminatedBy) : base(illuminatedBy) { }
|
||||
|
||||
public override void OnReceiveRemote()
|
||||
{
|
||||
foreach (var added in Data.Except(WorldObject._illuminatedBy))
|
||||
{
|
||||
WorldObject.SetIlluminated(added, true);
|
||||
}
|
||||
|
||||
foreach (var removed in WorldObject._illuminatedBy.Except(Data))
|
||||
{
|
||||
WorldObject.SetIlluminated(removed, false);
|
||||
}
|
||||
}
|
||||
}
|
@ -14,12 +14,6 @@ internal class IlluminatingLanternsMessage : QSBWorldObjectMessage<QSBLightSenso
|
||||
|
||||
public override void OnReceiveRemote()
|
||||
{
|
||||
if (WorldObject.AttachedObject.enabled)
|
||||
{
|
||||
// sensor is enabled, so this will already be synced
|
||||
return;
|
||||
}
|
||||
|
||||
WorldObject.AttachedObject._illuminatingDreamLanternList.Clear();
|
||||
WorldObject.AttachedObject._illuminatingDreamLanternList.AddRange(
|
||||
Data.Select(x => x.GetWorldObject<QSBDreamLantern>().AttachedObject));
|
||||
|
@ -1,28 +0,0 @@
|
||||
using QSB.Messaging;
|
||||
using QSB.Player;
|
||||
using System.Linq;
|
||||
|
||||
namespace QSB.EchoesOfTheEye.LightSensorSync.Messages;
|
||||
|
||||
/// <summary>
|
||||
/// always sent by host
|
||||
/// </summary>
|
||||
internal class PlayerIlluminatedByMessage : QSBMessage<(uint playerId, uint[] illuminatedBy)>
|
||||
{
|
||||
public PlayerIlluminatedByMessage(uint playerId, uint[] illuminatedBy) : base((playerId, illuminatedBy)) { }
|
||||
|
||||
public override void OnReceiveRemote()
|
||||
{
|
||||
var qsbPlayerLightSensor = QSBPlayerManager.GetPlayer(Data.playerId).QSBPlayerLightSensor;
|
||||
|
||||
foreach (var added in Data.illuminatedBy.Except(qsbPlayerLightSensor._illuminatedBy))
|
||||
{
|
||||
qsbPlayerLightSensor.SetIlluminated(added, true);
|
||||
}
|
||||
|
||||
foreach (var removed in qsbPlayerLightSensor._illuminatedBy.Except(Data.illuminatedBy))
|
||||
{
|
||||
qsbPlayerLightSensor.SetIlluminated(removed, false);
|
||||
}
|
||||
}
|
||||
}
|
@ -19,12 +19,6 @@ internal class PlayerIlluminatingLanternsMessage : QSBMessage<(uint playerId, in
|
||||
{
|
||||
var lightSensor = (SingleLightSensor)QSBPlayerManager.GetPlayer(Data.playerId).LightSensor;
|
||||
|
||||
if (lightSensor.enabled)
|
||||
{
|
||||
// sensor is enabled, so this will already be synced
|
||||
return;
|
||||
}
|
||||
|
||||
lightSensor._illuminatingDreamLanternList.Clear();
|
||||
lightSensor._illuminatingDreamLanternList.AddRange(
|
||||
Data.lanterns.Select(x => x.GetWorldObject<QSBDreamLantern>().AttachedObject));
|
||||
|
@ -6,8 +6,24 @@ namespace QSB.EchoesOfTheEye.LightSensorSync.Messages;
|
||||
internal class PlayerSetIlluminatedMessage : QSBMessage<(uint playerId, bool illuminated)>
|
||||
{
|
||||
public PlayerSetIlluminatedMessage(uint playerId, bool illuminated) : base((playerId, illuminated)) { }
|
||||
public override void OnReceiveLocal() => OnReceiveRemote();
|
||||
|
||||
public override void OnReceiveRemote() =>
|
||||
QSBPlayerManager.GetPlayer(Data.playerId).QSBPlayerLightSensor.SetIlluminated(From, Data.illuminated);
|
||||
public override void OnReceiveRemote()
|
||||
{
|
||||
var lightSensor = (SingleLightSensor)QSBPlayerManager.GetPlayer(Data.playerId).LightSensor;
|
||||
|
||||
if (lightSensor._illuminated == Data.illuminated)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lightSensor._illuminated = Data.illuminated;
|
||||
if (Data.illuminated)
|
||||
{
|
||||
lightSensor.OnDetectLight.Invoke();
|
||||
}
|
||||
else
|
||||
{
|
||||
lightSensor.OnDetectDarkness.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,22 @@ namespace QSB.EchoesOfTheEye.LightSensorSync.Messages;
|
||||
internal class SetIlluminatedMessage : QSBWorldObjectMessage<QSBLightSensor, bool>
|
||||
{
|
||||
public SetIlluminatedMessage(bool illuminated) : base(illuminated) { }
|
||||
public override void OnReceiveLocal() => OnReceiveRemote();
|
||||
public override void OnReceiveRemote() => WorldObject.SetIlluminated(From, Data);
|
||||
|
||||
public override void OnReceiveRemote()
|
||||
{
|
||||
if (WorldObject.AttachedObject._illuminated == Data)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
WorldObject.AttachedObject._illuminated = Data;
|
||||
if (Data)
|
||||
{
|
||||
WorldObject.AttachedObject.OnDetectLight.Invoke();
|
||||
}
|
||||
else
|
||||
{
|
||||
WorldObject.AttachedObject.OnDetectDarkness.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,23 @@
|
||||
using HarmonyLib;
|
||||
using QSB.AuthoritySync;
|
||||
using QSB.EchoesOfTheEye.LightSensorSync.Messages;
|
||||
using QSB.EchoesOfTheEye.LightSensorSync.WorldObjects;
|
||||
using QSB.Messaging;
|
||||
using QSB.Patches;
|
||||
using QSB.Player;
|
||||
using QSB.Tools.FlashlightTool;
|
||||
using QSB.Tools.ProbeTool;
|
||||
using QSB.Utility;
|
||||
using QSB.WorldSync;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
/*
|
||||
* For those who come here,
|
||||
* leave while you still can.
|
||||
*/
|
||||
|
||||
namespace QSB.EchoesOfTheEye.LightSensorSync.Patches;
|
||||
|
||||
[HarmonyPatch(typeof(SingleLightSensor))]
|
||||
@ -15,92 +25,26 @@ internal class LightSensorPatches : QSBPatch
|
||||
{
|
||||
public override QSBPatchTypes Type => QSBPatchTypes.OnClientConnect;
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(nameof(SingleLightSensor.Start))]
|
||||
private static bool Start(SingleLightSensor __instance)
|
||||
{
|
||||
if (!QSBWorldSync.AllObjectsReady)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var isPlayerLightSensor = LightSensorManager.IsPlayerLightSensor(__instance);
|
||||
var qsbPlayerLightSensor = isPlayerLightSensor ? __instance.GetComponent<QSBPlayerLightSensor>() : null;
|
||||
var qsbLightSensor = isPlayerLightSensor ? null : __instance.GetWorldObject<QSBLightSensor>();
|
||||
|
||||
if (__instance._lightDetector != null)
|
||||
{
|
||||
__instance._lightSources = new List<ILightSource>();
|
||||
__instance._lightSourceMask = LightSourceType.VOLUME_ONLY;
|
||||
if (__instance._detectFlashlight)
|
||||
{
|
||||
__instance._lightSourceMask |= LightSourceType.FLASHLIGHT;
|
||||
}
|
||||
|
||||
if (__instance._detectProbe)
|
||||
{
|
||||
__instance._lightSourceMask |= LightSourceType.PROBE;
|
||||
}
|
||||
|
||||
if (__instance._detectDreamLanterns)
|
||||
{
|
||||
__instance._lightSourceMask |= LightSourceType.DREAM_LANTERN;
|
||||
}
|
||||
|
||||
if (__instance._detectSimpleLanterns)
|
||||
{
|
||||
__instance._lightSourceMask |= LightSourceType.SIMPLE_LANTERN;
|
||||
}
|
||||
|
||||
__instance._lightDetector.OnLightVolumeEnter += __instance.OnLightSourceEnter;
|
||||
__instance._lightDetector.OnLightVolumeExit += __instance.OnLightSourceExit;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("LightSensor has no LightSourceDetector", __instance);
|
||||
}
|
||||
|
||||
if (__instance._sector != null)
|
||||
{
|
||||
__instance.enabled = false;
|
||||
__instance._lightDetector.GetShape().enabled = false;
|
||||
if (__instance._startIlluminated)
|
||||
{
|
||||
if (isPlayerLightSensor)
|
||||
{
|
||||
qsbPlayerLightSensor._locallyIlluminated = true;
|
||||
new PlayerSetIlluminatedMessage(qsbPlayerLightSensor.PlayerId, true).Send();
|
||||
}
|
||||
else
|
||||
{
|
||||
qsbLightSensor._locallyIlluminated = true;
|
||||
qsbLightSensor.OnDetectLocalLight?.Invoke();
|
||||
qsbLightSensor.SendMessage(new SetIlluminatedMessage(true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(nameof(SingleLightSensor.OnSectorOccupantsUpdated))]
|
||||
private static bool OnSectorOccupantsUpdated(SingleLightSensor __instance)
|
||||
{
|
||||
if (LightSensorManager.IsPlayerLightSensor(__instance))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (!QSBWorldSync.AllObjectsReady)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var isPlayerLightSensor = LightSensorManager.IsPlayerLightSensor(__instance);
|
||||
var qsbPlayerLightSensor = isPlayerLightSensor ? __instance.GetComponent<QSBPlayerLightSensor>() : null;
|
||||
var qsbLightSensor = isPlayerLightSensor ? null : __instance.GetWorldObject<QSBLightSensor>();
|
||||
var qsbLightSensor = __instance.GetWorldObject<QSBLightSensor>();
|
||||
|
||||
var containsAnyOccupants = __instance._sector.ContainsAnyOccupants(DynamicOccupant.Player | DynamicOccupant.Probe);
|
||||
if (containsAnyOccupants && !__instance.enabled)
|
||||
{
|
||||
__instance.enabled = true;
|
||||
__instance._lightDetector.GetShape().enabled = true;
|
||||
qsbLightSensor.RequestOwnership();
|
||||
if (__instance._preserveStateWhileDisabled)
|
||||
{
|
||||
__instance._fixedUpdateFrameDelayCount = 10;
|
||||
@ -110,118 +54,233 @@ internal class LightSensorPatches : QSBPatch
|
||||
{
|
||||
__instance.enabled = false;
|
||||
__instance._lightDetector.GetShape().enabled = false;
|
||||
qsbLightSensor.ReleaseOwnership();
|
||||
if (!__instance._preserveStateWhileDisabled)
|
||||
{
|
||||
if (isPlayerLightSensor)
|
||||
if (__instance._illuminated)
|
||||
{
|
||||
if (qsbPlayerLightSensor._locallyIlluminated)
|
||||
Delay.RunFramesLater(10, () =>
|
||||
{
|
||||
qsbPlayerLightSensor._locallyIlluminated = false;
|
||||
new PlayerSetIlluminatedMessage(qsbPlayerLightSensor.PlayerId, false).Send();
|
||||
}
|
||||
// no one else took ownership, so we can safely make not illuminated
|
||||
// ie turn off when no one else is there
|
||||
if (qsbLightSensor.Owner == 0)
|
||||
{
|
||||
__instance._illuminated = false;
|
||||
__instance.OnDetectDarkness.Invoke();
|
||||
qsbLightSensor.SendMessage(new SetIlluminatedMessage(false));
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
|
||||
if (qsbLightSensor._locallyIlluminated)
|
||||
{
|
||||
if (qsbLightSensor._locallyIlluminated)
|
||||
{
|
||||
qsbLightSensor._locallyIlluminated = false;
|
||||
qsbLightSensor.OnDetectLocalDarkness?.Invoke();
|
||||
qsbLightSensor.SendMessage(new SetIlluminatedMessage(false));
|
||||
}
|
||||
qsbLightSensor._locallyIlluminated = false;
|
||||
qsbLightSensor.OnDetectLocalDarkness?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// to prevent allocating a new list every frame
|
||||
/// </summary>
|
||||
private static readonly List<DreamLanternController> _prevIlluminatingDreamLanternList = new();
|
||||
private static readonly List<DreamLanternController> _illuminatingDreamLanternList = new();
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(nameof(SingleLightSensor.ManagedFixedUpdate))]
|
||||
private static bool ManagedFixedUpdate(SingleLightSensor __instance)
|
||||
{
|
||||
if (LightSensorManager.IsPlayerLightSensor(__instance))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (!QSBWorldSync.AllObjectsReady)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var isPlayerLightSensor = LightSensorManager.IsPlayerLightSensor(__instance);
|
||||
var qsbPlayerLightSensor = isPlayerLightSensor ? __instance.GetComponent<QSBPlayerLightSensor>() : null;
|
||||
var qsbLightSensor = isPlayerLightSensor ? null : __instance.GetWorldObject<QSBLightSensor>();
|
||||
var qsbLightSensor = __instance.GetWorldObject<QSBLightSensor>();
|
||||
|
||||
if (__instance._fixedUpdateFrameDelayCount > 0)
|
||||
{
|
||||
__instance._fixedUpdateFrameDelayCount--;
|
||||
return false;
|
||||
}
|
||||
|
||||
var illuminated = __instance._illuminated;
|
||||
var locallyIlluminated = qsbLightSensor._locallyIlluminated;
|
||||
if (__instance._illuminatingDreamLanternList != null)
|
||||
{
|
||||
_prevIlluminatingDreamLanternList.Clear();
|
||||
_prevIlluminatingDreamLanternList.AddRange(__instance._illuminatingDreamLanternList);
|
||||
_illuminatingDreamLanternList.Clear();
|
||||
_illuminatingDreamLanternList.AddRange(__instance._illuminatingDreamLanternList);
|
||||
}
|
||||
|
||||
var illuminated = __instance._illuminated;
|
||||
__instance.UpdateIllumination();
|
||||
bool locallyIlluminated;
|
||||
if (isPlayerLightSensor)
|
||||
if (qsbLightSensor.Owner == QSBPlayerManager.LocalPlayerId)
|
||||
{
|
||||
locallyIlluminated = qsbPlayerLightSensor._locallyIlluminated;
|
||||
qsbPlayerLightSensor._locallyIlluminated = __instance._illuminated;
|
||||
}
|
||||
else
|
||||
{
|
||||
locallyIlluminated = qsbLightSensor._locallyIlluminated;
|
||||
qsbLightSensor._locallyIlluminated = __instance._illuminated;
|
||||
}
|
||||
|
||||
__instance._illuminated = illuminated;
|
||||
|
||||
if (isPlayerLightSensor)
|
||||
{
|
||||
if (!locallyIlluminated && qsbPlayerLightSensor._locallyIlluminated)
|
||||
if (!illuminated && __instance._illuminated)
|
||||
{
|
||||
qsbPlayerLightSensor._locallyIlluminated = true;
|
||||
new PlayerSetIlluminatedMessage(qsbPlayerLightSensor.PlayerId, true).Send();
|
||||
}
|
||||
else if (locallyIlluminated && !qsbPlayerLightSensor._locallyIlluminated)
|
||||
{
|
||||
qsbPlayerLightSensor._locallyIlluminated = false;
|
||||
new PlayerSetIlluminatedMessage(qsbPlayerLightSensor.PlayerId, false).Send();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!locallyIlluminated && qsbLightSensor._locallyIlluminated)
|
||||
{
|
||||
qsbLightSensor._locallyIlluminated = true;
|
||||
qsbLightSensor.OnDetectLocalLight?.Invoke();
|
||||
__instance.OnDetectLight.Invoke();
|
||||
qsbLightSensor.SendMessage(new SetIlluminatedMessage(true));
|
||||
}
|
||||
else if (locallyIlluminated && !qsbLightSensor._locallyIlluminated)
|
||||
else if (illuminated && !__instance._illuminated)
|
||||
{
|
||||
qsbLightSensor._locallyIlluminated = false;
|
||||
qsbLightSensor.OnDetectLocalDarkness?.Invoke();
|
||||
__instance.OnDetectDarkness.Invoke();
|
||||
qsbLightSensor.SendMessage(new SetIlluminatedMessage(false));
|
||||
}
|
||||
}
|
||||
|
||||
if (__instance._illuminatingDreamLanternList != null
|
||||
&& !__instance._illuminatingDreamLanternList.SequenceEqual(_prevIlluminatingDreamLanternList))
|
||||
{
|
||||
if (isPlayerLightSensor)
|
||||
{
|
||||
new PlayerIlluminatingLanternsMessage(qsbPlayerLightSensor.PlayerId, __instance._illuminatingDreamLanternList).Send();
|
||||
}
|
||||
else
|
||||
if (__instance._illuminatingDreamLanternList != null &&
|
||||
!__instance._illuminatingDreamLanternList.SequenceEqual(_illuminatingDreamLanternList))
|
||||
{
|
||||
qsbLightSensor.SendMessage(new IlluminatingLanternsMessage(__instance._illuminatingDreamLanternList));
|
||||
}
|
||||
}
|
||||
if (!locallyIlluminated && qsbLightSensor._locallyIlluminated)
|
||||
{
|
||||
qsbLightSensor.OnDetectLocalLight?.Invoke();
|
||||
}
|
||||
else if (locallyIlluminated && !qsbLightSensor._locallyIlluminated)
|
||||
{
|
||||
qsbLightSensor.OnDetectLocalDarkness?.Invoke();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(nameof(SingleLightSensor.UpdateIllumination))]
|
||||
private static bool UpdateIllumination(SingleLightSensor __instance)
|
||||
{
|
||||
if (LightSensorManager.IsPlayerLightSensor(__instance))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (!QSBWorldSync.AllObjectsReady)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
var qsbLightSensor = __instance.GetWorldObject<QSBLightSensor>();
|
||||
|
||||
__instance._illuminated = false;
|
||||
qsbLightSensor._locallyIlluminated = false;
|
||||
__instance._illuminatingDreamLanternList?.Clear();
|
||||
if (__instance._lightSources == null || __instance._lightSources.Count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
var sensorWorldPos = __instance.transform.TransformPoint(__instance._localSensorOffset);
|
||||
var sensorWorldDir = Vector3.zero;
|
||||
if (__instance._directionalSensor)
|
||||
{
|
||||
sensorWorldDir = __instance.transform.TransformDirection(__instance._localDirection).normalized;
|
||||
}
|
||||
foreach (var lightSource in __instance._lightSources)
|
||||
{
|
||||
if ((__instance._lightSourceMask & lightSource.GetLightSourceType()) == lightSource.GetLightSourceType() &&
|
||||
lightSource.CheckIlluminationAtPoint(sensorWorldPos, __instance._sensorRadius, __instance._maxDistance))
|
||||
{
|
||||
switch (lightSource.GetLightSourceType())
|
||||
{
|
||||
case LightSourceType.UNDEFINED:
|
||||
{
|
||||
var owlight = lightSource as OWLight2;
|
||||
var occludableLight = owlight.GetLight().shadows != LightShadows.None &&
|
||||
owlight.GetLight().shadowStrength > 0.5f;
|
||||
if (owlight.CheckIlluminationAtPoint(sensorWorldPos, __instance._sensorRadius, __instance._maxDistance) &&
|
||||
!__instance.CheckOcclusion(owlight.transform.position, sensorWorldPos, sensorWorldDir, occludableLight))
|
||||
{
|
||||
__instance._illuminated = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case LightSourceType.FLASHLIGHT:
|
||||
{
|
||||
if (lightSource is QSBFlashlight qsbFlashlight)
|
||||
{
|
||||
var position = qsbFlashlight.Player.Camera.transform.position;
|
||||
var vector3 = __instance.transform.position - position;
|
||||
if (Vector3.Angle(qsbFlashlight.Player.Camera.transform.forward, vector3) <= __instance._maxSpotHalfAngle &&
|
||||
!__instance.CheckOcclusion(position, sensorWorldPos, sensorWorldDir))
|
||||
{
|
||||
__instance._illuminated = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var position = Locator.GetPlayerCamera().transform.position;
|
||||
var vector3 = __instance.transform.position - position;
|
||||
if (Vector3.Angle(Locator.GetPlayerCamera().transform.forward, vector3) <= __instance._maxSpotHalfAngle &&
|
||||
!__instance.CheckOcclusion(position, sensorWorldPos, sensorWorldDir))
|
||||
{
|
||||
__instance._illuminated = true;
|
||||
qsbLightSensor._locallyIlluminated = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case LightSourceType.PROBE:
|
||||
{
|
||||
if (lightSource is QSBProbe qsbProbe)
|
||||
{
|
||||
var probe = qsbProbe;
|
||||
if (probe != null &&
|
||||
probe.IsLaunched() &&
|
||||
!probe.IsRetrieving() &&
|
||||
probe.CheckIlluminationAtPoint(sensorWorldPos, __instance._sensorRadius, __instance._maxDistance) &&
|
||||
!__instance.CheckOcclusion(probe.GetLightSourcePosition(), sensorWorldPos, sensorWorldDir))
|
||||
{
|
||||
__instance._illuminated = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var probe = Locator.GetProbe();
|
||||
if (probe != null &&
|
||||
probe.IsLaunched() &&
|
||||
!probe.IsRetrieving() &&
|
||||
probe.CheckIlluminationAtPoint(sensorWorldPos, __instance._sensorRadius, __instance._maxDistance) &&
|
||||
!__instance.CheckOcclusion(probe.GetLightSourcePosition(), sensorWorldPos, sensorWorldDir))
|
||||
{
|
||||
__instance._illuminated = true;
|
||||
qsbLightSensor._locallyIlluminated = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case LightSourceType.DREAM_LANTERN:
|
||||
{
|
||||
var dreamLanternController = lightSource as DreamLanternController;
|
||||
if (dreamLanternController.IsLit() &&
|
||||
dreamLanternController.IsFocused(__instance._lanternFocusThreshold) &&
|
||||
dreamLanternController.CheckIlluminationAtPoint(sensorWorldPos, __instance._sensorRadius, __instance._maxDistance) &&
|
||||
!__instance.CheckOcclusion(dreamLanternController.GetLightPosition(), sensorWorldPos, sensorWorldDir))
|
||||
{
|
||||
__instance._illuminatingDreamLanternList.Add(dreamLanternController);
|
||||
__instance._illuminated = true;
|
||||
|
||||
var dreamLanternItem = dreamLanternController.GetComponent<DreamLanternItem>();
|
||||
qsbLightSensor._locallyIlluminated |= QSBPlayerManager.LocalPlayer.HeldItem?.AttachedObject == dreamLanternItem;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case LightSourceType.SIMPLE_LANTERN:
|
||||
foreach (var owlight in lightSource.GetLights())
|
||||
{
|
||||
var occludableLight = owlight.GetLight().shadows != LightShadows.None &&
|
||||
owlight.GetLight().shadowStrength > 0.5f;
|
||||
var maxDistance = Mathf.Min(__instance._maxSimpleLanternDistance, __instance._maxDistance);
|
||||
if (owlight.CheckIlluminationAtPoint(sensorWorldPos, __instance._sensorRadius, maxDistance) &&
|
||||
!__instance.CheckOcclusion(owlight.transform.position, sensorWorldPos, sensorWorldDir, occludableLight))
|
||||
{
|
||||
__instance._illuminated = true;
|
||||
|
||||
var simpleLanternItem = (SimpleLanternItem)lightSource;
|
||||
qsbLightSensor._locallyIlluminated |= QSBPlayerManager.LocalPlayer.HeldItem?.AttachedObject == simpleLanternItem;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case LightSourceType.VOLUME_ONLY:
|
||||
__instance._illuminated = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,197 @@
|
||||
using HarmonyLib;
|
||||
using QSB.EchoesOfTheEye.LightSensorSync.Messages;
|
||||
using QSB.Messaging;
|
||||
using QSB.Patches;
|
||||
using QSB.Player;
|
||||
using QSB.Tools.FlashlightTool;
|
||||
using QSB.Tools.ProbeTool;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
/*
|
||||
* For those who come here,
|
||||
* leave while you still can.
|
||||
*/
|
||||
|
||||
namespace QSB.EchoesOfTheEye.LightSensorSync.Patches;
|
||||
|
||||
/// <summary>
|
||||
/// remote player light sensors are disabled, so these will only run for the local player light sensor
|
||||
/// </summary>
|
||||
[HarmonyPatch(typeof(SingleLightSensor))]
|
||||
internal class PlayerLightSensorPatches : QSBPatch
|
||||
{
|
||||
public override QSBPatchTypes Type => QSBPatchTypes.OnClientConnect;
|
||||
|
||||
/// <summary>
|
||||
/// to prevent allocating a new list every frame
|
||||
/// </summary>
|
||||
private static readonly List<DreamLanternController> _illuminatingDreamLanternList = new();
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(nameof(SingleLightSensor.ManagedFixedUpdate))]
|
||||
private static bool ManagedFixedUpdate(SingleLightSensor __instance)
|
||||
{
|
||||
if (!LightSensorManager.IsPlayerLightSensor(__instance))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (__instance._fixedUpdateFrameDelayCount > 0)
|
||||
{
|
||||
__instance._fixedUpdateFrameDelayCount--;
|
||||
return false;
|
||||
}
|
||||
var illuminated = __instance._illuminated;
|
||||
if (__instance._illuminatingDreamLanternList != null)
|
||||
{
|
||||
_illuminatingDreamLanternList.Clear();
|
||||
_illuminatingDreamLanternList.AddRange(__instance._illuminatingDreamLanternList);
|
||||
}
|
||||
__instance.UpdateIllumination();
|
||||
if (!illuminated && __instance._illuminated)
|
||||
{
|
||||
__instance.OnDetectLight.Invoke();
|
||||
new PlayerSetIlluminatedMessage(QSBPlayerManager.LocalPlayerId, true).Send();
|
||||
}
|
||||
else if (illuminated && !__instance._illuminated)
|
||||
{
|
||||
__instance.OnDetectDarkness.Invoke();
|
||||
new PlayerSetIlluminatedMessage(QSBPlayerManager.LocalPlayerId, false).Send();
|
||||
}
|
||||
if (__instance._illuminatingDreamLanternList != null &&
|
||||
!__instance._illuminatingDreamLanternList.SequenceEqual(_illuminatingDreamLanternList))
|
||||
{
|
||||
new PlayerIlluminatingLanternsMessage(QSBPlayerManager.LocalPlayerId, __instance._illuminatingDreamLanternList).Send();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(nameof(SingleLightSensor.UpdateIllumination))]
|
||||
private static bool UpdateIllumination(SingleLightSensor __instance)
|
||||
{
|
||||
if (!LightSensorManager.IsPlayerLightSensor(__instance))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
__instance._illuminated = false;
|
||||
__instance._illuminatingDreamLanternList?.Clear();
|
||||
if (__instance._lightSources == null || __instance._lightSources.Count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
var sensorWorldPos = __instance.transform.TransformPoint(__instance._localSensorOffset);
|
||||
var sensorWorldDir = Vector3.zero;
|
||||
if (__instance._directionalSensor)
|
||||
{
|
||||
sensorWorldDir = __instance.transform.TransformDirection(__instance._localDirection).normalized;
|
||||
}
|
||||
foreach (var lightSource in __instance._lightSources)
|
||||
{
|
||||
if ((__instance._lightSourceMask & lightSource.GetLightSourceType()) == lightSource.GetLightSourceType() &&
|
||||
lightSource.CheckIlluminationAtPoint(sensorWorldPos, __instance._sensorRadius, __instance._maxDistance))
|
||||
{
|
||||
switch (lightSource.GetLightSourceType())
|
||||
{
|
||||
case LightSourceType.UNDEFINED:
|
||||
{
|
||||
var owlight = lightSource as OWLight2;
|
||||
var occludableLight = owlight.GetLight().shadows != LightShadows.None &&
|
||||
owlight.GetLight().shadowStrength > 0.5f;
|
||||
if (owlight.CheckIlluminationAtPoint(sensorWorldPos, __instance._sensorRadius, __instance._maxDistance) &&
|
||||
!__instance.CheckOcclusion(owlight.transform.position, sensorWorldPos, sensorWorldDir, occludableLight))
|
||||
{
|
||||
__instance._illuminated = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case LightSourceType.FLASHLIGHT:
|
||||
{
|
||||
if (lightSource is QSBFlashlight qsbFlashlight)
|
||||
{
|
||||
var position = qsbFlashlight.Player.Camera.transform.position;
|
||||
var vector3 = __instance.transform.position - position;
|
||||
if (Vector3.Angle(qsbFlashlight.Player.Camera.transform.forward, vector3) <= __instance._maxSpotHalfAngle &&
|
||||
!__instance.CheckOcclusion(position, sensorWorldPos, sensorWorldDir))
|
||||
{
|
||||
__instance._illuminated = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var position = Locator.GetPlayerCamera().transform.position;
|
||||
var vector3 = __instance.transform.position - position;
|
||||
if (Vector3.Angle(Locator.GetPlayerCamera().transform.forward, vector3) <= __instance._maxSpotHalfAngle &&
|
||||
!__instance.CheckOcclusion(position, sensorWorldPos, sensorWorldDir))
|
||||
{
|
||||
__instance._illuminated = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case LightSourceType.PROBE:
|
||||
{
|
||||
if (lightSource is QSBProbe qsbProbe)
|
||||
{
|
||||
var probe = qsbProbe;
|
||||
if (probe != null &&
|
||||
probe.IsLaunched() &&
|
||||
!probe.IsRetrieving() &&
|
||||
probe.CheckIlluminationAtPoint(sensorWorldPos, __instance._sensorRadius, __instance._maxDistance) &&
|
||||
!__instance.CheckOcclusion(probe.GetLightSourcePosition(), sensorWorldPos, sensorWorldDir))
|
||||
{
|
||||
__instance._illuminated = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var probe = Locator.GetProbe();
|
||||
if (probe != null &&
|
||||
probe.IsLaunched() &&
|
||||
!probe.IsRetrieving() &&
|
||||
probe.CheckIlluminationAtPoint(sensorWorldPos, __instance._sensorRadius, __instance._maxDistance) &&
|
||||
!__instance.CheckOcclusion(probe.GetLightSourcePosition(), sensorWorldPos, sensorWorldDir))
|
||||
{
|
||||
__instance._illuminated = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case LightSourceType.DREAM_LANTERN:
|
||||
{
|
||||
var dreamLanternController = lightSource as DreamLanternController;
|
||||
if (dreamLanternController.IsLit() &&
|
||||
dreamLanternController.IsFocused(__instance._lanternFocusThreshold) &&
|
||||
dreamLanternController.CheckIlluminationAtPoint(sensorWorldPos, __instance._sensorRadius, __instance._maxDistance) &&
|
||||
!__instance.CheckOcclusion(dreamLanternController.GetLightPosition(), sensorWorldPos, sensorWorldDir))
|
||||
{
|
||||
__instance._illuminatingDreamLanternList.Add(dreamLanternController);
|
||||
__instance._illuminated = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case LightSourceType.SIMPLE_LANTERN:
|
||||
foreach (var owlight in lightSource.GetLights())
|
||||
{
|
||||
var occludableLight = owlight.GetLight().shadows != LightShadows.None &&
|
||||
owlight.GetLight().shadowStrength > 0.5f;
|
||||
var maxDistance = Mathf.Min(__instance._maxSimpleLanternDistance, __instance._maxDistance);
|
||||
if (owlight.CheckIlluminationAtPoint(sensorWorldPos, __instance._sensorRadius, maxDistance) &&
|
||||
!__instance.CheckOcclusion(owlight.transform.position, sensorWorldPos, sensorWorldDir, occludableLight))
|
||||
{
|
||||
__instance._illuminated = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case LightSourceType.VOLUME_ONLY:
|
||||
__instance._illuminated = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
@ -2,74 +2,49 @@
|
||||
using QSB.Messaging;
|
||||
using QSB.Player;
|
||||
using QSB.WorldSync;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
/*
|
||||
* For those who come here,
|
||||
* leave while you still can.
|
||||
*/
|
||||
|
||||
namespace QSB.EchoesOfTheEye.LightSensorSync;
|
||||
|
||||
/// <summary>
|
||||
/// stores a bit of extra data needed for player light sensor sync
|
||||
/// todo you might be able to remove when you simplify light sensor after the fake sector thingy
|
||||
/// only purpose is to handle initial state sync.
|
||||
///
|
||||
/// we don't have to worry about start illuminated or sectors.
|
||||
/// authority is always given to local player light sensor.
|
||||
///
|
||||
/// 2 uses:
|
||||
/// - AlarmTotem.CheckPlayerVisible
|
||||
/// - GhostSensors.FixedUpdate_Sensors
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(SingleLightSensor))]
|
||||
public class QSBPlayerLightSensor : MonoBehaviour
|
||||
{
|
||||
private SingleLightSensor _lightSensor;
|
||||
[NonSerialized]
|
||||
public uint PlayerId;
|
||||
|
||||
internal bool _locallyIlluminated;
|
||||
internal readonly List<uint> _illuminatedBy = new();
|
||||
private uint _playerId;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_lightSensor = GetComponent<SingleLightSensor>();
|
||||
PlayerId = QSBPlayerManager.PlayerList.First(x => x.LightSensor == _lightSensor).PlayerId;
|
||||
_playerId = QSBPlayerManager.PlayerList.First(x => x.LightSensor == _lightSensor).PlayerId;
|
||||
|
||||
RequestInitialStatesMessage.SendInitialState += SendInitialState;
|
||||
QSBPlayerManager.OnRemovePlayer += OnPlayerLeave;
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
private void OnDestroy() =>
|
||||
RequestInitialStatesMessage.SendInitialState -= SendInitialState;
|
||||
QSBPlayerManager.OnRemovePlayer -= OnPlayerLeave;
|
||||
}
|
||||
|
||||
private void SendInitialState(uint to)
|
||||
{
|
||||
new PlayerIlluminatedByMessage(PlayerId, _illuminatedBy.ToArray()) { To = to }.Send();
|
||||
new PlayerSetIlluminatedMessage(_playerId, _lightSensor._illuminated) { To = to }.Send();
|
||||
if (_lightSensor._illuminatingDreamLanternList != null)
|
||||
{
|
||||
new PlayerIlluminatingLanternsMessage(PlayerId, _lightSensor._illuminatingDreamLanternList) { To = to }.Send();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPlayerLeave(PlayerInfo player) => SetIlluminated(player.PlayerId, false);
|
||||
|
||||
public void SetIlluminated(uint playerId, bool locallyIlluminated)
|
||||
{
|
||||
var illuminated = _illuminatedBy.Count > 0;
|
||||
if (locallyIlluminated)
|
||||
{
|
||||
_illuminatedBy.SafeAdd(playerId);
|
||||
}
|
||||
else
|
||||
{
|
||||
_illuminatedBy.QuickRemove(playerId);
|
||||
}
|
||||
|
||||
if (!illuminated && _illuminatedBy.Count > 0)
|
||||
{
|
||||
_lightSensor._illuminated = true;
|
||||
_lightSensor.OnDetectLight.Invoke();
|
||||
}
|
||||
else if (illuminated && _illuminatedBy.Count == 0)
|
||||
{
|
||||
_lightSensor._illuminated = false;
|
||||
_lightSensor.OnDetectDarkness.Invoke();
|
||||
new PlayerIlluminatingLanternsMessage(_playerId, _lightSensor._illuminatingDreamLanternList) { To = to }.Send();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,60 +1,60 @@
|
||||
using Cysharp.Threading.Tasks;
|
||||
using QSB.AuthoritySync;
|
||||
using QSB.EchoesOfTheEye.LightSensorSync.Messages;
|
||||
using QSB.Messaging;
|
||||
using QSB.Player;
|
||||
using QSB.Utility;
|
||||
using QSB.WorldSync;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
/*
|
||||
* For those who come here,
|
||||
* leave while you still can.
|
||||
*/
|
||||
|
||||
namespace QSB.EchoesOfTheEye.LightSensorSync.WorldObjects;
|
||||
|
||||
/// <summary>
|
||||
/// todo: simplify this after we do fake sectors (we dont need to store a list, we can just do it locally and then sync if it's disabled)
|
||||
/// BUG: this breaks in zone2.
|
||||
/// the sector it's enabled in is bigger than the sector the zone2 walls are enabled in :(
|
||||
/// maybe this can be fixed by making the collision group use the same sector.
|
||||
/// </summary>
|
||||
internal class QSBLightSensor : WorldObject<SingleLightSensor>
|
||||
internal class QSBLightSensor : AuthWorldObject<SingleLightSensor>
|
||||
{
|
||||
internal bool _locallyIlluminated;
|
||||
|
||||
public Action OnDetectLocalLight;
|
||||
public Action OnDetectLocalDarkness;
|
||||
|
||||
internal readonly List<uint> _illuminatedBy = new();
|
||||
|
||||
public override bool CanOwn => AttachedObject.enabled;
|
||||
|
||||
public override void SendInitialState(uint to)
|
||||
{
|
||||
this.SendMessage(new IlluminatedByMessage(_illuminatedBy.ToArray()) { To = to });
|
||||
base.SendInitialState(to);
|
||||
|
||||
this.SendMessage(new SetIlluminatedMessage(AttachedObject._illuminated) { To = to });
|
||||
if (AttachedObject._illuminatingDreamLanternList != null)
|
||||
{
|
||||
this.SendMessage(new IlluminatingLanternsMessage(AttachedObject._illuminatingDreamLanternList) { To = to });
|
||||
}
|
||||
}
|
||||
|
||||
public override async UniTask Init(CancellationToken ct) => QSBPlayerManager.OnRemovePlayer += OnPlayerLeave;
|
||||
public override void OnRemoval() => QSBPlayerManager.OnRemovePlayer -= OnPlayerLeave;
|
||||
private void OnPlayerLeave(PlayerInfo player) => SetIlluminated(player.PlayerId, false);
|
||||
|
||||
public void SetIlluminated(uint playerId, bool locallyIlluminated)
|
||||
public override async UniTask Init(CancellationToken ct)
|
||||
{
|
||||
var illuminated = _illuminatedBy.Count > 0;
|
||||
if (locallyIlluminated)
|
||||
{
|
||||
_illuminatedBy.SafeAdd(playerId);
|
||||
}
|
||||
else
|
||||
{
|
||||
_illuminatedBy.QuickRemove(playerId);
|
||||
}
|
||||
await base.Init(ct);
|
||||
|
||||
if (!illuminated && _illuminatedBy.Count > 0)
|
||||
// do this stuff here instead of Start, since world objects won't be ready by that point
|
||||
Delay.RunWhen(() => QSBWorldSync.AllObjectsReady, () =>
|
||||
{
|
||||
AttachedObject._illuminated = true;
|
||||
AttachedObject.OnDetectLight.Invoke();
|
||||
}
|
||||
else if (illuminated && _illuminatedBy.Count == 0)
|
||||
{
|
||||
AttachedObject._illuminated = false;
|
||||
AttachedObject.OnDetectDarkness.Invoke();
|
||||
}
|
||||
if (AttachedObject._sector != null)
|
||||
{
|
||||
if (AttachedObject._startIlluminated)
|
||||
{
|
||||
_locallyIlluminated = true;
|
||||
OnDetectLocalLight?.Invoke();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,10 @@
|
||||
using QSB.EchoesOfTheEye.Prisoner.WorldObjects;
|
||||
using QSB.Messaging;
|
||||
using QSB.Patches;
|
||||
|
||||
namespace QSB.EchoesOfTheEye.Prisoner.Messages;
|
||||
|
||||
internal class CellevatorCallMessage : QSBWorldObjectMessage<QSBPrisonCellElevator, int>
|
||||
{
|
||||
public CellevatorCallMessage(int floorIndex) : base(floorIndex) { }
|
||||
|
||||
public override void OnReceiveRemote() =>
|
||||
QSBPatch.RemoteCall(() => WorldObject.AttachedObject.CallElevatorToFloor(Data));
|
||||
public override void OnReceiveRemote() => WorldObject.AttachedObject.CallElevatorToFloor(Data);
|
||||
}
|
||||
|
@ -13,25 +13,26 @@ public class CellevatorPatches : QSBPatch
|
||||
public override QSBPatchTypes Type => QSBPatchTypes.OnClientConnect;
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(nameof(PrisonCellElevator.CallElevatorToFloor))]
|
||||
public static void CallElevatorToFloor(PrisonCellElevator __instance, int floorIndex)
|
||||
[HarmonyPatch(nameof(PrisonCellElevator.CallToTopFloor))]
|
||||
public static void CallToTopFloor(PrisonCellElevator __instance)
|
||||
{
|
||||
if (Remote)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (__instance._targetFloorIndex == floorIndex)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!QSBWorldSync.AllObjectsReady)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
__instance.GetWorldObject<QSBPrisonCellElevator>()
|
||||
.SendMessage(new CellevatorCallMessage(floorIndex));
|
||||
.SendMessage(new CellevatorCallMessage(1));
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(nameof(PrisonCellElevator.CallToBottomFloor))]
|
||||
public static void CallToBottomFloor(PrisonCellElevator __instance)
|
||||
{
|
||||
if (!QSBWorldSync.AllObjectsReady)
|
||||
{
|
||||
return;
|
||||
}
|
||||
__instance.GetWorldObject<QSBPrisonCellElevator>()
|
||||
.SendMessage(new CellevatorCallMessage(0));
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ internal class QSBPrisonerBrain : WorldObject<PrisonerBrain>, IGhostObject
|
||||
{
|
||||
public override void SendInitialState(uint to)
|
||||
{
|
||||
|
||||
// todo SendInitialState
|
||||
}
|
||||
|
||||
public override async UniTask Init(CancellationToken ct)
|
||||
|
@ -5,7 +5,5 @@ namespace QSB.EchoesOfTheEye.Prisoner.WorldObjects;
|
||||
|
||||
internal class QSBPrisonerMarker : WorldObject<PrisonerBehaviourCueMarker>
|
||||
{
|
||||
public override void SendInitialState(uint to) { }
|
||||
|
||||
public Transform Transform => AttachedObject.transform;
|
||||
}
|
||||
|
@ -15,8 +15,6 @@ internal abstract class QSBRotatingElements<T, U> : LinkedWorldObject<T, U>
|
||||
where T : MonoBehaviour
|
||||
where U : NetworkBehaviour
|
||||
{
|
||||
public override void SendInitialState(uint to) { }
|
||||
|
||||
protected abstract IEnumerable<SingleLightSensor> LightSensors { get; }
|
||||
private QSBLightSensor[] _qsbLightSensors;
|
||||
private int _litSensors;
|
||||
|
@ -75,32 +75,30 @@ public class RaftTransformSync : UnsectoredRigidbodySync, ILinkedNetworkBehaviou
|
||||
protected override void ApplyToAttached()
|
||||
{
|
||||
var targetPos = ReferenceTransform.FromRelPos(transform.position);
|
||||
var targetRot = ReferenceTransform.FromRelRot(transform.rotation);
|
||||
|
||||
if (Time.unscaledTime >= _lastSetPositionTime + ForcePositionAfterTime)
|
||||
var onRaft = Locator.GetPlayerController().GetGroundBody() == AttachedRigidbody;
|
||||
if (onRaft)
|
||||
{
|
||||
_lastSetPositionTime = Time.unscaledTime;
|
||||
|
||||
var targetRot = ReferenceTransform.FromRelRot(transform.rotation);
|
||||
|
||||
var onRaft = false;
|
||||
var localPos = Vector3.zero;
|
||||
var localRot = Quaternion.identity;
|
||||
if (Locator.GetPlayerController().GetGroundBody() == AttachedRigidbody)
|
||||
if (Time.unscaledTime >= _lastSetPositionTime + ForcePositionAfterTime)
|
||||
{
|
||||
onRaft = true;
|
||||
localPos = AttachedRigidbody.transform.InverseTransformPoint(Locator.GetPlayerTransform().position);
|
||||
localRot = AttachedRigidbody.transform.InverseTransformRotation(Locator.GetPlayerTransform().rotation);
|
||||
}
|
||||
_lastSetPositionTime = Time.unscaledTime;
|
||||
|
||||
var playerBody = Locator.GetPlayerBody();
|
||||
var relPos = AttachedTransform.ToRelPos(playerBody.GetPosition());
|
||||
var relRot = AttachedTransform.ToRelRot(playerBody.GetRotation());
|
||||
|
||||
AttachedRigidbody.SetPosition(targetPos);
|
||||
AttachedRigidbody.SetRotation(targetRot);
|
||||
|
||||
playerBody.SetPosition(AttachedTransform.FromRelPos(relPos));
|
||||
playerBody.SetRotation(AttachedTransform.FromRelRot(relRot));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AttachedRigidbody.SetPosition(targetPos);
|
||||
AttachedRigidbody.SetRotation(targetRot);
|
||||
|
||||
if (onRaft)
|
||||
{
|
||||
var playerTransform = Locator.GetPlayerBody().transform;
|
||||
playerTransform.position = AttachedRigidbody.transform.TransformPoint(localPos);
|
||||
playerTransform.rotation = AttachedRigidbody.transform.TransformRotation(localRot);
|
||||
}
|
||||
}
|
||||
|
||||
var targetVelocity = ReferenceRigidbody.FromRelVel(Velocity, targetPos);
|
||||
|
@ -52,9 +52,4 @@ public class QSBRaft : LinkedWorldObject<RaftController, RaftTransformSync>, IQS
|
||||
NetworkBehaviour.netIdentity.UpdateAuthQueue(AuthQueueAction.Force);
|
||||
}
|
||||
}
|
||||
|
||||
public override void SendInitialState(uint to)
|
||||
{
|
||||
// not really needed. things work fine without it
|
||||
}
|
||||
}
|
||||
|
@ -8,8 +8,6 @@ public class QSBRaftDock : WorldObject<RaftDock>, IQSBDropTarget
|
||||
{
|
||||
IItemDropTarget IQSBDropTarget.AttachedObject => AttachedObject;
|
||||
|
||||
public override void SendInitialState(uint to) { }
|
||||
|
||||
public void OnPressInteract() =>
|
||||
QSBPatch.RemoteCall(AttachedObject.OnPressInteract);
|
||||
}
|
||||
|
@ -29,7 +29,6 @@ public class QSBSlideProjector : WorldObject<SlideProjector>
|
||||
/// </summary>
|
||||
public void SetUser(uint user)
|
||||
{
|
||||
DebugLog.DebugWrite($"{this} - user = {user}");
|
||||
AttachedObject._interactReceiver.SetInteractionEnabled(user == 0 || user == _user);
|
||||
_user = user;
|
||||
}
|
||||
|
@ -1,16 +1,10 @@
|
||||
using QSB.EyeOfTheUniverse.MaskSync;
|
||||
using QSB.WorldSync;
|
||||
using System.Linq;
|
||||
|
||||
namespace QSB.EyeOfTheUniverse.InstrumentSync.WorldObjects;
|
||||
|
||||
internal class QSBQuantumInstrument : WorldObject<QuantumInstrument>
|
||||
{
|
||||
public override void SendInitialState(uint to)
|
||||
{
|
||||
// not needed since mid-game join is impossible here
|
||||
}
|
||||
|
||||
public void Gather()
|
||||
{
|
||||
var maskZoneController = QSBWorldSync.GetUnityObject<MaskZoneController>();
|
||||
@ -29,4 +23,4 @@ internal class QSBQuantumInstrument : WorldObject<QuantumInstrument>
|
||||
|
||||
AttachedObject.Gather();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,35 +7,30 @@ namespace QSB.EyeOfTheUniverse.Tomb;
|
||||
|
||||
internal class EyeTombWatcher : MonoBehaviour
|
||||
{
|
||||
private EyeTombController tomb;
|
||||
private bool _observedGrave;
|
||||
private EyeTombController _tomb;
|
||||
|
||||
private void Start()
|
||||
private void Awake()
|
||||
{
|
||||
tomb = GetComponent<EyeTombController>();
|
||||
tomb._graveObserveTrigger.OnGainFocus += OnObserveGrave;
|
||||
_tomb = GetComponent<EyeTombController>();
|
||||
_tomb._graveObserveTrigger.OnGainFocus += OnObserveGrave;
|
||||
enabled = false;
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
=> tomb._graveObserveTrigger.OnGainFocus -= OnObserveGrave;
|
||||
private void OnDestroy() =>
|
||||
_tomb._graveObserveTrigger.OnGainFocus -= OnObserveGrave;
|
||||
|
||||
private void OnObserveGrave()
|
||||
{
|
||||
_observedGrave = true;
|
||||
tomb._graveObserveTrigger.OnGainFocus -= OnObserveGrave;
|
||||
_tomb._graveObserveTrigger.OnGainFocus -= OnObserveGrave;
|
||||
enabled = true;
|
||||
}
|
||||
|
||||
|
||||
private void FixedUpdate()
|
||||
{
|
||||
if (!_observedGrave)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var canShowStage = true;
|
||||
foreach (var player in QSBPlayerManager.PlayerList)
|
||||
{
|
||||
var playerToStage = tomb._stageRoot.transform.position - player.Body.transform.position;
|
||||
var playerToStage = _tomb._stageRoot.transform.position - player.Body.transform.position;
|
||||
var playerLookDirection = player.Body.transform.forward;
|
||||
var angle = Vector3.Angle(playerLookDirection, playerToStage);
|
||||
if (angle < 70)
|
||||
@ -46,9 +41,9 @@ internal class EyeTombWatcher : MonoBehaviour
|
||||
|
||||
if (canShowStage)
|
||||
{
|
||||
tomb._stageRoot.SetActive(true);
|
||||
_tomb._stageRoot.SetActive(true);
|
||||
new ShowStageMessage().Send();
|
||||
enabled = false;
|
||||
Destroy(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
using QSB.Messaging;
|
||||
using QSB.Utility;
|
||||
using QSB.WorldSync;
|
||||
using UnityEngine;
|
||||
|
||||
namespace QSB.EyeOfTheUniverse.Tomb.Messages;
|
||||
|
||||
@ -10,5 +10,6 @@ internal class ShowStageMessage : QSBMessage
|
||||
{
|
||||
var tomb = QSBWorldSync.GetUnityObject<EyeTombController>();
|
||||
tomb._stageRoot.SetActive(true);
|
||||
Object.Destroy(tomb.GetComponent<EyeTombWatcher>());
|
||||
}
|
||||
}
|
||||
|
@ -11,11 +11,8 @@ internal class TombManager : WorldObjectManager
|
||||
|
||||
public override async UniTask BuildWorldObjects(OWScene scene, CancellationToken ct)
|
||||
{
|
||||
if (QSBCore.IsHost)
|
||||
{
|
||||
// sike!! no worldobjects here
|
||||
var tomb = QSBWorldSync.GetUnityObject<EyeTombController>();
|
||||
tomb.gameObject.AddComponent<EyeTombWatcher>();
|
||||
}
|
||||
// sike!! no worldobjects here
|
||||
var tomb = QSBWorldSync.GetUnityObject<EyeTombController>();
|
||||
tomb.gameObject.AddComponent<EyeTombWatcher>();
|
||||
}
|
||||
}
|
||||
|
13
QSB/GameVendor.cs
Normal file
13
QSB/GameVendor.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using System;
|
||||
|
||||
namespace QSB;
|
||||
|
||||
[Flags]
|
||||
public enum GameVendor
|
||||
{
|
||||
None = 0,
|
||||
Epic = 1,
|
||||
Steam = 2,
|
||||
Gamepass = 4
|
||||
}
|
||||
|
41
QSB/ItemSync/ItemState.cs
Normal file
41
QSB/ItemSync/ItemState.cs
Normal file
@ -0,0 +1,41 @@
|
||||
using QSB.Player;
|
||||
using UnityEngine;
|
||||
|
||||
namespace QSB.ItemSync;
|
||||
|
||||
/// <summary>
|
||||
/// used for initial state sync.
|
||||
/// we have to store this separately because it's not saved in the item itself, unfortunately.
|
||||
/// </summary>
|
||||
public class ItemState
|
||||
{
|
||||
/// <summary>
|
||||
/// if this is false, there's no need to sync initial state for this item
|
||||
/// </summary>
|
||||
public bool HasBeenInteractedWith;
|
||||
|
||||
public ItemStateType State;
|
||||
|
||||
// on ground
|
||||
public Transform Parent;
|
||||
public Vector3 LocalPosition;
|
||||
public Vector3 WorldPosition => Parent.TransformPoint(LocalPosition);
|
||||
public Vector3 LocalNormal;
|
||||
public Vector3 WorldNormal => Parent.TransformDirection(LocalNormal);
|
||||
public Sector Sector;
|
||||
public IItemDropTarget CustomDropTarget;
|
||||
public OWRigidbody Rigidbody;
|
||||
|
||||
// held
|
||||
public PlayerInfo HoldingPlayer;
|
||||
|
||||
// socketed
|
||||
public OWItemSocket Socket;
|
||||
}
|
||||
|
||||
public enum ItemStateType
|
||||
{
|
||||
OnGround,
|
||||
Held,
|
||||
Socketed
|
||||
}
|
@ -58,6 +58,14 @@ internal class DropItemMessage : QSBWorldObjectMessage<IQSBItem,
|
||||
var sector = Data.sectorId != -1 ? Data.sectorId.GetWorldObject<QSBSector>().AttachedObject : null;
|
||||
|
||||
WorldObject.DropItem(worldPos, worldNormal, parent, sector, customDropTarget);
|
||||
WorldObject.ItemState.HasBeenInteractedWith = true;
|
||||
WorldObject.ItemState.State = ItemStateType.OnGround;
|
||||
WorldObject.ItemState.LocalPosition = Data.localPosition;
|
||||
WorldObject.ItemState.Parent = parent;
|
||||
WorldObject.ItemState.LocalNormal = Data.localNormal;
|
||||
WorldObject.ItemState.Sector = sector;
|
||||
WorldObject.ItemState.CustomDropTarget = customDropTarget;
|
||||
WorldObject.ItemState.Rigidbody = parent.GetComponent<OWRigidbody>();
|
||||
|
||||
var player = QSBPlayerManager.GetPlayer(From);
|
||||
player.HeldItem = null;
|
||||
|
@ -5,13 +5,15 @@ using QSB.Utility;
|
||||
|
||||
namespace QSB.ItemSync.Messages;
|
||||
|
||||
internal class MoveToCarryMessage : QSBWorldObjectMessage<IQSBItem>
|
||||
internal class MoveToCarryMessage : QSBWorldObjectMessage<IQSBItem, uint>
|
||||
{
|
||||
public MoveToCarryMessage(uint playerHolding) : base(playerHolding) { }
|
||||
|
||||
public override void OnReceiveRemote()
|
||||
{
|
||||
WorldObject.StoreLocation();
|
||||
|
||||
var player = QSBPlayerManager.GetPlayer(From);
|
||||
var player = QSBPlayerManager.GetPlayer(Data);
|
||||
var itemType = WorldObject.GetItemType();
|
||||
|
||||
player.HeldItem = WorldObject;
|
||||
@ -30,6 +32,9 @@ internal class MoveToCarryMessage : QSBWorldObjectMessage<IQSBItem>
|
||||
};
|
||||
|
||||
WorldObject.PickUpItem(itemSocket);
|
||||
WorldObject.ItemState.HasBeenInteractedWith = true;
|
||||
WorldObject.ItemState.State = ItemStateType.Held;
|
||||
WorldObject.ItemState.HoldingPlayer = player;
|
||||
|
||||
switch (itemType)
|
||||
{
|
||||
|
@ -27,6 +27,9 @@ internal class SocketItemMessage : QSBMessage<(SocketMessageType Type, int Socke
|
||||
var qsbItem = Data.ItemId.GetWorldObject<IQSBItem>();
|
||||
|
||||
qsbItemSocket.PlaceIntoSocket(qsbItem);
|
||||
qsbItem.ItemState.HasBeenInteractedWith = true;
|
||||
qsbItem.ItemState.State = ItemStateType.Socketed;
|
||||
qsbItem.ItemState.Socket = qsbItemSocket.AttachedObject;
|
||||
|
||||
var player = QSBPlayerManager.GetPlayer(From);
|
||||
player.HeldItem = null;
|
||||
|
@ -20,7 +20,10 @@ internal class ItemToolPatches : QSBPatch
|
||||
{
|
||||
var qsbItem = item.GetWorldObject<IQSBItem>();
|
||||
QSBPlayerManager.LocalPlayer.HeldItem = qsbItem;
|
||||
qsbItem.SendMessage(new MoveToCarryMessage());
|
||||
qsbItem.ItemState.HasBeenInteractedWith = true;
|
||||
qsbItem.ItemState.State = ItemStateType.Held;
|
||||
qsbItem.ItemState.HoldingPlayer = QSBPlayerManager.LocalPlayer;
|
||||
qsbItem.SendMessage(new MoveToCarryMessage(QSBPlayerManager.LocalPlayer.PlayerId));
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
@ -29,6 +32,9 @@ internal class ItemToolPatches : QSBPatch
|
||||
{
|
||||
var item = __instance._heldItem;
|
||||
QSBPlayerManager.LocalPlayer.HeldItem = null;
|
||||
var qsbItem = item.GetWorldObject<IQSBItem>();
|
||||
qsbItem.ItemState.State = ItemStateType.Socketed;
|
||||
qsbItem.ItemState.Socket = socket;
|
||||
new SocketItemMessage(SocketMessageType.Socket, socket, item).Send();
|
||||
}
|
||||
|
||||
@ -38,6 +44,7 @@ internal class ItemToolPatches : QSBPatch
|
||||
{
|
||||
var item = socket.GetSocketedItem();
|
||||
var qsbItem = item.GetWorldObject<IQSBItem>();
|
||||
qsbItem.ItemState.HasBeenInteractedWith = true;
|
||||
QSBPlayerManager.LocalPlayer.HeldItem = qsbItem;
|
||||
new SocketItemMessage(SocketMessageType.StartUnsocket, socket, item).Send();
|
||||
}
|
||||
@ -92,6 +99,14 @@ internal class ItemToolPatches : QSBPatch
|
||||
|
||||
qsbItem.SendMessage(new DropItemMessage(hit.point, hit.normal, parent, sector, customDropTarget, targetRigidbody));
|
||||
|
||||
qsbItem.ItemState.State = ItemStateType.OnGround;
|
||||
qsbItem.ItemState.Parent = parent;
|
||||
qsbItem.ItemState.LocalPosition = parent.InverseTransformPoint(hit.point);
|
||||
qsbItem.ItemState.LocalNormal = parent.InverseTransformDirection(hit.normal);
|
||||
qsbItem.ItemState.Sector = sector;
|
||||
qsbItem.ItemState.CustomDropTarget = customDropTarget;
|
||||
qsbItem.ItemState.Rigidbody = targetRigidbody;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,8 @@ namespace QSB.ItemSync.WorldObjects.Items;
|
||||
|
||||
public interface IQSBItem : IWorldObject
|
||||
{
|
||||
ItemState ItemState { get; }
|
||||
|
||||
ItemType GetItemType();
|
||||
void PickUpItem(Transform itemSocket);
|
||||
void DropItem(Vector3 position, Vector3 normal, Transform parent, Sector sector, IItemDropTarget customDropTarget);
|
||||
|
@ -1,5 +1,7 @@
|
||||
using Cysharp.Threading.Tasks;
|
||||
using QSB.ItemSync.Messages;
|
||||
using QSB.ItemSync.WorldObjects.Sockets;
|
||||
using QSB.Messaging;
|
||||
using QSB.Patches;
|
||||
using QSB.Player;
|
||||
using QSB.SectorSync.WorldObjects;
|
||||
@ -12,12 +14,24 @@ namespace QSB.ItemSync.WorldObjects.Items;
|
||||
public class QSBItem<T> : WorldObject<T>, IQSBItem
|
||||
where T : OWItem
|
||||
{
|
||||
public ItemState ItemState { get; } = new();
|
||||
|
||||
private Transform _lastParent;
|
||||
private Vector3 _lastPosition;
|
||||
private Quaternion _lastRotation;
|
||||
private QSBSector _lastSector;
|
||||
private QSBItemSocket _lastSocket;
|
||||
|
||||
public override string ReturnLabel()
|
||||
{
|
||||
return $"{ToString()}" +
|
||||
$"\r\nState:{ItemState.State}" +
|
||||
$"\r\nParent:{ItemState.Parent?.name}" +
|
||||
$"\r\nLocalPosition:{ItemState.LocalPosition}" +
|
||||
$"\r\nLocalNormal:{ItemState.LocalNormal}" +
|
||||
$"\r\nHoldingPlayer:{ItemState.HoldingPlayer?.PlayerId}";
|
||||
}
|
||||
|
||||
public override async UniTask Init(CancellationToken ct)
|
||||
{
|
||||
await UniTask.WaitUntil(() => QSBWorldSync.AllObjectsAdded, cancellationToken: ct);
|
||||
@ -61,6 +75,7 @@ public class QSBItem<T> : WorldObject<T>, IQSBItem
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO at some point we should probably call the proper drop item code to account for funny overrides
|
||||
AttachedObject.transform.parent = _lastParent;
|
||||
AttachedObject.transform.localPosition = _lastPosition;
|
||||
AttachedObject.transform.localRotation = _lastRotation;
|
||||
@ -72,7 +87,30 @@ public class QSBItem<T> : WorldObject<T>, IQSBItem
|
||||
|
||||
public override void SendInitialState(uint to)
|
||||
{
|
||||
// todo SendInitialState
|
||||
if (!ItemState.HasBeenInteractedWith)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch (ItemState.State)
|
||||
{
|
||||
case ItemStateType.Held:
|
||||
((IQSBItem)this).SendMessage(new MoveToCarryMessage(ItemState.HoldingPlayer.PlayerId));
|
||||
break;
|
||||
case ItemStateType.Socketed:
|
||||
new SocketItemMessage(SocketMessageType.Socket, ItemState.Socket, AttachedObject).Send();
|
||||
break;
|
||||
case ItemStateType.OnGround:
|
||||
((IQSBItem)this).SendMessage(
|
||||
new DropItemMessage(
|
||||
ItemState.WorldPosition,
|
||||
ItemState.WorldNormal,
|
||||
ItemState.Parent,
|
||||
ItemState.Sector,
|
||||
ItemState.CustomDropTarget,
|
||||
ItemState.Rigidbody));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public ItemType GetItemType() => AttachedObject.GetItemType();
|
||||
|
@ -1,3 +1,6 @@
|
||||
namespace QSB.ItemSync.WorldObjects.Items;
|
||||
|
||||
/// <summary>
|
||||
/// TODO: SYNC THIS SHIT LMAOOOOOO
|
||||
/// </summary>
|
||||
internal class QSBVisionTorchItem : QSBItem<VisionTorchItem> { }
|
@ -17,9 +17,7 @@ public class QSBOtherDropTarget : WorldObject<MonoBehaviour>, IQSBDropTarget
|
||||
{
|
||||
if (AttachedObject is not IItemDropTarget)
|
||||
{
|
||||
throw new ArgumentException("QSBDropTarget.AttachedObject is not an IItemDropTarget!");
|
||||
throw new ArgumentException("QSBOtherDropTarget.AttachedObject is not an IItemDropTarget!");
|
||||
}
|
||||
}
|
||||
|
||||
public override void SendInitialState(uint to) { }
|
||||
}
|
||||
|
@ -13,9 +13,12 @@ public class Translation
|
||||
public string ProductUserID;
|
||||
public string Connect;
|
||||
public string Cancel;
|
||||
public string HostExistingOrNewOrCopy;
|
||||
public string HostNewOrCopy;
|
||||
public string HostExistingOrNew;
|
||||
public string ExistingSave;
|
||||
public string NewSave;
|
||||
public string CopySave;
|
||||
public string DisconnectAreYouSure;
|
||||
public string Yes;
|
||||
public string No;
|
||||
@ -41,5 +44,10 @@ public class Translation
|
||||
public string TimeSyncWaitForAllToReady;
|
||||
public string TimeSyncWaitForAllToDie;
|
||||
public string GalaxyMapEveryoneNotPresent;
|
||||
public string YouAreDead;
|
||||
public string WaitingForRespawn;
|
||||
public string WaitingForAllToDie;
|
||||
public string AttachToShip;
|
||||
public string DetachFromShip;
|
||||
public Dictionary<DeathType, string[]> DeathMessages;
|
||||
}
|
||||
|
381
QSB/Menus/FourChoicePopupMenu.cs
Normal file
381
QSB/Menus/FourChoicePopupMenu.cs
Normal file
@ -0,0 +1,381 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace QSB.Menus;
|
||||
|
||||
[RequireComponent(typeof(Canvas))]
|
||||
public class FourChoicePopupMenu : Menu
|
||||
{
|
||||
public Text _labelText;
|
||||
public SubmitAction _cancelAction;
|
||||
public SubmitAction _ok1Action;
|
||||
public SubmitAction _ok2Action;
|
||||
public SubmitAction _ok3Action;
|
||||
public ButtonWithHotkeyImageElement _cancelButton;
|
||||
public ButtonWithHotkeyImageElement _confirmButton1;
|
||||
public ButtonWithHotkeyImageElement _confirmButton2;
|
||||
public ButtonWithHotkeyImageElement _confirmButton3;
|
||||
public Canvas _rootCanvas;
|
||||
|
||||
protected Canvas _popupCanvas;
|
||||
protected GameObject _blocker;
|
||||
protected bool _closeMenuOnOk = true;
|
||||
protected IInputCommands _ok1Command;
|
||||
protected IInputCommands _ok2Command;
|
||||
protected IInputCommands _ok3Command;
|
||||
protected IInputCommands _cancelCommand;
|
||||
protected bool _usingGamepad;
|
||||
|
||||
public event PopupConfirmEvent OnPopupConfirm1;
|
||||
public event PopupConfirmEvent OnPopupConfirm2;
|
||||
public event PopupConfirmEvent OnPopupConfirm3;
|
||||
public event PopupValidateEvent OnPopupValidate;
|
||||
public event PopupCancelEvent OnPopupCancel;
|
||||
|
||||
public override Selectable GetSelectOnActivate()
|
||||
{
|
||||
_usingGamepad = OWInput.UsingGamepad();
|
||||
return _usingGamepad ? null : _selectOnActivate;
|
||||
}
|
||||
|
||||
public virtual void SetUpPopup(
|
||||
string message,
|
||||
IInputCommands ok1Command,
|
||||
IInputCommands ok2Command,
|
||||
IInputCommands ok3Command,
|
||||
IInputCommands cancelCommand,
|
||||
ScreenPrompt ok1Prompt,
|
||||
ScreenPrompt ok2Prompt,
|
||||
ScreenPrompt ok3Prompt,
|
||||
ScreenPrompt cancelPrompt,
|
||||
bool closeMenuOnOk = true,
|
||||
bool setCancelButtonActive = true)
|
||||
{
|
||||
_labelText.text = message;
|
||||
SetUpPopupCommands(ok1Command, ok2Command, ok3Command, cancelCommand, ok1Prompt, ok2Prompt, ok3Prompt, cancelPrompt);
|
||||
if (!(_cancelAction != null))
|
||||
{
|
||||
Debug.LogWarning("PopupMenu.SetUpPopup Cancel button not set!");
|
||||
return;
|
||||
}
|
||||
|
||||
_cancelAction.gameObject.SetActive(setCancelButtonActive);
|
||||
if (setCancelButtonActive)
|
||||
{
|
||||
_selectOnActivate = _cancelAction.GetRequiredComponent<Selectable>();
|
||||
return;
|
||||
}
|
||||
|
||||
_selectOnActivate = _ok1Action.GetRequiredComponent<Selectable>();
|
||||
}
|
||||
|
||||
public virtual void SetUpPopupCommands(
|
||||
IInputCommands ok1Command,
|
||||
IInputCommands ok2Command,
|
||||
IInputCommands ok3Command,
|
||||
IInputCommands cancelCommand,
|
||||
ScreenPrompt ok1Prompt,
|
||||
ScreenPrompt ok2Prompt,
|
||||
ScreenPrompt ok3Prompt,
|
||||
ScreenPrompt cancelPrompt)
|
||||
{
|
||||
_ok1Command = ok1Command;
|
||||
_ok2Command = ok2Command;
|
||||
_ok3Command = ok3Command;
|
||||
_cancelCommand = cancelCommand;
|
||||
_confirmButton1.SetPrompt(ok1Prompt, InputMode.Menu);
|
||||
_confirmButton2.SetPrompt(ok2Prompt, InputMode.Menu);
|
||||
_confirmButton3.SetPrompt(ok3Prompt, InputMode.Menu);
|
||||
_cancelButton.SetPrompt(cancelPrompt, InputMode.Menu);
|
||||
}
|
||||
|
||||
public virtual void ResetPopup()
|
||||
{
|
||||
_labelText.text = "";
|
||||
_ok1Command = null;
|
||||
_ok2Command = null;
|
||||
_ok3Command = null;
|
||||
_cancelCommand = null;
|
||||
_cancelButton.SetPrompt(null, InputMode.Menu);
|
||||
_confirmButton1.SetPrompt(null, InputMode.Menu);
|
||||
_confirmButton2.SetPrompt(null, InputMode.Menu);
|
||||
_confirmButton3.SetPrompt(null, InputMode.Menu);
|
||||
_selectOnActivate = null;
|
||||
}
|
||||
|
||||
public virtual void CloseMenuOnOk(bool value)
|
||||
{
|
||||
_closeMenuOnOk = value;
|
||||
}
|
||||
|
||||
public virtual bool EventsHaveListeners()
|
||||
{
|
||||
return OnPopupCancel != null
|
||||
|| OnPopupConfirm1 != null
|
||||
|| OnPopupConfirm2 != null
|
||||
|| OnPopupConfirm3 != null;
|
||||
}
|
||||
|
||||
public override void InitializeMenu()
|
||||
{
|
||||
base.InitializeMenu();
|
||||
if (_cancelAction != null)
|
||||
{
|
||||
_cancelAction.OnSubmitAction += InvokeCancel;
|
||||
}
|
||||
|
||||
_ok1Action.OnSubmitAction += InvokeOk1;
|
||||
_ok2Action.OnSubmitAction += InvokeOk2;
|
||||
_ok3Action.OnSubmitAction += InvokeOk3;
|
||||
_popupCanvas = gameObject.GetAddComponent<Canvas>();
|
||||
_popupCanvas.overrideSorting = true;
|
||||
_popupCanvas.sortingOrder = 30000;
|
||||
gameObject.GetAddComponent<GraphicRaycaster>();
|
||||
gameObject.GetAddComponent<CanvasGroup>();
|
||||
}
|
||||
|
||||
protected virtual void Update()
|
||||
{
|
||||
if (_cancelCommand != null && OWInput.IsNewlyPressed(_cancelCommand, InputMode.All))
|
||||
{
|
||||
InvokeCancel();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_ok1Command != null && OWInput.IsNewlyPressed(_ok1Command, InputMode.All))
|
||||
{
|
||||
InvokeOk1();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_ok2Command != null && OWInput.IsNewlyPressed(_ok2Command, InputMode.All))
|
||||
{
|
||||
InvokeOk2();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_ok3Command != null && OWInput.IsNewlyPressed(_ok3Command, InputMode.All))
|
||||
{
|
||||
InvokeOk3();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_usingGamepad != OWInput.UsingGamepad())
|
||||
{
|
||||
_usingGamepad = OWInput.UsingGamepad();
|
||||
if (_usingGamepad)
|
||||
{
|
||||
Locator.GetMenuInputModule().SelectOnNextUpdate(null);
|
||||
return;
|
||||
}
|
||||
|
||||
Locator.GetMenuInputModule().SelectOnNextUpdate(_selectOnActivate);
|
||||
}
|
||||
}
|
||||
|
||||
public override void EnableMenu(bool value)
|
||||
{
|
||||
if (value == _enabledMenu)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_enabledMenu = value;
|
||||
if (_enabledMenu && !_initialized)
|
||||
{
|
||||
InitializeMenu();
|
||||
}
|
||||
|
||||
if (!_addToMenuStackManager)
|
||||
{
|
||||
if (_enabledMenu)
|
||||
{
|
||||
Activate();
|
||||
if (_selectOnActivate != null)
|
||||
{
|
||||
var component = _selectOnActivate.GetComponent<SelectableAudioPlayer>();
|
||||
if (component != null)
|
||||
{
|
||||
component.SilenceNextSelectEvent();
|
||||
}
|
||||
|
||||
Locator.GetMenuInputModule().SelectOnNextUpdate(_selectOnActivate);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Deactivate(false);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (_enabledMenu)
|
||||
{
|
||||
MenuStackManager.SharedInstance.Push(this, true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (MenuStackManager.SharedInstance.Peek() == this)
|
||||
{
|
||||
MenuStackManager.SharedInstance.Pop(false);
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.LogError("Cannot disable Menu unless it is on the top the MenuLayerManager stack. Current menu on top: " + MenuStackManager.SharedInstance.Peek().name);
|
||||
}
|
||||
|
||||
public override void Activate()
|
||||
{
|
||||
base.Activate();
|
||||
if (_rootCanvas != null)
|
||||
{
|
||||
_blocker = CreateBlocker(_rootCanvas);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Deactivate(bool keepPreviousMenuVisible = false)
|
||||
{
|
||||
if (_rootCanvas != null)
|
||||
{
|
||||
DestroyBlocker(_blocker);
|
||||
}
|
||||
|
||||
var component = _cancelAction.GetComponent<UIStyleApplier>();
|
||||
if (component != null)
|
||||
{
|
||||
component.ChangeState(UIElementState.NORMAL, true);
|
||||
}
|
||||
|
||||
component = _ok1Action.GetComponent<UIStyleApplier>();
|
||||
if (component != null)
|
||||
{
|
||||
component.ChangeState(UIElementState.NORMAL, true);
|
||||
}
|
||||
|
||||
component = _ok2Action.GetComponent<UIStyleApplier>();
|
||||
if (component != null)
|
||||
{
|
||||
component.ChangeState(UIElementState.NORMAL, true);
|
||||
}
|
||||
|
||||
component = _ok3Action.GetComponent<UIStyleApplier>();
|
||||
if (component != null)
|
||||
{
|
||||
component.ChangeState(UIElementState.NORMAL, true);
|
||||
}
|
||||
|
||||
base.Deactivate(keepPreviousMenuVisible);
|
||||
}
|
||||
|
||||
public override void OnCancelEvent(GameObject selectedObj, BaseEventData eventData)
|
||||
{
|
||||
base.OnCancelEvent(selectedObj, eventData);
|
||||
OnPopupCancel?.Invoke();
|
||||
}
|
||||
|
||||
protected virtual void InvokeCancel()
|
||||
{
|
||||
EnableMenu(false);
|
||||
OnPopupCancel?.Invoke();
|
||||
}
|
||||
|
||||
protected virtual bool Validate()
|
||||
{
|
||||
var flag = true;
|
||||
if (OnPopupValidate != null)
|
||||
{
|
||||
var invocationList = OnPopupValidate.GetInvocationList();
|
||||
for (var i = 0; i < invocationList.Length; i++)
|
||||
{
|
||||
var flag2 = (bool)invocationList[i].DynamicInvoke(Array.Empty<object>());
|
||||
flag = flag && flag2;
|
||||
}
|
||||
}
|
||||
|
||||
return flag;
|
||||
}
|
||||
|
||||
protected virtual void InvokeOk1()
|
||||
{
|
||||
if (!Validate())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_closeMenuOnOk)
|
||||
{
|
||||
EnableMenu(false);
|
||||
}
|
||||
|
||||
OnPopupConfirm1?.Invoke();
|
||||
}
|
||||
|
||||
protected virtual void InvokeOk2()
|
||||
{
|
||||
if (!Validate())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_closeMenuOnOk)
|
||||
{
|
||||
EnableMenu(false);
|
||||
}
|
||||
|
||||
OnPopupConfirm2?.Invoke();
|
||||
}
|
||||
|
||||
protected virtual void InvokeOk3()
|
||||
{
|
||||
if (!Validate())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_closeMenuOnOk)
|
||||
{
|
||||
EnableMenu(false);
|
||||
}
|
||||
|
||||
OnPopupConfirm3?.Invoke();
|
||||
}
|
||||
|
||||
protected virtual GameObject CreateBlocker(Canvas rootCanvas)
|
||||
{
|
||||
var gameObject = new GameObject("Blocker");
|
||||
var rectTransform = gameObject.AddComponent<RectTransform>();
|
||||
rectTransform.SetParent(rootCanvas.transform, false);
|
||||
rectTransform.anchorMin = Vector3.zero;
|
||||
rectTransform.anchorMax = Vector3.one;
|
||||
rectTransform.sizeDelta = Vector2.zero;
|
||||
var canvas = gameObject.AddComponent<Canvas>();
|
||||
canvas.overrideSorting = true;
|
||||
canvas.sortingLayerID = _popupCanvas.sortingLayerID;
|
||||
canvas.sortingOrder = _popupCanvas.sortingOrder - 1;
|
||||
gameObject.AddComponent<GraphicRaycaster>();
|
||||
var image = gameObject.AddComponent<Image>();
|
||||
if (Locator.GetUIStyleManager() != null)
|
||||
{
|
||||
image.color = Locator.GetUIStyleManager().GetPopupBlockerColor();
|
||||
return gameObject;
|
||||
}
|
||||
|
||||
image.color = Color.clear;
|
||||
return gameObject;
|
||||
}
|
||||
|
||||
protected virtual void DestroyBlocker(GameObject blocker)
|
||||
{
|
||||
Destroy(blocker);
|
||||
}
|
||||
|
||||
public delegate void PopupConfirmEvent();
|
||||
|
||||
public delegate bool PopupValidateEvent();
|
||||
|
||||
public delegate void PopupCancelEvent();
|
||||
}
|
@ -3,6 +3,7 @@ using Mirror;
|
||||
using QSB.Localization;
|
||||
using QSB.Messaging;
|
||||
using QSB.Player.TransformSync;
|
||||
using QSB.SaveSync;
|
||||
using QSB.SaveSync.Messages;
|
||||
using QSB.Utility;
|
||||
using QSB.WorldSync;
|
||||
@ -33,27 +34,32 @@ internal class MenuManager : MonoBehaviour, IAddComponentOnStart
|
||||
private Button HostButton;
|
||||
private GameObject ConnectButton;
|
||||
private PopupInputMenu ConnectPopup;
|
||||
private ThreeChoicePopupMenu HostGameTypePopup;
|
||||
private FourChoicePopupMenu ExistingNewCopyPopup;
|
||||
private ThreeChoicePopupMenu NewCopyPopup;
|
||||
private ThreeChoicePopupMenu ExistingNewPopup;
|
||||
private Text _loadingText;
|
||||
private StringBuilder _nowLoadingSB;
|
||||
private const int _titleButtonIndex = 2;
|
||||
private float _connectPopupOpenTime;
|
||||
|
||||
private const string UpdateChangelog = $"QSB Version 0.20.2\r\nFixed issues with the Little Scout and attaching/detaching from the ship.";
|
||||
private const string UpdateChangelog = "QSB Version 0.21.0\r\nMultiplayer saves are now seperate from singleplayer, and items got an overhaul. A lot of bug fixes too.";
|
||||
|
||||
private Action<bool> PopupClose;
|
||||
|
||||
private bool _intentionalDisconnect;
|
||||
|
||||
private GameObject _threeChoicePopupBase;
|
||||
private GameObject _choicePopupPrefab;
|
||||
|
||||
public void Start()
|
||||
{
|
||||
Instance = this;
|
||||
|
||||
_threeChoicePopupBase = Instantiate(Resources.FindObjectsOfTypeAll<PopupMenu>().First(x => x.name == "TwoButton-Popup" && x.transform.parent.name == "PopupCanvas" && x.transform.parent.parent.name == "TitleMenu").gameObject);
|
||||
DontDestroyOnLoad(_threeChoicePopupBase);
|
||||
_threeChoicePopupBase.SetActive(false);
|
||||
if (!_choicePopupPrefab)
|
||||
{
|
||||
_choicePopupPrefab = Instantiate(Resources.FindObjectsOfTypeAll<PopupMenu>().First(x => x.name == "TwoButton-Popup" && x.transform.parent?.name == "PopupCanvas" && x.transform.parent?.parent?.name == "TitleMenu").gameObject);
|
||||
DontDestroyOnLoad(_choicePopupPrefab);
|
||||
_choicePopupPrefab.SetActive(false);
|
||||
}
|
||||
|
||||
MakeTitleMenus();
|
||||
QSBSceneManager.OnSceneLoaded += OnSceneLoaded;
|
||||
@ -119,7 +125,7 @@ internal class MenuManager : MonoBehaviour, IAddComponentOnStart
|
||||
{
|
||||
if (QSBSceneManager.CurrentScene != OWScene.TitleScreen)
|
||||
{
|
||||
DebugLog.ToConsole($"Error - Language changed while not in title screen?! Should be impossible!", OWML.Common.MessageType.Error);
|
||||
DebugLog.ToConsole("Error - Language changed while not in title screen?! Should be impossible!", OWML.Common.MessageType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -128,7 +134,25 @@ internal class MenuManager : MonoBehaviour, IAddComponentOnStart
|
||||
var text = QSBCore.DebugSettings.UseKcpTransport ? QSBLocalization.Current.PublicIPAddress : QSBLocalization.Current.ProductUserID;
|
||||
ConnectPopup.SetUpPopup(text, InputLibrary.menuConfirm, InputLibrary.cancel, new ScreenPrompt(QSBLocalization.Current.Connect), new ScreenPrompt(QSBLocalization.Current.Cancel), false);
|
||||
ConnectPopup.SetInputFieldPlaceholderText(text);
|
||||
HostGameTypePopup.SetUpPopup(QSBLocalization.Current.HostExistingOrNew,
|
||||
ExistingNewCopyPopup.SetUpPopup(QSBLocalization.Current.HostExistingOrNewOrCopy,
|
||||
InputLibrary.menuConfirm,
|
||||
InputLibrary.confirm2,
|
||||
InputLibrary.signalscope,
|
||||
InputLibrary.cancel,
|
||||
new ScreenPrompt(QSBLocalization.Current.ExistingSave),
|
||||
new ScreenPrompt(QSBLocalization.Current.NewSave),
|
||||
new ScreenPrompt(QSBLocalization.Current.CopySave),
|
||||
new ScreenPrompt(QSBLocalization.Current.Cancel));
|
||||
|
||||
NewCopyPopup.SetUpPopup(QSBLocalization.Current.HostNewOrCopy,
|
||||
InputLibrary.menuConfirm,
|
||||
InputLibrary.confirm2,
|
||||
InputLibrary.cancel,
|
||||
new ScreenPrompt(QSBLocalization.Current.NewSave),
|
||||
new ScreenPrompt(QSBLocalization.Current.CopySave),
|
||||
new ScreenPrompt(QSBLocalization.Current.Cancel));
|
||||
|
||||
ExistingNewPopup.SetUpPopup(QSBLocalization.Current.HostExistingOrNew,
|
||||
InputLibrary.menuConfirm,
|
||||
InputLibrary.confirm2,
|
||||
InputLibrary.cancel,
|
||||
@ -155,7 +179,7 @@ internal class MenuManager : MonoBehaviour, IAddComponentOnStart
|
||||
|
||||
public ThreeChoicePopupMenu CreateThreeChoicePopup(string message, string confirm1Text, string confirm2Text, string cancelText)
|
||||
{
|
||||
var newPopup = Instantiate(_threeChoicePopupBase);
|
||||
var newPopup = Instantiate(_choicePopupPrefab);
|
||||
|
||||
switch (LoadManager.GetCurrentScene())
|
||||
{
|
||||
@ -170,7 +194,7 @@ internal class MenuManager : MonoBehaviour, IAddComponentOnStart
|
||||
|
||||
newPopup.transform.localPosition = Vector3.zero;
|
||||
newPopup.transform.localScale = Vector3.one;
|
||||
newPopup.GetComponentsInChildren<LocalizedText>().ToList().ForEach(x => Destroy(x));
|
||||
newPopup.GetComponentsInChildren<LocalizedText>().ForEach(Destroy);
|
||||
|
||||
var originalPopup = newPopup.GetComponent<PopupMenu>();
|
||||
|
||||
@ -201,9 +225,66 @@ internal class MenuManager : MonoBehaviour, IAddComponentOnStart
|
||||
InputLibrary.cancel,
|
||||
new ScreenPrompt(confirm1Text),
|
||||
new ScreenPrompt(confirm2Text),
|
||||
new ScreenPrompt(cancelText),
|
||||
true,
|
||||
true);
|
||||
new ScreenPrompt(cancelText));
|
||||
return popup;
|
||||
}
|
||||
|
||||
public FourChoicePopupMenu CreateFourChoicePopup(string message, string confirm1Text, string confirm2Text, string confirm3Text, string cancelText)
|
||||
{
|
||||
var newPopup = Instantiate(_choicePopupPrefab);
|
||||
|
||||
switch (LoadManager.GetCurrentScene())
|
||||
{
|
||||
case OWScene.TitleScreen:
|
||||
newPopup.transform.parent = GameObject.Find("/TitleMenu/PopupCanvas").transform;
|
||||
break;
|
||||
case OWScene.SolarSystem:
|
||||
case OWScene.EyeOfTheUniverse:
|
||||
newPopup.transform.parent = GameObject.Find("/PauseMenu/PopupCanvas").transform;
|
||||
break;
|
||||
}
|
||||
|
||||
newPopup.transform.localPosition = Vector3.zero;
|
||||
newPopup.transform.localScale = Vector3.one;
|
||||
newPopup.GetComponentsInChildren<LocalizedText>().ForEach(Destroy);
|
||||
|
||||
var originalPopup = newPopup.GetComponent<PopupMenu>();
|
||||
|
||||
var ok1Button = originalPopup._confirmButton.gameObject;
|
||||
|
||||
var ok2Button = Instantiate(ok1Button, ok1Button.transform.parent);
|
||||
ok2Button.transform.SetSiblingIndex(1);
|
||||
|
||||
var ok3Button = Instantiate(ok1Button, ok1Button.transform.parent);
|
||||
ok3Button.transform.SetSiblingIndex(2);
|
||||
|
||||
var popup = newPopup.AddComponent<FourChoicePopupMenu>();
|
||||
popup._labelText = originalPopup._labelText;
|
||||
popup._cancelAction = originalPopup._cancelAction;
|
||||
popup._ok1Action = originalPopup._okAction;
|
||||
popup._ok2Action = ok2Button.GetComponent<SubmitAction>();
|
||||
popup._ok3Action = ok3Button.GetComponent<SubmitAction>();
|
||||
popup._cancelButton = originalPopup._cancelButton;
|
||||
popup._confirmButton1 = originalPopup._confirmButton;
|
||||
popup._confirmButton2 = ok2Button.GetComponent<ButtonWithHotkeyImageElement>();
|
||||
popup._confirmButton3 = ok3Button.GetComponent<ButtonWithHotkeyImageElement>();
|
||||
popup._rootCanvas = originalPopup._rootCanvas;
|
||||
popup._menuActivationRoot = originalPopup._menuActivationRoot;
|
||||
popup._startEnabled = originalPopup._startEnabled;
|
||||
popup._selectOnActivate = originalPopup._selectOnActivate;
|
||||
popup._selectableItemsRoot = originalPopup._selectableItemsRoot;
|
||||
popup._subMenus = originalPopup._subMenus;
|
||||
popup._menuOptions = originalPopup._menuOptions;
|
||||
popup.SetUpPopup(
|
||||
message,
|
||||
InputLibrary.menuConfirm,
|
||||
InputLibrary.confirm2,
|
||||
InputLibrary.signalscope,
|
||||
InputLibrary.cancel,
|
||||
new ScreenPrompt(confirm1Text),
|
||||
new ScreenPrompt(confirm2Text),
|
||||
new ScreenPrompt(confirm3Text),
|
||||
new ScreenPrompt(cancelText));
|
||||
return popup;
|
||||
}
|
||||
|
||||
@ -289,9 +370,62 @@ internal class MenuManager : MonoBehaviour, IAddComponentOnStart
|
||||
TwoButtonInfoPopup.OnPopupConfirm += () => OnCloseInfoPopup(true);
|
||||
TwoButtonInfoPopup.OnPopupCancel += () => OnCloseInfoPopup(false);
|
||||
|
||||
HostGameTypePopup = CreateThreeChoicePopup(QSBLocalization.Current.HostExistingOrNew, QSBLocalization.Current.ExistingSave, QSBLocalization.Current.NewSave, QSBLocalization.Current.Cancel);
|
||||
HostGameTypePopup.OnPopupConfirm1 += () => Host(false);
|
||||
HostGameTypePopup.OnPopupConfirm2 += () => Host(true);
|
||||
ExistingNewCopyPopup = CreateFourChoicePopup(QSBLocalization.Current.HostExistingOrNewOrCopy,
|
||||
QSBLocalization.Current.ExistingSave,
|
||||
QSBLocalization.Current.NewSave,
|
||||
QSBLocalization.Current.CopySave,
|
||||
QSBLocalization.Current.Cancel);
|
||||
ExistingNewCopyPopup.OnPopupConfirm1 += () => Host(false);
|
||||
ExistingNewCopyPopup.OnPopupConfirm2 += () => Host(true);
|
||||
ExistingNewCopyPopup.OnPopupConfirm3 += () =>
|
||||
{
|
||||
DebugLog.DebugWrite("Replacing multiplayer save with singleplayer save");
|
||||
QSBCore.IsInMultiplayer = true;
|
||||
|
||||
if (QSBCore.IsStandalone)
|
||||
{
|
||||
var currentProfile = QSBStandaloneProfileManager.SharedInstance.currentProfile;
|
||||
QSBStandaloneProfileManager.SharedInstance.SaveGame(currentProfile.gameSave, null, null, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
var gameSave = QSBMSStoreProfileManager.SharedInstance.currentProfileGameSave;
|
||||
QSBMSStoreProfileManager.SharedInstance.SaveGame(gameSave, null, null, null);
|
||||
}
|
||||
|
||||
Host(false);
|
||||
};
|
||||
|
||||
NewCopyPopup = CreateThreeChoicePopup(QSBLocalization.Current.HostNewOrCopy,
|
||||
QSBLocalization.Current.NewSave,
|
||||
QSBLocalization.Current.CopySave,
|
||||
QSBLocalization.Current.Cancel);
|
||||
NewCopyPopup.OnPopupConfirm1 += () => Host(true);
|
||||
NewCopyPopup.OnPopupConfirm2 += () =>
|
||||
{
|
||||
DebugLog.DebugWrite("Replacing multiplayer save with singleplayer save");
|
||||
QSBCore.IsInMultiplayer = true;
|
||||
|
||||
if (QSBCore.IsStandalone)
|
||||
{
|
||||
var currentProfile = QSBStandaloneProfileManager.SharedInstance.currentProfile;
|
||||
QSBStandaloneProfileManager.SharedInstance.SaveGame(currentProfile.gameSave, null, null, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
var gameSave = QSBMSStoreProfileManager.SharedInstance.currentProfileGameSave;
|
||||
QSBMSStoreProfileManager.SharedInstance.SaveGame(gameSave, null, null, null);
|
||||
}
|
||||
|
||||
Host(false);
|
||||
};
|
||||
|
||||
ExistingNewPopup = CreateThreeChoicePopup(QSBLocalization.Current.HostExistingOrNew,
|
||||
QSBLocalization.Current.ExistingSave,
|
||||
QSBLocalization.Current.NewSave,
|
||||
QSBLocalization.Current.Cancel);
|
||||
ExistingNewPopup.OnPopupConfirm1 += () => Host(false);
|
||||
ExistingNewPopup.OnPopupConfirm2 += () => Host(true);
|
||||
}
|
||||
|
||||
private static void SetButtonActive(Button button, bool active)
|
||||
@ -342,10 +476,10 @@ internal class MenuManager : MonoBehaviour, IAddComponentOnStart
|
||||
DisconnectPopup._labelText.text = popupText;
|
||||
|
||||
var langController = QSBWorldSync.GetUnityObject<PauseMenuManager>().transform.GetChild(0).GetComponent<FontAndLanguageController>();
|
||||
langController.AddTextElement(DisconnectButton.transform.GetChild(0).GetChild(1).GetComponent<Text>(), true, true, false);
|
||||
langController.AddTextElement(DisconnectPopup._labelText, false, true, false);
|
||||
langController.AddTextElement(DisconnectPopup._confirmButton._buttonText, false, true, false);
|
||||
langController.AddTextElement(DisconnectPopup._cancelButton._buttonText, false, true, false);
|
||||
langController.AddTextElement(DisconnectButton.transform.GetChild(0).GetChild(1).GetComponent<Text>());
|
||||
langController.AddTextElement(DisconnectPopup._labelText, false);
|
||||
langController.AddTextElement(DisconnectPopup._confirmButton._buttonText, false);
|
||||
langController.AddTextElement(DisconnectPopup._cancelButton._buttonText, false);
|
||||
}
|
||||
|
||||
private void MakeTitleMenus()
|
||||
@ -384,24 +518,35 @@ internal class MenuManager : MonoBehaviour, IAddComponentOnStart
|
||||
}
|
||||
|
||||
var mainMenuFontController = GameObject.Find("MainMenu").GetComponent<FontAndLanguageController>();
|
||||
mainMenuFontController.AddTextElement(HostButton.transform.GetChild(0).GetChild(1).GetComponent<Text>(), true, true, false);
|
||||
mainMenuFontController.AddTextElement(ConnectButton.transform.GetChild(0).GetChild(1).GetComponent<Text>(), true, true, false);
|
||||
mainMenuFontController.AddTextElement(HostButton.transform.GetChild(0).GetChild(1).GetComponent<Text>());
|
||||
mainMenuFontController.AddTextElement(ConnectButton.transform.GetChild(0).GetChild(1).GetComponent<Text>());
|
||||
|
||||
mainMenuFontController.AddTextElement(OneButtonInfoPopup._labelText, false, true, false);
|
||||
mainMenuFontController.AddTextElement(OneButtonInfoPopup._confirmButton._buttonText, false, true, false);
|
||||
mainMenuFontController.AddTextElement(OneButtonInfoPopup._labelText, false);
|
||||
mainMenuFontController.AddTextElement(OneButtonInfoPopup._confirmButton._buttonText, false);
|
||||
|
||||
mainMenuFontController.AddTextElement(TwoButtonInfoPopup._labelText, false, true, false);
|
||||
mainMenuFontController.AddTextElement(TwoButtonInfoPopup._confirmButton._buttonText, false, true, false);
|
||||
mainMenuFontController.AddTextElement(TwoButtonInfoPopup._cancelButton._buttonText, false, true, false);
|
||||
mainMenuFontController.AddTextElement(TwoButtonInfoPopup._labelText, false);
|
||||
mainMenuFontController.AddTextElement(TwoButtonInfoPopup._confirmButton._buttonText, false);
|
||||
mainMenuFontController.AddTextElement(TwoButtonInfoPopup._cancelButton._buttonText, false);
|
||||
|
||||
mainMenuFontController.AddTextElement(ConnectPopup._labelText, false, true, false);
|
||||
mainMenuFontController.AddTextElement(ConnectPopup._confirmButton._buttonText, false, true, false);
|
||||
mainMenuFontController.AddTextElement(ConnectPopup._cancelButton._buttonText, false, true, false);
|
||||
mainMenuFontController.AddTextElement(ConnectPopup._labelText, false);
|
||||
mainMenuFontController.AddTextElement(ConnectPopup._confirmButton._buttonText, false);
|
||||
mainMenuFontController.AddTextElement(ConnectPopup._cancelButton._buttonText, false);
|
||||
|
||||
mainMenuFontController.AddTextElement(HostGameTypePopup._labelText, false, true, false);
|
||||
mainMenuFontController.AddTextElement(HostGameTypePopup._confirmButton1._buttonText, false, true, false);
|
||||
mainMenuFontController.AddTextElement(HostGameTypePopup._confirmButton2._buttonText, false, true, false);
|
||||
mainMenuFontController.AddTextElement(HostGameTypePopup._cancelButton._buttonText, false, true, false);
|
||||
mainMenuFontController.AddTextElement(ExistingNewCopyPopup._labelText, false);
|
||||
mainMenuFontController.AddTextElement(ExistingNewCopyPopup._confirmButton1._buttonText, false);
|
||||
mainMenuFontController.AddTextElement(ExistingNewCopyPopup._confirmButton2._buttonText, false);
|
||||
mainMenuFontController.AddTextElement(ExistingNewCopyPopup._confirmButton3._buttonText, false);
|
||||
mainMenuFontController.AddTextElement(ExistingNewCopyPopup._cancelButton._buttonText, false);
|
||||
|
||||
mainMenuFontController.AddTextElement(NewCopyPopup._labelText, false);
|
||||
mainMenuFontController.AddTextElement(NewCopyPopup._confirmButton1._buttonText, false);
|
||||
mainMenuFontController.AddTextElement(NewCopyPopup._confirmButton2._buttonText, false);
|
||||
mainMenuFontController.AddTextElement(NewCopyPopup._cancelButton._buttonText, false);
|
||||
|
||||
mainMenuFontController.AddTextElement(ExistingNewPopup._labelText, false);
|
||||
mainMenuFontController.AddTextElement(ExistingNewPopup._confirmButton1._buttonText, false);
|
||||
mainMenuFontController.AddTextElement(ExistingNewPopup._confirmButton2._buttonText, false);
|
||||
mainMenuFontController.AddTextElement(ExistingNewPopup._cancelButton._buttonText, false);
|
||||
}
|
||||
|
||||
private void Disconnect()
|
||||
@ -421,23 +566,71 @@ internal class MenuManager : MonoBehaviour, IAddComponentOnStart
|
||||
|
||||
private void PreHost()
|
||||
{
|
||||
var doesSaveExist = PlayerData.LoadLoopCount() > 1;
|
||||
|
||||
if (!doesSaveExist)
|
||||
bool doesSingleplayerSaveExist = false;
|
||||
bool doesMultiplayerSaveExist = false;
|
||||
if (!QSBCore.IsStandalone)
|
||||
{
|
||||
Host(true);
|
||||
return;
|
||||
var manager = QSBMSStoreProfileManager.SharedInstance;
|
||||
doesSingleplayerSaveExist = manager.currentProfileGameSave.loopCount > 1;
|
||||
doesMultiplayerSaveExist = manager.currentProfileMultiplayerGameSave.loopCount > 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
var profile = QSBStandaloneProfileManager.SharedInstance.currentProfile;
|
||||
doesSingleplayerSaveExist = profile.gameSave.loopCount > 1;
|
||||
doesMultiplayerSaveExist = profile.multiplayerGameSave.loopCount > 1;
|
||||
}
|
||||
|
||||
HostGameTypePopup.EnableMenu(true);
|
||||
if (doesSingleplayerSaveExist && doesMultiplayerSaveExist)
|
||||
{
|
||||
DebugLog.DebugWrite("CASE 1 - Both singleplayer and multiplayer saves exist.");
|
||||
// ask if we want to use the existing multiplayer save,
|
||||
// start a new multiplayer save, or copy the singleplayer save
|
||||
ExistingNewCopyPopup.EnableMenu(true);
|
||||
}
|
||||
else if (doesSingleplayerSaveExist && !doesMultiplayerSaveExist)
|
||||
{
|
||||
DebugLog.DebugWrite("CASE 2 - Only a singleplayer save exists.");
|
||||
// ask if we want to start a new multiplayer save or copy the singleplayer save
|
||||
NewCopyPopup.EnableMenu(true);
|
||||
}
|
||||
else if (!doesSingleplayerSaveExist && doesMultiplayerSaveExist)
|
||||
{
|
||||
DebugLog.DebugWrite("CASE 3 - Only multiplayer save exists.");
|
||||
// ask if we want to use the existing multiplayer save or start a new one
|
||||
ExistingNewPopup.EnableMenu(true);
|
||||
}
|
||||
else if (!doesSingleplayerSaveExist && !doesMultiplayerSaveExist)
|
||||
{
|
||||
DebugLog.DebugWrite("CASE 4 - Neither a singleplayer or a multiplayer save exists.");
|
||||
// create a new multiplayer save - nothing to copy or load
|
||||
Host(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void Host(bool newSave)
|
||||
private void Host(bool newMultiplayerSave)
|
||||
{
|
||||
if (newSave)
|
||||
QSBCore.IsInMultiplayer = true;
|
||||
|
||||
if (newMultiplayerSave)
|
||||
{
|
||||
DebugLog.DebugWrite("Resetting game...");
|
||||
PlayerData.ResetGame();
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugLog.DebugWrite("Loading multiplayer game...");
|
||||
if (QSBCore.IsStandalone)
|
||||
{
|
||||
var profile = QSBStandaloneProfileManager.SharedInstance.currentProfile;
|
||||
PlayerData.Init(profile.multiplayerGameSave, profile.settingsSave, profile.graphicsSettings, profile.inputJSON);
|
||||
}
|
||||
else
|
||||
{
|
||||
var manager = QSBMSStoreProfileManager.SharedInstance;
|
||||
PlayerData.Init(manager.currentProfileMultiplayerGameSave, manager.currentProfileGameSettings, manager.currentProfileGraphicsSettings, manager.currentProfileInputJSON);
|
||||
}
|
||||
}
|
||||
|
||||
_intentionalDisconnect = false;
|
||||
|
||||
@ -474,8 +667,20 @@ internal class MenuManager : MonoBehaviour, IAddComponentOnStart
|
||||
|
||||
private void Connect()
|
||||
{
|
||||
QSBCore.IsInMultiplayer = true;
|
||||
_intentionalDisconnect = false;
|
||||
|
||||
if (QSBCore.IsStandalone)
|
||||
{
|
||||
var profile = QSBStandaloneProfileManager.SharedInstance.currentProfile;
|
||||
PlayerData.Init(profile.multiplayerGameSave, profile.settingsSave, profile.graphicsSettings, profile.inputJSON);
|
||||
}
|
||||
else
|
||||
{
|
||||
var manager = QSBMSStoreProfileManager.SharedInstance;
|
||||
PlayerData.Init(manager.currentProfileMultiplayerGameSave, manager.currentProfileGameSettings, manager.currentProfileGraphicsSettings, manager.currentProfileInputJSON);
|
||||
}
|
||||
|
||||
var address = ConnectPopup.GetInputText();
|
||||
if (address == string.Empty)
|
||||
{
|
||||
@ -506,6 +711,7 @@ internal class MenuManager : MonoBehaviour, IAddComponentOnStart
|
||||
|
||||
public void OnKicked(string reason)
|
||||
{
|
||||
QSBCore.IsInMultiplayer = false;
|
||||
_intentionalDisconnect = true;
|
||||
|
||||
PopupClose += _ =>
|
||||
@ -521,6 +727,8 @@ internal class MenuManager : MonoBehaviour, IAddComponentOnStart
|
||||
|
||||
private void OnDisconnected(string error)
|
||||
{
|
||||
QSBCore.IsInMultiplayer = false;
|
||||
|
||||
if (_intentionalDisconnect)
|
||||
{
|
||||
DebugLog.DebugWrite("intentional disconnect. dont show popup");
|
||||
|
@ -203,7 +203,7 @@ public class ThreeChoicePopupMenu : Menu
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.LogError("Cannot disable Menu unless it is on the top the MenuLayerManager stack. Current menu on top: " + MenuStackManager.SharedInstance.Peek().gameObject.name);
|
||||
Debug.LogError("Cannot disable Menu unless it is on the top the MenuLayerManager stack. Current menu on top: " + MenuStackManager.SharedInstance.Peek().name);
|
||||
}
|
||||
|
||||
public override void Activate()
|
||||
|
@ -72,7 +72,7 @@ public static class QSBMessageManager
|
||||
&& player.State is ClientState.AliveInSolarSystem or ClientState.AliveInEye or ClientState.DeadInSolarSystem
|
||||
&& msg is not (PlayerInformationMessage or PlayerReadyMessage or RequestStateResyncMessage or ServerStateMessage))
|
||||
{
|
||||
DebugLog.ToConsole($"Warning - Got message {msg} from player {msg.From}, but they were not ready. Asking for state resync, just in case.", MessageType.Warning);
|
||||
//DebugLog.ToConsole($"Warning - Got message {msg} from player {msg.From}, but they were not ready. Asking for state resync, just in case.", MessageType.Warning);
|
||||
new RequestStateResyncMessage().Send();
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ namespace QSB.MeteorSync.Patches;
|
||||
/// <summary>
|
||||
/// server only
|
||||
/// </summary>
|
||||
public class MeteorServerPatches : QSBPatch
|
||||
public class ServerMeteorPatches : QSBPatch
|
||||
{
|
||||
public override QSBPatchTypes Type => QSBPatchTypes.OnServerClientConnect;
|
||||
|
||||
@ -165,7 +165,7 @@ public class MeteorServerPatches : QSBPatch
|
||||
/// <summary>
|
||||
/// client only
|
||||
/// </summary>
|
||||
public class MeteorClientPatches : QSBPatch
|
||||
public class ClientMeteorPatches : QSBPatch
|
||||
{
|
||||
public override QSBPatchTypes Type => QSBPatchTypes.OnNonServerClientConnect;
|
||||
|
||||
|
@ -5,11 +5,6 @@ namespace QSB.MeteorSync.WorldObjects;
|
||||
|
||||
public class QSBMeteor : WorldObject<MeteorController>
|
||||
{
|
||||
public override void SendInitialState(uint to)
|
||||
{
|
||||
// we don't really need to sync initial state
|
||||
}
|
||||
|
||||
public static bool IsSpecialImpact(GameObject go) =>
|
||||
go == Locator.GetPlayerCollider().gameObject ||
|
||||
Locator.GetProbe() != null && go == Locator.GetProbe()._anchor._collider.gameObject;
|
||||
|
@ -5,11 +5,6 @@ namespace QSB.MeteorSync.WorldObjects;
|
||||
|
||||
public class QSBMeteorLauncher : WorldObject<MeteorLauncher>
|
||||
{
|
||||
public override void SendInitialState(uint to)
|
||||
{
|
||||
// we don't really need to sync initial state
|
||||
}
|
||||
|
||||
public void PreLaunchMeteor()
|
||||
{
|
||||
foreach (var launchParticle in AttachedObject._launchParticles)
|
||||
|
@ -14,6 +14,14 @@ internal class ModelShipManager : WorldObjectManager
|
||||
public override bool DlcOnly => false;
|
||||
|
||||
public override async UniTask BuildWorldObjects(OWScene scene, CancellationToken ct)
|
||||
{
|
||||
if (QSBCore.IsHost)
|
||||
{
|
||||
Instantiate(QSBNetworkManager.singleton.ModelShipPrefab).SpawnWithServerAuthority();
|
||||
}
|
||||
}
|
||||
|
||||
public override void UnbuildWorldObjects()
|
||||
{
|
||||
if (QSBCore.IsHost)
|
||||
{
|
||||
@ -27,8 +35,6 @@ internal class ModelShipManager : WorldObjectManager
|
||||
|
||||
NetworkServer.Destroy(ModelShipTransformSync.LocalInstance.gameObject);
|
||||
}
|
||||
|
||||
Instantiate(QSBNetworkManager.singleton.ModelShipPrefab).SpawnWithServerAuthority();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,8 @@ public abstract class QSBPatch
|
||||
{
|
||||
public abstract QSBPatchTypes Type { get; }
|
||||
|
||||
public virtual GameVendor PatchVendor => GameVendor.Epic | GameVendor.Steam | GameVendor.Gamepass;
|
||||
|
||||
public void DoPatches(Harmony instance) => instance.PatchAll(GetType());
|
||||
|
||||
#region remote calls
|
||||
|
@ -42,7 +42,7 @@ public static class QSBPatchManager
|
||||
|
||||
OnPatchType?.SafeInvoke(type);
|
||||
//DebugLog.DebugWrite($"Patch block {Enum.GetName(typeof(QSBPatchTypes), type)}", MessageType.Info);
|
||||
foreach (var patch in _patchList.Where(x => x.Type == type))
|
||||
foreach (var patch in _patchList.Where(x => x.Type == type && x.PatchVendor.HasFlag(QSBCore.GameVendor)))
|
||||
{
|
||||
//DebugLog.DebugWrite($" - Patching in {patch.GetType().Name}", MessageType.Info);
|
||||
try
|
||||
|
@ -40,4 +40,11 @@ internal class VolumePatches : QSBPatch
|
||||
comp.AddVolume(__instance);
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(ElectricityVolume), nameof(ElectricityVolume.OnEffectVolumeEnter))]
|
||||
public static bool OnEffectVolumeEnter(ElectricityVolume __instance, GameObject hitObj) =>
|
||||
// this is a dogshit fix to a bug where this would ApplyShock to remote players,
|
||||
// which would actually apply the shock affects to the entire planet / sector
|
||||
hitObj.name != "REMOTE_PlayerDetector";
|
||||
}
|
||||
|
@ -26,6 +26,16 @@ public class PlayerHUDMarker : HUDDistanceMarker
|
||||
_needsInitializing = true;
|
||||
}
|
||||
|
||||
private bool ShouldBeVisible()
|
||||
{
|
||||
if (_player == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return _player.IsReady && !_player.IsDead && !_player.InDreamWorld && _player.Visible;
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (_needsInitializing)
|
||||
@ -42,9 +52,9 @@ public class PlayerHUDMarker : HUDDistanceMarker
|
||||
{
|
||||
var isVisible = _canvasMarker.IsVisible();
|
||||
|
||||
if (_player.Visible != isVisible)
|
||||
if (ShouldBeVisible() != isVisible)
|
||||
{
|
||||
_canvasMarker.SetVisibility(_player.Visible);
|
||||
_canvasMarker.SetVisibility(ShouldBeVisible());
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -48,6 +48,11 @@ public partial class PlayerInfo
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
if (AnimationSync != null)
|
||||
{
|
||||
AnimationSync.Reset();
|
||||
}
|
||||
|
||||
EyeState = default;
|
||||
IsDead = default;
|
||||
IsReady = default;
|
||||
@ -92,7 +97,8 @@ public partial class PlayerInfo
|
||||
else
|
||||
{
|
||||
FlashlightActive = Locator.GetFlashlight()._flashlightOn;
|
||||
SuitedUp = Locator.GetPlayerBody().GetComponent<PlayerSpacesuit>().IsWearingSuit();
|
||||
SuitedUp = Locator.GetPlayerBody().GetComponent<PlayerSpacesuit>().IsWearingSuit()
|
||||
|| QSBSceneManager.CurrentScene == OWScene.EyeOfTheUniverse;
|
||||
}
|
||||
|
||||
new PlayerInformationMessage().Send();
|
||||
|
@ -1,5 +1,4 @@
|
||||
using OWML.Common;
|
||||
using QSB.EchoesOfTheEye.LightSensorSync;
|
||||
using QSB.Utility;
|
||||
using UnityEngine;
|
||||
|
||||
@ -55,6 +54,10 @@ public partial class PlayerInfo
|
||||
}
|
||||
private GameObject _body;
|
||||
|
||||
/// <summary>
|
||||
/// remote light sensor is disabled.
|
||||
/// it only acts as a storage of data and is always synced with the local light sensor.
|
||||
/// </summary>
|
||||
public LightSensor LightSensor
|
||||
{
|
||||
get
|
||||
@ -74,8 +77,6 @@ public partial class PlayerInfo
|
||||
}
|
||||
}
|
||||
|
||||
public QSBPlayerLightSensor QSBPlayerLightSensor;
|
||||
|
||||
public Vector3 Velocity
|
||||
{
|
||||
get
|
||||
|
@ -4,10 +4,10 @@ namespace QSB.Player;
|
||||
|
||||
public class PlayerMapMarker : MonoBehaviour
|
||||
{
|
||||
public string PlayerName;
|
||||
private Transform _playerTransform;
|
||||
private PlayerInfo _player;
|
||||
private CanvasMapMarker _canvasMarker;
|
||||
private bool _canvasMarkerInitialized;
|
||||
private bool _hasBeenSetUpForInit;
|
||||
|
||||
public void Awake()
|
||||
{
|
||||
@ -18,7 +18,6 @@ public class PlayerMapMarker : MonoBehaviour
|
||||
public void Start()
|
||||
{
|
||||
enabled = false;
|
||||
_playerTransform = Locator.GetPlayerTransform();
|
||||
}
|
||||
|
||||
public void OnDestroy()
|
||||
@ -27,22 +26,20 @@ public class PlayerMapMarker : MonoBehaviour
|
||||
GlobalMessenger.RemoveListener("ExitMapView", new Callback(OnExitMapView));
|
||||
}
|
||||
|
||||
public void Init(PlayerInfo player)
|
||||
{
|
||||
_player = player;
|
||||
_hasBeenSetUpForInit = true;
|
||||
}
|
||||
|
||||
public void InitMarker()
|
||||
{
|
||||
var obj = GameObject.FindWithTag("MapCamera");
|
||||
var markerManager = obj.GetRequiredComponent<MapController>().GetMarkerManager();
|
||||
_canvasMarker = markerManager.InstantiateNewMarker(true);
|
||||
var component = GetComponent<OWRigidbody>();
|
||||
if (component != null)
|
||||
{
|
||||
markerManager.RegisterMarker(_canvasMarker, component);
|
||||
}
|
||||
else
|
||||
{
|
||||
markerManager.RegisterMarker(_canvasMarker, transform);
|
||||
}
|
||||
markerManager.RegisterMarker(_canvasMarker, transform);
|
||||
|
||||
_canvasMarker.SetLabel(PlayerName.ToUpper());
|
||||
_canvasMarker.SetLabel(_player.Name.ToUpper());
|
||||
_canvasMarker.SetColor(Color.white);
|
||||
_canvasMarker.SetVisibility(false);
|
||||
_canvasMarkerInitialized = true;
|
||||
@ -51,21 +48,31 @@ public class PlayerMapMarker : MonoBehaviour
|
||||
private void OnEnterMapView() => enabled = true;
|
||||
private void OnExitMapView() => enabled = false;
|
||||
|
||||
private bool ShouldBeVisible()
|
||||
{
|
||||
if (_player == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var playerScreenPos = Locator.GetActiveCamera().WorldToScreenPoint(transform.position);
|
||||
var isInfrontOfCamera = playerScreenPos.z > 0f;
|
||||
|
||||
return _player.IsReady && !_player.IsDead && !_player.InDreamWorld && _player.Visible && isInfrontOfCamera;
|
||||
}
|
||||
|
||||
public void LateUpdate()
|
||||
{
|
||||
if (!_canvasMarkerInitialized)
|
||||
if (!_canvasMarkerInitialized && _hasBeenSetUpForInit)
|
||||
{
|
||||
InitMarker();
|
||||
}
|
||||
|
||||
var a = Locator.GetActiveCamera().WorldToScreenPoint(transform.position);
|
||||
var b = Locator.GetActiveCamera().WorldToScreenPoint(_playerTransform.position);
|
||||
var vector = a - b;
|
||||
vector.z = 0f;
|
||||
var flag = a.z > 0f;
|
||||
if (_canvasMarker.IsVisible() != flag)
|
||||
var shouldBeVisible = ShouldBeVisible();
|
||||
|
||||
if (_canvasMarker.IsVisible() != shouldBeVisible)
|
||||
{
|
||||
_canvasMarker.SetVisibility(flag);
|
||||
_canvasMarker.SetVisibility(shouldBeVisible);
|
||||
}
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@ using QSB.Tools.FlashlightTool;
|
||||
using QSB.Tools.ProbeTool;
|
||||
using QSB.Utility;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
@ -125,4 +126,67 @@ public static class QSBPlayerManager
|
||||
|
||||
public static IEnumerable<(PlayerInfo Player, IQSBItem HeldItem)> GetPlayerCarryItems()
|
||||
=> PlayerList.Select(x => (x, x.HeldItem));
|
||||
|
||||
private static Dictionary<int, PlayerInfo> _connectionIdToPlayer = new();
|
||||
|
||||
public static IEnumerator ValidatePlayers()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (QSBCore.IsInMultiplayer && QSBCore.IsHost)
|
||||
{
|
||||
_connectionIdToPlayer.Clear();
|
||||
|
||||
var playersToRemove = new List<PlayerInfo>();
|
||||
|
||||
foreach (var player in PlayerList)
|
||||
{
|
||||
var transformSync = player.TransformSync;
|
||||
|
||||
if (transformSync == null)
|
||||
{
|
||||
DebugLog.ToConsole($"Error - {player.PlayerId}'s TransformSync is null.", MessageType.Error);
|
||||
playersToRemove.Add(player);
|
||||
continue;
|
||||
}
|
||||
|
||||
var networkIdentity = transformSync.netIdentity;
|
||||
|
||||
if (networkIdentity == null)
|
||||
{
|
||||
DebugLog.ToConsole($"Error - {player.PlayerId}'s TransformSync's NetworkIdentity is null.", MessageType.Error);
|
||||
playersToRemove.Add(player);
|
||||
continue;
|
||||
}
|
||||
|
||||
var connectionToClient = networkIdentity.connectionToClient;
|
||||
|
||||
if (_connectionIdToPlayer.ContainsKey(connectionToClient.connectionId))
|
||||
{
|
||||
// oh god oh fuck
|
||||
DebugLog.ToConsole($"Error - {player.PlayerId}'s connectionToClient.connectionId is already being used?!?", MessageType.Error);
|
||||
playersToRemove.Add(player);
|
||||
continue;
|
||||
}
|
||||
|
||||
_connectionIdToPlayer.Add(connectionToClient.connectionId, player);
|
||||
}
|
||||
|
||||
if (playersToRemove.Count != 0)
|
||||
{
|
||||
DebugLog.DebugWrite($"Removing {playersToRemove.Count} invalid players.", MessageType.Success);
|
||||
|
||||
foreach (var player in playersToRemove)
|
||||
{
|
||||
OnRemovePlayer?.Invoke(player);
|
||||
player.HudMarker?.Remove();
|
||||
PlayerList.Remove(player);
|
||||
DebugLog.DebugWrite($"Remove Invalid Player : {player}", MessageType.Info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
yield return new WaitForSecondsRealtime(5);
|
||||
}
|
||||
}
|
||||
}
|
@ -29,8 +29,25 @@ public class PlayerTransformSync : SectoredTransformSync
|
||||
private Transform _visibleStickTip;
|
||||
private Transform _networkStickTip => _networkStickPivot.GetChild(0);
|
||||
|
||||
private bool _hasRanOnStartClient;
|
||||
|
||||
public override void OnStartClient()
|
||||
{
|
||||
if (_hasRanOnStartClient)
|
||||
{
|
||||
DebugLog.ToConsole($"ERROR - OnStartClient is being called AGAIN for {Player.PlayerId}'s PlayerTransformSync!", MessageType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
_hasRanOnStartClient = true;
|
||||
if (QSBPlayerManager.PlayerList.Any(x => x.TransformSync == this))
|
||||
{
|
||||
// this really shouldnt happen...
|
||||
DebugLog.ToConsole($"Error - A PlayerInfo already exists with TransformSync {name}", MessageType.Error);
|
||||
Destroy(gameObject); // probably bad
|
||||
return;
|
||||
}
|
||||
|
||||
var player = new PlayerInfo(this);
|
||||
QSBPlayerManager.PlayerList.SafeAdd(player);
|
||||
base.OnStartClient();
|
||||
@ -40,7 +57,19 @@ public class PlayerTransformSync : SectoredTransformSync
|
||||
JoinLeaveSingularity.Create(Player, true);
|
||||
}
|
||||
|
||||
public override void OnStartLocalPlayer() => LocalInstance = this;
|
||||
public override void OnStartLocalPlayer()
|
||||
{
|
||||
if (LocalInstance != null)
|
||||
{
|
||||
DebugLog.ToConsole($"ERROR - LocalInstance is already non-null in OnStartLocalPlayer!", MessageType.Error);
|
||||
Destroy(gameObject); // probably bad
|
||||
return;
|
||||
}
|
||||
|
||||
LocalInstance = this;
|
||||
}
|
||||
|
||||
public override void OnStopLocalPlayer() => LocalInstance = null;
|
||||
|
||||
public override void OnStopClient()
|
||||
{
|
||||
|
@ -4,9 +4,7 @@ using QSB.Player;
|
||||
using QSB.Player.Messages;
|
||||
using QSB.SectorSync;
|
||||
using QSB.Tools;
|
||||
using QSB.Utility;
|
||||
using QSB.WorldSync;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace QSB.PlayerBodySetup.Local;
|
||||
@ -21,8 +19,6 @@ public static class LocalPlayerCreation
|
||||
out Transform visibleStickPivot,
|
||||
out Transform visibleStickTip)
|
||||
{
|
||||
DebugLog.DebugWrite($"CREATE PLAYER");
|
||||
|
||||
sectorDetector.Init(Locator.GetPlayerSectorDetector());
|
||||
|
||||
// player body
|
||||
@ -38,7 +34,7 @@ public static class LocalPlayerCreation
|
||||
player.CameraBody = cameraBody.gameObject;
|
||||
visibleCameraRoot = cameraBody;
|
||||
|
||||
player.QSBPlayerLightSensor = player.LightSensor.gameObject.GetAddComponent<QSBPlayerLightSensor>();
|
||||
player.LightSensor.gameObject.GetAddComponent<QSBPlayerLightSensor>();
|
||||
|
||||
PlayerToolsManager.InitLocal();
|
||||
|
||||
|
@ -33,14 +33,10 @@ public static class RemotePlayerCreation
|
||||
out Transform visibleStickPivot,
|
||||
out Transform visibleStickTip)
|
||||
{
|
||||
DebugLog.DebugWrite($"CREATE PLAYER");
|
||||
|
||||
/*
|
||||
* CREATE PLAYER STRUCTURE
|
||||
*/
|
||||
|
||||
DebugLog.DebugWrite($"CREATE PLAYER STRUCTURE");
|
||||
|
||||
// Variable naming convention is broken here to reflect OW unity project (with REMOTE_ prefixed) for readability
|
||||
|
||||
var REMOTE_Player_Body = Object.Instantiate(GetPrefab());
|
||||
@ -54,8 +50,6 @@ public static class RemotePlayerCreation
|
||||
* SET UP PLAYER BODY
|
||||
*/
|
||||
|
||||
DebugLog.DebugWrite($"SET UP PLAYER BODY");
|
||||
|
||||
player.Body = REMOTE_Player_Body;
|
||||
player.ThrusterLightTracker = player.Body.GetComponentInChildren<ThrusterLightTracker>();
|
||||
player.FluidDetector = REMOTE_PlayerDetector.GetComponent<RemotePlayerFluidDetector>();
|
||||
@ -63,7 +57,7 @@ public static class RemotePlayerCreation
|
||||
player.AnimationSync.InitRemote(REMOTE_Traveller_HEA_Player_v2.transform);
|
||||
|
||||
REMOTE_Player_Body.GetComponent<PlayerHUDMarker>().Init(player);
|
||||
REMOTE_Player_Body.GetComponent<PlayerMapMarker>().PlayerName = player.Name;
|
||||
REMOTE_Player_Body.GetComponent<PlayerMapMarker>().Init(player);
|
||||
player._ditheringAnimator = REMOTE_Player_Body.GetComponent<QSBDitheringAnimator>();
|
||||
player.DreamWorldSpawnAnimator = REMOTE_Player_Body.GetComponent<DreamWorldSpawnAnimator>();
|
||||
player.AudioController = REMOTE_Player_Body.transform.Find("REMOTE_Audio_Player").GetComponent<QSBPlayerAudioController>();
|
||||
@ -72,15 +66,13 @@ public static class RemotePlayerCreation
|
||||
* SET UP PLAYER CAMERA
|
||||
*/
|
||||
|
||||
DebugLog.DebugWrite($"SET UP PLAYER CAMERA");
|
||||
|
||||
REMOTE_PlayerCamera.GetComponent<Camera>().enabled = false;
|
||||
var owcamera = REMOTE_PlayerCamera.GetComponent<OWCamera>();
|
||||
player.Camera = owcamera;
|
||||
player.CameraBody = REMOTE_PlayerCamera;
|
||||
visibleCameraRoot = REMOTE_PlayerCamera.transform;
|
||||
|
||||
player.QSBPlayerLightSensor = player.LightSensor.gameObject.GetAddComponent<QSBPlayerLightSensor>();
|
||||
player.LightSensor.gameObject.GetAddComponent<QSBPlayerLightSensor>();
|
||||
|
||||
PlayerToolsManager.InitRemote(player);
|
||||
|
||||
@ -88,8 +80,6 @@ public static class RemotePlayerCreation
|
||||
* SET UP ROASTING STICK
|
||||
*/
|
||||
|
||||
DebugLog.DebugWrite($"SET UP ROASTING STICK");
|
||||
|
||||
var REMOTE_Stick_Pivot = REMOTE_Stick_Root.transform.GetChild(0);
|
||||
var mallowRoot = REMOTE_Stick_Pivot.Find("REMOTE_Stick_Tip/Mallow_Root");
|
||||
var newSystem = mallowRoot.Find("MallowSmoke").gameObject.GetComponent<CustomRelativisticParticleSystem>();
|
||||
|
@ -728,7 +728,7 @@ internal class CustomNomaiRemoteCameraPlatform : NomaiShared
|
||||
DebugLog.ToConsole($"Warning - {playerId}'s VisibleAnimator is null!", MessageType.Error);
|
||||
}
|
||||
|
||||
mirror.Init(player.AnimationSync.VisibleAnimator, hologramCopy.GetChild(0).gameObject.GetComponent<Animator>());
|
||||
mirror.Init(player.AnimationSync.VisibleAnimator, hologramCopy.GetChild(0).gameObject.GetComponent<Animator>(), null);
|
||||
|
||||
_playerToHologram.Add(player, hologramCopy.gameObject);
|
||||
|
||||
|
@ -90,7 +90,7 @@
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="OuterWildsGameLibs" Version="1.1.12.168" IncludeAssets="compile" />
|
||||
<PackageReference Include="OuterWildsGameLibs" Version="1.1.12.201" IncludeAssets="compile" />
|
||||
<Reference Include="..\Mirror\*.dll" />
|
||||
<Reference Include="..\UniTask\*.dll" />
|
||||
<ProjectReference Include="..\EpicOnlineTransport\EpicOnlineTransport.csproj" />
|
||||
|
@ -5,7 +5,9 @@ using OWML.ModHelper;
|
||||
using QSB.Localization;
|
||||
using QSB.Menus;
|
||||
using QSB.Patches;
|
||||
using QSB.Player;
|
||||
using QSB.QuantumSync;
|
||||
using QSB.SaveSync;
|
||||
using QSB.Utility;
|
||||
using QSB.WorldSync;
|
||||
using System;
|
||||
@ -48,13 +50,18 @@ public class QSBCore : ModBehaviour
|
||||
public static AssetBundle ConversationAssetBundle { get; private set; }
|
||||
public static AssetBundle DebugAssetBundle { get; private set; }
|
||||
public static bool IsHost => NetworkServer.active;
|
||||
public static bool IsInMultiplayer => QSBNetworkManager.singleton.isNetworkActive;
|
||||
public static bool IsInMultiplayer;
|
||||
public static string QSBVersion => Helper.Manifest.Version;
|
||||
public static string GameVersion =>
|
||||
// ignore the last patch numbers like the title screen does
|
||||
Application.version.Split('.').Take(3).Join(delimiter: ".");
|
||||
public static bool DLCInstalled => EntitlementsManager.IsDlcOwned() == EntitlementsManager.AsyncOwnershipStatus.Owned;
|
||||
public static bool IncompatibleModsAllowed { get; private set; }
|
||||
public static GameVendor GameVendor { get; private set; } = GameVendor.None;
|
||||
public static bool IsStandalone => GameVendor is GameVendor.Epic or GameVendor.Steam;
|
||||
public static IProfileManager ProfileManager => IsStandalone
|
||||
? QSBStandaloneProfileManager.SharedInstance
|
||||
: QSBMSStoreProfileManager.SharedInstance;
|
||||
public static IMenuAPI MenuApi { get; private set; }
|
||||
public static DebugSettings DebugSettings { get; private set; } = new();
|
||||
public static Storage Storage { get; private set; } = new();
|
||||
@ -67,9 +74,42 @@ public class QSBCore : ModBehaviour
|
||||
// incompatible mods
|
||||
"Raicuparta.NomaiVR",
|
||||
"xen.NewHorizons",
|
||||
"Vesper.AutoResume"
|
||||
"Vesper.AutoResume",
|
||||
"Vesper.OuterWildsMMO",
|
||||
"_nebula.StopTime",
|
||||
"Leadpogrommer.PeacefulGhosts",
|
||||
"PacificEngine.OW_Randomizer",
|
||||
"xen.DayDream"
|
||||
};
|
||||
|
||||
private static void DetermineGameVendor()
|
||||
{
|
||||
var gameAssemblyTypes = typeof(AstroObject).Assembly.GetTypes();
|
||||
var isEpic = gameAssemblyTypes.Any(x => x.Name == "EpicEntitlementRetriever");
|
||||
var isSteam = gameAssemblyTypes.Any(x => x.Name == "SteamEntitlementRetriever");
|
||||
var isUWP = gameAssemblyTypes.Any(x => x.Name == "MSStoreEntitlementRetriever");
|
||||
|
||||
if (isEpic && !isSteam && !isUWP)
|
||||
{
|
||||
GameVendor = GameVendor.Epic;
|
||||
}
|
||||
else if (!isEpic && isSteam && !isUWP)
|
||||
{
|
||||
GameVendor = GameVendor.Steam;
|
||||
}
|
||||
else if (!isEpic && !isSteam && isUWP)
|
||||
{
|
||||
GameVendor = GameVendor.Gamepass;
|
||||
}
|
||||
else
|
||||
{
|
||||
// ???
|
||||
DebugLog.ToConsole($"FATAL - Could not determine game vendor.", MessageType.Fatal);
|
||||
}
|
||||
|
||||
DebugLog.DebugWrite($"Determined game vendor as {GameVendor}", MessageType.Info);
|
||||
}
|
||||
|
||||
public void Awake()
|
||||
{
|
||||
EpicRerouter.ModSide.Interop.Go();
|
||||
@ -77,6 +117,11 @@ public class QSBCore : ModBehaviour
|
||||
// no, we cant localize this - languages are loaded after the splash screen
|
||||
UIHelper.ReplaceUI(UITextType.PleaseUseController,
|
||||
"<color=orange>Quantum Space Buddies</color> is best experienced with friends...");
|
||||
|
||||
DetermineGameVendor();
|
||||
|
||||
QSBPatchManager.Init();
|
||||
QSBPatchManager.DoPatchType(QSBPatchTypes.OnModStart);
|
||||
}
|
||||
|
||||
public void Start()
|
||||
@ -133,7 +178,6 @@ public class QSBCore : ModBehaviour
|
||||
return;
|
||||
}
|
||||
|
||||
QSBPatchManager.Init();
|
||||
DeterministicManager.Init();
|
||||
QSBLocalization.Init();
|
||||
|
||||
@ -145,7 +189,7 @@ public class QSBCore : ModBehaviour
|
||||
QSBPatchManager.OnPatchType += OnPatchType;
|
||||
QSBPatchManager.OnUnpatchType += OnUnpatchType;
|
||||
|
||||
QSBPatchManager.DoPatchType(QSBPatchTypes.OnModStart);
|
||||
StartCoroutine(QSBPlayerManager.ValidatePlayers());
|
||||
}
|
||||
|
||||
private static void OnPatchType(QSBPatchTypes type)
|
||||
|
@ -21,6 +21,7 @@ using QSB.Patches;
|
||||
using QSB.Player;
|
||||
using QSB.Player.Messages;
|
||||
using QSB.Player.TransformSync;
|
||||
using QSB.SaveSync;
|
||||
using QSB.ShipSync;
|
||||
using QSB.ShipSync.TransformSync;
|
||||
using QSB.Syncs.Occasional;
|
||||
@ -101,7 +102,7 @@ public class QSBNetworkManager : NetworkManager, IAddComponentOnStart
|
||||
base.Awake();
|
||||
|
||||
InitPlayerName();
|
||||
StandaloneProfileManager.SharedInstance.OnProfileSignInComplete += _ => InitPlayerName();
|
||||
QSBCore.ProfileManager.OnProfileSignInComplete += _ => InitPlayerName();
|
||||
|
||||
playerPrefab = QSBCore.NetworkAssetBundle.LoadAsset<GameObject>("Assets/Prefabs/NETWORK_Player_Body.prefab");
|
||||
playerPrefab.GetRequiredComponent<NetworkIdentity>().SetValue("m_AssetId", 1.ToGuid().ToString("N"));
|
||||
@ -156,15 +157,22 @@ public class QSBNetworkManager : NetworkManager, IAddComponentOnStart
|
||||
{
|
||||
try
|
||||
{
|
||||
var titleScreenManager = FindObjectOfType<TitleScreenManager>();
|
||||
var profileManager = titleScreenManager._profileManager;
|
||||
if (profileManager.GetType().Name == "MSStoreProfileManager")
|
||||
if (!QSBCore.IsStandalone)
|
||||
{
|
||||
PlayerName = (string)profileManager.GetType().GetProperty("userDisplayName").GetValue(profileManager);
|
||||
PlayerName = QSBMSStoreProfileManager.SharedInstance.userDisplayName;
|
||||
}
|
||||
else
|
||||
{
|
||||
PlayerName = StandaloneProfileManager.SharedInstance.currentProfile.profileName;
|
||||
var currentProfile = QSBStandaloneProfileManager.SharedInstance.currentProfile;
|
||||
|
||||
if (currentProfile == null)
|
||||
{
|
||||
// probably havent created a profile yet
|
||||
Delay.RunWhen(() => QSBStandaloneProfileManager.SharedInstance.currentProfile != null, () => InitPlayerName());
|
||||
return;
|
||||
}
|
||||
|
||||
PlayerName = currentProfile.profileName;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user