Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ public override void OnBeginRunGame()
StratumUpdateChecker.Start(server);
StratumServerStats.Start(server);
StratumHarmonyVisibility.LogPatchedMethods(server);
StratumHarmonyVisibility.WarnFriendlyFireConflicts();
if (StratumRuntime.Config.Backup.Enabled)
{
new StratumBackupScheduler(server);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ public StratumFriendlyFireSystem(ServerMain server)
public static void Apply(StratumFriendlyFireConfig cfg)
{
StratumFriendlyFireHook.BlockGroupDamage = cfg != null && !cfg.AllowGroupDamage;
StratumHarmonyVisibility.WarnFriendlyFireConflicts();
}

private bool CheckAccess(TextCommandCallingArgs args, out TextCommandResult failure)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Linq;
using System.Reflection;
using HarmonyLib;
using Vintagestory.API.Server;

namespace Vintagestory.Server;

Expand Down Expand Up @@ -68,6 +69,78 @@ public static void LogPatchedMethods(ServerMain server)
}
}

// Methods the group friendly fire toggle (#277) relies on to drop a hit between group mates.
// A mod that Harmony-patches one of these and skips the original can silently defeat the
// toggle. No server platform can stop that, so when friendly fire is off we name the mod in the
// log for triage. Keyed by the declaring type's simple name plus the method name, which is
// enough to be unambiguous here and avoids caring about overloads or full namespaces.
private static readonly HashSet<string> FriendlyFireCriticalMethods = new HashSet<string>(System.StringComparer.Ordinal)
{
"Entity.ReceiveDamage",
"Entity.ShouldReceiveDamage",
"EntityAgent.ReceiveDamage",
"EntityAgent.ShouldReceiveDamage",
"EntityHumanoid.ShouldReceiveDamage",
"EntityPlayer.ShouldReceiveDamage",
"EntityAgent.OnInteract",
"EntityBehaviorHealth.OnEntityReceiveDamage",
"ServerSystemEntitySimulation.HandleEntityInteraction",
"EntityProjectileBase.CanDealDamage",
"EntityProjectileBase.DealDamage",
"EntityProjectileBase.ImpactOnEntity",
};

// Runs regardless of Diagnostics.LogModHarmonyPatches: this is a targeted safety warning for
// one feature, not the full patch dump. It is called after the friendly-fire state is applied,
// including startup, /friendlyfire changes, and /stratum reload.
public static void WarnFriendlyFireConflicts()
{
if (!StratumFriendlyFireHook.BlockGroupDamage)
{
return;
}

foreach (MethodBase method in Harmony.GetAllPatchedMethods())
{
string key = (method.DeclaringType?.Name ?? "?") + "." + method.Name;
if (!FriendlyFireCriticalMethods.Contains(key))
{
continue;
}

Patches info = Harmony.GetPatchInfo(method);
if (info == null)
{
continue;
}

HashSet<string> owners = new HashSet<string>(System.StringComparer.OrdinalIgnoreCase);
CollectOwners(owners, info.Prefixes);
CollectOwners(owners, info.Postfixes);
CollectOwners(owners, info.Transpilers);
CollectOwners(owners, info.Finalizers);
if (owners.Count == 0)
{
continue;
}

StratumRuntime.LogWarning($"harmony visibility: group friendly fire (/friendlyfire) relies on {key}, which mod(s) {string.Join(", ", owners.OrderBy(o => o, System.StringComparer.OrdinalIgnoreCase))} also patch. Confirm a hit between two group members is still blocked with that mod loaded.");
}
}

private static void CollectOwners(HashSet<string> owners, IReadOnlyCollection<Patch> patches)
{
if (patches == null)
{
return;
}

foreach (Patch patch in patches)
{
owners.Add(string.IsNullOrWhiteSpace(patch.owner) ? "(unknown)" : patch.owner);
}
}

private static void CountOwners(Dictionary<string, OwnerCounts> byOwner, IReadOnlyCollection<Patch> patches, System.Action<OwnerCounts> increment)
{
if (patches == null)
Expand Down