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 BountyHunt v0.2.0
plugins/BountyHunt.dll
Decompiled an hour ago
The result has been truncated due to the large size, download it to view full contents!
using 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 BountyHunt.Core; using HarmonyLib; using Microsoft.CodeAnalysis; using Sansara.Admin; using Sansara.Chronicle; using Sansara.Clock; using Sansara.Zones; using Splatform; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.6.2", FrameworkDisplayName = ".NET Framework 4.6.2")] [assembly: AssemblyCompany("BountyHunt")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.2.0.0")] [assembly: AssemblyInformationalVersion("0.2.0+0a28a0863308e1422f11378b1be3f1937cd7a91c")] [assembly: AssemblyProduct("BountyHunt")] [assembly: AssemblyTitle("BountyHunt")] [assembly: AssemblyVersion("0.2.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 Sansara.Clock { public static class RealClock { private static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); public const double WorldClockLimit = 1000000000.0; public static double Now => (DateTime.UtcNow - Epoch).TotalSeconds; public static bool IsWorldTime(double t) { if (t > 0.0) { return t < 1000000000.0; } return false; } public static double FromWorld(double t, double worldNow, double realNow) { if (!IsWorldTime(t)) { return t; } return realNow - (worldNow - t); } } } namespace Sansara.Zones { public enum ZoneBlock { None, Wait, Keep } public static class ZoneReset { private enum Kind { Other, Player, Piece, Tombstone, ItemStand, Character } private static readonly Dictionary<int, Kind> Kinds = new Dictionary<int, Kind>(); private static List<ZDO>[] _bySector; private static ZDOMan _bySectorOwner; private static HashSet<Vector2s> _generated; private static ZoneSystem _generatedOwner; private static HashSet<Vector2s> Generated { get { if (_generated == null || (Object)(object)_generatedOwner != (Object)(object)ZoneSystem.instance) { _generated = (HashSet<Vector2s>)(typeof(ZoneSystem).GetField("m_generatedZones", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(ZoneSystem.instance)); _generatedOwner = ZoneSystem.instance; if (_generated == null) { throw new MissingFieldException("ZoneSystem.m_generatedZones"); } } return _generated; } } public static bool IsGenerated(Vector2s zone) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) return Generated.Contains(zone); } public static List<ZDO> Objects(Vector2s zone) { //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) if (_bySector == null || _bySectorOwner != ZDOMan.instance) { _bySector = (List<ZDO>[])typeof(ZDOMan).GetField("m_objectsBySector", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(ZDOMan.instance); _bySectorOwner = ZDOMan.instance; if (_bySector == null) { throw new MissingFieldException("ZDOMan.m_objectsBySector"); } } uint sector = ZoneSystem.SectorToIndex(zone).Sector; List<ZDO> list = ((sector < _bySector.Length) ? _bySector[sector] : null); if (list == null) { return new List<ZDO>(); } return new List<ZDO>(list); } public static List<Vector2s> ZonesInCircle(Vector3 center, float radius) { //IL_0013: 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_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0033: 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_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004d: 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_00f3: 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_00e3: 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_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0076: 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_0083: 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_00a6: 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) List<Vector2s> list = new List<Vector2s>(); radius = Mathf.Max(0f, radius); Vector2s zone = ZoneSystem.GetZone(new Vector3(center.x - radius, 0f, center.z - radius)); Vector2s zone2 = ZoneSystem.GetZone(new Vector3(center.x + radius, 0f, center.z + radius)); for (int i = zone.x; i <= zone2.x; i++) { for (int j = zone.y; j <= zone2.y; j++) { Vector3 zonePos = ZoneSystem.GetZonePos(new Vector2s(i, j)); float num = Mathf.Max(0f, Mathf.Abs(center.x - zonePos.x) - 32f); float num2 = Mathf.Max(0f, Mathf.Abs(center.z - zonePos.z) - 32f); if (num * num + num2 * num2 <= radius * radius) { list.Add(new Vector2s(i, j)); } } } return list; } public static void Destroy(ZDO zdo) { zdo.SetOwner(ZDOMan.GetSessionID()); ZDOMan.instance.DestroyZDO(zdo); } public static bool IsPlayer(ZDO zdo) { return KindOf(zdo.GetPrefab()) == Kind.Player; } public static bool IsPlayerThing(ZDO zdo) { switch (KindOf(zdo.GetPrefab())) { case Kind.Piece: return zdo.GetLong(ZDOVars.s_creator, 0L) != 0; case Kind.Player: case Kind.Tombstone: return true; case Kind.ItemStand: return zdo.GetInt(ZDOVars.s_item, 0) != 0; case Kind.Character: return zdo.GetBool(ZDOVars.s_tamed, false); default: return false; } } public static bool AnyPlayerThing(IEnumerable<Vector2s> zones) { return zones.Any((Vector2s z) => Objects(z).Any(IsPlayerThing)); } public static ZoneBlock Check(IEnumerable<Vector2s> zones, Func<ZDO, bool> waitFor, out ZDO blocker) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) ZoneBlock zoneBlock = ZoneBlock.None; blocker = null; foreach (Vector2s zone in zones) { foreach (ZDO item in Objects(zone)) { ZoneBlock zoneBlock2 = BlockOf(item, waitFor); if (zoneBlock2 > zoneBlock) { zoneBlock = zoneBlock2; blocker = item; if (zoneBlock2 == ZoneBlock.Keep) { return zoneBlock; } } } } return zoneBlock; } private static ZoneBlock BlockOf(ZDO zdo, Func<ZDO, bool> waitFor) { switch (KindOf(zdo.GetPrefab())) { case Kind.Piece: if (zdo.GetLong(ZDOVars.s_creator, 0L) == 0L) { break; } goto IL_0059; case Kind.ItemStand: if (zdo.GetInt(ZDOVars.s_item, 0) == 0) { break; } goto IL_0059; case Kind.Character: if (!zdo.GetBool(ZDOVars.s_tamed, false)) { break; } goto IL_0059; case Kind.Player: case Kind.Tombstone: { return ZoneBlock.Wait; } IL_0059: return ZoneBlock.Keep; } if (waitFor == null || !waitFor(zdo)) { return ZoneBlock.None; } return ZoneBlock.Wait; } public static int DestroyAll(IEnumerable<Vector2s> zones, Func<ZDO, bool> keep = null) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) int num = 0; foreach (Vector2s zone in zones) { foreach (ZDO item in from z in Objects(zone) where !IsPlayer(z) && (keep == null || !keep(z)) select z) { Destroy(item); num++; } } return num; } public static List<ZDO> Remaining(IEnumerable<Vector2s> zones, Func<ZDO, bool> keep = null) { return (from z in zones.SelectMany(Objects) where !IsPlayer(z) && (keep == null || !keep(z)) select z).ToList(); } public static void Unregister(Vector2s zone) { //IL_0005: 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_0030: 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) Generated.Remove(zone); ZoneSystem instance = ZoneSystem.instance; if (instance.m_locationInstances.TryGetValue(zone, out var value)) { value.m_placed = false; instance.m_locationInstances[zone] = value; } } public static string PrefabName(ZDO zdo) { GameObject prefab = ZNetScene.instance.GetPrefab(zdo.GetPrefab()); return ((prefab != null) ? ((Object)prefab).name : null) ?? zdo.GetPrefab().ToString(); } private static Kind KindOf(int hash) { if (!Kinds.TryGetValue(hash, out var value)) { GameObject prefab = ZNetScene.instance.GetPrefab(hash); value = ((!((Object)(object)prefab == (Object)null)) ? (((Object)(object)prefab.GetComponent<Player>() != (Object)null) ? Kind.Player : (((Object)(object)prefab.GetComponent<TombStone>() != (Object)null) ? Kind.Tombstone : (((Object)(object)prefab.GetComponent<ItemStand>() != (Object)null && (Object)(object)prefab.GetComponent<Piece>() == (Object)null) ? Kind.ItemStand : (((Object)(object)prefab.GetComponent<Piece>() != (Object)null) ? Kind.Piece : (((Object)(object)prefab.GetComponent<Character>() != (Object)null) ? Kind.Character : Kind.Other))))) : Kind.Other); Kinds[hash] = value; } return value; } } } namespace Sansara.Admin { internal static class AdminChannel { private const float ScanSeconds = 1f; private const float DenialLogSeconds = 30f; private static ManualLogSource _log; private static bool _owner; private static bool _announced; private static float _nextScan; private static readonly HashSet<ZRpc> Registered = new HashSet<ZRpc>(); private static readonly Dictionary<ZRpc, float> LastDenial = new Dictionary<ZRpc, float>(); public static void Register(string module, string[] verbs, Func<string, string, string, string> handler, ManualLogSource log) { _log = log; if (AdminRegistry.Shared.Register(module, verbs, handler)) { log.LogWarning((object)("Админ-канал: модуль " + module + " зарегистрирован повторно — обработчик заменён.")); } if (AdminRegistry.Shared.ClaimOwner(typeof(AdminChannel).Assembly.GetName().Name)) { _owner = true; } } public static void Tick() { if (!_owner || (Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer() || Time.realtimeSinceStartup < _nextScan) { return; } _nextScan = Time.realtimeSinceStartup + 1f; try { Scan(); } catch (Exception ex) { _log.LogError((object)("Админ-канал: ошибка: " + ex)); } } public static void CheckFile(string guid, string logPrefix, Func<string, string, string, string> handler) { string path = Path.Combine(Paths.ConfigPath, guid + ".command"); if (!File.Exists(path)) { return; } string text; try { text = File.ReadAllText(path).Trim(); File.Delete(path); } catch (Exception ex) { _log.LogWarning((object)(logPrefix + ": файл команды не прочитан: " + ex.Message)); return; } _log.LogInfo((object)(logPrefix + ": команда «" + AdminProtocol.ForLog(text) + "».")); try { _log.LogInfo((object)(logPrefix + ": " + handler(text, "файл команд", "").Replace("\n", "; "))); } catch (Exception ex2) { _log.LogError((object)(logPrefix + ": ошибка команды: " + ex2)); } } private static void Scan() { if (!_announced) { _announced = true; _log.LogInfo((object)("Админ-канал: владелец " + AdminRegistry.Shared.Owner + ", модули: " + string.Join(", ", AdminRegistry.Shared.Modules().ToArray()) + ".")); } HashSet<ZRpc> live = new HashSet<ZRpc>(); foreach (ZNetPeer peer in ZNet.instance.GetPeers()) { if (peer != null && peer.m_rpc != null) { live.Add(peer.m_rpc); if (Registered.Add(peer.m_rpc)) { peer.m_rpc.Register<ZPackage>("Sansara_Admin", (Action<ZRpc, ZPackage>)OnRequest); } } } Registered.RemoveWhere((ZRpc r) => !live.Contains(r)); foreach (ZRpc item in new List<ZRpc>(LastDenial.Keys)) { if (!live.Contains(item)) { LastDenial.Remove(item); } } } private static void OnRequest(ZRpc rpc, ZPackage pkg) { //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Expected O, but got Unknown int num = 0; AdminReply adminReply; try { int protocol = pkg.ReadInt(); num = pkg.ReadInt(); string module = pkg.ReadString(); string line = pkg.ReadString(); adminReply = Handle(rpc, protocol, module, line); } catch (Exception ex) { ZNetPeer val = ZNet.instance.GetPeers().Find((ZNetPeer p) => p.m_rpc == rpc); if (val == null || !val.IsReady()) { return; } LogThrottled(rpc, "Админ-канал: неверный запрос от " + AdminProtocol.ForLog(val.m_playerName) + ": " + AdminProtocol.ForLog(ex.Message)); adminReply = new AdminReply(4, "неверный запрос"); } if (adminReply != null) { ZPackage val2 = new ZPackage(); val2.Write(1); val2.Write(num); val2.Write(adminReply.Code); val2.Write(AdminProtocol.Truncate(adminReply.Text)); rpc.Invoke("Sansara_AdminReply", new object[1] { val2 }); } } private static void LogThrottled(ZRpc rpc, string message) { float realtimeSinceStartup = Time.realtimeSinceStartup; if (!LastDenial.TryGetValue(rpc, out var value) || realtimeSinceStartup - value >= 30f) { LastDenial[rpc] = realtimeSinceStartup; _log.LogWarning((object)message); } } private static AdminReply Handle(ZRpc rpc, int protocol, string module, string line) { ZNetPeer val = ZNet.instance.GetPeers().Find((ZNetPeer p) => p.m_rpc == rpc); if (val == null || !val.IsReady()) { return null; } AdminReply adminReply = AdminProtocol.Check(protocol, module, line); if (adminReply != null) { return adminReply; } string text = AdminProtocol.ForLog(val.m_playerName); string text2 = AdminProtocol.ForLog(val.m_socket.GetHostName()); string text3 = AdminProtocol.ForLog(AdminProtocol.Shown(module, line)); bool flag = ((ZDOID)(ref val.m_characterID)).IsNone(); if (flag || !ZNet.instance.IsAdmin(val.m_socket.GetHostName())) { LogThrottled(rpc, "Админ-канал: отказано " + text + " (" + text2 + "): " + text3); return new AdminReply(1, flag ? "отказано: персонаж не в мире (ещё не вошёл или погиб)" : "отказано: нужны права администратора"); } Exception error; AdminReply adminReply2 = AdminRegistry.Shared.Dispatch(module, line, val.m_playerName, val.m_socket.GetHostName(), out error); if (error != null) { _log.LogError((object)("Админ-канал: " + text + " (" + text2 + ") → " + text3 + ": " + AdminProtocol.ForLog(error.Message) + Environment.NewLine + error.StackTrace)); } else { _log.LogInfo((object)("Админ-канал: " + text + " (" + text2 + ") → " + text3 + ": " + AdminProtocol.ForLog(AdminProtocol.FirstLine(adminReply2.Text)))); } return adminReply2; } } public sealed class AdminCommand { public string Verb = ""; public readonly List<string> Args = new List<string>(); public string Argument => string.Join(" ", Args); public static AdminCommand Parse(string text) { AdminCommand adminCommand = new AdminCommand(); List<string> list = Tokens(text); if (list.Count == 0) { return adminCommand; } adminCommand.Verb = list[0].ToLowerInvariant(); adminCommand.Args.AddRange(list.GetRange(1, list.Count - 1)); return adminCommand; } public static List<string> Tokens(string text) { List<string> list = new List<string>(); StringBuilder stringBuilder = new StringBuilder(); bool flag = false; bool flag2 = false; string text2 = text ?? ""; foreach (char c in text2) { if (c == '"') { flag = !flag; flag2 = true; } else if (!flag && char.IsWhiteSpace(c)) { if (flag2) { list.Add(stringBuilder.ToString()); stringBuilder.Clear(); flag2 = false; } } else { stringBuilder.Append(c); flag2 = true; } } if (flag2) { list.Add(stringBuilder.ToString()); } return list; } public static bool TryId(string argument, out string id) { string text = (argument ?? "").Trim(); id = ((text.Length > 1 && text[0] == '@') ? text.Substring(1).Trim() : null); if (id == "") { id = null; } return id != null; } } public sealed class AdminReply { public readonly int Code; public readonly string Text; public AdminReply(int code, string text) { Code = code; Text = text ?? ""; } } public static class AdminProtocol { public const int Version = 1; public const string RequestRpc = "Sansara_Admin"; public const string ReplyRpc = "Sansara_AdminReply"; public const int MaxLine = 256; public const int MaxReply = 4000; public const int Done = 0; public const int Denied = 1; public const int NoModule = 2; public const int Failed = 3; public const int Invalid = 4; public const int MaxLogText = 300; public static string Truncate(string text) { string text2 = text ?? ""; if (text2.Length > 4000) { return text2.Substring(0, 3999) + "…"; } return text2; } public static AdminReply Check(int protocol, string module, string line) { if (protocol != 1) { return new AdminReply(4, $"неверный запрос: обновите Sansara Admin (протокол сервера {1})"); } if (module == null || line == null) { return new AdminReply(4, "неверный запрос"); } if (Shown(module, line).Length > 256) { return new AdminReply(4, $"строка длиннее {256} символов"); } return null; } public static string Shown(string module, string line) { string text = (module ?? "").Trim(); if (!(text == "")) { return (text + " " + (line ?? "").Trim()).Trim(); } return "help"; } public static string ForLog(string s) { char[] array = (s ?? "").ToCharArray(); for (int i = 0; i < array.Length; i++) { if (char.IsControl(array[i])) { array[i] = ' '; } } if (array.Length > 300) { return new string(array, 0, 300); } return new string(array); } public static string FirstLine(string text) { string text2 = (text ?? "").Replace("\r", ""); int num = text2.IndexOf('\n'); if (num >= 0) { return text2.Substring(0, num); } return text2; } public static string HelpLine(string module, IEnumerable<string> verbs) { return module + ": " + string.Join(", ", verbs); } public static Dictionary<string, string[]> ParseHelp(string text) { Dictionary<string, string[]> dictionary = new Dictionary<string, string[]>(StringComparer.Ordinal); string[] array = (text ?? "").Replace("\r", "").Split(new char[1] { '\n' }); foreach (string text2 in array) { int num = text2.IndexOf(':'); if (num <= 0) { continue; } string text3 = text2.Substring(0, num).Trim(); if (IsName(text3)) { dictionary[text3] = (from v in text2.Substring(num + 1).Split(new char[1] { ',' }) select v.Trim()).Where(IsName).ToArray(); } } return dictionary; } public static bool IsName(string s) { if (!string.IsNullOrEmpty(s)) { return s.All((char c) => c >= 'a' && c <= 'z'); } return false; } public static string Unknown(string verb, IEnumerable<string> verbs) { return (string.IsNullOrEmpty(verb) ? "укажите действие" : ("неизвестное действие «" + verb + "»")) + "; есть: " + string.Join(", ", verbs); } } public sealed class AdminRegistry { public const string SharedKey = "sansara.Admin.v1"; public static readonly AdminRegistry Shared = new AdminRegistry("sansara.Admin.v1"); private readonly string _key; private Dictionary<string, Tuple<string[], Func<string, string, string, string>>> Table { get { object data = AppDomain.CurrentDomain.GetData(_key); if (data == null) { Dictionary<string, Tuple<string[], Func<string, string, string, string>>> dictionary = new Dictionary<string, Tuple<string[], Func<string, string, string, string>>>(StringComparer.Ordinal); AppDomain.CurrentDomain.SetData(_key, dictionary); return dictionary; } return (data as Dictionary<string, Tuple<string[], Func<string, string, string, string>>>) ?? throw new InvalidOperationException("данные " + _key + " другого формата"); } } public string Owner => (AppDomain.CurrentDomain.GetData(_key + ".owner") as string) ?? ""; public AdminRegistry(string key) { _key = key; } public bool Register(string module, string[] verbs, Func<string, string, string, string> handler) { string text = (module ?? "").Trim().ToLowerInvariant(); if (!AdminProtocol.IsName(text)) { throw new ArgumentException("имя модуля — латинские буквы: " + module); } string[] array = verbs ?? new string[0]; foreach (string text2 in array) { if (!AdminProtocol.IsName(text2)) { throw new ArgumentException("глагол — латинские буквы: " + text2); } } if (handler == null) { throw new ArgumentNullException("handler"); } Dictionary<string, Tuple<string[], Func<string, string, string, string>>> table = Table; bool result = table.ContainsKey(text); table[text] = Tuple.Create(verbs ?? new string[0], handler); return result; } public bool ClaimOwner(string assembly) { if (string.IsNullOrEmpty(assembly)) { throw new ArgumentException("пустое имя сборки", "assembly"); } string name = _key + ".owner"; if (!(AppDomain.CurrentDomain.GetData(name) is string text)) { AppDomain.CurrentDomain.SetData(name, assembly); return true; } return text == assembly; } public List<string> Modules() { return Table.Keys.OrderBy<string, string>((string k) => k, StringComparer.Ordinal).ToList(); } public string HelpText() { Dictionary<string, Tuple<string[], Func<string, string, string, string>>> table = Table; if (table.Count != 0) { return string.Join("\n", from k in Modules() select AdminProtocol.HelpLine(k, table[k].Item1)); } return "на сервере нет модулей Sansara Admin"; } public AdminReply Dispatch(string module, string line, string adminName, string adminId, out Exception error) { error = null; try { string text = (module ?? "").Trim().ToLowerInvariant(); if (text == "") { return new AdminReply(0, AdminProtocol.Truncate(HelpText())); } Dictionary<string, Tuple<string[], Func<string, string, string, string>>> table = Table; if (!table.TryGetValue(text, out var value)) { return new AdminReply(2, "модуля " + text + " нет на сервере; есть: " + ((table.Count == 0) ? "ничего" : string.Join(", ", Modules()))); } string text2 = value.Item2((line ?? "").Trim(), adminName ?? "", adminId ?? ""); return new AdminReply(0, AdminProtocol.Truncate(string.IsNullOrEmpty(text2) ? "готово" : text2)); } catch (Exception ex) { return new AdminReply(3, AdminProtocol.Truncate("ошибка: " + (error = ex).Message)); } } } public static class AdminUi { public const string Verb = "ui"; public const string Head = "ui=1"; public const string NoteKey = "note"; public const string MoreKey = "more"; private const int Reserve = 16; public static string Field(object value) { char[] array = ((!(value is bool)) ? (Convert.ToString(value, CultureInfo.InvariantCulture) ?? "") : (((bool)value) ? "1" : "0")).ToCharArray(); for (int i = 0; i < array.Length; i++) { if (array[i] == ';') { array[i] = ','; } else if (char.IsControl(array[i])) { array[i] = ' '; } } return new string(array).Trim(); } public static string Row(string key, params object[] fields) { if (!AdminProtocol.IsName(key)) { throw new ArgumentException("ключ строки ui — латинские буквы в нижнем регистре: " + key); } return key + "=" + string.Join(";", (fields ?? new object[0]).Select(Field)); } public static string Note(string text) { return Text(new string[1] { Row("note", text) }); } public static string Text(IEnumerable<string> rows) { List<string> list = (rows ?? Enumerable.Empty<string>()).ToList(); StringBuilder stringBuilder = new StringBuilder("ui=1"); for (int i = 0; i < list.Count; i++) { if (stringBuilder.Length + 1 + list[i].Length > 3984) { stringBuilder.Append('\n').Append("more").Append('=') .Append((list.Count - i).ToString(CultureInfo.InvariantCulture)); break; } stringBuilder.Append('\n').Append(list[i]); } return stringBuilder.ToString(); } } } namespace Sansara.Chronicle { public sealed class ChroniclePlayer { public long Id { get; } public string Name { get; } public ChroniclePlayer(long id, string name) { Id = id; Name = name ?? ""; } } public sealed class ChronicleEntry { public double Time; public string Date = ""; public string Kind = ""; public string Id = ""; public string Result = ""; public List<ChroniclePlayer> Players = new List<ChroniclePlayer>(); public const string Victory = "victory"; public bool IsVictory { get { if (!(Result == "victory")) { return Result == "found"; } return true; } } public static string Clean(string s) { return new string((s ?? "").Where((char c) => c != '\t' && c != '\n' && c != '\r' && c != '|' && c != '=').ToArray()).Trim(); } public string ToLine() { string text = string.Join("|", Players.Select((ChroniclePlayer p) => p.Id.ToString(CultureInfo.InvariantCulture) + "=" + Clean(p.Name))); return string.Join("\t", Time.ToString("R", CultureInfo.InvariantCulture), Clean(Date), Clean(Kind), Clean(Id), Clean(Result), text); } public static ChronicleEntry Parse(string line) { if (string.IsNullOrWhiteSpace(line) || line.TrimStart(Array.Empty<char>()).StartsWith("#", StringComparison.Ordinal)) { return null; } string[] array = line.TrimEnd(new char[1] { '\r' }).Split(new char[1] { '\t' }); if (array.Length < 5 || !double.TryParse(array[0], NumberStyles.Float, CultureInfo.InvariantCulture, out var result) || array[2].Trim() == "" || array[3].Trim() == "") { return null; } ChronicleEntry chronicleEntry = new ChronicleEntry { Time = result, Date = array[1].Trim(), Kind = array[2].Trim(), Id = array[3].Trim(), Result = array[4].Trim() }; if (array.Length > 5) { string[] array2 = array[5].Split(new char[1] { '|' }, StringSplitOptions.RemoveEmptyEntries); foreach (string text in array2) { int num = text.IndexOf('='); long.TryParse((num > 0) ? text.Substring(0, num) : "0", NumberStyles.Integer, CultureInfo.InvariantCulture, out var result2); chronicleEntry.Players.Add(new ChroniclePlayer(result2, (num >= 0) ? text.Substring(num + 1) : text)); } } return chronicleEntry; } public static List<ChronicleEntry> ParseAll(string text, out int bad) { List<ChronicleEntry> list = new List<ChronicleEntry>(); bad = 0; string[] array = (text ?? "").Split(new char[1] { '\n' }); foreach (string text2 in array) { ChronicleEntry chronicleEntry = Parse(text2); if (chronicleEntry != null) { list.Add(chronicleEntry); } else if (!string.IsNullOrWhiteSpace(text2) && !text2.TrimStart(Array.Empty<char>()).StartsWith("#", StringComparison.Ordinal)) { bad++; } } return list; } } public static class ChronicleFile { public static string PathFor(string configDir, string world) { string text = string.Concat((world ?? "world").Split(Path.GetInvalidFileNameChars())); return Path.Combine(configDir, "sansara.Chronicle", text + ".tsv"); } public static void Append(string path, ChronicleEntry entry) { Directory.CreateDirectory(Path.GetDirectoryName(path)); File.AppendAllText(path, entry.ToLine() + "\n", new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); } public static List<ChronicleEntry> Read(string path, out int bad) { bad = 0; if (!File.Exists(path)) { return new List<ChronicleEntry>(); } return ChronicleEntry.ParseAll(File.ReadAllText(path), out bad); } } public sealed class FirstVictory { public string Id; public string Date; public List<string> Names = new List<string>(); public int Count; } public sealed class Leader { public string Name; public int Count; } public static class ChronicleStats { public static Dictionary<string, FirstVictory> Firsts(IEnumerable<ChronicleEntry> all, string kind) { Dictionary<string, FirstVictory> dictionary = new Dictionary<string, FirstVictory>(); foreach (ChronicleEntry item in from e in all where e.Kind == kind && e.IsVictory orderby e.Time select e) { if (!dictionary.TryGetValue(item.Id, out var value)) { value = new FirstVictory { Id = item.Id, Date = item.Date, Names = (from p in item.Players select p.Name into n where n != "" select n).Distinct().ToList() }; dictionary[item.Id] = value; } value.Count++; } return dictionary; } public static List<Leader> Leaders(IEnumerable<ChronicleEntry> all, string kind) { Dictionary<long, int> dictionary = new Dictionary<long, int>(); Dictionary<long, string> names = new Dictionary<long, string>(); foreach (ChronicleEntry item in from e in all where e.Kind == kind && e.IsVictory orderby e.Time select e) { foreach (ChroniclePlayer item2 in from p in item.Players group p by (p.Id == 0L) ? p.Name : p.Id.ToString() into g select g.First()) { long key = ((item2.Id != 0L) ? item2.Id : item2.Name.GetHashCode()); dictionary[key] = ((!dictionary.TryGetValue(key, out var value)) ? 1 : (value + 1)); if (item2.Name != "") { names[key] = item2.Name; } } } string value2; return (from kv in dictionary select new Leader { Name = (names.TryGetValue(kv.Key, out value2) ? value2 : "?"), Count = kv.Value } into l orderby l.Count descending select l).ThenBy<Leader, string>((Leader l) => l.Name, StringComparer.Ordinal).ToList(); } public static string Names(IList<string> names, int max) { if (names.Count == 0) { return ""; } for (int num = names.Count; num >= 1; num--) { string text = string.Join(", ", names.Take(num)) + ((num < names.Count) ? $" +{names.Count - num}" : ""); if (text.Length <= max) { return text; } } string text2 = names[0]; if (text2.Length <= max) { return text2; } return text2.Substring(0, Math.Max(1, max - 1)) + "…"; } } } namespace BountyHunt { internal static class Hunt { private struct Camp { public string Location; public Vector3 Pos; } private const double KillWindow = 30.0; private const double FightGrace = 1800.0; private static readonly Random Rng = new Random(); private static readonly Dictionary<long, string> Names = new Dictionary<long, string>(); private static readonly Dictionary<long, ZDOID> LastCharacter = new Dictionary<long, ZDOID>(); private static readonly Dictionary<long, long> PeerPlayer = new Dictionary<long, long>(); private static string _path; private static HuntState _state; private static double _lastNearAt; private static float _nextPing; private static string _lastWait = ""; private static string _onlyPlayer; private static string _onlyId; private static bool _struck; private static bool _woundLogged; internal static readonly string[] Verbs = new string[5] { "status", "help", "start", "siege", "stop" }; private const string HelpText = "status — идёт ли охота или осада, где, когда следующая; лагеря в очереди восстановления\nstart [игрок|@ID] — добыча сейчас (у этого игрока или у случайного на поверхности)\nsiege [игрок|@ID] — осада лагеря сейчас (в кольце [Siege] от этого игрока или от случайного)\nsiege x,z — осада в лагере, ближайшем к точке (игроки не нужны)\nstop — тихо убрать добычу или существ осады (лагерь встанет в очередь восстановления); следующая через IntervalHours"; private const int SiegeTier = 5; private const double StrikeWindow = 30.0; private const float ResetWaitSeconds = 30f; private const float TempleKeepOff = 300f; private const int SearchZones = 4; private const string TreasureTagKey = "sansara.Treasure.tag"; private static bool _forceSiege; private static bool _noSiege; private static double _lastStrikeAt; private static float _strikePrint = float.NaN; private static float _nextSiegePing; private static string _busyPath; private static string _busyText; private static Vector3? _temple; private static bool _templeChecked; private static readonly HashSet<ZDOID> OwnDestroyed = new HashSet<ZDOID>(); private static readonly Dictionary<SiegeRestore, float> Resetting = new Dictionary<SiegeRestore, float>(); private static readonly Dictionary<SiegeRestore, List<Vector2s>> ResetZones = new Dictionary<SiegeRestore, List<Vector2s>>(); private static readonly Dictionary<SiegeRestore, string> WaitLogged = new Dictionary<SiegeRestore, string>(); private static Vector3 SiegeCenter => new Vector3((float)_state.X, 0f, (float)_state.Z); public static void Load() { string text = Path.Combine(Paths.ConfigPath, "sansara.BountyHunt"); Directory.CreateDirectory(text); string text2 = string.Concat(ZNet.instance.GetWorldName().Split(Path.GetInvalidFileNameChars())); _path = Path.Combine(text, text2 + ".hunt"); _state = (File.Exists(_path) ? HuntState.Parse(File.ReadAllText(_path)) : new HuntState()); double now = World.Now; if (_state.NextAt <= 0.0 || _state.NextAt > now + (double)Plugin.IntervalHours.Value * 3600.0 + 60.0) { _state.NextAt = now + Math.Min(3600.0, (double)Plugin.IntervalHours.Value * 3600.0); Save(); } ZDOMan instance = ZDOMan.instance; instance.m_onZDODestroyed = (Action<ZDO>)Delegate.Remove(instance.m_onZDODestroyed, new Action<ZDO>(OnDestroyed)); ZDOMan instance2 = ZDOMan.instance; instance2.m_onZDODestroyed = (Action<ZDO>)Delegate.Combine(instance2.m_onZDODestroyed, new Action<ZDO>(OnDestroyed)); Plugin.Log.LogInfo((object)(_state.Active ? string.Format("Охота: идёт {0} «{1}» ({2}), осталось {3:0} мин.", _state.IsSiege ? "осада" : "охота на", _state.Name, _state.Leader, (_state.ExpiresAt - now) / 60.0) : $"Охота: следующая добыча через {(_state.NextAt - now) / 60.0:0} мин.")); LoadSiege(); } private static void Save() { File.WriteAllText(_path, _state.Serialize()); } public static void Tick() { //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) //IL_0150: Unknown result type (might be due to invalid IL or missing references) //IL_01c6: Unknown result type (might be due to invalid IL or missing references) //IL_0136: Unknown result type (might be due to invalid IL or missing references) //IL_01ff: Unknown result type (might be due to invalid IL or missing references) double now = World.Now; TickRestores(); WriteBusy(); if (_state.Active && _state.IsSiege) { TickSiege(now); return; } if (!_state.Active) { if (now >= _state.NextAt) { TrySpawn(now); } return; } ZDO val = Leader(); if (val == null) { bool flag = HuntPlanner.IsKill(now, _lastNearAt, 30.0, _struck); Plugin.Log.LogInfo((object)(flag ? ("Охота: «" + _state.Name + "» убит.") : ("Охота: «" + _state.Name + "» пропал без охотников рядом."))); Finish(now, flag); return; } Vector3 position = val.GetPosition(); bool flag2 = HuntPlanner.Wounded(val.GetFloat(ZDOVars.s_health, -1f), val.GetFloat(ZDOVars.s_maxHealth, 0f)); if (flag2 && !_woundLogged) { _woundLogged = true; Plugin.Log.LogInfo((object)("Охота: «" + _state.Name + "» ранен — " + Health(val) + ", ближайший игрок в " + Nearest(position) + ".")); } Watch(now, position, Plugin.FightRadius.Value, Plugin.ShotRadius.Value, flag2, deathForfeits: true); Strikes(val); if (HuntPlanner.Expired(now, _state.ExpiresAt, now - _lastNearAt <= 30.0, 1800.0)) { Plugin.Log.LogInfo((object)("Охота: срок «" + _state.Name + "» вышел — добыча убрана.")); RemoveQuarry(val, position); Finish(now, kill: false); } else if (Time.time >= _nextPing) { _nextPing = Time.time + Mathf.Max(1f, Plugin.PingSeconds.Value); World.Ping(position, HuntTexts.PingName(_state.Name)); } } private static void Strikes(ZDO leader) { //IL_00be: 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) bool flag = false; foreach (World.PlayerInfo item in from p in World.Players() where p.PlayerId != 0 select p) { int s_attackers = ZDOVars.s_attackers; if (leader.GetBool(s_attackers + item.Peer.m_playerName, false)) { _struck = true; Names[item.PlayerId] = item.Peer.m_playerName; if (_state.Participants.Add(item.PlayerId)) { flag = true; Plugin.Log.LogInfo((object)$"Охота: охотник {item.PlayerId} ({item.Peer.m_playerName}) ранил главаря, в {World.DistanceXZ(item.Pos, leader.GetPosition()):0} м."); } } } if (flag) { Save(); } } private static void OnDestroyed(ZDO zdo) { //IL_0006: 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) try { bool own = OwnDestroyed.Remove(zdo.m_uid); if (_state != null && _state.Active) { if (_state.IsSiege) { OnSiegeDestroyed(zdo, own); } else if (!(zdo.GetString("sansara.BountyHunt.tag", "") != "L" + Tag(_state.StartedAt))) { Plugin.Log.LogInfo((object)($"Охота: объект «{_state.Name}» уничтожен (владелец {zdo.GetOwner()}, {Health(zdo)}, ударивших игроков " + $"{zdo.GetInt(ZDOVars.s_attackers, 0)}, ближайший игрок в {Nearest(zdo.GetPosition())}).")); Strikes(zdo); } } } catch (Exception ex) { Plugin.Log.LogError((object)("Охота: ошибка при гибели главаря: " + ex)); } } private static string Health(ZDO zdo) { float num = zdo.GetFloat(ZDOVars.s_maxHealth, 0f); float num2 = zdo.GetFloat(ZDOVars.s_health, -1f); if (!(num <= 0f)) { return $"здоровье {((num2 < 0f) ? num : num2):0}/{num:0}"; } return "здоровье неизвестно (у клиентов не загружен)"; } private static string Nearest(Vector3 pos) { //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) List<World.PlayerInfo> list = World.Players(); if (list.Count != 0) { return $"{list.Min((World.PlayerInfo p) => World.DistanceXZ(p.Pos, pos)):0} м"; } return "— (никого)"; } private static string Tag(double startedAt) { return ((long)startedAt).ToString(CultureInfo.InvariantCulture); } private static ZDO Leader() { //IL_0019: 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_0116: Unknown result type (might be due to invalid IL or missing references) ZDO zDO = ZDOMan.instance.GetZDO(new ZDOID(_state.LeaderUser, _state.LeaderId)); if (zDO != null && zDO.GetString("sansara.BountyHunt.tag", "") == "L" + Tag(_state.StartedAt)) { return zDO; } if (_state.X == 0.0 && _state.Z == 0.0) { return null; } zDO = World.Tagged(new Vector3((float)_state.X, 0f, (float)_state.Z), 2, "sansara.BountyHunt.tag", "L" + Tag(_state.StartedAt)).FirstOrDefault(); if (zDO != null) { _state.LeaderUser = ((ZDOID)(ref zDO.m_uid)).UserID; _state.LeaderId = ((ZDOID)(ref zDO.m_uid)).ID; Save(); Plugin.Log.LogInfo((object)$"Охота: «{_state.Name}» найден после перезапуска ({zDO.m_uid})."); } return zDO; } private static void RemoveQuarry(ZDO leader, Vector3 pos) { //IL_0000: 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) HashSet<ZDO> hashSet = new HashSet<ZDO>(World.Tagged(pos, 2, "sansara.BountyHunt.tag", "R" + Tag(_state.StartedAt))); foreach (KeyValuePair<long, uint> item in _state.Retinue) { ZDO zDO = ZDOMan.instance.GetZDO(new ZDOID(item.Key, item.Value)); if (zDO != null && zDO.GetString("sansara.BountyHunt.tag", "") == "R" + Tag(_state.StartedAt)) { hashSet.Add(zDO); } } if (leader != null) { hashSet.Add(leader); } foreach (ZDO item2 in hashSet) { World.Destroy(item2); } } public static void CheckCommand() { string path = Path.Combine(Paths.ConfigPath, "sansara.BountyHunt.command"); if (File.Exists(path)) { string text; try { text = File.ReadAllText(path).Trim(); File.Delete(path); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Охота: файл команды не прочитан: " + ex.Message)); return; } Plugin.Log.LogInfo((object)("Охота: команда «" + text + "».")); Plugin.Log.LogInfo((object)("Охота: " + Handle(text, "файл команд", "").Replace("\n", "; "))); } } public static string Handle(string line, string admin, string adminId) { AdminCommand adminCommand = AdminCommand.Parse(line); switch (adminCommand.Verb) { case "help": return "status — идёт ли охота или осада, где, когда следующая; лагеря в очереди восстановления\nstart [игрок|@ID] — добыча сейчас (у этого игрока или у случайного на поверхности)\nsiege [игрок|@ID] — осада лагеря сейчас (в кольце [Siege] от этого игрока или от случайного)\nsiege x,z — осада в лагере, ближайшем к точке (игроки не нужны)\nstop — тихо убрать добычу или существ осады (лагерь встанет в очередь восстановления); следующая через IntervalHours"; case "status": return Status(); case "start": case "siege": case "stop": if (!Plugin.Enabled.Value) { return "модуль выключен в настройках (Enabled = false)"; } if (_state == null) { return "охота ещё не запущена: мир загружается"; } if (!(adminCommand.Verb == "start")) { if (!(adminCommand.Verb == "siege")) { return Stop(); } return StartSiege(adminCommand.Argument); } return Start(adminCommand.Argument); default: return AdminProtocol.Unknown(adminCommand.Verb, Verbs); } } private static string Status() { //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: 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_0129: Unknown result type (might be due to invalid IL or missing references) string text = (Plugin.Enabled.Value ? "" : "выключена в настройках; "); if (_state == null) { if (Plugin.Enabled.Value) { return "охота ещё не запущена: мир загружается"; } return "модуль выключен в настройках (Enabled = false)"; } double now = World.Now; string text2 = RestoreStatus(); if (!_state.Active) { return $"{text}охоты нет; следующая через {Math.Max(0.0, (_state.NextAt - now) / 60.0):0} мин" + text2; } if (_state.IsSiege) { return text + SiegeStatus(now) + text2; } ZDO val = Leader(); Vector3 p = (Vector3)((val != null) ? val.GetPosition() : new Vector3((float)_state.X, 0f, (float)_state.Z)); return text + "идёт охота на «" + _state.Name + "» (" + _state.Leader + "), " + ((val != null) ? "главарь в " : "главарь не найден, место ") + Round(p) + "\n" + $"осталось {Math.Max(0.0, (_state.ExpiresAt - now) / 60.0):0} мин, охотников {_state.Participants.Count}, погибли {_state.Dead.Count}" + text2; } private static string Start(string who) { if (_state.Active) { return "уже идёт " + (_state.IsSiege ? "осада" : "охота на") + " «" + _state.Name + "» — сначала stop"; } double now = World.Now; double nextAt = _state.NextAt; if (AdminCommand.TryId(who, out var id)) { _onlyId = id; } else { _onlyPlayer = ((who == "") ? null : who); } _noSiege = true; try { TrySpawn(now); } finally { _noSiege = false; _onlyPlayer = null; _onlyId = null; } if (!_state.Active) { _state.NextAt = nextAt; Save(); return "добыча не появилась: " + _lastWait + "; расписание не изменено"; } return $"«{_state.Name}» ({_state.Leader}) в ({_state.X:0}, {_state.Z:0}), срок {Mathf.Max(0.05f, Plugin.LifetimeHours.Value):0.##} ч"; } private static string Stop() { //IL_0057: 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_005c: 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) if (!_state.Active) { return "охоты сейчас нет"; } double now = World.Now; if (_state.IsSiege) { return StopSiege(now); } ZDO val = Leader(); Vector3 pos = (Vector3)((val != null) ? val.GetPosition() : new Vector3((float)_state.X, 0f, (float)_state.Z)); RemoveQuarry(val, pos); string name = _state.Name; Plugin.Log.LogInfo((object)("Охота: «" + name + "» убран по команде.")); _state = _state.Next(now + Math.Max(0.05, Plugin.IntervalHours.Value) * 3600.0); _lastNearAt = 0.0; _struck = false; _woundLogged = false; Save(); return $"«{name}» убран; следующая через {Plugin.IntervalHours.Value:0.##} ч"; } private static void Watch(double now, Vector3 pos, float fightRadius, float shotRadius, bool wounded, bool deathForfeits) { //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0281: Unknown result type (might be due to invalid IL or missing references) //IL_0214: Unknown result type (might be due to invalid IL or missing references) //IL_021d: Unknown result type (might be due to invalid IL or missing references) bool flag = false; List<World.PlayerInfo> source = World.Players(); foreach (World.PlayerInfo item in source.Where((World.PlayerInfo p) => p.PlayerId != 0)) { float num = World.DistanceXZ(item.Pos, pos); if (HuntPlanner.Engaged(num, fightRadius, shotRadius, wounded)) { _lastNearAt = now; Names[item.PlayerId] = item.Peer.m_playerName; if (_state.Participants.Add(item.PlayerId)) { flag = true; Plugin.Log.LogInfo((object)string.Format("Охота: охотник {0} ({1}), в {2:0} м{3}{4}.", item.PlayerId, item.Peer.m_playerName, num, wounded ? ", главарь ранен" : "", _state.IsSiege ? " от центра лагеря" : "")); } } } HashSet<long> peerUids = new HashSet<long>(); foreach (ZNetPeer peer in ZNet.instance.GetPeers()) { peerUids.Add(peer.m_uid); World.PlayerInfo playerInfo = source.FirstOrDefault((World.PlayerInfo p) => p.Peer != null && p.Peer.m_uid == peer.m_uid); if (playerInfo.Peer != null && playerInfo.PlayerId != 0L) { PeerPlayer[peer.m_uid] = playerInfo.PlayerId; } if (PeerPlayer.TryGetValue(peer.m_uid, out var value) && _state.Participants.Contains(value)) { if (LastCharacter.TryGetValue(peer.m_uid, out var value2) && !((ZDOID)(ref value2)).IsNone() && value2 != peer.m_characterID && _state.Dead.Add(value)) { flag = true; Plugin.Log.LogInfo((object)string.Format("Охота: охотник {0} погиб{1}.", value, deathForfeits ? " — без награды" : "")); } LastCharacter[peer.m_uid] = peer.m_characterID; } } foreach (long item2 in LastCharacter.Keys.Where((long uid) => !peerUids.Contains(uid)).ToList()) { LastCharacter.Remove(item2); PeerPlayer.Remove(item2); } if (flag) { Save(); } } private static void Wait(double now, double seconds, string why) { _state.NextAt = now + seconds; if (why != _lastWait) { _lastWait = why; Plugin.Log.LogInfo((object)("Охота: добыча откладывается — " + why + ".")); } } private static void TrySpawn(double now) { //IL_011c: 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_0175: Unknown result type (might be due to invalid IL or missing references) if (World.RaidRunning) { Wait(now, 60.0, "идёт набег"); return; } List<World.PlayerInfo> list = World.Players(); bool forceSiege = _forceSiege; List<World.PlayerInfo> list2 = list.Where((World.PlayerInfo p) => p.PlayerId != 0L && World.OnSurface(p.Pos) && !World.NearTrial(p.Pos) && (_onlyPlayer == null || string.Equals(p.Peer.m_playerName, _onlyPlayer, StringComparison.OrdinalIgnoreCase)) && (_onlyId == null || p.Peer.m_socket.GetHostName() == _onlyId)).ToList(); if (list2.Count == 0) { Wait(now, 60.0, "нет игроков на поверхности мира"); return; } int num = HuntPlanner.OpenTier(new HashSet<string>(ZoneSystem.instance.GetGlobalKeys())); if ((forceSiege || (!_noSiege && num >= 5 && Rng.NextDouble() < (double)Plugin.SiegeChance.Value)) && (TrySiege(now, list2, list) || forceSiege)) { return; } if (Plugin.Targets.Count == 0) { Wait(now, 3600.0, "каталог пуст"); return; } foreach (World.PlayerInfo item in list2.OrderBy((World.PlayerInfo _) => Rng.Next()).Take(3)) { foreach (int item2 in HuntPlanner.TierOrder(BountyCatalog.TierOf(World.BiomeAt(item.Pos)), num, Rng)) { BountyTarget bountyTarget = HuntPlanner.Pick(Plugin.Targets, item2, Rng); if (bountyTarget != null && FindPlace(item.Pos, bountyTarget, list, out var place)) { Spawn(now, bountyTarget, place, item); return; } } } Wait(now, 300.0, "не нашлось места"); } private static bool FindPlace(Vector3 center, BountyTarget target, List<World.PlayerInfo> players, out Vector3 place) { //IL_0055: 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_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_0091: Unknown result type (might be due to invalid IL or missing references) //IL_0130: Unknown result type (might be due to invalid IL or missing references) //IL_0135: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_0117: 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) float min = Mathf.Max(50f, Plugin.MinDistance.Value); float num = Mathf.Max(min + 50f, Plugin.MaxDistance.Value); for (int i = 0; i < 80; i++) { HuntPlanner.RingPoint(Rng, center.x, center.z, min, num, out var x, out var z); Vector3 p = new Vector3((float)x, 0f, (float)z); if (BountyCatalog.TierOf(World.BiomeAt(p)) == target.Tier && World.DryLand(p.x, p.z, out var height)) { p.y = height + 0.5f; if (!players.Any((World.PlayerInfo o) => World.DistanceXZ(o.Pos, p) < min * 0.5f) && !World.NearPlayerThings(p) && !World.NearTrial(p) && !NearBusy(p)) { place = p; return true; } } } place = Vector3.zero; return false; } private static void Spawn(double now, BountyTarget target, Vector3 place, World.PlayerInfo near) { //IL_0026: 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_0090: 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) //IL_01bb: Unknown result type (might be due to invalid IL or missing references) //IL_01c8: Unknown result type (might be due to invalid IL or missing references) //IL_0280: Unknown result type (might be due to invalid IL or missing references) //IL_0293: Unknown result type (might be due to invalid IL or missing references) //IL_0298: Unknown result type (might be due to invalid IL or missing references) //IL_02da: Unknown result type (might be due to invalid IL or missing references) double num = SiegePlanner.UniqueStart(now, Math.Max(_state.LastStart, _state.StartedAt)); ZDO val = World.CreateCreature(target.Leader, place, Plugin.LeaderLevel.Value, "L" + Tag(num), target.Name); List<KeyValuePair<long, uint>> list = new List<KeyValuePair<long, uint>>(); for (int i = 0; i < target.RetinueCount; i++) { float num2 = ((float)i + 0.5f) * 2f * (float)Math.PI / (float)target.RetinueCount; float num3 = place.x + 5f * Mathf.Cos(num2); float num4 = place.z + 5f * Mathf.Sin(num2); ZDO val2 = World.CreateCreature(target.Retinue, new Vector3(num3, WorldGenerator.instance.GetHeight(num3, num4) + 0.5f, num4), Plugin.RetinueLevel.Value, "R" + Tag(num)); list.Add(new KeyValuePair<long, uint>(((ZDOID)(ref val2.m_uid)).UserID, ((ZDOID)(ref val2.m_uid)).ID)); } double num5 = Mathf.Max(0.05f, Plugin.LifetimeHours.Value); _state = new HuntState { NextAt = _state.NextAt, Active = true, Name = target.Name, Leader = target.Leader, Tier = target.Tier, LeaderUser = ((ZDOID)(ref val.m_uid)).UserID, LeaderId = ((ZDOID)(ref val.m_uid)).ID, Retinue = list, StartedAt = num, ExpiresAt = now + num5 * 3600.0, X = place.x, Z = place.z, LastStart = num, Restores = _state.Restores }; _lastNearAt = 0.0; _struck = false; _woundLogged = false; _nextPing = 0f; _lastWait = ""; Save(); Plugin.Log.LogInfo((object)($"Охота: «{target.Name}» ({target.Leader} ур.{Plugin.LeaderLevel.Value}, свита {target.RetinueCount} {target.Retinue}) " + $"в {Round(place)} ({target.Biome}), в {World.DistanceXZ(near.Pos, place):0} м от {near.Peer.m_playerName}; срок {num5:0.##} ч.")); string text = HuntTexts.Spotted(target, num5); World.MessageAll(text); World.Chat(place, text); } private static void Finish(double now, bool kill) { //IL_0163: Unknown result type (might be due to invalid IL or missing references) //IL_013e: Unknown result type (might be due to invalid IL or missing references) List<long> list = (kill ? HuntPlanner.Winners(_state.Participants, _state.Dead) : new List<long>()); List<long> list2 = new List<long>(); if (kill) { List<World.PlayerInfo> source = World.Players(); foreach (long id in list) { try { bool num = Rewards.Give(_state.Tier, _state.StartedAt, id); if (num) { list2.Add(id); } World.PlayerInfo playerInfo = source.FirstOrDefault((World.PlayerInfo o) => o.PlayerId == id); if (!num && playerInfo.Peer != null) { World.Message(playerInfo.Peer.m_uid, "Охота: добыча взята, но награду выдать не удалось — сообщите администратору."); } } catch (Exception arg) { Plugin.Log.LogError((object)$"Охота: награда игроку {id}: {arg}"); } } string value; string text = HuntTexts.Taken(_state.Name, list.Select((long key) => (!Names.TryGetValue(key, out value)) ? "" : value)); World.MessageAll(text); World.Chat(Vector3.zero, text); } else { string text2 = HuntTexts.Escaped(_state.Name); World.MessageAll(text2); World.Chat(Vector3.zero, text2); } Plugin.Log.LogInfo((object)("Охота: итог «" + _state.Name + "» — " + (kill ? "взята" : "ушла") + "; охотники " + string.Join(", ", _state.Participants) + "; погибли " + string.Join(", ", _state.Dead) + "; награждены " + string.Join(", ", list2) + ".")); WriteChronicle(now, kill, kill ? list : _state.Participants.ToList()); _state = _state.Next(now + Math.Max(0.05, Plugin.IntervalHours.Value) * 3600.0); _lastNearAt = 0.0; _struck = false; _woundLogged = false; Save(); Plugin.Log.LogInfo((object)$"Охота: следующая добыча через {Plugin.IntervalHours.Value:0.##} ч."); } private static void WriteChronicle(double now, bool kill, List<long> players) { try { string value; ChronicleEntry entry = new ChronicleEntry { Time = now, Date = DateTime.UtcNow.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture), Kind = "hunt", Id = _state.Leader, Result = (kill ? "victory" : "escaped"), Players = players.Select((long id) => new ChroniclePlayer(id, Names.TryGetValue(id, out value) ? value : "")).ToList() }; ChronicleFile.Append(ChronicleFile.PathFor(Paths.ConfigPath, ZNet.instance.GetWorldName()), entry); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Охота: летопись не записана: " + ex.Message)); } } private static string Round(Vector3 p) { //IL_0005: 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) return $"({p.x:0}, {p.y:0}, {p.z:0})"; } private static string SiegeTag(double startedAt) { return "S" + Tag(startedAt); } private static bool IsTreasure(ZDO zdo) { return zdo.GetString("sansara.Treasure.tag", "") != ""; } private static void LoadSiege() { //IL_0079: Unknown result type (might be due to invalid IL or missing references) string text = string.Concat(ZNet.instance.GetWorldName().Split(Path.GetInvalidFileNameChars())); _busyPath = Path.Combine(Paths.ConfigPath, "sansara.BountyHunt", text + ".busy.txt"); _busyText = (File.Exists(_busyPath) ? SafeRead(_busyPath) : ""); if (_state.Active && _state.IsSiege) { List<ZDO> list = World.Tagged(SiegeCenter, 4, "sansara.BountyHunt.tag", SiegeTag(_state.StartedAt)); _state.Mobs = list.Select((ZDO z) => new KeyValuePair<long, uint>(((ZDOID)(ref z.m_uid)).UserID, ((ZDOID)(ref z.m_uid)).ID)).ToList(); Save(); Plugin.Log.LogInfo((object)($"Охота: осада «{_state.Name}» — после перезапуска найдено {list.Count} из {HuntTexts.Creatures(_state.Total)}, " + $"погибло до перезапуска {_state.Killed}.")); if (list.Count == 0 && _state.Total > 0) { Plugin.Log.LogWarning((object)("Охота: осада «" + _state.Name + "» — существ осады в мире нет (откат мира или стёрты); осада окончена без победы и наград.")); QueueRestore(new List<KeyValuePair<long, uint>>()); _state = _state.Next(World.Now + Math.Max(0.05, Plugin.IntervalHours.Value) * 3600.0); Save(); } } foreach (SiegeRestore restore in _state.Restores) { Plugin.Log.LogInfo((object)("Охота: лагерь " + Zones(restore.Zones) + " ждёт восстановления" + ((restore.After > RealClock.Now) ? $" (через {(restore.After - RealClock.Now) / 60.0:0} мин)" : "") + ".")); } WriteBusy(); } private static string SafeRead(string path) { try { return File.ReadAllText(path); } catch (Exception) { return null; } } private static string StartSiege(string argument) { if (_state.Active) { return "уже идёт " + (_state.IsSiege ? "осада" : "охота на") + " «" + _state.Name + "» — сначала stop"; } double now = World.Now; double nextAt = _state.NextAt; if (SiegePlanner.TryParsePoint(argument, out var x, out var z)) { string text = SiegeAt(now, x, z); if (text != null) { return "осада не началась: " + text + "; расписание не изменено"; } } else { if (AdminCommand.TryId(argument, out var id)) { _onlyId = id; } else { _onlyPlayer = ((argument == "") ? null : argument); } _forceSiege = true; try { TrySpawn(now); } finally { _forceSiege = false; _onlyPlayer = null; _onlyId = null; } if (!_state.Active) { _state.NextAt = nextAt; Save(); return "осада не началась: " + _lastWait + "; расписание не изменено"; } } return $"осада «{_state.Name}» ({_state.Location}) в ({_state.X:0}, {_state.Z:0}): {HuntTexts.Creatures(_state.Total)}, " + $"метка {SiegeTag(_state.StartedAt)}, срок {Mathf.Max(0.05f, Plugin.SiegeLifetimeHours.Value):0.##} ч"; } private static bool TrySiege(double now, List<World.PlayerInfo> players, List<World.PlayerInfo> all) { //IL_0177: Unknown result type (might be due to invalid IL or missing references) //IL_017e: Unknown result type (might be due to invalid IL or missing references) if (!_forceSiege && HuntPlanner.OpenTier(new HashSet<string>(ZoneSystem.instance.GetGlobalKeys())) < 5) { Wait(now, 60.0, "Равнины ещё не открыты — осады нет"); return false; } List<Camp> list = Camps(); if (list.Count == 0) { Wait(now, 300.0, "в мире нет лагерей " + Plugin.SiegeLocations.Value); return false; } float min = Mathf.Max(50f, Plugin.SiegeMinDistance.Value); float max = Mathf.Max(min + 50f, Plugin.SiegeMaxDistance.Value); foreach (World.PlayerInfo player in players.OrderBy((World.PlayerInfo _) => Rng.Next()).Take(3)) { foreach (Camp item in (from _ in list.Where(delegate(Camp c) { //IL_0006: 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) float num = World.DistanceXZ(player.Pos, c.Pos); return num >= min && num <= max; }) orderby Rng.Next() select _).ToList().Take(20)) { if (Unsafe(item, all, min * 0.5f) == null) { string text = SpawnSiege(now, item, $"в {World.DistanceXZ(player.Pos, item.Pos):0} м от {player.Peer.m_playerName}"); if (text == null) { return true; } Wait(now, 300.0, text); return false; } } } Wait(now, 300.0, "не нашлось лагеря для осады"); return false; } private static string SiegeAt(double now, double x, double z) { //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_008a: 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_00c5: Unknown result type (might be due to invalid IL or missing references) Vector3 point = new Vector3((float)x, 0f, (float)z); List<Camp> list = (from c in Camps() orderby World.DistanceXZ(c.Pos, point) select c).ToList(); if (list.Count == 0) { return "в мире нет лагерей " + Plugin.SiegeLocations.Value; } string text = null; foreach (Camp item in list.Take(10)) { string text2 = Unsafe(item, null, 0f); if (text2 == null) { return SpawnSiege(now, item, $"в {World.DistanceXZ(item.Pos, point):0} м от точки ({x:0}, {z:0})"); } text = text ?? ("ближайший лагерь " + Round(item.Pos) + " не подходит — " + text2); } return text; } private static List<Camp> Camps() { //IL_004e: 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_0088: Unknown result type (might be due to invalid IL or missing references) HashSet<string> hashSet = new HashSet<string>(Plugin.SiegeLocations.Value.Split(new char[3] { ',', ';', ' ' }, StringSplitOptions.RemoveEmptyEntries), StringComparer.OrdinalIgnoreCase); List<Camp> list = new List<Camp>(); foreach (KeyValuePair<Vector2s, LocationInstance> locationInstance in ZoneSystem.instance.m_locationInstances) { string text = LocationName(locationInstance.Value); if (text != null && hashSet.Contains(text)) { list.Add(new Camp { Location = text, Pos = locationInstance.Value.m_position }); } } return list; } private static string LocationName(LocationInstance instance) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) ZoneLocation location = instance.m_location; if (location == null) { return null; } if (string.IsNullOrEmpty(location.m_prefabName)) { return location.m_prefab.Name; } return location.m_prefabName; } private static string Unsafe(Camp camp, List<World.PlayerInfo> players, float keepOff) { //IL_001a: 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_007d: 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_0060: Unknown result type (might be due to invalid IL or missing references) if (World.NearPlayerThings(camp.Pos)) { return "рядом вещи игроков"; } if (World.NearTrial(camp.Pos)) { return "рядом испытание Mine Portal"; } Vector3? val = StartTemple(); if (val.HasValue && World.DistanceXZ(val.Value, camp.Pos) < 300f) { return "рядом стартовый алтарь"; } if (NearBusy(camp.Pos)) { return "лагерь ждёт восстановления"; } if (players != null && players.Any((World.PlayerInfo p) => World.DistanceXZ(p.Pos, camp.Pos) < keepOff)) { return "рядом игроки"; } return null; } private static Vector3? StartTemple() { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) if (!_templeChecked) { _templeChecked = true; foreach (KeyValuePair<Vector2s, LocationInstance> locationInstance in ZoneSystem.instance.m_locationInstances) { if (LocationName(locationInstance.Value) == "StartTemple") { _temple = locationInstance.Value.m_position; break; } } } return _temple; } private static string SpawnSiege(double now, Camp camp, string where) { //IL_0167: Unknown result type (might be due to invalid IL or missing references) //IL_0173: Unknown result type (might be due to invalid IL or missing references) //IL_01b3: Unknown result type (might be due to invalid IL or missing references) //IL_0294: Unknown result type (might be due to invalid IL or missing references) //IL_02a6: Unknown result type (might be due to invalid IL or missing references) //IL_03a2: Unknown result type (might be due to invalid IL or missing references) //IL_03dc: Unknown result type (might be due to invalid IL or missing references) //IL_03e8: Unknown result type (might be due to invalid IL or missing references) //IL_0427: Unknown result type (might be due to invalid IL or missing references) //IL_044c: Unknown result type (might be due to invalid IL or missing references) List<string> list = new List<string>(); List<KeyValuePair<string, int>> list2 = SiegePlanner.ParseMix(Plugin.SiegeMix.Value, list); list.ForEach(delegate(string e) { Plugin.Log.LogWarning((object)("Охота: Siege.Mix — " + e)); }); foreach (KeyValuePair<string, int> item in list2.Where((KeyValuePair<string, int> m) => (Object)(object)ZNetScene.instance.GetPrefab(m.Key) == (Object)null).ToList()) { Plugin.Log.LogWarning((object)("Охота: существа " + item.Key + " нет в игре — в осаде пропущено.")); list2.Remove(item); } if (list2.Count == 0) { return "состав осады (Siege.Mix) пуст"; } int count = SiegePlanner.Count(Plugin.SiegeMinCount.Value, Plugin.SiegeMaxCount.Value, Rng); List<string> list3 = SiegePlanner.Compose(list2, count, Rng); double num = SiegePlanner.UniqueStart(now, Math.Max(_state.LastStart, _state.StartedAt)); string text = SiegeTag(num); int num2 = Mathf.Max(1, Plugin.SiegeLevel.Value); float num3 = Mathf.Max(8f, Plugin.SiegeSpreadRadius.Value); List<KeyValuePair<long, uint>> list4 = new List<KeyValuePair<long, uint>>(); foreach (string item2 in list3) { HuntPlanner.RingPoint(Rng, camp.Pos.x, camp.Pos.z, 6.0, num3, out var x, out var z); float height = WorldGenerator.instance.GetHeight((float)x, (float)z); ZDO val = World.CreateCreature(item2, new Vector3((float)x, height + 1f, (float)z), num2, text); list4.Add(new KeyValuePair<long, uint>(((ZDOID)(ref val.m_uid)).UserID, ((ZDOID)(ref val.m_uid)).ID)); } double num4 = Mathf.Max(0.05f, Plugin.SiegeLifetimeHours.Value); string text2 = SiegePlanner.CampName(camp.Location); _state = new HuntState { NextAt = _state.NextAt, Active = true, Kind = "siege", Name = text2, Leader = camp.Location, Location = camp.Location, Tier = 5, StartedAt = num, ExpiresAt = now + num4 * 3600.0, X = camp.Pos.x, Z = camp.Pos.z, Mobs = list4, Total = list4.Count, Restores = _state.Restores, LastStart = num }; _lastNearAt = 0.0; _lastStrikeAt = 0.0; _strikePrint = float.NaN; _nextSiegePing = 0f; _lastWait = ""; Save(); WriteBusy(); string text3 = string.Join(", ", from c in list3 group c by c into g select $"{g.Key} ×{g.Count()}"); Plugin.Log.LogInfo((object)($"Охота: осада «{text2}» ({camp.Location}) в {Round(camp.Pos)}, {where}: {HuntTexts.Creatures(list4.Count)} ур.{num2} ({text3}), " + $"метка {text}, зоны {Zones(SiegePlanner.ZonesInCircle(camp.Pos.x, camp.Pos.z, Plugin.SiegeRestoreRadius.Value))}; срок {num4:0.##} ч.")); string text4 = HuntTexts.SiegeStarted(list4.Count, Math.Max(5, BountyCatalog.TierOf(World.BiomeAt(camp.Pos))), num4); World.MessageAll(text4); World.Chat(camp.Pos, text4); return null; } private static void TickSiege(double now) { //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_0027: 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_02a0: Unknown result type (might be due to invalid IL or missing references) Vector3 siegeCenter = SiegeCenter; List<ZDO> list = SiegeMobs(); int num = Math.Min(_state.Killed, _state.Total); Watch(now, siegeCenter, Plugin.SiegeFightRadius.Value, 0f, wounded: false, Plugin.SiegeDeathForfeits.Value); SiegeMarks(list); float num2 = (float)(list.Count + num) + list.Sum((ZDO z) => z.GetFloat(ZDOVars.s_health, -1f) + (float)z.GetInt(ZDOVars.s_attackers, 0)); if (!float.IsNaN(_strikePrint) && Math.Abs(num2 - _strikePrint) > 0.01f) { _lastStrikeAt = now; } _strikePrint = num2; if (!_state.HalfAnnounced && list.Count > 0 && SiegePlanner.HalfDown(num, _state.Total)) { _state.HalfAnnounced = true; Save(); Plugin.Log.LogInfo((object)$"Охота: осада — погибло {num} из {_state.Total}."); string text = HuntTexts.SiegeHalf(list.Count); World.MessageAll(text); World.Chat(siegeCenter, text); } int count = SiegeWinners().Count; if (SiegePlanner.Won(num, _state.Total, Plugin.SiegeWinShare.Value, count)) { Plugin.Log.LogInfo((object)$"Охота: осада — погибло {num} из {_state.Total} (нужно {SiegePlanner.Needed(_state.Total, Plugin.SiegeWinShare.Value)}), охотников {count}: победа."); FinishSiege(now, won: true, list); return; } if (list.Count == 0) { Plugin.Log.LogInfo((object)$"Охота: осада — живых существ не осталось, погибло {num} из {_state.Total}, охотников {count}: не победа."); FinishSiege(now, won: false, list); return; } bool fighting = now - _lastNearAt <= 30.0 || now - _lastStrikeAt <= 30.0; if (HuntPlanner.Expired(now, _state.ExpiresAt, fighting, 1800.0)) { Plugin.Log.LogInfo((object)$"Охота: срок осады вышел — погибло {num} из {_state.Total}, оставшиеся убраны."); FinishSiege(now, won: false, list); } else if (Time.time >= _nextSiegePing) { _nextSiegePing = Time.time + Mathf.Max(1f, Plugin.PingSeconds.Value); World.Ping(siegeCenter, HuntTexts.SiegePingName(_state.Name)); } } private static List<ZDO> SiegeMobs() { return ByIds(_state.Mobs, SiegeTag(_state.StartedAt)); } private static List<ZDO> ByIds(IEnumerable<KeyValuePair<long, uint>> ids, string tag) { //IL_0029: 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) List<ZDO> list = new List<ZDO>(); foreach (KeyValuePair<long, uint> id in ids) { ZDO zDO = ZDOMan.instance.GetZDO(new ZDOID(id.Key, id.Value)); if (zDO != null && !OwnDestroyed.Contains(zDO.m_uid) && zDO.GetString("sansara.BountyHunt.tag", "") == tag) { list.Add(zDO); } } return list; } private static List<long> SiegeWinners() { if (!Plugin.SiegeDeathForfeits.Value) { return _state.Participants.ToList(); } return HuntPlanner.Winners(_state.Participants, _state.Dead); } private static void SiegeMarks(IList<ZDO> mobs) { //IL_00e7: 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) if (mobs.Count == 0) { return; } bool flag = false; foreach (World.PlayerInfo item in from p in World.Players() where p.PlayerId != 0 select p) { int s_attackers = ZDOVars.s_attackers; string key = s_attackers + item.Peer.m_playerName; ZDO val = ((IEnumerable<ZDO>)mobs).FirstOrDefault((Func<ZDO, bool>)((ZDO z) => z.GetBool(key, false))); if (val != null) { Names[item.PlayerId] = item.Peer.m_playerName; if (_state.Participants.Add(item.PlayerId)) { flag = true; _lastStrikeAt = World.Now; Plugin.Log.LogInfo((object)$"Охота: охотник {item.PlayerId} ({item.Peer.m_playerName}) ранил существо осады, в {World.DistanceXZ(item.Pos, val.GetPosition()):0} м."); } } } if (flag) { Save(); } } private static void OnSiegeDestroyed(ZDO zdo, bool own) { if (!own && !(zdo.GetString("sansara.BountyHunt.tag", "") != SiegeTag(_state.StartedAt))) { _state.Killed = Math.Min(_state.Total, _state.Killed + 1); Save(); Plugin.Log.LogInfo((object)($"Охота: существо осады {ZoneReset.PrefabName(zdo)} погибло — {_state.Killed} из {_state.Total} " + $"(ударивших игроков {zdo.GetInt(ZDOVars.s_attackers, 0)}).")); SiegeMarks(new List<ZDO> { zdo }); } } private static void FinishSiege(double now, bool won, List<ZDO> mobs) { //IL_0146: Unknown result type (might be due to invalid IL or missing references) //IL_0278: Unknown result type (might be due to invalid IL or missing references) List<long> list = (won ? SiegeWinners() : new List<long>()); List<long> list2 = new List<long>(); if (won) { List<World.PlayerInfo> source = World.Players(); foreach (long id in list) { try { bool num = Rewards.Give(5, _state.StartedAt, id, Math.Max(1, Plugin.SiegeRewardCount.Value)); if (num) { list2.Add(id); } World.PlayerInfo playerInfo = source.FirstOrDefault((World.PlayerInfo o) => o.PlayerId == id); if (!num && playerInfo.Peer != null) { World.Message(playerInfo.Peer.m_uid, "Охота: лагерь взят, но награду выдать не удалось — сообщите администратору."); } } catch (Exception arg) { Plugin.Log.LogError((object)$"Охота: награда игроку {id}: {arg}"); } } } string value; string text = (won ? HuntTexts.SiegeTaken(_state.Name, list.Select((long key) => (!Names.TryGetValue(key, out value)) ? "" : value)) : HuntTexts.SiegeHeld(_state.Name)); World.MessageAll(text); World.Chat(Vector3.zero, text); Plugin.Log.LogInfo((object)(string.Format("Охота: итог осады «{0}» — {1}; погибло {2} из {3}, живых {4}; ", _state.Name, won ? "взят" : "устоял", _state.Killed, _state.Total, mobs.Count) + "охотники " + string.Join(", ", _state.Participants) + "; погибли " + string.Join(", ", _state.Dead) + "; награждены " + string.Join(", ", list2) + ".")); WriteChronicle(now, won, won ? list : _state.Participants.ToList()); List<KeyValuePair<long, uint>> survivors = new List<KeyValuePair<long, uint>>(); if (won) { survivors = mobs.Select((ZDO z) => new KeyValuePair<long, uint>(((ZDOID)(ref z.m_uid)).UserID, ((ZDOID)(ref z.m_uid)).ID)).ToList(); } else { RemoveSiegeMobs(SiegeCenter, SiegeTag(_state.StartedAt), mobs); } QueueRestore(survivors); _state = _state.Next(now + Math.Max(0.05, Plugin.IntervalHours.Value) * 3600.0); _lastNearAt = 0.0; _lastStrikeAt = 0.0; Save(); WriteBusy(); Plugin.Log.LogInfo((object)$"Охота: следующая добыча через {Plugin.IntervalHours.Value:0.##} ч."); } private static string StopSiege(double now) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) int n = RemoveSiegeMobs(SiegeCenter, SiegeTag(_state.StartedAt), SiegeMobs()); string name = _state.Name; Plugin.Log.LogInfo((object)("Охота: осада «" + name + "» снята по команде, убрано " + HuntTexts.Creatures(n) + ".")); QueueRestore(new List<KeyValuePair<long, uint>>()); _state = _state.Next(now + Math.Max(0.05, Plugin.IntervalHours.Value) * 3600.0); _lastNearAt = 0.0; _lastStrikeAt = 0.0; Save(); WriteBusy(); return $"осада «{name}» снята, убрано {HuntTexts.Creatures(n)}; лагерь восстановится через {Plugin.SiegeRestoreDelayMinutes.Value:0.##} мин, " + $"когда рядом не будет игроков; следующая охота через {Plugin.IntervalHours.Value:0.##} ч"; } private static int RemoveSiegeMobs(Vector3 center, string tag, IEnumerable<ZDO> known) { //IL_0057: 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) HashSet<ZDO> hashSet = new HashSet<ZDO>(); foreach (ZDO item in known ?? Enumerable.Empty<ZDO>()) { if (item != null && item.GetString("sansara.BountyHunt.tag", "") == tag) { hashSet.Add(item); } } hashSet.UnionWith(World.Tagged(center, 4, "sansara.BountyHunt.tag", tag)); int num = 0; foreach (ZDO item2 in hashSet) { if (OwnDestroyed.Add(item2.m_uid)) { World.Destroy(item2); num++; } } if (OwnDestroyed.Count > 20000) { OwnDestroyed.Clear(); } return num; } private static void QueueRestore(List<KeyValuePair<long, uint>> survivors) { SiegeRestore siegeRestore = new SiegeRestore { After = RealClock.Now + (double)Math.Max(0f, Plugin.SiegeRestoreDelayMinutes.Value) * 60.0, X = _state.X, Z = _state.Z, Tag = SiegeTag(_state.StartedAt), Location = _state.Location, Zones = SiegePlanner.ZonesInCircle(_state.X, _state.Z, Mathf.Max(1f, Plugin.SiegeRestoreRadius.Value)), Mobs = survivors }; _state.Restores.Add(siegeRestore); Plugin.Log.LogInfo((object)($"Охота: лагерь {Zones(siegeRestore.Zones)} ждёт восстановления через {Plugin.SiegeRestoreDelayMinutes.Value:0.##} мин" + ((survivors.Count > 0) ? $"; выживших {survivors.Count} уберёт восстановление." : "."))); } private static void TickRestores() { //IL_0330: Unknown result type (might be due to invalid IL or missing references) //IL_033c: Unknown result type (might be due to invalid IL or missing references) //IL_0314: Unknown result type (might be due to invalid IL or missing references) //IL_0319: Unknown result type (might be due to invalid IL or missing references) //IL_031b: Unknown result type (might be due to invalid IL or missing references) //IL_0208: Unknown result type (might be due to invalid IL or missing references) //IL_01d3: Unknown result type (might be due to invalid IL or missing references) //IL_01cc: Unknown result type (might be due to invalid IL or missing references) //IL_028d: Unknown result type (might be due to invalid IL or missing references) //IL_0286: Unknown result type (might be due to invalid IL or missing references) if (_state.Restores.Count == 0) { return; } List<ZonePoint> list = null; foreach (SiegeRestore item in _state.Restores.ToList()) { try { if (Resetting.ContainsKey(item)) { CompleteRestore(item); continue; } list = list ?? (from p in World.Players() select SiegePlanner.ZoneOf(p.Pos.x, p.Pos.z)).ToList(); List<Vector2s> generated = item.Zones.Select(ToZone).Where(ZoneReset.IsGenerated).ToList(); ZDO blocker = null; switch (SiegePlanner.Decide(RealClock.Now, item, list, Mathf.Max(0, Plugin.SiegeRestoreClearZones.Value), () => ZoneReset.Check(generated, IsTreasure, out blocker) switch { ZoneBlock.Keep => RestoreBlock.Keep, ZoneBlock.Wait => RestoreBlock.Wait, _ => RestoreBlock.None, })) { case RestoreStep.WaitPlayers: LogWait(item, "players", "Охота: восстановление лагеря " + Zones(item.Zones) + " ждёт, пока рядом нет игроков."); break; case RestoreStep.WaitThings: { string text = ((blocker == null) ? "?" : (IsTreasure(blocker) ? "клад" : ZoneReset.PrefabName(blocker))); string reason = "things:" + text; string[] obj3 = new string[7] { "Охота: восстановление лагеря ", Zones(item.Zones), " ждёт — в его зонах ", text, " (", null, null }; ZDO obj4 = blocker; obj3[5] = Round((obj4 != null) ? obj4.GetPosition() : Vector3.zero); obj3[6] = "); попробую позже."; LogWait(item, reason, string.Concat(obj3)); break; } case RestoreStep.Cancel: { int n = RemoveSiegeMobs(new Vector3((float)item.X, 0f, (float)item.Z), item.Tag, ByIds(item.Mobs, item.Tag)); ManualLogSource log = Plugin.Log; string[] obj = new string[9] { "Охота: восстановление лагеря ", Zones(item.Zones), " отменено — в его зонах вещи игроков (", (blocker == null) ? "?" : ZoneReset.PrefabName(blocker), " в ", null, null, null, null }; ZDO obj2 = blocker; obj[5] = Round((obj2 != null) ? obj2.GetPosition() : Vector3.zero); obj[6] = "); убрано "; obj[7] = HuntTexts.Creatures(n); obj[8] = " осады."; log.LogWarning((object)string.Concat(obj)); DropRestore(item); break; } case RestoreStep.Reset: StartRestore(item, generated); break; } } catch (Exception arg) { Plugin.Log.LogError((object)$"Охота: ошибка восстановления лагеря {Zones(item.Zones)} — снят с очереди: {arg}"); if (ResetZones.TryGetValue(item, out var value)) { foreach (Vector2s item2 in value) { try { ZoneReset.Unregister(item2); } catch (Exception ex) { Plugin.Log.LogError((object)$"Охота: зона {item2.x},{item2.y} не снята с учёта: {ex.Message}"); } } } DropRestore(item); } } } private static void LogWait(SiegeRestore r, string reason, string text) { if (!WaitLogged.TryGetValue(r, out var value) || value != reason) { WaitLogged[r] = reason; Plugin.Log.LogInfo((object)text); } } private static void StartRestore(SiegeRestore r, List<Vector2s> generated) { //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_0052: Unknown result type (might be due to invalid IL or missing references) Vector3 val = new Vector3((float)r.X, 0f, (float)r.Z); int num = RemoveSiegeMobs(val, r.Tag, ByIds(r.Mobs, r.Tag)); string text = ((num > 0) ? $", существ осады {num}" : ""); if (!ZoneReset.IsGenerated(ZoneSystem.GetZone(val)) || generated.Count == 0) { Plugin.Log.LogInfo((object)("Охота: лагерь " + Zones(r.Zones) + " ещё не строился — восстанавливать нечего" + text + ".")); DropRestore(r); } else { ResetZones[r] = generated; Resetting[r] = Time.time; int num2 = ZoneReset.DestroyAll(generated, IsTreasure); Plugin.Log.LogInfo((object)$"Охота: восстановление лагеря {Zones(r.Zones)} — зон {generated.Count}, удалено объектов {num2}{text}."); } } private static void CompleteRestore(SiegeRestore r) { //IL_0103: Unknown result type (might be due to invalid IL or missing references) List<Vector2s> list = ResetZones[r]; List<ZDO> list2 = ZoneReset.Remaining(list, IsTreasure); if (list2.Count > 0 && Time.time - Resetting[r] < 30f) { foreach (ZDO item in list2) { ZoneReset.Destroy(item); } return; } if (list2.Count > 0) { string arg = string.Join(", ", from g in list2.GroupBy(ZoneReset.PrefabName) select $"{g.Key} ×{g.Count()}"); Plugin.Log.LogWarning((object)$"Охота: после очистки лагеря осталось объектов {list2.Count}: {arg}."); } foreach (Vector2s item2 in list) { ZoneReset.Unregister(item2); } Plugin.Log.LogInfo((object)("Охота: лагерь восстановлен — зоны " + string.Join("; ", list.Select((Vector2s z) => $"{z.x},{z.y}")) + "; заново построится при следующем визите.")); DropRestore(r); } private static void DropRestore(SiegeRestore r) { Resetting.Remove(r); ResetZones.Remove(r); WaitLogged.Remove(r); _state.Restores.Remove(r); Save(); WriteBusy(); } private static Vector2s ToZone(ZonePoint z) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) return new Vector2s(z.X, z.Y); } private static string Zones(IEnumerable<ZonePoint> zones) { return string.Join("; ", zones.Select((ZonePoint z) => z.ToString())); } private static List<ZonePoint> BusyZones() { List<ZonePoint> list = new List<ZonePoint>(); if (_state == null) { return list; } if (_state.Active && _state.IsSiege) { list.AddRange(SiegePlanner.ZonesInCircle(_state.X, _state.Z, Mathf.Max(1f, Plugin.SiegeRestoreRadius.Value))); } foreach (SiegeRestore restore in _state.Restores) { list.AddRange(restore.Zones); } return list; } private static bool NearBusy(Vector3 pos) { //IL_0000: 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) return SiegePlanner.NearBusy(SiegePlanner.ZoneOf(pos.x, pos.z), BusyZones(), 1); } private static void WriteBusy() { if (_busyPath == null) { return; } string text = SiegePlanner.BusyText(BusyZones()); if (text == _busyText) { return; } try { if (text == "") { File.Delete(_busyPath); } else { File.WriteAllText(_busyPath, text); } _busyText = text; } catch (Exception ex) { Plugin.Log.LogWarning((object)("Охота: файл занятых зон не записан: " + ex.Message)); } } private static string SiegeStatus(double now) { List<ZDO> list = SiegeMobs(); return $"идёт осада «{_state.Name}» ({_state.Location}) в ({_state.X:0}, {_state.Z:0}), метка {SiegeTag(_state.StartedAt)}\n" + $"живых {list.Count} из {_state.Total}, погибло {_state.Killed} (для победы {SiegePlanner.Needed(_state.Total, Plugin.SiegeWinShare.Value)}), " + $"охотников {_state.Participants.Count}, осталось {Math.Max(0.0, (_state.ExpiresAt - now) / 60.0):0} мин"; } private static string RestoreStatus() { if (_state.Restores.Count == 0) { return ""; } double now = RealClock.Now; string value; return string.Concat(_state.Restores.Select((SiegeRestore r) => $"\nждёт восстановления: {SiegePlanner.CampName(r.Location)} ({r.X:0}, {r.Z:0}), зоны {Zones(r.Zones)} — " + (Resetting.ContainsKey(r) ? "идёт очистка" : ((r.After > now) ? $"через {(r.After - now) / 60.0:0} мин" : ((WaitLogged.TryGetValue(r, out value) && value.StartsWith("things:", StringComparison.Ordinal)) ? ("ждёт: в зонах " + value.Substring(7)) : "когда рядом не будет игроков"))))); } } [BepInPlugin("sansara.BountyHunt", "Bounty Hunt", "0.2.0")] public sealed class Plugin : BaseUnityPlugin { public const string Guid = "sansara.BountyHunt"; public const string Name = "Bounty Hunt"; public const string Version = "0.2.0"; internal static ManualLogSource Log; internal static ConfigEntry<bool> Enabled; internal static ConfigEntry<float> IntervalHours; internal static ConfigEntry<float> LifetimeHours; internal static ConfigEntry<float> MinDistance; internal static ConfigEntry<float> MaxDistance; internal static ConfigEntry<float> FightRadius; internal static ConfigEntry<float> ShotRadius; internal static ConfigEntry<int> LeaderLevel; internal static ConfigEntry<int> RetinueLevel; internal static ConfigEntry<float> PingSeconds; internal static ConfigEntry<string> Catalog; internal static readonly ConfigEntry<string>[] RewardPool = new ConfigEntry<string>[9]; internal static readonly ConfigEntry<int>[] RewardCount = new ConfigEntry<int>[9]; internal static List<BountyTarget> Targets = new List<BountyTarget>(); internal static ConfigEntry<float> SiegeChance; internal static ConfigEntry<string> SiegeLocations; internal static ConfigEntry<float> SiegeMinDistance; internal static ConfigEntry<float> SiegeMaxDistance; internal static ConfigEntry<int> SiegeMinCount; internal static ConfigEntry<int> SiegeMaxCount; internal static ConfigEntry<string> SiegeMix; internal static ConfigEntry<int> SiegeLevel; internal static ConfigEntry<float> SiegeSpreadRadius; internal static ConfigEntry<float> SiegeWinShare; internal static ConfigEntry<float> SiegeFightRadius; internal static ConfigEntry<bool> SiegeDeathForfeits; internal static ConfigEntry<float> SiegeLifetimeHours; internal static ConfigEntry<int> SiegeRewardCount; internal static ConfigEntry<float> SiegeRestoreRadius; internal static ConfigEntry<float> SiegeRestoreDelayMinutes; internal static ConfigEntry<int> SiegeRestoreClearZones; private float _nextTick; private bool _started; private void Awake() { Log = ((BaseUnityPlugin)this).Logger; Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Охота за головами."); IntervalHours = ((BaseUnityPlugin)this).Config.Bind<float>("General", "IntervalHours", 6f, "Через сколько часов работы сервера после прошлой охоты появляется новая добыча."); LifetimeHours = ((BaseUnityPlugin)this).Config.Bind<float>("General", "LifetimeHours", 2f, "Сколько часов добыча ждёт охотников; потом уходит (если с ней не идёт бой)."); MinDistance = ((BaseUnityPlugin)this).Config.Bind<float>("General", "MinDistance", 600f, "Ближе всего к выбранному игроку, м."); MaxDistance = ((BaseUnityPlugin)this).Config.Bind<float>("General", "MaxDistance", 1500f, "Дальше всего от выбранного игрока, м."); FightRadius = ((BaseUnityPlugin)this).Config.Bind<float>("General", "FightRadius", 60f, "Игроки в этом радиусе от главаря — участники охоты (награда, если не погибли), м."); ShotRadius = ((BaseUnityPlugin)this).Config.Bind<float>("General", "ShotRadius", 150f, "Пока главарь ранен, участниками считаются и игроки в этом радиусе (стрелки издалека), м; не меньше FightRadius."); LeaderLevel = ((BaseUnityPlugin)this).Config.Bind<int>("General", "LeaderLevel", 4, "Уровень главаря (1 — без звёзд, 3 — две звезды, 4 — сильнее двух звёзд)."); RetinueLevel = ((BaseUnityPlugin)this).Config.Bind<int>("General", "RetinueLevel", 2, "Уровень свиты."); PingSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("General", "PingSeconds", 4f, "Как часто сервер ставит пинг на карте в точке главаря, с (пинг живёт 5 с)."); Catalog = ((BaseUnityPlugin)this).Config.Bind<string>("General", "Catalog", "Meadows:Вепрь-великан:Boar:Neck:3, BlackForest:Старый тролль:Troll:Greydwarf_Elite:2, Swamp:Вожак драугров:Draugr_Elite:Draugr:3, Mountain:Вожак-фенринг:Fenring:Wolf:4, Plains:Вождь фулингов:GoblinBrute:Goblin:4, Mistlands:Матка искателей:SeekerBrute:Seeker:3, AshLands:Обугленный воевода:Charred_Melee:Charred_Archer:3", "Биом:Имя:Главарь:Свита:Количество через запятую (биомы Meadows … AshLands, DeepNorth) — из чего выбирается добыча."); for (int i = 1; i <= 8; i++) { RewardPool[i] = ((BaseUnityPlugin)this).Config.Bind<string>($"Tier{i}", "RewardPool", "", "Награды Event Rewards за добычу этого уровня через запятую; пусто — без награды."); RewardCount[i] = ((BaseUnityPlugin)this).Config.Bind<int>($"Tier{i}", "RewardCount", 1, "Наград каждому охотнику."); } SiegeChance = ((BaseUnityPlugin)this).Config.Bind<float>("Siege", "Chance", 0.25f, "Вероятность, что подошедшая по времени охота будет осадой лагеря (если Равнины открыты и нашёлся лагерь); 0 — осад нет."); SiegeLocations = ((BaseUnityPlugin)this).Config.Bind<string>("Siege", "Locations", "GoblinCamp2", "Локации-лагеря для осады через запятую (имена префабов)."); SiegeMinDistance = ((BaseUnityPlugin)this).Config.Bind<float>("Siege", "MinDistance", 600f, "Ближе всего лагерь к выбранному игроку, м; других игроков ближе половины — нет."); SiegeMaxDistance = ((BaseUnityPlugin)this).Config.Bind<float>("Siege", "MaxDistance", 2000f, "Дальше всего лагерь от выбранного игрока, м."); SiegeMinCount = ((BaseUnityPlugin)this).Config.Bind<int>("Siege", "MinCount", 30, "Наименьшее число существ осады."); SiegeMaxCount = ((BaseUnityPlugin)this).Config.Bind<int>("Siege", "MaxCount", 50, "Наибольшее число существ осады."); SiegeMix = ((BaseUnityPlugin)this).Config.Bind<string>("Siege", "Mix", "GoblinBrute:2,GoblinShaman:1,Goblin:3", "Состав осады: Существо:вес через запятую; существа, которых нет в игре, пропускаются."); SiegeLevel = ((BaseUnityPlugin)this).Config.Bind<int>("Siege", "Level", 3, "Уровень суще