first commit

This commit is contained in:
2025-07-06 00:23:46 +02:00
commit 38f50c8819
1788 changed files with 112878 additions and 0 deletions

View 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);
}
}

View 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));
}
}

View 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;
}
}
}

View 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);
}
}
}

View 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;
}
}
}

View 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);
}
}
}