C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Newtonsoft.Json;
using Oxide.Game.Rust.Cui;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("Active Sort", "Mevent", "1.0.7")]
[Description("Sorts furnace and refinery on click")]
internal class ActiveSort : RustPlugin
{
#region Fields
private static ActiveSort _instance;
private readonly Dictionary<BasePlayer, ActiveSortUI> _components = new Dictionary<BasePlayer, ActiveSortUI>();
private const string PermUse = "activesort.use";
private const string Layer = "UI.ActiveSort";
private enum FurnaceType
{
Furnace,
Refinery
}
private readonly Dictionary<string, string> _furnaceItems = new Dictionary<string, string>
{
["sulfur.ore"] = "sulfur",
["metal.ore"] = "metal.fragments",
["hq.metal.ore"] = "metal.refined"
};
private readonly Dictionary<string, string> _refineryItems = new Dictionary<string, string>
{
["crude.oil"] = "lowgradefuel"
};
#endregion
#region Config
private static Configuration _config;
private class Configuration
{
[JsonProperty(PropertyName = "ShowUI")]
public bool ShowUI = true;
[JsonProperty(PropertyName = "ButtonPositionOffset")]
public Vector2 ButtonPositionOffset = new Vector2(0, 0);
[JsonProperty(PropertyName = "ButtonSize")]
public Vector2 ButtonSize = new Vector2(115, 30);
[JsonProperty(PropertyName = "ButtonColorHex")]
public string ButtonColorHex = "#6F8344";
[JsonProperty(PropertyName = "ButtonCaptionColorHex")]
public string ButtonCaptionColorHex = "#A5BA7A";
[JsonProperty(PropertyName = "ButtonCaptionFontSize")]
public int ButtonCaptionFontSize = 16;
[JsonProperty(PropertyName = "ButtonCaptionIsBold")]
public bool ButtonCaptionIsBold;
}
protected override void LoadConfig()
{
base.LoadConfig();
try
{
_config = Config.ReadObject<Configuration>();
if (_config == null) throw new Exception();
SaveConfig();
}
catch
{
PrintError("Your configuration file contains an error.Using default configuration values.");
LoadDefaultConfig();
}
}
protected override void SaveConfig()
{
Config.WriteObject(_config);
}
protected override void LoadDefaultConfig()
{
_config = new Configuration();
}
#endregion
#region Hooks
private void Init()
{
_instance = this;
permission.RegisterPermission(PermUse, this);
if (!_config.ShowUI)
{
Unsubscribe(nameof(OnPlayerLootEnd));
Unsubscribe(nameof(OnLootEntity));
}
}
private void Unload()
{
foreach (var player in BasePlayer.activePlayerList) CuiHelper.DestroyUi(player, Layer);
_components.Values.ToList().ForEach(component =>
{
if (component != null)
component.Kill();
});
_instance = null;
_config = null;
}
private void OnLootEntity(BasePlayer player, BaseEntity entity)
{
if (CanSort(player) && GetComponent(player) == null)
player.gameObject.AddComponent<ActiveSortUI>();
}
private void OnPlayerLootEnd(PlayerLoot inventory)
{
if (inventory == null) return;
var ui = inventory.GetComponent<ActiveSortUI>();
if (ui == null) return;
ui.Kill();
}
#endregion
#region Commands
[ConsoleCommand("activesort.sort")]
private void HandlerSortLoot(ConsoleSystem.Arg arg)
{
var player = arg.Player();
if (player == null || GetComponent(player) == null) return;
SortLoot(player);
}
#endregion
#region Component
private class ActiveSortUI : FacepunchBehaviour
{
private readonly Vector2 _buttonBasePosition = new Vector2(365, 85);
private BasePlayer _player;
private void Awake()
{
_player = GetComponent<BasePlayer>();
_instance._components[_player] = this;
RenderUI();
}
private void RenderUI()
{
CuiHelper.DestroyUi(_player, Layer);
CuiHelper.AddUi(_player, new CuiElementContainer
{
{
new CuiButton
{
RectTransform =
{
AnchorMin = "0.5 0.0",
AnchorMax = "0.5 0.0",
OffsetMax =
$"{_buttonBasePosition.x + _config.ButtonPositionOffset.x + _config.ButtonSize.x / 2} " +
$"{_buttonBasePosition.y + _config.ButtonPositionOffset.y + _config.ButtonSize.y / 2}",
OffsetMin =
$"{_buttonBasePosition.x + _config.ButtonPositionOffset.x - _config.ButtonSize.x / 2} " +
$"{_buttonBasePosition.y + _config.ButtonPositionOffset.y - _config.ButtonSize.y / 2}"
},
Text =
{
Align = TextAnchor.MiddleCenter,
Color = HexToCuiColor(_config.ButtonCaptionColorHex),
Font = _config.ButtonCaptionIsBold
? "RobotoCondensed-Bold.ttf"
: "robotocondensed-regular.ttf",
Text = _instance.Msg("BUTTON_CAPTION", _player.UserIDString),
FontSize = _config.ButtonCaptionFontSize
},
Button =
{
Color = HexToCuiColor(_config.ButtonColorHex),
Command = "activesort.sort"
}
},
"Overlay", Layer
}
});
}
private void OnDestroy()
{
CuiHelper.DestroyUi(_player, Layer);
_instance?._components?.Remove(_player);
Destroy(this);
}
public void Kill()
{
DestroyImmediate(this);
}
}
#endregion
#region Utils
private ActiveSortUI GetComponent(BasePlayer player)
{
ActiveSortUI sortUI;
return _components.TryGetValue(player, out sortUI) ? sortUI : null;
}
private static string HexToCuiColor(string hex, float alpha = 100)
{
if (string.IsNullOrEmpty(hex)) hex = "#FFFFFF";
var str = hex.Trim('#');
if (str.Length != 6) throw new Exception(hex);
var r = byte.Parse(str.Substring(0, 2), NumberStyles.HexNumber);
var g = byte.Parse(str.Substring(2, 2), NumberStyles.HexNumber);
var b = byte.Parse(str.Substring(4, 2), NumberStyles.HexNumber);
return $"{(double)r / 255} {(double)g / 255} {(double)b / 255} {alpha / 100f}";
}
private void PutToContainer(Dictionary<uint, int> data, Dictionary<string, Item> items, string shortname,
ItemContainer container,
BasePlayer player, ref int spaceLeft, bool reserve = false)
{
if (items.ContainsKey(shortname))
{
var stackToContainer = TakeStack(data, items[shortname]);
if (stackToContainer != null)
{
stackToContainer.MoveToContainer(container);
ReturnToPlayer(data, player, items[shortname]);
}
else
{
items[shortname].MoveToContainer(container);
items.Remove(shortname);
}
}
if (items.ContainsKey(shortname) || reserve) spaceLeft--;
}
private Item TakeStack(Dictionary<uint, int> data, Item item, int targetCount = -1)
{
int count;
if (!data.TryGetValue(item.uid, out count)) count = item.MaxStackable();
if (targetCount != -1) count = Math.Min(count, targetCount);
if (item.amount > count) return item.SplitItem(count);
return null;
}
private void FilterOnlyNotProcessed(Dictionary<uint, int> data, Dictionary<string, Item> items,
BasePlayer player, ItemContainer container,
FurnaceType type)
{
var allowedItems = type == FurnaceType.Furnace ? _furnaceItems : _refineryItems;
foreach (var shortname in items.Keys.ToList())
if (allowedItems.ContainsValue(shortname) &&
!items.ContainsKey(allowedItems.FirstOrDefault(x => x.Value == shortname).Key))
{
ReturnToPlayer(data, player, items[shortname]);
items.Remove(shortname);
}
}
private void FilterWhitelist(Dictionary<uint, int> data, Dictionary<string, Item> items, BasePlayer player,
ItemContainer container,
FurnaceType type)
{
var allowedItems = type == FurnaceType.Furnace ? _furnaceItems : _refineryItems;
foreach (var shortname in items.Keys.ToList())
if (!allowedItems.ContainsKey(shortname) && !allowedItems.ContainsValue(shortname))
{
ReturnToPlayer(data, player, items[shortname]);
items.Remove(shortname);
}
}
private void ReturnToPlayer(Dictionary<uint, int> data, BasePlayer player, Item item)
{
while (item != null)
{
var nextToGive = TakeStack(data, item);
if (nextToGive == null)
{
nextToGive = item;
item = null;
}
player.GiveItem(nextToGive);
}
}
private readonly Dictionary<BasePlayer, Dictionary<uint, int>> _stackByItem =
new Dictionary<BasePlayer, Dictionary<uint, int>>();
private Dictionary<string, Item> CloneAndPackItems(ItemContainer container, Dictionary<uint, int> data)
{
var items = new Dictionary<string, Item>();
foreach (var it in container.itemList)
if (items.ContainsKey(it.info.shortname))
{
items[it.info.shortname].amount += it.amount;
}
else
{
var item = ItemManager.Create(it.info, it.amount, it.skin);
items[it.info.shortname] = item;
int stackable;
if (data.TryGetValue(it.uid, out stackable))
{
data[item.uid] = stackable;
data.Remove(it.uid);
}
}
return items;
}
private void ClearContainer(ItemContainer container)
{
while (container.itemList.Count > 0)
{
var item = container.itemList[0];
item.RemoveFromContainer();
item.Remove();
}
}
#endregion
#region Lang
private string Msg(string key, string userIDString)
{
return lang.GetMessage(key, this, userIDString);
}
protected override void LoadDefaultMessages()
{
lang.RegisterMessages(new Dictionary<string, string>
{
["BUTTON_CAPTION"] = "Sort"
}, this);
lang.RegisterMessages(new Dictionary<string, string>
{
["BUTTON_CAPTION"] = "Сортировать"
}, this, "ru");
}
#endregion
#region API
private void SortLoot(BasePlayer player)
{
if (!CanSort(player)) return;
if (player.inventory.loot.containers == null || player.inventory.loot.containers.Count == 0) return;
var container = player.inventory.loot.containers[0];
var furnace = player.inventory.loot.entitySource.GetComponent<BaseOven>();
var type = FurnaceType.Furnace;
if (furnace.name.Contains("refinery")) type = FurnaceType.Refinery;
var allowedItems = type == FurnaceType.Furnace ? _furnaceItems : _refineryItems;
if (!_stackByItem.ContainsKey(player))
_stackByItem.Add(player, new Dictionary<uint, int>());
var data = _stackByItem[player];
foreach (var it in container.itemList.ToList())
{
data[it.uid] = it.MaxStackable();
if (it.info.shortname != "wood" && !allowedItems.ContainsKey(it.info.shortname))
ReturnToPlayer(data, player, it);
}
var items = CloneAndPackItems(container, data);
ClearContainer(container);
var spaceLeft = container.capacity;
PutToContainer(data, items, "wood", container, player, ref spaceLeft, true);
PutToContainer(data, items, "charcoal", container, player, ref spaceLeft, true);
FilterWhitelist(data, items, player, container, type);
FilterOnlyNotProcessed(data, items, player, container, type);
while (true)
{
var toSortKinds = items.Keys.Count(shortname => allowedItems.ContainsKey(shortname));
if (toSortKinds == 0)
{
if (items.Count > 0)
items.Keys.ToList().ForEach(shortname => { ReturnToPlayer(data, player, items[shortname]); });
break;
}
if (toSortKinds * 2 > spaceLeft)
{
var toCancel = items.Keys.ToList()[0];
ReturnToPlayer(data, player, items[toCancel]);
if (items.ContainsKey(allowedItems[toCancel]))
ReturnToPlayer(data, player, items[allowedItems[toCancel]]);
items.Remove(toCancel);
items.Remove(allowedItems[toCancel]);
continue;
}
var cellForEach = spaceLeft / toSortKinds;
var cellAdditional = spaceLeft % toSortKinds;
var cellCountByName = new Dictionary<string, int>();
foreach (var shortname in items.Keys)
if (allowedItems.ContainsKey(shortname))
{
cellCountByName[shortname] = cellForEach;
if (cellAdditional > 0)
{
cellCountByName[shortname]++;
cellAdditional--;
}
}
foreach (var shortname in cellCountByName.Keys)
{
var cellAmount = items[shortname].amount / (cellCountByName[shortname] - 1);
if (cellAmount > 0)
for (var i = 0; i < cellCountByName[shortname] - 2; i++)
{
var entry = TakeStack(data, items[shortname], cellAmount);
entry.MoveToContainer(container, -1, false);
}
var lastPart = TakeStack(data, items[shortname]);
if (lastPart == null)
lastPart = items[shortname];
else
ReturnToPlayer(data, player, items[shortname]);
lastPart.MoveToContainer(container, -1, false);
if (items.ContainsKey(allowedItems[shortname]))
{
var processedToContainer = TakeStack(data, items[allowedItems[shortname]]);
if (processedToContainer == null)
processedToContainer = items[allowedItems[shortname]];
else
ReturnToPlayer(data, player, items[allowedItems[shortname]]);
processedToContainer.MoveToContainer(container);
}
items.Remove(shortname);
items.Remove(allowedItems[shortname]);
}
}
var longestCookingTime = 0.0f;
foreach (var it in container.itemList)
{
var cookable = it.info.GetComponent<ItemModCookable>();
if (cookable != null)
{
var cookingTime = cookable.cookTime * it.amount;
if (cookingTime > longestCookingTime) longestCookingTime = cookingTime;
}
}
var fuelAmount = furnace.fuelType.GetComponent<ItemModBurnable>().fuelAmount;
var neededFuel = Mathf.CeilToInt(longestCookingTime * (furnace.cookingTemperature / 200.0f) / fuelAmount);
foreach (var it in container.itemList)
if (it.info.shortname == "wood")
{
if (neededFuel == 0)
{
ReturnToPlayer(data, player, it);
}
else if (it.amount > neededFuel)
{
var unneded = it.SplitItem(it.amount - neededFuel);
ReturnToPlayer(data, player, unneded);
}
break;
}
_stackByItem.Remove(player);
}
private bool CanSort(BasePlayer player)
{
if (player == null || !permission.UserHasPermission(player.UserIDString, PermUse)) return false;
var furnace = player.inventory?.loot?.entitySource?.GetComponent<BaseOven>();
return furnace != null && (furnace.name.Contains("furnace") || furnace.name.Contains("refinery"));
}
#endregion
}
}