Files
BoneSync/BoneSync/Sync/Components/SyncableBase.cs
2025-03-10 03:44:33 +02:00

360 lines
11 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using MelonLoader;
using StressLevelZero.Interaction;
using StressLevelZero.Pool;
using UnityEngine.Experimental.PlayerLoop;
using BoneSync.Networking.Messages;
using BoneSync.Networking;
using StressLevelZero.Data;
using System.Collections;
using StressLevelZero.Props;
using BoneSync.Patching;
using StressLevelZero.Props.Weapons;
using StressLevelZero.AI;
using PuppetMasta;
using UnityEngine.SceneManagement;
namespace BoneSync.Sync.Components
{
public static class TransformExtensions
{
public static string GetPath(this Transform current)
{
if (current.parent == null)
return "/" + current.name;
return current.parent.GetPath() + "/" + current.name;
}
public static Transform TransformFromPath(string path)
{
if (path.StartsWith("/"))
{
path = path.Substring(1);
}
string[] pathParts = path.Split('/');
Transform current = null;
foreach (string part in pathParts)
{
if (current == null)
{
current = GameObject.Find(part).transform;
}
else
{
current = current.Find(part);
}
}
return current;
}
}
[RegisterTypeInIl2Cpp]
public partial class Syncable : MonoBehaviour
{
public const int SYNC_FPS = 20;
public static Dictionary<GameObject, Syncable> syncablesCache = new Dictionary<GameObject, Syncable>();
public Syncable(IntPtr intPtr) : base(intPtr) {
syncablesCache[gameObject] = this;
}
private bool _syncCoroutineRunning;
public ulong _ownerId
{
private set;
get;
}
private ushort _syncId;
private float _lastSyncTime;
private bool _isInHolster;
private bool _attemptedRegister;
public bool Registered => _syncId != 0;
public bool isStale => Time.realtimeSinceStartup - _lastSyncTime > 5f;
public bool isOwner => _ownerId == BoneSync.lobby.GetLocalId();
public void SetInHolster(bool val)
{
MelonLogger.Msg(transform.GetPath() + " hosterState:" + val);
_isInHolster = val;
FindAndUpdateComponents();
}
public bool IsHolding()
{
if (interactableManager)
{
return interactableManager.grabbedHosts.Count > 0;
}
if (interactableHost)
{
return interactableHost.isAttached;
}
return false;
}
public InteractableHost interactableHost { private set; get; }
public InteractableHostManager interactableManager { private set; get; }
public Poolee poolee { private set; get; }
private Prop_Health propHealth;
private ObjectDestructable objectDestructable;
private Rigidbody[] rigidbodies;
private Transform[] _transforms;
private Gun gun;
private Magazine magazine;
private AlignPlug[] plugs;
private Socket[] sockets;
private AIBrain aiBrain;
private PullDevice pullDevice;
private ButtonToggle[] buttonToggles;
private SpawnFragment spawnFragment;
private void CheckAutoSync()
{
bool shouldAutoSync = ShouldAutoSync();
if (shouldAutoSync && (BoneSync.lobby.IsHost || ClientSpawningAllowed()))
{
MelonLogger.Msg("AutoSyncing: " + transform.GetPath());
RegisterSyncable();
}
}
private IEnumerator _OnEnableCo()
{
// wait for a couple frames to make sure all components are initialized, I hate this but it works
yield return null;
yield return null;
yield return null;
FindAndUpdateComponents();
CheckAutoSync();
yield break;
}
public void OnEnable()
{
syncablesCache[gameObject] = this;
FindAndUpdateComponents();
MelonCoroutines.Start(_OnEnableCo());
}
public bool ShouldAutoSync()
{
if (SceneSync.TimeSinceLastSceneChange < SceneSync.MAP_LOAD_GRACE_PERIOD) return false; // don't sync if scene just changed, to prevent some weird stuff that happens when a level is loaded
if (InPoolManagerTransform()) return false;
if (!gameObject.activeInHierarchy) return false;
if (poolee && poolee.pool) {
return true;
}
return false;
}
public string GetSyncableWorldPath()
{
if (transform == null) return "";
if (poolee && poolee.pool)
{
return "";
}
if (interactableHost || interactableManager)
{
return transform.GetPath();
}
return "";
}
public void FindAndUpdateComponents()
{
ObjectSyncCache.RemoveSyncable(this);
pullDevice = GetComponent<PullDevice>();
interactableManager = GetComponent<InteractableHostManager>();
interactableHost = GetComponent<InteractableHost>();
poolee = GetComponent<Poolee>();
propHealth = GetComponent<Prop_Health>();
objectDestructable = GetComponent<ObjectDestructable>();
gun = GetComponent<Gun>();
magazine = GetComponent<Magazine>();
sockets = GetComponentsInChildren<Socket>();
aiBrain = GetComponent<AIBrain>();
buttonToggles = GetComponentsInChildren<ButtonToggle>();
if (sockets.Length == 0)
{
plugs = GetComponentsInChildren<AlignPlug>();
} else {
plugs = new AlignPlug[0]; // don't use plugs if sockets are present
}
spawnFragment = GetComponent<SpawnFragment>();
UpdateTransformList();
TryPatchUnityEvents();
TrySendAttributeSync();
ObjectSyncCache.AddSyncable(this);
}
private void ResetSyncStatus()
{
_ownerId = 0;
_syncId = 0;
_attemptedRegister = false;
SetKinematic(false);
}
private bool InPoolManagerTransform()
{
if (transform.GetPath().StartsWith("/Pool Manager/"))
{
return true;
}
return false;
}
public bool CanBeSynced()
{
if (spawnFragment) return false; // if has spawn fragment, don't sync
FindAndUpdateComponents();
if (rigidbodies.Length > 0) return true;
return false;
}
private IEnumerator DespawnSyncable()
{
for (int i = 0; i < 3; i++)
{
gameObject?.SetActive(false);
yield return null;
}
yield break;
}
public bool IsPlugged()
{
if (plugs.Length == 0) return false;
for (int i = 0; i < plugs.Length; i++)
{
AlignPlug alignPlug = plugs[i];
if (alignPlug.GetSocket()) return true;
if (alignPlug._isEnterTransition) return true;
if (alignPlug._isExitTransition) return true;
}
return false;
}
private void _DiscardSyncable(bool force, bool despawn)
{
if (!this) return; // if object is destroyed, don't do anything
bool isRegistered = Registered;
if (isRegistered)
{
bool isPlugged = IsPlugged();
bool canDiscard = isOwner && (!isPlugged || force); // only owner can discard
//MelonLogger.Msg("Discarding syncable: " + transform.GetPath() + " force:" + force + " registered:" + isRegistered + " despawn:" + despawn + " isPlugged:" + isPlugged + " canDiscard:" + canDiscard);
//MelonLogger.Warning("Discarding registered syncable: " + transform.GetPath() + " force: " + force);
if (canDiscard)
{
_SendDiscard(true); // owner sends discard message
}
if (!canDiscard && !force) return;
}
//MelonLogger.Msg("Discarding syncable: " + transform.GetPath() + " force:" + force + " registered:" + isRegistered + " despawn:" + despawn + " isPlugged:" + IsPlugged());
try
{
EjectAllPlugs(true);
}
catch {
MelonLogger.Warning("Failed to eject plugs (should be fine)");
}
if (gameObject) syncablesCache.Remove(gameObject);
ObjectSyncCache.RemoveSyncable(this);
ResetSyncStatus();
Destroy(this); // delete the component
if (despawn) MelonCoroutines.Start(DespawnSyncable());
}
public void OnDestroy()
{
if (Registered)
{
MelonLogger.Warning("Destroying registered syncable: " + transform.GetPath());
}
DiscardSyncableImmediate(true, Registered);
//MelonLogger.Msg("Syncable destroyed: " + transform.GetPath());
}
private IEnumerator _FlagForDiscardCo(bool force, bool despawn) { yield return null; _DiscardSyncable(force, despawn); } // delay discard to prevent silly behavior
public void DiscardSyncable(bool force = false, bool despawn = false)
{
if (Registered && isOwner)
{
DiscardSyncableImmediate(force, despawn);
return;
}
MelonCoroutines.Start(_FlagForDiscardCo(force, despawn));
}
public void DiscardSyncableImmediate(bool force = false, bool despawn = false)
{
_DiscardSyncable(force, despawn);
}
public void OnDisable()
{
if (Registered && !isOwner)
{
MelonLogger.Warning("tried to disable non-owner syncable: " + transform.GetPath());
gameObject.SetActive(true);
return;
}
DiscardSyncable();
}
public void RegisterSyncable()
{
if (!BoneSync.IsConnected) return;
FindAndUpdateComponents();
if (Registered)
{
TryBecomeOwner();
return;
}
if (_attemptedRegister) return;
if (!CanBeSynced()) return;
_attemptedRegister = true;
_SendRegisterSync();
}
}
}