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

FREE PlayerStatTags : 1.3.6 1.3.6

Нет прав для скачивания
C#:
using System;
using Newtonsoft.Json;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using Oxide.Core;
using Oxide.Core.Plugins;
using Oxide.Core.Libraries.Covalence;


namespace Oxide.Plugins;

// Changelog
// minor - updated as the Death is still recorded efter the player reset is completed in Hardcore Mode.
// minor - Death now resets player stats completely without storing the Death/Suicide.
// minor - Added Hooks to check for Expansions.

[Info("PlayerStatTags", "CupsOhJoe", "1.3.6")]
[Description("Tracks player stats, assigns a single chat tag (by priority) to top players")]
internal class PlayerStatTags : RustPlugin
{

    #region Plugin References

    [PluginReference] private Plugin HarvestTagPack, BetterChat, Economics, ServerRewards;

    #endregion

    #region Configuration

    private const int currentConfigVersion = 5;
    private ConfigData config;

    private class ConfigData
    {
        [JsonProperty("Tags (Change Tags between [] only and set their priority as needed)")]
        public Dictionary<string, TagInfo> ChatTags = new()
    {
        {
            "PVPKills",
            new TagInfo
            {
                Tag = "[<color=#71201b>Killer</color>]", Priority = 1,
                Description = "Top PVP Kills: Awarded to the player with the highest number of PVP kills."
            }
        },
        {
            "PVPDistance",
            new TagInfo
            {
                Tag = "[<color=#ff2323>Sharp Shooter</color>]", Priority = 2,
                Description = "Top PVP Distance: Awarded to the player with the longest PVP engagement distance."
            }
        },
        {
            "PVEKills",
            new TagInfo
            {
                Tag = "[<color=#525600>Hunter</color>]", Priority = 3,
                Description = "Top PVE Kills: Awarded to the player with the highest number of PVE kills."
            }
        },
        {
            "PVEDistance",
            new TagInfo
            {
                Tag = "[<color=#d4af37>Snipe-Hunter</color>]", Priority = 4,
                Description = "Top PVE Distance: Awarded to the player with the longest PVE engagement distance."
            }
        },
        {
            "NPCKills",
            new TagInfo
            {
                Tag = "[<color=#3364b4>Bot Killer</color>]", Priority = 5,
                Description = "Top NPC Kills: Awarded to the player with the most NPC kills."
            }
        },
        {
            "AnimalKills",
            new TagInfo
            {
                Tag = "[<color=#d4af37>Animal Hunter</color>]", Priority = 6,
                Description = "Top Animal Kills: Awarded to the player with the most animal kills."
            }
        },
        {
            "NPCDistance",
            new TagInfo
            {
                Tag = "[<color=#00abff>Bot Sniper</color>]", Priority = 7,
                Description = "Top NPC Distance: Awarded to the player with the longest distance in NPC kills."
            }
        },
        {
            "SleepersKilled",
            new TagInfo
            {
                Tag = "[<color=#df7676>NoMercy</color>]", Priority = 8,
                Description = "Top Sleeper Kills: Awarded to the player with the highest number of sleeper kills."
            }
        },
        {
            "HeadShots",
            new TagInfo
            {
                Tag = "[<color=#7e3b3b>Head Shotter</color>]", Priority = 9,
                Description = "Top Headshots: Awarded to the player with the most headshots."
            }
        },
        {
            "Deaths",
            new TagInfo
            {
                Tag = "[<color=#505050>Death</color>]", Priority = 10,
                Description = "Deaths: Tracked for ratios; not typically used as a positive tag."
            }
        },
        {
            "Suicides",
            new TagInfo
            {
                Tag = "[<color=#aeaeae>Respawn</color>]", Priority = 11,
                Description = "Suicides: Tracked for ratios; not typically used as a positive tag."
            }
        },
        {
            "SkullsCrushed",
            new TagInfo
            {
                Tag = "[<color=#bebebe>Skull Crusher</color>]", Priority = 12,
                Description = "Skulls Crushed: Awarded to the player with the most skulls crushed."
            }
        },
        {
            "TimesWounded",
            new TagInfo
            {
                Tag = "[<color=#923a3a>Always Downed</color>]", Priority = 13,
                Description = "Times Wounded: Awarded to the player who was wounded the most."
            }
        },
        {
            "TimesHealed",
            new TagInfo
            {
                Tag = "[<color=#00ff12>Medic</color>]", Priority = 14,
                Description = "Times Healed: Awarded to the player who was healed the most (or healed others the most)."
            }
        },
        {
            "HeliHits",
            new TagInfo
            {
                Tag = "[<color=#00296a>Heli Hitter</color>]", Priority = 15,
                Description = "Helicopter Hits: Awarded to the player with the most helicopter hits."
            }
        },
        {
            "HeliKills",
            new TagInfo
            {
                Tag = "[<color=#3362ab>Heli Killer</color>]", Priority = 16,
                Description = "Helicopter Kills: Awarded to the player with the most helicopter kills."
            }
        },
        {
            "APCHits",
            new TagInfo
            {
                Tag = "[<color=#8e4d9d>Bradley Hitter</color>]", Priority = 17,
                Description = "APC Hits: Awarded to the player with the most APC hits."
            }
        },
        {
            "APCKills",
            new TagInfo
            {
                Tag = "[<color=#66007d>Bradley Killer</color>]", Priority = 18,
                Description = "APC Kills: Awarded to the player with the most APC kills."
            }
        },
        {
            "BarrelsDestroyed",
            new TagInfo
            {
                Tag = "[<color=#e3d984>Barrel Farmer</color>]", Priority = 19,
                Description = "Barrels Destroyed: Awarded to the player who destroyed the most barrels."
            }
        },
        {
            "ExplosivesThrown",
            new TagInfo
            {
                Tag = "[<color=#ee7d00>Ok Boomer</color>]", Priority = 20,
                Description = "Explosives Thrown: Awarded to the player who threw the most explosives."
            }
        },
        {
            "ArrowsFired",
            new TagInfo
            {
                Tag = "[<color=#9a5100>Archer</color>]", Priority = 21,
                Description = "Arrows Fired: Awarded to the player who fired the most arrows."
            }
        },
        {
            "BulletsFired",
            new TagInfo
            {
                Tag = "[<color=#7e00ed>Sprayer</color>]", Priority = 22,
                Description = "Bullets Fired: Awarded to the player who fired the most bullets."
            }
        },
        {
            "RocketsLaunched",
            new TagInfo
            {
                Tag = "[<color=#c68cf8>Rocket Man</color>]", Priority = 23,
                Description = "Rockets Launched: Awarded to the player who launched the most rockets."
            }
        },
        {
            "MLRSFired",
            new TagInfo
            {
                Tag = "[<color=#b3102e>Airstrike</color>]", Priority = 24,
                Description = "MLRS Fired: Awarded to the player who fired the most MLRS."
            }
        },
        {
            "WeaponTrapsDestroyed",
            new TagInfo
            {
                Tag = "[<color=#0040c0>Trap Trapper</color>]", Priority = 25,
                Description = "Weapon Traps Destroyed: Awarded to the player who destroyed the most weapon traps."
            }
        },
        {
            "DropsLooted",
            new TagInfo
            {
                Tag = "[<color=#c724ff>Supply Hunter</color>]", Priority = 26,
                Description = "Drops Looted: Awarded to the player who looted the most airdrops."
            }
        },
        {
            "FishCaught",
            new TagInfo
            {
                Tag = "[<color=#7d9eff>Fisherman</color>]", Priority = 27,
                Description = "Fish Caught: Awarded to the player who caught the most fish."
            }
        },
        {
            "Economics",
            new TagInfo
            {
                Tag = "[<color=#836b27>Earner</color>]", Priority = 28,
                Description = "Economics: Awarded to the player with the highest in-game economic balance."
            }
        },
        {
            "ServerRewards",
            new TagInfo
            {
                Tag = "[<color=#836b27>Reward Earner</color>]", Priority = 29,
                Description = "Server Rewards: Awarded to the player with the most server rewards points."
            }
        },
        {
            "StructuresBuilt",
            new TagInfo
            {
                Tag = "[<color=#0041ff>Architect</color>]", Priority = 30,
                Description = "Structures Built: Awarded to the player who built the most structures."
            }
        },
        {
            "StructuresDemolished",
            new TagInfo
            {
                Tag = "[<color=#836b27>Demolition Man</color>]", Priority = 31,
                Description = "Structures Demolished: Awarded to the player who demolished the most structures."
            }
        },
        {
            "ItemsDeployed",
            new TagInfo
            {
                Tag = "[<color=#598f2c>Deployer</color>]", Priority = 32,
                Description = "Items Deployed: Awarded to the player who deployed the most items."
            }
        },
        {
            "ItemsCrafted",
            new TagInfo
            {
                Tag = "[<color=#374b6a>Maker</color>]", Priority = 33,
                Description = "Items Crafted: Awarded to the player who crafted the most items."
            }
        },
        {
            "EntitiesRepaired",
            new TagInfo
            {
                Tag = "[<color=#048c8d>Fixer</color>]", Priority = 34,
                Description = "Entities Repaired: Awarded to the player who repaired the most entities."
            }
        },
        {
            "ResourcesGathered",
            new TagInfo
            {
                Tag = "[<color=#7200a6>Hoarder</color>]", Priority = 35,
                Description = "Resources Gathered: Awarded to the player who gathered the most resources."
            }
        },
        {
            "PlantsGathered",
            new TagInfo
            {
                Tag = "[<color=#048d0a>Farmer</color>]", Priority = 36,
                Description = "Plants Gathered: Awarded to the player who gathered the most plants."
            }
        },
        {
            "StructuresUpgraded",
            new TagInfo
            {
                Tag = "[<color=#769225>Home Improver</color>]", Priority = 37,
                Description = "Structures Upgraded: Awarded to the player who upgraded the most structures."
            }
        },
        {
            "HeliCrashes",
            new TagInfo
            {
                Tag = "[<color=#c8c0af>Crash Dummy</color>]", Priority = 38,
                Description = "Heli Crashes: Awarded to the player when they crash their own helicopter."
            }
        },
        {
            "CarCrashes",
            new TagInfo
            {
                Tag = "[<color=#8d8d8d>Road Rash</color>]", Priority = 39,
                Description = "Car Crashes: Awarded to the player with the most car crashes."
            }
        },
        {
            "VehicleKills",
            new TagInfo
            {
                Tag = "[<color=#5a5a5a>Destroyer</color>]", Priority = 40,
                Description = "Vehicle Kills: Awarded to the player who destroyed the most vehicles."
            }
        }
    };

        [JsonProperty("Remove Data on MAP Wipe")]
        public bool DeleteDataOnMapWipe = false;

        [JsonProperty("How often to Update Data (sec)")]
        public float UpdateInterval = 15f;

        [JsonProperty("How Often to Save Data to file (sec)")]
        public float SaveInterval = 60f;

        [JsonProperty("Enable Hardcore Mode (Reset stats on death)")]
        public bool EnableHardcoreMode = false;

        [JsonProperty("Hardcore Mode Notification Message")]
        public string HardcoreModeMessage = "Your stats have been reset due to hardcore mode!";

        [JsonProperty("Broadcast Hardcore Mode Resets")]
        public bool BroadcastHardcoreResets = true;

        [JsonProperty("How long to keep Ownership of Vehicles (sec)")]
        public float VehicleOwnershipClearInterval = 60f;

        [JsonProperty("Use BetterChat Integration")]
        public bool EnableBetterChatIntegration = false;

        [JsonProperty("Enable SteamID Exclusion")]
        public bool EnableSteamIDExclusion = false;

        [JsonProperty("Excluded SteamIDs (Players who will be excluded from stat tracking)")]
        public List<ulong> ExcludedSteamIDs = new List<ulong>();

        [JsonProperty("Config Version (DO NOT CHANGE)")]
        public int ConfigVersion = currentConfigVersion;
    }
    
    public class TagInfo
    {
        public string Tag { get; set; } = "";
        public int Priority { get; set; }
        public string Description { get; set; } = "";
    }

    protected override void LoadDefaultConfig()
    {
        Puts("Creating new default config file.");
        config = new ConfigData();
        SaveConfig();
    }

    private void LoadConfigData()
    {
        try
        {
            config = Config.ReadObject<ConfigData>();
            if (config == null)
            {
                LoadDefaultConfig();
                return;
            }

            if (config.ConfigVersion < currentConfigVersion)
            {
                Puts("Config file is outdated; updating with new keys.");
                config.ConfigVersion = currentConfigVersion;
                SaveConfig();
            }
        }
        catch
        {
            Puts("Could not load config, creating new default config.");
            LoadDefaultConfig();
        }
    }

    private void UpdateConfig()
    {
        var defaultConfig = new ConfigData();
        foreach (var kvp in defaultConfig.ChatTags)
            if (!config.ChatTags.ContainsKey(kvp.Key))
            {
                config.ChatTags[kvp.Key] = kvp.Value;
            }
            else
            {
                if (string.IsNullOrEmpty(config.ChatTags[kvp.Key].Description))
                    config.ChatTags[kvp.Key].Description = kvp.Value.Description;
            }

        // Let's add the Hardcode mode Thanks Rust STunts
        if (!config.GetType().GetFields().Any(f => f.Name == "EnableHardcoreMode"))
        {
            config.EnableHardcoreMode = defaultConfig.EnableHardcoreMode;
            config.HardcoreModeMessage = defaultConfig.HardcoreModeMessage;
            config.BroadcastHardcoreResets = defaultConfig.BroadcastHardcoreResets;
        }

        config.UpdateInterval = defaultConfig.UpdateInterval;
        config.SaveInterval = defaultConfig.SaveInterval;
        config.ConfigVersion = currentConfigVersion;
    }

    private void SaveConfig()
    {
        Config.WriteObject(config, true);
    }

    #endregion


    #region Data Storage

    private DataStore storedData;

    private class DataStore
    {
        public Dictionary<ulong, Dictionary<string, double>> playerStats = new();
    }

    private Dictionary<ulong, Dictionary<string, double>> playerStats => storedData.playerStats;

    private void LoadData()
    {
        try
        {
            storedData = Interface.Oxide.DataFileSystem.ReadObject<DataStore>("PlayerStatTagsData");
            if (storedData == null)
                storedData = new DataStore();
            CorrectDataFormat();

            if (config.EnableSteamIDExclusion)
            {
                foreach (var excludedId in config.ExcludedSteamIDs)
                {
                    if (storedData.playerStats.ContainsKey(excludedId))
                    {
                        DebugLog($"[Debug] Removing stats for excluded player {excludedId}");
                        storedData.playerStats.Remove(excludedId);
                    }
                }
            }
        }
        catch
        {
            storedData = new DataStore();
        }
    }

    private void SaveData()
    {
        Interface.Oxide.DataFileSystem.WriteObject("PlayerStatTagsData", storedData);
    }

    private void CorrectDataFormat()
    {
        foreach (var playerEntry in storedData.playerStats.ToList())
        {
            var playerId = playerEntry.Key;
            var stats = playerEntry.Value;
            foreach (var key in stats.Keys.ToList())
                if (!config.ChatTags.ContainsKey(key))
                {
                    DebugLog($"[Debug] Removing obsolete stat '{key}' for player {playerId}");
                    stats.Remove(key);
                }
        }
    }

    private void OnNewSave(string filename)
    {
        if (config.DeleteDataOnMapWipe)
        {
            var dataFile = Interface.Oxide.DataFileSystem.GetFile("PlayerStatTagsData");
            dataFile.Clear();
            dataFile.Save();
            Puts("Map wipe detected: PlayerStatTags data file deleted and new data store initialized.");
        }
    }

    private void ResetPlayerStats(ulong playerId)
    {
        if (playerStats.ContainsKey(playerId))
        {
            // Create a backup of stats for debugging
            var statsBackup = new Dictionary<string, double>(playerStats[playerId]);
            DebugLog($"[Debug] Resetting stats for player {playerId}. Previous stats: {string.Join(", ", statsBackup.Select(kvp => $"{kvp.Key}:{kvp.Value}"))}");

            // Remove player from top players list
            foreach (var category in config.ChatTags.Keys)
            {
                if (topPlayers.TryGetValue(category, out var topId) && topId == playerId)
                {
                    // Find new top player for this category
                    ulong newTopId = 0;
                    double newTopValue = 0;

                    foreach (var kvp in playerStats)
                    {
                        if (kvp.Key != playerId &&
                            kvp.Value.TryGetValue(category, out var value) &&
                            value > newTopValue)
                        {
                            newTopValue = value;
                            newTopId = kvp.Key;
                        }
                    }

                    topPlayers[category] = newTopId;
                    DebugLog($"[Debug] Top player for {category} changed from {playerId} to {newTopId} with value {newTopValue}");
                }
            }

            playerStats[playerId] = new Dictionary<string, double>();

            SaveData();

            UpdateTopPlayers();

            DebugLog($"[Debug] Force-updated top players list after resetting player {playerId}'s stats");
        }
    }

    #endregion

    #region Stat Tracking

    private bool harvestTrackingEnabled = true;
    private bool travelTrackingEnabled = true;

    private void OnItemDeployed(Deployer deployer, BaseEntity entity)
    {
        if (deployer == null) return;
        var player = deployer.GetOwnerPlayer();
        if (player == null || player.IsNpc || IsHumanNPC(player)) return;

        UpdateStat(player.userID, "ItemsDeployed", 1, player);
        if (playerStats[player.userID]["ItemsDeployed"] > GetTopValue("ItemsDeployed"))
            topPlayers["ItemsDeployed"] = player.userID;

        DebugLog($"[Debug] Player {player.userID} deployed an item");
    }
    
    private void ClearExpiredVehicleOwnerships()
    {
        var currentTime = Time.realtimeSinceStartup;
        // gto through the copy of dictionary
        foreach (var kvp in vehicleOwnership.ToList())
        {
            var vehicleId = kvp.Key;
            var info = kvp.Value;
            if (info.DismountTime > 0 && currentTime - info.DismountTime >= config.VehicleOwnershipClearInterval)
            {
                DebugLog(
                    $"[Debug] Vehicle {vehicleId} ownership expired after {config.VehicleOwnershipClearInterval} seconds, clearing owner.");
                // remove the owner after X amounts of seconds, I guees
                info.OwnerId = 0;
                info.DismountTime = 0f;
            }
        }
    }

    private Dictionary<string, ulong> topPlayers = new();
    private List<ulong> airdrops = new List<ulong>();

    private void UpdateStat(ulong id, string category, double amount, BasePlayer player)
    {
        if (config.EnableSteamIDExclusion && config.ExcludedSteamIDs.Contains(id))
        {
            DebugLog($"[Debug] Player {id} is in the exclusion list and will not be tracked for stats.");
            return;
        }

        if (player == null || player.net?.connection == null)
            return;

        if (!playerStats.ContainsKey(id))
            playerStats[id] = new Dictionary<string, double>();
        if (!playerStats[id].ContainsKey(category))
            playerStats[id][category] = 0;
        playerStats[id][category] += amount;
        DebugLog($"Updated stat '{category}' for player {id} by {amount}. New value: {playerStats[id][category]}");
    }
    private double GetTopValue(string category)
    {
        ulong topId;
        if (topPlayers.TryGetValue(category, out topId))
            if (playerStats.ContainsKey(topId) && playerStats[topId].ContainsKey(category))
                return playerStats[topId][category];
        return 0;
    }

    private void ComputeRatios()
    {
        foreach (var kvp in playerStats)
        {
            var stats = kvp.Value;
            if (stats.ContainsKey("PVPKills") && stats.ContainsKey("Deaths"))
            {
                var deaths = stats["Deaths"];
                var pvpkills = stats["PVPKills"];
                stats["KDR"] = deaths > 0 ? pvpkills / deaths : pvpkills;
                var suicides = stats.ContainsKey("Suicides") ? stats["Suicides"] : 0;
                stats["SDR"] = deaths > 0 ? suicides / deaths : suicides;
            }
        }
    }

    private void UpdateTopPlayers()
    {
        ComputeRatios();
        foreach (var category in config.ChatTags.Keys)
        {
            ulong currentTop = 0;
            double currentTopValue = 0;
            foreach (var kvp in playerStats)
            {
                if (config.EnableSteamIDExclusion && config.ExcludedSteamIDs.Contains(kvp.Key))
                    continue;

                double statValue = 0;
                if (kvp.Value.TryGetValue(category, out statValue))
                    if (statValue > currentTopValue)
                    {
                        currentTopValue = statValue;
                        currentTop = kvp.Key;
                    }
            }

            topPlayers[category] = currentTop;
            DebugLog($"Top for '{category}' is {currentTop} with value {currentTopValue}");
        }
    }

    #region Vehicle Pilot Tracking

    private class HeliPilotInfo
    {
        public ulong PilotId { get; set; }
        public float ExitTime { get; set; }
        public bool HasExited { get; set; }
    }

    private class VehicleOwnershipInfo
    {
        public ulong OwnerId { get; set; }
        public float DismountTime { get; set; }
    }

    private Dictionary<uint, VehicleOwnershipInfo> vehicleOwnership = new();
    
    //I might need this for the Heli Tracking
    //private Dictionary<uint, HeliPilotInfo> vehiclePilots = new Dictionary<uint, HeliPilotInfo>();


    // Change who the heck owns the mount/vehicle. Maybe
    private void ChangeVehicleOwnership(BaseCombatEntity vehicle, ulong userId)
    {
        var vehicleId = vehicle != null && vehicle.net != null ? vehicle.net.ID.Value : 0;
        DebugLog($"DEBUG: Changing vehicle ownership for vehicle {vehicleId} to user {userId}.");
        vehicle.OwnerID = userId;
        Interface.CallHook("OnVehicleOwnershipChanged", vehicle);
        DebugLog($"DEBUG: Vehicle {vehicleId} ownership updated to {vehicle.OwnerID}.");
    }

    private void OnEntityMounted(BaseMountable entity, BasePlayer player)
    {
        if (entity == null || player == null)
            return;

        // Check if the player is an NPC
        if (player.IsNpc || IsHumanNPC(player))
        {
            DebugLog($"[Debug] NPC {player.userID} mounting vehicle, skipping ownership tracking");
            return;
        }

        var vehicle = entity.GetComponentInParent<BaseVehicle>();
        uint entityId = 0;
        if (vehicle != null)
            entityId = (uint)vehicle.net.ID.Value;
        else
            entityId = (uint)entity.net.ID.Value;

        // Existing ownership tracking code
        vehicleOwnership[entityId] = new VehicleOwnershipInfo { OwnerId = player.userID, DismountTime = 0f };
        DebugLog($"[Debug] OnEntityMounted: Stored ownership with entity ID {entityId} for player {player.userID}.");

        if (vehicle != null)
        {
            ChangeVehicleOwnership(vehicle, player.userID);
            DebugLog($"[Debug] OnEntityMounted: Vehicle claimed by {player.userID}.");
        }
        else
        {
            DebugLog($"[Debug] OnEntityMounted: No parent vehicle found, using entity net ID {entityId} for ownership.");
        }
    }

    public bool IsHumanNPC(BasePlayer player)
    {
        foreach (var comp in player?.GetComponents<Component>())
            if (comp?.GetType()?.Name == "HumanPlayer")
                return true;
        return false;
    }
    

    #endregion

    private void PurgeNonPlayerData()
    {
        foreach (var entry in storedData.playerStats.Keys.ToList())
            if (!entry.ToString().StartsWith("7656119"))
            {
                DebugLog($"[Debug] Removing non-player data for SteamID: {entry}");
                storedData.playerStats.Remove(entry);
            }
    }

    #endregion

    #region Debug Logging

    private bool debugEnabled = false;

    private void DebugLog(string message)
    {
        if (debugEnabled)
            Puts("[Debug] " + message);
    }

    [ChatCommand("playerstattag")]
    private void PlayerStatTagCommand(BasePlayer player, string command, string[] args)
    {
        if (!permission.UserHasPermission(player.UserIDString, "playerstattags.admin"))
        {
            player.ChatMessage("You do not have permission to use this command.");
            return;
        }

        if (args.Length < 2)
        {
            player.ChatMessage("Usage: /playerstattag debug on/off");
            return;
        }

        if (args[0].ToLower() == "debug")
        {
            if (args[1].ToLower() == "on")
            {
                debugEnabled = true;
                player.ChatMessage("PlayerStatTags debugging is now ON.");
                Puts("PlayerStatTags debugging enabled by admin.");
            }
            else if (args[1].ToLower() == "off")
            {
                debugEnabled = false;
                player.ChatMessage("PlayerStatTags debugging is now OFF.");
                Puts("PlayerStatTags debugging disabled by admin.");
            }
            else
            {
                player.ChatMessage("Usage: /playerstattag debug on/off");
            }
        }
        else
        {
            player.ChatMessage("Unknown subcommand. Usage: /playerstattag debug on/off");
        }
    }

    [ChatCommand("purgedata")]
    private void PurgeDataCommand(BasePlayer player, string command, string[] args)
    {
        if (!permission.UserHasPermission(player.UserIDString, "playerstattags.admin"))
        {
            player.ChatMessage("You do not have permission to run this command.");
            return;
        }

        PurgeNonPlayerData();
        player.ChatMessage("Purged non-player data from stat records.");
    }

    [ChatCommand("clearstats")]
    private void ClearStatsCommand(BasePlayer player, string command, string[] args)
    {
        if (!permission.UserHasPermission(player.UserIDString, "playerstattags.admin"))
        {
            player.ChatMessage("You do not have permission to use this command.");
            return;
        }

        if (args.Length == 0)
        {
            player.ChatMessage("Usage: /clearstats all OR /clearstats <steamid>");
            return;
        }

        if (args[0].ToLower() == "all")
        {
            storedData.playerStats.Clear();
            SaveData();
            player.ChatMessage("All stats have been cleared.");
            Puts($"Admin {player.displayName} cleared all stats.");
        }
        else
        {
            ulong targetId;
            if (!ulong.TryParse(args[0], out targetId))
            {
                player.ChatMessage("Invalid SteamID.");
                return;
            }

            if (storedData.playerStats.ContainsKey(targetId))
            {
                storedData.playerStats.Remove(targetId);
                SaveData();
                player.ChatMessage($"Stats for {args[0]} have been cleared.");
                Puts($"Admin {player.displayName} cleared stats for SteamID {args[0]}.");
            }
            else
            {
                player.ChatMessage("No stats found for that SteamID.");
            }
        }
    }

    [ConsoleCommand("clearstats")]
    private void ClearStatsConsoleCommand(ConsoleSystem.Arg arg)
    {
        var args = arg.Args;
        if (args == null || args.Length == 0)
        {
            Puts("Usage: clearstats all OR clearstats <steamid>");
            return;
        }

        if (args[0].ToString().ToLower() == "all")
        {
            storedData.playerStats.Clear();
            SaveData();
            Puts("All stats have been cleared via console.");
        }
        else
        {
            ulong targetId;
            if (!ulong.TryParse(args[0], out targetId))
            {
                Puts("Invalid SteamID.");
                return;
            }

            if (storedData.playerStats.ContainsKey(targetId))
            {
                storedData.playerStats.Remove(targetId);
                SaveData();
                Puts($"Stats for SteamID {args[0]} have been cleared via console.");
            }
            else
            {
                Puts("No stats found for that SteamID.");
            }
        }
    }

    #endregion

    #region Hooks – Tracking Events

    private void OnServerInitialized()
    {
        Puts("PlayerStatTags initialized.");
        LoadConfigData();
        LoadData();
        CheckForBetterChat();
        CheckForServerRewards();

        // Check for expansion packs
        if (plugins.Exists("HarvestTagPack"))
        {
            Puts("HarvestTagPack detected - disabling redundant resource tracking in PlayerStatTags");
            harvestTrackingEnabled = false;
        }

        if (plugins.Exists("TravelTagPack"))
        {
            Puts("TravelTagPack detected - disabling redundant travel tracking in PlayerStatTags");
            travelTrackingEnabled = false;
        }
    }

    private void OnPlayerConnected(BasePlayer player)
    {
        // Inform player about hardcore mode if enabled
        if (config.EnableHardcoreMode && player != null)
        {
            timer.Once(5f, () => {
                if (player != null && player.IsConnected)
                {
                    player.ChatMessage("<color=red>WARNING: Hardcore Mode is enabled!</color> Your stats will be reset if you die.");
                }
            });
        }

        if (ServerRewards == null || player == null) return;

        timer.Once(1f, () => {
            if (ServerRewards != null)
            { 
                DebugLog($"Updating ServerRewards data for player {player.displayName} on connect");
                UpdatePlayerServerRewardsData(player.userID);
            }
        });
    }
    private void OnPlayerDisconnected(BasePlayer player)
    {
        if (ServerRewards == null || player == null) return;

        DebugLog($"Updating ServerRewards data for player {player.displayName} on disconnect");
        UpdatePlayerServerRewardsData(player.userID);
    }

    private void CheckForServerRewards()
    {
        if (ServerRewards != null)
        {
            try
            {
                Puts($"ServerRewards plugin detected (Version: {ServerRewards.Version})");
                Puts("Setting up polling for ServerRewards points");

                timer.Once(5f, () => {
                    if (ServerRewards != null)
                    {
                        Puts("Performing initial ServerRewards data update...");
                        UpdateAllServerRewardsData();
                    }
                });

                updateTimer = timer.Every(30f, () => {
                    if (ServerRewards != null)
                    {
                        DebugLog("Running scheduled ServerRewards data update...");
                        UpdateAllServerRewardsData();
                    }
                    else
                    {
                        DebugLog("ServerRewards is no longer loaded - canceling timer");
                        updateTimer.Destroy();
                        updateTimer = null;
                    }
                });

                Subscribe(nameof(OnPlayerConnected));
                Subscribe(nameof(OnPlayerDisconnected));
            }
            catch (Exception ex)
            {
                Puts($"Error while integrating with ServerRewards: {ex.Message}");
                Puts($"Stack Trace: {ex.StackTrace}");
            }
        }
        else
        {
            Puts("ServerRewards plugin not installed or not loaded. ServerRewards integration disabled.");
        }
    }

    private Timer updateTimer;

    private void UpdatePlayerServerRewardsData(ulong playerId)
    {
        if (ServerRewards == null || playerId == 0) return;

        try
        {
            object pointsObj = ServerRewards.Call("CheckPoints", playerId);
            if (pointsObj == null) return;

            int rewardPoints;

            if (pointsObj is int)
            {
                rewardPoints = (int)pointsObj;
            }
            else if (int.TryParse(pointsObj.ToString(), out int parsedPoints))
            {
                rewardPoints = parsedPoints;
            }
            else
            {
                DebugLog($"Could not parse points data for player {playerId}: {pointsObj}");
                return;
            }

            UpdatePlayerServerRewardsPoints(playerId, rewardPoints);
        }
        catch (Exception ex)
        {
            DebugLog($"Error updating ServerRewards data for player {playerId}: {ex.Message}");
        }
    }
    
    private void Init()
    {
        // Permissions, need to add more maybe?
        permission.RegisterPermission("playerstattags.admin", this);
        permission.RegisterPermission("playerstattags.test", this);

        LoadConfigData();
        LoadData();
        foreach (var category in config.ChatTags.Keys)
            topPlayers[category] = 0;

        UpdateTopPlayers();

        // Let mAke sure we continue to track the players
        timer.Every(config.UpdateInterval, UpdateTopPlayers);
        timer.Every(config.SaveInterval, SaveData);

        //Use the time from the config to automatically remove owners, hopefully just the dictionary :)
        timer.Every(config.VehicleOwnershipClearInterval, ClearExpiredVehicleOwnerships);
    }

    private void OnExplosiveThrown(BasePlayer player, BaseEntity explosive, ThrownWeapon item)
    {
        if (player == null) return;
        UpdateStat(player.userID, "ExplosivesThrown", 1, player);
        if (playerStats[player.userID]["ExplosivesThrown"] > GetTopValue("ExplosivesThrown"))
            topPlayers["ExplosivesThrown"] = player.userID;
    }

    private void OnEntityBuilt(Planner planner, GameObject gameObject)
    {
        if (planner == null) return;
        var player = planner.GetOwnerPlayer();
        if (player == null) return;
        UpdateStat(player.userID, "StructuresBuilt", 1, player);
        if (playerStats[player.userID]["StructuresBuilt"] > GetTopValue("StructuresBuilt"))
            topPlayers["StructuresBuilt"] = player.userID;
    }

    private void OnWeaponFired(BaseProjectile projectile, BasePlayer player, ItemModProjectile mod)
    {
        if (player == null || mod == null) return;
        var modStr = mod.ToString().ToLower();
        if (modStr.Contains("arrow"))
        {
            UpdateStat(player.userID, "ArrowsFired", 1, player);
            if (playerStats[player.userID]["ArrowsFired"] > GetTopValue("ArrowsFired"))
                topPlayers["ArrowsFired"] = player.userID;
        }

        if (modStr.Contains("ammo"))
        {
            UpdateStat(player.userID, "BulletsFired", 1, player);
            if (playerStats[player.userID]["BulletsFired"] > GetTopValue("BulletsFired"))
                topPlayers["BulletsFired"] = player.userID;
        }
        if (modStr.Contains("rocket") || (projectile?.ShortPrefabName?.Contains("rocket") ?? false))
        {
            UpdateStat(player.userID, "RocketsLaunched", 1, player);
            if (playerStats[player.userID]["RocketsLaunched"] > GetTopValue("RocketsLaunched"))
                topPlayers["RocketsLaunched"] = player.userID;
            DebugLog($"[Debug] Player {player.userID} launched a rocket");
        }
    }

    void OnEntitySpawned(SupplyDrop entity)
    {
        NextTick(() =>
        {
            if (entity != null)
                airdrops.Add(entity.net.ID.Value);
        });
    }

    void OnLootEntity(BasePlayer player, SupplyDrop entity)
    {
        if (player == null || entity?.net?.ID == null)
            return;

        if (airdrops.Contains(entity.net.ID.Value))
        {
            airdrops.Remove(entity.net.ID.Value);
            UpdateStat(player.userID, "DropsLooted", 1, player);
            DebugLog($"[Debug] Player {player.userID} looted airdrop. Current count: {playerStats[player.userID]["DropsLooted"]}");
        }
    }

    private void OnPlayerWounded(BasePlayer player)
    {
        if (player == null || player.IsNpc || IsHumanNPC(player)) return;

        UpdateStat(player.userID, "TimesWounded", 1, player);
        if (playerStats[player.userID]["TimesWounded"] > GetTopValue("TimesWounded"))
            topPlayers["TimesWounded"] = player.userID;

        DebugLog($"[Debug] Player {player.userID} was wounded. Current count: {playerStats[player.userID]["TimesWounded"]}");
    }

    private void OnMedicalItemUsed(MedicalTool tool, BasePlayer player)
    {
        if (player == null || player.IsNpc || IsHumanNPC(player)) return;

        UpdateStat(player.userID, "TimesHealed", 1, player);
        if (playerStats[player.userID]["TimesHealed"] > GetTopValue("TimesHealed"))
            topPlayers["TimesHealed"] = player.userID;

        DebugLog($"[Debug] Player {player.userID} used a medical item");
    }

    private void OnPlayerRecovered(BasePlayer player, BasePlayer healer)
    {
        if (healer == null || healer.IsNpc || IsHumanNPC(healer)) return;

        UpdateStat(healer.userID, "TimesHealed", 1, healer);
        if (playerStats[healer.userID]["TimesHealed"] > GetTopValue("TimesHealed"))
            topPlayers["TimesHealed"] = healer.userID;

        DebugLog($"[Debug] Player {healer.userID} healed another player. Current count: {playerStats[healer.userID]["TimesHealed"]}");
    }

    #endregion

    #region Server Rewards tracking

    private void UpdatePlayerServerRewardsPoints(ulong playerId, int points)
    {
        if (playerId == 0 || (config.EnableSteamIDExclusion && config.ExcludedSteamIDs.Contains(playerId))) return;

        bool pointsChanged = false;

        if (!playerStats.ContainsKey(playerId))
        {
            playerStats[playerId] = new Dictionary<string, double>();
            pointsChanged = true;
        }

        if (!playerStats[playerId].ContainsKey("ServerRewards") ||
            Math.Abs(playerStats[playerId]["ServerRewards"] - points) > 0.01) 
        {
            playerStats[playerId]["ServerRewards"] = points;
            pointsChanged = true;
            DebugLog($"ServerRewards points updated for player {playerId}: {points}");
        }

        if (pointsChanged)
        {
            double topPoints = 0;
            if (topPlayers.TryGetValue("ServerRewards", out var currentTopPlayer) &&
                playerStats.TryGetValue(currentTopPlayer, out var topPlayerStats) &&
                topPlayerStats.TryGetValue("ServerRewards", out topPoints))
            {
                if (points > topPoints)
                {
                    topPlayers["ServerRewards"] = playerId;
                    DebugLog($"New top ServerRewards player: {playerId} with {points} points");
                    SaveData();
                }
            }
            else if (points > 0)
            {
                topPlayers["ServerRewards"] = playerId;
                DebugLog($"First top ServerRewards player: {playerId} with {points} points");
                SaveData();
            }
        }
    }

    private void UpdateAllServerRewardsData()
    {
        if (ServerRewards == null)
        {
            DebugLog("ServerRewards plugin is null, cannot update data.");
            return;
        }

        try
        {
            int changedPlayers = 0;
            bool topPlayerChanged = false;

            ulong oldTopPlayer = 0;
            if (topPlayers.TryGetValue("ServerRewards", out var currentTop))
                oldTopPlayer = currentTop;

            foreach (var player in BasePlayer.activePlayerList)
            {
                if (player == null || player.userID == 0) continue;

                object pointsObj = ServerRewards.Call("CheckPoints", player.userID);

                if (pointsObj != null)
                {
                    int rewardPoints;

                    if (pointsObj is int)
                    {
                        rewardPoints = (int)pointsObj;
                    }
                    else if (int.TryParse(pointsObj.ToString(), out int parsedPoints))
                    {
                        rewardPoints = parsedPoints;
                    }
                    else
                    {
                        DebugLog($"Could not parse points data for player {player.displayName}: {pointsObj}");
                        continue;
                    }

                    bool pointsChanged = false;

                    if (!playerStats.ContainsKey(player.userID))
                    {
                        playerStats[player.userID] = new Dictionary<string, double>();
                        pointsChanged = true;
                    }

                    if (!playerStats[player.userID].ContainsKey("ServerRewards") ||
                        Math.Abs(playerStats[player.userID]["ServerRewards"] - rewardPoints) > 0.01)
                    {
                        playerStats[player.userID]["ServerRewards"] = rewardPoints;
                        pointsChanged = true;
                    }

                    if (pointsChanged)
                        changedPlayers++;

                    if (rewardPoints > 0)
                    {
                        double topPoints = 0;
                        if (topPlayers.TryGetValue("ServerRewards", out var topPlayerId) &&
                            playerStats.TryGetValue(topPlayerId, out var topPlayerStats) &&
                            topPlayerStats.TryGetValue("ServerRewards", out topPoints))
                        {
                            if (rewardPoints > topPoints)
                            {
                                topPlayers["ServerRewards"] = player.userID;
                                topPlayerChanged = true;
                                DebugLog($"New top ServerRewards player: {player.displayName} with {rewardPoints} points");
                            }
                        }
                        else
                        {
                            topPlayers["ServerRewards"] = player.userID;
                            topPlayerChanged = true;
                            DebugLog($"First top ServerRewards player: {player.displayName} with {rewardPoints} points");
                        }
                    }
                }
            }

            if (changedPlayers > 0 || topPlayerChanged)
            {
                SaveData();

                if (topPlayerChanged)
                {
                    ulong newTopPlayer = topPlayers["ServerRewards"];
                    if (newTopPlayer != oldTopPlayer)
                    {
                        string oldName = BasePlayer.FindByID(oldTopPlayer)?.displayName ?? oldTopPlayer.ToString();
                        string newName = BasePlayer.FindByID(newTopPlayer)?.displayName ?? newTopPlayer.ToString();
                        DebugLog($"Top ServerRewards player changed from {oldName} to {newName}");
                    }
                }

                DebugLog($"Updated ServerRewards data for {changedPlayers} players");
            }
        }
        catch (Exception ex)
        {
            Puts($"Error updating ServerRewards data: {ex.Message}");
        }
    }

    #endregion

    #region Updated OnEntityDeath for Vehicles

    private float crashThreshold = 10f;
    private string[] heliVehicleNames = {
        "minicopter",
        "attackhelicopter",
        "transporthelicopter",
        "scrapped_helicopter"
    };

    private void OnEntityDeath(BaseEntity entity, HitInfo hitinfo)
    {
        if (entity == null)
        {
            Puts("[Debug] OnEntityDeath: entity is null");
            return;
        }

        DebugLog($"[Debug] OnEntityDeath: Entity type: {entity.GetType().Name}, name: {entity.name}");

        var victim 
// ... [Code preview truncated due to length. Full source in attached file] ...

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