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 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 Registered = new HashSet(); private static readonly Dictionary LastDenial = new Dictionary(); public static void Register(string module, string[] verbs, Func 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 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 live = new HashSet(); 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("Sansara_Admin", (Action)OnRequest); } } } Registered.RemoveWhere((ZRpc r) => !live.Contains(r)); foreach (ZRpc item in new List(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 Args = new List(); public string Argument => string.Join(" ", Args); public static AdminCommand Parse(string text) { AdminCommand adminCommand = new AdminCommand(); List 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 Tokens(string text) { List list = new List(); 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 verbs) { return module + ": " + string.Join(", ", verbs); } public static Dictionary ParseHelp(string text) { Dictionary dictionary = new Dictionary(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 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>> Table { get { object data = AppDomain.CurrentDomain.GetData(_key); if (data == null) { Dictionary>> dictionary = new Dictionary>>(StringComparer.Ordinal); AppDomain.CurrentDomain.SetData(_key, dictionary); return dictionary; } return (data as Dictionary>>) ?? 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 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>> 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 Modules() { return Table.Keys.OrderBy((string k) => k, StringComparer.Ordinal).ToList(); } public string HelpText() { Dictionary>> 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>> 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 rows) { List list = (rows ?? Enumerable.Empty()).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 Players = new List(); 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()).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 ParseAll(string text, out int bad) { List list = new List(); 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()).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 Read(string path, out int bad) { bad = 0; if (!File.Exists(path)) { return new List(); } return ChronicleEntry.ParseAll(File.ReadAllText(path), out bad); } } public sealed class FirstVictory { public string Id; public string Date; public List Names = new List(); public int Count; } public sealed class Leader { public string Name; public int Count; } public static class ChronicleStats { public static Dictionary Firsts(IEnumerable all, string kind) { Dictionary dictionary = new Dictionary(); 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 Leaders(IEnumerable all, string kind) { Dictionary dictionary = new Dictionary(); Dictionary names = new Dictionary(); 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 l) => l.Name, StringComparer.Ordinal).ToList(); } public static string Names(IList 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 list = World.Players(); HashSet hashSet = new HashSet(); 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 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 Enabled; internal static ConfigEntry Keyword; internal static ConfigEntry StandRadius; internal static ConfigEntry CountdownSeconds; internal static ConfigEntry CooldownMinutes; internal static ConfigEntry CooldownRadius; internal static ConfigEntry AbandonMinutes; internal static ConfigEntry Raids; internal static readonly ConfigEntry[] RewardPool = new ConfigEntry[9]; internal static readonly ConfigEntry[] RewardCount = new ConfigEntry[9]; internal static Dictionary Table = new Dictionary(); 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("General", "Enabled", true, "Мод работает."); Keyword = ((BaseUnityPlugin)this).Config.Bind("General", "Keyword", "набег", "Текст таблички для вызова набега (без учёта регистра и пробелов по краям)."); StandRadius = ((BaseUnityPlugin)this).Config.Bind("General", "StandRadius", 3f, "Наибольшее расстояние от таблички до подставки с трофеем, м."); CountdownSeconds = ((BaseUnityPlugin)this).Config.Bind("General", "CountdownSeconds", 60, "Время на подготовку перед набегом, с."); CooldownMinutes = ((BaseUnityPlugin)this).Config.Bind("General", "CooldownMinutes", 15, "Перезарядка места после набега, реальные минуты."); CooldownRadius = ((BaseUnityPlugin)this).Config.Bind("General", "CooldownRadius", 100f, "Радиус места для перезарядки, м."); AbandonMinutes = ((BaseUnityPlugin)this).Config.Bind("General", "AbandonMinutes", 10, "Сколько набег может стоять без игроков в зоне, пока его не снимут."); Raids = ((BaseUnityPlugin)this).Config.Bind("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($"Tier{i}", "RewardPool", "", "Награды Event Rewards уровня через запятую; пусто — набег без награды."); RewardCount[i] = ((BaseUnityPlugin)this).Config.Bind($"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 list = new List(); 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 list = (from p in World.Players() where p.PlayerId != 0 select p).ToList(); Func func; if (who != "") { func = ((!AdminCommand.TryId(who, out var id)) ? ((Func)((World.PlayerInfo p) => string.Equals(p.Peer.m_playerName, who, StringComparison.OrdinalIgnoreCase))) : ((Func)((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 LastCharacter = new Dictionary(); private static readonly Dictionary PeerPlayer = new Dictionary(); private static readonly Dictionary Names = new Dictionary(); 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 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 peerUids = new HashSet(); 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 list = ((outcome == Outcome.Victory) ? RaidOutcome.Winners(_state.Participants, _state.Dead) : new List()); 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 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 SignPrefabs = new HashSet(); private static readonly HashSet StandPrefabs = new HashSet(); private static readonly Dictionary WardRadius = new Dictionary(); private static readonly List Found = new List(); 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() != (Object)null) { SignPrefabs.Add(stableHashCode); } if ((Object)(object)prefab.GetComponent() != (Object)null) { StandPrefabs.Add(stableHashCode); } PrivateArea component = prefab.GetComponent(); if ((Object)(object)component != (Object)null) { WardRadius[stableHashCode] = component.m_radius; } } Plugin.Log.LogInfo((object)$"Зов набега: префабов табличек {SignPrefabs.Count}, подставок {StandPrefabs.Count}, вардов {WardRadius.Count}."); } public static List 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 list = new List(); 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 Near(Vector3 pos, float radius, HashSet 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)null); return Found.Where((ZDO z) => prefabs.Contains(z.GetPrefab()) && Vector3.Distance(z.GetPosition(), pos) <= radius).ToList(); } public static List 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 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 list = new List(); foreach (ZDO item in Near(pos, radius, new HashSet(WardRadius.Keys))) { List list2 = new List(); 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)RandEventSystem.instance.m_events).FirstOrDefault((Func)((RandomEvent e) => e.m_name == name && e.m_enabled)); } public static IEnumerable 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 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 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 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 Participants = new HashSet(); public HashSet Dead = new HashSet(); public HashSet Rewarded = new HashSet(); public string Serialize() { List list = new List(); 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 values = list; return string.Join(";", values); } public static AttemptState Parse(string text) { if (string.IsNullOrEmpty(text)) { return null; } Dictionary dictionary = new Dictionary(); 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(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 map, string key, string fallback) { if (!map.TryGetValue(key, out var value)) { return fallback; } return value; } private static string Ids(IEnumerable ids) { return string.Join(",", ids.Select((long i) => i.ToString(Inv))); } private static HashSet ParseIds(string text) { return new HashSet(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 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 Winners(IEnumerable participants, ICollection 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 Parse(string text, List errors) { Dictionary dictionary = new Dictionary(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 table, Func enabled, bool busy) { List list = new List(); list.Add(AdminUi.Row("busy", busy)); List list2 = list; foreach (IGrouping item in (from g in table.Values.GroupBy((RaidEntry e) => e.Event, StringComparer.Ordinal) orderby g.Min((RaidEntry e) => e.Tier) select g).ThenBy, string>((IGrouping 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 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 Titles = new Dictionary { ["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 Permitted { get; } public WardInfo(bool enabled, bool inside, long creator, IEnumerable permitted) { Enabled = enabled; Inside = inside; Creator = creator; Permitted = new HashSet(permitted ?? Enumerable.Empty()); } } public static class WardRules { public static bool IsForeign(long caller, IEnumerable wards) { return wards.Any((WardInfo w) => w.Enabled && w.Inside && (caller == 0L || (w.Creator != caller && !w.Permitted.Contains(caller)))); } } }