Option suggestion "SleepersAlsoLootProtectedWhenKillProtected"

Hey nivex,

I understand that TruePvE doesn't have a sophisticated loot protection system, and that other plugins handle that.However, I would suggest a very simple sleeper loot protection, since TruePvE already has built-in sleeper kill protection.

A very simple option like "SleepersAlsoLootProtectedWhenKillProtected" would be sufficient. Maybe an "AllyCanLootSleeper" would be nice, too.

All other simple sleeper plugins always treat kill and loot together, which might conflict with TruePvE's kill protection if you use an additional one.

Cheers

Since you said you were learning in another thread, I will give a detailed response to this in hopes that it helps you and other server owners.

Its planned, and I want it too, but I have to be careful about performance. This plugin handles the vast majority of damage on the server already, and adding additional options will require more server resources. People often point out that another plugin has a certain option, but what gets missed is the cost of how those features are implemented. A lot of plugins prioritize features over efficiency, and in a Unity-based server that can translate directly into worse performancve for everyone. That is a big reason many servers struggle with performance (not only because they use too many plugins, but because they use too many that weren't written with performance in mind).

This is an Oxide plugin running inside a Unity game server, not a standalone Windows or Linux application, so choices like LINQ, extra allocations, and avoidable overhead heavily impact performance. Small costs add up fast at server scale. This plugin already shows high hook times, which some people mistake as "bad performance." In reality, those numbers are expected: the plugin is doing millions of damage-related evaluations across the server, so the hook time reflects real work being done, not inefficiency. This is to say that adding additional features like what those plugins have will require additional hook time, and it will be implemented with performance in mind. I don't care what memory usage states, it's bloated. There are very few allocations in this plugin so that's coming from elsewhere. Rust has made drastic improvements over time to reduce native allocations as well. Love to see it.

I completely understand what you're saying.Even the smallest check to see if a value or permission is set adds up when it has to be done 1000 times.And there are definitely some plugins that aren't very efficiently written.

I don't mean to badmouth any particular plugins, but I actually have a purchased one running that calls another plugin's interface far too often and then relies entirely on the garbage collector.I've submitted an elaborated bug report and hope the developer takes care of it.

That said, yes I'm learning and wrote my first plugin only for the sole purpose of loot protection for sleepers. I borrowed the isAlly() code from you, so it works with Teams, Clans and Friends. I attache the code here, maybe you can give a comment on it or it will safe some time if you choose to implement it in TruePvE.

By the way at the uMod plugin page of TruePvE is not stated, that it works with Teams, Clans and Friends. Maybe you should correct this. And last but not least: Is it possible to get TruePvE as git repository? Now that I've got a taste for it, I'd like to make these small additions directly in TruePvE. Then I'd have the option to merge them again in the next update if you don't like them in the main branch.

Greetz

using System;
using System.Collections.Generic;
using Oxide.Core.Plugins;

namespace Oxide.Plugins
{
    [Info("Player Loot Protection", "ToliburtiX", "1.0.0")]
    [Description("Protects players being looted by other players than allies. Works with Teams, Clans and Friends.")]
    class PlayerLootProtection : CovalencePlugin
    {
        [PluginReference]
        Plugin Clans, Friends;


        // Players (default group) may get loot protection.
        private const string permEnable = "playerlootprotection.enable";

        // Admins (admin group) may get bypass permission.
        private const string permBypass = "playerlootprotection.bypass";


        private void Init()
        {
            permission.RegisterPermission(permEnable, this);
            permission.RegisterPermission(permBypass, this);
        }


        private object isPlayerProtected(BasePlayer looter, ulong entityID)
        {
            if (!permission.UserHasPermission(looter.userID.ToString(), permBypass))
            {
                if (looter.userID != entityID && !IsAlly(looter.userID, entityID) && permission.UserHasPermission(entityID.ToString(), permEnable))
                {
                    NextFrame(looter.EndLooting);
                    return true;
                }
            }

            return null;
        }


        private object OnLootEntity(BasePlayer looter, BasePlayer sleeper)
        {
            return isPlayerProtected(looter, sleeper.userID);
        }


        private object OnLootEntity(BasePlayer looter, LootableCorpse corpse)
        {
            return isPlayerProtected(looter, corpse.playerSteamID);
        }


        private object OnLootEntity(BasePlayer looter, DroppedItemContainer container)
        {
            return isPlayerProtected(looter, container.playerSteamID);
        }


        private bool IsAlly(ulong vic, ulong atk) => vic switch
        {
            _ when vic == atk => true,
            _ when RelationshipManager.ServerInstance.playerToTeam.TryGetValue(vic, out var team) && team.members.Contains(atk) => true,
            _ when Clans != null && Convert.ToBoolean(Clans?.Call("IsClanMember", vic, atk)) => true,
            _ when Friends != null && Convert.ToBoolean(Friends?.Call("AreFriends", vic, atk)) => true,
            _ => false
        };

    }
}

hi, that's a great attempt. I like to avoid NextFrame/NextTick calls, especially when it involves looting. this can conflict with other plugins, and it creates allocations.

I've chosen different hooks that have immediate blocks for looting as well. I haven't tested it though so you might want to give that a try.

I don't like permissions so I will likely keep those removed. it's my standard that someone be required to use noclip or vanish to bypass certain functionality in my plugins so I've adapted that for invis/vanish here

using System;
using System.Collections.Generic;
using Oxide.Core.Plugins;

namespace Oxide.Plugins
{
    [Info("Player Loot Protection", "ToliburtiX", "1.0.1")]
    [Description("Protects players being looted by other players than allies. Works with Teams, Clans and Friends.")]
    class PlayerLootProtection : CovalencePlugin
    {
        [PluginReference]
        Plugin Clans, Friends;

        private object isPlayerProtected(BasePlayer looter, ulong ownerid) // entityID is commonly entity.net.ID
        {
            // we check least to most expensive for best performance
            if (looter.userID != ownerid && !canBypass(looter) && !IsAlly(looter.userID, ownerid))
            {
                return true;
            }

            return null;
        }

        // if the user is trusted enough to have invis or vanish then they're trusted enough to loot
        private bool canBypass(BasePlayer looter) => looter.isInvisible || looter.limitNetworking;


        private object CanLootPlayer(BasePlayer looter, BasePlayer sleeper)
        {
            // this is a bool hook ( a bool value will allow or block looting ) 
            // - null to allow the game or other plugins to continue checks
            // - false to block looting
            return isPlayerProtected(looter, sleeper.userID) != null ? (object)false : null; 
        }


        private object CanLootEntity(BasePlayer looter, LootableCorpse corpse)
        {
            // this hook is non-bool despite its name ( any non-null return blocks looting )
            // in non-bool hooks for consistency and to reduce plugin conflicts, my plugins return only:
            // - null to allow
            // - true to block
            // other non-null values also block, but sticking to one sentinel keeps behavior predictable
            return isPlayerProtected(looter, corpse.playerSteamID);
        }


        private object CanLootEntity(BasePlayer looter, DroppedItemContainer container)
        {
            return isPlayerProtected(looter, container.playerSteamID); // same as corpse hook
        }


        private bool IsAlly(ulong vic, ulong owner) => vic switch
        {
            _ when owner == 0 => true, // not owned by any player so allow it
            _ when vic == owner => true, // redundant
            _ when RelationshipManager.ServerInstance.playerToTeam.TryGetValue(vic, out var team) && team.members.Contains(owner) => true,
            _ when Clans != null && Convert.ToBoolean(Clans?.Call("IsClanMember", vic, owner)) => true,
            _ when Friends != null && Convert.ToBoolean(Friends?.Call("AreFriends", vic, owner)) => true,
            _ => false
        };

    }
}

Good evening,

many thanks for your review and the explaining comments. I like your attention to details like shortcut-evaluation in boolean-logic (check least to most expensive).

The CanLoot[Player|Entity]-Hooks are so much better, because they prevent opening the lootbox in first place. Works great. But the return values are a little bit confusing. Nice schema with null, false and true you have.

 I did some refactoring:
  • Took owner == 0 out of isAlly() again, because it does not fit the name isAlly()
  • victim == owner is fine, everyone should be an ally of themselves, deleted the redundant check
  • made isPlayerProtected an inliner, or in C# naming lambda expression

You are right, no extra permission are needed for this usecase. But now the difficulties begin. I think you exspected to use the oxide vanish plugin from whispers88. I'm using carbon and the inbuild vanish module. Source-Code here: https://github.com/CarbonCommunity/Carbon.Modules/blob/develop/src/VanishModule/VanishModule.cs

It works great and even opens locked boxes in vanish mode.

But I cannot get the vanish status. There is a IsPlayerVanished() method, but it always returns false. Also do looter.isInvisible or looter.limitNetworking. Even if I use debug.invis or debug.noclip on console, it is working as intended, but all status-functions in the plugin get false.


Any idea what to try next?

Greetz

using System;
using System.Collections.Generic;
using Oxide.Core.Plugins;

#if CARBON
using Carbon.Modules;
using Carbon.Base;
#endif


namespace Oxide.Plugins
{
    [Info("Player Loot Protection", "ToliburtiX", "1.0.2")]
    [Description("When installed, protects players being looted by other players than allies.\n" +
                 "No configuration or permissions needed. Works with Teams, Clans and Friends.\n" +
                 "Invisible/noClip players aka Admins automagically bypass protection.")]
    class PlayerLootProtection : CovalencePlugin
    {
        [PluginReference]
        Plugin Clans, Friends;


        // we check least to most expensive for best performance
        // orphans can be looted
        private object isPlayerProtected(BasePlayer looter, ulong ownerID) =>
                  0 != ownerID && !canBypass(looter) && !isAlly(looter.userID, ownerID)
                ? true : null;


        // if the user is trusted enough to have invis or vanish then they're trusted enough to loot
#if CARBON
        private bool canBypass(BasePlayer looter)
        {
            var vanishMod = Carbon.Base.BaseModule.GetModule<VanishModule>();
            return null != vanishMod ? vanishMod.IsPlayerVanished(looter.userID) : looter.isInvisible || looter.limitNetworking;
        }
#else
        private bool canBypass(BasePlayer looter) => looter.isInvisible || looter.limitNetworking;
#endif


        private bool isAlly(ulong vic, ulong owner) => vic switch
        {
            _ when vic == owner => true, // Everyone is an ally of themselves.
            _ when RelationshipManager.ServerInstance.playerToTeam.TryGetValue(vic, out var team) && team.members.Contains(owner) => true,
            _ when Clans != null && Convert.ToBoolean(Clans?.Call("IsClanMember", vic, owner)) => true,
            _ when Friends != null && Convert.ToBoolean(Friends?.Call("AreFriends", vic, owner)) => true,
            _ => false
        };


        private object CanLootPlayer(BasePlayer looter, BasePlayer sleeper)
        {
            // this is a bool hook ( a bool value will allow or block looting )
            // - null to allow the game or other plugins to continue checks
            // - false to block looting
            return isPlayerProtected(looter, sleeper.userID) != null ? (object)false : null;
        }


        private object CanLootEntity(BasePlayer looter, LootableCorpse corpse)
        {
            // this hook is non-bool despite its name ( any non-null return blocks looting )
            // in non-bool hooks for consistency and to reduce plugin conflicts, my plugins return only:
            // - null to allow
            // - true to block
            // other non-null values also block, but sticking to one sentinel keeps behavior predictable
            return isPlayerProtected(looter, corpse.playerSteamID);
        }


        private object CanLootEntity(BasePlayer looter, DroppedItemContainer container)
        {
            return isPlayerProtected(looter, container.playerSteamID); // same as corpse hook
        }
    }
}

 

hi, I'm not sure why you're having issues with the carbon vanish module, but that module sets player.limitNetworking to true in the DoVanish method so it shouldn't be required to check with the IsPlayerVanished method

Well, after a deep look in the oxide API documentation I found the problem. While CanLootEntity(BasePlayer looter, ...) takes the looting player as first argument, CanLootPlayer(BasePlayer sleeper, BasePlayer looter) takes the target as first argument, and the looter as second. After the corresponding changes, no extra treatment for carbon is necessary. looter.isInvisible || looter.limitNetworking will reflect the vanish status regardless of using inbuild invis, oxide vanish plugin or carbon vanish module.

Next thing to learn is how to handle a dropped backpack. 

ah right, nice catch

Hey nivex,

thanks for implementing Prevent non-ally from looting xxx features. Sleepers and corpses work like intended.

But backpacks can be looted regardless if they dropped intentionally by a living player, or dropped on death.

Greetings

hi, use these commands on the backpack

game console (F1): ent who
game chat: /tpve_prod

ent who --> Owner ID: 0
tpve_prod --> type=DroppedItem, prefab=generic_world

those shouldn't have an Owner ID of 0 if they're a rust backpack. neither when dropped intentionally nor on death, unless spawned via the spawn command or set to 0 by another plugin

rust backpacks haven't been added yet though, just the backpacks that spawn from a killed corpse that contains the players entire inventory. I can add it next update

this will not work if di.DroppedBy is 0 too.

    private object CanLootEntity(BasePlayer looter, DroppedItem di)
    {
        if (!config.options.Loot.Backpacks || di == null) return null;
        Item item = di.GetItem();
        if (item == null || !item.IsBackpack()) return null;
        return isPlayerProtected(looter, di, di.DroppedBy.IsSteamId() ? di.DroppedBy : di.OwnerID, true);
    }

Tried with TruePVE from github, completely without Teams, Clan and Friends.

Backpacks and everything else regarding player-loot-protection works like expected. Great job, nivex. Thanks a lot!

nice, thank you for trying it out