Files
BoneSync/BoneSync/Sync/ObjectSync.cs
2025-03-07 16:42:45 +02:00

341 lines
13 KiB
C#

using BoneSync.Data;
using BoneSync.Networking;
using BoneSync.Networking.Messages;
using BoneSync.Patching;
using BoneSync.Sync.Components;
using MelonLoader;
using StressLevelZero.Data;
using StressLevelZero.Interaction;
using StressLevelZero.Pool;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.SceneManagement;
using Random = UnityEngine.Random;
namespace BoneSync.Sync
{
internal class ObjectSync
{
private static ushort _nextSyncableId = 1;
public static ushort GetNextId()
{
return _nextSyncableId++;
}
public static RegisterSyncableInfo? GetRegisterInfo(Syncable syncable, bool allowClientRegister)
{
ulong ownerId = BoneSync.lobby.GetLocalId();
RegisterSyncableInfo? info = null;
string path = syncable.GetSyncableWorldPath();
if (path.Length > 0)
{
info = new RegisterSyncableInfo()
{
type = RegisterSyncType.RegisterFromPath,
transformPath = path,
ownerId = ownerId,
};
}
else if (syncable.poolee && syncable.poolee.pool && (BoneSync.lobby.IsHost || allowClientRegister))
{
string spawnableRealTitle = syncable?.poolee?.spawnObject?.title;
string spawnableAlternateTitle = syncable?.poolee?.pool?.name.Replace("pool - ", ""); // hacky, but use when real title is unknown
string spawnableTitle = spawnableRealTitle ?? spawnableAlternateTitle;
MelonLogger.Msg("Spawnable title: " + spawnableTitle);
info = new RegisterSyncableInfo()
{
ownerId = ownerId,
type = RegisterSyncType.RegisterAndSpawn,
spawnInfo = new SpawnPoolableInfo()
{
spawnableTitle = spawnableTitle,
spawnLocation = new SimpleSyncTransform(syncable.poolee.transform),
},
};
}
return info;
}
public static ushort SendRegisterSyncableMessage(Syncable syncable)
{
MelonLogger.Msg("Sending register syncable message for: " + syncable.transform.name);
RegisterSyncableInfo? infoNullable = GetRegisterInfo(syncable, syncable.ClientSpawningAllowed());
if (infoNullable == null)
{
MelonLogger.Warning("No valid registeration method for syncable");
return 0;
}
RegisterSyncableInfo info = infoNullable.Value;
if (BoneSync.lobby.IsHost)
{
info.id = GetNextId();
new RegisterSyncableMessage(info).Broadcast();
} else
{
info.callbackId = (ushort)Random.Range(1, ushort.MaxValue);
ObjectSyncCache.CallbackIdToSyncable[info.callbackId] = syncable;
new RegisterSyncableMessage(info).SendToHost();
}
MelonLogger.Msg("Sending register syncable message for: " + syncable.transform.name + " with id: " + info.id + " and type : " + info.type);
return info.id;
}
public static void SendObjectSyncMessage(Syncable syncable)
{
if (!syncable.Registered) return;
//MelonLogger.Msg("Sending object sync message for: " + syncable.transform.name);
ObjectSyncTransform[] objectSyncTransforms = syncable.GetObjectSyncTransforms();
ObjectSyncMessageData data = new ObjectSyncMessageData()
{
objectId = syncable.GetSyncId(),
objectSyncTransforms = objectSyncTransforms,
};
ObjectSyncMessage message = new ObjectSyncMessage(data);
message.Broadcast();
}
private static Syncable _MakeOrGetSyncable(GameObject gameObject, bool deleteSubSyncabled = true)
{
//Scene scene = gameObject.scene;
//MelonLogger.Msg("Making or getting syncable for: " + gameObject.name);
if (gameObject == null)
{
return null;
}
Syncable syncable = gameObject.GetComponent<Syncable>();
// delete all sub syncables
if (deleteSubSyncabled)
{
try
{
Syncable[] subSyncables = gameObject.GetComponentsInChildren<Syncable>(true);
for (int i = 0; i < subSyncables.Length; i++)
{
Syncable subSyncable = subSyncables[i];
if (subSyncable == syncable) continue;
if (subSyncable == null) continue;
bool isRegistered = subSyncable.Registered;
bool isPlugged = subSyncable.IsPlugged();
MelonLogger.Msg("Discarding subSyncable: " + subSyncable.transform.GetPath() + " registered:" + isRegistered + " plugged:" + isPlugged);
if (isRegistered || isPlugged) continue;
subSyncable.DiscardSyncable();
}
}
catch (Exception e)
{
MelonLogger.Warning("Failed to delete sub syncables: " + e.Message);
}
}
if (syncable == null)
{
syncable = gameObject.AddComponent<Syncable>();
// MelonLogger.Msg("Created syncable for: " + gameObject.name);
}
return syncable;
}
private static Syncable _GetSyncableFromCache(GameObject gameObject)
{
bool success = Syncable.syncablesCache.TryGetValue(gameObject, out Syncable syncable);
if (!success)
{
return null;
}
return syncable;
}
public static Syncable MakeOrGetSyncable(GameObject gameObject)
{
Syncable syncable = _GetSyncableFromCache(gameObject);
if (syncable == null)
{
syncable = _MakeOrGetSyncable(gameObject);
}
return syncable;
}
public static Syncable MakeOrGetSyncable(Poolee poolee)
{
return _MakeOrGetSyncable(poolee.gameObject);
}
public static Syncable MakeOrGetSyncable(InteractableHost interactableHost)
{
if (interactableHost.manager) return MakeOrGetSyncable(interactableHost.manager);
return _MakeOrGetSyncable(interactableHost.gameObject);
}
public static Syncable MakeOrGetSyncable(InteractableHostManager interactableHostManager)
{
return _MakeOrGetSyncable(interactableHostManager.gameObject, true);
}
public static Syncable SpawnPooleeAndMakeSyncable(SpawnPoolableInfo spawnInfo)
{
SimpleSyncTransform spawnLocation = spawnInfo.spawnLocation;
SpawnableObject spawnableObject = SpawnableManager.GetSpawnable(spawnInfo.spawnableTitle);
if (spawnableObject == null) {
MelonLogger.Warning("[SpawnPooleeAndMakeSyncable] Failed to find spawnable: " + spawnInfo.spawnableTitle);
return null;
}
Pool pool = SpawnableManager.GetPool(spawnableObject);
if (!pool)
{
MelonLogger.Warning("[SpawnPooleeAndMakeSyncable] Failed to find pool: " + spawnInfo.spawnableTitle);
return null;
}
Poolee poolee = CallPatchedMethods.InstantiatePoolee(pool, spawnLocation.position, spawnLocation.rotation, pool.Prefab.transform.localScale);
Syncable syncable = MakeOrGetSyncable(poolee);
MelonLogger.Msg("Spawned poolee with syncable: " + poolee.transform.GetPath());
return syncable;
}
public static void OnRegisterSyncMessage(RegisterSyncableMessage registerSyncableMessage)
{
MelonLogger.Msg("Received register sync message");
Syncable syncable = null;
RegisterSyncableInfo info = registerSyncableMessage.info;
if (BoneSync.lobby.IsHost)
{
info.id = GetNextId();
new RegisterSyncableMessage(info).Broadcast();
} else
{
// only host can register syncables, all spawn requests should be sent to host
if (registerSyncableMessage.senderId != BoneSync.lobby.GetHostId())
{
MelonLogger.Warning("Received register sync message from non-host: " + registerSyncableMessage.senderId);
return;
}
}
if (info.id == 0)
{
MelonLogger.Warning("Received register sync message with id 0");
return;
}
bool hasCallback = info.callbackId != 0;
if (hasCallback)
{
MelonLogger.Msg("Received register sync message with callback id: " + info.callbackId);
}
if (hasCallback && ObjectSyncCache.CallbackIdToSyncable.ContainsKey(info.callbackId))
{
MelonLogger.Msg("Found syncable for callback id: " + info.callbackId);
syncable = ObjectSyncCache.CallbackIdToSyncable[info.callbackId];
ObjectSyncCache.CallbackIdToSyncable.Remove(info.callbackId);
} else
{
switch (info.type)
{
case RegisterSyncType.RegisterFromPath:
MelonLogger.Msg("Registering syncable from path: " + info.transformPath + " with id: " + info.id);
syncable = ObjectSyncCache.GetSyncable(info.transformPath);
break;
case RegisterSyncType.RegisterAndSpawn:
MelonLogger.Msg("Registering and spawning syncable from pool: " + info.spawnInfo.Value.spawnableTitle + " with id: " + info.id);
syncable = SpawnPooleeAndMakeSyncable(info.spawnInfo.Value);
break;
}
}
if (!syncable)
{
MelonLogger.Warning("Failed to create/obtain syncable for register sync message "+ info.id);
return;
}
syncable.SetSyncId(info.id);
syncable.SetOwner(info.ownerId);
}
public static void OnObjectSyncMessage(ObjectSyncMessage objectSyncMessage)
{
ObjectSyncMessageData data = objectSyncMessage.objectSyncMessageData;
ushort objectId = data.objectId;
if (objectId == 0) return;
if (objectId >= _nextSyncableId && !BoneSync.lobby.IsHost)
{
_nextSyncableId = (ushort)(objectId + 1);
}
Syncable syncable = ObjectSyncCache.GetSyncable(objectId);
if (syncable == null)
{
//MelonLogger.Msg("SyncEvent: Syncable not found for id: " + objectId);
return;
}
syncable.ApplyObjectSyncTransforms(data.objectSyncTransforms);
}
public static void OnObjectDamageMessage(ObjectDamageMessage damageMessge)
{
ObjectDamageInfo damageInfo = damageMessge.objectEventInfo;
if (damageInfo.objectId == 0) return;
Syncable syncable = ObjectSyncCache.GetSyncable(damageInfo.objectId);
if (syncable == null)
{
//MelonLogger.Msg("DamageEvent: Syncable not found for id: " + damageInfo.objectId);
return;
}
syncable.Damage(damageInfo);
}
public static void SendObjectDamageMessage(Syncable syncable, ObjectDamageType damageType, ObjectHealthInfo healthInfo)
{
ObjectDamageInfo damageInfo = new ObjectDamageInfo()
{
objectId = syncable.GetSyncId(),
objectHealthInfo = healthInfo,
eventType = damageType,
};
ObjectDamageMessage message = new ObjectDamageMessage(damageInfo);
message.Broadcast();
message.Execute();
}
internal static void OnOwnershipChangeMessage(ushort syncId, ulong newOwnerId, bool force)
{
Syncable syncable = ObjectSyncCache.GetSyncable(syncId);
if (syncable == null)
{
MelonLogger.Warning("Ownership transfer request for non-existant syncable: " + syncId);
return;
}
if (force)
{
syncable.SetOwner(newOwnerId);
}
else
{
syncable.OnOwnershipTransferRequest(newOwnerId);
}
}
}
}