diff --git a/src/generated/resources/assets/occultism/lang/en_us.json b/src/generated/resources/assets/occultism/lang/en_us.json index f6422011d..e17ff1c3d 100644 --- a/src/generated/resources/assets/occultism/lang/en_us.json +++ b/src/generated/resources/assets/occultism/lang/en_us.json @@ -134,13 +134,13 @@ "book.occultism.dictionary_of_spirits.crafting_rituals.craft_soul_gem.usage.text": "To capture an entity, [#](ad03fc)right-click[#]() it with the soul gem. \\\n[#](ad03fc)Right-click[#]() again to release the entity.\n\\\n\\\nBosses cannot be captured.\n", "book.occultism.dictionary_of_spirits.crafting_rituals.craft_soul_gem.usage.title": "Usage", "book.occultism.dictionary_of_spirits.crafting_rituals.craft_stabilizer_tier1.name": "Storage Stabilizer Tier 1", - "book.occultism.dictionary_of_spirits.crafting_rituals.craft_stabilizer_tier1.spotlight.text": "This simple storage stabilizer is inhabited by a [#](ad03fc)Foliot[#]() that supports the dimensional matrix in keeping the storage dimension stable, thus allowing to store more items.\n\\\n\\\nBy default each Tier 1 Stabilizer adds **256** slots.\n", + "book.occultism.dictionary_of_spirits.crafting_rituals.craft_stabilizer_tier1.spotlight.text": "This simple storage stabilizer is inhabited by a [#](ad03fc)Foliot[#]() that supports the dimensional matrix in keeping the storage dimension stable, thus allowing to store more items.\n\\\n\\\nBy default each Tier 1 Stabilizer adds **64** item types and 512000 items storage capacity.\n", "book.occultism.dictionary_of_spirits.crafting_rituals.craft_stabilizer_tier2.name": "Storage Stabilizer Tier 2", - "book.occultism.dictionary_of_spirits.crafting_rituals.craft_stabilizer_tier2.spotlight.text": "This improved stabilizer is inhabited by a [#](ad03fc)Djinni[#]() that supports the dimensional matrix in keeping the storage dimension stable, thus allowing to store even more items.\n\\\n\\\nBy default each Tier 2 Stabilizer adds **512** slots.\n", + "book.occultism.dictionary_of_spirits.crafting_rituals.craft_stabilizer_tier2.spotlight.text": "This improved stabilizer is inhabited by a [#](ad03fc)Djinni[#]() that supports the dimensional matrix in keeping the storage dimension stable, thus allowing to store even more items.\n\\\n\\\nBy default each Tier 2 Stabilizer adds **128** item types and 1024000 items storage capacity.\n", "book.occultism.dictionary_of_spirits.crafting_rituals.craft_stabilizer_tier3.name": "Storage Stabilizer Tier 3", - "book.occultism.dictionary_of_spirits.crafting_rituals.craft_stabilizer_tier3.spotlight.text": "This advanced stabilizer is inhabited by an [#](ad03fc)Afrit[#]() that supports the dimensional matrix in keeping the storage dimension stable, thus allowing to store even more items.\n\\\n\\\nBy default each Tier 3 Stabilizer adds **1024** slots.\n", + "book.occultism.dictionary_of_spirits.crafting_rituals.craft_stabilizer_tier3.spotlight.text": "This advanced stabilizer is inhabited by an [#](ad03fc)Afrit[#]() that supports the dimensional matrix in keeping the storage dimension stable, thus allowing to store even more items.\n\\\n\\\nBy default each Tier 3 Stabilizer adds **256** item types and 2048000 items storage capacity.\n", "book.occultism.dictionary_of_spirits.crafting_rituals.craft_stabilizer_tier4.name": "Storage Stabilizer Tier 4", - "book.occultism.dictionary_of_spirits.crafting_rituals.craft_stabilizer_tier4.spotlight.text": "This highly advanced stabilizer is inhabited by a [#](ad03fc)Marid[#]() that supports the dimensional matrix in keeping the storage dimension stable, thus allowing to store even more items.\n\\\n\\\nBy default each Tier 4 Stabilizer adds **2048** slots.\n", + "book.occultism.dictionary_of_spirits.crafting_rituals.craft_stabilizer_tier4.spotlight.text": "This highly advanced stabilizer is inhabited by a [#](ad03fc)Marid[#]() that supports the dimensional matrix in keeping the storage dimension stable, thus allowing to store even more items.\n\\\n\\\nBy default each Tier 4 Stabilizer adds **512** item types and 4098000 items storage capacity.\n", "book.occultism.dictionary_of_spirits.crafting_rituals.craft_stable_wormhole.name": "Stable Wormhole", "book.occultism.dictionary_of_spirits.crafting_rituals.craft_stable_wormhole.spotlight.text": "The stable wormhole allows access to a dimensional matrix from a remote destination.\n\\\n\\\nShift-click a [](item://occultism:storage_controller) to link it, then place the wormhole in the world to use it as a remote access point.\n", "book.occultism.dictionary_of_spirits.crafting_rituals.craft_storage_controller_base.name": "Storage Actuator Base", @@ -672,19 +672,19 @@ "book.occultism.dictionary_of_spirits.storage.overview.intro.title": "Magic Storage", "book.occultism.dictionary_of_spirits.storage.overview.intro2.text": "Follow the steps shown in this category to get your own storage system!\nThe steps related to storage in [Binding Rituals](category://crafting_rituals/) show only the rituals, while here **all required steps** including crafting are shown.\n", "book.occultism.dictionary_of_spirits.storage.overview.name": "Magic Storage", - "book.occultism.dictionary_of_spirits.storage.storage_controller.config.text": "Slot amount and slot size can be configured in the \"[#](ad03fc)occultism-server.toml[#]()\" config file in the save directory of your world.\n\\\n\\\nIncreasing slot size does not impact performance, increasing slot amount (by a lot) can have a negative impact on performance.\n", + "book.occultism.dictionary_of_spirits.storage.storage_controller.config.text": "The item type amount and storage size can be configured in the \"[#](ad03fc)occultism-server.toml[#]()\" config file in the save directory of your world.\n", "book.occultism.dictionary_of_spirits.storage.storage_controller.config.title": "Configurablity", "book.occultism.dictionary_of_spirits.storage.storage_controller.intro.text": "The [](item://occultism:storage_controller) consists of a [Dimensional Matrix](entry://crafting_rituals/craft_dimensional_matrix) inhabited by a [#](ad03fc)Djinni[#]() that creates and manages a storage dimension, and a [Base](entry://crafting_rituals/craft_storage_controller_base) infused with a [#](ad03fc)Foliot[#]() that moves items in and out of the storage dimension.\n", "book.occultism.dictionary_of_spirits.storage.storage_controller.intro.title": "Storage Actuator", - "book.occultism.dictionary_of_spirits.storage.storage_controller.mods.text": "For other mods the storage controller behaves like a shulker box, anything that can interact with vanilla chests and shulker boxes can interact with the storage controller.\nDevices that count storage contents may have trouble with the stack sizes, if you run into this issue have your server admin set [this option](https://github.com/klikli-dev/occultism/issues/221#issuecomment-944904459).\n", + "book.occultism.dictionary_of_spirits.storage.storage_controller.mods.text": "For other mods the storage controller behaves like a shulker box, anything that can interact with vanilla chests and shulker boxes can interact with the storage controller.\nDevices that count storage contents may have trouble with the stack sizes.\n", "book.occultism.dictionary_of_spirits.storage.storage_controller.mods.title": "Interaction with Mods", "book.occultism.dictionary_of_spirits.storage.storage_controller.name": "Storage Actuator", "book.occultism.dictionary_of_spirits.storage.storage_controller.recipe.text": "This is the actual block that works as a storage, make sure to craft it!\nPlacing just the [](item://occultism:storage_controller_base) from the previous step won't work.\n", "book.occultism.dictionary_of_spirits.storage.storage_controller.safety.text": "Breaking the storage controller will store all contained items in the dropped item, you will not lose anything.\nThe same applies to breaking or replacing Storage Stabilizers (you will learn about these later).\n\\\n\\\nLike in a shulker box, your items are safe!\n", "book.occultism.dictionary_of_spirits.storage.storage_controller.safety.title": "Safety first!", - "book.occultism.dictionary_of_spirits.storage.storage_controller.size.text": "The storage controller by default provides **128** slots (_You will learn later how to increase that_). Each slot can hold up to **1024** items, even items that usually have smaller stack sizes or are not stackable at all.\n", + "book.occultism.dictionary_of_spirits.storage.storage_controller.size.text": "The storage controller holds up to **128** different types of items (_You will learn later how to increase that_). Additionally it is limited to 256000 items in total. It does not matter if you have 256000 different items or 256000 of one item, or any mix.\n", "book.occultism.dictionary_of_spirits.storage.storage_controller.size.title": "So much storage!", - "book.occultism.dictionary_of_spirits.storage.storage_controller.unique_items.text": "The only exception to the increased stack size are **items with unique properties** (\"NBT data\"), such as damaged equipment, which cannot stack at all and will take up a full slot. For optimal storage results you should limit the amount of these items in your system.\n", + "book.occultism.dictionary_of_spirits.storage.storage_controller.unique_items.text": "Items with unique properties (\"NBT data\"), such as damaged or enchanted equipment will take up one item type for each variation. For example two wooden swords with two different damage values take up two item types. Two wooden swords with the same (or no) damage take up one item type.\n", "book.occultism.dictionary_of_spirits.storage.storage_controller.unique_items.title": "Unique Items", "book.occultism.dictionary_of_spirits.storage.storage_controller.usage.text": "After crafting the [](item://occultism:storage_controller) (see following pages), place it in the world and [#](ad03fc)right-click[#]() it with an empty hand. This will open the GUI of the storage controller, from there on it will work much like a very big shulker box.\n", "book.occultism.dictionary_of_spirits.storage.storage_controller.usage.title": "Usage", @@ -693,7 +693,7 @@ "book.occultism.dictionary_of_spirits.storage.storage_stabilizer.demo.text": "**Note:** You do not need all 4 stabilizers, even one will increase your storage.\n", "book.occultism.dictionary_of_spirits.storage.storage_stabilizer.demo.title": "Storage Stabilizer Setup", "book.occultism.dictionary_of_spirits.storage.storage_stabilizer.name": "Extending Storage", - "book.occultism.dictionary_of_spirits.storage.storage_stabilizer.spotlight.text": "Storage Stabilizers increase the storage space in the storage dimension of the storage actuator. The higher the tier of the stabilizer, the more additional storage slots it provides. The following entries will show you how to craft each tier.\n\\\n\\\n", + "book.occultism.dictionary_of_spirits.storage.storage_stabilizer.spotlight.text": "Storage Stabilizers increase the storage space in the storage dimension of the storage actuator. The higher the tier of the stabilizer, the more additional storage it provides. The following entries will show you how to craft each tier.\n\\\n\\\n", "book.occultism.dictionary_of_spirits.storage.storage_stabilizer.upgrade.text": "It is **safe to destroy a storage stabilizer** to upgrade it. The items in the [Storage Actuator](entry://storage/storage_controller) will not be lost or dropped - you simply cannot add new items until you add enough storage stabilizers to have free slots again.\n", "book.occultism.dictionary_of_spirits.storage.storage_stabilizer.upgrade.title": "Upgrading", "book.occultism.dictionary_of_spirits.summoning_rituals.afrit_essence.intro.text": "[](item://occultism:afrit_essence) is required to safely call on the more powerful spirits, commonly used in the form of red chalk. To obtain the essence, an [#](ad03fc)Afrit[#]() needs to be summoned unbound into this plane, and killed. Be warned that this is no simple endeavour, and unbound spirit presents great danger to all nearby.\n", @@ -882,6 +882,8 @@ "gui.occultism.storage_controller.search.tooltip_sort_type_name": "Sort by item name.", "gui.occultism.storage_controller.shift": "Hold shift for more information.", "gui.occultism.storage_controller.space_info_label": "%d/%d", + "gui.occultism.storage_controller.space_info_label_new": "%s%% filled", + "gui.occultism.storage_controller.space_info_label_types": "%s%% of types", "item.minecraft.diamond_sword.occultism_spirit_tooltip": "%s is bound to this sword. May your foes tremor before its glory.", "item.occultism.afrit_essence": "Afrit Essence", "item.occultism.awakened_feather": "Awakened Feather", diff --git a/src/generated/resources/data/occultism/modonomicon/books/dictionary_of_spirits/categories/crafting_rituals.json b/src/generated/resources/data/occultism/modonomicon/books/dictionary_of_spirits/categories/crafting_rituals.json index 41064a306..a76ab41c7 100644 --- a/src/generated/resources/data/occultism/modonomicon/books/dictionary_of_spirits/categories/crafting_rituals.json +++ b/src/generated/resources/data/occultism/modonomicon/books/dictionary_of_spirits/categories/crafting_rituals.json @@ -4,6 +4,10 @@ "background_parallax_layers": [], "background_texture_zoom_multiplier": 1.0, "background_width": 512, + "condition": { + "type": "modonomicon:entry_read", + "entry_id": "occultism:getting_started/intro" + }, "entry_textures": "modonomicon:textures/gui/entry_textures.png", "icon": { "height": 16, diff --git a/src/generated/resources/data/occultism/modonomicon/books/dictionary_of_spirits/categories/familiar_rituals.json b/src/generated/resources/data/occultism/modonomicon/books/dictionary_of_spirits/categories/familiar_rituals.json index 1409de09b..d7e2f56ab 100644 --- a/src/generated/resources/data/occultism/modonomicon/books/dictionary_of_spirits/categories/familiar_rituals.json +++ b/src/generated/resources/data/occultism/modonomicon/books/dictionary_of_spirits/categories/familiar_rituals.json @@ -4,6 +4,10 @@ "background_parallax_layers": [], "background_texture_zoom_multiplier": 1.0, "background_width": 512, + "condition": { + "type": "modonomicon:entry_read", + "entry_id": "occultism:getting_started/intro" + }, "entry_textures": "modonomicon:textures/gui/entry_textures.png", "icon": { "height": 16, diff --git a/src/generated/resources/data/occultism/modonomicon/books/dictionary_of_spirits/categories/possession_rituals.json b/src/generated/resources/data/occultism/modonomicon/books/dictionary_of_spirits/categories/possession_rituals.json index e2ef4b31e..66d42ef89 100644 --- a/src/generated/resources/data/occultism/modonomicon/books/dictionary_of_spirits/categories/possession_rituals.json +++ b/src/generated/resources/data/occultism/modonomicon/books/dictionary_of_spirits/categories/possession_rituals.json @@ -4,6 +4,10 @@ "background_parallax_layers": [], "background_texture_zoom_multiplier": 1.0, "background_width": 512, + "condition": { + "type": "modonomicon:entry_read", + "entry_id": "occultism:getting_started/intro" + }, "entry_textures": "modonomicon:textures/gui/entry_textures.png", "icon": { "height": 16, diff --git a/src/generated/resources/data/occultism/modonomicon/books/dictionary_of_spirits/categories/spirits.json b/src/generated/resources/data/occultism/modonomicon/books/dictionary_of_spirits/categories/spirits.json index 2a373ef2c..c90cc7f2d 100644 --- a/src/generated/resources/data/occultism/modonomicon/books/dictionary_of_spirits/categories/spirits.json +++ b/src/generated/resources/data/occultism/modonomicon/books/dictionary_of_spirits/categories/spirits.json @@ -4,6 +4,10 @@ "background_parallax_layers": [], "background_texture_zoom_multiplier": 1.0, "background_width": 512, + "condition": { + "type": "modonomicon:entry_read", + "entry_id": "occultism:getting_started/intro" + }, "entry_textures": "modonomicon:textures/gui/entry_textures.png", "icon": { "height": 16, diff --git a/src/generated/resources/data/occultism/modonomicon/books/dictionary_of_spirits/categories/summoning_rituals.json b/src/generated/resources/data/occultism/modonomicon/books/dictionary_of_spirits/categories/summoning_rituals.json index 0698bae03..c5cdb303f 100644 --- a/src/generated/resources/data/occultism/modonomicon/books/dictionary_of_spirits/categories/summoning_rituals.json +++ b/src/generated/resources/data/occultism/modonomicon/books/dictionary_of_spirits/categories/summoning_rituals.json @@ -4,6 +4,10 @@ "background_parallax_layers": [], "background_texture_zoom_multiplier": 1.0, "background_width": 512, + "condition": { + "type": "modonomicon:entry_read", + "entry_id": "occultism:getting_started/intro" + }, "entry_textures": "modonomicon:textures/gui/entry_textures.png", "icon": { "height": 16, diff --git a/src/main/java/com/klikli_dev/occultism/api/client/gui/IStorageControllerGui.java b/src/main/java/com/klikli_dev/occultism/api/client/gui/IStorageControllerGui.java index d8980135a..16169e8f4 100644 --- a/src/main/java/com/klikli_dev/occultism/api/client/gui/IStorageControllerGui.java +++ b/src/main/java/com/klikli_dev/occultism/api/client/gui/IStorageControllerGui.java @@ -31,9 +31,9 @@ public interface IStorageControllerGui { //region Getter / Setter void setStacks(List stacks); - void setUsedSlots(int slots); + void setUsedStorageSize(int usedItemTypes, long usedTotalItemCount); - void setMaxSlots(int slots); + void setMaxStorageSize(int maxItemTypes, long maxTotalItemCount); void markDirty(); diff --git a/src/main/java/com/klikli_dev/occultism/api/common/blockentity/IStorageController.java b/src/main/java/com/klikli_dev/occultism/api/common/blockentity/IStorageController.java index 77613d588..fa18e10d3 100644 --- a/src/main/java/com/klikli_dev/occultism/api/common/blockentity/IStorageController.java +++ b/src/main/java/com/klikli_dev/occultism/api/common/blockentity/IStorageController.java @@ -54,17 +54,19 @@ public interface IStorageController { /** * @return the max slots available in this storage controller. */ - int getMaxSlots(); + int getMaxItemTypes(); /** - * @param slots the max slots available in the storage controller. + * + * @param maxItemTypes the maximum amount of different items the controller can hold. + * @param maxTotalItemCount the maximum total amount of items (all item types added up) the controller can hold. */ - void setMaxSlots(int slots); + void setStorageLimits(int maxItemTypes, long maxTotalItemCount); /** * @return the used up slots. Usually lazily updated when calling getStacks. */ - int getUsedSlots(); + int getUsedItemTypes(); /** * @return a list of block entity references for the machines liked for autocrafting diff --git a/src/main/java/com/klikli_dev/occultism/client/gui/storage/StorageControllerGuiBase.java b/src/main/java/com/klikli_dev/occultism/client/gui/storage/StorageControllerGuiBase.java index d0fcfcca1..e15cb2406 100644 --- a/src/main/java/com/klikli_dev/occultism/client/gui/storage/StorageControllerGuiBase.java +++ b/src/main/java/com/klikli_dev/occultism/client/gui/storage/StorageControllerGuiBase.java @@ -81,8 +81,10 @@ public abstract class StorageControllerGuiBase stacks; public List linkedMachines; public IStorageControllerContainer storageControllerContainer; - public int usedSlots; - public int maxSlots; + protected int maxItemTypes; + protected int usedItemTypes; + protected long maxTotalItemCount; + protected long usedTotalItemCount; public StorageControllerGuiMode guiMode = StorageControllerGuiMode.INVENTORY; protected ItemStack stackUnderMouse = ItemStack.EMPTY; protected EditBox searchBar; @@ -96,6 +98,7 @@ public abstract class StorageControllerGuiBase stacks) { } @Override - public void setUsedSlots(int slots) { - this.usedSlots = slots; + public void setMaxStorageSize(int maxItemTypes, long maxTotalItemCount) { + this.maxItemTypes = maxItemTypes; + this.maxTotalItemCount = maxTotalItemCount; } @Override - public void markDirty() { - this.init(); + public void setUsedStorageSize(int usedItemTypes, long usedTotalItemCount) { + this.usedItemTypes = usedItemTypes; + this.usedTotalItemCount = usedTotalItemCount; } @Override - public void setMaxSlots(int slots) { - this.maxSlots = slots; + public void markDirty() { + this.init(); } + @Override public void setLinkedMachines(List machines) { this.linkedMachines = machines; @@ -258,8 +264,18 @@ public void init() { new LabelWidget(this.leftPos + storageSpaceInfoLabelLeft, this.topPos + storageSpaceInfoLabelTop, true, -1, 2, 0x404040); this.storageSpaceLabel - .addLine(I18n.get(TRANSLATION_KEY_BASE + ".space_info_label", this.usedSlots, this.maxSlots), false); + .addLine(I18n.get(TRANSLATION_KEY_BASE + ".space_info_label_new", + String.format("%.2f", (double)this.usedTotalItemCount / (double)this.maxTotalItemCount * 100) + + ), false); this.addRenderableWidget(this.storageSpaceLabel); + + this.storageTypesLabel = + new LabelWidget(this.leftPos + storageSpaceInfoLabelLeft - 7, this.topPos + storageSpaceInfoLabelTop + 40, true, + -1, 2, 0x404040); + this.storageTypesLabel + .addLine(I18n.get(TRANSLATION_KEY_BASE + ".space_info_label_types", String.format("%.0f", (double)this.usedItemTypes / (double)this.maxItemTypes * 100)), false); + this.addRenderableWidget(this.storageTypesLabel); this.initButtons(); } diff --git a/src/main/java/com/klikli_dev/occultism/common/blockentity/StorageControllerBlockEntity.java b/src/main/java/com/klikli_dev/occultism/common/blockentity/StorageControllerBlockEntity.java index 6471fa3a5..1db6c05ba 100644 --- a/src/main/java/com/klikli_dev/occultism/common/blockentity/StorageControllerBlockEntity.java +++ b/src/main/java/com/klikli_dev/occultism/common/blockentity/StorageControllerBlockEntity.java @@ -38,7 +38,8 @@ import com.klikli_dev.occultism.common.entity.spirit.SpiritEntity; import com.klikli_dev.occultism.common.misc.DepositOrder; import com.klikli_dev.occultism.common.misc.ItemStackComparator; -import com.klikli_dev.occultism.common.misc.StorageControllerItemStackHandler; +import com.klikli_dev.occultism.common.misc.StorageControllerMapItemStackHandler; +import com.klikli_dev.occultism.datafixer.StorageControllerMapItemStackHandlerDataFixer; import com.klikli_dev.occultism.network.messages.MessageUpdateStacks; import com.klikli_dev.occultism.registry.OccultismBlockEntities; import com.klikli_dev.occultism.registry.OccultismBlocks; @@ -63,7 +64,6 @@ import net.minecraft.world.level.block.DirectionalBlock; import net.minecraft.world.level.block.state.BlockState; import net.neoforged.neoforge.items.ItemHandlerHelper; -import net.neoforged.neoforge.items.ItemStackHandler; import net.neoforged.neoforge.registries.DeferredBlock; import software.bernie.geckolib.animatable.GeoBlockEntity; import software.bernie.geckolib.core.animatable.instance.AnimatableInstanceCache; @@ -92,15 +92,17 @@ public class StorageControllerBlockEntity extends NetworkedBlockEntity implement public ItemStack orderStack = ItemStack.EMPTY; public Map linkedMachines = new HashMap<>(); public Map depositOrderSpirits = new HashMap<>(); - public ItemStackHandler itemStackHandler = new StorageControllerItemStackHandler(this, - Occultism.SERVER_CONFIG.storage.controllerBaseSlots.get(), - Occultism.SERVER_CONFIG.storage.controllerStackSize.get(), - Occultism.SERVER_CONFIG.storage.overrideItemStackSizes.get() + public StorageControllerMapItemStackHandler itemStackHandler = new StorageControllerMapItemStackHandler(this, + Occultism.SERVER_CONFIG.storage.controllerMaxItemTypes.get(), + Occultism.SERVER_CONFIG.storage.controllerMaxTotalItemCount.get() ); protected SortDirection sortDirection = SortDirection.DOWN; protected SortType sortType = SortType.AMOUNT; - protected int maxSlots = Occultism.SERVER_CONFIG.storage.controllerBaseSlots.get(); - protected int usedSlots = 0; + protected int maxItemTypes = Occultism.SERVER_CONFIG.storage.controllerMaxItemTypes.get(); + protected int usedItemTypes = 0; + protected long maxTotalItemCount = Occultism.SERVER_CONFIG.storage.controllerMaxTotalItemCount.get(); + protected long usedTotalItemCount = 0; + protected boolean stabilizersInitialized = false; protected GlobalBlockPos globalPos; protected MessageUpdateStacks cachedMessageUpdateStacks; @@ -119,13 +121,15 @@ public void tick() { } public void updateStabilizers() { - int additionalSlots = 0; + int additionalMaxItemTypes = 0; + long additionalTotalItemCount = 0; List stabilizerLocations = this.findValidStabilizers(); for (BlockPos pos : stabilizerLocations) { - additionalSlots += this.getSlotsForStabilizer(this.level.getBlockState(pos)); + additionalMaxItemTypes += this.getAdditionalMaxItemTypesForStabilizer(this.level.getBlockState(pos)); + additionalTotalItemCount += this.getAdditionalMaxTotalItemCountForStabilizer(this.level.getBlockState(pos)); } - this.setMaxSlots(Occultism.SERVER_CONFIG.storage.controllerBaseSlots.get() + additionalSlots); + this.setStorageLimits(Occultism.SERVER_CONFIG.storage.controllerMaxItemTypes.get() + additionalMaxItemTypes, Occultism.SERVER_CONFIG.storage.controllerMaxTotalItemCount.get() + additionalTotalItemCount); } public List findValidStabilizers() { @@ -148,31 +152,30 @@ public List findValidStabilizers() { return validStabilizers; } - protected int getSlotsForStabilizer(BlockState state) { + protected int getAdditionalMaxItemTypesForStabilizer(BlockState state) { Block block = state.getBlock(); if (block == OccultismBlocks.STORAGE_STABILIZER_TIER1.get()) - return Occultism.SERVER_CONFIG.storage.stabilizerTier1Slots.get(); + return Occultism.SERVER_CONFIG.storage.stabilizerTier1AdditionalMaxItemTypes.get(); if (block == OccultismBlocks.STORAGE_STABILIZER_TIER2.get()) - return Occultism.SERVER_CONFIG.storage.stabilizerTier2Slots.get(); + return Occultism.SERVER_CONFIG.storage.stabilizerTier2AdditionalMaxItemTypes.get(); if (block == OccultismBlocks.STORAGE_STABILIZER_TIER3.get()) - return Occultism.SERVER_CONFIG.storage.stabilizerTier3Slots.get(); + return Occultism.SERVER_CONFIG.storage.stabilizerTier3AdditionalMaxItemTypes.get(); if (block == OccultismBlocks.STORAGE_STABILIZER_TIER4.get()) - return Occultism.SERVER_CONFIG.storage.stabilizerTier4Slots.get(); + return Occultism.SERVER_CONFIG.storage.stabilizerTier4AdditionalMaxItemTypes.get(); return 0; } - protected void mergeIntoList(List list, ItemStack stackToAdd) { - boolean merged = false; - for (ItemStack stack : list) { - if (ItemHandlerHelper.canItemStacksStack(stackToAdd, stack)) { - stack.setCount(stack.getCount() + stackToAdd.getCount()); - merged = true; - break; - } - } - if (!merged) { - list.add(stackToAdd); - } + protected long getAdditionalMaxTotalItemCountForStabilizer(BlockState state) { + Block block = state.getBlock(); + if (block == OccultismBlocks.STORAGE_STABILIZER_TIER1.get()) + return Occultism.SERVER_CONFIG.storage.stabilizerTier1AdditionalMaxTotalItemCount.get(); + if (block == OccultismBlocks.STORAGE_STABILIZER_TIER2.get()) + return Occultism.SERVER_CONFIG.storage.stabilizerTier2AdditionalMaxTotalItemCount.get(); + if (block == OccultismBlocks.STORAGE_STABILIZER_TIER3.get()) + return Occultism.SERVER_CONFIG.storage.stabilizerTier3AdditionalMaxTotalItemCount.get(); + if (block == OccultismBlocks.STORAGE_STABILIZER_TIER4.get()) + return Occultism.SERVER_CONFIG.storage.stabilizerTier4AdditionalMaxTotalItemCount.get(); + return 0; } protected void validateLinkedMachines() { @@ -259,18 +262,14 @@ public void setSortType(SortType sortType) { @Override public List getStacks() { - ItemStackHandler handler = this.itemStackHandler; - int size = handler.getSlots(); - int usedSlots = 0; - List result = new ArrayList<>(size); - for (int slot = 0; slot < size; slot++) { - ItemStack stack = handler.getStackInSlot(slot); - if (!stack.isEmpty()) { - usedSlots++; - this.mergeIntoList(result, stack.copy()); - } + + List result = new ArrayList<>(this.itemStackHandler.getSlots()); + for (var entry : this.itemStackHandler.keyToCountMap().object2IntEntrySet()) { + result.add(entry.getKey().stack().copyWithCount(entry.getIntValue())); } - this.usedSlots = usedSlots; + + this.usedItemTypes = this.itemStackHandler.getSlots(); + this.usedTotalItemCount = this.itemStackHandler.totalItemCount(); return result; } @@ -278,28 +277,31 @@ public List getStacks() { public MessageUpdateStacks getMessageUpdateStacks() { if (this.cachedMessageUpdateStacks == null) { List stacks = this.getStacks(); - this.cachedMessageUpdateStacks = new MessageUpdateStacks(stacks, this.getUsedSlots(), this.getMaxSlots()); + this.cachedMessageUpdateStacks = new MessageUpdateStacks(stacks, this.maxItemTypes, this.usedItemTypes, + this.maxTotalItemCount, this.usedTotalItemCount); } return this.cachedMessageUpdateStacks; } @Override - public int getMaxSlots() { - return this.maxSlots; + public int getMaxItemTypes() { + return this.maxItemTypes; } @Override - public void setMaxSlots(int slots) { - this.maxSlots = slots; - this.itemStackHandler.setSize(this.maxSlots); + public void setStorageLimits(int maxItemTypes, long maxTotalItemCount) { + this.maxItemTypes = maxItemTypes; + this.maxTotalItemCount = maxTotalItemCount; + this.itemStackHandler.maxItemTypes(this.maxItemTypes); + this.itemStackHandler.maxTotalItemCount(this.maxTotalItemCount); //force resync this.cachedMessageUpdateStacks = null; this.markNetworkDirty(); } @Override - public int getUsedSlots() { - return this.usedSlots; + public int getUsedItemTypes() { + return this.usedItemTypes; } @Override @@ -367,9 +369,8 @@ public int insertStack(ItemStack stack, boolean simulate) { if (this.isBlacklisted(stack)) return stack.getCount(); - ItemStackHandler handler = this.itemStackHandler; - if (ItemHandlerHelper.insertItem(handler, stack, true).getCount() < stack.getCount()) { - stack = ItemHandlerHelper.insertItem(handler, stack, simulate); + if (this.itemStackHandler.insertItem(stack, true).getCount() < stack.getCount()) { + stack = this.itemStackHandler.insertItem(stack, simulate); } return stack.getCount(); @@ -383,22 +384,20 @@ public ItemStack getOneOfMostCommonItem(Predicate comparator, boolean var comparators = this.getComparatorsSortedByAmount(comparator); - ItemStackHandler handler = this.itemStackHandler; - //we start with the comparator representing the most common item, and if we don't find anything we move on. //Note: unless something weird happens we should always find something. for (var currentComparator : comparators) { - for (int slot = 0; slot < handler.getSlots(); slot++) { + for (int slot = 0; slot < this.itemStackHandler.getSlots(); slot++) { //first we force a simulation to check if the stack fits - ItemStack stack = handler.extractItem(slot, 1, true); + ItemStack stack = this.itemStackHandler.extractItem(slot, 1, true); if (stack.isEmpty()) { continue; } if (currentComparator.test(stack)) { //now we do the actual operation (note: can still be a simulation, if caller wants to simulate= - return handler.extractItem(slot, 1, simulate); + return this.itemStackHandler.extractItem(slot, 1, simulate); } //this slot does not match so we move on in the loop. @@ -414,13 +413,18 @@ public ItemStack getItemStack(Predicate comparator, int requestedSize if (requestedSize <= 0 || comparator == null) { return ItemStack.EMPTY; } - ItemStackHandler handler = this.itemStackHandler; + + //Shortcut for exact matches + if (comparator instanceof ItemStackComparator itemStackComparator && itemStackComparator.getMatchNbt()) { + return this.itemStackHandler.extractItem(itemStackComparator.getFilterStack(), requestedSize, simulate); + } + ItemStack firstMatchedStack = ItemStack.EMPTY; int remaining = requestedSize; - for (int slot = 0; slot < handler.getSlots(); slot++) { + for (int slot = 0; slot < this.itemStackHandler.getSlots(); slot++) { //first we force a simulation - ItemStack stack = handler.extractItem(slot, remaining, true); + ItemStack stack = this.itemStackHandler.extractItem(slot, remaining, true); if (stack.isEmpty()) { continue; } @@ -445,7 +449,7 @@ public ItemStack getItemStack(Predicate comparator, int requestedSize int toExtract = Math.min(stack.getCount(), remaining); //now we can leave simulation up to the caller - ItemStack extractedStack = handler.extractItem(slot, toExtract, simulate); + ItemStack extractedStack = this.itemStackHandler.extractItem(slot, toExtract, simulate); remaining -= extractedStack.getCount(); //if we got all we need we can exit here. @@ -467,11 +471,17 @@ public int getAvailableAmount(IItemStackComparator comparator) { if (comparator == null) { return 0; } + + //Shortcut for exact matches + if (comparator instanceof ItemStackComparator itemStackComparator && itemStackComparator.getMatchNbt()) { + return this.itemStackHandler.get(itemStackComparator.getFilterStack()); + } + int totalCount = 0; - ItemStackHandler handler = this.itemStackHandler; - int size = handler.getSlots(); + + int size = this.itemStackHandler.getSlots(); for (int slot = 0; slot < size; slot++) { - ItemStack stack = handler.getStackInSlot(slot); + ItemStack stack = this.itemStackHandler.getStackInSlot(slot); if (comparator.matches(stack)) totalCount += stack.getCount(); } @@ -491,7 +501,11 @@ public void load(CompoundTag compound) { //read stored items if (compound.contains("items")) { - this.itemStackHandler.deserializeNBT(compound.getCompound("items")); + var items = compound.getCompound("items"); + if (StorageControllerMapItemStackHandlerDataFixer.needsFixing(items)) { + items = StorageControllerMapItemStackHandlerDataFixer.fix(items); + } + this.itemStackHandler.deserializeNBT(items); this.cachedMessageUpdateStacks = null; } } @@ -508,8 +522,8 @@ public void loadNetwork(CompoundTag compound) { this.setSortDirection(SortDirection.get(compound.getInt("sortDirection"))); this.setSortType(SortType.get(compound.getInt("sortType"))); - if (compound.contains("maxSlots")) { - this.setMaxSlots(compound.getInt("maxSlots")); + if (compound.contains("maxItemTypes") && compound.contains("maxTotalItemCount")) { + this.setStorageLimits(compound.getInt("maxItemTypes"), compound.getLong("maxTotalItemCount")); } //read stored crafting matrix @@ -542,7 +556,8 @@ public void loadNetwork(CompoundTag compound) { public CompoundTag saveNetwork(CompoundTag compound) { compound.putInt("sortDirection", this.getSortDirection().getValue()); compound.putInt("sortType", this.getSortType().getValue()); - compound.putInt("maxSlots", this.maxSlots); + compound.putInt("maxItemTypes", this.maxItemTypes); + compound.putLong("maxTotalItemCount", this.maxTotalItemCount); //write stored crafting matrix ListTag matrixNbt = new ListTag(); diff --git a/src/main/java/com/klikli_dev/occultism/common/misc/IMapItemHandlerModifiable.java b/src/main/java/com/klikli_dev/occultism/common/misc/IMapItemHandlerModifiable.java new file mode 100644 index 000000000..840f56684 --- /dev/null +++ b/src/main/java/com/klikli_dev/occultism/common/misc/IMapItemHandlerModifiable.java @@ -0,0 +1,68 @@ +package com.klikli_dev.occultism.common.misc; + +import net.minecraft.world.item.ItemStack; +import net.neoforged.neoforge.fluids.FluidStack; +import net.neoforged.neoforge.fluids.capability.IFluidHandler; +import net.neoforged.neoforge.items.IItemHandlerModifiable; +import org.jetbrains.annotations.NotNull; + +public interface IMapItemHandlerModifiable extends IItemHandlerModifiable { + + + /** + * Gets the amount of items in the slot that match the given key (exact match including nbt/components!) + */ + int get(ItemStackKey key); + + /** + * Gets the amount of items in the slot that match the given stack (exact match/components!) + */ + int get(ItemStack stack); + + /** + *

+ * Inserts an ItemStack into the fitting slot and return the remainder. + * The ItemStack should not be modified in this function! + *

+ * Note: This behaviour is subtly different from {@link IFluidHandler#fill(FluidStack, IFluidHandler.FluidAction)} + * + * @param stack ItemStack to insert. This must not be modified by the item handler. + * @param simulate If true, the insertion is only simulated + * @return The remaining ItemStack that was not inserted (if the entire stack is accepted, then return an empty ItemStack). + * May be the same as the input ItemStack if unchanged, otherwise a new ItemStack. + * The returned ItemStack can be safely modified after. + **/ + @NotNull ItemStack insertItem(@NotNull ItemStack stack, boolean simulate); + + /** + * Extracts an ItemStack that matches the given key. + *

+ * The returned value must be empty if nothing is extracted, + * otherwise its stack size must be less than or equal to {@code amount} and {@link ItemStack#getMaxStackSize()}. + *

+ * + * @param key The ItemStackKey to find and extract. + * @param amount Amount to extract (may be greater than the current stack's max limit) + * @param simulate If true, the extraction is only simulated + * @return ItemStack extracted from the slot, must be empty if nothing can be extracted. + * The returned ItemStack can be safely modified after, so item handlers should return a new or copied stack. + **/ + @NotNull ItemStack extractItem(ItemStackKey key, int amount, boolean simulate); + + /** + * Extracts an ItemStack that matches the given stack. + *

+ * The returned value must be empty if nothing is extracted, + * otherwise its stack size must be less than or equal to {@code amount} and {@link ItemStack#getMaxStackSize()}. + *

+ * + * @param stack The ItemStack to find and extract. + * @param amount Amount to extract (may be greater than the current stack's max limit) + * @param simulate If true, the extraction is only simulated + * @return ItemStack extracted from the slot, must be empty if nothing can be extracted. + * The returned ItemStack can be safely modified after, so item handlers should return a new or copied stack. + **/ + @NotNull ItemStack extractItem(ItemStack stack, int amount, boolean simulate); + + boolean isItemValid(int slot, @NotNull ItemStackKey key); +} diff --git a/src/main/java/com/klikli_dev/occultism/common/misc/ItemStackKey.java b/src/main/java/com/klikli_dev/occultism/common/misc/ItemStackKey.java new file mode 100644 index 000000000..af24a00b2 --- /dev/null +++ b/src/main/java/com/klikli_dev/occultism/common/misc/ItemStackKey.java @@ -0,0 +1,61 @@ +package com.klikli_dev.occultism.common.misc; + +import com.mojang.serialization.Codec; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.Tag; +import net.minecraft.world.item.ItemStack; + +public record ItemStackKey(ItemStack stack) { + + public static final Codec CODEC = ItemStack.ITEM_WITH_COUNT_CODEC.xmap(ItemStackKey::new, ItemStackKey::stack); + + public static ItemStackKey of(ItemStack stack) { + return new ItemStackKey(stack.copyWithCount(1)); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof ItemStackKey key && ItemStack.isSameItemSameTags(this.stack, key.stack); + } + + @Override + public int hashCode() { + int result = this.stack.getItem().hashCode(); + + if (this.stack.hasTag()) { + result = this.hashCode(this.stack.getTag(), result); + } + + return result; + } + + private int hashCode(Tag tag, int result) { + if (tag instanceof CompoundTag) { + result = this.hashCode((CompoundTag) tag, result); + } else if (tag instanceof ListTag) { + result = this.hashCode((ListTag) tag, result); + } else { + result = 31 * result + tag.hashCode(); + } + + return result; + } + + private int hashCode(CompoundTag tag, int result) { + for (String key : tag.getAllKeys()) { + result = 31 * result + key.hashCode(); + result = this.hashCode(tag.get(key), result); + } + + return result; + } + + private int hashCode(ListTag tag, int result) { + for (Tag tagItem : tag) { + result = this.hashCode(tagItem, result); + } + + return result; + } +} diff --git a/src/main/java/com/klikli_dev/occultism/common/misc/MapItemStackHandler.java b/src/main/java/com/klikli_dev/occultism/common/misc/MapItemStackHandler.java new file mode 100644 index 000000000..e3486d155 --- /dev/null +++ b/src/main/java/com/klikli_dev/occultism/common/misc/MapItemStackHandler.java @@ -0,0 +1,397 @@ +package com.klikli_dev.occultism.common.misc; + +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; +import com.mojang.datafixers.util.Pair; +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtOps; +import net.minecraft.world.item.ItemStack; +import net.neoforged.neoforge.common.util.INBTSerializable; +import net.neoforged.neoforge.fluids.FluidStack; +import net.neoforged.neoforge.fluids.capability.IFluidHandler; +import net.neoforged.neoforge.items.IItemHandler; +import net.neoforged.neoforge.items.IItemHandlerModifiable; +import org.jetbrains.annotations.NotNull; + +import java.util.Map; +import java.util.Stack; +import java.util.stream.Collectors; + +public class MapItemStackHandler implements IItemHandler, IItemHandlerModifiable, IMapItemHandlerModifiable, INBTSerializable { + protected static final int VIRTUAL_SLOT = -1; + + //Note: liliandev (Neo Discord) in response to "not a string" error for our codec when using codec.unboundedmap: + //unboundedMap turns a map into + //{ + //"key1":{data}, + //"key2":{data}, + //[...] + //} + //and ItemStackKey Codec doesn't return things that can be turned into Strings. You would have to codec them as a list of key, value pairs and you can xmap that back to a map + private static final Codec> MAP_CODEC = Codec.list(Codec.pair(ItemStackKey.CODEC.fieldOf("itemStackkey").codec(), Codec.INT.fieldOf("int").codec())) + .xmap( + list -> list.stream().collect(Collectors.toMap(Pair::getFirst, Pair::getSecond)), + map -> map.entrySet().stream().map(e -> Pair.of(e.getKey(), e.getValue())) + .collect(Collectors.toList()) + ); + public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( + MAP_CODEC.fieldOf("keyToCountMap").forGetter(handler -> handler.keyToCountMap), + MAP_CODEC.fieldOf("keyToSlot").forGetter(handler -> handler.keyToSlot), + Codec.INT.listOf().fieldOf("emptySlots").forGetter(handler -> handler.emptySlots), + Codec.INT.fieldOf("nextSlot").forGetter(handler -> handler.nextSlotIndex), + Codec.INT.fieldOf("maxSlots").forGetter(handler -> handler.maxItemTypes), + Codec.LONG.fieldOf("totalItemCount").forGetter(handler -> handler.totalItemCount), + Codec.LONG.fieldOf("maxTotalItemCount").forGetter(handler -> handler.maxTotalItemCount) + ).apply(instance, (keyToCountMap, keyToSlot, emptySlots, nextSlot, maxSlots, totalItemCount, maxTotalItemCount) -> + new MapItemStackHandler(new Object2IntOpenHashMap<>(keyToCountMap), HashBiMap.create(keyToSlot), emptySlots.stream().collect(Collectors.toCollection(Stack::new)), nextSlot, maxSlots, totalItemCount, maxTotalItemCount)) + ); + /** + * The source of truth for contents of this handler. + */ + protected Object2IntOpenHashMap keyToCountMap; + /** + * Slot view for backwards compat with slot based item handlers. + */ + protected BiMap keyToSlot; + /** + * Temporarily store empty slots for reuse. This is necessary if we remove from the middle of keyToSlot. + */ + protected Stack emptySlots; + /** + * The next slot index to use if there are no empty slots. + */ + protected int nextSlotIndex; + /** + * The maximum amount of different item types supported. This effectively is a max slot count due to the 1 slot per item type limit. + */ + protected int maxItemTypes; + /** + * The total amount of items in the handler. + */ + protected long totalItemCount; + /** + * The maximum allowed total amount of items in the handler. + */ + protected long maxTotalItemCount; + + public MapItemStackHandler() { + this(-1, -1); + } + + + public MapItemStackHandler(int maxItemTypes, long maxTotalItemCount) { + this(new Object2IntOpenHashMap<>(), HashBiMap.create(), new Stack<>(), 0, maxItemTypes, 0, maxTotalItemCount); + } + + public MapItemStackHandler(Object2IntOpenHashMap keyToCountMap, BiMap keyToSlot, Stack emptySlots, int nextSlotIndex, int maxItemTypes, long totalItemCount, long maxTotalItemCount) { + this.keyToCountMap = keyToCountMap; + this.keyToSlot = keyToSlot; + this.emptySlots = emptySlots; + this.nextSlotIndex = nextSlotIndex; + this.maxItemTypes = maxItemTypes; + this.totalItemCount = totalItemCount; + this.maxTotalItemCount = maxTotalItemCount; + } + + public Object2IntOpenHashMap keyToCountMap() { + return this.keyToCountMap; + } + + public long totalItemCount() { + return this.totalItemCount; + } + + public int maxItemTypes() { + return this.maxItemTypes; + } + + public void maxItemTypes(int maxItemTypes) { + this.maxItemTypes = maxItemTypes; + } + + public long maxTotalItemCount() { + return this.maxTotalItemCount; + } + + public void maxTotalItemCount(long maxTotalItemCount) { + this.maxTotalItemCount = maxTotalItemCount; + } + + @Override + public int get(ItemStack stack) { + return this.get(ItemStackKey.of(stack)); + } + + public int get(ItemStackKey key) { + return this.keyToCountMap.getOrDefault(key, 0); + } + + @Override + public CompoundTag serializeNBT() { + return (CompoundTag) CODEC.encodeStart(NbtOps.INSTANCE, this).getOrThrow(false, e -> { + throw new RuntimeException("Failed to encode MapItemStackHandler: " + e); + }); + } + + @Override + public void deserializeNBT(CompoundTag nbt) { + CODEC.parse(NbtOps.INSTANCE, nbt).resultOrPartial(e -> { + throw new RuntimeException("Failed to decode MapItemStackHandler: " + e); + }).ifPresent(handler -> { + this.keyToCountMap = handler.keyToCountMap; + this.keyToSlot = handler.keyToSlot; + this.emptySlots = handler.emptySlots; + this.nextSlotIndex = handler.nextSlotIndex; + this.maxItemTypes = handler.maxItemTypes; + this.totalItemCount = handler.totalItemCount; + this.maxTotalItemCount = handler.maxTotalItemCount; + }); + } + + @Override + public void setStackInSlot(int slot, @NotNull ItemStack stack) { + //Note: This can go over the maxTotalItemCount limit because it would be too much of a hassle to enforce it here. + // that is because we can't just delete (=reduce count) items, nor can we deny setting a stack. + // maxItemTypes is self-enforced by not adding new slots here in any case + + var key = ItemStackKey.of(stack); + + var existingSlot = this.keyToSlot.get(key); + + //If the item type already exists in another slot, we cannot put it in this slot. + //the handler has a strict one slot per type limit. + if (existingSlot != null && existingSlot != slot) + return; + + //if the item type does not exist already AND the slot is empty AND the slot is within the current slot amount, we can place it + //if it is a higher slot we cannot add it, because otherwise we would grow the handler and create problems with iterators + if (existingSlot == null && //no existing slot for the given type + this.keyToSlot.inverse().get(slot) == null && //target slot is empty + slot < this.nextSlotIndex && //target slot is within our current size + !stack.isEmpty()) { //stack is not empty -> if it is we should not create a new slot, because no slot indicates empty + + this.keyToSlot.put(key, slot); //we do not call addToSlot() as that might choose another index from emptySlots + this.emptySlots.remove((Integer) slot); //remove from emptyslots + + this.keyToCountMap.put(key, stack.getCount()); + + this.totalItemCount += stack.getCount(); + + this.onContentsChanged(key); + } + //If it does exist and is the same slot, we just update the count + else if (existingSlot != null && existingSlot == slot) { + var existing = this.keyToCountMap.getOrDefault(key, 0); + this.totalItemCount -= existing; + + if (stack.isEmpty()) { //Setting an empty stack equals removal + this.keyToCountMap.removeInt(key); + this.removeFromSlots(key); + } else { + this.keyToCountMap.put(key, stack.getCount()); + this.totalItemCount += stack.getCount(); + } + + this.onContentsChanged(key); + } + } + + @Override + public int getSlots() { + //report at least one empty slot to allow inserting items, otherwise some handler code might not work. + return this.nextSlotIndex + 1; + } + + @Override + public @NotNull ItemStack getStackInSlot(int slot) { + var key = this.keyToSlot.inverse().get(slot); + return key != null ? key.stack() : ItemStack.EMPTY; + } + + @Override + public @NotNull ItemStack insertItem(int slot, @NotNull ItemStack stack, boolean simulate) { + //we don't need to deal with slots as they are only a "view". Because item stacks are they underlying storage key we can just forward. + return this.insertItem(stack, simulate); + } + + /** + *

+ * Inserts an ItemStack into the fitting slot and return the remainder. + * The ItemStack should not be modified in this function! + *

+ * Note: This behaviour is subtly different from {@link IFluidHandler#fill(FluidStack, IFluidHandler.FluidAction)} + * + * @param stack ItemStack to insert. This must not be modified by the item handler. + * @param simulate If true, the insertion is only simulated + * @return The remaining ItemStack that was not inserted (if the entire stack is accepted, then return an empty ItemStack). + * May be the same as the input ItemStack if unchanged, otherwise a new ItemStack. + * The returned ItemStack can be safely modified after. + **/ + @Override + public @NotNull ItemStack insertItem(@NotNull ItemStack stack, boolean simulate) { + if (stack.isEmpty()) + return ItemStack.EMPTY; + + var key = ItemStackKey.of(stack); + + if (!this.isItemValid(VIRTUAL_SLOT, key)) + return stack; + + int existing = this.keyToCountMap.getOrDefault(key, 0); + + int limit = this.getStackLimit(stack); + + if (existing > 0) { + //used in the original insert item of neoforge itemstackhandler, but not needed as the itemstackkey ensures stackability +// if (!ItemHandlerHelper.canItemStacksStack(stack, existing)) +// return stack; + + limit -= existing; + } + + if (existing == 0) { + //enforce max item types, if this type is not already present + if (this.maxItemTypes != -1 && this.keyToCountMap.size() >= this.maxItemTypes) + return stack; + } + + //enforce max total item count + limit = Math.min(limit, Math.toIntExact(this.maxTotalItemCount - this.totalItemCount)); + + if (limit <= 0) + return stack; + + boolean reachedLimit = stack.getCount() > limit; + + if (!simulate) { + if (existing <= 0) { + this.keyToCountMap.put(key, reachedLimit ? limit : stack.getCount()); + this.addToSlots(key); + + } else { + this.keyToCountMap.put(key, existing + (reachedLimit ? limit : stack.getCount())); + } + this.totalItemCount += reachedLimit ? limit : stack.getCount(); + this.onContentsChanged(key); + } + + return reachedLimit ? stack.copyWithCount(stack.getCount() - limit) : ItemStack.EMPTY; + } + + @Override + public @NotNull ItemStack extractItem(int slot, int amount, boolean simulate) { + if (amount == 0) + return ItemStack.EMPTY; + + this.validateSlotIndex(slot); + + var key = this.keyToSlot.inverse().get(slot); + if (key == null) + return ItemStack.EMPTY; + + var existing = this.keyToCountMap.getInt(key); + + if (existing <= 0) + return ItemStack.EMPTY; + +// int toExtract = Math.min(amount, key.stack().getMaxStackSize()); + //extraction is not limited to stack sizes + int toExtract = amount; + + if (existing <= toExtract) { + if (!simulate) { + this.keyToCountMap.removeInt(key); + this.totalItemCount -= existing; + + this.removeFromSlots(key); + + this.onContentsChanged(key); + + return key.stack().copyWithCount(existing); + } else { + return key.stack().copyWithCount(existing); + } + } else { + if (!simulate) { + this.keyToCountMap.put(key, existing - toExtract); + this.totalItemCount -= toExtract; + this.onContentsChanged(key); + } + return key.stack().copyWithCount(toExtract); + } + } + + public @NotNull ItemStack extractItem(ItemStackKey key, int amount, boolean simulate) { + var slot = this.keyToSlot.get(key); + + if (slot == null) + return ItemStack.EMPTY; + + return this.extractItem(slot, amount, simulate); + } + + public @NotNull ItemStack extractItem(ItemStack stack, int amount, boolean simulate) { + var key = ItemStackKey.of(stack); + return this.extractItem(key, amount, simulate); + } + + @Override + public int getSlotLimit(int slot) { + return Integer.MAX_VALUE; + } + + @Override + public boolean isItemValid(int slot, @NotNull ItemStack stack) { + return this.isItemValid(slot, ItemStackKey.of(stack)); + } + + @Override + public boolean isItemValid(int slot, @NotNull ItemStackKey key) { + return true; //just like ItemStackHandler + } + + /** + * Add a new key to the slot view. + * Only call if the key is not already present in keyToCountMap. + */ + protected void addToSlots(ItemStackKey key) { + if (!this.emptySlots.empty()) { + var index = this.emptySlots.pop(); + this.keyToSlot.put(key, index); + } else { + this.keyToSlot.put(key, this.nextSlotIndex++); + } + } + + /** + * Remove a key from the slot view. + * Only call if the key is entirely removed from keyToCountMap. + */ + protected void removeFromSlots(ItemStackKey key) { + var index = this.keyToSlot.get(key); + if (index != null) { + this.keyToSlot.remove(key); + this.emptySlots.push(index); + //Note: We intentionally do not modify nextSlot here to avoid shrinking the handler. + } + } + + protected int getStackLimit(@NotNull ItemStack stack) { + return this.getSlotLimit(VIRTUAL_SLOT); + } + + protected void validateSlotIndex(int slot) { + if (slot < 0 || !this.fitsInMaxSlots(slot)) + throw new RuntimeException("Slot " + slot + " not in valid range - [0," + (this.maxItemTypes != -1 ? this.maxItemTypes : Integer.MAX_VALUE) + ")"); + } + + protected boolean fitsInMaxSlots(int slot) { + return this.maxItemTypes != -1 && slot < this.maxItemTypes; + } + + protected void onContentsChanged(ItemStackKey key) { + } +} diff --git a/src/main/java/com/klikli_dev/occultism/common/misc/StorageControllerItemStackHandler.java b/src/main/java/com/klikli_dev/occultism/common/misc/StorageControllerItemStackHandler.java index 84ee69660..493f7bf00 100644 --- a/src/main/java/com/klikli_dev/occultism/common/misc/StorageControllerItemStackHandler.java +++ b/src/main/java/com/klikli_dev/occultism/common/misc/StorageControllerItemStackHandler.java @@ -172,7 +172,8 @@ public void deserializeNBT(CompoundTag nbt) { @Override protected void onContentsChanged(int slot) { - this.storageController.onContentsChanged(); + if(this.storageController != null) + this.storageController.onContentsChanged(); } public void prune() { diff --git a/src/main/java/com/klikli_dev/occultism/common/misc/StorageControllerMapItemStackHandler.java b/src/main/java/com/klikli_dev/occultism/common/misc/StorageControllerMapItemStackHandler.java new file mode 100644 index 000000000..4cbf5833f --- /dev/null +++ b/src/main/java/com/klikli_dev/occultism/common/misc/StorageControllerMapItemStackHandler.java @@ -0,0 +1,40 @@ +/* + * MIT License + * + * Copyright 2020 klikli-dev + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT + * OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +package com.klikli_dev.occultism.common.misc; + +import com.klikli_dev.occultism.api.common.blockentity.IStorageController; + +public class StorageControllerMapItemStackHandler extends MapItemStackHandler { + + protected IStorageController storageController; + + public StorageControllerMapItemStackHandler(IStorageController storageController, int maxItemTypes, long maxTotalItemCount) { + super(maxItemTypes, maxTotalItemCount); + this.storageController = storageController; + } + + @Override + protected void onContentsChanged(ItemStackKey key) { + this.storageController.onContentsChanged(); + } +} diff --git a/src/main/java/com/klikli_dev/occultism/config/OccultismServerConfig.java b/src/main/java/com/klikli_dev/occultism/config/OccultismServerConfig.java index fd5690068..68f4f6bd6 100644 --- a/src/main/java/com/klikli_dev/occultism/config/OccultismServerConfig.java +++ b/src/main/java/com/klikli_dev/occultism/config/OccultismServerConfig.java @@ -24,8 +24,7 @@ import java.util.ArrayList; import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; + import net.neoforged.neoforge.common.ModConfigSpec; import net.neoforged.neoforge.common.ModConfigSpec.BooleanValue; import net.neoforged.neoforge.common.ModConfigSpec.ConfigValue; @@ -247,41 +246,54 @@ public RitualSettings(ModConfigSpec.Builder builder) { } public static class StorageSettings { - public final ConfigValue stabilizerTier1Slots; - public final ConfigValue stabilizerTier2Slots; - public final ConfigValue stabilizerTier3Slots; - public final ConfigValue stabilizerTier4Slots; - public final ConfigValue controllerBaseSlots; - public final ConfigValue controllerStackSize; - public final BooleanValue overrideItemStackSizes; + public final ConfigValue stabilizerTier1AdditionalMaxItemTypes; + public final ConfigValue stabilizerTier1AdditionalMaxTotalItemCount; + public final ConfigValue stabilizerTier2AdditionalMaxItemTypes; + + public final ConfigValue stabilizerTier2AdditionalMaxTotalItemCount; + public final ConfigValue stabilizerTier3AdditionalMaxItemTypes; + + public final ConfigValue stabilizerTier3AdditionalMaxTotalItemCount; + public final ConfigValue stabilizerTier4AdditionalMaxItemTypes; + + public final ConfigValue stabilizerTier4AdditionalMaxTotalItemCount; + public final ConfigValue controllerMaxItemTypes; + public final ConfigValue controllerMaxTotalItemCount; public final BooleanValue unlinkWormholeOnBreak; public StorageSettings(ModConfigSpec.Builder builder) { builder.comment("Storage Settings").push("storage"); - this.stabilizerTier1Slots = - builder.comment("The amount of slots the storage stabilizer tier 1 provides.") - .define("stabilizerTier1Slots", 256); - this.stabilizerTier2Slots = + this.stabilizerTier1AdditionalMaxItemTypes = + builder.comment("The amount of additional slots the storage stabilizer tier 1 provides. 1 Slot holds one item type.") + .define("stabilizerTier1AdditionalMaxItemTypes", 64); + this.stabilizerTier1AdditionalMaxTotalItemCount = + builder.comment("The amount by which the stabilizer increases the maximum total item count the controller can hold. This is not per slot but the total amount of all items combined.") + .define("stabilizerTier1AdditionalMaxTotalItemCount", 512 * 1000L); + this.stabilizerTier2AdditionalMaxItemTypes = builder.comment("The amount of slots the storage stabilizer tier 2 provides.") - .define("stabilizerTier2Slots", 512); - this.stabilizerTier3Slots = + .define("stabilizerTier2AdditionalMaxItemTypes", 128); + this.stabilizerTier2AdditionalMaxTotalItemCount = + builder.comment("The amount by which the stabilizer increases the maximum total item count the controller can hold. This is not per slot but the total amount of all items combined.") + .define("stabilizerTier2AdditionalMaxTotalItemCount", 1024 * 1000L); + this.stabilizerTier3AdditionalMaxItemTypes = builder.comment("The amount of slots the storage stabilizer tier 3 provides.") - .define("stabilizerTier3Slots", 1024); - this.stabilizerTier4Slots = + .define("stabilizerTier3AdditionalMaxItemTypes", 256); + this.stabilizerTier3AdditionalMaxTotalItemCount = + builder.comment("The amount by which the stabilizer increases the maximum total item count the controller can hold. This is not per slot but the total amount of all items combined.") + .define("stabilizerTier3AdditionalMaxTotalItemCount", 2048 * 1000L); + this.stabilizerTier4AdditionalMaxItemTypes = builder.comment("The amount of slots the storage stabilizer tier 4 provides.") - .define("stabilizerTier4Slots", 2048); - this.controllerBaseSlots = + .define("stabilizerTier4AdditionalMaxItemTypes", 512); + this.stabilizerTier4AdditionalMaxTotalItemCount = + builder.comment("The amount by which the stabilizer increases the maximum total item count the controller can hold. This is not per slot but the total amount of all items combined.") + .define("stabilizerTier4AdditionalMaxTotalItemCount", 4096 * 1000L); + this.controllerMaxItemTypes = builder.comment("The amount of slots the storage actuator provides.") - .define("controllerBaseSlots", 128); - this.controllerStackSize = + .define("controllerMaxItemTypes", 128); + this.controllerMaxTotalItemCount = builder.comment("The stack size the storage actuator uses.") - .define("controllerStackSize", 1024); - this.overrideItemStackSizes = - builder.comment( - "True to use the configured controllerStackSize for all items, instead of the stack sizes provided by " + - "item type (such as 16 for ender pearls, 64 for iron ingot). WARNING: Setting this to " + - "false may have a negative impact on performance.") - .define("overrideItemStackSizes", true); + .define("controllerMaxTotalItemCount", 256 * 1000L); + this.unlinkWormholeOnBreak = builder.comment( "True to use the configured controllerStackSize for all items, instead of the stack sizes provided by " + diff --git a/src/main/java/com/klikli_dev/occultism/datafixer/StorageControllerMapItemStackHandlerDataFixer.java b/src/main/java/com/klikli_dev/occultism/datafixer/StorageControllerMapItemStackHandlerDataFixer.java new file mode 100644 index 000000000..702c8b076 --- /dev/null +++ b/src/main/java/com/klikli_dev/occultism/datafixer/StorageControllerMapItemStackHandlerDataFixer.java @@ -0,0 +1,30 @@ +package com.klikli_dev.occultism.datafixer; + +import com.klikli_dev.occultism.common.misc.StorageControllerItemStackHandler; +import com.klikli_dev.occultism.common.misc.StorageControllerMapItemStackHandler; +import net.minecraft.nbt.CompoundTag; + +public class StorageControllerMapItemStackHandlerDataFixer { + + public static boolean needsFixing(CompoundTag compound) { + return compound.contains("Items"); //Items indicates it is an old itemstackhandler + } + + public static CompoundTag fix(CompoundTag compound) { + var oldHandler = new StorageControllerItemStackHandler(null, 0, 0, true); + var newHandler = new StorageControllerMapItemStackHandler(null, -1, -1); + + oldHandler.deserializeNBT(compound); + + for (int i = 0; i < oldHandler.getSlots(); i++) { + var stack = oldHandler.getStackInSlot(i); + if (stack.isEmpty()) { + continue; + } + + newHandler.insertItem(stack, false); + } + + return newHandler.serializeNBT(); + } +} diff --git a/src/main/java/com/klikli_dev/occultism/datagen/OccultismBookProvider.java b/src/main/java/com/klikli_dev/occultism/datagen/OccultismBookProvider.java index 40bed84ca..68ebdeb08 100644 --- a/src/main/java/com/klikli_dev/occultism/datagen/OccultismBookProvider.java +++ b/src/main/java/com/klikli_dev/occultism/datagen/OccultismBookProvider.java @@ -64,8 +64,13 @@ protected BookModel generateBook() { var introReadCondition = BookEntryReadConditionModel.create() .withEntry(this.modLoc("getting_started/intro")); + spiritsCategory.withCondition(introReadCondition); storageCategory.withCondition(introReadCondition); ritualsCategory.withCondition(introReadCondition); + summoningRitualsCategory.withCondition(introReadCondition); + possessionRitualsCategory.withCondition(introReadCondition); + craftingRitualsCategory.withCondition(introReadCondition); + familiarRitualsCategory.withCondition(introReadCondition); pentaclesCategory.withCondition(introReadCondition); //https://tinyurl.com/occultism-graph diff --git a/src/main/java/com/klikli_dev/occultism/datagen/lang/ENUSProvider.java b/src/main/java/com/klikli_dev/occultism/datagen/lang/ENUSProvider.java index b75fc54f1..ab8fd294d 100644 --- a/src/main/java/com/klikli_dev/occultism/datagen/lang/ENUSProvider.java +++ b/src/main/java/com/klikli_dev/occultism/datagen/lang/ENUSProvider.java @@ -464,6 +464,8 @@ private void addGuiTranslations() { // Storage Controller GUI this.add("gui.occultism.storage_controller.space_info_label", "%d/%d"); + this.add("gui.occultism.storage_controller.space_info_label_new", "%s%% filled"); + this.add("gui.occultism.storage_controller.space_info_label_types", "%s%% of types"); this.add("gui.occultism.storage_controller.shift", "Hold shift for more information."); this.add("gui.occultism.storage_controller.search.tooltip@", "Prefix @: Search mod id."); this.add("gui.occultism.storage_controller.search.tooltip#", "Prefix #: Search in item tooltip."); @@ -1909,7 +1911,7 @@ private void addCraftingRitualsCategory(BookContextHelper helper) { This simple storage stabilizer is inhabited by a [#](%1$s)Foliot[#]() that supports the dimensional matrix in keeping the storage dimension stable, thus allowing to store more items. \\ \\ - By default each Tier 1 Stabilizer adds **256** slots. + By default each Tier 1 Stabilizer adds **64** item types and 512000 items storage capacity. """.formatted(COLOR_PURPLE)); helper.page("ritual"); @@ -1924,7 +1926,7 @@ private void addCraftingRitualsCategory(BookContextHelper helper) { This improved stabilizer is inhabited by a [#](%1$s)Djinni[#]() that supports the dimensional matrix in keeping the storage dimension stable, thus allowing to store even more items. \\ \\ - By default each Tier 2 Stabilizer adds **512** slots. + By default each Tier 2 Stabilizer adds **128** item types and 1024000 items storage capacity. """.formatted(COLOR_PURPLE)); helper.page("ritual"); @@ -1939,7 +1941,7 @@ private void addCraftingRitualsCategory(BookContextHelper helper) { This advanced stabilizer is inhabited by an [#](%1$s)Afrit[#]() that supports the dimensional matrix in keeping the storage dimension stable, thus allowing to store even more items. \\ \\ - By default each Tier 3 Stabilizer adds **1024** slots. + By default each Tier 3 Stabilizer adds **256** item types and 2048000 items storage capacity. """.formatted(COLOR_PURPLE)); helper.page("ritual"); @@ -1954,7 +1956,7 @@ private void addCraftingRitualsCategory(BookContextHelper helper) { This highly advanced stabilizer is inhabited by a [#](%1$s)Marid[#]() that supports the dimensional matrix in keeping the storage dimension stable, thus allowing to store even more items. \\ \\ - By default each Tier 4 Stabilizer adds **2048** slots. + By default each Tier 4 Stabilizer adds **512** item types and 4098000 items storage capacity. """.formatted(COLOR_PURPLE)); helper.page("ritual"); @@ -2624,24 +2626,21 @@ The same applies to breaking or replacing Storage Stabilizers (you will learn ab this.add(helper.pageTitle(), "So much storage!"); this.add(helper.pageText(), """ - The storage controller by default provides **128** slots (_You will learn later how to increase that_). Each slot can hold up to **1024** items, even items that usually have smaller stack sizes or are not stackable at all. + The storage controller holds up to **128** different types of items (_You will learn later how to increase that_). Additionally it is limited to 256000 items in total. It does not matter if you have 256000 different items or 256000 of one item, or any mix. """.formatted(COLOR_PURPLE)); helper.page("unique_items"); this.add(helper.pageTitle(), "Unique Items"); this.add(helper.pageText(), """ - The only exception to the increased stack size are **items with unique properties** ("NBT data"), such as damaged equipment, which cannot stack at all and will take up a full slot. For optimal storage results you should limit the amount of these items in your system. + Items with unique properties ("NBT data"), such as damaged or enchanted equipment will take up one item type for each variation. For example two wooden swords with two different damage values take up two item types. Two wooden swords with the same (or no) damage take up one item type. """.formatted(COLOR_PURPLE)); helper.page("config"); this.add(helper.pageTitle(), "Configurablity"); this.add(helper.pageText(), """ - Slot amount and slot size can be configured in the "[#](%1$s)occultism-server.toml[#]()" config file in the save directory of your world. - \\ - \\ - Increasing slot size does not impact performance, increasing slot amount (by a lot) can have a negative impact on performance. + The item type amount and storage size can be configured in the "[#](%1$s)occultism-server.toml[#]()" config file in the save directory of your world. """.formatted(COLOR_PURPLE)); helper.page("mods"); @@ -2649,7 +2648,7 @@ Increasing slot size does not impact performance, increasing slot amount (by a l this.add(helper.pageText(), """ For other mods the storage controller behaves like a shulker box, anything that can interact with vanilla chests and shulker boxes can interact with the storage controller. - Devices that count storage contents may have trouble with the stack sizes, if you run into this issue have your server admin set [this option](https://github.com/klikli-dev/occultism/issues/221#issuecomment-944904459). + Devices that count storage contents may have trouble with the stack sizes. """.formatted(COLOR_PURPLE)); @@ -2674,7 +2673,7 @@ Increasing slot size does not impact performance, increasing slot amount (by a l helper.page("spotlight"); this.add(helper.pageText(), """ - Storage Stabilizers increase the storage space in the storage dimension of the storage actuator. The higher the tier of the stabilizer, the more additional storage slots it provides. The following entries will show you how to craft each tier. + Storage Stabilizers increase the storage space in the storage dimension of the storage actuator. The higher the tier of the stabilizer, the more additional storage it provides. The following entries will show you how to craft each tier. \\ \\ """.formatted(COLOR_PURPLE)); diff --git a/src/main/java/com/klikli_dev/occultism/network/messages/MessageUpdateStacks.java b/src/main/java/com/klikli_dev/occultism/network/messages/MessageUpdateStacks.java index 2b6f2cd39..7bba90c5e 100644 --- a/src/main/java/com/klikli_dev/occultism/network/messages/MessageUpdateStacks.java +++ b/src/main/java/com/klikli_dev/occultism/network/messages/MessageUpdateStacks.java @@ -50,18 +50,22 @@ public class MessageUpdateStacks implements IMessage { private static final int DEFAULT_BUFFER_SIZE = 2 * 1024; private List stacks; - private int usedSlots; - private int maxSlots; + private int maxItemTypes; + private int usedItemTypes; + private long maxTotalItemCount; + private long usedTotalItemCount; private ByteBuf payload; public MessageUpdateStacks(FriendlyByteBuf buf) { this.decode(buf); } - public MessageUpdateStacks(List stacks, int usedSlots, int maxSlots) { + public MessageUpdateStacks(List stacks, int maxItemTypes, int usedItemTypes, long maxTotalItemCount, long usedTotalItemCount) { this.stacks = stacks; - this.usedSlots = usedSlots; - this.maxSlots = maxSlots; + this.maxItemTypes = maxItemTypes; + this.usedItemTypes = usedItemTypes; + this.maxTotalItemCount = maxTotalItemCount; + this.usedTotalItemCount = usedTotalItemCount; this.compress(); } @@ -72,8 +76,8 @@ public void onClientReceived(Minecraft minecraft, Player player) { if (minecraft.screen instanceof IStorageControllerGui gui) { if (gui != null) { gui.setStacks(this.stacks); - gui.setUsedSlots(this.usedSlots); - gui.setMaxSlots(this.maxSlots); + gui.setUsedStorageSize(this.usedItemTypes, this.usedTotalItemCount); + gui.setMaxStorageSize(this.maxItemTypes, this.maxTotalItemCount); gui.markDirty(); } } @@ -81,8 +85,10 @@ public void onClientReceived(Minecraft minecraft, Player player) { @Override public void encode(FriendlyByteBuf buf) { - buf.writeVarInt(this.usedSlots); - buf.writeVarInt(this.maxSlots); + buf.writeVarInt(this.usedItemTypes); + buf.writeVarLong(this.usedTotalItemCount); + buf.writeVarInt(this.maxItemTypes); + buf.writeVarLong(this.maxTotalItemCount); //write compressed size, then compressed data buf.writeVarInt(this.payload.readableBytes()); @@ -91,8 +97,11 @@ public void encode(FriendlyByteBuf buf) { @Override public void decode(FriendlyByteBuf buf) { - this.usedSlots = buf.readVarInt(); - this.maxSlots = buf.readVarInt(); + this.usedItemTypes = buf.readVarInt(); + this.usedTotalItemCount = buf.readVarLong(); + this.maxItemTypes = buf.readVarInt(); + this.maxTotalItemCount = buf.readVarLong(); + //read compressed size, then compressed data. int compressedSize = buf.readVarInt(); this.payload = Unpooled.buffer(compressedSize);