Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of BeenThere v0.3.0
BeenThere.dll
Decompiled a day agousing System; 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.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("BeenThere")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.3.0.0")] [assembly: AssemblyInformationalVersion("0.3.0+d20677791b35f1ca7b3ad5c47733190e39436f4d")] [assembly: AssemblyProduct("BeenThere")] [assembly: AssemblyTitle("BeenThere")] [assembly: AssemblyVersion("0.3.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace BeenThere { internal static class Attribution { public static string Current; public static string Source; public static string Push(HitData hit, GameObject source) { string current = Current; Current = PlayerName(hit); Source = Sources.PrefabName(source); return current; } public static string Push(string name, GameObject source) { string current = Current; Current = name; Source = Sources.PrefabName(source); return current; } public static void Pop(string prev) { Current = prev; if (prev == null) { Source = null; } } public static string PlayerName(HitData hit) { //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_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0058: 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) if (hit == null || ((ZDOID)(ref hit.m_attacker)).IsNone()) { return null; } Character attacker = hit.GetAttacker(); Player val = (Player)(object)((attacker is Player) ? attacker : null); if (val != null) { return val.GetPlayerName(); } if ((Object)(object)ZNet.instance == (Object)null) { return null; } foreach (PlayerInfo player in ZNet.instance.GetPlayerList()) { if (player.m_characterID == hit.m_attacker) { return player.m_name; } } return null; } } internal static class Scale { private static float Rng() { return Random.value; } private static bool Stackable(GameObject prefab) { //IL_004b: Unknown result type (might be due to invalid IL or missing references) ItemDrop val = (((Object)(object)prefab != (Object)null) ? prefab.GetComponent<ItemDrop>() : null); if ((Object)(object)val == (Object)null) { return false; } SharedData shared = val.m_itemData.m_shared; if (shared.m_maxStackSize <= 1) { return false; } Game instance = Game.instance; if (!((Object)(object)instance == (Object)null)) { return !instance.m_nonScaledDropTypes.Contains(shared.m_itemType); } return true; } public static float Multiplier(GameObject prefab, bool creature = false) { Settings effective = Plugin.Effective; string name = ((Object)prefab).name; bool flag = Stackable(prefab); if (effective.Tiers.OfItem(name) == null && (effective.Scope != CatchUpScope.Everywhere || !flag)) { return 1f; } string sourceTier = ((effective.Scope == CatchUpScope.Everywhere) ? Sources.TierOf(Attribution.Source) : null); string current = Attribution.Current; if (current != null) { return Rules.Multiplier(effective, Plugin.Record(current), name, sourceTier, creature, flag); } Player localPlayer = Player.m_localPlayer; return Rules.MultiplierNoAttacker(effective, name, (string key) => (Object)(object)ZoneSystem.instance != (Object)null && ZoneSystem.instance.GetGlobalKey(key), ((Object)(object)localPlayer != (Object)null) ? Plugin.Record(localPlayer.GetPlayerName()) : null, sourceTier, creature, flag); } public static int Count(GameObject prefab, int count, bool creature = false) { float num = Multiplier(prefab, creature); if (num != 1f) { return Rules.Scale(count, num, Rng); } return count; } public static void DropList(List<GameObject> list) { if (list == null || list.Count == 0) { return; } Dictionary<string, float> dictionary = new Dictionary<string, float>(); List<GameObject> list2 = new List<GameObject>(); foreach (GameObject item in list) { if ((Object)(object)item == (Object)null) { continue; } if (!dictionary.TryGetValue(((Object)item).name, out var value)) { value = (dictionary[((Object)item).name] = Multiplier(item)); } if (value != 1f) { for (int num2 = Rules.Scale(1, value, Rng); num2 > 1; num2--) { list2.Add(item); } } } list.AddRange(list2); } } [HarmonyPatch] internal static class Patches { [Serializable] [CompilerGenerated] private sealed class <>c { public static readonly <>c <>9 = new <>c(); public static ConsoleEvent <>9__28_0; public static ConsoleOptionsFetcher <>9__28_1; internal void <Commands>b__28_0(ConsoleEventArgs args) { Terminal context = args.Context; string text = ((args.Length > 1) ? args[1].ToLowerInvariant() : "status"); if (text == "catchup" && args.Length > 2) { Sync.RequestCatchUp(args[2]); context.AddString("BeenThere: asked the server for catch-up " + args[2]); return; } switch (text) { case "set": { if (!Rules.SplitPlayerAndTier(args.Args, 2, out var player2, out var tier2)) { context.AddString("usage: beenthere set <character name> <tier|none> (server admins only)"); break; } Sync.RequestAdminCatchUp(player2, tier2); context.AddString("BeenThere: asked the server to set " + player2 + " catch-up " + tier2); break; } case "credit": case "uncredit": { if (!Rules.SplitPlayerAndTier(args.Args, 2, out var player, out var tier)) { context.AddString("usage: beenthere " + text + " <character name> <tier> (server admins only)"); break; } Sync.RequestAdminCredit(player, tier, text == "credit"); context.AddString("BeenThere: asked the server to " + text + " " + tier + " for " + player); break; } default: if (text != "status") { context.AddString("usage: beenthere status | beenthere catchup <tier|none> | beenthere set <character name> <tier|none> | beenthere credit|uncredit <character name> <tier>"); } else { Status(context); } break; } } internal List<string> <Commands>b__28_1() { return new List<string> { "status", "catchup", "set", "credit", "uncredit" }; } } private static readonly FieldRef<CharacterDrop, Character> dropCharacter = AccessTools.FieldRefAccess<CharacterDrop, Character>("m_character"); private static readonly FieldRef<Character, HitData> lastHit = AccessTools.FieldRefAccess<Character, HitData>("m_lastHit"); private static readonly List<KeyValuePair<GameObject, int>> pickExtras = new List<KeyValuePair<GameObject, int>>(); private static bool spawningExtras; private static readonly MethodInfo pickableDrop = AccessTools.Method(typeof(Pickable), "Drop", (Type[])null, (Type[])null); [HarmonyPatch(typeof(ZNet), "Awake")] [HarmonyPostfix] private static void ZNetAwake(ZNet __instance) { Plugin.Snapshot = new Snapshot(); Sync.Register(); if (__instance.IsServer()) { string path = Path.Combine(Path.Combine(Paths.ConfigPath, "BeenThere"), "state.txt"); Plugin.Server = new Server(Plugin.LocalSettings, path, __instance.GetWorldName()); } } [HarmonyPatch(typeof(ZNet), "OnDestroy")] [HarmonyPostfix] private static void ZNetDestroy() { Plugin.Server = null; Plugin.Snapshot = new Snapshot(); Attribution.Current = null; Attribution.Source = null; Sources.Reset(); } [HarmonyPatch(typeof(ZRoutedRpc), "AddPeer")] [HarmonyPostfix] private static void AddPeer(ZNetPeer peer) { if (Plugin.Server != null) { Plugin.Server.OnCharacterConnected(peer.m_playerName); Plugin.Server.Broadcast(peer.m_uid); } } [HarmonyPatch(typeof(TreeBase), "RPC_Damage")] [HarmonyPrefix] private static void TreeBasePre(TreeBase __instance, HitData hit, ref string __state) { __state = Attribution.Push(hit, ((Component)__instance).gameObject); } [HarmonyPatch(typeof(TreeBase), "RPC_Damage")] [HarmonyPostfix] private static void TreeBasePost(string __state) { Attribution.Pop(__state); } [HarmonyPatch(typeof(TreeLog), "RPC_Damage")] [HarmonyPrefix] private static void TreeLogPre(TreeLog __instance, HitData hit, ref string __state) { __state = Attribution.Push(hit, ((Component)__instance).gameObject); } [HarmonyPatch(typeof(TreeLog), "RPC_Damage")] [HarmonyPostfix] private static void TreeLogPost(string __state) { Attribution.Pop(__state); } [HarmonyPatch(typeof(MineRock), "RPC_Hit")] [HarmonyPrefix] private static void MineRockPre(MineRock __instance, HitData hit, ref string __state) { __state = Attribution.Push(hit, ((Component)__instance).gameObject); } [HarmonyPatch(typeof(MineRock), "RPC_Hit")] [HarmonyPostfix] private static void MineRockPost(string __state) { Attribution.Pop(__state); } [HarmonyPatch(typeof(MineRock5), "DamageArea")] [HarmonyPrefix] private static void MineRock5Pre(MineRock5 __instance, HitData hit, ref string __state) { __state = Attribution.Push(hit, ((Component)__instance).gameObject); } [HarmonyPatch(typeof(MineRock5), "DamageArea")] [HarmonyPostfix] private static void MineRock5Post(string __state) { Attribution.Pop(__state); } [HarmonyPatch(typeof(Destructible), "Destroy")] [HarmonyPrefix] private static void DestructiblePre(Destructible __instance, HitData hit, ref string __state) { __state = Attribution.Push(hit, ((Component)__instance).gameObject); } [HarmonyPatch(typeof(Destructible), "Destroy")] [HarmonyPostfix] private static void DestructiblePost(string __state) { Attribution.Pop(__state); } [HarmonyPatch(typeof(WearNTear), "Destroy")] [HarmonyPrefix] private static void WearNTearPre(WearNTear __instance, HitData hitData, ref string __state) { __state = Attribution.Push(hitData, ((Component)__instance).gameObject); } [HarmonyPatch(typeof(WearNTear), "Destroy")] [HarmonyPostfix] private static void WearNTearPost(string __state) { Attribution.Pop(__state); } [HarmonyPatch(typeof(DropTable), "GetDropList", new Type[] { })] [HarmonyPostfix] private static void DropList(ref List<GameObject> __result) { try { Scale.DropList(__result); } catch (Exception ex) { Plugin.Log.LogError((object)("drop scaling failed, vanilla drops kept: " + ex)); } } [HarmonyPatch(typeof(CharacterDrop), "GenerateDropList")] [HarmonyPostfix] private static void CreatureDrops(CharacterDrop __instance, ref List<KeyValuePair<GameObject, int>> __result) { Settings effective = Plugin.Effective; if ((!effective.CreatureDrops && effective.Scope != CatchUpScope.Everywhere) || __result == null) { return; } try { Character val = dropCharacter.Invoke(__instance); string prev = Attribution.Push(((Object)(object)val != (Object)null) ? lastHit.Invoke(val) : null, ((Object)(object)val != (Object)null) ? ((Component)val).gameObject : ((Component)__instance).gameObject); try { for (int i = 0; i < __result.Count; i++) { KeyValuePair<GameObject, int> keyValuePair = __result[i]; if (!((Object)(object)keyValuePair.Key == (Object)null)) { int num = Math.Min(100, Scale.Count(keyValuePair.Key, keyValuePair.Value, creature: true)); if (num != keyValuePair.Value) { __result[i] = new KeyValuePair<GameObject, int>(keyValuePair.Key, num); } } } } finally { Attribution.Pop(prev); } } catch (Exception ex) { Plugin.Log.LogError((object)("creature drop scaling failed, vanilla drops kept: " + ex)); } } [HarmonyPatch(typeof(Pickable), "RPC_Pick")] [HarmonyPrefix] private static void PickPre(Pickable __instance, long sender, ref string __state) { pickExtras.Clear(); __state = Attribution.Push(Sync.PlayerNameOfPeer(sender), ((Component)__instance).gameObject); } [HarmonyPatch(typeof(Pickable), "RPC_Pick")] [HarmonyPostfix] private static void PickPost(Pickable __instance, string __state) { try { spawningExtras = true; int num = 100; foreach (KeyValuePair<GameObject, int> pickExtra in pickExtras) { for (int i = 0; i < pickExtra.Value; i++) { pickableDrop.Invoke(__instance, new object[3] { pickExtra.Key, num++, 1 }); } } } catch (Exception ex) { Plugin.Log.LogError((object)("extra pickable drops failed: " + ex)); } finally { spawningExtras = false; pickExtras.Clear(); Attribution.Pop(__state); } } [HarmonyPatch(typeof(Pickable), "Drop")] [HarmonyPrefix] private static void PickDrop(GameObject prefab, ref int stack) { if ((Object)(object)prefab == (Object)null || spawningExtras) { return; } try { int num = Scale.Count(prefab, stack); if (num != stack) { ItemDrop component = prefab.GetComponent<ItemDrop>(); if ((Object)(object)component != (Object)null && component.m_itemData.m_shared.m_maxStackSize <= 1) { pickExtras.Add(new KeyValuePair<GameObject, int>(prefab, num - stack)); } else { stack = num; } } } catch (Exception ex) { Plugin.Log.LogError((object)("pickable scaling failed, vanilla stack kept: " + ex)); } } [HarmonyPatch(typeof(Character), "OnDeath")] [HarmonyPrefix] private static void BossDeath(Character __instance) { //IL_0047: Unknown result type (might be due to invalid IL or missing references) try { if (__instance.IsBoss() && __instance.IsOwner()) { ZNetView component = ((Component)__instance).GetComponent<ZNetView>(); List<string> attackers = BossRecord.Attackers(((Object)(object)component != (Object)null) ? component.GetZDO() : null, KnownNames()); Sync.ReportBossDeath(Sources.PrefabName(((Component)__instance).gameObject), ((Component)__instance).transform.position, attackers); } } catch (Exception ex) { Plugin.Log.LogError((object)("boss death report failed: " + ex)); } } private static List<string> KnownNames() { //IL_0059: 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_0061: Unknown result type (might be due to invalid IL or missing references) List<string> list = new List<string>(); WorldState worldState = ((Plugin.Server != null) ? Plugin.Server.State : Plugin.Snapshot.State); if (worldState != null) { list.AddRange(worldState.Players.Keys); } if ((Object)(object)ZNet.instance != (Object)null) { foreach (PlayerInfo player in ZNet.instance.GetPlayerList()) { list.Add(player.m_name); } } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer != (Object)null) { list.Add(localPlayer.GetPlayerName()); } return list; } [HarmonyPatch(typeof(ZoneSystem), "RPC_SetGlobalKey")] [HarmonyPrefix] private static void WorldKeySet(long sender, string name) { if (Plugin.Server == null) { return; } try { Plugin.Server.OnWorldKey(sender, name); } catch (Exception ex) { Plugin.Log.LogError((object)("world key credit failed: " + ex)); } } [HarmonyPatch(typeof(Terminal), "InitTerminal")] [HarmonyPostfix] private static void Commands() { //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_0029: Expected O, but got Unknown //IL_0051: 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_004e: Expected O, but got Unknown object obj = <>c.<>9__28_0; if (obj == null) { ConsoleEvent val = delegate(ConsoleEventArgs args) { Terminal context = args.Context; string text = ((args.Length > 1) ? args[1].ToLowerInvariant() : "status"); if (text == "catchup" && args.Length > 2) { Sync.RequestCatchUp(args[2]); context.AddString("BeenThere: asked the server for catch-up " + args[2]); } else { switch (text) { case "set": { if (!Rules.SplitPlayerAndTier(args.Args, 2, out var player2, out var tier2)) { context.AddString("usage: beenthere set <character name> <tier|none> (server admins only)"); } else { Sync.RequestAdminCatchUp(player2, tier2); context.AddString("BeenThere: asked the server to set " + player2 + " catch-up " + tier2); } break; } case "credit": case "uncredit": { if (!Rules.SplitPlayerAndTier(args.Args, 2, out var player, out var tier)) { context.AddString("usage: beenthere " + text + " <character name> <tier> (server admins only)"); } else { Sync.RequestAdminCredit(player, tier, text == "credit"); context.AddString("BeenThere: asked the server to " + text + " " + tier + " for " + player); } break; } default: if (text != "status") { context.AddString("usage: beenthere status | beenthere catchup <tier|none> | beenthere set <character name> <tier|none> | beenthere credit|uncredit <character name> <tier>"); } else { Status(context); } break; } } }; <>c.<>9__28_0 = val; obj = (object)val; } object obj2 = <>c.<>9__28_1; if (obj2 == null) { ConsoleOptionsFetcher val2 = () => new List<string> { "status", "catchup", "set", "credit", "uncredit" }; <>c.<>9__28_1 = val2; obj2 = (object)val2; } new ConsoleCommand("beenthere", "beenthere [status | catchup <tier|none> | set <character name> <tier|none> | credit <character name> <tier> | uncredit <character name> <tier>]: your BeenThere record, ask for a catch-up tier, or (admins) set anyone's catch-up or repair their kill record", (ConsoleEvent)obj, false, false, false, false, false, false, (ConsoleOptionsFetcher)obj2, false, false, false); } private static void Status(Terminal t) { Settings effective = Plugin.Effective; Player localPlayer = Player.m_localPlayer; string text = ((localPlayer != null) ? localPlayer.GetPlayerName() : null); PlayerRecord playerRecord = Plugin.Record(text); string text2 = Rules.EffectiveCatchUp(effective, playerRecord); t.AddString("BeenThere for " + (text ?? "(no player)") + ": tier rate " + effective.TierRate + "x, catch-up rate " + effective.CatchUpRate + "x, catch-up tier " + ((text2.Length == 0) ? "none" : text2) + ", catch-up scope " + effective.Scope.ToString() + ((effective.Scope == CatchUpScope.Everywhere) ? (" (excluded: " + (effective.ExcludeBossSummons ? "boss summons " : "") + (effective.ExcludeFarmables ? "farmables " : "") + (effective.ExcludeOceanClutter ? "ocean clutter" : "") + ")") : "") + ", kill credit " + effective.KillCredit); ZoneSystem instance = ZoneSystem.instance; using List<Tier>.Enumerator enumerator = effective.Tiers.Tiers.GetEnumerator(); Tier current; float num; int num2; object obj; string text3; bool flag; for (; enumerator.MoveNext(); text3 = (string)obj, flag = num2 == 0 && current.WorldKey.Length > 0 && (Object)(object)instance != (Object)null && instance.GetGlobalKey(current.WorldKey), t.AddString(" " + current.Name + ": " + num + "x (" + text3 + (flag ? ", cleared in this world but not credited to you" : "") + ") " + string.Join(", ", current.Items))) { current = enumerator.Current; num = Rules.Multiplier(effective, playerRecord, (current.Items.Length != 0) ? current.Items[0] : ""); if (playerRecord != null) { num2 = (playerRecord.Kills.Contains(current.Name) ? 1 : 0); if (num2 != 0) { obj = "killed"; continue; } } else { num2 = 0; } obj = ((num > 1f) ? "catch-up" : "locked"); } } } [BepInPlugin("syxe.BeenThere", "BeenThere", "0.3.0")] public class Plugin : BaseUnityPlugin { public const string Guid = "syxe.BeenThere"; public const string Name = "BeenThere"; public const string Version = "0.3.0"; public static ManualLogSource Log; public static Settings LocalSettings; internal static Server Server; internal static Snapshot Snapshot = new Snapshot(); public static Settings Effective { get { Settings settings; if (Server == null) { settings = Snapshot.Settings; if (settings == null) { return LocalSettings; } } else { settings = Server.Settings; } return settings; } } private void Awake() { Log = ((BaseUnityPlugin)this).Logger; LocalSettings = Bind(((BaseUnityPlugin)this).Config); BindMyTier(((BaseUnityPlugin)this).Config); Harmony.CreateAndPatchAll(typeof(Patches), "syxe.BeenThere"); Log.LogInfo((object)("BeenThere 0.3.0 loaded; tiers " + string.Join(",", LocalSettings.Tiers.Tiers.ConvertAll((Tier t) => t.Name)))); } private void Update() { Server?.Poll(Time.unscaledTime); } public static PlayerRecord Record(string playerName) { if (playerName == null) { return null; } if (Server == null) { return Snapshot.State?.Get(playerName); } return Server.State.Get(playerName); } private static void BindMyTier(ConfigFile c) { ConfigEntry<string> entry = c.Bind<string>("CatchUp", "MyTier", "", "Your own catch-up tier request (a tier name, or none). Changing this in game asks the server, which applies its allowlist and cap. Does nothing on the server itself."); entry.SettingChanged += delegate { if (Server == null && !((Object)(object)ZNet.instance == (Object)null) && !((Object)(object)Player.m_localPlayer == (Object)null)) { string text = (string.IsNullOrWhiteSpace(entry.Value) ? "none" : entry.Value.Trim()); Sync.RequestCatchUp(text); Log.LogInfo((object)("MyTier changed, asked the server for catch-up " + text)); } }; } private static Settings Bind(ConfigFile c) { Settings settings = new Settings(); settings.TierRate = c.Bind<float>("Rates", "TierRate", 3f, "Multiplier on a tier's items for a player who has killed that tier's boss.").Value; settings.CatchUpRate = c.Bind<float>("Rates", "CatchUpRate", 3f, "Multiplier on the items of every tier up to a player's catch-up tier, until they kill that tier's boss.").Value; settings.DefaultCatchUp = c.Bind<string>("CatchUp", "DefaultTier", "", "Catch-up tier for a character with no record yet; empty for none.").Value; settings.AllowedCatchUp = c.Bind<string>("CatchUp", "AllowedTiers", "", "Comma-separated tiers a player may pick with 'beenthere catchup'; empty allows every tier.").Value; settings.MaxCatchUp = c.Bind<string>("CatchUp", "MaxTier", "", "Highest tier a player may pick with 'beenthere catchup'; empty for no cap.").Value; settings.NoAttacker = c.Bind<NoAttackerRule>("Rules", "NoAttackerRule", NoAttackerRule.WorldKey, "Rate for drops with no player on the hit (chain-felled trees, collapsing ore, fire): WorldKey gives the tier rate once the world's boss key is set, ZoneOwner uses the local player's record, None gives 1x.").Value; settings.CreatureDrops = c.Bind<bool>("Rules", "CreatureDrops", false, "Also scale tier items dropped by creatures (black metal scrap from fulings).").Value; settings.KillCreditRadius = c.Bind<float>("Rules", "KillCreditRadius", 0f, "Under the Connected rule, credit a boss kill only to connected players within this many metres of the death; 0 credits every connected player.").Value; settings.KillCredit = c.Bind<KillCredit>("Rules", "KillCredit", KillCredit.Attackers, "Who a boss death credits. Attackers: every character that landed a hit on the boss, read from the boss's own record (the Connected rule when that record cannot be read). Connected: every connected character, within KillCreditRadius when set.").Value; settings.TierRateMetals = c.Bind<bool>("Rules", "TierRateMetals", true, "Ore and scrap count for the tier rate. Off keeps the tier rate to wood and stone; the catch-up still covers them.").Value; settings.Scope = c.Bind<CatchUpScope>("CatchUp", "Scope", CatchUpScope.Everywhere, "Everywhere: every stackable drop from a biome at or below the player's catch-up tier (minus the excluded categories). TableOnly: only the tier table's items.").Value; settings.ExcludeBossSummons = c.Bind<bool>("CatchUp", "ExcludeBossSummons", true, "Catch-up leaves boss summoning items at vanilla: " + string.Join(", ", Exclusions.BossSummons) + ".").Value; settings.ExcludeFarmables = c.Bind<bool>("CatchUp", "ExcludeFarmables", true, "Catch-up leaves planted crops and their seeds at vanilla: " + string.Join(", ", Exclusions.Farmables) + ".").Value; settings.ExcludeOceanClutter = c.Bind<bool>("CatchUp", "ExcludeOceanClutter", true, "Catch-up leaves ocean creature parts at vanilla: " + string.Join(", ", Exclusions.OceanClutter) + ".").Value; string value = c.Bind<string>("Tiers", "Order", "Meadows,BlackForest,Swamp,Mountain,Plains,Mistlands,Ashlands,DeepNorth", "Tier names from first to last biome. Each needs its own key in this section: bosses|world key|items.").Value; Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); string[] array = value.Split(','); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length != 0) { TierTable.DefaultSpecs.TryGetValue(text, out var value2); dictionary[text] = c.Bind<string>("Tiers", text, value2 ?? "", "boss prefab(s)|world key|item prefab(s), comma-separated within a field.").Value; } } string error; TierTable tierTable = TierTable.Parse(value, dictionary, out error); if (tierTable == null) { Log.LogError((object)("tier config rejected, using the built-in table: " + error)); tierTable = TierTable.Default(); } settings.Tiers = tierTable; return settings; } } public enum NoAttackerRule { WorldKey, ZoneOwner, None } public enum CatchUpScope { TableOnly, Everywhere } public enum KillCredit { Attackers, Connected } public static class Exclusions { public static readonly HashSet<string> Metals = Set("CopperOre", "TinOre", "IronScrap", "SilverOre", "BlackMetalScrap", "FlametalOreNew", "GoldOre"); public static readonly HashSet<string> BossSummons = Set("TrophyDeer", "AncientSeed", "WitheredBone", "DragonEgg", "GoblinTotem", "TrophySeekerBrute", "DvergrKeyFragment", "BellFragment", "Bell", "HatefulBlood"); public static readonly HashSet<string> Farmables = Set("Carrot", "CarrotSeeds", "Turnip", "TurnipSeeds", "Onion", "OnionSeeds", "Barley", "Flax", "MushroomJotunPuffs", "MushroomMagecap", "Kale", "KaleSeeds", "Poteitr", "PoteitrSeeds", "OatSeeds", "Vineberry", "VineberrySeeds", "VineGreenSeeds"); public static readonly HashSet<string> OceanClutter = Set("Chitin", "SerpentScale", "BonemawSerpentScale"); private static HashSet<string> Set(params string[] names) { return new HashSet<string>(names, StringComparer.OrdinalIgnoreCase); } public static bool IsExcluded(Settings s, string item) { if ((!s.ExcludeBossSummons || !BossSummons.Contains(item)) && (!s.ExcludeFarmables || !Farmables.Contains(item))) { if (s.ExcludeOceanClutter) { return OceanClutter.Contains(item); } return false; } return true; } } public sealed class Settings { public float TierRate = 3f; public float CatchUpRate = 3f; public string DefaultCatchUp = ""; public string AllowedCatchUp = ""; public string MaxCatchUp = ""; public NoAttackerRule NoAttacker; public bool CreatureDrops; public float KillCreditRadius; public KillCredit KillCredit; public bool TierRateMetals = true; public CatchUpScope Scope = CatchUpScope.Everywhere; public bool ExcludeBossSummons = true; public bool ExcludeFarmables = true; public bool ExcludeOceanClutter = true; public TierTable Tiers = TierTable.Default(); public IEnumerable<string> Lines() { yield return "tierrate=" + TierRate.ToString("R", CultureInfo.InvariantCulture); yield return "catchuprate=" + CatchUpRate.ToString("R", CultureInfo.InvariantCulture); yield return "defaultcatchup=" + DefaultCatchUp; yield return "allowedcatchup=" + AllowedCatchUp; yield return "maxcatchup=" + MaxCatchUp; yield return "noattacker=" + NoAttacker; yield return "creaturedrops=" + (CreatureDrops ? "true" : "false"); yield return "killcreditradius=" + KillCreditRadius.ToString("R", CultureInfo.InvariantCulture); yield return "killcredit=" + KillCredit; yield return "tierratemetals=" + (TierRateMetals ? "true" : "false"); yield return "scope=" + Scope; yield return "excludebosssummons=" + (ExcludeBossSummons ? "true" : "false"); yield return "excludefarmables=" + (ExcludeFarmables ? "true" : "false"); yield return "excludeoceanclutter=" + (ExcludeOceanClutter ? "true" : "false"); foreach (string item in Tiers.Lines()) { yield return "tier=" + item; } } public static Settings FromLines(IEnumerable<string> lines, out string error) { Settings settings = new Settings(); List<string> list = new List<string>(); foreach (string line in lines) { int num = line.IndexOf('='); if (num < 0) { error = "settings line without '=': " + line; return null; } string text = line.Substring(0, num).Trim().ToLowerInvariant(); string text2 = line.Substring(num + 1).Trim(); switch (text) { case "tierrate": if (!Float(text2, out settings.TierRate)) { error = "bad tierrate"; return null; } break; case "catchuprate": if (!Float(text2, out settings.CatchUpRate)) { error = "bad catchuprate"; return null; } break; case "defaultcatchup": settings.DefaultCatchUp = text2; break; case "allowedcatchup": settings.AllowedCatchUp = text2; break; case "maxcatchup": settings.MaxCatchUp = text2; break; case "noattacker": if (!Enum.TryParse<NoAttackerRule>(text2, ignoreCase: true, out settings.NoAttacker)) { error = "bad noattacker: " + text2; return null; } break; case "creaturedrops": settings.CreatureDrops = text2.Equals("true", StringComparison.OrdinalIgnoreCase); break; case "killcreditradius": if (!Float(text2, out settings.KillCreditRadius)) { error = "bad killcreditradius"; return null; } break; case "killcredit": if (!Enum.TryParse<KillCredit>(text2, ignoreCase: true, out settings.KillCredit)) { error = "bad killcredit: " + text2; return null; } break; case "tierratemetals": settings.TierRateMetals = Bool(text2); break; case "scope": if (!Enum.TryParse<CatchUpScope>(text2, ignoreCase: true, out settings.Scope)) { error = "bad scope: " + text2; return null; } break; case "excludebosssummons": settings.ExcludeBossSummons = Bool(text2); break; case "excludefarmables": settings.ExcludeFarmables = Bool(text2); break; case "excludeoceanclutter": settings.ExcludeOceanClutter = Bool(text2); break; case "tier": list.Add(text2); break; } } settings.Tiers = TierTable.FromLines(list, out error); if (settings.Tiers != null) { return settings; } return null; } private static bool Bool(string text) { return text.Equals("true", StringComparison.OrdinalIgnoreCase); } private static bool Float(string text, out float value) { return float.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out value); } } public sealed class PlayerRecord { public string Name; public readonly HashSet<string> Kills = new HashSet<string>(StringComparer.OrdinalIgnoreCase); public string CatchUp; public PlayerRecord(string name) { Name = name; } } public static class Rules { public static float Multiplier(Settings s, PlayerRecord player, string item, string sourceTier = null, bool creature = false, bool stackable = true) { float num = 1f; Tier tier = s.Tiers.OfItem(item); if (tier != null && player != null && player.Kills.Contains(tier.Name) && (s.TierRateMetals || !Exclusions.Metals.Contains(item)) && (!creature || s.CreatureDrops)) { num = Math.Max(num, s.TierRate); } Tier tier2 = s.Tiers.ByName(EffectiveCatchUp(s, player)); if (tier2 != null && stackable && !Exclusions.IsExcluded(s, item)) { Tier tier3 = tier; if (tier3 == null && s.Scope == CatchUpScope.Everywhere) { tier3 = s.Tiers.ByName(sourceTier); } bool flag = !creature || s.CreatureDrops || s.Scope == CatchUpScope.Everywhere; if (tier3 != null && flag && tier2.Index >= tier3.Index) { num = Math.Max(num, s.CatchUpRate); } } return num; } public static float MultiplierNoAttacker(Settings s, string item, Func<string, bool> worldKeySet, PlayerRecord zoneOwner, string sourceTier = null, bool creature = false, bool stackable = true) { switch (s.NoAttacker) { case NoAttackerRule.WorldKey: { Tier tier = s.Tiers.OfItem(item); if (tier == null || (creature && !s.CreatureDrops) || (!s.TierRateMetals && Exclusions.Metals.Contains(item))) { return 1f; } if (tier.WorldKey.Length <= 0 || worldKeySet == null || !worldKeySet(tier.WorldKey)) { return 1f; } return s.TierRate; } case NoAttackerRule.ZoneOwner: if (zoneOwner != null) { return Multiplier(s, zoneOwner, item, sourceTier, creature, stackable); } return 1f; default: return 1f; } } public static string EffectiveCatchUp(Settings s, PlayerRecord player) { string text = player?.CatchUp ?? s.DefaultCatchUp; if (!IsNone(text)) { return text; } return ""; } public static bool CatchUpEndsWith(Settings s, PlayerRecord player, Tier credited) { Tier tier = s.Tiers.ByName(EffectiveCatchUp(s, player)); if (tier != null && credited != null) { return tier.Index <= credited.Index; } return false; } public static List<string> Attackers(IEnumerable<string> names, Func<string, bool> flagged) { HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal); List<string> list = new List<string>(); foreach (string name in names) { if (!string.IsNullOrEmpty(name) && hashSet.Add(name) && flagged(name)) { list.Add(name); } } return list; } public static bool IsNone(string tier) { if (!string.IsNullOrWhiteSpace(tier)) { return tier.Equals("none", StringComparison.OrdinalIgnoreCase); } return true; } public static string ValidateCatchUp(Settings s, string requested, out string canonical) { canonical = ""; if (IsNone(requested)) { return null; } Tier tier = s.Tiers.ByName(requested.Trim()); if (tier == null) { return "unknown tier " + requested + "; tiers are " + string.Join(", ", s.Tiers.Tiers.Select((Tier t) => t.Name)); } if (!string.IsNullOrWhiteSpace(s.AllowedCatchUp) && !(from a in s.AllowedCatchUp.Split(',') select a.Trim() into a where a.Length > 0 select a).Contains<string>(tier.Name, StringComparer.OrdinalIgnoreCase)) { return tier.Name + " is not an allowed catch-up tier; allowed: " + s.AllowedCatchUp; } Tier tier2 = s.Tiers.ByName(s.MaxCatchUp); if (tier2 != null && tier.Index > tier2.Index) { return "catch-up is capped at " + tier2.Name; } canonical = tier.Name; return null; } public static string ValidateTier(Settings s, string requested, out string canonical) { canonical = ""; if (IsNone(requested)) { return null; } Tier tier = s.Tiers.ByName(requested.Trim()); if (tier == null) { return "unknown tier " + requested + "; tiers are " + string.Join(", ", s.Tiers.Tiers.Select((Tier t) => t.Name)); } canonical = tier.Name; return null; } public static bool SplitPlayerAndTier(string[] args, int start, out string player, out string tier) { player = (tier = null); if (args == null || args.Length < start + 2) { return false; } tier = args[^1]; player = string.Join(" ", args, start, args.Length - 1 - start).Trim(); return player.Length > 0; } public static int Scale(int count, float m, Func<float> rng) { if (count <= 0 || m <= 1f) { return count; } float num = (float)count * m; int num2 = (int)Math.Floor(num); float num3 = num - (float)num2; if (num3 > 0f && rng() < num3) { num2++; } return Math.Max(count, num2); } } internal static class Sources { private static Dictionary<string, int> tierIndex; private static TierTable builtFor; private static readonly Biome[] biomes; public static void Reset() { tierIndex = null; builtFor = null; } public static string TierOf(string prefab) { if (string.IsNullOrEmpty(prefab)) { return null; } TierTable tiers = Plugin.Effective.Tiers; if (tierIndex == null || builtFor != tiers) { Build(tiers); } if (tierIndex == null) { return null; } if (!tierIndex.TryGetValue(prefab, out var value)) { return null; } return tiers.Tiers[value].Name; } private static void Build(TierTable tiers) { //IL_0038: 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) ZoneSystem instance = ZoneSystem.instance; if ((Object)(object)instance == (Object)null) { return; } Dictionary<string, int> dictionary = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase); int num = 0; foreach (ZoneVegetation item in instance.m_vegetation) { int num2 = Lowest(tiers, item.m_biome); if ((Object)(object)item.m_prefab != (Object)null && num2 >= 0) { num += Register(dictionary, item.m_prefab, num2, 0); } } SpawnSystem val = (((Object)(object)instance.m_zoneCtrlPrefab != (Object)null) ? instance.m_zoneCtrlPrefab.GetComponent<SpawnSystem>() : null); if ((Object)(object)val != (Object)null) { foreach (SpawnSystemList spawnList in val.m_spawnLists) { foreach (SpawnData spawner in spawnList.m_spawners) { int num3 = Lowest(tiers, spawner.m_biome); if ((Object)(object)spawner.m_prefab != (Object)null && num3 >= 0) { num += Register(dictionary, spawner.m_prefab, num3, 0); } } } } tierIndex = dictionary; builtFor = tiers; Plugin.Log.LogInfo((object)("source tiers built: " + dictionary.Count + " prefabs from " + instance.m_vegetation.Count + " vegetation entries" + (((Object)(object)val != (Object)null) ? (" and " + val.m_spawnLists.Count + " spawn lists") : ", no spawn lists"))); } private static int Register(Dictionary<string, int> map, GameObject prefab, int idx, int depth) { if ((Object)(object)prefab == (Object)null || depth > 4) { return 0; } int num = 0; if (!map.TryGetValue(((Object)prefab).name, out var value) || idx < value) { map[((Object)prefab).name] = idx; num = 1; } TreeBase component = prefab.GetComponent<TreeBase>(); if ((Object)(object)component != (Object)null) { num += Register(map, component.m_logPrefab, idx, depth + 1); } TreeLog component2 = prefab.GetComponent<TreeLog>(); if ((Object)(object)component2 != (Object)null) { num += Register(map, component2.m_subLogPrefab, idx, depth + 1); } Destructible component3 = prefab.GetComponent<Destructible>(); if ((Object)(object)component3 != (Object)null) { num += Register(map, component3.m_spawnWhenDestroyed, idx, depth + 1); } SpawnArea component4 = prefab.GetComponent<SpawnArea>(); if ((Object)(object)component4 != (Object)null) { foreach (SpawnData prefab2 in component4.m_prefabs) { num += Register(map, prefab2.m_prefab, idx, depth + 1); } } return num; } private unsafe static int Lowest(TierTable tiers, Biome mask) { //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_0011: 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) int num = -1; Biome[] array = biomes; for (int i = 0; i < array.Length; i++) { Biome val = array[i]; if ((mask & val) != 0) { Tier tier = tiers.OfBiome(((object)(*(Biome*)(&val))/*cast due to .constrained prefix*/).ToString()); if (tier != null && (num < 0 || tier.Index < num)) { num = tier.Index; } } } return num; } public static string PrefabName(GameObject go) { if ((Object)(object)go == (Object)null) { return null; } string name = ((Object)go).name; int num = name.IndexOf("(Clone)", StringComparison.Ordinal); if (num <= 0) { return name; } return name.Substring(0, num); } static Sources() { Biome[] array = new Biome[9]; RuntimeHelpers.InitializeArray(array, (RuntimeFieldHandle)/*OpCode not supported: LdMemberToken*/); biomes = (Biome[])(object)array; } } public sealed class WorldState { public string World = ""; public readonly Dictionary<string, PlayerRecord> Players = new Dictionary<string, PlayerRecord>(StringComparer.OrdinalIgnoreCase); public PlayerRecord Get(string name) { if (name == null || !Players.TryGetValue(name, out var value)) { return null; } return value; } public PlayerRecord GetOrAdd(string name) { PlayerRecord playerRecord = Get(name); if (playerRecord == null) { playerRecord = (Players[name] = new PlayerRecord(name)); } return playerRecord; } public bool Credit(IEnumerable<string> names, string tier) { bool flag = false; foreach (string name in names) { if (ValidName(name)) { flag |= GetOrAdd(name).Kills.Add(tier); } } return flag; } public bool Credit(IEnumerable<string> names, Tier tier, Settings s, out List<string> disarmed) { disarmed = new List<string>(); bool flag = false; foreach (string name in names) { if (ValidName(name)) { PlayerRecord orAdd = GetOrAdd(name); flag |= orAdd.Kills.Add(tier.Name); if (Rules.CatchUpEndsWith(s, orAdd, tier)) { orAdd.CatchUp = ""; disarmed.Add(orAdd.Name); flag = true; } } } return flag; } public bool Uncredit(string name, string tier) { return Get(name)?.Kills.Remove(tier) ?? false; } public bool SetCatchUp(string name, string tier) { if (!ValidName(name)) { return false; } PlayerRecord orAdd = GetOrAdd(name); if (orAdd.CatchUp == tier) { return false; } orAdd.CatchUp = tier; return true; } public static bool ValidName(string name) { if (!string.IsNullOrWhiteSpace(name)) { return name.IndexOfAny(new char[4] { ';', '=', '\n', '\r' }) < 0; } return false; } public string Serialize() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("# BeenThere state, one character per line. Edit while the server runs; it reloads on save.\n"); stringBuilder.Append("# player=<character name>;kills=<tiers>;catchup=<tier|none> (no catchup field: the server default applies)\n"); stringBuilder.Append("world=").Append(World).Append('\n'); foreach (PlayerRecord item in Players.Values.OrderBy<PlayerRecord, string>((PlayerRecord p) => p.Name, StringComparer.OrdinalIgnoreCase)) { stringBuilder.Append("player=").Append(item.Name); stringBuilder.Append(";kills=").Append(string.Join(",", item.Kills.OrderBy<string, string>((string k) => k, StringComparer.OrdinalIgnoreCase))); if (item.CatchUp != null) { stringBuilder.Append(";catchup=").Append((item.CatchUp.Length == 0) ? "none" : item.CatchUp); } stringBuilder.Append('\n'); } return stringBuilder.ToString(); } public static WorldState Parse(string text, out string error) { WorldState worldState = new WorldState(); string[] array = (text ?? "").Split('\n'); for (int i = 0; i < array.Length; i++) { string text2 = array[i].Trim(); if (text2.Length == 0 || text2[0] == '#') { continue; } string[] array2 = text2.Split(';'); int num = array2[0].IndexOf('='); string text3 = ((num < 0) ? "" : array2[0].Substring(0, num).Trim().ToLowerInvariant()); if (text3 == "world" && array2.Length == 1) { worldState.World = array2[0].Substring(num + 1).Trim(); continue; } if (text3 != "player") { error = "unknown line: " + text2; return null; } PlayerRecord playerRecord = null; string[] array3 = array2; foreach (string text4 in array3) { int num2 = text4.IndexOf('='); if (num2 < 0) { error = "field without '=': " + text2; return null; } string text5 = text4.Substring(0, num2).Trim().ToLowerInvariant(); string text6 = text4.Substring(num2 + 1).Trim(); switch (text5) { case "player": if (!ValidName(text6) || worldState.Players.ContainsKey(text6)) { error = "bad or repeated player name: " + text2; return null; } playerRecord = worldState.GetOrAdd(text6); break; case "kills": { string[] array4 = text6.Split(','); foreach (string text7 in array4) { if (text7.Trim().Length > 0) { playerRecord.Kills.Add(text7.Trim()); } } break; } case "catchup": playerRecord.CatchUp = (Rules.IsNone(text6) ? "" : text6); break; default: error = "unknown field " + text5 + ": " + text2; return null; } } } error = null; return worldState; } } public static class Wire { public static string Pack(Settings settings, WorldState state) { StringBuilder stringBuilder = new StringBuilder("[settings]\n"); foreach (string item in settings.Lines()) { stringBuilder.Append(item).Append('\n'); } stringBuilder.Append("[state]\n").Append(state.Serialize()); return stringBuilder.ToString(); } public static bool Unpack(string text, out Settings settings, out WorldState state, out string error) { settings = null; state = null; int num = text.IndexOf("[state]\n", StringComparison.Ordinal); if (!text.StartsWith("[settings]\n", StringComparison.Ordinal) || num < 0) { error = "not a BeenThere wire message"; return false; } IEnumerable<string> lines = from l in text.Substring(11, num - 11).Split('\n') where l.Trim().Length > 0 select l; settings = Settings.FromLines(lines, out error); if (settings == null) { return false; } state = WorldState.Parse(text.Substring(num + 8), out error); return state != null; } } internal sealed class Snapshot { public Settings Settings; public WorldState State; } internal sealed class Server { public readonly Settings Settings; public WorldState State; private readonly string path; private readonly string world; private DateTime loadedMtime; private float nextPoll; private readonly Dictionary<string, float> reported = new Dictionary<string, float>(StringComparer.OrdinalIgnoreCase); private const float ReportWindow = 15f; public Server(Settings settings, string path, string world) { Settings = settings; this.path = path; this.world = world; Load(); } private void Load() { State = null; if (File.Exists(path)) { loadedMtime = File.GetLastWriteTimeUtc(path); State = WorldState.Parse(File.ReadAllText(path), out var error); if (State == null) { Plugin.Log.LogError((object)("ignoring " + path + ": " + error + "; starting empty (fix the file and it reloads)")); } else if (State.World != world) { Plugin.Log.LogWarning((object)(path + " is for world '" + State.World + "', this is '" + world + "'; parking it as .other and starting empty")); File.Copy(path, path + ".other", overwrite: true); State = null; } } if (State == null) { State = new WorldState { World = world }; Save(); } Plugin.Log.LogInfo((object)("state for " + world + ": " + State.Players.Count + " characters from " + path)); } private void Save() { Directory.CreateDirectory(Path.GetDirectoryName(path)); File.WriteAllText(path, State.Serialize()); loadedMtime = File.GetLastWriteTimeUtc(path); } public void Poll(float now) { if (now < nextPoll) { return; } nextPoll = now + 1f; try { if (File.Exists(path) && !(File.GetLastWriteTimeUtc(path) == loadedMtime)) { DateTime lastWriteTimeUtc = File.GetLastWriteTimeUtc(path); string error; WorldState worldState = WorldState.Parse(File.ReadAllText(path), out error); loadedMtime = lastWriteTimeUtc; if (worldState == null) { Plugin.Log.LogWarning((object)("edited " + path + " ignored: " + error)); return; } worldState.World = world; State = worldState; Plugin.Log.LogInfo((object)("state reloaded from disk: " + State.Players.Count + " characters")); Broadcast(0L); } } catch (Exception ex) { Plugin.Log.LogError((object)("state poll failed: " + ex)); nextPoll = now + 30f; } } public void OnCharacterConnected(string name) { if (!WorldState.ValidName(name)) { return; } PlayerRecord record = State.Get(name); if (record == null) { State.GetOrAdd(name); Save(); Plugin.Log.LogInfo((object)("new character " + name + " added to state")); return; } ZoneSystem zs = ZoneSystem.instance; if (!((Object)(object)zs == (Object)null)) { List<string> list = (from t in Settings.Tiers.Tiers where t.WorldKey.Length > 0 && zs.GetGlobalKey(t.WorldKey) && !record.Kills.Contains(t.Name) select t.Name).ToList(); if (list.Count > 0) { Plugin.Log.LogInfo((object)(name + " connected; cleared in this world but not credited to them: " + string.Join(", ", list) + " (beenthere credit, if they were there)")); } } } public void Broadcast(long target) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Expected O, but got Unknown ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null) { ZPackage val = new ZPackage(); val.Write(Wire.Pack(Settings, State)); instance.InvokeRoutedRPC(target, "BeenThere_State", new object[1] { val }); } } public void OnBossDied(string bossPrefab, Vector3 pos, List<string> attackers) { //IL_0073: Unknown result type (might be due to invalid IL or missing references) Tier tier = Settings.Tiers.OfBoss(bossPrefab); if (tier == null) { Plugin.Log.LogInfo((object)(bossPrefab + " died; no tier lists it")); return; } reported[tier.Name] = Time.unscaledTime; if (Settings.KillCredit == KillCredit.Attackers && attackers != null) { Credit(tier, attackers, bossPrefab + " died (" + tier.Name + "); hit by"); return; } Credit(tier, ConnectedPlayers(pos), bossPrefab + " died (" + tier.Name + "); connected" + ((attackers == null && Settings.KillCredit == KillCredit.Attackers) ? ", the report carried no hit record" : "")); } public void OnWorldKey(long sender, string key) { //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: 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) Tier tier = Settings.Tiers.OfWorldKey(key); if (tier == null || (reported.TryGetValue(tier.Name, out var value) && Time.unscaledTime - value < 15f)) { return; } Vector3 pos = PeerPosition(sender); if (Settings.KillCredit == KillCredit.Attackers) { ZDO val = NearestBoss(tier, pos); if (val != null) { Credit(tier, BossRecord.Attackers(val, KnownNames()), key + " set (" + tier.Name + "); hit by"); return; } Plugin.Log.LogWarning((object)(key + " set but no " + string.Join("/", tier.Bosses) + " record is loaded here; crediting connected players instead")); } Credit(tier, ConnectedPlayers(pos), key + " set (" + tier.Name + "); connected"); } private void Credit(Tier tier, List<string> names, string how) { Plugin.Log.LogInfo((object)(how + ": " + ((names.Count == 0) ? "nobody" : string.Join(", ", names)))); if (names.Count != 0 && State.Credit(names, tier, Settings, out var disarmed)) { Save(); Broadcast(0L); string text = "BeenThere: " + tier.Name + " credited to " + string.Join(", ", names); if (disarmed.Count > 0) { text = text + "; catch-up ended for " + string.Join(", ", disarmed); } Sync.Message(0L, text); } } private static ZDO NearestBoss(Tier tier, Vector3 pos) { //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) ZDOMan instance = ZDOMan.instance; if (instance == null) { return null; } ZDO result = null; float num = float.MaxValue; List<ZDO> list = new List<ZDO>(); string[] bosses = tier.Bosses; foreach (string text in bosses) { list.Clear(); int num2 = 0; while (!instance.GetAllZDOsWithPrefabIterative(text, list, ref num2)) { } foreach (ZDO item in list) { float num3 = Vector3.Distance(item.GetPosition(), pos); if (num3 < num) { result = item; num = num3; } } } return result; } private List<string> KnownNames() { List<string> list = new List<string>(State.Players.Keys); foreach (ZNetPeer peer in ZNet.instance.GetPeers()) { if (!string.IsNullOrEmpty(peer.m_playerName) && !list.Contains(peer.m_playerName)) { list.Add(peer.m_playerName); } } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer != (Object)null && !list.Contains(localPlayer.GetPlayerName())) { list.Add(localPlayer.GetPlayerName()); } return list; } private static Vector3 PeerPosition(long sender) { //IL_003b: 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_001f: Unknown result type (might be due to invalid IL or missing references) if (sender == ZNet.GetUID() && (Object)(object)Player.m_localPlayer != (Object)null) { return ((Component)Player.m_localPlayer).transform.position; } return ZNet.instance.GetPeer(sender)?.m_refPos ?? Vector3.zero; } private List<string> ConnectedPlayers(Vector3 pos) { //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_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) float killCreditRadius = Settings.KillCreditRadius; List<string> list = new List<string>(); foreach (ZNetPeer peer in ZNet.instance.GetPeers()) { if (peer.IsReady() && !string.IsNullOrEmpty(peer.m_playerName) && (!(killCreditRadius > 0f) || !(Vector3.Distance(peer.m_refPos, pos) > killCreditRadius))) { list.Add(peer.m_playerName); } } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer != (Object)null && (killCreditRadius <= 0f || Vector3.Distance(((Component)localPlayer).transform.position, pos) <= killCreditRadius)) { list.Add(localPlayer.GetPlayerName()); } return list; } public void OnAdminCredit(long sender, string player, string requested, bool add) { if (!IsAdmin(sender)) { Sync.Message(sender, "BeenThere: only a server admin can credit or uncredit a kill"); return; } string canonical; string text = Rules.ValidateTier(Settings, requested, out canonical); if (text == null && canonical.Length == 0) { text = "name a tier to " + (add ? "credit" : "uncredit"); } if (text == null && !WorldState.ValidName(player)) { text = "bad character name"; } if (text != null) { Sync.Message(sender, "BeenThere: " + text); return; } Tier tier = Settings.Tiers.ByName(canonical); List<string> disarmed = new List<string>(); bool flag = (add ? State.Credit(new string[1] { player }, tier, Settings, out disarmed) : State.Uncredit(player, tier.Name)); if (flag) { Save(); Broadcast(0L); } Plugin.Log.LogInfo((object)("admin " + Sync.PlayerNameOfPeer(sender) + (add ? " credited " : " uncredited ") + tier.Name + " for " + player)); Sync.Message(sender, "BeenThere: " + player + (add ? " credited with " : " no longer credited with ") + tier.Name + ((disarmed.Count > 0) ? ", catch-up ended" : "") + (flag ? "" : " (no change)")); } public void OnAdminSetCatchUp(long sender, string player, string requested) { if (!IsAdmin(sender)) { Sync.Message(sender, "BeenThere: only a server admin can set another player's catch-up"); return; } string canonical; string text = Rules.ValidateTier(Settings, requested, out canonical); if (text == null && !WorldState.ValidName(player)) { text = "bad character name"; } if (text != null) { Sync.Message(sender, "BeenThere: " + text); return; } if (State.SetCatchUp(player, canonical)) { Save(); Broadcast(0L); } Plugin.Log.LogInfo((object)("admin " + Sync.PlayerNameOfPeer(sender) + " set " + player + " catch-up to " + ((canonical.Length == 0) ? "none" : canonical))); Sync.Message(sender, "BeenThere: " + player + " catch-up " + ((canonical.Length == 0) ? "cleared" : ("set to " + canonical))); } private bool IsAdmin(long sender) { if (sender == ZNet.GetUID()) { return true; } ZNetPeer peer = ZNet.instance.GetPeer(sender); if (peer != null && peer.m_socket != null) { return ZNet.instance.IsAdmin(peer.m_socket.GetHostName()); } return false; } public void OnSetCatchUp(long sender, string requested) { string text = Sync.PlayerNameOfPeer(sender); if (string.IsNullOrEmpty(text)) { return; } string canonical; string text2 = Rules.ValidateCatchUp(Settings, requested, out canonical); if (text2 != null) { Sync.Message(sender, "BeenThere: " + text2); return; } if (State.SetCatchUp(text, canonical)) { Save(); Broadcast(0L); } Plugin.Log.LogInfo((object)(text + " set catch-up to " + ((canonical.Length == 0) ? "none" : canonical))); Sync.Message(sender, "BeenThere: catch-up " + ((canonical.Length == 0) ? "cleared" : ("set to " + canonical))); } } internal static class BossRecord { public static List<string> Attackers(ZDO zdo, IEnumerable<string> names) { if (zdo == null) { return null; } return Rules.Attackers(names, (string name) => zdo.GetBool(ZDOVars.s_attackers + name, false)); } } internal static class Sync { public const string StateRpc = "BeenThere_State"; public const string BossRpc = "BeenThere_BossDied"; public const string BossRpc2 = "BeenThere_BossDied2"; public const string CatchUpRpc = "BeenThere_SetCatchUp"; public const string AdminCatchUpRpc = "BeenThere_AdminSetCatchUp"; public const string AdminCreditRpc = "BeenThere_AdminCredit"; private const char NameSeparator = ';'; public static void Register() { ZRoutedRpc instance = ZRoutedRpc.instance; instance.Register<ZPackage>("BeenThere_State", (Action<long, ZPackage>)OnState); instance.Register<string, Vector3>("BeenThere_BossDied", (Action<long, string, Vector3>)OnBossDied); instance.Register<string, Vector3, string>("BeenThere_BossDied2", (Action<long, string, Vector3, string>)OnBossDied2); instance.Register<string>("BeenThere_SetCatchUp", (Action<long, string>)OnSetCatchUp); instance.Register<string, string>("BeenThere_AdminSetCatchUp", (Action<long, string, string>)OnAdminSetCatchUp); instance.Register<string, string, bool>("BeenThere_AdminCredit", (Action<long, string, string, bool>)OnAdminCredit); } private static void OnAdminSetCatchUp(long sender, string player, string tier) { Plugin.Server?.OnAdminSetCatchUp(sender, player, tier); } public static void RequestAdminCatchUp(string player, string tier) { if (Plugin.Server != null) { Plugin.Server.OnAdminSetCatchUp(ZNet.GetUID(), player, tier); return; } ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null) { instance.InvokeRoutedRPC("BeenThere_AdminSetCatchUp", new object[2] { player, tier }); } } private static void OnAdminCredit(long sender, string player, string tier, bool add) { Plugin.Server?.OnAdminCredit(sender, player, tier, add); } public static void RequestAdminCredit(string player, string tier, bool add) { if (Plugin.Server != null) { Plugin.Server.OnAdminCredit(ZNet.GetUID(), player, tier, add); return; } ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null) { instance.InvokeRoutedRPC("BeenThere_AdminCredit", new object[3] { player, tier, add }); } } private static void OnState(long sender, ZPackage pkg) { if (Plugin.Server == null) { if (!Wire.Unpack(pkg.ReadString(), out var settings, out var state, out var error)) { Plugin.Log.LogWarning((object)("server state rejected: " + error)); return; } Plugin.Snapshot = new Snapshot { Settings = settings, State = state }; Plugin.Log.LogInfo((object)("server state received: " + state.Players.Count + " characters, tier rate " + settings.TierRate + ", catch-up rate " + settings.CatchUpRate)); } } private static void OnBossDied(long sender, string prefab, Vector3 pos) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) Plugin.Server?.OnBossDied(prefab, pos, null); } private static void OnBossDied2(long sender, string prefab, Vector3 pos, string attackers) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) Plugin.Server?.OnBossDied(prefab, pos, SplitNames(attackers)); } private static void OnSetCatchUp(long sender, string tier) { Plugin.Server?.OnSetCatchUp(sender, tier); } public static void ReportBossDeath(string prefab, Vector3 pos, List<string> attackers) { //IL_000d: 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_0034: Unknown result type (might be due to invalid IL or missing references) if (Plugin.Server != null) { Plugin.Server.OnBossDied(prefab, pos, attackers); return; } ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null) { if (attackers == null) { instance.InvokeRoutedRPC("BeenThere_BossDied", new object[2] { prefab, pos }); } else { instance.InvokeRoutedRPC("BeenThere_BossDied2", new object[3] { prefab, pos, string.Join(';'.ToString(), attackers) }); } } } private static List<string> SplitNames(string joined) { if (!string.IsNullOrEmpty(joined)) { return new List<string>(joined.Split(';')); } return new List<string>(); } public static void RequestCatchUp(string tier) { if (Plugin.Server != null) { Plugin.Server.OnSetCatchUp(ZNet.GetUID(), tier); return; } ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null) { instance.InvokeRoutedRPC("BeenThere_SetCatchUp", new object[1] { tier }); } } public static void Message(long target, string text) { ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null) { instance.InvokeRoutedRPC(target, "ShowMessage", new object[2] { 1, text }); } } public static string PlayerNameOfPeer(long uid) { if ((Object)(object)ZNet.instance == (Object)null) { return null; } if (uid == ZNet.GetUID()) { Player localPlayer = Player.m_localPlayer; if (localPlayer == null) { return null; } return localPlayer.GetPlayerName(); } return ZNet.instance.GetPeer(uid)?.m_playerName; } } public sealed class Tier { public string Name; public int Index; public string[] Bosses; public string WorldKey; public string[] Items; } public sealed class TierTable { public const string DefaultOrder = "Meadows,BlackForest,Swamp,Mountain,Plains,Mistlands,Ashlands,DeepNorth"; public static readonly Dictionary<string, string> DefaultSpecs = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) { ["Meadows"] = "Eikthyr|defeated_eikthyr|Wood", ["BlackForest"] = "gd_king|defeated_gdking|FineWood,RoundLog,Stone,CopperOre,TinOre", ["Swamp"] = "Bonemass|defeated_bonemass|ElderBark,IronScrap", ["Mountain"] = "Dragon|defeated_dragon|SilverOre,Crystal,WolfClaw", ["Plains"] = "GoblinKing|defeated_goblinking|BlackMetalScrap,Tar", ["Mistlands"] = "SeekerQueen|defeated_queen|BlackMarble,YggdrasilWood,Softtissue,BlackCore,Lantern", ["Ashlands"] = "Fader|defeated_fader|Grausten,FlametalOreNew,Blackwood", ["DeepNorth"] = "FrozenKing_p3|defeated_frozenking_p3|Frostwood,GoldOre" }; public readonly List<Tier> Tiers = new List<Tier>(); private readonly Dictionary<string, Tier> byName = new Dictionary<string, Tier>(StringComparer.OrdinalIgnoreCase); private readonly Dictionary<string, Tier> byItem = new Dictionary<string, Tier>(StringComparer.OrdinalIgnoreCase); private readonly Dictionary<string, Tier> byBoss = new Dictionary<string, Tier>(StringComparer.OrdinalIgnoreCase); private readonly Dictionary<string, Tier> byWorldKey = new Dictionary<string, Tier>(StringComparer.OrdinalIgnoreCase); public static TierTable Default() { string error; return Parse("Meadows,BlackForest,Swamp,Mountain,Plains,Mistlands,Ashlands,DeepNorth", DefaultSpecs, out error); } public static TierTable Parse(string order, IDictionary<string, string> specs, out string error) { List<string> list = new List<string>(); string[] array = (order ?? "").Split(','); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length != 0) { if (specs == null || !specs.TryGetValue(text, out var value) || string.IsNullOrWhiteSpace(value)) { error = "tier " + text + " is in the order but has no spec"; return null; } list.Add(text + "|" + value); } } return FromLines(list, out error); } public static TierTable FromLines(IEnumerable<string> lines, out string error) { TierTable tierTable = new TierTable(); foreach (string line in lines) { string[] array = line.Split('|'); if (array.Length != 4) { error = "tier line needs Name|bosses|key|items: " + line; return null; } Tier tier = new Tier { Name = array[0].Trim(), Index = tierTable.Tiers.Count, Bosses = Split(array[1]), WorldKey = array[2].Trim(), Items = Split(array[3]) }; if (tier.Name.Length == 0 || tier.Name.IndexOfAny(new char[4] { ',', ';', '|', '=' }) >= 0) { error = "bad tier name in: " + line; return null; } if (tierTable.byName.ContainsKey(tier.Name)) { error = "tier " + tier.Name + " listed twice"; return null; } string[] items = tier.Items; foreach (string text in items) { if (tierTable.byItem.TryGetValue(text, out var value)) { error = "item " + text + " is in both " + value.Name + " and " + tier.Name; return null; } } tierTable.Tiers.Add(tier); tierTable.byName[tier.Name] = tier; items = tier.Items; foreach (string key in items) { tierTable.byItem[key] = tier; } items = tier.Bosses; foreach (string key2 in items) { tierTable.byBoss[key2] = tier; } if (tier.WorldKey.Length > 0) { tierTable.byWorldKey[tier.WorldKey] = tier; } } if (tierTable.Tiers.Count == 0) { error = "no tiers"; return null; } error = null; return tierTable; } private static string[] Split(string field) { return (from s in field.Split(',') select s.Trim() into s where s.Length > 0 select s).ToArray(); } public Tier ByName(string name) { if (name == null || !byName.TryGetValue(name, out var value)) { return null; } return value; } public Tier OfItem(string prefab) { if (prefab == null || !byItem.TryGetValue(prefab, out var value)) { return null; } return value; } public Tier OfBoss(string prefab) { if (prefab == null || !byBoss.TryGetValue(prefab, out var value)) { return null; } return value; } public Tier OfWorldKey(string key) { if (key == null || !byWorldKey.TryGetValue(key, out var value)) { return null; } return value; } public Tier OfBiome(string biome) { if (string.IsNullOrEmpty(biome)) { return null; } if (biome.Equals("Ocean", StringComparison.OrdinalIgnoreCase)) { if (Tiers.Count <= 0) { return null; } return Tiers[0]; } return ByName(biome); } public IEnumerable<string> Lines() { foreach (Tier tier in Tiers) { yield return tier.Name + "|" + string.Join(",", tier.Bosses) + "|" + tier.WorldKey + "|" + string.Join(",", tier.Items); } } } }