Иконка ресурса

FREE RustStormRank : 0.5.14 0.5.14

Нет прав для скачивания
C#:
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Core.Libraries.Covalence;
using Oxide.Core.Plugins;
using Oxide.Game.Rust.Cui;
using Rust;
using UnityEngine;
namespace Oxide.Plugins {
    [Info("RustStormRank", "Milestorme", "0.5.14")]
    [Description("Low-overhead leaderboard and ranking core for RustStorm with current wipe, lifetime, team scopes, and polished rebuilt clean UI from stable core.")]
    public class RustStormRank : RustPlugin {
        [PluginReference]
        private Plugin Clans;
#region Fields
        private PluginConfig _config;
        private StoredData _data;
        private readonly HashSet<ulong> _dirtyPlayers = new HashSet<ulong>();
        private readonly HashSet<ulong> _dirtyTeams = new HashSet<ulong>();
        private readonly Dictionary<string, List<LeaderboardEntry>> _leaderboardCache = new Dictionary<string, List<LeaderboardEntry>>();
        private readonly Dictionary<ulong, RuntimePlayerState> _runtimeStates = new Dictionary<ulong, RuntimePlayerState>();
        private readonly Dictionary<ulong, Vector3> _lastPositionByPlayer = new Dictionary<ulong, Vector3>();
        private readonly Dictionary<ulong, int> _playersPageByViewer = new Dictionary<ulong, int>();
        private bool _leaderboardsDirty;
        private Timer _saveTimer;
        private Timer _recalcTimer;
        private Timer _activityTimer;
        private Timer _cleanupTimer;
        private Timer _dailyDiscordTimer;
        private DateTime _lastDailyDiscordPostUtc = DateTime.MinValue;
        private string _lastDailyDiscordPostSlotKey = string.Empty;
        private const string DataFileName = "RustStormRank_Data";
        private const string ArchiveFilePrefix = "RustStormRank_Archive_";
        private const string DefaultAdminPermission = "ruststormrank.admin";
        private const string UiRoot = "RustStormRank.UI";
        private const string UiBgMain = "0.025 0.030 0.040 0.985";
        private const string UiBgHeader = "0.055 0.070 0.095 0.985";
        private const string UiBgPanel = "0.070 0.085 0.110 0.955";
        private const string UiBgCard = "0.090 0.110 0.145 0.965";
        private const string UiBgStat = "0.145 0.165 0.205 0.940";
        private const string UiBgStatSoft = "0.115 0.135 0.170 0.930";
        private const string UiBgRow = "0.105 0.125 0.160 0.950";
        private const string UiBgRowAlt = "0.090 0.108 0.140 0.950";
        private const string UiBgRowTop = "0.135 0.125 0.095 0.955";
        private const string UiBgRowSelf = "0.180 0.255 0.340 0.975";
        private const string UiTextPrimary = "0.930 0.950 0.985 1.000";
        private const string UiTextMuted = "0.660 0.710 0.780 1.000";
        private const string UiTextSoft = "0.780 0.820 0.880 1.000";
        private const string UiAccentBlue = "0.360 0.770 1.000 1.000";
        private const string UiAccentGold = "0.930 0.720 0.370 1.000";
#endregion
#region Lifecycle
        private void Init() {
            LoadConfig();
            LoadData();
            permission.RegisterPermission(_config.General.AdminPermission, this);
            AddCovalenceCommand(_config.General.ChatCommand, nameof(CommandRank));
            AddCovalenceCommand("rankadmin", nameof(CommandRankAdmin));
        }
        private void OnServerInitialized() {
            EnsureDataContainers();
            DetectAndHandleWipe();
            PrimeOnlinePlayers();
            RemoveNpcRecords();
            StartTimers();
            RebuildAllCaches();
            Puts($"[{Name}] Ready. Players={_data.Players.Count}, Teams={_data.Teams.Count}");
            if (_config.Teams.UseClanTagsInTeamNames && Clans == null) PrintWarning("Team clan tags are enabled, but the Clans plugin was not found. Team names will be shown without clan tags.");
        }
        private void Unload() {
            DestroyTimers();
            var now = DateTime.UtcNow;
            foreach (var player in BasePlayer.activePlayerList) {
                if (IsValidPlayer(player)) {
                    var record = GetOrCreatePlayerRecord(player.userID, player.displayName);
                    RuntimePlayerState state;
                    if (_runtimeStates.TryGetValue(player.userID, out state)) AccumulatePlayedTime(record, state, now);
                }
                DestroyUi(player);
            }
            SaveData();
        }
        private void OnServerSave() {
            SaveData();
        }
        private void OnNewSave(string filename) {
            if (!_config.Wipe.AutoDetectMapWipe) return;
            DetectAndHandleWipe();
            RebuildAllCaches();
        }
#endregion
#region Player Hooks
        private void OnPlayerConnected(BasePlayer player) {
            if (!IsValidPlayer(player)) return;
            var record = GetOrCreatePlayerRecord(player.userID, player.displayName);
            record.LastKnownName = player.displayName;
            record.IsNpc = player.IsNpc;
            record.LastSeenUtc = DateTime.UtcNow;
            EnsureRuntimeState(player, record);
            _lastPositionByPlayer[player.userID] = player.transform.position;
            MarkPlayerDirty(player.userID);
        }
        private void OnPlayerDisconnected(BasePlayer player, string reason) {
            if (!IsValidPlayer(player)) return;
            var record = GetOrCreatePlayerRecord(player.userID, player.displayName);
            record.LastKnownName = player.displayName;
            record.IsNpc = player.IsNpc;
            record.LastSeenUtc = DateTime.UtcNow;
            RuntimePlayerState state;
            if (_runtimeStates.TryGetValue(player.userID, out state)) AccumulatePlayedTime(record, state, DateTime.UtcNow);
            MarkPlayerDirty(player.userID);
            _lastPositionByPlayer.Remove(player.userID);
            _runtimeStates.Remove(player.userID);
            DestroyUi(player);
        }
        private void OnPlayerRespawned(BasePlayer player) {
            if (!IsValidPlayer(player)) return;
            var record = GetOrCreatePlayerRecord(player.userID, player.displayName);
            record.LastKnownName = player.displayName;
            record.IsNpc = player.IsNpc;
            record.LastSeenUtc = DateTime.UtcNow;
            var now = DateTime.UtcNow;
            record.CurrentLifeStartUtc = now;
            var state = EnsureRuntimeState(player, record);
            state.CurrentLifeStartUtc = now;
            _lastPositionByPlayer[player.userID] = player.transform.position;
            MarkPlayerDirty(player.userID);
        }
#endregion
#region Combat / Resource / Build Hooks
        private void OnEntityTakeDamage(BaseCombatEntity entity, HitInfo info) {
            try {
                if (entity == null || info == null) return;
                var victimEntity = entity;
                var victimPlayer = victimEntity as BasePlayer;
                var attackerEntity = info.Initiator as BaseCombatEntity;
                var attackerPlayer = info.InitiatorPlayer;
                if (attackerEntity == null) return;
                var totalDamage = info.damageTypes != null ? info.damageTypes.Total() : 0f;
                if (totalDamage <= 0f) return;
                if (victimPlayer != null && attackerPlayer != null && attackerPlayer == victimPlayer) return;
                var victimIsPlayer = IsValidPlayer(victimPlayer);
                var attackerIsPlayer = IsValidPlayer(attackerPlayer);
                var victimIsNpc = victimPlayer != null && victimPlayer.IsNpc;
                var attackerIsNpc = attackerPlayer != null && attackerPlayer.IsNpc;
                var victimIsAnimal = IsTrackedAnimal(victimEntity);
                var attackerIsAnimal = IsTrackedAnimal(attackerEntity);
                if (victimIsPlayer && attackerIsPlayer) {
                    var victimRecord = GetOrCreatePlayerRecord(victimPlayer.userID, victimPlayer.displayName);
                    victimRecord.IsNpc = false;
                    victimRecord.CurrentWipe.PvP.DamageTaken += totalDamage;
                    victimRecord.Lifetime.PvP.DamageTaken += totalDamage;
                    MarkPlayerDirty(victimPlayer.userID);
                    var attackerRecord = GetOrCreatePlayerRecord(attackerPlayer.userID, attackerPlayer.displayName);
                    attackerRecord.IsNpc = false;
                    attackerRecord.CurrentWipe.PvP.DamageDealt += totalDamage;
                    attackerRecord.Lifetime.PvP.DamageDealt += totalDamage;
                    MarkPlayerDirty(attackerPlayer.userID);
                    return;
                }
                if (attackerIsPlayer && victimIsNpc) {
                    var attackerRecord = GetOrCreatePlayerRecord(attackerPlayer.userID, attackerPlayer.displayName);
                    attackerRecord.IsNpc = false;
                    attackerRecord.CurrentWipe.Survival.NpcDamageDone += totalDamage;
                    attackerRecord.Lifetime.Survival.NpcDamageDone += totalDamage;
                    MarkPlayerDirty(attackerPlayer.userID);
                    return;
                }
                if (attackerIsNpc && victimIsPlayer) {
                    var victimRecord = GetOrCreatePlayerRecord(victimPlayer.userID, victimPlayer.displayName);
                    victimRecord.IsNpc = false;
                    victimRecord.CurrentWipe.Survival.NpcDamageTaken += totalDamage;
                    victimRecord.Lifetime.Survival.NpcDamageTaken += totalDamage;
                    MarkPlayerDirty(victimPlayer.userID);
                    return;
                }
                if (attackerIsPlayer && victimIsAnimal) {
                    var attackerRecord = GetOrCreatePlayerRecord(attackerPlayer.userID, attackerPlayer.displayName);
                    attackerRecord.IsNpc = false;
                    attackerRecord.CurrentWipe.Survival.AnimalDamageDone += totalDamage;
                    attackerRecord.Lifetime.Survival.AnimalDamageDone += totalDamage;
                    MarkPlayerDirty(attackerPlayer.userID);
                    return;
                }
                if (attackerIsAnimal && victimIsPlayer) {
                    var victimRecord = GetOrCreatePlayerRecord(victimPlayer.userID, victimPlayer.displayName);
                    victimRecord.IsNpc = false;
                    victimRecord.CurrentWipe.Survival.AnimalDamageTaken += totalDamage;
                    victimRecord.Lifetime.Survival.AnimalDamageTaken += totalDamage;
                    MarkPlayerDirty(victimPlayer.userID);
                }
            }
            catch (Exception ex) {
                PrintError($"OnEntityTakeDamage error: {ex}");
            }
        }
        private void OnEntityDeath(BaseCombatEntity entity, HitInfo info) {
            try {
                if (entity == null) return;
                var victimEntity = entity;
                var victimPlayer = victimEntity as BasePlayer;
                var attackerPlayer = info?.InitiatorPlayer;
                var isTruePvPDeath = IsValidPlayer(victimPlayer) && IsValidPlayer(attackerPlayer) && attackerPlayer.userID != victimPlayer.userID;
                var isPlayerKilledNpc = victimPlayer != null && victimPlayer.IsNpc && IsValidPlayer(attackerPlayer) && attackerPlayer.userID != victimPlayer.userID;
                var isPlayerKilledAnimal = IsTrackedAnimal(victimEntity) && IsValidPlayer(attackerPlayer);
                if (IsValidPlayer(victimPlayer)) {
                    var victimRecord = GetOrCreatePlayerRecord(victimPlayer.userID, victimPlayer.displayName);
                    victimRecord.IsNpc = false;
                    victimRecord.CurrentWipe.Survival.Respawns++;
                    victimRecord.Lifetime.Survival.Respawns++;
                    if (isTruePvPDeath) {
                        victimRecord.CurrentWipe.PvP.Deaths++;
                        victimRecord.Lifetime.PvP.Deaths++;
                        victimRecord.CurrentWipe.PvP.KillStreakCurrent = 0;
                        victimRecord.Lifetime.PvP.KillStreakCurrent = 0;
                    }
                    FinalizeLife(victimPlayer, victimRecord);
                    MarkPlayerDirty(victimPlayer.userID);
                }
                if (isTruePvPDeath) {
                    var attackerRecord = GetOrCreatePlayerRecord(attackerPlayer.userID, attackerPlayer.displayName);
                    attackerRecord.IsNpc = false;
                    attackerRecord.CurrentWipe.PvP.Kills++;
                    attackerRecord.Lifetime.PvP.Kills++;
                    if (info != null && info.isHeadshot) {
                        attackerRecord.CurrentWipe.PvP.Headshots++;
                        attackerRecord.Lifetime.PvP.Headshots++;
                    }
                    attackerRecord.CurrentWipe.PvP.KillStreakCurrent++;
                    attackerRecord.Lifetime.PvP.KillStreakCurrent++;
                    if (attackerRecord.CurrentWipe.PvP.KillStreakCurrent > attackerRecord.CurrentWipe.PvP.KillStreakBest) attackerRecord.CurrentWipe.PvP.KillStreakBest = attackerRecord.CurrentWipe.PvP.KillStreakCurrent;
                    if (attackerRecord.Lifetime.PvP.KillStreakCurrent > attackerRecord.Lifetime.PvP.KillStreakBest) attackerRecord.Lifetime.PvP.KillStreakBest = attackerRecord.Lifetime.PvP.KillStreakCurrent;
                    MarkPlayerDirty(attackerPlayer.userID);
                    return;
                }
                if (isPlayerKilledNpc) {
                    var attackerRecord = GetOrCreatePlayerRecord(attackerPlayer.userID, attackerPlayer.displayName);
                    attackerRecord.IsNpc = false;
                    attackerRecord.CurrentWipe.Survival.NpcsKilled++;
                    attackerRecord.Lifetime.Survival.NpcsKilled++;
                    MarkPlayerDirty(attackerPlayer.userID);
                    return;
                }
                if (isPlayerKilledAnimal) {
                    var attackerRecord = GetOrCreatePlayerRecord(attackerPlayer.userID, attackerPlayer.displayName);
                    attackerRecord.IsNpc = false;
                    attackerRecord.CurrentWipe.Survival.AnimalsKilled++;
                    attackerRecord.Lifetime.Survival.AnimalsKilled++;
                    MarkPlayerDirty(attackerPlayer.userID);
                }
            }
            catch (Exception ex) {
                PrintError($"OnEntityDeath error: {ex}");
            }
        }
        private void OnDispenserGather(ResourceDispenser dispenser, BasePlayer player, Item item) {
            try {
                if (!IsValidPlayer(player) || item == null || item.info == null) return;
                if (item.amount <= 0) return;
                var record = GetOrCreatePlayerRecord(player.userID, player.displayName);
                record.IsNpc = player.IsNpc;
                ApplyGather(record.CurrentWipe.Farm, item.info.shortname, item.amount);
                ApplyGather(record.Lifetime.Farm, item.info.shortname, item.amount);
                MarkPlayerDirty(player.userID);
            }
            catch (Exception ex) {
                PrintError($"OnDispenserGather error: {ex}");
            }
        }
        private void OnCollectiblePickup(Item item, BasePlayer player, CollectibleEntity collectible) {
            try {
                if (!IsValidPlayer(player) || item == null || item.info == null) return;
                if (item.amount <= 0) return;
                var record = GetOrCreatePlayerRecord(player.userID, player.displayName);
                record.IsNpc = player.IsNpc;
                ApplyGather(record.CurrentWipe.Farm, item.info.shortname, item.amount);
                ApplyGather(record.Lifetime.Farm, item.info.shortname, item.amount);
                MarkPlayerDirty(player.userID);
            }
            catch (Exception ex) {
                PrintError($"OnCollectiblePickup error: {ex}");
            }
        }
        private void OnEntityBuilt(Planner planner, GameObject gameObject) {
            try {
                var player = planner?.GetOwnerPlayer();
                if (!IsValidPlayer(player) || gameObject == null) return;
                var record = GetOrCreatePlayerRecord(player.userID, player.displayName);
                record.IsNpc = player.IsNpc;
                record.CurrentWipe.Build.StructuresBuilt++;
                record.Lifetime.Build.StructuresBuilt++;
                MarkPlayerDirty(player.userID);
            }
            catch (Exception ex) {
                PrintError($"OnEntityBuilt error: {ex}");
            }
        }
        private void OnStructureUpgrade(BuildingBlock block, BasePlayer player, BuildingGrade.Enum grade) {
            try {
                if (!IsValidPlayer(player) || block == null) return;
                var record = GetOrCreatePlayerRecord(player.userID, player.displayName);
                record.IsNpc = player.IsNpc;
                record.CurrentWipe.Build.StructuresUpgraded++;
                record.Lifetime.Build.StructuresUpgraded++;
                MarkPlayerDirty(player.userID);
            }
            catch (Exception ex) {
                PrintError($"OnStructureUpgrade error: {ex}");
            }
        }
        private void OnStructureRepair(BaseCombatEntity entity, BasePlayer player) {
            try {
                if (!IsValidPlayer(player) || entity == null) return;
                var record = GetOrCreatePlayerRecord(player.userID, player.displayName);
                record.IsNpc = player.IsNpc;
                record.CurrentWipe.Build.RepairsPerformed++;
                record.Lifetime.Build.RepairsPerformed++;
                MarkPlayerDirty(player.userID);
            }
            catch (Exception ex) {
                PrintError($"OnStructureRepair error: {ex}");
            }
        }
#endregion
#region Activity Tracking
        private void PrimeOnlinePlayers() {
            foreach (var player in BasePlayer.activePlayerList) {
                if (!IsValidPlayer(player)) continue;
                var record = GetOrCreatePlayerRecord(player.userID, player.displayName);
                EnsureRuntimeState(player, record);
                _lastPositionByPlayer[player.userID] = player.transform.position;
            }
        }
        private void TickPlayerActivity() {
            try {
                var now = DateTime.UtcNow;
                foreach (var player in BasePlayer.activePlayerList) {
                    if (!IsValidPlayer(player)) continue;
                    var record = GetOrCreatePlayerRecord(player.userID, player.displayName);
                    record.IsNpc = player.IsNpc;
                    var state = EnsureRuntimeState(player, record);
                    AccumulatePlayedTime(record, state, now);
                    var currentPos = player.transform.position;
                    Vector3 lastPos;
                    if (_lastPositionByPlayer.TryGetValue(player.userID, out lastPos)) {
                        var moved = Vector3.Distance(lastPos, currentPos);
                        if (!float.IsNaN(moved) && !float.IsInfinity(moved) && moved > 0f && moved < 500f) {
                            record.CurrentWipe.Survival.DistanceTraveled += moved;
                            record.Lifetime.Survival.DistanceTraveled += moved;
                        }
                    }
                    _lastPositionByPlayer[player.userID] = currentPos;
                    var lifeSeconds = (float)(now - state.CurrentLifeStartUtc).TotalSeconds;
                    if (lifeSeconds > record.CurrentWipe.Survival.LongestLifeSeconds) record.CurrentWipe.Survival.LongestLifeSeconds = lifeSeconds;
                    if (lifeSeconds > record.Lifetime.Survival.LongestLifeSeconds) record.Lifetime.Survival.LongestLifeSeconds = lifeSeconds;
                    UpdateTeamStateForPlayer(player);
                    MarkPlayerDirty(player.userID);
                }
            }
            catch (Exception ex) {
                PrintError($"TickPlayerActivity error: {ex}");
            }
        }
        private void AccumulatePlayedTime(PlayerRecord record, RuntimePlayerState state, DateTime now) {
            if (record == null || state == null) return;
            if (state.LastActivityUpdateUtc <= DateTime.MinValue) {
                state.LastActivityUpdateUtc = now;
                return;
            }
            var elapsedSeconds = (float)(now - state.LastActivityUpdateUtc).TotalSeconds;
            if (elapsedSeconds <= 0f) {
                state.LastActivityUpdateUtc = now;
                return;
            }
            if (elapsedSeconds > 86400f) elapsedSeconds = 86400f;
            record.CurrentWipe.Survival.SecondsPlayed += elapsedSeconds;
            record.Lifetime.Survival.SecondsPlayed += elapsedSeconds;
            state.LastActivityUpdateUtc = now;
        }
        private RuntimePlayerState EnsureRuntimeState(BasePlayer player, PlayerRecord record = null) {
            RuntimePlayerState state;
            if (!_runtimeStates.TryGetValue(player.userID, out state)) {
                var lifeStartUtc = record != null && record.CurrentLifeStartUtc > DateTime.MinValue ? record.CurrentLifeStartUtc : DateTime.UtcNow;
                state = new RuntimePlayerState {
                    CurrentLifeStartUtc = lifeStartUtc, LastActivityUpdateUtc = DateTime.UtcNow, LastKnownTeamId = player.currentTeam
                }
                ;
                _runtimeStates[player.userID] = state;
            }
            if (record != null) {
                if (record.CurrentLifeStartUtc <= DateTime.MinValue) record.CurrentLifeStartUtc = state.CurrentLifeStartUtc;
                else if (state.CurrentLifeStartUtc != record.CurrentLifeStartUtc) state.CurrentLifeStartUtc = record.CurrentLifeStartUtc;
            }
            return state;
        }
        private void FinalizeLife(BasePlayer player, PlayerRecord record) {
            var state = EnsureRuntimeState(player, record);
            AccumulatePlayedTime(record, state, DateTime.UtcNow);
            var lifeSeconds = (float)(DateTime.UtcNow - state.CurrentLifeStartUtc).TotalSeconds;
            if (lifeSeconds > record.CurrentWipe.Survival.LongestLifeSeconds) record.CurrentWipe.Survival.LongestLifeSeconds = lifeSeconds;
            if (lifeSeconds > record.Lifetime.Survival.LongestLifeSeconds) record.Lifetime.Survival.LongestLifeSeconds = lifeSeconds;
            var now = DateTime.UtcNow;
            state.CurrentLifeStartUtc = now;
            record.CurrentLifeStartUtc = now;
        }
        private void UpdateTeamStateForPlayer(BasePlayer player) {
            var state = EnsureRuntimeState(player);
            var currentTeam = player.currentTeam;
            if (state.LastKnownTeamId == currentTeam) return;
            if (state.LastKnownTeamId != 0UL) MarkTeamDirty(state.LastKnownTeamId);
            if (currentTeam != 0UL) MarkTeamDirty(currentTeam);
            state.LastKnownTeamId = currentTeam;
        }
#endregion
#region Commands
        private void CommandRank(IPlayer iplayer, string command, string[] args) {
            var player = iplayer?.Object as BasePlayer;
            if (!IsValidPlayer(player)) {
                iplayer?.Reply("This command can only be used by an in-game player.");
                return;
            }
            var view = args != null && args.Length > 0 ? args[0].ToLowerInvariant() : "overview";
            var scope = ResolveScope(args);
            switch (view) {
                case "top" : OpenRankUi(player, scope == RankScope.Team ? RankScope.Team : scope, "top", player.userID);
                return;
                case "pvp" : case "farm" : case "build" : case "survival" : case "overview" : OpenRankUi(player, scope, view, player.userID);
                return;
                case "players" : OpenRankUi(player, scope == RankScope.Team ? RankScope.CurrentWipe : scope, "players", player.userID);
                return;
                case "team" : OpenRankUi(player, RankScope.Team, "top", player.userID);
                return;
                case "lifetime" : OpenRankUi(player, RankScope.Lifetime, "overview", player.userID);
                return;
                default : OpenRankUi(player, scope, "overview");
                return;
            }
        }
        private void CommandRankAdmin(IPlayer iplayer, string command, string[] args) {
            if (iplayer == null || !iplayer.HasPermission(_config.General.AdminPermission)) {
                iplayer?.Reply("You do not have permission to use this command.");
                return;
            }
            if (args == null || args.Length == 0) {
                iplayer.Reply("Usage: /rankadmin rebuild | wipe | webhook <overall|pvp|farm|build|survival|team>");
                return;
            }
            switch (args[0].ToLowerInvariant()) {
                case "rebuild" : RebuildAllCaches();
                iplayer.Reply("Rank caches rebuilt.");
                return;
                case "wipe" : ForceWipeRollover();
                iplayer.Reply("Current wipe data rolled over.");
                return;
                case "webhook" : if (args.Length < 2) {
                    iplayer.Reply("Usage: /rankadmin webhook <overall|pvp|farm|build|survival|team>");
                    return;
                }
                var scope = args[1].Equals("team", StringComparison.OrdinalIgnoreCase) ? RankScope.Team : RankScope.CurrentWipe;
                PostDiscordSummary(args[1].ToLowerInvariant(), scope);
                iplayer.Reply($"Webhook attempted for {args[1]}.");
                return;
                default : iplayer.Reply("Unknown subcommand.");
                return;
            }
        }
        [ConsoleCommand("ruststormrank.ui")]
        private void ConsoleCommandRankUi(ConsoleSystem.Arg arg) {
            var player = arg?.Player();
            if (!IsValidPlayer(player)) return;
            var args = arg.Args;
            var page = args != null && args.Length > 0 ? args[0].ToLowerInvariant() : "overview";
            var scope = args != null && args.Length > 1 ? ParseScope(args[1]) : RankScope.CurrentWipe;
            var targetUserId = player.userID;
            if (args != null && args.Length > 2) {
                ulong parsedTarget;
                if (ulong.TryParse(args[2], out parsedTarget) && parsedTarget != 0UL) targetUserId = parsedTarget;
            }
            OpenRankUi(player, scope, page, targetUserId);
        }
        [ConsoleCommand("ruststormrank.playerspage")]
        private void ConsoleCommandPlayersPage(ConsoleSystem.Arg arg) {
            var player = arg?.Player();
            if (!IsValidPlayer(player)) return;
            var args = arg.Args;
            var requestedPage = 0;
            if (args != null && args.Length > 0) int.TryParse(args[0], out requestedPage);
            if (requestedPage < 0) requestedPage = 0;
            _playersPageByViewer[player.userID] = requestedPage;
            var scope = args != null && args.Length > 1 ? ParseScope(args[1]) : RankScope.CurrentWipe;
            var targetUserId = player.userID;
            if (args != null && args.Length > 2) {
                ulong parsedTarget;
                if (ulong.TryParse(args[2], out parsedTarget) && parsedTarget != 0UL) targetUserId = parsedTarget;
            }
            OpenRankUi(player, scope, "players", targetUserId);
        }
        [ConsoleCommand("ruststormrank.close")]
        private void ConsoleCommandCloseUi(ConsoleSystem.Arg arg) {
            var player = arg?.Player();
            if (!IsValidPlayer(player)) return;
            DestroyUi(player);
        }
#endregion
#region UI
        private void OpenRankUi(BasePlayer player, RankScope scope, string page, ulong targetUserId = 0UL) {
            DestroyUi(player);
            var teamRecord = scope == RankScope.Team ? GetDisplayTeamRecord(player, targetUserId) : null;
            var teamId = teamRecord != null ? teamRecord.TeamId : 0UL;
            var record = GetDisplayPlayerRecord(player, scope == RankScope.Team ? 0UL : targetUserId);
            if (record == null) return;
            var normalizedTargetUserId = record.UserId;
            var stats = scope == RankScope.Team ? GetScopeStats(teamRecord, ResolveTeamDataScope(page)) : GetScopeStats(record, scope);
            var container = new CuiElementContainer();
            var panel = container.Add(new CuiPanel {
                Image = {
                    Color = UiBgMain
                }
                , RectTransform = {
                    AnchorMin = "0.17 0.13", AnchorMax = "0.83 0.87"
                }
                , CursorEnabled = true
            }
            , "Overlay", UiRoot);
            container.Add(new CuiPanel {
                Image = {
                    Color = UiBgHeader
                }
                , RectTransform = {
                    AnchorMin = "0 0.90", AnchorMax = "1 1"
                }
            }
            , panel, UiRoot + ".Header");
            AddLabel(container, panel, _config.General.UiTitle, 22, "0.03 0.925", "0.35 0.985", UiAccentBlue, TextAnchor.MiddleLeft);
            AddLabel(container, panel, $"{GetScopeTitle(scope)}", 18, "0.36 0.925", "0.65 0.985", UiTextPrimary, TextAnchor.MiddleLeft);
            var tabTargetId = scope == RankScope.Team ? teamId : normalizedTargetUserId;
            AddButton(container, panel, "Overview", BuildUiCommand("overview", scope, player.userID, tabTargetId), "0.03 0.84", "0.155 0.89", false, page == "overview");
            AddButton(container, panel, "PvP", BuildUiCommand("pvp", scope, player.userID, tabTargetId), "0.165 0.84", "0.255 0.89", false, page == "pvp");
            AddButton(container, panel, "Farm", BuildUiCommand("farm", scope, player.userID, tabTargetId), "0.265 0.84", "0.355 0.89", false, page == "farm");
            AddButton(container, panel, "Build", BuildUiCommand("build", scope, player.userID, tabTargetId), "0.365 0.84", "0.455 0.89", false, page == "build");
            AddButton(container, panel, "Survival", BuildUiCommand("survival", scope, player.userID, tabTargetId), "0.465 0.84", "0.59 0.89", false, page == "survival");
            AddButton(container, panel, "Top", BuildUiCommand("top", scope, player.userID, tabTargetId), "0.60 0.84", "0.69 0.89", false, page == "top");
            AddButton(container, panel, "Lifetime", scope == RankScope.Lifetime ? BuildUiCommand("overview", RankScope.CurrentWipe, player.userID, normalizedTargetUserId) : BuildUiCommand("overview", RankScope.Lifetime, player.userID, normalizedTargetUserId), "0.70 0.84", "0.79 0.89", false, scope == RankScope.Lifetime);
            if (scope == RankScope.Team) {
                if (teamId != 0UL && HasValidTeamMembers(teamId)) AddButton(container, panel, "Members", "ruststormrank.playerspage 0 " + ScopeToArg(scope) + " " + teamId, "0.80 0.84", "0.89 0.89", false, page == "players");
            }
            else {
                AddButton(container, panel, "Players", "ruststormrank.playerspage 0 " + ScopeToArg(scope) + " " + normalizedTargetUserId, "0.80 0.84", "0.89 0.89", false, page == "players");
            }
            AddButton(container, panel, "Teams", scope == RankScope.Team ? "ruststormrank.playerspage 0 " + ScopeToArg(RankScope.CurrentWipe) + " " + player.userID : BuildUiCommand("top", RankScope.Team, player.userID, 0UL), "0.90 0.84", "0.98 0.89", false, scope == RankScope.Team );
            AddButton(container, panel, "Close", "ruststormrank.close", "0.89 0.925", "0.97 0.975", true);
            AddLabel(container, panel, "Players Online: " + BasePlayer.activePlayerList.Count, 14, "0.68 0.925", "0.84 0.985", UiTextSoft, TextAnchor.MiddleRight);
            if (scope == RankScope.Team) {
                var teamName = teamRecord != null ? GetTeamDisplayName(teamRecord.TeamId) : "No Team";
                AddLabel(container, panel, "Viewing: " + teamName, 13, "0.03 0.805", "0.30 0.845", UiAccentGold, TextAnchor.MiddleLeft);
            }
            else if (normalizedTargetUserId != player.userID) {
                AddLabel(container, panel, "Viewing: " + record.LastKnownName, 13, "0.03 0.805", "0.30 0.845", UiAccentGold, TextAnchor.MiddleLeft);
            }
            AddSummaryCards(container, panel, stats, record, scope, teamRecord);
            if (page == "top") AddLeaderboardSection(container, panel, player, scope, scope == RankScope.Team ? teamId : normalizedTargetUserId);
            else if (page == "players") {
                if (scope == RankScope.Team) AddTeamMembersSection(container, panel, player, teamId);
                else AddPlayersSection(container, panel, player, scope, normalizedTargetUserId);
            }
            else AddStatsSection(container, panel, stats, scope, page, scope == RankScope.Team ? teamId : record.UserId);
            CuiHelper.AddUi(player, container);
        }
        private void AddSummaryCards(CuiElementContainer container, string parent, ScopeStats stats, PlayerRecord record, RankScope scope, TeamRecord teamRecord = null) {
            if (scope == RankScope.Team) {
                if (teamRecord == null || !HasValidTeamMembers(teamRecord.TeamId)) {
                    AddCard(container, parent, "Team Score", "-", "0.03 0.68", "0.20 0.81", "0.91 0.70 0.35 1.00");
                    AddCard(container, parent, "PvP", "-", "0.215 0.68", "0.385 0.81", "0.33 0.76 1.00 1.00");
                    AddCard(container, parent, "Farm", "-", "0.40 0.68", "0.57 0.81", "0.33 0.76 1.00 1.00");
                    AddCard(container, parent, "Build", "-", "0.585 0.68", "0.755 0.81", "0.33 0.76 1.00 1.00");
                    AddCard(container, parent, "Survival", "-", "0.77 0.68", "0.94 0.81", "0.33 0.76 1.00 1.00");
                    AddLabel(container, parent, "Leader: -", 15, "0.04 0.63", "0.34 0.67", "0.85 0.92 1.00 1.00", TextAnchor.MiddleLeft);
                    AddLabel(container, parent, "Rank: -", 15, "0.35 0.63", "0.50 0.67", UiTextSoft, TextAnchor.MiddleLeft);
                    AddLabel(container, parent, "Top: -", 15, "0.51 0.63", "0.72 0.67", UiAccentBlue, TextAnchor.MiddleLeft);
                    AddLabel(container, parent, "Played: -", 15, "0.73 0.63", "0.94 0.67", UiAccentGold, TextAnchor.MiddleRight);
                    return;
                }
                AddCard(container, parent, "Team Score", stats.ScoreCache.TeamScore.ToString("F1"), "0.03 0.68", "0.20 0.81", "0.91 0.70 0.35 1.00");
                AddCard(container, parent, "PvP", stats.ScoreCache.PvPScore.ToString("F1"), "0.215 0.68", "0.385 0.81", "0.33 0.76 1.00 1.00");
                AddCard(container, parent, "Farm", stats.ScoreCache.FarmScore.ToString("F1"), "0.40 0.68", "0.57 0.81", "0.33 0.76 1.00 1.00");
                AddCard(container, parent, "Build", stats.ScoreCache.BuildScore.ToString("F1"), "0.585 0.68", "0.755 0.81", "0.33 0.76 1.00 1.00");
                AddCard(container, parent, "Survival", stats.ScoreCache.SurvivalScore.ToString("F1"), "0.77 0.68", "0.94 0.81", "0.33 0.76 1.00 1.00");
                var teamRank = GetTeamRank(teamRecord.TeamId, RankScope.CurrentWipe);
                var teamRankColor = GetRankDisplayColor(teamRank);
                var leaderName = GetTeamLeaderName(teamRecord.TeamId);
                var topPlayerName = GetTopPlayerNameInTeam(teamRecord.TeamId, RankScope.CurrentWipe);
                AddLabel(container, parent, $"Leader: {leaderName}", 15, "0.04 0.63", "0.34 0.67", "0.85 0.92 1.00 1.00", TextAnchor.MiddleLeft);
                AddLabel(container, parent, teamRank > 0 ? $"Rank: #{teamRank}" : "Rank: -", 15, "0.35 0.63", "0.50 0.67", teamRank > 0 ? teamRankColor : UiTextSoft, TextAnchor.MiddleLeft);
                AddLabel(container, parent, $"Top: {topPlayerName}", 15, "0.51 0.63", "0.72 0.67", UiAccentBlue, TextAnchor.MiddleLeft);
                AddLabel(container, parent, $"Played: {FormatDuration(stats.Survival.SecondsPlayed)}", 15, "0.73 0.63", "0.94 0.67", UiAccentGold, TextAnchor.MiddleRight);
                return;
            }
            AddCard(container, parent, "Overall", stats.ScoreCache.OverallScore.ToString("F1"), "0.03 0.68", "0.20 0.81", "0.91 0.70 0.35 1.00");
            AddCard(container, parent, "PvP", stats.ScoreCache.PvPScore.ToString("F1"), "0.215 0.68", "0.385 0.81", "0.33 0.76 1.00 1.00");
            AddCard(container, parent, "Farm", stats.ScoreCache.FarmScore.ToString("F1"), "0.40 0.68", "0.57 0.81", "0.33 0.76 1.00 1.00");
            AddCard(container, parent, "Build", stats.ScoreCache.BuildScore.ToString("F1"), "0.585 0.68", "0.755 0.81", "0.33 0.76 1.00 1.00");
            AddCard(container, parent, "Survival", stats.ScoreCache.SurvivalScore.ToString("F1"), "0.77 0.68", "0.94 0.81", "0.33 0.76 1.00 1.00");
            var playerRank = GetPlayerRank(record.UserId, scope);
            var rankColor = GetRankDisplayColor(playerRank);
            AddLabel(container, parent, $"Player: {record.LastKnownName}", 15, "0.04 0.63", "0.34 0.67", "0.85 0.92 1.00 1.00", TextAnchor.MiddleLeft);
            AddLabel(container, parent, $"Rank: #{playerRank}", 15, "0.35 0.63", "0.50 0.67", rankColor, TextAnchor.MiddleLeft);
            AddLabel(container, parent, $"KDR: {GetKdr(stats.PvP):F2}", 15, "0.51 0.63", "0.63 0.67", UiAccentBlue, TextAnchor.MiddleLeft);
            AddLabel(container, parent, $"Played: {FormatDuration(stats.Survival.SecondsPlayed)}", 15, "0.64 0.63", "0.94 0.67", UiAccentGold, TextAnchor.MiddleRight);
        }
        private void AddStatsSection(CuiElementContainer container, string parent, ScopeStats stats, RankScope scope, string page, ulong recordUserId) {
            var body = container.Add(new CuiPanel {
                Image = {
                    Color = UiBgPanel
                }
                , RectTransform = {
                    AnchorMin = "0.03 0.05", AnchorMax = "0.97 0.62"
                }
            }
            , parent);
            AddLabel(container, body, GetPageTitle(page, scope), 19, "0.03 0.90", "0.97 0.98", UiTextPrimary, TextAnchor.MiddleLeft);
            switch (page) {
                case "pvp" : AddStatRow(container, body, 0.78f, "Kills", stats.PvP.Kills.ToString(), "Deaths", stats.PvP.Deaths.ToString(), "Headshot Kills", stats.PvP.Headshots.ToString());
                AddStatRow(container, body, 0.62f, "KDR", GetKdr(stats.PvP).ToString("F2"), "Damage Dealt", stats.PvP.DamageDealt.ToString("F0"), "Damage Taken", stats.PvP.DamageTaken.ToString("F0"));
                AddStatRow(container, body, 0.46f, "Best Streak", stats.PvP.KillStreakBest.ToString(), "Current Streak", stats.PvP.KillStreakCurrent.ToString(), "PvP Score", stats.ScoreCache.PvPScore.ToString("F1"));
                break;
                case "farm" : AddStatRow(container, body, 0.78f, "Wood", stats.Farm.WoodGathered.ToString(), "Stone", stats.Farm.StoneGathered.ToString(), "Metal", stats.Farm.MetalGathered.ToString());
                AddStatRow(container, body, 0.62f, "Sulfur", stats.Farm.SulfurGathered.ToString(), "Nodes", stats.Farm.NodesHarvested.ToString(), "Farm Score", stats.ScoreCache.FarmScore.ToString("F1"));
                AddStatRow(container, body, 0.46f, "Gather / Hour", CalculateGatherPerHour(stats).ToString("F0"), "Played", FormatDuration(stats.Survival.SecondsPlayed), "Scope", GetScopeTitle(scope));
                break;
                case "build" : AddStatRow(container, body, 0.78f, "Built", stats.Build.StructuresBuilt.ToString(), "Upgraded", stats.Build.StructuresUpgraded.ToString(), "Repaired", stats.Build.RepairsPerformed.ToString());
                AddStatRow(container, body, 0.62f, "Build / Hour", CalculateBuildPerHour(stats).ToString("F1"), "Build Score", stats.ScoreCache.BuildScore.ToString("F1"), "Scope", GetScopeTitle(scope));
                break;
                case "survival" : AddStatRow(container, body, 0.78f, "Played", FormatDuration(stats.Survival.SecondsPlayed), "Longest Life", FormatDuration(stats.Survival.LongestLifeSeconds), "Respawns", stats.Survival.Respawns.ToString());
                AddStatRow(container, body, 0.62f, "Distance", stats.Survival.DistanceTraveled.ToString("F0") + "m", "Survival Score", stats.ScoreCache.SurvivalScore.ToString("F1"), "Scope", GetScopeTitle(scope));
                AddStatRow(container, body, 0.46f, "NPCs Killed", stats.Survival.NpcsKilled.ToString(), "NPC Damage Done", stats.Survival.NpcDamageDone.ToString("F0"), "NPC Damage Taken", stats.Survival.NpcDamageTaken.ToString("F0"));
                AddStatRow(container, body, 0.30f, "Animals Killed", stats.Survival.AnimalsKilled.ToString(), "Animal Damage Done", stats.Survival.AnimalDamageDone.ToString("F0"), "Animal Damage Taken", stats.Survival.AnimalDamageTaken.ToString("F0"));
                break;
                default : if (scope == RankScope.Team) {
                    AddTeamOverviewSection(container, body, stats, recordUserId);
                    break;
                }
                AddLabel(container, body, "Score Summary", 16, "0.04 0.82", "0.30 0.88", UiAccentBlue, TextAnchor.MiddleLeft);
                AddDivider(container, body, "0.04 0.795", "0.96 0.799");
                AddMiniStat(container, body, "Overall Score", stats.ScoreCache.OverallScore.ToString("F1"), "0.04 0.71", "0.31 0.78");
                AddMiniStat(container, body, "PvP Score", stats.ScoreCache.PvPScore.ToString("F1"), "0.365 0.71", "0.635 0.78");
                AddMiniStat(container, body, "Farm Score", stats.ScoreCache.FarmScore.ToString("F1"), "0.69 0.71", "0.96 0.78");
                AddMiniStat(container, body, "Build Score", stats.ScoreCache.BuildScore.ToString("F1"), "0.04 0.62", "0.31 0.69");
                AddMiniStat(container, body, "Survival Score", stats.ScoreCache.SurvivalScore.ToString("F1"), "0.365 0.62", "0.635 0.69");
                AddMiniStat(container, body, "Rank", "#" + GetPlayerRank(recordUserId, scope), "0.69 0.62", "0.96 0.69", GetRankDisplayColor(GetPlayerRank(recordUserId, scope)));
                var currentTier = GetRankTier(stats.ScoreCache.OverallScore);
                var nextTier = GetNextRankTier(stats.ScoreCache.OverallScore);
                var progressPct = GetTierProgressPercent(stats.ScoreCache.OverallScore);
                AddLabel(container, body, "Tier Progression", 16, "0.04 0.49", "0.30 0.55", UiAccentBlue, TextAnchor.MiddleLeft);
                AddDivider(container, body, "0.04 0.465", "0.96 0.469");
                AddMiniStat(container, body, "Current Tier", GetTierDisplay(currentTier), "0.04 0.38", "0.31 0.45", GetTierColor(currentTier));
                AddMiniStat(container, body, "Next Tier", GetTierDisplay(nextTier), "0.365 0.38", "0.635 0.45", GetTierColor(nextTier));
                AddMiniStat(container, body, "Progress", progressPct.ToString("F0") + "%", "0.69 0.38", "0.96 0.45");
                AddProgressBar(container, body, progressPct, "0.69 0.345", "0.96 0.36", GetTierBarColor(currentTier, nextTier));
                AddLabel(container, body, "Activity Snapshot", 16, "0.04 0.25", "0.30 0.31", UiAccentBlue, TextAnchor.MiddleLeft);
                AddDivider(container, body, "0.04 0.225", "0.96 0.229");
                AddMiniStat(container, body, "Kills", stats.PvP.Kills.ToString(), "0.04 0.14", "0.31 0.21");
                AddMiniStat(container, body, "Nodes Gathered", stats.Farm.NodesHarvested.ToString(), "0.365 0.14", "0.635 0.21");
                AddMiniStat(container, body, "Structures Built", stats.Build.StructuresBuilt.ToString(), "0.69 0.14", "0.96 0.21");
                break;
            }
        }
        private void AddLeaderboardSection(CuiElementContainer container, string parent, BasePlayer player, RankScope scope, ulong targetUserId) {
            var body = container.Add(new CuiPanel {
                Image = {
                    Color = UiBgPanel
                }
                , RectTransform = {
                    AnchorMin = "0.03 0.04", AnchorMax = "0.97 0.60"
                }
            }
            , parent);
            var category = scope == RankScope.Team ? "team" : "overall";
            var key = GetLeaderboardCacheKey(scope, category);
            List<LeaderboardEntry> entries;
            if (!_leaderboardCache.TryGetValue(key, out entries)) entries = new List<LeaderboardEntry>();
            AddLabel(container, body, "Top Rankings", 19, "0.03 0.90", "0.97 0.98", UiTextPrimary, TextAnchor.MiddleLeft);
            AddLabel(container, body, "Competitive ladder for " + GetScopeTitle(scope), 14, "0.03 0.84", "0.60 0.90", UiTextMuted, TextAnchor.MiddleLeft);
            if (entries.Count == 0) {
                AddDivider(container, body, "0.05 0.775", "0.95 0.779");
                AddLabel(container, body, "No leaderboard data available yet.", 17, "0.05 0.45", "0.95 0.60", UiTextSoft, TextAnchor.MiddleCenter);
                return;
            }
            var selfRank = -1;
            if (scope != RankScope.Team) {
                for (var i = 0; i < entries.Count; i++) {
                    if (entries[i].EntryType == "player" && entries[i].Id == player.userID) {
                        selfRank = i + 1;
                        break;
                    }
                }
            }
            var max = Mathf.Min(20, entries.Count);
            const int rowsPerColumn = 10;
            const float rowHeight = 0.060f;
            const float rowGap = 0.010f;
            const float yTop = 0.75f;
            var useTwoColumns = max > rowsPerColumn;
            if (useTwoColumns) {
                AddLabel(container, body, "Rank", 12, "0.06 0.79", "0.14 0.84", UiTextMuted, TextAnchor.MiddleLeft);
                AddLabel(container, body, "Name", 12, "0.15 0.79", "0.38 0.84", UiTextMuted, TextAnchor.MiddleLeft);
                AddLabel(container, body, "Score", 12, "0.39 0.79", "0.46 0.84", UiTextMuted, TextAnchor.MiddleRight);
                AddLabel(container, body, "Rank", 12, "0.53 0.79", "0.61 0.84", UiTextMuted, TextAnchor.MiddleLeft);
                AddLabel(container, body, "Name", 12, "0.62 0.79", "0.85 0.84", UiTextMuted, TextAnchor.MiddleLeft);
                AddLabel(container, body, "Score", 12, "0.86 0.79", "0.93 0.84", UiTextMuted, TextAnchor.MiddleRight);
            }
            else {
                AddLabel(container, body, "Rank", 12, "0.06 0.79", "0.16 0.84", UiTextMuted, TextAnchor.MiddleLeft);
                AddLabel(container, body, "Name", 12, "0.17 0.79", "0.74 0.84", UiTextMuted, TextAnchor.MiddleLeft);
                AddLabel(container, body, "Score", 12, "0.76 0.79", "0.93 0.84", UiTextMuted, TextAnchor.MiddleRight);
            }
            AddDivider(container, body, "0.05 0.775", "0.95 0.779");
            for (var i = 0; i < max; i++) {
                var entry = entries[i];
                var columnIndex = useTwoColumns ? i / rowsPerColumn : 0;
                var rowIndex = useTwoColumns ? i % rowsPerColumn : i;
                var top = yTop - (rowIndex * (rowHeight + rowGap));
                var bottom = top - rowHeight;
        
// ... [Code preview truncated due to length. Full source in attached file] ...

Автор
15FPS
Скачиваний
0
Просмотры
1
Первый выпуск
Обновление
Оценка
0.00 звёзд 0 оценок
Сверху