using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Text.Json; using System.Text.RegularExpressions; using BepInEx; using BepInEx.Configuration; using BepInEx.Core.Logging.Interpolation; using BepInEx.Logging; using BepInEx.Unity.IL2CPP; using BepInEx.Unity.IL2CPP.Utils.Collections; using Faust.Config; using Faust.Services; using HarmonyLib; using Il2CppInterop.Runtime; using Il2CppInterop.Runtime.InteropTypes.Arrays; using Il2CppSystem.Collections.Generic; using Microsoft.CodeAnalysis; using ProjectM; using ProjectM.CastleBuilding; using ProjectM.Gameplay.Systems; using ProjectM.Network; using ProjectM.Physics; using ProjectM.Scripting; using ProjectM.Terrain; using Stunlock.Core; using Stunlock.Network; using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; using Unity.Entities; using Unity.Mathematics; using Unity.Physics; using Unity.Transforms; using UnityEngine; using VampireCommandFramework; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: AssemblyCompany("kdpen")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyCopyright("Copyright © 2026 Kristopher Penland")] [assembly: AssemblyDescription("Faust, Lord of Investigation — a server-side information layer for V Rising: the authoritative, global view of players, castles, plots, server entities (V Bloods, NPCs, resource nodes), and server activity. An admin moderation/oversight console you can also share as PvP intel or PvE community features, gated per-feature with an optional item cost (the Faustian toll), cooldown, usage limit, or unlock. Built to run with its companion client Raphael, Lord of Wisdom. Pre-1.0 testing. Server-side only.")] [assembly: AssemblyFileVersion("0.16.5.0")] [assembly: AssemblyInformationalVersion("0.16.5+be491f2809922d6fe4ce5b3d97a549515a9aab43")] [assembly: AssemblyProduct("Faust")] [assembly: AssemblyTitle("Faust")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.16.5.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] internal sealed class IsUnmanagedAttribute : Attribute { } [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 Faust { internal static class Core { private static bool _initInProgress; private static int _initAttempts; private static MonoBehaviour _monoBehaviour; public static World Server { get; private set; } public static EntityManager EntityManager { get; private set; } public static PrefabCollectionSystem PrefabCollectionSystem { get; private set; } public static ServerScriptMapper ServerScriptMapper { get; private set; } public static ServerGameSettingsSystem ServerGameSettingsSystem { get; private set; } public static ServerGameManager ServerGameManager => ServerScriptMapper.GetServerGameManager(); public static double ServerTime => ServerGameManager.ServerTime; public static CastleService Castle { get; private set; } public static PlayerInfoService PlayerInfo { get; private set; } public static ClanService Clan { get; private set; } public static BossService Boss { get; private set; } public static WorldScanService WorldScan { get; private set; } public static MapMarkerService MapMarkers { get; private set; } public static FaustStore Store { get; private set; } public static HeatmapStore Heatmap { get; private set; } public static UsageService Usage { get; private set; } public static UsageStatsService UsageStats { get; private set; } public static FeatureControlService Control { get; private set; } public static UnlockService Unlock { get; private set; } public static KillTrackingService Kills { get; private set; } public static ManualLogSource Log => Plugin.PluginLog; public static bool IsReady { get; private set; } private static MonoBehaviour MonoBehaviour { get { //IL_0012: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_monoBehaviour == (Object)null) { _monoBehaviour = (MonoBehaviour)(object)new GameObject("Faust").AddComponent(); Object.DontDestroyOnLoad((Object)(object)((Component)_monoBehaviour).gameObject); } return _monoBehaviour; } } internal static void InitPersistence() { Store = new FaustStore(); Store.Load(); Heatmap = new HeatmapStore(); Heatmap.Load(); Usage = new UsageService(); Usage.Load(); UsageStats = new UsageStatsService(); UsageStats.Load(); Control = new FeatureControlService(); Control.Load(); Unlock = new UnlockService(); Unlock.Load(); Kills = new KillTrackingService(); Kills.Load(); } internal static void TryInitialize(string trigger) { //IL_01c6: Unknown result type (might be due to invalid IL or missing references) //IL_01cd: Expected O, but got Unknown //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Expected O, but got Unknown //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Expected O, but got Unknown //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_014a: Unknown result type (might be due to invalid IL or missing references) //IL_0150: Expected O, but got Unknown if (IsReady || _initInProgress) { return; } _initInProgress = true; _initAttempts++; bool flag = default(bool); try { World val = FindServerWorld(); BepInExInfoLogInterpolatedStringHandler val2; if (val == null) { if (_initAttempts == 1) { ManualLogSource log = Log; val2 = new BepInExInfoLogInterpolatedStringHandler(56, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("Faust init ("); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(trigger); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("): Server world not yet present; will retry."); } log.LogInfo(val2); } return; } PrefabCollectionSystem existingSystemManaged = val.GetExistingSystemManaged(); if (existingSystemManaged == null || existingSystemManaged.SpawnableNameToPrefabGuidDictionary.Count == 0) { if (_initAttempts == 1) { ManualLogSource log2 = Log; val2 = new BepInExInfoLogInterpolatedStringHandler(68, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("Faust init ("); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(trigger); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("): PrefabCollectionSystem not yet populated; will retry."); } log2.LogInfo(val2); } return; } Server = val; EntityManager = val.EntityManager; PrefabCollectionSystem = existingSystemManaged; ServerScriptMapper = val.GetExistingSystemManaged(); ServerGameSettingsSystem = val.GetExistingSystemManaged(); Castle = new CastleService(); PlayerInfo = new PlayerInfoService(); Clan = new ClanService(); Boss = new BossService(); WorldScan = new WorldScanService(); WorldScan.Load(); MapMarkers = new MapMarkerService(); IsReady = true; ManualLogSource log3 = Log; val2 = new BepInExInfoLogInterpolatedStringHandler(60, 3, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("Faust initialized via "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(trigger); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(" (attempt #"); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(_initAttempts); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("). Prefab map has "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(existingSystemManaged.SpawnableNameToPrefabGuidDictionary.Count); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(" entries."); } log3.LogInfo(val2); HeatmapSampler.Start(); StartCoroutine(KillAutosaveLoop()); } catch (Exception ex) { ManualLogSource log4 = Log; BepInExErrorLogInterpolatedStringHandler val3 = new BepInExErrorLogInterpolatedStringHandler(35, 3, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val3).AppendLiteral("Faust init ("); ((BepInExLogInterpolatedStringHandler)val3).AppendFormatted(trigger); ((BepInExLogInterpolatedStringHandler)val3).AppendLiteral(") FAILED on attempt #"); ((BepInExLogInterpolatedStringHandler)val3).AppendFormatted(_initAttempts); ((BepInExLogInterpolatedStringHandler)val3).AppendLiteral(": "); ((BepInExLogInterpolatedStringHandler)val3).AppendFormatted(ex); } log4.LogError(val3); } finally { _initInProgress = false; } } private static World FindServerWorld() { Enumerator enumerator = World.s_AllWorlds.GetEnumerator(); while (enumerator.MoveNext()) { World current = enumerator.Current; if (current.Name == "Server") { return current; } } return null; } public static Coroutine StartCoroutine(IEnumerator routine) { return MonoBehaviour.StartCoroutine(CollectionExtensions.WrapToIl2Cpp(routine)); } private static IEnumerator KillAutosaveLoop() { WaitForSeconds wait = new WaitForSeconds(30f); bool flag = default(bool); while (true) { yield return wait; try { Kills?.Flush(); } catch (Exception ex) { ManualLogSource log = Log; BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(31, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[FAUST KILLS] autosave failed: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ex.Message); } log.LogError(val); } } } } internal static class EntityExtensions { public static bool Exists(this Entity entity) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) if (entity != Entity.Null) { EntityManager entityManager = Core.EntityManager; return ((EntityManager)(ref entityManager)).Exists(entity); } return false; } public static bool Has(this Entity entity) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) if (entity.Exists()) { EntityManager entityManager = Core.EntityManager; return ((EntityManager)(ref entityManager)).HasComponent(entity); } return false; } public static T Read(this Entity entity) where T : unmanaged { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) EntityManager entityManager = Core.EntityManager; return ((EntityManager)(ref entityManager)).GetComponentData(entity); } public static bool TryGetComponent(this Entity entity, out T component) where T : unmanaged { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) if (entity.Exists()) { EntityManager entityManager = Core.EntityManager; if (((EntityManager)(ref entityManager)).HasComponent(entity)) { entityManager = Core.EntityManager; component = ((EntityManager)(ref entityManager)).GetComponentData(entity); return true; } } component = default(T); return false; } public static ulong GetSteamId(this Entity playerCharacter) { //IL_0000: 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_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) if (playerCharacter.TryGetComponent(out PlayerCharacter component) && component.UserEntity.TryGetComponent(out User component2)) { return component2.PlatformId; } return 0uL; } public static PrefabGUID GetPrefabGuid(this Entity entity) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) if (!entity.TryGetComponent(out PrefabGUID component)) { return default(PrefabGUID); } return component; } public unsafe static bool TryResolvePlayer(string nameOrId, out ulong steamId, out string resolvedName, out Entity userEntity, out string error) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0042: 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_0056: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_018d: Unknown result type (might be due to invalid IL or missing references) //IL_0192: Unknown result type (might be due to invalid IL or missing references) steamId = 0uL; resolvedName = null; userEntity = Entity.Null; error = null; if (string.IsNullOrWhiteSpace(nameOrId)) { error = "Provide a character name or steamId."; return false; } ulong result; bool flag = ulong.TryParse(nameOrId, out result) && result > 1000; NativeArray entitiesByComponentType = Query.GetEntitiesByComponentType(includeDisabled: true); int num = 0; try { for (int i = 0; i < entitiesByComponentType.Length; i++) { if (!entitiesByComponentType[i].TryGetComponent(out User component)) { continue; } if (flag) { if (component.PlatformId == result) { steamId = component.PlatformId; resolvedName = ((object)(*(FixedString64Bytes*)(&component.CharacterName))/*cast due to .constrained prefix*/).ToString(); userEntity = entitiesByComponentType[i]; return true; } continue; } string text = ((object)(*(FixedString64Bytes*)(&component.CharacterName))/*cast due to .constrained prefix*/).ToString(); if (!string.IsNullOrEmpty(text) && text.Contains(nameOrId, StringComparison.OrdinalIgnoreCase)) { num++; steamId = component.PlatformId; resolvedName = text; userEntity = entitiesByComponentType[i]; if (string.Equals(text, nameOrId, StringComparison.OrdinalIgnoreCase)) { num = 1; break; } } } } finally { entitiesByComponentType.Dispose(); } if (flag) { error = $"No player with steamId {result}."; return false; } if (num == 0) { error = "No player matches '" + nameOrId + "'."; return false; } if (num > 1) { error = "'" + nameOrId + "' matches multiple players — be more specific or use the steamId."; steamId = 0uL; userEntity = Entity.Null; return false; } return true; } public static string GetPrefabName(this PrefabGUID prefabGuid) { //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_000b: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0014: 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_0044: Unknown result type (might be due to invalid IL or missing references) try { PrefabLookupMap prefabLookupMap = Core.PrefabCollectionSystem._PrefabLookupMap; NativeParallelHashMap guidToEntityMap = prefabLookupMap.GuidToEntityMap; if (guidToEntityMap.ContainsKey(prefabGuid)) { return ((PrefabLookupMap)(ref prefabLookupMap)).GetName(prefabGuid); } } catch { } return $"PrefabGuid({prefabGuid._Value})"; } } [BepInPlugin("kdpen.Faust", "Faust", "0.16.5")] [BepInDependency(/*Could not decode attribute arguments.*/)] public class Plugin : BasePlugin { internal static Harmony Harmony; internal static ManualLogSource PluginLog; internal static Plugin Instance { get; private set; } public override void Load() { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Expected O, but got Unknown //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Expected O, but got Unknown //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Expected O, but got Unknown //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Expected O, but got Unknown if (!(Application.productName != "VRisingServer")) { Instance = this; PluginLog = ((BasePlugin)this).Log; ManualLogSource log = ((BasePlugin)this).Log; bool flag = default(bool); BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(20, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Plugin "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted("kdpen.Faust"); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" v"); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted("0.16.5"); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" loading..."); } log.LogInfo(val); Settings.Initialize(((BasePlugin)this).Config); Core.InitPersistence(); Harmony = new Harmony("kdpen.Faust"); Harmony.PatchAll(Assembly.GetExecutingAssembly()); int num = Harmony.GetPatchedMethods().Count(); ManualLogSource log2 = ((BasePlugin)this).Log; val = new BepInExInfoLogInterpolatedStringHandler(44, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Harmony patches applied: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(num); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" method(s) patched."); } log2.LogInfo(val); CommandRegistry.RegisterAll(); ManualLogSource log3 = ((BasePlugin)this).Log; val = new BepInExInfoLogInterpolatedStringHandler(42, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Plugin "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted("kdpen.Faust"); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" v"); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted("0.16.5"); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" loaded. Awaiting game data init."); } log3.LogInfo(val); } } public override bool Unload() { CommandRegistry.UnregisterAssembly(); Core.MapMarkers?.Shutdown(); Core.Store?.CloseAllOpen(); Core.Heatmap?.Flush(); Core.Kills?.Flush(); Harmony harmony = Harmony; if (harmony != null) { harmony.UnpatchSelf(); } return true; } } internal static class Query { public static NativeArray GetEntitiesByComponentType(bool includeDisabled = false, bool includeSpawn = false, bool includePrefab = false) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0019: 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_0023: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0036: 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_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003f: 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_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) EntityQueryOptions val = (EntityQueryOptions)0; if (includeDisabled) { val = (EntityQueryOptions)(val | 2); } if (includeSpawn) { val = (EntityQueryOptions)(val | 0x40); } if (includePrefab) { val = (EntityQueryOptions)(val | 1); } EntityQueryBuilder val2 = new EntityQueryBuilder(AllocatorHandle.op_Implicit((Allocator)2)); val2 = ((EntityQueryBuilder)(ref val2)).AddAll(new ComponentType(Il2CppType.Of(), (AccessMode)1)); EntityQueryBuilder val3 = ((EntityQueryBuilder)(ref val2)).WithOptions(val); EntityManager entityManager = Core.EntityManager; EntityQuery val4 = ((EntityManager)(ref entityManager)).CreateEntityQuery(ref val3); NativeArray result = ((EntityQuery)(ref val4)).ToEntityArray(AllocatorHandle.op_Implicit((Allocator)2)); ((EntityQueryBuilder)(ref val3)).Dispose(); return result; } public static IEnumerable GetUsersOnline() { NativeArray users = GetEntitiesByComponentType(includeDisabled: true); try { for (int i = 0; i < users.Length; i++) { if (users[i].TryGetComponent(out User component) && component.IsConnected) { yield return users[i]; } } } finally { users.Dispose(); } } } public static class MyPluginInfo { public const string PLUGIN_GUID = "kdpen.Faust"; public const string PLUGIN_NAME = "Faust"; public const string PLUGIN_VERSION = "0.16.5"; } } namespace Faust.Services { internal sealed class BossService { public readonly struct BossSnapshot { public int Guid { get; init; } public string Name { get; init; } public bool Alive { get; init; } public bool Defeated { get; init; } public float X { get; init; } public float Z { get; init; } public string Region { get; init; } public float Hp { get; init; } public float HpMax { get; init; } public int Level { get; init; } } private const float Sentinel = 10000f; private static float MapLimit => Settings.BossMapLimit.Value; public List GetBosses() { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0032: 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_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_035b: Unknown result type (might be due to invalid IL or missing references) //IL_008b: 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_0097: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Unknown result type (might be due to invalid IL or missing references) //IL_0126: Unknown result type (might be due to invalid IL or missing references) //IL_0118: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_0144: Unknown result type (might be due to invalid IL or missing references) //IL_0146: Unknown result type (might be due to invalid IL or missing references) //IL_0138: Unknown result type (might be due to invalid IL or missing references) //IL_017e: Unknown result type (might be due to invalid IL or missing references) //IL_01a7: Unknown result type (might be due to invalid IL or missing references) //IL_02ac: Unknown result type (might be due to invalid IL or missing references) //IL_01c4: Unknown result type (might be due to invalid IL or missing references) //IL_01d2: Unknown result type (might be due to invalid IL or missing references) //IL_0200: Unknown result type (might be due to invalid IL or missing references) //IL_022c: Unknown result type (might be due to invalid IL or missing references) //IL_023a: Unknown result type (might be due to invalid IL or missing references) //IL_0251: Unknown result type (might be due to invalid IL or missing references) //IL_025f: Unknown result type (might be due to invalid IL or missing references) //IL_0261: Unknown result type (might be due to invalid IL or missing references) //IL_0272: Unknown result type (might be due to invalid IL or missing references) //IL_0280: Unknown result type (might be due to invalid IL or missing references) //IL_0282: Unknown result type (might be due to invalid IL or missing references) Dictionary dictionary = new Dictionary(); Dictionary dictionary2 = BuildMapTokenPositions(); NativeArray entitiesByComponentType = Query.GetEntitiesByComponentType(includeDisabled: true); try { float3 val2 = default(float3); for (int i = 0; i < entitiesByComponentType.Length; i++) { Entity val = entitiesByComponentType[i]; if (!val.Has() || !val.TryGetComponent(out Health component) || component.Value <= 0f) { continue; } int value = val.GetPrefabGuid()._Value; if (value == 0) { continue; } Translation component2; bool flag = val.TryGetComponent(out component2); LocalToWorld component3; bool flag2 = val.TryGetComponent(out component3); if (flag || flag2) { float num; float num2; bool flag3; if (flag && OnMap(component2.Value.x, component2.Value.z)) { num = component2.Value.x; num2 = component2.Value.z; flag3 = true; } else if (flag2 && OnMap(((LocalToWorld)(ref component3)).Position.x, ((LocalToWorld)(ref component3)).Position.z)) { num = ((LocalToWorld)(ref component3)).Position.x; num2 = ((LocalToWorld)(ref component3)).Position.z; flag3 = true; } else { num = (flag ? component2.Value.x : ((LocalToWorld)(ref component3)).Position.x); num2 = (flag ? component2.Value.z : ((LocalToWorld)(ref component3)).Position.z); flag3 = false; } if (!flag3 && dictionary2.TryGetValue(value, out var value2)) { num = value2.Item1; num2 = value2.Item2; flag3 = true; } if (!flag3 && TryFollowerPosition(val, out var x, out var z)) { num = x; num2 = z; flag3 = true; } ((float3)(ref val2))..ctor(num, flag2 ? ((LocalToWorld)(ref component3)).Position.y : 0f, num2); BossSnapshot value3; if (flag3) { int territoryIndexAt = Core.Castle.GetTerritoryIndexAt(val2); string region = Core.Castle.GetWorldRegionName(val2) ?? Core.Castle.GetRegionForTerritory(territoryIndexAt); value3 = new BossSnapshot { Guid = value, Name = EntityExtensions.GetPrefabName(new PrefabGUID(value)), Alive = true, Defeated = Core.Unlock.IsDefeatedAnywhere(value), X = val2.x, Z = val2.z, Region = region, Hp = component.Value, HpMax = component.MaxHealth._Value, Level = (val.TryGetComponent(out UnitLevel component4) ? component4.Level._Value : 0) }; } else { value3 = new BossSnapshot { Guid = value, Name = EntityExtensions.GetPrefabName(new PrefabGUID(value)), Alive = false, Defeated = Core.Unlock.IsDefeatedAnywhere(value) }; } if (!dictionary.TryGetValue(value, out var value4) || (!value4.Alive && value3.Alive)) { dictionary[value] = value3; } } } } finally { entitiesByComponentType.Dispose(); } foreach (int item in Core.Unlock.AllDefeatedGuids()) { if (!dictionary.ContainsKey(item)) { dictionary[item] = new BossSnapshot { Guid = item, Name = EntityExtensions.GetPrefabName(new PrefabGUID(item)), Alive = false, Defeated = true }; } } List list = new List(dictionary.Values); list.Sort((BossSnapshot a, BossSnapshot b) => (a.Alive != b.Alive) ? ((!a.Alive) ? 1 : (-1)) : string.CompareOrdinal(a.Name, b.Name)); return list; } private static Dictionary BuildMapTokenPositions() { //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Expected O, but got Unknown //IL_01db: Unknown result type (might be due to invalid IL or missing references) //IL_01e2: Expected O, but got Unknown //IL_0320: Unknown result type (might be due to invalid IL or missing references) //IL_0327: Expected O, but got Unknown //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_020d: Unknown result type (might be due to invalid IL or missing references) //IL_0212: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0036: 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_0041: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_0220: Unknown result type (might be due to invalid IL or missing references) //IL_0225: Unknown result type (might be due to invalid IL or missing references) //IL_0227: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: 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_0235: Unknown result type (might be due to invalid IL or missing references) //IL_0237: Unknown result type (might be due to invalid IL or missing references) //IL_023c: Unknown result type (might be due to invalid IL or missing references) //IL_0241: Unknown result type (might be due to invalid IL or missing references) //IL_0243: Unknown result type (might be due to invalid IL or missing references) //IL_0245: Unknown result type (might be due to invalid IL or missing references) //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_0254: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Unknown result type (might be due to invalid IL or missing references) //IL_0260: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_0129: Unknown result type (might be due to invalid IL or missing references) //IL_026c: Unknown result type (might be due to invalid IL or missing references) //IL_026e: Unknown result type (might be due to invalid IL or missing references) //IL_0143: Unknown result type (might be due to invalid IL or missing references) //IL_0288: Unknown result type (might be due to invalid IL or missing references) //IL_016c: Unknown result type (might be due to invalid IL or missing references) //IL_0150: Unknown result type (might be due to invalid IL or missing references) //IL_015e: Unknown result type (might be due to invalid IL or missing references) //IL_02b1: Unknown result type (might be due to invalid IL or missing references) //IL_0295: Unknown result type (might be due to invalid IL or missing references) //IL_02a3: Unknown result type (might be due to invalid IL or missing references) //IL_0177: Unknown result type (might be due to invalid IL or missing references) //IL_0179: Unknown result type (might be due to invalid IL or missing references) //IL_0185: Unknown result type (might be due to invalid IL or missing references) //IL_0187: Unknown result type (might be due to invalid IL or missing references) //IL_02bc: Unknown result type (might be due to invalid IL or missing references) //IL_02be: Unknown result type (might be due to invalid IL or missing references) //IL_02ca: Unknown result type (might be due to invalid IL or missing references) //IL_02cc: Unknown result type (might be due to invalid IL or missing references) Dictionary dictionary = new Dictionary(); bool flag = default(bool); try { NativeArray entitiesByComponentType = Query.GetEntitiesByComponentType(includeDisabled: true); try { for (int i = 0; i < entitiesByComponentType.Length; i++) { if (entitiesByComponentType[i].TryGetComponent(out Script_BloodAltar_TrackVBloodUnit_Shared component)) { int value = component.TrackedUnit._Value; if (value != 0 && OnMap(component.TrackPosition.x, component.TrackPosition.z)) { dictionary[value] = (component.TrackPosition.x, component.TrackPosition.z); } } } } finally { entitiesByComponentType.Dispose(); } } catch (Exception ex) { ManualLogSource log = Core.Log; BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(40, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[FAUST BOSS] altar-track source failed: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ex.Message); } log.LogWarning(val); } try { NativeArray entitiesByComponentType2 = Query.GetEntitiesByComponentType(includeDisabled: true); try { for (int j = 0; j < entitiesByComponentType2.Length; j++) { Entity entity = entitiesByComponentType2[j]; if (!entity.TryGetComponent(out MapIconData component2)) { continue; } Entity targetUser = component2.TargetUser; if (targetUser == Entity.Null || !targetUser.Exists() || !targetUser.Has()) { continue; } int value2 = targetUser.GetPrefabGuid()._Value; if (value2 == 0 || dictionary.ContainsKey(value2)) { continue; } float x; float z; if (entity.TryGetComponent(out LocalToWorld component3)) { x = ((LocalToWorld)(ref component3)).Position.x; z = ((LocalToWorld)(ref component3)).Position.z; } else { if (!entity.TryGetComponent(out Translation component4)) { continue; } x = component4.Value.x; z = component4.Value.z; } if (OnMap(x, z)) { dictionary[value2] = (x, z); } } } finally { entitiesByComponentType2.Dispose(); } } catch (Exception ex2) { ManualLogSource log2 = Core.Log; BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(37, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[FAUST BOSS] map-icon source failed: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ex2.Message); } log2.LogWarning(val); } try { NativeArray entitiesByComponentType3 = Query.GetEntitiesByComponentType(includeDisabled: true); try { for (int k = 0; k < entitiesByComponentType3.Length; k++) { Entity entity2 = entitiesByComponentType3[k]; if (!entity2.TryGetComponent(out MapIconTargetEntity component5)) { continue; } Entity entity3 = component5.TargetEntity._Entity; if (entity3 == Entity.Null || !entity3.Exists() || !entity3.Has()) { continue; } int value3 = entity3.GetPrefabGuid()._Value; if (value3 == 0 || dictionary.ContainsKey(value3)) { continue; } float x2; float z2; if (entity2.TryGetComponent(out LocalToWorld component6)) { x2 = ((LocalToWorld)(ref component6)).Position.x; z2 = ((LocalToWorld)(ref component6)).Position.z; } else { if (!entity2.TryGetComponent(out Translation component7)) { continue; } x2 = component7.Value.x; z2 = component7.Value.z; } if (OnMap(x2, z2)) { dictionary[value3] = (x2, z2); } } } finally { entitiesByComponentType3.Dispose(); } } catch (Exception ex3) { ManualLogSource log3 = Core.Log; BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(39, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[FAUST BOSS] map-target source failed: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ex3.Message); } log3.LogWarning(val); } return dictionary; } private static bool TryFollowerPosition(Entity e, out float x, out float z) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0080: 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_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) x = (z = 0f); if (!e.TryGetComponent(out Follower component)) { return false; } Entity value = component.Followed._Value; if (value == Entity.Null || !value.Exists()) { return false; } if (value.TryGetComponent(out Translation component2) && OnMap(component2.Value.x, component2.Value.z)) { x = component2.Value.x; z = component2.Value.z; return true; } if (value.TryGetComponent(out LocalToWorld component3) && OnMap(((LocalToWorld)(ref component3)).Position.x, ((LocalToWorld)(ref component3)).Position.z)) { x = ((LocalToWorld)(ref component3)).Position.x; z = ((LocalToWorld)(ref component3)).Position.z; return true; } return false; } public bool TryGetBoss(string nameOrGuid, out BossSnapshot snapshot) { snapshot = default(BossSnapshot); List bosses = GetBosses(); int result; bool flag = int.TryParse(nameOrGuid, out result); BossSnapshot? bossSnapshot = null; BossSnapshot? bossSnapshot2 = null; int num = 0; foreach (BossSnapshot item in bosses) { if (flag && item.Guid == result) { snapshot = item; return true; } if (item.Name != null) { if (string.Equals(item.Name, nameOrGuid, StringComparison.OrdinalIgnoreCase)) { bossSnapshot = item; } else if (item.Name.Contains(nameOrGuid, StringComparison.OrdinalIgnoreCase)) { bossSnapshot2 = item; num++; } } } if (bossSnapshot.HasValue) { snapshot = bossSnapshot.Value; return true; } if (bossSnapshot2.HasValue && num == 1) { snapshot = bossSnapshot2.Value; return true; } return false; } public List Diagnose(string filter) { //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_0107: 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) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_0139: Unknown result type (might be due to invalid IL or missing references) //IL_0152: Unknown result type (might be due to invalid IL or missing references) //IL_0169: 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) //IL_0182: Unknown result type (might be due to invalid IL or missing references) //IL_0184: Unknown result type (might be due to invalid IL or missing references) //IL_03ef: Unknown result type (might be due to invalid IL or missing references) //IL_0419: Unknown result type (might be due to invalid IL or missing references) //IL_041b: Unknown result type (might be due to invalid IL or missing references) if (!string.IsNullOrWhiteSpace(filter) && (filter.Equals("icons", StringComparison.OrdinalIgnoreCase) || filter.Equals("mapicons", StringComparison.OrdinalIgnoreCase))) { return DiagnoseIcons(); } int result; bool flag = int.TryParse(filter, out result); bool flag2 = !string.IsNullOrWhiteSpace(filter); int num = 0; int num2 = 0; int num3 = 0; int num4 = 0; int num5 = 0; int num6 = 0; int num7 = 0; int num8 = 0; int num9 = 0; int num10 = 0; int num11 = 0; int num12 = 0; List list = new List(); Dictionary dictionary = BuildMapTokenPositions(); float num13 = float.MaxValue; float num14 = float.MinValue; float num15 = float.MaxValue; float num16 = float.MinValue; Dictionary<(int, int), int> dictionary2 = new Dictionary<(int, int), int>(); Dictionary<(int, int), int> dictionary3 = new Dictionary<(int, int), int>(); NativeArray entitiesByComponentType = Query.GetEntitiesByComponentType(includeDisabled: true); try { for (int i = 0; i < entitiesByComponentType.Length; i++) { Entity entity = entitiesByComponentType[i]; num++; int value = entity.GetPrefabGuid()._Value; string prefabName = EntityExtensions.GetPrefabName(new PrefabGUID(value)); bool flag3 = entity.Has(); if (flag3) { num2++; } Health component; bool flag4 = entity.TryGetComponent(out component); if (flag4 && component.Value > 0f) { num3++; } LocalToWorld component2; bool flag5 = entity.TryGetComponent(out component2); Translation component3; bool flag6 = entity.TryGetComponent(out component3); bool flag7 = entity.Has(); if (flag7) { num7++; } float num17 = (flag5 ? ((LocalToWorld)(ref component2)).Position.x : 0f); float num18 = (flag5 ? ((LocalToWorld)(ref component2)).Position.z : 0f); float num19 = (flag6 ? component3.Value.x : 0f); float num20 = (flag6 ? component3.Value.z : 0f); bool flag8 = flag5 && OnMap(num17, num18); bool flag9 = flag6 && OnMap(num19, num20); if (!flag5) { num6++; } else if (flag8) { num4++; if (num17 < num13) { num13 = num17; } if (num17 > num14) { num14 = num17; } if (num18 < num15) { num15 = num18; } if (num18 > num16) { num16 = num18; } } else { num5++; if (flag3) { Bump(dictionary2, num17, num18); } } if (!flag6) { num10++; } else if (flag9) { num8++; } else { num9++; if (flag3) { Bump(dictionary3, num19, num20); } } if (flag3 && flag5 && flag6 && !flag8 && flag9) { num11++; } (float, float) value2; bool flag10 = dictionary.TryGetValue(value, out value2); if (flag3 && !flag8 && !flag9 && flag10) { num12++; } if (flag2 && (flag ? (value == result) : prefabName.Contains(filter, StringComparison.OrdinalIgnoreCase))) { list.Add($"{prefabName} guid={value} ltw=({F(num17)},{F(num18)}) tr=({F(num19)},{F(num20)}) token={(flag10 ? $"({F(value2.Item1)},{F(value2.Item2)})" : "-")} cs={B(flag3)} hp={(flag4 ? F(component.Value) : "-")}/{(flag4 ? F(component.MaxHealth._Value) : "-")} disabled={B(flag7)}"); } } } finally { entitiesByComponentType.Dispose(); } List list2 = new List { $"VBlood: {num} total · {num2} cs · {num3} alive · LTW[{num4} on/{num5} off/{num6} none] · TR[{num8} on/{num9} off/{num10} none] · {num7} disabled · off-LTW-but-on-TR(cs)={num11}." }; if (num4 > 0) { list2.Add($"Placed bounds (LTW): x[{F(num13)}..{F(num14)}] z[{F(num15)}..{F(num16)}]."); } if (dictionary2.Count > 0) { list2.Add(ClusterLine("Off-map LTW", dictionary2)); } if (dictionary3.Count > 0) { list2.Add(ClusterLine("Off-map TR", dictionary3)); } if (num11 > 0) { list2.Add($"⇒ {num11} consume-source bosses are off-map by LocalToWorld but ON-map by Translation " + "— the board should read Translation."); } list2.Add($"Map tokens: {dictionary.Count} V Blood position(s) resolved (altar-track + TargetUser-icon + TargetEntity-icon); {num12} off-map boss(es) rescued by a token (the combine). {((dictionary.Count == 0) ? "NONE found — no token source carries positions on this server; run '.faust admin bossdiag icons' for the breakdown." : "")}"); if (flag2) { list2.Add($"Matches for '{filter}': {list.Count}"); list2.AddRange(list); } else if (dictionary2.Count == 0 && num5 == 0) { list2.Add("No off-map bosses (LTW). Add a name/guid to dump rows, or 'icons' for the map-icon picture."); } return list2; } private static bool IsSentinel(float x, float z) { if (Math.Abs(Math.Abs(x) - 10000f) < 250f) { return Math.Abs(Math.Abs(z) - 10000f) < 250f; } return false; } private static bool OnMap(float x, float z) { if (!IsSentinel(x, z) && Math.Abs(x) <= MapLimit) { return Math.Abs(z) <= MapLimit; } return false; } private static void Bump(Dictionary<(int, int), int> map, float x, float z) { (int, int) key = ((int)Math.Round(x / 500f) * 500, (int)Math.Round(z / 500f) * 500); map[key] = ((!map.TryGetValue(key, out var value)) ? 1 : (value + 1)); } private static string ClusterLine(string label, Dictionary<(int, int), int> map) { List> source = map.OrderByDescending((KeyValuePair<(int, int), int> kv) => kv.Value).Take(6).ToList(); return $"{label} clusters (~500u, {map.Count} distinct): " + string.Join(" · ", source.Select((KeyValuePair<(int, int), int> kv) => $"({kv.Key.Item1},{kv.Key.Item2})x{kv.Value}")); } private List DiagnoseIcons() { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: 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) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: 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) //IL_013f: Unknown result type (might be due to invalid IL or missing references) //IL_0159: Unknown result type (might be due to invalid IL or missing references) //IL_0190: 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_0174: Unknown result type (might be due to invalid IL or missing references) int num = 0; int num2 = 0; int num3 = 0; int num4 = 0; int num5 = 0; int num6 = 0; int num7 = 0; int num8 = 0; List list = new List(); NativeArray entitiesByComponentType = Query.GetEntitiesByComponentType(includeDisabled: true); try { for (int i = 0; i < entitiesByComponentType.Length; i++) { Entity entity = entitiesByComponentType[i]; num++; if (!entity.TryGetComponent(out MapIconData component)) { continue; } if (entity.Has()) { num8++; } if (entity.Has()) { num7++; } Entity targetUser = component.TargetUser; int num9; if (targetUser != Entity.Null) { num9 = (targetUser.Exists() ? 1 : 0); if (num9 != 0) { num2++; } } else { num9 = 0; } if (num9 == 0 || !targetUser.Has()) { continue; } num3++; float num10 = 0f; float num11 = 0f; bool flag = false; Translation component3; if (entity.TryGetComponent(out LocalToWorld component2)) { num10 = ((LocalToWorld)(ref component2)).Position.x; num11 = ((LocalToWorld)(ref component2)).Position.z; flag = true; } else if (entity.TryGetComponent(out component3)) { num10 = component3.Value.x; num11 = component3.Value.z; flag = true; } if (!flag) { num6++; } else if (OnMap(num10, num11)) { num4++; } else { num5++; } if (list.Count < 15) { int value = targetUser.GetPrefabGuid()._Value; float v = 0f; float v2 = 0f; if (targetUser.TryGetComponent(out LocalToWorld component4)) { v = ((LocalToWorld)(ref component4)).Position.x; v2 = ((LocalToWorld)(ref component4)).Position.z; } list.Add($"{EntityExtensions.GetPrefabName(new PrefabGUID(value))} icon=({(flag ? (F(num10) + "," + F(num11)) : "-")}) unit=({F(v)},{F(v2)})"); } } } finally { entitiesByComponentType.Dispose(); } List list2 = new List { $"MapIcons: {num} total · {num2} w/TargetUser · {num3} target a VBlood · {num8} w/MapIconTargetEntity · {num7} w/TilePosition.", $"VBlood-icon positions (via TargetUser): {num4} on-map · {num5} off-map · {num6} no-position." }; list2.AddRange(DiagnoseTargetEntityIcons()); if (num3 == 0) { list2.Add("No VBlood-linked icons via TargetUser — boss icons link via MapIconTargetEntity (see below)."); } list2.AddRange(list); return list2; } private List DiagnoseTargetEntityIcons() { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: 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_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0055: 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: Unknown result type (might be due to invalid IL or missing references) //IL_0075: 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_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_0132: Unknown result type (might be due to invalid IL or missing references) //IL_0134: 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_0168: Unknown result type (might be due to invalid IL or missing references) //IL_0185: Unknown result type (might be due to invalid IL or missing references) //IL_0187: Unknown result type (might be due to invalid IL or missing references) //IL_01a4: Unknown result type (might be due to invalid IL or missing references) //IL_01a6: Unknown result type (might be due to invalid IL or missing references) //IL_01d5: Unknown result type (might be due to invalid IL or missing references) //IL_01d7: Unknown result type (might be due to invalid IL or missing references) //IL_021c: Unknown result type (might be due to invalid IL or missing references) int num = 0; int num2 = 0; int num3 = 0; int num4 = 0; int num5 = 0; int num6 = 0; int num7 = 0; List list = new List(); try { NativeArray entitiesByComponentType = Query.GetEntitiesByComponentType(includeDisabled: true); try { for (int i = 0; i < entitiesByComponentType.Length; i++) { Entity entity = entitiesByComponentType[i]; num++; if (!entity.TryGetComponent(out MapIconTargetEntity component)) { continue; } Entity entity2 = component.TargetEntity._Entity; if (entity2 == Entity.Null || !entity2.Exists()) { num3++; } else if (entity2.Has()) { num2++; float num8 = 0f; float num9 = 0f; bool flag = false; Translation component3; if (entity.TryGetComponent(out LocalToWorld component2)) { num8 = ((LocalToWorld)(ref component2)).Position.x; num9 = ((LocalToWorld)(ref component2)).Position.z; flag = true; } else if (entity.TryGetComponent(out component3)) { num8 = component3.Value.x; num9 = component3.Value.z; flag = true; } if (!flag) { num6++; } else if (OnMap(num8, num9)) { num4++; } else { num5++; } MapIconPosition component4; bool flag2 = entity.TryGetComponent(out component4); if (flag2) { num7++; } if (list.Count < 15) { int value = entity2.GetPrefabGuid()._Value; string value2 = (flag2 ? $"tile=({component4.TilePosition.x},{component4.TilePosition.y}) tilewc=({F((float)(component4.TilePosition.x - 6400) / 2f)},{F((float)(component4.TilePosition.y - 6400) / 2f)})" : "tile=-"); list.Add($"{EntityExtensions.GetPrefabName(new PrefabGUID(value))} world={(flag ? $"({F(num8)},{F(num9)})" : "-")} {value2}"); } } } } finally { entitiesByComponentType.Dispose(); } } catch (Exception ex) { return new List { "MapIconTargetEntity scan failed: " + ex.Message }; } List list2 = new List { $"MapIconTargetEntity: {num} total · {num2} resolve to a VBlood (direct Entity) · {num3} null/unsynced Entity · {num7} w/TilePosition.", $"VBlood target-icon positions: {num4} ON-map (these rescue parked bosses) · {num5} off-map · {num6} no-world-pos." }; if (num2 == 0 && num3 > 0) { list2.Add("⇒ VBlood target-icons resolve with a NULL Entity — need NetworkId resolution (TargetNetworkId) to link them."); } else if (num2 == 0) { list2.Add("⇒ No MapIconTargetEntity icons target a VBlood — boss lair positions are NOT in live map icons on this server."); } else if (num4 == 0) { list2.Add("⇒ VBlood target-icons exist but NONE carry an on-map world transform — lair pos may live in TilePosition only (see tilewc estimate)."); } list2.AddRange(list); return list2; } private static string F(float v) { return v.ToString("0", CultureInfo.InvariantCulture); } private static string B(bool b) { if (!b) { return "0"; } return "1"; } } internal sealed class CastleService { public enum PlotState { Unclaimed, Sealed, Fueled, Decaying } public readonly struct TerritoryInfo { public int TerritoryIndex { get; init; } public bool HasHeart { get; init; } public ulong OwnerSteamId { get; init; } public string OwnerName { get; init; } public bool OwnerOnline { get; init; } public long OwnerLastConnected { get; init; } public string Region { get; init; } public int SizeBlocks { get; init; } public PlotState State { get; init; } public long DecaySeconds { get; init; } public int HeartLevel { get; init; } public int Floors { get; init; } public long ClaimedUnix { get; init; } public string ClanName { get; init; } public long TotalItems { get; init; } } public readonly struct ResourceSummary { public int TerritoryIndex { get; init; } public ulong OwnerSteamId { get; init; } public string OwnerName { get; init; } public int Containers { get; init; } public long TotalItems { get; init; } public List<(int guid, long qty, string name)> Items { get; init; } public int Prisoners { get; init; } public List<(string name, string bloodType, int bloodQuality)> PrisonerList { get; init; } } private struct RegionPolygon { public WorldRegionType Region; public Aabb Bounds; public float2[] Vertices; } private const float BLOCK_SIZE = 10f; private Dictionary _blockToTerritory; private Dictionary _territoryRegion; private Dictionary _territoryCenters; private float2 _mapWorldMin; private float2 _mapWorldMax; private bool _mapBoundsValid; private List _regionPolygons; private static float BlockToWorld(float block) { return (10f * block - 6400f) / 2f; } private void EnsureBlockMap() { //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_01e6: Unknown result type (might be due to invalid IL or missing references) //IL_01ec: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_01f5: Unknown result type (might be due to invalid IL or missing references) //IL_0201: Unknown result type (might be due to invalid IL or missing references) //IL_020d: Unknown result type (might be due to invalid IL or missing references) //IL_0212: Unknown result type (might be due to invalid IL or missing references) //IL_0218: Unknown result type (might be due to invalid IL or missing references) //IL_0224: Unknown result type (might be due to invalid IL or missing references) //IL_0230: Unknown result type (might be due to invalid IL or missing references) //IL_0235: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_0129: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_01c1: Unknown result type (might be due to invalid IL or missing references) //IL_013f: Unknown result type (might be due to invalid IL or missing references) //IL_0146: Unknown result type (might be due to invalid IL or missing references) //IL_0133: Unknown result type (might be due to invalid IL or missing references) //IL_015c: Unknown result type (might be due to invalid IL or missing references) //IL_0163: Unknown result type (might be due to invalid IL or missing references) //IL_0150: Unknown result type (might be due to invalid IL or missing references) //IL_016d: Unknown result type (might be due to invalid IL or missing references) if (_blockToTerritory != null) { return; } _blockToTerritory = new Dictionary(); _territoryRegion = new Dictionary(); _territoryCenters = new Dictionary(); int2 val = default(int2); ((int2)(ref val))..ctor(int.MaxValue, int.MaxValue); int2 val2 = default(int2); ((int2)(ref val2))..ctor(int.MinValue, int.MinValue); NativeArray entitiesByComponentType = Query.GetEntitiesByComponentType(includeDisabled: true); try { for (int i = 0; i < entitiesByComponentType.Length; i++) { int castleTerritoryIndex = entitiesByComponentType[i].Read().CastleTerritoryIndex; if (entitiesByComponentType[i].TryGetComponent(out TerritoryWorldRegion component) && (int)component.Region != 0) { _territoryRegion[castleTerritoryIndex] = RegionName(component.Region); } EntityManager entityManager = Core.EntityManager; DynamicBuffer buffer = ((EntityManager)(ref entityManager)).GetBuffer(entitiesByComponentType[i], false); long num = 0L; long num2 = 0L; for (int j = 0; j < buffer.Length; j++) { int2 blockCoordinate = buffer[j].BlockCoordinate; _blockToTerritory[blockCoordinate] = castleTerritoryIndex; num += blockCoordinate.x; num2 += blockCoordinate.y; if (blockCoordinate.x < val.x) { val.x = blockCoordinate.x; } if (blockCoordinate.x > val2.x) { val2.x = blockCoordinate.x; } if (blockCoordinate.y < val.y) { val.y = blockCoordinate.y; } if (blockCoordinate.y > val2.y) { val2.y = blockCoordinate.y; } } if (buffer.Length > 0) { _territoryCenters[castleTerritoryIndex] = new float2(BlockToWorld((float)num / (float)buffer.Length), BlockToWorld((float)num2 / (float)buffer.Length)); } } } finally { entitiesByComponentType.Dispose(); } if (val2.x >= val.x) { _mapWorldMin = new float2(BlockToWorld(val.x), BlockToWorld(val.y)); _mapWorldMax = new float2(BlockToWorld(val2.x), BlockToWorld(val2.y)); _mapBoundsValid = true; } } public string GetRegionForTerritory(int territoryIndex) { if (territoryIndex < 0) { return null; } EnsureBlockMap(); if (!_territoryRegion.TryGetValue(territoryIndex, out var value)) { return null; } return value; } public bool TryGetTerritoryCenter(int territoryIndex, out float x, out float z) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) x = 0f; z = 0f; if (territoryIndex < 0) { return false; } EnsureBlockMap(); if (!_territoryCenters.TryGetValue(territoryIndex, out var value)) { return false; } x = value.x; z = value.y; return true; } public bool TryGetMapWorldBounds(out float minX, out float minZ, out float maxX, out float maxZ) { EnsureBlockMap(); minX = _mapWorldMin.x; minZ = _mapWorldMin.y; maxX = _mapWorldMax.x; maxZ = _mapWorldMax.y; return _mapBoundsValid; } private void EnsureRegionPolygons() { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0027: 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_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Expected O, but got Unknown //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: 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_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) if (_regionPolygons != null) { return; } _regionPolygons = new List(); NativeArray entitiesByComponentType = Query.GetEntitiesByComponentType(includeDisabled: true); try { for (int i = 0; i < entitiesByComponentType.Length; i++) { if (!entitiesByComponentType[i].Has()) { continue; } WorldRegionPolygon val = entitiesByComponentType[i].Read(); EntityManager entityManager = Core.EntityManager; DynamicBuffer buffer = ((EntityManager)(ref entityManager)).GetBuffer(entitiesByComponentType[i], false); if (buffer.Length >= 3) { float2[] array = (float2[])(object)new float2[buffer.Length]; for (int j = 0; j < buffer.Length; j++) { array[j] = buffer[j].VertexPos; } _regionPolygons.Add(new RegionPolygon { Region = val.WorldRegion, Bounds = val.PolygonBounds, Vertices = array }); } } } finally { entitiesByComponentType.Dispose(); } if (Settings.VerboseLogging.Value) { ManualLogSource log = Core.Log; bool flag = default(bool); BepInExInfoLogInterpolatedStringHandler val2 = new BepInExInfoLogInterpolatedStringHandler(45, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("[FAUST CASTLE] world-region polygons cached: "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(_regionPolygons.Count); } log.LogInfo(val2); } } public string GetWorldRegionName(float3 pos) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0067: 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_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) EnsureRegionPolygons(); foreach (RegionPolygon regionPolygon in _regionPolygons) { if (!(pos.x < regionPolygon.Bounds.Min.x) && !(pos.x > regionPolygon.Bounds.Max.x) && !(pos.z < regionPolygon.Bounds.Min.z) && !(pos.z > regionPolygon.Bounds.Max.z) && IsPointInPolygon(regionPolygon.Vertices, pos.x, pos.z)) { return RegionName(regionPolygon.Region); } } return null; } private string ResolveTerritoryRegion(Entity territoryEntity) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_001f: 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_000b: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) if (territoryEntity.TryGetComponent(out TerritoryWorldRegion component) && (int)component.Region != 0) { return RegionName(component.Region); } if (TryGetTerritorySamplePosition(territoryEntity, out var pos)) { return GetWorldRegionName(pos); } return null; } private bool TryGetTerritorySamplePosition(Entity territoryEntity, out float3 pos) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0019: 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_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0042: 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) pos = default(float3); EntityManager entityManager = Core.EntityManager; if (!((EntityManager)(ref entityManager)).HasComponent(territoryEntity)) { return false; } entityManager = Core.EntityManager; DynamicBuffer buffer = ((EntityManager)(ref entityManager)).GetBuffer(territoryEntity, false); if (buffer.Length == 0) { return false; } pos = BlockCoordToWorldCentre(buffer[0].BlockCoordinate); return true; } private static float3 BlockCoordToWorldCentre(int2 block) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0013: 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) float num = (float)block.x * 10f + 5f; float num2 = (float)block.y * 10f + 5f; return new float3((num - 6400f) / 2f, 0f, (num2 - 6400f) / 2f); } private static bool IsPointInPolygon(float2[] polygon, float px, float pz) { int num = 0; int num2 = 0; int num3 = polygon.Length - 1; while (num2 < polygon.Length) { if (polygon[num2].y > pz != polygon[num3].y > pz && px < (polygon[num3].x - polygon[num2].x) * (pz - polygon[num2].y) / (polygon[num3].y - polygon[num2].y) + polygon[num2].x) { num++; } num3 = num2++; } return (num & 1) != 0; } private static float3 ConvertPosToGrid(float3 pos) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0018: 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_0036: Unknown result type (might be due to invalid IL or missing references) return new float3((float)(Mathf.FloorToInt(pos.x * 2f) + 6400), pos.y, (float)(Mathf.FloorToInt(pos.z * 2f) + 6400)); } private static int2 ConvertPosToBlockCoord(float3 pos) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //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_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) float3 val = ConvertPosToGrid(pos); return new int2((int)math.floor(val.x / 10f), (int)math.floor(val.z / 10f)); } public int GetTerritoryIndexAt(float3 pos) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) EnsureBlockMap(); if (!_blockToTerritory.TryGetValue(ConvertPosToBlockCoord(pos), out var value)) { return -1; } return value; } public int GetNearestHeartTerritory(float3 pos) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0035: 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_003c: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0053: 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) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_005f: 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_006d: Unknown result type (might be due to invalid IL or missing references) NativeArray entitiesByComponentType = Query.GetEntitiesByComponentType(includeDisabled: true); int result = -1; float num = float.MaxValue; try { for (int i = 0; i < entitiesByComponentType.Length; i++) { if (!entitiesByComponentType[i].Has()) { continue; } LocalToWorld val = entitiesByComponentType[i].Read(); float3 position = ((LocalToWorld)(ref val)).Position; float num2 = math.distancesq(pos, position); if (!(num2 >= num)) { Entity castleTerritoryEntity = entitiesByComponentType[i].Read().CastleTerritoryEntity; if (castleTerritoryEntity.Exists()) { num = num2; result = castleTerritoryEntity.Read().CastleTerritoryIndex; } } } return result; } finally { entitiesByComponentType.Dispose(); } } public bool TryGetTerritory(int territoryIndex, out TerritoryInfo info, bool extras = false) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0036: 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) info = default(TerritoryInfo); if (territoryIndex < 0) { return false; } NativeArray entitiesByComponentType = Query.GetEntitiesByComponentType(includeDisabled: true); try { for (int i = 0; i < entitiesByComponentType.Length; i++) { CastleTerritory val = entitiesByComponentType[i].Read(); if (val.CastleTerritoryIndex == territoryIndex) { info = BuildInfo(entitiesByComponentType[i], val, extras); return true; } } } finally { entitiesByComponentType.Dispose(); } return false; } public List GetFreePlots() { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) List list = new List(); NativeArray entitiesByComponentType = Query.GetEntitiesByComponentType(includeDisabled: true); try { for (int i = 0; i < entitiesByComponentType.Length; i++) { CastleTerritory val = entitiesByComponentType[i].Read(); if (!val.CastleHeart.Exists()) { list.Add(BuildInfo(entitiesByComponentType[i], val)); } } } finally { entitiesByComponentType.Dispose(); } list.Sort((TerritoryInfo a, TerritoryInfo b) => b.SizeBlocks.CompareTo(a.SizeBlocks)); return list; } public List GetAllTerritories() { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) List list = new List(); NativeArray entitiesByComponentType = Query.GetEntitiesByComponentType(includeDisabled: true); try { for (int i = 0; i < entitiesByComponentType.Length; i++) { list.Add(BuildInfo(entitiesByComponentType[i], entitiesByComponentType[i].Read())); } } finally { entitiesByComponentType.Dispose(); } list.Sort((TerritoryInfo a, TerritoryInfo b) => b.SizeBlocks.CompareTo(a.SizeBlocks)); return list; } public List GetCastlesByDecay() { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) List list = new List(); NativeArray entitiesByComponentType = Query.GetEntitiesByComponentType(includeDisabled: true); try { for (int i = 0; i < entitiesByComponentType.Length; i++) { CastleTerritory val = entitiesByComponentType[i].Read(); if (val.CastleHeart.Exists()) { list.Add(BuildInfo(entitiesByComponentType[i], val)); } } } finally { entitiesByComponentType.Dispose(); } list.Sort(delegate(TerritoryInfo a, TerritoryInfo b) { long num = ((a.DecaySeconds < 0) ? long.MaxValue : a.DecaySeconds); long value = ((b.DecaySeconds < 0) ? long.MaxValue : b.DecaySeconds); return num.CompareTo(value); }); return list; } public unsafe bool TrySummarizeResources(int territoryIndex, out ResourceSummary summary) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002a: 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_0031: 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_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0073: 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_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_0102: 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) //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_0126: Unknown result type (might be due to invalid IL or missing references) //IL_012b: Unknown result type (might be due to invalid IL or missing references) //IL_012f: Unknown result type (might be due to invalid IL or missing references) //IL_0132: Unknown result type (might be due to invalid IL or missing references) //IL_0137: Unknown result type (might be due to invalid IL or missing references) //IL_020a: Unknown result type (might be due to invalid IL or missing references) //IL_0145: Unknown result type (might be due to invalid IL or missing references) //IL_014a: Unknown result type (might be due to invalid IL or missing references) //IL_015e: Unknown result type (might be due to invalid IL or missing references) //IL_017a: Unknown result type (might be due to invalid IL or missing references) //IL_018a: Unknown result type (might be due to invalid IL or missing references) summary = default(ResourceSummary); if (territoryIndex < 0) { return false; } Entity val = Entity.Null; NativeArray entitiesByComponentType = Query.GetEntitiesByComponentType(includeDisabled: true); try { for (int i = 0; i < entitiesByComponentType.Length; i++) { CastleTerritory val2 = entitiesByComponentType[i].Read(); if (val2.CastleTerritoryIndex == territoryIndex) { val = val2.CastleHeart; break; } } } finally { entitiesByComponentType.Dispose(); } if (!val.Exists()) { return false; } ulong ownerSteamId = 0uL; string ownerName = "Unknown"; if (val.TryGetComponent(out UserOwner component) && ((NetworkedEntity)(ref component.Owner)).GetEntityOnServer().TryGetComponent(out User component2)) { ownerSteamId = component2.PlatformId; ownerName = ((object)(*(FixedString64Bytes*)(&component2.CharacterName))/*cast due to .constrained prefix*/).ToString(); } Dictionary dictionary = new Dictionary(); int num = 0; long num2 = 0L; NativeArray entitiesByComponentType2 = Query.GetEntitiesByComponentType(includeDisabled: true); try { Entity val4 = default(Entity); for (int j = 0; j < entitiesByComponentType2.Length; j++) { Entity val3 = entitiesByComponentType2[j]; if (!val3.TryGetComponent(out CastleHeartConnection component3) || ((NetworkedEntity)(ref component3.CastleHeartEntity)).GetEntityOnServer() != val || !InventoryUtilities.TryGetInventoryEntity(Core.EntityManager, val3, ref val4, 0)) { continue; } EntityManager entityManager = Core.EntityManager; if (!((EntityManager)(ref entityManager)).HasComponent(val4)) { continue; } entityManager = Core.EntityManager; DynamicBuffer buffer = ((EntityManager)(ref entityManager)).GetBuffer(val4, false); bool flag = false; for (int k = 0; k < buffer.Length; k++) { InventoryBuffer val5 = buffer[k]; int guidHash = ((PrefabGUID)(ref val5.ItemType)).GuidHash; if (guidHash != 0 && val5.Amount > 0) { dictionary.TryGetValue(guidHash, out var value); dictionary[guidHash] = value + val5.Amount; num2 += val5.Amount; flag = true; } } if (flag) { num++; } } } finally { entitiesByComponentType2.Dispose(); } List<(int, long, string)> list = new List<(int, long, string)>(dictionary.Count); foreach (KeyValuePair item in dictionary) { list.Add((item.Key, item.Value, EntityExtensions.GetPrefabName(new PrefabGUID(item.Key)))); } list.Sort(((int guid, long qty, string name) a, (int guid, long qty, string name) b) => b.qty.CompareTo(a.qty)); List<(string, string, int)> list2 = CollectPrisoners(territoryIndex); summary = new ResourceSummary { TerritoryIndex = territoryIndex, OwnerSteamId = ownerSteamId, OwnerName = ownerName, Containers = num, TotalItems = num2, Items = list, Prisoners = list2.Count, PrisonerList = list2 }; return true; } private List<(string name, string bloodType, int bloodQuality)> CollectPrisoners(int territoryIndex) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_01b3: Unknown result type (might be due to invalid IL or missing references) //IL_01ba: Expected O, but got Unknown //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: 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_0045: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0052: 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_005b: 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_007a: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Expected O, but got Unknown //IL_0140: Unknown result type (might be due to invalid IL or missing references) //IL_0159: Unknown result type (might be due to invalid IL or missing references) List<(string, string, int)> list = new List<(string, string, int)>(); NativeArray val = default(NativeArray); bool flag = default(bool); try { val = Query.GetEntitiesByComponentType(includeDisabled: true); for (int i = 0; i < val.Length; i++) { Entity buffTarget; try { buffTarget = CreateGameplayEventServerUtility.GetBuffTarget(Core.EntityManager, val[i]); } catch { continue; } if (!buffTarget.Exists() || !buffTarget.Has()) { continue; } LocalToWorld val2 = buffTarget.Read(); if (GetTerritoryIndexAt(((LocalToWorld)(ref val2)).Position) != territoryIndex) { continue; } string text = (buffTarget.Has() ? buffTarget.Read().GetPrefabName() : "Prisoner"); string item = "-"; int item2 = -1; Blood component2; if (buffTarget.TryGetComponent(out BloodConsumeSource component)) { item = component.UnitBloodType._Value.GetPrefabName(); item2 = (int)Math.Round(component.BloodQuality); } else if (buffTarget.TryGetComponent(out component2)) { item = component2.BloodType.GetPrefabName(); item2 = (int)Math.Round(component2.Quality); } else if (Settings.VerboseLogging.Value) { ManualLogSource log = Core.Log; BepInExWarningLogInterpolatedStringHandler val3 = new BepInExWarningLogInterpolatedStringHandler(87, 3, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val3).AppendLiteral("[FAUST CASTLE] prisoner "); ((BepInExLogInterpolatedStringHandler)val3).AppendFormatted(text); ((BepInExLogInterpolatedStringHandler)val3).AppendLiteral(" has neither BloodConsumeSource nor Blood "); ((BepInExLogInterpolatedStringHandler)val3).AppendLiteral("(unitLevel="); ((BepInExLogInterpolatedStringHandler)val3).AppendFormatted(buffTarget.Has()); ((BepInExLogInterpolatedStringHandler)val3).AppendLiteral(" health="); ((BepInExLogInterpolatedStringHandler)val3).AppendFormatted(buffTarget.Has()); ((BepInExLogInterpolatedStringHandler)val3).AppendLiteral(")."); } log.LogWarning(val3); } list.Add((text, item, item2)); } } catch (Exception ex) { if (Settings.VerboseLogging.Value) { ManualLogSource log2 = Core.Log; BepInExWarningLogInterpolatedStringHandler val3 = new BepInExWarningLogInterpolatedStringHandler(40, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val3).AppendLiteral("[FAUST CASTLE] CollectPrisoners failed: "); ((BepInExLogInterpolatedStringHandler)val3).AppendFormatted(ex.Message); } log2.LogWarning(val3); } } finally { if (val.IsCreated) { val.Dispose(); } } return list; } private unsafe TerritoryInfo BuildInfo(Entity territoryEntity, CastleTerritory ct, bool extras = false) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0036: 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_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001c: 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_0023: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0125: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_013b: Unknown result type (might be due to invalid IL or missing references) //IL_0143: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Unknown result type (might be due to invalid IL or missing references) //IL_015a: Unknown result type (might be due to invalid IL or missing references) //IL_01b9: Unknown result type (might be due to invalid IL or missing references) //IL_0192: Unknown result type (might be due to invalid IL or missing references) //IL_019d: Unknown result type (might be due to invalid IL or missing references) //IL_01a6: Unknown result type (might be due to invalid IL or missing references) int sizeBlocks = 0; EntityManager entityManager = Core.EntityManager; if (((EntityManager)(ref entityManager)).HasComponent(territoryEntity)) { entityManager = Core.EntityManager; sizeBlocks = ((EntityManager)(ref entityManager)).GetBuffer(territoryEntity, false).Length; } string region = ResolveTerritoryRegion(territoryEntity); Entity castleHeart = ct.CastleHeart; if (!castleHeart.Exists()) { return new TerritoryInfo { TerritoryIndex = ct.CastleTerritoryIndex, HasHeart = false, Region = region, SizeBlocks = sizeBlocks, State = PlotState.Unclaimed, DecaySeconds = 0L, HeartLevel = -1, Floors = -1, ClaimedUnix = -1L, ClanName = null, TotalItems = -1L }; } CastleHeart val = castleHeart.Read(); ulong ownerSteamId = 0uL; string ownerName = "Unknown"; bool ownerOnline = false; long ownerLastConnected = 0L; Entity ownerUserEntity = Entity.Null; if (castleHeart.TryGetComponent(out UserOwner component)) { Entity entityOnServer = ((NetworkedEntity)(ref component.Owner)).GetEntityOnServer(); if (entityOnServer.TryGetComponent(out User component2)) { ownerUserEntity = entityOnServer; ownerSteamId = component2.PlatformId; ownerName = ((object)(*(FixedString64Bytes*)(&component2.CharacterName))/*cast due to .constrained prefix*/).ToString(); ownerOnline = component2.IsConnected; ownerLastConnected = component2.TimeLastConnected; } } PlotState state; long decaySeconds; if (double.IsPositiveInfinity(val.FuelEndTime)) { state = PlotState.Sealed; decaySeconds = -1L; } else { double val2 = FuelTimeRemaining(val); state = ((val.FuelEndTime - Core.ServerTime > 0.0 || val.FuelQuantity > 0) ? PlotState.Fueled : PlotState.Decaying); decaySeconds = (long)Math.Max(0.0, val2); } int heartLevel = -1; int floors = -1; long totalItems = -1L; string clanName = null; if (extras) { ComputeHeartExtras(castleHeart, out floors, out totalItems); heartLevel = ResolveHeartLevel(castleHeart); clanName = ResolveOwnerClan(ownerUserEntity); } return new TerritoryInfo { TerritoryIndex = ct.CastleTerritoryIndex, HasHeart = true, OwnerSteamId = ownerSteamId, OwnerName = ownerName, OwnerOnline = ownerOnline, OwnerLastConnected = ownerLastConnected, Region = region, SizeBlocks = sizeBlocks, State = state, DecaySeconds = decaySeconds, HeartLevel = heartLevel, Floors = floors, ClaimedUnix = -1L, ClanName = clanName, TotalItems = totalItems }; } private void ComputeHeartExtras(Entity heart, out int floors, out long totalItems) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0152: Unknown result type (might be due to invalid IL or missing references) //IL_0159: Expected O, but got Unknown //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) floors = -1; totalItems = -1L; try { HashSet hashSet = new HashSet(); long num = 0L; bool flag = false; NativeArray entitiesByComponentType = Query.GetEntitiesByComponentType(includeDisabled: true); try { Entity val3 = default(Entity); for (int i = 0; i < entitiesByComponentType.Length; i++) { Entity val = entitiesByComponentType[i]; if (!val.TryGetComponent(out CastleHeartConnection component) || ((NetworkedEntity)(ref component.CastleHeartEntity)).GetEntityOnServer() != heart) { continue; } if (val.Has() && val.Has()) { LocalToWorld val2 = val.Read(); hashSet.Add((int)math.round(((LocalToWorld)(ref val2)).Position.y)); } if (!InventoryUtilities.TryGetInventoryEntity(Core.EntityManager, val, ref val3, 0)) { continue; } EntityManager entityManager = Core.EntityManager; if (!((EntityManager)(ref entityManager)).HasComponent(val3)) { continue; } flag = true; entityManager = Core.EntityManager; DynamicBuffer buffer = ((EntityManager)(ref entityManager)).GetBuffer(val3, false); for (int j = 0; j < buffer.Length; j++) { InventoryBuffer val4 = buffer[j]; if (((PrefabGUID)(ref val4.ItemType)).GuidHash != 0 && val4.Amount > 0) { num += val4.Amount; } } } } finally { entitiesByComponentType.Dispose(); } if (hashSet.Count > 0) { floors = hashSet.Count; } if (flag) { totalItems = num; } } catch (Exception ex) { if (Settings.VerboseLogging.Value) { ManualLogSource log = Core.Log; bool flag2 = default(bool); BepInExWarningLogInterpolatedStringHandler val5 = new BepInExWarningLogInterpolatedStringHandler(42, 1, ref flag2); if (flag2) { ((BepInExLogInterpolatedStringHandler)val5).AppendLiteral("[FAUST CASTLE] ComputeHeartExtras failed: "); ((BepInExLogInterpolatedStringHandler)val5).AppendFormatted(ex.Message); } log.LogWarning(val5); } } } private int ResolveHeartLevel(Entity heart) { return -1; } private unsafe string ResolveOwnerClan(Entity ownerUserEntity) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Expected O, but got Unknown //IL_0020: 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_004a: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) try { if (!ownerUserEntity.Exists() || !ownerUserEntity.TryGetComponent(out User component)) { return null; } ref NetworkedEntity clanEntity = ref component.ClanEntity; object obj = NetworkedEntity.Empty; if (((object)Unsafe.As(ref clanEntity)/*cast due to .constrained prefix*/).Equals(obj)) { return null; } Entity entityOnServer = ((NetworkedEntity)(ref component.ClanEntity)).GetEntityOnServer(); if (entityOnServer.Exists() && entityOnServer.Has()) { ClanTeam val = entityOnServer.Read(); string text = ((object)(*(FixedString64Bytes*)(&val.Name))/*cast due to .constrained prefix*/).ToString(); return string.IsNullOrWhiteSpace(text) ? null : text; } } catch (Exception ex) { if (Settings.VerboseLogging.Value) { ManualLogSource log = Core.Log; bool flag = default(bool); BepInExWarningLogInterpolatedStringHandler val2 = new BepInExWarningLogInterpolatedStringHandler(40, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("[FAUST CASTLE] ResolveOwnerClan failed: "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(ex.Message); } log.LogWarning(val2); } } return null; } private static double FuelTimeRemaining(CastleHeart ch) { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) float num = Mathf.Min(Core.ServerGameSettingsSystem.Settings.CastleBloodEssenceDrainModifier, 3f); if (num <= 0f) { num = 1f; } double num2 = 480f / num; return ch.FuelEndTime - Core.ServerTime + num2 * (double)ch.FuelQuantity; } public unsafe static string RegionName(WorldRegionType region) { return Regex.Replace(((object)(*(WorldRegionType*)(®ion))/*cast due to .constrained prefix*/).ToString().Replace("_", ""), "(? "sealed", PlotState.Fueled => "fueled", PlotState.Decaying => "decaying", _ => "unclaimed", }; } } internal sealed class ClanService { public readonly struct ClanInfo { public string Name { get; init; } public int Members { get; init; } public int Online { get; init; } public int Castles { get; init; } public string Leader { get; init; } } public readonly struct Composition { public int Clans { get; init; } public int Clanned { get; init; } public int Independent { get; init; } public int OnlineClanned { get; init; } public int OnlineIndependent { get; init; } public int Largest { get; init; } public double AvgSize { get; init; } public List ClanList { get; init; } } public readonly struct ClanMember { public string Name { get; init; } public bool Online { get; init; } public bool Leader { get; init; } } public unsafe List GetClanMembers(string name) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002e: 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_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Invalid comparison between Unknown and I4 //IL_00fb: 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_0122: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrWhiteSpace(name)) { return null; } List result = null; NativeArray entitiesByComponentType = Query.GetEntitiesByComponentType(includeDisabled: true); try { for (int i = 0; i < entitiesByComponentType.Length; i++) { Entity val = entitiesByComponentType[i]; EntityManager entityManager = Core.EntityManager; if (!((EntityManager)(ref entityManager)).HasComponent(val)) { continue; } ClanTeam val2 = val.Read(); string text = ((object)(*(FixedString64Bytes*)(&val2.Name))/*cast due to .constrained prefix*/).ToString(); if (!string.Equals(text, name, StringComparison.OrdinalIgnoreCase) && !string.Equals(Wire.Safe(text), name, StringComparison.OrdinalIgnoreCase)) { continue; } entityManager = Core.EntityManager; DynamicBuffer buffer = ((EntityManager)(ref entityManager)).GetBuffer(val, false); entityManager = Core.EntityManager; ? val3; if (!((EntityManager)(ref entityManager)).HasComponent(val)) { val3 = default(DynamicBuffer); } else { entityManager = Core.EntityManager; val3 = ((EntityManager)(ref entityManager)).GetBuffer(val, false); } DynamicBuffer val4 = (DynamicBuffer)val3; List list = new List(buffer.Length); for (int j = 0; j < buffer.Length; j++) { bool leader = (int)buffer[j].ClanRole == 2; string name2 = null; bool online = false; if (val4.IsCreated && j < val4.Length && val4[j].UserEntity.TryGetComponent(out User component)) { name2 = ((object)(*(FixedString64Bytes*)(&component.CharacterName))/*cast due to .constrained prefix*/).ToString(); online = component.IsConnected; } list.Add(new ClanMember { Name = name2, Online = online, Leader = leader }); } list.Sort(delegate(ClanMember a, ClanMember b) { if (a.Leader != b.Leader) { if (!a.Leader) { return 1; } return -1; } return (a.Online != b.Online) ? ((!a.Online) ? 1 : (-1)) : string.Compare(a.Name, b.Name, StringComparison.OrdinalIgnoreCase); }); result = list; break; } } finally { entitiesByComponentType.Dispose(); } return result; } public unsafe Dictionary GetPlayerClanNames() { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0019: 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_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0035: 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_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0042: 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_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) Dictionary dictionary = new Dictionary(); NativeArray entitiesByComponentType = Query.GetEntitiesByComponentType(includeDisabled: true); try { for (int i = 0; i < entitiesByComponentType.Length; i++) { Entity val = entitiesByComponentType[i]; EntityManager entityManager = Core.EntityManager; if (!((EntityManager)(ref entityManager)).HasComponent(val)) { continue; } entityManager = Core.EntityManager; if (!((EntityManager)(ref entityManager)).HasComponent(val)) { continue; } ClanTeam val2 = val.Read(); string value = ((object)(*(FixedString64Bytes*)(&val2.Name))/*cast due to .constrained prefix*/).ToString(); entityManager = Core.EntityManager; DynamicBuffer buffer = ((EntityManager)(ref entityManager)).GetBuffer(val, false); for (int j = 0; j < buffer.Length; j++) { if (buffer[j].UserEntity.TryGetComponent(out User component)) { dictionary[component.PlatformId] = value; } } } return dictionary; } finally { entitiesByComponentType.Dispose(); } } public unsafe Composition GetComposition() { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_010c: 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) //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_0118: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Unknown result type (might be due to invalid IL or missing references) //IL_0128: Unknown result type (might be due to invalid IL or missing references) //IL_012d: Unknown result type (might be due to invalid IL or missing references) //IL_0131: Unknown result type (might be due to invalid IL or missing references) //IL_0134: Unknown result type (might be due to invalid IL or missing references) //IL_0139: Unknown result type (might be due to invalid IL or missing references) //IL_0147: Unknown result type (might be due to invalid IL or missing references) //IL_014c: Unknown result type (might be due to invalid IL or missing references) //IL_0150: Unknown result type (might be due to invalid IL or missing references) //IL_0165: Unknown result type (might be due to invalid IL or missing references) //IL_016a: Unknown result type (might be due to invalid IL or missing references) //IL_016e: Unknown result type (might be due to invalid IL or missing references) //IL_0171: Unknown result type (might be due to invalid IL or missing references) //IL_015b: Unknown result type (might be due to invalid IL or missing references) //IL_0161: Unknown result type (might be due to invalid IL or missing references) //IL_0176: 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_01fb: Unknown result type (might be due to invalid IL or missing references) //IL_019f: Unknown result type (might be due to invalid IL or missing references) //IL_01a4: Unknown result type (might be due to invalid IL or missing references) //IL_0221: Unknown result type (might be due to invalid IL or missing references) //IL_0223: Unknown result type (might be due to invalid IL or missing references) //IL_0228: Unknown result type (might be due to invalid IL or missing references) //IL_01b2: Unknown result type (might be due to invalid IL or missing references) //IL_01c5: Unknown result type (might be due to invalid IL or missing references) //IL_01ca: Unknown result type (might be due to invalid IL or missing references) //IL_01d0: Invalid comparison between Unknown and I4 int num = 0; int num2 = 0; int num3 = 0; int num4 = 0; NativeArray entitiesByComponentType = Query.GetEntitiesByComponentType(includeDisabled: true); try { for (int i = 0; i < entitiesByComponentType.Length; i++) { if (!entitiesByComponentType[i].TryGetComponent(out User component)) { continue; } ref NetworkedEntity clanEntity = ref component.ClanEntity; object obj = NetworkedEntity.Empty; if (((object)Unsafe.As(ref clanEntity)/*cast due to .constrained prefix*/).Equals(obj)) { num2++; if (component.IsConnected) { num4++; } } else { num++; if (component.IsConnected) { num3++; } } } } finally { entitiesByComponentType.Dispose(); } Dictionary dictionary = new Dictionary(); NativeArray entitiesByComponentType2 = Query.GetEntitiesByComponentType(includeDisabled: true); try { for (int j = 0; j < entitiesByComponentType2.Length; j++) { if (entitiesByComponentType2[j].TryGetComponent(out Team component2)) { dictionary.TryGetValue(component2.Value, out var value); dictionary[component2.Value] = value + 1; } } } finally { entitiesByComponentType2.Dispose(); } List list = new List(); int num5 = 0; NativeArray entitiesByComponentType3 = Query.GetEntitiesByComponentType(includeDisabled: true); try { for (int k = 0; k < entitiesByComponentType3.Length; k++) { Entity val = entitiesByComponentType3[k]; EntityManager entityManager = Core.EntityManager; if (!((EntityManager)(ref entityManager)).HasComponent(val)) { continue; } entityManager = Core.EntityManager; DynamicBuffer buffer = ((EntityManager)(ref entityManager)).GetBuffer(val, false); if (buffer.Length == 0) { continue; } entityManager = Core.EntityManager; ? val2; if (!((EntityManager)(ref entityManager)).HasComponent(val)) { val2 = default(DynamicBuffer); } else { entityManager = Core.EntityManager; val2 = ((EntityManager)(ref entityManager)).GetBuffer(val, false); } DynamicBuffer val3 = (DynamicBuffer)val2; int num6 = 0; string leader = ""; for (int l = 0; l < buffer.Length; l++) { if (val3.IsCreated && l < val3.Length && val3[l].UserEntity.TryGetComponent(out User component3)) { if (component3.IsConnected) { num6++; } if ((int)buffer[l].ClanRole == 2) { leader = ((object)(*(FixedString64Bytes*)(&component3.CharacterName))/*cast due to .constrained prefix*/).ToString(); } } } int value2; int castles = (dictionary.TryGetValue(val.Read().TeamValue, out value2) ? value2 : 0); ClanInfo item = default(ClanInfo); ClanTeam val4 = val.Read(); item.Name = ((object)(*(FixedString64Bytes*)(&val4.Name))/*cast due to .constrained prefix*/).ToString(); item.Members = buffer.Length; item.Online = num6; item.Castles = castles; item.Leader = leader; list.Add(item); if (buffer.Length > num5) { num5 = buffer.Length; } } } finally { entitiesByComponentType3.Dispose(); } list.Sort((ClanInfo a, ClanInfo b) => b.Members.CompareTo(a.Members)); return new Composition { Clans = list.Count, Clanned = num, Independent = num2, OnlineClanned = num3, OnlineIndependent = num4, Largest = num5, AvgSize = ((list.Count == 0) ? 0.0 : ((double)num / (double)list.Count)), ClanList = list }; } } internal static class FaustAccessGate { internal readonly struct GateResult { public bool Allowed { get; init; } public string DenyWire { get; init; } public FeatureConfig Feature { get; init; } public bool CostOwed { get; init; } public static GateResult Deny(string wire) { return new GateResult { Allowed = false, DenyWire = wire }; } public static GateResult Allow(FeatureConfig f, bool costOwed) { return new GateResult { Allowed = true, Feature = f, CostOwed = costOwed }; } } private static readonly Dictionary _lastQuery = new Dictionary(); private static double NowSeconds => (double)DateTime.UtcNow.Ticks / 10000000.0; public static GateResult TryAuthorize(ChatCommandContext ctx, string featureKey, bool bypassAccess = false) { //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_011e: Unknown result type (might be due to invalid IL or missing references) //IL_01b1: Unknown result type (might be due to invalid IL or missing references) //IL_0198: Unknown result type (might be due to invalid IL or missing references) //IL_02dc: Unknown result type (might be due to invalid IL or missing references) //IL_0233: Unknown result type (might be due to invalid IL or missing references) if (!Core.IsReady) { return GateResult.Deny("[FAUST:err] code=notready"); } if (!Settings.Enabled.Value) { return GateResult.Deny("[FAUST:err] code=disabled feature=" + featureKey); } FeatureConfig featureConfig = Settings.Feature(featureKey); if (featureConfig == null) { return GateResult.Deny("[FAUST:err] code=disabled feature=" + featureKey); } if (!Core.Control.IsAvailable(featureKey, out var code, out var secsLeft)) { return GateResult.Deny($"[FAUST:err] code={code} feature={featureKey} secs={secsLeft}"); } if (featureConfig.Access == AccessLevel.Off) { return GateResult.Deny("[FAUST:err] code=disabled feature=" + featureKey); } User user = ctx.Event.User; bool isAdmin = user.IsAdmin; bool flag = isAdmin && featureConfig.AdminsExempt.Value; if (Settings.RateLimitSeconds.Value > 0 && (!isAdmin || !Settings.RateLimitAdminsExempt.Value)) { double nowSeconds = NowSeconds; if (_lastQuery.TryGetValue(user.PlatformId, out var value)) { double num = (double)Settings.RateLimitSeconds.Value - (nowSeconds - value); if (num > 0.0) { return GateResult.Deny($"[FAUST:err] code=ratelimit feature={featureKey} secs={(int)Math.Ceiling(num)}"); } } _lastQuery[user.PlatformId] = nowSeconds; } if (!flag && !bypassAccess && !Core.Unlock.IsUnlocked(user.PlatformId, featureConfig, out var need)) { return GateResult.Deny("[FAUST:err] code=locked feature=" + featureKey + " need=" + need); } if (!bypassAccess && !flag && featureConfig.Access == AccessLevel.AdminOnly && !isAdmin) { return GateResult.Deny("[FAUST:err] code=noaccess feature=" + featureKey); } if (!flag && !PvpAllowed(featureConfig)) { return GateResult.Deny("[FAUST:err] code=pvp feature=" + featureKey); } if (!flag && featureConfig.HasProximity && !Proximity.PlayerNear(ctx.Event.SenderCharacterEntity, featureConfig.RequireNearPrefab.Value, featureConfig.RequireNearDistance.Value)) { string value2 = featureConfig.RequireNearDistance.Value.ToString("0.#", CultureInfo.InvariantCulture); return GateResult.Deny($"[FAUST:err] code=notnear feature={featureKey} item={featureConfig.RequireNearPrefab.Value} dist={value2}"); } if (!flag && !Core.Usage.Check(user.PlatformId, featureConfig, out var denyCode, out var secsLeft2)) { Core.UsageStats?.RecordCooldownHit(featureKey); return GateResult.Deny($"[FAUST:err] code={denyCode} feature={featureKey} secs={secsLeft2}"); } bool flag2 = !flag && featureConfig.HasCost; if (flag2 && !RequesterHasItem(ctx, featureConfig)) { return GateResult.Deny($"[FAUST:err] code=cost feature={featureKey} item={featureConfig.CostItemGuid.Value} qty={featureConfig.CostQuantity.Value}"); } return GateResult.Allow(featureConfig, flag2); } public static void Commit(ChatCommandContext ctx, GateResult gate) { //IL_0019: 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_0024: 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) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Expected O, but got Unknown //IL_00bd: Unknown result type (might be due to invalid IL or missing references) if (!gate.Allowed || gate.Feature == null) { return; } User user = ctx.Event.User; Core.Usage.Record(user.PlatformId, gate.Feature); if (gate.CostOwed) { ConsumeItem(ctx, gate.Feature); } Core.UsageStats?.RecordUse(gate.Feature.Key, user.PlatformId, gate.CostOwed, gate.CostOwed ? gate.Feature.CostQuantity.Value : 0); if (Settings.AuditQueries.Value) { ManualLogSource log = Core.Log; bool flag = default(bool); BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(38, 3, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[FAUST AUDIT] steamId="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(user.PlatformId); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" ran '"); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(gate.Feature.Key); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("' charged="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(gate.CostOwed); } log.LogInfo(val); } } private static bool RequesterHasItem(ChatCommandContext ctx, FeatureConfig f) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0011: 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_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) Entity senderCharacterEntity = ctx.Event.SenderCharacterEntity; Entity val = default(Entity); if (!InventoryUtilities.TryGetInventoryEntity(Core.EntityManager, senderCharacterEntity, ref val, 0)) { return false; } ServerGameManager serverGameManager = Core.ServerGameManager; return ((ServerGameManager)(ref serverGameManager)).GetInventoryItemCount(val, new PrefabGUID(f.CostItemGuid.Value)) >= f.CostQuantity.Value; } private static void ConsumeItem(ChatCommandContext ctx, FeatureConfig f) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) Entity senderCharacterEntity = ctx.Event.SenderCharacterEntity; Entity val = default(Entity); if (InventoryUtilities.TryGetInventoryEntity(Core.EntityManager, senderCharacterEntity, ref val, 0)) { ServerGameManager serverGameManager = Core.ServerGameManager; ((ServerGameManager)(ref serverGameManager)).TryRemoveInventoryItem(val, new PrefabGUID(f.CostItemGuid.Value), f.CostQuantity.Value); } } private static bool PvpAllowed(FeatureConfig f) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Invalid comparison between Unknown and I4 if (f.Availability == PvpAvailability.Always) { return true; } bool flag = (int)Core.ServerGameSettingsSystem.Settings.GameModeType == 0; if (f.Availability != PvpAvailability.PvEOnly) { return !flag; } return flag; } public static string AccessToken(FeatureConfig f, bool isAdmin) { return f.Access switch { AccessLevel.Off => "off", AccessLevel.Players => "players", AccessLevel.AdminOnly => isAdmin ? "players" : "admin", _ => "off", }; } public static string CostToken(FeatureConfig f) { if (!f.HasCost) { if (f.CooldownSeconds.Value <= 0) { return "0"; } return $"0:cd={f.CooldownSeconds.Value}"; } string text = $"{f.CostItemGuid.Value}x{f.CostQuantity.Value}"; if (f.CooldownSeconds.Value <= 0) { return text; } return $"{text}:cd={f.CooldownSeconds.Value}"; } } internal static class FaustPaths { private static string BaseDir => Path.Combine(Paths.ConfigPath, "Faust"); public static string DataDir { get { string text = Settings.DataNamespace?.Value; if (!string.IsNullOrWhiteSpace(text)) { return Path.Combine(BaseDir, SafeFolder(text)); } return BaseDir; } } public static bool IsNamespaced => !string.IsNullOrWhiteSpace(Settings.DataNamespace?.Value); private static string SafeFolder(string name) { StringBuilder stringBuilder = new StringBuilder(name.Length); string text = name.Trim(); foreach (char c in text) { stringBuilder.Append((char.IsLetterOrDigit(c) || c == '-' || c == '_') ? c : '_'); } if (stringBuilder.Length != 0) { return stringBuilder.ToString(); } return "data"; } public static long TotalDataBytes() { long num = 0L; try { string dataDir = DataDir; if (Directory.Exists(dataDir)) { string[] files = Directory.GetFiles(dataDir, "*.json"); foreach (string fileName in files) { num += new FileInfo(fileName).Length; } } } catch { } return num; } } internal sealed class FaustStore { private sealed class Session { public ulong Steam { get; set; } public long Connect { get; set; } public long Disconnect { get; set; } } private sealed class ConcPoint { public long T { get; set; } public int Count { get; set; } } private sealed class RegionSample { public long Day { get; set; } public string Region { get; set; } public int Castles { get; set; } public int Plots { get; set; } public int Players { get; set; } } private sealed class SaveFile { public int SchemaVersion { get; set; } = 1; public List Sessions { get; set; } = new List(); public List Concurrency { get; set; } = new List(); public List RegionDaily { get; set; } = new List(); public Dictionary Names { get; set; } = new Dictionary(); } public readonly struct PlayerMetrics { public long FirstSeenUnix { get; init; } public int SessionCount { get; init; } public long PlayMinutes { get; init; } public int PeakHour { get; init; } public double FreqPerWeek { get; init; } public int DaysIdle { get; init; } } public readonly struct PopulationStats { public int Dau { get; init; } public int Wau { get; init; } public int Mau { get; init; } public int NewToday { get; init; } public int ReturningToday { get; init; } public double Stickiness { get; init; } public double D1 { get; init; } public double D7 { get; init; } public double D30 { get; init; } } public readonly struct PlayerRow { public ulong Steam { get; init; } public string Name { get; init; } public bool Online { get; init; } public long LastOnlineUnix { get; init; } public bool Active24h { get; init; } public bool Active7d { get; init; } public int Sessions { get; init; } public long PlayMinutes { get; init; } public int DaysIdle { get; init; } } private readonly List _sessions = new List(); private readonly List _concurrency = new List(); private readonly List _regionDaily = new List(); private readonly Dictionary _names = new Dictionary(); private readonly HashSet _online = new HashSet(); private long _lastRegionSampleDay; private const long Hour = 3600L; private const long Day = 86400L; private const int MaxSliceIterations = 17544; private static string SaveDir => FaustPaths.DataDir; private static string SavePath => Path.Combine(SaveDir, "sessions.json"); private static long Now => DateTimeOffset.UtcNow.ToUnixTimeSeconds(); public int OnlineCount => _online.Count; public void Load() { //IL_024e: Unknown result type (might be due to invalid IL or missing references) //IL_0255: Expected O, but got Unknown //IL_01a7: Unknown result type (might be due to invalid IL or missing references) //IL_01ae: Expected O, but got Unknown bool flag = default(bool); try { if (!File.Exists(SavePath)) { return; } SaveFile saveFile = JsonSerializer.Deserialize(File.ReadAllText(SavePath)); if (saveFile == null) { return; } _sessions.Clear(); foreach (Session item in saveFile.Sessions ?? new List()) { if (item.Disconnect == 0L) { item.Disconnect = item.Connect; } _sessions.Add(item); } _concurrency.Clear(); if (saveFile.Concurrency != null) { _concurrency.AddRange(saveFile.Concurrency); } _regionDaily.Clear(); if (saveFile.RegionDaily != null) { _regionDaily.AddRange(saveFile.RegionDaily); } _names.Clear(); if (saveFile.Names != null) { foreach (KeyValuePair name in saveFile.Names) { if (ulong.TryParse(name.Key, out var result)) { _names[result] = name.Value; } } } PruneOldSessions(); PruneOldRegionDaily(); _lastRegionSampleDay = 0L; foreach (RegionSample item2 in _regionDaily) { if (item2.Day > _lastRegionSampleDay) { _lastRegionSampleDay = item2.Day; } } ManualLogSource log = Core.Log; BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(85, 4, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[FAUST STORE] loaded "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(_sessions.Count); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" session(s), "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(_concurrency.Count); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" concurrency point(s), "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(_regionDaily.Count); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" region sample(s), "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(_names.Count); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" name(s)."); } log.LogInfo(val); } catch (Exception ex) { ManualLogSource log2 = Core.Log; BepInExErrorLogInterpolatedStringHandler val2 = new BepInExErrorLogInterpolatedStringHandler(31, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("[FAUST STORE] failed loading "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(SavePath); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(": "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(ex); } log2.LogError(val2); } } private void SaveSync() { //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Expected O, but got Unknown try { Directory.CreateDirectory(SaveDir); SaveFile saveFile = new SaveFile { Sessions = _sessions, Concurrency = _concurrency, RegionDaily = _regionDaily }; foreach (KeyValuePair name in _names) { saveFile.Names[name.Key.ToString()] = name.Value; } File.WriteAllText(SavePath, JsonSerializer.Serialize(saveFile, new JsonSerializerOptions { WriteIndented = false })); } catch (Exception ex) { ManualLogSource log = Core.Log; bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(30, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[FAUST STORE] failed saving "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(SavePath); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(": "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ex); } log.LogError(val); } } public void OnConnect(ulong steam, string name) { if (steam == 0L) { return; } _online.Add(steam); bool flag = false; if (Settings.SessionTracking.Value) { if (!string.IsNullOrEmpty(name)) { _names[steam] = name; } foreach (Session session in _sessions) { if (session.Steam == steam && session.Disconnect == 0L) { session.Disconnect = Now; } } _sessions.Add(new Session { Steam = steam, Connect = Now, Disconnect = 0L }); PruneOldSessions(); flag = true; } if (RecordConcurrency()) { flag = true; } if (flag) { SaveSync(); } } public void OnDisconnect(ulong steam) { if (steam == 0L) { return; } _online.Remove(steam); bool flag = false; if (Settings.SessionTracking.Value) { long now = Now; foreach (Session session in _sessions) { if (session.Steam == steam && session.Disconnect == 0L) { session.Disconnect = now; } } flag = true; } if (RecordConcurrency()) { flag = true; } if (flag) { SaveSync(); } } public void CloseAllOpen() { long now = Now; bool flag = false; foreach (Session session in _sessions) { if (session.Disconnect == 0L) { session.Disconnect = now; flag = true; } } _online.Clear(); if (flag) { SaveSync(); } } private bool RecordConcurrency() { int value = Settings.MaxConcurrencyPoints.Value; if (!Settings.ConcurrencySampling.Value || value <= 0) { return false; } _concurrency.Add(new ConcPoint { T = Now, Count = _online.Count }); if (_concurrency.Count > value) { _concurrency.RemoveRange(0, _concurrency.Count - value); } return true; } private void PruneOldSessions() { int value = Settings.SessionRetentionDays.Value; if (value > 0) { long cutoff = Now - (long)value * 86400L; _sessions.RemoveAll((Session s) => s.Disconnect != 0L && s.Disconnect < cutoff); } } public PlayerMetrics GetMetrics(ulong steam) { long now = Now; long num = long.MaxValue; long num2 = 0L; long num3 = 0L; int num4 = 0; bool flag = false; int[] array = new int[24]; foreach (Session session in _sessions) { if (session.Steam == steam) { num4++; if (session.Connect < num) { num = session.Connect; } long num5 = ((session.Disconnect == 0L) ? now : session.Disconnect); if (session.Disconnect == 0L) { flag = true; } if (num5 > num2) { num2 = num5; } if (num5 > session.Connect) { num3 += num5 - session.Connect; } int hour = DateTimeOffset.FromUnixTimeSeconds(session.Connect).UtcDateTime.Hour; if (hour >= 0 && hour < 24) { array[hour]++; } } } if (num4 == 0) { return new PlayerMetrics { FirstSeenUnix = -1L, SessionCount = 0, PlayMinutes = -1L, PeakHour = -1, FreqPerWeek = -1.0, DaysIdle = -1 }; } int num6 = 0; for (int i = 1; i < 24; i++) { if (array[i] > array[num6]) { num6 = i; } } double num7 = Math.Max(1.0, (double)(now - num) / 604800.0); int daysIdle = (int)((!flag) ? ((now - num2) / 86400) : 0); return new PlayerMetrics { FirstSeenUnix = num, SessionCount = num4, PlayMinutes = num3 / 60, PeakHour = num6, FreqPerWeek = Math.Round((double)num4 / num7, 1), DaysIdle = daysIdle }; } public List<(ulong steam, string name, long minutes)> GetPlaytimeLeaderboard() { long now = Now; Dictionary dictionary = new Dictionary(); foreach (Session session in _sessions) { long num = ((session.Disconnect == 0L) ? now : session.Disconnect); if (num > session.Connect) { dictionary.TryGetValue(session.Steam, out var value); dictionary[session.Steam] = value + (num - session.Connect); } } string value2; return (from kvp in dictionary select (Key: kvp.Key, _names.TryGetValue(kvp.Key, out value2) ? value2 : kvp.Key.ToString(), kvp.Value / 60) into t orderby t.Item3 descending select t).ToList(); } public List<(long t, int count)> GetConcurrency(int max = 200) { int num = Math.Max(0, _concurrency.Count - max); List<(long, int)> list = new List<(long, int)>(_concurrency.Count - num); for (int i = num; i < _concurrency.Count; i++) { list.Add((_concurrency[i].T, _concurrency[i].Count)); } return list; } private static long DayFloor(long t) { return t - t % 86400; } public long[] GetHourHistogram(ulong? steam) { long now = Now; long[] array = new long[24]; foreach (Session session in _sessions) { if (!steam.HasValue || session.Steam == steam.Value) { long num = session.Connect; long num2 = ((session.Disconnect == 0L) ? now : session.Disconnect); int num3 = 0; while (num < num2 && num3++ < 17544) { int hour = DateTimeOffset.FromUnixTimeSeconds(num).UtcDateTime.Hour; long val = num - num % 3600 + 3600; long num4 = Math.Min(num2, val); array[hour] += num4 - num; num = num4; } } } long[] array2 = new long[24]; for (int i = 0; i < 24; i++) { array2[i] = array[i] / 60; } return array2; } public List<(long dayMidnightUtc, int dau, long minutes, int newCount)> GetDailySeries(int days) { long now = Now; long num = DayFloor(now) - (long)(days - 1) * 86400L; Dictionary dictionary = new Dictionary(); foreach (Session session in _sessions) { if (!dictionary.TryGetValue(session.Steam, out var value) || session.Connect < value) { dictionary[session.Steam] = session.Connect; } } long[] array = new long[days]; HashSet[] array2 = new HashSet[days]; int[] array3 = new int[days]; for (int i = 0; i < days; i++) { array2[i] = new HashSet(); } foreach (Session session2 in _sessions) { long num2 = ((session2.Disconnect == 0L) ? now : session2.Disconnect); if (num2 <= num) { continue; } long num3 = Math.Max(session2.Connect, num); int num4 = 0; while (num3 < num2 && num4++ < days + 1) { long num5 = DayFloor(num3); int num6 = (int)((num5 - num) / 86400); long num7 = Math.Min(num2, num5 + 86400); if (num6 >= 0 && num6 < days) { array[num6] += num7 - num3; if (array2[num6].Add(session2.Steam) && dictionary.TryGetValue(session2.Steam, out var value2) && DayFloor(value2) == num5) { array3[num6]++; } } num3 = num7; } } List<(long, int, long, int)> list = new List<(long, int, long, int)>(days); for (int j = 0; j < days; j++) { list.Add((num + (long)j * 86400L, array2[j].Count, array[j] / 60, array3[j])); } return list; } public List<(long dayMidnightUtc, int newPlayers)> GetNewPlayersSeries(int days) { long num = DayFloor(Now) - (long)(days - 1) * 86400L; Dictionary dictionary = new Dictionary(); foreach (Session session in _sessions) { if (!dictionary.TryGetValue(session.Steam, out var value) || session.Connect < value) { dictionary[session.Steam] = session.Connect; } } int[] array = new int[days]; foreach (long value2 in dictionary.Values) { int num2 = (int)((DayFloor(value2) - num) / 86400); if (num2 >= 0 && num2 < days) { array[num2]++; } } List<(long, int)> list = new List<(long, int)>(days); for (int i = 0; i < days; i++) { list.Add((num + (long)i * 86400L, array[i])); } return list; } public (int lt15, int m15to60, int h1to3, int gt3h) GetSessionLengthBuckets(ulong? steam) { long now = Now; int num = 0; int num2 = 0; int num3 = 0; int num4 = 0; foreach (Session session in _sessions) { if (steam.HasValue && session.Steam != steam.Value) { continue; } long num5 = ((session.Disconnect == 0L) ? now : session.Disconnect) - session.Connect; if (num5 > 0) { if (num5 < 900) { num++; } else if (num5 < 3600) { num2++; } else if (num5 < 10800) { num3++; } else { num4++; } } } return (lt15: num, m15to60: num2, h1to3: num3, gt3h: num4); } public long[] GetWeekdayHistogram(ulong? steam) { long now = Now; long[] array = new long[7]; foreach (Session session in _sessions) { if (!steam.HasValue || session.Steam == steam.Value) { long num = session.Connect; long num2 = ((session.Disconnect == 0L) ? now : session.Disconnect); int num3 = 0; while (num < num2 && num3++ < 17544) { int num4 = WeekdayMon0(num); long num5 = Math.Min(num2, DayFloor(num) + 86400); array[num4] += num5 - num; num = num5; } } } long[] array2 = new long[7]; for (int i = 0; i < 7; i++) { array2[i] = array[i] / 60; } return array2; } private static int WeekdayMon0(long unix) { return (int)(DateTimeOffset.FromUnixTimeSeconds(unix).UtcDateTime.DayOfWeek + 6) % 7; } public List<(long dayMidnightUtc, long minutes)> GetPlayerDailySeries(ulong steam, int days) { long now = Now; long num = DayFloor(now) - (long)(days - 1) * 86400L; long[] array = new long[days]; foreach (Session session in _sessions) { if (session.Steam != steam) { continue; } long num2 = ((session.Disconnect == 0L) ? now : session.Disconnect); if (num2 <= num) { continue; } long num3 = Math.Max(session.Connect, num); int num4 = 0; while (num3 < num2 && num4++ < days + 1) { long num5 = DayFloor(num3); int num6 = (int)((num5 - num) / 86400); long num7 = Math.Min(num2, num5 + 86400); if (num6 >= 0 && num6 < days) { array[num6] += num7 - num3; } num3 = num7; } } List<(long, long)> list = new List<(long, long)>(); for (int i = 0; i < days; i++) { if (array[i] > 0) { list.Add((num + (long)i * 86400L, array[i] / 60)); } } return list; } public List<(ulong steam, string name, long firstSeen, long playMinutes)> GetNewPlayersRoster(int days) { long now = Now; long num = DayFloor(now) - (long)(days - 1) * 86400L; Dictionary dictionary = new Dictionary(); Dictionary dictionary2 = new Dictionary(); foreach (Session session in _sessions) { if (!dictionary.TryGetValue(session.Steam, out var value) || session.Connect < value) { dictionary[session.Steam] = session.Connect; } long num2 = ((session.Disconnect == 0L) ? now : session.Disconnect); if (num2 > session.Connect) { dictionary2.TryGetValue(session.Steam, out var value2); dictionary2[session.Steam] = value2 + (num2 - session.Connect); } } List<(ulong, string, long, long)> list = new List<(ulong, string, long, long)>(); foreach (KeyValuePair item in dictionary) { if (item.Value >= num) { dictionary2.TryGetValue(item.Key, out var value3); list.Add((item.Key, _names.TryGetValue(item.Key, out var value4) ? value4 : item.Key.ToString(), item.Value, value3 / 60)); } } list.Sort(((ulong, string, long, long) a, (ulong, string, long, long) b) => b.Item3.CompareTo(a.Item3)); return list; } public int[] GetHourPlayerCounts(ulong? steam) { long now = Now; HashSet[] array = new HashSet[24]; for (int i = 0; i < 24; i++) { array[i] = new HashSet(); } foreach (Session session in _sessions) { if (!steam.HasValue || session.Steam == steam.Value) { long num = session.Connect; long num2 = ((session.Disconnect == 0L) ? now : session.Disconnect); int num3 = 0; while (num < num2 && num3++ < 17544) { int hour = DateTimeOffset.FromUnixTimeSeconds(num).UtcDateTime.Hour; array[hour].Add(session.Steam); num = num - num % 3600 + 3600; } } } int[] array2 = new int[24]; for (int j = 0; j < 24; j++) { array2[j] = array[j].Count; } return array2; } public List<(ulong steam, string name, long start, long end)> GetSessionTimeline(ulong? steam, int days) { long now = Now; long num = DayFloor(now) - (long)(days - 1) * 86400L; List<(ulong, string, long, long)> list = new List<(ulong, string, long, long)>(); foreach (Session session in _sessions) { if (!steam.HasValue || session.Steam == steam.Value) { long num2 = ((session.Disconnect == 0L) ? now : session.Disconnect); if (num2 > num) { list.Add((session.Steam, _names.TryGetValue(session.Steam, out var value) ? value : session.Steam.ToString(), session.Connect, num2)); } } } list.Sort(((ulong, string, long, long) a, (ulong, string, long, long) b) => a.Item3.CompareTo(b.Item3)); return list; } public List<(ulong steam, string name, int activeDays, string daysCsv)> GetActiveGrid(int days) { //IL_01ed: Unknown result type (might be due to invalid IL or missing references) //IL_01f4: Expected O, but got Unknown long now = Now; long num = DayFloor(now) - (long)(days - 1) * 86400L; Dictionary dictionary = new Dictionary(); foreach (Session session in _sessions) { long num2 = ((session.Disconnect == 0L) ? now : session.Disconnect); if (num2 <= num) { continue; } if (!dictionary.TryGetValue(session.Steam, out var value)) { value = new long[days]; dictionary[session.Steam] = value; } long num3 = Math.Max(session.Connect, num); int num4 = 0; while (num3 < num2 && num4++ < days + 1) { long num5 = DayFloor(num3); int num6 = (int)((num5 - num) / 86400); long num7 = Math.Min(num2, num5 + 86400); if (num6 >= 0 && num6 < days) { value[num6] += num7 - num3; } num3 = num7; } } List<(ulong, string, int, string)> list = new List<(ulong, string, int, string)>(dictionary.Count); bool flag = default(bool); foreach (KeyValuePair item in dictionary) { long[] value2 = item.Value; int num8 = 0; int num9 = 0; StringBuilder stringBuilder = new StringBuilder(); for (int num10 = days - 1; num10 >= 0; num10--) { long num11 = value2[num10] / 60; if (num11 > 0) { num8++; long num12 = (num + (long)num10 * 86400L) / 86400; string text = ((num9 == 0) ? "" : ",") + num12 + ":" + num11; if (stringBuilder.Length + text.Length <= 460) { stringBuilder.Append(text); num9++; } } } if (num8 == 0) { continue; } if (num9 < num8) { ManualLogSource log = Core.Log; BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(73, 3, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[FAUST STORE] activegrid: steam "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(item.Key); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" had "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(num8); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" active day(s), emitted "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(num9); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" (line cap)."); } log.LogWarning(val); } list.Add((item.Key, _names.TryGetValue(item.Key, out var value3) ? value3 : item.Key.ToString(), num8, stringBuilder.ToString())); } list.Sort(((ulong, string, int, string) a, (ulong, string, int, string) b) => b.Item3.CompareTo(a.Item3)); return list; } public bool ShouldSampleRegions() { return DayFloor(Now) > _lastRegionSampleDay; } public void RecordRegionSnapshot(IReadOnlyList<(string region, int players, int castles, int plots)> rows) { if (rows == null || rows.Count == 0) { return; } long day = DayFloor(Now); foreach (var row in rows) { object obj; if (!string.IsNullOrEmpty(row.region)) { (obj, _, _, _) = row; } else { obj = "-"; } string region = (string)obj; RegionSample regionSample = _regionDaily.Find((RegionSample x) => x.Day == day && string.Equals(x.Region, region, StringComparison.OrdinalIgnoreCase)); if (regionSample != null) { regionSample.Castles = row.castles; regionSample.Plots = row.plots; regionSample.Players = row.players; continue; } _regionDaily.Add(new RegionSample { Day = day, Region = region, Castles = row.castles, Plots = row.plots, Players = row.players }); } _lastRegionSampleDay = day; PruneOldRegionDaily(); SaveSync(); } private void PruneOldRegionDaily() { int value = Settings.SessionRetentionDays.Value; if (value > 0) { long cutoff = DayFloor(Now) - (long)value * 86400L; _regionDaily.RemoveAll((RegionSample s) => s.Day < cutoff); } } public List<(long day, string region, int castles, int plots, int players)> GetRegionDaily(int days) { long num = DayFloor(Now) - (long)(days - 1) * 86400L; List<(long, string, int, int, int)> list = new List<(long, string, int, int, int)>(); foreach (RegionSample item in _regionDaily) { if (item.Day >= num) { list.Add((item.Day, item.Region, item.Castles, item.Plots, item.Players)); } } list.Sort(((long, string, int, int, int) a, (long, string, int, int, int) b) => (a.Item1 == b.Item1) ? string.Compare(a.Item2, b.Item2, StringComparison.OrdinalIgnoreCase) : a.Item1.CompareTo(b.Item1)); return list; } private Dictionary PlayerSpans() { long now = Now; Dictionary dictionary = new Dictionary(); foreach (Session session in _sessions) { long num = ((session.Disconnect == 0L) ? now : session.Disconnect); if (!dictionary.TryGetValue(session.Steam, out var value)) { dictionary[session.Steam] = (session.Connect, num); } else { dictionary[session.Steam] = (Math.Min(value.Item1, session.Connect), Math.Max(value.Item2, num)); } } return dictionary; } public PopulationStats GetPopulationStats() { long now = Now; long num = DayFloor(now); Dictionary dictionary = PlayerSpans(); int num2 = 0; int num3 = 0; int num4 = 0; int num5 = 0; int num6 = 0; int num7 = 0; int num8 = 0; int num9 = 0; int num10 = 0; int num11 = 0; foreach (var (num12, num13) in dictionary.Values) { if (num13 >= num) { num2++; } if (num13 >= now - 604800) { num3++; } if (num13 >= now - 2592000) { num4++; } if (num12 >= num) { num5++; } if (num12 <= now - 86400) { num6++; if (num13 >= num12 + 86400) { num7++; } } if (num12 <= now - 604800) { num8++; if (num13 >= num12 + 604800) { num9++; } } if (num12 <= now - 2592000) { num10++; if (num13 >= num12 + 2592000) { num11++; } } } int returningToday = Math.Max(0, num2 - num5); return new PopulationStats { Dau = num2, Wau = num3, Mau = num4, NewToday = num5, ReturningToday = returningToday, Stickiness = ((num4 > 0) ? Math.Round((double)num2 / (double)num4, 2) : 0.0), D1 = Rate(num7, num6), D7 = Rate(num9, num8), D30 = Rate(num11, num10) }; } private static double Rate(int n, int d) { if (d <= 0) { return 0.0; } return Math.Round((double)n / (double)d, 2); } public (int seen24h, int seen7d, int seen30d, int dormant, int total) GetRecencyBuckets() { long now = Now; Dictionary dictionary = PlayerSpans(); int num = 0; int num2 = 0; int num3 = 0; foreach (var value in dictionary.Values) { long item = value.Item2; if (item >= now - 86400) { num++; } if (item >= now - 604800) { num2++; } if (item >= now - 2592000) { num3++; } } return (seen24h: num, seen7d: num2, seen30d: num3, dormant: dictionary.Count - num3, total: dictionary.Count); } public (int peak, long peakT, double avg, int p95, int now) GetConcurrencySummary(int days) { long num = ((days > 0) ? (Now - (long)days * 86400L) : 0); List list = new List(); int num2 = 0; long item = 0L; long num3 = 0L; foreach (ConcPoint item4 in _concurrency) { if (item4.T >= num) { list.Add(item4.Count); num3 += item4.Count; if (item4.Count > num2) { num2 = item4.Count; item = item4.T; } } } int count = list.Count; double item2 = ((count > 0) ? Math.Round((double)num3 / (double)count, 1) : 0.0); int item3 = 0; if (count > 0) { list.Sort(); int value = (int)Math.Ceiling(0.95 * (double)count) - 1; item3 = list[Math.Clamp(value, 0, count - 1)]; } return (peak: num2, peakT: item, avg: item2, p95: item3, now: _online.Count); } public List GetPlayerRoster() { long now = Now; Dictionary dictionary = new Dictionary(); foreach (Session session in _sessions) { long num = ((session.Disconnect == 0L) ? now : session.Disconnect); long num2 = Math.Max(0L, num - session.Connect); bool flag = session.Disconnect == 0; if (!dictionary.TryGetValue(session.Steam, out var value)) { dictionary[session.Steam] = (session.Connect, num, num2, 1, flag); } else { dictionary[session.Steam] = (Math.Min(value.Item1, session.Connect), Math.Max(value.Item2, num), value.Item3 + num2, value.Item4 + 1, value.Item5 || flag); } } List list = new List(dictionary.Count); foreach (KeyValuePair item in dictionary) { (long, long, long, int, bool) value2 = item.Value; bool flag2 = _online.Contains(item.Key) || value2.Item5; list.Add(new PlayerRow { Steam = item.Key, Name = (_names.TryGetValue(item.Key, out var value3) ? value3 : item.Key.ToString()), Online = flag2, LastOnlineUnix = (flag2 ? now : value2.Item2), Active24h = (value2.Item2 >= now - 86400), Active7d = (value2.Item2 >= now - 604800), Sessions = value2.Item4, PlayMinutes = value2.Item3 / 60, DaysIdle = (int)((!flag2) ? ((now - value2.Item2) / 86400) : 0) }); } list.Sort((PlayerRow x, PlayerRow y) => y.PlayMinutes.CompareTo(x.PlayMinutes)); return list; } public string GetName(ulong steam) { if (!_names.TryGetValue(steam, out var value) || string.IsNullOrEmpty(value)) { return steam.ToString(); } return value; } public (int sessions, int concurrency, int regionSamples, int names, long oldestConnectUnix) GetStorageStats() { long num = 0L; foreach (Session session in _sessions) { if (num == 0L || session.Connect < num) { num = session.Connect; } } return (sessions: _sessions.Count, concurrency: _concurrency.Count, regionSamples: _regionDaily.Count, names: _names.Count, oldestConnectUnix: num); } public int ClearOlderThan(int days) { if (days <= 0) { return 0; } long cutoff = Now - (long)days * 86400L; int num = _sessions.Count + _concurrency.Count + _regionDaily.Count; _sessions.RemoveAll((Session s) => s.Disconnect != 0L && s.Disconnect < cutoff); _concurrency.RemoveAll((ConcPoint c) => c.T < cutoff); _regionDaily.RemoveAll((RegionSample r) => r.Day < cutoff); int num2 = num - (_sessions.Count + _concurrency.Count + _regionDaily.Count); if (num2 > 0) { SaveSync(); } return num2; } public int WipeAll() { int result = _sessions.Count + _concurrency.Count + _regionDaily.Count + _names.Count; _sessions.Clear(); _concurrency.Clear(); _regionDaily.Clear(); _lastRegionSampleDay = 0L; _names.Clear(); if (Settings.SessionTracking.Value) { foreach (ulong item in _online) { _sessions.Add(new Session { Steam = item, Connect = Now, Disconnect = 0L }); } } SaveSync(); return result; } } internal sealed class FeatureControlService { private sealed class Rec { public long BlockedUntil { get; set; } public int SchedStart { get; set; } = -1; public int SchedEnd { get; set; } = -1; } private sealed class SaveFile { public int SchemaVersion { get; set; } = 1; public Dictionary Control { get; set; } = new Dictionary(); } public const string All = "all"; private readonly Dictionary _control = new Dictionary(); private static string SaveDir => FaustPaths.DataDir; private static string SavePath => Path.Combine(SaveDir, "feature_control.json"); private static long Now => DateTimeOffset.UtcNow.ToUnixTimeSeconds(); private static int NowMinuteOfDay => DateTime.Now.Hour * 60 + DateTime.Now.Minute; public void Load() { //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Expected O, but got Unknown //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Expected O, but got Unknown bool flag = default(bool); try { if (!File.Exists(SavePath)) { return; } SaveFile saveFile = JsonSerializer.Deserialize(File.ReadAllText(SavePath)); if (saveFile?.Control == null) { return; } _control.Clear(); foreach (KeyValuePair item in saveFile.Control) { _control[item.Key] = item.Value; } ManualLogSource log = Core.Log; BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(57, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[FAUST CONTROL] loaded runtime overrides for "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(_control.Count); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" feature(s)."); } log.LogInfo(val); } catch (Exception ex) { ManualLogSource log2 = Core.Log; BepInExErrorLogInterpolatedStringHandler val2 = new BepInExErrorLogInterpolatedStringHandler(33, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("[FAUST CONTROL] failed loading "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(SavePath); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(": "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(ex); } log2.LogError(val2); } } private void SaveSync() { //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Expected O, but got Unknown try { Directory.CreateDirectory(SaveDir); SaveFile saveFile = new SaveFile(); foreach (KeyValuePair item in _control) { saveFile.Control[item.Key] = item.Value; } File.WriteAllText(SavePath, JsonSerializer.Serialize(saveFile, new JsonSerializerOptions { WriteIndented = false })); } catch (Exception ex) { ManualLogSource log = Core.Log; bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(32, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[FAUST CONTROL] failed saving "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(SavePath); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(": "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ex); } log.LogError(val); } } private Rec Get(string key) { if (!_control.TryGetValue(key, out var value)) { return null; } return value; } private Rec GetOrAdd(string key) { if (!_control.TryGetValue(key, out var value)) { value = (_control[key] = new Rec()); } return value; } public bool IsAvailable(string feature, out string code, out int secsLeft) { code = null; secsLeft = 0; long now = Now; string[] array = new string[2] { "all", feature }; foreach (string key in array) { Rec rec = Get(key); if (rec == null) { continue; } if (rec.BlockedUntil == -1) { code = "blocked"; secsLeft = -1; return false; } if (rec.BlockedUntil > now) { code = "blocked"; secsLeft = (int)(rec.BlockedUntil - now); return false; } if (rec.SchedStart < 0 || rec.SchedEnd < 0) { continue; } int nowMinuteOfDay = NowMinuteOfDay; bool num; if (rec.SchedStart > rec.SchedEnd) { if (nowMinuteOfDay >= rec.SchedStart) { continue; } num = nowMinuteOfDay < rec.SchedEnd; } else { if (nowMinuteOfDay < rec.SchedStart) { goto IL_00d4; } num = nowMinuteOfDay < rec.SchedEnd; } if (num) { continue; } goto IL_00d4; IL_00d4: int num2 = rec.SchedStart - nowMinuteOfDay; if (num2 <= 0) { num2 += 1440; } code = "schedule"; secsLeft = num2 * 60; return false; } return true; } public void Block(string feature, int minutes) { GetOrAdd(feature).BlockedUntil = ((minutes <= 0) ? (-1) : (Now + (long)minutes * 60L)); SaveSync(); } public void Unblock(string feature) { if (_control.TryGetValue(feature, out var value)) { value.BlockedUntil = 0L; SaveSync(); } } public void SetSchedule(string feature, int startMin, int endMin) { Rec orAdd = GetOrAdd(feature); orAdd.SchedStart = startMin; orAdd.SchedEnd = endMin; SaveSync(); } public void ClearSchedule(string feature) { if (_control.TryGetValue(feature, out var value)) { value.SchedStart = -1; value.SchedEnd = -1; SaveSync(); } } public string Describe(string feature) { Rec rec = Get(feature); if (rec == null) { return "open"; } long now = Now; List list = new List(); if (rec.BlockedUntil == -1) { list.Add("BLOCKED (indefinite)"); } else if (rec.BlockedUntil > now) { list.Add($"BLOCKED ({(rec.BlockedUntil - now) / 60}m left)"); } if (rec.SchedStart >= 0 && rec.SchedEnd >= 0) { list.Add("schedule " + Hhmm(rec.SchedStart) + "-" + Hhmm(rec.SchedEnd)); } if (list.Count != 0) { return string.Join(", ", list); } return "open"; } private static string Hhmm(int min) { return $"{min / 60:00}:{min % 60:00}"; } } internal static class HeatmapSampler { private static bool _started; public static void Start() { //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Expected O, but got Unknown if (_started) { return; } _started = true; try { Core.StartCoroutine(SampleLoop()); Core.Log.LogInfo((object)"[FAUST HEATMAP] position sampler running (coroutine)."); } catch (Exception ex) { _started = false; ManualLogSource log = Core.Log; bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(41, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[FAUST HEATMAP] failed to start sampler: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ex); } log.LogError(val); } } private static IEnumerator SampleLoop() { yield return (object)new WaitForSeconds(5f); while (true) { int num = Math.Clamp(Settings.HeatmapSampleSeconds.Value, 30, 300); SampleOnce(); yield return (object)new WaitForSeconds((float)num); } } private static void SampleOnce() { //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_0121: Expected O, but got Unknown //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Expected O, but got Unknown bool flag = default(bool); try { if (!Core.IsReady || Core.Heatmap == null || !Settings.HeatmapEnabled.Value) { return; } List onlinePositions = Core.PlayerInfo.GetOnlinePositions(); if (onlinePositions.Count == 0) { return; } List<(ulong, float, float)> list = new List<(ulong, float, float)>(onlinePositions.Count); foreach (PlayerInfoService.PlayerPosition item in onlinePositions) { if (item.SteamId != 0L) { list.Add((item.SteamId, item.X, item.Z)); } } Core.Heatmap.RecordSamples(list, Settings.HeatmapCellSize.Value, Settings.HeatmapMaxCells.Value, Settings.HeatmapRetentionDays.Value); if (Settings.VerboseLogging.Value) { ManualLogSource log = Core.Log; BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(37, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[FAUST HEATMAP] sampled "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(list.Count); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" position(s)."); } log.LogInfo(val); } } catch (Exception ex) { ManualLogSource log2 = Core.Log; BepInExErrorLogInterpolatedStringHandler val2 = new BepInExErrorLogInterpolatedStringHandler(31, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("[FAUST HEATMAP] sample failed: "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(ex.Message); } log2.LogError(val2); } } } internal sealed class HeatmapStore { private sealed class CellRow { public ulong Steam { get; set; } public int Cx { get; set; } public int Cz { get; set; } public int Count { get; set; } } private sealed class DailyCellRow { public int Day { get; set; } public ulong Steam { get; set; } public int Cx { get; set; } public int Cz { get; set; } public int Count { get; set; } } private sealed class SaveFile { public int SchemaVersion { get; set; } = 2; public float CellSize { get; set; } public long Samples { get; set; } public List Cells { get; set; } = new List(); public List Daily { get; set; } = new List(); } public readonly struct HeatmapView { public List<(int cx, int cz, int count)> Cells { get; init; } public long Samples { get; init; } public float CellSize { get; init; } public int MinCx { get; init; } public int MinCz { get; init; } public int MaxCx { get; init; } public int MaxCz { get; init; } } private readonly Dictionary> _grid = new Dictionary>(); private readonly Dictionary>> _daily = new Dictionary>>(); private long _samples; private int _cellCount; private int _dailyCellCount; private float _cellSize; private int _dirtyTicks; private bool _cappedLogged; private bool _dailyCappedLogged; private const int SaveEveryTicks = 5; private static string SavePath => Path.Combine(FaustPaths.DataDir, "heatmap.json"); private static int Today => (int)(DateTime.UtcNow - DateTime.UnixEpoch).TotalDays; private static long Pack(int cx, int cz) { return ((long)cx << 32) | (uint)cz; } private static int Cx(long key) { return (int)(key >> 32); } private static int Cz(long key) { return (int)key; } public void Load() { //IL_02c5: Unknown result type (might be due to invalid IL or missing references) //IL_02cc: Expected O, but got Unknown //IL_01f6: Unknown result type (might be due to invalid IL or missing references) //IL_01fd: Expected O, but got Unknown bool flag = default(bool); try { if (!File.Exists(SavePath)) { return; } SaveFile saveFile = JsonSerializer.Deserialize(File.ReadAllText(SavePath)); if (saveFile == null) { return; } _grid.Clear(); _daily.Clear(); _cellCount = 0; _dailyCellCount = 0; _cellSize = saveFile.CellSize; _samples = saveFile.Samples; foreach (CellRow item in saveFile.Cells ?? new List()) { if (!_grid.TryGetValue(item.Steam, out var value)) { value = new Dictionary(); _grid[item.Steam] = value; } long key = Pack(item.Cx, item.Cz); if (!value.ContainsKey(key)) { _cellCount++; } value[key] = item.Count; } foreach (DailyCellRow item2 in saveFile.Daily ?? new List()) { if (!_daily.TryGetValue(item2.Day, out var value2)) { value2 = new Dictionary>(); _daily[item2.Day] = value2; } if (!value2.TryGetValue(item2.Steam, out var value3)) { value3 = new Dictionary(); value2[item2.Steam] = value3; } long key2 = Pack(item2.Cx, item2.Cz); if (!value3.ContainsKey(key2)) { _dailyCellCount++; } value3[key2] = item2.Count; } PruneDaily(Settings.HeatmapRetentionDays.Value); ManualLogSource log = Core.Log; BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(106, 6, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[FAUST HEATMAP] loaded "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(_cellCount); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" all-time cell(s) over "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(_grid.Count); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" player(s), "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(_dailyCellCount); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" daily cell(s) over "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(_daily.Count); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" day(s), "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(_samples); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" sample(s) @ cell="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(_cellSize); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("."); } log.LogInfo(val); } catch (Exception ex) { ManualLogSource log2 = Core.Log; BepInExErrorLogInterpolatedStringHandler val2 = new BepInExErrorLogInterpolatedStringHandler(33, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("[FAUST HEATMAP] failed loading "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(SavePath); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(": "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(ex); } log2.LogError(val2); } } public void Flush() { //IL_0208: Unknown result type (might be due to invalid IL or missing references) //IL_020f: Expected O, but got Unknown _dirtyTicks = 0; try { Directory.CreateDirectory(FaustPaths.DataDir); SaveFile saveFile = new SaveFile { CellSize = _cellSize, Samples = _samples }; foreach (KeyValuePair> item in _grid) { foreach (KeyValuePair item2 in item.Value) { saveFile.Cells.Add(new CellRow { Steam = item.Key, Cx = Cx(item2.Key), Cz = Cz(item2.Key), Count = item2.Value }); } } foreach (KeyValuePair>> item3 in _daily) { foreach (KeyValuePair> item4 in item3.Value) { foreach (KeyValuePair item5 in item4.Value) { saveFile.Daily.Add(new DailyCellRow { Day = item3.Key, Steam = item4.Key, Cx = Cx(item5.Key), Cz = Cz(item5.Key), Count = item5.Value }); } } } File.WriteAllText(SavePath, JsonSerializer.Serialize(saveFile, new JsonSerializerOptions { WriteIndented = false })); } catch (Exception ex) { ManualLogSource log = Core.Log; bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(32, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[FAUST HEATMAP] failed saving "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(SavePath); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(": "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ex); } log.LogError(val); } } public void RecordSamples(IReadOnlyList<(ulong steam, float x, float z)> samples, float configCellSize, int maxCells, int retentionDays) { //IL_013b: Unknown result type (might be due to invalid IL or missing references) //IL_0142: Expected O, but got Unknown //IL_0204: Unknown result type (might be due to invalid IL or missing references) //IL_020b: Expected O, but got Unknown if (samples == null || samples.Count == 0) { return; } if (_cellSize <= 0f) { _cellSize = ((configCellSize > 0f) ? configCellSize : 25f); } float cellSize = _cellSize; int today = Today; PruneDaily(retentionDays); if (!_daily.TryGetValue(today, out var value)) { value = new Dictionary>(); _daily[today] = value; } bool flag = default(bool); foreach (var sample in samples) { if (sample.steam == 0L) { continue; } int cx = (int)Math.Floor(sample.x / cellSize); int cz = (int)Math.Floor(sample.z / cellSize); long key = Pack(cx, cz); if (!_grid.TryGetValue(sample.steam, out var value2)) { value2 = new Dictionary(); _grid[sample.steam] = value2; } if (value2.TryGetValue(key, out var value3)) { value2[key] = value3 + 1; } else { if (maxCells > 0 && _cellCount >= maxCells) { if (!_cappedLogged) { ManualLogSource log = Core.Log; BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(72, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[FAUST HEATMAP] cell cap ("); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(maxCells); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(") reached — only existing cells keep counting."); } log.LogWarning(val); _cappedLogged = true; } continue; } value2[key] = 1; _cellCount++; } _samples++; if (!value.TryGetValue(sample.steam, out var value4)) { value4 = new Dictionary(); value[sample.steam] = value4; } if (value4.TryGetValue(key, out var value5)) { value4[key] = value5 + 1; } else if (maxCells <= 0 || _dailyCellCount < maxCells) { value4[key] = 1; _dailyCellCount++; } else if (!_dailyCappedLogged) { ManualLogSource log2 = Core.Log; BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(107, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[FAUST HEATMAP] daily cell cap ("); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(maxCells); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(") reached — windowed maps stop adding new cells until older days prune out."); } log2.LogWarning(val); _dailyCappedLogged = true; } } if (++_dirtyTicks >= 5) { Flush(); } } private void PruneDaily(int retentionDays) { if (retentionDays <= 0) { return; } int num = Today - retentionDays + 1; if (_daily.Count == 0) { return; } List list = null; foreach (int key in _daily.Keys) { if (key < num) { (list ?? (list = new List())).Add(key); } } if (list == null) { return; } foreach (int item in list) { if (_daily.TryGetValue(item, out var value)) { foreach (Dictionary value2 in value.Values) { _dailyCellCount -= value2.Count; } } _daily.Remove(item); } if (_dailyCellCount < 0) { _dailyCellCount = 0; } _dailyCappedLogged = false; } public HeatmapView GetHeatmap(ulong? steam, int days = 0) { Dictionary dictionary = new Dictionary(); if (days <= 0) { if (steam.HasValue) { if (_grid.TryGetValue(steam.Value, out var value)) { foreach (KeyValuePair item in value) { dictionary[item.Key] = item.Value; } } } else { foreach (Dictionary value6 in _grid.Values) { foreach (KeyValuePair item2 in value6) { dictionary.TryGetValue(item2.Key, out var value2); dictionary[item2.Key] = value2 + item2.Value; } } } } else { int num = Today - days + 1; foreach (KeyValuePair>> item3 in _daily) { if (item3.Key < num) { continue; } if (steam.HasValue) { if (!item3.Value.TryGetValue(steam.Value, out var value3)) { continue; } foreach (KeyValuePair item4 in value3) { dictionary.TryGetValue(item4.Key, out var value4); dictionary[item4.Key] = value4 + item4.Value; } continue; } foreach (Dictionary value7 in item3.Value.Values) { foreach (KeyValuePair item5 in value7) { dictionary.TryGetValue(item5.Key, out var value5); dictionary[item5.Key] = value5 + item5.Value; } } } } long num2 = 0L; int num3 = 0; int num4 = 0; int num5 = 0; int num6 = 0; bool flag = true; List<(int, int, int)> list = new List<(int, int, int)>(dictionary.Count); foreach (KeyValuePair item6 in dictionary) { int num7 = Cx(item6.Key); int num8 = Cz(item6.Key); list.Add((num7, num8, item6.Value)); num2 += item6.Value; if (flag) { num3 = (num5 = num7); num4 = (num6 = num8); flag = false; continue; } if (num7 < num3) { num3 = num7; } if (num7 > num5) { num5 = num7; } if (num8 < num4) { num4 = num8; } if (num8 > num6) { num6 = num8; } } list.Sort(((int, int, int) a, (int, int, int) b) => b.Item3.CompareTo(a.Item3)); return new HeatmapView { Cells = list, Samples = num2, CellSize = ((_cellSize <= 0f) ? Settings.HeatmapCellSize.Value : _cellSize), MinCx = num3, MinCz = num4, MaxCx = num5, MaxCz = num6 }; } public (int cells, long samples, int players) GetStats() { return (cells: _cellCount, samples: _samples, players: _grid.Count); } public int WipeAll() { int cellCount = _cellCount; _grid.Clear(); _daily.Clear(); _cellCount = 0; _dailyCellCount = 0; _samples = 0L; _cellSize = 0f; _cappedLogged = false; _dailyCappedLogged = false; Flush(); return cellCount; } } internal sealed class KillTrackingService { private sealed class KillBucket { public int Kills { get; set; } public int Pvp { get; set; } } private sealed class SaveFile { public int SchemaVersion { get; set; } = 1; public Dictionary PlayerKills { get; set; } = new Dictionary(); public Dictionary BossKills { get; set; } = new Dictionary(); } private sealed class MemKill { public int Kills; public int Pvp; } public readonly struct KillRow { public ulong Steam { get; init; } public int Kills { get; init; } public int Pvp { get; init; } } public readonly struct BossKillRow { public int Guid { get; init; } public int Count { get; init; } } private readonly Dictionary> _players = new Dictionary>(); private readonly Dictionary> _bosses = new Dictionary>(); private bool _dirty; private const long Day = 86400L; private static string SaveDir => FaustPaths.DataDir; private static string SavePath => Path.Combine(SaveDir, "kills.json"); private static long Now => DateTimeOffset.UtcNow.ToUnixTimeSeconds(); public static bool Enabled => Settings.KillTracking.Value; public (int playerBuckets, int bossBuckets) BucketCounts { get { int num = 0; foreach (Dictionary value in _players.Values) { num += value.Count; } int num2 = 0; foreach (Dictionary value2 in _bosses.Values) { num2 += value2.Count; } return (playerBuckets: num, bossBuckets: num2); } } private static long DayFloor(long t) { return t - t % 86400; } public void Load() { //IL_01ed: Unknown result type (might be due to invalid IL or missing references) //IL_01f4: Expected O, but got Unknown //IL_0185: Unknown result type (might be due to invalid IL or missing references) //IL_018c: Expected O, but got Unknown bool flag = default(bool); try { if (!File.Exists(SavePath)) { return; } SaveFile saveFile = JsonSerializer.Deserialize(File.ReadAllText(SavePath)); if (saveFile == null) { return; } _players.Clear(); _bosses.Clear(); foreach (KeyValuePair item in saveFile.PlayerKills ?? new Dictionary()) { if (SplitKey(item.Key, out var bar) && ulong.TryParse(item.Key.Substring(0, bar), out var result) && long.TryParse(item.Key.Substring(bar + 1), out var result2)) { MemKill memKill = PlayerBucket(result, result2); memKill.Kills = item.Value.Kills; memKill.Pvp = item.Value.Pvp; } } foreach (KeyValuePair item2 in saveFile.BossKills ?? new Dictionary()) { if (SplitKey(item2.Key, out var bar2) && int.TryParse(item2.Key.Substring(0, bar2), out var result3) && long.TryParse(item2.Key.Substring(bar2 + 1), out var result4)) { BossDays(result3)[result4] = item2.Value; } } Prune(); ManualLogSource log = Core.Log; BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(60, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[FAUST KILLS] loaded kill tallies for "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(_players.Count); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" player(s), "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(_bosses.Count); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" boss(es)."); } log.LogInfo(val); } catch (Exception ex) { ManualLogSource log2 = Core.Log; BepInExErrorLogInterpolatedStringHandler val2 = new BepInExErrorLogInterpolatedStringHandler(31, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("[FAUST KILLS] failed loading "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(SavePath); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(": "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(ex); } log2.LogError(val2); } } private static bool SplitKey(string key, out int bar) { bar = key.LastIndexOf('|'); return bar > 0; } private void SaveSync() { //IL_01d6: Unknown result type (might be due to invalid IL or missing references) //IL_01dd: Expected O, but got Unknown try { Directory.CreateDirectory(SaveDir); SaveFile saveFile = new SaveFile(); long key; foreach (var (value, dictionary2) in _players) { foreach (KeyValuePair item in dictionary2) { item.Deconstruct(out key, out var value2); long value3 = key; MemKill memKill = value2; saveFile.PlayerKills[$"{value}|{value3}"] = new KillBucket { Kills = memKill.Kills, Pvp = memKill.Pvp }; } } foreach (KeyValuePair> boss in _bosses) { boss.Deconstruct(out var key2, out var value4); int value5 = key2; foreach (KeyValuePair item2 in value4) { item2.Deconstruct(out key, out key2); long value6 = key; int value7 = key2; saveFile.BossKills[$"{value5}|{value6}"] = value7; } } File.WriteAllText(SavePath, JsonSerializer.Serialize(saveFile, new JsonSerializerOptions { WriteIndented = false })); } catch (Exception ex) { ManualLogSource log = Core.Log; bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(30, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[FAUST KILLS] failed saving "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(SavePath); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(": "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ex); } log.LogError(val); } } private MemKill PlayerBucket(ulong steam, long day) { if (!_players.TryGetValue(steam, out var value)) { value = (_players[steam] = new Dictionary()); } if (!value.TryGetValue(day, out var value2)) { value2 = (value[day] = new MemKill()); } return value2; } private Dictionary BossDays(int guid) { if (!_bosses.TryGetValue(guid, out var value)) { value = (_bosses[guid] = new Dictionary()); } return value; } private void Prune() { int value = Settings.SessionRetentionDays.Value; if (value <= 0) { return; } long cutoff = DayFloor(Now) - (long)value * 86400L; foreach (Dictionary value2 in _players.Values) { RemoveOld(value2, cutoff); } foreach (Dictionary value3 in _bosses.Values) { RemoveOld(value3, cutoff); } } private static void RemoveOld(Dictionary byDay, long cutoff) { List list = null; foreach (long key in byDay.Keys) { if (key < cutoff) { (list ?? (list = new List())).Add(key); } } if (list == null) { return; } foreach (long item in list) { byDay.Remove(item); } } public void RecordKill(ulong killerSteam, bool victimWasPlayer, int vbloodGuid) { if (!Enabled) { return; } long num = DayFloor(Now); if (killerSteam != 0L) { MemKill memKill = PlayerBucket(killerSteam, num); memKill.Kills++; if (victimWasPlayer) { memKill.Pvp++; } _dirty = true; } if (vbloodGuid != 0) { Dictionary dictionary = BossDays(vbloodGuid); dictionary.TryGetValue(num, out var value); dictionary[num] = value + 1; _dirty = true; } } public void Flush() { if (_dirty) { _dirty = false; Prune(); SaveSync(); } } private static long CutoffFor(int days) { if (days > 0) { return DayFloor(Now) - (long)(days - 1) * 86400L; } return long.MinValue; } public List GetTopKillers(int days) { long num = CutoffFor(days); List list = new List(); foreach (KeyValuePair> player in _players) { player.Deconstruct(out var key, out var value); ulong steam = key; Dictionary dictionary = value; int num2 = 0; int num3 = 0; foreach (var (num5, memKill2) in dictionary) { if (num5 >= num) { num2 += memKill2.Kills; num3 += memKill2.Pvp; } } if (num2 > 0) { list.Add(new KillRow { Steam = steam, Kills = num2, Pvp = num3 }); } } list.Sort((KillRow a, KillRow b) => b.Kills.CompareTo(a.Kills)); return list; } public List GetBossKills(int days) { long num = CutoffFor(days); List list = new List(); foreach (KeyValuePair> boss in _bosses) { boss.Deconstruct(out var key, out var value); int guid = key; Dictionary dictionary = value; int num2 = 0; foreach (KeyValuePair item in dictionary) { item.Deconstruct(out var key2, out key); long num3 = key2; int num4 = key; if (num3 >= num) { num2 += num4; } } if (num2 > 0) { list.Add(new BossKillRow { Guid = guid, Count = num2 }); } } list.Sort((BossKillRow a, BossKillRow b) => b.Count.CompareTo(a.Count)); return list; } public int WipeAll() { var (num, num2) = BucketCounts; _players.Clear(); _bosses.Clear(); SaveSync(); return num + num2; } } internal sealed class MapMarkerService { private readonly HashSet _marked = new HashSet(); private bool _active; private long _expiresAt; public bool Active => _active; private static long Now => DateTimeOffset.UtcNow.ToUnixTimeSeconds(); public string Enable(int minutes) { if (!Settings.MapMarkersEnabled.Value) { return "Map markers are EXPERIMENTAL and disabled. Set [Faust.MapMarkers] Enabled=true in the config (a TEST server is strongly recommended — this can crash a live server) and restart."; } _active = true; _expiresAt = ((minutes > 0) ? (Now + (long)minutes * 60L) : 0); int num = 0; int num2 = 0; foreach (ulong item in OnlineSteamIds()) { num++; if (TryAttach(item)) { num2++; } } string value = ((minutes > 0) ? $", auto-off in {minutes} min" : ""); return $"Player map markers ON{value}. Attached {num2}/{num} online player(s). " + "EXPERIMENTAL — open the map to verify, watch the console, and confirm visibility is admin-only before relying on it (default marker is ally-visible — see [Faust.MapMarkers] MarkerPrefabGuid)."; } public string Disable() { int count = _marked.Count; foreach (ulong item in new List(_marked)) { TryDetach(item); } _marked.Clear(); _active = false; _expiresAt = 0L; return $"Player map markers OFF; cleared {count} marker(s)."; } public string Describe() { if (Settings.MapMarkersEnabled.Value) { if (!_active) { return "off"; } return $"ON, {_marked.Count} marked{((_expiresAt > 0) ? $", expires in {Math.Max(0L, _expiresAt - Now) / 60}m" : "")}"; } return "disabled (set [Faust.MapMarkers] Enabled=true to use)"; } public void TickExpiry() { if (_active && _expiresAt > 0 && Now >= _expiresAt) { Disable(); } } public void OnPlayerConnect(ulong steam) { TickExpiry(); if (_active) { TryAttach(steam); } } public void OnPlayerDisconnect(ulong steam) { _marked.Remove(steam); } public void Shutdown() { if (_active || _marked.Count > 0) { Disable(); } } private static IEnumerable OnlineSteamIds() { foreach (Entity item in Query.GetUsersOnline()) { if (item.TryGetComponent(out User component) && component.PlatformId != 0L) { yield return component.PlatformId; } } } private static bool TryResolveCharacter(ulong steam, out Entity character) { //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_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_003d: 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_0047: Unknown result type (might be due to invalid IL or missing references) character = Entity.Null; foreach (Entity item in Query.GetUsersOnline()) { if (item.TryGetComponent(out User component) && component.PlatformId == steam) { Entity entityOnServer = ((NetworkedEntity)(ref component.LocalCharacter)).GetEntityOnServer(); if (entityOnServer.Exists()) { character = entityOnServer; return true; } return false; } } return false; } private bool TryAttach(ulong steam) { //IL_019e: Unknown result type (might be due to invalid IL or missing references) //IL_01a5: Expected O, but got Unknown //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Expected O, but got Unknown //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: 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_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_0123: Unknown result type (might be due to invalid IL or missing references) //IL_012b: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Unknown result type (might be due to invalid IL or missing references) //IL_0131: Unknown result type (might be due to invalid IL or missing references) //IL_015c: Unknown result type (might be due to invalid IL or missing references) //IL_0163: Expected O, but got Unknown if (_marked.Contains(steam)) { return false; } bool flag = default(bool); try { if (!TryResolveCharacter(steam, out var character)) { return false; } PrefabGUID val = default(PrefabGUID); ((PrefabGUID)(ref val))..ctor(Settings.MapMarkerPrefabGuid.Value); Entity val2 = default(Entity); if (!Core.PrefabCollectionSystem._PrefabGuidToEntityMap.TryGetValue(val, ref val2)) { ManualLogSource log = Core.Log; BepInExWarningLogInterpolatedStringHandler val3 = new BepInExWarningLogInterpolatedStringHandler(66, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val3).AppendLiteral("[FAUST MAP] marker prefab "); ((BepInExLogInterpolatedStringHandler)val3).AppendFormatted(((PrefabGUID)(ref val)).GuidHash); ((BepInExLogInterpolatedStringHandler)val3).AppendLiteral(" not found in the prefab map; skipping "); ((BepInExLogInterpolatedStringHandler)val3).AppendFormatted(steam); ((BepInExLogInterpolatedStringHandler)val3).AppendLiteral("."); } log.LogWarning(val3); return false; } EntityManager entityManager = Core.EntityManager; DynamicBuffer val4; if (!((EntityManager)(ref entityManager)).HasBuffer(character)) { entityManager = Core.EntityManager; val4 = ((EntityManager)(ref entityManager)).AddBuffer(character); } else { entityManager = Core.EntityManager; val4 = ((EntityManager)(ref entityManager)).GetBuffer(character, false); } DynamicBuffer val5 = val4; AttachMapIconsToEntity val6; for (int i = 0; i < val5.Length; i++) { val6 = val5[i]; if (((PrefabGUID)(ref val6.Prefab)).Equals(val)) { _marked.Add(steam); return false; } } val6 = new AttachMapIconsToEntity { Prefab = val }; val5.Add(val6); _marked.Add(steam); if (Settings.VerboseLogging.Value) { ManualLogSource log2 = Core.Log; BepInExInfoLogInterpolatedStringHandler val7 = new BepInExInfoLogInterpolatedStringHandler(32, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val7).AppendLiteral("[FAUST MAP] attached marker to "); ((BepInExLogInterpolatedStringHandler)val7).AppendFormatted(steam); ((BepInExLogInterpolatedStringHandler)val7).AppendLiteral("."); } log2.LogInfo(val7); } return true; } catch (Exception ex) { ManualLogSource log3 = Core.Log; BepInExErrorLogInterpolatedStringHandler val8 = new BepInExErrorLogInterpolatedStringHandler(47, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val8).AppendLiteral("[FAUST MAP] attach failed for "); ((BepInExLogInterpolatedStringHandler)val8).AppendFormatted(steam); ((BepInExLogInterpolatedStringHandler)val8).AppendLiteral(" (experimental): "); ((BepInExLogInterpolatedStringHandler)val8).AppendFormatted(ex.Message); } log3.LogError(val8); return false; } } private void TryDetach(ulong steam) { //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Expected O, but got Unknown //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0042: 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_004a: 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_0051: 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) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) _marked.Remove(steam); try { if (!TryResolveCharacter(steam, out var character)) { return; } EntityManager entityManager = Core.EntityManager; if (!((EntityManager)(ref entityManager)).HasBuffer(character)) { return; } PrefabGUID val = default(PrefabGUID); ((PrefabGUID)(ref val))..ctor(Settings.MapMarkerPrefabGuid.Value); entityManager = Core.EntityManager; DynamicBuffer buffer = ((EntityManager)(ref entityManager)).GetBuffer(character, false); for (int num = buffer.Length - 1; num >= 0; num--) { AttachMapIconsToEntity val2 = buffer[num]; if (((PrefabGUID)(ref val2.Prefab)).Equals(val)) { buffer.RemoveAt(num); } } } catch (Exception ex) { ManualLogSource log = Core.Log; bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val3 = new BepInExErrorLogInterpolatedStringHandler(47, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val3).AppendLiteral("[FAUST MAP] detach failed for "); ((BepInExLogInterpolatedStringHandler)val3).AppendFormatted(steam); ((BepInExLogInterpolatedStringHandler)val3).AppendLiteral(" (experimental): "); ((BepInExLogInterpolatedStringHandler)val3).AppendFormatted(ex.Message); } log.LogError(val3); } } } internal sealed class PlayerInfoService { public readonly struct PlayerSnapshot { public ulong SteamId { get; init; } public string Name { get; init; } public bool Online { get; init; } public long LastConnected { get; init; } public long FirstSeenUnix { get; init; } public int Sessions { get; init; } public long PlayMinutes { get; init; } public int PeakHour { get; init; } public double FreqPerWeek { get; init; } public int DaysIdle { get; init; } } public readonly struct PlayerPosition { public ulong SteamId { get; init; } public string Name { get; init; } public float X { get; init; } public float Z { get; init; } public int TerritoryIndex { get; init; } public string Region { get; init; } } public bool TryGetPlayer(ulong steamId, out PlayerSnapshot snapshot) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0025: 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) snapshot = default(PlayerSnapshot); NativeArray entitiesByComponentType = Query.GetEntitiesByComponentType(includeDisabled: true); try { for (int i = 0; i < entitiesByComponentType.Length; i++) { if (entitiesByComponentType[i].TryGetComponent(out User component) && component.PlatformId == steamId) { snapshot = Build(component); return true; } } } finally { entitiesByComponentType.Dispose(); } return false; } private unsafe static PlayerSnapshot Build(User u) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) FaustStore.PlayerMetrics metrics = Core.Store.GetMetrics(u.PlatformId); return new PlayerSnapshot { SteamId = u.PlatformId, Name = ((object)(*(FixedString64Bytes*)(&u.CharacterName))/*cast due to .constrained prefix*/).ToString(), Online = u.IsConnected, LastConnected = u.TimeLastConnected, FirstSeenUnix = metrics.FirstSeenUnix, Sessions = metrics.SessionCount, PlayMinutes = metrics.PlayMinutes, PeakHour = metrics.PeakHour, FreqPerWeek = metrics.FreqPerWeek, DaysIdle = metrics.DaysIdle }; } public unsafe List GetOnlinePositions() { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0021: 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_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) List list = new List(); foreach (Entity item in Query.GetUsersOnline()) { User val = item.Read(); if (((NetworkedEntity)(ref val.LocalCharacter)).GetEntityOnServer().TryGetComponent(out LocalToWorld component)) { float3 position = ((LocalToWorld)(ref component)).Position; int territoryIndexAt = Core.Castle.GetTerritoryIndexAt(position); string region = Core.Castle.GetWorldRegionName(position) ?? Core.Castle.GetRegionForTerritory(territoryIndexAt); list.Add(new PlayerPosition { SteamId = val.PlatformId, Name = ((object)(*(FixedString64Bytes*)(&val.CharacterName))/*cast due to .constrained prefix*/).ToString(), X = position.x, Z = position.z, TerritoryIndex = territoryIndexAt, Region = region }); } } return list; } } internal static class PrefabLookup { public static string Name(int guid) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) return EntityExtensions.GetPrefabName(new PrefabGUID(guid)); } public static bool Exists(int guid) { string text = Name(guid); if (!string.IsNullOrEmpty(text)) { return !text.StartsWith("PrefabGuid(", StringComparison.Ordinal); } return false; } public static List<(int guid, string name)> Search(string fragment, int max, out bool capped) { //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Expected O, but got Unknown //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0037: 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_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0041: 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) capped = false; List<(int, string)> list = new List<(int, string)>(); if (string.IsNullOrWhiteSpace(fragment) || Core.PrefabCollectionSystem == null) { return list; } try { Enumerator enumerator = Core.PrefabCollectionSystem._PrefabGuidToEntityMap.GetEnumerator(); while (enumerator.MoveNext()) { PrefabGUID key = enumerator.Current.Key; string prefabName = key.GetPrefabName(); if (prefabName.IndexOf(fragment, StringComparison.OrdinalIgnoreCase) >= 0) { list.Add((key._Value, prefabName)); if (list.Count >= max) { capped = true; break; } } } } catch (Exception ex) { ManualLogSource log = Core.Log; bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(30, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[FAUST PREFAB] search failed: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ex.Message); } log.LogError(val); } list.Sort(((int, string) a, (int, string) b) => string.CompareOrdinal(a.Item2, b.Item2)); return list; } } internal static class Proximity { public static bool PlayerNear(Entity character, int prefabHash, float distance) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0025: 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_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0054: 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) if (prefabHash == 0) { return true; } if (!character.TryGetComponent(out LocalToWorld component)) { return false; } float3 position = ((LocalToWorld)(ref component)).Position; float num = distance * distance; NativeArray entitiesByComponentType = Query.GetEntitiesByComponentType(includeDisabled: true); try { for (int i = 0; i < entitiesByComponentType.Length; i++) { Entity entity = entitiesByComponentType[i]; PrefabGUID prefabGuid = entity.GetPrefabGuid(); if (((PrefabGUID)(ref prefabGuid)).GuidHash == prefabHash && entity.TryGetComponent(out LocalToWorld component2) && math.distancesq(position, ((LocalToWorld)(ref component2)).Position) <= num) { return true; } } } finally { entitiesByComponentType.Dispose(); } return false; } } internal static class RegionStats { public static List<(string region, int players, int castles, int plots)> Gather() { Dictionary dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (PlayerInfoService.PlayerPosition onlinePosition in Core.PlayerInfo.GetOnlinePositions()) { string key = (string.IsNullOrEmpty(onlinePosition.Region) ? "-" : onlinePosition.Region); dictionary.TryGetValue(key, out var value); dictionary[key] = (value.Item1 + 1, value.Item2, value.Item3); } foreach (CastleService.TerritoryInfo allTerritory in Core.Castle.GetAllTerritories()) { string key2 = (string.IsNullOrEmpty(allTerritory.Region) ? "-" : allTerritory.Region); dictionary.TryGetValue(key2, out var value2); dictionary[key2] = (value2.Item1, value2.Item2 + (allTerritory.HasHeart ? 1 : 0), value2.Item3 + 1); } List<(string, int, int, int)> list = new List<(string, int, int, int)>(dictionary.Count); foreach (KeyValuePair item in dictionary) { list.Add(((item.Key == "-") ? null : item.Key, item.Value.Item1, item.Value.Item2, item.Value.Item3)); } list.Sort(((string, int, int, int) a, (string, int, int, int) b) => b.Item3.CompareTo(a.Item3)); return list; } } internal sealed class UnlockService { private sealed class SaveFile { public int SchemaVersion { get; set; } = 1; public Dictionary> Defeated { get; set; } = new Dictionary>(); public Dictionary> Grants { get; set; } = new Dictionary>(); } private readonly Dictionary> _defeated = new Dictionary>(); private readonly Dictionary> _grants = new Dictionary>(); private bool _tracks; private static string SaveDir => FaustPaths.DataDir; private static string SavePath => Path.Combine(SaveDir, "feature_unlocks.json"); public bool TracksUnlocks => _tracks; public void Load() { //IL_01ba: Unknown result type (might be due to invalid IL or missing references) //IL_01c1: Expected O, but got Unknown //IL_0152: Unknown result type (might be due to invalid IL or missing references) //IL_0159: Expected O, but got Unknown _tracks = false; foreach (FeatureConfig value in Settings.Features.Values) { if (value.HasUnlock) { _tracks = true; break; } } bool flag = default(bool); try { if (!File.Exists(SavePath)) { return; } SaveFile saveFile = JsonSerializer.Deserialize(File.ReadAllText(SavePath)); if (saveFile == null) { return; } _defeated.Clear(); foreach (KeyValuePair> item in saveFile.Defeated ?? new Dictionary>()) { if (ulong.TryParse(item.Key, out var result)) { _defeated[result] = new HashSet(item.Value); } } _grants.Clear(); foreach (KeyValuePair> item2 in saveFile.Grants ?? new Dictionary>()) { if (ulong.TryParse(item2.Key, out var result2)) { _grants[result2] = new HashSet(item2.Value); } } ManualLogSource log = Core.Log; BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(58, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[FAUST UNLOCK] loaded defeats for "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(_defeated.Count); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" player(s), grants for "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(_grants.Count); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("."); } log.LogInfo(val); } catch (Exception ex) { ManualLogSource log2 = Core.Log; BepInExErrorLogInterpolatedStringHandler val2 = new BepInExErrorLogInterpolatedStringHandler(32, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("[FAUST UNLOCK] failed loading "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(SavePath); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(": "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(ex); } log2.LogError(val2); } } private void SaveSync() { //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Expected O, but got Unknown try { Directory.CreateDirectory(SaveDir); SaveFile saveFile = new SaveFile(); foreach (KeyValuePair> item in _defeated) { saveFile.Defeated[item.Key.ToString()] = new List(item.Value); } foreach (KeyValuePair> grant in _grants) { saveFile.Grants[grant.Key.ToString()] = new List(grant.Value); } File.WriteAllText(SavePath, JsonSerializer.Serialize(saveFile, new JsonSerializerOptions { WriteIndented = false })); } catch (Exception ex) { ManualLogSource log = Core.Log; bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(31, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[FAUST UNLOCK] failed saving "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(SavePath); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(": "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ex); } log.LogError(val); } } public void RecordBossDefeat(ulong steam, int vbloodGuid) { if (steam != 0L && vbloodGuid != 0) { if (!_defeated.TryGetValue(steam, out var value)) { value = (_defeated[steam] = new HashSet()); } if (value.Add(vbloodGuid)) { SaveSync(); } } } public bool HasDefeatedBoss(ulong steam, int vbloodGuid) { if (_defeated.TryGetValue(steam, out var value)) { return value.Contains(vbloodGuid); } return false; } public bool IsDefeatedAnywhere(int vbloodGuid) { foreach (HashSet value in _defeated.Values) { if (value.Contains(vbloodGuid)) { return true; } } return false; } public HashSet AllDefeatedGuids() { HashSet hashSet = new HashSet(); foreach (HashSet value in _defeated.Values) { hashSet.UnionWith(value); } return hashSet; } public bool BeatFinalBoss(ulong steam) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) if (!_defeated.TryGetValue(steam, out var value)) { return false; } foreach (int item in value) { if (EntityExtensions.GetPrefabName(new PrefabGUID(item)).IndexOf("Dracula", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } } return false; } public bool IsGranted(ulong steam, string feature) { if (_grants.TryGetValue(steam, out var value)) { return value.Contains(feature); } return false; } public bool Grant(ulong steam, string feature) { if (!_grants.TryGetValue(steam, out var value)) { value = (_grants[steam] = new HashSet()); } if (!value.Add(feature)) { return false; } SaveSync(); return true; } public bool Revoke(ulong steam, string feature) { if (!_grants.TryGetValue(steam, out var value) || !value.Remove(feature)) { return false; } SaveSync(); return true; } public bool IsUnlocked(ulong steam, FeatureConfig f, out string need) { (UnlockKind Kind, int Guid) unlock = f.Unlock; UnlockKind item = unlock.Kind; int item2 = unlock.Guid; need = null; if (item == UnlockKind.None) { return true; } if (IsGranted(steam, f.Key)) { return true; } switch (item) { case UnlockKind.BossKill: need = "bosskill"; return HasDefeatedBoss(steam, item2); case UnlockKind.FinalBoss: need = "finalboss"; return BeatFinalBoss(steam); default: need = "grant"; return false; } } public int GrantedCount(string feature) { int num = 0; foreach (HashSet value in _grants.Values) { if (value.Contains(feature)) { num++; } } return num; } public int UnlockedCount(FeatureConfig f) { if (f.Unlock.Kind == UnlockKind.None) { return -1; } HashSet hashSet = new HashSet(_defeated.Keys); foreach (ulong key in _grants.Keys) { hashSet.Add(key); } int num = 0; foreach (ulong item in hashSet) { if (IsUnlocked(item, f, out var _)) { num++; } } return num; } public string Describe(ulong steam) { HashSet value2; int value = (_defeated.TryGetValue(steam, out value2) ? value2.Count : 0); HashSet value4; string value3 = (_grants.TryGetValue(steam, out value4) ? string.Join(", ", value4) : "(none)"); return $"V-blood defeats: {value}; granted features: {value3}"; } public (int players, int defeats, int grants) GetUnlockStats() { int num = 0; foreach (HashSet value in _defeated.Values) { num += value.Count; } int num2 = 0; foreach (HashSet value2 in _grants.Values) { num2 += value2.Count; } HashSet hashSet = new HashSet(_defeated.Keys); foreach (ulong key in _grants.Keys) { hashSet.Add(key); } return (players: hashSet.Count, defeats: num, grants: num2); } public int WipeAll() { int item = GetUnlockStats().players; _defeated.Clear(); _grants.Clear(); SaveSync(); return item; } } internal sealed class UsageService { private sealed class Rec { public long LastUse { get; set; } public long PeriodStart { get; set; } public int UsesThisPeriod { get; set; } public long WindowStart { get; set; } } private sealed class SaveFile { public int SchemaVersion { get; set; } = 1; public Dictionary Usage { get; set; } = new Dictionary(); } private readonly Dictionary _usage = new Dictionary(); private static string SaveDir => FaustPaths.DataDir; private static string SavePath => Path.Combine(SaveDir, "feature_usage.json"); private static long Now => DateTimeOffset.UtcNow.ToUnixTimeSeconds(); public int UsageCount => _usage.Count; private static string KeyOf(ulong steam, string feature) { return $"{steam}|{feature}"; } public void Load() { //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Expected O, but got Unknown //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Expected O, but got Unknown bool flag = default(bool); try { if (!File.Exists(SavePath)) { return; } SaveFile saveFile = JsonSerializer.Deserialize(File.ReadAllText(SavePath)); if (saveFile?.Usage == null) { return; } _usage.Clear(); foreach (KeyValuePair item in saveFile.Usage) { _usage[item.Key] = item.Value; } ManualLogSource log = Core.Log; BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(63, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[FAUST USAGE] loaded usage state for "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(_usage.Count); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" (player,feature) pair(s)."); } log.LogInfo(val); } catch (Exception ex) { ManualLogSource log2 = Core.Log; BepInExErrorLogInterpolatedStringHandler val2 = new BepInExErrorLogInterpolatedStringHandler(31, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("[FAUST USAGE] failed loading "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(SavePath); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(": "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(ex); } log2.LogError(val2); } } private void SaveSync() { //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Expected O, but got Unknown try { Directory.CreateDirectory(SaveDir); SaveFile saveFile = new SaveFile(); foreach (KeyValuePair item in _usage) { saveFile.Usage[item.Key] = item.Value; } File.WriteAllText(SavePath, JsonSerializer.Serialize(saveFile, new JsonSerializerOptions { WriteIndented = false })); } catch (Exception ex) { ManualLogSource log = Core.Log; bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(30, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[FAUST USAGE] failed saving "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(SavePath); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(": "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ex); } log.LogError(val); } } private static bool WindowOpen(Rec r, FeatureConfig f, long now) { if (f.WindowSeconds.Value > 0 && r.WindowStart > 0) { return now - r.WindowStart < f.WindowSeconds.Value; } return false; } public bool Check(ulong steam, FeatureConfig f, out string denyCode, out int secsLeft) { denyCode = null; secsLeft = 0; if (!_usage.TryGetValue(KeyOf(steam, f.Key), out var value)) { return true; } long now = Now; bool flag = WindowOpen(value, f, now); if (f.CooldownSeconds.Value > 0 && value.LastUse > 0 && !flag) { int num = (int)(f.CooldownSeconds.Value - (now - value.LastUse)); if (num > 0) { denyCode = "cooldown"; secsLeft = num; return false; } } if (f.HasWindowPolicy) { if (value.PeriodStart <= 0 || now - value.PeriodStart >= f.PeriodSeconds.Value) { return true; } if (flag) { return true; } int value2 = f.MaxUsesPerPeriod.Value; if (value2 > 0 && value.UsesThisPeriod >= value2) { secsLeft = (int)(f.PeriodSeconds.Value - (now - value.PeriodStart)); denyCode = "window"; if (secsLeft < 0) { secsLeft = 0; } return false; } } return true; } public void Record(ulong steam, FeatureConfig f) { if (f.CooldownSeconds.Value <= 0 && !f.HasWindowPolicy) { return; } long now = Now; string key = KeyOf(steam, f.Key); if (!_usage.TryGetValue(key, out var value)) { value = (_usage[key] = new Rec()); } value.LastUse = now; if (f.HasWindowPolicy) { if (value.PeriodStart <= 0 || now - value.PeriodStart >= f.PeriodSeconds.Value) { value.PeriodStart = now; value.UsesThisPeriod = 0; value.WindowStart = 0L; } if (f.WindowSeconds.Value > 0) { if (value.WindowStart <= 0 || now - value.WindowStart >= f.WindowSeconds.Value) { value.WindowStart = now; value.UsesThisPeriod++; } } else { value.UsesThisPeriod++; } } SaveSync(); } public int WipeAll() { int count = _usage.Count; _usage.Clear(); SaveSync(); return count; } } internal sealed class UsageStatsService { private sealed class Bucket { public int Uses { get; set; } public long ItemSpent { get; set; } public int CooldownHits { get; set; } public List Payers { get; set; } = new List(); } private sealed class SaveFile { public int SchemaVersion { get; set; } = 1; public Dictionary Stats { get; set; } = new Dictionary(); } private sealed class MemBucket { public int Uses; public int CooldownHits; public long ItemSpent; public readonly HashSet Payers = new HashSet(); } public readonly struct UsageRow { public string Feature { get; init; } public int Uses { get; init; } public int Payers { get; init; } public long ItemSpent { get; init; } public int CooldownHits { get; init; } } private readonly Dictionary> _byFeature = new Dictionary>(); private const long Day = 86400L; private static string SaveDir => FaustPaths.DataDir; private static string SavePath => Path.Combine(SaveDir, "feature_usage_stats.json"); private static long Now => DateTimeOffset.UtcNow.ToUnixTimeSeconds(); public int BucketCount { get { int num = 0; foreach (Dictionary value in _byFeature.Values) { num += value.Count; } return num; } } private static long DayFloor(long t) { return t - t % 86400; } public void Load() { //IL_01a4: Unknown result type (might be due to invalid IL or missing references) //IL_01ab: Expected O, but got Unknown //IL_015a: Unknown result type (might be due to invalid IL or missing references) //IL_0161: Expected O, but got Unknown bool flag = default(bool); try { if (!File.Exists(SavePath)) { return; } SaveFile saveFile = JsonSerializer.Deserialize(File.ReadAllText(SavePath)); if (saveFile?.Stats == null) { return; } _byFeature.Clear(); foreach (KeyValuePair stat in saveFile.Stats) { int num = stat.Key.LastIndexOf('|'); if (num <= 0 || !long.TryParse(stat.Key.Substring(num + 1), out var result)) { continue; } string feature = stat.Key.Substring(0, num); MemBucket bucket = GetBucket(feature, result); bucket.Uses = stat.Value.Uses; bucket.ItemSpent = stat.Value.ItemSpent; bucket.CooldownHits = stat.Value.CooldownHits; foreach (ulong item in stat.Value.Payers ?? new List()) { bucket.Payers.Add(item); } } Prune(); ManualLogSource log = Core.Log; BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(56, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[FAUST USAGESTATS] loaded usage tallies for "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(_byFeature.Count); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" feature(s)."); } log.LogInfo(val); } catch (Exception ex) { ManualLogSource log2 = Core.Log; BepInExErrorLogInterpolatedStringHandler val2 = new BepInExErrorLogInterpolatedStringHandler(36, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("[FAUST USAGESTATS] failed loading "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(SavePath); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(": "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(ex); } log2.LogError(val2); } } private void SaveSync() { //IL_013a: Unknown result type (might be due to invalid IL or missing references) //IL_0141: Expected O, but got Unknown try { Directory.CreateDirectory(SaveDir); SaveFile saveFile = new SaveFile(); foreach (var (value, dictionary2) in _byFeature) { foreach (var (value2, memBucket2) in dictionary2) { saveFile.Stats[$"{value}|{value2}"] = new Bucket { Uses = memBucket2.Uses, ItemSpent = memBucket2.ItemSpent, CooldownHits = memBucket2.CooldownHits, Payers = new List(memBucket2.Payers) }; } } File.WriteAllText(SavePath, JsonSerializer.Serialize(saveFile, new JsonSerializerOptions { WriteIndented = false })); } catch (Exception ex) { ManualLogSource log = Core.Log; bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(35, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[FAUST USAGESTATS] failed saving "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(SavePath); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(": "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ex); } log.LogError(val); } } private MemBucket GetBucket(string feature, long day) { if (!_byFeature.TryGetValue(feature, out var value)) { value = (_byFeature[feature] = new Dictionary()); } if (!value.TryGetValue(day, out var value2)) { value2 = (value[day] = new MemBucket()); } return value2; } private void Prune() { int value = Settings.SessionRetentionDays.Value; if (value <= 0) { return; } long num = DayFloor(Now) - (long)value * 86400L; foreach (KeyValuePair> item in _byFeature) { List list = new List(); foreach (long key in item.Value.Keys) { if (key < num) { list.Add(key); } } foreach (long item2 in list) { item.Value.Remove(item2); } } } public void RecordUse(string feature, ulong steam, bool paid, int qtySpent) { if (string.IsNullOrEmpty(feature)) { return; } MemBucket bucket = GetBucket(feature, DayFloor(Now)); bucket.Uses++; if (paid) { if (steam != 0L) { bucket.Payers.Add(steam); } if (qtySpent > 0) { bucket.ItemSpent += qtySpent; } } Prune(); SaveSync(); } public void RecordCooldownHit(string feature) { if (!string.IsNullOrEmpty(feature)) { GetBucket(feature, DayFloor(Now)).CooldownHits++; Prune(); SaveSync(); } } public List GetUsage(int days) { days = Math.Clamp(days, 1, 365); long num = DayFloor(Now) - (long)(days - 1) * 86400L; List list = new List(); foreach (KeyValuePair> item in _byFeature) { item.Deconstruct(out var key, out var value); string feature = key; Dictionary dictionary = value; int num2 = 0; int num3 = 0; long num4 = 0L; HashSet hashSet = new HashSet(); foreach (var (num6, memBucket2) in dictionary) { if (num6 < num) { continue; } num2 += memBucket2.Uses; num3 += memBucket2.CooldownHits; num4 += memBucket2.ItemSpent; foreach (ulong payer in memBucket2.Payers) { hashSet.Add(payer); } } if (num2 != 0 || num3 != 0 || num4 != 0L) { list.Add(new UsageRow { Feature = feature, Uses = num2, Payers = hashSet.Count, ItemSpent = num4, CooldownHits = num3 }); } } list.Sort((UsageRow a, UsageRow b) => b.Uses.CompareTo(a.Uses)); return list; } public int WipeAll() { int bucketCount = BucketCount; _byFeature.Clear(); SaveSync(); return bucketCount; } } internal static class Wire { public static string Region(string region) { if (!string.IsNullOrEmpty(region)) { return Safe(region); } return "-"; } public static string Safe(string value) { if (string.IsNullOrEmpty(value)) { return "_"; } StringBuilder stringBuilder = new StringBuilder(value.Length); foreach (char c in value) { switch (c) { case ' ': stringBuilder.Append('_'); break; default: stringBuilder.Append(c); break; case '\t': case '\n': case '\r': case ':': case ';': case '=': break; } } if (stringBuilder.Length != 0) { return stringBuilder.ToString(); } return "_"; } public static bool SendPage(ChatCommandContext ctx, string cmd, IReadOnlyList rows, int page, int pageSize = 20) { int num = ((rows.Count == 0) ? 1 : ((rows.Count + pageSize - 1) / pageSize)); if (page < 1) { page = 1; } if (page > num) { page = num; } int num2 = (page - 1) * pageSize; int num3 = Math.Min(num2 + pageSize, rows.Count); for (int i = num2; i < num3; i++) { ctx.Reply(rows[i]); } ctx.Reply($"[FAUST:end] cmd={cmd} page={page}/{num} count={rows.Count}"); return rows.Count > 0; } public static bool SendList(ChatCommandContext ctx, string cmd, IReadOnlyList rows) { for (int i = 0; i < rows.Count; i++) { ctx.Reply(rows[i]); } ctx.Reply($"[FAUST:end] cmd={cmd} count={rows.Count}"); return rows.Count > 0; } } internal sealed class WorldScanService { public readonly struct AssetSnapshot { public int Guid { get; init; } public string Name { get; init; } public bool IsUnit { get; init; } public float X { get; init; } public float Z { get; init; } public string Region { get; init; } public float Hp { get; init; } public float HpMax { get; init; } public int BloodTypeGuid { get; init; } public string BloodType { get; init; } public int BloodQuality { get; init; } public int UnitCategory { get; init; } public int ResourceTier { get; init; } } public readonly struct Filter { public string Kind { get; init; } public int Id { get; init; } public int BloodType { get; init; } public int BloodQMin { get; init; } public int UnitType { get; init; } } private sealed class SaveFile { public int SchemaVersion { get; set; } = 1; public Dictionary Whitelist { get; set; } = new Dictionary(); } private const float MapLimit = 6000f; private const int SnapshotSafetyCap = 200000; private readonly Dictionary _whitelist = new Dictionary(); private readonly List _cache = new List(); private double _lastScan = double.MinValue; private bool _snapshotTruncated; private static string SaveDir => FaustPaths.DataDir; private static string SavePath => Path.Combine(SaveDir, "worldscan_whitelist.json"); private static double NowSeconds => (double)DateTime.UtcNow.Ticks / 10000000.0; public int WhitelistCount => _whitelist.Count; public void Load() { //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Expected O, but got Unknown //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Expected O, but got Unknown bool flag = File.Exists(SavePath); bool flag2 = default(bool); try { if (flag) { SaveFile saveFile = JsonSerializer.Deserialize(File.ReadAllText(SavePath)); if (saveFile?.Whitelist != null) { foreach (KeyValuePair item in saveFile.Whitelist) { if (int.TryParse(item.Key, out var result)) { _whitelist[result] = item.Value; } } } ManualLogSource log = Core.Log; BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(48, 1, ref flag2); if (flag2) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[FAUST WORLDSCAN] loaded "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(_whitelist.Count); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" whitelisted prefab(s)."); } log.LogInfo(val); } } catch (Exception ex) { ManualLogSource log2 = Core.Log; BepInExErrorLogInterpolatedStringHandler val2 = new BepInExErrorLogInterpolatedStringHandler(35, 2, ref flag2); if (flag2) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("[FAUST WORLDSCAN] failed loading "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(SavePath); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(": "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(ex); } log2.LogError(val2); } if (!flag || _whitelist.Count == 0) { int value = Seed(); Core.Log.LogInfo((object)($"[FAUST WORLDSCAN] no whitelist found — seeded {value} prefab(s) from the catalog. " + "Trim with '.faust admin worldscan remove ' / 'clear'.")); } } private void SaveSync() { //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Expected O, but got Unknown try { Directory.CreateDirectory(SaveDir); SaveFile saveFile = new SaveFile(); foreach (KeyValuePair item in _whitelist) { saveFile.Whitelist[item.Key.ToString()] = item.Value; } File.WriteAllText(SavePath, JsonSerializer.Serialize(saveFile, new JsonSerializerOptions { WriteIndented = false })); } catch (Exception ex) { ManualLogSource log = Core.Log; bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(34, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[FAUST WORLDSCAN] failed saving "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(SavePath); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(": "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ex); } log.LogError(val); } } public bool AddToWhitelist(int guid) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) if (_whitelist.ContainsKey(guid)) { return false; } _whitelist[guid] = EntityExtensions.GetPrefabName(new PrefabGUID(guid)); _lastScan = double.MinValue; SaveSync(); return true; } public bool RemoveFromWhitelist(int guid) { if (!_whitelist.Remove(guid)) { return false; } _lastScan = double.MinValue; SaveSync(); return true; } public int ClearWhitelist() { int count = _whitelist.Count; _whitelist.Clear(); _lastScan = double.MinValue; SaveSync(); return count; } public List<(int guid, string name)> ListWhitelist() { List<(int, string)> list = new List<(int, string)>(_whitelist.Count); foreach (KeyValuePair item in _whitelist) { list.Add((item.Key, item.Value)); } list.Sort(((int, string) a, (int, string) b) => string.CompareOrdinal(a.Item2, b.Item2)); return list; } public int Seed() { //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Expected O, but got Unknown try { SeedFrom(unitsOnly: true); SeedFrom(unitsOnly: false); SeedFrom(unitsOnly: false); _lastScan = double.MinValue; SaveSync(); } catch (Exception ex) { ManualLogSource log = Core.Log; bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(31, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[FAUST WORLDSCAN] seed failed: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ex.Message); } log.LogError(val); } return _whitelist.Count; } private void SeedFrom(bool unitsOnly) where T : unmanaged { //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_004f: 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) NativeArray entitiesByComponentType = Query.GetEntitiesByComponentType(includeDisabled: true, includeSpawn: false, includePrefab: true); try { for (int i = 0; i < entitiesByComponentType.Length; i++) { Entity entity = entitiesByComponentType[i]; if (unitsOnly && (entity.Has() || entity.Has() || entity.Has())) { continue; } int value = entity.GetPrefabGuid()._Value; if (value != 0 && !_whitelist.ContainsKey(value)) { string prefabName = EntityExtensions.GetPrefabName(new PrefabGUID(value)); if (!unitsOnly || prefabName.StartsWith("CHAR_", StringComparison.Ordinal)) { _whitelist[value] = prefabName; } } } } finally { entitiesByComponentType.Dispose(); } } public List GetAssets(Filter filter, out bool truncated) { int num = Math.Max(5, Settings.WorldScanInterval.Value); if (NowSeconds - _lastScan >= (double)num) { Rescan(); } List list = new List(); foreach (AssetSnapshot item in _cache) { if (Matches(item, filter)) { list.Add(item); } } int value = Settings.WorldScanMaxResults.Value; bool flag = false; if (value > 0 && list.Count > value) { list.RemoveRange(value, list.Count - value); flag = true; } truncated = _snapshotTruncated || flag; return list; } private static bool Matches(AssetSnapshot a, Filter f) { if ((f.BloodType != 0 || f.BloodQMin >= 0 || f.UnitType != int.MinValue) && !a.IsUnit) { return false; } if (f.Kind == "units" && !a.IsUnit) { return false; } if (f.Kind == "nodes" && a.IsUnit) { return false; } if (f.Id != 0 && a.Guid != f.Id) { return false; } if (f.BloodType != 0 && a.BloodTypeGuid != f.BloodType) { return false; } if (f.BloodQMin >= 0 && a.BloodQuality < f.BloodQMin) { return false; } if (f.UnitType != int.MinValue && a.UnitCategory != f.UnitType) { return false; } return true; } private void Rescan() { //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Expected O, but got Unknown //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Expected O, but got Unknown _cache.Clear(); _snapshotTruncated = false; int cap = ((Settings.WorldScanMaxResults.Value <= 0) ? int.MaxValue : 200000); bool flag = default(bool); try { ScanUnits(cap); ScanNodes(cap); ScanNodes(cap); } catch (Exception ex) { ManualLogSource log = Core.Log; BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(31, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[FAUST WORLDSCAN] scan failed: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ex.Message); } log.LogError(val); } _lastScan = NowSeconds; if (_snapshotTruncated) { ManualLogSource log2 = Core.Log; BepInExWarningLogInterpolatedStringHandler val2 = new BepInExWarningLogInterpolatedStringHandler(222, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("[FAUST WORLDSCAN] snapshot hit the "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(200000); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(" safety backstop — some assets were not captured. Set [Faust.WorldScan] MaxResults=0 for an uncapped (complete) snapshot, or trim the whitelist with '.faust admin worldscan remove/clear'."); } log2.LogWarning(val2); } } private void ScanUnits(int cap) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Unknown result type (might be due to invalid IL or missing references) //IL_015d: Unknown result type (might be due to invalid IL or missing references) //IL_0167: Unknown result type (might be due to invalid IL or missing references) //IL_0131: Unknown result type (might be due to invalid IL or missing references) //IL_0133: Unknown result type (might be due to invalid IL or missing references) //IL_013f: Unknown result type (might be due to invalid IL or missing references) //IL_0141: Unknown result type (might be due to invalid IL or missing references) //IL_014d: Unknown result type (might be due to invalid IL or missing references) //IL_0176: Unknown result type (might be due to invalid IL or missing references) //IL_01a6: Unknown result type (might be due to invalid IL or missing references) //IL_01b4: Unknown result type (might be due to invalid IL or missing references) //IL_01c7: Unknown result type (might be due to invalid IL or missing references) //IL_01dc: Unknown result type (might be due to invalid IL or missing references) //IL_01fa: Unknown result type (might be due to invalid IL or missing references) //IL_0213: Unknown result type (might be due to invalid IL or missing references) //IL_0215: Unknown result type (might be due to invalid IL or missing references) NativeArray entitiesByComponentType = Query.GetEntitiesByComponentType(includeDisabled: true); try { for (int i = 0; i < entitiesByComponentType.Length; i++) { if (_cache.Count >= cap) { _snapshotTruncated = true; break; } Entity entity = entitiesByComponentType[i]; if (entity.Has() || entity.Has() || entity.Has() || entity.Has()) { continue; } int value = entity.GetPrefabGuid()._Value; if (value == 0 || !_whitelist.ContainsKey(value)) { continue; } string prefabName = EntityExtensions.GetPrefabName(new PrefabGUID(value)); if (!prefabName.StartsWith("CHAR_", StringComparison.Ordinal) || !entity.TryGetComponent(out LocalToWorld component)) { continue; } float3 position = ((LocalToWorld)(ref component)).Position; if (!(Math.Abs(position.x) > 6000f) && !(Math.Abs(position.z) > 6000f)) { int bloodTypeGuid = 0; string bloodType = "-"; int bloodQuality = -1; Blood component3; if (entity.TryGetComponent(out BloodConsumeSource component2)) { PrefabGUID value2 = component2.UnitBloodType._Value; bloodTypeGuid = value2._Value; bloodType = value2.GetPrefabName(); bloodQuality = (int)Math.Round(component2.BloodQuality); } else if (entity.TryGetComponent(out component3)) { bloodTypeGuid = component3.BloodType._Value; bloodType = component3.BloodType.GetPrefabName(); bloodQuality = (int)Math.Round(component3.Quality); } Health component4; bool flag = entity.TryGetComponent(out component4); EntityCategory component5; int unitCategory = ((!entity.TryGetComponent(out component5)) ? (-1) : ((int)((EntityCategory)(ref component5)).UnitCategory)); _cache.Add(new AssetSnapshot { Guid = value, Name = prefabName, IsUnit = true, X = position.x, Z = position.z, Region = (Core.Castle.GetWorldRegionName(position) ?? Core.Castle.GetRegionForTerritory(Core.Castle.GetTerritoryIndexAt(position))), Hp = (flag ? component4.Value : 0f), HpMax = (flag ? component4.MaxHealth._Value : 0f), BloodTypeGuid = bloodTypeGuid, BloodType = bloodType, BloodQuality = bloodQuality, UnitCategory = unitCategory, ResourceTier = -1 }); } } } finally { entitiesByComponentType.Dispose(); } } public List Diagnose(string fragment) { //IL_027e: Unknown result type (might be due to invalid IL or missing references) //IL_0285: Expected O, but got Unknown //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: 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_0041: 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_004a: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Expected I4, but got Unknown //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Expected I4, but got Unknown //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Unknown result type (might be due to invalid IL or missing references) //IL_0154: Unknown result type (might be due to invalid IL or missing references) //IL_0170: Unknown result type (might be due to invalid IL or missing references) List list = new List(); if (string.IsNullOrWhiteSpace(fragment) || Core.PrefabCollectionSystem == null) { list.Add("Usage: .faust admin worldscandiag "); return list; } int num = 0; int num2 = 0; try { Enumerator enumerator = Core.PrefabCollectionSystem._PrefabGuidToEntityMap.GetEnumerator(); while (enumerator.MoveNext()) { KeyValue current = enumerator.Current; PrefabGUID key = current.Key; string prefabName = key.GetPrefabName(); if (prefabName.IndexOf(fragment, StringComparison.OrdinalIgnoreCase) >= 0) { num++; if (num2 < 30) { Entity value = current.Value; bool flag = value.Has(); bool flag2 = value.Has(); bool flag3 = value.Has(); bool b = value.Has(); bool b2 = value.Has(); EntityCategory component; string value2 = (value.TryGetComponent(out component) ? $"main={(int)((EntityCategory)(ref component)).MainCategory} unit={(int)((EntityCategory)(ref component)).UnitCategory} res={component.ResourceLevel._Value}" : "no-EntityCategory"); string value3 = ((flag2 || flag3) ? "node" : ((flag && prefabName.StartsWith("CHAR_", StringComparison.Ordinal)) ? "unit" : "skip")); bool flag4 = _whitelist.ContainsKey(key._Value); list.Add($"{key._Value} {prefabName} | ul={B(flag)} yDmg={B(flag2)} yPick={B(flag3)} blood={B(b)} hp={B(b2)} | {value2} | {value3}{(flag4 ? " [whitelisted]" : "")}"); num2++; } } } } catch (Exception ex) { ManualLogSource log = Core.Log; bool flag5 = default(bool); BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(31, 1, ref flag5); if (flag5) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[FAUST WORLDSCAN] diag failed: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ex.Message); } log.LogError(val); } list.Insert(0, $"'{fragment}': {num} prefab(s){((num > 30) ? $", showing {30}" : "")}. " + "Columns: guid name | flags | EntityCategory(main/unit/res) | verdict."); return list; } private static string B(bool b) { if (!b) { return "0"; } return "1"; } private void ScanNodes(int cap) where T : unmanaged { //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0033: 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_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Unknown result type (might be due to invalid IL or missing references) NativeArray entitiesByComponentType = Query.GetEntitiesByComponentType(includeDisabled: true); try { for (int i = 0; i < entitiesByComponentType.Length; i++) { if (_cache.Count >= cap) { _snapshotTruncated = true; break; } Entity entity = entitiesByComponentType[i]; int value = entity.GetPrefabGuid()._Value; if (value != 0 && _whitelist.ContainsKey(value) && entity.TryGetComponent(out LocalToWorld component)) { float3 position = ((LocalToWorld)(ref component)).Position; if (!(Math.Abs(position.x) > 6000f) && !(Math.Abs(position.z) > 6000f)) { EntityCategory component2; int resourceTier = (entity.TryGetComponent(out component2) ? component2.ResourceLevel._Value : (-1)); _cache.Add(new AssetSnapshot { Guid = value, Name = EntityExtensions.GetPrefabName(new PrefabGUID(value)), IsUnit = false, X = position.x, Z = position.z, Region = (Core.Castle.GetWorldRegionName(position) ?? Core.Castle.GetRegionForTerritory(Core.Castle.GetTerritoryIndexAt(position))), BloodType = "-", BloodQuality = -1, UnitCategory = -1, ResourceTier = resourceTier }); } } } } finally { entitiesByComponentType.Dispose(); } } } } namespace Faust.Patches { [HarmonyPatch(typeof(DeathEventListenerSystem), "OnUpdate")] internal static class DeathEventListenerSystemPatch { [HarmonyPostfix] public static void OnUpdatePostfix(DeathEventListenerSystem __instance) { //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Expected O, but got Unknown //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0048: 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_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0056: 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) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) if (!Core.IsReady) { return; } bool flag = Core.Unlock != null && Core.Unlock.TracksUnlocks; bool flag2 = KillTrackingService.Enabled && Core.Kills != null; if (!flag && !flag2) { return; } NativeArray val = default(NativeArray); try { EntityQuery deathEventQuery = __instance._DeathEventQuery; val = ((EntityQuery)(ref deathEventQuery)).ToComponentDataArray(AllocatorHandle.op_Implicit((Allocator)2)); for (int i = 0; i < val.Length; i++) { DeathEvent val2 = val[i]; if (val2.Killer.Has()) { ulong steamId = val2.Killer.GetSteamId(); bool flag3 = val2.Died.Has(); int num = (flag3 ? val2.Died.GetPrefabGuid()._Value : 0); if (flag2) { Core.Kills.RecordKill(steamId, val2.Died.Has(), num); } if (flag && flag3 && steamId != 0L && num != 0) { Core.Unlock.RecordBossDefeat(steamId, num); } } } } catch (Exception ex) { ManualLogSource log = Core.Log; bool flag4 = default(bool); BepInExErrorLogInterpolatedStringHandler val3 = new BepInExErrorLogInterpolatedStringHandler(33, 1, ref flag4); if (flag4) { ((BepInExLogInterpolatedStringHandler)val3).AppendLiteral("[FAUST DEATH] death-hook failed: "); ((BepInExLogInterpolatedStringHandler)val3).AppendFormatted(ex.Message); } log.LogError(val3); } finally { if (val.IsCreated) { val.Dispose(); } } } } [HarmonyPatch(typeof(SpawnTeamSystem_OnPersistenceLoad), "OnUpdate")] internal static class GameDataInitializedPatch { [HarmonyPostfix] public static void OneShotInit() { Core.TryInitialize("GameDataInitializedPatch"); } } [HarmonyPatch(typeof(ServerBootstrapSystem), "OnUserConnected")] internal static class OnUserConnected_Patch { public unsafe static void Postfix(ServerBootstrapSystem __instance, NetConnectionId netConnectionId) { //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Expected O, but got Unknown //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0035: 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_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) try { if (Core.Store != null) { int num = __instance._NetEndPointToApprovedUserIndex[netConnectionId]; ServerClient val = ((Il2CppArrayBase)(object)__instance._ApprovedUsersLookup)[num]; EntityManager entityManager = ((ComponentSystemBase)__instance).EntityManager; User componentData = ((EntityManager)(ref entityManager)).GetComponentData(val.UserEntity); if (!((FixedString64Bytes)(ref componentData.CharacterName)).IsEmpty) { Core.Store.OnConnect(componentData.PlatformId, ((object)(*(FixedString64Bytes*)(&componentData.CharacterName))/*cast due to .constrained prefix*/).ToString()); Core.MapMarkers?.OnPlayerConnect(componentData.PlatformId); RegionSampler.SampleRegionsOncePerDay(); } } } catch (Exception ex) { ManualLogSource log = Core.Log; bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val2 = new BepInExErrorLogInterpolatedStringHandler(38, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("[FAUST STORE] OnUserConnected failed: "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(ex.Message); } log.LogError(val2); } } } [HarmonyPatch(typeof(ServerBootstrapSystem), "OnUserDisconnected")] internal static class OnUserDisconnected_Patch { public static void Prefix(ServerBootstrapSystem __instance, NetConnectionId netConnectionId, ConnectionStatusChangeReason connectionStatusReason, string extraData) { //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Expected O, but got Unknown //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0035: 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_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) try { if (Core.Store != null) { int num = __instance._NetEndPointToApprovedUserIndex[netConnectionId]; ServerClient val = ((Il2CppArrayBase)(object)__instance._ApprovedUsersLookup)[num]; EntityManager entityManager = ((ComponentSystemBase)__instance).EntityManager; User componentData = ((EntityManager)(ref entityManager)).GetComponentData(val.UserEntity); if (!((FixedString64Bytes)(ref componentData.CharacterName)).IsEmpty) { Core.Store.OnDisconnect(componentData.PlatformId); Core.MapMarkers?.OnPlayerDisconnect(componentData.PlatformId); RegionSampler.SampleRegionsOncePerDay(); } } } catch (Exception ex) { ManualLogSource log = Core.Log; bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val2 = new BepInExErrorLogInterpolatedStringHandler(41, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("[FAUST STORE] OnUserDisconnected failed: "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(ex.Message); } log.LogError(val2); } } } internal static class RegionSampler { public static void SampleRegionsOncePerDay() { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Expected O, but got Unknown try { if (Core.IsReady && Core.Store != null && Core.Store.ShouldSampleRegions()) { Core.Store.RecordRegionSnapshot(RegionStats.Gather()); } } catch (Exception ex) { ManualLogSource log = Core.Log; bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(36, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[FAUST STORE] region sample failed: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ex.Message); } log.LogError(val); } } } } namespace Faust.Config { internal sealed class ConfigField { public string Name { get; init; } public string[] Aliases { get; init; } = Array.Empty(); public string Hint { get; init; } public ConfigEntryBase Entry { get; init; } public Func Setter { get; init; } public bool Matches(string token) { if (!Name.Equals(token, StringComparison.OrdinalIgnoreCase)) { return Aliases.Any((string a) => a.Equals(token, StringComparison.OrdinalIgnoreCase)); } return true; } public string Show() { object boxedValue = Entry.BoxedValue; if (!(boxedValue is float num)) { if (boxedValue == null) { return ""; } return boxedValue.ToString(); } return num.ToString("0.###", CultureInfo.InvariantCulture); } public (bool ok, string note) Set(string raw) { return Setter(raw); } public void Reset() { Entry.BoxedValue = Entry.DefaultValue; } } internal static class ConfigEditor { private const StringComparison OIC = StringComparison.OrdinalIgnoreCase; public static ConfigField Find(IEnumerable fields, string token) { return fields.FirstOrDefault((ConfigField f) => f.Matches(token)); } public static string NameList(IEnumerable fields) { return string.Join(", ", fields.Select((ConfigField f) => f.Name)); } public static List FeatureFields(FeatureConfig f) { List list = new List(); list.Add(Enum("access", f.AccessRaw, new string[3] { "Off", "AdminOnly", "Players" })); list.Add(Enum("delivery", f.DeliveryRaw, new string[2] { "ServerMediated", "Free" })); list.Add(Int("costitem", f.CostItemGuid, -2147483648L, 2147483647L, "PrefabGUID hash (0 = free)", "costguid", "costitemguid")); list.Add(Int("costqty", f.CostQuantity, 0L, 2147483647L, ">= 0", "costquantity")); list.Add(Int("cooldown", f.CooldownSeconds, 0L, 2147483647L, "seconds, >= 0", "cooldownseconds", "cd")); list.Add(Bool("adminsexempt", f.AdminsExempt, "exempt")); list.Add(Enum("availability", f.AvailabilityRaw, new string[3] { "Always", "PvEOnly", "PvPOnly" }, "pvp")); list.Add(Int("window", f.WindowSeconds, 0L, 2147483647L, "seconds, >= 0", "windowseconds")); list.Add(Int("period", f.PeriodSeconds, 0L, 2147483647L, "seconds, >= 0 (86400 = daily)", "periodseconds")); list.Add(Int("maxuses", f.MaxUsesPerPeriod, 0L, 2147483647L, ">= 0 (0 = unlimited)", "maxusesperperiod", "max")); list.Add(UnlockField(f.UnlockRaw)); list.Add(Int("nearprefab", f.RequireNearPrefab, -2147483648L, 2147483647L, "PrefabGUID (0 = off)", "requirenearprefab", "near")); list.Add(Float("neardist", f.RequireNearDistance, 0f, float.MaxValue, "metres, >= 0", "requireneardistance", "dist")); return list; } public static List GlobalFields() { List list = new List(); list.Add(Bool("enabled", Settings.Enabled)); list.Add(Bool("audit", Settings.AuditQueries, "auditqueries")); list.Add(Bool("verbose", Settings.VerboseLogging, "verboselogging")); list.Add(Int("ratelimit", Settings.RateLimitSeconds, 0L, 2147483647L, "seconds, >= 0", "ratelimitseconds")); list.Add(Bool("ratelimitexempt", Settings.RateLimitAdminsExempt, "ratelimitadminsexempt")); list.Add(Str("resetsteamids", Settings.DataResetSteamIds, "dataresetsteamids")); list.Add(Bool("sessiontracking", Settings.SessionTracking)); list.Add(Bool("concurrencysampling", Settings.ConcurrencySampling)); list.Add(Int("maxconcurrencypoints", Settings.MaxConcurrencyPoints, 0L, 2147483647L, ">= 0")); list.Add(Int("sessionretentiondays", Settings.SessionRetentionDays, 0L, 2147483647L, ">= 0 (0 = forever)")); list.Add(Str("datanamespace", Settings.DataNamespace)); list.Add(Bool("heatmapenabled", Settings.HeatmapEnabled)); list.Add(Int("heatmapsample", Settings.HeatmapSampleSeconds, 30L, 300L, "30..300 seconds", "heatmapsampleseconds")); list.Add(Float("heatmapcellsize", Settings.HeatmapCellSize, 1f, float.MaxValue, "world units, > 0")); list.Add(Int("heatmapmaxcells", Settings.HeatmapMaxCells, 0L, 2147483647L, ">= 0 (0 = unlimited)")); list.Add(Int("heatmapretentiondays", Settings.HeatmapRetentionDays, 0L, 2147483647L, ">= 0 (0 = keep all days)")); list.Add(Bool("mapmarkersenabled", Settings.MapMarkersEnabled, "markersenabled")); list.Add(Int("mapmarkerprefab", Settings.MapMarkerPrefabGuid, -2147483648L, 2147483647L, "PrefabGUID", "mapmarkerprefabguid")); list.Add(Int("worldscaninterval", Settings.WorldScanInterval, 5L, 2147483647L, "seconds, >= 5")); list.Add(Int("worldscanmaxresults", Settings.WorldScanMaxResults, 0L, 2147483647L, ">= 0 (0 = unlimited)")); list.Add(Float("bossmaplimit", Settings.BossMapLimit, 100f, 20000f, "world units (boss on-map threshold; default 9000, keep < ~10000)")); return list; } private static ConfigField Bool(string name, ConfigEntry e, params string[] aliases) { return new ConfigField { Name = name, Aliases = aliases, Entry = (ConfigEntryBase)(object)e, Hint = "on | off", Setter = delegate(string raw) { if (!TryBool(raw, out var v)) { return (ok: false, note: "'" + raw + "' is not on/off (on|off|true|false|1|0|yes|no)."); } e.Value = v; return ((bool ok, string note))(ok: true, note: null); } }; } private static ConfigField Int(string name, ConfigEntry e, long min, long max, string hint, params string[] aliases) { return new ConfigField { Name = name, Aliases = aliases, Entry = (ConfigEntryBase)(object)e, Hint = hint, Setter = delegate(string raw) { if (!int.TryParse(raw, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { return (ok: false, note: "'" + raw + "' is not a whole number."); } if (result < min || result > max) { return (ok: false, note: $"{name} must be {min}..{max}."); } e.Value = result; return ((bool ok, string note))(ok: true, note: null); } }; } private static ConfigField Float(string name, ConfigEntry e, float min, float max, string hint, params string[] aliases) { return new ConfigField { Name = name, Aliases = aliases, Entry = (ConfigEntryBase)(object)e, Hint = hint, Setter = delegate(string raw) { if (!float.TryParse(raw, NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { return (ok: false, note: "'" + raw + "' is not a number."); } if (result < min || result > max) { return (ok: false, note: $"{name} must be {min.ToString("0.###", CultureInfo.InvariantCulture)}..{max.ToString("0.###", CultureInfo.InvariantCulture)}."); } e.Value = result; return ((bool ok, string note))(ok: true, note: null); } }; } private static ConfigField Str(string name, ConfigEntry e, params string[] aliases) { return new ConfigField { Name = name, Aliases = aliases, Entry = (ConfigEntryBase)(object)e, Hint = "text ('empty' or \"\" clears it)", Setter = delegate(string raw) { e.Value = ((raw.Equals("empty", StringComparison.OrdinalIgnoreCase) || raw == "\"\"" || raw == "-") ? "" : raw); return ((bool ok, string note))(ok: true, note: null); } }; } private static ConfigField Enum(string name, ConfigEntry e, string[] allowed, params string[] aliases) { return new ConfigField { Name = name, Aliases = aliases, Entry = (ConfigEntryBase)(object)e, Hint = string.Join(" | ", allowed), Setter = delegate(string raw) { string[] array = allowed; foreach (string text in array) { if (text.Equals(raw, StringComparison.OrdinalIgnoreCase)) { e.Value = text; return ((bool ok, string note))(ok: true, note: null); } } return (ok: false, note: name + " must be one of: " + string.Join(", ", allowed) + "."); } }; } private static ConfigField UnlockField(ConfigEntry e) { return new ConfigField { Name = "unlock", Entry = (ConfigEntryBase)(object)e, Hint = "None | FinalBoss | BossKill: | AllBosses | AllQuests", Setter = delegate(string raw) { raw = raw.Trim(); if (raw.Equals("None", StringComparison.OrdinalIgnoreCase)) { e.Value = "None"; return ((bool ok, string note))(ok: true, note: null); } if (raw.Equals("FinalBoss", StringComparison.OrdinalIgnoreCase)) { e.Value = "FinalBoss"; return ((bool ok, string note))(ok: true, note: null); } if (raw.Equals("AllBosses", StringComparison.OrdinalIgnoreCase)) { e.Value = "AllBosses"; return (ok: true, note: "reserved — admin-grant-only (no auto-detect yet)"); } if (raw.Equals("AllQuests", StringComparison.OrdinalIgnoreCase)) { e.Value = "AllQuests"; return (ok: true, note: "reserved — admin-grant-only (no auto-detect yet)"); } if (raw.StartsWith("BossKill:", StringComparison.OrdinalIgnoreCase)) { if (int.TryParse(raw.Substring("BossKill:".Length), NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { e.Value = $"BossKill:{result}"; return ((bool ok, string note))(ok: true, note: null); } return (ok: false, note: "BossKill needs a numeric V-Blood PrefabGUID, e.g. BossKill:-1905691330."); } return (ok: false, note: "unlock must be: None | FinalBoss | BossKill: | AllBosses | AllQuests."); } }; } private static bool TryBool(string s, out bool v) { v = false; switch (s.Trim().ToLowerInvariant()) { case "on": case "1": case "yes": case "enabled": case "true": case "enable": v = true; return true; case "no": case "0": case "off": case "disable": case "false": case "disabled": v = false; return true; default: return false; } } } internal enum AccessLevel { Off, AdminOnly, Players } internal enum DeliveryMode { ServerMediated, Free } internal enum PvpAvailability { Always, PvEOnly, PvPOnly } internal enum UnlockKind { None, BossKill, FinalBoss, GrantOnly } internal sealed class FeatureConfig { public string Key { get; } public ConfigEntry AccessRaw { get; init; } public ConfigEntry DeliveryRaw { get; init; } public ConfigEntry CostItemGuid { get; init; } public ConfigEntry CostQuantity { get; init; } public ConfigEntry CooldownSeconds { get; init; } public ConfigEntry AdminsExempt { get; init; } public ConfigEntry AvailabilityRaw { get; init; } public ConfigEntry WindowSeconds { get; init; } public ConfigEntry PeriodSeconds { get; init; } public ConfigEntry MaxUsesPerPeriod { get; init; } public ConfigEntry UnlockRaw { get; init; } public ConfigEntry RequireNearPrefab { get; init; } public ConfigEntry RequireNearDistance { get; init; } public bool HasProximity => RequireNearPrefab.Value != 0; public (UnlockKind Kind, int Guid) Unlock { get { string value = UnlockRaw.Value; if (string.IsNullOrWhiteSpace(value) || value.Equals("None", StringComparison.OrdinalIgnoreCase)) { return (Kind: UnlockKind.None, Guid: 0); } if (value.Equals("FinalBoss", StringComparison.OrdinalIgnoreCase)) { return (Kind: UnlockKind.FinalBoss, Guid: 0); } if (value.StartsWith("BossKill:", StringComparison.OrdinalIgnoreCase) && int.TryParse(value.Substring("BossKill:".Length), out var result)) { return (Kind: UnlockKind.BossKill, Guid: result); } return (Kind: UnlockKind.GrantOnly, Guid: 0); } } public bool HasUnlock => Unlock.Kind != UnlockKind.None; public AccessLevel Access { get { string value = AccessRaw.Value; if (!(value == "Players")) { if (value == "AdminOnly") { return AccessLevel.AdminOnly; } return AccessLevel.Off; } return AccessLevel.Players; } } public DeliveryMode Delivery { get { if (!(DeliveryRaw.Value == "Free")) { return DeliveryMode.ServerMediated; } return DeliveryMode.Free; } } public PvpAvailability Availability { get { string value = AvailabilityRaw.Value; if (!(value == "PvEOnly")) { if (value == "PvPOnly") { return PvpAvailability.PvPOnly; } return PvpAvailability.Always; } return PvpAvailability.PvEOnly; } } public bool HasCost { get { if (CostItemGuid.Value != 0) { return CostQuantity.Value > 0; } return false; } } public bool HasWindowPolicy => PeriodSeconds.Value > 0; public FeatureConfig(string key) { Key = key; } } internal static class Settings { public const string PlayerPositions = "playerpositions"; public const string CastleInfo = "castleinfo"; public const string PlayerInfo = "playerinfo"; public const string PlotAvailability = "plotavailability"; public const string CastleResources = "castleresources"; public const string Stats = "stats"; public const string AllCastles = "allcastles"; public const string DecayWatch = "decaywatch"; public const string Clans = "clans"; public const string Heatmap = "heatmap"; public const string Bosses = "bosses"; public const string Kills = "kills"; public const string WorldScan = "worldscan"; private static readonly Dictionary _features = new Dictionary(); public static ConfigEntry Enabled { get; private set; } public static ConfigEntry AuditQueries { get; private set; } public static ConfigEntry VerboseLogging { get; private set; } public static ConfigEntry RateLimitSeconds { get; private set; } public static ConfigEntry RateLimitAdminsExempt { get; private set; } public static ConfigEntry DataResetSteamIds { get; private set; } public static ConfigEntry MapMarkersEnabled { get; private set; } public static ConfigEntry MapMarkerPrefabGuid { get; private set; } public static ConfigEntry SessionTracking { get; private set; } public static ConfigEntry ConcurrencySampling { get; private set; } public static ConfigEntry KillTracking { get; private set; } public static ConfigEntry MaxConcurrencyPoints { get; private set; } public static ConfigEntry SessionRetentionDays { get; private set; } public static ConfigEntry DataNamespace { get; private set; } public static ConfigEntry HeatmapEnabled { get; private set; } public static ConfigEntry HeatmapSampleSeconds { get; private set; } public static ConfigEntry HeatmapCellSize { get; private set; } public static ConfigEntry HeatmapMaxCells { get; private set; } public static ConfigEntry HeatmapRetentionDays { get; private set; } public static ConfigEntry WorldScanInterval { get; private set; } public static ConfigEntry WorldScanMaxResults { get; private set; } public static ConfigEntry BossMapLimit { get; private set; } public static IReadOnlyDictionary Features => _features; public static FeatureConfig Feature(string key) { if (!_features.TryGetValue(key, out var value)) { return null; } return value; } public static bool MayResetData(ulong steamId, bool isAdmin) { if (!isAdmin) { return false; } string value = DataResetSteamIds.Value; if (string.IsNullOrWhiteSpace(value)) { return true; } string[] array = value.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); for (int i = 0; i < array.Length; i++) { if (ulong.TryParse(array[i], out var result) && result == steamId) { return true; } } return false; } public static void Initialize(ConfigFile config) { Enabled = config.Bind("Faust", "Enabled", true, "Master switch for the whole mod. When false, every query refuses with [FAUST:err] code=disabled."); AuditQueries = config.Bind("Faust", "AuditQueries", true, "Log who asked what, when, and whether they were charged — a privacy/abuse trail for admins."); RateLimitSeconds = config.Bind("Faust", "RateLimitSeconds", 0, "Anti-spam floor: minimum seconds a player must wait between ANY two Faust queries (a denied query with code=ratelimit + secs remaining). 0 = off (default). Set e.g. 5 to stop a player hammering a query and stressing the server. Applies across all features (it's a per-player, not per-feature, limit). NOTE: it also throttles paged reads, so admins driving the BloodCraftHub/Raphael UI should stay exempt (see RateLimitAdminsExempt)."); RateLimitAdminsExempt = config.Bind("Faust", "RateLimitAdminsExempt", true, "When true, admins are exempt from RateLimitSeconds (so admin dashboards/paging aren't throttled). Since features default to AdminOnly, the limit mainly guards features an admin has opened to players."); DataResetSteamIds = config.Bind("Faust.Data", "ResetSteamIds", "", "Comma-separated SteamIDs of the admins allowed to run the DESTRUCTIVE data commands ('.faust admin data clear' / 'data wipe'). EMPTY (default) = any admin may. Set it to lock data resets to specific senior admins on servers with layered admin teams (junior admins still get every other '.faust admin …' command and 'data status', just not clear/wipe)."); VerboseLogging = config.Bind("Diagnostics", "VerboseLogging", false, "Emit detailed per-query log lines (useful when testing; noisy in production)."); MapMarkersEnabled = config.Bind("Faust.MapMarkers", "Enabled", false, "EXPERIMENTAL — leave OFF unless you are testing. Master switch for '.faust admin showpositions', which makes the server attach native-map markers to online players (admin-visible). This touches server-authoritative networked MapIcon entities; if the archetype is wrong it can crash the server in a way no try/catch can stop. Validate on a TEST server before using in production. Default false."); MapMarkerPrefabGuid = config.Bind("Faust.MapMarkers", "MarkerPrefabGuid", -892362184, "PrefabGUID of the map-icon prefab to attach per player (default -892362184 = MapIcon_Player). Ignored unless Enabled = true."); SessionTracking = config.Bind("Faust.Collection", "SessionTracking", true, "Master switch for PASSIVE session logging (connect/disconnect over time). When false, Faust records no sessions: playerinfo's playtime/sessions/frequency/peak-hour and the stats playtime leaderboard return the -1 'not tracked' sentinel, and nothing is written to sessions.json. Turn off if you want Faust purely as a live-query tool with no historical collection."); ConcurrencySampling = config.Bind("Faust.Collection", "ConcurrencySampling", true, "Whether to sample the online-player count on each connect/disconnect (the concurrency series behind stats concurrency / population graphs). When false, no concurrency points are stored. Independent of SessionTracking (you can keep playtime history without the population series)."); KillTracking = config.Bind("Faust.Collection", "KillTracking", true, "Master switch for the PASSIVE kill tally (powers '.faust api kills' top-killers + '.faust api bosskills' boss-defeat counts). Fed from the existing death hook (only does work on a kill, nothing periodic), bucketed per UTC day and bounded by SessionRetentionDays. When false, no kills are recorded and the kill leaderboards return empty. Reset the data with '.faust admin data wipe kills'."); MaxConcurrencyPoints = config.Bind("Faust.Collection", "MaxConcurrencyPoints", 4000, "Hard cap on retained concurrency samples (oldest trimmed past this). Bounds memory and the sessions.json size. Lower it on a busy server to cap growth; 0 disables sampling entirely."); SessionRetentionDays = config.Bind("Faust.Collection", "SessionRetentionDays", 0, "Auto-prune session records older than this many days (checked on every connect and at load). 0 = keep forever (the default — most servers want full history, and it survives a world wipe since the same players return). Set e.g. 30/60/90 to bound long-term growth on a very busy or long-lived server and keep playtime/frequency windows recent. For a one-off cleanup without changing this, use '.faust admin data clear '."); HeatmapEnabled = config.Bind("Faust.Heatmap", "Enabled", false, "Master switch for player-position heat-map COLLECTION. When true, Faust snapshots every online player's map coordinates every SampleSeconds and bins them into a grid (for per-player and server-wide density maps, read via '.faust api heatmap'). OFF by default — it's the only collector that runs on a timer (others are read-on-demand or event-driven), so you opt in. Reset the collected data any time with '.faust admin data wipe heatmap'."); HeatmapSampleSeconds = config.Bind("Faust.Heatmap", "SampleSeconds", 60, "How often (seconds) to snapshot online player positions for the heat map. Clamped to 30–300 (every 30s to every 5 min). Lower = finer time resolution but more samples/disk; higher = lighter."); HeatmapCellSize = config.Bind("Faust.Heatmap", "CellSize", 25f, "Heat-map grid resolution in world units — positions are binned into CellSize×CellSize cells (smaller = sharper/heavier, larger = coarser/lighter). NOTE: the resolution is fixed once data exists; to change it, wipe the heat map first ('.faust admin data wipe heatmap'), or old and new cells would mix resolutions."); HeatmapMaxCells = config.Bind("Faust.Heatmap", "MaxCells", 250000, "Hard cap on distinct (player, cell) entries stored (bounds memory + heatmap.json size). Applied to BOTH the cumulative all-time grid and the per-day grid independently. Once reached, existing cells keep counting but no new cells are added. 0 = unlimited (not advised)."); HeatmapRetentionDays = config.Bind("Faust.Heatmap", "RetentionDays", 30, "How many UTC days of per-day heat-map history to keep, for the WINDOWED queries (.faust api heatmap — today / this week / this month / last N days). Older days are pruned (they still counted toward the ALL-TIME map, which is days=0 and is never pruned). 30 covers today/week/month; raise it for longer windows at the cost of more memory + heatmap.json size, lower it to trim footprint. 0 = keep all days (unbounded — not advised on a long-lived server). Changing this prunes on the next sample/load; it does not retroactively recover dropped days."); WorldScanInterval = config.Bind("Faust.WorldScan", "ScanIntervalSeconds", 10, "How often (seconds) the world-asset scan ('.faust api worldscan') may rebuild its snapshot of map units/resource nodes. Scanning the whole map is heavy, so a result is cached and reused for this long — queries inside the window read the cache (cheap); the first query after it triggers ONE rescan, server-wide. This is the de-facto rate limit (clamped to a 5s minimum). Raise it on a busy server. The scan is on-demand only — zero cost when nobody is querying."); WorldScanMaxResults = config.Bind("Faust.WorldScan", "MaxResults", 10000, "Cap on the number of assets a single '.faust api worldscan' query RETURNS, applied AFTER your filter (the wire is paged separately). The full whitelisted, on-map world is always snapshotted first, so a narrow filter (e.g. bloodqmin=99) scans every asset and returns all of its matches — no qualifying asset is ever missed. This cap only trims a broad result (e.g. type=all) before it goes to the wire; when it does, the reply carries 'truncated=1' (never silent) so you know to filter further. Set 0 for UNLIMITED — this also lifts the internal snapshot safety backstop, so the WHOLE whitelisted, on-map world is captured and no query is ever truncated (use this if you rely on exhaustive results; on a very asset-dense map it costs more memory/scan time, paid only when someone queries). Raise it (or use 0) if a broad query is being cut short. The scan is on-demand and cached, so the cost is paid only when someone queries."); BossMapLimit = config.Bind("Faust.Bosses", "MapLimit", 9000f, "Boss board (.faust api bosses) on-map threshold, in world units. A live V Blood within ±this on both axes is reported 'up' with its coordinates; beyond it, 'down' (no coords). In-game diagnostics (.faust admin bossdiag) showed the V Rising map extends WELL past ±5000 and that streamed-out bosses keep their REAL positions (there is no sentinel-parking), so the default is 9000 — generous enough to cover the whole map while still excluding any far off-map sentinel (~10000). Raise it further only if a boss you know is alive still reads 'down' (keep it below ~10000). Lower it only if a boss shows up at bogus far-off coordinates. See '.faust admin bossdiag '."); DataNamespace = config.Bind("Faust.Collection", "DataNamespace", "", "Optional label that scopes Faust's stored data to a subfolder (BepInEx/config/Faust//). Leave EMPTY (default) to keep ONE shared dataset for the server — the usual choice, since after a world wipe the same players return and their activity history stays relevant. Set a distinct name per world (e.g. 'season3') ONLY if you want each world's data kept fully separate; changing it starts a fresh dataset (the previous one is left on disk under its own name, not deleted). To reset data instead of separating it, use '.faust admin data wipe …'."); Bind(config, "playerpositions", AccessLevel.AdminOnly, DeliveryMode.ServerMediated); Bind(config, "castleinfo", AccessLevel.AdminOnly, DeliveryMode.ServerMediated); Bind(config, "playerinfo", AccessLevel.AdminOnly, DeliveryMode.ServerMediated); Bind(config, "plotavailability", AccessLevel.AdminOnly, DeliveryMode.ServerMediated); Bind(config, "castleresources", AccessLevel.AdminOnly, DeliveryMode.ServerMediated); Bind(config, "stats", AccessLevel.AdminOnly, DeliveryMode.ServerMediated); Bind(config, "allcastles", AccessLevel.AdminOnly, DeliveryMode.ServerMediated); Bind(config, "decaywatch", AccessLevel.AdminOnly, DeliveryMode.ServerMediated); Bind(config, "clans", AccessLevel.AdminOnly, DeliveryMode.ServerMediated); Bind(config, "heatmap", AccessLevel.AdminOnly, DeliveryMode.ServerMediated); Bind(config, "bosses", AccessLevel.AdminOnly, DeliveryMode.ServerMediated); Bind(config, "kills", AccessLevel.AdminOnly, DeliveryMode.ServerMediated); Bind(config, "worldscan", AccessLevel.AdminOnly, DeliveryMode.ServerMediated); } private static void Bind(ConfigFile config, string key, AccessLevel defaultAccess, DeliveryMode defaultDelivery) { string text = "Faust." + key; FeatureConfig value = new FeatureConfig(key) { AccessRaw = config.Bind(text, "Access", defaultAccess.ToString(), "Off | AdminOnly | Players. Who may run this query. Sensitive intel defaults to AdminOnly."), DeliveryRaw = config.Bind(text, "Delivery", defaultDelivery.ToString(), "ServerMediated (gateable/chargeable) | Free (BCH reads replicated state locally; un-gateable). Free is only meaningful for near-player/own-data features."), CostItemGuid = config.Bind(text, "CostItemGuid", 0, "Item PrefabGUID hash the requester must spend to run this query. 0 = free (the Faustian toll)."), CostQuantity = config.Bind(text, "CostQuantity", 0, "How many of CostItemGuid to charge per query. Ignored when CostItemGuid = 0."), CooldownSeconds = config.Bind(text, "CooldownSeconds", 0, "Per-player flat lockout between runs of this query, in seconds. 0 = no cooldown. (Use this for 'pay a cost, then locked N seconds'. For 'open a window once per period', use WindowSeconds + PeriodSeconds below.)"), AdminsExempt = config.Bind(text, "AdminsExempt", true, "When true, admins skip this feature's access check, cost, cooldown, window/period, and PvP gate."), AvailabilityRaw = config.Bind(text, "Availability", PvpAvailability.Always.ToString(), "Always | PvEOnly | PvPOnly. Gate the feature on the server's game mode (e.g. enemy-resource intel PvPOnly, or position intel PvEOnly). Always = no game-mode restriction."), WindowSeconds = config.Bind(text, "WindowSeconds", 0, "Rate-limit: once the feature is first used in a period, it stays OPEN for this many seconds (repeated uses within the window are free). 0 = no window (each use is discrete). Example: 600 with PeriodSeconds=86400 = 'a 10-minute window, once per day'."), PeriodSeconds = config.Bind(text, "PeriodSeconds", 0, "Rate-limit recurrence in seconds (e.g. 86400 = daily). 0 = no period (only CooldownSeconds applies). After MaxUsesPerPeriod uses/windows in a period, the feature locks until the period rolls over."), MaxUsesPerPeriod = config.Bind(text, "MaxUsesPerPeriod", 0, "How many uses (or window-opens, if WindowSeconds>0) are allowed per period. 0 = unlimited within the period. Example: 1 with PeriodSeconds=86400 = once per day."), UnlockRaw = config.Bind(text, "Unlock", "None", "Progression gate before a player may use this feature. 'None' = always available. 'FinalBoss' = after defeating Dracula (game completion). 'BossKill:' = after defeating that specific V Blood. Admins are exempt (AdminsExempt); '.faust admin grant " + key + "' overrides for any player. (AllBosses/AllQuests are reserved — admin-grant only for now.)"), RequireNearPrefab = config.Bind(text, "RequireNearPrefab", 0, "Proximity gate: if set to an object's PrefabGUID, the player may only use this feature while within RequireNearDistance metres of an instance of that object (e.g. an altar/station placed in a castle, or a world landmark) — so the ability is tied to a place, not usable anywhere. 0 = no proximity requirement. The object must be a placed/world object (one with a tile position)."), RequireNearDistance = config.Bind(text, "RequireNearDistance", 5f, "How close (metres) the player must be to a RequireNearPrefab object. Ignored when RequireNearPrefab = 0.") }; _features[key] = value; } } } namespace Faust.Commands { [CommandGroup("faust admin", null)] internal static class AdminCommands { private static bool ValidFeature(string key) { if (!(key == "all")) { return Settings.Features.ContainsKey(key); } return true; } private static bool CheckFeature(ChatCommandContext ctx, string feature) { if (ValidFeature(feature)) { return true; } ctx.Reply($"Unknown feature '{feature}'. Use one of: all, {string.Join(", ", Settings.Features.Keys)}."); return false; } [Command("block", null, null, "Block a feature now. Usage: .faust admin block [minutes] (no minutes = until unblocked)", null, true)] public static void Block(ChatCommandContext ctx, string feature, int minutes = 0) { feature = feature.ToLowerInvariant(); if (CheckFeature(ctx, feature)) { Core.Control.Block(feature, minutes); ctx.Reply((minutes <= 0) ? ("Faust '" + feature + "' BLOCKED until you unblock it.") : $"Faust '{feature}' BLOCKED for {minutes} minute(s) (auto-reopens after)."); } } [Command("unblock", null, null, "Clear a block/countdown. Usage: .faust admin unblock ", null, true)] public static void Unblock(ChatCommandContext ctx, string feature) { feature = feature.ToLowerInvariant(); if (CheckFeature(ctx, feature)) { Core.Control.Unblock(feature); ctx.Reply("Faust '" + feature + "' unblocked."); } } [Command("schedule", null, null, "Time-of-day window (server local). Usage: .faust admin schedule ", null, true)] public static void Schedule(ChatCommandContext ctx, string feature, string window) { feature = feature.ToLowerInvariant(); if (CheckFeature(ctx, feature)) { if (string.Equals(window, "clear", StringComparison.OrdinalIgnoreCase)) { Core.Control.ClearSchedule(feature); ctx.Reply("Faust '" + feature + "' schedule cleared (always open)."); return; } if (!TryParseWindow(window, out var startMin, out var endMin)) { ctx.Reply("Bad window. Use HH:MM-HH:MM (24h, server local), e.g. 18:00-22:00, or 'clear'."); return; } Core.Control.SetSchedule(feature, startMin, endMin); ctx.Reply($"Faust '{feature}' usable only {window} (server local time)."); } } [Command("status", null, null, "Show each feature's effective runtime control state. Usage: .faust admin status [feature]", null, true)] public static void Status(ChatCommandContext ctx, string feature = null) { if (!string.IsNullOrEmpty(feature)) { feature = feature.ToLowerInvariant(); if (CheckFeature(ctx, feature)) { ctx.Reply("Faust '" + feature + "': " + Core.Control.Describe(feature)); } return; } List list = new List(); foreach (string key in Settings.Features.Keys) { list.Add(" " + key + ": " + Core.Control.Describe(key)); } ReplyLines(ctx, "Faust control — all: " + Core.Control.Describe("all"), list); } [Command("grant", null, null, "Unlock a feature for a player (overrides its Unlock criterion). Usage: .faust admin grant ", null, true)] public static void Grant(ChatCommandContext ctx, string player, string feature) { feature = feature.ToLowerInvariant(); ulong steamId; string resolvedName; Entity userEntity; string error; if (!Settings.Features.ContainsKey(feature)) { ctx.Reply($"Unknown feature '{feature}'. Use one of: {string.Join(", ", Settings.Features.Keys)}."); } else if (!EntityExtensions.TryResolvePlayer(player, out steamId, out resolvedName, out userEntity, out error)) { ctx.Reply(error); } else { ctx.Reply(Core.Unlock.Grant(steamId, feature) ? $"Granted '{feature}' to {resolvedName}." : (resolvedName + " already had '" + feature + "' granted.")); } } [Command("revoke", null, null, "Remove a feature grant from a player. Usage: .faust admin revoke ", null, true)] public static void Revoke(ChatCommandContext ctx, string player, string feature) { feature = feature.ToLowerInvariant(); ulong steamId; string resolvedName; Entity userEntity; string error; if (!Settings.Features.ContainsKey(feature)) { ctx.Reply($"Unknown feature '{feature}'. Use one of: {string.Join(", ", Settings.Features.Keys)}."); } else if (!EntityExtensions.TryResolvePlayer(player, out steamId, out resolvedName, out userEntity, out error)) { ctx.Reply(error); } else { ctx.Reply(Core.Unlock.Revoke(steamId, feature) ? $"Revoked '{feature}' grant from {resolvedName}." : (resolvedName + " had no '" + feature + "' grant.")); } } [Command("unlocks", null, null, "Show a player's unlock progress (V-blood defeats + granted features). Usage: .faust admin unlocks ", null, true)] public static void Unlocks(ChatCommandContext ctx, string player) { if (!EntityExtensions.TryResolvePlayer(player, out var steamId, out var resolvedName, out var _, out var error)) { ctx.Reply(error); } else { ctx.Reply(resolvedName + " — " + Core.Unlock.Describe(steamId)); } } [Command("showpositions", null, null, "EXPERIMENTAL: show online players on the native map (admins). Usage: .faust admin showpositions [minutes]", null, true)] public static void ShowPositions(ChatCommandContext ctx, string mode = "status", int minutes = 0) { if (!Core.IsReady || Core.MapMarkers == null) { ctx.Reply("Faust isn't ready yet."); return; } switch (mode?.ToLowerInvariant()) { case "on": ctx.Reply(Core.MapMarkers.Enable(minutes)); break; case "off": ctx.Reply(Core.MapMarkers.Disable()); break; case "status": ctx.Reply("Map markers: " + Core.MapMarkers.Describe()); break; default: ctx.Reply("Usage: .faust admin showpositions [minutes]"); break; } } [Command("set", null, null, "Change one or MORE feature settings live (immediate + persisted). Usage: .faust admin set (NO spaces)", null, true)] public static void Set(ChatCommandContext ctx, string feature, string spec) { feature = feature.ToLowerInvariant(); if (!Settings.Features.TryGetValue(feature, out var value)) { ctx.Reply($"Unknown feature '{feature}'. Use one of: {string.Join(", ", Settings.Features.Keys)}."); } else { ApplySpec(ctx, feature, ConfigEditor.FeatureFields(value), spec); } } [Command("get", null, null, "Show a feature's live settings. Usage: .faust admin get [setting]", null, true)] public static void Get(ChatCommandContext ctx, string feature, string setting = null) { feature = feature.ToLowerInvariant(); if (!Settings.Features.TryGetValue(feature, out var value)) { ctx.Reply($"Unknown feature '{feature}'. Use one of: {string.Join(", ", Settings.Features.Keys)}."); } else { ShowSettings(ctx, feature, ConfigEditor.FeatureFields(value), setting); } } [Command("setglobal", null, null, "Change one or MORE global settings live (immediate + persisted). Usage: .faust admin setglobal (NO spaces)", null, true)] public static void SetGlobal(ChatCommandContext ctx, string spec) { ApplySpec(ctx, "global", ConfigEditor.GlobalFields(), spec); } [Command("getglobal", null, null, "Show Faust's global settings. Usage: .faust admin getglobal [setting]", null, true)] public static void GetGlobal(ChatCommandContext ctx, string setting = null) { ShowSettings(ctx, "global", ConfigEditor.GlobalFields(), setting); } [Command("resetcfg", null, null, "Reset settings to defaults. Usage: .faust admin resetcfg [setting]", null, true)] public static void ResetCfg(ChatCommandContext ctx, string target, string setting = null) { //IL_010b: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Expected O, but got Unknown //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_0229: Unknown result type (might be due to invalid IL or missing references) //IL_0230: Expected O, but got Unknown //IL_0248: Unknown result type (might be due to invalid IL or missing references) target = target.ToLowerInvariant(); List list; if (target == "global") { list = ConfigEditor.GlobalFields(); } else { if (!Settings.Features.TryGetValue(target, out var value)) { ctx.Reply($"Unknown target '{target}'. Use 'global' or one of: {string.Join(", ", Settings.Features.Keys)}."); return; } list = ConfigEditor.FeatureFields(value); } bool flag = default(bool); BepInExInfoLogInterpolatedStringHandler val; if (!string.IsNullOrEmpty(setting)) { ConfigField configField = ConfigEditor.Find(list, setting); if (configField == null) { ctx.Reply($"Unknown setting '{setting}'. Settings: {ConfigEditor.NameList(list)}."); return; } configField.Reset(); ManualLogSource log = Core.Log; val = new BepInExInfoLogInterpolatedStringHandler(35, 4, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[FAUST CONFIG] steamId="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ctx.Event.User.PlatformId); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" reset "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(target); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("."); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(configField.Name); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" -> "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(configField.Show()); } log.LogInfo(val); ctx.Reply($"Reset [{target}] {configField.Name} = {configField.Show()} (default)."); return; } foreach (ConfigField item in list) { item.Reset(); } ManualLogSource log2 = Core.Log; val = new BepInExInfoLogInterpolatedStringHandler(55, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[FAUST CONFIG] steamId="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ctx.Event.User.PlatformId); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" reset ALL "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(target); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" settings to defaults"); } log2.LogInfo(val); ctx.Reply($"Reset all {list.Count} '{target}' settings to defaults."); } private static void ApplySpec(ChatCommandContext ctx, string scope, List fields, string spec) { //IL_0164: Unknown result type (might be due to invalid IL or missing references) //IL_016b: Expected O, but got Unknown //IL_0183: Unknown result type (might be due to invalid IL or missing references) string[] array = (spec ?? string.Empty).Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); if (array.Length == 0) { ctx.Reply("Give setting=value pair(s), comma-separated, NO spaces (e.g. 'costitem=12345,costqty=1,cooldown=30'). Settings: " + ConfigEditor.NameList(fields) + "."); return; } List list = new List(); List list2 = new List(); string[] array2 = array; foreach (string text in array2) { int num = text.IndexOf('='); if (num <= 0 || num == text.Length - 1) { list2.Add("'" + text + "': use setting=value"); continue; } string text2 = text.Substring(0, num).Trim(); string raw = text.Substring(num + 1).Trim(); ConfigField configField = ConfigEditor.Find(fields, text2); if (configField == null) { list2.Add(text2 + ": unknown setting"); continue; } var (flag, text3) = configField.Set(raw); if (!flag) { list2.Add(configField.Name + ": " + text3); } else { list.Add(configField.Name + "=" + configField.Show() + ((text3 != null) ? (" (" + text3 + ")") : "")); } } if (list.Count > 0) { ManualLogSource log = Core.Log; bool flag2 = default(bool); BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(30, 3, ref flag2); if (flag2) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[FAUST CONFIG] steamId="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ctx.Event.User.PlatformId); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" set "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(scope); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(": "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(string.Join(", ", list)); } log.LogInfo(val); } List list3 = new List(); if (list.Count > 0) { list3.Add("Set [" + scope + "]: " + string.Join(", ", list)); } if (list2.Count > 0) { list3.Add("Rejected: " + string.Join("; ", list2)); } if (list3.Count == 0) { list3.Add("Nothing changed."); } ReplyLines(ctx, null, list3); } private static void ShowSettings(ChatCommandContext ctx, string scope, List fields, string setting) { if (!string.IsNullOrEmpty(setting)) { ConfigField configField = ConfigEditor.Find(fields, setting); if (configField == null) { ctx.Reply($"Unknown setting '{setting}'. Settings: {ConfigEditor.NameList(fields)}."); } else { ctx.Reply($"[{scope}] {configField.Name} = {configField.Show()} (valid: {configField.Hint})"); } } else { ReplyLines(ctx, "[" + scope + "] settings:", fields.Select((ConfigField x) => " " + x.Name + " = " + x.Show())); } } private static void ReplyLines(ChatCommandContext ctx, string header, IEnumerable lines) { StringBuilder stringBuilder = new StringBuilder(); if (header != null) { stringBuilder.AppendLine(header); } foreach (string line in lines) { if (stringBuilder.Length + line.Length > 400) { ctx.Reply(stringBuilder.ToString().TrimEnd()); stringBuilder.Clear(); } stringBuilder.AppendLine(line); } if (stringBuilder.Length > 0) { ctx.Reply(stringBuilder.ToString().TrimEnd()); } } [Command("prefab", null, null, "Look up prefab IDs/names for commands (no external dump needed). Usage: .faust admin prefab [page]", null, true)] public static void Prefab(ChatCommandContext ctx, string query, int page = 1) { if (!Core.IsReady) { ctx.Reply("Faust isn't ready yet."); return; } if (string.IsNullOrWhiteSpace(query)) { ctx.Reply("Usage: .faust admin prefab [page]"); return; } if (int.TryParse(query, out var result)) { ctx.Reply(PrefabLookup.Exists(result) ? $"{result} = {PrefabLookup.Name(result)}" : $"No prefab with ID {result} (it may exist but be unnamed)."); return; } bool capped; List<(int, string)> list = PrefabLookup.Search(query, 300, out capped); if (list.Count == 0) { ctx.Reply("No prefab name contains '" + query + "'."); return; } int num = Math.Max(1, (list.Count + 20 - 1) / 20); if (page < 1) { page = 1; } if (page > num) { page = num; } List list2 = new List(); for (int i = (page - 1) * 20; i < Math.Min(page * 20, list.Count); i++) { list2.Add($" {list[i].Item1} {list[i].Item2}"); } ReplyLines(ctx, $"Prefabs matching '{query}': {list.Count}{(capped ? "+" : "")} (page {page}/{num}):", list2); } [Command("worldscandiag", null, null, "Audit prefab categorization (units vs nodes + EntityCategory). Usage: .faust admin worldscandiag ", null, true)] public static void WorldScanDiag(ChatCommandContext ctx, string fragment) { if (!Core.IsReady || Core.WorldScan == null) { ctx.Reply("Faust isn't ready yet."); } else { ReplyLines(ctx, null, Core.WorldScan.Diagnose(fragment)); } } [Command("bossdiag", null, null, "Diagnostics for the boss board (Raphael §18): dump VBlood entity positions/components. Usage: .faust admin bossdiag [name|guid]", null, true)] public static void BossDiag(ChatCommandContext ctx, string filter = null) { if (!Core.IsReady || Core.Boss == null) { ctx.Reply("Faust isn't ready yet."); } else { ReplyLines(ctx, null, Core.Boss.Diagnose(filter)); } } [Command("worldscan", null, null, "Manage the world-scan asset whitelist. Usage: .faust admin worldscan [guid|page]", null, true)] public static void WorldScanAdmin(ChatCommandContext ctx, string mode, string arg = null) { //IL_01a7: Unknown result type (might be due to invalid IL or missing references) if (!Core.IsReady || Core.WorldScan == null) { ctx.Reply("Faust isn't ready yet."); return; } switch (mode?.ToLowerInvariant()) { case "add": { if (!int.TryParse(arg, out var result2)) { ctx.Reply("Usage: .faust admin worldscan add "); break; } ctx.Reply(Core.WorldScan.AddToWhitelist(result2) ? $"Added {result2} ({EntityExtensions.GetPrefabName(new PrefabGUID(result2))}) to the world-scan whitelist ({Core.WorldScan.WhitelistCount} total)." : $"{result2} is already whitelisted."); break; } case "del": case "rem": case "remove": { if (!int.TryParse(arg, out var result3)) { ctx.Reply("Usage: .faust admin worldscan remove "); break; } ctx.Reply(Core.WorldScan.RemoveFromWhitelist(result3) ? $"Removed {result3} from the world-scan whitelist ({Core.WorldScan.WhitelistCount} left)." : $"{result3} wasn't whitelisted."); break; } case "clear": ctx.Reply($"Cleared {Core.WorldScan.ClearWhitelist()} entr(ies). Nothing will show until you 'add' or 'seed'."); break; case "seed": ctx.Reply($"Seeded the world-scan whitelist from the prefab catalog — now {Core.WorldScan.Seed()} entr(ies). Trim with 'remove'/'clear'."); break; case "list": { int result = 1; int.TryParse(arg, out result); if (result < 1) { result = 1; } List<(int, string)> list = Core.WorldScan.ListWhitelist(); int num = Math.Max(1, (list.Count + 20 - 1) / 20); if (result > num) { result = num; } List list2 = new List(); for (int i = (result - 1) * 20; i < Math.Min(result * 20, list.Count); i++) { list2.Add($" {list[i].Item1} {list[i].Item2}"); } ReplyLines(ctx, $"World-scan whitelist: {list.Count} entr(ies) (page {result}/{num}):", list2); break; } default: ctx.Reply("Usage: .faust admin worldscan [guid|page]"); break; } } private static bool TryParseWindow(string window, out int startMin, out int endMin) { startMin = (endMin = -1); string[] array = window.Split('-'); if (array.Length != 2) { return false; } if (TryParseHhmm(array[0], out startMin)) { return TryParseHhmm(array[1], out endMin); } return false; } private static bool TryParseHhmm(string s, out int minutes) { minutes = -1; string[] array = s.Trim().Split(':'); if (array.Length != 2) { return false; } if (!int.TryParse(array[0], out var result) || !int.TryParse(array[1], out var result2)) { return false; } if (result < 0 || result > 23 || result2 < 0 || result2 > 59) { return false; } minutes = result * 60 + result2; return true; } } [CommandGroup("faust admin data", null)] internal static class AdminDataCommands { [Command("status", null, null, "Show Faust's collected-data footprint (counts, age, disk, namespace, retention).", null, true)] public static void Status(ChatCommandContext ctx) { (int sessions, int concurrency, int regionSamples, int names, long oldestConnectUnix) storageStats = Core.Store.GetStorageStats(); int item = storageStats.sessions; int item2 = storageStats.concurrency; int item3 = storageStats.regionSamples; int item4 = storageStats.names; long item5 = storageStats.oldestConnectUnix; (int players, int defeats, int grants) unlockStats = Core.Unlock.GetUnlockStats(); int item6 = unlockStats.players; int item7 = unlockStats.defeats; int item8 = unlockStats.grants; long value = FaustPaths.TotalDataBytes() / 1024; string value2 = (FaustPaths.IsNamespaced ? Settings.DataNamespace.Value : "(shared)"); int value3 = Settings.SessionRetentionDays.Value; var (value4, value5, value6) = Core.Heatmap.GetStats(); ctx.Reply($"Faust data — namespace: {value2}; on disk ~{value} KB; retention: {((value3 <= 0) ? "keep forever" : (value3 + " day(s)"))}."); ctx.Reply($" Activity: {item} session(s), {item2} concurrency point(s), {item3} region sample(s), {item4} name(s); oldest record {((item5 <= 0) ? "—" : FmtDate(item5))}. Online now: {Core.Store.OnlineCount}."); ctx.Reply($" Collection: SessionTracking={(Settings.SessionTracking.Value ? "on" : "off")}, ConcurrencySampling={(Settings.ConcurrencySampling.Value ? "on" : "off")}, KillTracking={(Settings.KillTracking.Value ? "on" : "off")}."); ctx.Reply($" Unlocks: {item6} player(s), {item7} V-blood defeat(s), {item8} grant(s). Usage locks: {Core.Usage.UsageCount} pair(s); usage tally: {Core.UsageStats.BucketCount} bucket(s)."); var (value7, value8) = Core.Kills.BucketCounts; ctx.Reply($" Kills: {value7} player-day + {value8} boss-day bucket(s)."); ctx.Reply($" WorldScan: {Core.WorldScan?.WhitelistCount ?? 0} whitelisted prefab(s) (scan every {Math.Max(5, Settings.WorldScanInterval.Value)}s, cap {Settings.WorldScanMaxResults.Value})."); ctx.Reply($" Heatmap: {(Settings.HeatmapEnabled.Value ? $"collecting every {Math.Clamp(Settings.HeatmapSampleSeconds.Value, 30, 300)}s" : "off")}; {value4} cell(s) over {value6} player(s), {value5} sample(s)."); ctx.Reply("Manage: '.faust admin data clear ' (prune old activity) · '.faust admin data wipe confirm'."); } private static bool MayReset(ChatCommandContext ctx) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) User user = ctx.Event.User; if (Settings.MayResetData(user.PlatformId, user.IsAdmin)) { return true; } ctx.Reply("You're not authorized to clear/reset Faust data on this server (restricted to specific admins via [Faust.Data] ResetSteamIds). You can still use '.faust admin data status'."); return false; } [Command("clear", null, null, "Prune collected ACTIVITY older than N days (sessions + concurrency). Usage: .faust admin data clear ", null, true)] public static void Clear(ChatCommandContext ctx, int days) { if (MayReset(ctx)) { if (days <= 0) { ctx.Reply("Give a positive day count, e.g. '.faust admin data clear 90'. (To erase everything, use '.faust admin data wipe activity confirm'.)"); return; } int value = Core.Store.ClearOlderThan(days); ctx.Reply($"Pruned {value} activity record(s) older than {days} day(s). " + "Playtime / first-seen / charts now reflect the trimmed window."); } } [Command("wipe", null, null, "Erase a collected-data store. Usage: .faust admin data wipe confirm", null, true)] public static void Wipe(ChatCommandContext ctx, string store, string confirm = null) { if (!MayReset(ctx)) { return; } store = store?.ToLowerInvariant(); bool flag; switch (store) { case "activity": case "unlocks": case "usage": case "heatmap": case "kills": case "all": flag = true; break; default: flag = false; break; } if (!flag) { ctx.Reply("Specify what to wipe: activity | unlocks | usage | heatmap | kills | all."); return; } if (!string.Equals(confirm, "confirm", StringComparison.OrdinalIgnoreCase)) { ctx.Reply($"This ERASES the '{store}' store and cannot be undone — {PreviewFor(store)}. Re-run as '.faust admin data wipe {store} confirm' to proceed."); return; } StringBuilder stringBuilder = new StringBuilder("Wiped"); if ((store == "activity" || store == "all") ? true : false) { StringBuilder stringBuilder2 = stringBuilder; StringBuilder stringBuilder3 = stringBuilder2; StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(21, 1, stringBuilder2); handler.AppendLiteral(" "); handler.AppendFormatted(Core.Store.WipeAll()); handler.AppendLiteral(" activity record(s);"); stringBuilder3.Append(ref handler); } if ((store == "unlocks" || store == "all") ? true : false) { StringBuilder stringBuilder2 = stringBuilder; StringBuilder stringBuilder4 = stringBuilder2; StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(32, 1, stringBuilder2); handler.AppendLiteral(" unlock progress for "); handler.AppendFormatted(Core.Unlock.WipeAll()); handler.AppendLiteral(" player(s);"); stringBuilder4.Append(ref handler); } if ((store == "usage" || store == "all") ? true : false) { StringBuilder stringBuilder2 = stringBuilder; StringBuilder stringBuilder5 = stringBuilder2; StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(41, 2, stringBuilder2); handler.AppendLiteral(" "); handler.AppendFormatted(Core.Usage.WipeAll()); handler.AppendLiteral(" usage lock(s) + "); handler.AppendFormatted(Core.UsageStats.WipeAll()); handler.AppendLiteral(" usage-tally bucket(s);"); stringBuilder5.Append(ref handler); } if ((store == "heatmap" || store == "all") ? true : false) { StringBuilder stringBuilder2 = stringBuilder; StringBuilder stringBuilder6 = stringBuilder2; StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(18, 1, stringBuilder2); handler.AppendLiteral(" "); handler.AppendFormatted(Core.Heatmap.WipeAll()); handler.AppendLiteral(" heatmap cell(s);"); stringBuilder6.Append(ref handler); } if ((store == "kills" || store == "all") ? true : false) { StringBuilder stringBuilder2 = stringBuilder; StringBuilder stringBuilder7 = stringBuilder2; StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(17, 1, stringBuilder2); handler.AppendLiteral(" "); handler.AppendFormatted(Core.Kills.WipeAll()); handler.AppendLiteral(" kill bucket(s);"); stringBuilder7.Append(ref handler); } stringBuilder.Append(" done."); ctx.Reply(stringBuilder.ToString()); } private static string PreviewFor(string store) { var (value, value2, value3, value4, _) = Core.Store.GetStorageStats(); var (value5, _, value6) = Core.Unlock.GetUnlockStats(); return store switch { "activity" => $"{value} session(s) + {value2} concurrency point(s) + {value3} region sample(s) + {value4} name(s) (playtime/charts reset)", "unlocks" => $"{value5} player(s)' V-blood defeats + {value6} grant(s) (progression gates reset)", "usage" => $"{Core.Usage.UsageCount} cooldown/window lock(s) + {Core.UsageStats.BucketCount} usage-tally bucket(s)", "heatmap" => $"{Core.Heatmap.GetStats().cells} heat-map cell(s) (player-position density reset)", "kills" => $"{Core.Kills.BucketCounts.playerBuckets + Core.Kills.BucketCounts.bossBuckets} kill bucket(s) (kill + boss-defeat leaderboards reset)", _ => "ALL collected data: activity, unlock progress, usage locks, the heat map, and kills", }; } private static string FmtDate(long unix) { return DateTimeOffset.FromUnixTimeSeconds(unix).UtcDateTime.ToString("yyyy-MM-dd") + " UTC"; } } [CommandGroup("faust api", null)] internal static class ApiCommands { private const int ApiVersion = 19; private static readonly string[] FeatureOrder = new string[13] { "playerpositions", "castleinfo", "playerinfo", "plotavailability", "castleresources", "stats", "allcastles", "decaywatch", "clans", "heatmap", "bosses", "kills", "worldscan" }; [Command("version", null, null, "BCH handshake: ApiVersion, ready flag, and each feature's resolved access + cost.", null, false)] public static void Version(ChatCommandContext ctx) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) bool flag = Core.IsReady && Settings.Enabled.Value; bool isAdmin = ctx.Event.User.IsAdmin; StringBuilder stringBuilder = new StringBuilder(); StringBuilder stringBuilder2 = stringBuilder.Append("[FAUST:version] "); StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(5, 1, stringBuilder2); handler.AppendLiteral("api="); handler.AppendFormatted(19); handler.AppendLiteral(" "); StringBuilder stringBuilder3 = stringBuilder2.Append(ref handler).Append("plugin=0.16.5 "); StringBuilder.AppendInterpolatedStringHandler handler2 = new StringBuilder.AppendInterpolatedStringHandler(6, 1, stringBuilder3); handler2.AppendLiteral("ready="); handler2.AppendFormatted(flag ? 1 : 0); stringBuilder3.Append(ref handler2); string[] featureOrder = FeatureOrder; foreach (string text in featureOrder) { FeatureConfig featureConfig = Settings.Feature(text); if (featureConfig != null) { stringBuilder.Append(' ').Append(text).Append('=') .Append(FaustAccessGate.AccessToken(featureConfig, isAdmin)) .Append(':') .Append(FaustAccessGate.CostToken(featureConfig)); } } ctx.Reply(stringBuilder.ToString()); } [Command("ping", null, null, "BCH probe: proves the Faust->BCH round-trip end to end.", null, false)] public static void Ping(ChatCommandContext ctx) { if (!Core.IsReady) { ctx.Reply("[FAUST:err] code=notready"); return; } ctx.Reply($"[FAUST:pong] api={19} plugin={"0.16.5"}"); } [Command("castleinfo", null, null, "BCH: castle/plot info. Usage: .faust api castleinfo ", null, false)] public static void CastleInfo(ChatCommandContext ctx, string token = "here") { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) FaustAccessGate.GateResult gate = FaustAccessGate.TryAuthorize(ctx, "castleinfo"); if (!gate.Allowed) { ctx.Reply(gate.DenyWire); return; } int result; if (string.Equals(token, "here", StringComparison.OrdinalIgnoreCase)) { result = Core.Castle.GetTerritoryIndexAt(SenderPos(ctx)); } else if (string.Equals(token, "nearest", StringComparison.OrdinalIgnoreCase)) { result = Core.Castle.GetNearestHeartTerritory(SenderPos(ctx)); } else if (!int.TryParse(token, out result)) { ctx.Reply("[FAUST:err] code=badtarget feature=castleinfo"); return; } if (result < 0 || !Core.Castle.TryGetTerritory(result, out var t, extras: true)) { ctx.Reply("[FAUST:err] code=notfound feature=castleinfo"); return; } ctx.Reply(CastleRow(in t, extras: true)); FaustAccessGate.Commit(ctx, gate); } [Command("castles", null, null, "BCH: every territory (claimed + open), full map (paged). Usage: .faust api castles [page]", null, false)] public static void Castles(ChatCommandContext ctx, int page = 1) { FaustAccessGate.GateResult gate = FaustAccessGate.TryAuthorize(ctx, "allcastles"); if (!gate.Allowed) { ctx.Reply(gate.DenyWire); return; } List allTerritories = Core.Castle.GetAllTerritories(); List list = new List(allTerritories.Count); foreach (CastleService.TerritoryInfo item in allTerritories) { list.Add(CastleRow(item)); } if (Wire.SendPage(ctx, "castles", list, page)) { FaustAccessGate.Commit(ctx, gate); } } [Command("decay", null, null, "BCH: claimed castles by soonest decay (paged). Usage: .faust api decay [page]", null, false)] public static void Decay(ChatCommandContext ctx, int page = 1) { FaustAccessGate.GateResult gate = FaustAccessGate.TryAuthorize(ctx, "decaywatch"); if (!gate.Allowed) { ctx.Reply(gate.DenyWire); return; } List castlesByDecay = Core.Castle.GetCastlesByDecay(); List list = new List(castlesByDecay.Count); foreach (CastleService.TerritoryInfo item in castlesByDecay) { list.Add(CastleRow(item)); } if (Wire.SendPage(ctx, "decay", list, page)) { FaustAccessGate.Commit(ctx, gate); } } [Command("plots", null, null, "BCH: free plots, largest first (paged). Usage: .faust api plots [page]", null, false)] public static void Plots(ChatCommandContext ctx, int page = 1) { FaustAccessGate.GateResult gate = FaustAccessGate.TryAuthorize(ctx, "plotavailability"); if (!gate.Allowed) { ctx.Reply(gate.DenyWire); return; } List freePlots = Core.Castle.GetFreePlots(); List list = new List(freePlots.Count); foreach (CastleService.TerritoryInfo item in freePlots) { string text = $"[FAUST:plot] tindex={item.TerritoryIndex} size={item.SizeBlocks} region={Wire.Region(item.Region)}"; if (Core.Castle.TryGetTerritoryCenter(item.TerritoryIndex, out var x, out var z)) { text = text + " posx=" + F(x) + " posz=" + F(z); } list.Add(text); } if (Wire.SendPage(ctx, "plots", list, page)) { FaustAccessGate.Commit(ctx, gate); } } [Command("pinfo", null, null, "BCH: player info. Usage: .faust api pinfo ", null, false)] public static void PlayerInfo(ChatCommandContext ctx, string nameOrId) { //IL_0048: Unknown result type (might be due to invalid IL or missing references) if (!EntityExtensions.TryResolvePlayer(nameOrId, out var steamId, out var _, out var _, out var _)) { FaustAccessGate.GateResult gateResult = FaustAccessGate.TryAuthorize(ctx, "playerinfo"); if (!gateResult.Allowed) { ctx.Reply(gateResult.DenyWire); } else { ctx.Reply("[FAUST:err] code=notfound feature=playerinfo"); } return; } bool bypassAccess = steamId == ctx.Event.User.PlatformId; FaustAccessGate.GateResult gate = FaustAccessGate.TryAuthorize(ctx, "playerinfo", bypassAccess); if (!gate.Allowed) { ctx.Reply(gate.DenyWire); return; } if (!Core.PlayerInfo.TryGetPlayer(steamId, out var snapshot)) { ctx.Reply("[FAUST:err] code=notfound feature=playerinfo"); return; } ctx.Reply($"[FAUST:player] steam={snapshot.SteamId} name={Wire.Safe(snapshot.Name)} online={snapshot.Online} lastonline={ToUnix(snapshot.LastConnected)} firstseen={snapshot.FirstSeenUnix} sessions={snapshot.Sessions} playmins={snapshot.PlayMinutes} freq={Freq(snapshot.FreqPerWeek)} peakhour={snapshot.PeakHour} daysidle={snapshot.DaysIdle}"); FaustAccessGate.Commit(ctx, gate); } [Command("resources", null, null, "BCH: sum a castle's container contents. Usage: .faust api resources [page]", null, false)] public static void Resources(ChatCommandContext ctx, string token = "here", int page = 1) { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) FaustAccessGate.GateResult gate = FaustAccessGate.TryAuthorize(ctx, "castleresources"); if (!gate.Allowed) { ctx.Reply(gate.DenyWire); return; } int result; if (string.Equals(token, "here", StringComparison.OrdinalIgnoreCase)) { result = Core.Castle.GetTerritoryIndexAt(SenderPos(ctx)); } else if (string.Equals(token, "nearest", StringComparison.OrdinalIgnoreCase)) { result = Core.Castle.GetNearestHeartTerritory(SenderPos(ctx)); } else if (!int.TryParse(token, out result)) { ctx.Reply("[FAUST:err] code=badtarget feature=castleresources"); return; } if (result < 0 || !Core.Castle.TrySummarizeResources(result, out var summary)) { ctx.Reply("[FAUST:err] code=notfound feature=castleresources"); return; } if (page <= 1) { ctx.Reply($"[FAUST:res] tindex={summary.TerritoryIndex} owner={Wire.Safe(summary.OwnerName)} steam={summary.OwnerSteamId} containers={summary.Containers} totalitems={summary.TotalItems} distinct={summary.Items.Count} prisoners={summary.Prisoners}"); } List list = new List(summary.Items.Count + summary.Prisoners); foreach (var item in summary.Items) { list.Add($"[FAUST:item] guid={item.guid} qty={item.qty} name={Wire.Safe(item.name)}"); } foreach (var prisoner in summary.PrisonerList) { list.Add($"[FAUST:prisoner] name={Wire.Safe(prisoner.name)} bloodtype={Wire.Safe(prisoner.bloodType)} bloodquality={prisoner.bloodQuality}"); } Wire.SendPage(ctx, "resources", list, page); FaustAccessGate.Commit(ctx, gate); } [Command("stats", null, null, "BCH: server stats. Usage: .faust api stats [page|player|days] [days|page]", null, false)] public static void Stats(ChatCommandContext ctx, string kind, string arg = null, string arg2 = null) { FaustAccessGate.GateResult gate = FaustAccessGate.TryAuthorize(ctx, "stats"); if (!gate.Allowed) { ctx.Reply(gate.DenyWire); return; } switch (kind?.ToLowerInvariant()) { case "playtime": { List list3 = new List(); int num2 = 1; foreach (var item in Core.Store.GetPlaytimeLeaderboard()) { list3.Add($"[FAUST:stat] kind=playtime rank={num2++} steam={item.steam} name={Wire.Safe(item.name)} value={item.minutes}"); } if (Wire.SendPage(ctx, "stats kind=playtime", list3, ArgPage(arg))) { FaustAccessGate.Commit(ctx, gate); } break; } case "concurrency": { List list8 = new List(); foreach (var item2 in Core.Store.GetConcurrency(int.MaxValue)) { list8.Add($"[FAUST:stat] kind=concurrency t={item2.t} avg={item2.count}"); } if (Wire.SendPage(ctx, "stats kind=concurrency", list8, ArgPage(arg))) { FaustAccessGate.Commit(ctx, gate); } break; } case "hours": { if (TryScope(ctx, arg, out var scope2, out var scopeToken2)) { long[] hourHistogram = Core.Store.GetHourHistogram(scope2); StringBuilder stringBuilder = new StringBuilder("[FAUST:hours] scope=" + scopeToken2); for (int i = 0; i < 24; i++) { StringBuilder stringBuilder2 = stringBuilder; StringBuilder stringBuilder3 = stringBuilder2; StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(3, 2, stringBuilder2); handler.AppendLiteral(" h"); handler.AppendFormatted(i, "00"); handler.AppendLiteral("="); handler.AppendFormatted(hourHistogram[i]); stringBuilder3.Append(ref handler); } ctx.Reply(stringBuilder.ToString()); int[] hourPlayerCounts = Core.Store.GetHourPlayerCounts(scope2); StringBuilder stringBuilder4 = new StringBuilder("[FAUST:hoursplayers] scope=" + scopeToken2); for (int j = 0; j < 24; j++) { StringBuilder stringBuilder2 = stringBuilder4; StringBuilder stringBuilder5 = stringBuilder2; StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(3, 2, stringBuilder2); handler.AppendLiteral(" p"); handler.AppendFormatted(j, "00"); handler.AppendLiteral("="); handler.AppendFormatted(hourPlayerCounts[j]); stringBuilder5.Append(ref handler); } ctx.Reply(stringBuilder4.ToString()); FaustAccessGate.Commit(ctx, gate); } break; } case "sessions": { if (TryScope(ctx, arg, out var scope, out var scopeToken)) { (int, int, int, int) sessionLengthBuckets = Core.Store.GetSessionLengthBuckets(scope); ctx.Reply($"[FAUST:sessions] scope={scopeToken} lt15={sessionLengthBuckets.Item1} m15_60={sessionLengthBuckets.Item2} h1_3={sessionLengthBuckets.Item3} gt3h={sessionLengthBuckets.Item4}"); FaustAccessGate.Commit(ctx, gate); } break; } case "daily": { int num5 = ArgDays(arg, 14); List list10 = new List(num5); foreach (var item3 in Core.Store.GetDailySeries(num5)) { list10.Add($"[FAUST:daily] day={item3.dayMidnightUtc} dau={item3.dau} minutes={item3.minutes} new={item3.newCount} returning={item3.dau - item3.newCount}"); } if (Wire.SendList(ctx, "daily", list10)) { FaustAccessGate.Commit(ctx, gate); } break; } case "newplayers": { int num4 = ArgDays(arg, 30); List list5 = new List(num4); foreach (var item4 in Core.Store.GetNewPlayersSeries(num4)) { list5.Add($"[FAUST:newplayers] day={item4.dayMidnightUtc} new={item4.newPlayers}"); } if (Wire.SendList(ctx, "newplayers", list5)) { FaustAccessGate.Commit(ctx, gate); } break; } case "weekdays": { if (TryScope(ctx, arg, out var scope3, out var scopeToken3)) { long[] weekdayHistogram = Core.Store.GetWeekdayHistogram(scope3); StringBuilder stringBuilder6 = new StringBuilder("[FAUST:weekdays] scope=" + scopeToken3); for (int k = 0; k < 7; k++) { StringBuilder stringBuilder2 = stringBuilder6; StringBuilder stringBuilder7 = stringBuilder2; StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(3, 2, stringBuilder2); handler.AppendLiteral(" d"); handler.AppendFormatted(k); handler.AppendLiteral("="); handler.AppendFormatted(weekdayHistogram[k]); stringBuilder7.Append(ref handler); } ctx.Reply(stringBuilder6.ToString()); FaustAccessGate.Commit(ctx, gate); } break; } case "pdaily": { if (string.IsNullOrWhiteSpace(arg) || !EntityExtensions.TryResolvePlayer(arg, out var steamId, out var _, out var _, out var _)) { ctx.Reply("[FAUST:err] code=notfound feature=stats"); break; } int days = ArgDays(arg2, 90); List list2 = new List(); foreach (var item5 in Core.Store.GetPlayerDailySeries(steamId, days)) { list2.Add($"[FAUST:pdaily] steam={steamId} day={item5.dayMidnightUtc} minutes={item5.minutes}"); } if (Wire.SendList(ctx, "pdaily", list2)) { FaustAccessGate.Commit(ctx, gate); } break; } case "population": { FaustStore.PopulationStats populationStats = Core.Store.GetPopulationStats(); ctx.Reply($"[FAUST:population] dau={populationStats.Dau} wau={populationStats.Wau} mau={populationStats.Mau} new_today={populationStats.NewToday} returning_today={populationStats.ReturningToday} stickiness={Dec(populationStats.Stickiness)} d1={Dec(populationStats.D1)} d7={Dec(populationStats.D7)} d30={Dec(populationStats.D30)}"); FaustAccessGate.Commit(ctx, gate); break; } case "recency": { (int, int, int, int, int) recencyBuckets = Core.Store.GetRecencyBuckets(); ctx.Reply($"[FAUST:recency] seen24h={recencyBuckets.Item1} seen7d={recencyBuckets.Item2} seen30d={recencyBuckets.Item3} dormant={recencyBuckets.Item4} total={recencyBuckets.Item5}"); FaustAccessGate.Commit(ctx, gate); break; } case "peak": { (int, long, double, int, int) concurrencySummary = Core.Store.GetConcurrencySummary(ArgDays(arg, 30)); ctx.Reply($"[FAUST:concsummary] peak={concurrencySummary.Item1} peak_t={concurrencySummary.Item2} avg={Dec(concurrencySummary.Item3)} p95={concurrencySummary.Item4} now={concurrencySummary.Item5}"); FaustAccessGate.Commit(ctx, gate); break; } case "players": { List playerRoster = Core.Store.GetPlayerRoster(); List list9 = new List(playerRoster.Count); foreach (FaustStore.PlayerRow item6 in playerRoster) { list9.Add($"[FAUST:prow] steam={item6.Steam} name={Wire.Safe(item6.Name)} online={item6.Online} lastonline={item6.LastOnlineUnix} active24h={item6.Active24h} active7d={item6.Active7d} sessions={item6.Sessions} playmins={item6.PlayMinutes} daysidle={item6.DaysIdle}"); } if (Wire.SendPage(ctx, "players", list9, ArgPage(arg))) { FaustAccessGate.Commit(ctx, gate); } break; } case "regions": { List<(string region, int players, int castles, int plots)> list6 = RegionStats.Gather(); List list7 = new List(list6.Count); foreach (var item7 in list6) { list7.Add($"[FAUST:region] name={Wire.Region(item7.region)} players={item7.players} castles={item7.castles} plots={item7.plots}"); } if (Wire.SendPage(ctx, "regions", list7, ArgPage(arg))) { FaustAccessGate.Commit(ctx, gate); } break; } case "regiondaily": { int num3 = ArgDays(arg, 30); List<(long day, string region, int castles, int plots, int players)> regionDaily = Core.Store.GetRegionDaily(num3); List list4 = new List(regionDaily.Count); foreach (var item8 in regionDaily) { list4.Add($"[FAUST:rdrow] day={item8.day} region={Wire.Region((item8.region == "-") ? null : item8.region)} castles={item8.castles} plots={item8.plots} players={item8.players}"); } if (Wire.SendPage(ctx, $"regiondaily days={num3}", list4, ArgPage(arg2))) { FaustAccessGate.Commit(ctx, gate); } break; } case "activegrid": { int num = ArgDays(arg, 30); List<(ulong steam, string name, int activeDays, string daysCsv)> activeGrid = Core.Store.GetActiveGrid(num); List list = new List(activeGrid.Count); foreach (var item9 in activeGrid) { list.Add($"[FAUST:agrow] steam={item9.steam} name={Wire.Safe(item9.name)} active={item9.activeDays} days={item9.daysCsv}"); } if (Wire.SendPage(ctx, $"activegrid days={num}", list, ArgPage(arg2))) { FaustAccessGate.Commit(ctx, gate); } break; } default: ctx.Reply("[FAUST:err] code=badtarget feature=stats"); break; } } [Command("clans", null, null, "BCH: clan composition — clanned vs independent + per-clan roster (paged). Usage: .faust api clans [page]", null, false)] public static void Clans(ChatCommandContext ctx, int page = 1) { FaustAccessGate.GateResult gate = FaustAccessGate.TryAuthorize(ctx, "clans"); if (!gate.Allowed) { ctx.Reply(gate.DenyWire); return; } ClanService.Composition composition = Core.Clan.GetComposition(); if (page <= 1) { ctx.Reply($"[FAUST:clansummary] clans={composition.Clans} clanned={composition.Clanned} independent={composition.Independent} online_clanned={composition.OnlineClanned} online_independent={composition.OnlineIndependent} largest={composition.Largest} avg={composition.AvgSize.ToString("0.0", CultureInfo.InvariantCulture)}"); } List list = new List(composition.ClanList.Count); foreach (ClanService.ClanInfo clan in composition.ClanList) { list.Add($"[FAUST:clan] name={Wire.Safe(clan.Name)} members={clan.Members} online={clan.Online} castles={clan.Castles} leader={Wire.Safe(clan.Leader)}"); } Wire.SendPage(ctx, "clans", list, page); FaustAccessGate.Commit(ctx, gate); } [Command("clanmembers", null, null, "BCH: a clan's member roster (paged). Usage: .faust api clanmembers [page]", null, false)] public static void ClanMembers(ChatCommandContext ctx, string clanName, int page = 1) { FaustAccessGate.GateResult gate = FaustAccessGate.TryAuthorize(ctx, "clans"); if (!gate.Allowed) { ctx.Reply(gate.DenyWire); return; } string name = (clanName ?? string.Empty).Trim(); List clanMembers = Core.Clan.GetClanMembers(name); if (clanMembers == null) { ctx.Reply("[FAUST:err] code=notfound feature=clans"); return; } List list = new List(clanMembers.Count); foreach (ClanService.ClanMember item in clanMembers) { list.Add($"[FAUST:clanmember] name={Wire.Safe(item.Name)} online={item.Online} role={(item.Leader ? "leader" : "member")}"); } Wire.SendPage(ctx, "clanmembers", list, page); FaustAccessGate.Commit(ctx, gate); } [Command("access", null, null, "BCH: per-feature access snapshot (paged). Usage: .faust api access [page]", null, false)] public static void Access(ChatCommandContext ctx, int page = 1) { FaustAccessGate.GateResult gate = FaustAccessGate.TryAuthorize(ctx, "stats"); if (!gate.Allowed) { ctx.Reply(gate.DenyWire); return; } List list = new List(FeatureOrder.Length); string[] featureOrder = FeatureOrder; foreach (string text in featureOrder) { FeatureConfig featureConfig = Settings.Feature(text); if (featureConfig != null) { string value = featureConfig.Access switch { AccessLevel.Players => "players", AccessLevel.AdminOnly => "admin", _ => "off", }; string value2 = (featureConfig.HasCost ? $"{featureConfig.CostItemGuid.Value}x{featureConfig.CostQuantity.Value}" : "0"); string value3 = (featureConfig.HasProximity ? $"{featureConfig.RequireNearPrefab.Value} neardist={featureConfig.RequireNearDistance.Value.ToString("0.#", CultureInfo.InvariantCulture)}" : "0 neardist=0"); list.Add($"[FAUST:access] feature={text} scope={value} cost={value2} cd={featureConfig.CooldownSeconds.Value} window={featureConfig.WindowSeconds.Value} period={featureConfig.PeriodSeconds.Value} maxuses={featureConfig.MaxUsesPerPeriod.Value} nearprefab={value3} granted={Core.Unlock.GrantedCount(text)} unlocked={Core.Unlock.UnlockedCount(featureConfig)}"); } } if (Wire.SendPage(ctx, "access", list, page)) { FaustAccessGate.Commit(ctx, gate); } } [Command("usage", null, null, "BCH: per-feature usage over the last N days (paged). Usage: .faust api usage [days=7] [page]", null, false)] public static void Usage(ChatCommandContext ctx, int days = 7, int page = 1) { FaustAccessGate.GateResult gate = FaustAccessGate.TryAuthorize(ctx, "stats"); if (!gate.Allowed) { ctx.Reply(gate.DenyWire); return; } List list = new List(); foreach (UsageStatsService.UsageRow item in Core.UsageStats.GetUsage(days)) { int value = Settings.Feature(item.Feature)?.CostItemGuid.Value ?? 0; list.Add($"[FAUST:usagerow] feature={item.Feature} uses={item.Uses} payers={item.Payers} itemspent={item.ItemSpent} item={value} cooldownhits={item.CooldownHits}"); } Wire.SendPage(ctx, "usage", list, page); FaustAccessGate.Commit(ctx, gate); } [Command("newplayers", null, null, "BCH: roster of players who first joined in the window (paged). Usage: .faust api newplayers roster [days=30] [page]", null, false)] public static void NewPlayers(ChatCommandContext ctx, string sub = "roster", int days = 30, int page = 1) { FaustAccessGate.GateResult gate = FaustAccessGate.TryAuthorize(ctx, "stats"); if (!gate.Allowed) { ctx.Reply(gate.DenyWire); return; } if (!string.Equals(sub, "roster", StringComparison.OrdinalIgnoreCase)) { ctx.Reply("[FAUST:err] code=badtarget feature=stats"); return; } days = Math.Clamp(days, 1, 90); Dictionary playerClanNames = Core.Clan.GetPlayerClanNames(); Dictionary dictionary = CastleCountsBySteam(); List<(ulong steam, string name, long firstSeen, long playMinutes)> newPlayersRoster = Core.Store.GetNewPlayersRoster(days); List list = new List(newPlayersRoster.Count); foreach (var item in newPlayersRoster) { string value2; string value = ((playerClanNames.TryGetValue(item.steam, out value2) && !string.IsNullOrEmpty(value2)) ? Wire.Safe(value2) : "-"); dictionary.TryGetValue(item.steam, out var value3); list.Add($"[FAUST:nprow] steam={item.steam} name={Wire.Safe(item.name)} firstseen={item.firstSeen} clan={value} playmins={item.playMinutes} castles={value3}"); } Wire.SendPage(ctx, $"newplayersroster days={days}", list, page); FaustAccessGate.Commit(ctx, gate); } [Command("sessions", null, null, "BCH: per-session online intervals (paged). Usage: .faust api sessions timeline [days=14] [page]", null, false)] public static void Sessions(ChatCommandContext ctx, string sub = "timeline", string target = "all", int days = 14, int page = 1) { FaustAccessGate.GateResult gate = FaustAccessGate.TryAuthorize(ctx, "stats"); if (!gate.Allowed) { ctx.Reply(gate.DenyWire); return; } if (!string.Equals(sub, "timeline", StringComparison.OrdinalIgnoreCase)) { ctx.Reply("[FAUST:err] code=badtarget feature=stats"); return; } days = Math.Clamp(days, 1, 90); ulong? steam = null; if (!string.Equals(target, "all", StringComparison.OrdinalIgnoreCase)) { if (!EntityExtensions.TryResolvePlayer(target, out var steamId, out var _, out var _, out var _)) { ctx.Reply("[FAUST:err] code=notfound feature=stats"); return; } steam = steamId; } List<(ulong steam, string name, long start, long end)> sessionTimeline = Core.Store.GetSessionTimeline(steam, days); List list = new List(sessionTimeline.Count); foreach (var item in sessionTimeline) { list.Add($"[FAUST:stl] steam={item.steam} name={Wire.Safe(item.name)} start={item.start} end={item.end}"); } Wire.SendPage(ctx, $"sessionstimeline days={days}", list, page); FaustAccessGate.Commit(ctx, gate); } [Command("positions", null, null, "BCH: positions of online players (paged). Usage: .faust api positions [page]", null, false)] public static void Positions(ChatCommandContext ctx, int page = 1) { FaustAccessGate.GateResult gate = FaustAccessGate.TryAuthorize(ctx, "playerpositions"); if (!gate.Allowed) { ctx.Reply(gate.DenyWire); return; } List onlinePositions = Core.PlayerInfo.GetOnlinePositions(); List list = new List(onlinePositions.Count); foreach (PlayerInfoService.PlayerPosition item in onlinePositions) { list.Add($"[FAUST:pos] steam={item.SteamId} name={Wire.Safe(item.Name)} x={F(item.X)} z={F(item.Z)} tindex={item.TerritoryIndex} region={Wire.Region(item.Region)}"); } if (Wire.SendPage(ctx, "positions", list, page)) { FaustAccessGate.Commit(ctx, gate); } } [Command("bosses", null, null, "BCH: V Blood boss status board (paged). Usage: .faust api bosses [page]", null, false)] public static void Bosses(ChatCommandContext ctx, int page = 1) { FaustAccessGate.GateResult gate = FaustAccessGate.TryAuthorize(ctx, "bosses"); if (!gate.Allowed) { ctx.Reply(gate.DenyWire); return; } List bosses = Core.Boss.GetBosses(); List list = new List(bosses.Count); foreach (BossService.BossSnapshot item in bosses) { list.Add(BossRow(item)); } Wire.SendPage(ctx, "bosses", list, page); FaustAccessGate.Commit(ctx, gate); } [Command("boss", null, null, "BCH: one V Blood boss by name or GUID. Usage: .faust api boss ", null, false)] public static void Boss(ChatCommandContext ctx, string nameOrGuid) { FaustAccessGate.GateResult gate = FaustAccessGate.TryAuthorize(ctx, "bosses"); if (!gate.Allowed) { ctx.Reply(gate.DenyWire); return; } if (!Core.Boss.TryGetBoss(nameOrGuid?.Trim(), out var snapshot)) { ctx.Reply("[FAUST:err] code=notfound feature=bosses"); return; } ctx.Reply(BossRow(snapshot)); FaustAccessGate.Commit(ctx, gate); } private static string BossRow(BossService.BossSnapshot b) { StringBuilder stringBuilder = new StringBuilder(); StringBuilder stringBuilder2 = stringBuilder; StringBuilder stringBuilder3 = stringBuilder2; StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(25, 2, stringBuilder2); handler.AppendLiteral("[FAUST:boss] guid="); handler.AppendFormatted(b.Guid); handler.AppendLiteral(" name="); handler.AppendFormatted(Wire.Safe(b.Name)); handler.AppendLiteral(" "); StringBuilder stringBuilder4 = stringBuilder3.Append(ref handler); StringBuilder stringBuilder5 = stringBuilder4; StringBuilder.AppendInterpolatedStringHandler handler2 = new StringBuilder.AppendInterpolatedStringHandler(17, 2, stringBuilder4); handler2.AppendLiteral("status="); handler2.AppendFormatted(b.Alive ? "up" : "down"); handler2.AppendLiteral(" defeated="); handler2.AppendFormatted(b.Defeated ? 1 : 0); stringBuilder5.Append(ref handler2); if (b.Alive) { int value = ((b.HpMax > 0f) ? ((int)Math.Round(b.Hp / b.HpMax * 100f)) : 0); stringBuilder2 = stringBuilder; StringBuilder stringBuilder6 = stringBuilder2; handler = new StringBuilder.AppendInterpolatedStringHandler(15, 3, stringBuilder2); handler.AppendLiteral(" x="); handler.AppendFormatted(F(b.X)); handler.AppendLiteral(" z="); handler.AppendFormatted(F(b.Z)); handler.AppendLiteral(" region="); handler.AppendFormatted(Wire.Region(b.Region)); handler.AppendLiteral(" "); stringBuilder4 = stringBuilder6.Append(ref handler); StringBuilder stringBuilder7 = stringBuilder4; handler2 = new StringBuilder.AppendInterpolatedStringHandler(24, 4, stringBuilder4); handler2.AppendLiteral("hp="); handler2.AppendFormatted(F(b.Hp)); handler2.AppendLiteral(" hpmax="); handler2.AppendFormatted(F(b.HpMax)); handler2.AppendLiteral(" hppct="); handler2.AppendFormatted(value); handler2.AppendLiteral(" level="); handler2.AppendFormatted(b.Level); stringBuilder7.Append(ref handler2); } return stringBuilder.ToString(); } [Command("kills", null, null, "BCH: top players by kills (paged). Usage: .faust api kills [days=0] [page] (days 0 = all-time)", null, false)] public static void Kills(ChatCommandContext ctx, int days = 0, int page = 1) { FaustAccessGate.GateResult gate = FaustAccessGate.TryAuthorize(ctx, "kills"); if (!gate.Allowed) { ctx.Reply(gate.DenyWire); return; } List topKillers = Core.Kills.GetTopKillers(days); List list = new List(topKillers.Count); for (int i = 0; i < topKillers.Count; i++) { KillTrackingService.KillRow killRow = topKillers[i]; list.Add($"[FAUST:kill] rank={i + 1} steam={killRow.Steam} name={Wire.Safe(Core.Store.GetName(killRow.Steam))} kills={killRow.Kills} pvp={killRow.Pvp}"); } Wire.SendPage(ctx, "kills", list, page); FaustAccessGate.Commit(ctx, gate); } [Command("bosskills", null, null, "BCH: V Blood defeat counts (paged). Usage: .faust api bosskills [days=0] [page] (days 0 = all-time)", null, false)] public static void BossKills(ChatCommandContext ctx, int days = 0, int page = 1) { //IL_00a8: Unknown result type (might be due to invalid IL or missing references) FaustAccessGate.GateResult gate = FaustAccessGate.TryAuthorize(ctx, "kills"); if (!gate.Allowed) { ctx.Reply(gate.DenyWire); return; } List bossKills = Core.Kills.GetBossKills(days); List list = new List(bossKills.Count); for (int i = 0; i < bossKills.Count; i++) { KillTrackingService.BossKillRow bossKillRow = bossKills[i]; list.Add($"[FAUST:bosskill] rank={i + 1} guid={bossKillRow.Guid} name={Wire.Safe(EntityExtensions.GetPrefabName(new PrefabGUID(bossKillRow.Guid)))} count={bossKillRow.Count}"); } Wire.SendPage(ctx, "bosskills", list, page); FaustAccessGate.Commit(ctx, gate); } [Command("worldscan", null, null, "BCH: map assets (units + resource nodes), filtered. Usage: .faust api worldscan [type=units|nodes|all,id=,bloodtype=,bloodqmin=<0-100>] [page]", null, false)] public static void WorldScan(ChatCommandContext ctx, string spec = "all", int page = 1) { FaustAccessGate.GateResult gate = FaustAccessGate.TryAuthorize(ctx, "worldscan"); if (!gate.Allowed) { ctx.Reply(gate.DenyWire); return; } WorldScanService.Filter filter = ParseScanFilter(spec); bool truncated; List assets = Core.WorldScan.GetAssets(filter, out truncated); List list = new List(assets.Count); foreach (WorldScanService.AssetSnapshot item in assets) { StringBuilder stringBuilder = new StringBuilder(); StringBuilder stringBuilder2 = stringBuilder; StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(32, 3, stringBuilder2); handler.AppendLiteral("[FAUST:asset] guid="); handler.AppendFormatted(item.Guid); handler.AppendLiteral(" name="); handler.AppendFormatted(Wire.Safe(item.Name)); handler.AppendLiteral(" kind="); handler.AppendFormatted(item.IsUnit ? "unit" : "node"); handler.AppendLiteral(" "); StringBuilder stringBuilder3 = stringBuilder2.Append(ref handler); StringBuilder stringBuilder4 = stringBuilder3; StringBuilder.AppendInterpolatedStringHandler handler2 = new StringBuilder.AppendInterpolatedStringHandler(13, 3, stringBuilder3); handler2.AppendLiteral("x="); handler2.AppendFormatted(F(item.X)); handler2.AppendLiteral(" z="); handler2.AppendFormatted(F(item.Z)); handler2.AppendLiteral(" region="); handler2.AppendFormatted(Wire.Region(item.Region)); stringBuilder4.Append(ref handler2); if (item.IsUnit) { if (item.HpMax > 0f) { stringBuilder3 = stringBuilder; StringBuilder stringBuilder5 = stringBuilder3; handler = new StringBuilder.AppendInterpolatedStringHandler(11, 2, stringBuilder3); handler.AppendLiteral(" hp="); handler.AppendFormatted(F(item.Hp)); handler.AppendLiteral(" hpmax="); handler.AppendFormatted(F(item.HpMax)); stringBuilder5.Append(ref handler); } stringBuilder3 = stringBuilder; StringBuilder stringBuilder6 = stringBuilder3; handler = new StringBuilder.AppendInterpolatedStringHandler(19, 2, stringBuilder3); handler.AppendLiteral(" bloodtype="); handler.AppendFormatted(Wire.Safe(item.BloodType)); handler.AppendLiteral(" bloodq="); handler.AppendFormatted(item.BloodQuality); stringBuilder6.Append(ref handler); if (item.UnitCategory != -1) { stringBuilder3 = stringBuilder; StringBuilder stringBuilder7 = stringBuilder3; handler = new StringBuilder.AppendInterpolatedStringHandler(10, 1, stringBuilder3); handler.AppendLiteral(" unittype="); handler.AppendFormatted(item.UnitCategory); stringBuilder7.Append(ref handler); } } else if (item.ResourceTier != -1) { stringBuilder3 = stringBuilder; StringBuilder stringBuilder8 = stringBuilder3; handler = new StringBuilder.AppendInterpolatedStringHandler(9, 1, stringBuilder3); handler.AppendLiteral(" restier="); handler.AppendFormatted(item.ResourceTier); stringBuilder8.Append(ref handler); } list.Add(stringBuilder.ToString()); } if (truncated) { ctx.Reply($"[FAUST:note] cmd=worldscan truncated=1 max={Settings.WorldScanMaxResults.Value}"); } Wire.SendPage(ctx, "worldscan", list, page); FaustAccessGate.Commit(ctx, gate); } private static WorldScanService.Filter ParseScanFilter(string spec) { string text = "all"; int result = 0; int result2 = 0; int bloodQMin = -1; int result3 = int.MinValue; string[] array = (spec ?? "").Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); foreach (string text2 in array) { int num = text2.IndexOf('='); if (num < 0) { text = NormalizeKind(text2, text); continue; } string text3 = text2.Substring(0, num).Trim().ToLowerInvariant(); string text4 = text2.Substring(num + 1).Trim(); switch (text3) { case "kind": case "type": text = NormalizeKind(text4, text); break; case "id": int.TryParse(text4, out result); break; case "bt": case "bloodtype": int.TryParse(text4, out result2); break; case "bloodqmin": case "quality": case "bloodq": { if (int.TryParse(text4, out var result4)) { bloodQMin = Math.Clamp(result4, 0, 100); } break; } case "unitcat": case "category": case "unittype": int.TryParse(text4, out result3); break; } } return new WorldScanService.Filter { Kind = text, Id = result, BloodType = result2, BloodQMin = bloodQMin, UnitType = result3 }; } private static string NormalizeKind(string v, string fallback) { switch (v.ToLowerInvariant()) { case "unit": case "npcs": case "units": case "npc": return "units"; case "node": case "nodes": case "resource": case "resources": return "nodes"; case "all": case "any": case "*": return "all"; default: return fallback; } } [Command("heatmap", null, null, "BCH: player-position heat map (density grid). Usage: .faust api heatmap [] [days=0] [page] (days 0 = all-time)", null, false)] public static void Heatmap(ChatCommandContext ctx, string target = "all", int days = 0, int page = 1) { FaustAccessGate.GateResult gate = FaustAccessGate.TryAuthorize(ctx, "heatmap"); if (!gate.Allowed) { ctx.Reply(gate.DenyWire); return; } if (days < 0) { days = 0; } ulong? steam = null; string value = "server"; if (!string.Equals(target, "all", StringComparison.OrdinalIgnoreCase) && !string.Equals(target, "server", StringComparison.OrdinalIgnoreCase)) { if (!EntityExtensions.TryResolvePlayer(target, out var steamId, out var _, out var _, out var _)) { ctx.Reply("[FAUST:err] code=notfound feature=heatmap"); return; } steam = steamId; value = steamId.ToString(); } HeatmapStore.HeatmapView heatmap = Core.Heatmap.GetHeatmap(steam, days); if (page <= 1) { string value2 = ""; if (heatmap.CellSize > 0f && Core.Castle.TryGetMapWorldBounds(out var minX, out var minZ, out var maxX, out var maxZ)) { value2 = $" mapbounds={CellIdx(minX, heatmap.CellSize)}:{CellIdx(minZ, heatmap.CellSize)}:{CellIdx(maxX, heatmap.CellSize)}:{CellIdx(maxZ, heatmap.CellSize)}"; } ctx.Reply($"[FAUST:hmhead] scope={value} days={days} retentiondays={Settings.HeatmapRetentionDays.Value} cell={F(heatmap.CellSize)} samples={heatmap.Samples} cells={heatmap.Cells.Count} bounds={heatmap.MinCx}:{heatmap.MinCz}:{heatmap.MaxCx}:{heatmap.MaxCz}{value2} collecting={Settings.HeatmapEnabled.Value}"); } List rows = PackCells(heatmap.Cells); if (Wire.SendPage(ctx, $"heatmap scope={value} days={days}", rows, page)) { FaustAccessGate.Commit(ctx, gate); } } private static List PackCells(List<(int cx, int cz, int count)> cells) { List list = new List(); StringBuilder stringBuilder = new StringBuilder(); foreach (var cell in cells) { string text = cell.cx + ":" + cell.cz + ":" + cell.count; if (stringBuilder.Length > 0 && stringBuilder.Length + 1 + text.Length > 480) { list.Add($"[FAUST:hmrow] data={stringBuilder}"); stringBuilder.Clear(); } if (stringBuilder.Length > 0) { stringBuilder.Append(','); } stringBuilder.Append(text); } if (stringBuilder.Length > 0) { list.Add($"[FAUST:hmrow] data={stringBuilder}"); } return list; } private static float3 SenderPos(ChatCommandContext ctx) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) LocalToWorld val = ctx.Event.SenderCharacterEntity.Read(); return ((LocalToWorld)(ref val)).Position; } private static Dictionary CastleCountsBySteam() { Dictionary dictionary = new Dictionary(); foreach (CastleService.TerritoryInfo allTerritory in Core.Castle.GetAllTerritories()) { if (allTerritory.HasHeart && allTerritory.OwnerSteamId != 0L) { dictionary.TryGetValue(allTerritory.OwnerSteamId, out var value); dictionary[allTerritory.OwnerSteamId] = value + 1; } } return dictionary; } private static int ArgPage(string arg) { if (!int.TryParse(arg, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) || result <= 0) { return 1; } return result; } private static int ArgDays(string arg, int fallback) { int result; return Math.Clamp((int.TryParse(arg, NumberStyles.Integer, CultureInfo.InvariantCulture, out result) && result > 0) ? result : fallback, 1, 90); } private static bool TryScope(ChatCommandContext ctx, string arg, out ulong? scope, out string scopeToken) { scope = null; scopeToken = "server"; if (string.IsNullOrWhiteSpace(arg)) { return true; } if (!EntityExtensions.TryResolvePlayer(arg, out var steamId, out var _, out var _, out var _)) { ctx.Reply("[FAUST:err] code=notfound feature=stats"); return false; } scope = steamId; scopeToken = steamId.ToString(); return true; } private static string CastleRow(in CastleService.TerritoryInfo t, bool extras = false) { StringBuilder stringBuilder = new StringBuilder($"[FAUST:castle] tindex={t.TerritoryIndex} owner={Wire.Safe(t.HasHeart ? t.OwnerName : "")} steam={t.OwnerSteamId} region={Wire.Region(t.Region)} size={t.SizeBlocks} state={CastleService.StateWire(t.State)} decay={t.DecaySeconds} online={t.OwnerOnline} lastonline={ToUnix(t.OwnerLastConnected)}"); if (Core.Castle.TryGetTerritoryCenter(t.TerritoryIndex, out var x, out var z)) { StringBuilder stringBuilder2 = stringBuilder; StringBuilder stringBuilder3 = stringBuilder2; StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(12, 2, stringBuilder2); handler.AppendLiteral(" posx="); handler.AppendFormatted(F(x)); handler.AppendLiteral(" posz="); handler.AppendFormatted(F(z)); stringBuilder3.Append(ref handler); } if (extras && t.HasHeart) { if (t.HeartLevel >= 0) { StringBuilder stringBuilder2 = stringBuilder; StringBuilder stringBuilder4 = stringBuilder2; StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(12, 1, stringBuilder2); handler.AppendLiteral(" heartlevel="); handler.AppendFormatted(t.HeartLevel); stringBuilder4.Append(ref handler); } if (t.Floors >= 0) { StringBuilder stringBuilder2 = stringBuilder; StringBuilder stringBuilder5 = stringBuilder2; StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(8, 1, stringBuilder2); handler.AppendLiteral(" floors="); handler.AppendFormatted(t.Floors); stringBuilder5.Append(ref handler); } if (t.ClaimedUnix >= 0) { StringBuilder stringBuilder2 = stringBuilder; StringBuilder stringBuilder6 = stringBuilder2; StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(9, 1, stringBuilder2); handler.AppendLiteral(" claimed="); handler.AppendFormatted(t.ClaimedUnix); stringBuilder6.Append(ref handler); } if (!string.IsNullOrEmpty(t.ClanName)) { StringBuilder stringBuilder2 = stringBuilder; StringBuilder stringBuilder7 = stringBuilder2; StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(6, 1, stringBuilder2); handler.AppendLiteral(" clan="); handler.AppendFormatted(Wire.Safe(t.ClanName)); stringBuilder7.Append(ref handler); } if (t.TotalItems >= 0) { StringBuilder stringBuilder2 = stringBuilder; StringBuilder stringBuilder8 = stringBuilder2; StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(7, 1, stringBuilder2); handler.AppendLiteral(" items="); handler.AppendFormatted(t.TotalItems); stringBuilder8.Append(ref handler); } } return stringBuilder.ToString(); } private static string F(float v) { return v.ToString("0.0", CultureInfo.InvariantCulture); } private static int CellIdx(float world, float cell) { return (int)Math.Floor(world / cell); } private static string Dec(double v) { return v.ToString("0.##", CultureInfo.InvariantCulture); } private static string Freq(double v) { if (!(v < 0.0)) { return v.ToString("0.0", CultureInfo.InvariantCulture); } return "-1"; } private static long ToUnix(long binary) { if (binary <= 0) { return binary; } try { return ((DateTimeOffset)DateTime.FromBinary(binary).ToUniversalTime()).ToUnixTimeSeconds(); } catch { return 0L; } } } [CommandGroup("faust", null)] internal static class HelpCommands { [Command("help", null, null, "Faust help menu. Usage: .faust help [players|castles|server|admin]", null, false)] public static void Help(ChatCommandContext ctx, string topic = null) { switch (topic?.ToLowerInvariant()) { case "players": ctx.Reply("PLAYERS: '.faust api pinfo ' — online state & last-online (yourself always; others admin-gated). Playtime/frequency stats arrive with session tracking. '.faust api positions' — online players' locations (admin-default)."); break; case "castles": ctx.Reply("CASTLES: '.faust api castleinfo ' — owner, region, size, decay state & time, owner online/last-online. '.faust api plots [page]' — open plots, largest first. '.faust api castles [page]' — every territory, full map (admin-default). '.faust api decay [page]' — claimed castles by soonest decay (admin-default). '.faust api resources ' — total resources in a castle (admin-default)."); break; case "server": ctx.Reply("SERVER: '.faust api stats playtime' — top players by total playtime; '.faust api stats concurrency' — population history; '.faust api stats hours [player]' — activity by hour-of-day; 'stats weekdays [player]' — by day of week; 'stats daily [days]' — DAU + play-minutes; 'stats newplayers [days]' — first-seen growth; 'stats sessions [player]' — session-length spread; 'stats pdaily [days]' — one player's daily trend; 'stats population' — DAU/WAU/MAU + retention; 'stats recency' — active vs dormant; 'stats peak' — concurrency peak/avg; 'stats regions' — players+castles per region; 'stats players' — per-player activity roster; '.faust api clans' — clanned vs independent + rosters (BCH charts). '.faust api kills [days]' — top killers; 'bosskills [days]' — V Blood defeat counts (days 0 = all-time). '.faust api worldscan [type=units|nodes,bloodqmin=N]' — map of NPC units (with blood type/quality) + resource nodes, filtered (admin-curated whitelist)."); break; case "admin": ctx.Reply("ADMIN — live config (immediate + persisted, no restart): '.faust admin set ' (no spaces) & 'get [setting]' for per-feature access/delivery/costitem/costqty/cooldown/window/period/maxuses/availability/unlock/nearprefab/neardist/adminsexempt; 'setglobal/getglobal' for global settings; 'resetcfg [setting]' restores defaults.\nOperational (no restart): '.faust admin block [minutes]', 'unblock', 'schedule ', 'status', 'grant|revoke ', 'unlocks '. Helpers: 'prefab ' looks up prefab IDs/names for commands; 'worldscan ' curates the asset map. Data: '.faust admin data status', 'data clear ', 'data wipe confirm'. '.faust api version' reports each feature's resolved access + price."); break; default: ctx.Reply("Faust help — topics: '.faust help players | castles | server | admin'.\nLive queries: '.faust api castleinfo|plots|pinfo|positions|bosses', plus the BCH handshake '.faust api version' / 'ping'. '.faust api bosses' / 'boss ' — V Blood status (where they are, health, up/down/defeated). More per docs/FAUST_DESIGN.md."); break; } } } internal static class RootCommands { [Command("faust", null, null, "Faust overview — what the mod does and how to get help.", null, false)] public static void Faust(ChatCommandContext ctx) { ctx.Reply("Faust, Lord of Investigation - on-demand intel about players, castles, plots, and the server (v0.16.5). Knowledge has a price: admins gate each query and may charge an item cost.\nBest used through the BloodCraftHub UI; also works as chat commands.\nLive: '.faust api castleinfo|plots|pinfo|positions'. Type '.faust help' for the menu."); } } }