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 RaidCall v0.4.1
plugins/RaidCall.dll
Decompiled 5 hours 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 RaidCall.Core; using Sansara.Admin; using Sansara.Chronicle; 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("RaidCall")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.4.1.0")] [assembly: AssemblyInformationalVersion("0.4.1+ee6a46e381b50add1bc5a1ee15a3276bde7e4e27")] [assembly: AssemblyProduct("RaidCall")] [assembly: AssemblyTitle("RaidCall")] [assembly: AssemblyVersion("0.4.1.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.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 RaidCall { internal static class ActiveFile { private const string StatePrefix = "state:"; private static string _path; public static ZDOID Current { get; private set; } = ZDOID.None; public static AttemptState State { get; private set; } public static void Load() { //IL_0045: 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) string text = Path.Combine(Paths.ConfigPath, "sansara.RaidCall"); Directory.CreateDirectory(text); string text2 = string.Concat(ZNet.instance.GetWorldName().Split(Path.GetInvalidFileNameChars())); _path = Path.Combine(text, text2 + ".active.txt"); Current = ZDOID.None; State = null; if (!File.Exists(_path)) { return; } string text3 = File.ReadAllText(_path).Trim(); if (text3.StartsWith("state:")) { State = AttemptState.Parse(text3.Substring("state:".Length)); if (State == null) { Plugin.Log.LogWarning((object)"Зов набега: состояние попытки по команде не прочитано — забыто."); } return; } string[] array = text3.Split(new char[1] { ':' }); if (array.Length == 2 && long.TryParse(array[0], out var result) && uint.TryParse(array[1], out var result2)) { Current = new ZDOID(result, result2); } } public static void Set(ZDOID id) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) Current = id; State = null; File.WriteAllText(_path, ((ZDOID)(ref id)).IsNone() ? "" : $"{((ZDOID)(ref id)).UserID}:{((ZDOID)(ref id)).ID}"); } public static void SetState(AttemptState state) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) Current = ZDOID.None; State = state; File.WriteAllText(_path, "state:" + state.Serialize()); } } internal static class Caller { public static void Scan() { //IL_002c: 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_005e: Unknown result type (might be due to invalid IL or missing references) string b = Plugin.Keyword.Value.Trim(); List<World.PlayerInfo> list = World.Players(); HashSet<ZDOID> hashSet = new HashSet<ZDOID>(); foreach (World.PlayerInfo item in list) { foreach (ZDO item2 in World.SignsNear(item.Pos, 32f)) { if (hashSet.Add(item2.m_uid) && !RaidRun.IsActiveSign(item2.m_uid) && string.Equals(World.SignText(item2).Trim(), b, StringComparison.OrdinalIgnoreCase)) { Handle(item2, list); } } } } private static void Handle(ZDO sign, List<World.PlayerInfo> players) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0051: 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_0056: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_01a9: Unknown result type (might be due to invalid IL or missing references) //IL_01b6: Unknown result type (might be due to invalid IL or missing references) //IL_01c3: Unknown result type (might be due to invalid IL or missing references) //IL_0154: Unknown result type (might be due to invalid IL or missing references) Vector3 position = sign.GetPosition(); ZDO val = World.StandWithItemNear(position, Plugin.StandRadius.Value); string text = ((val != null) ? World.ItemOnStand(val) : ""); Plugin.Table.TryGetValue(text, out var value); RandomEvent val2 = ((value != null) ? World.EventTemplate(value.Event) : null); Vector3 val3 = ((val != null) ? val.GetPosition() : position); long num = World.CallerOf(sign, players); CallFacts f = new CallFacts { HasTrophy = (val != null), TrophyKnown = (value != null), EventAvailable = (val2 != null), Busy = RaidRun.Busy, Now = World.Now, LastDoneAt = LastDoneNear(val3), CooldownSeconds = (double)Plugin.CooldownMinutes.Value * 60.0, ForeignWard = WardRules.IsForeign(num, World.WardsAround(val3)), BiomeFits = (val2 != null && CallRules.BiomeFits(World.BiomeAt(val3), World.SpawnBiomes(val2))) }; Refusal refusal = CallRules.Check(f); if (refusal == Refusal.None && !World.TakeItem(val, text)) { refusal = Refusal.TakeFailed; } if (refusal != Refusal.None) { World.SetSignText(sign, Texts.RefusalText(refusal, CallRules.CooldownLeft(f))); Plugin.Log.LogInfo((object)$"Зов набега: отказ {refusal} у {val3}, игрок {num}, трофей «{text}»."); } else { AttemptState state = new AttemptState { Stage = Stage.Countdown, Event = value.Event, Tier = value.Tier, Trophy = text, Caller = num, PosX = val3.x, PosY = val3.y, PosZ = val3.z, CountdownAt = World.Now }; RaidRun.Begin(sign, state); } } private static double LastDoneNear(Vector3 pos) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) double num = -1.0; foreach (ZDO item in World.SignsNear(pos, Plugin.CooldownRadius.Value)) { AttemptState attemptState = World.ReadState(item); if (attemptState != null && attemptState.Stage == Stage.Done && attemptState.DoneAt > num) { num = attemptState.DoneAt; } } return num; } } [BepInPlugin("sansara.RaidCall", "Raid Call", "0.4.1")] public sealed class Plugin : BaseUnityPlugin { public const string Guid = "sansara.RaidCall"; public const string Name = "Raid Call"; public const string Version = "0.4.1"; internal static ManualLogSource Log; internal static ConfigEntry<bool> Enabled; internal static ConfigEntry<string> Keyword; internal static ConfigEntry<float> StandRadius; internal static ConfigEntry<int> CountdownSeconds; internal static ConfigEntry<int> CooldownMinutes; internal static ConfigEntry<float> CooldownRadius; internal static ConfigEntry<int> AbandonMinutes; internal static ConfigEntry<string> Raids; internal static readonly ConfigEntry<string>[] RewardPool = new ConfigEntry<string>[9]; internal static readonly ConfigEntry<int>[] RewardCount = new ConfigEntry<int>[9]; internal static Dictionary<string, RaidEntry> Table = new Dictionary<string, RaidEntry>(); private float _nextTick; internal static bool Started; private float _validateAt; private bool _validated; private void Awake() { Log = ((BaseUnityPlugin)this).Logger; Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Мод работает."); Keyword = ((BaseUnityPlugin)this).Config.Bind<string>("General", "Keyword", "набег", "Текст таблички для вызова набега (без учёта регистра и пробелов по краям)."); StandRadius = ((BaseUnityPlugin)this).Config.Bind<float>("General", "StandRadius", 3f, "Наибольшее расстояние от таблички до подставки с трофеем, м."); CountdownSeconds = ((BaseUnityPlugin)this).Config.Bind<int>("General", "CountdownSeconds", 60, "Время на подготовку перед набегом, с."); CooldownMinutes = ((BaseUnityPlugin)this).Config.Bind<int>("General", "CooldownMinutes", 15, "Перезарядка места после набега, реальные минуты."); CooldownRadius = ((BaseUnityPlugin)this).Config.Bind<float>("General", "CooldownRadius", 100f, "Радиус места для перезарядки, м."); AbandonMinutes = ((BaseUnityPlugin)this).Config.Bind<int>("General", "AbandonMinutes", 10, "Сколько набег может стоять без игроков в зоне, пока его не снимут."); Raids = ((BaseUnityPlugin)this).Config.Bind<string>("General", "Raids", "TrophyBoar:army_eikthyr:1, TrophyNeck:army_eikthyr:1, TrophyDeer:army_eikthyr:1, TrophyEikthyr:army_eikthyr:2, TrophyGreydwarf:army_theelder:2, TrophyGreydwarfBrute:army_theelder:2, TrophyGreydwarfShaman:army_theelder:2, TrophyTheElder:army_theelder:3, TrophyForestTroll:foresttrolls:2, TrophyBjorn:foresttrolls:2, TrophySkeleton:skeletons:2, TrophyGhost:ghosts:2, TrophySkeletonHildir:hildirboss1:4, TrophyDraugr:army_bonemass:3, TrophyDraugrFem:army_bonemass:3, TrophyDraugrElite:army_bonemass:3, TrophyAbomination:army_bonemass:3, TrophyKvastur:army_bonemass:4, TrophyBonemass:army_bonemass:4, TrophyBlob:blobs:3, TrophyLeech:blobs:3, TrophySkeletonPoison:skeletons:3, TrophyWraith:ghosts:3, TrophySurtling:surtlings:3, TrophyHatchling:army_moder:4, TrophySGolem:army_moder:4, TrophySerpent:army_moder:4, TrophyDragonQueen:army_moder:5, TrophyWolf:wolves:4, TrophyFenring:wolves:4, TrophyUlv:bats:4, TrophyCultist:bats:4, TrophyCultist_Hildir:hildirboss2:4, TrophyGoblin:army_goblin:5, TrophyGoblinBrute:army_goblin:5, TrophyGoblinShaman:army_goblin:5, TrophyLox:army_goblin:5, TrophyGoblinKing:army_goblin:6, TrophyDeathsquito:bats:5, TrophyGrowth:blobs:5, TrophyBjornUndead:foresttrolls:5, TrophyGoblinBruteBrosBrute:hildirboss3:5, TrophyGoblinBruteBrosShaman:hildirboss3:5, TrophyGjall:army_gjall:6, TrophyTick:army_gjall:6, TrophyHare:army_gjall:6, TrophySeeker:army_seekers:6, TrophySeekerBrute:army_seekers:6, TrophyDvergr:army_seekers:6, TrophySeekerQueen:army_seekers:7, TrophyCharredMelee:army_charred:7, TrophyCharredArcher:army_charred:7, TrophyAsksvin:army_charred:7, TrophyVolture:army_charred:7, TrophyBonemawSerpent:army_charred:7, TrophyCharredMage:army_charredspawners:7, TrophyMorgen:army_charredspawners:7, TrophyFallenValkyrie:army_charredspawners:7, TrophyBlob_Lava:blobs:7, TrophyFader:army_charredspawners:8, TrophyJotunWarrior:army_jotuns:8, TrophyJotunWitch:army_jotuns:8, TrophyFrostTroll:army_jotuns:8, TrophyBarka:army_jotuns:8, TrophyWrithan:army_jotuns:8, TrophyMole:army_elakingar:8, TrophyElaking:army_elakingar:8, TrophyMoose:army_elakingar:8, TrophySeal:army_elakingar:8, TrophyDeerWhite:army_elakingar:8, TrophyBlob_Frost:blobs:8, TrophyBlob_Morkhalla:blobs:8", "Трофей:набег:уровень (1 Луга … 8 Дальний Север) через запятую."); 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, "Наград каждому выжившему участнику."); } AdminChannel.Register("raid", RaidAdmin.Verbs, RaidAdmin.Handle, Log); Log.LogInfo((object)"Raid Call 0.4.1 загружен."); } private void Update() { AdminChannel.Tick(); if (!Enabled.Value || (Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer() || ZDOMan.instance == null || (Object)(object)ZNetScene.instance == (Object)null || (Object)(object)ObjectDB.instance == (Object)null || (Object)(object)RandEventSystem.instance == (Object)null || WorldGenerator.instance == null || (Object)(object)ZoneSystem.instance == (Object)null || !ZoneSystem.instance.LocationsGenerated || Time.time < _nextTick) { return; } _nextTick = Time.time + 1f; if (!Started) { Started = true; _validateAt = Time.time + 30f; LoadTable(); World.Init(); ActiveFile.Load(); RaidRun.Restore(); } if (!_validated && Time.time >= _validateAt) { _validated = true; Validate(); } AdminChannel.CheckFile("sansara.RaidCall", "Зов набега", RaidAdmin.Handle); try { RaidRun.Tick(); } catch (Exception ex) { Log.LogError((object)("Зов набега: ошибка такта попытки: " + ex)); } try { Caller.Scan(); } catch (Exception ex2) { Log.LogError((object)("Зов набега: ошибка поиска табличек: " + ex2)); } } private static void LoadTable() { List<string> list = new List<string>(); Table = RaidTable.Parse(Raids.Value, list); foreach (string item in list) { Log.LogWarning((object)("Зов набега: Raids — " + item)); } Log.LogInfo((object)$"Зов набега: трофеев в таблице {Table.Count}, набегов {Table.Values.Select((RaidEntry e) => e.Event).Distinct().Count()}."); } private static void Validate() { foreach (RaidEntry value in Table.Values) { if ((Object)(object)ObjectDB.instance.GetItemPrefab(value.Trophy) == (Object)null) { Log.LogWarning((object)("Зов набега: трофея " + value.Trophy + " нет в игре — строка не сработает.")); } if (!RandEventSystem.instance.HaveEvent(value.Event)) { Log.LogWarning((object)("Зов набега: набега " + value.Event + " нет в игре или он выключен — строка не сработает.")); } } Log.LogInfo((object)"Зов набега: таблица сверена с игрой."); } } internal static class RaidAdmin { internal static readonly string[] Verbs = new string[5] { "status", "help", "start", "stop", "ui" }; private const string HelpText = "status — занято ли место, активная попытка (набег, уровень, трофей, кто позвал, место, отсчёт или время набега, участники и погибшие), трофеев в таблице\nstart <набег|трофей> [Имя|\"Имя с пробелами\"|@ID] — набег у игрока (без игрока — у вас) без таблички и трофея: отсчёт, награды выжившим по уровню, без перезарядки места; из файла команд игрок обязателен\nstop — снять активную попытку с итогом «брошено», без наград (идущий набег игры сбрасывается)\nui — данные для окна Sansara Admin (машинный ответ): идёт ли попытка, набеги таблицы с уровнями и трофеями"; private const string Loading = "набеги ещё не запущены: мир загружается"; public static string Handle(string line, string admin, string adminId) { AdminCommand adminCommand = AdminCommand.Parse(line); switch (adminCommand.Verb) { case "help": return "status — занято ли место, активная попытка (набег, уровень, трофей, кто позвал, место, отсчёт или время набега, участники и погибшие), трофеев в таблице\nstart <набег|трофей> [Имя|\"Имя с пробелами\"|@ID] — набег у игрока (без игрока — у вас) без таблички и трофея: отсчёт, награды выжившим по уровню, без перезарядки места; из файла команд игрок обязателен\nstop — снять активную попытку с итогом «брошено», без наград (идущий набег игры сбрасывается)\nui — данные для окна Sansara Admin (машинный ответ): идёт ли попытка, набеги таблицы с уровнями и трофеями"; case "status": return Status(); case "ui": if (!Plugin.Started) { return AdminUi.Note("набеги ещё не запущены: мир загружается"); } return RaidUi.Text(Plugin.Table, (string e) => World.EventTemplate(e) != null, RaidRun.Busy); case "start": case "stop": if (!Plugin.Enabled.Value) { return "модуль выключен в настройках (Enabled = false)"; } if (!Plugin.Started) { return "набеги ещё не запущены: мир загружается"; } if (!(adminCommand.Verb == "start")) { return RaidRun.Stop(admin); } return Start(adminCommand, admin, adminId); default: return AdminProtocol.Unknown(adminCommand.Verb, Verbs); } } private static string Start(AdminCommand c, string admin, string adminId) { //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: 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_0180: Unknown result type (might be due to invalid IL or missing references) //IL_018d: Unknown result type (might be due to invalid IL or missing references) //IL_0222: Unknown result type (might be due to invalid IL or missing references) //IL_0229: Unknown result type (might be due to invalid IL or missing references) if (c.Args.Count == 0) { return "укажите набег или трофей: start <набег|трофей> [Имя|@ID]"; } string argument = c.Args[0]; string text = string.Join(" ", c.Args.Skip(1)).Trim(); StartChoice startChoice = AdminStart.Resolve(argument, Plugin.Table); RandomEvent val = ((startChoice != null) ? World.EventTemplate(startChoice.Event) : null); bool flag = string.IsNullOrEmpty(adminId); World.PlayerInfo? playerInfo = FindPlayer(text, flag ? null : adminId); Vector3 val2 = playerInfo?.Pos ?? Vector3.zero; StartRefusal startRefusal = AdminStart.Check(new StartFacts { Resolved = (startChoice != null), EventEnabled = (val != null), Busy = RaidRun.Busy, FromFile = flag, PlayerGiven = (text != ""), PlayerFound = playerInfo.HasValue, PlayerY = val2.y, BiomeFits = (val != null && playerInfo.HasValue && CallRules.BiomeFits(World.BiomeAt(val2), World.SpawnBiomes(val))) }); if (startRefusal != StartRefusal.None) { return AdminStart.RefusalText(startRefusal, argument, text); } AttemptState state = new AttemptState { Stage = Stage.Countdown, Event = startChoice.Event, Tier = startChoice.Tier, Trophy = startChoice.Trophy, Caller = playerInfo.Value.PlayerId, PosX = val2.x, PosY = val2.y, PosZ = val2.z, CountdownAt = World.Now, ByCommand = true }; string playerName = playerInfo.Value.Peer.m_playerName; Plugin.Log.LogInfo((object)("Зов набега: набег " + startChoice.Event + " у игрока " + playerName + " по команде (" + AdminProtocol.ForLog(admin) + ").")); RaidRun.Begin(null, state); return AdminStart.Started(startChoice.Event, startChoice.Tier, playerName, val2.x, val2.z, Plugin.CountdownSeconds.Value); } private static World.PlayerInfo? FindPlayer(string who, string adminId) { List<World.PlayerInfo> list = (from p in World.Players() where p.PlayerId != 0 select p).ToList(); Func<World.PlayerInfo, bool> func; if (who != "") { func = ((!AdminCommand.TryId(who, out var id)) ? ((Func<World.PlayerInfo, bool>)((World.PlayerInfo p) => string.Equals(p.Peer.m_playerName, who, StringComparison.OrdinalIgnoreCase))) : ((Func<World.PlayerInfo, bool>)((World.PlayerInfo p) => p.Peer.m_socket.GetHostName() == id))); } else { if (adminId == null) { return null; } func = (World.PlayerInfo p) => p.Peer.m_socket.GetHostName() == adminId; } foreach (World.PlayerInfo item in list) { if (func(item)) { return item; } } return null; } private static string Status() { string text = (Plugin.Enabled.Value ? "" : "выключен в настройках; "); if (!Plugin.Started) { return text + "набеги ещё не запущены: мир загружается"; } AttemptState current = RaidRun.Current; RandomEvent currentRandomEvent = RandEventSystem.instance.GetCurrentRandomEvent(); bool flag = current != null && current.Stage == Stage.Running && currentRandomEvent != null && currentRandomEvent.m_name == current.Event; return text + RaidReport.Status(current, World.Now, Plugin.CountdownSeconds.Value, flag ? currentRandomEvent.m_time : 0f, flag ? currentRandomEvent.m_duration : 0f, (currentRandomEvent != null && !flag) ? currentRandomEvent.m_name : "", Plugin.Table.Count); } } internal static class RaidRun { private static ZDOID _sign = ZDOID.None; private static AttemptState _state; private static readonly Dictionary<long, ZDOID> LastCharacter = new Dictionary<long, ZDOID>(); private static readonly Dictionary<long, long> PeerPlayer = new Dictionary<long, long>(); private static readonly Dictionary<long, string> Names = new Dictionary<long, string>(); private static bool _waitLogged; private static double _emptySince = -1.0; private static double _lastTime; private static double _duration; private static int _lastShown = -1; public static bool Busy { get { if (_state == null) { return RandEventSystem.instance.GetCurrentRandomEvent() != null; } return true; } } internal static AttemptState Current => _state; private static Vector3 Pos => new Vector3(_state.PosX, _state.PosY, _state.PosZ); public static bool IsActiveSign(ZDOID id) { //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) if (_state != null) { return id == _sign; } return false; } internal static string Stop(string admin) { //IL_009e: Unknown result type (might be due to invalid IL or missing references) if (_state == null) { return "попытки набега сейчас нет"; } string text = _state.Event; bool flag = _state.Stage == Stage.Running; RandomEvent currentRandomEvent = RandEventSystem.instance.GetCurrentRandomEvent(); bool flag2 = false; if (flag && currentRandomEvent != null && currentRandomEvent.m_name == text) { RandEventSystem.instance.ResetRandomEvent(); flag2 = true; } Plugin.Log.LogInfo((object)("Зов набега: попытка " + text + " снята по команде (" + AdminProtocol.ForLog(admin) + ").")); ZDO val = (((ZDOID)(ref _sign)).IsNone() ? null : ZDOMan.instance.GetZDO(_sign)); if (val == null && !_state.ByCommand) { Clear(); return "набег " + text + " снят; табличка не найдена — итог не записан"; } Finish(val, Outcome.Abandoned); return "набег " + text + " снят с итогом «брошено», награды не выдаются" + (flag2 ? "; набег игры сброшен, его враги остаются в мире" : (flag ? "; набега игры уже не было" : "; набег не успел начаться")); } public static void Begin(ZDO sign, AttemptState state) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0003: 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_0056: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) _sign = sign?.m_uid ?? ZDOID.None; _state = state; _lastShown = -1; Save(sign); if (sign != null) { ActiveFile.Set(_sign); } string title = Texts.Title(state.Event); Show(sign, Texts.Countdown(title, Plugin.CountdownSeconds.Value)); World.MessageNear(Pos, 96f, Texts.StartMessage(title, Plugin.CountdownSeconds.Value)); Plugin.Log.LogInfo((object)($"Зов набега: вызов {state.Event} (уровень {state.Tier}, трофей {state.Trophy}) " + (state.ByCommand ? "по команде у игрока" : "игроком") + $" {state.Caller} у {Pos}, отсчёт {Plugin.CountdownSeconds.Value} с.")); } private static void Save(ZDO sign) { if (sign != null) { World.WriteState(sign, _state); } else { ActiveFile.SetState(_state); } } private static void Show(ZDO sign, string text) { if (sign != null) { World.SetSignText(sign, text); } } public unsafe static void Restore() { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) ZDO val = null; AttemptState attemptState = ActiveFile.State; string text = "по команде"; if (attemptState == null) { ZDOID current = ActiveFile.Current; if (((ZDOID)(ref current)).IsNone()) { return; } current = ActiveFile.Current; text = ((object)(*(ZDOID*)(¤t))/*cast due to .constrained prefix*/).ToString(); val = ZDOMan.instance.GetZDO(ActiveFile.Current); attemptState = ((val != null) ? World.ReadState(val) : null); } if (attemptState == null || attemptState.Stage == Stage.Done) { Plugin.Log.LogWarning((object)("Зов набега: активная попытка " + text + " не найдена или уже завершена — забыта.")); ActiveFile.Set(ZDOID.None); return; } if (attemptState.Tier < 1 || attemptState.Tier > 8) { Plugin.Log.LogWarning((object)$"Зов набега: у попытки {text} уровень {attemptState.Tier} вне 1..{8} — забыта."); ActiveFile.Set(ZDOID.None); return; } _sign = val?.m_uid ?? ZDOID.None; _state = attemptState; if (attemptState.Stage == Stage.Countdown) { attemptState.CountdownAt = World.Now; Save(val); Plugin.Log.LogInfo((object)("Зов набега: восстановлен отсчёт " + attemptState.Event + " — начат заново.")); return; } RandomEvent currentRandomEvent = RandEventSystem.instance.GetCurrentRandomEvent(); if (currentRandomEvent == null || currentRandomEvent.m_name != attemptState.Event) { Plugin.Log.LogInfo((object)("Зов набега: набег " + attemptState.Event + " не восстановлен игрой после перезапуска.")); Finish(val, Outcome.Disrupted); return; } _duration = currentRandomEvent.m_duration; _lastTime = currentRandomEvent.m_time; Plugin.Log.LogInfo((object)$"Зов набега: восстановлен набег {attemptState.Event}, время {currentRandomEvent.m_time:0}/{currentRandomEvent.m_duration:0} с, участников {attemptState.Participants.Count}."); } public static void Tick() { //IL_0019: Unknown result type (might be due to invalid IL or missing references) if (_state != null) { ZDO val = (((ZDOID)(ref _sign)).IsNone() ? null : ZDOMan.instance.GetZDO(_sign)); if (val == null && !_state.ByCommand) { Plugin.Log.LogWarning((object)((_state.Stage == Stage.Countdown) ? ("Зов набега: табличка разрушена до начала набега — набег " + _state.Event + " отменён.") : ("Зов набега: табличка попытки " + _state.Event + " разрушена — итог не засчитывается, набег доигрывается сам."))); Clear(); } else if (_state.Stage == Stage.Countdown) { TickCountdown(val); } else { TickRunning(val); } } } private static void TickCountdown(ZDO sign) { //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_01ca: Unknown result type (might be due to invalid IL or missing references) //IL_01a1: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) int num = Mathf.CeilToInt((float)(_state.CountdownAt + (double)Plugin.CountdownSeconds.Value - World.Now)); string title = Texts.Title(_state.Event); if (num > 0) { if (num != _lastShown && (num % 10 == 0 || num <= 5)) { _lastShown = num; Show(sign, Texts.Countdown(title, num)); if (sign == null && (num == 30 || num == 10)) { World.MessageNear(Pos, 96f, Texts.Countdown(title, num)); } } return; } if (RandEventSystem.instance.GetCurrentRandomEvent() != null) { if (!_waitLogged) { _waitLogged = true; Plugin.Log.LogInfo((object)("Зов набега: идёт чужой набег — " + _state.Event + " ждёт его конца.")); } return; } _waitLogged = false; RandEventSystem.instance.SetRandomEventByName(_state.Event, Pos); RandomEvent currentRandomEvent = RandEventSystem.instance.GetCurrentRandomEvent(); if (currentRandomEvent == null || currentRandomEvent.m_name != _state.Event) { Plugin.Log.LogError((object)("Зов набега: игра не запустила набег " + _state.Event + ".")); Finish(sign, Outcome.Disrupted); return; } _state.Stage = Stage.Running; _state.StartedAt = World.Now; _duration = currentRandomEvent.m_duration; _lastTime = 0.0; _emptySince = -1.0; LastCharacter.Clear(); PeerPlayer.Clear(); Names.Clear(); Save(sign); Show(sign, Texts.Running(title)); if (sign == null) { World.MessageNear(Pos, 96f, Texts.Running(title)); } Plugin.Log.LogInfo((object)$"Зов набега: набег {_state.Event} начался у {Pos}, длительность {currentRandomEvent.m_duration:0} с."); } private static void TickRunning(ZDO sign) { //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_0287: Unknown result type (might be due to invalid IL or missing references) //IL_022a: Unknown result type (might be due to invalid IL or missing references) //IL_0233: Unknown result type (might be due to invalid IL or missing references) RandomEvent currentRandomEvent = RandEventSystem.instance.GetCurrentRandomEvent(); if (currentRandomEvent == null || currentRandomEvent.m_name != _state.Event) { Finish(sign, (currentRandomEvent != null) ? Outcome.Disrupted : RaidOutcome.OnEventGone(_lastTime, _duration)); return; } _lastTime = currentRandomEvent.m_time; bool flag = false; bool flag2 = false; List<World.PlayerInfo> list = World.Players(); foreach (World.PlayerInfo item in list) { if (item.PlayerId != 0L) { bool flag3 = World.DistanceXZ(item.Pos, currentRandomEvent.m_pos) < currentRandomEvent.m_eventRange; flag2 = flag2 || flag3; if (flag3 || _state.Participants.Contains(item.PlayerId)) { Names[item.PlayerId] = item.Peer.m_playerName; } if (flag3 && _state.Participants.Add(item.PlayerId)) { flag = true; Plugin.Log.LogInfo((object)$"Зов набега: участник {item.PlayerId} ({item.Peer.m_playerName})."); } } } HashSet<long> peerUids = new HashSet<long>(); foreach (ZNetPeer peer in ZNet.instance.GetPeers()) { peerUids.Add(peer.m_uid); World.PlayerInfo playerInfo = list.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)$"Зов набега: участник {value} погиб — без награды."); } 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(sign); } double now = World.Now; _emptySince = (flag2 ? (-1.0) : ((_emptySince < 0.0) ? now : _emptySince)); if (RaidOutcome.IsAbandoned(_emptySince, now, (double)Plugin.AbandonMinutes.Value * 60.0)) { RandEventSystem.instance.ResetRandomEvent(); Finish(sign, Outcome.Abandoned); } } private static void Finish(ZDO sign, Outcome outcome) { //IL_0092: Unknown result type (might be due to invalid IL or missing references) try { string title = Texts.Title(_state.Event); _state.Stage = Stage.Done; _state.DoneAt = World.Now; _state.Result = outcome; List<long> list = ((outcome == Outcome.Victory) ? RaidOutcome.Winners(_state.Participants, _state.Dead) : new List<long>()); foreach (long item in list.Where((long id) => !_state.Rewarded.Contains(id))) { try { if (Rewards.Give(_sign, _state, item)) { _state.Rewarded.Add(item); } } catch (Exception ex) { Plugin.Log.LogError((object)($"Зов набега: ошибка выдачи награды игроку {item}: " + ex)); } } Save(sign); Show(sign, Texts.Result(outcome, title)); foreach (World.PlayerInfo item2 in from p in World.Players() where _state.Participants.Contains(p.PlayerId) select p) { World.Message(item2.Peer.m_uid, Texts.ResultMessage(outcome, title, list.Contains(item2.PlayerId), _state.Rewarded.Contains(item2.PlayerId))); } Plugin.Log.LogInfo((object)string.Format("Зов набега: итог {0} — {1}; участники {2}; погибли {3}; награждены {4}.", _state.Event, outcome, string.Join(", ", _state.Participants), string.Join(", ", _state.Dead), string.Join(", ", _state.Rewarded))); WriteChronicle(outcome, (outcome == Outcome.Victory) ? list : _state.Participants.ToList()); } finally { Clear(); } } private static void WriteChronicle(Outcome outcome, List<long> players) { try { string value; ChronicleEntry entry = new ChronicleEntry { Time = World.Now, Date = DateTime.UtcNow.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture), Kind = "raid", Id = _state.Event, Result = outcome switch { Outcome.Abandoned => "abandoned", Outcome.Victory => "victory", _ => "disrupted", }, 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.LogError((object)("Зов набега: ошибка записи в летопись: " + ex)); } } private static void Clear() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) _state = null; _sign = ZDOID.None; LastCharacter.Clear(); PeerPlayer.Clear(); ActiveFile.Set(ZDOID.None); } } internal static class Rewards { private static readonly Random Rng = new Random(); private static MethodInfo _enqueue; private static bool _checked; public static bool Give(ZDOID sign, AttemptState state, long playerId) { if (state.Tier < 1 || state.Tier > 8) { Plugin.Log.LogWarning((object)$"Зов набега: награда игроку {playerId} не выдана — уровень {state.Tier} вне 1..{8}."); return false; } string[] array = Plugin.RewardPool[state.Tier].Value.Split(new char[3] { ',', ';', ' ' }, StringSplitOptions.RemoveEmptyEntries); if (array.Length == 0) { Plugin.Log.LogWarning((object)$"Зов набега: награда игроку {playerId} не выдана — RewardPool уровня {state.Tier} пуст."); return false; } MethodInfo methodInfo = Api(); if (methodInfo == null) { Plugin.Log.LogError((object)$"Зов набега: награда игроку {playerId} не выдана — нет Event Rewards (ExternalRewards.Enqueue)."); return false; } bool result = false; for (int i = 1; i <= Math.Max(1, Plugin.RewardCount[state.Tier].Value); i++) { string text = array[Rng.Next(array.Length)]; string text2 = RewardReason.Of(((ZDOID)(ref sign)).IsNone() ? null : $"{((ZDOID)(ref sign)).UserID}:{((ZDOID)(ref sign)).ID}", state.StartedAt, i); string text3 = (string)methodInfo.Invoke(null, new object[3] { playerId, text, text2 }); if (text3 != null) { Plugin.Log.LogError((object)$"Зов набега: награда {text} игроку {playerId} не выдана: {text3}."); continue; } result = true; Plugin.Log.LogInfo((object)$"Зов набега: игроку {playerId} поставлена награда {text} ({text2})."); } return result; } private static MethodInfo Api() { if (!_checked) { _checked = true; Type type = AccessTools.TypeByName("EventRewards.ExternalRewards"); _enqueue = ((type == null) ? null : AccessTools.Method(type, "Enqueue", new Type[3] { typeof(long), typeof(string), typeof(string) }, (Type[])null)); } return _enqueue; } } internal static class World { internal struct PlayerInfo { public ZNetPeer Peer; public ZDO Character; public long PlayerId; public Vector3 Pos; } internal const string StateKey = "raidcall_state"; private static readonly HashSet<int> SignPrefabs = new HashSet<int>(); private static readonly HashSet<int> StandPrefabs = new HashSet<int>(); private static readonly Dictionary<int, float> WardRadius = new Dictionary<int, float>(); private static readonly List<ZDO> Found = new List<ZDO>(); public static double Now => ZNet.instance.GetTimeSeconds(); public static void Init() { foreach (GameObject prefab in ZNetScene.instance.m_prefabs) { int stableHashCode = StringExtensionMethods.GetStableHashCode(((Object)prefab).name); if ((Object)(object)prefab.GetComponent<Sign>() != (Object)null) { SignPrefabs.Add(stableHashCode); } if ((Object)(object)prefab.GetComponent<ItemStand>() != (Object)null) { StandPrefabs.Add(stableHashCode); } PrivateArea component = prefab.GetComponent<PrivateArea>(); if ((Object)(object)component != (Object)null) { WardRadius[stableHashCode] = component.m_radius; } } Plugin.Log.LogInfo((object)$"Зов набега: префабов табличек {SignPrefabs.Count}, подставок {StandPrefabs.Count}, вардов {WardRadius.Count}."); } public static List<PlayerInfo> Players() { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) List<PlayerInfo> list = new List<PlayerInfo>(); foreach (ZNetPeer peer in ZNet.instance.GetPeers()) { ZDO val = (((ZDOID)(ref peer.m_characterID)).IsNone() ? null : ZDOMan.instance.GetZDO(peer.m_characterID)); if (val != null) { list.Add(new PlayerInfo { Peer = peer, Character = val, PlayerId = val.GetLong(ZDOVars.s_playerID, 0L), Pos = val.GetPosition() }); } } return list; } public static float DistanceXZ(Vector3 a, Vector3 b) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) return Utils.DistanceXZ(a, b); } private static List<ZDO> Near(Vector3 pos, float radius, HashSet<int> prefabs) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) Found.Clear(); int num = Mathf.CeilToInt(radius / 64f); ZDOMan.instance.FindSectorObjects(ZoneSystem.GetZone(pos), new SimulationDistance(num, 0, true), Found, (List<ZDO>)null); return Found.Where((ZDO z) => prefabs.Contains(z.GetPrefab()) && Vector3.Distance(z.GetPosition(), pos) <= radius).ToList(); } public static List<ZDO> SignsNear(Vector3 pos, float radius) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) return Near(pos, radius, SignPrefabs); } public static ZDO StandWithItemNear(Vector3 pos, float radius) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) return (from z in Near(pos, radius, StandPrefabs) where z.GetInt(ZDOVars.s_item, 0) != 0 orderby Vector3.Distance(z.GetPosition(), pos) select z).FirstOrDefault(); } public static string ItemOnStand(ZDO stand) { GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(stand.GetInt(ZDOVars.s_item, 0)); if (!((Object)(object)itemPrefab != (Object)null)) { return ""; } return ((Object)itemPrefab).name; } public static List<WardInfo> WardsAround(Vector3 pos) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) float radius = ((WardRadius.Count > 0) ? WardRadius.Values.Max() : 32f); List<WardInfo> list = new List<WardInfo>(); foreach (ZDO item in Near(pos, radius, new HashSet<int>(WardRadius.Keys))) { List<long> list2 = new List<long>(); int num = item.GetInt(ZDOVars.s_permitted, 0); for (int i = 0; i < num; i++) { list2.Add(item.GetLong("pu_id" + i, 0L)); } bool inside = DistanceXZ(item.GetPosition(), pos) <= WardRadius[item.GetPrefab()]; list.Add(new WardInfo(item.GetBool(ZDOVars.s_enabled, false), inside, item.GetLong(ZDOVars.s_creator, 0L), list2)); } return list; } public static int BiomeAt(Vector3 pos) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Expected I4, but got Unknown return (int)WorldGenerator.instance.GetBiome(pos); } public static RandomEvent EventTemplate(string name) { return ((IEnumerable<RandomEvent>)RandEventSystem.instance.m_events).FirstOrDefault((Func<RandomEvent, bool>)((RandomEvent e) => e.m_name == name && e.m_enabled)); } public static IEnumerable<int> SpawnBiomes(RandomEvent ev) { return from s in ev.m_spawn where s.m_enabled select (int)s.m_biome; } public static long CallerOf(ZDO sign, List<PlayerInfo> players) { long owner = sign.GetOwner(); foreach (PlayerInfo player in players) { if (player.Peer.m_uid == owner) { return player.PlayerId; } } PlayerInfo playerInfo = (from p in players where Vector3.Distance(p.Pos, sign.GetPosition()) <= 10f orderby Vector3.Distance(p.Pos, sign.GetPosition()) select p).FirstOrDefault(); if (playerInfo.Peer == null) { return 0L; } return playerInfo.PlayerId; } public static void SetSignText(ZDO sign, string text) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) sign.SetOwner(ZDOMan.GetSessionID()); sign.Set(ZDOVars.s_text, text); ZDOMan.instance.ForceSendZDO(sign.m_uid); } public static string SignText(ZDO sign) { return sign.GetString(ZDOVars.s_text, ""); } public static AttemptState ReadState(ZDO sign) { return AttemptState.Parse(sign.GetString("raidcall_state", "")); } public static void WriteState(ZDO sign, AttemptState state) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) sign.SetOwner(ZDOMan.GetSessionID()); sign.Set("raidcall_state", state.Serialize()); ZDOMan.instance.ForceSendZDO(sign.m_uid); } public static bool TakeItem(ZDO stand, string expectedPrefab) { //IL_003b: 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) if (ItemOnStand(stand) != expectedPrefab) { return false; } stand.SetOwner(ZDOMan.GetSessionID()); stand.Set(ZDOVars.s_item, 0, false); stand.Set(ZDOVars.s_type, 0, false); ZDOMan.instance.ForceSendZDO(stand.m_uid); ZRoutedRpc.instance.InvokeRoutedRPC(0L, stand.m_uid, "SetVisualItem", new object[4] { 0, 0, 0, 0 }); return true; } public static void Message(long peerUid, string text) { ZRoutedRpc.instance.InvokeRoutedRPC(peerUid, "ShowMessage", new object[2] { 2, text }); } public static void MessageNear(Vector3 pos, float radius, string text) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) foreach (PlayerInfo item in Players()) { if (DistanceXZ(item.Pos, pos) <= radius) { Message(item.Peer.m_uid, text); } } } } } namespace RaidCall.Core { public enum StartRefusal { None, NotInTable, EventDisabled, Busy, NoPlayerFromFile, PlayerNotFound, Underground, WrongBiome } public sealed class StartChoice { public string Event = ""; public int Tier; public string Trophy = ""; } public struct StartFacts { public bool Resolved; public bool EventEnabled; public bool Busy; public bool FromFile; public bool PlayerGiven; public bool PlayerFound; public float PlayerY; public bool BiomeFits; } public static class AdminStart { public const float DungeonHeight = 3000f; private static readonly CultureInfo Inv = CultureInfo.InvariantCulture; public static StartChoice Resolve(string argument, IDictionary<string, RaidEntry> table) { string a = (argument ?? "").Trim(); if (a == "") { return null; } RaidEntry raidEntry = table.Values.FirstOrDefault((RaidEntry e) => string.Equals(e.Trophy, a, StringComparison.OrdinalIgnoreCase)); if (raidEntry != null) { return new StartChoice { Event = raidEntry.Event, Tier = raidEntry.Tier, Trophy = raidEntry.Trophy }; } List<RaidEntry> list = table.Values.Where((RaidEntry e) => string.Equals(e.Event, a, StringComparison.OrdinalIgnoreCase)).ToList(); if (list.Count != 0) { return new StartChoice { Event = list[0].Event, Tier = list.Min((RaidEntry e) => e.Tier) }; } return null; } public static StartRefusal Check(StartFacts f) { if (!f.Resolved) { return StartRefusal.NotInTable; } if (!f.EventEnabled) { return StartRefusal.EventDisabled; } if (f.Busy) { return StartRefusal.Busy; } if (f.FromFile && !f.PlayerGiven) { return StartRefusal.NoPlayerFromFile; } if (!f.PlayerFound) { return StartRefusal.PlayerNotFound; } if (f.PlayerY >= 3000f) { return StartRefusal.Underground; } if (!f.BiomeFits) { return StartRefusal.WrongBiome; } return StartRefusal.None; } public static string RefusalText(StartRefusal r, string argument, string player) { switch (r) { case StartRefusal.NotInTable: return "«" + argument + "» — нет такого набега или трофея в таблице Raids"; case StartRefusal.EventDisabled: return "этого набега нет в игре или он выключен"; case StartRefusal.Busy: return "уже идёт попытка или набег игры — сначала status/stop"; case StartRefusal.NoPlayerFromFile: return "из файла команд укажите игрока: start <набег|трофей> <Имя|@ID>"; case StartRefusal.PlayerNotFound: if (!(player == "")) { return "игрок «" + player + "» не найден в игре"; } return "вы не в игре: укажите игрока"; case StartRefusal.Underground: return "игрок в подземелье: набег начинается только на поверхности"; case StartRefusal.WrongBiome: return "враги этого набега не приходят в биом игрока"; default: return "набег не начат"; } } public static string Started(string eventName, int tier, string player, float x, float z, int seconds) { return "набег " + Texts.Title(eventName) + " (" + eventName + "), уровень " + tier.ToString(Inv) + ", у игрока " + player + " в (" + x.ToString("0", Inv) + "; " + z.ToString("0", Inv) + "), отсчёт " + seconds.ToString(Inv) + " с"; } } public static class RewardReason { public static string Of(string signKey, double startedAt, int n) { return "raid:" + (signKey ?? "cmd") + ":" + ((long)startedAt).ToString(CultureInfo.InvariantCulture) + ((n > 1) ? ("/" + n.ToString(CultureInfo.InvariantCulture)) : ""); } } public enum Stage { Countdown, Running, Done } public sealed class AttemptState { private static readonly CultureInfo Inv = CultureInfo.InvariantCulture; public Stage Stage; public string Event = ""; public int Tier; public string Trophy = ""; public long Caller; public float PosX; public float PosY; public float PosZ; public double CountdownAt; public double StartedAt = -1.0; public double DoneAt = -1.0; public Outcome? Result; public bool ByCommand; public HashSet<long> Participants = new HashSet<long>(); public HashSet<long> Dead = new HashSet<long>(); public HashSet<long> Rewarded = new HashSet<long>(); public string Serialize() { List<string> list = new List<string>(); list.Add("stage=" + Stage); list.Add("event=" + Event); list.Add("tier=" + Tier.ToString(Inv)); list.Add("trophy=" + Trophy); list.Add("caller=" + Caller.ToString(Inv)); list.Add("pos=" + string.Join(",", PosX.ToString("R", Inv), PosY.ToString("R", Inv), PosZ.ToString("R", Inv))); list.Add("countdown=" + CountdownAt.ToString("R", Inv)); list.Add("started=" + StartedAt.ToString("R", Inv)); list.Add("done=" + DoneAt.ToString("R", Inv)); list.Add("result=" + Result); list.Add("members=" + Ids(Participants)); list.Add("dead=" + Ids(Dead)); list.Add("rewarded=" + Ids(Rewarded)); list.Add("cmd=" + (ByCommand ? "1" : "0")); List<string> values = list; return string.Join(";", values); } public static AttemptState Parse(string text) { if (string.IsNullOrEmpty(text)) { return null; } Dictionary<string, string> dictionary = new Dictionary<string, string>(); string[] array = text.Split(new char[1] { ';' }); foreach (string text2 in array) { int num = text2.IndexOf('='); if (num > 0) { dictionary[text2.Substring(0, num)] = text2.Substring(num + 1); } } if (!dictionary.TryGetValue("stage", out var value) || !Enum.TryParse<Stage>(value, out var result) || !dictionary.TryGetValue("event", out var value2) || value2 == "") { return null; } try { AttemptState attemptState = new AttemptState { Stage = result, Event = value2 }; attemptState.Tier = int.Parse(Get(dictionary, "tier", "0"), Inv); attemptState.Trophy = Get(dictionary, "trophy", ""); attemptState.Caller = long.Parse(Get(dictionary, "caller", "0"), Inv); string[] array2 = Get(dictionary, "pos", "0,0,0").Split(new char[1] { ',' }); attemptState.PosX = float.Parse(array2[0], Inv); attemptState.PosY = float.Parse(array2[1], Inv); attemptState.PosZ = float.Parse(array2[2], Inv); attemptState.CountdownAt = double.Parse(Get(dictionary, "countdown", "0"), Inv); attemptState.StartedAt = double.Parse(Get(dictionary, "started", "-1"), Inv); attemptState.DoneAt = double.Parse(Get(dictionary, "done", "-1"), Inv); string text3 = Get(dictionary, "result", ""); attemptState.Result = ((text3 == "") ? ((Outcome?)null) : new Outcome?((Outcome)Enum.Parse(typeof(Outcome), text3))); attemptState.Participants = ParseIds(Get(dictionary, "members", "")); attemptState.Dead = ParseIds(Get(dictionary, "dead", "")); attemptState.Rewarded = ParseIds(Get(dictionary, "rewarded", "")); attemptState.ByCommand = Get(dictionary, "cmd", "0") == "1"; return attemptState; } catch (Exception ex) when (ex is FormatException || ex is OverflowException || ex is ArgumentException || ex is IndexOutOfRangeException) { return null; } } private static string Get(Dictionary<string, string> map, string key, string fallback) { if (!map.TryGetValue(key, out var value)) { return fallback; } return value; } private static string Ids(IEnumerable<long> ids) { return string.Join(",", ids.Select((long i) => i.ToString(Inv))); } private static HashSet<long> ParseIds(string text) { return new HashSet<long>(from i in text.Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries) select long.Parse(i, Inv)); } } public enum Refusal { None, NoTrophy, UnknownTrophy, EventUnavailable, Busy, Cooldown, ForeignWard, WrongBiome, TakeFailed } public struct CallFacts { public bool HasTrophy; public bool TrophyKnown; public bool EventAvailable; public bool Busy; public double Now; public double LastDoneAt; public double CooldownSeconds; public bool ForeignWard; public bool BiomeFits; } public static class CallRules { public static Refusal Check(CallFacts f) { if (!f.HasTrophy) { return Refusal.NoTrophy; } if (!f.TrophyKnown) { return Refusal.UnknownTrophy; } if (!f.EventAvailable) { return Refusal.EventUnavailable; } if (f.Busy) { return Refusal.Busy; } if (CooldownLeft(f) > 0.0) { return Refusal.Cooldown; } if (f.ForeignWard) { return Refusal.ForeignWard; } if (!f.BiomeFits) { return Refusal.WrongBiome; } return Refusal.None; } public static double CooldownLeft(CallFacts f) { if (!(f.LastDoneAt < 0.0)) { return Math.Max(0.0, f.LastDoneAt + f.CooldownSeconds - f.Now); } return 0.0; } public static bool BiomeFits(int placeBiome, IEnumerable<int> spawnBiomes) { return spawnBiomes.Any((int b) => (b & placeBiome) != 0); } } public enum Outcome { Victory, Disrupted, Abandoned } public static class RaidOutcome { public const double EndTolerance = 2.5; public static Outcome OnEventGone(double lastTime, double duration) { if (!(duration > 0.0) || !(lastTime >= duration - 2.5)) { return Outcome.Disrupted; } return Outcome.Victory; } public static bool IsAbandoned(double emptySince, double now, double abandonSeconds) { if (emptySince >= 0.0) { return now - emptySince >= abandonSeconds; } return false; } public static List<long> Winners(IEnumerable<long> participants, ICollection<long> dead) { return participants.Where((long p) => !dead.Contains(p)).Distinct().ToList(); } } public static class RaidReport { private static readonly CultureInfo Inv = CultureInfo.InvariantCulture; public static string Status(AttemptState state, double now, int countdownSeconds, double eventTime, double eventDuration, string otherEvent, int tableSize) { string text = "трофеев в таблице " + tableSize.ToString(Inv); bool flag = !string.IsNullOrEmpty(otherEvent); if (state == null) { return (flag ? ("занято: попытки нет, идёт набег игры " + otherEvent) : "свободно: попытки нет") + "; " + text; } string text2 = ((state.Trophy == "") ? "без трофея" : ("трофей " + state.Trophy)); string text3 = (state.ByCommand ? "по команде у игрока " : "позвал игрок ") + state.Caller.ToString(Inv); string text4 = "занято: " + Texts.Title(state.Event) + " (" + state.Event + "), уровень " + state.Tier.ToString(Inv) + ", " + text2 + ", " + text3 + ", место (" + state.PosX.ToString("0", Inv) + "; " + state.PosY.ToString("0", Inv) + "; " + state.PosZ.ToString("0", Inv) + ")"; string text5 = ((state.Stage == Stage.Countdown) ? ("отсчёт: до начала " + Math.Max(0.0, Math.Ceiling(state.CountdownAt + (double)countdownSeconds - now)).ToString("0", Inv) + " с" + (flag ? (", ждёт конца набега игры " + otherEvent) : "")) : ("идёт: " + eventTime.ToString("0", Inv) + " из " + eventDuration.ToString("0", Inv) + " с")); return text4 + "\n" + text5 + "; участники " + state.Participants.Count.ToString(Inv) + ", погибли " + state.Dead.Count.ToString(Inv) + "; " + text; } } public sealed class RaidEntry { public string Trophy { get; } public string Event { get; } public int Tier { get; } public RaidEntry(string trophy, string eventName, int tier) { Trophy = trophy; Event = eventName; Tier = tier; } } public static class RaidTable { public const int MaxTier = 8; public const string Default = "TrophyBoar:army_eikthyr:1, TrophyNeck:army_eikthyr:1, TrophyDeer:army_eikthyr:1, TrophyEikthyr:army_eikthyr:2, TrophyGreydwarf:army_theelder:2, TrophyGreydwarfBrute:army_theelder:2, TrophyGreydwarfShaman:army_theelder:2, TrophyTheElder:army_theelder:3, TrophyForestTroll:foresttrolls:2, TrophyBjorn:foresttrolls:2, TrophySkeleton:skeletons:2, TrophyGhost:ghosts:2, TrophySkeletonHildir:hildirboss1:4, TrophyDraugr:army_bonemass:3, TrophyDraugrFem:army_bonemass:3, TrophyDraugrElite:army_bonemass:3, TrophyAbomination:army_bonemass:3, TrophyKvastur:army_bonemass:4, TrophyBonemass:army_bonemass:4, TrophyBlob:blobs:3, TrophyLeech:blobs:3, TrophySkeletonPoison:skeletons:3, TrophyWraith:ghosts:3, TrophySurtling:surtlings:3, TrophyHatchling:army_moder:4, TrophySGolem:army_moder:4, TrophySerpent:army_moder:4, TrophyDragonQueen:army_moder:5, TrophyWolf:wolves:4, TrophyFenring:wolves:4, TrophyUlv:bats:4, TrophyCultist:bats:4, TrophyCultist_Hildir:hildirboss2:4, TrophyGoblin:army_goblin:5, TrophyGoblinBrute:army_goblin:5, TrophyGoblinShaman:army_goblin:5, TrophyLox:army_goblin:5, TrophyGoblinKing:army_goblin:6, TrophyDeathsquito:bats:5, TrophyGrowth:blobs:5, TrophyBjornUndead:foresttrolls:5, TrophyGoblinBruteBrosBrute:hildirboss3:5, TrophyGoblinBruteBrosShaman:hildirboss3:5, TrophyGjall:army_gjall:6, TrophyTick:army_gjall:6, TrophyHare:army_gjall:6, TrophySeeker:army_seekers:6, TrophySeekerBrute:army_seekers:6, TrophyDvergr:army_seekers:6, TrophySeekerQueen:army_seekers:7, TrophyCharredMelee:army_charred:7, TrophyCharredArcher:army_charred:7, TrophyAsksvin:army_charred:7, TrophyVolture:army_charred:7, TrophyBonemawSerpent:army_charred:7, TrophyCharredMage:army_charredspawners:7, TrophyMorgen:army_charredspawners:7, TrophyFallenValkyrie:army_charredspawners:7, TrophyBlob_Lava:blobs:7, TrophyFader:army_charredspawners:8, TrophyJotunWarrior:army_jotuns:8, TrophyJotunWitch:army_jotuns:8, TrophyFrostTroll:army_jotuns:8, TrophyBarka:army_jotuns:8, TrophyWrithan:army_jotuns:8, TrophyMole:army_elakingar:8, TrophyElaking:army_elakingar:8, TrophyMoose:army_elakingar:8, TrophySeal:army_elakingar:8, TrophyDeerWhite:army_elakingar:8, TrophyBlob_Frost:blobs:8, TrophyBlob_Morkhalla:blobs:8"; public static Dictionary<string, RaidEntry> Parse(string text, List<string> errors) { Dictionary<string, RaidEntry> dictionary = new Dictionary<string, RaidEntry>(StringComparer.OrdinalIgnoreCase); string[] array = (text ?? "").Split(new char[3] { ',', ';', '\n' }, StringSplitOptions.RemoveEmptyEntries); for (int i = 0; i < array.Length; i++) { string text2 = array[i].Trim(); if (text2.Length == 0) { continue; } string[] array2 = text2.Split(new char[1] { ':' }); if (array2.Length != 3 || array2[0].Trim() == "" || array2[1].Trim() == "" || !int.TryParse(array2[2].Trim(), out var result) || result < 1 || result > 8) { errors.Add($"строка «{text2}» пропущена: нужно Трофей:набег:уровень 1–{8}"); continue; } string text3 = array2[0].Trim(); if (dictionary.ContainsKey(text3)) { errors.Add("трофей " + text3 + " указан повторно, оставлена первая строка"); } else { dictionary[text3] = new RaidEntry(text3, array2[1].Trim(), result); } } return dictionary; } } public static class RaidUi { public static string Text(IDictionary<string, RaidEntry> table, Func<string, bool> enabled, bool busy) { List<string> list = new List<string>(); list.Add(AdminUi.Row("busy", busy)); List<string> list2 = list; foreach (IGrouping<string, RaidEntry> item in (from g in table.Values.GroupBy<RaidEntry, string>((RaidEntry e) => e.Event, StringComparer.Ordinal) orderby g.Min((RaidEntry e) => e.Tier) select g).ThenBy<IGrouping<string, RaidEntry>, string>((IGrouping<string, RaidEntry> g) => g.Key, StringComparer.Ordinal)) { string text = string.Join(",", from e in item group e by e.Tier into t orderby t.Key select t.Key.ToString(CultureInfo.InvariantCulture) + ":" + t.Select((RaidEntry e) => e.Trophy).OrderBy<string, string>((string s) => s, StringComparer.Ordinal).First()); list2.Add(AdminUi.Row("raid", item.Key, enabled(item.Key), item.Min((RaidEntry e) => e.Tier), text)); } return AdminUi.Text(list2); } } public static class Texts { public const int SignLimit = 50; private static readonly Dictionary<string, string> Titles = new Dictionary<string, string> { ["army_eikthyr"] = "Свита Эйктюра", ["army_theelder"] = "Грейдворфы", ["foresttrolls"] = "Тролли", ["army_bonemass"] = "Драугры", ["blobs"] = "Слизни", ["skeletons"] = "Скелеты", ["ghosts"] = "Призраки", ["surtlings"] = "Суртлинги", ["army_moder"] = "Дракончики", ["wolves"] = "Волки", ["bats"] = "Летучие мыши", ["hildirboss1"] = "Бренна", ["hildirboss2"] = "Гейрафа", ["hildirboss3"] = "Зил и Тангр", ["army_goblin"] = "Фулинги", ["army_gjall"] = "Гьяллы", ["army_seekers"] = "Искатели", ["army_charred"] = "Обугленные", ["army_charredspawners"] = "Камни обугленных", ["army_jotuns"] = "Ётуны", ["army_elakingar"] = "Элакинги" }; public static string Title(string eventName) { if (!Titles.TryGetValue(eventName, out var value)) { return eventName; } return value; } public static string Countdown(string title, int seconds) { return $"Набег: {title} через {seconds} с"; } public static string Running(string title) { return "Набег: " + title + "! Держитесь"; } public static string Result(Outcome outcome, string title) { return outcome switch { Outcome.Victory => "Выстояли: " + title, Outcome.Abandoned => "Набег брошен: " + title, _ => "Набег сорван: " + title, }; } public static string RefusalText(Refusal refusal, double cooldownLeftSeconds) { return refusal switch { Refusal.NoTrophy => "Повесьте трофей на подставку рядом", Refusal.UnknownTrophy => "Этот трофей не зовёт набег", Refusal.EventUnavailable => "Этот набег сейчас недоступен", Refusal.Busy => "Сейчас идёт другой набег", Refusal.Cooldown => $"Место отдыхает ещё {(int)Math.Ceiling(cooldownLeftSeconds / 60.0)} мин", Refusal.ForeignWard => "Чужой вард: здесь звать нельзя", Refusal.WrongBiome => "Эти враги сюда не приходят", Refusal.TakeFailed => "Не удалось взять трофей", _ => "Набег нельзя позвать", }; } public static string StartMessage(string title, int seconds) { return $"Зов набега: {title} через {seconds} с. Готовьтесь!"; } public static string ResultMessage(Outcome outcome, string title, bool survived, bool rewarded) { switch (outcome) { case Outcome.Victory: if (!survived) { return "Выстояли: " + title + "! Павшим награды нет."; } if (!rewarded) { return "Выстояли: " + title + "!"; } return "Выстояли: " + title + "! Награда ждёт."; case Outcome.Abandoned: return "Набег брошен: " + title + ". Награды нет."; default: return "Набег сорван: " + title + ". Награды нет."; } } } public sealed class WardInfo { public bool Enabled { get; } public bool Inside { get; } public long Creator { get; } public HashSet<long> Permitted { get; } public WardInfo(bool enabled, bool inside, long creator, IEnumerable<long> permitted) { Enabled = enabled; Inside = inside; Creator = creator; Permitted = new HashSet<long>(permitted ?? Enumerable.Empty<long>()); } } public static class WardRules { public static bool IsForeign(long caller, IEnumerable<WardInfo> wards) { return wards.Any((WardInfo w) => w.Enabled && w.Inside && (caller == 0L || (w.Creator != caller && !w.Permitted.Contains(caller)))); } } }