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

FREE RSVote : 1.3.1 1.3.1

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

namespace Oxide.Plugins
{
    [Info("RustServer.gg Vote", "TimRS", "1.3.1")]
    [Description("Voting reward plugin for RustServers.gg")]
    public class RSVote : RustPlugin
    {
        #region Vars

        private const string ApiUrl = "https://rustservers.gg/vote-api.php";
        private const string ServerPageUrl = "https://rustservers.gg/server/";
        private const string NoteShortname = "note";
        private const float RequestTimeout = 10f;

        private const string ActionStatus = "status";
        private const string ActionClaim = "claim";

        // Response codes returned by the vote api.
        private const int CodeNoVote = 0;
        private const int CodeRewardPending = 1;
        private const int CodeAlreadyClaimed = 2;

        // Cooldown expiry per player, pruned periodically.
        private readonly Dictionary<ulong, DateTime> cooldown = new Dictionary<ulong, DateTime>();

        // Players with a request in flight, so a double command cannot claim twice.
        private readonly HashSet<ulong> busy = new HashSet<ulong>();

        #endregion

        #region Oxide Hooks

        private void Init()
        {
            foreach (var command in config.commands)
            {
                if (string.IsNullOrEmpty(command) == true)
                {
                    continue;
                }

                cmd.AddChatCommand(command, this, cmdChat);
            }

            // Drop expired cooldowns only, so nobody gets an early reset.
            timer.Every(300f, PruneCooldowns);

            ValidateConfig();
            Puts($"Loaded server with id '{config.serverId}'");
        }

        private void Unload()
        {
            cooldown.Clear();
            busy.Clear();
        }

        protected override void LoadDefaultMessages()
        {
            lang.RegisterMessages(new Dictionary<string, string>
            {
                ["Cooldown"] = "Please wait {0} more second(s) before trying again.",
                ["Checking"] = "Trying to claim reward...",
                ["NoVote"] = "Vote at the servers page to claim reward!\nCheck the note in your inventory for the link.",
                ["NoVoteNoNote"] = "Vote at the servers page to claim reward!\n{0}",
                ["NoteAlert"] = "A note with the vote link was placed in your inventory!",
                ["NoteName"] = "Vote Link",
                ["NoteText"] = "Vote here to claim your reward:\n\n{0}\n\nSelect the link and copy it with CTRL + C.",
                ["AlreadyClaimed"] = "You already claimed your reward(s)",
                ["RewardGiven"] = "You received {0} for voting!",
                ["RewardFailed"] = "Failed to claim reward, please contact the server owner!",
                ["RequestFailed"] = "The vote server could not be reached, please try again later.",
                ["NotConfigured"] = "Voting is not configured on this server, please contact the server owner!"
            }, this);
        }

        #endregion

        #region Commands

        private void cmdChat(BasePlayer player, string command, string[] args)
        {
            var userId = player.userID;

            if (IsConfigured() == false)
            {
                Message(player, "NotConfigured");
                return;
            }

            DateTime expiry;
            if (cooldown.TryGetValue(userId, out expiry) == true && expiry > DateTime.UtcNow)
            {
                var remaining = Math.Max(1, (int)Math.Ceiling((expiry - DateTime.UtcNow).TotalSeconds));
                Message(player, "Cooldown", remaining);
                return;
            }

            // A request is already running for this player, ignore the spam.
            if (busy.Add(userId) == false)
            {
                return;
            }

            cooldown[userId] = DateTime.UtcNow.AddSeconds(config.cooldownSeconds);

            Message(player, "Checking");
            RequestStatus(player.UserIDString, userId);
        }

        #endregion

        #region Core

        private void RequestStatus(string steamId, ulong userId)
        {
            Execute(BuildUrl(ActionStatus, steamId), ActionStatus, userId, (code, response) =>
            {
                switch (code)
                {
                    case CodeNoVote:
                    {
                        var player = BasePlayer.FindByID(userId);
                        if (GiveVoteNote(player) == true)
                        {
                            // The url is passed even though the default text does not use it,
                            // older lang files still have a {0} placeholder in this message.
                            Message(player, "NoVote", VotePageUrl());
                            ShowAlert(player, "NoteAlert");
                        }
                        else
                        {
                            Message(player, "NoVoteNoNote", VotePageUrl());
                        }

                        break;
                    }

                    case CodeRewardPending:
                        ClaimReward(steamId, userId);
                        return;

                    case CodeAlreadyClaimed:
                        MessageById(userId, "AlreadyClaimed");
                        break;

                    default:
                        PrintWarning($"Unexpected status code '{code}' for '{steamId}'");
                        MessageById(userId, "RequestFailed");
                        break;
                }

                busy.Remove(userId);
            });
        }

        private void ClaimReward(string steamId, ulong userId)
        {
            // Pick and validate the reward up front: consuming the vote server side
            // only to then fail handing something out would cost the player their vote.
            var reward = SelectReward();
            if (reward == null)
            {
                PrintWarning("No usable rewards are configured, check 'Reward items' and 'Reward Commands'");
                MessageById(userId, "RewardFailed");
                busy.Remove(userId);
                return;
            }

            Execute(BuildUrl(ActionClaim, steamId), ActionClaim, userId, (code, response) =>
            {
                busy.Remove(userId);

                // Only hand out the reward once the vote is actually consumed,
                // otherwise the player could keep re-running the command.
                if (code != CodeRewardPending)
                {
                    PrintWarning($"Claim for '{steamId}' returned '{code}', no reward was given");
                    MessageById(userId, "RequestFailed");
                    return;
                }

                GiveReward(userId, reward);
            });
        }

        private void GiveReward(ulong userId, IReward reward)
        {
            var player = BasePlayer.FindByID(userId);
            if (player == null)
            {
                PrintWarning($"Player '{userId}' left before receiving {reward.Info()}, the vote was already claimed");
                return;
            }

            if (reward.GiveTo(player) == false)
            {
                PrintWarning($"Failed to give reward to '{userId}' ({reward.Info()})");
                Message(player, "RewardFailed");
                return;
            }

            Puts($"Player {userId} received reward for voting ({reward.Info()})");
            Message(player, "RewardGiven", reward.Info());
        }

        private IReward SelectReward()
        {
            var items = UsableRewards(config.rewardItems);
            var commands = UsableRewards(config.rewardCommands);

            if (items.Count == 0 && commands.Count == 0)
            {
                return null;
            }

            if (items.Count == 0)
            {
                return Pick(commands);
            }

            if (commands.Count == 0)
            {
                return Pick(items);
            }

            return Core.Random.Range(0, 2) == 0
                ? Pick(items)
                : Pick(commands);
        }

        private static IReward Pick(List<IReward> rewards)
        {
            return rewards[Core.Random.Range(0, rewards.Count)];
        }

        private static List<IReward> UsableRewards<T>(T[] rewards) where T : IReward
        {
            var usable = new List<IReward>();
            if (rewards == null)
            {
                return usable;
            }

            foreach (var reward in rewards)
            {
                if (reward != null && reward.IsUsable() == true)
                {
                    usable.Add(reward);
                }
            }

            return usable;
        }

        private void Execute(string url, string action, ulong userId, Action<int, string> callback)
        {
            webrequest.Enqueue(url, null, (httpCode, response) =>
            {
                if (httpCode != 200)
                {
                    PrintWarning($"Vote api returned http {httpCode} for action '{action}'");
                    MessageById(userId, "RequestFailed");
                    busy.Remove(userId);
                    return;
                }

                var code = 0;
                if (int.TryParse((response ?? string.Empty).Trim(), out code) == false)
                {
                    PrintWarning($"Failed to parse response from '{response}' for action '{action}'");
                    MessageById(userId, "RequestFailed");
                    busy.Remove(userId);
                    return;
                }

                callback(code, response);
            }, this, Core.Libraries.RequestMethod.GET, null, RequestTimeout);
        }

        // Puts the vote link in a note so the player can select and copy it,
        // chat text cannot be copied out of the game.
        private bool GiveVoteNote(BasePlayer player)
        {
            if (config.giveVoteNote == false || player == null || player.IsConnected == false)
            {
                return false;
            }

            var text = string.Format(lang.GetMessage("NoteText", this, player.UserIDString), VotePageUrl());

            // Do not fill the inventory up with duplicates on every check.
            if (HasVoteNote(player, text) == true)
            {
                return true;
            }

            var note = ItemManager.CreateByName(NoteShortname, 1);
            if (note == null)
            {
                PrintWarning($"Could not create a '{NoteShortname}' item, falling back to a chat message");
                return false;
            }

            note.name = lang.GetMessage("NoteName", this, player.UserIDString);
            note.text = text;
            note.MarkDirty();

            player.GiveItem(note, BaseEntity.GiveItemReason.PickedUp);
            return true;
        }

        private static bool HasVoteNote(BasePlayer player, string text)
        {
            var inventory = player.inventory;
            if (inventory == null)
            {
                return false;
            }

            return ContainerHasNote(inventory.containerMain, text)
                   || ContainerHasNote(inventory.containerBelt, text)
                   || ContainerHasNote(inventory.containerWear, text);
        }

        private static bool ContainerHasNote(ItemContainer container, string text)
        {
            if (container?.itemList == null)
            {
                return false;
            }

            foreach (var item in container.itemList)
            {
                if (item?.info == null || item.info.shortname != NoteShortname)
                {
                    continue;
                }

                if (item.text == text)
                {
                    return true;
                }
            }

            return false;
        }

        private string VotePageUrl()
        {
            return ServerPageUrl + config.serverId;
        }

        private string BuildUrl(string action, string steamId)
        {
            return $"{ApiUrl}?action={action}" +
                   $"&key={Uri.EscapeDataString(config.apiKey ?? string.Empty)}" +
                   $"&server={Uri.EscapeDataString(config.serverId ?? string.Empty)}" +
                   $"&steamid={Uri.EscapeDataString(steamId)}";
        }

        private void PruneCooldowns()
        {
            var now = DateTime.UtcNow;
            var expired = new List<ulong>();

            foreach (var entry in cooldown)
            {
                if (entry.Value <= now)
                {
                    expired.Add(entry.Key);
                }
            }

            foreach (var userId in expired)
            {
                cooldown.Remove(userId);
            }
        }

        private bool IsConfigured()
        {
            return string.IsNullOrEmpty(config.apiKey) == false
                   && string.IsNullOrEmpty(config.serverId) == false;
        }

        private void ValidateConfig()
        {
            if (IsConfigured() == false)
            {
                PrintError("'API Key' and/or 'Server Id' are not set, voting is disabled until you fill them in!");
            }

            if (UsableRewards(config.rewardItems).Count == 0 && UsableRewards(config.rewardCommands).Count == 0)
            {
                PrintError("No usable rewards configured! Check 'Reward items' and 'Reward Commands'.");
            }

            foreach (var item in config.rewardItems ?? new RewardItem[0])
            {
                if (item != null && item.IsUsable() == false)
                {
                    PrintWarning($"Reward item '{item.shortname}' is invalid and will be skipped");
                }
            }
        }

        private void Message(BasePlayer player, string key, params object[] args)
        {
            if (player == null || player.IsConnected == false)
            {
                return;
            }

            player.ChatMessage(GetText(player, key, args));
        }

        // Center screen game tip, harder to miss than a chat line.
        private void ShowAlert(BasePlayer player, string key, params object[] args)
        {
            if (config.showAlert == false || player == null || player.IsConnected == false)
            {
                return;
            }

            // The console command takes the text as a single argument,
            // quotes and newlines would break it apart.
            var text = GetText(player, key, args)
                .Replace("\"", "'")
                .Replace("\n", " ");

            player.SendConsoleCommand("gametip.showgametip", text);

            timer.Once(config.alertSeconds, () =>
            {
                if (player != null && player.IsConnected == true)
                {
                    player.SendConsoleCommand("gametip.hidegametip");
                }
            });
        }

        private string GetText(BasePlayer player, string key, params object[] args)
        {
            var message = lang.GetMessage(key, this, player.UserIDString);

            if (args.Length == 0)
            {
                return message;
            }

            try
            {
                return string.Format(message, args);
            }
            catch (FormatException)
            {
                // A hand edited lang file can contain stray braces, show it as is
                // instead of dropping the message entirely.
                PrintWarning($"Message '{key}' has an invalid placeholder, check your lang file");
                return message;
            }
        }

        private void MessageById(ulong userId, string key, params object[] args)
        {
            Message(BasePlayer.FindByID(userId), key, args);
        }

        #endregion

        #region Config

        private class ConfigData
        {
            [JsonProperty("API Key")]
            public string apiKey = string.Empty;

            [JsonProperty("Server Id")]
            public string serverId = string.Empty;

            [JsonProperty("Cooldown (seconds)")]
            public int cooldownSeconds = 5;

            [JsonProperty("Give vote link as note")]
            public bool giveVoteNote = true;

            [JsonProperty("Show on screen alert")]
            public bool showAlert = true;

            [JsonProperty("Alert duration (seconds)")]
            public float alertSeconds = 8f;

            [JsonProperty("Chat Commands")]
            public string[] commands =
            {
                "claim",
                "vote",
                "votes",
            };

            [JsonProperty("Reward items")]
            public RewardItem[] rewardItems = new[]
            {
                new RewardItem
                {
                    shortname = "wood",
                    description = "1000-2000 Wood",
                    amountMin = 1000,
                    amountMax = 2000,
                },
                new RewardItem
                {
                    shortname = "stones",
                    description = "1000-2000 Stones",
                    amountMin = 1000,
                    amountMax = 2000,
                },
                new RewardItem
                {
                    shortname = "metal.refined",
                    description = "1000-2000 High Quality Metal",
                    amountMin = 1000,
                    amountMax = 2000,
                },
            };

            [JsonProperty("Reward Commands Help")]
            public string commandHelp = "use {steamid} to add user steam id\n" +
                                        "Examples:\n" +
                                        "sr add {steamid} 10 - to add ServerRewards points\n" +
                                        "deposit {steamid} 10 - to add Economics points\n";

            // Left empty on purpose: placeholder entries would run bogus console
            // commands and report them as successful rewards.
            [JsonProperty("Reward Commands")]
            public RewardCommand[] rewardCommands = new RewardCommand[0];
        }

        private ConfigData config = new ConfigData();

        protected override void LoadConfig()
        {
            base.LoadConfig();

            try
            {
                config = Config.ReadObject<ConfigData>();
                if (config == null)
                {
                    LoadDefaultConfig();
                }
            }
            catch (Exception ex)
            {
                PrintError($"Configuration file is corrupt! Check your config file at https://jsonlint.com/ ({ex.Message})");
                PrintError("Loading default configuration, your config file was left untouched.");
                LoadDefaultConfig();
                return;
            }

            if (config.cooldownSeconds < 1)
            {
                config.cooldownSeconds = 1;
            }

            if (config.alertSeconds < 1f)
            {
                config.alertSeconds = 1f;
            }

            SaveConfig();
        }

        protected override void LoadDefaultConfig()
        {
            config = new ConfigData();
        }

        protected override void SaveConfig()
        {
            Config.WriteObject(config);
        }

        #endregion

        #region Rewards

        private interface IReward
        {
            bool GiveTo(BasePlayer player);

            bool IsUsable();

            string Info();
        }

        private class RewardItem : IReward
        {
            [JsonProperty("Item Shortname")] public string shortname;

            [JsonProperty("Description")] public string description;

            [JsonProperty("Item Amount Min")] public int amountMin;

            [JsonProperty("Item Amount Max")] public int amountMax;

            [JsonProperty("Item Skin id")] public ulong skinId;

            public Item GetItem()
            {
                var def = ItemManager.FindItemDefinition(shortname);
                if (def == null)
                {
                    return null;
                }

                var min = Math.Max(1, Math.Min(amountMin, amountMax));
                var max = Math.Max(1, Math.Max(amountMin, amountMax));

                // Core.Random.Range is max exclusive, +1 keeps amountMax reachable.
                var item = ItemManager.Create(def, Core.Random.Range(min, max + 1));
                if (item == null)
                {
                    return null;
                }

                item.skin = skinId;
                return item;
            }

            public bool GiveTo(BasePlayer player)
            {
                var item = GetItem();
                if (item == null)
                {
                    return false;
                }

                player.GiveItem(item, BaseEntity.GiveItemReason.PickedUp);
                return true;
            }

            public bool IsUsable()
            {
                return string.IsNullOrEmpty(shortname) == false
                       && ItemManager.FindItemDefinition(shortname) != null;
            }

            public string Info()
            {
                return string.IsNullOrEmpty(description) == false
                    ? $"Item: {description}"
                    : $"Item: {amountMin}-{amountMax}x {shortname}";
            }
        }

        private class RewardCommand : IReward
        {
            [JsonProperty("Command")] public string command = string.Empty;

            [JsonProperty("Description")] public string description = string.Empty;

            [JsonProperty("Type")] public string type = string.Empty;

            public bool GiveTo(BasePlayer player)
            {
                if (IsUsable() == false)
                {
                    return false;
                }

                var line = command;
                line = ReplaceToken(line, "{steamid}", player.UserIDString);
                line = ReplaceToken(line, "{steam}", player.UserIDString);
                line = ReplaceToken(line, "{id}", player.UserIDString);
                ConsoleSystem.Run(ConsoleSystem.Option.Server, line);
                return true;
            }

            public bool IsUsable()
            {
                return string.IsNullOrEmpty(command) == false
                       && command.StartsWith("example ", StringComparison.OrdinalIgnoreCase) == false;
            }

            public string Info()
            {
                return string.IsNullOrEmpty(type) == false
                    ? $"{type}: {description}"
                    : description;
            }

            private static string ReplaceToken(string input, string token, string value)
            {
                var index = input.IndexOf(token, StringComparison.OrdinalIgnoreCase);
                while (index >= 0)
                {
                    input = input.Substring(0, index) + value + input.Substring(index + token.Length);
                    index = input.IndexOf(token, index + value.Length, StringComparison.OrdinalIgnoreCase);
                }

                return input;
            }
        }

        #endregion
    }
}

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