using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BoneLib.BoneMenu;
using BoneLib.Notifications;
using DoppelNames;
using HarmonyLib;
using Il2CppSLZ.Marrow;
using Il2CppSLZ.Marrow.Warehouse;
using LabFusion.Data;
using LabFusion.Entities;
using LabFusion.Network;
using LabFusion.Network.Serialization;
using LabFusion.Player;
using LabFusion.Representation;
using LabFusion.SDK.Modules;
using LabFusion.UI;
using MelonLoader;
using MelonLoader.Preferences;
using Microsoft.CodeAnalysis;
using ThirdEyeBonelabMod.Bots;
using UnityEngine;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: MelonInfo(typeof(Mod), "DoppelNames", "1.0.0", "you", null)]
[assembly: MelonGame("Stress Level Zero", "BONELAB")]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("DoppelNames")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("DoppelNames")]
[assembly: AssemblyTitle("DoppelNames")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
[CompilerGenerated]
[Embedded]
internal sealed class EmbeddedAttribute : Attribute
{
}
}
namespace System.Runtime.CompilerServices
{
[CompilerGenerated]
[Embedded]
[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
internal sealed class RefSafetyRulesAttribute : Attribute
{
public readonly int Version;
public RefSafetyRulesAttribute(int P_0)
{
Version = P_0;
}
}
}
namespace DoppelNames
{
internal static class DoppelHook
{
public static void Prefix(ref IEnumerable<Bot> __0)
{
HashSet<object> hiddenBots = Mod.HiddenBots;
if (hiddenBots.Count == 0 || __0 == null)
{
return;
}
bool flag = false;
foreach (Bot item in __0)
{
if (hiddenBots.Contains(item))
{
flag = true;
break;
}
}
if (!flag)
{
return;
}
List<Bot> list = new List<Bot>();
foreach (Bot item2 in __0)
{
if (!hiddenBots.Contains(item2))
{
list.Add(item2);
}
}
__0 = list;
}
}
internal static class FusionFakes
{
private sealed class Fake
{
public ulong PlatformID;
public byte SmallID;
public PlayerID Id;
public string Name;
public string Barcode;
public SerializedAvatarStats Stats;
public RigSkeleton Skeleton;
public readonly RigPose Pose = new RigPose();
public float Created;
public int Failures;
public int PosesSent;
public float NextReport;
public readonly Dictionary<ulong, float> Introduced = new Dictionary<ulong, float>();
}
private const ulong FakePlatformBase = 940053923165634560uL;
private static ulong counter;
private const float IntroDelay = 1.5f;
private const float PoseDelay = 0.6f;
private static readonly List<Fake> all = new List<Fake>();
private static readonly Dictionary<ulong, float> firstSeen = new Dictionary<ulong, float>();
private static bool warnedNoIds;
public static string LastReason = "";
[MethodImpl(MethodImplOptions.NoInlining)]
public static List<string> LobbyNames()
{
List<string> list = new List<string>();
foreach (PlayerID playerID in PlayerIDManager.PlayerIDs)
{
try
{
if (playerID.Metadata.IsValid)
{
string value = playerID.Metadata.Username.GetValue();
if (!string.IsNullOrWhiteSpace(value))
{
list.Add(value);
}
}
}
catch
{
}
}
return list;
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static bool TryGetAvatar(string part, out string barcode, out Vector3 scale)
{
//IL_0005: Unknown result type (might be due to invalid IL or missing references)
//IL_000a: Unknown result type (might be due to invalid IL or missing references)
//IL_0166: Unknown result type (might be due to invalid IL or missing references)
//IL_016b: Unknown result type (might be due to invalid IL or missing references)
barcode = null;
scale = Vector3.one;
if (string.IsNullOrWhiteSpace(part))
{
return false;
}
NetworkPlayer val2 = default(NetworkPlayer);
foreach (PlayerID playerID in PlayerIDManager.PlayerIDs)
{
try
{
if (!playerID.Metadata.IsValid)
{
continue;
}
string value = playerID.Metadata.Username.GetValue();
if (string.IsNullOrEmpty(value) || value.IndexOf(part.Trim(), StringComparison.OrdinalIgnoreCase) < 0)
{
continue;
}
RigManager val = null;
if (playerID.IsMe)
{
barcode = RigData.GetAvatarBarcode();
val = RigData.Refs.RigManager;
}
else if (NetworkPlayerManager.TryGetPlayer(playerID.SmallID, ref val2) && val2.HasRig)
{
val = val2.RigRefs.RigManager;
barcode = val2.AvatarSetter.AvatarBarcode;
if (string.IsNullOrEmpty(barcode) && (Object)(object)val != (Object)null && val.AvatarCrate != null)
{
barcode = ((ScannableReference)val.AvatarCrate).Barcode.ID;
}
}
if (string.IsNullOrEmpty(barcode))
{
continue;
}
if ((Object)(object)val != (Object)null && (Object)(object)val.avatar != (Object)null)
{
scale = ((Component)val.avatar).transform.localScale;
}
return true;
}
catch
{
}
}
return false;
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static bool IsHosting()
{
try
{
return NetworkInfo.HasServer && NetworkInfo.IsHost;
}
catch
{
return false;
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static object Add(string name, object rig, string barcodeOverride = null)
{
//IL_019f: Unknown result type (might be due to invalid IL or missing references)
//IL_01f9: Unknown result type (might be due to invalid IL or missing references)
//IL_0203: Expected O, but got Unknown
//IL_0227: Unknown result type (might be due to invalid IL or missing references)
//IL_0231: Expected O, but got Unknown
RigManager val = (RigManager)((rig is RigManager) ? rig : null);
if ((Object)(object)val == (Object)null)
{
LastReason = "the bot has no rig yet";
return null;
}
if ((Object)(object)val.avatar == (Object)null)
{
LastReason = "the bot has no avatar yet";
return null;
}
if ((Object)(object)val.physicsRig == (Object)null)
{
LastReason = "the bot has no physics rig yet";
return null;
}
if (barcodeOverride == null && val.AvatarCrate == null)
{
LastReason = "the bot's avatar has no crate";
return null;
}
string text = barcodeOverride ?? ((ScannableReference)val.AvatarCrate).Barcode.ID;
if (string.IsNullOrEmpty(text))
{
LastReason = "the bot's avatar has no barcode";
return null;
}
PlayerID localID = PlayerIDManager.LocalID;
if (localID == null)
{
LastReason = "your own player id is missing";
return null;
}
byte? uniquePlayerID = PlayerIDManager.GetUniquePlayerID();
if (!uniquePlayerID.HasValue)
{
LastReason = "no free player slot";
if (!warnedNoIds)
{
warnedNoIds = true;
MelonLogger.Warning("DoppelNames: no free player slot for a server-side bot.");
}
return null;
}
Dictionary<string, string> dictionary = new Dictionary<string, string>(localID.Metadata.Metadata.LocalDictionary);
dictionary["Username"] = name;
dictionary["Nickname"] = "";
dictionary["Description"] = "";
dictionary["Loading"] = "false";
dictionary["PermissionLevel"] = ((object)(PermissionLevel)0/*cast due to .constrained prefix*/).ToString();
Fake fake = new Fake
{
PlatformID = 940053923165634560L + ++counter,
SmallID = uniquePlayerID.Value,
Name = name,
Barcode = text,
Stats = new SerializedAvatarStats(val.avatar),
Created = Time.unscaledTime
};
fake.Id = new PlayerID(fake.PlatformID, fake.SmallID, dictionary, new List<string>());
PlayerIDManager.ReserveSmallID(fake.SmallID);
all.Add(fake);
MelonLogger.Msg($"DoppelNames: server-side bot '{name}' created (player slot {fake.SmallID}).");
return fake;
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static void Tick(object handle, object rig)
{
//IL_02bf: Unknown result type (might be due to invalid IL or missing references)
//IL_02c9: Expected O, but got Unknown
//IL_0308: Unknown result type (might be due to invalid IL or missing references)
Fake fake = (Fake)handle;
if (fake.Failures > 40)
{
return;
}
try
{
float unscaledTime = Time.unscaledTime;
List<ulong> list = Recipients(unscaledTime);
if (list.Count == 0)
{
if (unscaledTime >= fake.NextReport)
{
fake.NextReport = unscaledTime + 10f;
MelonLogger.Msg("DoppelNames: '" + fake.Name + "': waiting, no other players in the lobby.");
}
return;
}
foreach (ulong item in list)
{
if (!fake.Introduced.ContainsKey(item) && !(unscaledTime - firstSeen[item] < 1.5f))
{
bool flag = unscaledTime - fake.Created < 4.5f;
SendJoin(fake, item, flag);
fake.Introduced[item] = unscaledTime;
MelonLogger.Msg($"DoppelNames: introduced '{fake.Name}' (slot {fake.SmallID}) to player {item}" + (flag ? " with a join notice." : "."));
}
}
RigManager val = (RigManager)((rig is RigManager) ? rig : null);
if ((Object)(object)val == (Object)null)
{
return;
}
List<ulong> list2 = new List<ulong>();
foreach (KeyValuePair<ulong, float> item2 in fake.Introduced)
{
if (unscaledTime - item2.Value >= 0.6f && list.Contains(item2.Key))
{
list2.Add(item2.Key);
}
}
if (list2.Count == 0)
{
if (unscaledTime >= fake.NextReport)
{
fake.NextReport = unscaledTime + 10f;
MelonLogger.Msg($"DoppelNames: '{fake.Name}': {list.Count} player(s) here, none ready for poses yet.");
}
return;
}
if (fake.Skeleton == null)
{
fake.Skeleton = new RigSkeleton(val);
}
fake.Pose.ReadSkeleton(fake.Skeleton);
PlayerPoseUpdateData val2 = PlayerPoseUpdateData.Create(fake.Pose);
NetWriter val3 = NetWriter.Create(val2.GetSize());
try
{
val2.Serialize((INetSerializer)(object)val3);
NetMessage val4 = NetMessage.Create(NativeMessageTag.PlayerPoseUpdate, val3, CommonMessageRoutes.UnreliableToOtherClients, (byte?)fake.SmallID);
try
{
foreach (ulong item3 in list2)
{
MessageSender.SendFromServer(item3, (NetworkChannel)1, val4);
}
}
finally
{
((IDisposable)val4)?.Dispose();
}
}
finally
{
((IDisposable)val3)?.Dispose();
}
fake.PosesSent++;
if (unscaledTime >= fake.NextReport)
{
fake.NextReport = unscaledTime + 10f;
MelonLogger.Msg($"DoppelNames: '{fake.Name}': {fake.PosesSent} pose updates sent so far to {list2.Count} player(s).");
}
}
catch (Exception ex)
{
fake.Skeleton = null;
if (++fake.Failures == 41)
{
MelonLogger.Warning("DoppelNames: server-side bot '" + fake.Name + "' stopped updating: " + ex.Message);
}
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static void Remove(object handle)
{
Fake fake = (Fake)handle;
all.Remove(fake);
try
{
List<ulong> list = Recipients(Time.unscaledTime);
foreach (ulong item in list)
{
if (fake.Introduced.ContainsKey(item))
{
SendLeave(fake, item);
}
}
}
catch (Exception ex)
{
MelonLogger.Warning("DoppelNames: could not announce a bot leaving: " + ex.Message);
}
PlayerIDManager.UnreserveSmallID(fake.SmallID);
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static void Forget(object handle)
{
Fake fake = (Fake)handle;
all.Remove(fake);
PlayerIDManager.UnreserveSmallID(fake.SmallID);
}
private static void SendJoin(Fake f, ulong recipient, bool notify)
{
//IL_0030: Unknown result type (might be due to invalid IL or missing references)
ConnectionResponseData val = ConnectionResponseData.Create(f.Id, f.Barcode, f.Stats, notify);
NetWriter val2 = NetWriter.Create();
try
{
NetSerializerExtensions.SerializeValue<ConnectionResponseData>((INetSerializer)(object)val2, ref val);
NetMessage val3 = NetMessage.Create(NativeMessageTag.ConnectionResponse, val2, CommonMessageRoutes.None, (byte?)null);
try
{
MessageSender.SendFromServer(recipient, (NetworkChannel)0, val3);
}
finally
{
((IDisposable)val3)?.Dispose();
}
}
finally
{
((IDisposable)val2)?.Dispose();
}
}
private static void SendLeave(Fake f, ulong recipient)
{
//IL_0028: Unknown result type (might be due to invalid IL or missing references)
DisconnectMessageData val = DisconnectMessageData.Create(f.PlatformID, "");
NetWriter val2 = NetWriter.Create();
try
{
NetSerializerExtensions.SerializeValue<DisconnectMessageData>((INetSerializer)(object)val2, ref val);
NetMessage val3 = NetMessage.Create(NativeMessageTag.Disconnect, val2, CommonMessageRoutes.None, (byte?)null);
try
{
MessageSender.SendFromServer(recipient, (NetworkChannel)0, val3);
}
finally
{
((IDisposable)val3)?.Dispose();
}
}
finally
{
((IDisposable)val2)?.Dispose();
}
}
private static List<ulong> Recipients(float now)
{
List<ulong> list = new List<ulong>();
HashSet<ulong> hashSet = new HashSet<ulong>();
foreach (PlayerID playerID in PlayerIDManager.PlayerIDs)
{
if (!playerID.IsMe)
{
ulong platformID = playerID.PlatformID;
hashSet.Add(platformID);
list.Add(platformID);
if (!firstSeen.ContainsKey(platformID))
{
firstSeen[platformID] = now;
}
}
}
List<ulong> list2 = null;
foreach (ulong key in firstSeen.Keys)
{
if (!hashSet.Contains(key))
{
(list2 ?? (list2 = new List<ulong>())).Add(key);
}
}
if (list2 != null)
{
foreach (ulong item in list2)
{
firstSeen.Remove(item);
foreach (Fake item2 in all)
{
item2.Introduced.Remove(item);
}
}
}
return list;
}
}
public class Mod : MelonMod
{
private sealed class Entry
{
public object Bot;
public string Name;
public bool Puppet;
public bool NetAnnounced;
public int Id;
public float Born;
public object TagHandle;
public object Fake;
public float FakeNextTry;
public string AvatarWanted;
public Vector3 AvatarScale = Vector3.one;
public bool AvatarDone;
public bool HiddenWarned;
public bool AvatarAsked;
public float AvatarAskedAt;
public bool FakeWarned;
}
internal struct Inbound
{
public int Owner;
public byte[] Bytes;
}
private MelonPreferences_Entry<bool> prefEnabled;
private MelonPreferences_Entry<bool> prefTags;
private MelonPreferences_Entry<bool> prefPopups;
private MelonPreferences_Entry<bool> prefServer;
private MelonPreferences_Entry<string> prefCustom;
private MelonPreferences_Entry<bool> prefLobby;
private MelonPreferences_Entry<string> prefTarget;
private MelonPreferences_Entry<string> prefFixed;
private MelonPreferences_Entry<bool> prefRandomAvatar;
private MelonPreferences_Entry<string> prefCopyAvatar;
private Page avatarPage;
private MethodInfo useAvatarM;
private Page playerPage;
private FieldInfo botsF;
private FieldInfo nameIsF;
private FieldInfo netOwnerF;
private FieldInfo netIdF;
private FieldInfo rigF;
private bool doppelReady;
private float nextResolve;
internal static readonly HashSet<object> HiddenBots = new HashSet<object>(ReferenceEqualityComparer.Instance);
private readonly Dictionary<IntPtr, Entry> tracked = new Dictionary<IntPtr, Entry>();
private readonly Dictionary<int, string> remoteNames = new Dictionary<int, string>();
private readonly Random rng = new Random();
private float nextPoll;
private float nextSnapshot;
private float nextFakeTick;
private float quietUntil;
private bool fusionPresent;
private bool fusionReady;
private bool loggedError;
internal static readonly ConcurrentQueue<Inbound> Inbox = new ConcurrentQueue<Inbound>();
private const byte KindJoin = 1;
private const byte KindLeave = 2;
private const byte KindSnapshot = 3;
private const BindingFlags Any = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;
private static readonly string[] SafeAvatars = new string[19]
{
"fa534c5a83ee4ec6bd641fec424c4142.Avatar.Heavy", "fa534c5a83ee4ec6bd641fec424c4142.Avatar.Fast", "fa534c5a83ee4ec6bd641fec424c4142.Avatar.CharFurv4GB", "fa534c5a83ee4ec6bd641fec424c4142.Avatar.CharTallv4", "fa534c5a83ee4ec6bd641fec424c4142.Avatar.Strong", "SLZ.BONELAB.Content.Avatar.Anime", "SLZ.BONELAB.Content.Avatar.CharJimmy", "SLZ.BONELAB.Content.Avatar.FordBW", "SLZ.BONELAB.Content.Avatar.CharFord", "SLZ.BONELAB.Core.Avatar.PeasantFemaleA",
"c3534c5a-10bf-48e9-beca-4ca850656173", "c3534c5a-2236-4ce5-9385-34a850656173", "c3534c5a-87a3-48b2-87cd-f0a850656173", "c3534c5a-f12c-44ef-b953-b8a850656173", "c3534c5a-3763-4ddf-bd86-6ca850656173", "SLZ.BONELAB.Content.Avatar.Nullbody", "fa534c5a83ee4ec6bd641fec424c4142.Avatar.Charskeleton", "c3534c5a-d388-4945-b4ff-9c7a53656375", "SLZ.BONELAB.Content.Avatar.DogDuckSeason"
};
private static readonly string[] Names = new string[111]
{
"Timmy", "Jake", "Mason", "Liam", "Noah", "Ethan", "Logan", "Lucas", "Owen", "Caleb",
"Dylan", "Tyler", "Ryan", "Aiden", "Jayden", "Carter", "Wyatt", "Hunter", "Eli", "Landon",
"Connor", "Colton", "Brody", "Gavin", "Austin", "Chase", "Cole", "Nolan", "Jace", "Parker",
"Blake", "Declan", "Finn", "Grant", "Henry", "Isaac", "Jasper", "Kai", "Leo", "Miles",
"Nate", "Oscar", "Pete", "Quinn", "Rex", "Sam", "Theo", "Vince", "Zane", "Max",
"Emma", "Olivia", "Ava", "Mia", "Chloe", "Ella", "Lily", "Zoe", "Nora", "Hazel",
"Piper", "Ruby", "Stella", "Violet", "Willow", "Ivy", "Maya", "Layla", "Aria", "Brooke",
"Cara", "Dana", "Erin", "Faith", "Grace", "Hannah", "Iris", "Jade", "Kylie", "Lena",
"Molly", "Nina", "Paige", "Riley", "Sadie", "Tessa", "Uma", "Vera", "Wendy", "Alex",
"Casey", "Drew", "Jamie", "Jordan", "Morgan", "Sky", "Taylor", "Avery", "Bobby", "Danny",
"Eddie", "Freddie", "Georgie", "Jimmy", "Kenny", "Larry", "Mikey", "Ricky", "Tommy", "Willy",
"Zach"
};
public override void OnInitializeMelon()
{
MelonPreferences_Category val = MelonPreferences.CreateCategory("DoppelNames");
prefEnabled = val.CreateEntry<bool>("Enabled", true, "Turn the whole add-on on or off", (string)null, false, false, (ValueValidator)null, (string)null);
prefTags = val.CreateEntry<bool>("NameTags", true, "Show name tags above bots (needs LabFusion)", (string)null, false, false, (ValueValidator)null, (string)null);
prefPopups = val.CreateEntry<bool>("JoinLeavePopups", true, "Show 'has joined/left the server' pop-ups", (string)null, false, false, (ValueValidator)null, (string)null);
prefServer = val.CreateEntry<bool>("ServerSideBots", true, "When you are the host: make bots show up for players who do NOT have this mod (experimental)", (string)null, false, false, (ValueValidator)null, (string)null);
prefCustom = val.CreateEntry<string>("CustomNames", "", "Your own names for bots, separated by commas (e.g. Sam, Alex, Jordan). New bots use these first, then the random list.", (string)null, false, false, (ValueValidator)null, (string)null);
prefLobby = val.CreateEntry<bool>("UseLobbyUsernames", false, "Name new bots after players in your Fusion lobby (their real usernames) before using other names", (string)null, false, false, (ValueValidator)null, (string)null);
prefTarget = val.CreateEntry<string>("CopyPlayer", "", "Type part of a player's username: every new bot gets that player's exact username (they must be in your lobby). Leave empty to turn off.", (string)null, false, false, (ValueValidator)null, (string)null);
prefRandomAvatar = val.CreateEntry<bool>("RandomAvatar", false, "Every new bot wears a random BONELAB avatar instead of yours", (string)null, false, false, (ValueValidator)null, (string)null);
prefCopyAvatar = val.CreateEntry<string>("CopyAvatarFrom", "", "Type part of a player's username: every new bot wears the avatar that player is wearing (they must be in your lobby). Overrides RandomAvatar. Leave empty to turn off.", (string)null, false, false, (ValueValidator)null, (string)null);
prefFixed = val.CreateEntry<string>("FixedName", "", "If set, EVERY new bot gets exactly this name (overrides everything else). Also settable in BoneMenu > DoppelNames.", (string)null, false, false, (ValueValidator)null, (string)null);
((MelonBase)this).LoggerInstance.Msg("DoppelNames loaded. Waiting for Doppel.");
}
public override void OnLateInitializeMelon()
{
try
{
BuildMenu();
}
catch (Exception ex)
{
((MelonBase)this).LoggerInstance.Warning("Could not build the BoneMenu page (settings still work in MelonPreferences.cfg): " + ex.Message);
}
fusionPresent = AppDomain.CurrentDomain.GetAssemblies().Any((Assembly a) => a.GetName().Name == "LabFusion");
if (!fusionPresent)
{
((MelonBase)this).LoggerInstance.Warning("LabFusion is not installed: pop-ups will work, but name tags need LabFusion.");
return;
}
try
{
fusionReady = FusionBridge.Start();
}
catch (Exception ex2)
{
((MelonBase)this).LoggerInstance.Warning("Could not register the network message; names stay on your screen only. " + ex2.Message);
}
}
public override void OnDeinitializeMelon()
{
foreach (Entry value in tracked.Values)
{
DestroyTag(value);
DropFake(value, quiet: false);
}
tracked.Clear();
HiddenBots.Clear();
if (fusionReady)
{
try
{
FusionBridge.Stop();
}
catch
{
}
}
}
public override void OnSceneWasUnloaded(int buildIndex, string sceneName)
{
quietUntil = Time.unscaledTime + 4f;
}
public override void OnUpdate()
{
if (!prefEnabled.Value)
{
if (tracked.Count <= 0)
{
return;
}
foreach (Entry value in tracked.Values)
{
DestroyTag(value);
DropFake(value, quiet: false);
}
tracked.Clear();
return;
}
float unscaledTime = Time.unscaledTime;
if (!doppelReady)
{
if (!(unscaledTime < nextResolve))
{
nextResolve = unscaledTime + 2f;
doppelReady = ResolveDoppel();
}
return;
}
try
{
if (fusionReady)
{
DrainInbox();
}
if (unscaledTime >= nextPoll)
{
nextPoll = unscaledTime + 0.2f;
Poll(unscaledTime);
}
if (fusionReady && unscaledTime >= nextSnapshot)
{
nextSnapshot = unscaledTime + 4f;
if (!ServerSideActive())
{
SendSnapshot();
}
}
if (unscaledTime >= nextFakeTick)
{
nextFakeTick = unscaledTime + 0.05f;
TickFakes();
}
}
catch (Exception ex)
{
if (!loggedError)
{
loggedError = true;
((MelonBase)this).LoggerInstance.Error("DoppelNames hit an error (logged once): " + ex);
}
}
}
public override void OnLateUpdate()
{
if (!doppelReady || !fusionPresent || rigF == null || !prefEnabled.Value || !prefTags.Value)
{
return;
}
foreach (Entry value in tracked.Values)
{
if (value.TagHandle != null)
{
try
{
FusionTags.Follow(value.TagHandle, rigF.GetValue(value.Bot));
}
catch
{
}
}
}
}
private static Type FindType(string full)
{
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (Assembly assembly in assemblies)
{
try
{
Type type = assembly.GetType(full, throwOnError: false);
if (type != null)
{
return type;
}
}
catch
{
}
}
return null;
}
private bool ResolveDoppel()
{
Type type = FindType("ThirdEyeBonelabMod.Bots.BotManager");
Type type2 = FindType("ThirdEyeBonelabMod.Bots.Bot");
if (type == null || type2 == null)
{
return false;
}
botsF = type.GetField("bots", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
nameIsF = type2.GetField("nameIs", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
netOwnerF = type2.GetField("netOwner", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
netIdF = type2.GetField("netId", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
rigF = type2.GetField("rig", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
if (botsF == null || netOwnerF == null || netIdF == null)
{
((MelonBase)this).LoggerInstance.Error("Found Doppel, but it looks like a different version than 1.4.0 (a field this add-on needs is missing). Names are disabled.");
nextResolve = float.MaxValue;
return false;
}
Type type3 = FindType("ThirdEyeBonelabMod.Bots.BotRig");
if (type3 != null)
{
MethodInfo[] methods = type3.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
foreach (MethodInfo methodInfo in methods)
{
if (methodInfo.Name == "UseAvatar" && methodInfo.IsStatic && methodInfo.GetParameters().Length == 4)
{
useAvatarM = methodInfo;
break;
}
}
}
if (useAvatarM == null)
{
((MelonBase)this).LoggerInstance.Warning("Could not find Doppel's avatar function: the Random Avatar option is unavailable.");
}
TryPatchDoppel();
((MelonBase)this).LoggerInstance.Msg("Hooked into Doppel.");
return true;
}
private void TryPatchDoppel()
{
//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
//IL_00ac: Expected O, but got Unknown
try
{
Type type = FindType("ThirdEyeBonelabMod.Bots.NetBots");
MethodInfo methodInfo = null;
if (type != null)
{
MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
foreach (MethodInfo methodInfo2 in methods)
{
if (methodInfo2.Name == "Update" && methodInfo2.IsStatic && methodInfo2.GetParameters().Length == 1)
{
methodInfo = methodInfo2;
break;
}
}
}
if (methodInfo == null)
{
throw new MissingMethodException("NetBots.Update");
}
HarmonyMethod val = new HarmonyMethod(typeof(DoppelHook).GetMethod("Prefix"));
((MelonBase)this).HarmonyInstance.Patch((MethodBase)methodInfo, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
}
catch (Exception ex)
{
((MelonBase)this).LoggerInstance.Warning("Could not stop Doppel's own sharing of server-side bots; players who also have Doppel may see them twice: " + ex.Message);
}
}
private void Poll(float now)
{
if (!(botsF.GetValue(null) is IDictionary dictionary))
{
return;
}
List<KeyValuePair<IntPtr, object>> list = new List<KeyValuePair<IntPtr, object>>(dictionary.Count);
foreach (DictionaryEntry item in dictionary)
{
list.Add(new KeyValuePair<IntPtr, object>((IntPtr)item.Key, item.Value));
}
HashSet<IntPtr> hashSet = new HashSet<IntPtr>(list.Select((KeyValuePair<IntPtr, object> k) => k.Key));
bool flag = now < quietUntil;
foreach (IntPtr item2 in tracked.Keys.ToList())
{
if (hashSet.Contains(item2))
{
continue;
}
Entry entry = tracked[item2];
tracked.Remove(item2);
DestroyTag(entry);
DropFake(entry, quiet: false);
HiddenBots.Remove(entry.Bot);
if (!(entry.Puppet || flag) && !string.IsNullOrEmpty(entry.Name))
{
Popup(entry.Name + " has left the server");
if (fusionReady && entry.NetAnnounced && entry.Id > 0 && !ServerSideActive())
{
FusionSend(Pack(2, entry.Id, entry.Name));
}
}
}
foreach (KeyValuePair<IntPtr, object> item3 in list)
{
if (!tracked.TryGetValue(item3.Key, out var value))
{
value = new Entry
{
Bot = item3.Value,
Born = now,
Puppet = IsPuppet(item3.Value)
};
tracked[item3.Key] = value;
if (!value.Puppet)
{
value.Name = PickName();
nameIsF?.SetValue(value.Bot, value.Name);
ChooseAvatar(value);
if (ServerSideActive())
{
HiddenBots.Add(value.Bot);
}
if (!flag)
{
Popup(value.Name + " has joined the server");
}
}
}
if (value.Puppet)
{
ResolvePuppetName(value);
}
else
{
EnsureAvatar(value, now);
EnsureFake(value, now);
if (!value.NetAnnounced && !ServerSideActive())
{
TryAnnounceOverNetwork(value, now, flag);
}
}
EnsureTag(value);
}
}
private void ChooseAvatar(Entry e)
{
//IL_0061: Unknown result type (might be due to invalid IL or missing references)
//IL_0063: Unknown result type (might be due to invalid IL or missing references)
if (useAvatarM == null)
{
return;
}
string text = (prefCopyAvatar.Value ?? "").Trim();
if (text.Length > 0 && fusionPresent)
{
try
{
if (FusionFakes.TryGetAvatar(text, out var barcode, out var scale))
{
e.AvatarWanted = barcode;
e.AvatarScale = scale;
return;
}
((MelonBase)this).LoggerInstance.Warning("Could not find the avatar of a player matching '" + text + "' (are they in your lobby?)");
}
catch (Exception ex)
{
((MelonBase)this).LoggerInstance.Warning("Could not read that player's avatar: " + ex.Message);
}
}
if (prefRandomAvatar.Value)
{
e.AvatarWanted = SafeAvatars[rng.Next(SafeAvatars.Length)];
}
}
private void EnsureAvatar(Entry e, float now)
{
//IL_0118: Unknown result type (might be due to invalid IL or missing references)
if (e.AvatarWanted == null || e.AvatarDone || e.Puppet)
{
return;
}
if (e.AvatarAsked)
{
if (now - e.AvatarAskedAt > 8f)
{
e.AvatarDone = true;
}
}
else
{
if (now - e.Born < 2.5f)
{
return;
}
object obj = rigF?.GetValue(e.Bot);
Object val = (Object)((obj is Object) ? obj : null);
if (obj == null || val == (Object)null)
{
return;
}
e.AvatarAsked = true;
e.AvatarAskedAt = now;
try
{
useAvatarM.Invoke(null, new object[4]
{
obj,
e.AvatarWanted,
e.AvatarScale,
(Action)delegate
{
e.AvatarDone = true;
}
});
((MelonBase)this).LoggerInstance.Msg("Avatar for '" + e.Name + "': " + e.AvatarWanted);
}
catch (Exception ex)
{
e.AvatarDone = true;
((MelonBase)this).LoggerInstance.Warning("Could not give '" + e.Name + "' a random avatar: " + (ex.InnerException ?? ex).Message);
e.AvatarWanted = null;
}
}
}
private bool ServerSideActive()
{
if (!fusionPresent || !prefServer.Value)
{
return false;
}
try
{
return FusionFakes.IsHosting();
}
catch
{
return false;
}
}
private void EnsureFake(Entry e, float now)
{
if (e.Fake == null && !e.Puppet && !e.HiddenWarned && now - e.Born > 15f && HiddenBots.Contains(e.Bot))
{
e.HiddenWarned = true;
HiddenBots.Remove(e.Bot);
((MelonBase)this).LoggerInstance.Warning("Server-side bot '" + e.Name + "' could not be created; letting Doppel share it the normal way.");
}
if (e.Fake != null || e.Puppet || string.IsNullOrEmpty(e.Name) || now < e.FakeNextTry || (e.AvatarWanted != null && !e.AvatarDone) || !ServerSideActive())
{
return;
}
e.FakeNextTry = now + 0.5f;
try
{
e.Fake = FusionFakes.Add(e.Name, rigF?.GetValue(e.Bot), e.AvatarWanted);
if (e.Fake == null && !e.FakeWarned && now - e.Born > 5f)
{
e.FakeWarned = true;
((MelonBase)this).LoggerInstance.Warning("Server-side bot '" + e.Name + "' still not created: " + FusionFakes.LastReason);
}
}
catch (Exception ex)
{
e.FakeNextTry = now + 5f;
((MelonBase)this).LoggerInstance.Warning("Could not make a server-side bot: " + ex.Message);
}
}
private void DropFake(Entry e, bool quiet)
{
if (e.Fake == null)
{
return;
}
object fake = e.Fake;
e.Fake = null;
HiddenBots.Remove(e.Bot);
try
{
if (quiet || !FusionFakes.IsHosting())
{
FusionFakes.Forget(fake);
}
else
{
FusionFakes.Remove(fake);
}
}
catch (Exception ex)
{
((MelonBase)this).LoggerInstance.Warning("Could not remove a server-side bot: " + ex.Message);
}
}
private void TickFakes()
{
if (!fusionPresent)
{
return;
}
bool flag = ServerSideActive();
foreach (Entry value in tracked.Values)
{
if (value.Fake == null)
{
continue;
}
if (!flag)
{
DropFake(value, quiet: false);
continue;
}
try
{
FusionFakes.Tick(value.Fake, rigF?.GetValue(value.Bot));
}
catch
{
}
}
}
private bool IsPuppet(object bot)
{
return Convert.ToInt32(netOwnerF.GetValue(bot)) >= 0;
}
private void ResolvePuppetName(Entry e)
{
if (string.IsNullOrEmpty(e.Name))
{
int num = Convert.ToInt32(netOwnerF.GetValue(e.Bot));
int num2 = Convert.ToInt32(netIdF.GetValue(e.Bot));
if (remoteNames.TryGetValue((num << 8) | (num2 & 0xFF), out var value))
{
e.Name = value;
nameIsF?.SetValue(e.Bot, value);
}
}
}
private void TryAnnounceOverNetwork(Entry e, float now, bool quiet)
{
if (!fusionReady)
{
e.NetAnnounced = true;
}
else
{
if (!FusionBridge.IsOnline())
{
return;
}
int num = Convert.ToInt32(netIdF.GetValue(e.Bot));
if (num <= 0 || num > 255)
{
if (now - e.Born > 8f)
{
e.NetAnnounced = true;
}
return;
}
e.Id = num;
e.NetAnnounced = true;
if (!quiet)
{
FusionSend(Pack(1, num, e.Name));
}
}
}
private void SendSnapshot()
{
if (!FusionBridge.IsOnline())
{
return;
}
List<KeyValuePair<int, string>> list = new List<KeyValuePair<int, string>>();
foreach (Entry value in tracked.Values)
{
if (!value.Puppet && !string.IsNullOrEmpty(value.Name))
{
int num = Convert.ToInt32(netIdF.GetValue(value.Bot));
if (num > 0 && num <= 255)
{
value.Id = num;
list.Add(new KeyValuePair<int, string>(num, value.Name));
}
}
}
if (list.Count != 0)
{
FusionSend(PackSnapshot(list));
}
}
private static void FusionSend(byte[] payload)
{
try
{
FusionBridge.Send(payload);
}
catch
{
}
}
private void DrainInbox()
{
Inbound result;
while (Inbox.TryDequeue(out result))
{
try
{
Handle(result.Owner, result.Bytes);
}
catch (Exception ex)
{
((MelonBase)this).LoggerInstance.Warning("Bad message: " + ex.Message);
}
}
}
private void Handle(int owner, byte[] bytes)
{
using BinaryReader binaryReader = new BinaryReader(new MemoryStream(bytes));
byte b = binaryReader.ReadByte();
int num = binaryReader.ReadByte();
if (b == 3)
{
HashSet<int> keep = new HashSet<int>();
for (int i = 0; i < num; i++)
{
int num2 = binaryReader.ReadByte();
string text = Clean(binaryReader.ReadString());
if (text.Length != 0)
{
int num3 = (owner << 8) | num2;
keep.Add(num3);
remoteNames[num3] = text;
}
}
{
foreach (int item in remoteNames.Keys.Where((int k) => k >> 8 == owner && !keep.Contains(k)).ToList())
{
remoteNames.Remove(item);
}
return;
}
}
if (num < 1)
{
return;
}
int num4 = binaryReader.ReadByte();
string text2 = Clean(binaryReader.ReadString());
if (text2.Length == 0)
{
return;
}
switch (b)
{
case 1:
remoteNames[(owner << 8) | num4] = text2;
if (Time.unscaledTime >= quietUntil)
{
Popup(text2 + " has joined the server");
}
break;
case 2:
remoteNames.Remove((owner << 8) | num4);
if (Time.unscaledTime >= quietUntil)
{
Popup(text2 + " has left the server");
}
break;
}
}
private static byte[] Pack(byte kind, int id, string name)
{
using MemoryStream memoryStream = new MemoryStream();
using BinaryWriter binaryWriter = new BinaryWriter(memoryStream);
binaryWriter.Write(kind);
binaryWriter.Write((byte)1);
binaryWriter.Write((byte)id);
binaryWriter.Write(name);
binaryWriter.Flush();
return memoryStream.ToArray();
}
private static byte[] PackSnapshot(List<KeyValuePair<int, string>> items)
{
using MemoryStream memoryStream = new MemoryStream();
using BinaryWriter binaryWriter = new BinaryWriter(memoryStream);
binaryWriter.Write((byte)3);
binaryWriter.Write((byte)Math.Min(items.Count, 255));
foreach (KeyValuePair<int, string> item in items.Take(255))
{
binaryWriter.Write((byte)item.Key);
binaryWriter.Write(item.Value);
}
binaryWriter.Flush();
return memoryStream.ToArray();
}
private static string Clean(string s)
{
if (string.IsNullOrEmpty(s))
{
return "";
}
StringBuilder stringBuilder = new StringBuilder();
foreach (char c in s)
{
if (char.IsLetterOrDigit(c) || c == ' ' || c == '_' || c == '-' || c == '.')
{
stringBuilder.Append(c);
}
if (stringBuilder.Length >= 20)
{
break;
}
}
return stringBuilder.ToString().Trim();
}
private void Popup(string message)
{
//IL_0024: Unknown result type (might be due to invalid IL or missing references)
//IL_0029: Unknown result type (might be due to invalid IL or missing references)
//IL_002f: Unknown result type (might be due to invalid IL or missing references)
//IL_0034: Unknown result type (might be due to invalid IL or missing references)
//IL_0039: Unknown result type (might be due to invalid IL or missing references)
//IL_003b: Unknown result type (might be due to invalid IL or missing references)
//IL_0040: Unknown result type (might be due to invalid IL or missing references)
//IL_0045: Unknown result type (might be due to invalid IL or missing references)
//IL_0047: Unknown result type (might be due to invalid IL or missing references)
//IL_004c: Unknown result type (might be due to invalid IL or missing references)
//IL_0057: Unknown result type (might be due to invalid IL or missing references)
//IL_0063: Expected O, but got Unknown
((MelonBase)this).LoggerInstance.Msg(message);
if (!prefPopups.Value)
{
return;
}
try
{
Notifier.Send(new Notification
{
Title = NotificationText.op_Implicit("Server"),
Message = NotificationText.op_Implicit(message),
Type = (NotificationType)0,
PopupLength = 2.5f,
ShowTitleOnPopup = true
});
}
catch
{
}
}
private void EnsureTag(Entry e)
{
if (!fusionPresent)
{
return;
}
if (!prefTags.Value || string.IsNullOrEmpty(e.Name))
{
if (e.TagHandle != null)
{
DestroyTag(e);
}
return;
}
try
{
if (e.TagHandle == null)
{
e.TagHandle = FusionTags.Create(e.Name);
}
else
{
FusionTags.SetName(e.TagHandle, e.Name);
}
}
catch (Exception ex)
{
if (!loggedError)
{
loggedError = true;
((MelonBase)this).LoggerInstance.Error("Could not make a name tag (logged once): " + ex);
}
}
}
private void DestroyTag(Entry e)
{
if (e.TagHandle != null)
{
try
{
FusionTags.Destroy(e.TagHandle);
}
catch
{
}
e.TagHandle = null;
}
}
private void BuildMenu()
{
//IL_000b: Unknown result type (might be due to invalid IL or missing references)
//IL_001e: Unknown result type (might be due to invalid IL or missing references)
//IL_0046: Unknown result type (might be due to invalid IL or missing references)
//IL_006e: Unknown result type (might be due to invalid IL or missing references)
//IL_0096: Unknown result type (might be due to invalid IL or missing references)
//IL_00be: Unknown result type (might be due to invalid IL or missing references)
//IL_0100: Unknown result type (might be due to invalid IL or missing references)
//IL_011f: Unknown result type (might be due to invalid IL or missing references)
//IL_013d: Unknown result type (might be due to invalid IL or missing references)
Page val = Page.Root.CreatePage("DoppelNames", Color.cyan, 0, true);
val.CreateBool("Name tags", Color.white, prefTags.Value, (Action<bool>)delegate(bool v)
{
prefTags.Value = v;
Save();
});
val.CreateBool("Join/leave pop-ups", Color.white, prefPopups.Value, (Action<bool>)delegate(bool v)
{
prefPopups.Value = v;
Save();
});
val.CreateBool("Random avatar for new bots", Color.white, prefRandomAvatar.Value, (Action<bool>)delegate(bool v)
{
prefRandomAvatar.Value = v;
Save();
});
val.CreateBool("Show bots to players without the mod (host)", Color.white, prefServer.Value, (Action<bool>)delegate(bool v)
{
prefServer.Value = v;
Save();
});
val.CreateString("Type a bot name", Color.white, string.IsNullOrEmpty(prefFixed.Value) ? "None" : prefFixed.Value, (Action<string>)delegate(string v)
{
string text = Clean(v);
prefFixed.Value = ((text.Length == 0 || text.Equals("None", StringComparison.OrdinalIgnoreCase)) ? "" : text);
if (prefFixed.Value.Length > 0)
{
prefTarget.Value = "";
}
Save();
Popup((prefFixed.Value.Length > 0) ? ("New bots will be named " + prefFixed.Value) : "Bots use random names again");
});
playerPage = val.CreatePage("Copy a player's name", Color.green, 0, true);
RefreshPlayerPage();
avatarPage = val.CreatePage("Copy a player's avatar", Color.green, 0, true);
RefreshAvatarPage();
val.CreateFunction("Back to random names", Color.yellow, (Action)delegate
{
prefFixed.Value = "";
prefTarget.Value = "";
prefCopyAvatar.Value = "";
Save();
Popup("Bots use random names and their normal avatar again");
});
}
private void RefreshPlayerPage()
{
//IL_0018: Unknown result type (might be due to invalid IL or missing references)
//IL_006b: Unknown result type (might be due to invalid IL or missing references)
//IL_00ef: Unknown result type (might be due to invalid IL or missing references)
playerPage.RemoveAll();
playerPage.CreateFunction("Refresh player list", Color.yellow, (Action)RefreshPlayerPage);
List<string> list = null;
if (fusionPresent)
{
try
{
list = FusionFakes.LobbyNames();
}
catch
{
}
}
if (list == null || list.Count == 0)
{
playerPage.CreateFunction("(no players found - join a lobby, then refresh)", Color.gray, (Action)delegate
{
});
return;
}
foreach (string item in list)
{
string name = Clean(item);
if (name.Length != 0)
{
playerPage.CreateFunction(name, Color.white, (Action)delegate
{
prefFixed.Value = "";
prefTarget.Value = name;
Save();
Popup("New bots will be named " + name);
});
}
}
}
private void RefreshAvatarPage()
{
//IL_0018: Unknown result type (might be due to invalid IL or missing references)
//IL_003a: Unknown result type (might be due to invalid IL or missing references)
//IL_008d: Unknown result type (might be due to invalid IL or missing references)
//IL_0111: Unknown result type (might be due to invalid IL or missing references)
avatarPage.RemoveAll();
avatarPage.CreateFunction("Refresh player list", Color.yellow, (Action)RefreshAvatarPage);
avatarPage.CreateFunction("Stop copying avatars", Color.yellow, (Action)delegate
{
prefCopyAvatar.Value = "";
Save();
Popup("New bots keep their normal avatar" + (prefRandomAvatar.Value ? " (random)" : ""));
});
List<string> list = null;
if (fusionPresent)
{
try
{
list = FusionFakes.LobbyNames();
}
catch
{
}
}
if (list == null || list.Count == 0)
{
avatarPage.CreateFunction("(no players found - join a lobby, then refresh)", Color.gray, (Action)delegate
{
});
return;
}
foreach (string item in list)
{
string name = Clean(item);
if (name.Length != 0)
{
avatarPage.CreateFunction(name, Color.white, (Action)delegate
{
prefCopyAvatar.Value = name;
Save();
Popup("New bots will wear " + name + "'s avatar");
});
}
}
}
private static void Save()
{
try
{
MelonPreferences.Save();
}
catch
{
}
}
private string PickName()
{
HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (Entry value in tracked.Values)
{
if (!string.IsNullOrEmpty(value.Name))
{
hashSet.Add(value.Name);
}
}
foreach (string value2 in remoteNames.Values)
{
hashSet.Add(value2);
}
string text = Clean(prefFixed.Value);
if (text.Length > 0)
{
return text;
}
string text2 = (prefTarget.Value ?? "").Trim();
if (text2.Length > 0 && fusionPresent)
{
try
{
foreach (string item in FusionFakes.LobbyNames())
{
string text3 = Clean(item);
if (text3.Length > 0 && text3.IndexOf(text2, StringComparison.OrdinalIgnoreCase) >= 0)
{
return text3;
}
}
}
catch
{
}
}
if (prefLobby.Value && fusionPresent)
{
try
{
foreach (string item2 in FusionFakes.LobbyNames())
{
string text4 = Clean(item2);
if (text4.Length > 0 && !hashSet.Contains(text4))
{
return text4;
}
}
}
catch
{
}
}
string[] array = (prefCustom.Value ?? "").Split(',');
foreach (string s in array)
{
string text5 = Clean(s);
if (text5.Length > 0 && !hashSet.Contains(text5))
{
return text5;
}
}
for (int j = 0; j < 40; j++)
{
string text6 = Names[rng.Next(Names.Length)];
if (!hashSet.Contains(text6))
{
return text6;
}
}
return Names[rng.Next(Names.Length)] + rng.Next(2, 99);
}
}
public class NamesData : INetSerializable
{
public byte[] bytes;
public int? GetSize()
{
return ((bytes != null) ? bytes.Length : 0) + 8;
}
public void Serialize(INetSerializer serializer)
{
serializer.SerializeValue(ref bytes);
}
}
public class NamesMessage : ModuleMessageHandler
{
protected override void OnHandleMessage(ReceivedMessage received)
{
try
{
byte? sender = ((ReceivedMessage)(ref received)).Sender;
if (!sender.HasValue)
{
return;
}
object obj = sender.Value;
int owner;
if (obj is IConvertible convertible)
{
owner = convertible.ToInt32(null);
}
else
{
object obj2 = obj.GetType().GetProperty("SmallID")?.GetValue(obj);
if (obj2 == null)
{
return;
}
owner = Convert.ToInt32(obj2);
}
NamesData namesData = ((ReceivedMessage)(ref received)).ReadData<NamesData>();
if (namesData?.bytes != null && namesData.bytes.Length >= 2 && namesData.bytes.Length <= 8192)
{
Mod.Inbox.Enqueue(new Mod.Inbound
{
Owner = owner,
Bytes = namesData.bytes
});
}
}
catch (Exception ex)
{
MelonLogger.Warning("DoppelNames: could not read a message: " + ex.Message);
}
}
}
internal static class FusionBridge
{
private const long Tag = 7391645520138472951L;
[MethodImpl(MethodImplOptions.NoInlining)]
public static bool Start()
{
if (ModuleMessageManager.TagToHandlerLookup.TryGetValue(7391645520138472951L, out var value))
{
ModuleMessageManager.TagToHandlerLookup.Remove(7391645520138472951L);
ModuleMessageManager.Handlers.Remove(value);
ModuleMessageManager.TypeToHandlerLookup.Remove(((object)value).GetType());
}
NamesMessage namesMessage = new NamesMessage();
FieldInfo field = typeof(ModuleMessageHandler).GetField("_tag", BindingFlags.Instance | BindingFlags.NonPublic);
if (field == null)
{
throw new Exception("this Fusion keeps its handlers' numbers some other way");
}
field.SetValue(namesMessage, 7391645520138472951L);
PropertyInfo property = typeof(MessageHandler).GetProperty("NetAttributes", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (property != null)
{
property.SetValue(namesMessage, Array.CreateInstance(property.PropertyType.GetElementType(), 0));
}
ModuleMessageManager.Handlers.Add((ModuleMessageHandler)(object)namesMessage);
ModuleMessageManager.TypeToHandlerLookup[typeof(NamesMessage)] = (ModuleMessageHandler)(object)namesMessage;
ModuleMessageManager.TagToHandlerLookup[7391645520138472951L] = (ModuleMessageHandler)(object)namesMessage;
return true;
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static void Stop()
{
if (ModuleMessageManager.TagToHandlerLookup.TryGetValue(7391645520138472951L, out var value))
{
ModuleMessageManager.TagToHandlerLookup.Remove(7391645520138472951L);
ModuleMessageManager.Handlers.Remove(value);
ModuleMessageManager.TypeToHandlerLookup.Remove(((object)value).GetType());
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static bool IsOnline()
{
try
{
return NetworkInfo.HasServer && PlayerIDManager.HasOtherPlayers;
}
catch
{
return false;
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static void Send(byte[] payload)
{
//IL_000f: Unknown result type (might be due to invalid IL or missing references)
NamesData namesData = new NamesData
{
bytes = payload
};
MessageRelay.RelayModule<NamesMessage, NamesData>(namesData, CommonMessageRoutes.ReliableToOtherClients);
}
}
internal static class FusionTags
{
private sealed class Handle
{
public RigHeadUI HeadUI;
public RigNameTag Tag;
public int Failures;
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static object Create(string name)
{
//IL_0001: Unknown result type (might be due to invalid IL or missing references)
//IL_0006: Unknown result type (might be due to invalid IL or missing references)
//IL_000f: Expected O, but got Unknown
//IL_000f: Unknown result type (might be due to invalid IL or missing references)
//IL_0015: Expected O, but got Unknown
RigNameTag val = new RigNameTag
{
Username = name
};
RigHeadUI val2 = new RigHeadUI();
val2.RegisterElement((IPopupLayoutElement)(object)val);
val2.Spawn();
return new Handle
{
HeadUI = val2,
Tag = val
};
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static void SetName(object handle, string name)
{
Handle handle2 = (Handle)handle;
if (handle2.Tag.Username != name)
{
handle2.Tag.Username = name;
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static void Follow(object handle, object rig)
{
Handle handle2 = (Handle)handle;
if (handle2.Failures > 30)
{
return;
}
RigManager val = (RigManager)((rig is RigManager) ? rig : null);
if ((Object)(object)val == (Object)null)
{
return;
}
try
{
handle2.HeadUI.UpdateScale(val);
handle2.HeadUI.UpdateTransform(val);
}
catch (Exception ex)
{
if (++handle2.Failures == 31)
{
MelonLogger.Warning("DoppelNames: a name tag stopped following its bot: " + ex.Message);
}
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static void Destroy(object handle)
{
Handle handle2 = (Handle)handle;
handle2.HeadUI.Despawn();
}
}
}