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 TameLab v0.5.0
plugins/TameLab.dll
Decompiled 2 hours agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Jotunn.Utils; using Microsoft.CodeAnalysis; using Sansara.Admin; using Sansara.Bank; using SansaraPath; using TameLab.Core; 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("TameLab")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.5.0.0")] [assembly: AssemblyInformationalVersion("0.5.0+88fb6f13f4f159e7aebb95013d1ddbe1438c74da")] [assembly: AssemblyProduct("TameLab")] [assembly: AssemblyTitle("TameLab")] [assembly: AssemblyVersion("0.5.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace Sansara.Admin { internal static class AdminChannel { private const float ScanSeconds = 1f; private const float DenialLogSeconds = 30f; private static ManualLogSource _log; private static bool _owner; private static bool _announced; private static float _nextScan; private static readonly HashSet<ZRpc> Registered = new HashSet<ZRpc>(); private static readonly Dictionary<ZRpc, float> LastDenial = new Dictionary<ZRpc, float>(); public static void Register(string module, string[] verbs, Func<string, string, string, string> handler, ManualLogSource log) { _log = log; if (AdminRegistry.Shared.Register(module, verbs, handler)) { log.LogWarning((object)("Админ-канал: модуль " + module + " зарегистрирован повторно — обработчик заменён.")); } if (AdminRegistry.Shared.ClaimOwner(typeof(AdminChannel).Assembly.GetName().Name)) { _owner = true; } } public static void Tick() { if (!_owner || (Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer() || Time.realtimeSinceStartup < _nextScan) { return; } _nextScan = Time.realtimeSinceStartup + 1f; try { Scan(); } catch (Exception ex) { _log.LogError((object)("Админ-канал: ошибка: " + ex)); } } public static void CheckFile(string guid, string logPrefix, Func<string, string, string, string> handler) { string path = Path.Combine(Paths.ConfigPath, guid + ".command"); if (!File.Exists(path)) { return; } string text; try { text = File.ReadAllText(path).Trim(); File.Delete(path); } catch (Exception ex) { _log.LogWarning((object)(logPrefix + ": файл команды не прочитан: " + ex.Message)); return; } _log.LogInfo((object)(logPrefix + ": команда «" + AdminProtocol.ForLog(text) + "».")); try { _log.LogInfo((object)(logPrefix + ": " + handler(text, "файл команд", "").Replace("\n", "; "))); } catch (Exception ex2) { _log.LogError((object)(logPrefix + ": ошибка команды: " + ex2)); } } private static void Scan() { if (!_announced) { _announced = true; _log.LogInfo((object)("Админ-канал: владелец " + AdminRegistry.Shared.Owner + ", модули: " + string.Join(", ", AdminRegistry.Shared.Modules().ToArray()) + ".")); } HashSet<ZRpc> live = new HashSet<ZRpc>(); foreach (ZNetPeer peer in ZNet.instance.GetPeers()) { if (peer != null && peer.m_rpc != null) { live.Add(peer.m_rpc); if (Registered.Add(peer.m_rpc)) { peer.m_rpc.Register<ZPackage>("Sansara_Admin", (Action<ZRpc, ZPackage>)OnRequest); } } } Registered.RemoveWhere((ZRpc r) => !live.Contains(r)); foreach (ZRpc item in new List<ZRpc>(LastDenial.Keys)) { if (!live.Contains(item)) { LastDenial.Remove(item); } } } private static void OnRequest(ZRpc rpc, ZPackage pkg) { //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Expected O, but got Unknown int num = 0; AdminReply adminReply; try { int protocol = pkg.ReadInt(); num = pkg.ReadInt(); string module = pkg.ReadString(); string line = pkg.ReadString(); adminReply = Handle(rpc, protocol, module, line); } catch (Exception ex) { ZNetPeer val = ZNet.instance.GetPeers().Find((ZNetPeer p) => p.m_rpc == rpc); if (val == null || !val.IsReady()) { return; } LogThrottled(rpc, "Админ-канал: неверный запрос от " + AdminProtocol.ForLog(val.m_playerName) + ": " + AdminProtocol.ForLog(ex.Message)); adminReply = new AdminReply(4, "неверный запрос"); } if (adminReply != null) { ZPackage val2 = new ZPackage(); val2.Write(1); val2.Write(num); val2.Write(adminReply.Code); val2.Write(AdminProtocol.Truncate(adminReply.Text)); rpc.Invoke("Sansara_AdminReply", new object[1] { val2 }); } } private static void LogThrottled(ZRpc rpc, string message) { float realtimeSinceStartup = Time.realtimeSinceStartup; if (!LastDenial.TryGetValue(rpc, out var value) || realtimeSinceStartup - value >= 30f) { LastDenial[rpc] = realtimeSinceStartup; _log.LogWarning((object)message); } } private static AdminReply Handle(ZRpc rpc, int protocol, string module, string line) { ZNetPeer val = ZNet.instance.GetPeers().Find((ZNetPeer p) => p.m_rpc == rpc); if (val == null || !val.IsReady()) { return null; } AdminReply adminReply = AdminProtocol.Check(protocol, module, line); if (adminReply != null) { return adminReply; } string text = AdminProtocol.ForLog(val.m_playerName); string text2 = AdminProtocol.ForLog(val.m_socket.GetHostName()); string text3 = AdminProtocol.ForLog(AdminProtocol.Shown(module, line)); bool flag = ((ZDOID)(ref val.m_characterID)).IsNone(); if (flag || !ZNet.instance.IsAdmin(val.m_socket.GetHostName())) { LogThrottled(rpc, "Админ-канал: отказано " + text + " (" + text2 + "): " + text3); return new AdminReply(1, flag ? "отказано: персонаж не в мире (ещё не вошёл или погиб)" : "отказано: нужны права администратора"); } Exception error; AdminReply adminReply2 = AdminRegistry.Shared.Dispatch(module, line, val.m_playerName, val.m_socket.GetHostName(), out error); if (error != null) { _log.LogError((object)("Админ-канал: " + text + " (" + text2 + ") → " + text3 + ": " + AdminProtocol.ForLog(error.Message) + Environment.NewLine + error.StackTrace)); } else { _log.LogInfo((object)("Админ-канал: " + text + " (" + text2 + ") → " + text3 + ": " + AdminProtocol.ForLog(AdminProtocol.FirstLine(adminReply2.Text)))); } return adminReply2; } } public sealed class AdminCommand { public string Verb = ""; public readonly List<string> Args = new List<string>(); public string Argument => string.Join(" ", Args); public static AdminCommand Parse(string text) { AdminCommand adminCommand = new AdminCommand(); List<string> list = Tokens(text); if (list.Count == 0) { return adminCommand; } adminCommand.Verb = list[0].ToLowerInvariant(); adminCommand.Args.AddRange(list.GetRange(1, list.Count - 1)); return adminCommand; } public static List<string> Tokens(string text) { List<string> list = new List<string>(); StringBuilder stringBuilder = new StringBuilder(); bool flag = false; bool flag2 = false; string text2 = text ?? ""; foreach (char c in text2) { if (c == '"') { flag = !flag; flag2 = true; } else if (!flag && char.IsWhiteSpace(c)) { if (flag2) { list.Add(stringBuilder.ToString()); stringBuilder.Clear(); flag2 = false; } } else { stringBuilder.Append(c); flag2 = true; } } if (flag2) { list.Add(stringBuilder.ToString()); } return list; } public static bool TryId(string argument, out string id) { string text = (argument ?? "").Trim(); id = ((text.Length > 1 && text[0] == '@') ? text.Substring(1).Trim() : null); if (id == "") { id = null; } return id != null; } } public sealed class AdminReply { public readonly int Code; public readonly string Text; public AdminReply(int code, string text) { Code = code; Text = text ?? ""; } } public static class AdminProtocol { public const int Version = 1; public const string RequestRpc = "Sansara_Admin"; public const string ReplyRpc = "Sansara_AdminReply"; public const int MaxLine = 256; public const int MaxReply = 4000; public const int Done = 0; public const int Denied = 1; public const int NoModule = 2; public const int Failed = 3; public const int Invalid = 4; public const int MaxLogText = 300; public static string Truncate(string text) { string text2 = text ?? ""; if (text2.Length > 4000) { return text2.Substring(0, 3999) + "…"; } return text2; } public static AdminReply Check(int protocol, string module, string line) { if (protocol != 1) { return new AdminReply(4, $"неверный запрос: обновите Sansara Admin (протокол сервера {1})"); } if (module == null || line == null) { return new AdminReply(4, "неверный запрос"); } if (Shown(module, line).Length > 256) { return new AdminReply(4, $"строка длиннее {256} символов"); } return null; } public static string Shown(string module, string line) { string text = (module ?? "").Trim(); if (!(text == "")) { return (text + " " + (line ?? "").Trim()).Trim(); } return "help"; } public static string ForLog(string s) { char[] array = (s ?? "").ToCharArray(); for (int i = 0; i < array.Length; i++) { if (char.IsControl(array[i])) { array[i] = ' '; } } if (array.Length > 300) { return new string(array, 0, 300); } return new string(array); } public static string FirstLine(string text) { string text2 = (text ?? "").Replace("\r", ""); int num = text2.IndexOf('\n'); if (num >= 0) { return text2.Substring(0, num); } return text2; } public static string HelpLine(string module, IEnumerable<string> verbs) { return module + ": " + string.Join(", ", verbs); } public static Dictionary<string, string[]> ParseHelp(string text) { Dictionary<string, string[]> dictionary = new Dictionary<string, string[]>(StringComparer.Ordinal); string[] array = (text ?? "").Replace("\r", "").Split(new char[1] { '\n' }); foreach (string text2 in array) { int num = text2.IndexOf(':'); if (num <= 0) { continue; } string text3 = text2.Substring(0, num).Trim(); if (IsName(text3)) { dictionary[text3] = (from v in text2.Substring(num + 1).Split(new char[1] { ',' }) select v.Trim()).Where(IsName).ToArray(); } } return dictionary; } public static bool IsName(string s) { if (!string.IsNullOrEmpty(s)) { return s.All((char c) => c >= 'a' && c <= 'z'); } return false; } public static string Unknown(string verb, IEnumerable<string> verbs) { return (string.IsNullOrEmpty(verb) ? "укажите действие" : ("неизвестное действие «" + verb + "»")) + "; есть: " + string.Join(", ", verbs); } } public sealed class AdminRegistry { public const string SharedKey = "sansara.Admin.v1"; public static readonly AdminRegistry Shared = new AdminRegistry("sansara.Admin.v1"); private readonly string _key; private Dictionary<string, Tuple<string[], Func<string, string, string, string>>> Table { get { object data = AppDomain.CurrentDomain.GetData(_key); if (data == null) { Dictionary<string, Tuple<string[], Func<string, string, string, string>>> dictionary = new Dictionary<string, Tuple<string[], Func<string, string, string, string>>>(StringComparer.Ordinal); AppDomain.CurrentDomain.SetData(_key, dictionary); return dictionary; } return (data as Dictionary<string, Tuple<string[], Func<string, string, string, string>>>) ?? throw new InvalidOperationException("данные " + _key + " другого формата"); } } public string Owner => (AppDomain.CurrentDomain.GetData(_key + ".owner") as string) ?? ""; public AdminRegistry(string key) { _key = key; } public bool Register(string module, string[] verbs, Func<string, string, string, string> handler) { string text = (module ?? "").Trim().ToLowerInvariant(); if (!AdminProtocol.IsName(text)) { throw new ArgumentException("имя модуля — латинские буквы: " + module); } string[] array = verbs ?? new string[0]; foreach (string text2 in array) { if (!AdminProtocol.IsName(text2)) { throw new ArgumentException("глагол — латинские буквы: " + text2); } } if (handler == null) { throw new ArgumentNullException("handler"); } Dictionary<string, Tuple<string[], Func<string, string, string, string>>> table = Table; bool result = table.ContainsKey(text); table[text] = Tuple.Create(verbs ?? new string[0], handler); return result; } public bool ClaimOwner(string assembly) { if (string.IsNullOrEmpty(assembly)) { throw new ArgumentException("пустое имя сборки", "assembly"); } string name = _key + ".owner"; if (!(AppDomain.CurrentDomain.GetData(name) is string text)) { AppDomain.CurrentDomain.SetData(name, assembly); return true; } return text == assembly; } public List<string> Modules() { return Table.Keys.OrderBy<string, string>((string k) => k, StringComparer.Ordinal).ToList(); } public string HelpText() { Dictionary<string, Tuple<string[], Func<string, string, string, string>>> table = Table; if (table.Count != 0) { return string.Join("\n", from k in Modules() select AdminProtocol.HelpLine(k, table[k].Item1)); } return "на сервере нет модулей Sansara Admin"; } public AdminReply Dispatch(string module, string line, string adminName, string adminId, out Exception error) { error = null; try { string text = (module ?? "").Trim().ToLowerInvariant(); if (text == "") { return new AdminReply(0, AdminProtocol.Truncate(HelpText())); } Dictionary<string, Tuple<string[], Func<string, string, string, string>>> table = Table; if (!table.TryGetValue(text, out var value)) { return new AdminReply(2, "модуля " + text + " нет на сервере; есть: " + ((table.Count == 0) ? "ничего" : string.Join(", ", Modules()))); } string text2 = value.Item2((line ?? "").Trim(), adminName ?? "", adminId ?? ""); return new AdminReply(0, AdminProtocol.Truncate(string.IsNullOrEmpty(text2) ? "готово" : text2)); } catch (Exception ex) { return new AdminReply(3, AdminProtocol.Truncate("ошибка: " + (error = ex).Message)); } } } public static class AdminUi { public const string Verb = "ui"; public const string Head = "ui=1"; public const string NoteKey = "note"; public const string MoreKey = "more"; private const int Reserve = 16; public static string Field(object value) { char[] array = ((!(value is bool)) ? (Convert.ToString(value, CultureInfo.InvariantCulture) ?? "") : (((bool)value) ? "1" : "0")).ToCharArray(); for (int i = 0; i < array.Length; i++) { if (array[i] == ';') { array[i] = ','; } else if (char.IsControl(array[i])) { array[i] = ' '; } } return new string(array).Trim(); } public static string Row(string key, params object[] fields) { if (!AdminProtocol.IsName(key)) { throw new ArgumentException("ключ строки ui — латинские буквы в нижнем регистре: " + key); } return key + "=" + string.Join(";", (fields ?? new object[0]).Select(Field)); } public static string Note(string text) { return Text(new string[1] { Row("note", text) }); } public static string Text(IEnumerable<string> rows) { List<string> list = (rows ?? Enumerable.Empty<string>()).ToList(); StringBuilder stringBuilder = new StringBuilder("ui=1"); for (int i = 0; i < list.Count; i++) { if (stringBuilder.Length + 1 + list[i].Length > 3984) { stringBuilder.Append('\n').Append("more").Append('=') .Append((list.Count - i).ToString(CultureInfo.InvariantCulture)); break; } stringBuilder.Append('\n').Append(list[i]); } return stringBuilder.ToString(); } } } namespace Sansara.Bank { public sealed class CoinAccount { public string Name = ""; public long Coins; } public sealed class CoinAccounts { private const string Header = "accounts\t1"; public readonly Dictionary<string, CoinAccount> Players = new Dictionary<string, CoinAccount>(StringComparer.Ordinal); public long Balance(string id) { if (!Players.TryGetValue(id, out var value)) { return 0L; } return value.Coins; } public void Deposit(string id, string name, long coins) { if (coins < 0) { throw new ArgumentOutOfRangeException("coins"); } if (!Players.TryGetValue(id, out var value)) { value = new CoinAccount(); Players[id] = value; } if (!string.IsNullOrEmpty(name)) { value.Name = Clean(name); } value.Coins += coins; } public bool TrySpend(string id, long coins) { if (coins < 0) { throw new ArgumentOutOfRangeException("coins"); } if (!Players.TryGetValue(id, out var value) || value.Coins < coins) { return false; } value.Coins -= coins; return true; } public string Serialize() { StringBuilder stringBuilder = new StringBuilder("accounts\t1").Append('\n'); foreach (KeyValuePair<string, CoinAccount> player in Players) { stringBuilder.Append(Clean(player.Key)).Append('\t').Append(player.Value.Coins.ToString(CultureInfo.InvariantCulture)) .Append('\t') .Append(Clean(player.Value.Name)) .Append('\n'); } return stringBuilder.ToString(); } public static CoinAccounts Parse(string text) { CoinAccounts coinAccounts = new CoinAccounts(); string[] array = (text ?? "").Replace("\r", "").Split(new char[1] { '\n' }, StringSplitOptions.RemoveEmptyEntries); if (array.Length == 0) { return coinAccounts; } if (array[0] != "accounts\t1") { throw new FormatException("нет заголовка"); } for (int i = 1; i < array.Length; i++) { string[] array2 = array[i].Split(new char[1] { '\t' }); if (array2.Length != 3 || array2[0] == "" || !long.TryParse(array2[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) || result < 0) { throw new FormatException("строка " + (i + 1)); } coinAccounts.Players[array2[0]] = new CoinAccount { Coins = result, Name = array2[2] }; } return coinAccounts; } private static string Clean(string s) { return (s ?? "").Replace('\t', ' ').Replace('\n', ' ').Replace("\r", ""); } } } namespace TameLab { [BepInPlugin("sansara.TameLab", "Tame Lab", "0.5.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [NetworkCompatibility(/*Could not decode attribute arguments.*/)] public sealed class Plugin : BaseUnityPlugin { public const string Guid = "sansara.TameLab"; public const string Name = "Tame Lab"; public const string Version = "0.5.0"; private const string EffectsSource = "Wolf"; internal static ManualLogSource Log; internal static ConfigEntry<bool> StableEnabled; internal static readonly Dictionary<string, ConfigEntry<string>> Prices = new Dictionary<string, ConfigEntry<string>>(); internal static ConfigEntry<int> StableLimit; internal static ConfigEntry<float> StableRadius; internal static ConfigEntry<float> PetHealthMultiplier; internal static ConfigEntry<float> PetRegenMinutes; internal static readonly HashSet<string> Added = new HashSet<string>(StringComparer.Ordinal); internal static readonly HashSet<int> AddedHashes = new HashSet<int>(); private float _nextTick; private float _nextScan; private bool _started; private void Awake() { //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Expected O, but got Unknown //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Expected O, but got Unknown //IL_0141: Unknown result type (might be due to invalid IL or missing references) //IL_0146: Unknown result type (might be due to invalid IL or missing references) //IL_014e: Expected O, but got Unknown //IL_014e: Unknown result type (might be due to invalid IL or missing references) //IL_0158: Expected O, but got Unknown //IL_018b: Unknown result type (might be due to invalid IL or missing references) Log = ((BaseUnityPlugin)this).Logger; StableEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Stable", "Enabled", true, "Лавка: счёт монет в Чертоге славы и покупка ручного существа в окне F8 (настройки читает сервер)."); Good[] goods = StableRules.Goods; foreach (Good good in goods) { Prices[good.Prefab] = ((BaseUnityPlugin)this).Config.Bind<string>("Prices", good.Prefab, good.DefaultPrices, good.Description + ": цены в монетах через запятую — без звёзд, 1 звезда, 2 звезды."); } StableLimit = ((BaseUnityPlugin)this).Config.Bind<int>("Stable", "MaxTotalPerPlayer", 3, "Сколько живых купленных существ всех видов вместе может быть у игрока."); StableRadius = ((BaseUnityPlugin)this).Config.Bind<float>("Stable", "BuyRadius", 30f, "Покупать можно не дальше стольких метров от сундука счёта (весь Чертог славы); 0 — откуда угодно."); PetHealthMultiplier = ((BaseUnityPlugin)this).Config.Bind<float>("Pet", "HealthMultiplier", 3f, new ConfigDescription("Во сколько раз у ручного существа больше здоровья, чем у дикого того же уровня. Дикие не меняются.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 10f), new object[1] { (object)new ConfigurationManagerAttributes { IsAdminOnly = true } })); PetRegenMinutes = ((BaseUnityPlugin)this).Config.Bind<float>("Pet", "RegenMinutes", 15f, new ConfigDescription("За сколько минут ручное существо само восстанавливает здоровье от нуля до полного. В игре 60 минут.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 120f), new object[1] { (object)new ConfigurationManagerAttributes { IsAdminOnly = true } })); AdminChannel.Register("bank", BankAdmin.Verbs, Stable.Instance.HandleAdmin, Log); ShopTab.Register(); new Harmony("sansara.TameLab").PatchAll(); Log.LogInfo((object)"Tame Lab 0.5.0 загружен."); } private void Update() { ShopTab.Update(); AdminChannel.Tick(); if (!StableEnabled.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)ZoneSystem.instance == (Object)null || !ZoneSystem.instance.LocationsGenerated) { return; } try { if (_started && Time.time >= _nextScan) { _nextScan = Time.time + 1f; Stable.Instance.Scan(); } if (Time.time < _nextTick) { return; } _nextTick = Time.time + 5f; if (!_started) { if (!Stable.Instance.Prepare()) { _nextTick = float.MaxValue; return; } _started = true; } AdminChannel.CheckFile("sansara.TameLab", "Лавка", Stable.Instance.HandleAdmin); Stable.Instance.Tick(); } catch (Exception ex) { Log.LogError((object)("Лавка: ошибка: " + ex)); } } internal static void Apply() { ZNetScene instance = ZNetScene.instance; if ((Object)(object)instance == (Object)null) { return; } GameObject prefab = instance.GetPrefab("Wolf"); Tameable val = ((prefab != null) ? prefab.GetComponent<Tameable>() : null); Good[] goods = StableRules.Goods; for (int i = 0; i < goods.Length; i++) { string prefab2 = goods[i].Prefab; GameObject prefab3 = instance.GetPrefab(prefab2); if ((Object)(object)prefab3 == (Object)null) { Log.LogWarning((object)("Префаб " + prefab2 + " не найден.")); continue; } if ((Object)(object)prefab3.GetComponent<MonsterAI>() == (Object)null || (Object)(object)prefab3.GetComponent<Humanoid>() == (Object)null) { Log.LogWarning((object)("Префаб " + prefab2 + " без MonsterAI или Humanoid, приручаемость не добавлена.")); continue; } if ((Object)(object)prefab3.GetComponent<Tameable>() != (Object)null) { Log.LogInfo((object)(Added.Contains(prefab2) ? (prefab2 + ": приручаемость уже добавлена.") : (prefab2 + ": приручаем в игре, не меняю."))); continue; } Tameable val2 = prefab3.AddComponent<Tameable>(); val2.m_commandable = true; val2.m_fedDuration = float.MaxValue; if ((Object)(object)val != (Object)null) { val2.m_tamedEffect = val.m_tamedEffect; val2.m_sootheEffect = val.m_sootheEffect; val2.m_petEffect = val.m_petEffect; } Added.Add(prefab2); AddedHashes.Add(StringExtensionMethods.GetStableHashCode(((Object)prefab3).name)); Log.LogInfo((object)(prefab2 + ": приручаемость добавлена.")); } } } [HarmonyPatch(typeof(ZNetScene), "Awake")] internal static class ZNetSceneAwakePatch { private static void Postfix() { Plugin.Apply(); } } [HarmonyPatch(typeof(Tameable), "TamingUpdate")] internal static class TamingUpdatePatch { private static bool Prefix(Tameable __instance) { return !Plugin.Added.Contains(Utils.GetPrefabName(((Component)__instance).gameObject)); } } [HarmonyPatch(typeof(Tameable), "Tame")] internal static class SpecialMobsPatch { private static readonly string[] Tags = new string[3] { "sansara.BountyHunt.tag", "sansara.Path.hunt", "sansara.Path.hunt.retinue" }; private static bool Prefix(Tameable __instance, ZNetView ___m_nview, MonsterAI ___m_monsterAI) { ZDO val = (((Object)(object)___m_nview != (Object)null) ? ___m_nview.GetZDO() : null); if (val == null || !Plugin.Added.Contains(Utils.GetPrefabName(((Component)__instance).gameObject))) { return true; } if ((Object)(object)___m_monsterAI != (Object)null && ___m_monsterAI.IsEventCreature()) { return false; } string[] tags = Tags; foreach (string text in tags) { if (val.GetString(text, "") != "") { return false; } } return string.IsNullOrWhiteSpace(val.GetString(ZDOVars.s_overrideHoverName, "")); } } [HarmonyPatch(typeof(Tameable), "GetName")] internal static class LeaderNamePatch { private static void Postfix(Tameable __instance, ZNetView ___m_nview, Character ___m_character, ref string __result) { ZDO val = (((Object)(object)___m_nview != (Object)null && ___m_nview.IsValid()) ? ___m_nview.GetZDO() : null); if (val != null && !((Object)(object)___m_character == (Object)null) && !___m_character.IsTamed()) { string text = val.GetString(ZDOVars.s_overrideHoverName, ""); if (!string.IsNullOrWhiteSpace(text) && Plugin.Added.Contains(Utils.GetPrefabName(((Component)__instance).gameObject))) { __result = text; } } } } [HarmonyPatch(typeof(ZDOMan), "HandleDestroyedZDO")] internal static class NoEwpLootPatch { [HarmonyPriority(800)] private static void Prefix(ZDOMan __instance, ZDOID uid, Dictionary<ZDOID, long> ___m_deadZDOs) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) try { if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer()) { ZDO zDO = __instance.GetZDO(uid); if (zDO != null && Plugin.AddedHashes.Contains(zDO.GetPrefab()) && zDO.GetBool(ZDOVars.s_tamed, false)) { ___m_deadZDOs[uid] = ZNet.instance.GetTime().Ticks; } } } catch (Exception ex) { Plugin.Log.LogError((object)("Удаление ручного существа: " + ex)); } } } [HarmonyPatch(typeof(Character), "GetMaxHealthBase")] internal static class PetHealthPatch { private static void Postfix(Character __instance, ZNetView ___m_nview, ref float __result) { if (TameStats.IsPet(__instance, ___m_nview)) { __result *= Plugin.PetHealthMultiplier.Value; } } } internal static class TameStats { internal static bool IsPet(Character character, ZNetView view) { if ((Object)(object)view == (Object)null || !view.IsValid() || character.IsPlayer() || !character.IsTamed()) { return false; } return Plugin.AddedHashes.Contains(view.GetZDO().GetPrefab()); } internal static void Refresh(Character character) { character.m_regenAllHPTime = Plugin.PetRegenMinutes.Value * 60f; if (!character.IsDead()) { float maxHealth = character.GetMaxHealth(); float num = character.GetMaxHealthBase() * (float)character.GetLevel(); if (!(Mathf.Abs(num - maxHealth) < 1f)) { float num2 = StableRules.ScaleHealth(character.GetHealth(), maxHealth, num); character.SetMaxHealth(num); character.SetHealth(num2); Plugin.Log.LogInfo((object)$"{Utils.GetPrefabName(((Component)character).gameObject)} уровня {character.GetLevel()}: максимум здоровья {maxHealth:0} → {num:0}, сейчас {num2:0}."); } } } } [HarmonyPatch(typeof(BaseAI), "UpdateRegeneration")] internal static class RegenerationPatch { internal static bool Active; private static void Prefix(Character ___m_character, ZNetView ___m_nview) { if (TameStats.IsPet(___m_character, ___m_nview)) { TameStats.Refresh(___m_character); } Active = true; } private static void Finalizer() { Active = false; } } [HarmonyPatch(typeof(Character), "RPC_Heal")] internal static class HealPatch { private static bool Prefix(Character __instance) { if (!RegenerationPatch.Active && __instance.IsTamed()) { return !Plugin.Added.Contains(Utils.GetPrefabName(((Component)__instance).gameObject)); } return true; } } internal static class ShopTab { private const float AskEverySeconds = 2f; private static ZRpc _server; private static ShopState _state; private static string _stateText = ""; private static float _askedAt = -100f; private static string _confirm = ""; public static void Register() { PathTabs.Register("Лавка", (Action)Draw); } public static void Update() { ZRpc val = (((Object)(object)ZNet.instance != (Object)null && !ZNet.instance.IsServer()) ? ZNet.instance.GetServerRPC() : null); if (val != _server) { _server = val; _state = null; _stateText = ""; _confirm = ""; if (val != null) { val.Register<string>("sansara.TameLab.State", (Action<ZRpc, string>)OnState); } } } private static void OnState(ZRpc rpc, string text) { if (text != _stateText) { _stateText = text; _state = ShopState.Parse(text); PathTabs.Refresh(); } } private static void Send(string verb, string arg) { ZRpc server = _server; if (server != null) { server.Invoke("sansara.TameLab.Request", new object[2] { verb, arg }); } } private static void Draw() { if (Time.time - _askedAt >= 2f) { _askedAt = Time.time; Send("state", ""); } PathTabs.Header("Лавка"); if (_state == null) { PathTabs.Line("Запрашиваю данные… Если это сообщение не исчезает, на этом сервере лавки нет."); return; } PathTabs.Line($"На счету: {_state.Balance} монет"); PathTabs.Line("Пополнить счёт можно монетами через сундук в Чертоге славы, у западной стены."); PathTabs.Line("Ручное существо идёт за вами и сражается рядом. Кормить и лечить его нельзя, здоровье восстанавливается само. Погибшее не возвращается."); PathTabs.Line($"Куплено живых существ: {_state.TotalOwned} из {_state.Limit}"); if (!_state.Near) { PathTabs.Line("Покупать можно только в Чертоге славы."); } foreach (ShopItem item in _state.Items) { Good good = StableRules.GoodOf(item.Prefab); if (good != null) { PathTabs.Header(good.Description); PathTabs.Line($"Сейчас у вас: {item.Owned}"); for (int i = 0; i < item.Prices.Length; i++) { DrawTier(good, item, i); } } } } private static void DrawTier(Good good, ShopItem item, int tier) { long num = item.Prices[tier]; string text = $"{good.Name} {StableRules.Stars(tier)}: {num} монет"; Deal deal = StableRules.Decide(_state.Balance, num, _state.TotalOwned, _state.Limit, _state.Near); if (deal != Deal.Buy) { PathTabs.Line((deal == Deal.NeedCoins) ? $"{text} (не хватает {num - _state.Balance})" : text); return; } string arg = StableRules.BuyArg(good, tier); if (_confirm != arg) { PathTabs.Button("Купить: " + text, (Action)delegate { Choose(arg); }); return; } PathTabs.Line($"{good.Name} {StableRules.Stars(tier)} за {num} монет? Появится рядом с вами."); PathTabs.Button("Да", (Action)delegate { Choose(""); Send("buy", arg); }); PathTabs.Button("Отмена", (Action)delegate { Choose(""); }); } private static void Choose(string arg) { _confirm = arg; PathTabs.Refresh(); } } internal sealed class Stable : IBankHost { internal const string RequestRpc = "sansara.TameLab.Request"; internal const string StateRpc = "sansara.TameLab.State"; private const string RoleKey = "sansara.PortalHall.role"; private const string OwnerKey = "sansara.TameLab.owner"; private const float SpawnAhead = 3f; internal static readonly Stable Instance = new Stable(); private readonly List<ZDO> _found = new List<ZDO>(); private readonly HashSet<ZRpc> _registered = new HashSet<ZRpc>(); private CoinAccounts _accounts = new CoinAccounts(); private ZDO _chest; private long _handled = -1L; private long _lastDepositor; private string _path; private readonly Dictionary<string, long[]> _prices = new Dictionary<string, long[]>(); private bool _ready; private bool _missingReported; private FieldInfo _ewpSkip; private bool _ewpLooked; private static Vector3 FameAnchor => new Vector3(10820f, 0f, 300f); public bool Prepare() { _prices.Clear(); Good[] goods = StableRules.Goods; foreach (Good good in goods) { string value = Plugin.Prices[good.Prefab].Value; long[] array = StableRules.ParsePrices(value); if (array == null) { Plugin.Log.LogError((object)$"Лавка: цены {good.Prefab} неверны («{value}»), нужно {3} положительных числа. Лавка выключена."); return false; } GameObject prefab = ZNetScene.instance.GetPrefab(good.Prefab); if ((Object)(object)((prefab != null) ? prefab.GetComponent<Tameable>() : null) == (Object)null) { Plugin.Log.LogError((object)("Лавка: префаб " + good.Prefab + " не найден или не приручаем. Лавка выключена.")); return false; } _prices[good.Prefab] = array; } string text = Path.Combine(Paths.ConfigPath, "sansara.Bank"); Directory.CreateDirectory(text); string text2 = string.Concat(ZNet.instance.GetWorldName().Split(Path.GetInvalidFileNameChars())); _path = Path.Combine(text, text2 + ".accounts"); try { _accounts = (File.Exists(_path) ? CoinAccounts.Parse(File.ReadAllText(_path)) : new CoinAccounts()); } catch (FormatException ex) { Plugin.Log.LogError((object)("Лавка: файл счетов повреждён (" + ex.Message + "): " + _path + ". Лавка выключена, файл не тронут.")); return false; } _ready = true; Plugin.Log.LogInfo((object)string.Format("Лавка: цены {0}, предел {1}, радиус {2}, счетов {3}, здоровье ручных ×{4}, восстановление {5} мин.", string.Join("; ", _prices.Select((KeyValuePair<string, long[]> p) => p.Key + " " + string.Join(", ", p.Value))), Plugin.StableLimit.Value, Plugin.StableRadius.Value, _accounts.Players.Count, Plugin.PetHealthMultiplier.Value, Plugin.PetRegenMinutes.Value)); return true; } public bool Save() { try { string text = _path + ".tmp"; File.WriteAllText(text, _accounts.Serialize()); if (File.Exists(_path)) { File.Replace(text, _path, null); } else { File.Move(text, _path); } return true; } catch (Exception ex) { Plugin.Log.LogError((object)("Лавка: счета не сохранены: " + ex.Message)); return false; } } public bool FindOnline(string name, out string id, out string shownName) { foreach (ZNetPeer peer in ZNet.instance.GetPeers()) { if (string.Equals(peer.m_playerName, name, StringComparison.OrdinalIgnoreCase)) { ISocket socket = peer.m_socket; id = ((socket != null) ? socket.GetHostName() : null) ?? ""; shownName = peer.m_playerName; return id != ""; } } id = ""; shownName = ""; return false; } public string HandleAdmin(string line, string admin, string adminId) { if (!_ready) { return "лавка не запущена: мир загружается, выключена настройкой или файл счетов повреждён (см. журнал)"; } string result = BankAdmin.Handle(line, _accounts, this); SendStateToAll(); return result; } public void Scan() { HashSet<ZRpc> live = new HashSet<ZRpc>(); foreach (ZNetPeer peer in ZNet.instance.GetPeers()) { if (peer?.m_rpc != null) { live.Add(peer.m_rpc); if (_registered.Add(peer.m_rpc)) { peer.m_rpc.Register<string, string>("sansara.TameLab.Request", (Action<ZRpc, string, string>)OnRequest); } } } _registered.RemoveWhere((ZRpc r) => !live.Contains(r)); } private void OnRequest(ZRpc rpc, string verb, string arg) { ZNetPeer val = ZNet.instance.GetPeers().Find((ZNetPeer p) => p.m_rpc == rpc); if (!_ready || val == null || !val.IsReady() || ((ZDOID)(ref val.m_characterID)).IsNone()) { return; } try { if (verb == "buy" && StableRules.TryParseBuy(arg, out var good, out var tier)) { Buy(val, good, tier); } SendState(val); } catch (Exception arg2) { Plugin.Log.LogError((object)$"Лавка: ошибка запроса {val.m_playerName}: {arg2}"); } } private void Buy(ZNetPeer peer, Good good, int tier) { //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: 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_0102: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Unknown result type (might be due to invalid IL or missing references) string id = Id(peer); long num = _prices[good.Prefab][tier]; int owned = StableRules.Goods.Sum((Good g) => Owned(id, g.Prefab)); Deal deal = StableRules.Decide(_accounts.Balance(id), num, owned, Plugin.StableLimit.Value, Near(peer)); if (deal != Deal.Buy) { Message(peer, StableRules.Refusal(deal, good, _accounts.Balance(id), num, Plugin.StableLimit.Value)); return; } _accounts.TrySpend(id, num); if (!Save()) { _accounts.Deposit(id, peer.m_playerName, num); Message(peer, "Покупка не удалась, монеты на месте."); return; } ZDO zDO = ZDOMan.instance.GetZDO(peer.m_characterID); try { Spawn(good.Prefab, zDO.GetPosition() + zDO.GetRotation() * Vector3.forward * 3f, StableRules.Level(tier), id, peer.m_playerName); } catch { _accounts.Deposit(id, peer.m_playerName, num); Save(); Message(peer, "Покупка не удалась, монеты на месте."); throw; } Plugin.Log.LogInfo((object)$"Лавка: {peer.m_playerName} ({id}) купил {good.Prefab} уровня {StableRules.Level(tier)} за {num}, на счету {_accounts.Balance(id)}."); Message(peer, StableRules.Bought(good, tier, _accounts.Balance(id))); } private bool Near(ZNetPeer peer) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) float value = Plugin.StableRadius.Value; if (value <= 0f) { return true; } ZDO zDO = ZDOMan.instance.GetZDO(peer.m_characterID); if (zDO != null && _chest != null) { return Vector3.Distance(zDO.GetPosition(), _chest.GetPosition()) <= value; } return false; } private void SendState(ZNetPeer peer) { string id = Id(peer); ShopState shopState = new ShopState { Balance = _accounts.Balance(id), Limit = Plugin.StableLimit.Value, Near = Near(peer) }; Good[] goods = StableRules.Goods; foreach (Good good in goods) { shopState.Items.Add(new ShopItem { Prefab = good.Prefab, Owned = Owned(id, good.Prefab), Prices = _prices[good.Prefab] }); } peer.m_rpc.Invoke("sansara.TameLab.State", new object[1] { shopState.Serialize() }); } private void SendStateToAll() { foreach (ZNetPeer peer in ZNet.instance.GetPeers()) { if (peer.IsReady() && !((ZDOID)(ref peer.m_characterID)).IsNone() && _registered.Contains(peer.m_rpc)) { SendState(peer); } } } public void Tick() { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) _found.Clear(); ZDOMan.instance.FindSectorObjects(ZoneSystem.GetZone(FameAnchor), new SimulationDistance(1, 0, true), _found, (List<ZDO>)null); _chest = ((IEnumerable<ZDO>)_found).FirstOrDefault((Func<ZDO, bool>)((ZDO z) => z.GetString("sansara.PortalHall.role", "") == "bank.chest")); ZDO val = ((IEnumerable<ZDO>)_found).FirstOrDefault((Func<ZDO, bool>)((ZDO z) => z.GetString("sansara.PortalHall.role", "") == "bank.sign")); if (_chest == null) { if (!_missingReported) { _missingReported = true; Plugin.Log.LogInfo((object)"Лавка: сундука счёта в Чертоге славы нет (нужен Portal Hall 0.5.0), жду."); } return; } _missingReported = false; if (val != null) { SetSign(val, "Счёт: кладите монеты. Покупки в окне F8"); } try { Process(_chest); } catch (Exception ex) { _handled = _chest.DataRevision; Plugin.Log.LogError((object)("Лавка: ошибка в сундуке счёта: " + ex)); } } private void Process(ZDO chest) { //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Expected O, but got Unknown //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Expected O, but got Unknown //IL_017f: Unknown result type (might be due to invalid IL or missing references) //IL_0186: Expected O, but got Unknown //IL_01b6: Unknown result type (might be due to invalid IL or missing references) if (chest.GetInt(ZDOVars.s_inUse, 0) == 1) { return; } byte[] byteArray = chest.GetByteArray(ZDOVars.s_items, (byte[])null); if (byteArray == null || byteArray.Length == 0 || _handled == chest.DataRevision) { return; } ZNetPeer val = FindDepositor(chest); if (val == null) { return; } GameObject prefab = ZNetScene.instance.GetPrefab(chest.GetPrefab()); Container val2 = ((prefab != null) ? prefab.GetComponent<Container>() : null); if ((Object)(object)val2 == (Object)null) { _handled = chest.DataRevision; return; } Inventory inventory = new Inventory("bank", (Sprite)null, val2.m_width, val2.m_height); inventory.Load(new ZPackage(byteArray)); List<ItemData> list = inventory.GetAllItems().ToList(); List<ItemData> list2 = list.Where((ItemData i) => (Object)(object)i.m_dropPrefab != (Object)null && ((Object)i.m_dropPrefab).name == "Coins").ToList(); long num = ((IEnumerable<ItemData>)list2).Sum((Func<ItemData, long>)((ItemData i) => i.m_stack)); if (num == 0L) { if (list.Count > 0) { Message(val, "Этот сундук принимает только монеты."); } _handled = chest.DataRevision; return; } string text = Id(val); _accounts.Deposit(text, val.m_playerName, num); if (!Save()) { _accounts.TrySpend(text, num); return; } list2.ForEach(delegate(ItemData c) { inventory.RemoveItem(c); }); ZPackage val3 = new ZPackage(); inventory.Save(val3); chest.SetOwner(ZDOMan.GetSessionID()); chest.Set(ZDOVars.s_items, val3.GetArray()); ZDOMan.instance.ForceSendZDO(chest.m_uid); _handled = chest.DataRevision; Plugin.Log.LogInfo((object)$"Лавка: {val.m_playerName} ({text}) внёс {num}, на счету {_accounts.Balance(text)}."); Message(val, StableRules.Deposited(num, _accounts.Balance(text))); if (_registered.Contains(val.m_rpc)) { SendState(val); } } private void Spawn(string prefabName, Vector3 pos, int level, string ownerId, string playerName) { //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) GameObject prefab = ZNetScene.instance.GetPrefab(prefabName); ZNetView component = prefab.GetComponent<ZNetView>(); int stableHashCode = StringExtensionMethods.GetStableHashCode(((Object)prefab).name); FieldInfo fieldInfo = EwpSkip(); object obj = fieldInfo?.GetValue(null); ZDO val; try { fieldInfo?.SetValue(null, true); val = ZDOMan.instance.CreateNewZDO(pos, stableHashCode); } finally { if (obj != null) { fieldInfo.SetValue(null, obj); } } val.Persistent = component.m_persistent; val.Type = component.m_type; val.Distant = component.m_distant; val.SetPrefab(stableHashCode); val.SetRotation(Quaternion.identity); val.Set(ZDOVars.s_level, level, false); val.Set(ZDOVars.s_tamed, true); val.Set(ZDOVars.s_follow, playerName); val.Set("sansara.TameLab.owner", ownerId); } private FieldInfo EwpSkip() { if (!_ewpLooked) { _ewpLooked = true; Type type = AccessTools.TypeByName("ExpandWorld.Prefab.HandleCreated"); _ewpSkip = ((type == null) ? null : AccessTools.Field(type, "Skip")); } return _ewpSkip; } private int Owned(string id, string prefab) { List<ZDO> list = new List<ZDO>(); int num = 0; while (!ZDOMan.instance.GetAllZDOsWithPrefabIterative(prefab, list, ref num)) { } return list.Count((ZDO z) => z.GetString("sansara.TameLab.owner", "") == id); } private static string Id(ZNetPeer peer) { ISocket socket = peer.m_socket; string text = ((socket != null) ? socket.GetHostName() : null) ?? ""; if (!(text != "")) { return "player:" + peer.m_uid; } return text; } private ZNetPeer FindDepositor(ZDO chest) { //IL_003d: Unknown result type (might be due to invalid IL or missing references) long owner = chest.GetOwner(); bool flag = owner == ZDOMan.GetSessionID(); 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) { continue; } long num = val.GetLong(ZDOVars.s_playerID, 0L); bool num2; if (!flag) { num2 = peer.m_uid == owner; } else { if (num != _lastDepositor) { continue; } num2 = num != 0; } if (num2) { _lastDepositor = num; return peer; } } return null; } private static void SetSign(ZDO sign, string text) { //IL_0047: Unknown result type (might be due to invalid IL or missing references) text = "<color=white>" + text + "</color>"; if (sign.GetString(ZDOVars.s_text, "") != text) { sign.SetOwner(ZDOMan.GetSessionID()); sign.Set(ZDOVars.s_text, text); ZDOMan.instance.ForceSendZDO(sign.m_uid); } } private static void Message(ZNetPeer peer, string text) { ZRoutedRpc.instance.InvokeRoutedRPC(peer.m_uid, "ShowMessage", new object[2] { 2, text }); } } } namespace TameLab.Core { public interface IBankHost { bool FindOnline(string name, out string id, out string shownName); bool Save(); } public static class BankAdmin { public static readonly string[] Verbs = new string[4] { "help", "list", "add", "take" }; private const string Help = "list — балансы всех игроков\nadd <игрок|@ID> <сумма> — пополнить счёт\ntake <игрок|@ID> <сумма> — списать со счёта"; public static string Handle(string line, CoinAccounts accounts, IBankHost host) { AdminCommand adminCommand = AdminCommand.Parse(line); switch (adminCommand.Verb) { case "help": return "list — балансы всех игроков\nadd <игрок|@ID> <сумма> — пополнить счёт\ntake <игрок|@ID> <сумма> — списать со счёта"; case "list": return List(accounts); case "add": case "take": return Change(adminCommand, accounts, host); default: return AdminProtocol.Unknown(adminCommand.Verb, Verbs); } } private static string List(CoinAccounts accounts) { if (accounts.Players.Count == 0) { return "счетов пока нет"; } IEnumerable<string> values = from p in accounts.Players orderby p.Value.Coins descending select $"{p.Value.Name} (@{p.Key}): {p.Value.Coins}"; return $"счетов {accounts.Players.Count}, всего монет {accounts.Players.Values.Sum((CoinAccount p) => p.Coins)}\n" + string.Join("\n", values); } private static string Change(AdminCommand c, CoinAccounts accounts, IBankHost host) { bool flag = c.Verb == "add"; if (c.Args.Count < 2 || !long.TryParse(c.Args[c.Args.Count - 1], NumberStyles.None, CultureInfo.InvariantCulture, out var result) || result <= 0) { return "укажите игрока и положительную сумму: " + c.Verb + " <игрок|@ID> <сумма>"; } string text = string.Join(" ", c.Args.Take(c.Args.Count - 1)); if (!Resolve(text, accounts, host, out var id, out var name)) { return "игрока «" + text + "» нет ни среди счетов, ни на сервере; для отключённого укажите @ID"; } if (flag) { accounts.Deposit(id, name, result); } else if (!accounts.TrySpend(id, result)) { return $"на счету {name} (@{id}) только {accounts.Balance(id)}, списание {result} не выполнено"; } if (!host.Save()) { if (flag) { accounts.TrySpend(id, result); } else { accounts.Deposit(id, name, result); } return "счета не сохранены, операция отменена; см. журнал"; } return string.Format("{0} {1}: {2} (@{3}), теперь на счету {4}", flag ? "пополнено" : "списано", result, name, id, accounts.Balance(id)); } private static bool Resolve(string who, CoinAccounts accounts, IBankHost host, out string id, out string name) { if (AdminCommand.TryId(who, out id)) { name = (accounts.Players.TryGetValue(id, out var value) ? value.Name : ""); return true; } string typed = who; string text = accounts.Players.Keys.FirstOrDefault((string k) => string.Equals(accounts.Players[k].Name, typed, StringComparison.OrdinalIgnoreCase)); if (text != null) { id = text; name = accounts.Players[text].Name; return true; } return host.FindOnline(who, out id, out name); } } public enum Deal { NeedCoins, LimitReached, TooFar, Buy } public sealed class Good { public string Prefab { get; } public string Name { get; } public string Description { get; } public string DefaultPrices { get; } public Good(string prefab, string name, string description, string prices) { Prefab = prefab; Name = name; Description = description; DefaultPrices = prices; } } public static class StableRules { public const int Tiers = 3; public const string ChestRole = "bank.chest"; public const string SignRole = "bank.sign"; public const string OnlyCoins = "Этот сундук принимает только монеты."; public const string SignText = "Счёт: кладите монеты. Покупки в окне F8"; public const float DefaultHealthMultiplier = 3f; public const float DefaultRegenMinutes = 15f; public static readonly Good[] Goods = new Good[2] { new Good("GoblinBrute", "Брут", "Брут фулингов", "10000,20000,30000"), new Good("SeekerBrute", "Искатель-солдат", "Искатель-солдат", "20000,40000,60000") }; public static float ScaleHealth(float health, float oldMax, float newMax) { if (oldMax <= 0f || health <= 0f) { if (!(health <= 0f)) { return newMax; } return 0f; } return ((health >= oldMax) ? 1f : (health / oldMax)) * newMax; } public static Good GoodOf(string prefab) { return Goods.FirstOrDefault((Good g) => g.Prefab == prefab); } public static int Level(int tier) { return tier + 1; } public static long[] ParsePrices(string text) { string[] array = (text ?? "").Split(new char[3] { ',', ';', ' ' }, StringSplitOptions.RemoveEmptyEntries); if (array.Length != 3) { return null; } long[] array2 = new long[3]; for (int i = 0; i < 3; i++) { if (!long.TryParse(array[i], NumberStyles.Integer, CultureInfo.InvariantCulture, out array2[i]) || array2[i] <= 0) { return null; } } return array2; } public static Deal Decide(long balance, long price, int owned, int limit, bool near) { if (!near) { return Deal.TooFar; } if (balance < price) { return Deal.NeedCoins; } if (owned < limit) { return Deal.Buy; } return Deal.LimitReached; } public static bool TryParseBuy(string arg, out Good good, out int tier) { good = null; tier = 0; string[] array = (arg ?? "").Split(new char[1] { ':' }); if (array.Length != 2 || !int.TryParse(array[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out tier) || tier < 0 || tier >= 3) { return false; } good = GoodOf(array[0]); return good != null; } public static string BuyArg(Good good, int tier) { return good.Prefab + ":" + tier.ToString(CultureInfo.InvariantCulture); } public static string Deposited(long coins, long balance) { return $"Зачислено монет: {coins}. На счету: {balance}."; } public static string Bought(Good good, int tier, long balance) { return $"{good.Name} {Stars(tier)} куплен. Остаток на счету: {balance}."; } public static string Refusal(Deal deal, Good good, long balance, long price, int limit) { return deal switch { Deal.TooFar => "Покупать можно только в Чертоге славы.", Deal.NeedCoins => $"На счету {balance}, не хватает {price - balance}.", _ => $"У вас уже достаточно купленных существ (предел {limit}).", }; } public static string Stars(int tier) { return tier switch { 0 => "без звёзд", 1 => "с 1 звездой", _ => $"с {tier} звёздами", }; } } public sealed class ShopItem { public string Prefab = ""; public int Owned; public long[] Prices = new long[3]; } public sealed class ShopState { public long Balance; public int Limit; public bool Near; public readonly List<ShopItem> Items = new List<ShopItem>(); public int TotalOwned => Items.Sum((ShopItem x) => x.Owned); public string Serialize() { CultureInfo i = CultureInfo.InvariantCulture; List<string> list = new List<string> { Balance.ToString(i), Limit.ToString(i), Near ? "1" : "0" }; list.AddRange(Items.Select((ShopItem x) => x.Prefab + "|" + x.Owned.ToString(i) + "|" + string.Join(",", x.Prices.Select((long p) => p.ToString(i))))); return string.Join(";", list); } public static ShopState Parse(string text) { string[] array = (text ?? "").Split(new char[1] { ';' }); CultureInfo invariantCulture = CultureInfo.InvariantCulture; ShopState shopState = new ShopState(); if (array.Length < 3 || !long.TryParse(array[0], NumberStyles.Integer, invariantCulture, out shopState.Balance) || !int.TryParse(array[1], NumberStyles.Integer, invariantCulture, out shopState.Limit) || (array[2] != "0" && array[2] != "1")) { return null; } shopState.Near = array[2] == "1"; for (int i = 3; i < array.Length; i++) { string[] array2 = array[i].Split(new char[1] { '|' }); ShopItem shopItem = new ShopItem(); if (array2.Length != 3 || array2[0] == "" || !int.TryParse(array2[1], NumberStyles.Integer, invariantCulture, out shopItem.Owned)) { return null; } shopItem.Prefab = array2[0]; shopItem.Prices = StableRules.ParsePrices(array2[2]); if (shopItem.Prices == null) { return null; } shopState.Items.Add(shopItem); } return shopState; } } }