first commit
This commit is contained in:
16
NitroxServer-Subnautica/App.config
Normal file
16
NitroxServer-Subnautica/App.config
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
|
||||
</startup>
|
||||
<runtime>
|
||||
<!-- This is required so that assembly loading won't throw error for unsigned assemblies. -->
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<probing privatePath="lib" />
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Mono.Cecil" publicKeyToken="50cebf1cceb9d05e" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-0.10.4.0" newVersion="0.10.4.0" />
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
</configuration>
|
56
NitroxServer-Subnautica/AppMutex.cs
Normal file
56
NitroxServer-Subnautica/AppMutex.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace NitroxServer_Subnautica;
|
||||
|
||||
public static class AppMutex
|
||||
{
|
||||
private static readonly SemaphoreSlim mutexReleaseGate = new(1);
|
||||
private static readonly SemaphoreSlim callerGate = new(1);
|
||||
|
||||
public static void Hold(Action onWaitingForMutex = null, CancellationToken ct = default)
|
||||
{
|
||||
Thread thread = new(o =>
|
||||
{
|
||||
bool first = true;
|
||||
Mutex mutex = new(false, typeof(AppMutex).Assembly.FullName, out bool _);
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
while (!mutex.WaitOne(100, false))
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
if (first)
|
||||
{
|
||||
first = false;
|
||||
onWaitingForMutex?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (AbandonedMutexException)
|
||||
{
|
||||
// Mutex was abandoned in another process, it will still get acquired
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
callerGate.Release();
|
||||
mutexReleaseGate.Wait(-1);
|
||||
mutex.ReleaseMutex();
|
||||
}
|
||||
});
|
||||
mutexReleaseGate.Wait(-1, ct);
|
||||
callerGate.Wait(0, ct);
|
||||
thread.Start();
|
||||
|
||||
while (!callerGate.Wait(100, ct))
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public static void Release()
|
||||
{
|
||||
mutexReleaseGate.Release();
|
||||
}
|
||||
}
|
102
NitroxServer-Subnautica/Communication/IpcHost.cs
Normal file
102
NitroxServer-Subnautica/Communication/IpcHost.cs
Normal file
@@ -0,0 +1,102 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Pipes;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxServer_Subnautica.Communication;
|
||||
|
||||
/// <summary>
|
||||
/// Exposes an IPC channel for other local processes to communicate with the server.
|
||||
/// </summary>
|
||||
public class IpcHost : IDisposable
|
||||
{
|
||||
private readonly CancellationTokenSource commandReadCancellation;
|
||||
private readonly NamedPipeServerStream server = new($"Nitrox Server {NitroxEnvironment.CurrentProcessId}", PipeDirection.In, 1);
|
||||
|
||||
private IpcHost(CancellationTokenSource commandReadCancellation)
|
||||
{
|
||||
this.commandReadCancellation = commandReadCancellation;
|
||||
}
|
||||
|
||||
public static IpcHost StartReadingCommands(Action<string> onCommandReceived, CancellationToken cancellationToken = default)
|
||||
{
|
||||
Log.Info("Starting IPC host for command input");
|
||||
ArgumentNullException.ThrowIfNull(onCommandReceived);
|
||||
|
||||
IpcHost host = new(CancellationTokenSource.CreateLinkedTokenSource(cancellationToken));
|
||||
Thread thread = new(async () =>
|
||||
{
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
string command = await host.ReadStringAsync(cancellationToken);
|
||||
onCommandReceived(command);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
});
|
||||
thread.IsBackground = true;
|
||||
thread.Start();
|
||||
return host;
|
||||
}
|
||||
|
||||
public async Task<string> ReadStringAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (!await WaitForConnection())
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
byte[] sizeBytes = new byte[4];
|
||||
await server.ReadExactlyAsync(sizeBytes, cancellationToken);
|
||||
byte[] stringBytes = new byte[BitConverter.ToUInt32(sizeBytes)];
|
||||
await server.ReadExactlyAsync(stringBytes, cancellationToken);
|
||||
|
||||
return Encoding.UTF8.GetString(stringBytes);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
commandReadCancellation?.Cancel();
|
||||
server.Dispose();
|
||||
}
|
||||
|
||||
private async Task<bool> WaitForConnection()
|
||||
{
|
||||
if (server.IsConnected)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
try
|
||||
{
|
||||
await server.WaitForConnectionAsync();
|
||||
return true;
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
try
|
||||
{
|
||||
server.Disconnect();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
@@ -0,0 +1,21 @@
|
||||
using NitroxModel_Subnautica.Packets;
|
||||
using NitroxServer.Communication.Packets.Processors.Abstract;
|
||||
using NitroxServer.GameLogic;
|
||||
|
||||
namespace NitroxServer_Subnautica.Communication.Packets.Processors
|
||||
{
|
||||
class CyclopsDamagePointRepairedProcessor : AuthenticatedPacketProcessor<CyclopsDamagePointRepaired>
|
||||
{
|
||||
private readonly PlayerManager playerManager;
|
||||
|
||||
public CyclopsDamagePointRepairedProcessor(PlayerManager playerManager)
|
||||
{
|
||||
this.playerManager = playerManager;
|
||||
}
|
||||
|
||||
public override void Process(CyclopsDamagePointRepaired packet, NitroxServer.Player simulatingPlayer)
|
||||
{
|
||||
playerManager.SendPacketToOtherPlayers(packet, simulatingPlayer);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,26 @@
|
||||
using NitroxModel_Subnautica.Packets;
|
||||
using NitroxServer.Communication.Packets.Processors.Abstract;
|
||||
using NitroxServer.GameLogic;
|
||||
|
||||
namespace NitroxServer_Subnautica.Communication.Packets.Processors
|
||||
{
|
||||
/// <summary>
|
||||
/// This is the absolute damage state. The current simulation owner is the only one who sends this packet to the server
|
||||
/// </summary>
|
||||
public class CyclopsDamageProcessor : AuthenticatedPacketProcessor<CyclopsDamage>
|
||||
{
|
||||
private readonly PlayerManager playerManager;
|
||||
|
||||
public CyclopsDamageProcessor(PlayerManager playerManager)
|
||||
{
|
||||
this.playerManager = playerManager;
|
||||
}
|
||||
|
||||
public override void Process(CyclopsDamage packet, NitroxServer.Player simulatingPlayer)
|
||||
{
|
||||
Log.Debug($"New cyclops damage from {simulatingPlayer.Id} {packet}");
|
||||
|
||||
playerManager.SendPacketToOtherPlayers(packet, simulatingPlayer);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,21 @@
|
||||
using NitroxModel_Subnautica.Packets;
|
||||
using NitroxServer.Communication.Packets.Processors.Abstract;
|
||||
using NitroxServer.GameLogic;
|
||||
|
||||
namespace NitroxServer_Subnautica.Communication.Packets.Processors
|
||||
{
|
||||
class CyclopsFireCreatedProcessor : AuthenticatedPacketProcessor<CyclopsFireCreated>
|
||||
{
|
||||
private readonly PlayerManager playerManager;
|
||||
|
||||
public CyclopsFireCreatedProcessor(PlayerManager playerManager)
|
||||
{
|
||||
this.playerManager = playerManager;
|
||||
}
|
||||
|
||||
public override void Process(CyclopsFireCreated packet, NitroxServer.Player simulatingPlayer)
|
||||
{
|
||||
playerManager.SendPacketToOtherPlayers(packet, simulatingPlayer);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,81 @@
|
||||
using System.Collections.Generic;
|
||||
using NitroxModel.DataStructures.GameLogic;
|
||||
using NitroxModel_Subnautica.DataStructures;
|
||||
using NitroxServer.GameLogic.Entities;
|
||||
|
||||
namespace NitroxServer_Subnautica.GameLogic.Entities;
|
||||
|
||||
public class SimulationWhitelist : ISimulationWhitelist
|
||||
{
|
||||
/// <inheritdoc cref="ISimulationWhitelist.MovementWhitelist" />
|
||||
public static readonly HashSet<NitroxTechType> MovementWhitelist = new()
|
||||
{
|
||||
TechType.Shocker.ToDto(),
|
||||
TechType.Biter.ToDto(),
|
||||
TechType.Blighter.ToDto(),
|
||||
TechType.BoneShark.ToDto(),
|
||||
TechType.Crabsnake.ToDto(),
|
||||
TechType.CrabSquid.ToDto(),
|
||||
TechType.Crash.ToDto(),
|
||||
TechType.GhostLeviathan.ToDto(),
|
||||
TechType.GhostLeviathanJuvenile.ToDto(),
|
||||
TechType.GhostRayBlue.ToDto(),
|
||||
TechType.GhostRayRed.ToDto(),
|
||||
TechType.Mesmer.ToDto(),
|
||||
TechType.LavaLizard.ToDto(),
|
||||
TechType.LavaEyeye.ToDto(),
|
||||
TechType.LavaBoomerang.ToDto(),
|
||||
TechType.LargeFloater.ToDto(),
|
||||
TechType.LargeKoosh.ToDto(),
|
||||
TechType.SpineEel.ToDto(),
|
||||
TechType.Spinefish.ToDto(),
|
||||
TechType.Sandshark.ToDto(),
|
||||
TechType.SeaDragon.ToDto(),
|
||||
TechType.SeaEmperor.ToDto(),
|
||||
TechType.SeaEmperorBaby.ToDto(),
|
||||
TechType.SeaEmperorJuvenile.ToDto(),
|
||||
TechType.SeaEmperorLeviathan.ToDto(),
|
||||
TechType.ReaperLeviathan.ToDto(),
|
||||
TechType.Stalker.ToDto(),
|
||||
TechType.Warper.ToDto(),
|
||||
TechType.Bladderfish.ToDto(),
|
||||
TechType.Boomerang.ToDto(),
|
||||
TechType.Cutefish.ToDto(),
|
||||
TechType.Eyeye.ToDto(),
|
||||
TechType.Jellyray.ToDto(),
|
||||
TechType.GarryFish.ToDto(),
|
||||
TechType.Gasopod.ToDto(),
|
||||
TechType.HoleFish.ToDto(),
|
||||
TechType.Hoopfish.ToDto(),
|
||||
TechType.Hoverfish.ToDto(),
|
||||
TechType.Oculus.ToDto(),
|
||||
TechType.RabbitRay.ToDto(),
|
||||
TechType.Reefback.ToDto(),
|
||||
TechType.Reginald.ToDto(),
|
||||
TechType.SeaTreader.ToDto(),
|
||||
TechType.Skyray.ToDto(),
|
||||
TechType.Spadefish.ToDto(),
|
||||
TechType.Spinefish.ToDto(),
|
||||
TechType.BlueAmoeba.ToDto(),
|
||||
TechType.Shuttlebug.ToDto(),
|
||||
TechType.CaveCrawler.ToDto(),
|
||||
TechType.Floater.ToDto(),
|
||||
TechType.LavaLarva.ToDto(),
|
||||
TechType.Rockgrub.ToDto(),
|
||||
TechType.Shuttlebug.ToDto(),
|
||||
TechType.Bloom.ToDto(),
|
||||
TechType.RockPuncher.ToDto(),
|
||||
TechType.Peeper.ToDto(),
|
||||
TechType.Jumper.ToDto(),
|
||||
TechType.Constructor.ToDto()
|
||||
};
|
||||
|
||||
/// <inheritdoc cref="ISimulationWhitelist.UtilityWhitelist" />
|
||||
public static readonly HashSet<NitroxTechType> UtilityWhitelist = new()
|
||||
{
|
||||
TechType.CrashHome.ToDto()
|
||||
};
|
||||
|
||||
HashSet<NitroxTechType> ISimulationWhitelist.MovementWhitelist => MovementWhitelist;
|
||||
HashSet<NitroxTechType> ISimulationWhitelist.UtilityWhitelist => UtilityWhitelist;
|
||||
}
|
@@ -0,0 +1,15 @@
|
||||
using NitroxModel.DataStructures.GameLogic.Entities;
|
||||
using NitroxModel.DataStructures.GameLogic.Entities.Metadata;
|
||||
using NitroxServer.GameLogic.Entities.Spawning;
|
||||
using NitroxServer.Helper;
|
||||
|
||||
namespace NitroxServer_Subnautica.GameLogic.Entities.Spawning.EntityBootstrappers;
|
||||
|
||||
public class CrashHomeBootstrapper : IEntityBootstrapper
|
||||
{
|
||||
public void Prepare(ref WorldEntity entity, DeterministicGenerator deterministicBatchGenerator)
|
||||
{
|
||||
// Set 0 for spawnTime so that CrashHome.Update can spawn a Crash if Start() couldn't
|
||||
entity.Metadata = new CrashHomeMetadata(0);
|
||||
}
|
||||
}
|
@@ -0,0 +1,17 @@
|
||||
using NitroxModel.DataStructures.GameLogic.Entities;
|
||||
using NitroxServer.GameLogic.Entities.Spawning;
|
||||
using NitroxServer.Helper;
|
||||
|
||||
namespace NitroxServer_Subnautica.GameLogic.Entities.Spawning;
|
||||
|
||||
public class GeyserBootstrapper : IEntityBootstrapper
|
||||
{
|
||||
public void Prepare(ref WorldEntity entity, DeterministicGenerator deterministicBatchGenerator)
|
||||
{
|
||||
entity = new GeyserWorldEntity(entity.Transform, entity.Level, entity.ClassId,
|
||||
entity.SpawnedByServer, entity.Id, entity.TechType,
|
||||
entity.Metadata, entity.ParentId, entity.ChildEntities,
|
||||
XORRandom.NextFloat(), 15 * XORRandom.NextFloat());
|
||||
// The value 15 doesn't mean anything in particular, it's just an initial eruption time window so geysers don't all erupt at the same time at first
|
||||
}
|
||||
}
|
@@ -0,0 +1,125 @@
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.DataStructures.GameLogic;
|
||||
using NitroxModel.DataStructures.GameLogic.Entities;
|
||||
using NitroxModel.DataStructures.Unity;
|
||||
using NitroxServer.GameLogic.Entities.Spawning;
|
||||
using NitroxServer.Helper;
|
||||
using static NitroxServer_Subnautica.GameLogic.Entities.Spawning.ReefbackSpawnData;
|
||||
|
||||
namespace NitroxServer_Subnautica.GameLogic.Entities.Spawning;
|
||||
|
||||
public class ReefbackBootstrapper : IEntityBootstrapper
|
||||
{
|
||||
private readonly float creatureProbabilitySum = 0;
|
||||
private readonly float plantsProbabilitySum = 0;
|
||||
|
||||
public ReefbackBootstrapper()
|
||||
{
|
||||
foreach (ReefbackSlotCreature creature in SpawnableCreatures)
|
||||
{
|
||||
creatureProbabilitySum += creature.Probability;
|
||||
}
|
||||
foreach (ReefbackSlotPlant plant in SpawnablePlants)
|
||||
{
|
||||
plantsProbabilitySum += plant.Probability;
|
||||
}
|
||||
}
|
||||
|
||||
public void Prepare(ref WorldEntity entity, DeterministicGenerator generator)
|
||||
{
|
||||
// From ReefbackLife.Initialize
|
||||
if (entity.Transform.LocalScale.X <= 0.8f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// In case the grassIndex is chosen randomly
|
||||
int grassIndex = XORRandom.NextIntRange(1, GRASS_VARIANTS_COUNT);
|
||||
|
||||
entity = new ReefbackEntity(entity.Transform, entity.Level, entity.ClassId,
|
||||
entity.SpawnedByServer, entity.Id, entity.TechType,
|
||||
entity.Metadata, entity.ParentId, entity.ChildEntities,
|
||||
grassIndex, entity.Transform.Position);
|
||||
|
||||
NitroxTransform plantSlotsRootTransform = DuplicateTransform(PlantSlotsRootTransform);
|
||||
plantSlotsRootTransform.SetParent(entity.Transform, false);
|
||||
|
||||
// ReefbackLife.SpawnPlants equivalent
|
||||
for (int i = 0; i < PLANT_SLOTS_COUNT; i++)
|
||||
{
|
||||
NitroxTransform slotTransform = DuplicateTransform(PlantSlotsCoordinates[i]);
|
||||
slotTransform.SetParent(plantSlotsRootTransform, false);
|
||||
|
||||
float random = XORRandom.NextFloat() * plantsProbabilitySum;
|
||||
float totalProbability = 0f;
|
||||
int chosenPlantIndex = 0;
|
||||
for (int k = 0; k < SpawnablePlants.Count; k++)
|
||||
{
|
||||
totalProbability += SpawnablePlants[k].Probability;
|
||||
if (random <= totalProbability)
|
||||
{
|
||||
chosenPlantIndex = k;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ReefbackSlotPlant slotPlant = SpawnablePlants[chosenPlantIndex];
|
||||
string randomId = slotPlant.ClassIds[XORRandom.NextIntRange(0, slotPlant.ClassIds.Count)];
|
||||
|
||||
NitroxId id = generator.NextId();
|
||||
NitroxTransform plantTransform = new(slotTransform.Position, slotPlant.StartRotationQuaternion, NitroxVector3.One);
|
||||
plantTransform.SetParent(plantSlotsRootTransform);
|
||||
// It is necessary to set parent to null afterwards so that the entity doesn't accidentally modifies the transform by losing reference to the parent
|
||||
plantTransform.SetParent(null, false);
|
||||
|
||||
ReefbackChildEntity plantEntity = new(plantTransform, entity.Level, randomId, true, id, NitroxTechType.None, null, entity.Id, [],
|
||||
ReefbackChildEntity.ReefbackChildType.PLANT);
|
||||
|
||||
entity.ChildEntities.Add(plantEntity);
|
||||
}
|
||||
|
||||
NitroxTransform creatureSlotsRootTransform = DuplicateTransform(CreatureSlotsRootTransform);
|
||||
creatureSlotsRootTransform.SetParent(entity.Transform, false);
|
||||
|
||||
// ReefbackLife.SpawnCreatures equivalent
|
||||
for (int i = 0; i < CREATURE_SLOTS_COUNT; i++)
|
||||
{
|
||||
NitroxTransform slotTransform = DuplicateTransform(CreatureSlotsCoordinates[i]);
|
||||
slotTransform.SetParent(creatureSlotsRootTransform, false);
|
||||
|
||||
float random = XORRandom.NextFloat() * creatureProbabilitySum;
|
||||
float totalProbability = 0f;
|
||||
int chosenCreatureIndex = 0;
|
||||
for (int k = 0; k < SpawnableCreatures.Count; k++)
|
||||
{
|
||||
totalProbability += SpawnableCreatures[k].Probability;
|
||||
if (random <= totalProbability)
|
||||
{
|
||||
chosenCreatureIndex = k;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ReefbackSlotCreature slotCreature = SpawnableCreatures[chosenCreatureIndex];
|
||||
int spawnCount = XORRandom.NextIntRange(slotCreature.MinNumber, slotCreature.MaxNumber + 1);
|
||||
for (int j = 0; j < spawnCount; j++)
|
||||
{
|
||||
NitroxId id = generator.NextId();
|
||||
NitroxTransform creatureTransform = new(slotTransform.LocalPosition + XORRandom.NextInsideSphere(5f), slotTransform.LocalRotation, NitroxVector3.One);
|
||||
creatureTransform.SetParent(CreatureSlotsRootTransform, false);
|
||||
creatureTransform.SetParent(null, false);
|
||||
|
||||
ReefbackChildEntity creatureEntity = new(creatureTransform, entity.Level, slotCreature.ClassId, true, id, NitroxTechType.None, null, entity.Id, [],
|
||||
ReefbackChildEntity.ReefbackChildType.CREATURE);
|
||||
|
||||
|
||||
entity.ChildEntities.Add(creatureEntity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static NitroxTransform DuplicateTransform(NitroxTransform transform)
|
||||
{
|
||||
return new(transform.LocalPosition, transform.LocalRotation, transform.LocalScale);
|
||||
}
|
||||
}
|
@@ -0,0 +1,133 @@
|
||||
using System.Collections.Generic;
|
||||
using NitroxModel.DataStructures.Unity;
|
||||
|
||||
namespace NitroxServer_Subnautica.GameLogic.Entities.Spawning;
|
||||
|
||||
/// <summary>
|
||||
/// Data from <see cref="ReefbackSlotsData"/> generated by ReefbackLife_OnEnable_Patch.cs
|
||||
/// </summary>
|
||||
public static class ReefbackSpawnData
|
||||
{
|
||||
public const int PLANT_SLOTS_COUNT = 28;
|
||||
public const int CREATURE_SLOTS_COUNT = 10;
|
||||
public const int GRASS_VARIANTS_COUNT = 3;
|
||||
|
||||
public static readonly NitroxTransform PlantSlotsRootTransform = new(new(0f, 0.49f, -0.22f), new(0.5039341f, 0.4992565f, 0.4960347f, -0.5007424f), new(1f, 1f, 1f));
|
||||
public static readonly NitroxTransform CreatureSlotsRootTransform = new(new(0f, 0f, 0f), new(0f, 0f, 0f, 1f), new(1f, 1f, 1f));
|
||||
|
||||
public static List<ReefbackSlotCreature> SpawnableCreatures { get; } =
|
||||
[
|
||||
new() { Probability = 1f, MinNumber = 1, MaxNumber = 2, ClassId = "3fcd548b-781f-46ba-b076-7412608deeef" },
|
||||
new() { Probability = 1f, MinNumber = 1, MaxNumber = 2, ClassId = "fa4cfe65-4eaf-4d51-ba0d-e8cc9632fd47" },
|
||||
new() { Probability = 1f, MinNumber = 1, MaxNumber = 2, ClassId = "0a993944-87d3-441e-b21d-6c314f723cc7" },
|
||||
new() { Probability = 1f, MinNumber = 1, MaxNumber = 2, ClassId = "bf9ccd04-60af-4144-aaa1-4ac184c686c2" },
|
||||
new() { Probability = 1f, MinNumber = 1, MaxNumber = 1, ClassId = "79c1aef0-e505-469c-ab36-c22c76aeae44" },
|
||||
new() { Probability = 1f, MinNumber = 1, MaxNumber = 1, ClassId = "495befa0-0e6b-400d-9734-227e5a732f75" },
|
||||
new() { Probability = 1f, MinNumber = 1, MaxNumber = 1, ClassId = "284ceeb6-b437-4aca-a8bd-d54f336cbef8" },
|
||||
new() { Probability = 1f, MinNumber = 1, MaxNumber = 2, ClassId = "cf171ce2-e3d2-4cec-9757-60dbd480e486" },
|
||||
new() { Probability = 1f, MinNumber = 1, MaxNumber = 2, ClassId = "d040bec1-0368-4f7c-aed6-93b5e1852d45" },
|
||||
new() { Probability = 0.5f, MinNumber = 1, MaxNumber = 3, ClassId = "4064a71a-c464-4db2-942a-56391fe69951" },
|
||||
new() { Probability = 1f, MinNumber = 1, MaxNumber = 1, ClassId = "ce23b9ee-fd98-4677-9919-20248356f7cf" },
|
||||
new() { Probability = 1f, MinNumber = 1, MaxNumber = 1, ClassId = "8ffbb5b5-21b4-4687-9118-730d59330c9a" },
|
||||
new() { Probability = 1f, MinNumber = 1, MaxNumber = 1, ClassId = "a7b70c23-8e57-43e0-ab39-e02a29341376" },
|
||||
new() { Probability = 1f, MinNumber = 1, MaxNumber = 1, ClassId = "08cb3290-504b-4191-97ee-6af1588af5c0" },
|
||||
];
|
||||
|
||||
public static List<ReefbackSlotPlant> SpawnablePlants { get; } =
|
||||
[
|
||||
new() { ClassIds = ["061af756-643c-42ad-9645-a522f1338084", "93a9886d-f2d3-4b6c-8e5f-216f569f82b2"], Probability = 1f, StartRotation = new(270f, 0f, 0f) },
|
||||
new() { ClassIds = ["fc7c1098-13af-417a-8038-0053b65498e5", "61a5e0e6-01d5-4ae2-aea6-1186cd769025", "31834aae-35ce-49c1-b5ba-ac4227750679", "99cdec62-302b-4999-ba49-f50c73575a4d"], Probability = 2f, StartRotation = new(270f, 0f, 0f) },
|
||||
new() { ClassIds = ["e80b22ff-064d-46ca-b71e-456d6b3426ab"], Probability = 1f, StartRotation = new(0f, 0f, 0f) },
|
||||
new() { ClassIds = ["6d9e37de-f808-4621-a762-e0d6340b30dc"], Probability = 1f, StartRotation = new(270f, 0f, 0f) },
|
||||
new() { ClassIds = ["242b7f63-7553-456b-8d16-b318040097ae", "1fcbf0f8-01fd-4454-a48d-3b3266e5b84e", "11bd0c8e-6d57-46cb-928a-f0e825726674", "7c55e785-a250-41ae-869d-c4be026f9ce6", "43a597df-da05-4f5f-92df-29d76c0b2f53", "133ae1eb-99ec-4b1d-b32b-9c9daf144b8f"], Probability = 1f, StartRotation = new(0f, 0f, 0f) },
|
||||
new() { ClassIds = ["fb941ab6-9c74-4673-b6a5-2dcb40720d34"], Probability = 1f, StartRotation = new(270f, 0f, 0f) },
|
||||
new() { ClassIds = ["7f656699-358a-416d-9ecd-f911e3d51bf1", "54dad6b2-77c8-4f9a-9294-2621ca296754"], Probability = 1f, StartRotation = new(225f, 0f, 0f) },
|
||||
new() { ClassIds = ["e8047056-e202-49b3-829f-7458615103ac", "3dbab1b9-cc52-4da4-8633-89b33add18f4"], Probability = 1f, StartRotation = new(270f, 0f, 0f) },
|
||||
new() { ClassIds = ["e0608e57-e9df-4f43-bb3a-8c56a42d2c1f", "1edd7411-8f1d-4e7a-8378-0ce7ccb6ea82", "48d6184a-320e-41d2-abca-5b96a94e72e0", "3d4d3892-e43a-45b1-85b8-4a6462257c79"], Probability = 0.3f, StartRotation = new(270f, 0f, 0f) },
|
||||
new() { ClassIds = ["34b59c1d-876e-4962-a8f7-e205d189d2be", "1f384257-9d4a-4307-829f-024c0e1ce1c0", "0719b0fa-95df-4b37-a581-4f1e07424c62", "28fb4ab7-e1eb-4de3-89a9-98f54394e0f6"], Probability = 0.3f, StartRotation = new(270f, 0f, 0f) },
|
||||
new() { ClassIds = ["2d970c98-6f77-4270-8be2-91dc863d15d5", "eb6634e5-3a58-4a0d-ae4e-b673e1fa51ea", "df03263c-ebfb-4e7c-b002-1ec3d67c1215", "c197a6ca-f910-43db-92ab-2e35e423a6f1"], Probability = 0.3f, StartRotation = new(270f, 0f, 0f) },
|
||||
new() { ClassIds = ["70eb6270-bf5e-4d6a-8182-484ffcfd8de6", "f0713f3d-586b-4c71-88a3-18dd6c3dd2a4", "9a643563-9278-4c77-8bd2-f9b4b1a1053a", "4e31161e-c812-4c8c-bfd4-00cf4b743884"], Probability = 0.3f, StartRotation = new(270f, 0f, 0f) },
|
||||
new() { ClassIds = ["171c6a5b-879b-4785-be7a-6584b2c8c442"], Probability = 1f, StartRotation = new(270f, 0f, 0f) },
|
||||
new() { ClassIds = ["84794dd0-2c70-4239-9536-230d56811ad4"], Probability = 1f, StartRotation = new(0f, 0f, 0f) },
|
||||
new() { ClassIds = ["aa1abbb9-716c-44b8-a2b8-cb4d9d0f22bb", "7ecc9cdd-3afc-4005-bff7-01ba62e95a03", "26940e53-d3eb-4770-ae99-6ce4335445d3", "c87e584c-7e38-4589-b408-8eca51f474c1", "a71da66c-6d43-45c1-bc7f-a789cfc61e46"], Probability = 2f, StartRotation = new(0f, 0f, 0f) },
|
||||
new() { ClassIds = ["22bf7b03-8154-410b-a6fb-8ba315f68987", "450bf7b5-b6cf-4139-921f-3cb9ea505d5f", "c71f41ce-b586-4e85-896e-d25e8b5b9de0", "598c95d8-7420-4907-8f70-ba18b4e6adcb"], Probability = 1.5f, StartRotation = new(0f, 0f, 0f) },
|
||||
new() { ClassIds = ["36fcb5c8-07f6-4d20-b026-f8c41b8e2358"], Probability = 1f, StartRotation = new(0f, 0f, 0f) },
|
||||
new() { ClassIds = ["4525e0f3-9c9a-449f-8d6c-48088711ac99"], Probability = 1f, StartRotation = new(0f, 0f, 0f) },
|
||||
new() { ClassIds = ["b707aa52-1a27-43c4-9500-f346befb8251"], Probability = 1f, StartRotation = new(0f, 0f, 0f) },
|
||||
new() { ClassIds = ["1a806d20-dc8f-4e6e-9281-f353ed155abf"], Probability = 1f, StartRotation = new(0f, 0f, 0f) },
|
||||
new() { ClassIds = ["4601400c-5e12-4e4a-9e45-4cab5f06a598"], Probability = 1f, StartRotation = new(0f, 0f, 0f) },
|
||||
new() { ClassIds = ["31ccc496-c26b-4ed9-8e86-3334582d8d5b", "4bc33bd6-cfa1-46a7-bac8-074ba3b76044"], Probability = 3f, StartRotation = new(0f, 0f, 0f) },
|
||||
];
|
||||
|
||||
public static List<NitroxTransform> CreatureSlotsCoordinates { get; } =
|
||||
[
|
||||
new(new(-22.9f, 17f, 0f), new(0f, 0f, 0f, 1f), new(1f, 1f, 1f)),
|
||||
new(new(5.1f, 17.9f, 22.12f), new(0f, 0f, 0f, 1f), new(1f, 1f, 1f)),
|
||||
new(new(5.1f, 17.9f, -11.6f), new(0f, 0f, 0f, 1f), new(1f, 1f, 1f)),
|
||||
new(new(-5.1f, 17.9f, 5.57f), new(0f, 0f, 0f, 1f), new(1f, 1f, 1f)),
|
||||
new(new(23.62f, 17.9f, 5.57f), new(0f, 0f, 0f, 1f), new(1f, 1f, 1f)),
|
||||
new(new(-16.4f, 17.9f, 25.9f), new(0f, 0f, 0f, 1f), new(1f, 1f, 1f)),
|
||||
new(new(-8.7f, 17.9f, -30.3f), new(0f, 0f, 0f, 1f), new(1f, 1f, 1f)),
|
||||
new(new(15.4f, 17.9f, -30.3f), new(0f, 0f, 0f, 1f), new(1f, 1f, 1f)),
|
||||
new(new(20.9f, 17.9f, -13.9f), new(0f, 0f, 0f, 1f), new(1f, 1f, 1f)),
|
||||
new(new(-17.3f, 17.9f, 22.12f), new(0f, 0f, 0f, 1f), new(1f, 1f, 1f)),
|
||||
];
|
||||
|
||||
public static List<NitroxTransform> PlantSlotsCoordinates { get; } =
|
||||
[
|
||||
new(new(-4.460001f, 0f, 10f), new(0.6152681f, -0.3477891f, -0.2830537f, 0.6483584f), new(1f, 1f, 1f)),
|
||||
new(new(-3.481001f, 6.719f, 8.196002f), new(0.4696728f, -0.1773425f, -0.3922334f, 0.7707854f), new(1f, 1f, 1f)),
|
||||
new(new(7.452f, 2.863f, 8.1f), new(0.5340302f, 0.3158718f, 0.2073223f, 0.7563427f), new(1f, 1f, 1f)),
|
||||
new(new(5.12f, -7.519994f, 7.599998f), new(0.7192559f, 0.07982004f, -0.09835583f, 0.6831002f), new(1f, 1f, 1f)),
|
||||
new(new(15.631f, 2.149f, 7.193f), new(0.4893234f, 0.05595651f, -0.1356857f, 0.8596632f), new(1f, 1f, 1f)),
|
||||
new(new(7.3761f, 6.8008f, 6.9998f), new(-0.5506698f, -0.5092787f, -0.486309f, -0.4482206f), new(1f, 1f, 1f)),
|
||||
new(new(20.231f, 6.098f, 3.22f), new(-0.580992f, -0.5104024f, 0.01308015f, -0.6338507f), new(1f, 1f, 1f)),
|
||||
new(new(17.307f, -2.754f, 6.889f), new(-0.6737934f, -0.3733953f, -0.5835185f, -0.2570692f), new(1f, 1f, 1f)),
|
||||
new(new(10.411f, -8.146f, 6.401f), new(-0.4313123f, -0.6596806f, -0.2941538f, -0.5406151f), new(1f, 1f, 1f)),
|
||||
new(new(10.903f, -12.232f, 6.880001f), new(-0.7411356f, -0.3174673f, -0.1293507f, -0.5772356f), new(1f, 1f, 1f)),
|
||||
new(new(-11.188f, 5.817f, 7.423f), new(-0.6473981f, 0.005354047f, -0.5044534f, -0.5712914f), new(1f, 1f, 1f)),
|
||||
new(new(-7.186f, -5.598001f, 9.095995f), new(-0.5481309f, -0.3323998f, -0.6145787f, -0.4597346f), new(1f, 1f, 1f)),
|
||||
new(new(18.337f, -9.234001f, 3.704004f), new(-0.54471f, -0.6272081f, -0.5153005f, -0.2106334f), new(1f, 1f, 1f)),
|
||||
new(new(3.708f, 14.976f, 7.343f), new(-0.5388643f, -0.5702056f, -0.4023229f, -0.4718338f), new(1f, 1f, 1f)),
|
||||
new(new(4.624f, -2.568f, 8.295f), new(-0.4445406f, -0.4816067f, -0.7076716f, -0.2638929f), new(1f, 1f, 1f)),
|
||||
new(new(2.332f, -12.735f, 8.085f), new(-0.5660617f, -0.5615287f, -0.575052f, -0.1832343f), new(1f, 1f, 1f)),
|
||||
new(new(8.237998f, 14.162f, 6.878995f), new(-0.3394944f, -0.7236302f, -0.4614432f, -0.3849328f), new(1f, 1f, 1f)),
|
||||
new(new(-8.105976f, 1.567999f, 9.326996f), new(0.1392731f, -0.7048326f, -0.6932754f, -0.05642127f), new(1f, 1f, 1f)),
|
||||
new(new(-11.8123f, -2.4774f, 7.2488f), new(-0.5196269f, -0.4540258f, -0.4950719f, -0.5279701f), new(1f, 1f, 1f)),
|
||||
new(new(0.7281f, 3.8064f, 8.772f), new(-0.5932296f, -0.4087047f, -0.4483426f, -0.5291768f), new(1f, 1f, 1f)),
|
||||
new(new(0.1910008f, -4.7408f, 8.755607f), new(-0.7313722f, -0.04450077f, -0.1555427f, -0.6625111f), new(1f, 1f, 1f)),
|
||||
new(new(15.488f, 10.689f, 5.394005f), new(-0.6516617f, -0.3269425f, -0.2105236f, -0.6512492f), new(1f, 1f, 1f)),
|
||||
new(new(21.61f, 0.04200077f, 4.873005f), new(-0.6519138f, -0.6227682f, -0.315786f, -0.2957152f), new(1f, 1f, 1f)),
|
||||
new(new(-12.5287f, -10.26078f, 6.387112f), new(-0.3034183f, -0.62968f, -0.6713617f, -0.2464023f), new(1f, 1f, 1f)),
|
||||
new(new(-10.744f, 10.656f, 6.481f), new(-0.4014427f, -0.5361503f, -0.452841f, -0.5884911f), new(1f, 1f, 1f)),
|
||||
new(new(-1.322f, -8.544998f, 8.412007f), new(-0.3098519f, -0.268013f, -0.6520668f, -0.6379418f), new(1f, 1f, 1f)),
|
||||
new(new(0.9206981f, 11.9358f, 7.261901f), new(-0.2040951f, -0.5998042f, -0.6168172f, -0.4670296f), new(1f, 1f, 1f)),
|
||||
new(new(9.23f, -0.908f, 9.564f), new(-0.6778914f, -0.05161315f, 0.0538789f, -0.7313663f), new(1f, 1f, 1f)),
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Based on <see cref="ReefbackSlotsData.ReefbackSlotCreature"/>
|
||||
/// </summary>
|
||||
public struct ReefbackSlotCreature
|
||||
{
|
||||
public int MinNumber;
|
||||
public int MaxNumber;
|
||||
public float Probability;
|
||||
public string ClassId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Based on <see cref="ReefbackSlotsData.ReefbackSlotPlant"/>
|
||||
/// </summary>
|
||||
public struct ReefbackSlotPlant
|
||||
{
|
||||
public List<string> ClassIds;
|
||||
public float Probability;
|
||||
public NitroxVector3 StartRotation
|
||||
{
|
||||
set => StartRotationQuaternion = NitroxQuaternion.FromEuler(value);
|
||||
}
|
||||
|
||||
public NitroxQuaternion StartRotationQuaternion;
|
||||
}
|
||||
}
|
@@ -0,0 +1,34 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace NitroxServer_Subnautica.GameLogic.Entities.Spawning
|
||||
{
|
||||
public static class SlotsHelper
|
||||
{
|
||||
private static Dictionary<EntitySlotData.EntitySlotType, EntitySlot.Type> typeMapping = new Dictionary<EntitySlotData.EntitySlotType, EntitySlot.Type>
|
||||
{
|
||||
{ EntitySlotData.EntitySlotType.Small, EntitySlot.Type.Small },
|
||||
{ EntitySlotData.EntitySlotType.Medium, EntitySlot.Type.Medium },
|
||||
{ EntitySlotData.EntitySlotType.Large, EntitySlot.Type.Large },
|
||||
{ EntitySlotData.EntitySlotType.Tall, EntitySlot.Type.Tall },
|
||||
{ EntitySlotData.EntitySlotType.Creature, EntitySlot.Type.Creature }
|
||||
};
|
||||
|
||||
public static List<EntitySlot.Type> ConvertSlotTypes(EntitySlotData.EntitySlotType entitySlotType)
|
||||
{
|
||||
List<EntitySlot.Type> slotsTypes = new List<EntitySlot.Type>();
|
||||
|
||||
foreach (KeyValuePair<EntitySlotData.EntitySlotType, EntitySlot.Type> mapping in typeMapping)
|
||||
{
|
||||
EntitySlotData.EntitySlotType slotType = mapping.Key;
|
||||
EntitySlot.Type type = mapping.Value;
|
||||
|
||||
if ((entitySlotType & slotType) == slotType)
|
||||
{
|
||||
slotsTypes.Add(type);
|
||||
}
|
||||
}
|
||||
|
||||
return slotsTypes;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
using NitroxModel.DataStructures.GameLogic.Entities;
|
||||
using NitroxModel.DataStructures.GameLogic.Entities.Metadata;
|
||||
using NitroxServer.GameLogic.Entities.Spawning;
|
||||
using NitroxServer.Helper;
|
||||
|
||||
namespace NitroxServer_Subnautica.GameLogic.Entities.Spawning;
|
||||
|
||||
public class StayAtLeashPositionBootstrapper : IEntityBootstrapper
|
||||
{
|
||||
public void Prepare(ref WorldEntity spawnedEntity, DeterministicGenerator generator)
|
||||
{
|
||||
spawnedEntity.Metadata = new StayAtLeashPositionMetadata(spawnedEntity.Transform.Position);
|
||||
}
|
||||
}
|
@@ -0,0 +1,34 @@
|
||||
using System.Collections.Generic;
|
||||
using NitroxModel.DataStructures.GameLogic;
|
||||
using NitroxModel.DataStructures.GameLogic.Entities;
|
||||
using NitroxModel_Subnautica.DataStructures;
|
||||
using NitroxServer.GameLogic.Entities.Spawning;
|
||||
using NitroxServer.Helper;
|
||||
using NitroxServer_Subnautica.GameLogic.Entities.Spawning.EntityBootstrappers;
|
||||
|
||||
namespace NitroxServer_Subnautica.GameLogic.Entities.Spawning;
|
||||
|
||||
public class SubnauticaEntityBootstrapperManager : IEntityBootstrapperManager
|
||||
{
|
||||
private static readonly Dictionary<NitroxTechType, IEntityBootstrapper> entityBootstrappersByTechType = new()
|
||||
{
|
||||
[TechType.CrashHome.ToDto()] = new CrashHomeBootstrapper(),
|
||||
[TechType.ReaperLeviathan.ToDto()] = new StayAtLeashPositionBootstrapper(),
|
||||
[TechType.SeaDragon.ToDto()] = new StayAtLeashPositionBootstrapper(),
|
||||
[TechType.GhostLeviathan.ToDto()] = new StayAtLeashPositionBootstrapper(),
|
||||
};
|
||||
private static readonly Dictionary<string, IEntityBootstrapper> entityBootstrappersByClassId = new()
|
||||
{
|
||||
["ce0b4131-86e2-444b-a507-45f7b824a286"] = new GeyserBootstrapper(),
|
||||
["8d3d3c8b-9290-444a-9fea-8e5493ecd6fe"] = new ReefbackBootstrapper()
|
||||
};
|
||||
|
||||
public void PrepareEntityIfRequired(ref WorldEntity spawnedEntity, DeterministicGenerator generator)
|
||||
{
|
||||
if (entityBootstrappersByTechType.TryGetValue(spawnedEntity.TechType, out IEntityBootstrapper bootstrapper) ||
|
||||
(!string.IsNullOrEmpty(spawnedEntity.ClassId) && entityBootstrappersByClassId.TryGetValue(spawnedEntity.ClassId, out bootstrapper)))
|
||||
{
|
||||
bootstrapper.Prepare(ref spawnedEntity, generator);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,70 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NitroxModel.DataStructures.GameLogic;
|
||||
using NitroxModel.DataStructures.Unity;
|
||||
using NitroxModel_Subnautica.DataStructures;
|
||||
using NitroxServer.GameLogic.Entities.Spawning;
|
||||
using NitroxServer.UnityStubs;
|
||||
|
||||
namespace NitroxServer_Subnautica.GameLogic.Entities.Spawning
|
||||
{
|
||||
public class SubnauticaEntitySpawnPointFactory : EntitySpawnPointFactory
|
||||
{
|
||||
private readonly Dictionary<string, EntitySpawnPoint> spawnPointsByUid = new Dictionary<string, EntitySpawnPoint>();
|
||||
|
||||
public override List<EntitySpawnPoint> From(AbsoluteEntityCell absoluteEntityCell, NitroxTransform transform, GameObject gameObject)
|
||||
{
|
||||
List<EntitySpawnPoint> spawnPoints = new List<EntitySpawnPoint>();
|
||||
EntitySlotsPlaceholder entitySlotsPlaceholder = gameObject.GetComponent<EntitySlotsPlaceholder>();
|
||||
|
||||
if (gameObject.CreateEmptyObject)
|
||||
{
|
||||
SerializedEntitySpawnPoint entitySpawnPoint = new(gameObject.SerializedComponents, gameObject.Layer, absoluteEntityCell, transform);
|
||||
|
||||
HandleParenting(spawnPoints, entitySpawnPoint, gameObject);
|
||||
spawnPoints.Add(entitySpawnPoint);
|
||||
}
|
||||
else if (!ReferenceEquals(entitySlotsPlaceholder, null))
|
||||
{
|
||||
foreach (EntitySlotData entitySlotData in entitySlotsPlaceholder.slotsData)
|
||||
{
|
||||
List<EntitySlot.Type> slotTypes = SlotsHelper.ConvertSlotTypes(entitySlotData.allowedTypes);
|
||||
List<string> stringSlotTypes = slotTypes.Select(s => s.ToString()).ToList();
|
||||
EntitySpawnPoint entitySpawnPoint = new(absoluteEntityCell,
|
||||
entitySlotData.localPosition.ToDto(),
|
||||
entitySlotData.localRotation.ToDto(),
|
||||
stringSlotTypes,
|
||||
entitySlotData.density,
|
||||
entitySlotData.biomeType.ToString());
|
||||
|
||||
|
||||
HandleParenting(spawnPoints, entitySpawnPoint, gameObject);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
EntitySpawnPoint entitySpawnPoint = new(absoluteEntityCell, transform.LocalPosition, transform.LocalRotation, transform.LocalScale, gameObject.ClassId);
|
||||
|
||||
HandleParenting(spawnPoints, entitySpawnPoint, gameObject);
|
||||
}
|
||||
|
||||
return spawnPoints;
|
||||
}
|
||||
|
||||
private void HandleParenting(List<EntitySpawnPoint> spawnPoints, EntitySpawnPoint entitySpawnPoint, GameObject gameObject)
|
||||
{
|
||||
if (gameObject.Parent != null && spawnPointsByUid.TryGetValue(gameObject.Parent, out EntitySpawnPoint parent))
|
||||
{
|
||||
entitySpawnPoint.Parent = parent;
|
||||
parent.Children.Add(entitySpawnPoint);
|
||||
}
|
||||
|
||||
spawnPointsByUid[gameObject.Id] = entitySpawnPoint;
|
||||
|
||||
if (gameObject.Parent == null)
|
||||
{
|
||||
spawnPoints.Add(entitySpawnPoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
21
NitroxServer-Subnautica/GameLogic/SubnauticaWorldModifier.cs
Normal file
21
NitroxServer-Subnautica/GameLogic/SubnauticaWorldModifier.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using NitroxModel.DataStructures.GameLogic.Entities;
|
||||
using NitroxServer.GameLogic;
|
||||
using NitroxServer.Serialization.World;
|
||||
|
||||
namespace NitroxServer_Subnautica.GameLogic;
|
||||
|
||||
public class SubnauticaWorldModifier : IWorldModifier
|
||||
{
|
||||
// This constant is defined by Subnautica and should never be modified
|
||||
private const int TOTAL_LEAKS = 11;
|
||||
|
||||
public void ModifyWorld(World world)
|
||||
{
|
||||
// Creating entities for the 11 RadiationLeakPoint located at (Aurora Scene) //Aurora-MainPrefab/Aurora/radiationleaks/RadiationLeaks(Clone)
|
||||
for (int i = 0; i < TOTAL_LEAKS; i++)
|
||||
{
|
||||
RadiationLeakEntity leakEntity = new(new(), i, new(0));
|
||||
world.WorldEntityManager.AddOrUpdateGlobalRootEntity(leakEntity);
|
||||
}
|
||||
}
|
||||
}
|
30
NitroxServer-Subnautica/NitroxServer-Subnautica.csproj
Normal file
30
NitroxServer-Subnautica/NitroxServer-Subnautica.csproj
Normal file
@@ -0,0 +1,30 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<OutputType>Exe</OutputType>
|
||||
<RootNamespace>NitroxServer_Subnautica</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\NitroxModel-Subnautica\NitroxModel-Subnautica.csproj" />
|
||||
<ProjectReference Include="..\NitroxServer\NitroxServer.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AssetsTools.NET" Version="3.0.0-preview1" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="[2.1.10]" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="protobuf-net">
|
||||
<HintPath>..\Nitrox.Assets.Subnautica\protobuf-net.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="..\Nitrox.Assets.Subnautica\**\*.tpk" CopyToOutputDirectory="PreserveNewest" />
|
||||
</ItemGroup>
|
||||
|
||||
<Import Project="..\Nitrox.Shared.targets" />
|
||||
</Project>
|
602
NitroxServer-Subnautica/Program.cs
Normal file
602
NitroxServer-Subnautica/Program.cs
Normal file
@@ -0,0 +1,602 @@
|
||||
global using NitroxModel.Logger;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using NitroxModel;
|
||||
using NitroxModel.Core;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.DataStructures.GameLogic;
|
||||
using NitroxModel.DataStructures.Util;
|
||||
using NitroxModel.Helper;
|
||||
using NitroxServer;
|
||||
using NitroxServer_Subnautica.Communication;
|
||||
using NitroxServer.ConsoleCommands.Processor;
|
||||
|
||||
namespace NitroxServer_Subnautica;
|
||||
|
||||
[SuppressMessage("Usage", "DIMA001:Dependency Injection container is used directly")]
|
||||
public class Program
|
||||
{
|
||||
private static Lazy<string> gameInstallDir;
|
||||
private static readonly CircularBuffer<string> inputHistory = new(1000);
|
||||
private static int currentHistoryIndex;
|
||||
private static readonly CancellationTokenSource serverCts = new();
|
||||
|
||||
private static async Task Main(string[] args)
|
||||
{
|
||||
AppDomain.CurrentDomain.AssemblyResolve += AssemblyResolver.Handler;
|
||||
AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += AssemblyResolver.Handler;
|
||||
|
||||
await StartServer(args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize server here so that the JIT can compile the EntryPoint method without having to resolve dependencies
|
||||
/// that require the <see cref="AppDomain.AssemblyResolve" /> handler.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// https://stackoverflow.com/a/6089153/1277156
|
||||
/// </remarks>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static async Task StartServer(string[] args)
|
||||
{
|
||||
// The thread that writers to console is paused while selecting text in console. So console writer needs to be async.
|
||||
Log.Setup(true, isConsoleApp: !args.Contains("--embedded", StringComparer.OrdinalIgnoreCase));
|
||||
AppDomain.CurrentDomain.UnhandledException += CurrentDomainOnUnhandledException;
|
||||
PosixSignalRegistration.Create(PosixSignal.SIGTERM, CloseWindowHandler);
|
||||
PosixSignalRegistration.Create(PosixSignal.SIGQUIT, CloseWindowHandler);
|
||||
PosixSignalRegistration.Create(PosixSignal.SIGINT, CloseWindowHandler);
|
||||
PosixSignalRegistration.Create(PosixSignal.SIGHUP, CloseWindowHandler);
|
||||
|
||||
CultureManager.ConfigureCultureInfo();
|
||||
if (!Console.IsInputRedirected)
|
||||
{
|
||||
Console.TreatControlCAsInput = true;
|
||||
}
|
||||
|
||||
Log.Info($"Starting NitroxServer {NitroxEnvironment.ReleasePhase} v{NitroxEnvironment.Version} for {GameInfo.Subnautica.FullName}");
|
||||
Log.Debug($@"Process start args: ""{string.Join(@""", """, Environment.GetCommandLineArgs())}""");
|
||||
|
||||
Task handleConsoleInputTask;
|
||||
Server server;
|
||||
try
|
||||
{
|
||||
handleConsoleInputTask = HandleConsoleInputAsync(ConsoleCommandHandler(), serverCts.Token);
|
||||
AppMutex.Hold(() => Log.Info("Waiting on other Nitrox servers to initialize before starting.."), serverCts.Token);
|
||||
|
||||
Stopwatch watch = Stopwatch.StartNew();
|
||||
|
||||
// Allow game path to be given as command argument
|
||||
string gameDir;
|
||||
if (args.Length > 0 && Directory.Exists(args[0]) && File.Exists(Path.Combine(args[0], GameInfo.Subnautica.ExeName)))
|
||||
{
|
||||
gameDir = Path.GetFullPath(args[0]);
|
||||
gameInstallDir = new Lazy<string>(() => gameDir);
|
||||
}
|
||||
else
|
||||
{
|
||||
gameInstallDir = new Lazy<string>(() =>
|
||||
{
|
||||
return gameDir = NitroxUser.GamePath;
|
||||
});
|
||||
}
|
||||
Log.Info($"Using game files from: \'{gameInstallDir.Value}\'");
|
||||
|
||||
// TODO: Fix DI to not be slow (should not use IO in type constructors). Instead, use Lazy<T> (et al). This way, cancellation can be faster.
|
||||
NitroxServiceLocator.InitializeDependencyContainer(new SubnauticaServerAutoFacRegistrar());
|
||||
NitroxServiceLocator.BeginNewLifetimeScope();
|
||||
server = NitroxServiceLocator.LocateService<Server>();
|
||||
string serverSaveName = Server.GetSaveName(args, "My World");
|
||||
Log.SaveName = serverSaveName;
|
||||
|
||||
using (CancellationTokenSource portWaitCts = CancellationTokenSource.CreateLinkedTokenSource(serverCts.Token))
|
||||
{
|
||||
TimeSpan portWaitTimeout = TimeSpan.FromSeconds(30);
|
||||
portWaitCts.CancelAfter(portWaitTimeout);
|
||||
await WaitForAvailablePortAsync(server.Port, portWaitTimeout, portWaitCts.Token);
|
||||
}
|
||||
|
||||
if (!serverCts.IsCancellationRequested)
|
||||
{
|
||||
if (!server.Start(serverSaveName, serverCts))
|
||||
{
|
||||
throw new Exception("Unable to start server.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Info($"Server started ({Math.Round(watch.Elapsed.TotalSeconds, 1)}s)");
|
||||
Log.Info("To get help for commands, run help in console or /help in chatbox");
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Allow other servers to start initializing.
|
||||
AppMutex.Release();
|
||||
}
|
||||
|
||||
await handleConsoleInputTask;
|
||||
server.Stop(true);
|
||||
|
||||
try
|
||||
{
|
||||
if (Environment.UserInteractive && Console.In != StreamReader.Null && Debugger.IsAttached)
|
||||
{
|
||||
Task.Delay(100).Wait(); // Wait for async logs to flush to console
|
||||
Console.WriteLine($"{Environment.NewLine}Press any key to continue . . .");
|
||||
Console.ReadKey(true);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
Action<string> ConsoleCommandHandler()
|
||||
{
|
||||
ConsoleCommandProcessor commandProcessor = null;
|
||||
return submit =>
|
||||
{
|
||||
try
|
||||
{
|
||||
commandProcessor ??= NitroxServiceLocator.LocateService<ConsoleCommandProcessor>();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
commandProcessor?.ProcessCommand(submit, Optional.Empty, Perms.CONSOLE);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private static void CloseWindowHandler(PosixSignalContext context)
|
||||
{
|
||||
context.Cancel = false;
|
||||
serverCts?.Cancel();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles per-key input of the console and passes input submit to <see cref="ConsoleCommandProcessor" />.
|
||||
/// </summary>
|
||||
private static async Task HandleConsoleInputAsync(Action<string> submitHandler, CancellationToken ct = default)
|
||||
{
|
||||
ConcurrentQueue<string> commandQueue = new();
|
||||
|
||||
if (Console.IsInputRedirected)
|
||||
{
|
||||
Log.Info("Server input stream is redirected");
|
||||
_ = Task.Run(() =>
|
||||
{
|
||||
while (!ct.IsCancellationRequested)
|
||||
{
|
||||
string commandRead = Console.ReadLine();
|
||||
commandQueue.Enqueue(commandRead);
|
||||
}
|
||||
}, ct).ContinueWith(t =>
|
||||
{
|
||||
if (t.IsFaulted)
|
||||
{
|
||||
Log.Error(t.Exception);
|
||||
}
|
||||
}, ct);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Info("Server input stream is available");
|
||||
StringBuilder inputLineBuilder = new();
|
||||
|
||||
void ClearInputLine()
|
||||
{
|
||||
currentHistoryIndex = 0;
|
||||
inputLineBuilder.Clear();
|
||||
Console.Write($"\r{new string(' ', Console.WindowWidth - 1)}\r");
|
||||
}
|
||||
|
||||
void RedrawInput(int start = 0, int end = 0)
|
||||
{
|
||||
int lastPosition = Console.CursorLeft;
|
||||
// Expand range to end if end value is -1
|
||||
if (start > -1 && end == -1)
|
||||
{
|
||||
end = Math.Max(inputLineBuilder.Length - start, 0);
|
||||
}
|
||||
|
||||
if (start == 0 && end == 0)
|
||||
{
|
||||
// Redraw entire line
|
||||
Console.Write($"\r{new string(' ', Console.WindowWidth - 1)}\r{inputLineBuilder}");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Redraw part of line
|
||||
string changedInputSegment = inputLineBuilder.ToString(start, end);
|
||||
Console.CursorVisible = false;
|
||||
Console.Write($"{changedInputSegment}{new string(' ', inputLineBuilder.Length - changedInputSegment.Length - Console.CursorLeft + 1)}");
|
||||
Console.CursorVisible = true;
|
||||
}
|
||||
Console.CursorLeft = lastPosition;
|
||||
}
|
||||
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
while (!ct.IsCancellationRequested)
|
||||
{
|
||||
if (!Console.KeyAvailable)
|
||||
{
|
||||
try
|
||||
{
|
||||
await Task.Delay(10, ct);
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
ConsoleKeyInfo keyInfo = Console.ReadKey(true);
|
||||
// Handle (ctrl) hotkeys
|
||||
if ((keyInfo.Modifiers & ConsoleModifiers.Control) != 0)
|
||||
{
|
||||
switch (keyInfo.Key)
|
||||
{
|
||||
case ConsoleKey.C:
|
||||
if (inputLineBuilder.Length > 0)
|
||||
{
|
||||
ClearInputLine();
|
||||
continue;
|
||||
}
|
||||
|
||||
await serverCts.CancelAsync();
|
||||
return;
|
||||
case ConsoleKey.D:
|
||||
await serverCts.CancelAsync();
|
||||
return;
|
||||
default:
|
||||
// Unhandled modifier key
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (keyInfo.Modifiers == 0)
|
||||
{
|
||||
switch (keyInfo.Key)
|
||||
{
|
||||
case ConsoleKey.LeftArrow when Console.CursorLeft > 0:
|
||||
Console.CursorLeft--;
|
||||
continue;
|
||||
case ConsoleKey.RightArrow when Console.CursorLeft < inputLineBuilder.Length:
|
||||
Console.CursorLeft++;
|
||||
continue;
|
||||
case ConsoleKey.Backspace:
|
||||
if (inputLineBuilder.Length > Console.CursorLeft - 1 && Console.CursorLeft > 0)
|
||||
{
|
||||
inputLineBuilder.Remove(Console.CursorLeft - 1, 1);
|
||||
Console.CursorLeft--;
|
||||
Console.Write(' ');
|
||||
Console.CursorLeft--;
|
||||
RedrawInput();
|
||||
}
|
||||
continue;
|
||||
case ConsoleKey.Delete:
|
||||
if (inputLineBuilder.Length > 0 && Console.CursorLeft < inputLineBuilder.Length)
|
||||
{
|
||||
inputLineBuilder.Remove(Console.CursorLeft, 1);
|
||||
RedrawInput(Console.CursorLeft, inputLineBuilder.Length - Console.CursorLeft);
|
||||
}
|
||||
continue;
|
||||
case ConsoleKey.Home:
|
||||
Console.CursorLeft = 0;
|
||||
continue;
|
||||
case ConsoleKey.End:
|
||||
Console.CursorLeft = inputLineBuilder.Length;
|
||||
continue;
|
||||
case ConsoleKey.Escape:
|
||||
ClearInputLine();
|
||||
continue;
|
||||
case ConsoleKey.Tab:
|
||||
if (Console.CursorLeft + 4 < Console.WindowWidth)
|
||||
{
|
||||
inputLineBuilder.Insert(Console.CursorLeft, " ");
|
||||
RedrawInput(Console.CursorLeft, -1);
|
||||
Console.CursorLeft += 4;
|
||||
}
|
||||
continue;
|
||||
case ConsoleKey.UpArrow when inputHistory.Count > 0 && currentHistoryIndex > -inputHistory.Count:
|
||||
inputLineBuilder.Clear();
|
||||
inputLineBuilder.Append(inputHistory[--currentHistoryIndex]);
|
||||
RedrawInput();
|
||||
Console.CursorLeft = Math.Min(inputLineBuilder.Length, Console.WindowWidth);
|
||||
continue;
|
||||
case ConsoleKey.DownArrow when inputHistory.Count > 0 && currentHistoryIndex < 0:
|
||||
if (currentHistoryIndex == -1)
|
||||
{
|
||||
ClearInputLine();
|
||||
continue;
|
||||
}
|
||||
inputLineBuilder.Clear();
|
||||
inputLineBuilder.Append(inputHistory[++currentHistoryIndex]);
|
||||
RedrawInput();
|
||||
Console.CursorLeft = Math.Min(inputLineBuilder.Length, Console.WindowWidth);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// Handle input submit to submit handler
|
||||
if (keyInfo.Key == ConsoleKey.Enter)
|
||||
{
|
||||
string submit = inputLineBuilder.ToString();
|
||||
if (inputHistory.Count == 0 || inputHistory[inputHistory.LastChangedIndex] != submit)
|
||||
{
|
||||
inputHistory.Add(submit);
|
||||
}
|
||||
currentHistoryIndex = 0;
|
||||
commandQueue.Enqueue(submit);
|
||||
inputLineBuilder.Clear();
|
||||
Console.WriteLine();
|
||||
continue;
|
||||
}
|
||||
|
||||
// If unhandled key, append as input.
|
||||
if (keyInfo.KeyChar != 0)
|
||||
{
|
||||
Console.Write(keyInfo.KeyChar);
|
||||
if (Console.CursorLeft - 1 < inputLineBuilder.Length)
|
||||
{
|
||||
inputLineBuilder.Insert(Console.CursorLeft - 1, keyInfo.KeyChar);
|
||||
RedrawInput(Console.CursorLeft, -1);
|
||||
}
|
||||
else
|
||||
{
|
||||
inputLineBuilder.Append(keyInfo.KeyChar);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, ct).ContinueWith(t =>
|
||||
{
|
||||
if (t.IsFaulted)
|
||||
{
|
||||
Log.Error(t.Exception);
|
||||
}
|
||||
}, ct);
|
||||
}
|
||||
|
||||
using IpcHost ipcHost = IpcHost.StartReadingCommands(command => commandQueue.Enqueue(command), ct);
|
||||
|
||||
if (!Console.IsInputRedirected)
|
||||
{
|
||||
// Important to not hang process: keep command handler on the main thread when input not redirected (i.e. don't Task.Run)
|
||||
while (!ct.IsCancellationRequested)
|
||||
{
|
||||
while (commandQueue.TryDequeue(out string command))
|
||||
{
|
||||
submitHandler(command);
|
||||
}
|
||||
try
|
||||
{
|
||||
await Task.Delay(10, ct);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Important to not hang process (when running launcher from release exe): free main thread if input redirected
|
||||
await Task.Run(async () =>
|
||||
{
|
||||
while (!ct.IsCancellationRequested)
|
||||
{
|
||||
while (commandQueue.TryDequeue(out string command))
|
||||
{
|
||||
submitHandler(command);
|
||||
}
|
||||
try
|
||||
{
|
||||
await Task.Delay(10, ct);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}, ct).ContinueWithHandleError();
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task WaitForAvailablePortAsync(int port, TimeSpan timeout = default, CancellationToken ct = default)
|
||||
{
|
||||
if (timeout == default)
|
||||
{
|
||||
timeout = TimeSpan.FromSeconds(30);
|
||||
}
|
||||
else
|
||||
{
|
||||
Validate.IsTrue(timeout.TotalSeconds >= 5, "Timeout must be at least 5 seconds.");
|
||||
}
|
||||
|
||||
int messageLength = 0;
|
||||
void PrintPortWarn(TimeSpan timeRemaining)
|
||||
{
|
||||
string message = $"Port {port} UDP is already in use. Please change the server port or close out any program that may be using it. Retrying for {Math.Floor(timeRemaining.TotalSeconds)} seconds until it is available...";
|
||||
messageLength = message.Length;
|
||||
Log.Warn(message);
|
||||
}
|
||||
|
||||
DateTimeOffset time = DateTimeOffset.UtcNow;
|
||||
bool first = true;
|
||||
try
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
IPEndPoint endPoint = IPGlobalProperties.GetIPGlobalProperties().GetActiveUdpListeners().FirstOrDefault(ip => ip.Port == port);
|
||||
if (endPoint == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (first)
|
||||
{
|
||||
first = false;
|
||||
PrintPortWarn(timeout);
|
||||
}
|
||||
else if (Environment.UserInteractive && !Console.IsInputRedirected && Console.In != StreamReader.Null)
|
||||
{
|
||||
// If not first time, move cursor up the number of lines it takes up to overwrite previous message
|
||||
int numberOfLines = (int)Math.Ceiling( ((double)messageLength + 15) / Console.BufferWidth );
|
||||
for (int i = 0; i < numberOfLines; i++)
|
||||
{
|
||||
if (Console.CursorTop > 0) // Check to ensure we don't go out of bounds
|
||||
{
|
||||
Console.CursorTop--;
|
||||
}
|
||||
}
|
||||
Console.CursorLeft = 0;
|
||||
|
||||
PrintPortWarn(timeout - (DateTimeOffset.UtcNow - time));
|
||||
}
|
||||
|
||||
await Task.Delay(500, ct);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
private static void CurrentDomainOnUnhandledException(object sender, UnhandledExceptionEventArgs e)
|
||||
{
|
||||
if (e.ExceptionObject is Exception ex)
|
||||
{
|
||||
Log.Error(ex);
|
||||
}
|
||||
if (!Environment.UserInteractive || Console.IsInputRedirected || Console.In == StreamReader.Null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Implement log file opening by server name
|
||||
/*string mostRecentLogFile = Log.GetMostRecentLogFile(); // Log.SaveName
|
||||
if (mostRecentLogFile == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Log.Info("Press L to open log file before closing. Press any other key to close . . .");*/
|
||||
Log.Info("Press L to open log folder before closing. Press any other key to close . . .");
|
||||
ConsoleKeyInfo key = Console.ReadKey(true);
|
||||
|
||||
if (key.Key == ConsoleKey.L)
|
||||
{
|
||||
// Log.Info($"Opening log file at: {mostRecentLogFile}..");
|
||||
// using Process process = FileSystem.Instance.OpenOrExecuteFile(mostRecentLogFile);
|
||||
|
||||
Process.Start(new ProcessStartInfo
|
||||
{
|
||||
FileName = Log.LogDirectory,
|
||||
Verb = "open",
|
||||
UseShellExecute = true
|
||||
})?.Dispose();
|
||||
}
|
||||
|
||||
Environment.Exit(1);
|
||||
}
|
||||
|
||||
private static class AssemblyResolver
|
||||
{
|
||||
private static string currentExecutableDirectory;
|
||||
private static readonly Dictionary<string, Assembly> resolvedAssemblyCache = [];
|
||||
|
||||
public static Assembly Handler(object sender, ResolveEventArgs args)
|
||||
{
|
||||
static Assembly ResolveFromLib(ReadOnlySpan<char> dllName)
|
||||
{
|
||||
dllName = dllName.Slice(0, Math.Max(dllName.IndexOf(','), 0));
|
||||
if (dllName.IsEmpty)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (!dllName.EndsWith(".dll"))
|
||||
{
|
||||
dllName = string.Concat(dllName, ".dll");
|
||||
}
|
||||
if (dllName.EndsWith(".resources.dll"))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
string dllNameStr = dllName.ToString();
|
||||
// If available, return cached assembly
|
||||
if (resolvedAssemblyCache.TryGetValue(dllNameStr, out Assembly val))
|
||||
{
|
||||
return val;
|
||||
}
|
||||
|
||||
// Load DLLs where this program (exe) is located
|
||||
string dllPath = Path.Combine(GetExecutableDirectory(), "lib", dllNameStr);
|
||||
// Prefer to use Newtonsoft dll from game instead of our own due to protobuf issues. TODO: Remove when we do our own deserialization of game data instead of using the game's protobuf.
|
||||
if (dllPath.IndexOf("Newtonsoft.Json.dll", StringComparison.OrdinalIgnoreCase) >= 0 || !File.Exists(dllPath))
|
||||
{
|
||||
// Try find game managed libraries
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||
{
|
||||
dllPath = Path.Combine(gameInstallDir.Value, "Resources", "Data", "Managed", dllNameStr);
|
||||
}
|
||||
else
|
||||
{
|
||||
dllPath = Path.Combine(gameInstallDir.Value, "Subnautica_Data", "Managed", dllNameStr);
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Read assemblies as bytes as to not lock the file so that Nitrox can patch assemblies while server is running.
|
||||
Assembly assembly = Assembly.Load(File.ReadAllBytes(dllPath));
|
||||
return resolvedAssemblyCache[dllNameStr] = assembly;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Assembly assembly = ResolveFromLib(args.Name);
|
||||
if (assembly == null && !args.Name.Contains(".resources"))
|
||||
{
|
||||
assembly = Assembly.Load(args.Name);
|
||||
}
|
||||
|
||||
return assembly;
|
||||
}
|
||||
|
||||
private static string GetExecutableDirectory()
|
||||
{
|
||||
if (currentExecutableDirectory != null)
|
||||
{
|
||||
return currentExecutableDirectory;
|
||||
}
|
||||
string pathAttempt = Assembly.GetEntryAssembly()?.Location;
|
||||
if (string.IsNullOrWhiteSpace(pathAttempt))
|
||||
{
|
||||
using Process proc = Process.GetCurrentProcess();
|
||||
pathAttempt = proc.MainModule?.FileName;
|
||||
}
|
||||
return currentExecutableDirectory = new Uri(Path.GetDirectoryName(pathAttempt ?? ".") ?? Directory.GetCurrentDirectory()).LocalPath;
|
||||
}
|
||||
}
|
||||
}
|
30
NitroxServer-Subnautica/Properties/AssemblyInfo.cs
Normal file
30
NitroxServer-Subnautica/Properties/AssemblyInfo.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using System.Reflection;
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyDescription("Nitrox server implemenation for the game Subnautica")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
// COMMON: [assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("eff1d7a5-efd6-413a-8d5f-dc2408e4c9b7")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
// COMMON: [assembly: AssemblyVersion("X.X.X.X")]
|
||||
// COMMON: [assembly: AssemblyFileVersion("X.X.X.X")]
|
11
NitroxServer-Subnautica/Properties/launchSettings.json
Normal file
11
NitroxServer-Subnautica/Properties/launchSettings.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"profiles": {
|
||||
"NitroxServer-Subnautica": {
|
||||
"commandName": "Project"
|
||||
},
|
||||
"Launch test save": {
|
||||
"commandName": "Project",
|
||||
"commandLineArgs": "--save test"
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
using AddressablesTools.Catalog;
|
||||
using AddressablesTools.JSON;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace AddressablesTools
|
||||
{
|
||||
public static class AddressablesJsonParser
|
||||
{
|
||||
internal static ContentCatalogDataJson CCDJsonFromString(string data)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<ContentCatalogDataJson>(data);
|
||||
}
|
||||
|
||||
public static ContentCatalogData FromString(string data)
|
||||
{
|
||||
ContentCatalogDataJson ccdJson = CCDJsonFromString(data);
|
||||
|
||||
ContentCatalogData catalogData = new ContentCatalogData();
|
||||
catalogData.Read(ccdJson);
|
||||
|
||||
return catalogData;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
namespace AddressablesTools.Catalog
|
||||
{
|
||||
internal class ClassJsonObject
|
||||
{
|
||||
public string AssemblyName { get; }
|
||||
public string ClassName { get; }
|
||||
public string JsonText { get; }
|
||||
|
||||
public ClassJsonObject(string assemblyName, string className, string jsonText)
|
||||
{
|
||||
AssemblyName = assemblyName;
|
||||
ClassName = className;
|
||||
JsonText = jsonText;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,177 @@
|
||||
using AddressablesTools.JSON;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace AddressablesTools.Catalog
|
||||
{
|
||||
public class ContentCatalogData
|
||||
{
|
||||
public string LocatorId { get; set; }
|
||||
public ObjectInitializationData InstanceProviderData { get; set; }
|
||||
public ObjectInitializationData SceneProviderData { get; set; }
|
||||
public ObjectInitializationData[] ResourceProviderData { get; set; }
|
||||
public string[] ProviderIds { get; set; }
|
||||
public string[] InternalIds { get; set; }
|
||||
public SerializedType[] ResourceTypes { get; set; }
|
||||
public string[] InternalIdPrefixes { get; set; }
|
||||
|
||||
public Dictionary<object, List<ResourceLocation>> Resources { get; set; }
|
||||
|
||||
internal void Read(ContentCatalogDataJson data)
|
||||
{
|
||||
LocatorId = data.m_LocatorId;
|
||||
|
||||
InstanceProviderData = new ObjectInitializationData();
|
||||
InstanceProviderData.Read(data.m_InstanceProviderData);
|
||||
|
||||
SceneProviderData = new ObjectInitializationData();
|
||||
SceneProviderData.Read(data.m_SceneProviderData);
|
||||
|
||||
ResourceProviderData = new ObjectInitializationData[data.m_ResourceProviderData.Length];
|
||||
for (int i = 0; i < ResourceProviderData.Length; i++)
|
||||
{
|
||||
ResourceProviderData[i] = new ObjectInitializationData();
|
||||
ResourceProviderData[i].Read(data.m_ResourceProviderData[i]);
|
||||
}
|
||||
|
||||
ProviderIds = new string[data.m_ProviderIds.Length];
|
||||
for (int i = 0; i < ProviderIds.Length; i++)
|
||||
{
|
||||
ProviderIds[i] = data.m_ProviderIds[i];
|
||||
}
|
||||
|
||||
InternalIds = new string[data.m_InternalIds.Length];
|
||||
for (int i = 0; i < InternalIds.Length; i++)
|
||||
{
|
||||
InternalIds[i] = data.m_InternalIds[i];
|
||||
}
|
||||
|
||||
ResourceTypes = new SerializedType[data.m_resourceTypes.Length];
|
||||
for (int i = 0; i < ResourceTypes.Length; i++)
|
||||
{
|
||||
ResourceTypes[i] = new SerializedType();
|
||||
ResourceTypes[i].Read(data.m_resourceTypes[i]);
|
||||
}
|
||||
|
||||
InternalIdPrefixes = new string[data.m_InternalIdPrefixes.Length];
|
||||
for (int i = 0; i < InternalIdPrefixes.Length; i++)
|
||||
{
|
||||
InternalIdPrefixes[i] = data.m_InternalIdPrefixes[i];
|
||||
}
|
||||
|
||||
ReadResources(data);
|
||||
}
|
||||
|
||||
private void ReadResources(ContentCatalogDataJson data)
|
||||
{
|
||||
List<Bucket> buckets;
|
||||
|
||||
MemoryStream bucketStream = new MemoryStream(Convert.FromBase64String(data.m_BucketDataString));
|
||||
using (BinaryReader bucketReader = new BinaryReader(bucketStream))
|
||||
{
|
||||
int bucketCount = bucketReader.ReadInt32();
|
||||
buckets = new List<Bucket>(bucketCount);
|
||||
|
||||
for (int i = 0; i < bucketCount; i++)
|
||||
{
|
||||
int offset = bucketReader.ReadInt32();
|
||||
|
||||
int entryCount = bucketReader.ReadInt32();
|
||||
int[] entries = new int[entryCount];
|
||||
for (int j = 0; j < entryCount; j++)
|
||||
{
|
||||
entries[j] = bucketReader.ReadInt32();
|
||||
}
|
||||
|
||||
buckets.Add(new Bucket(offset, entries));
|
||||
}
|
||||
}
|
||||
|
||||
List<object> keys;
|
||||
|
||||
MemoryStream keyDataStream = new MemoryStream(Convert.FromBase64String(data.m_KeyDataString));
|
||||
using (BinaryReader keyReader = new BinaryReader(keyDataStream))
|
||||
{
|
||||
int keyCount = keyReader.ReadInt32();
|
||||
keys = new List<object>(keyCount);
|
||||
|
||||
for (int i = 0; i < keyCount; i++)
|
||||
{
|
||||
keyDataStream.Position = buckets[i].offset;
|
||||
keys.Add(SerializedObjectDecoder.Decode(keyReader));
|
||||
}
|
||||
}
|
||||
|
||||
List<ResourceLocation> locations;
|
||||
|
||||
MemoryStream entryDataStream = new MemoryStream(Convert.FromBase64String(data.m_EntryDataString));
|
||||
MemoryStream extraDataStream = new MemoryStream(Convert.FromBase64String(data.m_ExtraDataString));
|
||||
using (BinaryReader entryReader = new BinaryReader(entryDataStream))
|
||||
using (BinaryReader extraReader = new BinaryReader(extraDataStream))
|
||||
{
|
||||
int entryCount = entryReader.ReadInt32();
|
||||
locations = new List<ResourceLocation>(entryCount);
|
||||
|
||||
for (int i = 0; i < entryCount; i++)
|
||||
{
|
||||
int internalIdIndex = entryReader.ReadInt32();
|
||||
int providerIndex = entryReader.ReadInt32();
|
||||
int dependencyKeyIndex = entryReader.ReadInt32();
|
||||
int depHash = entryReader.ReadInt32();
|
||||
int dataIndex = entryReader.ReadInt32();
|
||||
int primaryKeyIndex = entryReader.ReadInt32();
|
||||
int resourceTypeIndex = entryReader.ReadInt32();
|
||||
|
||||
string internalId = InternalIds[internalIdIndex];
|
||||
|
||||
string providerId = ProviderIds[providerIndex];
|
||||
|
||||
object dependencyKey = null;
|
||||
if (dependencyKeyIndex >= 0)
|
||||
{
|
||||
dependencyKey = keys[dependencyKeyIndex];
|
||||
}
|
||||
|
||||
object objData = null;
|
||||
if (dataIndex >= 0)
|
||||
{
|
||||
extraDataStream.Position = dataIndex;
|
||||
objData = SerializedObjectDecoder.Decode(extraReader);
|
||||
}
|
||||
|
||||
object primaryKey = keys[primaryKeyIndex];
|
||||
SerializedType resourceType = ResourceTypes[resourceTypeIndex];
|
||||
|
||||
var loc = new ResourceLocation();
|
||||
loc.ReadCompact(internalId, providerId, dependencyKey, objData, depHash, primaryKey, resourceType);
|
||||
locations.Add(loc);
|
||||
}
|
||||
}
|
||||
|
||||
Resources = new Dictionary<object, List<ResourceLocation>>(buckets.Count);
|
||||
for (int i = 0; i < buckets.Count; i++)
|
||||
{
|
||||
int[] bucketEntries = buckets[i].entries;
|
||||
List<ResourceLocation> locs = new List<ResourceLocation>(bucketEntries.Length);
|
||||
for (int j = 0; j < bucketEntries.Length; j++)
|
||||
{
|
||||
locs.Add(locations[bucketEntries[j]]);
|
||||
}
|
||||
Resources[keys[i]] = locs;
|
||||
}
|
||||
}
|
||||
|
||||
private struct Bucket
|
||||
{
|
||||
public int offset;
|
||||
public int[] entries;
|
||||
|
||||
public Bucket(int offset, int[] entries)
|
||||
{
|
||||
this.offset = offset;
|
||||
this.entries = entries;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
using AddressablesTools.JSON;
|
||||
|
||||
namespace AddressablesTools.Catalog
|
||||
{
|
||||
public class ObjectInitializationData
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public SerializedType ObjectType { get; set; }
|
||||
public string Data { get; set; }
|
||||
|
||||
internal void Read(ObjectInitializationDataJson obj)
|
||||
{
|
||||
Id = obj.m_Id;
|
||||
ObjectType = new SerializedType();
|
||||
ObjectType.Read(obj.m_ObjectType);
|
||||
Data = obj.m_Data;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,29 @@
|
||||
namespace AddressablesTools.Catalog
|
||||
{
|
||||
public class ResourceLocation
|
||||
{
|
||||
public string InternalId { get; set; }
|
||||
public string ProviderId { get; set; }
|
||||
public object Dependency { get; set; }
|
||||
public object Data { get; set; }
|
||||
public int HashCode { get; set; }
|
||||
public int DependencyHashCode { get; set; }
|
||||
public string PrimaryKey { get; set; }
|
||||
public SerializedType Type { get; set; }
|
||||
|
||||
internal void ReadCompact(
|
||||
string internalId, string providerId, object dependencyKey, object data,
|
||||
int depHashCode, object primaryKey, SerializedType resourceType
|
||||
)
|
||||
{
|
||||
InternalId = internalId;
|
||||
ProviderId = providerId;
|
||||
Dependency = dependencyKey;
|
||||
Data = data;
|
||||
HashCode = internalId.GetHashCode() * 31 + providerId.GetHashCode();
|
||||
DependencyHashCode = depHashCode;
|
||||
PrimaryKey = primaryKey.ToString();
|
||||
Type = resourceType;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,109 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace AddressablesTools.Catalog
|
||||
{
|
||||
internal static class SerializedObjectDecoder
|
||||
{
|
||||
internal enum ObjectType
|
||||
{
|
||||
AsciiString,
|
||||
UnicodeString,
|
||||
UInt16,
|
||||
UInt32,
|
||||
Int32,
|
||||
Hash128,
|
||||
Type,
|
||||
JsonObject
|
||||
}
|
||||
|
||||
internal static object Decode(BinaryReader br)
|
||||
{
|
||||
ObjectType type = (ObjectType)br.ReadByte();
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case ObjectType.AsciiString:
|
||||
{
|
||||
string str = ReadString4(br);
|
||||
return str;
|
||||
}
|
||||
|
||||
case ObjectType.UnicodeString:
|
||||
{
|
||||
string str = ReadString4Unicode(br);
|
||||
return str;
|
||||
}
|
||||
|
||||
case ObjectType.UInt16:
|
||||
{
|
||||
return br.ReadUInt16();
|
||||
}
|
||||
|
||||
case ObjectType.UInt32:
|
||||
{
|
||||
return br.ReadUInt32();
|
||||
}
|
||||
|
||||
case ObjectType.Int32:
|
||||
{
|
||||
return br.ReadInt32();
|
||||
}
|
||||
|
||||
case ObjectType.Hash128:
|
||||
{
|
||||
// read as string for now
|
||||
string str = ReadString1(br);
|
||||
return str;
|
||||
}
|
||||
|
||||
case ObjectType.Type:
|
||||
{
|
||||
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
throw new NotSupportedException($"{nameof(ObjectType)}.{nameof(ObjectType.Type)} is only supported on windows because it uses {nameof(Type.GetTypeFromCLSID)}");
|
||||
}
|
||||
string str = ReadString1(br);
|
||||
return Type.GetTypeFromCLSID(new Guid(str));
|
||||
}
|
||||
|
||||
case ObjectType.JsonObject:
|
||||
{
|
||||
string assemblyName = ReadString1(br);
|
||||
string className = ReadString1(br);
|
||||
string jsonText = ReadString4Unicode(br);
|
||||
ClassJsonObject jsonObj = new ClassJsonObject(assemblyName, className, jsonText);
|
||||
return jsonObj;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string ReadString1(BinaryReader br)
|
||||
{
|
||||
int length = br.ReadByte();
|
||||
string str = Encoding.ASCII.GetString(br.ReadBytes(length));
|
||||
return str;
|
||||
}
|
||||
|
||||
private static string ReadString4(BinaryReader br)
|
||||
{
|
||||
int length = br.ReadInt32();
|
||||
string str = Encoding.ASCII.GetString(br.ReadBytes(length));
|
||||
return str;
|
||||
}
|
||||
|
||||
private static string ReadString4Unicode(BinaryReader br)
|
||||
{
|
||||
int length = br.ReadInt32();
|
||||
string str = Encoding.Unicode.GetString(br.ReadBytes(length));
|
||||
return str;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
using AddressablesTools.JSON;
|
||||
|
||||
namespace AddressablesTools.Catalog
|
||||
{
|
||||
public class SerializedType
|
||||
{
|
||||
public string AssemblyName { get; set; }
|
||||
public string ClassName { get; set; }
|
||||
|
||||
internal void Read(SerializedTypeJson type)
|
||||
{
|
||||
AssemblyName = type.m_AssemblyName;
|
||||
ClassName = type.m_ClassName;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
namespace AddressablesTools.JSON
|
||||
{
|
||||
#pragma warning disable IDE1006
|
||||
internal class ContentCatalogDataJson
|
||||
{
|
||||
public string m_LocatorId { get; set; }
|
||||
public ObjectInitializationDataJson m_InstanceProviderData { get; set; }
|
||||
public ObjectInitializationDataJson m_SceneProviderData { get; set; }
|
||||
public ObjectInitializationDataJson[] m_ResourceProviderData { get; set; }
|
||||
public string[] m_ProviderIds { get; set; }
|
||||
public string[] m_InternalIds { get; set; }
|
||||
public string m_KeyDataString { get; set; }
|
||||
public string m_BucketDataString { get; set; }
|
||||
public string m_EntryDataString { get; set; }
|
||||
public string m_ExtraDataString { get; set; }
|
||||
public SerializedTypeJson[] m_resourceTypes { get; set; }
|
||||
public string[] m_InternalIdPrefixes { get; set; }
|
||||
}
|
||||
#pragma warning restore IDE1006
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
namespace AddressablesTools.JSON
|
||||
{
|
||||
#pragma warning disable IDE1006
|
||||
internal class ObjectInitializationDataJson
|
||||
{
|
||||
public string m_Id { get; set; }
|
||||
public SerializedTypeJson m_ObjectType { get; set; }
|
||||
public string m_Data { get; set; }
|
||||
}
|
||||
#pragma warning restore IDE1006
|
||||
}
|
@@ -0,0 +1,10 @@
|
||||
namespace AddressablesTools.JSON
|
||||
{
|
||||
#pragma warning disable IDE1006
|
||||
internal class SerializedTypeJson
|
||||
{
|
||||
public string m_AssemblyName { get; set; }
|
||||
public string m_ClassName { get; set; }
|
||||
}
|
||||
#pragma warning restore IDE1006
|
||||
}
|
@@ -0,0 +1,29 @@
|
||||
using System.IO;
|
||||
using AssetsTools.NET.Extra;
|
||||
using NitroxModel.Helper;
|
||||
using NitroxServer_Subnautica.Resources.Parsers.Helper;
|
||||
|
||||
namespace NitroxServer_Subnautica.Resources.Parsers;
|
||||
|
||||
public abstract class AssetParser
|
||||
{
|
||||
protected static readonly string rootPath;
|
||||
protected static readonly AssetsManager assetsManager;
|
||||
|
||||
private static readonly ThreadSafeMonoCecilTempGenerator monoGen;
|
||||
|
||||
static AssetParser()
|
||||
{
|
||||
rootPath = ResourceAssetsParser.FindDirectoryContainingResourceAssets();
|
||||
assetsManager = new AssetsManager();
|
||||
assetsManager.LoadClassPackage(Path.Combine(NitroxUser.AssetsPath, "Resources", "classdata.tpk"));
|
||||
assetsManager.LoadClassDatabaseFromPackage("2019.4.36f1");
|
||||
assetsManager.SetMonoTempGenerator(monoGen = new ThreadSafeMonoCecilTempGenerator(Path.Combine(rootPath, "Managed")));
|
||||
}
|
||||
|
||||
public static void Dispose()
|
||||
{
|
||||
assetsManager.UnloadAll(true);
|
||||
monoGen.Dispose();
|
||||
}
|
||||
}
|
@@ -0,0 +1,28 @@
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using AssetsTools.NET;
|
||||
using AssetsTools.NET.Extra;
|
||||
|
||||
namespace NitroxServer_Subnautica.Resources.Parsers.Abstract;
|
||||
|
||||
public abstract class BundleFileParser<T> : AssetParser
|
||||
{
|
||||
protected static AssetsFileInstance assetFileInst;
|
||||
protected static AssetsFile bundleFile;
|
||||
|
||||
protected BundleFileParser(string bundleName, int index)
|
||||
{
|
||||
string standaloneFolderName = "StandaloneWindows64";
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||
{
|
||||
standaloneFolderName = "StandaloneOSX";
|
||||
}
|
||||
string bundlePath = Path.Combine(ResourceAssetsParser.FindDirectoryContainingResourceAssets(), "StreamingAssets", "aa", standaloneFolderName, bundleName);
|
||||
BundleFileInstance bundleFileInst = assetsManager.LoadBundleFile(bundlePath);
|
||||
assetFileInst = assetsManager.LoadAssetsFileFromBundle(bundleFileInst, index, true);
|
||||
bundleFile = assetFileInst.file;
|
||||
}
|
||||
|
||||
public abstract T ParseFile();
|
||||
|
||||
}
|
@@ -0,0 +1,18 @@
|
||||
using System.IO;
|
||||
using AssetsTools.NET;
|
||||
using AssetsTools.NET.Extra;
|
||||
|
||||
namespace NitroxServer_Subnautica.Resources.Parsers.Abstract;
|
||||
|
||||
public abstract class ResourceFileParser<T> : AssetParser
|
||||
{
|
||||
protected static readonly AssetsFileInstance resourceInst;
|
||||
protected static readonly AssetsFile resourceFile;
|
||||
|
||||
static ResourceFileParser()
|
||||
{
|
||||
resourceInst = assetsManager.LoadAssetsFile(Path.Combine(rootPath, "resources.assets"), true);
|
||||
resourceFile = resourceInst.file;
|
||||
}
|
||||
public abstract T ParseFile();
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
using AssetsTools.NET;
|
||||
using AssetsTools.NET.Extra;
|
||||
using NitroxServer_Subnautica.Resources.Parsers.Abstract;
|
||||
using NitroxServer_Subnautica.Resources.Parsers.Helper;
|
||||
|
||||
namespace NitroxServer_Subnautica.Resources.Parsers;
|
||||
|
||||
public class EntityDistributionsParser : ResourceFileParser<string>
|
||||
{
|
||||
public override string ParseFile()
|
||||
{
|
||||
AssetFileInfo assetFileInfo = resourceFile.GetAssetInfo(assetsManager, "EntityDistributions", AssetClassID.TextAsset);
|
||||
AssetTypeValueField assetValue = assetsManager.GetBaseField(resourceInst, assetFileInfo);
|
||||
string json = assetValue["m_Script"].AsString;
|
||||
|
||||
assetsManager.UnloadAll();
|
||||
return json;
|
||||
}
|
||||
}
|
@@ -0,0 +1,23 @@
|
||||
using AssetsTools.NET;
|
||||
using NitroxModel.DataStructures.Unity;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxServer_Subnautica.Resources.Parsers.Helper;
|
||||
|
||||
public static class AssetTypeValueFieldExtension
|
||||
{
|
||||
public static Vector3 ToVector3(this AssetTypeValueField valueField)
|
||||
{
|
||||
return new Vector3(valueField["x"].AsFloat, valueField["y"].AsFloat, valueField["z"].AsFloat);
|
||||
}
|
||||
|
||||
public static NitroxVector3 ToNitroxVector3(this AssetTypeValueField valueField)
|
||||
{
|
||||
return new NitroxVector3(valueField["x"].AsFloat, valueField["y"].AsFloat, valueField["z"].AsFloat);
|
||||
}
|
||||
|
||||
public static NitroxQuaternion ToNitroxQuaternion(this AssetTypeValueField valueField)
|
||||
{
|
||||
return new NitroxQuaternion(valueField["x"].AsFloat, valueField["y"].AsFloat, valueField["z"].AsFloat, valueField["w"].AsFloat);
|
||||
}
|
||||
}
|
@@ -0,0 +1,147 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using AssetsTools.NET;
|
||||
using AssetsTools.NET.Extra;
|
||||
using NitroxModel.DataStructures.Unity;
|
||||
|
||||
namespace NitroxServer_Subnautica.Resources.Parsers.Helper;
|
||||
|
||||
public class AssetsBundleManager : AssetsManager
|
||||
{
|
||||
private readonly string aaRootPath;
|
||||
private readonly Dictionary<AssetsFileInstance, string[]> dependenciesByAssetFileInst = new();
|
||||
private ThreadSafeMonoCecilTempGenerator monoTempGenerator;
|
||||
|
||||
public AssetsBundleManager(string aaRootPath)
|
||||
{
|
||||
this.aaRootPath = aaRootPath;
|
||||
}
|
||||
|
||||
public string CleanBundlePath(string bundlePath)
|
||||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||
{
|
||||
bundlePath = bundlePath.Replace('\\', '/');
|
||||
}
|
||||
|
||||
return aaRootPath + bundlePath.Substring(bundlePath.IndexOf('}') + 1);
|
||||
}
|
||||
|
||||
public AssetsFileInstance LoadBundleWithDependencies(string[] bundlePaths)
|
||||
{
|
||||
BundleFileInstance bundleFile = LoadBundleFile(CleanBundlePath(bundlePaths[0]));
|
||||
AssetsFileInstance assetFileInstance = LoadAssetsFileFromBundle(bundleFile, 0);
|
||||
|
||||
dependenciesByAssetFileInst[assetFileInstance] = bundlePaths;
|
||||
return assetFileInstance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copied from https://github.com/nesrak1/AssetsTools.NET#full-monobehaviour-writing-example
|
||||
/// </summary>
|
||||
/// <param name="inst"><see cref="AssetsFileInstance" /> instance currently used</param>
|
||||
/// <param name="targetGameObjectValue"><see cref="AssetFileInfo" /> of the target GameObject</param>
|
||||
/// <param name="targetClassName">Class name of the target MonoBehaviour</param>
|
||||
public AssetFileInfo GetMonoBehaviourFromGameObject(AssetsFileInstance inst, AssetFileInfo targetGameObjectValue, string targetClassName)
|
||||
{
|
||||
//example for finding a specific script and modifying the script on a GameObject
|
||||
AssetTypeValueField playerBf = GetBaseField(inst, targetGameObjectValue);
|
||||
AssetTypeValueField playerComponentArr = playerBf["m_Component"]["Array"];
|
||||
|
||||
AssetFileInfo monoBehaviourInf = null;
|
||||
//first let's search for the MonoBehaviour we want in a GameObject
|
||||
foreach (AssetTypeValueField child in playerComponentArr.Children)
|
||||
{
|
||||
//get component info (but don't deserialize yet, loading assets we don't need is wasteful)
|
||||
AssetTypeValueField childPtr = child["component"];
|
||||
AssetExternal childExt = GetExtAsset(inst, childPtr, true);
|
||||
AssetFileInfo childInf = childExt.info;
|
||||
|
||||
//skip if not MonoBehaviour
|
||||
if (childInf.GetTypeId(inst.file) != (int)AssetClassID.MonoBehaviour)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
//actually deserialize the MonoBehaviour asset now
|
||||
AssetTypeValueField childBf = GetExtAssetSafe(inst, childPtr).baseField;
|
||||
AssetTypeValueField monoScriptPtr = childBf["m_Script"];
|
||||
|
||||
//get MonoScript from MonoBehaviour
|
||||
AssetExternal monoScriptExt = GetExtAsset(childExt.file, monoScriptPtr);
|
||||
AssetTypeValueField monoScriptBf = monoScriptExt.baseField;
|
||||
|
||||
string className = monoScriptBf["m_ClassName"].AsString;
|
||||
if (className == targetClassName)
|
||||
{
|
||||
monoBehaviourInf = childInf;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return monoBehaviourInf;
|
||||
}
|
||||
|
||||
public NitroxTransform GetTransformFromGameObject(AssetsFileInstance assetFileInst, AssetTypeValueField rootGameObject)
|
||||
{
|
||||
AssetTypeValueField componentArray = rootGameObject["m_Component"]["Array"];
|
||||
|
||||
AssetTypeValueField transformRef = componentArray[0]["component"];
|
||||
AssetTypeValueField transformField = GetExtAsset(assetFileInst, transformRef).baseField;
|
||||
|
||||
return new(transformField["m_LocalPosition"].ToNitroxVector3(), transformField["m_LocalRotation"].ToNitroxQuaternion(), transformField["m_LocalScale"].ToNitroxVector3());
|
||||
}
|
||||
|
||||
public new void SetMonoTempGenerator(IMonoBehaviourTemplateGenerator generator)
|
||||
{
|
||||
monoTempGenerator = (ThreadSafeMonoCecilTempGenerator)generator;
|
||||
base.SetMonoTempGenerator(generator);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a ready to use <see cref="AssetsManager" /> with loaded <see cref="AssetsManager.classDatabase" />, <see cref="AssetsManager.classPackage" /> and
|
||||
/// <see cref="IMonoBehaviourTemplateGenerator" />.
|
||||
/// </summary>
|
||||
public AssetsBundleManager Clone()
|
||||
{
|
||||
AssetsBundleManager bundleManagerInst = new(aaRootPath) { classDatabase = classDatabase, classPackage = classPackage };
|
||||
bundleManagerInst.SetMonoTempGenerator(monoTempGenerator);
|
||||
return bundleManagerInst;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="AssetsManager.UnloadAll" />
|
||||
public new void UnloadAll(bool unloadClassData = false)
|
||||
{
|
||||
if (unloadClassData)
|
||||
{
|
||||
monoTempGenerator.Dispose();
|
||||
}
|
||||
dependenciesByAssetFileInst.Clear();
|
||||
base.UnloadAll(unloadClassData);
|
||||
}
|
||||
|
||||
private AssetExternal GetExtAssetSafe(AssetsFileInstance relativeTo, AssetTypeValueField valueField)
|
||||
{
|
||||
string[] bundlePaths = dependenciesByAssetFileInst[relativeTo];
|
||||
for (int i = 0; i < bundlePaths.Length; i++)
|
||||
{
|
||||
if (i != 0)
|
||||
{
|
||||
BundleFileInstance dependenciesBundleFile = LoadBundleFile(CleanBundlePath(bundlePaths[i]));
|
||||
LoadAssetsFileFromBundle(dependenciesBundleFile, 0);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return GetExtAsset(relativeTo, valueField);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("Could find AssetTypeValueField in given dependencies");
|
||||
}
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
using AssetsTools.NET;
|
||||
using AssetsTools.NET.Extra;
|
||||
|
||||
namespace NitroxServer_Subnautica.Resources.Parsers.Helper;
|
||||
|
||||
public static class AssetsFileMetadataExtension
|
||||
{
|
||||
public static AssetFileInfo GetAssetInfo(this AssetsFile assetsFile, AssetsManager assetsManager, string assetName, AssetClassID classID)
|
||||
{
|
||||
foreach (AssetFileInfo assetInfo in assetsFile.GetAssetsOfType(classID))
|
||||
{
|
||||
if (AssetHelper.GetAssetNameFast(assetsFile, assetsManager.classDatabase, assetInfo).Equals(assetName))
|
||||
{
|
||||
return assetInfo;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@@ -0,0 +1,41 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using AssetsTools.NET;
|
||||
using AssetsTools.NET.Extra;
|
||||
using Mono.Cecil;
|
||||
|
||||
namespace NitroxServer_Subnautica.Resources.Parsers.Helper;
|
||||
|
||||
public class ThreadSafeMonoCecilTempGenerator : IMonoBehaviourTemplateGenerator, IDisposable
|
||||
{
|
||||
private readonly MonoCecilTempGenerator generator;
|
||||
private readonly Lock locker = new();
|
||||
|
||||
public ThreadSafeMonoCecilTempGenerator(string managedPath)
|
||||
{
|
||||
generator = new MonoCecilTempGenerator(managedPath);
|
||||
}
|
||||
|
||||
public AssetTypeTemplateField GetTemplateField(
|
||||
AssetTypeTemplateField baseField,
|
||||
string assemblyName,
|
||||
string nameSpace,
|
||||
string className,
|
||||
UnityVersion unityVersion)
|
||||
{
|
||||
lock (locker)
|
||||
{
|
||||
return generator.GetTemplateField(baseField, assemblyName, nameSpace, className, unityVersion);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (KeyValuePair<string, AssemblyDefinition> pair in generator.loadedAssemblies)
|
||||
{
|
||||
pair.Value.Dispose();
|
||||
}
|
||||
generator.loadedAssemblies.Clear();
|
||||
}
|
||||
}
|
@@ -0,0 +1,436 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using AddressablesTools;
|
||||
using AddressablesTools.Catalog;
|
||||
using AssetsTools.NET;
|
||||
using AssetsTools.NET.Extra;
|
||||
using Newtonsoft.Json;
|
||||
using NitroxModel.DataStructures.Unity;
|
||||
using NitroxModel.Helper;
|
||||
using NitroxServer_Subnautica.Resources.Parsers.Helper;
|
||||
using NitroxServer.GameLogic.Entities;
|
||||
using NitroxServer.Resources;
|
||||
|
||||
namespace NitroxServer_Subnautica.Resources.Parsers;
|
||||
|
||||
public class PrefabPlaceholderGroupsParser : IDisposable
|
||||
{
|
||||
private readonly string prefabDatabasePath;
|
||||
private readonly string aaRootPath;
|
||||
private readonly AssetsBundleManager am;
|
||||
private readonly ThreadSafeMonoCecilTempGenerator monoGen;
|
||||
private readonly JsonSerializer serializer;
|
||||
|
||||
private readonly ConcurrentDictionary<string, string> classIdByRuntimeKey = new();
|
||||
private readonly ConcurrentDictionary<string, string[]> addressableCatalog = new();
|
||||
private readonly ConcurrentDictionary<string, PrefabPlaceholderAsset> placeholdersByClassId = new();
|
||||
private readonly ConcurrentDictionary<string, PrefabPlaceholdersGroupAsset> groupsByClassId = new();
|
||||
public ConcurrentDictionary<string, string[]> RandomPossibilitiesByClassId = [];
|
||||
|
||||
public PrefabPlaceholderGroupsParser()
|
||||
{
|
||||
string resourcePath = ResourceAssetsParser.FindDirectoryContainingResourceAssets();
|
||||
string managedPath = Path.Combine(resourcePath, "Managed");
|
||||
|
||||
string streamingAssetsPath = Path.Combine(resourcePath, "StreamingAssets");
|
||||
prefabDatabasePath = Path.Combine(streamingAssetsPath, "SNUnmanagedData", "prefabs.db");
|
||||
aaRootPath = Path.Combine(streamingAssetsPath, "aa");
|
||||
|
||||
am = new AssetsBundleManager(aaRootPath);
|
||||
|
||||
// ReSharper disable once StringLiteralTypo)
|
||||
am.LoadClassPackage(Path.Combine(NitroxUser.AssetsPath, "Resources", "classdata.tpk"));
|
||||
am.LoadClassDatabaseFromPackage("2019.4.36f1");
|
||||
am.SetMonoTempGenerator(monoGen = new(managedPath));
|
||||
|
||||
serializer = new()
|
||||
{
|
||||
TypeNameHandling = TypeNameHandling.Auto
|
||||
};
|
||||
}
|
||||
|
||||
public Dictionary<string, PrefabPlaceholdersGroupAsset> ParseFile()
|
||||
{
|
||||
// Get all prefab-classIds linked to the (partial) bundle path
|
||||
Dictionary<string, string> prefabDatabase = LoadPrefabDatabase(prefabDatabasePath);
|
||||
|
||||
// Loading all prefabs by their classId and file paths (first the path to the prefab then the dependencies)
|
||||
LoadAddressableCatalog(prefabDatabase);
|
||||
|
||||
string nitroxCachePath = Path.Combine(NitroxUser.AppDataPath, "Cache");
|
||||
Directory.CreateDirectory(nitroxCachePath);
|
||||
|
||||
Dictionary<string, PrefabPlaceholdersGroupAsset> prefabPlaceholdersGroupPaths = null;
|
||||
string prefabPlaceholdersGroupAssetCachePath = Path.Combine(nitroxCachePath, "PrefabPlaceholdersGroupAssetsCache.json");
|
||||
if (File.Exists(prefabPlaceholdersGroupAssetCachePath))
|
||||
{
|
||||
Cache? cache = DeserializeCache(prefabPlaceholdersGroupAssetCachePath);
|
||||
if (cache.HasValue)
|
||||
{
|
||||
prefabPlaceholdersGroupPaths = cache.Value.PrefabPlaceholdersGroupPaths;
|
||||
RandomPossibilitiesByClassId = cache.Value.RandomPossibilitiesByClassId;
|
||||
Log.Info($"Successfully loaded cache with {prefabPlaceholdersGroupPaths.Count} prefab placeholder groups and {RandomPossibilitiesByClassId.Count} random spawn behaviours.");
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback solution
|
||||
if (prefabPlaceholdersGroupPaths == null)
|
||||
{
|
||||
prefabPlaceholdersGroupPaths = MakeAndSerializeCache(prefabPlaceholdersGroupAssetCachePath);
|
||||
Log.Info($"Successfully built cache with {prefabPlaceholdersGroupPaths.Count} prefab placeholder groups and {RandomPossibilitiesByClassId.Count} random spawn behaviours. Future server starts will take less time.");
|
||||
}
|
||||
|
||||
// Select only prefabs with a PrefabPlaceholdersGroups component in the root ans link them with their dependencyPaths
|
||||
// Do not remove: the internal cache list is slowing down the process more than loading a few assets again. There maybe is a better way in the new AssetToolsNetVersion but we need a byte to texture library bc ATNs sub-package is only for netstandard.
|
||||
am.UnloadAll();
|
||||
|
||||
// Get all needed data for the filtered PrefabPlaceholdersGroups to construct PrefabPlaceholdersGroupAssets and add them to the dictionary by classId
|
||||
return prefabPlaceholdersGroupPaths;
|
||||
}
|
||||
|
||||
private Dictionary<string, PrefabPlaceholdersGroupAsset> MakeAndSerializeCache(string filePath)
|
||||
{
|
||||
ConcurrentDictionary<string, string[]> prefabPlaceholdersGroupPaths = GetAllPrefabPlaceholdersGroupsFast();
|
||||
Dictionary<string, PrefabPlaceholdersGroupAsset> prefabPlaceholdersGroupAssets = new(GetPrefabPlaceholderGroupAssetsByGroupClassId(prefabPlaceholdersGroupPaths));
|
||||
using StreamWriter stream = File.CreateText(filePath);
|
||||
serializer.Serialize(stream, new Cache(prefabPlaceholdersGroupAssets, RandomPossibilitiesByClassId));
|
||||
|
||||
return prefabPlaceholdersGroupAssets;
|
||||
}
|
||||
|
||||
private Cache? DeserializeCache(string filePath)
|
||||
{
|
||||
try
|
||||
{
|
||||
using StreamReader reader = File.OpenText(filePath);
|
||||
|
||||
return (Cache)serializer.Deserialize(reader, typeof(Cache));
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.Error(exception, "An error occurred while deserializing the game Cache. Re-creating it.");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Dictionary<string, string> LoadPrefabDatabase(string fullFilename)
|
||||
{
|
||||
Dictionary<string, string> prefabFiles = new();
|
||||
if (!File.Exists(fullFilename))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
using FileStream input = File.OpenRead(fullFilename);
|
||||
using BinaryReader binaryReader = new(input);
|
||||
int num = binaryReader.ReadInt32();
|
||||
|
||||
for (int i = 0; i < num; i++)
|
||||
{
|
||||
string key = binaryReader.ReadString();
|
||||
string value = binaryReader.ReadString();
|
||||
prefabFiles[key] = value;
|
||||
}
|
||||
|
||||
return prefabFiles;
|
||||
}
|
||||
|
||||
private void LoadAddressableCatalog(Dictionary<string, string> prefabDatabase)
|
||||
{
|
||||
ContentCatalogData ccd = AddressablesJsonParser.FromString(File.ReadAllText(Path.Combine(aaRootPath, "catalog.json")));
|
||||
Dictionary<string, string> classIdByPath = prefabDatabase.ToDictionary(m => m.Value, m => m.Key);
|
||||
|
||||
foreach (KeyValuePair<object, List<ResourceLocation>> entry in ccd.Resources)
|
||||
{
|
||||
if (entry.Key is string primaryKey && primaryKey.Length == 32 &&
|
||||
classIdByPath.TryGetValue(entry.Value[0].PrimaryKey, out string classId))
|
||||
{
|
||||
classIdByRuntimeKey.TryAdd(primaryKey, classId);
|
||||
}
|
||||
}
|
||||
foreach (KeyValuePair<string, string> prefabAddressable in prefabDatabase)
|
||||
{
|
||||
foreach (ResourceLocation resourceLocation in ccd.Resources[prefabAddressable.Value])
|
||||
{
|
||||
if (resourceLocation.ProviderId != "UnityEngine.ResourceManagement.ResourceProviders.BundledAssetProvider")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
List<ResourceLocation> resourceLocations = ccd.Resources[resourceLocation.Dependency];
|
||||
|
||||
if (!addressableCatalog.TryAdd(prefabAddressable.Key, resourceLocations.Select(x => x.InternalId).ToArray()))
|
||||
{
|
||||
throw new InvalidOperationException($"Couldn't add item to {nameof(addressableCatalog)}");
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gathers bundle paths by class id for prefab placeholder groups.
|
||||
/// Also fills <see cref="RandomPossibilitiesByClassId"/>
|
||||
/// </summary>
|
||||
private ConcurrentDictionary<string, string[]> GetAllPrefabPlaceholdersGroupsFast()
|
||||
{
|
||||
ConcurrentDictionary<string, string[]> prefabPlaceholdersGroupPaths = new();
|
||||
|
||||
// First step is to find out about the hash of the types PrefabPlaceholdersGroup and SpawnRandom
|
||||
// to be able to recognize them easily later on
|
||||
byte[] prefabPlaceholdersGroupHash = [];
|
||||
byte[] spawnRandomHash = [];
|
||||
|
||||
for (int aaIndex = 0; aaIndex < addressableCatalog.Count; aaIndex++)
|
||||
{
|
||||
KeyValuePair<string, string[]> keyValuePair = addressableCatalog.ElementAt(aaIndex);
|
||||
BundleFileInstance bundleFile = am.LoadBundleFile(am.CleanBundlePath(keyValuePair.Value[0]));
|
||||
AssetsFileInstance assetFileInstance = am.LoadAssetsFileFromBundle(bundleFile, 0);
|
||||
|
||||
foreach (AssetFileInfo monoScriptInfo in assetFileInstance.file.GetAssetsOfType(AssetClassID.MonoScript))
|
||||
{
|
||||
AssetTypeValueField monoScript = am.GetBaseField(assetFileInstance, monoScriptInfo);
|
||||
|
||||
switch (monoScript["m_Name"].AsString)
|
||||
{
|
||||
case "SpawnRandom":
|
||||
spawnRandomHash = new byte[16];
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
spawnRandomHash[i] = monoScript["m_PropertiesHash"][i].AsByte;
|
||||
}
|
||||
break;
|
||||
case "PrefabPlaceholdersGroup":
|
||||
prefabPlaceholdersGroupHash = new byte[16];
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
prefabPlaceholdersGroupHash[i] = monoScript["m_PropertiesHash"][i].AsByte;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (prefabPlaceholdersGroupHash.Length > 0 && spawnRandomHash.Length > 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Now use the bundle paths and the hashes to find out which items from the catalog are important
|
||||
// We fill prefabPlaceholdersGroupPaths and RandomPossibilitiesByClassId when we find objects with a SpawnRandom
|
||||
Parallel.ForEach(addressableCatalog, (keyValuePair) =>
|
||||
{
|
||||
string[] assetPaths = keyValuePair.Value;
|
||||
|
||||
AssetsBundleManager bundleManagerInst = am.Clone();
|
||||
AssetsFileInstance assetFileInstance = bundleManagerInst.LoadBundleWithDependencies(assetPaths);
|
||||
|
||||
foreach (TypeTreeType typeTreeType in assetFileInstance.file.Metadata.TypeTreeTypes)
|
||||
{
|
||||
if (typeTreeType.TypeId != (int)AssetClassID.MonoBehaviour)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (typeTreeType.TypeHash.data.SequenceEqual(prefabPlaceholdersGroupHash))
|
||||
{
|
||||
prefabPlaceholdersGroupPaths.TryAdd(keyValuePair.Key, keyValuePair.Value);
|
||||
break;
|
||||
}
|
||||
else if (typeTreeType.TypeHash.data.SequenceEqual(spawnRandomHash))
|
||||
{
|
||||
AssetsFileInstance assetFileInst = bundleManagerInst.LoadBundleWithDependencies(assetPaths);
|
||||
|
||||
GetPrefabGameObjectInfoFromBundle(bundleManagerInst, assetFileInst, out AssetFileInfo prefabGameObjectInfo);
|
||||
|
||||
AssetFileInfo spawnRandomInfo = bundleManagerInst.GetMonoBehaviourFromGameObject(assetFileInst, prefabGameObjectInfo, "SpawnRandom");
|
||||
// See SpawnRandom.Start
|
||||
AssetTypeValueField spawnRandom = bundleManagerInst.GetBaseField(assetFileInst, spawnRandomInfo);
|
||||
List<string> classIds = [];
|
||||
foreach (AssetTypeValueField assetReference in spawnRandom["assetReferences"])
|
||||
{
|
||||
classIds.Add(classIdByRuntimeKey[assetReference["m_AssetGUID"].AsString]);
|
||||
}
|
||||
|
||||
RandomPossibilitiesByClassId.TryAdd(keyValuePair.Key, [.. classIds]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bundleManagerInst.UnloadAll();
|
||||
});
|
||||
|
||||
return prefabPlaceholdersGroupPaths;
|
||||
}
|
||||
|
||||
private ConcurrentDictionary<string, PrefabPlaceholdersGroupAsset> GetPrefabPlaceholderGroupAssetsByGroupClassId(ConcurrentDictionary<string, string[]> prefabPlaceholdersGroupPaths)
|
||||
{
|
||||
ConcurrentDictionary<string, PrefabPlaceholdersGroupAsset> prefabPlaceholderGroupsByGroupClassId = new();
|
||||
|
||||
Parallel.ForEach(prefabPlaceholdersGroupPaths, (keyValuePair) =>
|
||||
{
|
||||
AssetsBundleManager bundleManagerInst = am.Clone();
|
||||
AssetsFileInstance assetFileInst = bundleManagerInst.LoadBundleWithDependencies(keyValuePair.Value);
|
||||
|
||||
PrefabPlaceholdersGroupAsset prefabPlaceholderGroup = GetAndCachePrefabPlaceholdersGroupOfBundle(bundleManagerInst, assetFileInst, keyValuePair.Key);
|
||||
bundleManagerInst.UnloadAll();
|
||||
|
||||
if (!prefabPlaceholderGroupsByGroupClassId.TryAdd(keyValuePair.Key, prefabPlaceholderGroup))
|
||||
{
|
||||
throw new InvalidOperationException($"Couldn't add item to {nameof(prefabPlaceholderGroupsByGroupClassId)}");
|
||||
}
|
||||
});
|
||||
return prefabPlaceholderGroupsByGroupClassId;
|
||||
}
|
||||
|
||||
private static void GetPrefabGameObjectInfoFromBundle(AssetsBundleManager amInst, AssetsFileInstance assetFileInst, out AssetFileInfo prefabGameObjectInfo)
|
||||
{
|
||||
//Get the main asset with "m_Container" of the "AssetBundle-asset" inside the bundle
|
||||
AssetFileInfo assetBundleInfo = assetFileInst.file.Metadata.GetAssetInfo(1);
|
||||
AssetTypeValueField assetBundleValue = amInst.GetBaseField(assetFileInst, assetBundleInfo);
|
||||
AssetTypeValueField assetBundleContainer = assetBundleValue["m_Container.Array"];
|
||||
long rootAssetPathId = assetBundleContainer.Children[0][1]["asset.m_PathID"].AsLong;
|
||||
|
||||
prefabGameObjectInfo = assetFileInst.file.Metadata.GetAssetInfo(rootAssetPathId);
|
||||
}
|
||||
|
||||
private PrefabPlaceholdersGroupAsset GetAndCachePrefabPlaceholdersGroupOfBundle(AssetsBundleManager amInst, AssetsFileInstance assetFileInst, string classId)
|
||||
{
|
||||
GetPrefabGameObjectInfoFromBundle(amInst, assetFileInst, out AssetFileInfo prefabGameObjectInfo);
|
||||
return GetAndCachePrefabPlaceholdersGroupGroup(amInst, assetFileInst, prefabGameObjectInfo, classId);
|
||||
}
|
||||
|
||||
private PrefabPlaceholdersGroupAsset GetAndCachePrefabPlaceholdersGroupGroup(AssetsBundleManager amInst, AssetsFileInstance assetFileInst, AssetFileInfo rootGameObjectInfo, string classId)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(classId) && groupsByClassId.TryGetValue(classId, out PrefabPlaceholdersGroupAsset cachedGroup))
|
||||
{
|
||||
return cachedGroup;
|
||||
}
|
||||
|
||||
AssetFileInfo prefabPlaceholdersGroupInfo = amInst.GetMonoBehaviourFromGameObject(assetFileInst, rootGameObjectInfo, "PrefabPlaceholdersGroup");
|
||||
if (prefabPlaceholdersGroupInfo == null)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
AssetTypeValueField prefabPlaceholdersGroupScript = amInst.GetBaseField(assetFileInst, prefabPlaceholdersGroupInfo);
|
||||
List<AssetTypeValueField> prefabPlaceholdersOnGroup = prefabPlaceholdersGroupScript["prefabPlaceholders"].Children;
|
||||
|
||||
IPrefabAsset[] prefabPlaceholders = new IPrefabAsset[prefabPlaceholdersOnGroup.Count];
|
||||
|
||||
for (int index = 0; index < prefabPlaceholdersOnGroup.Count; index++)
|
||||
{
|
||||
AssetTypeValueField prefabPlaceholderPtr = prefabPlaceholdersOnGroup[index];
|
||||
AssetTypeValueField prefabPlaceholder = amInst.GetExtAsset(assetFileInst, prefabPlaceholderPtr).baseField;
|
||||
|
||||
AssetTypeValueField gameObjectPtr = prefabPlaceholder["m_GameObject"];
|
||||
AssetTypeValueField gameObjectField = amInst.GetExtAsset(assetFileInst, gameObjectPtr).baseField;
|
||||
NitroxTransform transform = amInst.GetTransformFromGameObject(assetFileInst, gameObjectField);
|
||||
IPrefabAsset asset = GetAndCacheAsset(amInst, prefabPlaceholder["prefabClassId"].AsString);
|
||||
asset.Transform = transform;
|
||||
prefabPlaceholders[index] = asset;
|
||||
}
|
||||
|
||||
PrefabPlaceholdersGroupAsset prefabPlaceholdersGroup = new(classId, prefabPlaceholders);
|
||||
AssetTypeValueField rootGameObjectField = amInst.GetBaseField(assetFileInst, rootGameObjectInfo);
|
||||
NitroxTransform groupTransform = amInst.GetTransformFromGameObject(assetFileInst, rootGameObjectField);
|
||||
prefabPlaceholdersGroup.Transform = groupTransform;
|
||||
|
||||
groupsByClassId[classId] = prefabPlaceholdersGroup;
|
||||
return prefabPlaceholdersGroup;
|
||||
}
|
||||
|
||||
private IPrefabAsset GetAndCacheAsset(AssetsBundleManager amInst, string classId)
|
||||
{
|
||||
if (string.IsNullOrEmpty(classId))
|
||||
{
|
||||
return default;
|
||||
}
|
||||
if (groupsByClassId.TryGetValue(classId, out PrefabPlaceholdersGroupAsset cachedGroup))
|
||||
{
|
||||
return cachedGroup;
|
||||
}
|
||||
else if (placeholdersByClassId.TryGetValue(classId, out PrefabPlaceholderAsset cachedPlaceholder))
|
||||
{
|
||||
return cachedPlaceholder;
|
||||
}
|
||||
if (!addressableCatalog.TryGetValue(classId, out string[] assetPaths))
|
||||
{
|
||||
Log.Error($"Couldn't get PrefabPlaceholder with classId: {classId}");
|
||||
return default;
|
||||
}
|
||||
|
||||
AssetsFileInstance assetFileInst = amInst.LoadBundleWithDependencies(assetPaths);
|
||||
|
||||
GetPrefabGameObjectInfoFromBundle(amInst, assetFileInst, out AssetFileInfo prefabGameObjectInfo);
|
||||
|
||||
AssetFileInfo placeholdersGroupInfo = amInst.GetMonoBehaviourFromGameObject(assetFileInst, prefabGameObjectInfo, "PrefabPlaceholdersGroup");
|
||||
if (placeholdersGroupInfo != null)
|
||||
{
|
||||
PrefabPlaceholdersGroupAsset groupAsset = GetAndCachePrefabPlaceholdersGroupOfBundle(amInst, assetFileInst, classId);
|
||||
groupsByClassId[classId] = groupAsset;
|
||||
return groupAsset;
|
||||
}
|
||||
|
||||
AssetFileInfo spawnRandomInfo = amInst.GetMonoBehaviourFromGameObject(assetFileInst, prefabGameObjectInfo, "SpawnRandom");
|
||||
if (spawnRandomInfo != null)
|
||||
{
|
||||
// See SpawnRandom.Start
|
||||
AssetTypeValueField spawnRandom = amInst.GetBaseField(assetFileInst, spawnRandomInfo);
|
||||
List<string> classIds = new();
|
||||
foreach (AssetTypeValueField assetReference in spawnRandom["assetReferences"])
|
||||
{
|
||||
classIds.Add(classIdByRuntimeKey[assetReference["m_AssetGUID"].AsString]);
|
||||
}
|
||||
|
||||
return new PrefabPlaceholderRandomAsset(classIds);
|
||||
}
|
||||
|
||||
AssetFileInfo databoxSpawnerInfo = amInst.GetMonoBehaviourFromGameObject(assetFileInst, prefabGameObjectInfo, "DataboxSpawner");
|
||||
if (databoxSpawnerInfo != null)
|
||||
{
|
||||
// NB: This spawning should be cancelled if the techType is from a known tech
|
||||
// But it doesn't matter if we still spawn it so we do so.
|
||||
// See DataboxSpawner.Start
|
||||
AssetTypeValueField databoxSpawner = amInst.GetBaseField(assetFileInst, databoxSpawnerInfo);
|
||||
string runtimeKey = databoxSpawner["databoxPrefabReference"]["m_AssetGUID"].AsString;
|
||||
|
||||
PrefabPlaceholderAsset databoxAsset = new(classIdByRuntimeKey[runtimeKey]);
|
||||
placeholdersByClassId[classId] = databoxAsset;
|
||||
return databoxAsset;
|
||||
}
|
||||
|
||||
AssetFileInfo entitySlotInfo = amInst.GetMonoBehaviourFromGameObject(assetFileInst, prefabGameObjectInfo, "EntitySlot");
|
||||
NitroxEntitySlot? nitroxEntitySlot = null;
|
||||
if (entitySlotInfo != null)
|
||||
{
|
||||
AssetTypeValueField entitySlot = amInst.GetBaseField(assetFileInst, entitySlotInfo);
|
||||
string biomeType = ((BiomeType)entitySlot["biomeType"].AsInt).ToString();
|
||||
|
||||
List<string> allowedTypes = [];
|
||||
foreach (AssetTypeValueField allowedType in entitySlot["allowedTypes"])
|
||||
{
|
||||
allowedTypes.Add(((EntitySlot.Type)allowedType.AsInt).ToString());
|
||||
}
|
||||
|
||||
nitroxEntitySlot = new NitroxEntitySlot(biomeType, allowedTypes);
|
||||
}
|
||||
|
||||
PrefabPlaceholderAsset prefabPlaceholderAsset = new(classId, nitroxEntitySlot);
|
||||
placeholdersByClassId[classId] = prefabPlaceholderAsset;
|
||||
return prefabPlaceholderAsset;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
monoGen.Dispose();
|
||||
am.UnloadAll(true);
|
||||
}
|
||||
|
||||
record struct Cache(Dictionary<string, PrefabPlaceholdersGroupAsset> PrefabPlaceholdersGroupPaths, ConcurrentDictionary<string, string[]> RandomPossibilitiesByClassId);
|
||||
}
|
@@ -0,0 +1,51 @@
|
||||
using AssetsTools.NET;
|
||||
using AssetsTools.NET.Extra;
|
||||
using NitroxModel.DataStructures.GameLogic;
|
||||
using NitroxModel.Helper;
|
||||
using NitroxServer_Subnautica.Resources.Parsers.Abstract;
|
||||
using NitroxServer_Subnautica.Resources.Parsers.Helper;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using SixLabors.ImageSharp.Processing;
|
||||
|
||||
namespace NitroxServer_Subnautica.Resources.Parsers;
|
||||
|
||||
public class RandomStartParser : BundleFileParser<RandomStartGenerator>
|
||||
{
|
||||
public RandomStartParser() : base("essentials.unity_0ee8dd89ed55f05bc38a09cc77137d4e.bundle", 0) { }
|
||||
|
||||
public override RandomStartGenerator ParseFile()
|
||||
{
|
||||
AssetFileInfo assetFile = bundleFile.GetAssetInfo(assetsManager, "RandomStart", AssetClassID.Texture2D);
|
||||
AssetTypeValueField textureValueField = assetsManager.GetBaseField(assetFileInst, assetFile);
|
||||
TextureFile textureFile = TextureFile.ReadTextureFile(textureValueField);
|
||||
byte[] texDat = textureFile.GetTextureData(assetFileInst);
|
||||
assetsManager.UnloadAll();
|
||||
|
||||
if (texDat is not { Length: > 0 })
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
Image<Bgra32> texture = Image.LoadPixelData<Bgra32>(texDat, textureFile.m_Width, textureFile.m_Height);
|
||||
texture.Mutate(x => x.Flip(FlipMode.Vertical));
|
||||
return new RandomStartGenerator(new PixelProvider(texture));
|
||||
}
|
||||
|
||||
private class PixelProvider : RandomStartGenerator.IPixelProvider
|
||||
{
|
||||
private readonly Image<Bgra32> texture;
|
||||
|
||||
public PixelProvider(Image<Bgra32> texture)
|
||||
{
|
||||
Validate.NotNull(texture);
|
||||
this.texture = texture;
|
||||
}
|
||||
|
||||
public byte GetRed(int x, int y) => texture[x, y].R;
|
||||
|
||||
public byte GetGreen(int x, int y) => texture[x, y].G;
|
||||
|
||||
public byte GetBlue(int x, int y) => texture[x, y].B;
|
||||
}
|
||||
}
|
@@ -0,0 +1,37 @@
|
||||
using System.Collections.Generic;
|
||||
using AssetsTools.NET;
|
||||
using AssetsTools.NET.Extra;
|
||||
using NitroxServer_Subnautica.Resources.Parsers.Abstract;
|
||||
using NitroxServer_Subnautica.Resources.Parsers.Helper;
|
||||
using UWE;
|
||||
|
||||
namespace NitroxServer_Subnautica.Resources.Parsers;
|
||||
|
||||
public class WorldEntityInfoParser : ResourceFileParser<Dictionary<string, WorldEntityInfo>>
|
||||
{
|
||||
public override Dictionary<string, WorldEntityInfo> ParseFile()
|
||||
{
|
||||
Dictionary<string, WorldEntityInfo> worldEntitiesByClassId = new();
|
||||
|
||||
AssetFileInfo assetFileInfo = resourceFile.GetAssetInfo(assetsManager, "WorldEntityData", AssetClassID.MonoBehaviour);
|
||||
AssetTypeValueField assetValue = assetsManager.GetBaseField(resourceInst, assetFileInfo);
|
||||
|
||||
foreach (AssetTypeValueField info in assetValue["infos"])
|
||||
{
|
||||
WorldEntityInfo entityData = new()
|
||||
{
|
||||
classId = info["classId"].AsString,
|
||||
techType = (TechType)info["techType"].AsInt,
|
||||
slotType = (EntitySlot.Type)info["slotType"].AsInt,
|
||||
prefabZUp = info["prefabZUp"].AsBool,
|
||||
cellLevel = (LargeWorldEntity.CellLevel)info["cellLevel"].AsInt,
|
||||
localScale = info["localScale"].ToVector3()
|
||||
};
|
||||
|
||||
worldEntitiesByClassId.Add(entityData.classId, entityData);
|
||||
}
|
||||
|
||||
assetsManager.UnloadAll();
|
||||
return worldEntitiesByClassId;
|
||||
}
|
||||
}
|
26
NitroxServer-Subnautica/Resources/ResourceAssets.cs
Normal file
26
NitroxServer-Subnautica/Resources/ResourceAssets.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using System.Collections.Generic;
|
||||
using NitroxModel.DataStructures.GameLogic;
|
||||
using NitroxModel.Helper;
|
||||
using NitroxServer.Resources;
|
||||
using UWE;
|
||||
|
||||
namespace NitroxServer_Subnautica.Resources;
|
||||
|
||||
public class ResourceAssets
|
||||
{
|
||||
public Dictionary<string, WorldEntityInfo> WorldEntitiesByClassId { get; init; } = new();
|
||||
public string LootDistributionsJson { get; init; } = "";
|
||||
public Dictionary<string, PrefabPlaceholdersGroupAsset> PrefabPlaceholdersGroupsByGroupClassId { get; init; } = new();
|
||||
public Dictionary<string, string[]> RandomPossibilitiesByClassId { get; init; }
|
||||
public RandomStartGenerator NitroxRandom { get; init; }
|
||||
|
||||
public static void ValidateMembers(ResourceAssets resourceAssets)
|
||||
{
|
||||
Validate.NotNull(resourceAssets);
|
||||
Validate.IsTrue(resourceAssets.WorldEntitiesByClassId.Count > 0);
|
||||
Validate.IsTrue(resourceAssets.LootDistributionsJson != "");
|
||||
Validate.IsTrue(resourceAssets.PrefabPlaceholdersGroupsByGroupClassId.Count > 0);
|
||||
Validate.IsTrue(resourceAssets.RandomPossibilitiesByClassId.Count > 0);
|
||||
Validate.NotNull(resourceAssets.NitroxRandom);
|
||||
}
|
||||
}
|
62
NitroxServer-Subnautica/Resources/ResourceAssetsParser.cs
Normal file
62
NitroxServer-Subnautica/Resources/ResourceAssetsParser.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
using System.IO;
|
||||
using NitroxModel;
|
||||
using NitroxModel.Helper;
|
||||
using NitroxServer_Subnautica.Resources.Parsers;
|
||||
|
||||
namespace NitroxServer_Subnautica.Resources;
|
||||
|
||||
public static class ResourceAssetsParser
|
||||
{
|
||||
private static ResourceAssets resourceAssets;
|
||||
|
||||
public static ResourceAssets Parse()
|
||||
{
|
||||
if (resourceAssets != null)
|
||||
{
|
||||
return resourceAssets;
|
||||
}
|
||||
|
||||
using (PrefabPlaceholderGroupsParser prefabPlaceholderGroupsParser = new())
|
||||
{
|
||||
resourceAssets = new ResourceAssets
|
||||
{
|
||||
WorldEntitiesByClassId = new WorldEntityInfoParser().ParseFile(),
|
||||
LootDistributionsJson = new EntityDistributionsParser().ParseFile(),
|
||||
PrefabPlaceholdersGroupsByGroupClassId = prefabPlaceholderGroupsParser.ParseFile(),
|
||||
NitroxRandom = new RandomStartParser().ParseFile(),
|
||||
RandomPossibilitiesByClassId = new(prefabPlaceholderGroupsParser.RandomPossibilitiesByClassId)
|
||||
};
|
||||
}
|
||||
AssetParser.Dispose();
|
||||
|
||||
ResourceAssets.ValidateMembers(resourceAssets);
|
||||
return resourceAssets;
|
||||
}
|
||||
|
||||
public static string FindDirectoryContainingResourceAssets()
|
||||
{
|
||||
string subnauticaPath = NitroxUser.GamePath;
|
||||
if (string.IsNullOrEmpty(subnauticaPath))
|
||||
{
|
||||
throw new DirectoryNotFoundException("Could not locate Subnautica installation directory for resource parsing.");
|
||||
}
|
||||
|
||||
if (File.Exists(Path.Combine(subnauticaPath, GameInfo.Subnautica.DataFolder, "resources.assets")))
|
||||
{
|
||||
return Path.Combine(subnauticaPath, GameInfo.Subnautica.DataFolder);
|
||||
}
|
||||
if (File.Exists(Path.Combine("..", "resources.assets"))) // SubServer => Subnautica/Subnautica_Data/SubServer
|
||||
{
|
||||
return Path.GetFullPath(Path.Combine(".."));
|
||||
}
|
||||
if (File.Exists(Path.Combine("..", GameInfo.Subnautica.DataFolder, "resources.assets"))) // SubServer => Subnautica/SubServer
|
||||
{
|
||||
return Path.GetFullPath(Path.Combine("..", GameInfo.Subnautica.DataFolder));
|
||||
}
|
||||
if (File.Exists("resources.assets")) // SubServer/* => Subnautica/Subnautica_Data/
|
||||
{
|
||||
return Directory.GetCurrentDirectory();
|
||||
}
|
||||
throw new FileNotFoundException("Make sure resources.assets is in current or parent directory and readable.");
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
using NitroxServer.Serialization;
|
||||
|
||||
namespace NitroxServer_Subnautica.Serialization
|
||||
{
|
||||
class SubnauticaServerJsonSerializer : ServerJsonSerializer
|
||||
{
|
||||
public SubnauticaServerJsonSerializer()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,30 @@
|
||||
using NitroxModel.DataStructures.Unity;
|
||||
using NitroxModel_Subnautica.DataStructures.Surrogates;
|
||||
using NitroxServer.Serialization;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxServer_Subnautica.Serialization
|
||||
{
|
||||
class SubnauticaServerProtoBufSerializer : ServerProtoBufSerializer
|
||||
{
|
||||
public SubnauticaServerProtoBufSerializer(params string[] assemblies) : base(assemblies)
|
||||
{
|
||||
RegisterHardCodedTypes();
|
||||
}
|
||||
|
||||
// Register here all hard coded types, that come from NitroxModel-Subnautica or NitroxServer-Subnautica
|
||||
private void RegisterHardCodedTypes()
|
||||
{
|
||||
Model.Add(typeof(Light), true);
|
||||
Model.Add(typeof(BoxCollider), true);
|
||||
Model.Add(typeof(SphereCollider), true);
|
||||
Model.Add(typeof(MeshCollider), true);
|
||||
Model.Add(typeof(Vector3), false).SetSurrogate(typeof(Vector3Surrogate));
|
||||
Model.Add(typeof(NitroxVector3), false).SetSurrogate(typeof(Vector3Surrogate));
|
||||
Model.Add(typeof(Quaternion), false).SetSurrogate(typeof(QuaternionSurrogate));
|
||||
Model.Add(typeof(NitroxQuaternion), false).SetSurrogate(typeof(QuaternionSurrogate));
|
||||
Model.Add(typeof(Transform), false).SetSurrogate(typeof(NitroxTransform));
|
||||
Model.Add(typeof(GameObject), false).SetSurrogate(typeof(NitroxServer.UnityStubs.GameObject));
|
||||
}
|
||||
}
|
||||
}
|
67
NitroxServer-Subnautica/SubnauticaServerAutoFacRegistrar.cs
Normal file
67
NitroxServer-Subnautica/SubnauticaServerAutoFacRegistrar.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
using Autofac;
|
||||
using NitroxModel;
|
||||
using NitroxModel.DataStructures.GameLogic.Entities;
|
||||
using NitroxModel.GameLogic.FMOD;
|
||||
using NitroxModel.Helper;
|
||||
using NitroxModel_Subnautica.DataStructures.GameLogic.Entities;
|
||||
using NitroxModel_Subnautica.Helper;
|
||||
using NitroxServer;
|
||||
using NitroxServer.GameLogic;
|
||||
using NitroxServer.GameLogic.Entities;
|
||||
using NitroxServer.GameLogic.Entities.Spawning;
|
||||
using NitroxServer.Resources;
|
||||
using NitroxServer.Serialization;
|
||||
using NitroxServer_Subnautica.GameLogic;
|
||||
using NitroxServer_Subnautica.GameLogic.Entities;
|
||||
using NitroxServer_Subnautica.GameLogic.Entities.Spawning;
|
||||
using NitroxServer_Subnautica.Resources;
|
||||
using NitroxServer_Subnautica.Serialization;
|
||||
|
||||
namespace NitroxServer_Subnautica
|
||||
{
|
||||
public class SubnauticaServerAutoFacRegistrar : ServerAutoFacRegistrar
|
||||
{
|
||||
public override void RegisterDependencies(ContainerBuilder containerBuilder)
|
||||
{
|
||||
base.RegisterDependencies(containerBuilder);
|
||||
|
||||
containerBuilder.RegisterType<SimulationWhitelist>()
|
||||
.As<ISimulationWhitelist>()
|
||||
.SingleInstance();
|
||||
containerBuilder.Register(c => new SubnauticaServerProtoBufSerializer(
|
||||
"Assembly-CSharp",
|
||||
"Assembly-CSharp-firstpass",
|
||||
"NitroxModel",
|
||||
"NitroxModel-Subnautica"))
|
||||
.As<ServerProtoBufSerializer, IServerSerializer>()
|
||||
.SingleInstance();
|
||||
containerBuilder.Register(c => new SubnauticaServerJsonSerializer())
|
||||
.As<ServerJsonSerializer, IServerSerializer>()
|
||||
.SingleInstance();
|
||||
|
||||
containerBuilder.RegisterType<SubnauticaEntitySpawnPointFactory>().As<EntitySpawnPointFactory>().SingleInstance();
|
||||
|
||||
ResourceAssets resourceAssets = ResourceAssetsParser.Parse();
|
||||
|
||||
containerBuilder.Register(c => resourceAssets).SingleInstance();
|
||||
containerBuilder.Register(c => resourceAssets.WorldEntitiesByClassId).SingleInstance();
|
||||
containerBuilder.Register(c => resourceAssets.PrefabPlaceholdersGroupsByGroupClassId).SingleInstance();
|
||||
containerBuilder.Register(c => resourceAssets.NitroxRandom).SingleInstance();
|
||||
containerBuilder.RegisterType<SubnauticaUweWorldEntityFactory>().As<IUweWorldEntityFactory>().SingleInstance();
|
||||
|
||||
SubnauticaUwePrefabFactory prefabFactory = new SubnauticaUwePrefabFactory(resourceAssets.LootDistributionsJson);
|
||||
containerBuilder.Register(c => prefabFactory).As<IUwePrefabFactory>().SingleInstance();
|
||||
containerBuilder.RegisterType<SubnauticaEntityBootstrapperManager>()
|
||||
.As<IEntityBootstrapperManager>()
|
||||
.SingleInstance();
|
||||
|
||||
containerBuilder.RegisterType<SubnauticaMap>().As<IMap>().InstancePerLifetimeScope();
|
||||
containerBuilder.RegisterType<EntityRegistry>().AsSelf().InstancePerLifetimeScope();
|
||||
containerBuilder.RegisterType<SubnauticaWorldModifier>().As<IWorldModifier>().InstancePerLifetimeScope();
|
||||
containerBuilder.Register(c => FMODWhitelist.Load(GameInfo.Subnautica)).InstancePerLifetimeScope();
|
||||
|
||||
containerBuilder.Register(_ => new RandomSpawnSpoofer(resourceAssets.RandomPossibilitiesByClassId))
|
||||
.SingleInstance();
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user