using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Jotunn.Extensions;
using Jotunn.Managers;
using Newtonsoft.Json;
using UnityEngine;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: AssemblyTitle("GlitnirRanking")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("GlitnirRanking")]
[assembly: AssemblyCopyright("Copyright © 2026")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("72b7e2b4-c4d2-4b2f-91db-99d0311d97bc")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace Glitnir.Ranking;
[BepInPlugin("com.glitnir.ranking", "Glitnir Ranking", "0.7.31")]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
public class GlitnirRankingPlugin : BaseUnityPlugin
{
private sealed class SnapshotTopEntryData
{
public int Position;
public string PlayerName = "";
public int Points;
public bool IsLocalPlayer;
public int TotalKillsPontuadas;
public int TotalBossesPontuadas;
public int TotalSkillLevelUpsPontuados;
public int TotalFishingPontuadas;
public int TotalCraftPontuadas;
public int TotalFarmJackpotsPontuados;
public int TotalUniqueCraftJackpotsPontuados;
public int TotalDeaths;
public int KillPointsTotal;
public int BossPointsTotal;
public int SkillPointsTotal;
public int FishingPointsTotal;
public int CraftPointsTotal;
public int FarmJackpotPointsTotal;
public int UniqueCraftJackpotPointsTotal;
public int DeathPenaltyPointsTotal;
public string LastReason = "";
public string LastUpdateUtc = "";
}
private sealed class SnapshotPlayerData
{
public bool HasData;
public int Position;
public string PlayerName = "";
public int Points;
public int TotalKillsPontuadas;
public int TotalBossesPontuadas;
public int TotalSkillLevelUpsPontuados;
public int KillPointsTotal;
public int BossPointsTotal;
public int SkillPointsTotal;
public string LastReason = "";
public string LastUpdateUtc = "";
public bool RankingEnabled;
public bool EnableKillPoints;
public bool EnableBossPoints;
public bool EnableSkillPoints;
public bool EnableMarketplaceQuestPoints;
public int TopCount;
public bool RewardClaimsEnabled;
public bool RewardCanClaim;
public bool RewardAlreadyClaimed;
public int RewardRank;
public int RewardMinPoints;
public string RewardLabel = "";
public string RewardPrefabName = "";
public int RewardAmount;
public string RewardBlockReason = "";
public string RewardClaimCycleId = "";
public string HudKillRules = "";
public string HudBossRules = "";
public string HudSkillJackpotRules = "";
public string HudMarketplaceQuestRules = "";
public string HudFishingRules = "";
public string HudCraftRules = "";
public string HudFarmJackpotRules = "";
public string HudUniqueCraftJackpotRules = "";
public string HudDeathPenaltyRules = "";
public string HudExplorationMapJackpotRules = "";
public Dictionary<string, int> ProgressCounters = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
public Dictionary<string, float> FloatProgressCounters = new Dictionary<string, float>(StringComparer.OrdinalIgnoreCase);
}
private sealed class PendingKillCheck
{
public string PrefabName = "";
public float FirstSeenTime;
public float LastHitTime;
}
private sealed class RankRewardInfo
{
public int Rank;
public int MinPoints;
public string Label = "";
public string PrefabName = "";
public int Amount;
}
private enum DebugCategory
{
General,
Hit,
Kill,
Skill,
PendingKill,
Snapshot,
Points
}
private sealed class RuleGuideGroup
{
public readonly string Name;
public readonly List<string> Rows = new List<string>();
public RuleGuideGroup(string name)
{
Name = (string.IsNullOrWhiteSpace(name) ? "Outros" : name);
}
}
private class SkillGuideGroup
{
public string Name;
public List<SkillGuideMilestone> Milestones;
public SkillGuideGroup(string name)
{
Name = (string.IsNullOrWhiteSpace(name) ? "Habilidade" : name);
Milestones = new List<SkillGuideMilestone>();
}
}
private class SkillGuideMilestone
{
public string Level;
public string Points;
public SkillGuideMilestone(string level, string points)
{
Level = (string.IsNullOrWhiteSpace(level) ? "?" : level);
Points = (string.IsNullOrWhiteSpace(points) ? "0" : points);
}
}
public const string ModGuid = "com.glitnir.ranking";
public const string ModName = "Glitnir Ranking";
public const string ModVersion = "0.7.31";
internal static GlitnirRankingPlugin Instance;
internal static ManualLogSource Log;
private const string RpcRequestSnapshot = "glitnir.ranking.requestsnapshot";
private const string RpcReceiveSnapshot = "glitnir.ranking.receivesnapshot";
private const string RpcReportHit = "glitnir.ranking.reporthit";
private const string RpcReportKill = "glitnir.ranking.reportkill";
private const string RpcReportSkillGain = "glitnir.ranking.reportskillgain";
private const string RpcRequestRewardClaim = "glitnir.ranking.requestrewardclaim";
private const string RpcGrantRewardItem = "glitnir.ranking.grantrewarditem";
private const string RpcFinalizeRewardClaim = "glitnir.ranking.finalizerewardclaim";
private const string RpcRewardClaimFeedback = "glitnir.ranking.rewardclaimfeedback";
private const string RpcReportMarketplaceQuestComplete = "glitnir.ranking.marketquestcomplete";
private const string RpcReportFishCaught = "glitnir.ranking.reportfishcaught";
private const string RpcReportCraftedItem = "glitnir.ranking.reportcrafteditem";
private const string RpcReportFarmHarvest = "glitnir.ranking.reportfarmharvest";
private const string RpcReportPlayerDeath = "glitnir.ranking.reportplayerdeath";
private const string RpcReportExplorationMap = "glitnir.ranking.reportexplorationmap";
private const float DamageCreditLifetimeSeconds = 180f;
private const float ProcessedKillLifetimeSeconds = 180f;
private const float LocalRecentHitLifetimeSeconds = 15f;
private const float ServerPendingKillDelaySeconds = 0.25f;
private const float ServerPendingKillTimeoutSeconds = 12f;
private const float ServerPendingKillCheckInterval = 0.25f;
private const int MaxHudChars = 3000;
private const int MaxPlayerNameLength = 32;
private const int MaxReasonLength = 64;
private const float ClientRefreshInterval = 1f;
private readonly Dictionary<string, string> _hudPrefabDisplayNameCache = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
private readonly Dictionary<string, GameObject> _hudPrefabObjectCache = new Dictionary<string, GameObject>(StringComparer.OrdinalIgnoreCase);
private int _hudObjectDbItemCountCached = -1;
private int _hudZNetScenePrefabCountCached = -1;
private Type _hudLocalizationType;
private FieldInfo _hudLocalizationInstanceField;
private MethodInfo _hudLocalizationLocalizeMethod;
private bool _hudLocalizationReflectionReady;
private Harmony _harmony;
private bool _rpcsRegistered;
private string _rulesFilePath;
private string _uiFolderPath;
private string _uiFilePath;
private string _databaseFilePath;
private string _legacyDatabaseFilePath;
private ConfigFile _rulesConfig;
private FileSystemWatcher _rulesConfigWatcher;
private bool _rulesConfigReloadQueued;
private float _rulesConfigReloadAt;
private float _rulesConfigApplyAt;
private const float RulesConfigApplyInterval = 1f;
private ConfigEntry<bool> _cfgRankingEnabled;
private ConfigEntry<bool> _cfgEnableKillPoints;
private ConfigEntry<bool> _cfgEnableBossPoints;
private ConfigEntry<int> _cfgDefaultKillPoints;
private ConfigEntry<int> _cfgTopCount;
private ConfigEntry<bool> _cfgAllowRepeatedBossPoints;
private ConfigEntry<bool> _cfgDebugLogging;
private ConfigEntry<bool> _cfgLogHitReports;
private ConfigEntry<bool> _cfgLogKillReports;
private ConfigEntry<bool> _cfgLogSkillReports;
private ConfigEntry<bool> _cfgLogPendingKillReports;
private ConfigEntry<bool> _cfgLogSnapshotRequests;
private ConfigEntry<bool> _cfgLogPointsChanges;
private ConfigEntry<bool> _cfgEnableSkillPoints;
private ConfigEntry<bool> _cfgIgnoreTamedKills;
private ConfigEntry<bool> _cfgEnableMarketplaceQuestPoints;
private ConfigEntry<bool> _cfgEnableFishingPoints;
private ConfigEntry<bool> _cfgEnableDeathPenalty;
private ConfigEntry<bool> _cfgEnableExplorationJackpots;
private ConfigEntry<string> _cfgExplorationMapJackpotRules;
private ConfigEntry<string> _cfgDeathPenaltyRules;
private ConfigEntry<string> _cfgCombatCategoryRules;
private ConfigEntry<string> _cfgProductionCategoryRules;
private ConfigEntry<string> _cfgMarketplaceQuestPointMap;
private ConfigEntry<string> _cfgFarmJackpotRules;
private ConfigEntry<string> _cfgUniqueCraftJackpotRules;
private ConfigEntry<bool> _cfgRewardClaimsEnabled;
private ConfigEntry<string> _cfgRewardClaimCycleId;
private ConfigEntry<int> _cfgRewardTop1MinPoints;
private ConfigEntry<string> _cfgRewardTop1Label;
private ConfigEntry<string> _cfgRewardTop1Prefab;
private ConfigEntry<int> _cfgRewardTop1Amount;
private ConfigEntry<int> _cfgRewardTop2MinPoints;
private ConfigEntry<string> _cfgRewardTop2Label;
private ConfigEntry<string> _cfgRewardTop2Prefab;
private ConfigEntry<int> _cfgRewardTop2Amount;
private ConfigEntry<int> _cfgRewardTop3MinPoints;
private ConfigEntry<string> _cfgRewardTop3Label;
private ConfigEntry<string> _cfgRewardTop3Prefab;
private ConfigEntry<int> _cfgRewardTop3Amount;
private ConfigEntry<bool> _cfgDiscordTop3WebhookEnabled;
private ConfigEntry<string> _cfgDiscordTop3WebhookUrl;
private ConfigEntry<bool> _cfgIgnoreAdminsInRanking;
private ConfigEntry<string> _cfgIgnoredAdminNames;
private readonly Dictionary<string, ConfigEntry<int>> _cfgKillPointEntries = new Dictionary<string, ConfigEntry<int>>(StringComparer.OrdinalIgnoreCase);
private readonly Dictionary<string, ConfigEntry<int>> _cfgBossPointEntries = new Dictionary<string, ConfigEntry<int>>(StringComparer.OrdinalIgnoreCase);
private readonly Dictionary<string, ConfigEntry<int>> _cfgFishingPointEntries = new Dictionary<string, ConfigEntry<int>>(StringComparer.OrdinalIgnoreCase);
private readonly Dictionary<string, ConfigEntry<int>> _cfgCraftPointEntries = new Dictionary<string, ConfigEntry<int>>(StringComparer.OrdinalIgnoreCase);
private readonly Dictionary<string, ConfigEntry<string>> _cfgFarmJackpotEntries = new Dictionary<string, ConfigEntry<string>>(StringComparer.OrdinalIgnoreCase);
private readonly Dictionary<string, ConfigEntry<string>> _cfgUniqueCraftJackpotEntries = new Dictionary<string, ConfigEntry<string>>(StringComparer.OrdinalIgnoreCase);
private readonly Dictionary<string, ConfigEntry<string>> _cfgSkillMilestoneJackpotPointEntries = new Dictionary<string, ConfigEntry<string>>(StringComparer.OrdinalIgnoreCase);
private RankingDatabase _database = new RankingDatabase();
private RankingRules _rules = new RankingRules();
private readonly Dictionary<int, MarketplaceQuestPointRule> _marketplaceQuestPointsByUid = new Dictionary<int, MarketplaceQuestPointRule>();
private readonly Dictionary<string, Dictionary<string, float>> _damageCredits = new Dictionary<string, Dictionary<string, float>>(StringComparer.Ordinal);
private readonly Dictionary<string, float> _processedKills = new Dictionary<string, float>(StringComparer.Ordinal);
private readonly Dictionary<string, float> _ignoredTamedKills = new Dictionary<string, float>(StringComparer.Ordinal);
private readonly Dictionary<string, float> _localRecentHits = new Dictionary<string, float>(StringComparer.Ordinal);
private readonly Dictionary<string, PendingKillCheck> _pendingKillChecks = new Dictionary<string, PendingKillCheck>(StringComparer.Ordinal);
private float _serverPendingKillCheckAt;
private bool _hudVisible;
private float _hudOpenTime;
private Rect _windowRect = new Rect(660f, 56f, 548f, 804f);
private Rect _iconRect = new Rect(24f, 180f, 56f, 56f);
private Vector2 _rankingScrollPosition = Vector2.zero;
private Vector2 _playerInfoScrollPosition = Vector2.zero;
private Vector2 _rulesInfoScrollPosition = Vector2.zero;
private Vector2 _actionsScrollPosition = Vector2.zero;
private readonly HashSet<string> _expandedActionPlayers = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
private int _hudTabIndex = 0;
private float _hudTabSwitchTime = 0f;
private readonly HashSet<string> _expandedRuleCategories = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
private readonly HashSet<string> _expandedGuideSkillRows = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
private readonly HashSet<string> _expandedRuleSubCategories = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
private bool _ruleCategoriesInitialized;
private readonly HashSet<string> _expandedPerformanceCategories = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
private bool _performanceCategoriesInitialized;
private float _clientRefreshTimer;
private float _explorationReportTimer;
private float _lastReportedMapExplorePercent = -1f;
private long _lastKnownServerPeerUid;
private bool _requestedInitialServerSnapshot;
private string _cachedTopText = "Carregando ranking...";
private string _cachedPlayerText = "Aguardando dados do servidor...";
private string _statusText = "Sincronizando...";
private float _statusUntil = 0f;
private bool _rewardClaimRequestPending;
private string _rewardClaimPendingCycleId = "";
private int _rewardClaimPendingRank = 0;
private readonly HashSet<string> _pendingRewardClaims = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
private bool _iconDragging;
private Vector2 _iconDragOffset = Vector2.zero;
private Vector2 _iconMouseDownPosition = Vector2.zero;
private bool _iconMovedDuringDrag;
private readonly List<SnapshotTopEntryData> _cachedTopEntries = new List<SnapshotTopEntryData>();
private SnapshotPlayerData _cachedPlayerData = new SnapshotPlayerData();
private GUIStyle _windowStyle;
private GUIStyle _headerStyle;
private GUIStyle _sectionTitleStyle;
private GUIStyle _rankingTextStyle;
private GUIStyle _playerTextStyle;
private GUIStyle _footerStyle;
private GUIStyle _statusStyle;
private GUIStyle _iconButtonStyle;
private GUIStyle _panelHeadingStyle;
private GUIStyle _cardLabelStyle;
private GUIStyle _cardValueStyle;
private GUIStyle _bodyRowStyle;
private GUIStyle _bodyValueStyle;
private GUIStyle _mutedBodyStyle;
private GUIStyle _rankIndexStyle;
private GUIStyle _rankNameStyle;
private GUIStyle _rankPointsStyle;
private GUIStyle _youTagStyle;
private GUIStyle _emptyStateStyle;
private GUIStyle _configStyle;
private bool _stylesReady;
private string _uiTitleFontNames = "Cinzel Decorative|Cinzel|Trajan Pro|Palatino Linotype|Georgia|Times New Roman";
private string _uiBodyFontNames = "Cormorant Garamond|Cormorant SC|Palatino Linotype|Georgia|Garamond|Arial";
private string _uiAccentFontNames = "Cinzel|Palatino Linotype|Georgia|Arial";
private bool _uiUseSystemFonts = false;
private KeyCode _uiToggleKey = (KeyCode)102;
private float _uiIconZoom = 0.96f;
private Font _uiTitleFont;
private Font _uiBodyFont;
private Font _uiAccentFont;
private Texture2D _uiTransparentTexture;
private Texture2D _uiWhiteTexture;
private Texture2D _uiPanelTexture;
private Texture2D _uiBackgroundTexture;
private Texture2D _uiRankingIconTexture;
private Texture2D _uiTitleRankingTexture;
private Texture2D _uiTitleTopTexture;
private Texture2D _uiTitleGuideTexture;
private Texture2D _uiTitlePlayerTexture;
private Texture2D _uiTitleActionsTexture;
private readonly Dictionary<string, Texture2D> _uiRuleCategoryIcons = new Dictionary<string, Texture2D>(StringComparer.OrdinalIgnoreCase);
private const float RuleCategoryIconSize = 24f;
private Texture2D _uiRank1IconTexture;
private Texture2D _uiRank2IconTexture;
private Texture2D _uiRank3IconTexture;
private bool _uiTexturesLoaded;
private readonly HashSet<string> _missingUiTextureWarnings = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
private const string DefaultKillPointRules = "Boar:1";
private const string DefaultCombatCategoryRules = "Prados:Boar=1;Floresta Negra:Troll=5;Pântano:Draugr=2;Montanha:Wolf=2;Planícies:Goblin=3;Oceano:Serpent=6;Mistlands:Seeker=4;Ashlands:Charred_Melee=4;Especiais:Haldor=0";
private const string DefaultProductionCategoryRules = "Armas:SwordIron=800;Armaduras:HelmetBronze=800;Comidas:DeerStew=25";
private const float ActionPlayerCollapsedHeight = 58f;
private const float ActionPlayerExpandedHeight = 560f;
private const string HudColorPoints = "#00FF7F";
private const string HudColorDanger = "#FF5555";
private const string HudColorProgress = "#4FC3F7";
private const string HudColorHighlight = "#FFD700";
private const string HudColorMuted = "#D7DCE5";
private const string HudColorName = "#FFFFFF";
private const string HudColorSeparator = "#8F6E38";
private static readonly Dictionary<string, string> ManualPortuguesePrefabNames = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ "deerstew", "Ensopado de cervo" },
{ "honeysglazedchicken", "Frango glaceado com mel" },
{ "mushroomjotun", "Cogumelo Jotun" },
{ "helmetbronze", "Elmo de bronze" },
{ "helmetcarapace", "Elmo de carapaça" },
{ "helmetdrake", "Elmo de draco" },
{ "helmetdverger", "Tiara Dvergr" },
{ "helmetfenris", "Capuz de Fenris" },
{ "helmetiron", "Elmo de ferro" },
{ "helmetleather", "Elmo de couro" },
{ "helmetmage", "Capuz de mago" },
{ "helmetpadded", "Elmo acolchoado" },
{ "helmetroot", "Máscara de raiz" },
{ "helmettrollleather", "Capuz de couro de troll" },
{ "helmetflametal", "Elmo de flametal" },
{ "armorbronzechest", "Peitoral de bronze" },
{ "armorbronzelegs", "Perneiras de bronze" },
{ "armorcarapacechest", "Peitoral de carapaça" },
{ "armorcarapacelegs", "Perneiras de carapaça" },
{ "armorfenringchest", "Peitoral de Fenris" },
{ "armorfenringlegs", "Perneiras de Fenris" },
{ "armorironchest", "Peitoral de ferro" },
{ "armorironlegs", "Perneiras de ferro" },
{ "armorleatherchest", "Túnica de couro" },
{ "armorleatherlegs", "Calças de couro" },
{ "armormagechest", "Manto de mago" },
{ "armormagelegs", "Calças de mago" },
{ "armorpaddedcuirass", "Couraça acolchoada" },
{ "armorpaddedgreaves", "Grevas acolchoadas" },
{ "armorragschest", "Túnica de trapos" },
{ "armorragslegs", "Calças de trapos" },
{ "armorrootchest", "Armadura de raiz" },
{ "armorrootlegs", "Perneiras de raiz" },
{ "armortrollleatherchest", "Túnica de couro de troll" },
{ "armortrollleatherlegs", "Calças de couro de troll" },
{ "knifeblackmetal", "Faca de metal negro" },
{ "knifecopper", "Faca de cobre" },
{ "knifeflint", "Faca de sílex" },
{ "knifesilver", "Faca de prata" }
};
private static readonly Dictionary<string, string> HudFriendlyNames = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ "Boar", "Javali" },
{ "Neck", "Nixe" },
{ "Greyling", "Greydwarf Jovem" },
{ "Greydwarf", "Greydwarf" },
{ "Greydwarf_Elite", "Greydwarf Elite" },
{ "Greydwarf_Shaman", "Xamã Greydwarf" },
{ "Troll", "Troll" },
{ "Skeleton", "Esqueleto" },
{ "Skeleton_Poison", "Esqueleto Venenoso" },
{ "Draugr", "Draugr" },
{ "Draugr_Elite", "Draugr Elite" },
{ "Blob", "Gosma" },
{ "BlobElite", "Gosma Elite" },
{ "Leech", "Sanguessuga" },
{ "Surtling", "Surtling" },
{ "Abomination", "Abominação" },
{ "Wolf", "Lobo" },
{ "Hatchling", "Draco" },
{ "Fenring", "Fenring" },
{ "StoneGolem", "Golem de Pedra" },
{ "Goblin", "Fuling" },
{ "GoblinArcher", "Fuling Arqueiro" },
{ "GoblinBrute", "Fuling Berserker" },
{ "GoblinShaman", "Xamã Fuling" },
{ "Lox", "Lox" },
{ "Deathsquito", "Mosquito da Morte" },
{ "Serpent", "Serpente Marinha" },
{ "Bat", "Morcego" },
{ "Ulv", "Ulv" },
{ "Hare", "Lebre" },
{ "Tick", "Carrapato" },
{ "Seeker", "Seeker" },
{ "SeekerBrute", "Seeker Brutamontes" },
{ "Gjall", "Gjall" },
{ "Dverger", "Dvergr" },
{ "Charred_Melee", "Carbonizado Guerreiro" },
{ "Charred_Archer", "Carbonizado Arqueiro" },
{ "Charred_Mage", "Carbonizado Mago" },
{ "Morgen", "Morgen" },
{ "Asksvin", "Asksvin" },
{ "Volture", "Abutre das Cinzas" },
{ "Eikthyr", "Eikthyr" },
{ "gd_king", "O Ancião" },
{ "Bonemass", "Massa Óssea" },
{ "Dragon", "Moder" },
{ "GoblinKing", "Yagluth" },
{ "SeekerQueen", "Rainha Seeker" },
{ "Fader", "Fader" },
{ "Fish1", "Peixe Pequeno" },
{ "Fish2", "Perca" },
{ "Fish3", "Lúcio" },
{ "Fish4_cave", "Tetra" },
{ "Fish5", "Atum" },
{ "Fish6", "Peixe Coral" },
{ "Fish7", "Baiacu" },
{ "Fish8", "Tamboril" },
{ "Fish9", "Salmão do Norte" },
{ "Fish10", "Peixe de Magma" },
{ "Fish11", "Bacalhau" },
{ "Fish12", "Esturjão" },
{ "Carrot", "Cenoura" },
{ "Turnip", "Nabo" },
{ "Onion", "Cebola" },
{ "Barley", "Cevada" },
{ "Flax", "Linho" },
{ "Sap", "Seiva" },
{ "JotunPuffs", "Cogumelo Jotun" },
{ "Magecap", "Chapéu de Mago" },
{ "Vineberry", "Baga de Videira" },
{ "Fiddleheadfern", "Samambaia" },
{ "Run", "Corrida" },
{ "Jump", "Salto" },
{ "Swords", "Espadas" },
{ "Knives", "Facas" },
{ "Clubs", "Porretes" },
{ "Polearms", "Armas de Haste" },
{ "Spears", "Lanças" },
{ "Blocking", "Bloqueio" },
{ "Axes", "Machados" },
{ "Bows", "Arcos" },
{ "Crossbows", "Bestas" },
{ "ElementalMagic", "Magia Elemental" },
{ "BloodMagic", "Magia de Sangue" },
{ "Fishing", "Pesca" },
{ "Cooking", "Culinária" }
};
private void Awake()
{
//IL_013f: Unknown result type (might be due to invalid IL or missing references)
//IL_0149: Expected O, but got Unknown
Instance = this;
Log = ((BaseUnityPlugin)this).Logger;
_rulesFilePath = ((BaseUnityPlugin)this).Config.ConfigFilePath;
_uiFolderPath = Path.Combine(Paths.ConfigPath, "GlitnirRankingUI");
_uiFilePath = ((BaseUnityPlugin)this).Config.ConfigFilePath;
_databaseFilePath = Path.Combine(Paths.ConfigPath, "glitnir.ranking.database.json");
_legacyDatabaseFilePath = Path.Combine(Paths.ConfigPath, "glitnir.ranking.database.db");
EnsureUiDirectoryExists();
LoadUiSettings();
_cfgDiscordTop3WebhookEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Discord", "EnableTop3Webhook", true, "Ativa o webhook do Discord quando um jogador entra no Top 3 do ranking.");
_cfgDiscordTop3WebhookUrl = ((BaseUnityPlugin)this).Config.Bind<string>("Discord", "Top3WebhookUrl", "", "URL do webhook do Discord para anunciar ascensão ao Top 3.");
_cfgIgnoreAdminsInRanking = ((BaseUnityPlugin)this).Config.Bind<bool>("Ranking", "IgnoreAdminsInRanking", true, "Se ativo, jogadores presentes na adminlist do servidor nao pontuam nem aparecem no ranking.");
_cfgIgnoredAdminNames = ((BaseUnityPlugin)this).Config.Bind<string>("Ranking", "IgnoredAdminNames", "", "Fallback opcional: nomes ou IDs de admins para ignorar. Separe por virgula, ponto e virgula ou quebra de linha.");
InitializeSyncedRulesConfig();
SetupRulesConfigWatcher();
if (IsDedicatedServerInstance())
{
LoadDatabase();
}
else
{
_database = new RankingDatabase();
}
_harmony = new Harmony("com.glitnir.ranking");
_harmony.PatchAll();
((BaseUnityPlugin)this).Logger.LogInfo((object)"Glitnir Ranking 0.7.31 carregado com sucesso.");
}
private void OnDestroy()
{
try
{
if (IsDedicatedServerInstance())
{
SaveDatabase();
}
try
{
if (_rulesConfigWatcher != null)
{
_rulesConfigWatcher.EnableRaisingEvents = false;
_rulesConfigWatcher.Dispose();
_rulesConfigWatcher = null;
}
}
catch
{
}
SaveUiSettings();
Harmony harmony = _harmony;
if (harmony != null)
{
harmony.UnpatchSelf();
}
}
catch
{
}
}
private void Update()
{
//IL_0057: Unknown result type (might be due to invalid IL or missing references)
//IL_005f: Unknown result type (might be due to invalid IL or missing references)
RegisterRpcsIfNeeded();
ProcessRulesConfigReloadIfNeeded();
RefreshRulesFromSyncedConfigIfNeeded();
if (!Application.isBatchMode)
{
CleanupOldLocalRecentHits();
CheckInitialServerSnapshotRequest();
}
if (Application.isBatchMode)
{
CleanupOldDamageCredits();
CleanupOldProcessedKills();
ProcessPendingKillChecks();
return;
}
if ((int)_uiToggleKey != 0 && Input.GetKeyDown(_uiToggleKey))
{
ToggleRankingHud();
}
if (_hudVisible)
{
if (Input.GetKeyDown((KeyCode)27))
{
CloseRankingHudAndCollapseAll();
return;
}
_clientRefreshTimer += Time.unscaledDeltaTime;
if (_clientRefreshTimer >= 1f)
{
_clientRefreshTimer = 0f;
RequestSnapshotFromServer();
}
ProcessLocalExplorationMapReport();
}
if (IsServerInstance())
{
CleanupOldDamageCredits();
CleanupOldProcessedKills();
ProcessPendingKillChecks();
}
}
private void CheckInitialServerSnapshotRequest()
{
try
{
if (!_rpcsRegistered || ZRoutedRpc.instance == null || (Object)(object)ZNet.instance == (Object)null)
{
return;
}
long serverPeerUid = GetServerPeerUid();
if (serverPeerUid == 0)
{
_lastKnownServerPeerUid = 0L;
_requestedInitialServerSnapshot = false;
return;
}
if (serverPeerUid != _lastKnownServerPeerUid)
{
_lastKnownServerPeerUid = serverPeerUid;
_requestedInitialServerSnapshot = false;
_cachedTopText = "Sincronizando ranking...";
_cachedPlayerText = "Aguardando dados do servidor...";
}
if (!_requestedInitialServerSnapshot && (Object)(object)Player.m_localPlayer != (Object)null)
{
_requestedInitialServerSnapshot = true;
_clientRefreshTimer = 0f;
RequestSnapshotFromServer();
}
}
catch (Exception ex)
{
((BaseUnityPlugin)this).Logger.LogWarning((object)("[Ranking] Falha ao solicitar snapshot inicial: " + ex.Message));
}
}
private void RegisterRpcsIfNeeded()
{
if (!_rpcsRegistered && ZRoutedRpc.instance != null)
{
ZRoutedRpc.instance.Register<ZPackage>("glitnir.ranking.requestsnapshot", (Action<long, ZPackage>)RPC_RequestSnapshot);
ZRoutedRpc.instance.Register<ZPackage>("glitnir.ranking.receivesnapshot", (Action<long, ZPackage>)RPC_ReceiveSnapshot);
ZRoutedRpc.instance.Register<ZPackage>("glitnir.ranking.reporthit", (Action<long, ZPackage>)RPC_ReportHit);
ZRoutedRpc.instance.Register<ZPackage>("glitnir.ranking.reportkill", (Action<long, ZPackage>)RPC_ReportKill);
ZRoutedRpc.instance.Register<ZPackage>("glitnir.ranking.reportskillgain", (Action<long, ZPackage>)RPC_ReportSkillGain);
ZRoutedRpc.instance.Register<ZPackage>("glitnir.ranking.requestrewardclaim", (Action<long, ZPackage>)RPC_RequestRewardClaim);
ZRoutedRpc.instance.Register<ZPackage>("glitnir.ranking.grantrewarditem", (Action<long, ZPackage>)RPC_GrantRewardItem);
ZRoutedRpc.instance.Register<ZPackage>("glitnir.ranking.finalizerewardclaim", (Action<long, ZPackage>)RPC_FinalizeRewardClaim);
ZRoutedRpc.instance.Register<ZPackage>("glitnir.ranking.rewardclaimfeedback", (Action<long, ZPackage>)RPC_RewardClaimFeedback);
ZRoutedRpc.instance.Register<ZPackage>("glitnir.ranking.marketquestcomplete", (Action<long, ZPackage>)RPC_ReportMarketplaceQuestComplete);
ZRoutedRpc.instance.Register<ZPackage>("glitnir.ranking.reportfishcaught", (Action<long, ZPackage>)RPC_ReportFishCaught);
ZRoutedRpc.instance.Register<ZPackage>("glitnir.ranking.reportcrafteditem", (Action<long, ZPackage>)RPC_ReportCraftedItem);
ZRoutedRpc.instance.Register<ZPackage>("glitnir.ranking.reportfarmharvest", (Action<long, ZPackage>)RPC_ReportFarmHarvest);
ZRoutedRpc.instance.Register<ZPackage>("glitnir.ranking.reportplayerdeath", (Action<long, ZPackage>)RPC_ReportPlayerDeath);
ZRoutedRpc.instance.Register<ZPackage>("glitnir.ranking.reportexplorationmap", (Action<long, ZPackage>)RPC_ReportExplorationMap);
_rpcsRegistered = true;
}
}
private void DebugLog(DebugCategory category, string message)
{
if (_rules != null && _rules.DebugLogging)
{
bool flag = false;
if (category switch
{
DebugCategory.Hit => _rules.LogHitReports,
DebugCategory.Kill => _rules.LogKillReports,
DebugCategory.Skill => _rules.LogSkillReports,
DebugCategory.PendingKill => _rules.LogPendingKillReports,
DebugCategory.Snapshot => _rules.LogSnapshotRequests,
DebugCategory.Points => _rules.LogPointsChanges,
_ => true,
})
{
((BaseUnityPlugin)this).Logger.LogInfo((object)("[Ranking Debug] " + message));
}
}
}
private void SetStatus(string text, float seconds = 2.5f)
{
_statusText = (string.IsNullOrWhiteSpace(text) ? "" : text);
_statusUntil = Time.unscaledTime + Mathf.Max(0.1f, seconds);
}
private bool IsServerInstance()
{
if (Application.isBatchMode)
{
return true;
}
try
{
if ((Object)(object)ZNet.instance != (Object)null)
{
return ZNet.instance.IsServer();
}
}
catch
{
}
return false;
}
private bool IsDedicatedServerInstance()
{
return Application.isBatchMode;
}
private bool IsConnectedToDedicatedServer()
{
return !Application.isBatchMode && GetServerPeerUid() != 0;
}
private void LoadDatabase()
{
_database = new RankingDatabase();
try
{
if (!string.IsNullOrWhiteSpace(_databaseFilePath) && File.Exists(_databaseFilePath))
{
string text = File.ReadAllText(_databaseFilePath, Encoding.UTF8);
RankingDatabase database = JsonConvert.DeserializeObject<RankingDatabase>(text);
_database = NormalizeDatabase(database);
((BaseUnityPlugin)this).Logger.LogInfo((object)("[Ranking] Banco JSON carregado com " + _database.Entries.Count + " jogador(es)."));
}
else
{
_database = new RankingDatabase();
((BaseUnityPlugin)this).Logger.LogInfo((object)"[Ranking] Nenhum banco encontrado. Um novo database JSON sera criado ao salvar.");
}
}
catch (Exception ex)
{
_database = new RankingDatabase();
((BaseUnityPlugin)this).Logger.LogError((object)("Erro ao carregar banco JSON do ranking: " + ex));
}
}
private RankingDatabase LoadLegacyDatabaseFile(string filePath)
{
RankingDatabase result = new RankingDatabase();
try
{
if (string.IsNullOrWhiteSpace(filePath))
{
return result;
}
if (!File.Exists(filePath))
{
return result;
}
RankingDatabase rankingDatabase = new RankingDatabase();
string[] array = File.ReadAllLines(filePath, Encoding.UTF8);
string[] array2 = array;
foreach (string text in array2)
{
string text2 = ((text == null) ? "" : text.Trim());
if (string.IsNullOrWhiteSpace(text2) || text2.StartsWith("#"))
{
continue;
}
if (text2.StartsWith("Claim|", StringComparison.Ordinal))
{
string[] array3 = text2.Split(new char[1] { '|' });
if (array3.Length < 5)
{
continue;
}
RewardClaimRecord rewardClaimRecord = new RewardClaimRecord();
rewardClaimRecord.CycleId = SafeLimit(DecodeDatabaseField(array3[1]), 64);
rewardClaimRecord.PlayerName = SanitizePlayerName(DecodeDatabaseField(array3[2]));
rewardClaimRecord.Rank = Mathf.Clamp(ParseInt(array3[3], 0), 0, int.MaxValue);
rewardClaimRecord.ClaimedAtUtc = SafeLimit(DecodeDatabaseField(array3[4]), 64);
rewardClaimRecord.Status = ((array3.Length >= 6) ? SafeLimit(DecodeDatabaseField(array3[5]), 16) : "claimed");
if (!string.IsNullOrWhiteSpace(rewardClaimRecord.CycleId) && !string.IsNullOrWhiteSpace(rewardClaimRecord.PlayerName) && rewardClaimRecord.Rank > 0)
{
string claimKey = BuildRewardClaimKey(rewardClaimRecord.CycleId, rewardClaimRecord.PlayerName, rewardClaimRecord.Rank);
if (!rankingDatabase.Claims.Any((RewardClaimRecord x) => string.Equals(BuildRewardClaimKey(x.CycleId, x.PlayerName, x.Rank), claimKey, StringComparison.OrdinalIgnoreCase)))
{
rankingDatabase.Claims.Add(rewardClaimRecord);
}
}
}
else if (text2.StartsWith("QuestCredit|", StringComparison.Ordinal))
{
string[] array4 = text2.Split(new char[1] { '|' });
if (array4.Length < 4)
{
continue;
}
MarketplaceQuestCreditRecord marketplaceQuestCreditRecord = new MarketplaceQuestCreditRecord();
marketplaceQuestCreditRecord.PlayerName = SanitizePlayerName(DecodeDatabaseField(array4[1]));
marketplaceQuestCreditRecord.QuestKey = SafeKey(DecodeDatabaseField(array4[2]));
marketplaceQuestCreditRecord.GrantedAtUtc = ((array4.Length >= 4) ? SafeLimit(DecodeDatabaseField(array4[3]), 64) : "");
if (!string.IsNullOrWhiteSpace(marketplaceQuestCreditRecord.PlayerName) && !string.IsNullOrWhiteSpace(marketplaceQuestCreditRecord.QuestKey))
{
string creditKey = BuildMarketplaceQuestCreditKey(marketplaceQuestCreditRecord.PlayerName, marketplaceQuestCreditRecord.QuestKey);
if (!rankingDatabase.MarketplaceQuestCredits.Any((MarketplaceQuestCreditRecord x) => string.Equals(BuildMarketplaceQuestCreditKey(x.PlayerName, x.QuestKey), creditKey, StringComparison.OrdinalIgnoreCase)))
{
rankingDatabase.MarketplaceQuestCredits.Add(marketplaceQuestCreditRecord);
}
}
}
else
{
if (!text2.StartsWith("Entry|", StringComparison.Ordinal))
{
continue;
}
string[] array5 = text2.Split(new char[1] { '|' });
if (array5.Length < 10)
{
continue;
}
RankingEntry entry = new RankingEntry();
entry.PlayerName = SanitizePlayerName(DecodeDatabaseField(array5[1]));
entry.Points = Mathf.Clamp(ParseInt(array5[2], 0), -2147483647, int.MaxValue);
entry.LastReason = SafeReason(DecodeDatabaseField(array5[3]));
entry.LastUpdateUtc = SafeLimit(DecodeDatabaseField(array5[4]), 64);
entry.TotalKillsPontuadas = Mathf.Clamp(ParseInt(array5[5], 0), 0, int.MaxValue);
entry.TotalBossesPontuadas = Mathf.Clamp(ParseInt(array5[6], 0), 0, int.MaxValue);
entry.KillPointsTotal = Mathf.Clamp(ParseInt(array5[7], 0), -2147483647, int.MaxValue);
entry.BossPointsTotal = Mathf.Clamp(ParseInt(array5[8], 0), -2147483647, int.MaxValue);
entry.BossCredits = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
string text3 = DecodeDatabaseField(array5[9]);
if (!string.IsNullOrWhiteSpace(text3))
{
string[] array6 = text3.Split(new char[1] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
string[] array7 = array6;
foreach (string value in array7)
{
string text4 = SafeKey(value);
if (!string.IsNullOrWhiteSpace(text4))
{
entry.BossCredits.Add(text4);
}
}
}
if (array5.Length >= 11)
{
entry.TotalSkillLevelUpsPontuados = Mathf.Clamp(ParseInt(array5[10], 0), 0, int.MaxValue);
}
if (array5.Length >= 12)
{
entry.SkillPointsTotal = Mathf.Clamp(ParseInt(array5[11], 0), -2147483647, int.MaxValue);
}
if (array5.Length >= 13)
{
entry.TotalCraftPontuadas = Mathf.Clamp(ParseInt(array5[12], 0), 0, int.MaxValue);
}
if (array5.Length >= 14)
{
entry.CraftPointsTotal = Mathf.Clamp(ParseInt(array5[13], 0), -2147483647, int.MaxValue);
}
if (array5.Length >= 15)
{
entry.TotalFarmJackpotsPontuados = Mathf.Clamp(ParseInt(array5[14], 0), 0, int.MaxValue);
}
if (array5.Length >= 16)
{
entry.FarmJackpotPointsTotal = Mathf.Clamp(ParseInt(array5[15], 0), -2147483647, int.MaxValue);
}
if (array5.Length >= 17)
{
entry.TotalUniqueCraftJackpotsPontuados = Mathf.Clamp(ParseInt(array5[16], 0), 0, int.MaxValue);
}
if (array5.Length >= 18)
{
entry.UniqueCraftJackpotPointsTotal = Mathf.Clamp(ParseInt(array5[17], 0), -2147483647, int.MaxValue);
}
if (array5.Length >= 19)
{
entry.ProgressCounters = DeserializeStringIntDictionary(DecodeDatabaseField(array5[18]));
}
if (array5.Length >= 20)
{
entry.GenericJackpotCredits = DeserializeStringSet(DecodeDatabaseField(array5[19]));
}
if (array5.Length >= 21)
{
entry.TotalDeaths = Mathf.Clamp(ParseInt(array5[20], 0), 0, int.MaxValue);
}
if (array5.Length >= 22)
{
entry.DeathPenaltyPointsTotal = Mathf.Clamp(ParseInt(array5[21], 0), 0, int.MaxValue);
}
if (!string.IsNullOrWhiteSpace(entry.PlayerName))
{
RankingEntry rankingEntry = rankingDatabase.Entries.FirstOrDefault((RankingEntry x) => string.Equals(x.PlayerName, entry.PlayerName, StringComparison.OrdinalIgnoreCase));
if (rankingEntry != null)
{
rankingEntry.Points = entry.Points;
rankingEntry.LastReason = entry.LastReason;
rankingEntry.LastUpdateUtc = entry.LastUpdateUtc;
rankingEntry.TotalKillsPontuadas = entry.TotalKillsPontuadas;
rankingEntry.TotalBossesPontuadas = entry.TotalBossesPontuadas;
rankingEntry.KillPointsTotal = entry.KillPointsTotal;
rankingEntry.BossPointsTotal = entry.BossPointsTotal;
rankingEntry.BossCredits = entry.BossCredits;
rankingEntry.TotalSkillLevelUpsPontuados = entry.TotalSkillLevelUpsPontuados;
rankingEntry.SkillPointsTotal = entry.SkillPointsTotal;
rankingEntry.TotalCraftPontuadas = entry.TotalCraftPontuadas;
rankingEntry.CraftPointsTotal = entry.CraftPointsTotal;
rankingEntry.TotalFarmJackpotsPontuados = entry.TotalFarmJackpotsPontuados;
rankingEntry.FarmJackpotPointsTotal = entry.FarmJackpotPointsTotal;
rankingEntry.TotalUniqueCraftJackpotsPontuados = entry.TotalUniqueCraftJackpotsPontuados;
rankingEntry.UniqueCraftJackpotPointsTotal = entry.UniqueCraftJackpotPointsTotal;
rankingEntry.ProgressCounters = entry.ProgressCounters ?? new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
rankingEntry.GenericJackpotCredits = entry.GenericJackpotCredits ?? new HashSet<string>(StringComparer.OrdinalIgnoreCase);
rankingEntry.TotalDeaths = entry.TotalDeaths;
rankingEntry.DeathPenaltyPointsTotal = entry.DeathPenaltyPointsTotal;
}
else
{
rankingDatabase.Entries.Add(entry);
}
}
}
}
return NormalizeDatabase(rankingDatabase);
}
catch (Exception ex)
{
((BaseUnityPlugin)this).Logger.LogError((object)("Erro ao importar banco legado do ranking: " + ex));
return result;
}
}
private RankingDatabase NormalizeDatabase(RankingDatabase database)
{
if (database == null)
{
database = new RankingDatabase();
}
if (database.Entries == null)
{
database.Entries = new List<RankingEntry>();
}
if (database.Claims == null)
{
database.Claims = new List<RewardClaimRecord>();
}
if (database.MarketplaceQuestCredits == null)
{
database.MarketplaceQuestCredits = new List<MarketplaceQuestCreditRecord>();
}
if (database.FishingCatchCredits == null)
{
database.FishingCatchCredits = new List<FishingCatchCreditRecord>();
}
foreach (RankingEntry entry in database.Entries)
{
if (entry != null)
{
entry.PlayerName = SanitizePlayerName(entry.PlayerName);
entry.LastReason = SafeReason(entry.LastReason);
entry.LastUpdateUtc = SafeLimit(entry.LastUpdateUtc, 64);
entry.Points = Mathf.Clamp(entry.Points, -2147483647, int.MaxValue);
entry.TotalKillsPontuadas = Mathf.Clamp(entry.TotalKillsPontuadas, 0, int.MaxValue);
entry.TotalBossesPontuadas = Mathf.Clamp(entry.TotalBossesPontuadas, 0, int.MaxValue);
entry.KillPointsTotal = Mathf.Clamp(entry.KillPointsTotal, -2147483647, int.MaxValue);
entry.BossPointsTotal = Mathf.Clamp(entry.BossPointsTotal, -2147483647, int.MaxValue);
entry.TotalSkillLevelUpsPontuados = Mathf.Clamp(entry.TotalSkillLevelUpsPontuados, 0, int.MaxValue);
entry.SkillPointsTotal = Mathf.Clamp(entry.SkillPointsTotal, -2147483647, int.MaxValue);
entry.TotalFishingPontuadas = Mathf.Clamp(entry.TotalFishingPontuadas, 0, int.MaxValue);
entry.FishingPointsTotal = Mathf.Clamp(entry.FishingPointsTotal, -2147483647, int.MaxValue);
entry.TotalCraftPontuadas = Mathf.Clamp(entry.TotalCraftPontuadas, 0, int.MaxValue);
entry.CraftPointsTotal = Mathf.Clamp(entry.CraftPointsTotal, -2147483647, int.MaxValue);
entry.TotalFarmJackpotsPontuados = Mathf.Clamp(entry.TotalFarmJackpotsPontuados, 0, int.MaxValue);
entry.FarmJackpotPointsTotal = Mathf.Clamp(entry.FarmJackpotPointsTotal, -2147483647, int.MaxValue);
entry.TotalUniqueCraftJackpotsPontuados = Mathf.Clamp(entry.TotalUniqueCraftJackpotsPontuados, 0, int.MaxValue);
entry.UniqueCraftJackpotPointsTotal = Mathf.Clamp(entry.UniqueCraftJackpotPointsTotal, -2147483647, int.MaxValue);
entry.TotalDeaths = Mathf.Clamp(entry.TotalDeaths, 0, int.MaxValue);
entry.DeathPenaltyPointsTotal = Mathf.Clamp(entry.DeathPenaltyPointsTotal, 0, int.MaxValue);
entry.BossCredits = ((entry.BossCredits == null) ? new HashSet<string>(StringComparer.OrdinalIgnoreCase) : new HashSet<string>(entry.BossCredits.Where((string x) => !string.IsNullOrWhiteSpace(x)).Select(SafeKey), StringComparer.OrdinalIgnoreCase));
entry.GenericJackpotCredits = ((entry.GenericJackpotCredits == null) ? new HashSet<string>(StringComparer.OrdinalIgnoreCase) : new HashSet<string>(entry.GenericJackpotCredits.Where((string x) => !string.IsNullOrWhiteSpace(x)).Select(SafeKey), StringComparer.OrdinalIgnoreCase));
entry.SkillLevel100JackpotCredits = ((entry.SkillLevel100JackpotCredits == null) ? new HashSet<string>(StringComparer.OrdinalIgnoreCase) : new HashSet<string>(entry.SkillLevel100JackpotCredits.Where((string x) => !string.IsNullOrWhiteSpace(x)).Select(SafeKey), StringComparer.OrdinalIgnoreCase));
entry.ProgressCounters = ((entry.ProgressCounters == null) ? new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase) : entry.ProgressCounters.Where((KeyValuePair<string, int> x) => !string.IsNullOrWhiteSpace(x.Key)).GroupBy<KeyValuePair<string, int>, string>((KeyValuePair<string, int> x) => SafeKey(x.Key), StringComparer.OrdinalIgnoreCase).ToDictionary<IGrouping<string, KeyValuePair<string, int>>, string, int>((IGrouping<string, KeyValuePair<string, int>> x) => x.Key, (IGrouping<string, KeyValuePair<string, int>> x) => Mathf.Clamp(x.Last().Value, 0, int.MaxValue), StringComparer.OrdinalIgnoreCase));
}
}
database.Entries = (from x in database.Entries.Where((RankingEntry x) => x != null && !string.IsNullOrWhiteSpace(x.PlayerName)).GroupBy<RankingEntry, string>((RankingEntry x) => x.PlayerName, StringComparer.OrdinalIgnoreCase)
select x.First()).OrderBy<RankingEntry, string>((RankingEntry x) => x.PlayerName, StringComparer.OrdinalIgnoreCase).ToList();
database.Claims = (from x in database.Claims.Where((RewardClaimRecord x) => x != null && !string.IsNullOrWhiteSpace(x.CycleId) && !string.IsNullOrWhiteSpace(x.PlayerName) && x.Rank > 0).Select(delegate(RewardClaimRecord x)
{
x.CycleId = SafeLimit(x.CycleId, 64);
x.PlayerName = SanitizePlayerName(x.PlayerName);
x.Rank = Mathf.Clamp(x.Rank, 1, int.MaxValue);
x.ClaimedAtUtc = SafeLimit(x.ClaimedAtUtc, 64);
x.Status = SafeLimit(string.IsNullOrWhiteSpace(x.Status) ? "claimed" : x.Status, 16);
return x;
}).GroupBy<RewardClaimRecord, string>((RewardClaimRecord x) => BuildRewardClaimKey(x.CycleId, x.PlayerName, x.Rank), StringComparer.OrdinalIgnoreCase)
select x.First()).ToList();
database.MarketplaceQuestCredits = (from x in database.MarketplaceQuestCredits.Where((MarketplaceQuestCreditRecord x) => x != null && !string.IsNullOrWhiteSpace(x.PlayerName) && !string.IsNullOrWhiteSpace(x.QuestKey)).Select(delegate(MarketplaceQuestCreditRecord x)
{
x.PlayerName = SanitizePlayerName(x.PlayerName);
x.QuestKey = SafeKey(x.QuestKey);
x.GrantedAtUtc = SafeLimit(x.GrantedAtUtc, 64);
return x;
}).GroupBy<MarketplaceQuestCreditRecord, string>((MarketplaceQuestCreditRecord x) => BuildMarketplaceQuestCreditKey(x.PlayerName, x.QuestKey), StringComparer.OrdinalIgnoreCase)
select x.First()).ToList();
database.FishingCatchCredits = (from x in database.FishingCatchCredits.Where((FishingCatchCreditRecord x) => x != null && !string.IsNullOrWhiteSpace(x.ZdoKey)).Select(delegate(FishingCatchCreditRecord x)
{
x.PlayerName = SanitizePlayerName(x.PlayerName);
x.FishPrefab = SafeKey(x.FishPrefab);
x.ZdoKey = SafeKey(x.ZdoKey);
x.GrantedAtUtc = SafeLimit(x.GrantedAtUtc, 64);
return x;
}).GroupBy<FishingCatchCreditRecord, string>((FishingCatchCreditRecord x) => x.ZdoKey, StringComparer.OrdinalIgnoreCase)
select x.First()).ToList();
return database;
}
private void SaveDatabase()
{
try
{
if (IsDedicatedServerInstance() && !string.IsNullOrWhiteSpace(_databaseFilePath) && _database != null)
{
_database = NormalizeDatabase(_database);
string contents = JsonConvert.SerializeObject((object)_database, (Formatting)1);
File.WriteAllText(_databaseFilePath, contents, Encoding.UTF8);
}
}
catch (Exception ex)
{
((BaseUnityPlugin)this).Logger.LogError((object)("Erro ao salvar banco JSON do ranking: " + ex));
}
}
private string SerializeStringSet(HashSet<string> values)
{
if (values == null || values.Count == 0)
{
return "";
}
return string.Join("\n", values.Where((string x) => !string.IsNullOrWhiteSpace(x)).Select(SafeKey).Distinct<string>(StringComparer.OrdinalIgnoreCase)
.OrderBy<string, string>((string x) => x, StringComparer.OrdinalIgnoreCase)
.ToArray());
}
private HashSet<string> DeserializeStringSet(string raw)
{
HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
if (string.IsNullOrWhiteSpace(raw))
{
return hashSet;
}
string[] array = raw.Split(new char[1] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
foreach (string value in array)
{
string text = SafeKey(value);
if (!string.IsNullOrWhiteSpace(text))
{
hashSet.Add(text);
}
}
return hashSet;
}
private string SerializeStringIntDictionary(Dictionary<string, int> values)
{
if (values == null || values.Count == 0)
{
return "";
}
return string.Join("\n", (from p in values
where !string.IsNullOrWhiteSpace(p.Key)
select EncodeDatabaseField(SafeKey(p.Key)) + ":" + Mathf.Clamp(p.Value, 0, int.MaxValue)).OrderBy<string, string>((string x) => x, StringComparer.OrdinalIgnoreCase).ToArray());
}
private Dictionary<string, int> DeserializeStringIntDictionary(string raw)
{
Dictionary<string, int> dictionary = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
if (string.IsNullOrWhiteSpace(raw))
{
return dictionary;
}
string[] array = raw.Split(new char[1] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
foreach (string text in array)
{
string text2 = ((text == null) ? "" : text.Trim());
if (string.IsNullOrWhiteSpace(text2))
{
continue;
}
int num = text2.LastIndexOf(':');
if (num <= 0 || num >= text2.Length - 1)
{
continue;
}
string text3 = text2.Substring(0, num);
string s = text2.Substring(num + 1);
if (int.TryParse(s, out var result))
{
string value = DecodeDatabaseField(text3);
if (string.IsNullOrWhiteSpace(value))
{
value = text3;
}
value = SafeKey(value);
if (!string.IsNullOrWhiteSpace(value))
{
dictionary[value] = Mathf.Clamp(result, 0, int.MaxValue);
}
}
}
return dictionary;
}
private string SerializeStringFloatDictionary(Dictionary<string, float> values)
{
if (values == null || values.Count == 0)
{
return "";
}
return string.Join("\n", (from p in values
where !string.IsNullOrWhiteSpace(p.Key)
select EncodeDatabaseField(SafeKey(p.Key)) + ":" + Mathf.Clamp(p.Value, 0f, 1000000f).ToString("R", CultureInfo.InvariantCulture)).OrderBy<string, string>((string x) => x, StringComparer.OrdinalIgnoreCase).ToArray());
}
private Dictionary<string, float> DeserializeStringFloatDictionary(string raw)
{
Dictionary<string, float> dictionary = new Dictionary<string, float>(StringComparer.OrdinalIgnoreCase);
if (string.IsNullOrWhiteSpace(raw))
{
return dictionary;
}
string[] array = raw.Split(new char[1] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
foreach (string text in array)
{
string text2 = ((text == null) ? "" : text.Trim());
if (string.IsNullOrWhiteSpace(text2))
{
continue;
}
int num = text2.LastIndexOf(':');
if (num <= 0 || num >= text2.Length - 1)
{
continue;
}
string text3 = text2.Substring(0, num);
string s = text2.Substring(num + 1);
if (float.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
{
string value = DecodeDatabaseField(text3);
if (string.IsNullOrWhiteSpace(value))
{
value = text3;
}
value = SafeKey(value);
if (!string.IsNullOrWhiteSpace(value))
{
dictionary[value] = Mathf.Clamp(result, 0f, 1000000f);
}
}
}
return dictionary;
}
private string EncodeDatabaseField(string value)
{
try
{
byte[] bytes = Encoding.UTF8.GetBytes(value ?? "");
return Convert.ToBase64String(bytes);
}
catch
{
return "";
}
}
private string DecodeDatabaseField(string value)
{
try
{
if (string.IsNullOrWhiteSpace(value))
{
return "";
}
byte[] bytes = Convert.FromBase64String(value);
return Encoding.UTF8.GetString(bytes);
}
catch
{
return "";
}
}
private void EnsureRulesFileExists()
{
try
{
if (!File.Exists(_rulesFilePath))
{
File.WriteAllText(_rulesFilePath, BuildDefaultRulesFileContents(), Encoding.UTF8);
}
}
catch (Exception ex)
{
((BaseUnityPlugin)this).Logger.LogError((object)("Erro ao criar arquivo de regras: " + ex));
}
}
private string BuildDefaultRulesFileContents()
{
StringBuilder stringBuilder = new StringBuilder(16384);
stringBuilder.AppendLine("# =======================================================================");
stringBuilder.AppendLine("# GLITNIR RANKING - CONFIGURACAO DE PONTUACAO");
stringBuilder.AppendLine("# =======================================================================");
stringBuilder.AppendLine("# Este arquivo controla as regras do ranking do servidor Glitnir.");
stringBuilder.AppendLine("# Prefab = nome interno do item/criatura/colheita no Valheim.");
stringBuilder.AppendLine("# Pontos = valor entregue ao jogador quando a regra for validada.");
stringBuilder.AppendLine("#");
stringBuilder.AppendLine("# Organizacao principal:");
stringBuilder.AppendLine("# - KillPoints: pontos por criatura normal. Peixes NAO ficam aqui.");
stringBuilder.AppendLine("# - FishingPoints: somente pontos por peixe realmente pescado.");
stringBuilder.AppendLine("# - BossPoints: pontos por bosses e minibosses.");
stringBuilder.AppendLine("# - CraftPoints.Rules: pontos por craft, em uma linha limpa separada por ponto e virgula.");
stringBuilder.AppendLine("# - FarmJackpots.Rules: jackpots de colheita, em uma linha limpa separada por ponto e virgula.");
stringBuilder.AppendLine("# - UniqueCraftJackpots.Rules: jackpots unicos por craft, em uma linha limpa separada por ponto e virgula.");
stringBuilder.AppendLine("#");
stringBuilder.AppendLine("# Nao use virgulas nas listas de producao/jackpot. Use ;");
stringBuilder.AppendLine("# Exemplos:");
stringBuilder.AppendLine("# CraftPoints.Rules=MeadHealthMinor:20;ArrowNeedle:2");
stringBuilder.AppendLine("# FarmJackpots.Rules=Carrot:200:1000");
stringBuilder.AppendLine("# UniqueCraftJackpots.Rules=IronSword:1:800");
stringBuilder.AppendLine("# =======================================================================");
stringBuilder.AppendLine();
stringBuilder.AppendLine("RankingEnabled=true");
stringBuilder.AppendLine("EnableKillPoints=true");
stringBuilder.AppendLine("EnableBossPoints=true");
stringBuilder.AppendLine("DefaultKillPoints=1");
stringBuilder.AppendLine("TopCount=10");
stringBuilder.AppendLine("AllowRepeatedBossPoints=false");
stringBuilder.AppendLine();
stringBuilder.AppendLine("# Logging");
stringBuilder.AppendLine("DebugLogging=false");
stringBuilder.AppendLine("LogHitReports=false");
stringBuilder.AppendLine("LogKillReports=false");
stringBuilder.AppendLine("LogSkillReports=false");
stringBuilder.AppendLine("LogPendingKillReports=false");
stringBuilder.AppendLine("LogSnapshotRequests=false");
stringBuilder.AppendLine("LogPointsChanges=false");
stringBuilder.AppendLine();
stringBuilder.AppendLine("# Skill milestone jackpot points");
stringBuilder.AppendLine("EnableSkillPoints=true");
stringBuilder.AppendLine("IgnoreTamedKills=true");
stringBuilder.AppendLine("EnableMarketplaceQuestPoints=true");
stringBuilder.AppendLine("EnableFishingPoints=true");
stringBuilder.AppendLine("EnableDeathPenalty=true");
stringBuilder.AppendLine();
stringBuilder.AppendLine("# Marketplace quest points");
stringBuilder.AppendLine("[MarketplaceQuestPoints]");
stringBuilder.AppendLine("QuestPointMap=ccq_b21:450,ccq_b22:150");
stringBuilder.AppendLine();
stringBuilder.AppendLine("# Penalidade por morte");
stringBuilder.AppendLine("[DeathPenalty]");
stringBuilder.AppendLine("# Formato: quantidadeDeMortes:pontosPerdidos,quantidadeDeMortes:pontosPerdidos");
stringBuilder.AppendLine("# Exemplo: 1:0,2:100,5:300,10:800");
stringBuilder.AppendLine("Rules=1:0,2:100,5:300,10:800");
stringBuilder.AppendLine();
stringBuilder.AppendLine("# Reward claims");
stringBuilder.AppendLine("RewardClaimsEnabled=true");
stringBuilder.AppendLine("RewardClaimCycleId=temporada_001");
stringBuilder.AppendLine("RewardTop1MinPoints=300");
stringBuilder.AppendLine("RewardTop1Label=Recompensa do 1 lugar");
stringBuilder.AppendLine("RewardTop1Prefab=Coins");
stringBuilder.AppendLine("RewardTop1Amount=500");
stringBuilder.AppendLine("RewardTop2MinPoints=200");
stringBuilder.AppendLine("RewardTop2Label=Recompensa do 2 lugar");
stringBuilder.AppendLine("RewardTop2Prefab=AmberPearl");
stringBuilder.AppendLine("RewardTop2Amount=10");
stringBuilder.AppendLine("RewardTop3MinPoints=100");
stringBuilder.AppendLine("RewardTop3Label=Recompensa do 3 lugar");
stringBuilder.AppendLine("RewardTop3Prefab=Ruby");
stringBuilder.AppendLine("RewardTop3Amount=5");
stringBuilder.AppendLine();
AppendBossPoints(stringBuilder);
AppendKillPoints(stringBuilder);
AppendFishingPoints(stringBuilder);
AppendSkillMilestoneJackpotPoints(stringBuilder);
AppendProductionRankingRules(stringBuilder);
return stringBuilder.ToString();
}
private void AppendBossPoints(StringBuilder sb)
{
sb.AppendLine("# =========================");
sb.AppendLine("# BOSS POINTS");
sb.AppendLine("# =========================");
sb.AppendLine("# Exemplo. Adicione outros bosses seguindo o mesmo formato.");
sb.AppendLine("BossPoints.Eikthyr=50");
sb.AppendLine();
}
private void AppendPoint(StringBuilder sb, string section, string key, int points)
{
sb.AppendLine(section + "." + key + "=" + points);
}
private void AppendKillPoint(StringBuilder sb, string prefabName, int points, string comment = null)
{
}
private void AppendKillPoints(StringBuilder sb)
{
sb.AppendLine("# =========================");
sb.AppendLine("# KILL POINTS");
sb.AppendLine("# =========================");
sb.AppendLine("# Pontos por criaturas normais em UMA linha configuravel.");
sb.AppendLine("# Formato: Prefab:pontos,Prefab:pontos");
sb.AppendLine("# Exemplo: Skeleton:1,Troll:5,Deathsquito:3");
sb.AppendLine("# Para adicionar mobs futuramente, acrescente no final da linha Rules.");
sb.AppendLine("# Peixes nao devem ficar aqui; use [FishingPoints].");
sb.AppendLine("[KillPoints]");
sb.AppendLine("Rules=Boar:1");
sb.AppendLine();
}
private void AppendFishingPoints(StringBuilder sb)
{
sb.AppendLine("# =========================");
sb.AppendLine("# FISHING POINTS");
sb.AppendLine("# =========================");
sb.AppendLine("# Exemplo. Peixe NÃO pontua em KillPoints.");
AppendFishingPoint(sb, "Fish1", 1);
sb.AppendLine();
}
private void AppendFishingPoint(StringBuilder sb, string prefabName, int points)
{
sb.AppendLine("FishingPoints." + prefabName + "=" + points);
}
private void AppendSkillPoint(StringBuilder sb, string skillKey, int points, string comment = null)
{
if (!string.IsNullOrWhiteSpace(comment))
{
sb.AppendLine(comment);
}
sb.AppendLine("SkillPoints." + skillKey + "=" + points);
}
private void AppendSkillMilestoneJackpotPoints(StringBuilder sb)
{
sb.AppendLine("# =========================");
sb.AppendLine("# SKILL MILESTONE JACKPOT POINTS");
sb.AppendLine("# =========================");
sb.AppendLine("# Pontos únicos por marco de skill. Formato: SkillMilestoneJackpotPoints.Skill=20:pontos,40:pontos,60:pontos,80:pontos,100:pontos");
sb.AppendLine("# Exemplo: Bows 20/40/60/80/100. Cada marco pontua uma única vez por jogador/ciclo.");
AppendSkillMilestoneJackpotPoint(sb, "BloodMagic", "20:1500,40:3000,60:5000,80:7500,100:12000");
AppendSkillMilestoneJackpotPoint(sb, "ElementalMagic", "20:1000,40:2000,60:3500,80:5500,100:8000");
AppendSkillMilestoneJackpotPoint(sb, "Bows", "20:500,40:1000,60:2000,80:3000,100:4000");
AppendSkillMilestoneJackpotPoint(sb, "Crossbows", "20:500,40:1000,60:2000,80:3000,100:4000");
AppendSkillMilestoneJackpotPoint(sb, "Swords", "20:500,40:1000,60:2000,80:3000,100:4000");
AppendSkillMilestoneJackpotPoint(sb, "Axes", "20:500,40:1000,60:2000,80:3000,100:4000");
AppendSkillMilestoneJackpotPoint(sb, "Clubs", "20:500,40:1000,60:2000,80:3000,100:4000");
AppendSkillMilestoneJackpotPoint(sb, "Spears", "20:500,40:1000,60:2000,80:3000,100:4000");
AppendSkillMilestoneJackpotPoint(sb, "Polearms", "20:500,40:1000,60:2000,80:3000,100:4000");
AppendSkillMilestoneJackpotPoint(sb, "Knives", "20:500,40:1000,60:2000,80:3000,100:4000");
AppendSkillMilestoneJackpotPoint(sb, "Blocking", "20:400,40:800,60:1500,80:2500,100:3500");
AppendSkillMilestoneJackpotPoint(sb, "Run", "20:300,40:700,60:1200,80:2000,100:3000");
AppendSkillMilestoneJackpotPoint(sb, "Jump", "20:300,40:700,60:1200,80:2000,100:3000");
AppendSkillMilestoneJackpotPoint(sb, "WoodCutting", "20:300,40:700,60:1200,80:1800,100:2500");
AppendSkillMilestoneJackpotPoint(sb, "Pickaxes", "20:300,40:700,60:1200,80:1800,100:2500");
AppendSkillMilestoneJackpotPoint(sb, "Farming", "20:0,40:0,60:0,80:0,100:0");
sb.AppendLine();
}
private void AppendSkillMilestoneJackpotPoint(StringBuilder sb, string skillKey, string milestones)
{
sb.AppendLine("SkillMilestoneJackpotPoints." + skillKey + "=" + milestones);
}
private void AppendProductionRankingRules(StringBuilder sb)
{
sb.AppendLine("# =======================================================================");
sb.AppendLine("# PRODUCAO E JACKPOTS - GLITNIR RANKING");
sb.AppendLine("# =======================================================================");
sb.AppendLine("# Estes blocos usam UMA entrada por sistema para manter o arquivo limpo.");
sb.AppendLine("# Nao use virgulas. Separe cada regra com ponto e virgula (;).");
sb.AppendLine("# Formatos:");
sb.AppendLine("# CraftPoints.Rules = Prefab:pontos;OutroPrefab:pontos");
sb.AppendLine("# FarmJackpots.Rules = Prefab:quantidade:pontos;OutroPrefab:quantidade:pontos");
sb.AppendLine("# UniqueCraftJackpots.Rules = Prefab:quantidade:pontos;OutroPrefab:quantidade:pontos");
sb.AppendLine("# Exemplos reais:");
sb.AppendLine("# MeadHealthMinor:20");
sb.AppendLine("# Carrot:200:1000");
sb.AppendLine("# IronSword:1:800");
sb.AppendLine("# =======================================================================");
sb.AppendLine();
sb.AppendLine("# =========================");
sb.AppendLine("# CRAFT POINTS");
sb.AppendLine("# =========================");
sb.AppendLine("# Pontos por craft de qualquer prefab configurado.");
sb.AppendLine("# Exemplo de item: MeadHealthMinor:20");
sb.AppendLine("CraftPoints.Rules=MeadHealthMinor:20");
sb.AppendLine();
sb.AppendLine("# =========================");
sb.AppendLine("# FARM JACKPOTS");
sb.AppendLine("# =========================");
sb.AppendLine("# Jackpot por colheita. Premia uma vez por jogador/ciclo quando bater a quantidade.");
sb.AppendLine("# Exemplo de item: Carrot:200:1000");
sb.AppendLine("FarmJackpots.Rules=Carrot:200:1000");
sb.AppendLine();
sb.AppendLine("# =========================");
sb.AppendLine("# UNIQUE CRAFT JACKPOTS");
sb.AppendLine("# =========================");
sb.AppendLine("# Jackpot unico por craft. Ideal para armas, armaduras e marcos especiais.");
sb.AppendLine("# Exemplo de item: IronSword:1:800");
sb.AppendLine("UniqueCraftJackpots.Rules=IronSword:1:800");
sb.AppendLine();
}
private void AppendStringPoint(StringBuilder sb, string section, string key, string value)
{
sb.AppendLine(section + "." + key + "=" + value);
}
private void LoadRules()
{
_rules = new RankingRules();
try
{
if (!File.Exists(_rulesFilePath))
{
return;
}
string[] array = File.ReadAllLines(_rulesFilePath, Encoding.UTF8);
foreach (string text in array)
{
string text2 = text.Trim();
if (string.IsNullOrWhiteSpace(text2) || text2.StartsWith("#") || text2.StartsWith(";") || text2.StartsWith("//"))
{
continue;
}
int num = text2.IndexOf('=');
if (num <= 0)
{
continue;
}
string text3 = text2.Substring(0, num).Trim();
string text4 = text2.Substring(num + 1).Trim();
if (text3.Equals("RankingEnabled", StringComparison.OrdinalIgnoreCase))
{
_rules.RankingEnabled = ParseBool(text4, _rules.RankingEnabled);
}
else if (text3.Equals("EnableKillPoints", StringComparison.OrdinalIgnoreCase))
{
_rules.EnableKillPoints = ParseBool(text4, _rules.EnableKillPoints);
}
else if (text3.Equals("EnableBossPoints", StringComparison.OrdinalIgnoreCase))
{
_rules.EnableBossPoints = ParseBool(text4, _rules.EnableBossPoints);
}
else if (text3.Equals("DefaultKillPoints", StringComparison.OrdinalIgnoreCase))
{
_rules.DefaultKillPoints = ParseInt(text4, _rules.DefaultKillPoints);
}
else if (text3.Equals("TopCount", StringComparison.OrdinalIgnoreCase))
{
_rules.TopCount = Mathf.Clamp(ParseInt(text4, _rules.TopCount), 1, 50);
}
else if (text3.Equals("AllowRepeatedBossPoints", StringComparison.OrdinalIgnoreCase))
{
_rules.AllowRepeatedBossPoints = ParseBool(text4, _rules.AllowRepeatedBossPoints);
}
else if (text3.Equals("DebugLogging", StringComparison.OrdinalIgnoreCase))
{
_rules.DebugLogging = ParseBool(text4, _rules.DebugLogging);
}
else if (text3.Equals("LogHitReports", StringComparison.OrdinalIgnoreCase))
{
_rules.LogHitReports = ParseBool(text4, _rules.LogHitReports);
}
else if (text3.Equals("LogKillReports", StringComparison.OrdinalIgnoreCase))
{
_rules.LogKillReports = ParseBool(text4, _rules.LogKillReports);
}
else if (text3.Equals("LogPendingKillReports", StringComparison.OrdinalIgnoreCase))
{
_rules.LogPendingKillReports = ParseBool(text4, _rules.LogPendingKillReports);
}
else if (text3.Equals("LogSnapshotRequests", StringComparison.OrdinalIgnoreCase))
{
_rules.LogSnapshotRequests = ParseBool(text4, _rules.LogSnapshotRequests);
}
else if (text3.Equals("LogPointsChanges", StringComparison.OrdinalIgnoreCase))
{
_rules.LogPointsChanges = ParseBool(text4, _rules.LogPointsChanges);
}
else if (text3.Equals("EnableSkillPoints", StringComparison.OrdinalIgnoreCase))
{
_rules.EnableSkillPoints = ParseBool(text4, _rules.EnableSkillPoints);
}
else if (text3.Equals("LogSkillReports", StringComparison.OrdinalIgnoreCase))
{
_rules.LogSkillReports = ParseBool(text4, _rules.LogSkillReports);
}
else if (text3.Equals("EnableFishingPoints", StringComparison.OrdinalIgnoreCase))
{
_rules.EnableFishingPoints = ParseBool(text4, _rules.EnableFishingPoints);
}
else if (text3.Equals("EnableDeathPenalty", StringComparison.OrdinalIgnoreCase))
{
_rules.EnableDeathPenalty = ParseBool(text4, _rules.EnableDeathPenalty);
}
else if (text3.Equals("DeathPenalty.Rules", StringComparison.OrdinalIgnoreCase) || text3.Equals("DeathPenaltyRules", StringComparison.OrdinalIgnoreCase))
{
_rules.DeathPenaltyRules = ParseDeathPenaltyRules(text4);
}
else if (text3.Equals("RewardClaimsEnabled", StringComparison.OrdinalIgnoreCase))
{
_rules.RewardClaimsEnabled = ParseBool(text4, _rules.RewardClaimsEnabled);
}
else if (text3.Equals("RewardClaimCycleId", StringComparison.OrdinalIgnoreCase))
{
_rules.RewardClaimCycleId = SafeLimit(text4, 64);
}
else if (text3.Equals("RewardTop1MinPoints", StringComparison.OrdinalIgnoreCase))
{
_rules.RewardTop1MinPoints = Mathf.Max(0, ParseInt(text4, _rules.RewardTop1MinPoints));
}
else if (text3.Equals("RewardTop1Label", StringComparison.OrdinalIgnoreCase))
{
_rules.RewardTop1Label = SafeLimit(text4, 96);
}
else if (text3.Equals("RewardTop1Prefab", StringComparison.OrdinalIgnoreCase))
{
_rules.RewardTop1Prefab = SafeLimit(text4, 96);
}
else if (text3.Equals("RewardTop1Amount", StringComparison.OrdinalIgnoreCase))
{
_rules.RewardTop1Amount = Mathf.Max(0, ParseInt(text4, _rules.RewardTop1Amount));
}
else if (text3.Equals("RewardTop2MinPoints", StringComparison.OrdinalIgnoreCase))
{
_rules.RewardTop2MinPoints = Mathf.Max(0, ParseInt(text4, _rules.RewardTop2MinPoints));
}
else if (text3.Equals("RewardTop2Label", StringComparison.OrdinalIgnoreCase))
{
_rules.RewardTop2Label = SafeLimit(text4, 96);
}
else if (text3.Equals("RewardTop2Prefab", StringComparison.OrdinalIgnoreCase))
{
_rules.RewardTop2Prefab = SafeLimit(text4, 96);
}
else if (text3.Equals("RewardTop2Amount", StringComparison.OrdinalIgnoreCase))
{
_rules.RewardTop2Amount = Mathf.Max(0, ParseInt(text4, _rules.RewardTop2Amount));
}
else if (text3.Equals("RewardTop3MinPoints", StringComparison.OrdinalIgnoreCase))
{
_rules.RewardTop3MinPoints = Mathf.Max(0, ParseInt(text4, _rules.RewardTop3MinPoints));
}
else if (text3.Equals("RewardTop3Label", StringComparison.OrdinalIgnoreCase))
{
_rules.RewardTop3Label = SafeLimit(text4, 96);
}
else if (text3.Equals("RewardTop3Prefab", StringComparison.OrdinalIgnoreCase))
{
_rules.RewardTop3Prefab = SafeLimit(text4, 96);
}
else if (text3.Equals("RewardTop3Amount", StringComparison.OrdinalIgnoreCase))
{
_rules.RewardTop3Amount = Mathf.Max(0, ParseInt(text4, _rules.RewardTop3Amount));
}
else if (text3.StartsWith("KillPoints.", StringComparison.OrdinalIgnoreCase))
{
string text5 = SafeKey(text3.Substring("KillPoints.".Length));
if (!IsFishPrefab(text5))
{
_rules.KillPoints[text5] = ParseInt(text4, 0);
}
}
else if (text3.StartsWith("BossPoints.", StringComparison.OrdinalIgnoreCase))
{
_rules.BossPoints[SafeKey(text3.Substring("BossPoints.".Length))] = ParseInt(text4, 0);
}
else if (text3.StartsWith("FishingPoints.", StringComparison.OrdinalIgnoreCase))
{
string text6 = SafeKey(text3.Substring("FishingPoints.".Length));
if (IsFishPrefab(text6))
{
_rules.FishingPoints[text6] = ParseInt(text4, 0);
}
}
}
}
catch (Exception ex)
{
((BaseUnityPlugin)this).Logger.LogError((object)("Erro ao carregar regras do ranking: " + ex));
}
}
private bool ParseBool(string raw, bool fallback)
{
bool result;
return bool.TryParse(raw, out result) ? result : fallback;
}
private int ParseInt(string raw, int fallback)
{
int result;
return int.TryParse(raw, out result) ? result : fallback;
}
private void InitializeSyncedRulesConfig()
{
try
{
string filePath = Path.Combine(Paths.ConfigPath, "glitnir.ranking.rules.cfg");
Dictionary<string, string> dictionary = LoadLegacyRulesOverrides(filePath);
MergeSectionOverrides(dictionary, LoadSectionRulesOverrides(filePath));
_rulesConfig = ((BaseUnityPlugin)this).Config;
try
{
SynchronizationManager.Instance.RegisterCustomConfig(_rulesConfig);
}
catch
{
}
BindSyncedRulesConfigEntries(dictionary);
ApplyRulesFromSyncedConfig();
_rulesConfig.Save();
CleanupFishKillPointConfigEntries();
RewriteRulesConfigHeaderAndGroupedSections();
}
catch (Exception ex)
{
((BaseUnityPlugin)this).Logger.LogError((object)("Erro ao inicializar config sincronizada do ranking: " + ex));
EnsureRulesFileExists();
LoadRules();
}
}
private void SetupRulesConfigWatcher()
{
try
{
if (!string.IsNullOrWhiteSpace(_rulesFilePath))
{
string directoryName = Path.GetDirectoryName(_rulesFilePath);
string fileName = Path.GetFileName(_rulesFilePath);
if (!string.IsNullOrWhiteSpace(directoryName) && !string.IsNullOrWhiteSpace(fileName))
{
_rulesConfigWatcher = new FileSystemWatcher(directoryName, fileName);
_rulesConfigWatcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.Size | NotifyFilters.LastWrite | NotifyFilters.CreationTime;
_rulesConfigWatcher.Changed += OnRulesConfigFileChanged;
_rulesConfigWatcher.Created += OnRulesConfigFileChanged;
_rulesConfigWatcher.Renamed += OnRulesConfigFileChanged;
_rulesConfigWatcher.EnableRaisingEvents = true;
}
}
}
catch (Exception ex)
{
((BaseUnityPlugin)this).Logger.LogWarning((object)("Não foi possível iniciar watcher da config do ranking: " + ex.Message));
}
}
private void OnRulesConfigFileChanged(object sender, FileSystemEventArgs args)
{
_rulesConfigReloadQueued = true;
_rulesConfigReloadAt = Time.realtimeSinceStartup + 0.5f;
}
private void ProcessRulesConfigReloadIfNeeded()
{
if (!_rulesConfigReloadQueued || Time.realtimeSinceStartup < _rulesConfigReloadAt)
{
return;
}
_rulesConfigReloadQueued = false;
try
{
ConfigFile rulesConfig = _rulesConfig;
if (rulesConfig != null)
{
rulesConfig.Reload();
}
BindSyncedRulesConfigEntries(null);
ApplyRulesFromSyncedConfig();
}
catch (Exception ex)
{
((BaseUnityPlugin)this).Logger.LogError((object)("Erro ao recarregar config do ranking: " + ex));
}
}
private void RefreshRulesFromSyncedConfigIfNeeded()
{
if (!(Time.realtimeSinceStartup < _rulesConfigApplyAt))
{
_rulesConfigApplyAt = Time.realtimeSinceStartup + 1f;
ApplyRulesFromSyncedConfig();
}
}
private Dictionary<string, string> LoadLegacyRulesOverrides(string filePath)
{
Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
try
{
if (string.IsNullOrWhiteSpace(filePath) || !File.Exists(filePath))
{
return dictionary;
}
bool flag = false;
string[] array = File.ReadAllLines(filePath, Encoding.UTF8);
foreach (string text in array)
{
string text2 = text.Trim();
if (text2.StartsWith("[") && text2.EndsWith("]"))
{
flag = true;
break;
}
}
if (flag)
{
return dictionary;
}
string[] array2 = File.ReadAllLines(filePath, Encoding.UTF8);
foreach (string text3 in array2)
{
string text4 = text3.Trim();
if (string.IsNullOrWhiteSpace(text4) || text4.StartsWith("#") || text4.StartsWith(";") || text4.StartsWith("//"))
{
continue;
}
int num = text4.IndexOf('=');
if (num > 0)
{
string text5 = text4.Substring(0, num).Trim();
string value = text4.Substring(num + 1).Trim();
if (!string.IsNullOrWhiteSpace(text5))
{
dictionary[text5] = value;
}
}
}
}
catch (Exception ex)
{
((BaseUnityPlugin)this).Logger.LogWarning((object)("Falha ao ler overrides legados da config do ranking: " + ex.Message));
}
return dictionary;
}
private void MergeSectionOverrides(Dictionary<string, string> target, Dictionary<string, string> source)
{
if (target == null || source == null)
{
return;
}
foreach (KeyValuePair<string, string> item in source)
{
if (!string.IsNullOrWhiteSpace(item.Key))
{
target[item.Key] = item.Value;
}
}
}
private Dictionary<string, string> LoadSectionRulesOverrides(string filePath)
{
Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
try
{
if (string.IsNullOrWhiteSpace(filePath) || !File.Exists(filePath))
{
return dictionary;
}
string text = string.Empty;
HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "KillPoints", "BossPoints", "FishingPoints", "CraftPoints", "FarmJackpots", "UniqueCraftJackpots", "SkillMilestoneJackpotPoints", "DeathPenalty", "ExplorationMapJackpots" };
string[] array = File.ReadAllLines(filePath, Encoding.UTF8);
foreach (string text2 in array)
{
string text3 = ((text2 != null) ? text2.Trim() : string.Empty);
if (string.IsNullOrWhiteSpace(text3) || text3.StartsWith("#") || text3.StartsWith(";") || text3.StartsWith("//"))
{
continue;
}
if (text3.StartsWith("[") && text3.EndsWith("]"))
{
text = SafeKey(text3.Substring(1, text3.Length - 2));
continue;
}
int num = text3.IndexOf('=');
if (num <= 0)
{
continue;
}
string text4 = text3.Substring(0, num).Trim();
string value = text3.Substring(num + 1).Trim();
if (string.IsNullOrWhiteSpace(text4))
{
continue;
}
string text5 = text4;
if (!text4.Contains(".") && !string.IsNullOrWhiteSpace(text))
{
text5 = text + "." + text4;
}
int num2 = text5.IndexOf('.');
if (num2 > 0)
{
string text6 = text5.Substring(0, num2).Trim();
string text7 = SafeKey(text5.Substring(num2 + 1));
if (hashSet.Contains(text6) && !string.IsNullOrWhiteSpace(text7) && (!text6.Equals("KillPoints", StringComparison.OrdinalIgnoreCase) || !IsFishPrefab(text7)))
{
dictionary[text6 + "." + text7] = value;
}
}
}
}
catch (Exception ex)
{
((BaseUnityPlugin)this).Logger.LogWarning((object)("Falha ao ler entradas dinâmicas da config do ranking: " + ex.Message));
}
return dictionary;
}
private bool IsLegacyRulesFileFormat(string filePath)
{
try
{
if (string.IsNullOrWhiteSpace(filePath) || !File.Exists(filePath))
{
return false;
}
string[] array = File.ReadAllLines(filePath, Encoding.UTF8);
foreach (string text in array)
{
string text2 = text.Trim();
if (!string.IsNullOrWhiteSpace(text2) && !text2.StartsWith("#") && !text2.StartsWith(";") && !text2.StartsWith("//"))
{
return !text2.StartsWith("[") || !text2.EndsWith("]");
}
}
}
catch (Exception ex)
{
((BaseUnityPlugin)this).Logger.LogWarning((object)("Falha ao detectar formato legado da config do ranking: " + ex.Message));
}
return false;
}
private void BindSyncedRulesConfigEntries(Dictionary<string, string> legacyOverrides)
{
if (_rulesConfig != null)
{
legacyOverrides = legacyOverrides ?? new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
RankingRules rankingRules = new RankingRules();
_cfgRankingEnabled = ConfigFileExtensions.BindConfig<bool>(_rulesConfig, "General", "RankingEnabled", ReadLegacyBool(legacyOverrides, "RankingEnabled", rankingRules.RankingEnabled), "Ativa o sistema de ranking.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
_cfgEnableKillPoints = ConfigFileExtensions.BindConfig<bool>(_rulesConfig, "General", "EnableKillPoints", ReadLegacyBool(legacyOverrides, "EnableKillPoints", rankingRules.EnableKillPoints), "Ativa a pontuação por criaturas normais.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
_cfgEnableBossPoints = ConfigFileExtensions.BindConfig<bool>(_rulesConfig, "General", "EnableBossPoints", ReadLegacyBool(legacyOverrides, "EnableBossPoints", rankingRules.EnableBossPoints), "Ativa a pontuação por bosses e minibosses.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
_cfgDefaultKillPoints = ConfigFileExtensions.BindConfig<int>(_rulesConfig, "General", "DefaultKillPoints", ReadLegacyInt(legacyOverrides, "DefaultKillPoints", rankingRules.DefaultKillPoints), "Pontos padrão para kills sem regra específica.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
_cfgTopCount = ConfigFileExtensions.BindConfig<int>(_rulesConfig, "General", "TopCount", ReadLegacyInt(legacyOverrides, "TopCount", rankingRules.TopCount), "Quantidade de jogadores exibidos no topo do ranking.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
_cfgAllowRepeatedBossPoints = ConfigFileExtensions.BindConfig<bool>(_rulesConfig, "General", "AllowRepeatedBossPoints", ReadLegacyBool(legacyOverrides, "AllowRepeatedBossPoints", rankingRules.AllowRepeatedBossPoints), "Permite pontuar boss repetido.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
_cfgEnableSkillPoints = ConfigFileExtensions.BindConfig<bool>(_rulesConfig, "General", "EnableSkillPoints", ReadLegacyBool(legacyOverrides, "EnableSkillPoints", rankingRules.EnableSkillPoints), "Ativa jackpots por marcos de skill.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
_cfgIgnoreTamedKills = ConfigFileExtensions.BindConfig<bool>(_rulesConfig, "General", "IgnoreTamedKills", ReadLegacyBool(legacyOverrides, "IgnoreTamedKills", rankingRules.IgnoreTamedKills), "Ignora pontos por matar criaturas domadas.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
_cfgEnableMarketplaceQuestPoints = ConfigFileExtensions.BindConfig<bool>(_rulesConfig, "General", "EnableMarketplaceQuestPoints", ReadLegacyBool(legacyOverrides, "EnableMarketplaceQuestPoints", rankingRules.EnableMarketplaceQuestPoints), "Ativa pontuação por conclusão de quests do Marketplace.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
_cfgEnableFishingPoints = ConfigFileExtensions.BindConfig<bool>(_rulesConfig, "General", "EnableFishingPoints", ReadLegacyBool(legacyOverrides, "EnableFishingPoints", rankingRules.EnableFishingPoints), "Ativa pontuação própria por pesca.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
_cfgEnableDeathPenalty = ConfigFileExtensions.BindConfig<bool>(_rulesConfig, "General", "EnableDeathPenalty", ReadLegacyBool(legacyOverrides, "EnableDeathPenalty", rankingRules.EnableDeathPenalty), "Ativa penalidade por morte no ranking.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
_cfgDebugLogging = ConfigFileExtensions.BindConfig<bool>(_rulesConfig, "Logging", "DebugLogging", ReadLegacyBool(legacyOverrides, "DebugLogging", rankingRules.DebugLogging), "Ativa logs de depuração.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
_cfgLogHitReports = ConfigFileExtensions.BindConfig<bool>(_rulesConfig, "Logging", "LogHitReports", ReadLegacyBool(legacyOverrides, "LogHitReports", rankingRules.LogHitReports), "Ativa logs de hit reports.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
_cfgLogKillReports = ConfigFileExtensions.BindConfig<bool>(_rulesConfig, "Logging", "LogKillReports", ReadLegacyBool(legacyOverrides, "LogKillReports", rankingRules.LogKillReports), "Ativa logs de kill reports.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
_cfgLogSkillReports = ConfigFileExtensions.BindConfig<bool>(_rulesConfig, "Logging", "LogSkillReports", ReadLegacyBool(legacyOverrides, "LogSkillReports", rankingRules.LogSkillReports), "Ativa logs de skill reports.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
_cfgLogPendingKillReports = ConfigFileExtensions.BindConfig<bool>(_rulesConfig, "Logging", "LogPendingKillReports", ReadLegacyBool(legacyOverrides, "LogPendingKillReports", rankingRules.LogPendingKillReports), "Ativa logs das checagens pendentes de kill.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
_cfgLogSnapshotRequests = ConfigFileExtensions.BindConfig<bool>(_rulesConfig, "Logging", "LogSnapshotRequests", ReadLegacyBool(legacyOverrides, "LogSnapshotRequests", rankingRules.LogSnapshotRequests), "Ativa logs de snapshots do ranking.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
_cfgLogPointsChanges = ConfigFileExtensions.BindConfig<bool>(_rulesConfig, "Logging", "LogPointsChanges", ReadLegacyBool(legacyOverrides, "LogPointsChanges", rankingRules.LogPointsChanges), "Ativa logs de alterações de pontos.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
_cfgRewardClaimsEnabled = ConfigFileExtensions.BindConfig<bool>(_rulesConfig, "Rewards", "RewardClaimsEnabled", ReadLegacyBool(legacyOverrides, "RewardClaimsEnabled", rankingRules.RewardClaimsEnabled), "Ativa o resgate de recompensas no HUD.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
_cfgRewardClaimCycleId = ConfigFileExtensions.BindConfig<string>(_rulesConfig, "Rewards", "RewardClaimCycleId", ReadLegacyString(legacyOverrides, "RewardClaimCycleId", rankingRules.RewardClaimCycleId), "Identificador do ciclo atual de recompensas.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
_cfgRewardTop1MinPoints = ConfigFileExtensions.BindConfig<int>(_rulesConfig, "Rewards", "RewardTop1MinPoints", ReadLegacyInt(legacyOverrides, "RewardTop1MinPoints", rankingRules.RewardTop1MinPoints), "Pontos mínimos para o Top 1 resgatar.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
_cfgRewardTop1Label = ConfigFileExtensions.BindConfig<string>(_rulesConfig, "Rewards", "RewardTop1Label", ReadLegacyString(legacyOverrides, "RewardTop1Label", rankingRules.RewardTop1Label), "Texto exibido para a recompensa do Top 1.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
_cfgRewardTop1Prefab = ConfigFileExtensions.BindConfig<string>(_rulesConfig, "Rewards", "RewardTop1Prefab", ReadLegacyString(legacyOverrides, "RewardTop1Prefab", rankingRules.RewardTop1Prefab), "Prefab do item entregue ao Top 1.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
_cfgRewardTop1Amount = ConfigFileExtensions.BindConfig<int>(_rulesConfig, "Rewards", "RewardTop1Amount", ReadLegacyInt(legacyOverrides, "RewardTop1Amount", rankingRules.RewardTop1Amount), "Quantidade do item entregue ao Top 1.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
_cfgRewardTop2MinPoints = ConfigFileExtensions.BindConfig<int>(_rulesConfig, "Rewards", "RewardTop2MinPoints", ReadLegacyInt(legacyOverrides, "RewardTop2MinPoints", rankingRules.RewardTop2MinPoints), "Pontos mínimos para o Top 2 resgatar.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
_cfgRewardTop2Label = ConfigFileExtensions.BindConfig<string>(_rulesConfig, "Rewards", "RewardTop2Label", ReadLegacyString(legacyOverrides, "RewardTop2Label", rankingRules.RewardTop2Label), "Texto exibido para a recompensa do Top 2.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
_cfgRewardTop2Prefab = ConfigFileExtensions.BindConfig<string>(_rulesConfig, "Rewards", "RewardTop2Prefab", ReadLegacyString(legacyOverrides, "RewardTop2Prefab", rankingRules.RewardTop2Prefab), "Prefab do item entregue ao Top 2.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
_cfgRewardTop2Amount = ConfigFileExtensions.BindConfig<int>(_rulesConfig, "Rewards", "RewardTop2Amount", ReadLegacyInt(legacyOverrides, "RewardTop2Amount", rankingRules.RewardTop2Amount), "Quantidade do item entregue ao Top 2.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
_cfgRewardTop3MinPoints = ConfigFileExtensions.BindConfig<int>(_rulesConfig, "Rewards", "RewardTop3MinPoints", ReadLegacyInt(legacyOverrides, "RewardTop3MinPoints", rankingRules.RewardTop3MinPoints), "Pontos mínimos para o Top 3 resgatar.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
_cfgRewardTop3Label = ConfigFileExtensions.BindConfig<string>(_rulesConfig, "Rewards", "RewardTop3Label", ReadLegacyString(legacyOverrides, "RewardTop3Label", rankingRules.RewardTop3Label), "Texto exibido para a recompensa do Top 3.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
_cfgRewardTop3Prefab = ConfigFileExtensions.BindConfig<string>(_rulesConfig, "Rewards", "RewardTop3Prefab", ReadLegacyString(legacyOverrides, "RewardTop3Prefab", rankingRules.RewardTop3Prefab), "Prefab do item entregue ao Top 3.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
_cfgRewardTop3Amount = ConfigFileExtensions.BindConfig<int>(_rulesConfig, "Rewards", "RewardTop3Amount", ReadLegacyInt(legacyOverrides, "RewardTop3Amount", rankingRules.RewardTop3Amount), "Quantidade do item entregue ao Top 3.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
_cfgMarketplaceQuestPointMap = ConfigFileExtensions.BindConfig<string>(_rulesConfig, "MarketplaceQuestPoints", "QuestPointMap", BuildMarketplaceQuestPointMapDefault(legacyOverrides), "Formato: questId:pontos separados por vírgula. Ex: ccq_b21:450,ccq_b22:150", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
_cfgDeathPenaltyRules = ConfigFileExtensions.BindConfig<string>(_rulesConfig, "DeathPenalty", "Rules", ReadLegacyString(legacyOverrides, "DeathPenalty.Rules", ReadLegacyString(legacyOverrides, "DeathPenaltyRules", "1:0,2:100,5:300,10:800")), "Penalidade por morte em marcos. Formato: mortes:pontosPerdidos,mortes:pontosPerdidos. Exemplo: 1:0,2:100,5:300,10:800", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
_cfgEnableExplorationJackpots = ConfigFileExtensions.BindConfig<bool>(_rulesConfig, "ExplorationMapJackpots", "Enabled", ReadLegacyBool(legacyOverrides, "ExplorationMapJackpots.Enabled", fallback: true), "Ativa jackpots por porcentagem de mapa revelado.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
_cfgExplorationMapJackpotRules = ConfigFileExtensions.BindConfig<string>(_rulesConfig, "ExplorationMapJackpots", "Rules", ReadLegacyString(legacyOverrides, "ExplorationMapJackpots.Rules", ReadLegacyString(legacyOverrides, "ExplorationMapJackpotRules", "5:25;10:50;25:150;50:400;75:800;100:1500")), "Jackpots por porcentagem do mapa revelado. Formato: porcentagem:pontos;porcentagem:pontos. Exemplo: 5:25;10:50;25:150", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
_cfgCombatCategoryRules = ConfigFileExtensions.BindConfig<string>(_rulesConfig, "HudCategories", "Combat", ReadLegacyString(legacyOverrides, "HudCategories.Combat", "Prados:Boar=1;Floresta Negra:Troll=5;Pântano:Draugr=2;Montanha:Wolf=2;Planícies:Goblin=3;Oceano:Serpent=6;Mistlands:Seeker=4;Ashlands:Charred_Melee=4;Especiais:Haldor=0"), "Combate por categoria/bioma COM pontos. Formato: Categoria:Prefab=pontos,Prefab=pontos;Outra:Prefab=pontos. Exemplo: Prados:Boar=10,Deer=15;Floresta Negra:Troll=250. Peixes continuam somente em FishingPoints.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
_cfgProductionCategoryRules = ConfigFileExtensions.BindConfig<string>(_rulesConfig, "HudCategories", "Production", ReadLegacyString(legacyOverrides, "HudCategories.Production", "Armas:SwordIron=800;Armaduras:HelmetBronze=800;Comidas:DeerStew=25"), "Producao/Conquistas por categoria COM pontos. O nome da categoria do config vira a aba do HUD. Formato obrigatorio: Categoria:Prefab=pontos,Prefab=pontos;Outra:Prefab=pontos. Exemplo: Armas:SwordIron=800;Armaduras:HelmetBronze=800;Comidas:DeerStew=25. Tambem organiza FarmJackpots e UniqueCraftJackpots.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
BindPointSectionEntries("BossPoints", "Pontos por bosses e minibosses.", legacyOverrides, _cfgBossPointEntries);
BindPointSectionEntries("FishingPoints", "Pontos por peixe pescado.", legacyOverrides, _cfgFishingPointEntries);
BindStringPointSectionEntries("SkillMilestoneJackpotPoints", "Pontos únicos por marco de skill. Formato: 20:500,40:1000,60:2000,80:3000,100:4000.", legacyOverrides, _cfgSkillMilestoneJackpotPointEntries);
_cfgFarmJackpotRules = ConfigFileExtensions.BindConfig<string>(_rulesConfig, "FarmJackpots", "Rules", ReadGroupedRulesDefault(legacyOverrides, "FarmJackpots", "Carrot:200:1000;Turnip:200:1200;Onion:200:1500;Barley:500:2000;Flax:500:2000"), "Jackpots de colheita. Edite somente esta linha. Formato: Prefab:quantidade:pontos;Prefab:quantidade:pontos. Exemplo: Carrot:200:1000;Barley:500:2000.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
_cfgUniqueCraftJackpotRules = ConfigFileExtensions.BindConfig<string>(_rulesConfig, "UniqueCraftJackpots", "Rules", ReadGroupedRulesDefault(legacyOverrides, "UniqueCraftJackpots", "SwordIron:1:800;SwordSilver:1:1500;SwordBlackmetal:1:2500;ArmorWolfChest:1:2000;ArmorCarapaceChest:1:3500"), "Jackpot único por craft. Edite somente esta linha. Formato: Prefab:quantidade:pontos;Prefab:quantidade:pontos. Exemplo: SwordIron:1:800;ArmorWolfChest:1:2000.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
}
}
private void RewriteRulesConfigHeaderAndGroupedSections()
{
try
{
if (string.IsNullOrWhiteSpace(_rulesFilePath) || !File.Exists(_rulesFilePath))
{
return;
}
string[] array = File.ReadAllLines(_rulesFilePath, Encoding.UTF8);
List<string> list = new List<string>(array.Length + 40);
string empty = string.Empty;
bool flag = false;
HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "KillPoints", "CraftPoints", "FarmJackpots", "UniqueCraftJackpots", "DeathPenalty", "ExplorationMapJackpots" };
list.Add("## Glitnir Ranking - Regras de pontuação");
list.Add("##");
list.Add("## Edite este arquivo no servidor. As regras marcadas como synced são enviadas aos clientes.");
list.Add("##");
list.Add("## Combate e Produção agora ficam em [HudCategories], com categoria + prefab + pontos.");
list.Add("## [FarmJackpots] Rules = Prefab:quantidade:pontos;Prefab:quantidade:pontos");
list.Add("## [UniqueCraftJackpots] Rules = Prefab:quantidade:pontos;Prefab:quantidade:pontos");
list.Add("## [DeathPenalty] Rules = mortes:pontosPerdidos,mortes:pontosPerdidos");
list.Add("## [ExplorationMapJackpots] Rules = porcentagem:pontos;porcentagem:pontos");
list.Add("##");
list.Add("## Exemplos de prefabs reais comuns:");
list.Add("## Carrot:200:1000");
list.Add("## SwordIron:1:800");
list.Add("## ArmorWolfChest:1:2000");
list.Add("## 1:0,2:100,5:300,10:800");
list.Add("##");
list.Add("## Importante: se um item não pontuar, ative DebugLogging/LogPointsChanges e veja no log");
list.Add("## o prefab detectado pelo ranking no momento do kill, craft ou colheita.");
list.Add("");
bool flag2 = false;
bool flag3 = false;
bool flag4 = false;
bool flag5 = false;
string[] array2 = array;
foreach (string text in array2)
{
string text2 = ((text != null) ? text.Trim() : string.Empty);
if (text2.StartsWith("## Glitnir Ranking - Regras de pontuação", StringComparison.OrdinalIgnoreCase))
{
continue;
}
if (text2.StartsWith("[") && text2.EndsWith("]"))
{
string text3 = text2.Substring(1, text2.Length - 2).Trim();
flag = hashSet.Contains(text3);
empty = text3;
if (flag)
{
if (text3.Equals("FarmJackpots", StringComparison.OrdinalIgnoreCase) && !flag2)
{
AppendGroupedFarmSection(list);
flag2 = true;
}
else if (text3.Equals("UniqueCraftJackpots", StringComparison.OrdinalIgnoreCase) && !flag3)
{
AppendGroupedUniqueCraftSection(list);
flag3 = true;
}
else if (text3.Equals("DeathPenalty", StringComparison.OrdinalIgnoreCase) && !flag4)
{
AppendGroupedDeathPenaltySection(list);
flag4 = true;
}
else if (text3.Equals("ExplorationMapJackpots", StringComparison.OrdinalIgnoreCase) && !flag5)
{
AppendGroupedExplorationMapSection(list);
flag5 = true;
}
}
else
{
list.Add(text);
}
}
else if (!flag)
{
list.Add(text);
}
}
if (!flag2)
{
AppendGroupedFarmSection(list);
}
if (!flag3)
{
AppendGroupedUniqueCraftSection(list);
}
if (!flag4)
{
AppendGroupedDeathPenaltySection(list);
}
if (!flag5)
{
AppendGroupedExplorationMapSection(list);
}
File.WriteAllLines(_rulesFilePath, list.ToArray(), Encoding.UTF8);
}
catch (Exception ex)
{
((BaseUnityPlugin)this).Logger.LogWarning((object)("[Ranking] Falha ao organizar config do ranking: " + ex.Message));
}
}
private void AppendGroupedFarmSection(List<string> lines)
{
lines.Add("");
lines.Add("[FarmJackpots]");
lines.Add("");
lines.Add("## Jackpot de colheita por prefab configurado.");
lines.Add("## Formato: Prefab:quantidade:pontos;Prefab:quantidade:pontos");
lines.Add("## Exemplo: Carrot:200:1000;Barley:500:2000");
lines.Add("Rules = " + ((_cfgFarmJackpotRules != null) ? SanitizeDelimitedJackpotRules(_cfgFarmJackpotRules.Value) : "Carrot:200:1000;Barley:500:2000"));
}
private void AppendGroupedUniqueCraftSection(List<string> lines)
{
lines.Add("");
lines.Add("[UniqueCraftJackpots]");
lines.Add("");
lines.Add("## Jackpot único por craft de qualquer prefab configurado.");
lines.Add("## Formato: Prefab:quantidade:pontos;Prefab:quantidade:pontos");
lines.Add("## Exemplo: SwordIron:1:800;ArmorWolfChest:1:2000");
lines.Add("Rules = " + ((_cfgUniqueCraftJackpotRules != null) ? SanitizeDelimitedJackpotRules(_cfgUniqueCraftJackpotRules.Value) : "SwordIron:1:800;ArmorWolfChest:1:2000"));
}
private void AppendGroupedDeathPenaltySection(List<string> lines)
{
lines.Add("");
lines.Add("[DeathPenalty]");
lines.Add("");
lines.Add("## Penalidade por morte em marcos únicos por ciclo.");
lines.Add("## Formato: mortes:pontosPerdidos,mortes:pontosPerdidos");
lines.Add("## Exemplo: 1:0,2:100,5:300,10:800");
lines.Add("Rules = " + ((_cfgDeathPenaltyRules != null) ? SanitizeDeathPenaltyRules(_cfgDeathPenaltyRules.Value) : "1:0,2:100,5:300,10:800"));
}
private void AppendGroupedExplorationMapSection(List<string> lines)
{
lines.Add("");
lines.Add("[ExplorationMapJackpots]");
lines.Add("");
lines.Add("## Jackpot por porcentagem de mapa revelado.");
lines.Add("## Formato: porcentagem:pontos;porcentagem:pontos");
lines.Add("## Exemplo: 5:25;10:50;25:150;50:400;75:800;100:1500");
lines.Add("Enabled = " + ((_cfgEnableExplorationJackpots != null) ? _cfgEnableExplorationJackpots.Value.ToString() : "true"));
lines.Add("Rules = " + ((_cfgExplorationMapJackpotRules != null) ? _cfgExplorationMapJackpotRules.Value : "5:25;10:50;25:150;50:400;75:800;100:1500"));
}
private void CleanupFishKillPointConfigEntries()
{
try
{
if (string.IsNullOrWhiteSpace(_rulesFilePath) || !File.Exists(_rulesFilePath))
{
return;
}
string[] array = File.ReadAllLines(_rulesFilePath, Encoding.UTF8);
List<string> list = new List<string>(array.Length);
bool flag = false;
bool flag2 = false;
string[] array2 = array;
foreach (string text in array2)
{
string text2 = ((text != null) ? text.Trim() : string.Empty);
if (text2.StartsWith("[") && text2.EndsWith("]"))
{
flag = text2.Equals("[KillPoints]", StringComparison.OrdinalIgnoreCase);
}
int num = text2.IndexOf('=');
if (num > 0)
{
string text3 = text2.Substring(0, num).Trim();
string text4 = text3;
if (text3.StartsWith("KillPoints.", StringComparison.OrdinalIgnoreCase))
{
text4 = text3.Substring("KillPoints.".Length).Trim();
}
else if (!flag)
{
text4 = null;
}
if (!string.IsNullOrWhiteSpace(text4) && IsFishPrefab(text4))
{
flag2 = true;
continue;
}
}
list.Add(text);
}
if (flag2)
{
File.WriteAllLines(_rulesFilePath, list.ToArray(), Encoding.UTF8);
((BaseUnityPlugin)this).Logger.LogInfo((object)"[Ranking] Prefabs de peixe removidos da configuração KillPoints.");
}
}
catch (Exception ex)
{
((BaseUnityPlugin)this).Logger.LogWarning((object)("[Ranking] Falha ao limpar prefabs de peixe do config: " + ex.Message));
}
}
private void CleanupGroupedProductionConfigEntries()
{
try
{
if (string.IsNullOrWhiteSpace(_rulesFilePath) || !File.Exists(_rulesFilePath))
{
return;
}
string[] array = File.ReadAllLines(_rulesFilePath, Encoding.UTF8);
List<string> list = new List<string>(array.Length);
string item = string.Empty;
bool flag = false;
HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "KillPoints", "CraftPoints", "FarmJackpots", "UniqueCraftJackpots", "DeathPenalty", "ExplorationMapJackpots" };
string[] array2 = array;
foreach (string text in array2)
{
string text2 = ((text != null) ? text.Trim() : string.Empty);
if (text2.StartsWith("[") && text2.EndsWith("]"))
{
item = text2.Substring(1, text2.Length - 2).Trim();
list.Add(text);
continue;
}
int num = text2.IndexOf('=');
if (num > 0 && hashSet.Contains(item))
{
string text3 = text2.Substring(0, num).Trim();
if (!text3.Equals("Rules", StringComparison.OrdinalIgnoreCase))
{
flag = true;
continue;
}
}
list.Add(text);
}
if (flag)
{
File.WriteAllLines(_rulesFilePath, list.ToArray(), Encoding.UTF8);
((BaseUnityPlugin)this).Logger.LogInfo((object)"[Ranking] Config de produção reorganizada: CraftPoints/FarmJackpots/UniqueCraftJackpots agora usam somente a chave Rules.");
}
}
catch (Exception ex)
{
((BaseUnityPlugin)this).Logger.LogWarning((object)("[Ranking] Falha ao reorganizar config de produção: " + ex.Message));
}
}
private string BuildMarketplaceQuestPointMapDefault(Dictionary<string, string> legacyOverrides)
{
string text = ReadLegacyString(legacyOverrides, "MarketplaceQuestPoints.QuestPointMap", null);
if (!string.IsNullOrWhiteSpace(text))
{
return SanitizeMarketplaceQuestPointMap(text);
}
text = ReadLegacyString(legacyOverrides, "QuestPointMap", null);
if (!string.IsNullOrWhiteSpace(text))
{
return SanitizeMarketplaceQuestPointMap(text);
}
Dictionary<string, int> mergedDefaultPointEntries = GetMergedDefaultPointEntries("MarketplaceQuestPoints", legacyOverrides);
if (mergedDefaultPointEntries.Count <= 0)
{
return "ccq_b21:450,ccq_b22:150";
}
return string.Join(",", from p in mergedDefaultPointEntries.OrderBy<KeyValuePair<string, int>, string>((KeyValuePair<string, int> p) => p.Key, StringComparer.OrdinalIgnoreCase)
select SafeMarketplaceQuestKey(p.Key) + ":" + Mathf.Max(0, p.Value));
}
private Dictionary<string, int> ParseMarketplaceQuestPointMap(string raw)
{
Dictionary<string, int> dictionary = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
if (string.IsNullOrWhiteSpace(raw