first commit

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

View File

@@ -0,0 +1,203 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using HarmonyLib;
using NitroxClient.Communication.Abstract;
using NitroxClient.GameLogic.Bases;
using NitroxClient.GameLogic.Spawning.Bases;
using NitroxClient.GameLogic.Spawning.Metadata;
using NitroxClient.Helpers;
using NitroxClient.MonoBehaviours;
using NitroxModel.DataStructures;
using NitroxModel.DataStructures.GameLogic;
using NitroxModel.DataStructures.GameLogic.Bases;
using NitroxModel.DataStructures.GameLogic.Entities;
using NitroxModel.DataStructures.GameLogic.Entities.Bases;
using NitroxModel.Helper;
using NitroxModel.Packets;
using NitroxPatcher.PatternMatching;
using UnityEngine;
using UWE;
using static System.Reflection.Emit.OpCodes;
using static NitroxClient.GameLogic.Bases.BuildingHandler;
namespace NitroxPatcher.Patches.Dynamic;
public sealed partial class Constructable_Construct_Patch : NitroxPatch, IDynamicPatch
{
public static readonly MethodInfo TARGET_METHOD = Reflect.Method((Constructable t) => t.Construct());
private static TemporaryBuildData Temp => BuildingHandler.Main.Temp;
public static readonly InstructionsPattern InstructionsPattern = new()
{
Div,
Stfld,
Ldc_I4_0,
Ret,
Ldarg_0,
{ InstructionPattern.Call(nameof(Constructable), nameof(Constructable.UpdateMaterial)), "Insert" }
};
public static readonly List<CodeInstruction> InstructionsToAdd = new()
{
new(Ldarg_0),
new(Ldc_I4_1), // True for "constructing"
new(Call, Reflect.Method(() => ConstructionAmountModified(default, default)))
};
public static IEnumerable<CodeInstruction> Transpiler(MethodBase original, IEnumerable<CodeInstruction> instructions) =>
instructions.Transform(InstructionsPattern, (label, instruction) =>
{
if (label.Equals("Insert"))
{
return InstructionsToAdd;
}
return null;
});
public static void ConstructionAmountModified(Constructable constructable, bool constructing)
{
// We only manage the amount change, not the deconstruction/construction action
if (!constructable.TryGetNitroxId(out NitroxId entityId))
{
Log.ErrorOnce($"[{nameof(ConstructionAmountModified)}] Couldn't find a NitroxEntity on {constructable.name}");
return;
}
float amount = NitroxModel.Helper.Mathf.Clamp01(constructable.constructedAmount);
// An object is destroyed when amount = 0 AND if we are destructing
// so we don't need the broadcast if we are trying to construct with not enough resources (amount = 0)
if (amount == 0f && constructing)
{
return;
}
/*
* Different cases:
* - Normal module (only Constructable), just let it go to 1.0f normally
* - ConstructableBase:
* - if it's already in a base, simply update the current base
* - else, create a new base
*/
if (amount == 1f)
{
Resolve<ThrottledPacketSender>().RemovePendingPackets(entityId);
if (constructable is ConstructableBase constructableBase)
{
CoroutineHost.StartCoroutine(BroadcastObjectBuilt(constructableBase, entityId));
return;
}
IEnumerator postSpawner = BuildingPostSpawner.ApplyPostSpawner(constructable.gameObject, entityId);
// Can be null if no post spawner is set for the constructable's techtype
if (postSpawner != null)
{
CoroutineHost.StartCoroutine(postSpawner);
}
// To avoid any unrequired throttled packet to be sent we clean the pending throttled packets for this object
Resolve<IPacketSender>().Send(new ModifyConstructedAmount(entityId, 1f));
return;
}
// update as a normal module
Resolve<ThrottledPacketSender>().SendThrottled(new ModifyConstructedAmount(entityId, amount),
(packet) => { return packet.GhostId; }, 0.1f);
}
public static IEnumerator BroadcastObjectBuilt(ConstructableBase constructableBase, NitroxId entityId)
{
BaseGhost baseGhost = constructableBase.model.GetComponent<BaseGhost>();
constructableBase.SetState(true, true);
if (!baseGhost.targetBase)
{
Log.Error("Something wrong happened, couldn't find base after finishing building ghost");
yield break;
}
Base targetBase = baseGhost.targetBase;
Base parentBase = null;
if (constructableBase.tr.parent)
{
parentBase = constructableBase.GetComponentInParent<Base>(true);
}
// If a module was spawned we need to transfer the ghost id to it for further recognition
BuildUtils.TryTransferIdFromGhostToModule(baseGhost, entityId, constructableBase, out GameObject moduleObject);
// Have a delay for baseGhost to be actually destroyed
yield return null;
if (parentBase)
{
// update existing base
if (!parentBase.TryGetNitroxId(out NitroxId parentId))
{
BuildingHandler.Main.FailedOperations++;
Log.Error($"[{nameof(BroadcastObjectBuilt)}] Parent base doesn't have a NitroxEntity, which is not normal");
yield break;
}
MoonpoolManager moonpoolManager = parentBase.gameObject.EnsureComponent<MoonpoolManager>();
GlobalRootEntity builtPiece = null;
if (moduleObject)
{
if (moduleObject.TryGetComponent(out IBaseModule builtModule))
{
builtPiece = InteriorPieceEntitySpawner.From(builtModule, Resolve<EntityMetadataManager>());
}
else if (moduleObject.GetComponent<VehicleDockingBay>())
{
builtPiece = moonpoolManager.LatestRegisteredMoonpool;
}
else if (moduleObject.TryGetComponent(out MapRoomFunctionality mapRoomFunctionality))
{
builtPiece = BuildUtils.CreateMapRoomEntityFrom(mapRoomFunctionality, parentBase, entityId, parentId);
}
}
SendUpdateBase(parentBase, parentId, entityId, builtPiece, moonpoolManager);
}
else
{
// Must happen before NitroxEntity.SetNewId because else, if a moonpool was marked with the same id, this id be will unlinked from the base object
if (baseGhost.targetBase.TryGetComponent(out MoonpoolManager moonpoolManager))
{
moonpoolManager.LateAssignNitroxEntity(entityId);
moonpoolManager.OnPostRebuildGeometry(baseGhost.targetBase);
}
// create a new base
NitroxEntity.SetNewId(baseGhost.targetBase.gameObject, entityId);
BuildingHandler.Main.EnsureTracker(entityId).ResetToId();
Resolve<IPacketSender>().Send(new PlaceBase(entityId, BuildEntitySpawner.From(targetBase, Resolve<EntityMetadataManager>())));
}
if (moduleObject)
{
yield return BuildingPostSpawner.ApplyPostSpawner(moduleObject, entityId);
}
Temp.Dispose();
}
private static void SendUpdateBase(Base @base, NitroxId baseId, NitroxId pieceId, GlobalRootEntity builtPieceEntity, MoonpoolManager moonpoolManager)
{
List<Entity> childEntities = BuildUtils.GetChildEntities(@base, baseId, Resolve<EntityMetadataManager>());
// We get InteriorPieceEntity children from the base and make up a dictionary with their updated data (their BaseFace)
Dictionary<NitroxId, NitroxBaseFace> updatedChildren = childEntities.OfType<InteriorPieceEntity>()
.ToDictionary(entity => entity.Id, entity => entity.BaseFace);
// Same for MapRooms
Dictionary<NitroxId, NitroxInt3> updatedMapRooms = childEntities.OfType<MapRoomEntity>()
.ToDictionary(entity => entity.Id, entity => entity.Cell);
BuildingHandler.Main.EnsureTracker(baseId).LocalOperations++;
int operationId = BuildingHandler.Main.GetCurrentOperationIdOrDefault(baseId);
UpdateBase updateBase = new(baseId, pieceId, BuildEntitySpawner.GetBaseData(@base), builtPieceEntity, updatedChildren, moonpoolManager.GetMoonpoolsUpdate(), updatedMapRooms, Temp.ChildrenTransfer, operationId);
// TODO: (for server-side) Find a way to optimize this (maybe by copying BaseGhost.Finish() => Base.CopyFrom)
Resolve<IPacketSender>().Send(updateBase);
}
}