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 SansaraPath v0.6.1
plugins/SansaraPath.dll
Decompiled a day ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Jotunn.Entities; using Jotunn.Managers; using Jotunn.Utils; using Microsoft.CodeAnalysis; using Sansara.Admin; using Sansara.Chronicle; using Sansara.Deeds; using SansaraPath.Core; using Splatform; using TMPro; using UnityEngine; using UnityEngine.Events; using UnityEngine.UI; [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("SansaraPath")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.6.1.0")] [assembly: AssemblyInformationalVersion("0.6.1+60a4ae76ed7918200cbfb333ad77d944d474136c")] [assembly: AssemblyProduct("SansaraPath")] [assembly: AssemblyTitle("SansaraPath")] [assembly: AssemblyVersion("0.6.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.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 Sansara.Deeds { public static class DeedBus { public const string Key = "sansara.Deeds.v1"; private static Dictionary<string, Action<string, string, int, string, long[], string[]>> Table { get { object data = AppDomain.CurrentDomain.GetData("sansara.Deeds.v1"); if (data == null) { Dictionary<string, Action<string, string, int, string, long[], string[]>> dictionary = new Dictionary<string, Action<string, string, int, string, long[], string[]>>(StringComparer.Ordinal); AppDomain.CurrentDomain.SetData("sansara.Deeds.v1", dictionary); return dictionary; } return (data as Dictionary<string, Action<string, string, int, string, long[], string[]>>) ?? throw new InvalidOperationException("данные sansara.Deeds.v1 другого формата"); } } public static bool Subscribe(string owner, Action<string, string, int, string, long[], string[]> handler) { try { Table[owner] = handler ?? throw new ArgumentNullException("handler"); return true; } catch (Exception) { return false; } } public static bool Unsubscribe(string owner) { try { Table.Remove(owner); return true; } catch (Exception) { return false; } } public static int Publish(string kind, string id, int tier, string result, long[] playerIds, string[] playerNames, Action<string> log) { if (playerIds == null || playerNames == null || playerIds.Length == 0 || playerIds.Length != playerNames.Length) { return 0; } KeyValuePair<string, Action<string, string, int, string, long[], string[]>>[] array; try { array = Table.ToArray(); } catch (Exception ex) { log?.Invoke("Шина подвигов: " + ex.Message); return 0; } KeyValuePair<string, Action<string, string, int, string, long[], string[]>>[] array2 = array; for (int i = 0; i < array2.Length; i++) { KeyValuePair<string, Action<string, string, int, string, long[], string[]>> keyValuePair = array2[i]; try { keyValuePair.Value(kind, id, tier, result, playerIds, playerNames); } catch (Exception arg) { log?.Invoke($"Шина подвигов: подписчик {keyValuePair.Key}: {arg}"); } } return array.Length; } } } 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 SansaraPath { internal static class ClientSide { private sealed class Bill { public int Id; public string Token = ""; public int Coins; public long Paid; } private sealed class Confirmation { public float At; public bool Payout; public string Arg; } private sealed class PendingReply { public string Stamp; public Grade Grade; public Affix Affix; public float Value; public Affix Affix2; public float Value2; public string Name = ""; } internal const string BillsKey = "sansara.Path.bills"; private const float ConfirmDelaySeconds = 0.5f; private static float _payoutNoRoomAt = -1000f; private static bool _billsResent; private static readonly List<Confirmation> Confirmations = new List<Confirmation>(); private static Player _runesPlayer; private static ProfileView _runesProfile; private static string _runesWanted = ""; private static string _runesApplied = ""; internal static readonly Dictionary<long, string> TitlesById = new Dictionary<long, string>(); internal static readonly Dictionary<string, string> TitlesBySender = new Dictionary<string, string>(StringComparer.Ordinal); private static float _noRoomMessageAt = -1000f; private static readonly HashSet<string> SealWarned = new HashSet<string>(StringComparer.Ordinal); private static ZRpc _server; private static readonly HashSet<string> WornSent = new HashSet<string>(StringComparer.Ordinal); private static readonly MethodInfo InventoryChanged = AccessTools.Method(typeof(Inventory), "Changed", (Type[])null, (Type[])null); internal static ProfileView Profile; private const float ProfileRetrySeconds = 5f; private static float _profileRequestedAt = -1000f; private static readonly Dictionary<string, string> SentSeals = new Dictionary<string, string>(StringComparer.Ordinal); internal static readonly HashSet<string> RejectedSeals = new HashSet<string>(StringComparer.Ordinal); private const int PendingCap = 50; private static readonly Dictionary<string, PendingReply> Pending = new Dictionary<string, PendingReply>(StringComparer.Ordinal); private static string _forgeBonusText; private static int[] _forgeBonusByRank = PathSettings.DefaultForgeBonusByRank; internal static bool Connected { get { if (_server != null && (Object)(object)ZNet.instance != (Object)null && !ZNet.instance.IsServer()) { return ZNet.instance.GetServerPeer()?.m_rpc == _server; } return false; } } internal static void Notify(string text) { MessageHud instance = MessageHud.instance; if (instance != null) { instance.ShowMessage((MessageType)1, text, 0, (Sprite)null, false, true); } } private static string CoinName() { GameObject val = (((Object)(object)ObjectDB.instance != (Object)null) ? ObjectDB.instance.GetItemPrefab("Coins") : null); ItemDrop val2 = (((Object)(object)val != (Object)null) ? val.GetComponent<ItemDrop>() : null); if (!((Object)(object)val2 != (Object)null)) { return null; } return val2.m_itemData.m_shared.m_name; } internal static int CoinsInBag() { Inventory val = (((Object)(object)Player.m_localPlayer != (Object)null) ? ((Humanoid)Player.m_localPlayer).GetInventory() : null); string text = CoinName(); if (val != null && text != null) { return val.CountItems(text, -1, true); } return 0; } internal static bool HasBill(int id) { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || !localPlayer.m_customData.TryGetValue("sansara.Path.bills", out var value) || string.IsNullOrEmpty(value)) { return false; } for (int i = 0; i < value.Length; i++) { int num = 0; bool flag = false; for (; i < value.Length && value[i] >= '0' && value[i] <= '9'; i++) { num = num * 10 + (value[i] - 48); flag = true; } if (flag && num == id && i < value.Length && value[i] == ':') { return true; } for (; i < value.Length && value[i] != ','; i++) { } } return false; } private static List<Bill> ReadBills(Player player) { List<Bill> list = new List<Bill>(); if (!player.m_customData.TryGetValue("sansara.Path.bills", out var value) || string.IsNullOrEmpty(value)) { return list; } string[] array = value.Split(new char[1] { ',' }); foreach (string text in array) { string[] array2 = text.Split(new char[1] { ':' }); if (array2.Length == 4 && int.TryParse(array2[0], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) && result > 0 && array2[1] != "" && int.TryParse(array2[2], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result2) && long.TryParse(array2[3], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result3)) { list.Add(new Bill { Id = result, Token = array2[1], Coins = result2, Paid = result3 }); } else if (text != "") { Plugin.Log.LogWarning((object)("Путь: непонятная запись счёта «" + text + "» — пропущена")); } } return list; } private static void WriteBills(Player player, List<Bill> bills) { if (bills.Count == 0) { player.m_customData.Remove("sansara.Path.bills"); return; } StringBuilder stringBuilder = new StringBuilder(); foreach (Bill bill in bills) { if (stringBuilder.Length > 0) { stringBuilder.Append(','); } stringBuilder.Append(bill.Id.ToString(CultureInfo.InvariantCulture)).Append(':').Append(bill.Token) .Append(':') .Append(bill.Coins.ToString(CultureInfo.InvariantCulture)) .Append(':') .Append(bill.Paid.ToString(CultureInfo.InvariantCulture)); } player.m_customData["sansara.Path.bills"] = stringBuilder.ToString(); } private static void TickBills() { if (_billsResent || !Connected) { return; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return; } _billsResent = true; if (!localPlayer.m_customData.ContainsKey("sansara.Path.bills")) { return; } List<Bill> list = ReadBills(localPlayer); long now = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); int count = list.Count; list.RemoveAll((Bill b) => now - b.Paid > 2678400); if (list.Count != count) { Plugin.Log.LogWarning((object)$"Путь: забыто старых счетов без ответа сервера: {count - list.Count}"); } WriteBills(localPlayer, list); foreach (Bill item in list) { QueuePaid(item.Id, item.Token); Plugin.Log.LogInfo((object)$"Путь: повтор оплаты заказа №{item.Id} ({item.Coins} монет)"); } } private static void QueuePaid(int id, string token) { Confirmations.Add(new Confirmation { At = Time.realtimeSinceStartup + 0.5f, Arg = MasterOrders.PaidArg(id, token) }); } private static void QueuePayoutTaken(string uid) { Confirmations.Add(new Confirmation { At = Time.realtimeSinceStartup + 0.5f, Payout = true, Arg = uid }); } private static void TickConfirmations() { if (Confirmations.Count == 0) { return; } if (!Connected) { Confirmations.Clear(); return; } float realtimeSinceStartup = Time.realtimeSinceStartup; while (Confirmations.Count > 0 && Confirmations[0].At <= realtimeSinceStartup) { Confirmation confirmation = Confirmations[0]; Confirmations.RemoveAt(0); if (confirmation.Payout) { _server.Invoke("SansaraPath_PayoutTaken", new object[1] { confirmation.Arg }); } else { Request("order-paid", confirmation.Arg); } } } private static void OnOrderBill(ZRpc rpc, int id, string token, int coins) { try { string arg = id.ToString(CultureInfo.InvariantCulture); Player localPlayer = Player.m_localPlayer; Inventory val = (((Object)(object)localPlayer != (Object)null) ? ((Humanoid)localPlayer).GetInventory() : null); string text = CoinName(); if (val == null || text == null || coins <= 0 || id <= 0 || string.IsNullOrEmpty(token) || token.IndexOf(':') >= 0 || token.IndexOf(',') >= 0) { Request("order-cancel", arg); return; } List<Bill> list = null; if (HasBill(id)) { list = ReadBills(localPlayer); if (list.Exists((Bill b) => b.Id == id && b.Token == token)) { QueuePaid(id, token); return; } int num = list.RemoveAll((Bill b) => b.Id == id); if (num > 0) { Plugin.Log.LogWarning((object)$"Путь: старый счёт заказа №{id} с другой меткой забыт ({num}): сервер его не примет"); WriteBills(localPlayer, list); } } int num2 = val.CountItems(text, -1, true); if (num2 < coins) { Notify($"Не хватает монет на заказ №{id}: нужно {coins}, в сумке {num2}."); Request("order-cancel", arg); return; } val.RemoveItem(text, coins, -1, true); if (list == null) { list = ReadBills(localPlayer); } list.Add(new Bill { Id = id, Token = token, Coins = coins, Paid = DateTimeOffset.UtcNow.ToUnixTimeSeconds() }); WriteBills(localPlayer, list); QueuePaid(id, token); Plugin.Log.LogInfo((object)$"Путь: залог {coins} монет за заказ №{id}"); } catch (Exception ex) { Plugin.Log.LogError((object)("Путь: оплата заказа: " + ex)); } } private static void OnOrderPaidSeen(ZRpc rpc, int id, string token) { try { Player localPlayer = Player.m_localPlayer; if (!((Object)(object)localPlayer == (Object)null) && HasBill(id)) { List<Bill> list = ReadBills(localPlayer); if (list.RemoveAll((Bill b) => b.Id == id && b.Token == (token ?? "")) > 0) { WriteBills(localPlayer, list); } } } catch (Exception ex) { Plugin.Log.LogError((object)("Путь: ответ на оплату заказа: " + ex)); } } private static void OnPayout(ZRpc rpc, ZPackage pkg) { try { string text = pkg.ReadString(); int num = pkg.ReadInt(); int num2 = pkg.ReadInt(); bool refund = pkg.ReadBool(); Player localPlayer = Player.m_localPlayer; Inventory val = (((Object)(object)localPlayer != (Object)null) ? ((Humanoid)localPlayer).GetInventory() : null); GameObject val2 = (((Object)(object)ObjectDB.instance != (Object)null) ? ObjectDB.instance.GetItemPrefab("Coins") : null); if (val == null || (Object)(object)val2 == (Object)null || string.IsNullOrEmpty(text) || num <= 0) { return; } localPlayer.m_customData.TryGetValue("sansara.Path.payouts", out var value); if (MasterOrders.HasReceived(value, text)) { QueuePayoutTaken(text); } else if (!val.CanAddItem(val2, num)) { if (Time.realtimeSinceStartup - _payoutNoRoomAt > 60f) { _payoutNoRoomAt = Time.realtimeSinceStartup; Notify(PathTexts.PayoutNoRoom(num2, num)); } } else { val.AddItem("Coins", num, 1, 0, 0L, "", false, false); localPlayer.m_customData["sansara.Path.payouts"] = MasterOrders.AddReceived(value, text, 100); Notify(PathTexts.PayoutReceived(num2, num, refund)); QueuePayoutTaken(text); Plugin.Log.LogInfo((object)$"Путь: выплата {text}: +{num} монет за заказ №{num2}"); Request("profile", ""); } } catch (Exception ex) { Plugin.Log.LogError((object)("Путь: выплата: " + ex)); } } private static void TickRunes() { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || (Object)(object)RunesEffect.Template == (Object)null) { _runesPlayer = null; return; } if (_runesProfile != Profile) { _runesProfile = Profile; _runesWanted = RunesEffect.KeyOf(Profile); } string text = (Connected ? _runesWanted : ""); SEMan sEMan = ((Character)localPlayer).GetSEMan(); bool flag = sEMan.HaveStatusEffect(RunesEffect.Hash); if (localPlayer != _runesPlayer || !(text == _runesApplied) || flag != (text != "")) { _runesPlayer = localPlayer; _runesApplied = text; RunesEffect.Apply(sEMan, (text == "") ? null : Profile); if (text != "") { Plugin.Log.LogInfo((object)("Путь: руны судьбы: " + text)); } } } private static void OnTitles(ZRpc rpc, string text) { try { TitlesById.Clear(); TitlesBySender.Clear(); string[] array = (text ?? "").Split(new char[1] { '\n' }); for (int i = 0; i < array.Length; i++) { string[] array2 = array[i].TrimEnd(new char[1] { '\r' }).Split(new char[1] { '\t' }); if (array2.Length >= 4 && !(array2[3] == "")) { if (long.TryParse(array2[0], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) && result != 0L) { TitlesById[result] = array2[3]; } if (array2[1] != "" && array2[2] != "") { TitlesBySender[SansaraPath.Core.Profile.KeyOf(array2[1], array2[2])] = array2[3]; } } } } catch (Exception ex) { Plugin.Log.LogError((object)("Путь: титулы: " + ex)); } } internal unsafe static string TitleOfSender(PlatformUserID user, string name) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) if (TitlesBySender.Count == 0 || string.IsNullOrEmpty(name)) { return null; } if (!string.IsNullOrEmpty(user.m_userID) && TitlesBySender.TryGetValue(SansaraPath.Core.Profile.KeyOf(user.m_userID, name), out var value)) { return value; } if (!((PlatformUserID)(ref user)).IsValid || !TitlesBySender.TryGetValue(SansaraPath.Core.Profile.KeyOf(((object)(*(PlatformUserID*)(&user))/*cast due to .constrained prefix*/).ToString(), name), out value)) { return null; } return value; } private static void OnSeal(ZRpc rpc, ZPackage pkg) { try { string uid = pkg.ReadString(); int tier = pkg.ReadInt(); string text = pkg.ReadString(); Inventory val = (((Object)(object)Player.m_localPlayer != (Object)null) ? ((Humanoid)Player.m_localPlayer).GetInventory() : null); if (val == null) { return; } if (val.GetAllItems().Any((ItemData i) => ItemKeys.GetSeal(i.m_customData) == uid)) { _server.Invoke("SansaraPath_SealTaken", new object[1] { uid }); return; } GameObject val2 = (((Object)(object)ObjectDB.instance != (Object)null) ? ObjectDB.instance.GetItemPrefab(text) : null); ItemDrop val3 = (((Object)(object)val2 != (Object)null) ? val2.GetComponent<ItemDrop>() : null); if ((Object)(object)val3 == (Object)null || val3.m_itemData.m_shared.m_maxStackSize != 1) { if (SealWarned.Add(text)) { Plugin.Log.LogWarning((object)("Путь: SealPrefab «" + text + "» не найден или стакается — печать не создана")); } return; } ItemData val4 = val.AddItem(text, 1, 1, 0, 0L, "", false, false); if (val4 == null) { if (Time.realtimeSinceStartup - _noRoomMessageAt > 60f) { _noRoomMessageAt = Time.realtimeSinceStartup; MessageHud instance = MessageHud.instance; if (instance != null) { instance.ShowMessage((MessageType)1, PathTexts.SealNoRoom(tier), 0, (Sprite)null, false, true); } } } else { val4.m_customData["sansara.Path.seal"] = uid; val4.m_customData["sansara.Path.sealTier"] = tier.ToString(CultureInfo.InvariantCulture); MarkChanged(val); _server.Invoke("SansaraPath_SealTaken", new object[1] { uid }); } } catch (Exception ex) { Plugin.Log.LogError((object)("Путь: печать: " + ex)); } } internal static string SealFor(int tier) { Inventory obj = (((Object)(object)Player.m_localPlayer != (Object)null) ? ((Humanoid)Player.m_localPlayer).GetInventory() : null); ItemData val = ((obj != null) ? ((IEnumerable<ItemData>)obj.GetAllItems()).FirstOrDefault((Func<ItemData, bool>)delegate(ItemData i) { string seal = ItemKeys.GetSeal(i.m_customData); return seal != null && !RejectedSeals.Contains(seal) && ItemKeys.GetSealTier(i.m_customData) == tier; }) : null); if (val == null) { return null; } return ItemKeys.GetSeal(val.m_customData); } internal static bool HasTrophy(string prefab) { Inventory val = (((Object)(object)Player.m_localPlayer != (Object)null) ? ((Humanoid)Player.m_localPlayer).GetInventory() : null); if (!string.IsNullOrEmpty(prefab) && val != null) { return val.GetAllItems().Any((ItemData i) => (Object)(object)i.m_dropPrefab != (Object)null && ((Object)i.m_dropPrefab).name == prefab); } return false; } private static void RemoveTrophy(string prefab) { if (!string.IsNullOrEmpty(prefab)) { Inventory val = (((Object)(object)Player.m_localPlayer != (Object)null) ? ((Humanoid)Player.m_localPlayer).GetInventory() : null); ItemData val2 = ((val != null) ? ((IEnumerable<ItemData>)val.GetAllItems()).FirstOrDefault((Func<ItemData, bool>)((ItemData i) => (Object)(object)i.m_dropPrefab != (Object)null && ((Object)i.m_dropPrefab).name == prefab)) : null); if (val2 != null) { val.RemoveItem(val2, 1); return; } ManualLogSource log = Plugin.Log; string text = prefab; Player localPlayer = Player.m_localPlayer; log.LogWarning((object)("Путь: трофей " + text + " для Шедевра не найден в инвентаре игрока " + ((localPlayer != null) ? localPlayer.GetPlayerName() : null))); } } private static void RemoveSeal(string uid) { if (!string.IsNullOrEmpty(uid)) { Inventory val = (((Object)(object)Player.m_localPlayer != (Object)null) ? ((Humanoid)Player.m_localPlayer).GetInventory() : null); ItemData val2 = ((val != null) ? ((IEnumerable<ItemData>)val.GetAllItems()).FirstOrDefault((Func<ItemData, bool>)((ItemData i) => ItemKeys.GetSeal(i.m_customData) == uid)) : null); if (val2 != null) { val.RemoveItem(val2); } } } public static void Register(ZNetPeer serverPeer) { _server = serverPeer.m_rpc; WornSent.Clear(); Pending.Clear(); SentSeals.Clear(); RejectedSeals.Clear(); Profile = null; _profileRequestedAt = -1000f; _billsResent = false; Confirmations.Clear(); TitlesById.Clear(); TitlesBySender.Clear(); _server.Register<ZPackage>("SansaraPath_Crafted", (Action<ZRpc, ZPackage>)OnCrafted); _server.Register<string>("SansaraPath_Profile", (Action<ZRpc, string>)OnProfile); _server.Register<string>("SansaraPath_Message", (Action<ZRpc, string>)OnMessage); _server.Register<ZPackage>("SansaraPath_Seal", (Action<ZRpc, ZPackage>)OnSeal); _server.Register<string>("SansaraPath_Titles", (Action<ZRpc, string>)OnTitles); _server.Register<int, string, int>("SansaraPath_OrderBill", (Action<ZRpc, int, string, int>)OnOrderBill); _server.Register<int, string>("SansaraPath_OrderPaidSeen", (Action<ZRpc, int, string>)OnOrderPaidSeen); _server.Register<ZPackage>("SansaraPath_Payout", (Action<ZRpc, ZPackage>)OnPayout); } public static void SendCraft(string uid, string prefab, string sealUid, string masterpiece) { if (!Connected) { return; } if (!string.IsNullOrEmpty(sealUid) && !string.IsNullOrEmpty(uid)) { if (SentSeals.Count >= 50) { SentSeals.Clear(); } SentSeals[uid] = sealUid; } _server.Invoke("SansaraPath_Craft", new object[4] { uid, prefab, sealUid ?? "", masterpiece ?? "" }); } public static void SendUpgrade(string prefab, int levels) { if (Connected) { _server.Invoke("SansaraPath_Upgrade", new object[2] { prefab, levels }); } } public static void SendStack(string prefab, int batches, int tier) { if (Connected) { _server.Invoke("SansaraPath_Stack", new object[3] { prefab, batches, tier }); } } public static void SendWorn(string uid) { if (Connected && WornSent.Add(uid)) { _server.Invoke("SansaraPath_Worn", new object[1] { uid }); } } public static void Request(string action, string arg) { if (Connected) { _server.Invoke("SansaraPath_Request", new object[2] { action, arg ?? "" }); } } public static void Tick() { TickRunes(); TickConfirmations(); TickBills(); if (Profile == null && !((Object)(object)Player.m_localPlayer == (Object)null) && Connected && !(Time.realtimeSinceStartup - _profileRequestedAt < 5f)) { _profileRequestedAt = Time.realtimeSinceStartup; Request("profile", "auto"); } } internal static float ForgeBonus(string prefab) { ProfileView profile = Profile; if (profile == null || !Connected || !PluginConfig.CraftEnabled(profile.Craft) || !ItemInfo.TryPrefab(prefab, out var data)) { return 0f; } return Masterpieces.ForgeBonus(profile.Craft, profile.Rank, ItemInfo.KindOf(data), ForgeBonusByRank()); } private static int[] ForgeBonusByRank() { string text = PluginConfig.ForgeBonusByRank.Value ?? ""; if (text != _forgeBonusText) { int[] array = PathSettings.ParseInts(text, PathSettings.DefaultForgeBonusByRank, "ForgeBonusByRank", new List<string>()); if (array.Length != PathSettings.DefaultForgeBonusByRank.Length || array.Any((int b) => b < 0 || b > 50)) { array = PathSettings.DefaultForgeBonusByRank; } _forgeBonusByRank = array; _forgeBonusText = text; } return _forgeBonusByRank; } public static void MarkChanged(Inventory inventory) { if (inventory != null) { InventoryChanged?.Invoke(inventory, null); } } private static void OnCrafted(ZRpc rpc, ZPackage pkg) { try { string uid = pkg.ReadString(); string stamp = pkg.ReadString(); Grade grade = Grades.Parse(pkg.ReadString()); Affix affix = Affixes.Parse(pkg.ReadString()); float.TryParse(pkg.ReadString(), NumberStyles.Float, CultureInfo.InvariantCulture, out var result); string text = pkg.ReadString(); Affix affix2 = Affixes.Parse(pkg.ReadString()); float.TryParse(pkg.ReadString(), NumberStyles.Float, CultureInfo.InvariantCulture, out var result2); string name = pkg.ReadString(); string prefab = pkg.ReadString(); if (grade == Grade.Masterpiece && Profile != null) { Profile.MasterpieceReady = ""; } if (SentSeals.TryGetValue(uid, out var value)) { SentSeals.Remove(uid); if (string.IsNullOrEmpty(text)) { RejectedSeals.Add(value); } } Inventory val = (((Object)(object)Player.m_localPlayer != (Object)null) ? ((Humanoid)Player.m_localPlayer).GetInventory() : null); ItemData val2 = ((val != null) ? ((IEnumerable<ItemData>)val.GetAllItems()).FirstOrDefault((Func<ItemData, bool>)((ItemData i) => ItemKeys.GetUid(i.m_customData) == uid)) : null); if (val2 == null) { if (Pending.Count >= 50) { Pending.Remove(Pending.Keys.First()); } Pending[uid] = new PendingReply { Stamp = stamp, Grade = grade, Affix = affix, Value = result, Affix2 = affix2, Value2 = result2, Name = name }; Plugin.Log.LogWarning((object)("Путь: изделие " + uid + " не найдено в инвентаре — ответ отложен до первого показа")); RemoveSeal(text); RemoveTrophy(prefab); Request("profile", ""); } else { Pending.Remove(uid); WriteKeys(val2, stamp, grade, affix, result, affix2, result2, name); ItemStats.FillDurability(val2); MarkChanged(val); RemoveSeal(text); RemoveTrophy(prefab); Request("profile", ""); } } catch (Exception ex) { Plugin.Log.LogError((object)("Путь: ответ на создание: " + ex)); } } public static void TryApplyPending(ItemData item) { if (item != null && Pending.Count != 0 && !ItemKeys.HasStamp(item.m_customData)) { string uid = ItemKeys.GetUid(item.m_customData); if (uid != null && Pending.TryGetValue(uid, out var value)) { Pending.Remove(uid); WriteKeys(item, value.Stamp, value.Grade, value.Affix, value.Value, value.Affix2, value.Value2, value.Name); ItemStats.FillDurability(item); Player localPlayer = Player.m_localPlayer; MarkChanged((localPlayer != null) ? ((Humanoid)localPlayer).GetInventory() : null); } } } private static void WriteKeys(ItemData item, string stamp, Grade grade, Affix affix, float value, Affix affix2, float value2, string name) { if (grade == Grade.Masterpiece) { ItemKeys.WriteMasterpiece(item.m_customData, stamp, affix, value, affix2, value2, name); } else { ItemKeys.Write(item.m_customData, stamp, grade, affix, value); } } private static void OnProfile(ZRpc rpc, string text) { Profile = ProfileView.Parse(text); } private static void OnMessage(ZRpc rpc, string text) { MessageHud instance = MessageHud.instance; if (instance != null) { instance.ShowMessage((MessageType)1, text, 0, (Sprite)null, false, true); } } } internal sealed class CraftScope { public ItemData Upgrade; public int OldQuality; public Dictionary<string, string> Tags; public Recipe Recipe; public ItemKind StackKind; public int Batches = 1; public int OthersAmount; } [HarmonyPatch(typeof(InventoryGui), "DoCrafting")] internal static class DoCraftingPatch { internal static bool Active; internal static ItemData Created; internal static string ExpectedName; private static void Prefix(ItemData ___m_craftUpgradeItem, Recipe ___m_craftRecipe, bool ___m_multiCrafting, int ___m_multiCraftAmount, out CraftScope __state) { Active = true; Created = null; ExpectedName = ___m_craftRecipe?.m_item?.m_itemData?.m_shared?.m_name; __state = new CraftScope { Upgrade = ___m_craftUpgradeItem, OldQuality = (___m_craftUpgradeItem?.m_quality ?? 0), Tags = ItemKeys.Snapshot(___m_craftUpgradeItem?.m_customData) }; if (___m_craftUpgradeItem == null && (Object)(object)___m_craftRecipe != (Object)null) { try { StackFlow.Prepare(__state, ___m_craftRecipe, (!___m_multiCrafting) ? 1 : ___m_multiCraftAmount); } catch (Exception ex) { __state.StackKind = ItemKind.None; Plugin.Log.LogError((object)("Путь: подготовка стопки: " + ex)); } } } private static void Postfix(CraftScope __state) { ItemData created = Created; if (created == null || __state == null) { return; } try { CraftFlow.OnCreated(created, __state); } catch (Exception ex) { Plugin.Log.LogError((object)("Путь: создание: " + ex)); } } private static void Finalizer() { Active = false; Created = null; ExpectedName = null; } } [HarmonyPatch(typeof(Inventory), "AddItem", new Type[] { typeof(string), typeof(int), typeof(int), typeof(int), typeof(long), typeof(string), typeof(Vector2i), typeof(bool), typeof(bool), typeof(bool) })] internal static class AddItemCapturePatch { private static void Postfix(ItemData __result) { if (DoCraftingPatch.Active && DoCraftingPatch.Created == null && __result != null && DoCraftingPatch.ExpectedName != null && __result.m_shared.m_name == DoCraftingPatch.ExpectedName) { DoCraftingPatch.Created = __result; } } } internal static class CraftFlow { public static void OnCreated(ItemData created, CraftScope scope) { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || (Object)(object)created.m_dropPrefab == (Object)null || ItemKeys.IsEventReward(created.m_customData) || (scope.Upgrade != null && ItemKeys.IsEventReward(scope.Upgrade.m_customData))) { return; } if (scope.Upgrade == null && scope.StackKind != ItemKind.None) { StackFlow.OnCrafted(localPlayer, scope, created); } else { if (ItemInfo.KindOf(created) == ItemKind.None) { return; } bool flag = PluginConfig.Enabled.Value && ClientSide.Connected; string name = ((Object)created.m_dropPrefab).name; if (scope.Upgrade == null) { if (flag) { string text = Guid.NewGuid().ToString("N"); created.m_customData["sansara.Path.uid"] = text; int tier = ItemInfo.TierOf(created); ProfileView profile = ClientSide.Profile; bool flag2 = profile != null && profile.MasterpieceReady != "" && ClientSide.HasTrophy(Masterpieces.TrophyFor(tier)); string sealUid = (flag2 ? null : ClientSide.SealFor(tier)); ClientSide.SendCraft(text, name, sealUid, flag2 ? "1" : ""); } } else if (!(created.m_shared.m_name != scope.Upgrade.m_shared.m_name)) { if (scope.Tags.Count > 0) { ItemKeys.CopyTo(scope.Tags, created.m_customData); ItemStats.FillDurability(created); ClientSide.MarkChanged(((Humanoid)localPlayer).GetInventory()); } int num = created.m_quality - scope.OldQuality; if (flag && num > 0) { ClientSide.SendUpgrade(name, num); } } } } } [HarmonyPatch(typeof(Humanoid), "EquipItem")] internal static class EquipPatch { private static void Postfix(Humanoid __instance, ItemData item, bool __result) { if (__result && item != null && (object)__instance == Player.m_localPlayer) { ClientSide.TryApplyPending(item); string uid = ItemKeys.GetUid(item.m_customData); if (uid != null && ItemKeys.HasStamp(item.m_customData)) { ClientSide.SendWorn(uid); } } } } [HarmonyPatch(typeof(PlayerController), "TakeInput")] internal static class ControllerInputPatch { private static void Postfix(ref bool __result) { if (PathWindow.BlocksGame) { __result = false; } } } [HarmonyPatch(typeof(Player), "TakeInput")] internal static class PlayerInputPatch { private static void Postfix(ref bool __result) { if (PathWindow.BlocksGame) { __result = false; } } } [HarmonyPatch(typeof(Menu), "Update")] internal static class MenuPatch { private static bool Prefix() { return !PathWindow.BlocksGame; } } internal static class PathBindGuard { private static readonly FieldInfo Binds = AccessTools.Field(typeof(Terminal), "m_binds"); private static object _saved; internal static void PatchServerDevcommands(Harmony harmony) { //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Expected O, but got Unknown Type type = AccessTools.TypeByName("ServerDevcommands.BindManager"); MethodInfo methodInfo = ((type != null) ? AccessTools.Method(type, "GetBestKeyCommands", (Type[])null, (Type[])null) : null); if (!(methodInfo == null)) { harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(PathBindGuard), "SdcPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.Log.LogInfo((object)"Привязки клавиш Server Devcommands приглушаются, пока вы печатаете в окне «Путь»."); } } private static bool SdcPrefix(ref IList __result, MethodBase __originalMethod) { if (!PathWindow.TextFocused) { return true; } __result = (IList)Activator.CreateInstance(((MethodInfo)__originalMethod).ReturnType); return false; } internal static void Suspend() { if (PathWindow.TextFocused && !(Binds == null) && _saved == null) { object value = Binds.GetValue(null); if (!(value is ICollection { Count: 0 })) { _saved = value; Binds.SetValue(null, Activator.CreateInstance(Binds.FieldType)); } } } internal static void Resume() { if (_saved != null) { Binds.SetValue(null, _saved); _saved = null; } } } [HarmonyPatch(typeof(Chat), "Update")] [HarmonyAfter(new string[] { "sansara.Admin" })] internal static class ChatBindsPatch { private static void Prefix() { PathBindGuard.Suspend(); } private static void Finalizer() { PathBindGuard.Resume(); } } internal static class ItemInfo { public static ItemKind KindOf(ItemData item) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Expected I4, but got Unknown if (item != null) { return ItemClass.Classify((int)item.m_shared.m_itemType, ((object)Unsafe.As<SkillType, SkillType>(ref item.m_shared.m_skillType)/*cast due to .constrained prefix*/).ToString(), item.m_shared.m_maxStackSize, ItemKeys.IsEventReward(item.m_customData)); } return ItemKind.None; } public static bool TryPrefab(string prefab, out ItemData data) { data = null; if (string.IsNullOrEmpty(prefab) || prefab.Length > 128 || (Object)(object)ObjectDB.instance == (Object)null) { return false; } GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(prefab); ItemDrop val = (((Object)(object)itemPrefab != (Object)null) ? itemPrefab.GetComponent<ItemDrop>() : null); data = (((Object)(object)val != (Object)null) ? val.m_itemData : null); return data != null; } public static bool Orderable(string prefab, out ItemData data) { if (!TryPrefab(prefab, out data) || KindOf(data) == ItemKind.None) { return false; } Recipe recipe = ObjectDB.instance.GetRecipe(data); if ((Object)(object)recipe != (Object)null) { return recipe.m_enabled; } return false; } public static int TierOf(ItemData data) { return TierOfRecipe(((Object)(object)ObjectDB.instance != (Object)null) ? ObjectDB.instance.GetRecipe(data) : null); } public static int TierOfRecipe(Recipe recipe) { if ((Object)(object)recipe == (Object)null || recipe.m_resources == null) { return 1; } return Tiers.Of(from r in recipe.m_resources where r != null && (Object)(object)r.m_resItem != (Object)null select ((Object)((Component)r.m_resItem).gameObject).name); } public static string StationOf(Recipe recipe) { CraftingStation val = (((Object)(object)recipe != (Object)null) ? recipe.m_craftingStation : null); if ((Object)(object)val == (Object)null) { return ""; } if (string.Equals(val.m_name, "$piece_cauldron", StringComparison.OrdinalIgnoreCase)) { return "piece_cauldron"; } if (string.Equals(val.m_name, "$piece_meadcauldron", StringComparison.OrdinalIgnoreCase)) { return "piece_MeadCauldron"; } return ((Object)((Component)val).gameObject).name; } public static ItemKind StackKindOf(Recipe recipe) { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Expected I4, but got Unknown if ((Object)(object)recipe == (Object)null || (Object)(object)recipe.m_item == (Object)null || recipe.m_requireOnlyOneIngredient) { return ItemKind.None; } SharedData shared = recipe.m_item.m_itemData.m_shared; return ItemClass.StackKind((int)shared.m_itemType, ((object)Unsafe.As<SkillType, SkillType>(ref shared.m_skillType)/*cast due to .constrained prefix*/).ToString(), shared.m_maxStackSize, eventReward: false, StationOf(recipe)); } public static bool TryStackRecipe(string prefab, out ItemKind kind, out int tier) { kind = ItemKind.None; tier = 1; if (string.IsNullOrEmpty(prefab) || prefab.Length > 128 || (Object)(object)ObjectDB.instance == (Object)null) { return false; } foreach (Recipe recipe in ObjectDB.instance.m_recipes) { if ((Object)(object)recipe == (Object)null || !recipe.m_enabled || (Object)(object)recipe.m_item == (Object)null || ((Object)((Component)recipe.m_item).gameObject).name != prefab) { continue; } ItemKind itemKind = StackKindOf(recipe); if (itemKind != ItemKind.None) { int num = TierOfRecipe(recipe); if (kind == ItemKind.None || num > tier) { kind = itemKind; tier = num; } } } return kind != ItemKind.None; } } internal static class ItemStats { public static float Percent(ItemData item, Affix wanted) { Dictionary<string, string> dictionary = item?.m_customData; if (dictionary == null || dictionary.Count == 0 || !PluginConfig.Enabled.Value) { return 0f; } float value = PluginConfig.MaxAffixPercent.Value; float num = 0f; if (ItemKeys.TryAffix(dictionary, out var affix, out var value2) && affix == wanted) { num += Affixes.Clamp(value2, value); } if (ItemKeys.TryAffix2(dictionary, out var affix2, out var value3) && affix2 == wanted) { num += Affixes.Clamp(value3, value); } if (!(num > 0f) || !ItemKeys.IsEventReward(dictionary)) { return num; } return 0f; } public static void FillDurability(ItemData item) { if (item != null && item.m_shared.m_useDurability) { item.m_durability = item.GetMaxDurability(); } } } internal static class Net { public const string Craft = "SansaraPath_Craft"; public const string Upgrade = "SansaraPath_Upgrade"; public const string Worn = "SansaraPath_Worn"; public const string Request = "SansaraPath_Request"; public const string SealTaken = "SansaraPath_SealTaken"; public const string PayoutTaken = "SansaraPath_PayoutTaken"; public const string Stack = "SansaraPath_Stack"; public const string Crafted = "SansaraPath_Crafted"; public const string Profile = "SansaraPath_Profile"; public const string Message = "SansaraPath_Message"; public const string Seal = "SansaraPath_Seal"; public const string Titles = "SansaraPath_Titles"; public const string OrderBill = "SansaraPath_OrderBill"; public const string OrderPaidSeen = "SansaraPath_OrderPaidSeen"; public const string Payout = "SansaraPath_Payout"; } [HarmonyPatch(typeof(ZNet), "OnNewConnection")] internal static class NewConnectionPatch { private static void Postfix(ZNet __instance, ZNetPeer peer) { try { if (peer?.m_rpc == null) { return; } if (__instance.IsServer()) { if (ServerSide.Active) { ServerSide.Register(peer); } } else { ClientSide.Register(peer); } } catch (Exception ex) { Plugin.Log.LogError((object)("Путь: OnNewConnection: " + ex)); } } } internal static class OrderCatalog { internal sealed class Entry { public string Prefab = ""; public string Name = ""; public string Lower = ""; public int Tier; public Craft Craft; public Sprite Icon; } private static ObjectDB _db; private static readonly List<Entry> Items = new List<Entry>(); private static readonly Dictionary<string, Entry> ByPrefab = new Dictionary<string, Entry>(StringComparer.Ordinal); internal static List<Entry> All { get { if (_db != ObjectDB.instance || Items.Count == 0) { Build(); } return Items; } } internal static Entry Find(string prefab) { _ = All; if (string.IsNullOrEmpty(prefab) || !ByPrefab.TryGetValue(prefab, out var value)) { return null; } return value; } internal static string NameOf(string prefab) { return Find(prefab)?.Name ?? prefab; } private static void Build() { _db = ObjectDB.instance; Items.Clear(); ByPrefab.Clear(); if ((Object)(object)_db == (Object)null) { return; } foreach (GameObject item in _db.m_items) { if (!((Object)(object)item == (Object)null) && !ByPrefab.ContainsKey(((Object)item).name) && ItemInfo.Orderable(((Object)item).name, out var data)) { string text = ((Localization.instance != null) ? Localization.instance.Localize(data.m_shared.m_name) : data.m_shared.m_name); Sprite[] icons = data.m_shared.m_icons; Entry entry = new Entry { Prefab = ((Object)item).name, Name = text, Lower = text.ToLowerInvariant(), Tier = ItemInfo.TierOf(data), Craft = Crafts.Of(ItemInfo.KindOf(data)), Icon = ((icons != null && icons.Length != 0) ? icons[0] : null) }; Items.Add(entry); ByPrefab[entry.Prefab] = entry; } } Items.Sort((Entry a, Entry b) => (a.Tier == b.Tier) ? string.Compare(a.Name, b.Name, StringComparison.CurrentCulture) : a.Tier.CompareTo(b.Tier)); } } internal static class PathAdmin { internal static readonly string[] Verbs = new string[18] { "status", "help", "info", "recipes", "xp", "reset", "audit", "saga", "deed", "seal", "seals", "sealcandidates", "masterpiece", "masterpieces", "runes", "orders", "order", "payouts" }; private const string HelpText = "Персонаж — имя или ключ <platform id>/<имя>, как в info.\nstatus — сколько персонажей и изделий\ninfo <персонаж> — ремесло, опыт, главы, Слава, лимиты периода\nrecipes <персонаж> — рецепты Знахаря: сколько, каких уровней, список\nxp <персонаж> <smith|armorer|fletcher|herbalist> <±N> — добавить или снять опыт (главы проверяются сразу)\nreset <персонаж> — снять ремесло и срок смены; опыт, главы, Слава и титул остаются\naudit — надетые изделия с ключами «Пути», которых нет в реестре\nsaga <персонаж> — саги биомов: главы, «пройдена», «полная»\ndeed <персонаж> <hunt|siege|trial|treasure|order|raid> <уровень 1–7> — засчитать главу\nseal <персонаж> <уровень 1–7> — выдать «Печать саги»\nseals — печати: выданные, доставленные, использованные\nsealcandidates — нестакающиеся предметы без действия (выбор SealPrefab)\nmasterpiece <персонаж> reset — снять ожидание и срок Шедевра\nmasterpieces — созданные Шедевры: имя, префаб, создатель, дата\nrunes <персонаж> [reset] — руны судьбы: открытые, активные, срок смены; reset — снять срок смены\norders — незакрытые заказы мастерам: состояние, изделие, плата, заказчик, мастер, сроки\norder <номер> cancel — отменить заказ (и взятый) с возвратом монет заказчику\npayouts — невыданные выплаты: кому, сколько, за какой заказ"; public static string Handle(string line, string admin, string adminId) { AdminCommand adminCommand = AdminCommand.Parse(line); if (!ServerSide.Active) { return "модуль работает только на выделенном сервере"; } try { string brokenReason = ServerSide.BrokenReason; if (brokenReason != null) { return "состояние не прочитано (" + brokenReason + "); модуль остановлен, файл не тронут — исправьте или переименуйте его и перезапустите сервер"; } PathState st = ServerSide.State; PathSettings s = ServerSide.Settings(); switch (adminCommand.Verb) { case "help": return "Персонаж — имя или ключ <platform id>/<имя>, как в info.\nstatus — сколько персонажей и изделий\ninfo <персонаж> — ремесло, опыт, главы, Слава, лимиты периода\nrecipes <персонаж> — рецепты Знахаря: сколько, каких уровней, список\nxp <персонаж> <smith|armorer|fletcher|herbalist> <±N> — добавить или снять опыт (главы проверяются сразу)\nreset <персонаж> — снять ремесло и срок смены; опыт, главы, Слава и титул остаются\naudit — надетые изделия с ключами «Пути», которых нет в реестре\nsaga <персонаж> — саги биомов: главы, «пройдена», «полная»\ndeed <персонаж> <hunt|siege|trial|treasure|order|raid> <уровень 1–7> — засчитать главу\nseal <персонаж> <уровень 1–7> — выдать «Печать саги»\nseals — печати: выданные, доставленные, использованные\nsealcandidates — нестакающиеся предметы без действия (выбор SealPrefab)\nmasterpiece <персонаж> reset — снять ожидание и срок Шедевра\nmasterpieces — созданные Шедевры: имя, префаб, создатель, дата\nrunes <персонаж> [reset] — руны судьбы: открытые, активные, срок смены; reset — снять срок смены\norders — незакрытые заказы мастерам: состояние, изделие, плата, заказчик, мастер, сроки\norder <номер> cancel — отменить заказ (и взятый) с возвратом монет заказчику\npayouts — невыданные выплаты: кому, сколько, за какой заказ"; case "status": return PathTexts.Status(st); case "audit": return PathTexts.Audit(st, s.ResetOffsetHours); case "info": { string error3; Profile profile3 = st.Find(adminCommand.Argument, out error3); return (profile3 == null) ? error3 : PathTexts.Info(st, s, profile3); } case "recipes": { string error9; Profile profile9 = st.Find(adminCommand.Argument, out error9); return (profile9 == null) ? error9 : PathTexts.RecipesInfo(profile9); } case "xp": { if (adminCommand.Args.Count < 3 || !int.TryParse(adminCommand.Args[adminCommand.Args.Count - 1], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result3)) { return "xp <персонаж> <smith|armorer|fletcher|herbalist> <±N>"; } Craft craft = Crafts.Parse(adminCommand.Args[adminCommand.Args.Count - 2]); string error4; Profile profile4 = st.Find(string.Join(" ", adminCommand.Args.Take(adminCommand.Args.Count - 2)), out error4); if (profile4 == null) { return error4; } List<ChapterEvent> events; string result4 = st.AdminXp(s, profile4, craft, result3, out events); ServerSide.Announce(events); Plugin.Log.LogInfo((object)$"Путь: {admin}: xp {profile4.Key} {Crafts.Code(craft)} {result3}"); return result4; } case "reset": { string error6; Profile profile6 = st.Find(adminCommand.Argument, out error6); if (profile6 == null) { return error6; } Plugin.Log.LogInfo((object)("Путь: " + admin + ": reset " + profile6.Key)); return st.AdminReset(profile6); } case "saga": { string error8; Profile profile8 = st.Find(adminCommand.Argument, out error8); if (profile8 == null) { return error8; } StringBuilder stringBuilder = new StringBuilder(profile8.Name + ": Слава " + profile8.Renown); for (int num = 1; num <= 7; num++) { profile8.Sagas.TryGetValue(num, out var value); int mask = value?.Mask ?? 0; string text2 = string.Join(", ", (from i in Enumerable.Range(0, 5) where (mask & (1 << i)) != 0 select i).Select(Sagas.ChapterName)); stringBuilder.Append('\n').Append(Tiers.Biome(num)).Append(": ") .Append((text2 == "") ? "—" : text2) .Append((value != null && value.Awarded == 2) ? " (полная)" : ((value != null && value.Awarded == 1) ? " (пройдена)" : "")); } return stringBuilder.ToString(); } case "deed": { if (adminCommand.Args.Count < 3 || !int.TryParse(adminCommand.Args[adminCommand.Args.Count - 1], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result5)) { return "deed <персонаж> <вид> <уровень>"; } string text = adminCommand.Args[adminCommand.Args.Count - 2].ToLowerInvariant(); string error7; Profile profile7 = st.Find(string.Join(" ", adminCommand.Args.Take(adminCommand.Args.Count - 2)), out error7); if (profile7 == null) { return error7; } DeedOutcome deedOutcome = st.AdminDeed(s, profile7, text, result5, DateTimeOffset.UtcNow.ToUnixTimeSeconds()); ServerSide.DispatchAdmin(deedOutcome); Plugin.Log.LogInfo((object)$"Путь: {admin}: deed {profile7.Key} {text} {result5}"); return deedOutcome.Applied ? $"{profile7.Name}: глава засчитана, Слава {profile7.Renown}" : "глава не засчитана: уже есть или вид/уровень не подходят"; } case "seal": { if (adminCommand.Args.Count < 2 || !int.TryParse(adminCommand.Args[adminCommand.Args.Count - 1], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result2) || result2 < 1 || result2 > 7) { return "seal <персонаж> <уровень 1–7>"; } string error; Profile profile = st.Find(string.Join(" ", adminCommand.Args.Take(adminCommand.Args.Count - 1)), out error); if (profile == null) { return error; } SealRecord sealRecord = st.IssueSeal(profile.Key, result2, DateTimeOffset.UtcNow.ToUnixTimeSeconds()); if (sealRecord == null) { return "уровень вне 1–7"; } Plugin.Log.LogInfo((object)$"Путь: {admin}: seal {profile.Key} {result2} → {sealRecord.Uid}"); return $"{profile.Name}: {Sagas.SealName(result2)} выдана ({sealRecord.Uid}), придёт при входе или в течение {PluginConfig.PlayerScanSeconds.Value:0} с"; } case "seals": { List<string> list = (from x in st.Seals.Values orderby x.Issued select x.Uid + " " + Sagas.SealName(x.Tier) + " · владелец " + x.Owner + " · " + ((x.UsedBy != "") ? ("использована: " + x.UsedBy) : (x.Delivered ? "у игрока" : "ждёт выдачи"))).ToList(); if (list.Count == 0) { list.Add("печатей нет"); } if (st.SealSuspects.Count > 0) { list.Add($"подозрительных печатей/ID: {st.SealSuspects.Count}"); list.AddRange(from x in st.SealSuspects.Values.Take(10) select " " + x.Uid + " · " + x.Key + " · " + x.Name); } return string.Join("\n", list); } case "sealcandidates": return ServerSide.SealCandidates(); case "masterpiece": { if (adminCommand.Args.Count < 2 || !string.Equals(adminCommand.Args[adminCommand.Args.Count - 1], "reset", StringComparison.OrdinalIgnoreCase)) { return "masterpiece <персонаж> reset"; } string error5; Profile profile5 = st.Find(string.Join(" ", adminCommand.Args.Take(adminCommand.Args.Count - 1)), out error5); if (profile5 == null) { return error5; } Plugin.Log.LogInfo((object)("Путь: " + admin + ": masterpiece reset " + profile5.Key)); return st.AdminMasterpieceReset(profile5); } case "masterpieces": { List<string> list2 = (from x in st.Items.Values where x.Grade == Grade.Masterpiece orderby x.Created select x).Select(delegate(ItemRecord x) { st.Profiles.TryGetValue(x.Crafter, out var value2); string text3 = ((value2 != null && value2.Name != "") ? value2.Name : x.Crafter); return x.MasterpieceName + " · " + x.Prefab + " · " + text3 + " · " + PathTexts.Date(x.Created, s.ResetOffsetHours); }).ToList(); return (list2.Count == 0) ? "Шедевров нет" : string.Join("\n", list2); } case "runes": { bool flag = adminCommand.Args.Count >= 2 && string.Equals(adminCommand.Args[adminCommand.Args.Count - 1], "reset", StringComparison.OrdinalIgnoreCase); string error2; Profile profile2 = st.Find(flag ? string.Join(" ", adminCommand.Args.Take(adminCommand.Args.Count - 1)) : adminCommand.Argument, out error2); if (profile2 == null) { return error2; } if (!flag) { return PathTexts.RunesInfo(s, profile2, DateTimeOffset.UtcNow.ToUnixTimeSeconds()); } Plugin.Log.LogInfo((object)("Путь: " + admin + ": runes reset " + profile2.Key)); return st.AdminRuneReset(profile2); } case "orders": return PathTexts.OrdersInfo(st, s, DateTimeOffset.UtcNow.ToUnixTimeSeconds()); case "order": { if (adminCommand.Args.Count != 2 || !string.Equals(adminCommand.Args[1], "cancel", StringComparison.OrdinalIgnoreCase) || !int.TryParse(adminCommand.Args[0].TrimStart(new char[1] { '№' }), NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { return "order <номер> cancel"; } Plugin.Log.LogInfo((object)$"Путь: {admin}: order {result} cancel"); return st.AdminOrderCancel(s, result, DateTimeOffset.UtcNow.ToUnixTimeSeconds()); } case "payouts": return PathTexts.PayoutsInfo(st, s); default: return AdminProtocol.Unknown(adminCommand.Verb, Verbs); } } catch (Exception ex) { Plugin.Log.LogError((object)$"Путь: {admin}: «{AdminProtocol.ForLog(line)}»: {ex}"); return "ошибка: " + ex.Message; } } } internal static class PathHelp { internal sealed class Section { internal readonly string Title; internal readonly string[] Lines; internal Section(string title, params string[] lines) { Title = title; Lines = lines; } } internal static readonly Section[] Sections = new Section[10] { new Section("Что такое «Путь»", "«Путь» — это ваше ремесло и ваши подвиги на сервере. Всё собрано в этом окне (F8).", "Ничего не запрещено: любую вещь может сделать кто угодно. «Путь» только награждает тех, кто делает своё дело хорошо."), new Section("Ремесло", "Выберите одно ремесло во вкладке «Ремесло»:", "• Кузнец — оружие, инструменты, щиты;", "• Бронник — броня;", "• Оружейник — луки, арбалеты, стрелы и болты;", "• Знахарь — еда и основы медовух из котлов.", "Сменить ремесло можно, но не часто: часть опыта перейдёт в новое ремесло, завершённые главы останутся за вами.", "За вещи своего ремесла вы получаете опыт. Опыт и главы «Пути мастера» поднимают ранг: Подмастерье → Умелец → Мастер → Великий мастер."), new Section("Качество и клеймо", "Чем выше ранг, тем чаще вещь выходит Добротной или Отменной. У такой вещи есть свойство — острота, закалка, лёгкость или крепость — и ваше клеймо в подсказке.", "Когда вашу вещь надевает другой игрок, вам идут Слава и опыт. Делать вещи для других — главный путь к рангу.", "Стрелы, болты, еда и медовухи качества не имеют: мастер иногда получает лишнюю партию."), new Section("Саги", "В каждом биоме своя сага из пяти глав: Охота за головами, испытание через портал, клад, заказ и набег — в этом биоме.", "Главы засчитываются сами, когда вы участвуете в событии. Прогресс — во вкладке «Саги».", "3 главы — титул «Покоритель», все 5 — «Хранитель» и Печать саги.", "Печать отдайте мастеру: его следующая вещь этого биома выйдет Отменной."), new Section("Титул и Слава", "Титул выбирается во вкладке «Путь», его видно над головой и в чате.", "Слава копится за главы, ранги и надетые вещи. Лучшие — на доске в Чертоге славы."), new Section("Руны судьбы", "Небольшие постоянные бонусы: меньше выносливости на прыжок, перекат или плавание, скрытность, сопротивление стихиям и другие.", "Руна биома открывается за его сагу, руна ремесла — на ранге «Мастер». «Хранитель» и «Великий мастер» её усиливают.", "Одновременно действует до пяти рун. Поставить можно в любой момент, снять или заменить — раз в сутки."), new Section("Шедевр", "Великий мастер раз в неделю может создать Шедевр — вещь с вашим именем и двумя свойствами.", "Во вкладке «Ремесло» придумайте имя и нажмите «Подготовить». Держите в сумке трофей босса биома этой вещи и создайте её — трофей уйдёт, о Шедевре узнают все."), new Section("Кузница потенциала", "Мастер, улучшающий вещь своего ремесла, получает прибавку к шансу успеха. Чем выше ранг, тем больше прибавка."), new Section("Заказы мастерам", "Нужна хорошая вещь? Закажите её во вкладке «Заказы»: выберите вещь, качество и плату. Монеты уходят в залог.", "Мастер берёт заказ, делает вещь и передаёт вам любым способом. Как только вы её наденете, мастер получит монеты.", "Пока заказ никто не взял, его можно отменить — монеты вернутся. Не выполненный в срок заказ тоже возвращает монеты.", "Если в сумке нет места, монеты подождут: освободите место."), new Section("Если что-то не так", "Окно не обновилось — закройте и откройте его снова. Остались вопросы — спросите администратора.") }; } internal static class PathWindow { [CompilerGenerated] private static class <>O { public static UnityAction <0>__Close; } private const float Width = 760f; private const float Height = 620f; private const float ViewWidth = 700f; private const float ViewHeight = 400f; private const float LineWidth = 652f; private static readonly string[] TabNames = new string[6] { "Путь", "Ремесло", "Саги", "Руны", "Заказы", "Помощь" }; private static GameObject _panel; private static ScrollRect _scroll; private static RectTransform _content; private static readonly List<Button> TabButtons = new List<Button>(); private static bool _open; private static bool _blocked; private static int _closedFrame = -1; private static int _tab; private static string _confirm = ""; private static ProfileView _shown; private static bool _dirty; private static readonly List<InputField> Inputs = new List<InputField>(); private static readonly List<string> InputIds = new List<string>(); private static InputField _caretInput; private static string _masterpieceName = ""; private static string _orderSearch = ""; private static string _orderPrefab = ""; private static Grade _orderGrade = Grade.Common; private static string _orderCoins = ""; private static string _orderMaster = ""; private const int CatalogShown = 40; private static bool _wasFocused; private static bool _bindsPatched; private const int HelpTab = 5; internal static bool IsOpen => _open; internal static bool TextFocused { get { if (_open) { if (!_wasFocused) { return AnyInputFocused(); } return true; } return false; } } internal static bool BlocksGame { get { if (!_open) { return Time.frameCount == _closedFrame; } return true; } } private static bool AnyInputFocused() { for (int i = 0; i < Inputs.Count; i++) { if ((Object)(object)Inputs[i] != (Object)null && Inputs[i].isFocused) { return true; } } return false; } private static string FocusedInputId() { for (int i = 0; i < Inputs.Count; i++) { if ((Object)(object)Inputs[i] != (Object)null && Inputs[i].isFocused) { return InputIds[i]; } } return null; } internal static void Update() { if (_open && ((Object)(object)Player.m_localPlayer == (Object)null || (Object)(object)ZNet.instance == (Object)null || (Object)(object)_panel == (Object)null || GameUiOpen())) { Close(); return; } if ((Object)(object)_caretInput != (Object)null && _caretInput.isFocused) { _caretInput.MoveTextEnd(false); _caretInput = null; } bool textFocused = TextFocused; _wasFocused = _open && AnyInputFocused(); if (_open && !textFocused && (ZInput.GetKeyDown((KeyCode)27, false) || ZInput.GetButtonDown("JoyButtonB"))) { Close(); return; } if (!textFocused && HotkeyDown()) { if (_open) { Close(); } else { TryOpen(); } } if (_open && (_dirty || _shown != ClientSide.Profile)) { Rebuild(); } } private static bool HotkeyDown() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000d: 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) KeyboardShortcut value = PluginConfig.Hotkey.Value; if ((int)((KeyboardShortcut)(ref value)).MainKey == 0 || !ZInput.GetKeyDown(((KeyboardShortcut)(ref value)).MainKey, false)) { return false; } return ((KeyboardShortcut)(ref value)).Modifiers.All((KeyCode m) => ZInput.GetKey(m, false)); } private static bool GameUiOpen() { if (!Console.IsVisible() && (!((Object)(object)Chat.instance != (Object)null) || !Chat.instance.HasFocus()) && !Menu.IsVisible() && !InventoryGui.IsVisible() && !Minimap.IsOpen()) { return StoreGui.IsVisible(); } return true; } private static void TryOpen() { //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)Player.m_localPlayer == (Object)null || (Object)(object)ZNet.instance == (Object)null || GameUiOpen() || TextInput.IsVisible() || (Object)(object)GUIManager.CustomGUIFront == (Object)null) { return; } if (!ClientSide.Connected) { MessageHud instance = MessageHud.instance; if (instance != null) { instance.ShowMessage((MessageType)1, "«Путь» работает на сервере Sansara.", 0, (Sprite)null, false, true); } return; } if ((Object)(object)_panel == (Object)null) { Build(); } _panel.transform.localScale = Vector3.one * Mathf.Clamp(PluginConfig.Scale.Value, 0.75f, 1.75f); _panel.SetActive(true); _open = true; _confirm = ""; _dirty = true; if (!_blocked) { GUIManager.BlockInput(true); _blocked = true; } ClientSide.Request("profile", ""); } private static void Close() { _open = false; _closedFrame = Time.frameCount; _confirm = ""; _wasFocused = false; _caretInput = null; for (int i = 0; i < Inputs.Count; i++) { if ((Object)(object)Inputs[i] != (Object)null && Inputs[i].isFocused) { Inputs[i].DeactivateInputField(); } } if ((Object)(object)_panel != (Object)null) { _panel.SetActive(false); } if (_blocked) { GUIManager.BlockInput(false); _blocked = false; } } private static void Build() { //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Expected O, but got Unknown //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Unknown result type (might be due to invalid IL or missing references) //IL_013c: Unknown result type (might be due to invalid IL or missing references) //IL_0146: Expected O, but got Unknown //IL_017e: Unknown result type (might be due to invalid IL or missing references) //IL_0197: Unknown result type (might be due to invalid IL or missing references) //IL_01b1: Unknown result type (might be due to invalid IL or missing references) //IL_01b6: Unknown result type (might be due to invalid IL or missing references) //IL_01b7: Unknown result type (might be due to invalid IL or missing references) //IL_01bd: Unknown result type (might be due to invalid IL or missing references) //IL_01be: Unknown result type (might be due to invalid IL or missing references) //IL_01ce: Unknown result type (might be due to invalid IL or missing references) //IL_020e: Unknown result type (might be due to invalid IL or missing references) //IL_0218: Expected O, but got Unknown //IL_0239: Unknown result type (might be due to invalid IL or missing references) //IL_023a: Unknown result type (might be due to invalid IL or missing references) //IL_0245: Unknown result type (might be due to invalid IL or missing references) //IL_0273: Unknown result type (might be due to invalid IL or missing references) //IL_0278: Unknown result type (might be due to invalid IL or missing references) //IL_027e: Expected O, but got Unknown GUIManager instance = GUIManager.Instance; Vector2 val = default(Vector2); ((Vector2)(ref val))..ctor(0.5f, 0.5f); Vector2 val2 = default(Vector2); ((Vector2)(ref val2))..ctor(0.5f, 1f); if (!_bindsPatched) { _bindsPatched = true; PathBindGuard.PatchServerDevcommands(new Harmony("sansara.Path.binds")); } _panel = instance.CreateWoodpanel(GUIManager.CustomGUIFront.transform, val, val, Vector2.zero, 760f, 620f, true); ((Object)_panel).name = "SansaraPathWindow"; instance.CreateText("Путь", _panel.transform, val2, val2, new Vector2(0f, -40f), instance.AveriaSerifBold, 34, instance.ValheimOrange, true, Color.black, 400f, 50f, false).GetComponent<Text>().alignment = (TextAnchor)4; TabButtons.Clear(); for (int i = 0; i < TabNames.Length; i++) { int tab = i; Button component = instance.CreateButton(TabNames[i], _panel.transform, val2, val2, new Vector2(((float)i - 2.5f) * 120f, -95f), 112f, 40f).GetComponent<Button>(); ((UnityEvent)component.onClick).AddListener((UnityAction)delegate { SelectTab(tab); }); TabButtons.Add(component); } GameObject obj = instance.CreateScrollView(_panel.transform, false, true, 8f, 10f, instance.ValheimScrollbarHandleColorBlock, new Color(0f, 0f, 0f, 0.35f), 700f, 400f); RectTransform val3 = (RectTransform)obj.transform; val3.anchorMin = val; val3.anchorMax = val; val3.anchoredPosition = new Vector2(0f, -25f); _scroll = obj.GetComponentInChildren<ScrollRect>(); _content = _scroll.content; VerticalLayoutGroup component2 = ((Component)_content).GetComponent<VerticalLayoutGroup>(); ((HorizontalOrVerticalLayoutGroup)component2).spacing = 6f; ((LayoutGroup)component2).padding = new RectOffset(10, 10, 10, 10); Vector2 val4 = default(Vector2); ((Vector2)(ref val4))..ctor(0.5f, 0f); ButtonClickedEvent onClick = instance.CreateButton("Закрыть (Esc)", _panel.transform, val4, val4, new Vector2(0f, 40f), 220f, 40f).GetComponent<Button>().onClick; object obj2 = <>O.<0>__Close; if (obj2 == null) { UnityAction val5 = Close; <>O.<0>__Close = val5; obj2 = (object)val5; } ((UnityEvent)onClick).AddListener((UnityAction)obj2); _panel.SetActive(false); } private static void SelectTab(int tab) { if (tab != _tab) { _tab = tab; _confirm = ""; _dirty = true; if ((Object)(object)_scroll != (Object)null) { _scroll.verticalNormalizedPosition = 1f; } } } private static void Rebuild() { _dirty = false; _shown = ClientSide.Profile; string text = FocusedInputId(); Inputs.Clear(); InputIds.Clear(); _caretInput = null; for (int i = 0; i < TabButtons.Count; i++) { ((Selectable)TabButtons[i]).interactable = i != _tab; } for (int num = ((Transform)_content).childCount - 1; num >= 0; num--) { GameObject gameObject = ((Component)((Transform)_content).GetChild(num)).gameObject; gameObject.SetActive(false); Object.Destroy((Object)(object)gameObject); } ProfileView shown = _shown; if (_tab == 5) { DrawHelp(); } else if (shown == null) { Line("Загрузка…"); } else if (_tab == 0) { DrawPath(shown); } else if (_tab == 1) { DrawCraft(shown); } else if (_tab == 2) { DrawSagas(shown); } else if (_tab == 3) { DrawRunes(shown); } else { DrawOrders(shown); } int num2 = ((text != null) ? InputIds.IndexOf(text) : (-1)); if (num2 >= 0) { ((Selectable)Inputs[num2]).Select(); Inputs[num2].ActivateInputField(); _caretInput = Inputs[num2]; } } private static void DrawHelp() { PathHelp.Section[] sections = PathHelp.Sections; foreach (PathHelp.Section obj in sections) { Header(obj.Title); string[] lines = obj.Lines; for (int j = 0; j < lines.Length; j++) { Line(lines[j]); } } } private static void DrawPath(ProfileView v) { Header(v.Name); Line("Титул: " + ((v.Title == "") ? "нет" : v.Title)); Line("Слава: " + v.Renown); Line((v.Craft == Craft.None) ? "Ремесло не выбрано — вкладка «Ремесло»." : ("Ремесло: " + Crafts.Name(v.Craft) + ", " + Ranks.Name(v.Rank))); if (v.TitleOptions.Count == 0) { return; } Header("Выбрать титул"); foreach (KeyValuePair<string, string> titleOption in v.TitleOptions) { string code = titleOption.Key; AddButton(titleOption.Value, delegate { ClientSide.Request("title", code); }); } if (v.Title != "") { AddButton("Без титула", delegate { ClientSide.Request("title", ""); }); } } private static void DrawCraft(ProfileView v) { if (v.Craft == Craft.None) { Line("Выберите ремесло. Оно ничего не запрещает: любые вещи делаются как обычно, а изделия своего ремесла со временем выходят лучше и получают ваше клеймо, стопки — лишнюю партию."); } else { Header(Crafts.Name(v.Craft) + " — " + Ranks.Name(v.Rank)); Line("Делает: " + Crafts.Makes(v.Craft) + "."); if (!PluginConfig.CraftEnabled(v.Craft)) { Line("Ремесло на сервере выключено: опыта и бонусов нет."); } Line((v.NextXp < 0) ? $"Опыт: {v.Xp} (высший ранг)" : ($"Опыт: {v.Xp} / {v.NextXp}" + ((v.Xp >= v.NextXp) ? " — завершите главу саги (вкладка «Саги»)" : ""))); if (Crafts.HasItems(v.Craft)) { Line("Шанс изделия: Добротное " + PathTexts.Num(v.GoodChance) + " %, Отменное " + PathTexts.Num(v.FineChance) + " %"); Line($"За период: полный опыт за первые {v.FullCrafts} одинаковых изделия; улучшений с опытом осталось " + $"{v.UpgradesLeft}; бонусов за надевание другими — {v.WornLeft}."); Line((v.Craft == Craft.Armorer) ? "Свойства: Закалка (прочность), Лёгкость (вес), Крепость (броня и блок)." : ((v.Craft == Craft.Fletcher) ? "Свойства: Острота (урон), Закалка (прочность), Лёгкость (вес)." : "Свойства: Острота (урон), Закалка (прочность), Лёгкость (вес), Крепость (броня и блок).")); } if (Crafts.HasStacks(v.Craft)) { Line("Лишняя партия при создании " + ((v.Craft == Craft.Herbalist) ? "в котле" : "стрел и болтов") + ": " + PathTexts.Num(v.YieldChance) + " % на каждую партию; с бонусом наград «Выход изготовления» не складывается — действует больший."); Line($"Опыт за стопки — уровень рецепта × число партий; за период осталось {v.StackXpLeft} из {v.StackXpPerDay}."); } if (v.Craft == Craft.Herbalist) { Line($"Разных рецептов создано: {v.Recipes} (условия глав — вкладка «Саги»)."); } if (v.GrandMaster) { DrawMasterpiece(v); } } if (v.NextSwitch != "") { Line("Сменить ремесло можно после " + v.NextSwitch + "."); return; } Craft[] all = Crafts.All; foreach (Craft craft in all) { if (craft == v.Craft || !PluginConfig.CraftEnabled(craft)) { continue; } string code = Crafts.Code(craft); if (_confirm == code) { Line((v.Craft == Craft.None) ? ("Стать: " + Crafts.Name(craft) + "? Сменить ремесло можно будет через " + PathTexts.Num((float)v.SwitchDays) + " дн.") : $"Сменить на: {Crafts.Nam