Minecolonies & Automatic Warehouse Sorting

in #utopian-io3 years ago

Hi everyone, I almost forgot to write about this update I merged 11 days ago.
Players have been asking for a long time for a way to automatically sort the warehouse.

Now, sorting the warehouse, in general, can be approached from different points. We could sort chest by chest or we could sort the complete warehosue and distribute things to the right chests.
Now, the first one is rather uninteresting since if the things are not very well distributed they will continue not very well distributed.
While the second one is rather complex since we don't have the same "information" the player has to distribute things evenly.

Based on that I wrote a small greedy algorithm which sorts the chests based on best effort and tries to evenly distributed all the load between all the chests.

Preparation:
First of all, I enhanced the ItemStorage class (our custom item storage which overrides equal and hash correctly and has an unlimited amount) with the creative tab index.

For non Minecraft players: The creative tab index is the tab of items when you are in unlimited item mode where you can find the item.

Since this value is protected within the minecraft classes I used forge reflection to make it public.

Adding it into the src/main/resources/META-INF/minecolonies_at.cfg and rebuilding the project then does the trick.

Then, I added a sort button to the warehouse GUI.

Where I added it first to the XML.

Afterward, I registered the button handler.

Made sure it wouldn't show for low level warehouses.

and then I send a custom message to the server side.

GUI action happens on the client side.

I had to create and register this message then.

The message itself then would search the building in the colony (deny the message if the colony or building are not existent or the player didn't have permission)
then again check if the warehouse is sortable and then query the combined inventory to hand it to the sorting utils.

To make it as generic as possible the sorting utils sorts any kind of combined inventory handler and doesn't care if it is a colony building, a warehouse or something else.

Finally, I had to improved our implementation of the combined inventory handler so I am able to tell where the chest I am currently iterating in ends. (So I can separate between the different inventories).

Before coming to the sorting, I had to fix one small thing in the racks to make sure the combined inventories are handled correct there.

Making sure that the rack always has an inventory handler.

Sorting:
The main method of the sorting process is the static sort method which receives the inventory.

First of all, if the inventory exists, it will iterate through all the slots and extracts the items into a map.

Removing the items from the inventories and sorting the reference.

In the next step it would calculate some stats we will need for the sorting.

The method calculates some stats based on the previously collected map.

The method will basically iterate over all itemStorages, calculate how many total stacks we have (considering that there is a max stack size per item for inventories) and then also return information how many items from how many different creative tabs we have.

final Map<Integer, Integer> creativeTabs = new HashMap<>();
int sum = 0;
for (final Map.Entry<ItemStorage, Integer> entry : map.entrySet())
{
sum += Math.ceil((double) entry.getValue() / entry.getKey().getItemStack().getMaxStackSize());

creativeTabs.put(entry.getKey().getCreativeTabIndex(), creativeTabs.getOrDefault(entry.getKey().getCreativeTabIndex(), 0) + (int) Math.ceil((double) entry.getValue() / entry.getKey().getItemStack().getMaxStackSize()));

}
Based on that information we would then order the map and then iterate again over the map and push the items to the inventory.

For that, I wrote a compare function returning, clasically, an integer.
Which receives two itemstorage map entries and then first sorts them by creative tab, then by item id and then by damage value.

Which brings us to the core part, the iterating over the inventories to push it.

The method would receive all information we previously gathered plus information regarding the inventory (as in current slot and max slots).

There we would set a slotLimit variable. Retrieve the stack and retrieve the total size of the item type.

Then, we'd iterate over the item until we inserted all items of this type. (While making sure not to dump into the same slot again since then we'd lose items).

Consequently, we would increase the temporary size, decrement the required slots and decrease the creative tab information.

Finally, after we finished inserting an item we would check if this was the last item of this creative tab. If this is the case we would check on some metric how well we distributed the item over all inventories we have (comparing on how many slots we filled until now compared to how many items we have and how many slots we have available).

And based on that calculate where the next inventory would start.

The End:
I hoped you liked the more technical post today. I can promise you that the next post is going to be a bit more exciting and less backend.

Nevertheless, I had a lot of fun coding this and I bet it's going to make the players lives a lot easier.

See you the next time!

Repository:
https://github.com/ldtteam/minecolonies

Pull Request:
https://github.com/ldtteam/minecolonies/pull/2999

Coin Marketplace

STEEM 0.51
TRX 0.09
JST 0.069
BTC 50106.56
ETH 4389.01
BNB 604.67
SBD 6.15