using BepInEx; using BepInEx.Configuration; using R2API; using R2API.Utils; using RoR2; using UnityEngine; using UnityEngine.Networking; namespace AntiYoink { // This attribute specifies that we have a dependency on a given BepInEx Plugin, // We need the R2API ItemAPI dependency because we are using for adding our item to the game. // You don't need this if you're not using R2API in your plugin, // it's just to tell BepInEx to initialize R2API before this plugin so it's safe to use R2API. [BepInDependency(ItemAPI.PluginGUID)] // This one is because we use a .language file for language tokens // More info in https://risk-of-thunder.github.io/R2Wiki/Mod-Creation/Assets/Localization/ [BepInDependency(LanguageAPI.PluginGUID)] // This attribute is required, and lists metadata for your plugin. [BepInPlugin(PluginGUID, PluginName, PluginVersion)] // We only do server-side stuff :) [NetworkCompatibility(compatibility: CompatibilityLevel.NoNeedForSync)] // This is the main declaration of our plugin class. // BepInEx searches for all classes inheriting from BaseUnityPlugin to initialize on startup. // BaseUnityPlugin itself inherits from MonoBehaviour, // so you can use this as a reference for what you can declare and use in your plugin class // More information in the Unity Docs: https://docs.unity3d.com/ScriptReference/MonoBehaviour.html public class AntiYoinkPlugin : BaseUnityPlugin { // The Plugin GUID should be a unique ID for this plugin, // which is human readable (as it is used in places like the config). public const string PluginGUID = PluginAuthor + "." + PluginName; public const string PluginAuthor = "devplayer0"; public const string PluginName = "AntiYoink"; public const string PluginVersion = "0.1.0"; private const bool DevMode = true; public static ConfigEntry MaxItemCountDelta { get; set; } private float naughtyLast = 0; // The Awake() method is run at the very start when the game is initialized. public void Awake() { // Init our logging class so that we can properly log for debugging Log.Init(Logger); MaxItemCountDelta = Config.Bind( "General", "MaxItemCountDelta", 3, "Maximum delta between player with the lowest number of items"); if (DevMode) { On.RoR2.Networking.NetworkManagerSystemSteam.OnClientConnect += (s, u, t) => { }; } } public void OnEnable() { On.RoR2.GenericPickupController.AttemptGrant += GenericPickupController_AttemptGrant; On.RoR2.PickupPickerController.OnInteractionBegin += PickupPickerController_OnInteractionBegin; } public void OnDisable() { On.RoR2.GenericPickupController.AttemptGrant -= GenericPickupController_AttemptGrant; On.RoR2.PickupPickerController.OnInteractionBegin -= PickupPickerController_OnInteractionBegin; } public static int GetTotalItems(Inventory inv) { int itemCount = 0; foreach (var i in inv.itemAcquisitionOrder) { if (ItemCatalog.GetItemDef(i).hidden) continue; itemCount += inv.GetItemCount(i); } return itemCount; } private bool AllowedToPickUp(CharacterBody body) { CharacterBody lowestPlayer = body; int yoinkerItemCount = GetTotalItems(body.inventory); int delta = 0; foreach (var player in PlayerCharacterMasterController.instances) { var playerBody = player.master.GetBody(); if (!playerBody || !playerBody.healthComponent.alive || playerBody == body) continue; int itemCount = GetTotalItems(playerBody.inventory); if (yoinkerItemCount - itemCount > delta) { lowestPlayer = playerBody; delta = yoinkerItemCount - itemCount; } } if (lowestPlayer != body && delta > MaxItemCountDelta.Value) { if (Run.instance.time - naughtyLast > 1) { ChatMessage.Send($"Naughty, naughty! {body.GetUserName()} has {delta} more items than poor {lowestPlayer.GetUserName()}"); naughtyLast = Run.instance.time; } return false; } return true; } private void GenericPickupController_AttemptGrant(On.RoR2.GenericPickupController.orig_AttemptGrant orig, GenericPickupController self, CharacterBody body) { if (!NetworkServer.active) { Debug.LogWarning("[Server] AntiYoink hook function 'GenericPickupController_AttemptGrant' called on client"); return; } TeamComponent component = body.GetComponent(); if (!component || component.teamIndex != TeamIndex.Player) { return; } if (!AllowedToPickUp(body)) { return; } orig(self, body); } // For Artifact of Command private void PickupPickerController_OnInteractionBegin(On.RoR2.PickupPickerController.orig_OnInteractionBegin orig, PickupPickerController self, Interactor activator) { if (NetworkServer.active) { CharacterBody body = activator.GetComponent(); if (body && !AllowedToPickUp(body)) { return; } } orig(self, activator); } } }