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

FREE TechTreeControl : 0.5.0 0.5.0

Нет прав для скачивания
C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Collections.Generic;
using System.Linq;
using Oxide.Core;
using Oxide.Core.Plugins;
using UnityEngine;
using static TechTreeData;

namespace Oxide.Plugins
{
    [Info("Tech Tree Control", "WhiteThunder", "0.5.0")]
    [Description("Allows customizing Tech Tree research requirements.")]
    internal class TechTreeControl : CovalencePlugin
    {
        #region Fields

        [PluginReference]
        private readonly Plugin PopupNotifications;

        private const string PermissionAnyOrderLevel1 = "techtreecontrol.anyorder.level1";
        private const string PermissionAnyOrderLevel2 = "techtreecontrol.anyorder.level2";
        private const string PermissionAnyOrderLevel3 = "techtreecontrol.anyorder.level3";
        private const string PermissionAnyOrderIO = "techtreecontrol.anyorder.io";

        private readonly object True = true;
        private readonly object False = false;

        private Configuration _config;

        #endregion

        #region Hooks

        private void Init()
        {
            permission.RegisterPermission(PermissionAnyOrderLevel1, this);
            permission.RegisterPermission(PermissionAnyOrderLevel2, this);
            permission.RegisterPermission(PermissionAnyOrderLevel3, this);
            permission.RegisterPermission(PermissionAnyOrderIO, this);

            _config.Init(this);

            if (!_config.IsCustomCurrencyEnabledAndValid)
            {
                Unsubscribe(nameof(OnTechTreeNodeUnlock));
            }
        }

        private void OnServerInitialized()
        {
            if (_config.EnablePopupNotifications && PopupNotifications == null)
            {
                LogWarning("PopupNotifications integration is enabled in the config, but the PopupNotifications plugin isn't loaded.");
            }
        }

        private object OnTechTreeNodeUnlock(Workbench workbench, NodeInstance node, BasePlayer player)
        {
            var currencyAmountOverride = _config.GetResearchCostOverride(node.itemDef);
            if (currencyAmountOverride is int currencyAmount)
            {
                var taxRate = ConVar.Server.GetTaxRateForWorkbenchUnlock(workbench.Workbenchlevel);
                if (taxRate > 0)
                {
                    currencyAmount += Mathf.CeilToInt(currencyAmount * (taxRate / 100f));
                }
            }
            else
            {
                currencyAmount = Workbench.ScrapForResearch(node.itemDef, workbench.Workbenchlevel, out var tax);
                currencyAmount += tax;
            }

            var itemid = _config.CustomCurrency.ItemId;
            if (player.inventory.GetAmount(itemid) >= currencyAmount)
            {
                player.inventory.Take(null, itemid, currencyAmount);
                player.blueprints.Unlock(node.itemDef);
                Interface.CallHook("OnTechTreeNodeUnlocked", workbench, node, player);
            }

            return False;
        }

        private object CanUnlockTechTreeNode(BasePlayer player, NodeInstance node, TechTreeData techTree)
        {
            var blueprintRuleset = _config.GetPlayerBlueprintRuleset(this, player.UserIDString);
            if (blueprintRuleset == null)
                return null;

            if (node.itemDef != null && !blueprintRuleset.IsAllowed(node.itemDef))
            {
                var message = GetMessage(player.UserIDString,
                    blueprintRuleset.IsOptional(node.itemDef)
                        ? LangEntry.BlueprintDisallowedOptional
                        : LangEntry.BlueprintDisallowed);

                if (_config.EnablePopupNotifications)
                {
                    PopupNotifications?.Call("CreatePopupNotification", message, player);
                }

                if (_config.EnableChatFeedback)
                {
                    player.ChatMessage(message);
                }

                return False;
            }

            return null;
        }

        private object CanUnlockTechTreeNodePath(BasePlayer player, NodeInstance node, TechTreeData techTree)
        {
            if (HasPermissionToUnlockAny(player, techTree))
                return True;

            var blueprintRuleset = _config.GetPlayerBlueprintRuleset(this, player.UserIDString);
            if (blueprintRuleset == null)
                return null;

            if (node.itemDef != null && !blueprintRuleset.HasPrerequisites(node.itemDef))
                return True;

            if (HasUnlockPath(player, node, techTree, blueprintRuleset))
                return True;

            return null;
        }

        private object OnResearchCostDetermine(ItemDefinition itemDefinition)
        {
            return _config.GetResearchCostOverride(itemDefinition);
        }

        #endregion

        #region Helper Methods

        public static void LogError(string message) => Interface.Oxide.LogError($"[Tech Tree Control] {message}");
        public static void LogWarning(string message) => Interface.Oxide.LogWarning($"[Tech Tree Control] {message}");

        private static bool HasUnlockPath(BasePlayer player, NodeInstance node, TechTreeData techTree, BlueprintRuleset blueprintRuleset)
        {
            if (node.inputs.Count == 0)
                return true;

            var hasUnlockPath = false;

            foreach (var inputNodeId in node.inputs)
            {
                var inputNode = techTree.GetByID(inputNodeId);
                if (inputNode.itemDef == null)
                {
                    // This input node appears to be an entry node, so consider it unlocked.return true;
                }

                if (!techTree.HasPlayerUnlocked(player, inputNode) && !blueprintRuleset.IsOptional(inputNode.itemDef))
                {
                    // This input node does not provide an unlock path.// Continue iterating the other input nodes in case they provide an unlock path.continue;
                }

                if (HasUnlockPath(player, inputNode, techTree, blueprintRuleset))
                {
                    hasUnlockPath = true;
                }
            }

            return hasUnlockPath;
        }

        private bool HasPermissionToUnlockAny(BasePlayer player, TechTreeData techTree)
        {
            if (techTree.name == "TechTreeT3")
                return permission.UserHasPermission(player.UserIDString, PermissionAnyOrderLevel3);

            if (techTree.name == "TechTreeT2")
                return permission.UserHasPermission(player.UserIDString, PermissionAnyOrderLevel2);

            if (techTree.name == "TechTreeT0")
                return permission.UserHasPermission(player.UserIDString, PermissionAnyOrderLevel1);

            if (techTree.name == "TechTreeIO")
                return permission.UserHasPermission(player.UserIDString, PermissionAnyOrderIO);

            return false;
        }

        #endregion

        #region Configuration

        [JsonObject(MemberSerialization.OptIn)]
        private class CustomCurrency
        {
            [JsonProperty("Enabled")]
            public bool Enabled;

            [JsonProperty("Item short name")]
            public string ItemShortName = "scrap";

            [JsonIgnore]
            public int ItemId;

            [JsonIgnore]
            public bool IsEnabledAndValid => Enabled && ItemId != 0;

            public void Init()
            {
                if (!Enabled)
                    return;

                var itemDefinition = ItemManager.FindItemDefinition(ItemShortName);
                if (itemDefinition == null)
                {
                    LogWarning($"Invalid item short name in config: {ItemShortName}");
                }
                else
                {
                    ItemId = itemDefinition.itemid;
                }
            }
        }

        [JsonObject(MemberSerialization.OptIn)]
        private class BlueprintRuleset
        {
            public static readonly BlueprintRuleset DefaultRuleset = new();

            [JsonProperty("Name")]
            private string Name;

            [JsonProperty("Optional blueprints")]
            private string[] OptionalBlueprints = Array.Empty<string>();

            [JsonProperty("OptionalBlueprints")]
            private string[] DeprecatedOptionalBlueprints { set => OptionalBlueprints = value; }

            [JsonProperty("Allowed blueprints", DefaultValueHandling = DefaultValueHandling.Ignore)]
            private string[] AllowedBlueprints;

            [JsonProperty("AllowedBlueprints")]
            private string[] DeprecatedAllowedBlueprints { set => AllowedBlueprints = value; }

            [JsonProperty("Disallowed blueprints", DefaultValueHandling = DefaultValueHandling.Ignore)]
            private string[] DisallowedBlueprints;

            [JsonProperty("DisallowedBlueprints")]
            private string[] DeprecatedDisallowedBlueprints { set => DisallowedBlueprints = value; }

            [JsonProperty("Blueprints with no prerequisites")]
            private string[] BlueprintsWithNoPrerequisites = Array.Empty<string>();

            [JsonProperty("BlueprintsWithNoPrerequisites")]
            private string[] DeprecatedBlueprintsWithNoPrerequisites { set => BlueprintsWithNoPrerequisites = value; }

            public string Permission { get; private set; }
            private HashSet<int> _optionalBlueprints = new();
            private HashSet<int> _allowedBlueprints = new();
            private HashSet<int> _disallowedBlueprints = new();
            private HashSet<int> _blueprintsWithNoPrerequisites = new();

            public void Init(TechTreeControl plugin)
            {
                if (!string.IsNullOrWhiteSpace(Name))
                {
                    Permission = $"{nameof(TechTreeControl)}.ruleset.{Name}".ToLower();
                    plugin.permission.RegisterPermission(Permission, plugin);
                }

                CacheItemIds(OptionalBlueprints, _optionalBlueprints);
                CacheItemIds(AllowedBlueprints, _allowedBlueprints);
                CacheItemIds(DisallowedBlueprints, _disallowedBlueprints);
                CacheItemIds(BlueprintsWithNoPrerequisites, _blueprintsWithNoPrerequisites);
            }

            public bool HasPrerequisites(ItemDefinition itemDefinition)
            {
                return !_blueprintsWithNoPrerequisites.Contains(itemDefinition.itemid);
            }

            public bool IsAllowed(ItemDefinition itemDefinition)
            {
                if (AllowedBlueprints != null)
                    return _allowedBlueprints.Contains(itemDefinition.itemid);

                if (DisallowedBlueprints != null)
                    return !_disallowedBlueprints.Contains(itemDefinition.itemid);

                return true;
            }

            public bool IsOptional(ItemDefinition itemDefinition)
            {
                return _optionalBlueprints.Contains(itemDefinition.itemid);
            }

            private static void CacheItemIds(IEnumerable<string> shortNameList, HashSet<int> cachedItemIds)
            {
                if (shortNameList == null)
                    return;

                foreach (var itemShortName in shortNameList)
                {
                    var itemDefinition = ItemManager.FindItemDefinition(itemShortName);
                    if (itemDefinition == null)
                    {
                        LogError($"Invalid item short name in config: {itemShortName}");
                        continue;
                    }

                    cachedItemIds.Add(itemDefinition.itemid);
                }
            }
        }

        [JsonObject(MemberSerialization.OptIn)]
        private class Configuration : BaseConfiguration
        {
            [JsonProperty("Enable chat feedback")]
            public bool EnableChatFeedback = true;

            [JsonProperty("Enable PopupNotifications integration")]
            public bool EnablePopupNotifications;

            [JsonProperty("Research costs")]
            private Dictionary<string, int> ResearchCosts = new();

            [JsonProperty("Custom currency")]
            public CustomCurrency CustomCurrency = new();

            [JsonProperty("ResearchCosts")]
            private Dictionary<string, int> DeprecatedResearchCosts { set => ResearchCosts = value; }

            [JsonProperty("Blueprint rulesets")]
            private BlueprintRuleset[] BlueprintRulesets = Array.Empty<BlueprintRuleset>();

            [JsonProperty("BlueprintRulesets")]
            private BlueprintRuleset[] DeprecatedBlueprintRulesets { set => BlueprintRulesets = value; }

            private Dictionary<int, object> _researchCostByItemId = new();

            [JsonIgnore]
            public bool IsCustomCurrencyEnabledAndValid => CustomCurrency is { IsEnabledAndValid: true };

            public void Init(TechTreeControl plugin)
            {
                CustomCurrency?.Init();

                if (BlueprintRulesets != null)
                {
                    foreach (var ruleset in BlueprintRulesets)
                    {
                        ruleset.Init(plugin);
                    }
                }

                if (ResearchCosts != null)
                {
                    foreach (var entry in ResearchCosts)
                    {
                        var itemDefinition = ItemManager.FindItemDefinition(entry.Key);
                        if (itemDefinition == null)
                        {
                            LogError($"Invalid item short name in config: {entry.Key}");
                            continue;
                        }

                        _researchCostByItemId[itemDefinition.itemid] = entry.Value;
                    }
                }
            }

            public object GetResearchCostOverride(ItemDefinition itemDefinition)
            {
                return _researchCostByItemId.TryGetValue(itemDefinition.itemid, out var costOverride)
                    ? costOverride
                    : null;
            }

            public BlueprintRuleset GetPlayerBlueprintRuleset(TechTreeControl plugin, string userIdString)
            {
                if (BlueprintRulesets != null)
                {
                    for (var i = BlueprintRulesets.Length - 1; i >= 0; i--)
                    {
                        var ruleset = BlueprintRulesets[i];
                        if (ruleset.Permission != null
                            && plugin.permission.UserHasPermission(userIdString, ruleset.Permission))
                            return ruleset;
                    }
                }

                return BlueprintRuleset.DefaultRuleset;
            }
        }

        private Configuration GetDefaultConfig() => new();

        #region Configuration Helpers

        private class BaseConfiguration
        {
            public string ToJson() => JsonConvert.SerializeObject(this);

            public Dictionary<string, object> ToDictionary() => JsonHelper.Deserialize(ToJson()) as Dictionary<string, object>;
        }

        private static class JsonHelper
        {
            public static object Deserialize(string json) => ToObject(JToken.Parse(json));

            private static object ToObject(JToken token)
            {
                switch (token.Type)
                {
                    case JTokenType.Object:
                        return token.Children<JProperty>().ToDictionary(prop => prop.Name,
                                                  prop => ToObject(prop.Value));

                    case JTokenType.Array:
                        return token.Select(ToObject).ToList();

                    default:
                        return ((JValue)token).Value;
                }
            }
        }

        private bool MaybeUpdateConfig(BaseConfiguration config)
        {
            var currentWithDefaults = config.ToDictionary();
            var currentRaw = Config.ToDictionary(x => x.Key, x => x.Value);
            return MaybeUpdateConfigDict(currentWithDefaults, currentRaw);
        }

        private bool MaybeUpdateConfigDict(Dictionary<string, object> currentWithDefaults, Dictionary<string, object> currentRaw)
        {
            var changed = false;

            foreach (var key in currentWithDefaults.Keys)
            {
                if (currentRaw.TryGetValue(key, out var currentRawValue))
                {
                    var currentDictValue = currentRawValue as Dictionary<string, object>;

                    if (currentWithDefaults[key] is Dictionary<string, object> defaultDictValue)
                    {
                        if (currentDictValue == null)
                        {
                            currentRaw[key] = currentWithDefaults[key];
                            changed = true;
                        }
                        else if (MaybeUpdateConfigDict(defaultDictValue, currentDictValue))
                        {
                            changed = true;
                        }
                    }
                }
                else
                {
                    currentRaw[key] = currentWithDefaults[key];
                    changed = true;
                }
            }

            return changed;
        }

        protected override void LoadDefaultConfig() => _config = GetDefaultConfig();

        protected override void LoadConfig()
        {
            base.LoadConfig();
            try
            {
                _config = Config.ReadObject<Configuration>();
                if (_config == null)
                {
                    throw new JsonException();
                }

                if (MaybeUpdateConfig(_config))
                {
                    LogWarning("Configuration appears to be outdated; updating and saving");
                    SaveConfig();
                }
            }
            catch
            {
                LogWarning($"Configuration file {Name}.json is invalid; using defaults");
                LoadDefaultConfig();
            }
        }

        protected override void SaveConfig()
        {
            Log($"Configuration changes saved to {Name}.json");
            Config.WriteObject(_config, true);
        }

        #endregion

        #endregion

        #region Localization

        private class LangEntry
        {
            public static readonly List<LangEntry> AllLangEntries = new();

            public static readonly LangEntry BlueprintDisallowed = new("BlueprintDisallowed", "You don't have permission to unlock that blueprint.");
            public static readonly LangEntry BlueprintDisallowedOptional = new("BlueprintDisallowed.Optional", "You don't have permission to unlock that blueprint, but it can be skipped.");

            public string Name;
            public string English;

            public LangEntry(string name, string english)
            {
                Name = name;
                English = english;

                AllLangEntries.Add(this);
            }
        }

        private string GetMessage(string playerId, LangEntry langEntry) =>
            lang.GetMessage(langEntry.Name, this, playerId);

        private void ChatMessage(BasePlayer player, LangEntry langEntry) =>
            player.ChatMessage(GetMessage(player.UserIDString, langEntry));

        protected override void LoadDefaultMessages()
        {
            var englishLangKeys = new Dictionary<string, string>();

            foreach (var langEntry in LangEntry.AllLangEntries)
            {
                englishLangKeys[langEntry.Name] = langEntry.English;
            }

            lang.RegisterMessages(englishLangKeys, this, "en");
        }

        #endregion
    }
}

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