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

FREE ScrapLeaderboard : 2.5.3 2.5.3

Нет прав для скачивания
C#:
/*
================================================================================================
  ScrapLeaderboard
  Version: 2.5.1
  Author: Orangemart
================================================================================================

  OVERVIEW:
  This plugin handles scrap deposits, enforces real-time limits, logs transactions, 
  and updates the ServerInfo leaderboard automatically.UPDATES v2.5.0
  - ADDED: Native CUI in-game leaderboard (/leaderboard or /topscrap)

  UPDATES v2.4.1
  - perf: optimize data saving and prevent chat message spam

  UPDATES v2.4.1:
  - FIXED: Duplication exploit caused by rapid-fire hooks queueing multiple timers.UPDATES v2.4.0:
  - ADDED: Dynamic team capacity allocation and physical scrap refunds for over-the-limit deposits.- FIXED: Race condition limit bypass for rapid deposits (shift-clicking/conveyors).- FIXED: Teammates bypassing MaxDepositLimit on split deposits.CONFIGURATION (oxide/config/ScrapLeaderboard.json):
  - EnableTeamSplitting: (bool) Should deposits be split among teammates? (Default: true)
  - SplitWithOfflineTeammates: (bool) Distribute scrap to offline team members? (Default: false)
================================================================================================
*/
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Oxide.Core;
using Oxide.Core.Libraries.Covalence;
using Oxide.Core.Plugins;
using Oxide.Game.Rust.Cui;
using UnityEngine;
namespace Oxide.Plugins {
    [Info("ScrapLeaderboard", "Orangemart", "2.5.3")]
    [Description("Handles scrap deposits, enforces limits, and updates the ServerInfo leaderboard.")]
    public class ScrapLeaderboard : CovalencePlugin {
        // ==========================================================================
        // Configuration
        // ==========================================================================
        private int DepositItemID;
        private ulong DepositBoxSkinID;
        private int MaxDepositLimit;
        private bool EnableTeamSplitting;
        private bool SplitWithOfflineTeammates;
        // ==========================================================================
        // Constants & Paths
        // ==========================================================================
        private const string ServerInfoPath = "oxide/config/ServerInfo.json";
        private const string PermPlace = "scrapleaderboard.place";
        private const string PermAdmin = "scrapleaderboard.admin";
        private const string UIPanelName = "ScrapLeaderboardUI";
        // Data Paths
        private const string DataFolder = "ScrapLeaderboard";
        private const string LogFileName = "ScrapLog";
        private bool saveQueued = false;
        // ==========================================================================
        // Data Structures
        // ==========================================================================
        private DepositLog depositLog;
        private Dictionary<ItemId, string> depositTrack = new Dictionary<ItemId, string>();
        public Dictionary<string, int> playerTotalsCache = new Dictionary<string, int>();
        public Dictionary<string, float> lastLimitMessageTime = new Dictionary<string, float>();
        public static ScrapLeaderboard instance;
        [HookMethod("GetPlayerTotalsCache")]
        private Dictionary<string, int> GetPlayerTotalsCache() {
            return playerTotalsCache;
        }
        // ==========================================================================
        // Oxide Hooks
        // ==========================================================================
        void Init() {
            instance = this;
            LoadConfiguration();
            LoadAndMigrateData();
            RecalculateTotals();
            permission.RegisterPermission(PermPlace, this);
            permission.RegisterPermission(PermAdmin, this);
            // Force refresh components on reload
            NextTick(() => {
                foreach (var entity in BaseNetworkable.serverEntities) {
                    if (entity is StorageContainer container) OnEntitySpawned(container);
                }
            }
            );
        }
        void OnServerInitialized(bool initial) {
            foreach (var entity in BaseNetworkable.serverEntities) {
                if (entity is StorageContainer container) OnEntitySpawned(container);
            }
        }
        void Unload() {
            Interface.Oxide.DataFileSystem.WriteObject($"{DataFolder}/{LogFileName}", depositLog);
            var boxes = UnityEngine.Object.FindObjectsOfType<DepositBoxRestriction>();
            foreach (var box in boxes) {
                UnityEngine.Object.Destroy(box);
            }
            instance = null;
        }
        void OnServerSave() {
            // Add this hook to save the data whenever the Rust server saves its map
            Interface.Oxide.DataFileSystem.WriteObject($"{DataFolder}/{LogFileName}", depositLog);
        }
        void OnEntitySpawned(StorageContainer container) {
            if (container == null || container.skinID != DepositBoxSkinID) return;
            var existing = container.GetComponent<DepositBoxRestriction>();
            if (existing != null) UnityEngine.Object.Destroy(existing);
            var mono = container.gameObject.AddComponent<DepositBoxRestriction>();
            mono.container = container.inventory;
            mono.InitDepositBox();
        }
        // ==========================================================================
        // Configuration Loading
        // ==========================================================================
        protected override void LoadDefaultConfig() {
            PrintWarning("Creating a new configuration file.");
            Config["DepositItemID"] = -932201673;
            Config["DepositBoxSkinID"] = 3616815672;
            Config["MaxDepositLimit"] = 250000;
            Config["EnableTeamSplitting"] = true;
            Config["SplitWithOfflineTeammates"] = false;
            SaveConfig();
        }
        private void LoadConfiguration() {
            bool configUpdated = false;
            T GetConfig<T>(string key, T defaultValue) {
                if (Config[key] == null) {
                    Config[key] = defaultValue;
                    configUpdated = true;
                }
                return (T)Convert.ChangeType(Config[key], typeof(T));
            }
            DepositItemID = GetConfig("DepositItemID", -932201673);
            DepositBoxSkinID = GetConfig("DepositBoxSkinID", 3616815672ul);
            MaxDepositLimit = GetConfig("MaxDepositLimit", 250000);
            EnableTeamSplitting = GetConfig("EnableTeamSplitting", true);
            SplitWithOfflineTeammates = GetConfig("SplitWithOfflineTeammates", false);
            if (configUpdated) {
                SaveConfig();
                Puts("Configuration file updated with new options.");
            }
        }
        // ==========================================================================
        // UI Methods
        // ==========================================================================
        [Command("leaderboard", "topscrap")]
        private void CmdShowLeaderboard(IPlayer player, string command, string[] args) {
            var rustPlayer = player.Object as BasePlayer;
            if (rustPlayer == null) return;
            DrawLeaderboardUI(rustPlayer);
        }
        [Command("leaderboard_close")]
        private void CmdCloseLeaderboard(IPlayer player, string command, string[] args) {
            var rustPlayer = player.Object as BasePlayer;
            if (rustPlayer == null) return;
            CuiHelper.DestroyUi(rustPlayer, UIPanelName);
        }
        private void DrawLeaderboardUI(BasePlayer player) {
            CuiHelper.DestroyUi(player, UIPanelName);
            var elements = new CuiElementContainer();
            // Widened panel for two columns
            elements.Add(new CuiPanel {
                Image = {
                    Color = "0.1 0.1 0.1 0.85", Material = "assets/content/ui/uibackgroundblur-ingamemenu.mat"
                }
                , RectTransform = {
                    AnchorMin = "0.15 0.05", AnchorMax = "0.85 0.9"
                }
                , CursorEnabled = true
            }
            , "Overlay", UIPanelName);
            // Title
            elements.Add(new CuiLabel {
                Text = {
                    Text = "🏆 Top Scrap Depositors", FontSize = 24, Align = TextAnchor.MiddleCenter
                }
                , RectTransform = {
                    AnchorMin = "0 0.92", AnchorMax = "1 0.98"
                }
            }
            , UIPanelName);
            // Close Button
            elements.Add(new CuiButton {
                Button = {
                    Command = "leaderboard_close", Color = "0.8 0.2 0.2 0.8"
                }
                , RectTransform = {
                    AnchorMin = "0.95 0.92", AnchorMax = "0.99 0.98"
                }
                , Text = {
                    Text = "X", FontSize = 18, Align = TextAnchor.MiddleCenter
                }
            }
            , UIPanelName);
            // Subtle Vertical Center Divider
            elements.Add(new CuiPanel {
                Image = {
                    Color = "0.5 0.5 0.5 0.15"
                }
                , RectTransform = {
                    AnchorMin = "0.5 0.12", AnchorMax = "0.502 0.89"
                }
            }
            , UIPanelName);
            long totalDeposited = playerTotalsCache.Values.Sum();
            var sortedPlayers = playerTotalsCache.OrderByDescending(kv => kv.Value).ToList();
            // Increased from Top 20 to Top 40 for the two-column layout
            var topPlayers = sortedPlayers.Take(40).ToList();
            int itemsPerColumn = 20;
            float yStart = 0.88f;
            float step = 0.038f;
            float currentY = yStart;
            for (int i = 0; i < topPlayers.Count; i++) {
                bool isRightColumn = i >= itemsPerColumn;
                if (i == itemsPerColumn) {
                    currentY = yStart;
                    // Reset for right column
                }
                string minX = isRightColumn ? "0.52" : "0.03";
                string maxX = isRightColumn ? "0.98" : "0.48";
                var kv = topPlayers[i];
                string steamId = kv.Key;
                string name = covalence.Players.FindPlayerById(steamId)?.Name ?? steamId;
                int deposited = kv.Value;
                double percentage = totalDeposited > 0 ? (double)deposited / totalDeposited * 100 : 0;
                string color = i switch {
                    0 => "<color=#FFD700>",  // Gold
                    1 => "<color=#C0C0C0>",  // Silver
                    2 => "<color=#CD7F32>",  // Bronze
                    _ => "<color=#FFFFFF>"
                }
                ;
                elements.Add(new CuiLabel {
                    Text = {
                        Text = $"{color}{i + 1}.{name}</color> - {deposited:N0} ({percentage:F2}%)", FontSize = 15, Align = TextAnchor.MiddleLeft
                    }
                    , RectTransform = {
                        AnchorMin = $"{minX} {currentY - 0.03f}", AnchorMax = $"{maxX} {currentY}"
                    }
                }
                , UIPanelName);
                currentY -= step;
            }
            // Player's Own Rank Calculator
            int myRank = sortedPlayers.FindIndex(kv => kv.Key == player.UserIDString) + 1;
            int myTotal = myRank > 0 ? sortedPlayers[myRank - 1].Value : 0;
            string myRankText = myRank > 0 ? $"Your Rank: #{myRank} ({myTotal:N0} scrap)" : "Your Rank: Unranked (0 scrap)";
            elements.Add(new CuiLabel {
                Text = {
                    Text = myRankText, FontSize = 16, Align = TextAnchor.MiddleCenter, Color = "0.6 0.8 1 1"
                }
                , RectTransform = {
                    AnchorMin = "0 0.06", AnchorMax = "1 0.12"
                }
            }
            , UIPanelName);
            // Total Server Deposits
            elements.Add(new CuiLabel {
                Text = {
                    Text = $"Total Server Deposits: {totalDeposited:N0} scrap", FontSize = 14, Align = TextAnchor.MiddleCenter, Color = "0.7 0.7 0.7 1"
                }
                , RectTransform = {
                    AnchorMin = "0 0.01", AnchorMax = "1 0.05"
                }
            }
            , UIPanelName);
            CuiHelper.AddUi(player, elements);
        }
        // ==========================================================================
        // Commands
        // ==========================================================================
        [Command("depositbox")]
        private void CmdGiveDepositBox(IPlayer player, string command, string[] args) {
            if (!player.HasPermission(PermPlace)) {
                player.Reply(Lang("NoPermission", player.Id));
                return;
            }
            var rustPlayer = player.Object as BasePlayer;
            if (rustPlayer == null) return;
            rustPlayer.inventory.containerMain.GiveItem(ItemManager.CreateByItemID(833533164, 1, DepositBoxSkinID));
            player.Reply(Lang("BoxGiven", player.Id));
        }
        [Command("scrapleaderboard")]
        private void CmdScrapLeaderboard(IPlayer player, string command, string[] args) {
            if (!player.HasPermission(PermAdmin) && !player.IsAdmin) {
                player.Reply("You must be an admin to use this command.");
                return;
            }
            int prizePool = 100000;
            if (args.Length > 0 && int.TryParse(args[0], out int parsedAmount)) {
                prizePool = parsedAmount;
            }
            GenerateDepositSummaryFiles(prizePool);
            player.Reply($"✅ Summary files generated in 'oxide/data/{DataFolder}/' (Pool: {prizePool:N0})");
            if (!File.Exists(ServerInfoPath)) {
                player.Reply("❌ ServerInfo.json not found.Skipping leaderboard update.");
                return;
            }
            try {
                long totalDeposited = playerTotalsCache.Values.Sum();
                var sortedPlayers = playerTotalsCache.OrderByDescending(kv => kv.Value).Take(40).ToList();
                var allLines = new List<string> {
                    Lang("TitleLine1", player.Id), string.Format(Lang("TitleLine2", player.Id), totalDeposited.ToString("N0")), ""
                }
                ;
                for (int i = 0; i < sortedPlayers.Count; i++) {
                    var kv = sortedPlayers[i];
                    var steamId = kv.Key;
                    var name = covalence.Players.FindPlayerById(steamId)?.Name ?? steamId;
                    var deposited = kv.Value;
                    double percentage = totalDeposited > 0 ? (double)deposited / totalDeposited * 100 : 0;
                    allLines.Add($"{i + 1}.{name} - {deposited:N0} ({percentage:F2}%)");
                }
                var page1Lines = allLines.Take(23).ToList();
                var page2Lines = allLines.Skip(23).ToList();
                var leaderboardTab = new JObject {
                    ["ButtonText"]
                    = Lang("ButtonText", player.Id), ["HeaderText"] = Lang("HeaderText", player.Id), ["Pages"] = new JArray {
                        new JObject {
                            ["TextLines"]
                            = JArray.FromObject(page1Lines), ["ImageSettings"] = new JArray()
                        }
                        , new JObject {
                            ["TextLines"]
                            = JArray.FromObject(page2Lines), ["ImageSettings"] = new JArray()
                        }
                    }
                    , ["TabButtonAnchor"] = 4, ["TabButtonFontSize"] = 16, ["HeaderAnchor"] = 0, ["HeaderFontSize"] = 32, ["TextFontSize"] = 16, ["TextAnchor"] = 3, ["OxideGroup"] = ""
                }
                ;
                var serverInfoRaw = File.ReadAllText(ServerInfoPath);
                var serverInfo = JsonConvert.DeserializeObject<Dictionary<string, object>>(serverInfoRaw);
                if (serverInfo["settings"] is JObject settings && settings["Tabs"] is JArray tabs) {
                    for (int i = tabs.Count - 1; i >= 0; i--) {
                        if (tabs[i]["ButtonText"]?.ToString() == Lang("ButtonText", player.Id)) {
                            tabs.RemoveAt(i);
                        }
                    }
                    tabs.Add(leaderboardTab);
                    File.WriteAllText(ServerInfoPath, JsonConvert.SerializeObject(serverInfo, Formatting.Indented));
                    Puts("✅ ServerInfo.json updated with 2-page leaderboard.");
                    server.Command("oxide.reload ServerInfo");
                    player.Reply("✅ Leaderboard updated and ServerInfo reloaded!");
                }
                else {
                    player.Reply("❌ 'settings' or 'Tabs' section missing in ServerInfo.json.");
                }
            }
            catch (Exception ex) {
                player.Reply($"❌ Error updating leaderboard: {ex.Message}");
                Puts($"❌ Error: {ex.Message}");
            }
        }
        // ==========================================================================
        // Core Logic
        // ==========================================================================
        private void LoadAndMigrateData() {
            string newFilePath = $"{DataFolder}/{LogFileName}";
            if (!Interface.Oxide.DataFileSystem.ExistsDatafile(newFilePath)) {
                if (Interface.Oxide.DataFileSystem.ExistsDatafile("DepositBoxLog")) {
                    Puts("⚠️ Old data file found.Migrating 'DepositBoxLog' to 'ScrapLeaderboard/ScrapLog'...");
                    depositLog = Interface.Oxide.DataFileSystem.ReadObject<DepositLog>("DepositBoxLog");
                    SaveDepositLog();
                }
                else {
                    depositLog = new DepositLog();
                }
            }
            else {
                depositLog = Interface.Oxide.DataFileSystem.ReadObject<DepositLog>(newFilePath);
            }
        }
        private void SaveDepositLog() {
            if (saveQueued) return;
            saveQueued = true;
            // Wait 5 seconds before actually writing to disk
            timer.Once(5f, () => {
                Interface.Oxide.DataFileSystem.WriteObject($"{DataFolder}/{LogFileName}", depositLog);
                saveQueued = false;
            }
            );
        }
        private void RecalculateTotals() {
            playerTotalsCache.Clear();
            if (depositLog?.Deposits == null) return;
            foreach (var entry in depositLog.Deposits) {
                if (!playerTotalsCache.ContainsKey(entry.SteamId)) playerTotalsCache[entry.SteamId] = 0;
                playerTotalsCache[entry.SteamId] += entry.AmountDeposited;
            }
        }
        private void GenerateDepositSummaryFiles(int prizePool) {
            int totalDeposits = playerTotalsCache.Values.Sum();
            var summary = new Dictionary<string, object>();
            var claims = new Dictionary<string, int>();
            var csvBuilder = new StringBuilder();
            // 1.UPDATED: Added 'name' to the CSV header
            csvBuilder.AppendLine("steamid,name,total_deposited,percentage,sats_reward");
            foreach (var entry in playerTotalsCache) {
                double percentage = totalDeposits > 0 ? (double)entry.Value / totalDeposits : 0;
                int reward = (int)Math.Round(percentage * prizePool);
                // 2.UPDATED: Fetch the player's Steam name.// We strip out any commas or quotes from their name so it doesn't accidentally break the CSV columns!
                string playerName = covalence.Players.FindPlayerById(entry.Key)?.Name ?? entry.Key;
                playerName = playerName.Replace(",", "").Replace("\"", "'");
                summary[entry.Key] = new {
                    name = playerName,  // Added to the JSON summary as well for good measure
                    total_deposited = entry.Value, percentage = percentage * 100, sats_reward = reward
                }
                ;
                claims[entry.Key] = reward;
                // 3.UPDATED: Inserted {playerName} into the CSV row builder
                csvBuilder.AppendLine($"{entry.Key},{playerName},{entry.Value},{(percentage * 100).ToString("F2", CultureInfo.InvariantCulture)},{reward}");
            }
            string dirPath = Interface.Oxide.DataDirectory + $"/{DataFolder}";
            if (!Directory.Exists(dirPath)) Directory.CreateDirectory(dirPath);
            File.WriteAllText(Path.Combine(dirPath, "ScrapSummary.json"), JsonConvert.SerializeObject(summary, Formatting.Indented));
            File.WriteAllText(Path.Combine(dirPath, "ScrapClaims.json"), JsonConvert.SerializeObject(claims, Formatting.Indented));
            File.WriteAllText(Path.Combine(dirPath, "ScrapSummary.csv"), csvBuilder.ToString());
        }
        private void LogDeposit(string userId, int amount, string sourceName = null, int originalTotal = 0, int teammateCount = 0) {
            depositLog.Deposits.Add(new DepositEntry {
                SteamId = userId, Timestamp = DateTime.UtcNow.ToString("o"), AmountDeposited = amount
            }
            );
            if (!playerTotalsCache.ContainsKey(userId)) playerTotalsCache[userId] = 0;
            playerTotalsCache[userId] += amount;
            int newTotal = playerTotalsCache[userId];
            SaveDepositLog();
            var player = BasePlayer.Find(userId);
            if (player != null && player.IsConnected) {
                // Case 1: Received from someone else
                if (sourceName != null) {
                    player.ChatMessage(Lang("DepositSplitReceived", userId).Replace("{amount}", amount.ToString("N0")).Replace("{source}", sourceName).Replace("{total}", newTotal.ToString("N0")));
                }
                // Case 2: Deposited by self, but it was split (Requires >0 teammates)
                else if (originalTotal > 0 && teammateCount > 0) {
                    player.ChatMessage(Lang("DepositSplitSelf", userId).Replace("{original}", originalTotal.ToString("N0")).Replace("{count}", teammateCount.ToString()).Replace("{amount}", amount.ToString("N0"))  // The amount THEY kept.Replace("{total}", newTotal.ToString("N0")));
                }
                // Case 3: Deposited by self, no split (standard)
                else {
                    player.ChatMessage(Lang("DepositRecorded", userId).Replace("{amount}", amount.ToString("N0")).Replace("{total}", newTotal.ToString("N0")));
                }
            }
        }
        // ==========================================================================
        // Helper Classes
        // ==========================================================================
        public class DepositBoxRestriction : FacepunchBehaviour {
            public ItemContainer container;
            public void InitDepositBox() {
                if (container == null) return;
                container.canAcceptItem += CanAcceptItem;
                container.onItemAddedRemoved += OnItemAddedRemoved;
            }
            // --- NEW HELPER METHODS ---
            private int GetPlayerTotal(string userId) {
                if (ScrapLeaderboard.instance.playerTotalsCache.TryGetValue(userId, out int total)) return total;
                return 0;
            }
            private int GetPlayerCapacity(string userId) {
                return Math.Max(0, ScrapLeaderboard.instance.MaxDepositLimit - GetPlayerTotal(userId));
            }
            private int GetTeamCapacity(BasePlayer depositor) {
                int capacity = GetPlayerCapacity(depositor.UserIDString);
                if (!ScrapLeaderboard.instance.EnableTeamSplitting || depositor.Team == null) return capacity;
                foreach (var memberId in depositor.Team.members) {
                    if (memberId == depositor.userID) continue;
                    bool isEligible = ScrapLeaderboard.instance.SplitWithOfflineTeammates;
                    if (!isEligible) {
                        var teammate = BasePlayer.FindByID(memberId);
                        if (teammate != null && teammate.IsConnected) isEligible = true;
                    }
                    if (isEligible) {
                        capacity += GetPlayerCapacity(memberId.ToString());
                    }
                }
                return capacity;
            }
            // --------------------------
            private bool CanAcceptItem(Item item, int targetPos) {
                if (ScrapLeaderboard.instance == null) return false;
                if (item == null || item.info == null || item.info.itemid != ScrapLeaderboard.instance.DepositItemID) return false;
                var player = item.GetOwnerPlayer();
                if (player == null) return false;
                // Check if the team as a whole has ANY capacity left.// If they do, we let it in and handle partial refunds in OnItemAddedRemoved.int teamCapacity = GetTeamCapacity(player);
                if (teamCapacity <= 0) {
                    float currentTime = UnityEngine.Time.realtimeSinceStartup;
                    // Check if the player is in the dictionary, and if 2 seconds have passed since the last message
                    if (!ScrapLeaderboard.instance.lastLimitMessageTime.TryGetValue(player.UserIDString, out float lastTime) || currentTime - lastTime > 2f) {
                        player.ChatMessage(ScrapLeaderboard.instance.Lang("TeamLimitReached", player.UserIDString));
                        ScrapLeaderboard.instance.lastLimitMessageTime[player.UserIDString] = currentTime;
                    }
                    return false;
                }
                if (ScrapLeaderboard.instance.depositTrack.ContainsKey(item.uid)) ScrapLeaderboard.instance.depositTrack[item.uid] = player.UserIDString;
                else ScrapLeaderboard.instance.depositTrack.Add(item.uid, player.UserIDString);
                return true;
            }
            private void OnItemAddedRemoved(Item item, bool added) {
                if (ScrapLeaderboard.instance == null) return;
                if (!added || item == null || item.info == null || item.info.itemid != ScrapLeaderboard.instance.DepositItemID) return;
                if (ScrapLeaderboard.instance.depositTrack.TryGetValue(item.uid, out string playerId)) {
                    // FIX: Remove immediately to prevent duplicate timers from rapid hook firing!
                    ScrapLeaderboard.instance.depositTrack.Remove(item.uid);
                    ScrapLeaderboard.instance.timer.Once(0.1f, () => {
                        if (ScrapLeaderboard.instance == null) return;
                        if (item == null || item.amount < 1) return;
                        var depositor = BasePlayer.Find(playerId);
                        if (depositor == null) {
                            // Fallback if depositor disconnects during the 0.1s delay
                            int cap = GetPlayerCapacity(playerId);
                            int logAmt = Math.Min(item.amount, cap);
                            if (logAmt > 0) ScrapLeaderboard.instance.LogDeposit(playerId, logAmt);
                            item.Remove();
                            return;
                        }
                        List<string> beneficiaries = new List<string> {
                            depositor.UserIDString
                        }
                        ;
                        if (ScrapLeaderboard.instance.EnableTeamSplitting && depositor.Team != null) {
                            foreach (var memberId in depositor.Team.members) {
                                if (memberId == depositor.userID) continue;
                                bool isEligible = ScrapLeaderboard.instance.SplitWithOfflineTeammates;
                                if (!isEligible) {
                                    var teammate = BasePlayer.FindByID(memberId);
                                    if (teammate != null && teammate.IsConnected) isEligible = true;
                                }
                                if (isEligible) beneficiaries.Add(memberId.ToString());
                            }
                        }
                        int totalAmount = item.amount;
                        int count = beneficiaries.Count;
                        int baseSplit = totalAmount / count;
                        int remainder = totalAmount % count;
                        int overflowPool = 0;
                        Dictionary<string, int> allocations = new Dictionary<string, int>();
                        // PASS 1: Allocate up to limits and gather excess into the overflow pool
                        foreach (var userId in beneficiaries) {
                            int intendedAmount = baseSplit;
                            if (userId == depositor.UserIDString) intendedAmount += remainder;
                            int allowance = GetPlayerCapacity(userId);
                            if (intendedAmount > allowance) {
                                allocations[userId] = allowance;
                                overflowPool += (intendedAmount - allowance);
                                // Send excess back to depositor's pool
                            }
                            else {
                                allocations[userId] = intendedAmount;
                            }
                        }
                        // PASS 2: Re-allocate overflow pool back to the depositor
                        int physicalRefund = 0;
                        if (overflowPool > 0) {
                            int depositorCurrentAllocation = allocations[depositor.UserIDString];
                            int depositorTotal = GetPlayerTotal(depositor.UserIDString);
                            int depositorRemainingCapacity = ScrapLeaderboard.instance.MaxDepositLimit - (depositorTotal + depositorCurrentAllocation);
                            if (overflowPool > depositorRemainingCapacity) {
                                // Depositor also hit their limit.Refund the rest as physical scrap.allocations[depositor.UserIDString] += Math.Max(0, depositorRemainingCapacity);
                                physicalRefund = overflowPool - Math.Max(0, depositorRemainingCapacity);
                            }
                            else {
                                // Depositor absorbs the teammates' excess
                                allocations[depositor.UserIDString] += overflowPool;
                            }
                        }
                        // Log all valid allocations
                        foreach (var kvp in allocations) {
                            string userId = kvp.Key;
                            int amountToLog = kvp.Value;
                            if (amountToLog > 0) {
                                if (userId == depositor.UserIDString) {
                                    ScrapLeaderboard.instance.LogDeposit(userId, amountToLog, null, totalAmount - physicalRefund, count - 1);
                                }
                                else {
                                    ScrapLeaderboard.instance.LogDeposit(userId, amountToLog, depositor.displayName);
                                }
                            }
                        }
                        // Return un-depositable scrap back to the player's inventory
                        if (physicalRefund > 0) {
                            Item refundItem = ItemManager.CreateByItemID(ScrapLeaderboard.instance.DepositItemID, physicalRefund);
                            if (refundItem != null) {
                                depositor.GiveItem(refundItem);
                                string refundMsg = ScrapLeaderboard.instance.Lang("DepositRefunded", depositor.UserIDString).Replace("{refund}", physicalRefund.ToString("N0"));
                                depositor.ChatMessage(refundMsg);
                            }
                        }
                        item.Remove();
                    }
                    );
                }
            }
            public void Destroy() {
                if (container != null) {
                    container.canAcceptItem -= CanAcceptItem;
                    container.onItemAddedRemoved -= OnItemAddedRemoved;
                }
                UnityEngine.Object.Destroy(this);
            }
        }
        private class DepositLog {
            [JsonProperty("deposits")]
            public List<DepositEntry> Deposits {
                get;
                set;
            }
            = new List<DepositEntry>();
        }
        private class DepositEntry {
            [JsonProperty("steamid")]
            public string SteamId {
                get;
                set;
            }
            [JsonProperty("timestamp")]
            public string Timestamp {
                get;
                set;
            }
            [JsonProperty("amount_deposited")]
            public int AmountDeposited {
                get;
                set;
            }
        }
        // ==========================================================================
        // Localization
        // ==========================================================================
        public string Lang(string key, string id = null) => lang.GetMessage(key, this, id);
        protected override void LoadDefaultMessages() {
            lang.RegisterMessages(new Dictionary<string, string> {
                ["NoPermission"]
                = "You do not have permission to place this box.", ["BoxGiven"] = "You have received a Deposit Box.", ["DepositRecorded"] = "Deposit: {amount} scrap.Total: {total}.", ["DepositSplitReceived"] = "Received split deposit: {amount} scrap from {source}.Total: {total}.",  // Updated to reflect that the depositor might have absorbed overflow
                ["DepositSplitSelf"]
                = "You deposited {original} scrap.Split with {count} teammate(s).You were credited {amount}.Your Total: {total}.",  // New messages for the dynamic limits & refunds
                ["TeamLimitReached"]
                = "Limit reached.You and your eligible teammates have hit the maximum deposit limit.", ["DepositRefunded"] = "Partial limit reached.Returned {refund} scrap to your inventory.", ["ButtonText"] = "Leaderboard", ["HeaderText"] = "Top Scrap Depositors", ["TitleLine1"] = "🏆 Top Scrap Depositors This Wipe", ["TitleLine2"] = "Total Deposited: {0} scrap"
            }
            , this, "en");
        }
    }
}

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