Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sync leviathans gameplay #2106

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Sync Sea Treaders
  • Loading branch information
tornac1234 committed Mar 24, 2024
commit c6f169c6ce29e0f54d678e8a00183ecb674ab842
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
using System.Collections.Generic;
using System.Linq;
using FluentAssertions;
using HarmonyLib;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using NitroxTest.Patcher;

namespace NitroxPatcher.Patches.Dynamic;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
using System.Collections.Generic;
using System.Linq;
using FluentAssertions;
using HarmonyLib;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using NitroxTest.Patcher;

namespace NitroxPatcher.Patches.Dynamic;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
using System.Collections.Generic;
using System.Linq;
using FluentAssertions;
using HarmonyLib;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using NitroxTest.Patcher;

namespace NitroxPatcher.Patches.Dynamic;
Expand Down
16 changes: 16 additions & 0 deletions Nitrox.Test/Patcher/Patches/Dynamic/Poop_Perform_PatchTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using HarmonyLib;
using NitroxTest.Patcher;

namespace NitroxPatcher.Patches.Dynamic;

[TestClass]
public class Poop_Perform_PatchTest
{
[TestMethod]
public void Sanity()
{
IEnumerable<CodeInstruction> originalIl = PatchTestHelper.GetInstructionsFromMethod(Poop_Perform_Patch.TARGET_METHOD);
IEnumerable<CodeInstruction> transformedIl = Poop_Perform_Patch.Transpiler(originalIl);
transformedIl.Count().Should().Be(originalIl.Count() + 1);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using HarmonyLib;
using NitroxTest.Patcher;

namespace NitroxPatcher.Patches.Dynamic;

[TestClass]
public class SeaTreaderSounds_SpawnChunks_PatchTest
{
[TestMethod]
public void Sanity()
{
IEnumerable<CodeInstruction> originalIl = PatchTestHelper.GetInstructionsFromMethod(SeaTreaderSounds_SpawnChunks_Patch.TARGET_METHOD);
IEnumerable<CodeInstruction> transformedIl = SeaTreaderSounds_SpawnChunks_Patch.Transpiler(originalIl);
transformedIl.Count().Should().Be(originalIl.Count() + 3);
}
}
5 changes: 5 additions & 0 deletions Nitrox.Test/Server/Serialization/WorldPersistenceTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,11 @@ private static void EntityTest(Entity entity, Entity entityAfter)
case EatableMetadata metadata when entityAfter.Metadata is EatableMetadata metadataAfter:
Assert.AreEqual(metadata.TimeDecayStart, metadataAfter.TimeDecayStart);
break;
case SeaTreaderMetadata metadata when entityAfter.Metadata is SeaTreaderMetadata metadataAfter:
Assert.AreEqual(metadata.ReverseDirection, metadataAfter.ReverseDirection);
Assert.AreEqual(metadata.GrazingEndTime, metadataAfter.GrazingEndTime);
Assert.AreEqual(metadata.LeashPosition, metadataAfter.LeashPosition);
break;
default:
Assert.Fail($"Runtime type of {nameof(Entity)}.{nameof(Entity.Metadata)} is not equal: {entity.Metadata?.GetType().Name} - {entityAfter.Metadata?.GetType().Name}");
break;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.GameLogic;
using NitroxModel.Packets;

namespace NitroxClient.Communication.Packets.Processors;

public class CreaturePoopPerformedProcessor : ClientPacketProcessor<CreaturePoopPerformed>
{
public override void Process(CreaturePoopPerformed packet)
{
AI.CreaturePoopPerformed(packet.CreatureId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.MonoBehaviours;
using NitroxModel.Packets;
using UnityEngine;

namespace NitroxClient.Communication.Packets.Processors;

public class SeaTreaderChunkPickedUpProcessor : ClientPacketProcessor<SeaTreaderChunkPickedUp>
{
public override void Process(SeaTreaderChunkPickedUp packet)
{
if (NitroxEntity.TryGetComponentFrom(packet.ChunkId, out SinkingGroundChunk sinkingGroundChunk))
{
GameObject.Destroy(sinkingGroundChunk.gameObject);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.MonoBehaviours;
using NitroxClient.Unity.Helper;
using NitroxModel.Packets;
using NitroxModel_Subnautica.DataStructures;
using UnityEngine;

namespace NitroxClient.Communication.Packets.Processors;

public class SeaTreaderSpawnedChunkProcessor : ClientPacketProcessor<SeaTreaderSpawnedChunk>
{
public override void Process(SeaTreaderSpawnedChunk packet)
{
if (NitroxEntity.TryGetComponentFrom(packet.CreatureId, out SeaTreader seaTreader) &&
seaTreader.TryGetComponentInChildren(out SeaTreaderSounds seaTreaderSounds))
{
GameObject chunkObject = GameObjectHelper.InstantiateWithId(seaTreaderSounds.stepChunkPrefab, packet.ChunkId);
chunkObject.transform.position = packet.Position.ToUnity();
chunkObject.transform.rotation = packet.Rotation.ToUnity();
}
}
}
2 changes: 1 addition & 1 deletion NitroxClient/Debuggers/NetworkDebugger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public class NetworkDebugger : BaseDebugger, INetworkDebugger
{
nameof(PlayerMovement), nameof(EntityTransformUpdates), nameof(PlayerStats), nameof(SpawnEntities), nameof(VehicleMovement), nameof(PlayerCinematicControllerCall),
nameof(FMODAssetPacket), nameof(FMODEventInstancePacket), nameof(FMODCustomEmitterPacket), nameof(FMODStudioEmitterPacket), nameof(FMODCustomLoopingEmitterPacket),
nameof(SimulationOwnershipChange), nameof(CellVisibilityChanged)
nameof(SimulationOwnershipChange), nameof(CellVisibilityChanged), nameof(CreatureActionChanged)
};
private readonly List<PacketDebugWrapper> packets = new List<PacketDebugWrapper>(PACKET_STORED_COUNT);

Expand Down
14 changes: 12 additions & 2 deletions NitroxClient/GameLogic/AI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public class AI
/// </summary>
private readonly HashSet<Type> creatureActionWhitelist =
[
typeof(AttackLastTarget), typeof(RangedAttackLastTarget), typeof(AttackCyclops)
typeof(AttackLastTarget), typeof(RangedAttackLastTarget), typeof(AttackCyclops), typeof(Poop)
];

/// <summary>
Expand All @@ -32,7 +32,7 @@ public class AI
/// </summary>
private readonly HashSet<Type> syncedCreatureWhitelist =
[
typeof(ReaperLeviathan), typeof(SeaDragon)
typeof(ReaperLeviathan), typeof(SeaDragon), typeof(SeaTreader), typeof(GhostLeviathan)
];

public AI(IPacketSender packetSender)
Expand Down Expand Up @@ -143,6 +143,16 @@ public static void RangedAttackLastTargetUpdate(NitroxId creatureId, NitroxId ta
}
}

public static void CreaturePoopPerformed(NitroxId creatureId)
{
if (NitroxEntity.TryGetComponentFrom(creatureId, out Poop poop))
{
// Code from Poop.Perform
SafeAnimator.SetBool(poop.creature.GetAnimator(), poop.animationParameterName, false);
poop.recourceSpawned = true;
}
}

public bool TryGetActionForCreature(Creature creature, out CreatureAction action)
{
// TODO: Fix ondeath cinematic being played for all players when getting bitten by a reaper
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using NitroxClient.GameLogic.Spawning.Metadata.Extractor.Abstract;
using NitroxModel.DataStructures.GameLogic.Entities.Metadata;
using NitroxModel_Subnautica.DataStructures;

namespace NitroxClient.GameLogic.Spawning.Metadata.Extractor;

public class SeaTreaderMetadataExtractor : EntityMetadataExtractor<SeaTreader, SeaTreaderMetadata>
{
public override SeaTreaderMetadata Extract(SeaTreader seaTreader)
{
if (!DayNightCycle.main)
{
return null;
}
float grazingEndTime = DayNightCycle.main.timePassedAsFloat + seaTreader.grazingTimeLeft;
return new(seaTreader.reverseDirection, grazingEndTime, seaTreader.leashPosition.ToDto());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System;
using NitroxClient.GameLogic.Spawning.Metadata.Processor.Abstract;
using NitroxModel.DataStructures.GameLogic.Entities.Metadata;
using NitroxModel_Subnautica.DataStructures;
using UnityEngine;

namespace NitroxClient.GameLogic.Spawning.Metadata.Processor;

public class SeaTreaderMetadataProcessor : EntityMetadataProcessor<SeaTreaderMetadata>
{
public override void ProcessMetadata(GameObject gameObject, SeaTreaderMetadata metadata)
{
if (gameObject.TryGetComponent(out SeaTreader seaTreader))
{
if (!seaTreader.isInitialized)
{
seaTreader.InitializeOnce();
}

seaTreader.reverseDirection = metadata.ReverseDirection;

float grazingTimeLeft = Math.Max(0, metadata.GrazingEndTime - DayNightCycle.main.timePassedAsFloat);

seaTreader.grazing = grazingTimeLeft > 0;
seaTreader.grazingTimeLeft = grazingTimeLeft;

seaTreader.leashPosition = metadata.LeashPosition.ToUnity();
seaTreader.leashPosition.y = gameObject.transform.position.y;
seaTreader.isInitialized = true;
seaTreader.InitializeAgain();
}
else
{
Log.Error($"Could not find {nameof(SeaTreader)} on {gameObject.name}");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ namespace NitroxModel.DataStructures.GameLogic.Entities.Metadata
[ProtoInclude(76, typeof(RadiationMetadata))]
[ProtoInclude(77, typeof(CrashHomeMetadata))]
[ProtoInclude(78, typeof(EatableMetadata))]
[ProtoInclude(79, typeof(SeaTreaderMetadata))]
public abstract class EntityMetadata
{
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using System;
using System.Runtime.Serialization;
using BinaryPack.Attributes;
using NitroxModel.DataStructures.Unity;

namespace NitroxModel.DataStructures.GameLogic.Entities.Metadata;

[Serializable]
[DataContract]
public class SeaTreaderMetadata : EntityMetadata
{
[DataMember(Order = 1)]
public bool ReverseDirection { get; }

[DataMember(Order = 2)]
public float GrazingEndTime { get; }

[DataMember(Order = 3)]
public NitroxVector3 LeashPosition { get; }

[IgnoreConstructor]
protected SeaTreaderMetadata()
{
// Constructor for serialization. Has to be "protected" for json serialization.
}

public SeaTreaderMetadata(bool reverseDirection, float grazingEndTime, NitroxVector3 leashPosition)
{
ReverseDirection = reverseDirection;
GrazingEndTime = grazingEndTime;
LeashPosition = leashPosition;
}

public override string ToString()
{
return $"[{nameof(SeaTreaderMetadata)} ReverseDirection: {ReverseDirection}, GrazingEndTime: {GrazingEndTime}, LeashPosition: {LeashPosition}]";
}
}
15 changes: 15 additions & 0 deletions NitroxModel/Packets/CreaturePoopPerformed.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System;
using NitroxModel.DataStructures;

namespace NitroxModel.Packets;

[Serializable]
public class CreaturePoopPerformed : Packet
{
public NitroxId CreatureId { get; }

public CreaturePoopPerformed(NitroxId creatureId)
{
CreatureId = creatureId;
}
}
15 changes: 15 additions & 0 deletions NitroxModel/Packets/SeaTreaderChunkPickedUp.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System;
using NitroxModel.DataStructures;

namespace NitroxModel.Packets;

[Serializable]
public class SeaTreaderChunkPickedUp : Packet
{
public NitroxId ChunkId { get; }

public SeaTreaderChunkPickedUp(NitroxId chunkId)
{
ChunkId = chunkId;
}
}
22 changes: 22 additions & 0 deletions NitroxModel/Packets/SeaTreaderSpawnedChunk.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System;
using NitroxModel.DataStructures;
using NitroxModel.DataStructures.Unity;

namespace NitroxModel.Packets;

[Serializable]
public class SeaTreaderSpawnedChunk : Packet
{
public NitroxId CreatureId { get; }
public NitroxId ChunkId { get; }
public NitroxVector3 Position { get; }
public NitroxQuaternion Rotation { get; }

public SeaTreaderSpawnedChunk(NitroxId creatureId, NitroxId chunkId, NitroxVector3 position, NitroxQuaternion rotation)
{
CreatureId = creatureId;
ChunkId = chunkId;
Position = position;
Rotation = rotation;
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
using System.Reflection;
using NitroxClient.Communication.Abstract;
using NitroxClient.MonoBehaviours;
using NitroxModel.DataStructures;
using NitroxModel.Helper;
using NitroxModel.Packets;
using System.Reflection;

namespace NitroxPatcher.Patches.Dynamic;

Expand All @@ -12,12 +13,24 @@ public sealed partial class BreakableResource_BreakIntoResources_Patch : NitroxP

public static void Prefix(BreakableResource __instance)
{
if (!__instance.TryGetNitroxEntity(out NitroxEntity destroyedEntity))
if (!__instance.TryGetNitroxId(out NitroxId destroyedId))
{
Log.Warn($"[{nameof(BreakableResource_BreakIntoResources_Patch)}] Could not find {nameof(NitroxEntity)} for breakable entity {__instance.gameObject.GetFullHierarchyPath()}.");
return;
}
// Send packet to destroy the entity
Resolve<IPacketSender>().Send(new EntityDestroyed(destroyedEntity.Id));

// Case by case handling

// Sea Treaders spawn resource chunks but we don't register them on server-side as they're auto destroyed after 60s
// So we need to broadcast their deletion differently
if (__instance.GetComponent<SinkingGroundChunk>())
{
Resolve<IPacketSender>().Send(new SeaTreaderChunkPickedUp(destroyedId));
}
// Generic case
else
{
Resolve<IPacketSender>().Send(new EntityDestroyed(destroyedId));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public static bool Prefix(CreatureAction __instance)
return true;
}

// Perform should never be done on non-executing clients because it usually only calls swim behaviours
// Perform is too specific for each action so it should always be synced case by case (and never run directly on remote players)
return false;
}
}
Loading