374 lines
12 KiB
C#
374 lines
12 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;
|
|
using BoneSync.Data;
|
|
|
|
|
|
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 bool isValid => this != null && gameObject != null;
|
|
|
|
public void SetInHolster(bool val)
|
|
{
|
|
SyncLogger.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;
|
|
|
|
public bool IsStatic() => rigidbodies.Length == 0;
|
|
private void CheckAutoSync()
|
|
{
|
|
FindAndUpdateComponents();
|
|
bool shouldAutoSync = ShouldAutoSync();
|
|
if (shouldAutoSync && (BoneSync.lobby.IsHost || ClientSpawningAllowed()))
|
|
{
|
|
SyncLogger.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;
|
|
CheckAutoSync();
|
|
yield return new WaitForSeconds(SceneSync.MAP_LOAD_GRACE_PERIOD);
|
|
CheckAutoSync(); // check again after grace period
|
|
}
|
|
|
|
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;
|
|
}
|
|
if (IsStatic() && buttonToggles.Length != 0) return true;
|
|
return false;
|
|
}
|
|
|
|
public string GetSyncableWorldPath()
|
|
{
|
|
if (!isValid) return "";
|
|
if (transform == null) return "";
|
|
if (poolee && poolee.pool)
|
|
{
|
|
return "";
|
|
}
|
|
if (interactableHost || interactableManager)
|
|
{
|
|
return transform.GetPath();
|
|
}
|
|
return "";
|
|
}
|
|
|
|
|
|
|
|
public void FindAndUpdateComponents()
|
|
{
|
|
if (!isValid) return;
|
|
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 (!isValid) return; // if object is destroyed, don't do anything
|
|
bool isRegistered = Registered;
|
|
|
|
//SyncLogger.Debug("Discarding syncable: " + transform.GetPath() + " force:" + force + " registered:" + isRegistered + " despawn:" + despawn + " isPlugged:" + IsPlugged());
|
|
|
|
if (isRegistered)
|
|
{
|
|
bool isPlugged = IsPlugged();
|
|
|
|
bool canDiscard = isOwner && (!isPlugged || force); // only owner can discard
|
|
SyncLogger.Debug("Discarding syncable: " + transform.GetPath() + " force:" + force + " registered:" + isRegistered + " despawn:" + despawn + " isPlugged:" + isPlugged + " canDiscard:" + canDiscard);
|
|
//SyncLogger.Warning("Discarding registered syncable: " + transform.GetPath() + " force: " + force);
|
|
|
|
if (canDiscard)
|
|
{
|
|
_SendDiscard(true); // owner sends discard message
|
|
}
|
|
|
|
if (!canDiscard && !force) return;
|
|
}
|
|
|
|
//SyncLogger.Msg("Discarding syncable: " + transform.GetPath() + " force:" + force + " registered:" + isRegistered + " despawn:" + despawn + " isPlugged:" + IsPlugged());
|
|
|
|
try
|
|
{
|
|
EjectAllPlugs(true);
|
|
}
|
|
catch {
|
|
SyncLogger.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)
|
|
{
|
|
SyncLogger.Warning("Destroying registered syncable: " + transform.GetPath());
|
|
}
|
|
DiscardSyncableImmediate(true, Registered);
|
|
//SyncLogger.Msg("Syncable destroyed: " + transform.GetPath());
|
|
}
|
|
|
|
private IEnumerator _FlagForDiscardCo(bool force, bool despawn) {
|
|
yield return null;
|
|
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)
|
|
{
|
|
SyncLogger.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();
|
|
}
|
|
}
|
|
}
|