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 EventRewards v0.5.14
plugins/EventRewards.dll
Decompiled 16 hours 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.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using System.Text.RegularExpressions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using EventRewards.Core; using EventRewards.Patches; using HarmonyLib; using Jotunn.Entities; using Jotunn.Managers; using Jotunn.Utils; using Microsoft.CodeAnalysis; using Newtonsoft.Json; using Newtonsoft.Json.Linq; 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("EventRewards")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.5.14.0")] [assembly: AssemblyInformationalVersion("0.5.14+05004fdaa4ffbb029f3dec5e3bf805d8eabc20a4")] [assembly: AssemblyProduct("EventRewards")] [assembly: AssemblyTitle("EventRewards")] [assembly: AssemblyVersion("0.5.14.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] internal sealed class IsReadOnlyAttribute : Attribute { } [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 EventRewards { internal static class DeliveryServer { private sealed class Presence { public ZDOID Character; public float Since; public Vector3 LastPos; public bool Still; public long PeerUid; public bool Local; } private sealed class Retry { public float Next; public float Delay; } internal struct OnlineCharacter { public long PlayerId; public long PeerUid; public bool Local; public int Code; public string Name; } private sealed class Parsed<T> { public bool Ok; public T Value; public string Error; } private const float TeleportJumpMeters = 50f; private const float StillMeters = 2f; private const float RosterSaveSeconds = 300f; private const float SpawnHeight = 1f; private const float RetryFirstSeconds = 10f; private const float RetryMaxSeconds = 300f; private const int RecoveredQueueReserve = 1000; private const int ZAnimBoolOffset = 438569; private static string _loadError; private static PlayerRoster _roster; private static DeliveryQueue _queue; private static float _timer; private static float _rosterSaveTimer; private static bool _rosterDirty; private static bool _queueDirty; private static int _inWaterKey; private static readonly Dictionary<long, Presence> Presences = new Dictionary<long, Presence>(); private static readonly HashSet<long> Online = new HashSet<long>(); private static readonly HashSet<int> WarnedEntries = new HashSet<int>(); private static readonly HashSet<string> HuntWarned = new HashSet<string>(StringComparer.Ordinal); private static readonly Dictionary<int, Retry> Retries = new Dictionary<int, Retry>(); private static readonly RateLimiter IdentityWarnings = new RateLimiter(1, 300.0); public static string RosterPath => Path.Combine(Paths.ConfigPath, "sansara.EventRewards.players.json"); public static string QueuePath => Path.Combine(Paths.ConfigPath, "sansara.EventRewards.queue.json"); public static bool Running { get; private set; } internal static PlayerRoster Roster => _roster; public static void Init() { Shutdown(); if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer()) { if (!LoadRoster(out var error) || !LoadQueue(out error)) { _loadError = error; Plugin.Log.LogError((object)("Event Rewards: " + error + "; список игроков и очередь доставки отключены до исправления файла")); } else { Running = true; Plugin.Log.LogInfo((object)($"Event Rewards: игроков в списке {_roster.Count}, в очереди доставки ожидают {_queue.Pending(null).Count}" + (PluginConfig.DeliveryEnabled.Value ? "" : " (доставка выключена в cfg)"))); } } } public static void Shutdown() { if (Running) { if (_rosterDirty) { SaveRoster(); } if (_queueDirty) { SaveQueue(); } } Running = false; _loadError = null; _roster = null; _queue = null; _timer = 0f; _rosterSaveTimer = 0f; _rosterDirty = false; _queueDirty = false; Presences.Clear(); Online.Clear(); WarnedEntries.Clear(); HuntWarned.Clear(); Retries.Clear(); } public static void Tick(float deltaTime) { _timer += deltaTime; _rosterSaveTimer += deltaTime; if (_timer < PluginConfig.DeliveryCheckIntervalSeconds.Value) { return; } _timer = 0f; try { Check(); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Event Rewards: ошибка проверки игроков и доставки: " + ex)); } } private static void Check() { //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)ZNet.instance == (Object)null || ZDOMan.instance == null) { return; } DateTime utcNow = DateTime.UtcNow; float realtimeSinceStartup = Time.realtimeSinceStartup; Online.Clear(); foreach (ZNetPeer peer in ZNet.instance.GetPeers()) { ZDO val = CharacterOf(peer); if (val != null) { Observe(val, peer.m_characterID, peer.m_playerID, peer.m_playerName, HostOf(peer), peer.m_uid, local: false, utcNow, realtimeSinceStartup); } } if (!ZNet.instance.IsDedicated() && (Object)(object)Player.m_localPlayer != (Object)null) { ZDO zDO = ZDOMan.instance.GetZDO(((Character)Player.m_localPlayer).GetZDOID()); if (zDO != null) { ZDOID uid = zDO.m_uid; Game instance = Game.instance; long? obj; if (instance == null) { obj = null; } else { PlayerProfile playerProfile = instance.GetPlayerProfile(); obj = ((playerProfile != null) ? new long?(playerProfile.GetPlayerID()) : ((long?)null)); } long? num = obj; long valueOrDefault = num.GetValueOrDefault(); Game instance2 = Game.instance; object fallbackName; if (instance2 == null) { fallbackName = null; } else { PlayerProfile playerProfile2 = instance2.GetPlayerProfile(); fallbackName = ((playerProfile2 != null) ? playerProfile2.GetName() : null); } Observe(zDO, uid, valueOrDefault, (string)fallbackName, "local", ZDOMan.GetSessionID(), local: true, utcNow, realtimeSinceStartup); } } foreach (long item in Presences.Keys.Where((long id) => !Online.Contains(id)).ToList()) { Presences.Remove(item); } RevocationServer.OnCheck(OnlineCharacters()); if (_queueDirty) { SaveQueue(); } if (_rosterDirty && _rosterSaveTimer >= 300f) { SaveRoster(); } if (!PluginConfig.DeliveryEnabled.Value || _queueDirty) { return; } foreach (long item2 in Online.ToList()) { if (_queue.HasPending(item2)) { TryDeliver(item2, utcNow, realtimeSinceStartup); } } } private static ZDO CharacterOf(ZNetPeer peer) { //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) if (peer == null || !peer.IsReady() || ((ZDOID)(ref peer.m_characterID)).IsNone() || ZDOMan.instance == null) { return null; } if (((ZDOID)(ref peer.m_characterID)).UserID != peer.m_uid) { WarnIdentity(peer.m_uid, $"Event Rewards: {RewardLoss.Sanitize(peer.m_playerName)} ({peer.m_uid}) сообщил чужой ZDOID персонажа {peer.m_characterID}, пропущен"); return null; } ZDO zDO = ZDOMan.instance.GetZDO(peer.m_characterID); if (zDO == null) { return null; } if (zDO.GetOwner() != peer.m_uid) { return null; } return zDO; } private static void Observe(ZDO zdo, ZDOID character, long fallbackId, string fallbackName, string host, long uid, bool local, DateTime now, float t) { //IL_0139: Unknown result type (might be due to invalid IL or missing references) //IL_013e: Unknown result type (might be due to invalid IL or missing references) //IL_0162: Unknown result type (might be due to invalid IL or missing references) //IL_0163: Unknown result type (might be due to invalid IL or missing references) //IL_0171: Unknown result type (might be due to invalid IL or missing references) //IL_0172: Unknown result type (might be due to invalid IL or missing references) //IL_014f: Unknown result type (might be due to invalid IL or missing references) //IL_0154: Unknown result type (might be due to invalid IL or missing references) //IL_01c5: Unknown result type (might be due to invalid IL or missing references) //IL_01c6: Unknown result type (might be due to invalid IL or missing references) //IL_018d: Unknown result type (might be due to invalid IL or missing references) //IL_018f: Unknown result type (might be due to invalid IL or missing references) //IL_0194: Unknown result type (might be due to invalid IL or missing references) //IL_0199: Unknown result type (might be due to invalid IL or missing references) long num = zdo.GetLong(ZDOVars.s_playerID, 0L); if (num == 0L) { num = fallbackId; } if (num == 0L) { return; } string text = zdo.GetString(ZDOVars.s_playerName, ""); if (string.IsNullOrWhiteSpace(text)) { text = fallbackName; } switch (_roster.Touch(num, text, host, now)) { case RosterChange.HostMismatch: { _roster.TryGetById(num, out var entry2); WarnIdentity(num, $"Event Rewards: персонаж {RewardLoss.Sanitize(text)} ({uid}, платформа «{host}») выдаёт себя за " + PlayerCode.Format(entry2.Code) + " " + entry2.Name + ", привязанного к другой платформе; не учитывается (если это тот же игрок — eventreward players unbind " + PlayerCode.Format(entry2.Code) + ")"); return; } case RosterChange.New: { _roster.TryGetById(num, out var entry); Plugin.Log.LogInfo((object)("Event Rewards: новый игрок " + PlayerCode.Format(entry.Code) + " " + entry.Name)); SaveRoster(); break; } case RosterChange.Updated: SaveRoster(); break; case RosterChange.Seen: _rosterDirty = true; break; } Online.Add(num); Vector3 position = zdo.GetPosition(); if (!Presences.TryGetValue(num, out var value) || value.Character != character) { value = new Presence { Character = character, Since = t, LastPos = position, Still = false }; Presences[num] = value; } else { Vector3 val = position - value.LastPos; float magnitude = ((Vector3)(ref val)).magnitude; if (magnitude > 50f) { value.Since = t; } value.Still = magnitude < 2f; } value.LastPos = position; value.PeerUid = uid; value.Local = local; } internal static bool IsOnline(long playerId) { if (Running) { return Online.Contains(playerId); } return false; } internal static List<OnlineCharacter> OnlineCharacters() { List<OnlineCharacter> list = new List<OnlineCharacter>(); if (!Running) { return list; } foreach (KeyValuePair<long, Presence> presence in Presences) { if (Online.Contains(presence.Key)) { _roster.TryGetById(presence.Key, out var entry); list.Add(new OnlineCharacter { PlayerId = presence.Key, PeerUid = presence.Value.PeerUid, Local = presence.Value.Local, Code = (entry?.Code ?? 0), Name = (entry?.Name ?? "?") }); } } return list; } internal static bool TryGetOnlineByPeer(long peerUid, out OnlineCharacter character) { foreach (OnlineCharacter item in OnlineCharacters()) { if (item.PeerUid == peerUid) { character = item; return true; } } character = default(OnlineCharacter); return false; } internal static List<int> PendingWith(ICollection<string> rewardIds, long playerId) { List<int> list = new List<int>(); if (!Running || rewardIds == null) { return list; } foreach (DeliveryEntry item in _queue.Pending(null)) { if (rewardIds.Contains(item.RewardId) && (playerId == 0L || item.PlayerId == playerId)) { list.Add(item.N); } } return list; } public static string HostOf(ZNetPeer peer) { try { object obj; if (peer == null) { obj = null; } else { ISocket socket = peer.m_socket; obj = ((socket != null) ? socket.GetHostName() : null); } if (obj == null) { obj = ""; } return (string)obj; } catch (Exception) { return ""; } } public static long PlayerIdOf(ZNetPeer peer) { ZDO val = CharacterOf(peer); long num = ((val != null) ? val.GetLong(ZDOVars.s_playerID, 0L) : 0); if (num == 0L) { if (val == null) { return 0L; } return peer.m_playerID; } return num; } private static void WarnIdentity(long key, string text) { if (IdentityWarnings.TryAcquire(key, Time.realtimeSinceStartup)) { Plugin.Log.LogWarning((object)text); } } private static bool InWater(ZDO zdo) { try { if (_inWaterKey == 0) { _inWaterKey = 438569 + ZSyncAnimation.GetHash("inWater"); } return zdo.GetInt(_inWaterKey, 0) != 0; } catch (Exception) { return false; } } private static void TryDeliver(long playerId, DateTime now, float t) { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Unknown result type (might be due to invalid IL or missing references) //IL_0117: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Unknown result type (might be due to invalid IL or missing references) if (!Presences.TryGetValue(playerId, out var value) || t - value.Since < PluginConfig.DeliverySettleSeconds.Value || !value.Still) { return; } ZDO zDO = ZDOMan.instance.GetZDO(value.Character); if (zDO == null || zDO.GetBool(ZDOVars.s_dead, false) || InWater(zDO)) { return; } foreach (DeliveryEntry item in _queue.PendingFor(playerId)) { if (!ServerCatalog.Current.TryGet(item.RewardId, out var definition)) { if (WarnedEntries.Add(item.N)) { Plugin.Log.LogWarning((object)($"Event Rewards: #{item.N} {item.RewardId} — такой награды нет в наборе сервера, запись ждёт " + $"(исправьте набор и eventreward reload или eventreward cancel {item.N})")); } } else { if (Retries.TryGetValue(item.N, out var value2) && t < value2.Next) { continue; } string error; ZDO val = SpawnReward(definition, playerId, zDO.GetPosition() + Vector3.up * 1f, out error); if (val != null) { Retries.Remove(item.N); _queue.MarkDelivered(item.N, now); RosterEntry entry; string text = (_roster.TryGetById(playerId, out entry) ? entry.Name : "?"); if (!SaveQueue()) { ZDOMan.instance.DestroyZDO(val); _queue.Unmark(item.N); Plugin.Log.LogError((object)($"Event Rewards: #{item.N} {item.RewardId} → {PlayerCode.Format(item.Code)} {text}: файл очереди " + "не записан — предмет убран, запись снова ожидает")); break; } Plugin.Log.LogInfo((object)DeliveryText.DeliveredLog(item, text)); string text2 = DeliveryText.FormatMessage(PluginConfig.DeliveryMessage.Value, definition.Name); if (text2.Length <= 0) { break; } if (value.Local) { MessageHud instance = MessageHud.instance; if (instance != null) { instance.ShowMessage((MessageType)2, text2, 0, (Sprite)null, false, true); } break; } ZRoutedRpc instance2 = ZRoutedRpc.instance; if (instance2 != null) { instance2.InvokeRoutedRPC(value.PeerUid, "ShowMessage", new object[2] { 2, text2 }); } break; } float num = ((value2 == null) ? 10f : Math.Min(value2.Delay * 2f, 300f)); Retries[item.N] = new Retry { Next = t + num, Delay = num }; if (WarnedEntries.Add(item.N)) { Plugin.Log.LogWarning((object)$"Event Rewards: #{item.N} {item.RewardId} не выложена: {error}; повтор с паузой до {300f:0} с"); } } } } private static ZDO SpawnReward(RewardDefinition def, long recipient, Vector3 position, out string error) { //IL_0196: Unknown result type (might be due to invalid IL or missing references) //IL_01af: Unknown result type (might be due to invalid IL or missing references) //IL_01e9: Unknown result type (might be due to invalid IL or missing references) error = null; GameObject val = (((Object)(object)ObjectDB.instance != (Object)null) ? ObjectDB.instance.GetItemPrefab(def.BasePrefab) : null); ItemDrop val2 = (((Object)(object)val != (Object)null) ? val.GetComponent<ItemDrop>() : null); ZNetView val3 = (((Object)(object)val != (Object)null) ? val.GetComponent<ZNetView>() : null); if ((Object)(object)val2 == (Object)null || (Object)(object)val3 == (Object)null) { error = "предмет " + def.BasePrefab + " не найден в ObjectDB"; return null; } int stableHashCode = StringExtensionMethods.GetStableHashCode(((Object)val).name); if ((Object)(object)ZNetScene.instance == (Object)null || (Object)(object)ZNetScene.instance.GetPrefab(stableHashCode) == (Object)null) { error = "префаб " + ((Object)val).name + " не зарегистрирован в ZNetScene"; return null; } ItemData val4; try { val4 = val2.m_itemData.Clone(); val4.m_dropPrefab = val; val4.m_stack = 1; val4.m_quality = def.Quality; val4.m_variant = def.Variant; val4.m_durability = BonusCalculator.ApplyMaxDurability(val4.GetMaxDurability(), BonusCalculator.EffectiveDurabilityPercent(def)); val4.m_crafterID = 0L; val4.m_crafterName = ""; val4.m_worldLevel = (byte)Game.m_worldLevel; val4.m_pickedUp = false; val4.m_cheated = false; val4.m_equipped = false; val4.m_customData = new Dictionary<string, string> { ["sansara.EventRewards.id"] = def.Id }; RewardTag.SetOwner(val4.m_customData, recipient); } catch (Exception ex) { error = "данные предмета не собраны: " + ex.Message; return null; } ZDO val5 = null; try { val5 = ZDOMan.instance.CreateNewZDO(position, stableHashCode); val5.Persistent = val3.m_persistent; val5.Type = val3.m_type; val5.Distant = val3.m_distant; val5.SetPrefab(stableHashCode); val5.SetRotation(Quaternion.Euler(0f, Random.Range(0f, 360f), 0f)); val5.Set(ZDOVars.s_spawnTime, ZNet.instance.GetTime().Ticks); ItemDrop.SaveToZDO(val4, val5, -1); return val5; } catch (Exception ex2) { if (val5 != null) { ZDOMan.instance.DestroyZDO(val5); } error = "ZDO предмета не создан: " + ex2.Message; return null; } } public static bool EnqueueHunt(HuntState state, HuntSettings settings) { if (state == null || settings == null || !state.Completed || !settings.AutoDeliver) { return false; } if (!Running) { if (HuntWarned.Add("off:" + state.Id)) { Plugin.Log.LogError((object)("Event Hunt: автовыдача " + settings.RewardId + " невозможна — очередь доставки отключена (" + (_loadError ?? "не сервер") + ")")); } return false; } if (!ServerCatalog.Current.TryGet(settings.RewardId, out var _)) { if (HuntWarned.Add("reward:" + state.Id + ":" + settings.RewardId)) { Plugin.Log.LogError((object)("Event Hunt: награды " + settings.RewardId + " нет в наборе сервера — участники не поставлены в очередь (исправьте набор, eventreward reload; очередь пополнится при следующем изменении или перезапуске)")); } return false; } HuntEnqueueResult huntEnqueueResult = HuntDelivery.Enqueue(state, settings.RewardId, _queue, _roster, DateTime.UtcNow); if (huntEnqueueResult.RosterChanged) { SaveRoster(); } if (huntEnqueueResult.Added.Count > 0) { if (!SaveQueue()) { Plugin.Log.LogError((object)"Event Hunt: очередь не записана — новые записи охоты сохранятся при следующей успешной записи"); } foreach (DeliveryEntry item in huntEnqueueResult.Added) { Plugin.Log.LogInfo((object)DeliveryText.EnqueuedLog(item, _roster.TryGetById(item.PlayerId, out var entry) ? entry.Name : "?")); } } foreach (KeyValuePair<long, HuntPlayer> conflict in huntEnqueueResult.Conflicts) { if (HuntWarned.Add("conflict:" + state.Id + ":" + conflict.Key)) { Plugin.Log.LogWarning((object)("Event Hunt: участник " + RewardLoss.Sanitize(conflict.Value.Name) + " (платформа «" + conflict.Value.Host + "») сдавал с ID персонажа, который в списке игроков привязан к другой платформе, — не поставлен в очередь, проверьте и выдайте вручную")); } } List<KeyValuePair<string, int>> list = HuntDelivery.ManualOnly(state); if (list.Count > 0 && HuntWarned.Add("manual:" + state.Id + ":" + list.Count)) { Plugin.Log.LogWarning((object)("Event Hunt: участники без ID персонажа (вклад записан до 0.5.11) — выдать " + settings.RewardId + " вручную: " + string.Join(", ", list.Select((KeyValuePair<string, int> m) => $"{m.Key} ({m.Value})")))); } return huntEnqueueResult.StateChanged; } public static string DescribeHuntRecipient(long playerId, string huntId) { if (!Running) { return ""; } RosterEntry entry; string text = (_roster.TryGetById(playerId, out entry) ? (PlayerCode.Format(entry.Code) + " ") : ""); string text2 = DeliveryReason.Hunt(huntId); foreach (DeliveryEntry entry2 in _queue.Entries) { if (entry2.PlayerId == playerId && !(entry2.Reason != text2)) { string arg = ((entry2.State == DeliveryState.Delivered) ? "доставлено" : ((entry2.State == DeliveryState.Cancelled) ? "отменено" : "в очереди")); return $"{text}{arg} #{entry2.N}"; } } return text + "не в очереди"; } public static string HandleCommand(string sub, string arg1, string arg2) { if (!Running) { return "eventreward " + sub + ": " + (_loadError ?? "работает только на сервере"); } switch (sub) { case "players": { string text = (arg1 ?? "").Trim(); if (text == "unbind" || text.StartsWith("unbind ", StringComparison.Ordinal)) { return Unbind(text.Substring("unbind".Length).Trim()); } return DeliveryText.Players(_roster, Online, text, ServerOffset()); } case "give": return Give(arg1, arg2); case "queue": return QueueList(arg1); case "cancel": return Cancel(arg1); case "revoke": case "unrevoke": return RevocationServer.HandleCommand(sub, arg1, arg2); default: return "eventreward: неизвестная команда '" + sub + "'"; } } private static string Unbind(string codeArg) { if (!PlayerCode.TryParse(codeArg, out var code)) { return "eventreward players unbind: укажите код игрока, например eventreward players unbind @3"; } if (!_roster.TryGetByCode(code, out var entry)) { return "eventreward players unbind: нет игрока с кодом " + PlayerCode.Format(code); } string host = entry.Host; _roster.Unbind(code); SaveRoster(); Plugin.Log.LogInfo((object)("Event Rewards: привязка " + PlayerCode.Format(code) + " " + entry.Name + " к платформе «" + host + "» снята")); return "eventreward players unbind: привязка " + PlayerCode.Format(code) + " " + entry.Name + " к платформе «" + host + "» снята; персонаж привяжется к платформе, с которой войдёт первым"; } private static string Give(string idList, string codeArg) { List<RewardDefinition> defs; RosterEntry player; string text = GiveCommand.ValidateDelivery(idList, codeArg, ServerCatalog.Current, _roster, out defs, out player); if (text != null) { return "eventreward give: " + text; } DateTime now = DateTime.UtcNow; List<DeliveryEntry> list = defs.Select((RewardDefinition d) => _queue.Enqueue(d.Id, player.PlayerId, player.Code, "admin", now)).ToList(); bool flag = SaveQueue(); foreach (DeliveryEntry item in list) { Plugin.Log.LogInfo((object)DeliveryText.EnqueuedLog(item, player.Name)); } StringBuilder stringBuilder = new StringBuilder("eventreward give: в очереди "); stringBuilder.Append(string.Join(", ", list.Select((DeliveryEntry e) => $"#{e.N} {e.RewardId}"))); stringBuilder.Append(" → " + PlayerCode.Format(player.Code) + " " + player.Name + "; "); stringBuilder.Append((!PluginConfig.DeliveryEnabled.Value) ? "доставка выключена в cfg ([Delivery (server)] Enabled)" : (Online.Contains(player.PlayerId) ? "игрок в игре — награда появится у его ног, когда он постоит на месте несколько секунд" : "игрок не в игре — доставка при следующем входе")); if (!flag) { stringBuilder.Append("; ВНИМАНИЕ: файл очереди не записан, доставка приостановлена — см. журнал сервера"); } return stringBuilder.ToString(); } private static string QueueList(string codeArg) { int? code = null; if (!string.IsNullOrWhiteSpace(codeArg)) { if (!PlayerCode.TryParse(codeArg.Trim(), out var code2)) { return "eventreward queue: код игрока '" + codeArg + "' должен быть вида @N (см. eventreward players)"; } code = code2; } return DeliveryText.Queue(_queue, _roster, ServerCatalog.Current, code, ServerOffset()); } private static string Cancel(string arg) { if (!DeliveryQueue.TryParseNumber((arg ?? "").Trim(), out var n)) { return "eventreward cancel: укажите номер записи, например eventreward cancel 12 (см. eventreward queue)"; } _queue.TryGet(n, out var entry); switch (_queue.Cancel(n, DateTime.UtcNow)) { case CancelResult.NotFound: return $"eventreward cancel: записи #{n} нет"; case CancelResult.AlreadyDelivered: return $"eventreward cancel: #{n} уже доставлена {DeliveryText.FormatTime(entry.ClosedAt, ServerOffset())}"; case CancelResult.AlreadyCancelled: return $"eventreward cancel: #{n} уже отменена"; default: { Retries.Remove(n); bool flag = SaveQueue(); RosterEntry entry2; string text = (_roster.TryGetById(entry.PlayerId, out entry2) ? entry2.Name : "?"); Plugin.Log.LogInfo((object)$"Event Rewards: отменено #{entry.N} {entry.RewardId} → {PlayerCode.Format(entry.Code)} {text}"); return $"eventreward cancel: #{entry.N} {entry.RewardId} → {PlayerCode.Format(entry.Code)} {text} отменена" + (flag ? "" : "; ВНИМАНИЕ: файл очереди не записан — см. журнал сервера"); } } } private static TimeSpan ServerOffset() { return TimeZoneInfo.Local.GetUtcOffset(DateTime.UtcNow); } private static bool LoadFile<T>(string path, string what, Func<string, Parsed<T>> parse, Func<T> empty, out T value, out bool recovered, out string error) { value = default(T); recovered = false; error = null; try { if (File.Exists(path)) { Parsed<T> parsed = parse(File.ReadAllText(path)); if (!parsed.Ok) { error = what + " " + path + " не прочитан(а): " + parsed.Error; return false; } value = parsed.Value; return true; } List<string> list = ServerFiles.RecoveryCandidates(path); if (list.Count == 0) { value = empty(); Plugin.Log.LogInfo((object)("Event Rewards: файла " + path + " нет, " + what + " пуст(а) (создастся при первой записи)")); return true; } foreach (string item in list) { Parsed<T> parsed2 = parse(File.ReadAllText(item)); if (!parsed2.Ok) { Plugin.Log.LogWarning((object)("Event Rewards: " + item + " не прочитан: " + parsed2.Error)); continue; } value = parsed2.Value; recovered = true; Plugin.Log.LogWarning((object)("Event Rewards: файла " + path + " нет — " + what + " восстановлен(а) из " + item)); return true; } error = "файла " + path + " нет, а " + string.Join(" и ", list) + " не читаются — " + what + " не восстановлен(а); исправьте или удалите эти файлы"; return false; } catch (Exception ex) { error = what + " " + path + " не прочитан(а): " + ex.Message; return false; } } private static bool LoadRoster(out string error) { if (!LoadFile(RosterPath, "список игроков", delegate(string json) { RosterResult rosterResult = PlayerRosterJson.Parse(json); return new Parsed<PlayerRoster> { Ok = rosterResult.Success, Value = rosterResult.Roster, Error = (rosterResult.Success ? null : rosterResult.Errors[0]) }; }, () => new PlayerRoster(), out _roster, out var recovered, out error)) { return false; } if (recovered) { SaveRoster(); } return true; } private static bool LoadQueue(out string error) { if (!LoadFile(QueuePath, "очередь доставки", delegate(string json) { DeliveryQueueResult deliveryQueueResult = DeliveryQueueJson.Parse(json); return new Parsed<DeliveryQueue> { Ok = deliveryQueueResult.Success, Value = deliveryQueueResult.Queue, Error = (deliveryQueueResult.Success ? null : deliveryQueueResult.Errors[0]) }; }, () => new DeliveryQueue(), out _queue, out var recovered, out error)) { return false; } if (recovered) { _queue.ReserveNumbers(1000); Plugin.Log.LogWarning((object)($"Event Rewards: номера записей очереди продолжатся с #{_queue.NextN}; записи, сделанные после " + "резервной копии, потеряны — проверьте журнал сервера (строки «в очереди» и «доставлено»)")); if (!SaveQueue()) { error = "восстановленная очередь доставки не записана"; return false; } } return true; } private static void SaveRoster() { try { ServerFiles.WriteAtomic(RosterPath, PlayerRosterJson.Serialize(_roster)); _rosterDirty = false; _rosterSaveTimer = 0f; } catch (Exception ex) { _rosterDirty = true; Plugin.Log.LogError((object)("Event Rewards: список игроков не сохранён в " + RosterPath + ": " + ex.Message)); } } private static bool SaveQueue() { try { ServerFiles.WriteAtomic(QueuePath, DeliveryQueueJson.Serialize(_queue)); if (_queueDirty) { Plugin.Log.LogInfo((object)"Event Rewards: очередь доставки снова сохраняется, доставка продолжена"); } _queueDirty = false; return true; } catch (Exception ex) { if (!_queueDirty) { Plugin.Log.LogError((object)("Event Rewards: очередь доставки не сохранена в " + QueuePath + ": " + ex.Message + "; доставка приостановлена до успешной записи")); } _queueDirty = true; return false; } } } internal sealed class EventRewardCommand : ConsoleCommand { public override string Name => "eventreward"; public override string Help => "list | give <id>[,<id>...] [count] | give <id>[,<id>...] @<код> | players [фильтр] | players unbind @<код> | queue [@код] | cancel <№> | revoke <id>[,<id>...] @<код>|all | revoke list | unrevoke #<№> | reload"; public override List<string> CommandOptionList() { return new List<string> { "list", "give", "players", "queue", "cancel", "revoke", "unrevoke", "reload" }; } public override void Run(string[] args) { switch ((args.Length != 0) ? args[0].ToLowerInvariant() : "") { case "list": List(); break; case "give": Give(args); break; case "reload": Reload(); break; case "players": Remote("players", string.Join(" ", args.Skip(1)).Trim(), ""); break; case "queue": Remote("queue", (args.Length > 1) ? args[1] : "", ""); break; case "cancel": Remote("cancel", (args.Length > 1) ? args[1] : "", ""); break; case "revoke": Revoke(args); break; case "unrevoke": Unrevoke(args); break; default: Print("Использование: eventreward " + ((ConsoleCommand)this).Help); break; } } private static void List() { RewardCatalog catalog = RewardState.Catalog; if (catalog.Count == 0) { Print("Наград нет (сервер без мода или пустой набор)"); return; } foreach (RewardDefinition item in catalog.All) { Print(string.Format("{0}: {1} ({2}, качество {3}{4})", item.Id, item.Name, item.BasePrefab, item.Quality, (item.Variant != 0) ? (", вариант " + item.Variant) : "")); } } private static void Give(string[] args) { if (!SynchronizationManager.Instance.PlayerIsAdmin) { Print("Отказано: нужны права администратора"); return; } GiveRequest giveRequest = GiveCommand.Parse(args.Skip(1).ToList()); if (!giveRequest.Ok) { Print(giveRequest.Error); return; } if (giveRequest.Code.HasValue) { Remote("give", giveRequest.IdList, PlayerCode.Format(giveRequest.Code.Value)); return; } List<string> list = GiveCommand.UnknownIds(giveRequest.Ids, RewardState.Catalog); if (list.Count > 0) { Print(GiveCommand.FormatUnknown(list)); return; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { Print("Нет персонажа"); return; } Inventory inventory = ((Humanoid)localPlayer).GetInventory(); List<string> list2 = new List<string>(); bool flag = false; foreach (string id in giveRequest.Ids) { RewardState.Catalog.TryGet(id, out var definition); int i; for (i = 0; i < giveRequest.Count; i++) { if (!inventory.HaveEmptySlot()) { Print("Нет места в инвентаре"); flag = true; break; } ItemData val = inventory.AddItem(definition.BasePrefab, 1, definition.Quality, definition.Variant, 0L, "", false, false); if (val == null) { Print("Не удалось создать " + definition.BasePrefab); flag = true; break; } val.m_customData["sansara.EventRewards.id"] = definition.Id; DurabilityScale.FillToMax(val); } if (i > 0) { list2.Add($"{definition.Name} × {i}"); } if (flag) { break; } } RewardState.MarkDirty(); Print("Выдано: " + ((list2.Count > 0) ? string.Join(", ", list2) : "ничего")); } private static void Revoke(string[] args) { RevokeRequest revokeRequest = RevokeCommand.ParseRevoke(args.Skip(1).ToList()); if (!revokeRequest.Ok) { Print(revokeRequest.Error); } else if (revokeRequest.Action == RevokeAction.List) { Remote("revoke", "list", "", "0.5.14"); } else { Remote("revoke", revokeRequest.IdList, revokeRequest.Target, "0.5.14"); } } private static void Unrevoke(string[] args) { RevokeRequest revokeRequest = RevokeCommand.ParseUnrevoke(args.Skip(1).ToList()); if (!revokeRequest.Ok) { Print(revokeRequest.Error); } else { Remote("unrevoke", "#" + revokeRequest.N, "", "0.5.14"); } } private static void Reload() { if ((Object)(object)ZNet.instance == (Object)null) { Print("Нет подключения к миру"); } else if (ZNet.instance.IsServer()) { Print("eventreward reload: " + ServerCatalog.Reload()); RewardNetwork.BroadcastCatalog(); } else if (!SynchronizationManager.Instance.PlayerIsAdmin) { Print("Отказано: нужны права администратора"); } else if (!ModCompatibility.IsModuleOnServer("sansara.EventRewards")) { Print("На сервере нет мода Event Rewards"); } else if (!RewardNetwork.RequestReload()) { Print("Нет подключения к серверу"); } else { Print("Запрос отправлен на сервер"); } } private static void Remote(string sub, string arg1, string arg2, string minServer = "0.5.11") { if ((Object)(object)ZNet.instance == (Object)null) { Print("Нет подключения к миру"); } else if (ZNet.instance.IsServer()) { Print(DeliveryServer.HandleCommand(sub, arg1, arg2)); } else if (!SynchronizationManager.Instance.PlayerIsAdmin) { Print("Отказано: нужны права администратора"); } else if (!ModCompatibility.IsModuleOnServer("sansara.EventRewards")) { Print("На сервере нет мода Event Rewards"); } else if (!RewardNetwork.RequestDelivery(sub, arg1, arg2)) { Print("Нет подключения к серверу"); } else { Print("Запрос отправлен на сервер (нужен Event Rewards " + minServer + " или новее на сервере)"); } } private static void Print(string text) { if ((Object)(object)Console.instance != (Object)null) { Console.instance.Print(text); } } } internal sealed class HuntCommand : ConsoleCommand { private static readonly List<string> Subcommands = new List<string> { "setchest", "start", "stop", "status" }; public override string Name => "eventhunt"; public override string Help => "setchest | start | stop | status (ивент «Охота», только администратор)"; public override List<string> CommandOptionList() { return Subcommands; } public override void Run(string[] args) { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) string text = ((args.Length != 0) ? args[0].ToLowerInvariant() : ""); if (!Subcommands.Contains(text)) { Print("Использование: eventhunt setchest | start | stop | status"); return; } if ((Object)(object)ZNet.instance == (Object)null) { Print("Нет подключения к миру"); return; } ZDOID id = ZDOID.None; if (text == "setchest" && !TryGetHoveredContainer(out id)) { Print("eventhunt setchest: наведите курсор на сундук"); } else if (ZNet.instance.IsServer()) { Print(HuntServer.HandleCommand(text, id)); } else if (!SynchronizationManager.Instance.PlayerIsAdmin) { Print("Отказано: нужны права администратора"); } else if (!ModCompatibility.IsModuleOnServer("sansara.EventRewards")) { Print("На сервере нет мода Event Rewards"); } else if (!RewardNetwork.RequestHunt(text, id)) { Print("Нет подключения к серверу"); } else { Print("Запрос отправлен на сервер (нужен Event Rewards 0.5.1 или новее на сервере)"); } } private static bool TryGetHoveredContainer(out ZDOID id) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) id = ZDOID.None; GameObject val = (((Object)(object)Player.m_localPlayer != (Object)null) ? ((Humanoid)Player.m_localPlayer).GetHoverObject() : null); if ((Object)(object)val == (Object)null) { return false; } Container componentInParent = val.GetComponentInParent<Container>(); if ((Object)(object)componentInParent == (Object)null) { return false; } ZNetView componentInParent2 = ((Component)componentInParent).GetComponentInParent<ZNetView>(); if ((Object)(object)componentInParent2 == (Object)null || !componentInParent2.IsValid()) { return false; } id = componentInParent2.GetZDO().m_uid; return true; } private static void Print(string text) { if ((Object)(object)Console.instance != (Object)null) { Console.instance.Print(text); } } } internal static class HuntServer { private const int StatusDecreases = 5; public static bool Watching; public static ZDOID ChestId = ZDOID.None; private static bool _running; private static string _loadError; private static HuntSettings _settings; private static HuntState _state; private static int _trophyHash; private static uint _lastRevision; private static bool _haveRevision; private static float _timer; private static bool _missingChestWarned; private static bool _oldFormatWarned; private static readonly ItemData Scratch = new ItemData(); private static readonly List<ZDO> SearchBuffer = new List<ZDO>(); private const float ChestMatchDistance = 0.5f; public static string SettingsPath => Path.Combine(Paths.ConfigPath, "sansara.EventRewards.hunt.json"); public static string StatePath => Path.Combine(Paths.ConfigPath, "sansara.EventRewards.hunt.state.json"); public static void Init() { Shutdown(); if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer()) { return; } _running = true; if (!LoadSettings(out var error)) { _loadError = error; Plugin.Log.LogError((object)("Event Hunt: " + error)); return; } if (!LoadState(out error)) { _loadError = error; Plugin.Log.LogError((object)("Event Hunt: " + error)); return; } UpdateWatch(); Plugin.Log.LogInfo((object)("Event Hunt: id " + _state.Id + ", " + (_state.Active ? "идёт" : "не запущен") + ", " + string.Format("прогресс {0} / {1}, сундук {2}", _state.Best, _settings.Goal, (_state.Chest != null) ? "задан" : "не задан") + (_settings.AutoDeliver ? (", автовыдача " + _settings.RewardId) : ""))); if (DeliveryServer.EnqueueHunt(_state, _settings)) { Save(); } } public static void Shutdown() { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) _running = false; Watching = false; ChestId = ZDOID.None; _settings = null; _state = null; _loadError = null; _haveRevision = false; _timer = 0f; _missingChestWarned = false; _oldFormatWarned = false; } public static void Tick(float deltaTime) { if (!Watching) { return; } _timer += deltaTime; if (_timer < _settings.CheckIntervalSeconds) { return; } _timer = 0f; try { Check(null); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Event Hunt: ошибка проверки сундука: " + ex)); } } public static void OnChestData(ZDO zdo) { try { Check(zdo); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Event Hunt: ошибка разбора данных сундука: " + ex)); } } private static void Check(ZDO zdo) { if (zdo == null) { zdo = ResolveChest(); } if (zdo == null) { if (!_missingChestWarned) { Plugin.Log.LogWarning((object)$"Event Hunt: сундук {_state.Chest.Prefab} ({_state.Chest.X:0.0}, {_state.Chest.Y:0.0}, {_state.Chest.Z:0.0}) не найден (разрушен?)"); } _missingChestWarned = true; return; } _missingChestWarned = false; if (_haveRevision && zdo.DataRevision == _lastRevision) { return; } _lastRevision = zdo.DataRevision; _haveRevision = true; if (!TryCount(zdo, out var count)) { return; } long playerId; string host; string text = ResolvePlayer(zdo.GetOwner(), out playerId, out host); int lastCount = _state.LastCount; HuntUpdateResult huntUpdateResult = HuntTracker.Update(_state, count, text, _settings.Goal, DateTime.UtcNow, playerId, host); if (huntUpdateResult.Changed) { Plugin.Log.LogInfo((object)$"Event Hunt: в сундуке {count} ({count - lastCount:+#;-#;0}), игрок {text}, прогресс {_state.Best} / {_settings.Goal}"); Save(); if (huntUpdateResult.GoalReached) { Announce(HuntText.Format(_settings.VictoryMessage, _state.Best, _settings.Goal)); Plugin.Log.LogInfo((object)"Event Hunt: цель достигнута"); } else if (huntUpdateResult.ProgressIncreased) { Announce(HuntText.Format(_settings.ProgressMessage, _state.Best, _settings.Goal)); } if (_state.Completed && DeliveryServer.EnqueueHunt(_state, _settings)) { Save(); } } } private static bool TryCount(ZDO zdo, out int count) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Expected O, but got Unknown count = 0; byte[] byteArray = zdo.GetByteArray(ZDOVars.s_items, (byte[])null); if (byteArray == null) { return true; } try { ZPackage val = new ZPackage(byteArray); int num = val.ReadInt(); if (num < 108) { if (!_oldFormatWarned) { Plugin.Log.LogWarning((object)$"Event Hunt: сундук в старом формате {num}, считаю 0"); } _oldFormatWarned = true; return true; } int num2 = val.ReadUShort(); for (int i = 0; i < num2; i++) { if (HuntCounter.Matches(ItemData.Load(val, Scratch, (Item)num), Scratch.m_quality, Scratch.m_worldLevel, Scratch.m_customData, _trophyHash, _settings.TrophyQuality, _settings.TrophyWorldLevel, _state.Id)) { count += Scratch.m_stack; } } return true; } catch (Exception ex) { Plugin.Log.LogWarning((object)("Event Hunt: содержимое сундука не прочитано: " + ex.Message)); return false; } } private static string ResolvePlayer(long owner, out long playerId, out string host) { playerId = 0L; host = ""; if (owner == 0L || owner == ZDOMan.GetSessionID()) { return "неизвестно"; } ZNet instance = ZNet.instance; ZNetPeer val = ((instance != null) ? instance.GetPeer(owner) : null); if (val == null) { return "неизвестно"; } playerId = DeliveryServer.PlayerIdOf(val); host = DeliveryServer.HostOf(val); if (string.IsNullOrWhiteSpace(val.m_playerName)) { return "неизвестно"; } return val.m_playerName; } private static void Announce(string text) { if (!string.IsNullOrEmpty(text)) { Plugin.Log.LogInfo((object)("Event Hunt: объявление: " + text)); ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null) { instance.InvokeRoutedRPC(0L, "ShowMessage", new object[2] { 2, text }); } } } public static string HandleCommand(string sub, ZDOID target) { //IL_0043: Unknown result type (might be due to invalid IL or missing references) if (!_running) { return "eventhunt: модуль работает только на сервере"; } return sub switch { "setchest" => SetChest(target), "start" => Start(), "stop" => Stop(), "status" => Status(), _ => "eventhunt: неизвестная команда '" + sub + "'", }; } private static string SetChest(ZDOID target) { //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_0160: Unknown result type (might be due to invalid IL or missing references) //IL_016e: Unknown result type (might be due to invalid IL or missing references) //IL_017c: Unknown result type (might be due to invalid IL or missing references) if (_state == null) { return "eventhunt setchest: " + (_loadError ?? "состояние не загружено"); } if (_state.Active) { return "eventhunt setchest: ивент идёт, сначала eventhunt stop"; } if (((ZDOID)(ref target)).IsNone()) { return "eventhunt setchest: наведите курсор на сундук"; } ZDO zDO = ZDOMan.instance.GetZDO(target); if (zDO == null) { return "eventhunt setchest: объект не найден на сервере"; } GameObject val = (((Object)(object)ZNetScene.instance != (Object)null) ? ZNetScene.instance.GetPrefab(zDO.GetPrefab()) : null); if ((Object)(object)val == (Object)null || (Object)(object)val.GetComponentInChildren<Container>(true) == (Object)null) { return "eventhunt setchest: это не контейнер"; } if ((Object)(object)val.GetComponentInChildren<Vagon>(true) != (Object)null || (Object)(object)val.GetComponentInChildren<Ship>(true) != (Object)null || (Object)(object)val.GetComponent<ZSyncTransform>() != (Object)null) { return "eventhunt setchest: " + ((Object)val).name + " передвигается (телега, корабль), нужен неподвижный сундук"; } Vector3 position = zDO.GetPosition(); _state.Chest = new HuntChest { UserId = ((ZDOID)(ref target)).UserID, Id = ((ZDOID)(ref target)).ID, Prefab = ((Object)val).name, X = position.x, Y = position.y, Z = position.z }; UpdateWatch(); Save(); TryCount(zDO, out var count); return $"eventhunt setchest: сундук {((Object)val).name} ({position.x:0}, {position.y:0}, {position.z:0}) задан, засчитываемых трофеев в нём: {count}"; } private static string Start() { if (_state == null) { return "eventhunt start: " + (_loadError ?? "состояние не загружено"); } if (_state.Active) { return "eventhunt start: ивент уже идёт"; } if (!LoadSettings(out var error)) { return "eventhunt start: " + error + " (прежние настройки оставлены)"; } if (_state.Id != _settings.Id) { SwitchEvent(); } if (_state.Chest == null) { return "eventhunt start: сначала eventhunt setchest"; } ZDO val = ResolveChest(); if (val == null) { return "eventhunt start: сундук не найден на сервере, задайте его заново"; } if (!TryCount(val, out var count)) { return "eventhunt start: содержимое сундука не прочитано"; } HuntTracker.Start(_state, count, DateTime.UtcNow); bool flag = HuntTracker.CheckGoal(_state, _settings.Goal); _lastRevision = val.DataRevision; _haveRevision = true; UpdateWatch(); Save(); Plugin.Log.LogInfo((object)$"Event Hunt: запущен {_state.Id}, в сундуке {count}, прогресс {_state.Best} / {_settings.Goal}"); Announce(HuntText.Format(_settings.StartMessage, _state.Best, _settings.Goal)); if (flag) { Announce(HuntText.Format(_settings.VictoryMessage, _state.Best, _settings.Goal)); Plugin.Log.LogInfo((object)"Event Hunt: цель достигнута"); } if (DeliveryServer.EnqueueHunt(_state, _settings)) { Save(); } return $"eventhunt start: ивент {_state.Id} идёт; в сундуке {count}, прогресс {_state.Best} / {_settings.Goal}" + ((count > 0) ? " (уже лежавшие трофеи записаны на «неизвестно»)" : "") + (flag ? "; цель уже достигнута, объявлена победа" : ""); } private static string Stop() { if (_state == null) { return "eventhunt stop: " + (_loadError ?? "состояние не загружено"); } if (!_state.Active) { return "eventhunt stop: ивент не запущен"; } HuntTracker.Stop(_state); UpdateWatch(); Save(); if (!_state.Completed) { Announce(HuntText.Format(_settings.StopMessage, _state.Best, _settings.Goal)); } Plugin.Log.LogInfo((object)$"Event Hunt: остановлен {_state.Id}, прогресс {_state.Best} / {_settings.Goal}"); return $"eventhunt stop: ивент {_state.Id} остановлен, прогресс {_state.Best} / {_settings.Goal}"; } private static string Status() { if (_state == null || _settings == null) { return "eventhunt status: " + (_loadError ?? "состояние не загружено"); } StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("Ивент " + _state.Id + ": " + (_state.Active ? "идёт" : "остановлен") + (_state.Completed ? ", цель достигнута" : "")); stringBuilder.Append($"\nПрогресс (максимум): {_state.Best} / {_settings.Goal}, сейчас в сундуке: {_state.LastCount}"); stringBuilder.Append(string.Format("\nТрофей: {0}, уровень {1}, уровень мира {2}, метка {3}={4}", _settings.TrophyPrefab, _settings.TrophyQuality, _settings.TrophyWorldLevel, "sansara.EventHunt.id", _state.Id)); if (_settings.Id != _state.Id) { stringBuilder.Append("\nВ настройках уже id " + _settings.Id + ": он начнётся после eventhunt stop и eventhunt start"); } if (_state.Chest == null) { stringBuilder.Append("\nСундук: не задан"); } else { bool flag = ResolveChest() != null; stringBuilder.Append(string.Format("\nСундук: {0} ({1:0}, {2:0}, {3:0}){4}", _state.Chest.Prefab, _state.Chest.X, _state.Chest.Y, _state.Chest.Z, flag ? "" : " — НЕ НАЙДЕН")); } List<KeyValuePair<string, int>> list = HuntTracker.Participants(_state); stringBuilder.Append($"\nУчастники ({list.Count}):"); foreach (KeyValuePair<string, int> item in list) { stringBuilder.Append($"\n {item.Key}: {item.Value}"); } stringBuilder.Append("\nАвтовыдача: " + (_settings.AutoDeliver ? "включена" : "выключена") + ", награда " + _settings.RewardId + ((_settings.AutoDeliver && !_state.Completed) ? " (после победы)" : "")); List<KeyValuePair<long, HuntPlayer>> list2 = HuntDelivery.Recipients(_state); if (list2.Count > 0) { stringBuilder.Append($"\nПо персонажам ({list2.Count}):"); foreach (KeyValuePair<long, HuntPlayer> item2 in list2) { string text = DeliveryServer.DescribeHuntRecipient(item2.Key, _state.Id); stringBuilder.Append(string.Format("\n {0}: {1}{2}", item2.Value.Name, item2.Value.Count, (text.Length > 0) ? (" — " + text) : "")); } } List<KeyValuePair<string, int>> list3 = HuntDelivery.ManualOnly(_state); if (list3.Count > 0) { stringBuilder.Append("\nБез ID персонажа (вклад до 0.5.11, выдать вручную " + _settings.RewardId + "):"); foreach (KeyValuePair<string, int> item3 in list3) { stringBuilder.Append($"\n {item3.Key}: {item3.Value}"); } } List<HuntLogEntry> list4 = HuntTracker.LastDecreases(_state, 5); if (list4.Count > 0) { stringBuilder.Append("\nПоследние уменьшения (UTC):"); foreach (HuntLogEntry item4 in list4) { stringBuilder.Append($"\n {item4.Time:yyyy-MM-dd HH:mm:ss} {item4.Delta} (стало {item4.Count}), {item4.Player}"); } } return stringBuilder.ToString(); } private static bool LoadSettings(out string error) { error = null; try { if (!File.Exists(SettingsPath)) { WriteFile(SettingsPath, HuntSettingsJson.Serialize(HuntSettings.Default())); Plugin.Log.LogInfo((object)("Event Hunt: создан файл настроек по умолчанию " + SettingsPath)); } HuntSettingsResult huntSettingsResult = HuntSettingsJson.Parse(File.ReadAllText(SettingsPath)); foreach (string warning in huntSettingsResult.Warnings) { Plugin.Log.LogWarning((object)("Event Hunt: " + warning)); } if (!huntSettingsResult.Success) { error = "настройки " + SettingsPath + " не приняты: " + huntSettingsResult.Errors[0]; return false; } _settings = huntSettingsResult.Settings; _trophyHash = StringExtensionMethods.GetStableHashCode(_settings.TrophyPrefab); if ((Object)(object)ObjectDB.instance != (Object)null && (Object)(object)ObjectDB.instance.GetItemPrefab(_settings.TrophyPrefab) == (Object)null) { Plugin.Log.LogWarning((object)("Event Hunt: предмет " + _settings.TrophyPrefab + " не найден в ObjectDB")); } return true; } catch (Exception ex) { error = "настройки " + SettingsPath + " не прочитаны: " + ex.Message; return false; } } private static bool LoadState(out string error) { error = null; try { if (!File.Exists(StatePath)) { _state = HuntState.New(_settings.Id); return true; } HuntStateResult huntStateResult = HuntStateJson.Parse(File.ReadAllText(StatePath)); if (!huntStateResult.Success) { error = "состояние " + StatePath + " не прочитано: " + huntStateResult.Errors[0] + "; модуль отключён до исправления файла"; return false; } _state = huntStateResult.State; if (_state.Id != _settings.Id) { if (_state.Active) { Plugin.Log.LogWarning((object)("Event Hunt: в настройках id " + _settings.Id + ", а идёт " + _state.Id + "; ивент продолжается до eventhunt stop")); } else { SwitchEvent(); } } return true; } catch (Exception ex) { error = "состояние " + StatePath + " не прочитано: " + ex.Message; return false; } } private static void SwitchEvent() { if (File.Exists(StatePath)) { string text = Path.Combine(Paths.ConfigPath, "sansara.EventRewards.hunt.state." + _state.Id + ".json"); int num = 2; while (File.Exists(text)) { text = Path.Combine(Paths.ConfigPath, $"sansara.EventRewards.hunt.state.{_state.Id}.{num}.json"); num++; } File.Copy(StatePath, text, overwrite: false); Plugin.Log.LogInfo((object)("Event Hunt: новый ивент " + _settings.Id + "; состояние " + _state.Id + " сохранено в " + text)); } else { Plugin.Log.LogInfo((object)("Event Hunt: новый ивент " + _settings.Id + " (файла состояния " + _state.Id + " не было)")); } HuntChest chest = _state.Chest; _state = HuntState.New(_settings.Id); _state.Chest = chest; Save(); } private static void Save() { try { WriteFile(StatePath, HuntStateJson.Serialize(_state)); } catch (Exception ex) { Plugin.Log.LogError((object)("Event Hunt: состояние не сохранено в " + StatePath + ": " + ex.Message)); } } private static void WriteFile(string path, string text) { ServerFiles.WriteAtomic(path, text); } private static ZDO ResolveChest() { //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Unknown result type (might be due to invalid IL or missing references) //IL_0130: Unknown result type (might be due to invalid IL or missing references) //IL_0135: Unknown result type (might be due to invalid IL or missing references) //IL_0199: Unknown result type (might be due to invalid IL or missing references) HuntChest huntChest = _state?.Chest; if (huntChest == null || ZDOMan.instance == null) { return null; } int stableHashCode = StringExtensionMethods.GetStableHashCode(huntChest.Prefab); Vector3 val = default(Vector3); ((Vector3)(ref val))..ctor(huntChest.X, huntChest.Y, huntChest.Z); ZDO val2 = (((ZDOID)(ref ChestId)).IsNone() ? null : ZDOMan.instance.GetZDO(ChestId)); Vector3 val3; if (val2 != null && val2.GetPrefab() == stableHashCode) { val3 = val2.GetPosition() - val; if (((Vector3)(ref val3)).sqrMagnitude <= 0.25f) { return val2; } } ZDO val4 = null; float num = 0.25f; SearchBuffer.Clear(); ZDOMan.instance.FindSectorObjects(ZoneSystem.GetZone(val), new SimulationDistance(1, 0, true), SearchBuffer, (List<ZDO>)null); foreach (ZDO item in SearchBuffer) { if (item.GetPrefab() == stableHashCode) { val3 = item.GetPosition() - val; float sqrMagnitude = ((Vector3)(ref val3)).sqrMagnitude; if (sqrMagnitude <= num) { val4 = item; num = sqrMagnitude; } } } SearchBuffer.Clear(); if (val4 == null) { return null; } ChestId = val4.m_uid; _haveRevision = false; if (huntChest.UserId != ((ZDOID)(ref ChestId)).UserID || huntChest.Id != ((ZDOID)(ref ChestId)).ID) { huntChest.UserId = ((ZDOID)(ref ChestId)).UserID; huntChest.Id = ((ZDOID)(ref ChestId)).ID; Save(); } Plugin.Log.LogInfo((object)$"Event Hunt: сундук {huntChest.Prefab} найден, ZDOID {ChestId}"); return val4; } private static ZDOID ChestFromState() { //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) if (_state?.Chest != null) { return new ZDOID(_state.Chest.UserId, _state.Chest.Id); } return ZDOID.None; } private static void UpdateWatch() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) ChestId = ChestFromState(); if (_running && _state?.Chest != null && ResolveChest() == null) { ChestId = ZDOID.None; } Watching = _running && _settings != null && _state != null && _state.Active && _state.Chest != null; _timer = 0f; if (!Watching) { _haveRevision = false; } } } [BepInPlugin("sansara.EventRewards", "Event Rewards", "0.5.14")] [BepInDependency(/*Could not decode attribute arguments.*/)] [NetworkCompatibility(/*Could not decode attribute arguments.*/)] public sealed class Plugin : BaseUnityPlugin { public const string Guid = "sansara.EventRewards"; public const string Version = "0.5.14"; internal static ManualLogSource Log; private void Awake() { //IL_001b: Unknown result type (might be due to invalid IL or missing references) Log = ((BaseUnityPlugin)this).Logger; PluginConfig.Bind(((BaseUnityPlugin)this).Config); new Harmony("sansara.EventRewards").PatchAll(typeof(Plugin).Assembly); RewardNetwork.Init(); CommandManager.Instance.AddConsoleCommand((ConsoleCommand)(object)new EventRewardCommand()); CommandManager.Instance.AddConsoleCommand((ConsoleCommand)(object)new HuntCommand()); CustomLocalization localization = LocalizationManager.Instance.GetLocalization(); string text = "Russian"; string text2 = "item_newgameplusitem"; localization.AddTranslation(ref text, ref text2, "Трофей ивента"); Log.LogInfo((object)"Event Rewards 0.5.14 loaded"); } } internal static class PluginConfig { public static ConfigEntry<bool> Enabled; private static ConfigEntry<float> _maxCarryWeight; private static ConfigEntry<float> _maxMoveSpeed; private static ConfigEntry<float> _maxStaminaRegen; private static ConfigEntry<float> _maxRunDrainReduction; private static ConfigEntry<float> _maxHealth; private static ConfigEntry<float> _maxStamina; private static ConfigEntry<float> _maxDamage; private static ConfigEntry<float> _maxBlockPower; private static ConfigEntry<float> _maxDurability; private static ConfigEntry<float> _maxToolTier; public static ConfigEntry<int> MaxQualityAboveBase; private static ConfigEntry<string> _maxResistance; private static ConfigEntry<string> _allowedEffects; private static ConfigEntry<int> _maxAirJumps; private static ConfigEntry<float> _maxEitr; private static ConfigEntry<float> _maxEitrRegen; private static ConfigEntry<float> _maxEitrCostReduction; private static ConfigEntry<float> _maxHealthCostReduction; private static ConfigEntry<float> _maxSkillLevel; private static ConfigEntry<float> _maxSkillGain; private static ConfigEntry<float> _maxDamageSkill; private static ConfigEntry<float> _maxAdrenaline; private static ConfigEntry<float> _maxAdrenalineThresholdReduction; private static ConfigEntry<float> _maxAdrenalineDuration; private static ConfigEntry<float> _maxAdrenalineOnHit; private static ConfigEntry<float> _maxShipSpeed; private static ConfigEntry<float> _maxAdrenalineEffectDuration; private static ConfigEntry<string> _allowedAdrenalineEffects; private static ConfigEntry<float> _maxHealthRegen; private static ConfigEntry<float> _maxFoodStats; private static ConfigEntry<float> _maxFoodDrainReduction; private static ConfigEntry<float> _maxPotionDuration; private static ConfigEntry<float> _maxPotionCooldownReduction; private static ConfigEntry<float> _maxPotionPower; private static ConfigEntry<float> _maxBuildStaminaReduction; private static ConfigEntry<float> _maxBuildRange; private static ConfigEntry<float> _maxArmor; private static ConfigEntry<float> _maxFallDamageReduction; private static ConfigEntry<float> _maxCraftCostReduction; private static ConfigEntry<float> _maxUpgradeCostReduction; private static ConfigEntry<float> _maxAmmoSave; private static ConfigEntry<float> _maxCraftYield; public static ConfigEntry<bool> AnnounceRewardLoss; public static ConfigEntry<string> RewardLossMessage; public static ConfigEntry<bool> DeliveryEnabled; public static ConfigEntry<float> DeliveryCheckIntervalSeconds; public static ConfigEntry<float> DeliverySettleSeconds; public static ConfigEntry<string> DeliveryMessage; public static ConfigEntry<bool> AnnounceRevocation; public static ConfigEntry<string> RevocationMessage; public static ConfigEntry<float> RevocationRescanMinutes; private static string _warnedMaxResistance; public static void Bind(ConfigFile cfg) { //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Expected O, but got Unknown //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Expected O, but got Unknown //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Expected O, but got Unknown //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Expected O, but got Unknown //IL_0117: Unknown result type (might be due to invalid IL or missing references) //IL_0121: Expected O, but got Unknown //IL_0141: Unknown result type (might be due to invalid IL or missing references) //IL_014b: Expected O, but got Unknown //IL_016b: Unknown result type (might be due to invalid IL or missing references) //IL_0175: Expected O, but got Unknown //IL_0195: Unknown result type (might be due to invalid IL or missing references) //IL_019f: Expected O, but got Unknown //IL_01cd: Unknown result type (might be due to invalid IL or missing references) //IL_01d7: Expected O, but got Unknown //IL_01fd: Unknown result type (might be due to invalid IL or missing references) //IL_0207: Expected O, but got Unknown //IL_0268: Unknown result type (might be due to invalid IL or missing references) //IL_0272: Expected O, but got Unknown //IL_02a2: Unknown result type (might be due to invalid IL or missing references) //IL_02ac: Expected O, but got Unknown //IL_02cc: Unknown result type (might be due to invalid IL or missing references) //IL_02d6: Expected O, but got Unknown //IL_02f6: Unknown result type (might be due to invalid IL or missing references) //IL_0300: Expected O, but got Unknown //IL_0320: Unknown result type (might be due to invalid IL or missing references) //IL_032a: Expected O, but got Unknown //IL_0358: Unknown result type (might be due to invalid IL or missing references) //IL_0362: Expected O, but got Unknown //IL_0382: Unknown result type (might be due to invalid IL or missing references) //IL_038c: Expected O, but got Unknown //IL_03ac: Unknown result type (might be due to invalid IL or missing references) //IL_03b6: Expected O, but got Unknown //IL_03d6: Unknown result type (might be due to invalid IL or missing references) //IL_03e0: Expected O, but got Unknown //IL_040e: Unknown result type (might be due to invalid IL or missing references) //IL_0418: Expected O, but got Unknown //IL_0438: Unknown result type (might be due to invalid IL or missing references) //IL_0442: Expected O, but got Unknown //IL_0470: Unknown result type (might be due to invalid IL or missing references) //IL_047a: Expected O, but got Unknown //IL_04a8: Unknown result type (might be due to invalid IL or missing references) //IL_04b2: Expected O, but got Unknown //IL_04e0: Unknown result type (might be due to invalid IL or missing references) //IL_04ea: Expected O, but got Unknown //IL_0537: Unknown result type (might be due to invalid IL or missing references) //IL_0541: Expected O, but got Unknown //IL_056f: Unknown result type (might be due to invalid IL or missing references) //IL_0579: Expected O, but got Unknown //IL_05a7: Unknown result type (might be due to invalid IL or missing references) //IL_05b1: Expected O, but got Unknown //IL_05df: Unknown result type (might be due to invalid IL or missing references) //IL_05e9: Expected O, but got Unknown //IL_0617: Unknown result type (might be due to invalid IL or missing references) //IL_0621: Expected O, but got Unknown //IL_064f: Unknown result type (might be due to invalid IL or missing references) //IL_0659: Expected O, but got Unknown //IL_0687: Unknown result type (might be due to invalid IL or missing references) //IL_0691: Expected O, but got Unknown //IL_06bf: Unknown result type (might be due to invalid IL or missing references) //IL_06c9: Expected O, but got Unknown //IL_06e9: Unknown result type (might be due to invalid IL or missing references) //IL_06f3: Expected O, but got Unknown //IL_0713: Unknown result type (might be due to invalid IL or missing references) //IL_071d: Expected O, but got Unknown //IL_074b: Unknown result type (might be due to invalid IL or missing references) //IL_0755: Expected O, but got Unknown //IL_0783: Unknown result type (might be due to invalid IL or missing references) //IL_078d: Expected O, but got Unknown //IL_07bb: Unknown result type (might be due to invalid IL or missing references) //IL_07c5: Expected O, but got Unknown //IL_07f3: Unknown result type (might be due to invalid IL or missing references) //IL_07fd: Expected O, but got Unknown //IL_082b: Unknown result type (might be due to invalid IL or missing references) //IL_0835: Expected O, but got Unknown //IL_08d7: Unknown result type (might be due to invalid IL or missing references) //IL_08e1: Expected O, but got Unknown //IL_092a: Unknown result type (might be due to invalid IL or missing references) //IL_0934: Expected O, but got Unknown //IL_0962: Unknown result type (might be due to invalid IL or missing references) //IL_096c: Expected O, but got Unknown Enabled = cfg.Bind<bool>("General", "Enabled", true, "Включает бонусы и подсказки наград на этом клиенте. Предметы с меткой остаются ванильными при false."); Enabled.SettingChanged += delegate { RewardState.MarkDirty(); }; AcceptableValueRange<float> val = new AcceptableValueRange<float>(0f, 100000f); _maxCarryWeight = cfg.Bind<float>("Limits (server)", "MaxCarryWeight", 500f, new ConfigDescription("Предел бонуса carryWeight, единицы веса.", (AcceptableValueBase)(object)val, Array.Empty<object>())); _maxMoveSpeed = cfg.Bind<float>("Limits (server)", "MaxMoveSpeedPercent", 20f, new ConfigDescription("Предел бонуса moveSpeed, %.", (AcceptableValueBase)(object)val, Array.Empty<object>())); _maxStaminaRegen = cfg.Bind<float>("Limits (server)", "MaxStaminaRegenPercent", 50f, new ConfigDescription("Предел бонуса staminaRegen, %.", (AcceptableValueBase)(object)val, Array.Empty<object>())); _maxRunDrainReduction = cfg.Bind<float>("Limits (server)", "MaxRunStaminaDrainReductionPercent", 50f, new ConfigDescription("Предел экономии runStaminaDrain, %.", (AcceptableValueBase)(object)val, Array.Empty<object>())); _maxHealth = cfg.Bind<float>("Limits (server)", "MaxHealth", 50f, new ConfigDescription("Предел бонуса maxHealth, единицы здоровья.", (AcceptableValueBase)(object)val, Array.Empty<object>())); _maxStamina = cfg.Bind<float>("Limits (server)", "MaxStamina", 50f, new ConfigDescription("Предел бонуса maxStamina, единицы выносливости.", (AcceptableValueBase)(object)val, Array.Empty<object>())); _maxDamage = cfg.Bind<float>("Limits (server)", "MaxDamagePercent", 20f, new ConfigDescription("Предел бонуса damage, %.", (AcceptableValueBase)(object)val, Array.Empty<object>())); _maxBlockPower = cfg.Bind<float>("Limits (server)", "MaxBlockPowerPercent", 100f, new ConfigDescription("Предел бонуса blockPower (прочность блока и отбрасывание при парировании самого предмета), %.", (AcceptableValueBase)(object)val, Array.Empty<object>())); _maxToolTier = cfg.Bind<float>("Limits (server)", "MaxToolTier", 10f, new ConfigDescription("Предел бонуса toolTier (минимальный уровень инструмента).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 100f), Array.Empty<object>())); MaxQualityAboveBase = cfg.Bind<int>("Limits (server)", "MaxQualityAboveBase", 0, new ConfigDescription("На сколько уровней выше обычного максимума предмета можно выдавать награду (как после Кузницы возможностей). 0 — не выше обычного максимума; -1 — без ограничения (технический предел игры 65535). Не действует на предметы с одним уровнем качества (пояса).", (AcceptableValueBase)(object)new AcceptableValueRange<int>(-1, 65535), Array.Empty<object>())); _maxResistance = cfg.Bind<string>("Limits (server)", "MaxResistance", "veryResistant", "Предел бонуса resist: уровень сильнее предела понижается до него с предупреждением. Значения (регистр не важен): none (сопротивления наград отключены), slightlyResistant, resistant, veryResistant, immune. Неверное значение — используется veryResistant (предупреждение в журнале). frost: immune всегда понижается до veryResistant."); _allowedEffects = cfg.Bind<string>("Limits (server)", "AllowedEffects", "Wishbone,Demister,WindRun,SlowFall,GP_Eikthyr,GP_TheElder,GP_Bonemass,GP_Moder,GP_Yagluth,GP_Queen,GP_Fader,Potion_frostresist,Potion_poisonresist,Potion_barleywine,Potion_swimmer,Potion_LightFoot,Potion_tamer,Potion_BugRepellent", "Имена ванильных StatusEffect, разрешённые в поле effects наград (через запятую, с учётом регистра). Силы боссов (GP_*) из effects действуют без таймера, пока награда надета. Медовухи без группы (Potion_*) из effects действуют без таймера, пока награда надета; медовухи лечения, выносливости и эйтра сюда не добавлять — постоянный эффект группы навсегда запретит пить медовухи этой группы. Пусто — эффекты наград отключены."); _maxAirJumps = cfg.Bind<int>("Limits (server)", "MaxAirJumps", 1, new ConfigDescription("Предел бонуса doubleJump (число прыжков в воздухе).", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 10), Array.Empty<object>())); AcceptableValueRange<float> val2 = new AcceptableValueRange<float>(0f, 100f); _maxEitr = cfg.Bind<float>("Limits (server)", "MaxEitr", 100f, new ConfigDescription("Предел бонуса maxEitr, единицы эйтра (базового эйтра у игрока нет, бонус даёт эйтр и без еды).", (AcceptableValueBase)(object)val, Array.Empty<object>())); _maxEitrRegen = cfg.Bind<float>("Limits (server)", "MaxEitrRegenPercent", 100f, new ConfigDescription("Предел бонуса eitrRegen, %. Полный комплект мага даёт +100 %.", (AcceptableValueBase)(object)val, Array.Empty<object>())); _maxEitrCostReduction = cfg.Bind<float>("Limits (server)", "MaxEitrCostReductionPercent", 50f, new ConfigDescription("Предел экономии eitrCost (расход эйтра на заклинания), %.", (AcceptableValueBase)(object)val2, Array.Empty<object>())); _maxHealthCostReduction = cfg.Bind<float>("Limits (server)", "MaxHealthCostReductionPercent", 50f, new ConfigDescription("Предел экономии healthCost (цена здоровьем у магии крови), %.", (AcceptableValueBase)(object)val2, Array.Empty<object>())); _maxSkillLevel = cfg.Bind<float>("Limits (server)", "MaxSkillLevel", 25f, new ConfigDescription("Предел бонуса skillLevel для каждого навыка, уровни.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1000f), Array.Empty<object>())); _maxSkillGain = cfg.Bind<float>("Limits (server)", "MaxSkillGainPercent", 100f, new ConfigDescription("Предел бонуса skillGain (ускорение роста навыка) для каждого навыка, %.", (AcceptableValueBase)(object)val, Array.Empty<object>())); _maxDamageSkill = cfg.Bind<float>("Limits (server)", "MaxDamageSkillPercent", 100f, new ConfigDescription("Предел бонуса damageSkill (урон атак навыком) для каждого навыка, %.", (AcceptableValueBase)(object)val, Array.Empty<object>())); _maxAdrenaline = cfg.Bind<float>("Limits (server)", "MaxAdrenalinePercent", 100f, new ConfigDescription("Предел бонуса adrenaline (ускорение набора адреналина), %.", (AcceptableValueBase)(object)val, Array.Empty<object>())); _maxAdrenalineThresholdReduction = cfg.Bind<float>("Limits (server)", "MaxAdrenalineThresholdReductionPercent", 50f, new ConfigDescription("Предел бонуса adrenalineThreshold (снижение порога шкалы наградного украшения), %.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 90f), Array.Empty<object>())); _maxAdrenalineDuration = cfg.Bind<float>("Limits (server)", "MaxAdrenalineDurationPercent", 100f, new ConfigDescription("Предел бонуса adrenalineDuration (удлинение эффекта полной шкалы наградного украшения), %.", (AcceptableValueBase)(object)val, Array.Empty<object>())); _maxAdrenalineOnHit = cfg.Bind<float>("Limits (server)", "MaxAdrenalineOnHit", 2f, new ConfigDescription("Предел бонуса adrenalineOnHit, единиц адреналина за попадание по врагу (для сравнения: идеальный блок даёт 5, обычный — 2).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 100f), Array.Empty<object>())); _maxShipSpeed = cfg.Bind<float>("Limits (server)", "MaxShipSpeedPercent", 50f, new ConfigDescription("Предел бонуса shipSpeed одной награды (прибавка к максимальной скорости корабля под парусом и на вёслах), %. Сумму нескольких наград игрока клиенты ограничивают 50 %; на корабле действует лучшее значение среди игроков на борту, не сумма.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 50f), Array.Empty<object>())); _maxAdrenalineEffectDuration = cfg.Bind<float>("Limits (server)", "MaxAdrenalineEffectDuration", 180f, new ConfigDescription("Предел adrenalineEffectDuration (длительность дополнительных эффектов полной шкалы), секунды.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 3600f), Array.Empty<object>())); _allowedAdrenalineEffects = cfg.Bind<string>("Limits (server)", "AllowedAdrenalineEffects", "TrinketBlackDamageHealth,TrinketBlackStamina,TrinketBronzeHealth,TrinketBronzeStamina,TrinketCarapaceEitr,TrinketChitinSwim,TrinketFlametalEitr,TrinketFlametalStaminaHealth,TrinketIronHealth,TrinketIronStamina,TrinketScaleStaminaDamage,TrinketSilverDamage,TrinketSilverResist,Demister,WindRun,SlowFall", "Имена ванильных StatusEffect, разрешённые в поле adrenalineEffects наград-украшений (через запятую, с учётом регистра). Пусто — дополнительные эффекты полной шкалы отключены."); _maxHealthRegen = cfg.Bind<float>("Limits (server)", "MaxHealthRegenPercent", 100f, new ConfigDescription("Предел бонуса healthRegen (восстановление здоровья от еды), %. Сумму наград игрока клиенты ограничивают 200 %. Для сравнения: «Отдых» у огня +200 %, мощная медовуха лечения +25 %.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 200f), Array.Empty<object>())); _maxFoodStats = cfg.Bind<float>("Limits (server)", "MaxFoodStatsPercent", 50f, new ConfigDescription("Предел бонуса foodStats (здоровье, выносливость и эйтр, которые даёт еда; база 25/75 и бонусы наград не растут), %. Сумму наград клиенты ограничивают 100 %.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 100f), Array.Empty<object>())); _maxFoodDrainReduction = cfg.Bind<float>("Limits (server)", "MaxFoodDrainReductionPercent", 50f, new ConfigDescription("Предел бонуса foodDrain (замедление выдыхания еды), %. Сумму наград клиенты ограничивают 90 %.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 90f), Array.Empty<object>())); _maxPotionDuration = cfg.Bind<float>("Limits (server)", "MaxPotionDurationPercent", 100f, new ConfigDescription("Предел бонуса potionDuration (длительность медовух без перезарядки: сопротивления, мощные и др.), %. Сумму наград клиенты ограничивают 200 %.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 200f), Array.Empty<object>())); _maxPotionCooldownReduction = cfg.Bind<float>("Limits (server)", "MaxPotionCooldownReductionPercent", 50f, new ConfigDescription("Предел бонуса potionCooldown (сокращение запрета на группу у мгновенных медовух лечения, выносливости и эйтра, в игре 120 с), %. Сумму наград клиенты ограничивают 90 %; время лечения медовухи (+2 с) не сокращается.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 90f), Array.Empty<object>())); _maxPotionPower = cfg.Bind<float>("Limits (server)", "MaxPotionPowerPercent", 50f, new ConfigDescription("Предел бонуса potionPower (сила медовух: лечение, выносливость и эйтр, прибавка мощных медовух к восстановлению), %. Сумму наград клиенты ограничивают 100 %.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 100f), Array.Empty<object>())); _maxBuildStaminaReduction = cfg.Bind<float>("Limits (server)", "MaxBuildStaminaReductionPercent", 50f, new ConfigDescription("Предел бонуса buildStamina (экономия выносливости на строительство молотом, мотыгой и культиватором: установка, разбор, ремонт), %. Сумму наград клиенты ограничивают 90 %.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 90f), Array.Empty<object>())); _maxBuildRange = cfg.Bind<float>("Limits (server)", "MaxBuildRange", 10f, new ConfigDescription("Предел бонуса buildRange (прибавка к дальности строительства, в игре 5 м: установка, разбор, ремонт, мотыга и культиватор), метры. Сумму наград клиенты ограничивают 20 м.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 20f), Array.Empty<object>())); _maxArmor = cfg.Bind<float>("Limits (server)", "MaxArmorPercent", 100f, new ConfigDescription("Предел бонуса armorPercent (броня самого предмета-награды: вся ванильная броня предмета с учётом качества и уровня мира умножается на 1 + N/100), %.", (AcceptableValueBase)(object)val, Array.Empty<object>())); _maxDurability = cfg.Bind<float>("Limits (server)", "MaxDurabilityPercent", 400f, new ConfigDescription("Предел бонуса durability (прочность самого предмета-награды: максимальная ванильная прочность с учётом качества умножается на 1 + N/100; 400 — впятеро), %.", (AcceptableValueBase)(object)val, Array.Empty<object>())); _maxFallDamageReduction = cfg.Bind<float>("Limits (server)", "MaxFallDamageReductionPercent", 50f, new ConfigDescription("Предел бонуса fallDamage (снижение урона от падения у игрока с наградой), %. Сумму наград клиенты ограничивают 90 %. «Словно пёрышко» и плащ из перьев по-прежнему убирают урон полностью.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 90f), Array.Empty<object>())); _maxCraftCostReduction = cfg.Bind<float>("Limits (server)", "MaxCraftCostReductionPercent", 30f, new ConfigDescription("Предел бонуса craftCost (экономия материалов на изготовление новой вещи у станции или в инвентаре; каждый материал округляется вверх, не меньше 1), %. Постройки не дешевеют. Сумму наград клиенты ограничивают 50 %.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 50f), Array.Empty<object>())); _maxUpgradeCostReduction = cfg.Bind<float>("Limits (server)", "MaxUpgradeCostReductionPercent", 30f, new ConfigDescription("Предел бонуса upgradeCost (экономия материалов на улучшение вещи; каждый материал округляется вверх, не меньше 1), %. Сумму наград клиенты ограничивают 50 %.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 50f), Array.Empty<object>())); _maxAmmoSave = cfg.Bind<float>("Limits (server)", "MaxAmmoSavePercent", 30f, new ConfigDescription("Предел бонуса ammoSave (шанс не потратить стрелу, болт или другой снаряд при выстреле), %. Сумму наград клиенты ограничивают 50 %.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 50f), Array.Empty<object>())); _maxCraftYield = cfg.Bind<float>("Limits (server)", "MaxCraftYieldPercent", 50f, new ConfigDescription("Предел бонуса craftYield (больше штук за изготовление по рецепту, дающему несколько предметов: стрелы, болты; дробная часть отбрасывается), %. Сумму наград клиенты ограничивают 100 %.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 100f), Array.Empty<object>())); AnnounceRewardLoss = cfg.Bind<bool>("Announcements (server)", "AnnounceRewardLoss", true, "Объявлять всем игрокам (сообщение по центру экрана, видно и без мода), что награда с onDeath = destroy пропала при смерти игрока. Строка в журнале сервера пишется всегда."); RewardLossMessage = cfg.Bind<string>("Announcements (server)", "RewardLossMessage", "Боги смеются: «{reward}» забрала Хель, и {player} вновь начинает путь. Вставай, викинг, сага не окончена!", "Текст объявления; {player} — имя игрока, {reward} — название награды (теги оформления из имён удаляются). Пустая строка — без объявления."); AnnounceRevocation = cfg.Bind<bool>("Announcements (server)", "AnnounceRevocation", true, "Объявлять всем игрокам (сообщение по центру экрана, видно и без мода), что у игрока изъяты награды (eventreward revoke). При false сообщение видит только сам владелец. Строка в журнале сервера пишется всегда; изъятие из сундуков и с земли только записывается в журнал."); RevocationMessage = cfg.Bind<string>("Announcements (server)", "RevocationMessage", "Суд Форсети: дар «{reward}» возвращается к асам. Владелец — {player}.", "Текст сообщения об изъятии; {player} — имя игрока, {reward} — название награды (несколько — через «, »; теги оформления из имён удаляются). Пустая строка — без сообщения."); RevocationRescanMinutes = cfg.Bind<float>("Revocation (server)", "RescanMinutes", 10f, new ConfigDescription("Пока есть действующие изъятия, сервер заново проверяет все сундуки, повозки, корабли, надгробия и предметы на земле раз в столько минут (и сразу после eventreward revoke).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 120f), Array.Empty<object>())); DeliveryEnabled = cfg.Bind<bool>("Delivery (server)", "Enabled", true, "Доставка наград из очереди (eventreward give <id> @<код>, автовыдача охоты): сервер кладёт предмет к ногам персонажа, когда тот в игре. При false очередь копится, команды работают, ничего не выкладывается."); DeliveryCheckIntervalSeconds = cfg.Bind<float>("Delivery (server)", "CheckIntervalSeconds", 3f, new ConfigDescription("Период проверки игроков в сети (список игроков и доставка), секунды.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 60f), Array.Empty<object>())); DeliverySettleSeconds = cfg.Bind<float>("Delivery (server)", "SettleSeconds", 10f, new ConfigDescription("Сколько секунд персонаж должен пробыть в мире после входа, возрождения или телепорта (скачок больше 50 м), прежде чем ему выложат награду.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 120f), Array.Empty<object>())); DeliveryMessage = cfg.Bind<string>("Delivery (server)", "Message", "Дар асов: «{reward}» лежит у ваших ног", "Сообщение получателю в центре экрана (ванильное, видно и без мода); {reward} — название награды. Пустая строка — без сообщения."); } private static ResistLevel MaxResistanceLevel() { string value = _maxResistance.Value; if (Resist.TryParseLimit(value, out var level)) { return level; } if (_warnedMaxResistance != value) { _warnedMaxResistance = value; Plugin.Log.LogWarning((object)("MaxResistance = '" + value + "' не распознано, используется veryResistant (допустимо: none, slightlyResistant, resistant, veryResistant, immune)")); } return level; } public static BonusLimits Limits() { return new BonusLimits { MaxCarryWeight = _maxCarryWeight.Value, MaxMoveSpeedPercent = _maxMoveSpeed.Value, MaxStaminaRegenPercent = _maxStaminaRegen.Value, MaxRunStaminaDrainReductionPercent = _maxRunDrainReduction.Value, MaxHealth = _maxHealth.Value, MaxStamina = _maxStamina.Value, MaxDamagePercent = _maxDamage.Value, MaxBlockPowerPercent = _maxBlockPower.Value, MaxToolTier = _maxToolTier.Value, MaxResistance = MaxResistanceLevel(), MaxAirJumps = _maxAirJumps.Value, MaxEitr = _maxEitr.Value, MaxEitrRegenPercent = _maxEitrRegen.Value, MaxEitrCostReductionPercent = _maxEitrCostReduction.Value, MaxHealthCostReductionPercent = _maxHealthCostReduction.Value, MaxSkillLevel = _maxSkillLevel.Value, MaxSkillGainPercent = _maxSkillGain.Value, MaxDamageSkillPercent = _maxDamageSkill.Value, AllowedEffects = BonusLimits.ParseEffectList(_allowedEffects.Value), MaxAdrenalinePercent = _maxAdrenaline.Value, MaxAdrenalineThresholdReductionPercent = _maxAdrenalineThresholdReduction.Value, MaxAdrenalineDurationPercent = _maxAdrenalineDuration.Value, MaxAdrenalineOnHit = _maxAdrenalineOnHit.Value, MaxShipSpeedPercent = _maxShipSpeed.Value, MaxAdrenalineEffectDuration = _maxAdrenalineEffectDuration.Value, AllowedAdrenalineEffects = BonusLimits.ParseEffectList(_allowedAdrenalineEffects.Value), MaxHealthRegenPercent = _maxHealthRegen.Value, MaxFoodStatsPercent = _maxFoodStats.Value, MaxFoodDrainReductionPercent = _maxFoodDrainReduction.Value, MaxPotionDurationPercent = _maxPotionDuration.Value, MaxPotionCooldownReductionPercent = _maxPotionCooldownReduction.Value, MaxPotionPowerPercent = _maxPotionPower.Value, MaxBuildStaminaReductionPercent = _maxBuildStaminaReduction.Value, MaxBuildRange = _maxBuildRange.Value, MaxArmorPercent = _maxArmor.Value, MaxFallDamageReductionPercent = _maxFallDamageReduction.Value, MaxDurabilityPercent = _maxDurability.Value, MaxCraftCostReductionPercent = _maxCraftCostReduction.Value, MaxUpgradeCostReductionPercent = _maxUpgradeCostReduction.Value, MaxAmmoSavePercent = _maxAmmoSave.Value, MaxCraftYieldPercent = _maxCraftYield.Value }; } } internal static class RevocationClient { private const float PassSeconds = 10f; private const float RetrySeconds = 1f; private static HashSet<string> _ids = new HashSet<string>(StringComparer.Ordinal); private static bool _pending; private static float _timer; public static bool IsRevoked(string rewardId) { if (rewardId != null && _ids.Count > 0) { return _ids.Contains(rewardId); } return false; } public static void SetIds(IEnumerable<string> ids) { HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal); if (ids != null) { foreach (string id in ids) { if (!string.IsNullOrEmpty(id)) { hashSet.Add(id); } } } bool num = !hashSet.SetEquals(_ids); _ids = hashSet; _pending = hashSet.Count > 0; _timer = 0f; if (num) { RewardState.MarkDirty(); Plugin.Log.LogInfo((object)$"Изъятые награды с сервера: {hashSet.Count}"); } } public static void Reset() { _ids = new HashSet<string>(StringComparer.Ordinal); _pending = false; _timer = 0f; } public static void Tick(Player player, float dt) { if (_ids.Count == 0) { return; } _timer -= dt; if (_timer > 0f) { return; } _timer = (_pending ? 1f : 10f); if (!CanApply(player)) { return; } _pending = false; try { Apply(player); } catch (Exception ex) { Plugin.Log.LogError((object)("Изъятие наград: " + ex)); } } private static bool CanApply(Player player) { if ((Object)(object)player != (Object)null && (Object)(object)player == (Object)(object)Player.m_localPlayer && !((Character)player).IsDead() && !((Character)player).IsTeleporting() && !DeathFatePatch.Holding && DeathFatePatch.InOnDeath == 0) { return !InventoryGui.IsVisible(); } return false; } private static void Apply(Player player) { Inventory inventory = ((Humanoid)player).GetInventory(); if (inventory == null) { return; } List<ItemData> list = RevocationMatch.Select(inventory.GetAllItems().ToList(), (ItemData i) => RewardTag.GetId(i.m_customData), _ids); if (list.Count == 0) { return;