Skipping entity (foundation ID) - it's a player (steamID) who is in the save multiple times on server restartSolved

Client-side error is "Local player is being set up multiple times"

TP'ing at safe zones like /bandit and disconnecting while remaining a sleeper then reconnecting causes the bug. I've been able to replicate my own player gameobject multiple times. It also shows a client-side error "Local player is being set up multiple times" after you reconnect and get close to where any of your replicated player(s) are.

Then the - what seems like random - foundations get deleted when server loads up the saved .map file by stating "Skipping entity (foundation ID) - it's a player (steamID) who is in the save multiple times"

Having "Auto Wake Up After Teleport": true, under NTeleportation's config fixes it

BTW, if there are already replicated player game objects, they'll get saved. Only way to fix is by finding all objects related to the player(s) steamID's and killing them all. Some can be invisible, but C4 works 🤣

https://discord.com/channels/560127830160048128/810593365262729247/1436914524937654283

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using HarmonyLib;
using Network;

namespace Oxide.Plugins
{
    [Info("LoadEntityBasePlayerFix", "Pengoo", "1.0.0")]
    [Description("Removes BasePlayer data from non-player entities when loading saves to prevent erroneous skipping.")]
    public class LoadEntityBasePlayerFix : RustPlugin
    {
        private const string PlayerPrefabPath = "assets/prefabs/player/player.prefab";

        private static LoadEntityBasePlayerFix Instance;

        private Harmony harmony;

        private void Init()
        {
            Instance = this;
            TryPatchProtoEntityHooks();
        }

        private void Unload()
        {
            harmony?.UnpatchAll(harmony?.Id);
            harmony = null;
            Instance = null;
        }

        private void TryPatchProtoEntityHooks()
        {
            try
            {
                harmony = new Harmony("loadentitybaseplayerfix.harmony");
            }
            catch (Exception ex)
            {
                PrintError($"Failed to initialize Harmony: {ex}");
                harmony = null;
                return;
            }

            var targetTypes = FindProtoEntityTypes();

            if (targetTypes.Count == 0)
            {
                PrintWarning("Could not find ProtoBuf.Entity type. No patches were applied.");
                return;
            }

            var postfixMethod = typeof(LoadEntityBasePlayerFix).GetMethod(nameof(ReadFromStreamPostfix), BindingFlags.Static | BindingFlags.NonPublic);
            var postfixPatch = postfixMethod != null ? new HarmonyMethod(postfixMethod) : null;

            if (postfixPatch == null)
            {
                PrintWarning("Failed to locate ReadFromStreamPostfix method. No patches were applied.");
                return;
            }

            var patchedAny = false;

            foreach (var type in targetTypes)
            {
                MethodInfo[] methods;
                try
                {
                    methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
                        .Where(m => m.Name == "ReadFromStream")
                        .ToArray();
                }
                catch (Exception ex)
                {
                    PrintWarning($"Failed while scanning methods on {type.FullName}: {ex.Message}");
                    continue;
                }

                foreach (var method in methods)
                {
                    try
                    {
                        harmony.Patch(method, postfix: postfixPatch);
                        patchedAny = true;
                    }
                    catch (Exception ex)
                    {
                        PrintWarning($"Failed to patch {type.FullName}.{method.Name}: {ex.Message}");
                    }
                }
            }

            if (!patchedAny)
            {
                PrintWarning("No ReadFromStream overloads were patched.");
            }
            else
            {
                Puts($"LoadEntityBasePlayerFix: patched ReadFromStream on {targetTypes.Count} ProtoBuf.Entity type(s).");
            }
        }

        private static List<Type> FindProtoEntityTypes()
        {
            var result = new List<Type>();
            var visited = new HashSet<Assembly>();

            foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
            {
                if (!visited.Add(assembly))
                {
                    continue;
                }

                try
                {
                    var type = assembly.GetType("ProtoBuf.Entity", false);
                    if (type != null && !result.Contains(type))
                    {
                        result.Add(type);
                    }
                }
                catch
                {
                    // ignore assemblies we cannot inspect
                }
            }

            return result;
        }

        private static void ReadFromStreamPostfix(global::ProtoBuf.Entity __instance)
        {
            if (Instance == null || __instance == null)
            {
                return;
            }

            if (__instance.basePlayer == null)
            {
                return;
            }

            var networkable = __instance.baseNetworkable;
            if (networkable == null)
            {
                return;
            }

            var prefabId = networkable.prefabID;
            if (prefabId == 0u)
            {
                return;
            }

            string prefabName = null;
            try
            {
                prefabName = StringPool.Get(prefabId);
            }
            catch
            {
                // ignored
            }

            if (string.Equals(prefabName, PlayerPrefabPath, StringComparison.Ordinal))
            {
                return;
            }

            var playerId = GetProtoPlayerUserId(__instance.basePlayer);
            var netId = networkable.uid.IsValid ? networkable.uid.Value : 0UL;
            Instance.LogFix(prefabName, prefabId, playerId, netId);

            TryResetProtoPlayer(__instance.basePlayer);
            __instance.basePlayer = null;
        }

        private static ulong GetProtoPlayerUserId(object protoPlayer)
        {
            if (protoPlayer == null)
            {
                return 0UL;
            }

            var type = protoPlayer.GetType();
            const BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;

            try
            {
                var property = type.GetProperty("userid", flags);
                if (property != null)
                {
                    var value = property.GetValue(protoPlayer, null);
                    if (value != null && ulong.TryParse(value.ToString(), out var parsed))
                    {
                        return parsed;
                    }
                }
            }
            catch
            {
                // ignored
            }

            try
            {
                var field = type.GetField("userid", flags);
                if (field != null)
                {
                    var value = field.GetValue(protoPlayer);
                    if (value != null && ulong.TryParse(value.ToString(), out var parsed))
                    {
                        return parsed;
                    }
                }
            }
            catch
            {
                // ignored
            }

            return 0UL;
        }

        private static void TryResetProtoPlayer(object protoPlayer)
        {
            if (protoPlayer == null)
            {
                return;
            }

            var type = protoPlayer.GetType();
            const BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;

            try
            {
                var method = type.GetMethod("ResetToPool", flags, null, Type.EmptyTypes, null);
                method?.Invoke(protoPlayer, Array.Empty<object>());
            }
            catch
            {
                // ignored
            }
        }

        private void LogFix(string prefabName, uint prefabId, ulong playerId, ulong netId)
        {
            if (string.IsNullOrEmpty(prefabName))
            {
                prefabName = $"#{prefabId}";
            }

            Puts($"[LoadEntityBasePlayerFix] Cleared basePlayer (playerId={playerId}) from prefab={prefabName} netId={netId}");
        }
    }
}


Merged post

LoadEntityBasePlayerFix.cs

I wrote a bit in discord umod. And this guy PenGoo sent me this so entities can't get deleted! CHEERS

Yeah it's a nice fix to prevent foundations from disapearing after a restart! But it won't prevent same player objects from replicating which in turn could cause some other issues.

I'm not seeing anything that would cause this, any ideas?

I don't think it's your plugin in itself, but more a side-effect of something FP changed about sleeping players / safe zones lately? I didn't lookup into code much as debugging it took me 10 hours straight and tons of server restarts yesterday. A few key tests I did:

- I did not manage to replicate myself in a vanilla way. I did test for example to go to bandit, disconnect (to become a sleeper) and reconnect (stay as sleeper) re-disconnect.

- When disconnecting in safe zone as a sleeper from this pluging (probably any others too) while having "Seconds Until Teleporting Home Offline Players Within SafeZones" will TP the <dead but not killed> player object to their home, adding to the confusion.

- At some point I did manage to replicate myself multiple times which were ran simultaneously. What I mean is, I was within one player, saw myself as another and it both were responding to my input. Another was far in the map in safe zone so I, on the beach, couldn't use any weapon like if I was in safe zone. Another, still far in the map, was stretching my body from where I was (my POV player) to that replicated player, etc.

I guess I'd look at StartSleeping if something could be done?



Merged post

About base entities being tied to that playerID, even entities said player never interacted with, which ends up destoying other people's bases doesn't seem to make sense at all. Could it be some memory issue caused when a player object gets replicated as it should always be unique?

yes I think they've updated StartSleeping and mine is missing a couple lines that are needed. It looks like I can use the native method now that Vanish is using the OnPlayerColliderEnable hook.

Sounds great!

if the entity issue is caused by this then the same fix for sleeping will fix that too

Merged post

testing shows this when the issue occurs:
1. the real player is in the activePlayerList
2. the duplicate player is in the sleepingPlayerList
3. the real player is in the serverEntities list with its own network ID and is connected
4. the duplicate player is in the serverEntities list with a separate network ID and is not connected
5. the duplicate player can be killed with explosives, assuming godmode does not prevent the damage

I removed the duplicates, killed the player and I'm still getting kicked so I am wiping the server again to test the fix

When you kill them all you get kicked but you should be fine after reconnecting since all dups are removed and the map is also fine, no entity bug. That's how I fixed my live server

I was still getting a local player error kick after reconnecting

Exception (cs:136537): NullReferenceException: Object reference not set to an instance of an object. BasePlayer.%16e30ba28d3285496f337734fdcb078b8c43ced0 (%d14c36427718255cc840e29a218c75a1d722c22f+%06d7d3481f71ce7f3d4771d52b5ba47d5427730a& %f2b93cd3c8e62b27d215157ff926b1ead4178c90) (at <00000000000000000000000000000000>:0) BasePlayer.%16e30ba28d3285496f337734fdcb078b8c43ced0 () (at <00000000000000000000000000000000>:0) BasePlayer.%d185c220e41791e6b9c5448cef9a749bf8f6613b (%4b88400350cf62216335c3e6292827a724f7a09e %8ea54b3649a2f408b07f9567197a64593f8f81b4, System.Single %97b3b28356a408b12f6a07081c387422897a5da8) (at <00000000000000000000000000000000>:0) BasePlayer.ClientUpdateLocalPlayer (System.Single %9b8cfc63ccc903d0af6d223f9c830cbad8fb2390) (at <00000000000000000000000000000000>:0) BasePlayer.%ff2160a36a19482e0bd3e961997a7b391a66d49d (System.Single %9b8cfc63ccc903d0af6d223f9c830cbad8fb2390) (at <00000000000000000000000000000000>:0) Client.Update () (at <00000000000000000000000000000000>:0) ​

I don't recall having that one, usually it was (but didn't make me crash): Local player is being set up multiple times!

thanks trader, this is fixed in 1.9.2 
see update notes for other changes

Thanks for all you do!

nivex

thanks trader, this is fixed in 1.9.2 
see update notes for other changes

We dont have to run the above plugin by theswingingturtle to clear any old entities? does the new plugin update of NTeleportation fix everyhting related to that?

WiZUBW4RT3ov3AE.png SlayersRust

We dont have to run the above plugin by theswingingturtle to clear any old entities? does the new plugin update of NTeleportation fix everyhting related to that?

It fixes only future player duplication, but you can run the above plugin for next restarts until you wipe. That would prevent foundations from being removed, but probably won't remove duplicated players.

Best way if you don't want some players to bug would be to find all duplicated player entities and kill them then do server.save and test the .sav over a test server to make sure you got them all, as in there's no ignored entities in console during startup. It could be tedious work to find them all though.
Locked automatically