· 5 years ago · May 18, 2020, 11:50 PM
1using Oxide.Game.Rust.Cui;
2using System;
3using System.Collections.Generic;
4using System.Linq;
5using UnityEngine;
6
7namespace Oxide.Plugins {
8 [Info("Meeps Active Sort", "RustRD.com - COMPILE THAT SHIT ON THE FLY YO", "1.0.4")]
9 [Description("Sorts furnace and refinery on click")]
10 class ActiveSort : RustPlugin {
11 private const string permUse = "activesort.use";
12
13 #region TRANSLATION TABLES
14 Dictionary<string, string> furnaceItems = new Dictionary<string, string> {
15 ["sulfur.ore"] = "sulfur",
16 ["metal.ore"] = "metal.fragments",
17 ["hq.metal.ore"] = "metal.refined"
18 };
19
20 Dictionary<string, string> refineryItems = new Dictionary<string, string> {
21 ["crude.oil"] = "lowgradefuel"
22 };
23 #endregion
24
25 enum FurnaceType {
26 Furnace,
27 Refinery
28 }
29 #region CONFIG
30 class PluginConfig {
31 public bool ShowUI = true;
32 public Vector2 ButtonPositionOffset = new Vector2(0, 0);
33 public Vector2 ButtonSize = new Vector2(115, 30);
34 public string ButtonColorHex = "#6F8344";
35 public string ButtonCaptionColorHex = "#A5BA7A";
36 public int ButtonCaptionFontSize = 16;
37 public bool ButtonCaptionIsBold = false;
38 }
39 private PluginConfig config;
40 #endregion
41 #region L10N
42 private string l10n(string key, string UserIDString) {
43 return lang.GetMessage(key, this, UserIDString);
44 }
45 protected override void LoadDefaultMessages() {
46 lang.RegisterMessages(new Dictionary<string, string> {
47 ["BUTTON_CAPTION"] = "Sort"
48 }, this);
49 lang.RegisterMessages(new Dictionary<string, string> {
50 ["BUTTON_CAPTION"] = "Сортировать"
51 }, this, "ru");
52 }
53 #endregion
54 #region HOOKS
55 private void Init() {
56 permission.RegisterPermission(permUse, this);
57 config = Config.ReadObject<PluginConfig>();
58 Config.WriteObject(config);
59 if (!config.ShowUI) {
60 Unsubscribe(nameof(OnPlayerLootEnd));
61 Unsubscribe(nameof(OnLootEntity));
62 }
63 }
64
65 protected override void LoadDefaultConfig() {
66 Config.WriteObject(new PluginConfig(), true);
67 }
68
69 private void Unload() {
70 foreach (var comp in UnityEngine.Object.FindObjectsOfType<ActiveSortUI>()) {
71 UnityEngine.Object.Destroy(comp);
72 }
73 }
74
75 private void OnLootEntity(BasePlayer player, BaseEntity entity) {
76 if (CanSort(player)) {
77 if (player.GetComponent<ActiveSortUI>() == null) {
78 var ui = player.gameObject.AddComponent<ActiveSortUI>();
79 ui.Init(this);
80 }
81 }
82 }
83
84 private void OnPlayerLootEnd(PlayerLoot inventory) {
85 ActiveSortUI ui = inventory?.GetComponent<ActiveSortUI>();
86
87 if (ui != null) {
88 UnityEngine.Object.Destroy(ui);
89 }
90 }
91 #endregion
92 #region API
93 private void SortLoot(BasePlayer player) {
94 if (!CanSort(player)) {
95 return;
96 }
97
98 if (player.inventory.loot.containers == null || player.inventory.loot.containers.Count == 0) {
99 return;
100 }
101
102 var container = player.inventory.loot.containers[0];
103 var furnace = player.inventory.loot.entitySource.GetComponent<BaseOven>();
104 var type = FurnaceType.Furnace;
105 if (furnace.name.Contains("refinery")) {
106 type = FurnaceType.Refinery;
107 }
108 var allowedItems = type == FurnaceType.Furnace ? furnaceItems : refineryItems;
109
110 foreach (var it in container.itemList.ToList()) {
111 if (it.info.shortname != "wood" && !allowedItems.ContainsKey(it.info.shortname)) {
112 ReturnToPlayer(player, it);
113 }
114 }
115
116 Dictionary<string, Item> items = CloneAndPackItems(container);
117 ClearContainer(container);
118
119 int spaceLeft = container.capacity;
120 PutToContainer(items, "wood", container, player, ref spaceLeft, true);
121 PutToContainer(items, "charcoal", container, player, ref spaceLeft, true);
122 FilterWhitelist(items, player, container, type);
123 FilterOnlyNotProcessed(items, player, container, type);
124
125 while (true) {
126 int toSortKinds = items.Keys.Where(shortname => allowedItems.ContainsKey(shortname)).Count();
127 if (toSortKinds == 0) {
128 if (items.Count > 0) {
129 items.Keys.ToList().ForEach(shortname => {
130 ReturnToPlayer(player, items[shortname]);
131 });
132 }
133 break;
134 }
135
136 if (toSortKinds * 2 > spaceLeft) {
137 string toCancel = items.Keys.ToList()[0];
138 ReturnToPlayer(player, items[toCancel]);
139 if (items.ContainsKey(allowedItems[toCancel])) {
140 ReturnToPlayer(player, items[allowedItems[toCancel]]);
141 }
142
143 items.Remove(toCancel);
144 items.Remove(allowedItems[toCancel]);
145 continue;
146 }
147
148 int cellForEach = spaceLeft / toSortKinds;
149 int cellAdditional = spaceLeft % toSortKinds;
150
151 Dictionary<string, int> cellCountByName = new Dictionary<string, int>();
152 foreach (var shortname in items.Keys) {
153 if (allowedItems.ContainsKey(shortname)) {
154 cellCountByName[shortname] = cellForEach;
155 if (cellAdditional > 0) {
156 cellCountByName[shortname]++;
157 cellAdditional--;
158 }
159 }
160 }
161
162 foreach (var shortname in cellCountByName.Keys) {
163 int cellAmount = items[shortname].amount / (cellCountByName[shortname] - 1);
164 if (cellAmount > 0) {
165 for (int i = 0; i < cellCountByName[shortname] - 2; i++) {
166 Item entry = TakeStack(items[shortname], cellAmount);
167 entry.MoveToContainer(container, -1, false);
168 }
169 }
170 Item lastPart = TakeStack(items[shortname]);
171 if (lastPart == null) {
172 lastPart = items[shortname];
173 } else {
174 ReturnToPlayer(player, items[shortname]);
175 }
176
177 lastPart.MoveToContainer(container, -1, false);
178 if (items.ContainsKey(allowedItems[shortname])) {
179 Item processedToContainer = TakeStack(items[allowedItems[shortname]]);
180 if (processedToContainer == null) {
181 processedToContainer = items[allowedItems[shortname]];
182 } else {
183 ReturnToPlayer(player, items[allowedItems[shortname]]);
184 }
185
186 processedToContainer.MoveToContainer(container, -1, true);
187 }
188
189 items.Remove(shortname);
190 items.Remove(allowedItems[shortname]);
191 }
192 }
193
194 float longestCookingTime = 0.0f;
195
196 foreach (var it in container.itemList) {
197 var cookable = it.info.GetComponent<ItemModCookable>();
198 if (cookable != null) {
199 float cookingTime = cookable.cookTime * it.amount;
200 if (cookingTime > longestCookingTime) {
201 longestCookingTime = cookingTime;
202 }
203 }
204 }
205
206 float fuelAmount = furnace.fuelType.GetComponent<ItemModBurnable>().fuelAmount;
207 int neededFuel = Mathf.CeilToInt(longestCookingTime * (furnace.cookingTemperature / 200.0f) / fuelAmount);
208
209 foreach (var it in container.itemList) {
210 if (it.info.shortname == "wood") {
211 if (neededFuel == 0) {
212 ReturnToPlayer(player, it);
213 } else if (it.amount > neededFuel) {
214 var unneded = it.SplitItem(it.amount - neededFuel);
215 ReturnToPlayer(player, unneded);
216 }
217 break;
218 }
219 }
220 }
221
222 private bool CanSort(BasePlayer player) {
223 if (player == null) {
224 return false;
225 }
226
227 if (!permission.UserHasPermission(player.UserIDString, permUse)) {
228 return false;
229 }
230 var furnace = player.inventory?.loot?.entitySource?.GetComponent<BaseOven>();
231 return furnace != null && (furnace.name.Contains("furnace") || furnace.name.Contains("refinery"));
232 }
233 #endregion
234
235 private void PutToContainer(Dictionary<string, Item> items, string shortname, ItemContainer container, BasePlayer player, ref int spaceLeft, bool reserve = false) {
236 if (items.ContainsKey(shortname)) {
237 var stackToContainer = TakeStack(items[shortname]);
238 if (stackToContainer != null) {
239 stackToContainer.MoveToContainer(container);
240 ReturnToPlayer(player, items[shortname]);
241 } else {
242 items[shortname].MoveToContainer(container);
243 items.Remove(shortname);
244 }
245 }
246
247 if (items.ContainsKey(shortname) || reserve) {
248 spaceLeft--;
249 }
250 }
251
252 private Item TakeStack(Item item, int targetCount = -1) {
253 int count = item.info.stackable;
254 if (targetCount != -1) {
255 count = Math.Min(item.info.stackable, targetCount);
256 }
257 if (item.amount > count) {
258 return item.SplitItem(count);
259 }
260
261 return null;
262 }
263
264 private void FilterOnlyNotProcessed(Dictionary<string, Item> items, BasePlayer player, ItemContainer container, FurnaceType type) {
265 var allowedItems = type == FurnaceType.Furnace ? furnaceItems : refineryItems;
266 foreach (var shortname in items.Keys.ToList()) {
267 if (allowedItems.ContainsValue(shortname) && !items.ContainsKey(allowedItems.FirstOrDefault(x => x.Value == shortname).Key)) {
268 ReturnToPlayer(player, items[shortname]);
269 items.Remove(shortname);
270 }
271 }
272 }
273
274 private void FilterWhitelist(Dictionary<string, Item> items, BasePlayer player, ItemContainer container, FurnaceType type) {
275 var allowedItems = type == FurnaceType.Furnace ? furnaceItems : refineryItems;
276 foreach (var shortname in items.Keys.ToList()) {
277 if (!allowedItems.ContainsKey(shortname) && !allowedItems.ContainsValue(shortname)) {
278 ReturnToPlayer(player, items[shortname]);
279 items.Remove(shortname);
280 }
281 }
282 }
283
284 private void ReturnToPlayer(BasePlayer player, Item item) {
285 while (item != null) {
286 var nextToGive = TakeStack(item);
287 if (nextToGive == null) {
288 nextToGive = item;
289 item = null;
290 }
291
292 player.GiveItem(nextToGive);
293 }
294 }
295
296 private Dictionary<string, Item> CloneAndPackItems(ItemContainer container) {
297 var items = new Dictionary<string, Item>();
298 foreach (var it in container.itemList) {
299 if (items.ContainsKey(it.info.shortname)) {
300 items[it.info.shortname].amount += it.amount;
301 } else {
302 items[it.info.shortname] = ItemManager.Create(it.info, it.amount, it.skin);
303 }
304 }
305
306 return items;
307 }
308
309 private void ClearContainer(ItemContainer container) {
310 while (container.itemList.Count > 0) {
311 var item = container.itemList[0];
312 item.RemoveFromContainer();
313 item.Remove(0f);
314 }
315 }
316 #region GUI HANDLERS
317 [ConsoleCommand("activesort.sort")]
318 private void HandlerSortLoot(ConsoleSystem.Arg arg) {
319 if (arg.Player() == null) {
320 return;
321 }
322
323 if (arg.Player().GetComponent<ActiveSortUI>() == null) {
324 return;
325 }
326
327 SortLoot(arg.Player());
328 }
329 #endregion
330 #region GUI COMPONENT
331 class ActiveSortUI : MonoBehaviour {
332 private const string baseName = "activesort.sort_button";
333 private Vector2 buttonBasePosition = new Vector2(365, 85);
334 private BasePlayer player;
335 private ActiveSort plugin;
336
337 public void Init(ActiveSort plugin) {
338 this.plugin = plugin;
339 RenderUI();
340 }
341
342 private void RenderUI() {
343 List<CuiElement> elements = new List<CuiElement>();
344 var button = new CuiElement {
345 Parent = "Overlay",
346 Name = baseName
347 };
348 button.Components.Add(new CuiButtonComponent {
349 Color = ParseColor(plugin.config.ButtonColorHex),
350 Command = "activesort.sort"
351 });
352 button.Components.Add(new CuiRectTransformComponent {
353 AnchorMin = "0.5 0.0",
354 AnchorMax = "0.5 0.0",
355 OffsetMax = $"{buttonBasePosition.x + plugin.config.ButtonPositionOffset.x + plugin.config.ButtonSize.x / 2} " +
356 $"{buttonBasePosition.y + plugin.config.ButtonPositionOffset.y + plugin.config.ButtonSize.y / 2}",
357 OffsetMin = $"{buttonBasePosition.x + plugin.config.ButtonPositionOffset.x - plugin.config.ButtonSize.x / 2} " +
358 $"{buttonBasePosition.y + plugin.config.ButtonPositionOffset.y - plugin.config.ButtonSize.y / 2}"
359 });
360 elements.Add(button);
361
362 var caption = new CuiElement {
363 Parent = baseName,
364 Name = $"{baseName}.caption"
365 };
366 caption.Components.Add(new CuiTextComponent {
367 Align = TextAnchor.MiddleCenter,
368 Color = ParseColor(plugin.config.ButtonCaptionColorHex),
369 Font = plugin.config.ButtonCaptionIsBold ? "RobotoCondensed-Bold.ttf" : "robotocondensed-regular.ttf",
370 Text = plugin.l10n("BUTTON_CAPTION", player.UserIDString),
371 FontSize = plugin.config.ButtonCaptionFontSize
372 });
373 caption.Components.Add(new CuiRectTransformComponent {
374 AnchorMax = "1 1",
375 AnchorMin = "0 0"
376 });
377 elements.Add(caption);
378
379 CuiHelper.AddUi(player, elements);
380 }
381
382 private string ParseColor(string hexColor) {
383 Color c = new Color();
384 ColorUtility.TryParseHtmlString(hexColor, out c);
385 return $"{c.r} {c.g} {c.b} {c.a}";
386 }
387
388 void Awake() {
389 player = GetComponent<BasePlayer>();
390 }
391
392 void OnDestroy() {
393 CuiHelper.DestroyUi(player, baseName);
394 }
395 }
396 #endregion
397 }
398}