Skip to content

Commit

Permalink
Add basic sync for PvP (only allowed with knifes and heatblades for now)
Browse files Browse the repository at this point in the history
  • Loading branch information
tornac1234 committed Jan 5, 2025
1 parent 9bb3664 commit 35e5ca7
Show file tree
Hide file tree
Showing 8 changed files with 189 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System.Collections.Generic;
using System.Linq;
using FluentAssertions;
using HarmonyLib;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using NitroxTest.Patcher;

namespace NitroxPatcher.Patches.Dynamic;

[TestClass]
public class Knife_OnToolUseAnim_PatchTest
{
[TestMethod]
public void Sanity()
{
IEnumerable<CodeInstruction> originalIl = PatchTestHelper.GetInstructionsFromMethod(Knife_OnToolUseAnim_Patch.TARGET_METHOD);
IEnumerable<CodeInstruction> transformedIl = Knife_OnToolUseAnim_Patch.Transpiler(originalIl);
transformedIl.Count().Should().Be(originalIl.Count());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxModel.Packets;

namespace NitroxClient.Communication.Packets.Processors;

public class PvPAttackProcessor : ClientPacketProcessor<PvPAttack>
{
public override void Process(PvPAttack packet)
{
if (Player.main && Player.main.liveMixin)
{
Player.main.liveMixin.TakeDamage(packet.Damage);
}
}
}
24 changes: 24 additions & 0 deletions NitroxModel/Packets/PvPAttack.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System;

namespace NitroxModel.Packets;

[Serializable]
public class PvPAttack : Packet
{
public ushort TargetPlayerId { get; }
public float Damage { get; set; }
public AttackType Type { get; }

public PvPAttack(ushort targetPlayerId, float damage, AttackType type)
{
TargetPlayerId = targetPlayerId;
Damage = damage;
Type = type;
}

public enum AttackType : byte
{
KnifeHit,
HeatbladeHit
}
}
36 changes: 36 additions & 0 deletions NitroxPatcher/Patches/Dynamic/Knife_OnToolUseAnim_Patch.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using HarmonyLib;
using NitroxModel.Helper;

namespace NitroxPatcher.Patches.Dynamic;

/// <summary>
/// Registers knife hits's dealer as the main Player object
/// </summary>
public sealed partial class Knife_OnToolUseAnim_Patch : NitroxPatch, IDynamicPatch
{
public static readonly MethodInfo TARGET_METHOD = Reflect.Method((Knife t) => t.OnToolUseAnim(default));

/*
*
* bool flag = liveMixin.IsAlive();
* REPLACE below line
* liveMixin.TakeDamage(this.damage, vector, this.damageType, null);
* BY:
* liveMixin.TakeDamage(this.damage, vector, this.damageType, Player.mainObject);
* this.GiveResourceOnDamage(gameObject, liveMixin.IsAlive(), flag);
*/
public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
{
return new CodeMatcher(instructions).MatchEndForward([
new CodeMatch(OpCodes.Ldloc_0),
new CodeMatch(OpCodes.Ldarg_0),
new CodeMatch(OpCodes.Ldfld),
new CodeMatch(OpCodes.Ldnull)
])
.Set(OpCodes.Ldsfld, Reflect.Field(() => Player.mainObject))
.InstructionEnumeration();
}
}
49 changes: 45 additions & 4 deletions NitroxPatcher/Patches/Dynamic/LiveMixin_TakeDamage_Patch.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
using System.Reflection;
using NitroxClient.Communication.Abstract;
using NitroxClient.GameLogic;
using NitroxClient.GameLogic.PlayerLogic;
using NitroxClient.GameLogic.Spawning.Metadata;
using NitroxModel.DataStructures;
using NitroxModel.DataStructures.GameLogic.Entities.Metadata;
using NitroxModel.DataStructures.Util;
using NitroxModel.Helper;
using NitroxModel.Packets;
using UnityEngine;

namespace NitroxPatcher.Patches.Dynamic;
Expand All @@ -26,11 +29,10 @@ public static bool Prefix(out float __state, LiveMixin __instance, GameObject de
return Resolve<LiveMixinManager>().ShouldApplyNextHealthUpdate(__instance, dealer);
}

public static void Postfix(float __state, LiveMixin __instance, bool __runOriginal)
public static void Postfix(float __state, LiveMixin __instance, float originalDamage, GameObject dealer, bool __runOriginal)
{
// Did we realize a change in health?
if (!__runOriginal || __state == __instance.health || Resolve<LiveMixinManager>().IsRemoteHealthChanging ||
__instance.GetComponent<BaseCell>())
bool healthChanged = __state != __instance.health;
if (!__runOriginal || !ShouldBroadcastDamage(__instance, dealer, originalDamage, healthChanged))
{
return;
}
Expand All @@ -46,4 +48,43 @@ public static void Postfix(float __state, LiveMixin __instance, bool __runOrigin
}
}
}

private static bool ShouldBroadcastDamage(LiveMixin victim, GameObject dealer, float damage, bool healthChanged)
{
if (Resolve<LiveMixinManager>().IsRemoteHealthChanging || victim.GetComponent<BaseCell>())
{
return false;
}

if (victim.TryGetComponent(out RemotePlayerIdentifier remotePlayerIdentifier))
{
// Handle it internally
HandlePvP(remotePlayerIdentifier.RemotePlayer, dealer, damage);
return false;
}

// The health change check must happen after the PvP one
return healthChanged;
}

private static void HandlePvP(RemotePlayer remotePlayer, GameObject dealer, float damage)
{
if (dealer == Player.mainObject && Inventory.main.GetHeldObject())
{
PvPAttack.AttackType attackType;
switch (Inventory.main.GetHeldTool())
{
case HeatBlade:
attackType = PvPAttack.AttackType.HeatbladeHit;
break;
case Knife:
attackType = PvPAttack.AttackType.KnifeHit;
break;
default:
// We don't want to send non-registered attacks
return;
}
Resolve<IPacketSender>().Send(new PvPAttack(remotePlayer.PlayerId, damage, attackType));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System.Collections.Generic;
using NitroxModel.Packets;
using NitroxServer.Communication.Packets.Processors.Abstract;
using NitroxServer.GameLogic;
using NitroxServer.Serialization;

namespace NitroxServer.Communication.Packets.Processors;

public class PvPAttackProcessor : AuthenticatedPacketProcessor<PvPAttack>
{
private readonly ServerConfig serverConfig;
private readonly PlayerManager playerManager;

// TODO: In the future, do a whole config for damage sources
private static readonly Dictionary<PvPAttack.AttackType, float> damageMultiplierByType = new()
{
{ PvPAttack.AttackType.KnifeHit, 0.5f },
{ PvPAttack.AttackType.HeatbladeHit, 1f }
};

public PvPAttackProcessor(ServerConfig serverConfig, PlayerManager playerManager)
{
this.serverConfig = serverConfig;
this.playerManager = playerManager;
}

public override void Process(PvPAttack packet, Player player)
{
if (serverConfig.PvPEnabled &&
playerManager.TryGetPlayerById(packet.TargetPlayerId, out Player targetPlayer) &&
damageMultiplierByType.TryGetValue(packet.Type, out float multiplier))
{
packet.Damage *= multiplier;
targetPlayer.SendPacket(packet);
}
}
}
9 changes: 9 additions & 0 deletions NitroxServer/GameLogic/PlayerManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ namespace NitroxServer.GameLogic
public class PlayerManager
{
private readonly ThreadSafeDictionary<string, Player> allPlayersByName;
private readonly ThreadSafeDictionary<ushort, Player> connectedPlayersById = [];
private readonly ThreadSafeDictionary<INitroxConnection, ConnectionAssets> assetsByConnection = new();
private readonly ThreadSafeDictionary<string, PlayerContext> reservations = new();
private readonly ThreadSafeSet<string> reservedPlayerNames = new("Player"); // "Player" is often used to identify the local player and should not be used by any user
Expand Down Expand Up @@ -215,6 +216,8 @@ public Player PlayerConnected(INitroxConnection connection, string reservationKe
allPlayersByName[playerContext.PlayerName] = player;
}

connectedPlayersById.Add(playerContext.PlayerId, player);

// TODO: make a ConnectedPlayer wrapper so this is not stateful
player.PlayerContext = playerContext;
player.Connection = connection;
Expand Down Expand Up @@ -248,6 +251,7 @@ public void PlayerDisconnected(INitroxConnection connection)
{
Player player = assetPackage.Player;
reservedPlayerNames.Remove(player.Name);
connectedPlayersById.Remove(player.Id);
}

assetsByConnection.Remove(connection);
Expand Down Expand Up @@ -301,6 +305,11 @@ public bool TryGetPlayerByName(string playerName, out Player foundPlayer)
return false;
}

public bool TryGetPlayerById(ushort playerId, out Player player)
{
return connectedPlayersById.TryGetValue(playerId, out player);
}

public Player GetPlayer(INitroxConnection connection)
{
if (!assetsByConnection.TryGetValue(connection, out ConnectionAssets assetPackage))
Expand Down
3 changes: 3 additions & 0 deletions NitroxServer/Serialization/ServerConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -117,5 +117,8 @@ public string SaveName

[PropertyDescription("When true, will reject any build actions detected as desynced")]
public bool SafeBuilding { get; set; } = true;

[PropertyDescription("Activates/Deactivates Player versus Player damage/interactions")]
public bool PvPEnabled { get; set; } = true;
}
}

0 comments on commit 35e5ca7

Please sign in to comment.