// Copyright (c) 2022-2026, David Karnok & Contributors
// Licensed under the Apache License, Version 2.0
using HarmonyLib;
using SpaceCraft;
using System;
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.InputSystem;
using UnityEngine.UI;
namespace CheatInventoryStacking
{
public partial class Plugin
{
static bool IsBackAndForthStackable(MachineRocketBackAndForth __instance)
{
return (__instance is MachineRocketBackAndForthTrade && stackTradeRockets.Value)
|| (__instance is MachineRocketBackAndForthInterplanetaryExchange && stackInterplanetaryRockets.Value);
}
///
/// Conditionally disallow stacking in trade rockets.
///
///
[HarmonyPostfix]
[HarmonyPatch(typeof(MachineRocketBackAndForth), nameof(MachineRocketBackAndForth.SetInventoryRocketBackAndForth))]
static void Patch_MachineRocketBackAndForth_SetInventoryRocketBackAndForth(
MachineRocketBackAndForth __instance,
Inventory inventory)
{
if (!IsBackAndForthStackable(__instance))
{
noStackingInventories.Add(inventory.GetId());
}
}
[HarmonyPrefix]
[HarmonyPatch(typeof(MachineRocketBackAndForth), "OnRocketBackAndForthInventoryModified")]
static bool Patch_MachineRocketBackAndForth_OnRocketBackAndForthInventoryModified(
MachineRocketBackAndForth __instance,
Inventory ____inventory)
{
if (stackSize.Value > 1 && IsBackAndForthStackable(__instance))
{
if (__instance.GetComponent().GetSetting() == 1
&& ____inventory.GetSize() * stackSize.Value <= fInventoryWorldObjectsInInventory(____inventory).Count)
{
__instance.SendBackAndForthRocket();
}
return false;
}
return true;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(UiWindowTrade), "OnClickButtons")]
static bool Patch_UiWindowTrade_OnClickButtons(
UiWindowTrade __instance,
int ____inventorySize,
MachineRocketBackAndForth ____machineRocketBackAndForth,
Dictionary ____groupsWithNumber,
UiGroupLine uiGroupLine,
Group group,
int changeOfValue)
{
if (stackSize.Value <= 1 || !IsBackAndForthStackable(____machineRocketBackAndForth))
{
return true;
}
if (____inventorySize <= 0)
{
return false;
}
var toChange = changeOfValue;
if (Keyboard.current[Key.LeftShift].isPressed || Keyboard.current[Key.RightShift].isPressed)
{
toChange *= 10;
}
if (Keyboard.current[Key.LeftCtrl].isPressed || Keyboard.current[Key.RightCtrl].isPressed)
{
toChange *= 10;
}
var newCount = 0;
if (toChange < 0)
{
if (____groupsWithNumber.TryGetValue(group, out var count))
{
newCount = Math.Max(0, count + toChange);
____groupsWithNumber[group] = newCount;
}
else
{
return false;
}
}
else if (toChange > 0)
{
int stacks = 0;
int n = stackSize.Value;
int groupCurrent = 0;
foreach (var entry in ____groupsWithNumber)
{
var m = entry.Value;
if (entry.Key == group)
{
groupCurrent = m;
}
else
{
// first see how many full stacks this group occupies
stacks += m / n;
// check if incomplete stack exists because that counts as full stack use here
if (m % n != 0)
{
stacks++;
}
}
}
// the other items already make the inventory full, do nothing
if (stacks >= ____inventorySize)
{
return false;
}
groupCurrent += toChange;
// how many stacks would the current group with the new amount use
int groupCurrentStackUse = groupCurrent / n;
if (groupCurrent % n != 0)
{
groupCurrentStackUse++;
}
// if it would create more stacks than the inventory capacity
// change the count to be the available free stacks times the stack size
if (stacks + groupCurrentStackUse > ____inventorySize)
{
int freeStacks = ____inventorySize - stacks;
groupCurrent = freeStacks * n;
}
newCount = groupCurrent;
____groupsWithNumber[group] = groupCurrent;
}
List toLink = [];
foreach (var gc in ____groupsWithNumber)
{
for (int i = 0; i < gc.Value; i++)
{
toLink.Add(gc.Key);
}
}
____machineRocketBackAndForth.SetMachineRocketBackAndForthLinkedGroups(toLink);
uiGroupLine.UpdateQuantity(newCount);
mUiWindowTradeUpdateTokenUi.Invoke(__instance, []);
return false;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(UiWindowInterplanetaryExchange), "OnClickButtons")]
static bool Patch_UiWindowInterplanetaryExchange_OnClickButtons(
int ____inventorySize,
MachineRocketBackAndForthInterplanetaryExchange ____machineRocketBackAndForthInterplanetary,
Dictionary ____groupsWithNumber,
UiGroupLine uiGroupLine,
Group group,
int changeOfValue,
PlanetData ____destinationPlanet
)
{
if (stackSize.Value <= 1 || !IsBackAndForthStackable(____machineRocketBackAndForthInterplanetary))
{
return true;
}
if (____inventorySize <= 0)
{
return false;
}
var toChange = changeOfValue;
if (Keyboard.current[Key.LeftShift].isPressed || Keyboard.current[Key.RightShift].isPressed)
{
toChange *= 10;
}
if (Keyboard.current[Key.LeftCtrl].isPressed || Keyboard.current[Key.RightCtrl].isPressed)
{
toChange *= 10;
}
var newCount = 0;
if (toChange < 0)
{
if (____groupsWithNumber.TryGetValue(group, out var count))
{
newCount = Math.Max(0, count + toChange);
____groupsWithNumber[group] = newCount;
}
else
{
return false;
}
}
else if (toChange > 0)
{
____groupsWithNumber.TryGetValue(group, out var currentAmount);
var availableAmount = LogisticInterplanetaryManager.Instance.GetGroupCount(group, ____destinationPlanet.GetPlanetHash());
if (currentAmount + toChange > availableAmount)
{
toChange = availableAmount - currentAmount;
}
if (toChange <= 0)
{
return false;
}
int stacks = 0;
int n = stackSize.Value;
int groupCurrent = 0;
foreach (var entry in ____groupsWithNumber)
{
var m = entry.Value;
if (entry.Key == group)
{
groupCurrent = m;
}
else
{
// first see how many full stacks this group occupies
stacks += m / n;
// check if incomplete stack exists because that counts as full stack use here
if (m % n != 0)
{
stacks++;
}
}
}
// the other items already make the inventory full, do nothing
if (stacks >= ____inventorySize)
{
return false;
}
groupCurrent += toChange;
// how many stacks would the current group with the new amount use
int groupCurrentStackUse = groupCurrent / n;
if (groupCurrent % n != 0)
{
groupCurrentStackUse++;
}
// if it would create more stacks than the inventory capacity
// change the count to be the available free stacks times the stack size
if (stacks + groupCurrentStackUse > ____inventorySize)
{
int freeStacks = ____inventorySize - stacks;
groupCurrent = freeStacks * n;
}
newCount = groupCurrent;
____groupsWithNumber[group] = groupCurrent;
}
List toLink = [];
foreach (var gc in ____groupsWithNumber)
{
for (int i = 0; i < gc.Value; i++)
{
toLink.Add(gc.Key);
}
}
____machineRocketBackAndForthInterplanetary.SetMachineRocketBackAndForthLinkedGroups(toLink);
uiGroupLine.UpdateQuantity(newCount);
return false;
}
[HarmonyPostfix]
[HarmonyPatch(typeof(UiWindowInterplanetaryExchange), "UpdateUiForSelectedItems")]
static void Patach_UiWindowInterplanetaryExchange_UpdateUiForSelectedItems(
int ____inventorySize,
TextMeshProUGUI ___textInventorySpace
)
{
if (stackSize.Value <= 1 || !stackInterplanetaryRockets.Value)
{
return;
}
var txt = ___textInventorySpace.text;
var idx = txt.IndexOf('/');
if (idx > 0)
{
___textInventorySpace.text = txt[..idx] + "/ " + (____inventorySize * stackSize.Value);
}
}
static Color defaultBackgroundColor = new(0.25f, 0.25f, 0.25f, 0.9f);
static Color defaultTextColor = new(1f, 1f, 1f, 1f);
static readonly Dictionary groupCountsCache = [];
static readonly List groupSetCache = [];
[HarmonyPrefix]
[HarmonyPatch(typeof(GroupList), nameof(GroupList.AddGroups), [typeof(List), typeof(GroupInfosDisplayerBlocksSwitches), typeof(bool), typeof(bool)])]
static bool Patch_GroupList_AddGroups(GroupList __instance,
List _groups,
bool _showBacklines,
GameObject ___imageMore,
int ___showMoreAt,
GameObject ___grid,
List ___groupsDisplayer,
GroupInfosDisplayerBlocksSwitches _infosDisplayerGroup,
GameObject ___groupDisplayerGameObject,
bool disableNavigation
)
{
if (__instance.GetComponentInParent() == null
|| stackSize.Value <= 1
|| !stackInterplanetaryRockets.Value
|| !groupListEnabled.Value)
{
return true;
}
var idg = _infosDisplayerGroup ?? new GroupInfosDisplayerBlocksSwitches();
groupCountsCache.Clear();
groupSetCache.Clear();
foreach (var gr in _groups)
{
if (!groupCountsCache.TryGetValue(gr.id, out var cnt))
{
groupSetCache.Add(gr);
}
groupCountsCache[gr.id] = cnt + 1;
}
for (int i = 0; i < groupSetCache.Count; i++)
{
Group _group = groupSetCache[i];
var count = groupCountsCache[_group.id];
if (___showMoreAt > 0 && ___groupsDisplayer.Count == ___showMoreAt)
{
GameObject obj = Instantiate(___imageMore, ___grid.transform);
obj.GetComponent().localScale = new Vector3(1f, 1f, 1f);
obj.SetActive(value: true);
}
GameObject gameObject = Instantiate(___groupDisplayerGameObject, ___grid.transform);
gameObject.GetComponent().localScale = new Vector3(1f, 1f, 1f);
GroupDisplayer component = gameObject.GetComponent();
component.SetGroupAndUpdateDisplay(_group, greyed: false, showName: false, isLocked: false, _showBacklines);
___groupsDisplayer.Add(component);
EventsHelpers.AddTriggerEvent(component.gameObject, EventTriggerType.Select, evt => {
mGroupListOnGroupListHovered.Invoke(__instance, [evt]);
});
EventsHelpers.AddTriggerEvent(component.gameObject, EventTriggerType.Deselect, evt => {
mGroupListOnGroupListHoveredOut.Invoke(__instance, [evt]);
});
gameObject.AddComponent().SetHoverGroupEvent(_group, idg, default, null, null);
EventsHelpers.AddTriggerEvent(gameObject, EventTriggerType.PointerClick,
evt =>
{
mGroupListOnGroupClicked.Invoke(__instance, [evt]);
},
new EventTriggerCallbackData(_group));
if (disableNavigation)
{
gameObject.GetComponent