Decompiled source of DoNotTheHammer v1.0.0

DoNotTheHammer.dll

Decompiled 2 months ago
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using System.Runtime.CompilerServices;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using UnityEngine;

[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: CompilationRelaxations(8)]
[assembly: AssemblyVersion("0.0.0.0")]
namespace CreatorProtect;

[HarmonyPatch(typeof(Terminal), "InitTerminal")]
internal static class Patch_Terminal_InitTerminal
{
	private static bool _registered;

	private static void Postfix()
	{
		//IL_001f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0033: Expected O, but got Unknown
		//IL_002e: Unknown result type (might be due to invalid IL or missing references)
		if (!_registered)
		{
			_registered = true;
			new ConsoleCommand("cprot", "CreatorProtect. Subcommands: id | look | scan | list | add <id> | remove <id>", new ConsoleEvent(Commands.Handle), false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
			Plugin.Log.LogInfo((object)"Registered console command: cprot");
		}
	}
}
internal static class Commands
{
	internal static void Handle(ConsoleEventArgs args)
	{
		Terminal context = args.Context;
		switch ((args.Length > 1) ? args[1].ToLowerInvariant() : "help")
		{
		case "id":
			Id(context);
			return;
		case "look":
			Look(context);
			return;
		case "scan":
			Scan(context);
			return;
		case "list":
			List(context);
			return;
		case "add":
			Mutate(context, args, add: true);
			return;
		case "remove":
			Mutate(context, args, add: false);
			return;
		}
		context.AddString("cprot id      - show the character ID you are logged in as");
		context.AddString("cprot look    - show the creator ID of the piece you are looking at");
		context.AddString("cprot scan    - count loaded pieces per creator ID");
		context.AddString("cprot list    - show the current protection settings");
		context.AddString("cprot add <id>    - protect pieces created by that ID");
		context.AddString("cprot remove <id> - stop protecting that ID");
	}

	private static void Id(Terminal term)
	{
		long num = Plugin.LocalPlayerId();
		if (num == 0)
		{
			term.AddString("No character loaded, so there is no ID to report yet.");
			return;
		}
		term.AddString("Your character ID is " + num.ToString(CultureInfo.InvariantCulture));
		term.AddString("Add it to ProtectedCreatorIds to protect everything this character has built.");
	}

	private static void Look(Terminal term)
	{
		Piece val = RaycastPiece();
		if ((Object)(object)val == (Object)null)
		{
			term.AddString("No build piece under the crosshair. Stand closer and look straight at it.");
			return;
		}
		long creator = val.GetCreator();
		long creator2;
		bool flag = Plugin.IsProtected(val, out creator2);
		term.AddString("Piece:   " + ((Object)val).name);
		term.AddString("Creator: " + creator.ToString(CultureInfo.InvariantCulture) + ((creator == 0) ? "  (no creator recorded)" : ""));
		term.AddString("Protected right now: " + (flag ? "yes" : "no"));
		if (!flag && creator != 0)
		{
			term.AddString("Run: cprot add " + creator.ToString(CultureInfo.InvariantCulture) + "   to protect this builder.");
		}
	}

	private static void Scan(Terminal term)
	{
		List<Piece> list = AllPieces();
		if (list == null)
		{
			term.AddString("Could not read the piece list from the game.");
			return;
		}
		Dictionary<long, int> dictionary = new Dictionary<long, int>();
		for (int i = 0; i < list.Count; i++)
		{
			Piece val = list[i];
			if (!((Object)(object)val == (Object)null))
			{
				long creator = val.GetCreator();
				if (dictionary.TryGetValue(creator, out var value))
				{
					dictionary[creator] = value + 1;
				}
				else
				{
					dictionary[creator] = 1;
				}
			}
		}
		List<KeyValuePair<long, int>> list2 = new List<KeyValuePair<long, int>>(dictionary);
		list2.Sort((KeyValuePair<long, int> a, KeyValuePair<long, int> b) => b.Value.CompareTo(a.Value));
		term.AddString("Creators of the " + list.Count + " pieces currently loaded around you:");
		int num = 0;
		int num2 = 0;
		while (num2 < list2.Count && num < 15)
		{
			long key = list2[num2].Key;
			string text = ((key == 0) ? "0 (no creator recorded)" : key.ToString(CultureInfo.InvariantCulture));
			string text2 = (Plugin.IsCreatorProtected(key) ? "  [protected]" : "");
			term.AddString("  " + list2[num2].Value.ToString(CultureInfo.InvariantCulture) + " pieces  <- creator " + text + text2);
			num2++;
			num++;
		}
		term.AddString("Only loaded zones are counted, so stand in the base you care about.");
		term.AddString("The creator with by far the most pieces is almost certainly your builder character.");
	}

	private static void List(Terminal term)
	{
		term.AddString("Protected creators: " + Plugin.DescribeProtected());
		term.AddString("ProtectAllCreators: " + Plugin.ProtectAllCreators.Value);
		term.AddString("ProtectUnownedPieces: " + Plugin.ProtectUnownedPieces.Value);
		term.AddString("Bypass IDs: " + ((Plugin.BypassPlayerIds.Value.Length == 0) ? "(none)" : Plugin.BypassPlayerIds.Value));
		term.AddString("BlockRemoval=" + Plugin.BlockRemoval.Value + "  BlockPlayerDamage=" + Plugin.BlockPlayerDamage.Value + "  BlockAllDamage=" + Plugin.BlockAllDamage.Value);
		term.AddString("BlockResourceDrops=" + Plugin.BlockResourceDrops.Value + "  PreventCollapse=" + Plugin.PreventCollapse.Value + "  ServerEnforcement=" + Plugin.ServerEnforcement.Value);
	}

	private static void Mutate(Terminal term, ConsoleEventArgs args, bool add)
	{
		if (args.Length < 3)
		{
			term.AddString("Usage: cprot " + (add ? "add" : "remove") + " <character id>");
			return;
		}
		if (!long.TryParse(args[2].Trim(), NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
		{
			term.AddString(args[2] + " is not a number.");
			return;
		}
		if (!(add ? Plugin.AddProtectedId(result) : Plugin.RemoveProtectedId(result)))
		{
			term.AddString(add ? "That ID was already protected." : "That ID was not in the protected list.");
			return;
		}
		term.AddString((add ? "Now protecting creator " : "No longer protecting creator ") + result.ToString(CultureInfo.InvariantCulture));
		term.AddString("Protected creators: " + Plugin.DescribeProtected());
		term.AddString("Saved to the config file. Remember the server needs the same setting.");
	}

	private static Piece RaycastPiece()
	{
		//IL_004d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0058: Unknown result type (might be due to invalid IL or missing references)
		GameCamera instance = GameCamera.instance;
		if ((Object)(object)instance == (Object)null)
		{
			return null;
		}
		int mask = LayerMask.GetMask(new string[5] { "piece", "piece_nonsolid", "static_solid", "Default_small", "terrain" });
		RaycastHit val = default(RaycastHit);
		if (!Physics.Raycast(((Component)instance).transform.position, ((Component)instance).transform.forward, ref val, 50f, mask))
		{
			return null;
		}
		if ((Object)(object)((RaycastHit)(ref val)).collider == (Object)null)
		{
			return null;
		}
		return ((Component)((RaycastHit)(ref val)).collider).GetComponentInParent<Piece>();
	}

	private static List<Piece> AllPieces()
	{
		FieldInfo fieldInfo = AccessTools.Field(typeof(Piece), "s_allPieces");
		if (fieldInfo == null)
		{
			return null;
		}
		return fieldInfo.GetValue(null) as List<Piece>;
	}
}
[HarmonyPatch(typeof(Player), "CheckCanRemovePiece")]
internal static class Patch_Player_CheckCanRemovePiece
{
	private static void Postfix(Player __instance, Piece piece, ref bool __result)
	{
		if (__result && Plugin.BlockRemoval.Value && !Plugin.LocalMayModify(piece))
		{
			__result = false;
			Plugin.NotifyBlocked(__instance);
			Plugin.IsProtected(piece, out var creator);
			Plugin.Trace("Blocked removal of " + ((Object)piece).name + " (creator " + creator + ") by " + Plugin.LocalPlayerId());
		}
	}
}
[HarmonyPatch(typeof(WearNTear), "Remove")]
internal static class Patch_WearNTear_Remove
{
	private static bool Prefix(WearNTear __instance, bool blockDrop)
	{
		if (!Plugin.BlockRemoval.Value)
		{
			return true;
		}
		Piece piece = Plugin.PieceOf((Component)(object)__instance);
		if (!Plugin.IsProtected(piece, out var creator))
		{
			return true;
		}
		if (!Plugin.MayModify(creator, Plugin.LocalPlayerId()))
		{
			Plugin.Trace("Blocked WearNTear.Remove on " + ((Object)__instance).name + " (creator " + creator + ")");
			return false;
		}
		ZNetView component = ((Component)__instance).GetComponent<ZNetView>();
		if ((Object)(object)component != (Object)null && component.IsValid() && !component.IsOwner())
		{
			component.ClaimOwnership();
		}
		return true;
	}
}
[HarmonyPatch(typeof(WearNTear), "RPC_Remove")]
internal static class Patch_WearNTear_RPC_Remove
{
	private static bool Prefix(WearNTear __instance, long sender, bool blockDrop)
	{
		if (!Plugin.BlockRemoval.Value || !Plugin.ServerEnforcement.Value)
		{
			return true;
		}
		Piece piece = Plugin.PieceOf((Component)(object)__instance);
		if (!Plugin.IsProtected(piece, out var creator))
		{
			return true;
		}
		long num = Plugin.ResolveActor(sender);
		if (Plugin.MayModify(creator, num))
		{
			return true;
		}
		Plugin.Trace("Vetoed networked removal of " + ((Object)__instance).name + " (creator " + creator + ") requested by character " + num);
		return false;
	}
}
[HarmonyPatch(typeof(WearNTear), "ApplyDamage")]
internal static class Patch_WearNTear_ApplyDamage
{
	private static bool Prefix(WearNTear __instance, float damage, HitData hitData, ref bool __result)
	{
		Piece piece = Plugin.PieceOf((Component)(object)__instance);
		if (!Plugin.IsProtected(piece, out var creator))
		{
			return true;
		}
		if (Plugin.BlockAllDamage.Value)
		{
			__result = false;
			return false;
		}
		if (!Plugin.BlockPlayerDamage.Value)
		{
			return true;
		}
		if (hitData == null)
		{
			return true;
		}
		Character attacker = hitData.GetAttacker();
		Player val = (Player)(object)((attacker is Player) ? attacker : null);
		if ((Object)(object)val == (Object)null)
		{
			return true;
		}
		long playerID = val.GetPlayerID();
		if (Plugin.MayModify(creator, playerID))
		{
			return true;
		}
		Plugin.Trace("Blocked " + damage + " damage to " + ((Object)__instance).name + " (creator " + creator + ") from character " + playerID);
		__result = false;
		return false;
	}
}
[HarmonyPatch(typeof(Piece), "DropResources")]
internal static class Patch_Piece_DropResources
{
	private static bool Prefix(Piece __instance, HitData hitData)
	{
		if (!Plugin.BlockResourceDrops.Value)
		{
			return true;
		}
		if (!Plugin.IsProtected(__instance, out var creator))
		{
			return true;
		}
		if (Plugin.MayModify(creator, Plugin.LocalPlayerId()))
		{
			return true;
		}
		Plugin.Trace("Suppressed resource drop from " + ((Object)__instance).name + " (creator " + creator + ")");
		return false;
	}
}
[HarmonyPatch(typeof(WearNTear), "HaveSupport")]
internal static class Patch_WearNTear_HaveSupport
{
	private static void Postfix(WearNTear __instance, ref bool __result)
	{
		if (!__result && Plugin.PreventCollapse.Value)
		{
			Piece piece = Plugin.PieceOf((Component)(object)__instance);
			if (Plugin.IsProtected(piece, out var _))
			{
				__result = true;
			}
		}
	}
}
[BepInPlugin("creatorprotect", "CreatorProtect", "1.0.0")]
public class Plugin : BaseUnityPlugin
{
	public const string Guid = "creatorprotect";

	public const string Name = "CreatorProtect";

	public const string Version = "1.0.0";

	internal static ManualLogSource Log;

	internal static ConfigEntry<bool> ProtectAllCreators;

	internal static ConfigEntry<string> ProtectedCreatorIds;

	internal static ConfigEntry<string> BypassPlayerIds;

	internal static ConfigEntry<bool> ProtectUnownedPieces;

	internal static ConfigEntry<bool> BlockRemoval;

	internal static ConfigEntry<bool> BlockPlayerDamage;

	internal static ConfigEntry<bool> BlockAllDamage;

	internal static ConfigEntry<bool> BlockResourceDrops;

	internal static ConfigEntry<bool> PreventCollapse;

	internal static ConfigEntry<bool> ServerEnforcement;

	internal static ConfigEntry<bool> ShowMessage;

	internal static ConfigEntry<string> BlockedMessage;

	internal static ConfigEntry<bool> Verbose;

	private static readonly HashSet<long> ProtectedSet = new HashSet<long>();

	private static readonly HashSet<long> BypassSet = new HashSet<long>();

	private void Awake()
	{
		//IL_001b: Unknown result type (might be due to invalid IL or missing references)
		Log = ((BaseUnityPlugin)this).Logger;
		BindConfig();
		ReloadIds();
		new Harmony("creatorprotect").PatchAll(typeof(Plugin).Assembly);
		Log.LogInfo((object)("CreatorProtect 1.0.0 loaded. Protected creators: " + DescribeProtected()));
	}

	private void BindConfig()
	{
		ProtectedCreatorIds = ((BaseUnityPlugin)this).Config.Bind<string>("1 - Who is protected", "ProtectedCreatorIds", "", "Comma-separated character IDs whose placed pieces are protected. To find an ID, look at a piece in game and run: cprot look. Or run: cprot scan, to list every builder in the loaded area.");
		ProtectAllCreators = ((BaseUnityPlugin)this).Config.Bind<bool>("1 - Who is protected", "ProtectAllCreators", false, "If true, every piece is protected from everyone except whoever placed it, and ProtectedCreatorIds is ignored. This also stops players wrecking each other's builds.");
		BypassPlayerIds = ((BaseUnityPlugin)this).Config.Bind<string>("1 - Who is protected", "BypassPlayerIds", "", "Comma-separated character IDs allowed to remove and damage protected pieces. Put your admin character here.");
		ProtectUnownedPieces = ((BaseUnityPlugin)this).Config.Bind<bool>("1 - Who is protected", "ProtectUnownedPieces", false, "Also protect pieces with no recorded creator (ID 0). Those are usually world-generated structures, or pieces placed while the client did not own the object. Enabling this protects a great deal.");
		BlockRemoval = ((BaseUnityPlugin)this).Config.Bind<bool>("2 - What is blocked", "BlockRemoval", true, "Block hammer deconstruction of protected pieces. This is the setting that stops material refunds, and therefore the unintended recipe unlocks.");
		BlockPlayerDamage = ((BaseUnityPlugin)this).Config.Bind<bool>("2 - What is blocked", "BlockPlayerDamage", true, "Block damage to protected pieces caused by players: pickaxes, weapons, stray arrows, fire.");
		BlockAllDamage = ((BaseUnityPlugin)this).Config.Bind<bool>("2 - What is blocked", "BlockAllDamage", false, "Block every source of damage to protected pieces, including enemies, raids, weather and decay. Leave this false to keep trolls and raids threatening.");
		BlockResourceDrops = ((BaseUnityPlugin)this).Config.Bind<bool>("2 - What is blocked", "BlockResourceDrops", true, "Protected pieces drop no build materials unless their own creator removed them. This is the safety net for progression: an enemy smashing a wall cannot hand out materials the players should not have.");
		PreventCollapse = ((BaseUnityPlugin)this).Config.Bind<bool>("2 - What is blocked", "PreventCollapse", true, "Protected pieces never collapse from lost structural support, so someone removing their own piece cannot bring a protected build down with it.");
		ServerEnforcement = ((BaseUnityPlugin)this).Config.Bind<bool>("3 - Enforcement", "ServerEnforcement", true, "Also veto removal requests that arrive over the network. On a dedicated server this blocks players who do not have the mod installed, for every object the server itself owns.");
		ShowMessage = ((BaseUnityPlugin)this).Config.Bind<bool>("4 - Feedback", "ShowMessage", true, "Show a centre-screen message when a removal is blocked.");
		BlockedMessage = ((BaseUnityPlugin)this).Config.Bind<string>("4 - Feedback", "BlockedMessage", "Protected build - you did not place this", "The message shown when a removal is blocked.");
		Verbose = ((BaseUnityPlugin)this).Config.Bind<bool>("4 - Feedback", "Verbose", false, "Log every blocked action to the BepInEx log. Useful while setting the mod up.");
		ProtectedCreatorIds.SettingChanged += OnIdsChanged;
		BypassPlayerIds.SettingChanged += OnIdsChanged;
	}

	private static void OnIdsChanged(object sender, EventArgs e)
	{
		ReloadIds();
		Log.LogInfo((object)("Reloaded IDs. Protected creators: " + DescribeProtected()));
	}

	internal static void ReloadIds()
	{
		ParseIds(ProtectedCreatorIds.Value, ProtectedSet);
		ParseIds(BypassPlayerIds.Value, BypassSet);
	}

	private static void ParseIds(string raw, HashSet<long> into)
	{
		into.Clear();
		if (string.IsNullOrEmpty(raw))
		{
			return;
		}
		string[] array = raw.Split(new char[4] { ',', ';', ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
		for (int i = 0; i < array.Length; i++)
		{
			if (long.TryParse(array[i].Trim(), NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
			{
				into.Add(result);
			}
			else
			{
				Log.LogWarning((object)("Could not parse ID " + array[i] + " - ignoring it."));
			}
		}
	}

	private static string Join(HashSet<long> set)
	{
		List<string> list = new List<string>();
		foreach (long item in set)
		{
			list.Add(item.ToString(CultureInfo.InvariantCulture));
		}
		return string.Join(",", list.ToArray());
	}

	internal static string DescribeProtected()
	{
		if (ProtectAllCreators.Value)
		{
			return "(every creator protects their own pieces)";
		}
		if (ProtectedSet.Count == 0)
		{
			return "(none configured)";
		}
		return Join(ProtectedSet);
	}

	internal static bool AddProtectedId(long id)
	{
		if (!ProtectedSet.Add(id))
		{
			return false;
		}
		ProtectedCreatorIds.Value = Join(ProtectedSet);
		return true;
	}

	internal static bool RemoveProtectedId(long id)
	{
		if (!ProtectedSet.Remove(id))
		{
			return false;
		}
		ProtectedCreatorIds.Value = Join(ProtectedSet);
		return true;
	}

	internal static long LocalPlayerId()
	{
		Game instance = Game.instance;
		if ((Object)(object)instance == (Object)null)
		{
			return 0L;
		}
		PlayerProfile playerProfile = instance.GetPlayerProfile();
		if (playerProfile == null)
		{
			return 0L;
		}
		return playerProfile.GetPlayerID();
	}

	internal static Piece PieceOf(Component component)
	{
		if ((Object)(object)component == (Object)null)
		{
			return null;
		}
		Piece val = component.GetComponent<Piece>();
		if ((Object)(object)val == (Object)null)
		{
			val = component.GetComponentInParent<Piece>();
		}
		return val;
	}

	internal static bool IsCreatorProtected(long creator)
	{
		if (creator == 0)
		{
			return ProtectUnownedPieces.Value;
		}
		if (ProtectAllCreators.Value)
		{
			return true;
		}
		return ProtectedSet.Contains(creator);
	}

	internal static bool IsProtected(Piece piece, out long creator)
	{
		creator = 0L;
		if ((Object)(object)piece == (Object)null)
		{
			return false;
		}
		creator = piece.GetCreator();
		return IsCreatorProtected(creator);
	}

	internal static bool MayModify(long creator, long actor)
	{
		if (actor == 0)
		{
			return false;
		}
		if (actor == creator)
		{
			return true;
		}
		return BypassSet.Contains(actor);
	}

	internal static bool LocalMayModify(Piece piece)
	{
		if (!IsProtected(piece, out var creator))
		{
			return true;
		}
		return MayModify(creator, LocalPlayerId());
	}

	internal static long ResolveActor(long sender)
	{
		ZNet instance = ZNet.instance;
		if ((Object)(object)instance != (Object)null)
		{
			ZNetPeer peer = instance.GetPeer(sender);
			if (peer != null && peer.m_playerID != 0)
			{
				return peer.m_playerID;
			}
		}
		return LocalPlayerId();
	}

	internal static void NotifyBlocked(Player player)
	{
		if (!((Object)(object)player == (Object)null) && ShowMessage.Value)
		{
			((Character)player).Message((MessageType)2, BlockedMessage.Value, 0, (Sprite)null, false);
		}
	}

	internal static void Trace(string message)
	{
		if (Verbose.Value)
		{
			Log.LogInfo((object)message);
		}
	}
}