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.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, objects, 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, or unlock. Built to run with its companion client Raphael, Lord of Wisdom. Pre-1.0 testing. Server-side only.")] [assembly: AssemblyFileVersion("0.15.0.0")] [assembly: AssemblyInformationalVersion("0.15.0+5301ddd2c961057a2094fb1bd35667879409d6a8")] [assembly: AssemblyProduct("Faust")] [assembly: AssemblyTitle("Faust")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.15.0.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 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 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(); } internal static void TryInitialize(string trigger) { //IL_019d: Unknown result type (might be due to invalid IL or missing references) //IL_01a4: 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_012c: Unknown result type (might be due to invalid IL or missing references) //IL_0132: 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(); 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(); } 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)); } } 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.15.0")] [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.15.0"); ((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.15.0"); ((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(); 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.15.0"; } } namespace Faust.Services { 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_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: 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_004e: 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_0054: 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_0065: Unknown result type (might be due to invalid IL or missing references) //IL_0074: 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_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0093: 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_0099: Unknown result type (might be due to invalid IL or missing references) //IL_009a: 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); 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) { string item = (buffTarget.Has() ? buffTarget.Read().GetPrefabName() : "Prisoner"); string item2 = "-"; int item3 = -1; if (buffTarget.Has()) { Blood val3 = buffTarget.Read(); item2 = val3.BloodType.GetPrefabName(); item3 = (int)Math.Round(val3.Quality); } list.Add((item, item2, item3)); } } } catch (Exception ex) { if (Settings.VerboseLogging.Value) { ManualLogSource log = Core.Log; bool flag = default(bool); BepInExWarningLogInterpolatedStringHandler val4 = new BepInExWarningLogInterpolatedStringHandler(40, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val4).AppendLiteral("[FAUST CASTLE] CollectPrisoners failed: "); ((BepInExLogInterpolatedStringHandler)val4).AppendFormatted(ex.Message); } log.LogWarning(val4); } } 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 (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_0110: Unknown result type (might be due to invalid IL or missing references) //IL_0117: Expected O, but got Unknown //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: 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); 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 SaveFile { public int SchemaVersion { get; set; } = 1; public float CellSize { get; set; } public long Samples { get; set; } public List Cells { 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 long _samples; private int _cellCount; private float _cellSize; private int _dirtyTicks; private bool _cappedLogged; private const int SaveEveryTicks = 5; private static string SavePath => Path.Combine(FaustPaths.DataDir, "heatmap.json"); 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_0191: Unknown result type (might be due to invalid IL or missing references) //IL_0198: Expected O, but got Unknown //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0103: 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(); _cellCount = 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; } ManualLogSource log = Core.Log; BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(68, 4, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[FAUST HEATMAP] loaded "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(_cellCount); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" cell(s) over "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(_grid.Count); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" player(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_010e: Unknown result type (might be due to invalid IL or missing references) //IL_0115: 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 }); } } 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) { //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Expected O, but got Unknown if (samples == null || samples.Count == 0) { return; } if (_cellSize <= 0f) { _cellSize = ((configCellSize > 0f) ? configCellSize : 25f); } float cellSize = _cellSize; 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 value)) { value = new Dictionary(); _grid[sample.steam] = value; } if (value.TryGetValue(key, out var value2)) { value[key] = value2 + 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; } value[key] = 1; _cellCount++; } _samples++; } if (++_dirtyTicks >= 5) { Flush(); } } public HeatmapView GetHeatmap(ulong? steam) { Dictionary dictionary = new Dictionary(); if (steam.HasValue) { if (_grid.TryGetValue(steam.Value, out var value)) { foreach (KeyValuePair item in value) { dictionary[item.Key] = item.Value; } } } else { foreach (Dictionary value3 in _grid.Values) { foreach (KeyValuePair item2 in value3) { dictionary.TryGetValue(item2.Key, out var value2); dictionary[item2.Key] = value2 + item2.Value; } } } long num = 0L; int num2 = 0; int num3 = 0; int num4 = 0; int num5 = 0; bool flag = true; List<(int, int, int)> list = new List<(int, int, int)>(dictionary.Count); foreach (KeyValuePair item3 in dictionary) { int num6 = Cx(item3.Key); int num7 = Cz(item3.Key); list.Add((num6, num7, item3.Value)); num += item3.Value; if (flag) { num2 = (num4 = num6); num3 = (num5 = num7); flag = false; continue; } if (num6 < num2) { num2 = num6; } if (num6 > num4) { num4 = num6; } if (num7 < num3) { num3 = num7; } if (num7 > num5) { num5 = num7; } } list.Sort(((int, int, int) a, (int, int, int) b) => b.Item3.CompareTo(a.Item3)); return new HeatmapView { Cells = list, Samples = num, CellSize = ((_cellSize <= 0f) ? Settings.HeatmapCellSize.Value : _cellSize), MinCx = num2, MinCz = num3, MaxCx = num4, MaxCz = num5 }; } public (int cells, long samples, int players) GetStats() { return (cells: _cellCount, samples: _samples, players: _grid.Count); } public int WipeAll() { int cellCount = _cellCount; _grid.Clear(); _cellCount = 0; _samples = 0L; _cellSize = 0f; _cappedLogged = false; Flush(); return cellCount; } } 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 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 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; } } } namespace Faust.Patches { [HarmonyPatch(typeof(DeathEventListenerSystem), "OnUpdate")] internal static class DeathEventListenerSystemPatch { [HarmonyPostfix] public static void OnUpdatePostfix(DeathEventListenerSystem __instance) { //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Expected O, but got Unknown //IL_001d: 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_0029: 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_003f: 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_0045: 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_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_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_006c: 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_0072: Unknown result type (might be due to invalid IL or missing references) if (!Core.IsReady || Core.Unlock == null || !Core.Unlock.TracksUnlocks) { 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() && val2.Died.Has()) { ulong steamId = val2.Killer.GetSteamId(); int value = val2.Died.GetPrefabGuid()._Value; if (steamId != 0L && value != 0) { Core.Unlock.RecordBossDefeat(steamId, value); } } } } catch (Exception ex) { ManualLogSource log = Core.Log; bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val3 = new BepInExErrorLogInterpolatedStringHandler(34, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val3).AppendLiteral("[FAUST UNLOCK] 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 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 ObjectScan = "objectscan"; 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"; 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 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 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)."); 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). Once reached, existing cells keep counting but no new cells are added. 0 = unlimited (not advised)."); 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, "objectscan", AccessLevel.AdminOnly, DeliveryMode.Free); 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); } 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) { StringBuilder stringBuilder = new StringBuilder(); if (!string.IsNullOrEmpty(feature)) { feature = feature.ToLowerInvariant(); if (CheckFeature(ctx, feature)) { ctx.Reply("Faust '" + feature + "': " + Core.Control.Describe(feature)); } return; } StringBuilder stringBuilder2 = stringBuilder; StringBuilder stringBuilder3 = stringBuilder2; StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(21, 1, stringBuilder2); handler.AppendLiteral("Faust control — all: "); handler.AppendFormatted(Core.Control.Describe("all")); stringBuilder3.AppendLine(ref handler); foreach (string key in Settings.Features.Keys) { stringBuilder2 = stringBuilder; StringBuilder stringBuilder4 = stringBuilder2; handler = new StringBuilder.AppendInterpolatedStringHandler(4, 2, stringBuilder2); handler.AppendLiteral(" "); handler.AppendFormatted(key); handler.AppendLiteral(": "); handler.AppendFormatted(Core.Control.Describe(key)); stringBuilder4.AppendLine(ref handler); } ctx.Reply(stringBuilder.ToString()); } [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; } } 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; StringBuilder stringBuilder = new StringBuilder(); StringBuilder stringBuilder2 = stringBuilder; StringBuilder stringBuilder3 = stringBuilder2; StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(52, 3, stringBuilder2); handler.AppendLiteral("Faust data — namespace: "); handler.AppendFormatted(value2); handler.AppendLiteral("; on disk ~"); handler.AppendFormatted(value); handler.AppendLiteral(" KB; retention: "); handler.AppendFormatted((value3 <= 0) ? "keep forever" : (value3 + " day(s)")); handler.AppendLiteral("."); stringBuilder3.AppendLine(ref handler); stringBuilder2 = stringBuilder; StringBuilder stringBuilder4 = stringBuilder2; handler = new StringBuilder.AppendInterpolatedStringHandler(106, 6, stringBuilder2); handler.AppendLiteral(" Activity: "); handler.AppendFormatted(item); handler.AppendLiteral(" session(s), "); handler.AppendFormatted(item2); handler.AppendLiteral(" concurrency point(s), "); handler.AppendFormatted(item3); handler.AppendLiteral(" region sample(s), "); handler.AppendFormatted(item4); handler.AppendLiteral(" name(s); oldest record "); handler.AppendFormatted((item5 <= 0) ? "—" : FmtDate(item5)); handler.AppendLiteral(". Online now: "); handler.AppendFormatted(Core.Store.OnlineCount); handler.AppendLiteral("."); stringBuilder4.AppendLine(ref handler); stringBuilder2 = stringBuilder; StringBuilder stringBuilder5 = stringBuilder2; handler = new StringBuilder.AppendInterpolatedStringHandler(53, 2, stringBuilder2); handler.AppendLiteral(" Collection: SessionTracking="); handler.AppendFormatted(Settings.SessionTracking.Value ? "on" : "off"); handler.AppendLiteral(", "); handler.AppendLiteral("ConcurrencySampling="); handler.AppendFormatted(Settings.ConcurrencySampling.Value ? "on" : "off"); handler.AppendLiteral("."); stringBuilder5.AppendLine(ref handler); (int cells, long samples, int players) stats = Core.Heatmap.GetStats(); int item9 = stats.cells; long item10 = stats.samples; int item11 = stats.players; stringBuilder2 = stringBuilder; StringBuilder stringBuilder6 = stringBuilder2; handler = new StringBuilder.AppendInterpolatedStringHandler(101, 5, stringBuilder2); handler.AppendLiteral(" Unlocks: "); handler.AppendFormatted(item6); handler.AppendLiteral(" player(s), "); handler.AppendFormatted(item7); handler.AppendLiteral(" V-blood defeat(s), "); handler.AppendFormatted(item8); handler.AppendLiteral(" grant(s). "); handler.AppendLiteral("Usage locks: "); handler.AppendFormatted(Core.Usage.UsageCount); handler.AppendLiteral(" pair(s); usage tally: "); handler.AppendFormatted(Core.UsageStats.BucketCount); handler.AppendLiteral(" bucket(s)."); stringBuilder6.AppendLine(ref handler); stringBuilder2 = stringBuilder; StringBuilder stringBuilder7 = stringBuilder2; handler = new StringBuilder.AppendInterpolatedStringHandler(50, 4, stringBuilder2); handler.AppendLiteral(" Heatmap: "); handler.AppendFormatted(Settings.HeatmapEnabled.Value ? $"collecting every {Math.Clamp(Settings.HeatmapSampleSeconds.Value, 30, 300)}s" : "off"); handler.AppendLiteral("; "); handler.AppendFormatted(item9); handler.AppendLiteral(" cell(s) over "); handler.AppendFormatted(item11); handler.AppendLiteral(" player(s), "); handler.AppendFormatted(item10); handler.AppendLiteral(" sample(s)."); stringBuilder7.AppendLine(ref handler); stringBuilder.Append("Manage: '.faust admin data clear ' (prune old activity) · '.faust admin data wipe confirm'."); ctx.Reply(stringBuilder.ToString()); } 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 "all": flag = true; break; default: flag = false; break; } if (!flag) { ctx.Reply("Specify what to wipe: activity | unlocks | usage | heatmap | 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); } 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)", _ => "ALL collected data: activity, unlock progress, usage locks, and the heat map", }; } 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 = 17; private static readonly string[] FeatureOrder = new string[11] { "playerpositions", "castleinfo", "playerinfo", "plotavailability", "objectscan", "castleresources", "stats", "allcastles", "decaywatch", "clans", "heatmap" }; [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(17); handler.AppendLiteral(" "); StringBuilder stringBuilder3 = stringBuilder2.Append(ref handler).Append("plugin=0.15.0 "); 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={17} plugin={"0.15.0"}"); } [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) { FaustAccessGate.GateResult gate = FaustAccessGate.TryAuthorize(ctx, "clans"); if (!gate.Allowed) { ctx.Reply(gate.DenyWire); return; } int page = 1; string text = (clanName ?? string.Empty).Trim(); int num = text.LastIndexOf(' '); if (num > 0 && int.TryParse(text.Substring(num + 1), NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) && result > 0) { page = result; text = text.Substring(0, num).TrimEnd(); } List clanMembers = Core.Clan.GetClanMembers(text); 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"); list.Add($"[FAUST:access] feature={text} scope={value} cost={value2} 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("heatmap", null, null, "BCH: player-position heat map (density grid). Usage: .faust api heatmap [] [page]", null, false)] public static void Heatmap(ChatCommandContext ctx, string target = "all", int page = 1) { FaustAccessGate.GateResult gate = FaustAccessGate.TryAuthorize(ctx, "heatmap"); if (!gate.Allowed) { ctx.Reply(gate.DenyWire); return; } ulong? steam = null; string text = "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; text = steamId.ToString(); } HeatmapStore.HeatmapView heatmap = Core.Heatmap.GetHeatmap(steam); if (page <= 1) { string value = ""; if (heatmap.CellSize > 0f && Core.Castle.TryGetMapWorldBounds(out var minX, out var minZ, out var maxX, out var maxZ)) { value = $" mapbounds={CellIdx(minX, heatmap.CellSize)}:{CellIdx(minZ, heatmap.CellSize)}:{CellIdx(maxX, heatmap.CellSize)}:{CellIdx(maxZ, heatmap.CellSize)}"; } ctx.Reply($"[FAUST:hmhead] scope={text} cell={F(heatmap.CellSize)} samples={heatmap.Samples} cells={heatmap.Cells.Count} bounds={heatmap.MinCx}:{heatmap.MinCz}:{heatmap.MaxCx}:{heatmap.MaxCz}{value} collecting={Settings.HeatmapEnabled.Value}"); } List rows = PackCells(heatmap.Cells); if (Wire.SendPage(ctx, "heatmap scope=" + text, 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)."); break; case "admin": ctx.Reply("ADMIN: per-feature access/cost/cooldown/window/PvP/unlock live in BepInEx/config/kdpen.Faust.cfg. Live (no restart): '.faust admin block [minutes]', 'unblock', 'schedule ', 'status', 'grant|revoke ', 'unlocks '. Collected data: '.faust admin data status', 'data clear ', 'data wipe confirm' (data survives a world wipe by default — reset it here when you want to). '.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', plus the BCH handshake '.faust api version' / 'ping'. More per docs/FAUST_DESIGN.md build order."); 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.15.0). 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."); } } }