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

FREE BenchCraft : 0.0.15 0.0.15

Нет прав для скачивания
C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6

using Newtonsoft.Json;
using Oxide.Core.Plugins;
using Oxide.Game.Rust.Cui;
using System;
using System.Collections.Generic;
using UnityEngine;

namespace Oxide.Plugins
{
	[Info("BenchCraft", "ignignokt84", "0.0.15", ResourceId = 2338)]
	[Description("Involve repair benches in crafting")]
	class BenchCraft : RustPlugin
	{
		#region Variables

		[PluginReference]
		Plugin ZLevelsRemastered;
		bool useZLevels;

		BenchCraftData data = new BenchCraftData();
		// map of item shortname -> craft time
		Dictionary<string, float> timeCache = new Dictionary<string, float>();
		// map of player ID -> GUIFlags
		Dictionary<ulong, int> guiCache = new Dictionary<ulong, int>();
		// map of crafting task ID -> timer
		Dictionary<int, Timer> timers = new Dictionary<int, Timer>();

		const string PermNoPenalty = "benchcraft.nopenalty";
		const string PermNoBoost = "benchcraft.noboost";
		const string PermCraftAnywhere = "benchcraft.craftanywhere";
		const string PermIgnore = "benchcraft.ignore";

		CuiElementContainer GUIContainer;
		CuiElement GUIElement;
		CuiTextComponent GUIText;
		CuiElement LockBackground;
		CuiRectTransformComponent GUIPosition;

		const string GUIName = "BenchCraftOverlay";

		enum GUIFlags
		{
			Boost = 1,
			Penalty = 2,
			Lock = 4
		}

		enum Option
		{
			boost,
			penalty,
			baseRate,
			proximity,
			useRepairBench,
			useResearchTable,
			useItemList,
			useZLevels,
			instantBoost
		}

		object[] defaults = new object[] { 0f, 0f, 1f, 1f, true, false, false, true, false };

		// repeat rate for timers
		const float tickrate = 0.5f;

		bool instacraft = false;

		#endregion

		#region Lang

		// load default messages to Lang
		void LoadDefaultMessages()
		{
			var messages = new Dictionary<string, string>
			{
				{"Prefix", "<color=orange>[ BenchCraft ]</color> "},
				{"RequiresBench", "Crafting {0} requires a workbench"},
				{"WarnPenaltyTooHigh", "{0} penalty must be less than 1.00: penalty will be ignored" },
				{"BoostText", "Boost\n{0:+0%;-0%}" },
				{"PenaltyText", "Penalty\n{0:+0%;-0%}" },
				{"BoostTextInstant", "Boost\n{0:+0;-0}" },
				{"PenaltyTextInstant", "Penalty\n{0:+0;-0}" },
				{"BoostTextInstantInf", "Boost\n∞" },
				{"MissingItem", "Item does not exist in time map: {0}" }
			};
			lang.RegisterMessages(messages, this);
		}

		// get message from Lang
		string GetMessage(string key, string userId = null) => lang.GetMessage(key, this, userId);

		#endregion

		#region Loading/Unloading

		// on load
		void Loaded()
		{
			LoadDefaultMessages();
			permission.RegisterPermission(PermNoPenalty, this);
			permission.RegisterPermission(PermNoBoost, this);
			permission.RegisterPermission(PermCraftAnywhere, this);
			permission.RegisterPermission(PermIgnore, this);
			LoadData();
			CheckPenalties();
			InitGUI();
			useZLevels = ConfigValue<bool>(Option.useZLevels) && ZLevelsRemastered != null;
		}

		// on unload
		void Unload()
		{
			RestoreFromCache();
			DestroyTimers();
			DestroyAllGui();
		}

		// server initialized
		void OnServerInitialized()
		{
			SaveToCache();
			instacraft = ConVar.Craft.instant;
		}

		void OnPluginLoaded(Plugin plugin)
		{
			if (plugin.Name == "ZLevelsRemastered")
			{
				ZLevelsRemastered = plugin;
				useZLevels = ConfigValue<bool>(Option.useZLevels) && ZLevelsRemastered != null;
			}
		}

		void OnPluginUnloaded(Plugin plugin)
		{
			if (plugin.Name == "ZLevelsRemastered")
			{
				ZLevelsRemastered = null;
				useZLevels = false;
			}
		}

		// initialize GUI elements
		void InitGUI()
		{
			GUIContainer = new CuiElementContainer();

			GUIElement = new CuiElement() {
				Name = GUIName,
				FadeOut = data.ui.fadeOut,
				Components = {
					(GUIText = new CuiTextComponent() {
						Text = "",
						FontSize = data.ui.fontSize,
						Color = data.ui.boostTextColor,
						FadeIn = data.ui.fadeIn,
						Align = TextAnchor.MiddleCenter
					}),
					(GUIPosition = new CuiRectTransformComponent() {
						AnchorMin = data.ui.anchorMin,
						AnchorMax = data.ui.anchorMax
					})
				}
			};

			LockBackground = new CuiElement() {
				Name = GUIName,
				FadeOut = data.ui.fadeOut,
				Components = {
					new CuiImageComponent() {
						Sprite = "assets/icons/lock.png",
						Color = data.ui.lockIconColor,
						FadeIn = data.ui.fadeIn
					},
					new CuiRectTransformComponent() {
						AnchorMin = data.ui.lockAnchorMin,
						AnchorMax = data.ui.lockAnchorMax
					}
				}
			};
		}

		#endregion

		#region Configuration

		// load default config
		bool LoadDefaultConfig()
		{
			data.items = new Dictionary<string, CraftSettings>();
			CheckConfig();
			return true;
		}

		// load data
		void LoadData()
		{
			bool dirty = false;
			Config.Settings.NullValueHandling = NullValueHandling.Include;
			try
			{
				data = Config.ReadObject<BenchCraftData>();
			}
			catch (Exception) { }
			dirty = CheckConfig();
			if (data.items == null)
				dirty |= LoadDefaultConfig();
			if (dirty)
				SaveData();
		}

		// write data container to config
		void SaveData()
		{
			Config.WriteObject(data);
		}

		// get value from config (handles type conversion)
		T GetConfig<T>(string group, string name, T value)
		{
			if (Config[group, name] == null)
			{
				Config[group, name] = value;
				SaveConfig();
			}
			return (T)Convert.ChangeType(Config[group, name], typeof(T));
		}

		// validate config
		bool CheckConfig()
		{
			bool dirty = false;
			foreach (Option option in Enum.GetValues(typeof(Option)))
				if (!data.config.ContainsKey(option))
				{
					data.config[option] = defaults[(int)option];
					dirty = true;
				}
			if (data.ui == null)
			{
				data.ui = new UIConfig();
				dirty = true;
			}
			return dirty;
		}

		#endregion

		#region Hooks

		// on item craft task created
		object OnItemCraft(ItemCraftTask task, BasePlayer player, Item item)
		{
			if (hasPermission(player, PermIgnore) || instacraft) return null;
			if (task.blueprint.targetItem.shortname == "door.key") return null; // exclude door keys
			float boost = ConfigValue<float>(Option.boost);
			float penalty = ConfigValue<float>(Option.penalty);
			float baseRate = ConfigValue<float>(Option.baseRate);
			bool requireBench = false;
			if (ConfigValue<bool>(Option.useItemList) && data.items.Count > 0)
			{
				CraftSettings settings;
				if (data.items.TryGetValue(task.blueprint.targetItem.shortname, out settings))
				{
					boost = settings.boost.HasValue ? settings.boost.Value : boost;
					penalty = settings.penalty.HasValue ? settings.penalty.Value : penalty;
					baseRate = settings.baseRate.HasValue ? settings.baseRate.Value : baseRate;
					requireBench = settings.requireBench.HasValue ? settings.requireBench.Value : false;
				}
			}
			requireBench &= !hasPermission(player, PermCraftAnywhere);
			bool inRange = IsPlayerInRange(task.owner);
			if (requireBench && !inRange)
			{
				NextTick(() => {
					SendMessage(player, "RequiresBench", new object[] { task.blueprint.targetItem.displayName.translated });
					player.inventory.crafting.CancelTask(task.taskUID, true);
				});
				return null;
			}
			if (hasPermission(player, PermNoBoost))
				boost = 0f;
			if (hasPermission(player, PermNoPenalty))
				penalty = 0f;

			if (!useZLevels)
				if (timeCache.ContainsKey(task.blueprint.targetItem.shortname))
					// alter standard crafting time
					task.blueprint.time = timeCache[task.blueprint.targetItem.shortname] * baseRate;
				else
					PrintWarning(GetMessage("MissingItem"), task.blueprint.targetItem.shortname);

			// if boost and penalty will not affect crafting, skip timer creation
			if (boost > 0f || penalty > 0f || requireBench)
			{
				int taskId = task.taskUID;
				if (timers.ContainsKey(taskId))
					timers[taskId].Destroy();
				// create timer to check proximity at interval defined by tickrate constant
				timers[taskId] = timer.Every(tickrate, () => RealtimeScale(ref task, taskId, boost, penalty, requireBench));
			}

			return null;
		}

		// update at crafting finished for instacraft
		void OnItemCraftFinished(ItemCraftTask task, Item item)
		{
			if (hasPermission(task.owner, PermIgnore) || !instacraft) return;
			float boost = ConfigValue<float>(Option.boost);
			float penalty = ConfigValue<float>(Option.penalty);
			if (ConfigValue<bool>(Option.useItemList) && data.items.Count > 0)
			{
				CraftSettings settings;
				if (data.items.TryGetValue(task.blueprint.targetItem.shortname, out settings))
				{
					boost = settings.boost.HasValue ? settings.boost.Value : boost;
					penalty = settings.penalty.HasValue ? settings.penalty.Value : penalty;
				}
			}
			if (hasPermission(task.owner, PermNoBoost))
				boost = 0f;
			if (hasPermission(task.owner, PermNoPenalty))
				penalty = 0f;
			int bonus = 0;
			if (IsPlayerInRange(task.owner) && (boost > 0f))
			{
				if (UnityEngine.Random.value < boost)
				{
					bonus = item.amount;
					item.amount *= 2;
				}
			}
			else if (penalty > 0f)
			{
				if (UnityEngine.Random.value < penalty)
				{
					bonus = -item.amount;
					item.amount = 0;
				}
			}
			if (bonus != 0)
				UpdateGUI(task.owner, bonus);
		}

		#endregion

		#region Crafting Timers / Scaling

		// scale remaining time based on proximity to workbench
		void RealtimeScale(ref ItemCraftTask task, int taskId, float boost, float penalty, bool requireBench)
		{
			try
			{
				bool destroy = false;
				// task killed?
				if (task == null) destroy = true;
				// crafting not started
				else if (task.endTime == 0f) return;

				ItemCrafter crafter = task.owner.inventory.crafting;

				// crafting queue empty
				if (crafter.queue.Count == 0)
					destroy = true;

				// task is at the top of the crafting queue
				else if (task == crafter.queue.Peek())
				{
					// task is cancelled
					if (task.cancelled)
						destroy = true;

					// task is processing
					else
					{
						bool inRange = IsPlayerInRange(task.owner);
						if (!inRange && requireBench)
						{
							SendMessage(task.owner, "RequiresBench", new object[] { task.blueprint.targetItem.displayName.translated });
							task.owner.inventory.crafting.CancelTask(task.taskUID, true);
							destroy = true;
						}
						else
						{
							if (ConfigValue<bool>(Option.instantBoost) && boost > 0f && inRange)
							{
								task.endTime = Time.realtimeSinceStartup;
								UpdateGUI(task.owner, 1);
							}
							else
							{
								float factor = inRange ? boost : -penalty;
								ScaleTask(task, factor, requireBench);
								UpdateGUI(task.owner, factor, requireBench);
							}
						}
					}
				}
				if (destroy)
				{
					DestroyGUI(task.owner);
					DestroyTimer(taskId);
					return;
				}
			}
			// just in case, destroy GUI and timer
			catch (Exception)
			{
				DestroyGUI(task.owner);
				DestroyTimer(taskId);
				return;
			}
		}

		// scale the task based on the tickrate and boost/penalty
		void ScaleTask(ItemCraftTask task, float factor, bool requireBench)
		{
			task.endTime -= (tickrate * factor);

			// update player with remaining time (adjusted for current crafting rate)
			float remaining = (task.endTime - Time.realtimeSinceStartup) / (1f + factor);
			task.owner.Command("note.craft_start", new object[] { task.taskUID, remaining, task.amount });
		}

		// destroy a timer if it exists
		void DestroyTimer(int timer)
		{
			if (timers.ContainsKey(timer))
				NextTick(() => { if (timers[timer] == null || timers[timer].Destroyed) return; timers[timer].Destroy(); timers.Remove(timer); });
		}

		#endregion

		#region GUI

		// show GUI
		void ShowGUI(BasePlayer player, int flags)
		{
			CuiHelper.AddUi(player, GUIContainer);
			guiCache[player.userID] = flags;
		}

		// update GUI (instacraft)
		void UpdateGUI(BasePlayer player, int bonus)
		{
			int flags = bonus > 0 ? (int)GUIFlags.Boost : 0;
			flags += bonus < 0 ? (int)GUIFlags.Penalty : 0;

			int cacheFlags = 0;
			guiCache.TryGetValue(player.userID, out cacheFlags);

			// no GUI change
			if (cacheFlags == flags) return;

			// has existing GUI
			if (cacheFlags > 0) DestroyGUI(player);

			// needs GUI built
			if (flags > 0)
			{
				GUIContainer.Clear();
				if (bonus > 0)
				{
					if (ConfigValue<bool>(Option.instantBoost) && !instacraft)
						GUIText.Text = GetMessage("BoostTextInstantInf", player.UserIDString);
					else
						GUIText.Text = String.Format(GetMessage("BoostTextInstant", player.UserIDString), bonus);
					GUIText.Color = data.ui.boostTextColor;
				}
				else if (bonus < 0)
				{
					GUIText.Text = String.Format(GetMessage("PenaltyTextInstant", player.UserIDString), bonus);
					GUIText.Color = data.ui.penaltyTextColor;
				}

				GUIElement.Name = GUIName;
				GUIElement.Parent = "Hud";
				GUIPosition.AnchorMin = data.ui.anchorMin;
				GUIPosition.AnchorMax = data.ui.anchorMax;

				if (bonus != 0)
					GUIContainer.Add(GUIElement);
				ShowGUI(player, flags);
				if (!ConfigValue<bool>(Option.instantBoost))
					timer.In(tickrate, () => DestroyGUI(player));
			}
		}

		// update GUI if needed
		void UpdateGUI(BasePlayer player, float factor, bool requireBench)
		{
			int flags = factor > 0f ? (int)GUIFlags.Boost : 0;
			flags += factor < 0f ? (int)GUIFlags.Penalty : 0;
			flags += requireBench ? (int)GUIFlags.Lock : 0;

			int cacheFlags = 0;
			guiCache.TryGetValue(player.userID, out cacheFlags);

			// no GUI change
			if (cacheFlags == flags) return;

			// has existing GUI
			if (cacheFlags > 0) DestroyGUI(player);

			// needs GUI built
			if (flags > 0)
			{
				GUIContainer.Clear();
				if (factor > 0)
				{
					GUIText.Text = String.Format(GetMessage("BoostText", player.UserIDString), factor);
					GUIText.Color = data.ui.boostTextColor;
				}
				else if (factor < 0)
				{
					GUIText.Text = String.Format(GetMessage("PenaltyText", player.UserIDString), factor);
					GUIText.Color = data.ui.penaltyTextColor;
				}

				if (factor != 0 && requireBench)
				{
					GUIElement.Name = "SonOf" + GUIName;
					GUIElement.Parent = GUIName;
					GUIPosition.AnchorMin = "0 0";
					GUIPosition.AnchorMax = "1 1";
				}
				else
				{
					GUIElement.Name = GUIName;
					GUIElement.Parent = "Hud";
					GUIPosition.AnchorMin = data.ui.anchorMin;
					GUIPosition.AnchorMax = data.ui.anchorMax;
				}

				if (requireBench)
					GUIContainer.Add(LockBackground);
				if (factor != 0)
					GUIContainer.Add(GUIElement);
				ShowGUI(player, flags);
			}
		}

		// destroy GUI
		void DestroyGUI(BasePlayer player)
		{
			CuiHelper.DestroyUi(player, GUIName);
			guiCache.Remove(player.userID);
		}

		// destroy GUIs for all players
		void DestroyAllGui()
		{
			foreach (BasePlayer player in BasePlayer.activePlayerList)
				DestroyGUI(player);
		}

		#endregion

		#region Messaging

		// send reply to a player
		void SendMessage(BasePlayer player, string message, object[] options = null)
		{
			string msg = GetMessage(message, player.UserIDString);
			if (options != null && options.Length > 0)
				msg = String.Format(msg, options);
			SendReply(player, GetMessage("Prefix", player.UserIDString) + msg);
		}

		// log a console warning for invalid penalty
		void WarnPenaltyTooHigh(string id) => PrintWarning(GetMessage("WarnPenaltyTooHigh"), id);

		#endregion

		#region Helper Procedures

		// cache original item names and crafting times
		void SaveToCache()
		{
			foreach (ItemBlueprint bp in ItemManager.bpList)
				timeCache[bp.targetItem.shortname] = bp.time;
		}

		// restore all original crafting times
		void RestoreFromCache()
		{
			foreach (ItemBlueprint bp in ItemManager.bpList)
				bp.time = timeCache[bp.targetItem.shortname];
		}

		// check for penalties that are >= 1.0 and override them to 0.0
		void CheckPenalties()
		{
			if (ConfigValue<float>(Option.penalty) >= 1f)
			{
				data.config[Option.penalty] = 0f;
				WarnPenaltyTooHigh("Global");
			}
			if (ConfigValue<bool>(Option.useItemList) && data.items != null && data.items.Count > 0)
				foreach (KeyValuePair<string, CraftSettings> entry in data.items)
					if (entry.Value.penalty >= 1f)
					{
						entry.Value.penalty = 0f;
						WarnPenaltyTooHigh(entry.Key);
					}
		}

		// refund items on crafting cancellation
		void RefundItems(ItemCraftTask task)
		{
			task.owner.Command("note.craft_done", new object[] { task.taskUID, 0 });
			if (task.takenItems != null && task.takenItems.Count > 0)
			{
				foreach (Item takenItem in task.takenItems)
				{
					if (takenItem != null && takenItem.amount > 0)
					{
						if (takenItem.IsBlueprint() && takenItem.blueprintTargetDef == task.blueprint.targetItem)
							takenItem.UseItem(task.numCrafted);
						if (takenItem.amount <= 0 || takenItem.MoveToContainer(task.owner.inventory.containerMain, -1, true))
							continue;
						takenItem.Drop(task.owner.inventory.containerMain.dropPosition, task.owner.inventory.containerMain.dropVelocity, new Quaternion());
						task.owner.Command("note.inv", new object[] { takenItem.info.itemid, -takenItem.amount });
					}
				}
			}
		}

		// determine if player is in range of a repair bench or research table
		bool IsPlayerInRange(BasePlayer player)
		{
			List<StorageContainer> entities = new List<StorageContainer>();
			Vis.Entities(player.transform.position, ConfigValue<float>(Option.proximity), entities);
			foreach (StorageContainer e in entities)
				if ((ConfigValue<bool>(Option.useRepairBench) && e is RepairBench) || (ConfigValue<bool>(Option.useResearchTable) && e is ResearchTable))
					return true;
			return false;
		}

		// check if player has permission
		private bool hasPermission(BasePlayer player, string permname)
		{
			return permission.UserHasPermission(player.UserIDString, permname);
		}

		// get value of config option and convert type
		T ConfigValue<T>(Option option)
		{
			return (T)Convert.ChangeType(data.config[option], typeof(T));
		}

		// destroy all timers
		void DestroyTimers()
		{
			foreach (Timer timer in timers.Values)
				timer.Destroy();
		}

		#endregion

		#region Subclasses

		class BenchCraftData
		{
			public Dictionary<Option, object> config = new Dictionary<Option, object>();
			public Dictionary<string, CraftSettings> items;
			public UIConfig ui;
		}
		class CraftSettings
		{
			public float? boost;
			public float? penalty;
			public float? baseRate;
			public bool? requireBench;
		}
		class UIConfig
		{
			public float fadeIn = 0.2f;
			public float fadeOut = 0.2f;
			public int fontSize = 10;
			public string boostTextColor = "0 1 0.3 1";
			public string penaltyTextColor = "1 0 0 1";
			public string lockIconColor = "0.9 0 0 0.6";
			public string anchorMin = "0.9375 0.144";
			public string anchorMax = "0.9775 0.20";
			public string lockAnchorMin = "0.9425 0.144";
			public string lockAnchorMax = "0.9725 0.20";
		}

		#endregion
	}
}

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