using System.Collections;
using System.Collections.Generic;
using System.Linq;
using NitroxClient.Communication;
using NitroxClient.GameLogic.Spawning.WorldEntities;
using NitroxModel.Packets;
using UnityEngine;
namespace NitroxClient.MonoBehaviours.Cyclops;
///
/// Script responsible for creating a virtual counterpart of every cyclops, which will always be horizontal and motionless so that simulated movement is always clear.
/// Contains a pawn for each player entering the regular cyclops.
///
public class VirtualCyclops : MonoBehaviour
{
public static VirtualCyclops Instance;
public const string NAME = "VirtualCyclops";
private static readonly Dictionary cacheColliderCopy = [];
private readonly Dictionary virtualOpenableByName = [];
private readonly Dictionary realOpenableByName = [];
private readonly Dictionary virtualConstructableByRealGameObject = [];
public NitroxCyclops Cyclops;
public Transform axis;
private Rigidbody rigidbody;
private Vector3 InitialPosition;
private Quaternion InitialRotation;
public static void Initialize()
{
CreateVirtualCyclops();
Multiplayer.OnAfterMultiplayerEnd += Dispose;
}
public static void Dispose()
{
Destroy(Instance.gameObject);
Instance = null;
Multiplayer.OnAfterMultiplayerEnd -= Dispose;
}
///
/// Initializes the object with reduced utility to ensure the virtual cyclops won't be eating too much performance.
///
public static void CreateVirtualCyclops()
{
if (Instance)
{
return;
}
LightmappedPrefabs.main.RequestScenePrefab("cyclops", (cyclopsPrefab) =>
{
SubConsoleCommand.main.OnSubPrefabLoaded(cyclopsPrefab);
GameObject model = SubConsoleCommand.main.GetLastCreatedSub();
model.name = NAME;
Vector3 position = Vector3.up * 500;
Quaternion rotation = Quaternion.identity;
model.transform.position = position;
model.transform.rotation = rotation;
Instance = model.AddComponent();
Instance.axis = model.GetComponent().subAxis;
GameObject.Destroy(model.GetComponent());
GameObject.Destroy(model.GetComponent());
GameObject.Destroy(model.GetComponent());
GameObject.Destroy(model.GetComponent());
Instance.InitialPosition = position;
Instance.InitialRotation = rotation;
Instance.rigidbody = Instance.GetComponent();
Instance.rigidbody.constraints = RigidbodyConstraints.FreezeAll;
model.GetComponent().enabled = false;
model.GetComponent().lockInterpolation = false;
model.GetComponent().stabilizerEnabled = false;
model.GetComponent().isKinematic = true;
model.GetComponent().invincible = true;
Instance.RegisterVirtualOpenables();
Instance.ToggleRenderers(false);
Instance.DisableBadComponents();
model.SetActive(true);
});
}
public static IEnumerator InitializeConstructablesCache()
{
List constructableTechTypes = [];
CraftData.GetBuilderGroupTech(TechGroup.InteriorModules, constructableTechTypes, true);
CraftData.GetBuilderGroupTech(TechGroup.Miscellaneous, constructableTechTypes, true);
TaskResult result = new();
foreach (TechType techType in constructableTechTypes)
{
yield return DefaultWorldEntitySpawner.RequestPrefab(techType, result);
if (result.value && result.value.GetComponent())
{
// We immediately destroy the copy because we only want to cache it for now
Destroy(CreateColliderCopy(result.value, techType));
}
}
}
public void Populate()
{
foreach (Constructable constructable in Cyclops.GetComponentsInChildren(true))
{
ReplicateConstructable(constructable);
}
foreach (Openable openable in Cyclops.GetComponentsInChildren(true))
{
openable.blocked = false;
ReplicateOpening(openable, openable.isOpen);
realOpenableByName.Add(openable.name, openable);
}
}
public void Depopulate()
{
foreach (GameObject virtualObject in virtualConstructableByRealGameObject.Values)
{
Destroy(virtualObject);
}
virtualConstructableByRealGameObject.Clear();
foreach (Openable openable in realOpenableByName.Values)
{
openable.blocked = false;
}
realOpenableByName.Clear();
}
public void SetCurrentCyclops(NitroxCyclops nitroxCyclops)
{
if (Cyclops)
{
Cyclops.Virtual = null;
Depopulate();
Cyclops = null;
}
Cyclops = nitroxCyclops;
if (Cyclops)
{
Populate();
}
}
public void Update()
{
transform.position = InitialPosition;
transform.rotation = InitialRotation;
}
public void ToggleRenderers(bool toggled)
{
foreach (Renderer renderer in transform.GetComponentsInChildren(true))
{
renderer.enabled = toggled;
}
}
private void RegisterVirtualOpenables()
{
foreach (Openable openable in transform.GetComponentsInChildren(true))
{
virtualOpenableByName.Add(openable.name, openable);
}
}
private void DisableBadComponents()
{
CyclopsLightingPanel cyclopsLightingPanel = GetComponentInChildren(true);
cyclopsLightingPanel.floodlightsOn = false;
cyclopsLightingPanel.lightingOn = false;
cyclopsLightingPanel.SetExternalLighting(false);
cyclopsLightingPanel.cyclopsRoot.ForceLightingState(false);
cyclopsLightingPanel.enabled = false;
Destroy(cyclopsLightingPanel);
// Disable a source of useless logs
foreach (FMOD_CustomEmitter customEmitter in GetComponentsInChildren(true))
{
customEmitter.enabled = false;
}
foreach (PlayerCinematicController cinematicController in GetComponentsInChildren(true))
{
cinematicController.enabled = false;
}
}
public void ReplicateOpening(Openable openable, bool openState)
{
if (virtualOpenableByName.TryGetValue(openable.name, out Openable virtualOpenable))
{
using (PacketSuppressor.Suppress())
{
virtualOpenable.PlayOpenAnimation(openState, virtualOpenable.animTime);
}
}
}
public void ReplicateBlock(Openable openable, bool blockState)
{
if (realOpenableByName.TryGetValue(openable.name, out Openable realOpenable))
{
realOpenable.blocked = blockState;
}
}
public void ReplicateConstructable(Constructable constructable)
{
if (virtualConstructableByRealGameObject.ContainsKey(constructable.gameObject))
{
return;
}
GameObject colliderCopy = CreateColliderCopy(constructable.gameObject, constructable.techType);
colliderCopy.transform.parent = transform;
colliderCopy.transform.CopyLocals(constructable.transform);
virtualConstructableByRealGameObject.Add(constructable.gameObject, colliderCopy);
}
///
/// Creates an empty shell simulating the presence of modules by copying its children containing a collider.
///
public static GameObject CreateColliderCopy(GameObject realObject, TechType techType)
{
if (cacheColliderCopy.TryGetValue(techType, out GameObject colliderCopy))
{
return GameObject.Instantiate(colliderCopy);
}
colliderCopy = new GameObject($"{realObject.name}-collidercopy");
// This will act as a prefab but will stay in the material world so we put it out of hands in the meantime
colliderCopy.transform.position = Vector3.up * 1000 + Vector3.right * 10 * cacheColliderCopy.Count;
Transform transform = realObject.transform;
Dictionary copiedTransformByRealTransform = [];
copiedTransformByRealTransform[transform] = colliderCopy.transform;
IEnumerable uniqueColliderObjects = realObject.GetComponentsInChildren(true).Select(c => c.gameObject).Distinct();
foreach (GameObject colliderObject in uniqueColliderObjects)
{
GameObject copiedColliderObject = new(colliderObject.name);
copiedColliderObject.transform.CopyLocals(colliderObject.transform);
foreach (Collider collider in colliderObject.GetComponents())
{
collider.CopyComponent(copiedColliderObject);
}
// "child" is always a copied transform looking for its copied parent
Transform child = copiedColliderObject.transform;
// "parent" is always the real parent of the real transform corresponding to "child"
Transform parent = colliderObject.transform.parent;
while (!copiedTransformByRealTransform.ContainsKey(parent))
{
Transform copiedParent = copiedTransformByRealTransform[parent] = Instantiate(parent);
child.SetParent(copiedParent, false);
child = copiedParent;
parent = parent.parent;
}
// At the top of the tree we can simply stick the latest child to the collider
child.SetParent(colliderCopy.transform, false);
}
cacheColliderCopy.Add(techType, colliderCopy);
return GameObject.Instantiate(colliderCopy);
}
public void UnregisterConstructable(GameObject realObject)
{
if (virtualConstructableByRealGameObject.TryGetValue(realObject, out GameObject virtualConstructable))
{
Destroy(virtualConstructable);
virtualConstructableByRealGameObject.Remove(realObject);
}
}
}