diff --git a/Nitrox.Model.Subnautica/Packets/DeathMarkersChanged.cs b/Nitrox.Model.Subnautica/Packets/DeathMarkersChanged.cs
new file mode 100644
index 0000000000..e74275feb6
--- /dev/null
+++ b/Nitrox.Model.Subnautica/Packets/DeathMarkersChanged.cs
@@ -0,0 +1,15 @@
+using System;
+using Nitrox.Model.Packets;
+
+namespace NitroxModel.Packets;
+
+[Serializable]
+public class DeathMarkersChanged : Packet
+{
+ public bool MarkDeathPointsWithBeacon { get; }
+
+ public DeathMarkersChanged(bool markDeathPointsWithBeacon)
+ {
+ MarkDeathPointsWithBeacon = markDeathPointsWithBeacon;
+ }
+}
diff --git a/Nitrox.Model.Subnautica/Packets/InitialPlayerSync.cs b/Nitrox.Model.Subnautica/Packets/InitialPlayerSync.cs
index 38c2a7bf7c..390afaac12 100644
--- a/Nitrox.Model.Subnautica/Packets/InitialPlayerSync.cs
+++ b/Nitrox.Model.Subnautica/Packets/InitialPlayerSync.cs
@@ -41,6 +41,7 @@ public class InitialPlayerSync : Packet
public SessionSettings SessionSettings { get; }
public bool InPrecursor { get; }
public bool DisplaySurfaceWater { get; }
+ public bool MarkDeathPointsWithBeacon { get; }
public InitialPlayerSync(NitroxId playerGameObjectId,
bool firstTimeConnecting,
@@ -67,7 +68,8 @@ public InitialPlayerSync(NitroxId playerGameObjectId,
bool keepInventoryOnDeath,
SessionSettings sessionSettings,
bool inPrecursor,
- bool displaySurfaceWater)
+ bool displaySurfaceWater,
+ bool markDeathPointsWithBeacon)
{
AssignedEscapePodId = assignedEscapePodId;
PlayerGameObjectId = playerGameObjectId;
@@ -95,6 +97,7 @@ public InitialPlayerSync(NitroxId playerGameObjectId,
SessionSettings = sessionSettings;
InPrecursor = inPrecursor;
DisplaySurfaceWater = displaySurfaceWater;
+ MarkDeathPointsWithBeacon = markDeathPointsWithBeacon;
}
/// Used for deserialization
@@ -124,7 +127,8 @@ public InitialPlayerSync(
bool keepInventoryOnDeath,
SessionSettings sessionSettings,
bool inPrecursor,
- bool displaySurfaceWater)
+ bool displaySurfaceWater,
+ bool markDeathPointsWithBeacon)
{
AssignedEscapePodId = assignedEscapePodId;
PlayerGameObjectId = playerGameObjectId;
@@ -152,6 +156,7 @@ public InitialPlayerSync(
SessionSettings = sessionSettings;
InPrecursor = inPrecursor;
DisplaySurfaceWater = displaySurfaceWater;
+ MarkDeathPointsWithBeacon = markDeathPointsWithBeacon;
}
}
}
diff --git a/Nitrox.Model/Serialization/SubnauticaServerConfig.cs b/Nitrox.Model/Serialization/SubnauticaServerConfig.cs
index 21bbd63164..93f723ef01 100644
--- a/Nitrox.Model/Serialization/SubnauticaServerConfig.cs
+++ b/Nitrox.Model/Serialization/SubnauticaServerConfig.cs
@@ -30,6 +30,9 @@ public class SubnauticaServerConfig : NitroxConfig
[PropertyDescription("Prevents players from losing items on death")]
public bool KeepInventoryOnDeath { get; set; } = false;
+ [PropertyDescription("Places a beacon where players die")]
+ public bool MarkDeathPointsWithBeacon { get; set; } = true;
+
[PropertyDescription("Measured in milliseconds")]
public int SaveInterval
{
diff --git a/Nitrox.Server.Subnautica/Models/Commands/SetDeathMarkersCommand.cs b/Nitrox.Server.Subnautica/Models/Commands/SetDeathMarkersCommand.cs
new file mode 100644
index 0000000000..9fc3f333e7
--- /dev/null
+++ b/Nitrox.Server.Subnautica/Models/Commands/SetDeathMarkersCommand.cs
@@ -0,0 +1,42 @@
+using System.IO;
+using Nitrox.Model.DataStructures.GameLogic;
+using Nitrox.Model.Serialization;
+using Nitrox.Server.Subnautica.Models.Commands.Abstract;
+using Nitrox.Server.Subnautica.Models.Commands.Abstract.Type;
+using Nitrox.Server.Subnautica.Models.GameLogic;
+using NitroxModel.Packets;
+
+namespace Nitrox.Server.Subnautica.Models.Commands;
+
+internal class SetDeathMarkersCommand : Command
+{
+ private readonly PlayerManager playerManager;
+ private readonly SubnauticaServerConfig serverConfig;
+ private readonly Server server;
+
+ public SetDeathMarkersCommand(PlayerManager playerManager, SubnauticaServerConfig serverConfig, Server server) : base("deathmarkers", Perms.ADMIN, "Sets \"Death Markers\" setting to on/off. If \"on\", a beacon will appear at the location where a player dies.")
+ {
+ this.playerManager = playerManager;
+ this.serverConfig = serverConfig;
+ this.server = server;
+ AddParameter(new TypeBoolean("state", true, "on/off to enable/disable death markers"));
+ }
+
+ protected override void Execute(CallArgs args)
+ {
+ bool newDeathMarkersState = args.Get(0);
+ using (serverConfig.Update(Path.Combine(KeyValueStore.Instance.GetSavesFolderDir(), server.Name)))
+ {
+ if (serverConfig.MarkDeathPointsWithBeacon != newDeathMarkersState)
+ {
+ serverConfig.MarkDeathPointsWithBeacon = newDeathMarkersState;
+ playerManager.SendPacketToAllPlayers(new DeathMarkersChanged(newDeathMarkersState));
+ SendMessageToAllPlayers($"MarkDeathPointsWithBeacon changed to \"{newDeathMarkersState}\" by {args.SenderName}");
+ }
+ else
+ {
+ SendMessage(args.Sender, $"MarkDeathPointsWithBeacon already set to {newDeathMarkersState}");
+ }
+ }
+ }
+}
diff --git a/Nitrox.Server.Subnautica/Models/GameLogic/JoiningManager.cs b/Nitrox.Server.Subnautica/Models/GameLogic/JoiningManager.cs
index 7bf0fc7095..d9a1d9acd7 100644
--- a/Nitrox.Server.Subnautica/Models/GameLogic/JoiningManager.cs
+++ b/Nitrox.Server.Subnautica/Models/GameLogic/JoiningManager.cs
@@ -185,7 +185,8 @@ private void SendInitialSync(INitroxConnection connection, string reservationKey
serverConfig.KeepInventoryOnDeath,
sessionSettings,
player.InPrecursor,
- player.DisplaySurfaceWater
+ player.DisplaySurfaceWater,
+ serverConfig.MarkDeathPointsWithBeacon
);
player.SendPacket(initialPlayerSync);
diff --git a/NitroxClient/Communication/MultiplayerSession/ConnectionState/CommunicatingState.cs b/NitroxClient/Communication/MultiplayerSession/ConnectionState/CommunicatingState.cs
index d07d22a42b..2557c9e6d0 100644
--- a/NitroxClient/Communication/MultiplayerSession/ConnectionState/CommunicatingState.cs
+++ b/NitroxClient/Communication/MultiplayerSession/ConnectionState/CommunicatingState.cs
@@ -1,4 +1,4 @@
-using System.Threading.Tasks;
+using System.Threading.Tasks;
using NitroxClient.Communication.Abstract;
namespace NitroxClient.Communication.MultiplayerSession.ConnectionState
diff --git a/NitroxClient/Communication/Packets/Processors/DeathMarkersChangedProcessor.cs b/NitroxClient/Communication/Packets/Processors/DeathMarkersChangedProcessor.cs
new file mode 100644
index 0000000000..e2645c9edb
--- /dev/null
+++ b/NitroxClient/Communication/Packets/Processors/DeathMarkersChangedProcessor.cs
@@ -0,0 +1,20 @@
+using NitroxClient.Communication.Packets.Processors.Abstract;
+using NitroxClient.GameLogic;
+using NitroxModel.Packets;
+
+namespace NitroxClient.Communication.Packets.Processors;
+
+public class DeathMarkersChangedProcessor : ClientPacketProcessor
+{
+ private readonly LocalPlayer localPlayer;
+
+ public DeathMarkersChangedProcessor(LocalPlayer localPlayer)
+ {
+ this.localPlayer = localPlayer;
+ }
+
+ public override void Process(DeathMarkersChanged packet)
+ {
+ localPlayer.MarkDeathPointsWithBeacon = packet.MarkDeathPointsWithBeacon;
+ }
+}
diff --git a/NitroxClient/Communication/Packets/Processors/PlayerDeathProcessor.cs b/NitroxClient/Communication/Packets/Processors/PlayerDeathProcessor.cs
index e03151017b..e4e3bfd3ca 100644
--- a/NitroxClient/Communication/Packets/Processors/PlayerDeathProcessor.cs
+++ b/NitroxClient/Communication/Packets/Processors/PlayerDeathProcessor.cs
@@ -1,4 +1,4 @@
-using NitroxClient.Communication.Packets.Processors.Abstract;
+using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.GameLogic;
using Nitrox.Model.Helper;
using Nitrox.Model.Packets;
@@ -21,7 +21,6 @@ public override void Process(PlayerDeathEvent playerDeath)
Log.Debug($"{player.PlayerName} died");
Log.InGame(Language.main.Get("Nitrox_PlayerDied").Replace("{PLAYER}", player.PlayerName));
player.PlayerDeathEvent.Trigger(player);
-
// TODO: Add any death related triggers (i.e. scoreboard updates, rewards, etc.)
}
}
diff --git a/NitroxClient/GameLogic/InitialSync/PlayerInitialSyncProcessor.cs b/NitroxClient/GameLogic/InitialSync/PlayerInitialSyncProcessor.cs
index 912f05b818..562de22b3e 100644
--- a/NitroxClient/GameLogic/InitialSync/PlayerInitialSyncProcessor.cs
+++ b/NitroxClient/GameLogic/InitialSync/PlayerInitialSyncProcessor.cs
@@ -36,7 +36,7 @@ public PlayerInitialSyncProcessor(Items item, ItemContainers itemContainers, Loc
AddStep(sync => AddStartingItemsToPlayer(sync.FirstTimeConnecting));
AddStep(sync => SetPlayerStats(sync.PlayerStatsData));
AddStep(sync => SetPlayerGameMode(sync.GameMode));
- AddStep(sync => ApplySettings(sync.KeepInventoryOnDeath, sync.SessionSettings.FastHatch, sync.SessionSettings.FastGrow));
+ AddStep(sync => ApplySettings(sync.KeepInventoryOnDeath, sync.SessionSettings.FastHatch, sync.SessionSettings.FastGrow, sync.MarkDeathPointsWithBeacon));
}
private void SetPlayerPermissions(Perms permissions)
@@ -139,9 +139,10 @@ private static void SetPlayerGameMode(NitroxGameMode gameMode)
GameModeUtils.SetGameMode((GameModeOption)(int)gameMode, GameModeOption.None);
}
- private void ApplySettings(bool keepInventoryOnDeath, bool fastHatch, bool fastGrow)
+ private void ApplySettings(bool keepInventoryOnDeath, bool fastHatch, bool fastGrow, bool markDeathPointsWithBeacon)
{
localPlayer.KeepInventoryOnDeath = keepInventoryOnDeath;
+ localPlayer.MarkDeathPointsWithBeacon = markDeathPointsWithBeacon;
NoCostConsoleCommand.main.fastHatchCheat = fastHatch;
NoCostConsoleCommand.main.fastGrowCheat = fastGrow;
if (!fastHatch && !fastGrow)
@@ -160,4 +161,9 @@ private void ApplySettings(bool keepInventoryOnDeath, bool fastHatch, bool fastG
}
Log.InGame(cheatsEnabled.ToString());
}
+
+ private void SetPlayerMarkDeathPointsWithBeacon(bool markDeathPointsWithBeacon)
+ {
+ localPlayer.MarkDeathPointsWithBeacon = markDeathPointsWithBeacon;
+ }
}
diff --git a/NitroxClient/GameLogic/LocalPlayer.cs b/NitroxClient/GameLogic/LocalPlayer.cs
index 327f52a0c1..9edb6e9e36 100644
--- a/NitroxClient/GameLogic/LocalPlayer.cs
+++ b/NitroxClient/GameLogic/LocalPlayer.cs
@@ -39,10 +39,10 @@ public class LocalPlayer : ILocalNitroxPlayer
///
public ushort? PlayerId => multiplayerSession?.Reservation?.PlayerId;
public PlayerSettings PlayerSettings => multiplayerSession.PlayerSettings;
-
public Perms Permissions { get; set; }
public IntroCinematicMode IntroCinematicMode { get; set; }
public bool KeepInventoryOnDeath { get; set; }
+ public bool MarkDeathPointsWithBeacon { get; set; }
public LocalPlayer(IMultiplayerSession multiplayerSession, IPacketSender packetSender, ThrottledPacketSender throttledPacketSender)
{
@@ -55,6 +55,7 @@ public LocalPlayer(IMultiplayerSession multiplayerSession, IPacketSender packetS
Permissions = Perms.PLAYER;
IntroCinematicMode = IntroCinematicMode.NONE;
KeepInventoryOnDeath = false;
+ MarkDeathPointsWithBeacon = false;
}
public void BroadcastLocation(Vector3 location, Vector3 velocity, Quaternion bodyRotation, Quaternion aimingRotation)
diff --git a/NitroxClient/MonoBehaviours/Gui/InGame/DeathBeacon.cs b/NitroxClient/MonoBehaviours/Gui/InGame/DeathBeacon.cs
new file mode 100644
index 0000000000..1d7b800683
--- /dev/null
+++ b/NitroxClient/MonoBehaviours/Gui/InGame/DeathBeacon.cs
@@ -0,0 +1,38 @@
+using Nitrox.Model.DataStructures.Unity;
+using UnityEngine;
+
+namespace NitroxClient.MonoBehaviours.Gui.InGame;
+
+public class DeathBeacon : MonoBehaviour
+{
+ private const float DESPAWN_DISTANCE = 20f;
+ private const float DESPAWN_DISTANCE_SQUARED = DESPAWN_DISTANCE * DESPAWN_DISTANCE;
+ private const float CHECK_RATE = 1.5f; // in seconds
+
+ public static void SpawnDeathBeacon(NitroxVector3 location, string playerName)
+ {
+ GameObject beacon = new($"{playerName}DeathBeacon");
+ beacon.transform.position = location.ToUnity();
+ PingInstance signal = beacon.AddComponent();
+ signal.pingType = PingType.Signal;
+ signal.origin = beacon.transform;
+ signal.minDist = DESPAWN_DISTANCE + 15f;
+ signal._label = Language.main.Get("Nitrox_PlayerDeathBeaconLabel").Replace("{PLAYER}", playerName);
+ beacon.AddComponent();
+ signal.displayPingInManager = true;
+ signal.Initialize();
+ }
+
+ private void Start()
+ {
+ InvokeRepeating(nameof(CheckPlayerDistance), 0, CHECK_RATE);
+ }
+
+ private void CheckPlayerDistance()
+ {
+ if ((Player.main.transform.position - transform.position).sqrMagnitude <= DESPAWN_DISTANCE_SQUARED)
+ {
+ Destroy(gameObject);
+ }
+ }
+}
diff --git a/NitroxClient/MonoBehaviours/PlayerDeathBroadcaster.cs b/NitroxClient/MonoBehaviours/PlayerDeathBroadcaster.cs
index 32ff578210..fb3cbe8af6 100644
--- a/NitroxClient/MonoBehaviours/PlayerDeathBroadcaster.cs
+++ b/NitroxClient/MonoBehaviours/PlayerDeathBroadcaster.cs
@@ -1,4 +1,5 @@
using NitroxClient.GameLogic;
+using NitroxClient.MonoBehaviours.Gui.InGame;
using UnityEngine;
namespace NitroxClient.MonoBehaviours;
@@ -16,6 +17,10 @@ public void Awake()
private void PlayerDeath(Player player)
{
+ if (localPlayer.MarkDeathPointsWithBeacon)
+ {
+ DeathBeacon.SpawnDeathBeacon(player.transform.position.ToDto(), localPlayer.PlayerName);
+ }
localPlayer.BroadcastDeath(player.transform.position);
}
diff --git a/NitroxPatcher/Patches/Dynamic/Player_OnKill_Patch.cs b/NitroxPatcher/Patches/Dynamic/Player_OnKill_Patch.cs
index 59edf93284..12dce05e3d 100644
--- a/NitroxPatcher/Patches/Dynamic/Player_OnKill_Patch.cs
+++ b/NitroxPatcher/Patches/Dynamic/Player_OnKill_Patch.cs
@@ -1,4 +1,4 @@
-using System.Collections.Generic;
+using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
@@ -11,6 +11,7 @@ public sealed partial class Player_OnKill_Patch : NitroxPatch, IDynamicPatch
private static readonly MethodInfo TARGET_METHOD = Reflect.Method((Player t) => t.OnKill(default(DamageType)));
private static readonly MethodInfo SKIP_METHOD = Reflect.Method(() => GameModeUtils.IsPermadeath());
+
public static IEnumerable Transpiler(MethodBase original, IEnumerable instructions)
{
List instructionList = instructions.ToList();