Merge pull request #543 from misternebula/dev

0.21.0
This commit is contained in:
_nebula 2022-08-18 12:39:27 +01:00 committed by GitHub
commit b1768e7afb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
147 changed files with 4470 additions and 949 deletions

View File

@ -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>

View File

@ -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>

View File

@ -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" />

View File

@ -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>

View File

@ -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>();
}
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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) { }
}

View File

@ -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");
}
}
}

View File

@ -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());
}
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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.

View 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));
}
}
}

View 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));
}
}
}

View 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; }
}

View 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));
}
}

View File

@ -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,

View File

@ -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;
}
}
}

View File

@ -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));
}

View File

@ -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?
}
}

View File

@ -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);
}
}
}

View File

@ -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>();
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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
)
);
}

View File

@ -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))

View File

@ -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))]

View File

@ -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> { }

View File

@ -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();

View File

@ -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)

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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)

View File

@ -17,7 +17,7 @@ public class QSBGhostController : WorldObject<GhostController>, IGhostObject
{
public override void SendInitialState(uint to)
{
// todo SendInitialState
}
public QSBGhostEffects _effects;

View File

@ -16,7 +16,7 @@ public class QSBGhostEffects : WorldObject<GhostEffects>, IGhostObject
{
public override void SendInitialState(uint to)
{
// todo SendInitialState
}
public override bool ShouldDisplayDebug() => false;

View File

@ -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)

View File

@ -11,6 +11,6 @@ internal class QSBGhostNodeMap : WorldObject<GhostNodeMap>
{
public override void SendInitialState(uint to)
{
// todo SendInitialState??
}
}

View File

@ -15,7 +15,7 @@ public class QSBGhostSensors : WorldObject<GhostSensors>, IGhostObject
{
public override void SendInitialState(uint to)
{
// todo SendInitialState
}
public override string ReturnLabel() => "";

View File

@ -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> { }

View File

@ -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);
}
}
}

View File

@ -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));

View File

@ -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);
}
}
}

View File

@ -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));

View File

@ -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();
}
}
}

View File

@ -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();
}
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}
}

View File

@ -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();
}
}
});
}
}

View File

@ -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);
}

View File

@ -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));
}
}

View File

@ -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)

View File

@ -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;
}

View File

@ -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;

View File

@ -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);

View File

@ -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
}
}

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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();
}
}
}

View File

@ -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);
}
}
}

View File

@ -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>());
}
}

View File

@ -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
View 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
View 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
}

View File

@ -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;

View File

@ -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)
{

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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);

View File

@ -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();

View File

@ -1,3 +1,6 @@
namespace QSB.ItemSync.WorldObjects.Items;
/// <summary>
/// TODO: SYNC THIS SHIT LMAOOOOOO
/// </summary>
internal class QSBVisionTorchItem : QSBItem<VisionTorchItem> { }

View File

@ -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) { }
}

View File

@ -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;
}

View 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();
}

View File

@ -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");

View File

@ -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()

View File

@ -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();
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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)

View File

@ -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();
}
}
}
}

View File

@ -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

View File

@ -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

View File

@ -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";
}

View File

@ -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

View File

@ -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();

View File

@ -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

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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()
{

View File

@ -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();

View File

@ -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>();

View File

@ -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);

View File

@ -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" />

View File

@ -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)

View File

@ -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