first commit
This commit is contained in:
104
NitroxClient/GameLogic/Helper/BaseSerializationHelper.cs
Normal file
104
NitroxClient/GameLogic/Helper/BaseSerializationHelper.cs
Normal file
@@ -0,0 +1,104 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
|
||||
namespace NitroxClient.GameLogic.Helper;
|
||||
|
||||
public static class BaseSerializationHelper
|
||||
{
|
||||
public static byte[] CompressBytes(byte[] array)
|
||||
{
|
||||
if (array == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
using MemoryStream output = new();
|
||||
using DeflateStream stream = new(output, CompressionLevel.Optimal);
|
||||
CompressStream(stream, array);
|
||||
stream.Close();
|
||||
return output.ToArray();
|
||||
}
|
||||
|
||||
public static byte[] DecompressBytes(byte[] array, int size)
|
||||
{
|
||||
if (array == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
using MemoryStream input = new(array);
|
||||
using DeflateStream stream = new(input, CompressionMode.Decompress);
|
||||
return DecompressStream(stream, size);
|
||||
}
|
||||
|
||||
public static void CompressStream(Stream stream, byte[] array)
|
||||
{
|
||||
using BinaryWriter writer = new(stream);
|
||||
|
||||
ushort zeroCounter = 0;
|
||||
foreach (byte value in array)
|
||||
{
|
||||
if (value == 0 && zeroCounter != ushort.MaxValue)
|
||||
{
|
||||
zeroCounter++;
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.Write(zeroCounter);
|
||||
writer.Write(value);
|
||||
zeroCounter = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (zeroCounter != 0)
|
||||
{
|
||||
writer.Write(zeroCounter);
|
||||
}
|
||||
|
||||
writer.Close();
|
||||
}
|
||||
|
||||
public static byte[] DecompressStream(Stream stream, int size)
|
||||
{
|
||||
using BinaryReader reader = new(stream);
|
||||
byte[] result = new byte[size];
|
||||
|
||||
int i = 0;
|
||||
bool zeroPart = true;
|
||||
while (i < size)
|
||||
{
|
||||
if (zeroPart)
|
||||
{
|
||||
ushort zeroLength = reader.ReadUInt16();
|
||||
|
||||
for (int c = 0; c < zeroLength; c++)
|
||||
{
|
||||
result[i] = 0;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
result[i] = reader.ReadByte();
|
||||
i++;
|
||||
}
|
||||
|
||||
zeroPart = !zeroPart;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static byte[] CompressData<TInput>(TInput[] array, Converter<TInput, byte> converter)
|
||||
{
|
||||
return CompressBytes(Array.ConvertAll(array, converter));
|
||||
}
|
||||
|
||||
public static TInput[] DecompressData<TInput>(byte[] array, int size, Converter<byte, TInput> converter)
|
||||
{
|
||||
if (array == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return Array.ConvertAll(DecompressBytes(array, size), converter);
|
||||
}
|
||||
}
|
37
NitroxClient/GameLogic/Helper/BatteryChildEntityHelper.cs
Normal file
37
NitroxClient/GameLogic/Helper/BatteryChildEntityHelper.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using NitroxModel.DataStructures.GameLogic.Entities;
|
||||
using NitroxModel.DataStructures.GameLogic;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel_Subnautica.DataStructures;
|
||||
using System.Collections.Generic;
|
||||
using UWE;
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using NitroxModel.Core;
|
||||
|
||||
namespace NitroxClient.GameLogic.Helper;
|
||||
|
||||
/// <summary>
|
||||
/// Vehicles and items are created without a battery loaded into them. Subnautica usually spawns these in async; however, this
|
||||
/// is disabled in nitrox so we can properly tag the id. Here we create the installed battery (with a new NitroxId) and have the
|
||||
/// entity spawner take care of loading it in.
|
||||
/// </summary>
|
||||
public static class BatteryChildEntityHelper
|
||||
{
|
||||
private static readonly Lazy<Entities> entities = new (() => NitroxServiceLocator.LocateService<Entities>());
|
||||
|
||||
public static void TryPopulateInstalledBattery(GameObject gameObject, List<Entity> toPopulate, NitroxId parentId)
|
||||
{
|
||||
if (gameObject.TryGetComponent(out EnergyMixin energyMixin))
|
||||
{
|
||||
PopulateInstalledBattery(energyMixin, toPopulate, parentId);
|
||||
}
|
||||
}
|
||||
|
||||
public static void PopulateInstalledBattery(EnergyMixin energyMixin, List<Entity> toPopulate, NitroxId parentId)
|
||||
{
|
||||
InstalledBatteryEntity installedBattery = new(new NitroxId(), energyMixin.defaultBattery.ToDto(), null, parentId, new List<Entity>());
|
||||
toPopulate.Add(installedBattery);
|
||||
|
||||
CoroutineHost.StartCoroutine(entities.Value.SpawnEntityAsync(installedBattery));
|
||||
}
|
||||
}
|
36
NitroxClient/GameLogic/Helper/EquipmentHelper.cs
Normal file
36
NitroxClient/GameLogic/Helper/EquipmentHelper.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NitroxModel.DataStructures.Util;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxClient.GameLogic.Helper
|
||||
{
|
||||
public class EquipmentHelper
|
||||
{
|
||||
private static readonly List<Func<GameObject, Equipment>> equipmentFinders = new()
|
||||
{
|
||||
o => o.GetComponent<Charger>().AliveOrNull()?.equipment,
|
||||
o => o.GetComponent<BaseNuclearReactor>().AliveOrNull()?.equipment,
|
||||
o => o.GetComponent<CyclopsDecoyLoadingTube>().AliveOrNull()?.decoySlots,
|
||||
o => o.GetComponent<Exosuit>().AliveOrNull()?.modules,
|
||||
o => o.GetComponent<SeaMoth>().AliveOrNull()?.modules,
|
||||
o => o.GetComponent<UpgradeConsole>().AliveOrNull()?.modules,
|
||||
o => o.GetComponent<Vehicle>().AliveOrNull()?.modules,
|
||||
o => o.GetComponent<VehicleUpgradeConsoleInput>().AliveOrNull()?.equipment,
|
||||
o => string.Equals("Player", o.GetComponent<Player>().AliveOrNull()?.name, StringComparison.InvariantCulture) ? Inventory.main.equipment : null
|
||||
};
|
||||
|
||||
public static Optional<Equipment> FindEquipmentComponent(GameObject owner)
|
||||
{
|
||||
foreach (Func<GameObject, Equipment> equipmentFinder in equipmentFinders)
|
||||
{
|
||||
Equipment equipment = equipmentFinder(owner);
|
||||
if (equipment != null)
|
||||
{
|
||||
return Optional.Of(equipment);
|
||||
}
|
||||
}
|
||||
return Optional.Empty;
|
||||
}
|
||||
}
|
||||
}
|
106
NitroxClient/GameLogic/Helper/InventoryContainerHelper.cs
Normal file
106
NitroxClient/GameLogic/Helper/InventoryContainerHelper.cs
Normal file
@@ -0,0 +1,106 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using NitroxClient.GameLogic.Bases;
|
||||
using NitroxClient.GameLogic.PlayerLogic;
|
||||
using NitroxClient.Unity.Helper;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.DataStructures.Util;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxClient.GameLogic.Helper
|
||||
{
|
||||
public class InventoryContainerHelper
|
||||
{
|
||||
private static readonly Regex LockerRegex = new(@"Locker0([0-9])StorageRoot$", RegexOptions.IgnoreCase);
|
||||
private const string LOCKER_BASE_NAME = "submarine_locker_01_0";
|
||||
private const string PLAYER_OBJECT_NAME = "Player";
|
||||
private const string ESCAPEPOD_OBJECT_NAME = "EscapePod";
|
||||
|
||||
public static Optional<ItemsContainer> TryGetContainerByOwner(GameObject owner)
|
||||
{
|
||||
SeamothStorageContainer seamothStorageContainer = owner.GetComponent<SeamothStorageContainer>();
|
||||
if (seamothStorageContainer)
|
||||
{
|
||||
return Optional.Of(seamothStorageContainer.container);
|
||||
}
|
||||
StorageContainer storageContainer = owner.GetComponentInChildren<StorageContainer>(true);
|
||||
if (storageContainer)
|
||||
{
|
||||
return Optional.Of(storageContainer.container);
|
||||
}
|
||||
BaseBioReactor baseBioReactor = owner.GetComponentInChildren<BaseBioReactor>(true);
|
||||
if (baseBioReactor)
|
||||
{
|
||||
return Optional.Of(baseBioReactor.container);
|
||||
}
|
||||
if (owner.name == PLAYER_OBJECT_NAME)
|
||||
{
|
||||
return Optional.Of(Inventory.Get().container);
|
||||
}
|
||||
RemotePlayerIdentifier remotePlayerId = owner.GetComponent<RemotePlayerIdentifier>();
|
||||
if (remotePlayerId)
|
||||
{
|
||||
return Optional.Of(remotePlayerId.RemotePlayer.Inventory);
|
||||
}
|
||||
|
||||
return Optional.Empty;
|
||||
}
|
||||
|
||||
|
||||
public static bool TryGetOwnerId(Transform ownerTransform, out NitroxId ownerId)
|
||||
{
|
||||
Transform parent = ownerTransform.parent;
|
||||
if (!parent)
|
||||
{
|
||||
Log.Error("Trying to get the ownerId of a storage that doesn't have a parent");
|
||||
ownerId = null;
|
||||
return false;
|
||||
}
|
||||
// TODO: in the future maybe use a switch on the PrefabId (it's always the same structure in a prefab)
|
||||
// and then statically look for the right object because we'll know exactly which one it is
|
||||
|
||||
// To treat the WaterPark in parent case, we need its case to happen before the IBaseModule one because
|
||||
// IBaseModule will get the WaterPark but not get the id on the right object like in the first case
|
||||
if (parent.TryGetComponent(out WaterPark waterPark))
|
||||
{
|
||||
return waterPark.planter.TryGetIdOrWarn(out ownerId);
|
||||
}
|
||||
else if (parent.GetComponent<Constructable>() || parent.GetComponent<IBaseModule>().AliveOrNull())
|
||||
{
|
||||
return parent.TryGetIdOrWarn(out ownerId);
|
||||
}
|
||||
else if (parent.TryGetComponentInParent(out LargeRoomWaterPark largeRoomWaterPark, true) &&
|
||||
parent.TryGetNitroxId(out ownerId))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
// For regular water parks, the main object contains the StorageRoot and the planter at the same level
|
||||
else if (LockerRegex.IsMatch(ownerTransform.gameObject.name))
|
||||
{
|
||||
string lockerId = ownerTransform.gameObject.name.Substring(7, 1);
|
||||
string lockerName = $"{LOCKER_BASE_NAME}{lockerId}";
|
||||
GameObject locker = parent.gameObject.FindChild(lockerName);
|
||||
if (!locker)
|
||||
{
|
||||
Log.Error($"Could not find Locker Object: {lockerName}");
|
||||
ownerId = null;
|
||||
return false;
|
||||
}
|
||||
if (!locker.TryGetComponentInChildren(out StorageContainer storageContainer, true))
|
||||
{
|
||||
Log.Error($"Could not find {nameof(StorageContainer)} From Object: {lockerName}");
|
||||
ownerId = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
return storageContainer.TryGetIdOrWarn(out ownerId);
|
||||
}
|
||||
else if (parent.name.StartsWith(ESCAPEPOD_OBJECT_NAME))
|
||||
{
|
||||
StorageContainer storageContainer = parent.RequireComponentInChildren<StorageContainer>(true);
|
||||
return storageContainer.TryGetIdOrWarn(out ownerId);
|
||||
}
|
||||
|
||||
return parent.TryGetIdOrWarn(out ownerId);
|
||||
}
|
||||
}
|
||||
}
|
56
NitroxClient/GameLogic/Helper/TransientLocalObjectManager.cs
Normal file
56
NitroxClient/GameLogic/Helper/TransientLocalObjectManager.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NitroxModel.DataStructures.Util;
|
||||
|
||||
namespace NitroxClient.GameLogic.Helper
|
||||
{
|
||||
/**
|
||||
* Class used for temporarily storing variables local to patched methods. Certain circumstances require that these
|
||||
* be referenced at a later point and most of the time it is too prohibitive to expose global statics.
|
||||
*
|
||||
* An example use-case is the created gameobject from the vehicle constructor class. This gameobject is only accessible
|
||||
* locally when crafted. We need to access it at future times to retrieve and set its GUID.
|
||||
*/
|
||||
public static class TransientLocalObjectManager
|
||||
{
|
||||
public enum TransientObjectType
|
||||
{
|
||||
BASE_GHOST_NEWLY_CONSTRUCTED_BASE_GAMEOBJECT,
|
||||
|
||||
LATEST_DECONSTRUCTED_BASE_PIECE_GHOST,
|
||||
LATEST_DECONSTRUCTED_BASE_PIECE_GUID,
|
||||
|
||||
LATER_CONSTRUCTED_BASE,
|
||||
LATER_OBJECT_LATEST_BASE,
|
||||
LATER_OBJECT_LATEST_CELL,
|
||||
}
|
||||
|
||||
private static readonly Dictionary<TransientObjectType, object> localObjectsById = new();
|
||||
|
||||
public static void Add(TransientObjectType key, object o)
|
||||
{
|
||||
localObjectsById[key] = o;
|
||||
}
|
||||
|
||||
public static void Remove(TransientObjectType key)
|
||||
{
|
||||
localObjectsById.Remove(key);
|
||||
}
|
||||
|
||||
public static Optional<object> Get(TransientObjectType key)
|
||||
{
|
||||
localObjectsById.TryGetValue(key, out object obj);
|
||||
return Optional.OfNullable(obj);
|
||||
}
|
||||
|
||||
public static T Require<T>(TransientObjectType key)
|
||||
{
|
||||
if (!localObjectsById.TryGetValue(key, out object obj))
|
||||
{
|
||||
throw new Exception($"Did not have an entry for key: {key}");
|
||||
}
|
||||
|
||||
return (T)obj;
|
||||
}
|
||||
}
|
||||
}
|
69
NitroxClient/GameLogic/Helper/VehicleChildEntityHelper.cs
Normal file
69
NitroxClient/GameLogic/Helper/VehicleChildEntityHelper.cs
Normal file
@@ -0,0 +1,69 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.DataStructures.GameLogic;
|
||||
using NitroxModel.DataStructures.GameLogic.Entities;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxClient.GameLogic.Helper;
|
||||
|
||||
public static class VehicleChildEntityHelper
|
||||
{
|
||||
private static readonly HashSet<Type> interactiveChildTypes = new HashSet<Type> // we must sync ids of these types when creating vehicles (mainly cyclops)
|
||||
{
|
||||
typeof(Openable),
|
||||
typeof(CyclopsLocker),
|
||||
typeof(Fabricator),
|
||||
typeof(FireExtinguisherHolder),
|
||||
typeof(StorageContainer),
|
||||
typeof(SeamothStorageContainer),
|
||||
typeof(VehicleDockingBay),
|
||||
typeof(DockedVehicleHandTarget),
|
||||
typeof(UpgradeConsole),
|
||||
typeof(DockingBayDoor),
|
||||
typeof(CyclopsDecoyLoadingTube),
|
||||
typeof(BatterySource),
|
||||
typeof(SubNameInput),
|
||||
typeof(WeldablePoint),
|
||||
typeof(CyclopsVehicleStorageTerminalManager),
|
||||
typeof(CyclopsLightingPanel)
|
||||
};
|
||||
|
||||
public static void PopulateChildren(NitroxId vehicleId, string vehiclePath, List<Entity> toPopulate, GameObject current)
|
||||
{
|
||||
string currentPath = current.GetFullHierarchyPath();
|
||||
string relativePathName = currentPath.Replace(vehiclePath, string.Empty).TrimStart('/');
|
||||
|
||||
if (relativePathName.Length > 0)
|
||||
{
|
||||
// generate PathBasedChildEntities for gameObjects under the main vehicle.
|
||||
foreach (MonoBehaviour mono in current.GetComponents<MonoBehaviour>())
|
||||
{
|
||||
// We don't to accidentally tag this game object unless we know it has an applicable mono
|
||||
if (interactiveChildTypes.Contains(mono.GetType()))
|
||||
{
|
||||
NitroxId id = NitroxEntity.GetIdOrGenerateNew(mono.gameObject);
|
||||
|
||||
PathBasedChildEntity pathBasedChildEntity = new(relativePathName, id, null, null, vehicleId, new());
|
||||
toPopulate.Add(pathBasedChildEntity);
|
||||
|
||||
if (mono is BatterySource batterySource) // cyclops has a battery source as a deeply-nested child
|
||||
{
|
||||
BatteryChildEntityHelper.PopulateInstalledBattery(batterySource, pathBasedChildEntity.ChildEntities, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// both seamoth and exosuit have energymixin as a direct component. populate the battery if it exists
|
||||
BatteryChildEntityHelper.TryPopulateInstalledBattery(current, toPopulate, vehicleId);
|
||||
}
|
||||
|
||||
foreach (Transform child in current.transform)
|
||||
{
|
||||
PopulateChildren(vehicleId, vehiclePath, toPopulate, child.gameObject);
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user