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 10, 2024
1 parent 8abb67b commit df9fb08
Show file tree
Hide file tree
Showing 7 changed files with 180 additions and 5 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 string TargetPlayerName { get; }
public float Damage { get; set; }
public AttackType Type { get; }

public PvPAttack(string targetPlayerName, float damage, AttackType type)
{
TargetPlayerName = targetPlayerName;
Damage = damage;
Type = type;
}

public enum AttackType
{
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();
}
}
50 changes: 45 additions & 5 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 @@ -13,9 +16,10 @@ public sealed partial class LiveMixin_TakeDamage_Patch : NitroxPatch, IDynamicPa
{
private static readonly MethodInfo TARGET_METHOD = Reflect.Method((LiveMixin t) => t.TakeDamage(default(float), default(Vector3), default(DamageType), default(GameObject)));

public static bool Prefix(out float? __state, LiveMixin __instance, GameObject dealer)
public static bool Prefix(out float __state, LiveMixin __instance, GameObject dealer)
{
__state = null;
// Persist the previous health value
__state = __instance.health;

if (!Resolve<LiveMixinManager>().IsWhitelistedUpdateType(__instance))
{
Expand All @@ -28,10 +32,10 @@ public static bool Prefix(out float? __state, LiveMixin __instance, GameObject d
return Resolve<LiveMixinManager>().ShouldApplyNextHealthUpdate(__instance, dealer);
}

public static void Postfix(float? __state, LiveMixin __instance, float originalDamage, Vector3 position, DamageType type, GameObject dealer)
public static void Postfix(float __state, LiveMixin __instance, float originalDamage, GameObject dealer, bool __runOriginal)
{
// Did we realize a change in health?
if (!__state.HasValue || __state.Value == __instance.health)
bool healthChanged = __state != __instance.health;
if (!__runOriginal || !ShouldBroadcastDamage(__instance, dealer, originalDamage, healthChanged))
{
return;
}
Expand All @@ -47,4 +51,40 @@ public static void Postfix(float? __state, LiveMixin __instance, float originalD
}
}
}

private static bool ShouldBroadcastDamage(LiveMixin victim, GameObject dealer, float damage, bool healthChanged)
{
foreach (MonoBehaviour monoBehaviour in victim.GetComponents<MonoBehaviour>())
{
switch (monoBehaviour)
{
case RemotePlayerIdentifier remotePlayerIdentifier:
// Handle it internally
HandlePvP(remotePlayerIdentifier.RemotePlayer, dealer, damage);
return false;
}
}
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.PlayerName, 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.TryGetPlayerByName(packet.TargetPlayerName, out Player targetPlayer) &&
DamageMultiplierByType.TryGetValue(packet.Type, out float multiplier))
{
packet.Damage *= multiplier;
targetPlayer.SendPacket(packet);
}
}
}
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 df9fb08

Please sign in to comment.