├── .gitignore
├── Config
├── Localization.txt
└── XUi
│ └── windows.xml
├── ModInfo.xml
├── Properties
└── AssemblyInfo.cs
├── QuickStack.csproj
├── QuickStack.dll
├── QuickStack.sln
├── QuickStackConfig.xml
├── README.md
├── Source
├── ConsoleCmdReloadQuickstack.cs
├── Init.cs
├── NetPackages
│ ├── NetPackageDoQuickStack.cs
│ ├── NetPackageFindOpenableContainers.cs
│ ├── NetPackageInvManageAction.cs
│ └── NetPackageUnlockContainers.cs
├── Patches.cs
└── QuickStack.cs
└── UIAtlases
└── UIAtlas
├── ui_game_symbol_quickrestock.png
└── ui_game_symbol_quickstack.png
/.gitignore:
--------------------------------------------------------------------------------
1 | .vs
2 | obj
3 | *.pdb
4 | *.user
--------------------------------------------------------------------------------
/Config/Localization.txt:
--------------------------------------------------------------------------------
1 | Key,File,Type,UsedInMainMenu,NoTranslate,english,Context / Alternate Text,german,latam,french,italian,japanese,koreana,polish,brazilian,russian,turkish,schinese,tchinese,spanish
2 | lblStashQuickStack,UI,Tooltip,,,Quick Stack\nFirst click: Move items from inventory to fill up existing nearby container stacks.\nSecond click: Also create new stacks to move items.,,Schnelles Stapeln\nErster Klick: Verschiebe Gegenstände aus dem Inventar um vorhandene Behälterstapel in der Nähe aufzufüllen.\nZweiter Klick: Erstellt auch neue Stapel um Gegenstände zu verschieben.,,,,,,,,"Сложить\nПервый щелчок: переместить предметы из инвентаря, заполнив стопки в ближайших контейнерах.\nВторой щелчок: также создать новые стопки перемещаемых предметов.",,,,
3 | lblStashQuickRestock,UI,Tooltip,,,Quick Restock\nFirst click: Take items from nearby container stacks to fill up existing inventory stacks.\nSecond click: Also create new stacks to take items.,,Schnelles Auffüllen\nErster Klick: Nimm Gegenstände aus nahegelegenen Containerstapeln um vorhandene Inventarstapel aufzufüllen.\nZweiter Klick: Erstellt auch neue Stapel um Gegenstände zu entnehmen.,,,,,,,,"Забрать\nПервый щелчок: взять предметы в стопках из ближайших контейнеров, заполнив существующие стопки в инвентаре.\nВторой щелчок: также создать новые стопки забираемых предметов.",,,,
4 |
--------------------------------------------------------------------------------
/Config/XUi/windows.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/ModInfo.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("QuickStack")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("QuickStack")]
13 | [assembly: AssemblyCopyright("Copyright © 2022")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("edb270ae-1949-4dcf-a5ab-558e753e8a3d")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/QuickStack.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Release
6 | AnyCPU
7 | {EDB270AE-1949-4DCF-A5AB-558E753E8A3D}
8 | Library
9 | Properties
10 | QuickStack
11 | QuickStack
12 | v4.8
13 | 512
14 | true
15 | $(SDTD_DIR)
16 |
17 |
18 | true
19 | portable
20 | false
21 | .\
22 | DEBUG;TRACE
23 | prompt
24 | 4
25 |
26 |
27 | portable
28 | true
29 | .\
30 | TRACE
31 | prompt
32 | 4
33 | true
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | $(SDTD_DIR)\7DaysToDie_Data\Managed\Assembly-CSharp.dll
46 | False
47 |
48 |
49 | $(SDTD_DIR)\7DaysToDie_Data\Managed\LogLibrary.dll
50 | False
51 |
52 |
53 | $(SDTD_DIR)\7DaysToDie_Data\Managed\NGUI.dll
54 | False
55 |
56 |
57 | $(SDTD_DIR)\7DaysToDie_Data\Managed\UnityEngine.dll
58 | False
59 |
60 |
61 | $(SDTD_DIR)\7DaysToDie_Data\Managed\UnityEngine.CoreModule.dll
62 | False
63 |
64 |
65 | $(SDTD_DIR)\Mods\0_TFP_Harmony\0Harmony.dll
66 | False
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
--------------------------------------------------------------------------------
/QuickStack.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Westwud1/QuickStack/8ef51449030a96fc1eb8be7f16499b87b25edafc/QuickStack.dll
--------------------------------------------------------------------------------
/QuickStack.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.0.32014.148
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuickStack", "QuickStack.csproj", "{EDB270AE-1949-4DCF-A5AB-558E753E8A3D}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | Release|Any CPU = Release|Any CPU
12 | EndGlobalSection
13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | {EDB270AE-1949-4DCF-A5AB-558E753E8A3D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | {EDB270AE-1949-4DCF-A5AB-558E753E8A3D}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 | {EDB270AE-1949-4DCF-A5AB-558E753E8A3D}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 | {EDB270AE-1949-4DCF-A5AB-558E753E8A3D}.Release|Any CPU.Build.0 = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | GlobalSection(ExtensibilityGlobals) = postSolution
23 | SolutionGuid = {B49B2B4F-0638-4F9C-8693-D13ACBED26C6}
24 | EndGlobalSection
25 | EndGlobal
26 |
--------------------------------------------------------------------------------
/QuickStackConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 308
5 |
6 |
7 | 308 120
8 |
9 |
10 | 308 122
11 |
12 |
13 | true
14 |
15 |
16 |
17 | 7 7 7
18 |
19 |
20 |
21 | 255 0 0 255
22 |
23 |
24 |
25 | 128 0 0 0
26 |
27 |
28 |
361 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # QuickStack
2 |
3 | For more details about this mod, please refer to the [mod page](https://www.nexusmods.com/7daystodie/mods/1357).
4 |
5 | ## Build
6 |
7 | Prerequisites:
8 | - Visual Studio (using 2022, haven't tested with 2019)
9 | - .NET Framework 4.8
10 |
11 | 1. Go to your Mods folder. Usually located in "C:\Users\\AppData\Roaming\7DaysToDie\Mods"
12 | 2. Open git bash and run: git clone git@github.com:Westwud1/QuickStack.git
13 | 3. This repository also includes the .dll file, so the mod is technically installed and you should be able to use it. The other files (visual studio settings or build files) do not affect the mod.
14 | 4. Set the environment variable "SDTD_DIR" to your 7 days to die directory. This is used for the project to be able to correctly find the dependencies.
15 | 5. Open QuickStack.sln and build the QuickStack library.
16 |
17 | If you have any issues, please refer to [this](https://www.youtube.com/watch?v=n463fVZ26tY&list=PLJeCuPbkcF5RhAOkX7ghThq7hIfZ5Ypgj&index=3&ab_channel=SphereII) youtube video.
18 |
--------------------------------------------------------------------------------
/Source/ConsoleCmdReloadQuickstack.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | public class ConsoleCmdReloadQuickStack : ConsoleCmdAbstract
5 | {
6 | public override void Execute(List _params, CommandSenderInfo _senderInfo)
7 | {
8 | try
9 | {
10 | if (_params.Count > 0)
11 | SingletonMonoBehaviour.Instance.Output("[QuickStack] Ignoring extra parameters");
12 |
13 | QuickStack.LoadConfig();
14 | }
15 | catch (Exception e)
16 | {
17 | QuickStack.printExceptionInfo(e);
18 | }
19 | }
20 |
21 | public override string[] getCommands()
22 | {
23 | return new string[]
24 | {
25 | "reloadquickstack",
26 | "reloadqs"
27 | };
28 | }
29 |
30 | public override string getDescription()
31 | {
32 | return "Reloads QuickStack config";
33 | }
34 | }
--------------------------------------------------------------------------------
/Source/Init.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using HarmonyLib;
3 |
4 | //Harmony entry point.
5 | public class QuickStackModApi : IModApi
6 | {
7 | public void InitMod(Mod modInstance)
8 | {
9 | QuickStack.configFilePath = modInstance.Path + "/QuickStackConfig.xml";
10 | QuickStack.LoadConfig();
11 | Harmony harmony = new Harmony(GetType().ToString());
12 | harmony.PatchAll(Assembly.GetExecutingAssembly());
13 | }
14 | }
15 |
16 |
--------------------------------------------------------------------------------
/Source/NetPackages/NetPackageDoQuickStack.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | // Server => Client
5 | // Informs client it is safe to quick stack/restock
6 | // To a list of containers
7 | class NetPackageDoQuickStack : NetPackageInvManageAction
8 | {
9 | public override NetPackageDirection PackageDirection => NetPackageDirection.ToClient;
10 | protected QuickStackType type;
11 |
12 | public NetPackageDoQuickStack Setup(Vector3i _center, List _containerEntities, QuickStackType _type)
13 | {
14 | try
15 | {
16 | Setup(_center, _containerEntities);
17 | type = _type;
18 | return this;
19 | }
20 | catch (Exception e)
21 | {
22 | QuickStack.printExceptionInfo(e);
23 | return null;
24 | }
25 | }
26 |
27 | public override int GetLength()
28 | {
29 | try
30 | {
31 | return base.GetLength() + 1;
32 | }
33 | catch (Exception e)
34 | {
35 | QuickStack.printExceptionInfo(e);
36 | return 0;
37 | }
38 | }
39 |
40 | public override void ProcessPackage(World _world, GameManager _callbacks)
41 | {
42 | try
43 | {
44 | if (containerEntities == null || _world == null || containerEntities.Count == 0)
45 | {
46 | return;
47 | }
48 |
49 | switch (type)
50 | {
51 | case QuickStackType.Stack:
52 | QuickStack.ClientMoveQuickStack(center, containerEntities);
53 | break;
54 |
55 | case QuickStackType.Restock:
56 | QuickStack.ClientMoveQuickRestock(center, containerEntities);
57 | break;
58 |
59 | default:
60 | break;
61 | }
62 |
63 | ConnectionManager.Instance.SendToServer(NetPackageManager.GetPackage().Setup(center, containerEntities));
64 | }
65 | catch (Exception e)
66 | {
67 | QuickStack.printExceptionInfo(e);
68 | }
69 | }
70 |
71 | public override void read(PooledBinaryReader _reader)
72 | {
73 | try
74 | {
75 | base.read(_reader);
76 | type = (QuickStackType)_reader.ReadByte();
77 | }
78 | catch (Exception e)
79 | {
80 | QuickStack.printExceptionInfo(e);
81 | }
82 | }
83 |
84 | public override void write(PooledBinaryWriter _writer)
85 | {
86 | try
87 | {
88 | base.write(_writer);
89 | _writer.Write((byte)type);
90 | }
91 | catch (Exception e)
92 | {
93 | QuickStack.printExceptionInfo(e);
94 | }
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/Source/NetPackages/NetPackageFindOpenableContainers.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | // Client => Server
5 | // Requests a list of containers that are safe to modify
6 | class NetPackageFindOpenableContainers : NetPackage
7 | {
8 | public override NetPackageDirection PackageDirection => NetPackageDirection.ToServer;
9 | public override bool AllowedBeforeAuth => false;
10 | protected int playerEntityId;
11 | protected QuickStackType type;
12 |
13 | public NetPackageFindOpenableContainers Setup(int _playerEntityId, QuickStackType _type)
14 | {
15 | try
16 | {
17 | playerEntityId = _playerEntityId;
18 | type = _type;
19 | return this;
20 | }
21 | catch (Exception e)
22 | {
23 | QuickStack.printExceptionInfo(e);
24 | return null;
25 | }
26 | }
27 |
28 | public override int GetLength()
29 | {
30 | return 5;
31 | }
32 |
33 | public override void ProcessPackage(World _world, GameManager _callbacks)
34 | {
35 | try
36 | {
37 | if (_world == null)
38 | {
39 | return;
40 | }
41 |
42 | if (type >= QuickStackType.Count || type < QuickStackType.Stack)
43 | {
44 | return;
45 | }
46 | if (!_world.Players.dict.TryGetValue(playerEntityId, out var playerEntity) || playerEntity == null)
47 | {
48 | return;
49 | }
50 |
51 | var cinfo = ConnectionManager.Instance.Clients.ForEntityId(playerEntityId);
52 | if (cinfo == null)
53 | {
54 | return;
55 | }
56 |
57 | List openableEntities = new List(256);
58 |
59 | var center = new Vector3i(playerEntity.position);
60 | foreach (var centerEntityPair in QuickStack.FindNearbyLootContainers(center, playerEntityId))
61 | {
62 | if (centerEntityPair.Item2 == null)
63 | {
64 | continue;
65 | }
66 | openableEntities.Add(centerEntityPair.Item1);
67 | GameManager.Instance.lockedTileEntities.Add(centerEntityPair.Item2, playerEntityId);
68 | }
69 |
70 | if (openableEntities.Count > 0)
71 | {
72 | cinfo.SendPackage(NetPackageManager.GetPackage().Setup(center, openableEntities, type));
73 | }
74 | }
75 | catch(Exception e)
76 | {
77 | QuickStack.printExceptionInfo(e);
78 | }
79 | }
80 |
81 | public override void read(PooledBinaryReader _reader)
82 | {
83 | try
84 | {
85 | // ignore entity ID sent by client
86 | _ = _reader.ReadInt32();
87 | // use the NetPackage-provided one instead
88 | playerEntityId = Sender.entityId;
89 | type = (QuickStackType)_reader.ReadByte();
90 | }
91 | catch (Exception e)
92 | {
93 | QuickStack.printExceptionInfo(e);
94 | }
95 | }
96 |
97 | public override void write(PooledBinaryWriter _writer)
98 | {
99 | try
100 | {
101 | base.write(_writer);
102 | _writer.Write(playerEntityId);
103 | _writer.Write((byte)type);
104 | }
105 | catch (Exception e)
106 | {
107 | QuickStack.printExceptionInfo(e);
108 | }
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/Source/NetPackages/NetPackageInvManageAction.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | // Net Package for sending a list of container entities
5 | public abstract class NetPackageInvManageAction : NetPackage
6 | {
7 | public override NetPackageDirection PackageDirection => NetPackageDirection.Both;
8 | public override bool AllowedBeforeAuth => false;
9 | public abstract override void ProcessPackage(World _world, GameManager _callbacks);
10 | protected Vector3i center;
11 | protected List containerEntities = new List();
12 |
13 | protected NetPackageInvManageAction Setup(Vector3i _center, List _containerEntities)
14 | {
15 | try
16 | {
17 | center = _center;
18 | containerEntities = _containerEntities;
19 | return this;
20 | }
21 | catch (Exception e)
22 | {
23 | QuickStack.printExceptionInfo(e);
24 | return null;
25 | }
26 | }
27 |
28 | // Requantizes Vector3i to a 3-bytes. Requires -128 < x, y, z <= 128
29 | protected static void WriteOptimized(PooledBinaryWriter _writer, Vector3i ivec3)
30 | {
31 | try
32 | {
33 | _writer.Write((sbyte)ivec3.x);
34 | _writer.Write((sbyte)ivec3.y);
35 | _writer.Write((sbyte)ivec3.z);
36 | }
37 | catch (Exception e)
38 | {
39 | QuickStack.printExceptionInfo(e);
40 | }
41 | }
42 |
43 | protected static void ReadOptimized(PooledBinaryReader _reader, out Vector3i ivec3)
44 | {
45 | try
46 | {
47 | ivec3 = new Vector3i
48 | {
49 | x = _reader.ReadSByte(),
50 | y = _reader.ReadSByte(),
51 | z = _reader.ReadSByte()
52 | };
53 | }
54 | catch (Exception e)
55 | {
56 | QuickStack.printExceptionInfo(e);
57 | ivec3 = new Vector3i(0, 0, 0);
58 | }
59 | }
60 |
61 | // Vector3i without any requantization. Full range, but takes up 4x more space
62 | protected static void Write(PooledBinaryWriter _writer, Vector3i ivec3)
63 | {
64 | try
65 | {
66 | _writer.Write(ivec3.x);
67 | _writer.Write(ivec3.y);
68 | _writer.Write(ivec3.z);
69 | }
70 | catch (Exception e)
71 | {
72 | QuickStack.printExceptionInfo(e);
73 | }
74 | }
75 |
76 | protected static void Read(PooledBinaryReader _reader, out Vector3i ivec3)
77 | {
78 | try
79 | {
80 | ivec3 = new Vector3i
81 | {
82 | x = _reader.ReadInt32(),
83 | y = _reader.ReadInt32(),
84 | z = _reader.ReadInt32()
85 | };
86 | }
87 | catch (Exception e)
88 | {
89 | QuickStack.printExceptionInfo(e);
90 | ivec3 = new Vector3i(0, 0, 0);
91 | }
92 | }
93 |
94 | public override int GetLength()
95 | {
96 | try
97 | {
98 | return 3 * sizeof(int) + sizeof(ushort) + 3 * containerEntities.Count;
99 | }
100 | catch (Exception e)
101 | {
102 | QuickStack.printExceptionInfo(e);
103 | return 0;
104 | }
105 | }
106 |
107 | public override void read(PooledBinaryReader _reader)
108 | {
109 | try
110 | {
111 | Read(_reader, out center);
112 |
113 | int count = _reader.ReadInt16();
114 | containerEntities = new List(count);
115 | for (int i = 0; i < count; ++i)
116 | {
117 | ReadOptimized(_reader, out var idx);
118 | containerEntities.Add(idx);
119 | }
120 | }
121 | catch (Exception e)
122 | {
123 | QuickStack.printExceptionInfo(e);
124 | }
125 | }
126 |
127 | public override void write(PooledBinaryWriter _writer)
128 | {
129 | try
130 | {
131 | base.write(_writer);
132 |
133 | Write(_writer, center);
134 |
135 | if (containerEntities == null)
136 | {
137 | _writer.Write((ushort)0);
138 | return;
139 | }
140 |
141 | _writer.Write((ushort)containerEntities.Count);
142 | foreach (var id in containerEntities)
143 | {
144 | WriteOptimized(_writer, id);
145 | }
146 | }
147 | catch (Exception e)
148 | {
149 | QuickStack.printExceptionInfo(e);
150 | }
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/Source/NetPackages/NetPackageUnlockContainers.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | // Client => Server
5 | // Notifies server that containers are no longer in-use
6 | class NetPackageUnlockContainers : NetPackageInvManageAction
7 | {
8 | public override NetPackageDirection PackageDirection => NetPackageDirection.ToServer;
9 |
10 | public new NetPackageUnlockContainers Setup(Vector3i _center, List _containerEntities)
11 | {
12 | try
13 | {
14 | _ = base.Setup(_center, _containerEntities);
15 | return this;
16 | }
17 | catch (Exception e)
18 | {
19 | QuickStack.printExceptionInfo(e);
20 | return null;
21 | }
22 | }
23 |
24 | public override void ProcessPackage(World _world, GameManager _callbacks)
25 | {
26 | try
27 | {
28 | if (containerEntities == null || _world == null)
29 | {
30 | return;
31 | }
32 |
33 | foreach (var offset in containerEntities)
34 | {
35 | var entity = _world.GetTileEntity(0, center + offset);
36 | if (entity != null)
37 | {
38 | GameManager.Instance.lockedTileEntities.Remove(entity);
39 | }
40 | }
41 | }
42 | catch (Exception e)
43 | {
44 | QuickStack.printExceptionInfo(e);
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Source/Patches.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Audio;
3 | using HarmonyLib;
4 |
5 | internal class Patches
6 | {
7 | // This patch is used to initialize the functionality for the quick slot locking and the UI functionality for the QuickStack and QuickRestock buttons
8 | [HarmonyPatch(typeof(XUiC_BackpackWindow), "Init")]
9 | private class QS_1
10 | {
11 | public static void Postfix(XUiC_BackpackWindow __instance)
12 | {
13 | try
14 | {
15 | QuickStack.backpackWindow = __instance;
16 | QuickStack.playerControls = __instance.GetChildByType();
17 | QuickStack.playerBackpack = __instance.backpackGrid;
18 | QuickStack.lastClickTimes.Fill(0.0f);
19 |
20 | XUiController[] slots = QuickStack.playerBackpack.GetItemStackControllers();
21 |
22 | // Handle hotkey for locking slots
23 | for (int i = 0; i < slots.Length; ++i)
24 | {
25 | slots[i].OnPress += (XUiController _sender, int _mouseButton) =>
26 | {
27 | for (int j = 0; j < QuickStack.quickLockHotkeys.Length; j++)
28 | {
29 | if (!UICamera.GetKey(QuickStack.quickLockHotkeys[j]))
30 | return;
31 | }
32 |
33 | XUiC_ItemStack itemStack = _sender as XUiC_ItemStack;
34 |
35 | itemStack.UserLockedSlot = !itemStack.UserLockedSlot;
36 | QuickStack.backpackWindow.UpdateLockedSlots(QuickStack.playerControls);
37 | itemStack.xui.PlayMenuClickSound();
38 | };
39 | }
40 |
41 | // Handle clicking on QuickStack and QuickRestock
42 | XUiController childById = QuickStack.playerControls.GetChildById("btnMoveQuickStack");
43 | if (childById != null)
44 | {
45 | childById.OnPress += delegate (XUiController _sender, int _args)
46 | {
47 | QuickStack.QuickStackOnClick();
48 | };
49 | }
50 |
51 | childById = QuickStack.playerControls.GetChildById("btnMoveQuickRestock");
52 | if (childById != null)
53 | {
54 | childById.OnPress += delegate (XUiController _sender, int _args)
55 | {
56 | QuickStack.QuickRestockOnClick();
57 | };
58 | }
59 | }
60 | catch (Exception e)
61 | {
62 | QuickStack.printExceptionInfo(e);
63 | }
64 | }
65 | }
66 |
67 | // This patch is used to update the UI whenever the backpack is opened
68 | [HarmonyPatch(typeof(XUiC_BackpackWindow), "OnOpen")]
69 | private class QS_2
70 | {
71 | public static void Postfix(XUiC_BackpackWindow __instance)
72 | {
73 | QuickStack.UpdateUI();
74 | }
75 | }
76 |
77 | // This patch is used to add a binding to know whether the player is not accessing other loot container inventories with some exceptions like workstations.
78 | // This is used in the xml file to make the quickstack icon visible only when the player inventory is open.
79 | [HarmonyPatch(typeof(XUiC_BackpackWindow), "GetBindingValue")]
80 | private class QS_3
81 | {
82 | public static void Postfix(ref bool __result, XUiC_BackpackWindow __instance, ref string value, string bindingName)
83 | {
84 | try
85 | {
86 | if (__result == false)
87 | {
88 | if (bindingName != null)
89 | {
90 | if (bindingName == "notlootingorvehiclestorage")
91 | {
92 | bool flag1 = __instance.xui.vehicle != null && __instance.xui.vehicle.GetVehicle().HasStorage();
93 | bool flag2 = __instance.xui.lootContainer != null && __instance.xui.lootContainer.EntityId == -1;
94 | bool flag3 = __instance.xui.lootContainer != null && GameManager.Instance.World.GetEntity(__instance.xui.lootContainer.EntityId) is EntityDrone;
95 | value = (!flag1 && !flag2 && !flag3).ToString();
96 | __result = true;
97 | }
98 | }
99 | }
100 | }
101 | catch (Exception e)
102 | {
103 | QuickStack.printExceptionInfo(e);
104 | }
105 | }
106 | }
107 |
108 | // QuickStack and Restock functionality by pressing hotkeys (useful if other mods remove the UI buttons)
109 | [HarmonyPatch(typeof(GameManager), "UpdateTick")]
110 | private class QS_4
111 | {
112 | public static void Postfix(EntityPlayerLocal __instance)
113 | {
114 | try
115 | {
116 | if (UICamera.GetKeyDown(QuickStack.quickStackHotkeys[QuickStack.quickStackHotkeys.Length - 1]))
117 | {
118 | for (int i = 0; i < QuickStack.quickStackHotkeys.Length - 1; i++)
119 | {
120 | if (!UICamera.GetKey(QuickStack.quickStackHotkeys[i]))
121 | return;
122 | }
123 |
124 | QuickStack.QuickStackOnClick();
125 | Manager.PlayButtonClick();
126 | }
127 | else if (UICamera.GetKeyDown(QuickStack.quickRestockHotkeys[QuickStack.quickRestockHotkeys.Length - 1]))
128 | {
129 | for (int i = 0; i < QuickStack.quickRestockHotkeys.Length - 1; i++)
130 | {
131 | if (!UICamera.GetKey(QuickStack.quickRestockHotkeys[i]))
132 | return;
133 | }
134 |
135 | QuickStack.QuickRestockOnClick();
136 | Manager.PlayButtonClick();
137 | }
138 | }
139 | catch (Exception e)
140 | {
141 | QuickStack.printExceptionInfo(e);
142 | }
143 | }
144 | }
145 |
146 | // This patch is used to update the slot color in the backpack if the slot is locked by the player.
147 | [HarmonyPatch(typeof(XUiC_ItemStack), "updateBorderColor")]
148 | private class QS_5
149 | {
150 | [HarmonyPostfix]
151 | public static void Postfix(XUiC_ItemStack __instance)
152 | {
153 | try
154 | {
155 | if (__instance.UserLockedSlot && QuickStack.lockBorderColor.a > 0)
156 | __instance.selectionBorderColor = QuickStack.lockBorderColor;
157 | }
158 | catch (Exception e)
159 | {
160 | QuickStack.printExceptionInfo(e);
161 | }
162 | }
163 | }
164 | }
--------------------------------------------------------------------------------
/Source/QuickStack.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Xml;
5 | using UnityEngine;
6 |
7 | public enum QuickStackType : byte
8 | {
9 | Stack = 0,
10 | Restock,
11 | Count
12 | }
13 |
14 | internal class QuickStack
15 | {
16 | public static string configFilePath;
17 | public static float[] lastClickTimes = new float[(int)QuickStackType.Count];
18 | public static bool lockModeIconVisible = true;
19 | public static Vector3i stashDistance = new Vector3i(7, 7, 7);
20 | public static XUiC_Backpack playerBackpack;
21 | public static XUiC_BackpackWindow backpackWindow;
22 | public static XUiC_ContainerStandardControls playerControls;
23 | public static KeyCode[] quickLockHotkeys;
24 | public static KeyCode[] quickStackHotkeys;
25 | public static KeyCode[] quickRestockHotkeys;
26 | public static Color32 lockIconColor = new Color32(255, 0, 0, 255);
27 | public static Color32 lockBorderColor = new Color32(128, 0, 0, 0);
28 |
29 | public static void printExceptionInfo(Exception e)
30 | {
31 | Log.Warning($"[QuickStack] {e.Message}");
32 | Log.Warning($"[QuickStack] {e.StackTrace}");
33 | }
34 |
35 | // Checks if a loot container is openable by a player
36 | // HOST OR SERVER ONLY
37 | public static bool IsContainerUnlocked(int _entityIdThatOpenedIt, TileEntity _tileEntity)
38 | {
39 | try
40 | {
41 | if (!ConnectionManager.Instance.IsServer)
42 | {
43 | return false;
44 | }
45 |
46 | if (_tileEntity == null)
47 | {
48 | return false;
49 | }
50 |
51 | // Handle locked containers
52 | if ((_tileEntity is TileEntitySecureLootContainer lootContainer) && lootContainer.IsLocked())
53 | {
54 | // Handle Host
55 | if (!GameManager.IsDedicatedServer && _entityIdThatOpenedIt == GameManager.Instance.World.GetPrimaryPlayerId())
56 | {
57 | if (!lootContainer.IsUserAllowed(GameManager.Instance.persistentLocalPlayer.PrimaryId))
58 | {
59 | return false;
60 | }
61 | }
62 | else
63 | {
64 | // Handle Client
65 | var cinfo = ConnectionManager.Instance.Clients.ForEntityId(_entityIdThatOpenedIt);
66 | if (cinfo == null || !lootContainer.IsUserAllowed(cinfo.CrossplatformId))
67 | {
68 | return false;
69 | }
70 | }
71 | }
72 | // Handle locked composite storages
73 | else if ((_tileEntity is TileEntityComposite compositeContainer) && compositeContainer.teData.GetFeatureIndex() > 0)
74 | {
75 | TEFeatureLockable fLockable = compositeContainer.GetFeature();
76 | TEFeatureStorage fStorage = compositeContainer.GetFeature();
77 |
78 | // Handle Host
79 | if (!GameManager.IsDedicatedServer && _entityIdThatOpenedIt == GameManager.Instance.World.GetPrimaryPlayerId())
80 | {
81 | if (!fLockable.IsUserAllowed(GameManager.Instance.persistentLocalPlayer.PrimaryId))
82 | {
83 | return false;
84 | }
85 | }
86 | else
87 | {
88 | // Handle Client
89 | var cinfo = ConnectionManager.Instance.Clients.ForEntityId(_entityIdThatOpenedIt);
90 | if (cinfo == null || !fLockable.IsUserAllowed(cinfo.CrossplatformId))
91 | {
92 | return false;
93 | }
94 | }
95 | }
96 |
97 | // Handle in-use containers
98 | if (GameManager.Instance.lockedTileEntities.ContainsKey(_tileEntity) &&
99 | (GameManager.Instance.World.GetEntity(GameManager.Instance.lockedTileEntities[_tileEntity]) is EntityAlive entityAlive) &&
100 | !entityAlive.IsDead())
101 | {
102 | return false;
103 | }
104 |
105 | return true;
106 | }
107 | catch (Exception e)
108 | {
109 | printExceptionInfo(e);
110 | return false;
111 | }
112 | }
113 |
114 | public static bool IsValidLoot(TileEntityLootContainer _tileEntity)
115 | {
116 | try
117 | {
118 | return (_tileEntity.GetTileEntityType() == TileEntityType.Loot ||
119 | _tileEntity.GetTileEntityType() == TileEntityType.SecureLoot ||
120 | _tileEntity.GetTileEntityType() == TileEntityType.SecureLootSigned);
121 | }
122 | catch (Exception e)
123 | {
124 | printExceptionInfo(e);
125 | return false;
126 | }
127 | }
128 |
129 | public static (ITileEntityLootable, TileEntity) GetInventoryFromBlockPosition(Vector3i position)
130 | {
131 | try
132 | {
133 | TileEntityLootContainer lootContainer = GameManager.Instance.World.GetTileEntity(0, position) as TileEntityLootContainer;
134 |
135 | if (lootContainer != null)
136 | {
137 | if (IsValidLoot(lootContainer) && !lootContainer.IsUserAccessing())
138 | return (lootContainer, lootContainer);
139 |
140 | return (null, lootContainer);
141 | }
142 |
143 | TileEntityComposite compositeContainer = GameManager.Instance.World.GetTileEntity(0, position) as TileEntityComposite;
144 |
145 | if (compositeContainer == null)
146 | return (null, null);
147 |
148 | if (compositeContainer.teData.GetFeatureIndex() == 0)
149 | return (null, compositeContainer);
150 |
151 | if (compositeContainer.IsUserAccessing())
152 | return (null, compositeContainer);
153 |
154 | return (compositeContainer.GetFeature(), compositeContainer);
155 | }
156 | catch (Exception e)
157 | {
158 | printExceptionInfo(e);
159 | return (null, null);
160 | }
161 | }
162 |
163 | // Yields all openable loot containers in a cubic radius about a point
164 | public static IEnumerable> FindNearbyLootContainers(Vector3i _center, int _playerEntityId)
165 | {
166 | for (int i = -stashDistance.x; i <= stashDistance.x; i++)
167 | {
168 | for (int j = -stashDistance.y; j <= stashDistance.y; j++)
169 | {
170 | for (int k = -stashDistance.z; k <= stashDistance.z; k++)
171 | {
172 | var offset = new Vector3i(i, j, k);
173 |
174 | var val = GetInventoryFromBlockPosition(_center + offset);
175 |
176 | if (val.Item1 == null)
177 | continue;
178 |
179 | if (!IsContainerUnlocked(_playerEntityId, val.Item2))
180 | continue;
181 |
182 | yield return new ValueTuple(offset, val.Item2);
183 | }
184 | }
185 | }
186 | }
187 |
188 | // Gets the EItemMoveKind for the current move type based on the last time that move type was requested
189 | internal static XUiM_LootContainer.EItemMoveKind GetMoveKind(QuickStackType _type = QuickStackType.Stack)
190 | {
191 | try
192 | {
193 | float unscaledTime = Time.unscaledTime;
194 | float lastClickTime = lastClickTimes[(int)_type];
195 | lastClickTimes[(int)_type] = unscaledTime;
196 |
197 | if (unscaledTime - lastClickTime < 2.0f)
198 | {
199 | return XUiM_LootContainer.EItemMoveKind.FillAndCreate;
200 | }
201 | else
202 | {
203 | return XUiM_LootContainer.EItemMoveKind.FillOnly;
204 | }
205 | }
206 | catch (Exception e)
207 | {
208 | printExceptionInfo(e);
209 | return XUiM_LootContainer.EItemMoveKind.FillOnly;
210 | }
211 | }
212 |
213 | // Quickstack functionality
214 | // SINGLEPLAYER ONLY
215 | public static void MoveQuickStack()
216 | {
217 | try
218 | {
219 | if (backpackWindow.xui.lootContainer != null && backpackWindow.xui.lootContainer.EntityId == -1)
220 | return;
221 |
222 | var moveKind = GetMoveKind();
223 |
224 | EntityPlayerLocal primaryPlayer = GameManager.Instance.World.GetPrimaryPlayer();
225 |
226 | for (int i = -stashDistance.x; i <= stashDistance.x; i++)
227 | {
228 | for (int j = -stashDistance.y; j <= stashDistance.y; j++)
229 | {
230 | for (int k = -stashDistance.z; k <= stashDistance.z; k++)
231 | {
232 | Vector3i blockPos = new Vector3i((int)primaryPlayer.position.x + i, (int)primaryPlayer.position.y + j, (int)primaryPlayer.position.z + k);
233 |
234 | var val = GetInventoryFromBlockPosition(blockPos);
235 |
236 | if (val.Item1 == null)
237 | continue;
238 |
239 | XUiM_LootContainer.StashItems(backpackWindow, playerBackpack, val.Item1, 0, playerControls.LockedSlots, moveKind, playerControls.MoveStartBottomRight);
240 | val.Item2.SetModified();
241 | }
242 | }
243 | }
244 | }
245 | catch (Exception e)
246 | {
247 | printExceptionInfo(e);
248 | }
249 | }
250 |
251 | public static void ClientMoveQuickStack(Vector3i center, IEnumerable _entityContainers)
252 | {
253 | try
254 | {
255 | if (backpackWindow.xui.lootContainer != null && backpackWindow.xui.lootContainer.EntityId == -1)
256 | return;
257 |
258 | var moveKind = GetMoveKind();
259 |
260 | if (_entityContainers == null)
261 | return;
262 |
263 | foreach (var offset in _entityContainers)
264 | {
265 | var val = GetInventoryFromBlockPosition(center + offset);
266 |
267 | if (val.Item1 == null)
268 | continue;
269 |
270 | XUiM_LootContainer.StashItems(backpackWindow, playerBackpack, val.Item1, 0, playerControls.LockedSlots, moveKind, playerControls.MoveStartBottomRight);
271 | val.Item2.SetModified();
272 | }
273 | }
274 | catch (Exception e)
275 | {
276 | printExceptionInfo(e);
277 | }
278 | }
279 |
280 | // Restock functionality
281 | // SINGLEPLAYER ONLY
282 | public static void MoveQuickRestock()
283 | {
284 | try
285 | {
286 | if (backpackWindow.xui.lootContainer != null && backpackWindow.xui.lootContainer.EntityId == -1)
287 | return;
288 |
289 | var moveKind = GetMoveKind(QuickStackType.Restock);
290 |
291 | EntityPlayerLocal primaryPlayer = GameManager.Instance.World.GetPrimaryPlayer();
292 | LocalPlayerUI playerUI = LocalPlayerUI.GetUIForPlayer(primaryPlayer);
293 | XUiC_LootWindowGroup lootWindowGroup = (XUiC_LootWindowGroup)((XUiWindowGroup)playerUI.windowManager.GetWindow("looting")).Controller;
294 |
295 | for (int i = -stashDistance.x; i <= stashDistance.x; i++)
296 | {
297 | for (int j = -stashDistance.y; j <= stashDistance.y; j++)
298 | {
299 | for (int k = -stashDistance.z; k <= stashDistance.z; k++)
300 | {
301 | Vector3i blockPos = new Vector3i((int)primaryPlayer.position.x + i, (int)primaryPlayer.position.y + j, (int)primaryPlayer.position.z + k);
302 |
303 | var val = GetInventoryFromBlockPosition(blockPos);
304 |
305 | if (val.Item1 == null)
306 | continue;
307 |
308 | lootWindowGroup.SetTileEntityChest("QUICKSTACK", val.Item1);
309 | bool[] lockedSlots = new bool[lootWindowGroup.lootWindow.lootContainer.items.Length];
310 | lockedSlots.Fill(false);
311 | XUiM_LootContainer.StashItems(backpackWindow, lootWindowGroup.lootWindow.lootContainer, playerUI.mXUi.PlayerInventory, 0, lockedSlots, moveKind, playerControls.MoveStartBottomRight);
312 | val.Item2.SetModified();
313 | }
314 | }
315 | }
316 | }
317 | catch (Exception e)
318 | {
319 | printExceptionInfo(e);
320 | }
321 | }
322 |
323 | public static void ClientMoveQuickRestock(Vector3i center, IEnumerable _entityContainers)
324 | {
325 | try
326 | {
327 | if (backpackWindow.xui.lootContainer != null && backpackWindow.xui.lootContainer.EntityId == -1)
328 | return;
329 |
330 | var moveKind = GetMoveKind(QuickStackType.Restock);
331 |
332 | if (_entityContainers == null)
333 | return;
334 |
335 | EntityPlayerLocal primaryPlayer = GameManager.Instance.World.GetPrimaryPlayer();
336 | LocalPlayerUI playerUI = LocalPlayerUI.GetUIForPlayer(primaryPlayer);
337 | XUiC_LootWindowGroup lootWindowGroup = (XUiC_LootWindowGroup)((XUiWindowGroup)playerUI.windowManager.GetWindow("looting")).Controller;
338 |
339 | foreach (var offset in _entityContainers)
340 | {
341 | var val = GetInventoryFromBlockPosition(center + offset);
342 |
343 | if (val.Item1 == null)
344 | continue;
345 |
346 | lootWindowGroup.SetTileEntityChest("QUICKSTACK", val.Item1);
347 | bool[] lockedSlots = new bool[lootWindowGroup.lootWindow.lootContainer.items.Length];
348 | lockedSlots.Fill(false);
349 | XUiM_LootContainer.StashItems(backpackWindow, lootWindowGroup.lootWindow.lootContainer, playerUI.mXUi.PlayerInventory, 0, lockedSlots, moveKind, playerControls.MoveStartBottomRight);
350 | val.Item2.SetModified();
351 | }
352 | }
353 | catch (Exception e)
354 | {
355 | printExceptionInfo(e);
356 | }
357 | }
358 |
359 | // UI Delegates
360 | public static void QuickStackOnClick()
361 | {
362 | try
363 | {
364 | // Singleplayer
365 | if (ConnectionManager.Instance.IsSinglePlayer)
366 | {
367 | MoveQuickStack();
368 | // Multiplayer (Client)
369 | }
370 | else if (!ConnectionManager.Instance.IsServer)
371 | {
372 | ConnectionManager.Instance.SendToServer(NetPackageManager.GetPackage().Setup(GameManager.Instance.World.GetPrimaryPlayerId(), QuickStackType.Stack));
373 | // Multiplayer (Host)
374 | }
375 | else if (!GameManager.IsDedicatedServer)
376 | {
377 | // But we do the steps of Multiplayer quick stack in-place because
378 | // The host has access to locking functions
379 | var player = GameManager.Instance.World.GetPrimaryPlayer();
380 | var center = new Vector3i(player.position);
381 | List offsets = new List(1024);
382 | foreach (var pair in FindNearbyLootContainers(center, player.entityId))
383 | {
384 | offsets.Add(pair.Item1);
385 | }
386 | ClientMoveQuickStack(center, offsets);
387 | }
388 | }
389 | catch (Exception e)
390 | {
391 | printExceptionInfo(e);
392 | }
393 | }
394 |
395 | public static void QuickRestockOnClick()
396 | {
397 | try
398 | {
399 | // Singleplayer
400 | if (ConnectionManager.Instance.IsSinglePlayer)
401 | {
402 | MoveQuickRestock();
403 | // Multiplayer (Client)
404 | }
405 | else if (!ConnectionManager.Instance.IsServer)
406 | {
407 | ConnectionManager.Instance.SendToServer(NetPackageManager.GetPackage().Setup(GameManager.Instance.World.GetPrimaryPlayerId(), QuickStackType.Restock));
408 | // Multiplayer (Host)
409 | }
410 | else if (!GameManager.IsDedicatedServer)
411 | {
412 | // TODO: could be cleaned up a bit...
413 | // But we do the steps of Multiplayer quick stack in-place because
414 | // The host has access to locking functions
415 | var player = GameManager.Instance.World.GetPrimaryPlayer();
416 | var center = new Vector3i(player.position);
417 | List offsets = new List(1024);
418 | foreach (var pair in FindNearbyLootContainers(center, player.entityId))
419 | {
420 | offsets.Add(pair.Item1);
421 | }
422 | ClientMoveQuickRestock(center, offsets);
423 | }
424 | }
425 | catch (Exception e)
426 | {
427 | printExceptionInfo(e);
428 | }
429 | }
430 |
431 | public static void UpdateUI()
432 | {
433 | try
434 | {
435 | if (playerBackpack == null)
436 | return;
437 |
438 | XUiController[] slots = playerBackpack.GetItemStackControllers();
439 |
440 | for (int i = 0; i < slots.Length; ++i)
441 | (slots[i].GetChildById("iconSlotLock").ViewComponent as XUiV_Sprite).Color = lockIconColor;
442 |
443 | playerControls.GetChildById("btnToggleLockMode").ViewComponent.IsVisible = lockModeIconVisible;
444 | }
445 | catch (Exception e)
446 | {
447 | printExceptionInfo(e);
448 | }
449 | }
450 |
451 | public static void LoadConfig()
452 | {
453 | try
454 | {
455 | var stopwatch = System.Diagnostics.Stopwatch.StartNew();
456 |
457 | if (!File.Exists(configFilePath))
458 | throw new Exception($"Unable to find config at: {configFilePath}");
459 |
460 | XmlDocument xml = new XmlDocument();
461 | xml.Load(configFilePath);
462 |
463 | string[] quickLockButtons = xml.GetElementsByTagName("QuickLockButtons")[0].InnerText.Trim().Split(' ');
464 | if (quickLockButtons.Length == 0)
465 | throw new Exception("Must have at least one value for tag QuickLockButtons");
466 | quickLockHotkeys = new KeyCode[quickLockButtons.Length];
467 | for (int i = 0; i < quickLockButtons.Length; i++)
468 | quickLockHotkeys[i] = (KeyCode)int.Parse(quickLockButtons[i]);
469 |
470 | string[] quickStackButtons = xml.GetElementsByTagName("QuickStackButtons")[0].InnerText.Trim().Split(' ');
471 | if (quickStackButtons.Length == 0)
472 | throw new Exception("Must have at least one value for tag QuickStackButtons");
473 | quickStackHotkeys = new KeyCode[quickStackButtons.Length];
474 | for (int i = 0; i < quickStackButtons.Length; i++)
475 | quickStackHotkeys[i] = (KeyCode)int.Parse(quickStackButtons[i]);
476 |
477 | string[] quickRestockButtons = xml.GetElementsByTagName("QuickRestockButtons")[0].InnerText.Trim().Split(' ');
478 | if (quickRestockButtons.Length == 0)
479 | throw new Exception("Must have at least one value for tag QuickRestockButtons");
480 | quickRestockHotkeys = new KeyCode[quickRestockButtons.Length];
481 | for (int i = 0; i < quickRestockButtons.Length; i++)
482 | quickRestockHotkeys[i] = (KeyCode)int.Parse(quickRestockButtons[i]);
483 |
484 | lockModeIconVisible = Boolean.Parse(xml.GetElementsByTagName("LockModeIconVisible")[0].InnerText);
485 |
486 | string[] stashDistanceStr = xml.GetElementsByTagName("QuickStashDistance")[0].InnerText.Trim().Split(' ');
487 | if (stashDistanceStr.Length != 3)
488 | throw new Exception("Must have exactly three values for tag QuickStashDistance");
489 | stashDistance.x = Math.Min(Math.Max(int.Parse(stashDistanceStr[0]), 0), 127);
490 | stashDistance.y = Math.Min(Math.Max(int.Parse(stashDistanceStr[1]), 0), 127);
491 | stashDistance.z = Math.Min(Math.Max(int.Parse(stashDistanceStr[2]), 0), 127);
492 |
493 | string[] iconColor = xml.GetElementsByTagName("LockedSlotsIconColor")[0].InnerText.Trim().Split(' ');
494 | if (iconColor.Length != 4)
495 | throw new Exception("Must have exactly four values for tag LockedSlotsIconColor");
496 | lockIconColor = new Color32(Byte.Parse(iconColor[0]), Byte.Parse(iconColor[1]), Byte.Parse(iconColor[2]), Byte.Parse(iconColor[3]));
497 |
498 | string[] borderColor = xml.GetElementsByTagName("LockedSlotsBorderColor")[0].InnerText.Trim().Split(' ');
499 | if (borderColor.Length != 4)
500 | throw new Exception("Must have exactly four values for tag LockedSlotsBorderColor");
501 | lockBorderColor = new Color32(Byte.Parse(borderColor[0]), Byte.Parse(borderColor[1]), Byte.Parse(borderColor[2]), Byte.Parse(borderColor[3]));
502 |
503 | UpdateUI();
504 |
505 | Log.Out($"[QuickStack] Loaded config in {stopwatch.ElapsedMilliseconds} ms");
506 | }
507 | catch (Exception e)
508 | {
509 | quickLockHotkeys = new KeyCode[1];
510 | quickLockHotkeys[0] = KeyCode.LeftAlt;
511 |
512 | quickStackHotkeys = new KeyCode[2];
513 | quickStackHotkeys[0] = KeyCode.LeftAlt;
514 | quickStackHotkeys[1] = KeyCode.X;
515 |
516 | quickRestockHotkeys = new KeyCode[2];
517 | quickRestockHotkeys[0] = KeyCode.LeftAlt;
518 | quickRestockHotkeys[1] = KeyCode.Z;
519 |
520 | lockModeIconVisible = true;
521 |
522 | stashDistance = new Vector3i(7, 7, 7);
523 |
524 | lockIconColor = new Color32(255, 0, 0, 255);
525 | lockBorderColor = new Color32(128, 0, 0, 0);
526 |
527 | Log.Warning("[QuickStack] Failed to load or parse config");
528 | printExceptionInfo(e);
529 | }
530 | }
531 | }
532 |
--------------------------------------------------------------------------------
/UIAtlases/UIAtlas/ui_game_symbol_quickrestock.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Westwud1/QuickStack/8ef51449030a96fc1eb8be7f16499b87b25edafc/UIAtlases/UIAtlas/ui_game_symbol_quickrestock.png
--------------------------------------------------------------------------------
/UIAtlases/UIAtlas/ui_game_symbol_quickstack.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Westwud1/QuickStack/8ef51449030a96fc1eb8be7f16499b87b25edafc/UIAtlases/UIAtlas/ui_game_symbol_quickstack.png
--------------------------------------------------------------------------------