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

FREE PerkMachines : 2.0.0 2.0.0

Нет прав для скачивания
C#:
using System;
using System.Collections.Generic;
using Oxide.Core;
using Oxide.Core.Plugins;
using Oxide.Game.Rust.Cui;
using UnityEngine;

namespace Oxide.Plugins
{
    [Info("PerkMachines", "KillaDome (fixed by Copilot)", "2.0.0")]
    [Description("Perk machines: walk up, press E to buy perks via external currency plugin, plus perk effects & HUD.")]
    public class PerkMachines : RustPlugin
    {
        #region Plugin Reference

        [PluginReference] private Plugin ImageLibrary;

        #endregion

        #region Config/Data

        private class PerkConfig
        {
            // Correct tea shortname (in case you still want to use items later)
            public string TeaShortname = "maxhealthtea.pure";

            // Machine skins per perk
            public Dictionary<string, ulong> MachineSkins = new Dictionary<string, ulong>
            {
                {"Juggernog", 3613126822},
                {"SpeedCola", 3613111201},
                {"QuickRevive", 3613119446},
                {"DoubleTap", 3613106495}
            };

            // Item skin → perk; also used for machine skin reverse lookup
            public Dictionary<ulong, string> SkinToPerk = new Dictionary<ulong, string>
            {
                {3613126822, "Juggernog"},
                {3613111201, "SpeedCola"},
                {3613119446, "QuickRevive"},
                {3613106495, "DoubleTap"}
            };

            // Default price (can be overridden by economy plugin)
            public int DefaultPrice = 100;
            public string DefaultCurrencyLabel = "scrap"; // label only, real charging is external

            // Perk effects
            public float JuggernogBonus = 50f;
            public float JuggernogMaxCap = 200f;
            public float DoubleTapMultiplier = 2f;
            public float QuickReviveRespawnHealth = 100f;

            public bool LockSpawnedMachines = true;
            public bool Debug = false;
            public int MaxPerks = 4;

            public Dictionary<string, string> PerkIconUrls = new Dictionary<string, string>
            {
                {"Juggernog", "https://yourserver.com/icons/jugg.png"},
                {"SpeedCola", "https://yourserver.com/icons/speed.png"},
                {"QuickRevive", "https://yourserver.com/icons/revive.png"},
                {"DoubleTap", "https://yourserver.com/icons/double.png"}
            };
        }

        private PerkConfig cfg;

        private class PlayerPerkData
        {
            public HashSet<string> Active = new HashSet<string>();
            public Dictionary<string, double> ExpireAt = new Dictionary<string, double>();
            public float ExtraHealthGiven = 0f;
        }

        private readonly Dictionary<ulong, PlayerPerkData> playerPerks = new Dictionary<ulong, PlayerPerkData>();

        private const string IL_CATEGORY = "PerkIcons";
        private bool ilReady;

        #endregion

        #region Lifecycle

        protected override void LoadDefaultConfig()
        {
            cfg = new PerkConfig();
            SaveConfig();
        }

        protected override void LoadConfig()
        {
            base.LoadConfig();
            try
            {
                cfg = Config.ReadObject<PerkConfig>() ?? new PerkConfig();
            }
            catch
            {
                cfg = new PerkConfig();
            }

            SaveConfig();
        }

        protected override void SaveConfig() => Config.WriteObject(cfg, true);

        private void OnServerInitialized()
        {
            timer.Once(2f, LoadImages);
            timer.Every(1f, CheckExpired);
            Puts("PerkMachines v2.0.0 loaded");
        }

        private void Unload()
        {
            foreach (var player in BasePlayer.activePlayerList)
                CuiHelper.DestroyUi(player, "perk_ui_container");

            playerPerks.Clear();
        }

        private void LoadImages()
        {
            if (ImageLibrary == null)
            {
                PrintWarning("ImageLibrary not found — perk icons will not load.");
                return;
            }

            var urls = new List<string>();
            foreach (var kv in cfg.PerkIconUrls)
                urls.Add(kv.Value);

            try
            {
                ImageLibrary.Call("AddImageList", IL_CATEGORY, urls, (ulong)0);
                ilReady = true;
                Puts($"[PerkMachines] Registered {urls.Count} icon URLs with ImageLibrary.");
            }
            catch (Exception ex)
            {
                PrintWarning($"ImageLibrary.AddImageList failed: {ex.Message}");
                ilReady = false;
            }
        }

        #endregion

        #region Utilities

        private PlayerPerkData GetPerkData(ulong uid)
        {
            if (!playerPerks.TryGetValue(uid, out var d))
            {
                d = new PlayerPerkData();
                playerPerks[uid] = d;
            }

            return d;
        }

        private void DebugMsg(string msg)
        {
            if (cfg.Debug)
                Puts("[PerkMachines] " + msg);
        }

        private BasePlayer FindPlayerByName(string name)
        {
            foreach (var p in BasePlayer.activePlayerList)
            {
                if (p.displayName != null &&
                    p.displayName.IndexOf(name, StringComparison.OrdinalIgnoreCase) >= 0)
                    return p;
            }

            return null;
        }

        #endregion

        #region Perk granting & expiration

        private void GrantPerk(BasePlayer player, string perk)
        {
            if (player == null || string.IsNullOrEmpty(perk))
                return;

            var d = GetPerkData(player.userID);
            if (d.Active.Contains(perk))
            {
                player.ChatMessage($"Perk {perk} already active");
                return;
            }

            var can = Interface.CallHook("CanApplyPerk", player, perk);
            if (can is bool b && !b)
            {
                DebugMsg($"CanApplyPerk blocked for {player.userID} {perk}");
                return;
            }

            float duration = 300f;
            if (perk == "QuickRevive")
                duration = 0f;

            d.Active.Add(perk);
            if (duration > 0f)
                d.ExpireAt[perk] = Time.realtimeSinceStartup + duration;

            ApplyPerkEffect(player, perk, true);
            player.ChatMessage($"Perk activated: {perk}");
            Interface.CallHook("OnPerkGranted", player, perk);
            RefreshUI(player);
            DebugMsg($"Granted {perk} to {player.userID}");
        }

        private void RevokePerk(BasePlayer player, string perk)
        {
            if (player == null || string.IsNullOrEmpty(perk))
                return;

            var d = GetPerkData(player.userID);
            if (!d.Active.Contains(perk))
                return;

            d.Active.Remove(perk);
            d.ExpireAt.Remove(perk);

            ApplyPerkEffect(player, perk, false);
            Interface.CallHook("OnPerkExpired", player, perk);
            player.ChatMessage($"Perk expired: {perk}");
            RefreshUI(player);
            DebugMsg($"Revoked {perk} from {player.userID}");
        }

        private void CheckExpired()
        {
            var now = Time.realtimeSinceStartup;
            var toRevoke = new List<(ulong uid, string perk)>();

            foreach (var kv in playerPerks)
            {
                foreach (var ex in kv.Value.ExpireAt)
                {
                    if (ex.Value <= now)
                        toRevoke.Add((kv.Key, ex.Key));
                }
            }

            foreach (var t in toRevoke)
            {
                var p = BasePlayer.FindByID(t.uid);
                if (p != null)
                {
                    RevokePerk(p, t.perk);
                }
                else if (playerPerks.TryGetValue(t.uid, out var d))
                {
                    d.Active.Remove(t.perk);
                    d.ExpireAt.Remove(t.perk);
                }
            }
        }

        #endregion

        #region Perk effects

        private void ApplyPerkEffect(BasePlayer player, string perk, bool apply)
        {
            if (player == null)
                return;

            var d = GetPerkData(player.userID);

            switch (perk)
            {
                case "Juggernog":
                    if (apply)
                    {
                        float give = cfg.JuggernogBonus;
                        float currentMax = player.health + d.ExtraHealthGiven;

                        if (currentMax + give > cfg.JuggernogMaxCap)
                            give = Math.Max(0f, cfg.JuggernogMaxCap - currentMax);

                        d.ExtraHealthGiven += give;
                        player.health = Mathf.Min(player.health + give, cfg.JuggernogMaxCap);
                        player.ChatMessage($"Juggernog active (+{give} HP)");
                    }
                    else
                    {
                        player.health = Mathf.Max(1f, player.health - d.ExtraHealthGiven);
                        d.ExtraHealthGiven = 0f;
                        player.ChatMessage("Juggernog expired");
                    }
                    break;

                case "SpeedCola":
                    if (apply)
                        player.ChatMessage("Speed Cola active: faster reload (visual only)");
                    else
                        player.ChatMessage("Speed Cola expired");
                    break;

                case "DoubleTap":
                    if (apply)
                        player.ChatMessage("Double Tap active: increased damage");
                    else
                        player.ChatMessage("Double Tap expired");
                    break;

                case "QuickRevive":
                    if (apply)
                    {
                        TryInstantRevive(player);
                        player.ChatMessage("Quick Revive applied");
                    }
                    else
                    {
                        player.ChatMessage("Quick Revive expired");
                    }
                    break;
            }
        }

        private void TryInstantRevive(BasePlayer player)
        {
            try
            {
                if (player == null)
                    return;

                if (player.health <= 0f || player.IsWounded())
                {
                    player.health = cfg.QuickReviveRespawnHealth;
                    if (player.metabolism != null)
                        player.metabolism.bleeding.value = 0f;

                    player.StopSpectating();
                    player.SendNetworkUpdate();
                }
            }
            catch (Exception ex)
            {
                DebugMsg("TryInstantRevive error: " + ex.Message);
            }
        }

        #endregion

        #region Combat hooks

        private object OnEntityTakeDamage(BaseCombatEntity entity, HitInfo info)
        {
            if (info?.InitiatorPlayer == null)
                return null;

            var inst = info.InitiatorPlayer;
            var d = GetPerkData(inst.userID);

            if (d.Active.Contains("DoubleTap"))
                info.damageTypes.ScaleAll(cfg.DoubleTapMultiplier);

            return null;
        }

        #endregion

        #region Perk machine spawn (visual only)

        [ChatCommand("spawnperk")]
        private void SpawnPerkCommand(BasePlayer player, string cmd, string[] args)
        {
            if (player == null)
                return;

            if (args == null || args.Length == 0)
            {
                player.ChatMessage("Usage: /spawnperk <jug|speed|revive|double>");
                return;
            }

            string key = args[0].ToLower();
            string perkName = key switch
            {
                "jug" => "Juggernog",
                "speed" => "SpeedCola",
                "revive" => "QuickRevive",
                "double" => "DoubleTap",
                _ => null
            };

            if (perkName == null)
            {
                player.ChatMessage("Invalid perk. Use: jug/speed/revive/double");
                return;
            }

            var pos = player.transform.position + player.transform.forward * 2f + Vector3.up * 0.1f;
            var rot = Quaternion.LookRotation(-player.transform.forward, Vector3.up);

            SpawnPerkMachine(pos, rot, perkName);
        }

        private void SpawnPerkMachine(Vector3 pos, Quaternion rot, string perkName)
        {
            DebugMsg($"SpawnPerkMachine called for '{perkName}' at {pos}");

            if (!cfg.MachineSkins.ContainsKey(perkName))
            {
                DebugMsg($"SpawnPerkMachine: missing skin for {perkName}");
                return;
            }

            const string prefab = "assets/prefabs/deployable/vendingmachine/vendingmachine.deployed.prefab";

            var entity = GameManager.server.CreateEntity(prefab, pos, rot, true);
            if (entity == null)
            {
                PrintError($"SpawnPerkMachine: CreateEntity returned null for prefab '{prefab}'");
                return;
            }

            var vm = entity as VendingMachine;
            if (vm == null)
            {
                PrintError($"SpawnPerkMachine: entity is not a VendingMachine (type = {entity.GetType().FullName})");
                entity.Kill();
                return;
            }

            vm.Spawn();

            vm.skinID = cfg.MachineSkins[perkName];
            vm.shopName = $"{perkName} Machine";

            // Clear any sell orders and inventory: we don't use vending UI
            if (vm.sellOrders != null && vm.sellOrders.sellOrders != null)
                vm.sellOrders.sellOrders.Clear();
            vm.inventory?.Clear();

            if (cfg.LockSpawnedMachines)
            {
                try
                {
                    vm.SetFlag(BaseEntity.Flags.Locked, true);
                }
                catch (Exception ex)
                {
                    PrintWarning($"Failed to lock vending machine: {ex.Message}");
                }
            }

            vm.SendNetworkUpdateImmediate();

            Interface.CallHook("OnPerkVendingSpawned", vm, perkName);
            DebugMsg($"Spawned {perkName} machine (entID={vm.net?.ID}) at {vm.transform.position}");
        }

        #endregion

        #region Interaction: press E to buy

        // When a player tries to loot (press E) a vending machine
        private object CanLootEntity(BasePlayer player, BaseEntity entity)
        {
            if (player == null || entity == null)
                return null;

            var vm = entity as VendingMachine;
            if (vm == null)
                return null;

            // We only handle machines whose skinID maps to a perk
            if (!cfg.SkinToPerk.TryGetValue(vm.skinID, out var perkName))
                return null; // not a perk machine, allow default behavior

            // This is our perk machine: handle purchase directly, block UI
            TryBuyPerkFromMachine(player, vm, perkName);
            return false; // block the normal vending UI
        }

        private void TryBuyPerkFromMachine(BasePlayer player, VendingMachine vm, string perkName)
        {
            if (player == null || vm == null || string.IsNullOrEmpty(perkName))
                return;

            // Optional external pre-check: CanBuyPerk(buyer, perk, vm, null)
            var can = Interface.CallHook("CanBuyPerk", player, perkName, vm, null);
            if (can is bool b && !b)
            {
                DebugMsg($"External CanBuyPerk blocked {perkName} for {player.displayName}");
                return;
            }

            int price = cfg.DefaultPrice;
            string currencyLabel = cfg.DefaultCurrencyLabel;

            // External plugin can override price/label: OnPerkPurchaseCost(player, perk, out cost, out label)
            try
            {
                var result = Interface.CallHook("OnPerkPurchaseCost", player, perkName, price, currencyLabel);
                if (result is object[] arr && arr.Length >= 2)
                {
                    if (arr[0] is int p) price = p;
                    if (arr[1] is string lab) currencyLabel = lab;
                }
            }
            catch (Exception ex)
            {
                DebugMsg($"OnPerkPurchaseCost hook error: {ex.Message}");
            }

            // External plugin actually charges currency: OnPerkPurchaseCharge(player, perk, price, currencyLabel)
            bool charged = true;
            try
            {
                var res = Interface.CallHook("OnPerkPurchaseCharge", player, perkName, price, currencyLabel);
                if (res is bool cb)
                    charged = cb;
            }
            catch (Exception ex)
            {
                DebugMsg($"OnPerkPurchaseCharge hook error: {ex.Message}");
                charged = false;
            }

            if (!charged)
            {
                player.ChatMessage($"You cannot afford {perkName} ({price} {currencyLabel}).");
                return;
            }

            // If charge succeeded, grant perk
            GrantPerk(player, perkName);
            Interface.CallHook("OnPerkPurchased", player, perkName, vm);
        }

        #endregion

        #region HUD / ImageLibrary UI

        private void RefreshUI(BasePlayer player)
        {
            if (player == null)
                return;

            CuiHelper.DestroyUi(player, "perk_ui_container");

            var d = GetPerkData(player.userID);
            if (d.Active.Count == 0)
                return;

            var container = new CuiElementContainer();

            var mainPanel = new CuiPanel
            {
                Image = { Color = "0 0 0 0" },
                RectTransform = { AnchorMin = "0.015 0.85", AnchorMax = "0.25 0.97" },
                CursorEnabled = false
            };

            container.Add(mainPanel, "Hud", "perk_ui_container");

            float y = 0.8f;
            foreach (var perk in d.Active)
            {
                cfg.PerkIconUrls.TryGetValue(perk, out var url);
                string rawImage = GetImageCached(url) ?? string.Empty;

                var iconElement = new CuiElement
                {
                    Parent = "perk_ui_container",
                    Components =
                    {
                        new CuiRawImageComponent { Png = rawImage, Color = "1 1 1 1" },
                        new CuiRectTransformComponent
                        {
                            AnchorMin = $"0 {y}",
                            AnchorMax = $"0.15 {y + 0.15f}"
                        }
                    }
                };
                container.Add(iconElement);

                double rem = d.ExpireAt.ContainsKey(perk)
                    ? d.ExpireAt[perk] - Time.realtimeSinceStartup
                    : -1;

                string text = rem > 0 ? $"{perk}: {Math.Round(rem)}s" : perk;

                var label = new CuiLabel
                {
                    RectTransform =
                    {
                        AnchorMin = $"0.16 {y}",
                        AnchorMax = $"1 {y + 0.15f}"
                    },
                    Text =
                    {
                        Text = text,
                        FontSize = 14,
                        Color = "1 1 1 1",
                        Align = TextAnchor.MiddleLeft
                    }
                };

                container.Add(label, "perk_ui_container");

                y -= 0.18f;
            }

            CuiHelper.AddUi(player, container);
        }

        private string GetImageCached(string url)
        {
            if (ImageLibrary == null || !ilReady || string.IsNullOrEmpty(url))
                return null;

            try
            {
                return ImageLibrary.Call<string>("GetImage", IL_CATEGORY, url);
            }
            catch
            {
                return null;
            }
        }

        private void RefreshAllUI()
        {
            foreach (var p in BasePlayer.activePlayerList)
                RefreshUI(p);
        }

        #endregion

        #region Commands: status / grant

        [ChatCommand("perks")]
        private void PerksCommand(BasePlayer player, string cmd, string[] args)
        {
            if (player == null)
                return;

            if (args == null || args.Length == 0)
            {
                player.ChatMessage("PerkMachines: /perks status | /perks list | /perks grant <perk> <player>");
                return;
            }

            var sub = args[0].ToLower();

            if (sub == "status")
            {
                var d = GetPerkData(player.userID);
                if (d.Active.Count == 0)
                {
                    player.ChatMessage("No active perks");
                    return;
                }

                foreach (var pk in d.Active)
                {
                    double rem = d.ExpireAt.ContainsKey(pk)
                        ? d.ExpireAt[pk] - Time.realtimeSinceStartup
                        : -1;

                    if (rem > 0)
                        player.ChatMessage($"{pk} - {Math.Round(rem)}s remaining");
                    else
                        player.ChatMessage($"{pk} - permanent/one-shot");
                }

                return;
            }

            if (sub == "list")
            {
                var listed = new HashSet<string>();
                foreach (var s in cfg.SkinToPerk.Values)
                {
                    if (listed.Add(s))
                        player.ChatMessage(s);
                }

                return;
            }

            if (sub == "grant" && args.Length >= 3)
            {
                if (!player.IsAdmin)
                {
                    player.ChatMessage("Admin only");
                    return;
                }

                var perk = args[1];
                var target = FindPlayerByName(args[2]);

                if (target == null)
                {
                    player.ChatMessage("Player not found");
                    return;
                }

                GrantPerk(target, perk);
                player.ChatMessage($"Granted {perk} to {target.displayName}");
                return;
            }

            player.ChatMessage("Unknown subcommand");
        }

        #endregion
    }
}

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