diff --git a/src/generated/resources/assets/anvilcraft/lang/en_ud.json b/src/generated/resources/assets/anvilcraft/lang/en_ud.json index 6f290a24ff..db90114115 100644 --- a/src/generated/resources/assets/anvilcraft/lang/en_ud.json +++ b/src/generated/resources/assets/anvilcraft/lang/en_ud.json @@ -221,6 +221,8 @@ "anvilcraft.configuration.show_multiphase_stored_id.tooltip": "ᗡI pǝɹoʇs ǝsɐɥdᴉʇꞁnɯ sʍoɥs ʇɐɥʇ ǝuᴉꞁ dᴉʇꞁooʇ ɐ ppⱯ", "anvilcraft.configuration.sliding_rail_stick_to_each_other": "ɹǝɥʇO ɥɔɐƎ o⟘ ʞɔᴉʇS ꞁᴉɐᴚ ᵷuᴉpᴉꞁS", "anvilcraft.configuration.sliding_rail_stick_to_each_other.tooltip": "sꞁᴉɐɹ ɹǝɥʇo oʇ uᴉɐɥɔ ꞁꞁᴉʍ ꞁᴉɐɹ ᵷuᴉpᴉꞁs ɐ ᵷuᴉꞁꞁnd ɹo ᵷuᴉɥsnԀ", + "anvilcraft.configuration.storage_recover_max_size": "ǝzᴉS xɐW ɹǝʌoɔǝᴚ ǝᵷɐɹoʇS", + "anvilcraft.configuration.storage_recover_max_size.tooltip": "uoᴉʇɐʇs ɹǝʌoɔǝɹ ,sǝᵷɐɹoʇs uᴉ sǝᴉɹʇuǝ ǝɥʇ ɟo ǝzᴉs xɐɯ ǝɥ⟘", "anvilcraft.configuration.title": "uoᴉʇɐɹnᵷᴉɟuoƆ ʇɟɐɹɔꞁᴉʌuⱯ", "anvilcraft.configuration.transcendence_anvil_beyond_max_level": "ꞁǝʌǝꞀ xɐW puoʎǝᗺ ꞁᴉʌuⱯ ǝɔuǝpuǝɔsuɐɹ⟘", "anvilcraft.configuration.transcendence_anvil_beyond_max_level.tooltip": "ꞁᴉʌuⱯ ǝɔuǝpuǝɔsuɐɹ⟘ uᴉ ꞁǝʌǝꞁ xɐɯ puoʎǝq sʞooᗺ pǝʇuɐɥɔuƎ ɥʇᴉʍ sɯǝʇᴉ ᵷuᴉuᴉqɯoƆ", @@ -567,12 +569,16 @@ "block.anvilcraft.zinc_block": "ɔuᴉZ ɟo ʞɔoꞁᗺ", "block.anvilcraft.zinc_pressure_plate": "ǝʇɐꞁԀ ǝɹnssǝɹԀ ɔuᴉZ", "book.anvilcraft.material_list.missing_header": ":ᵷuᴉssᴉW", + "category.anvilcraft.block": "sɯǝʇI ʞɔoꞁᗺ", "category.anvilcraft.enchanted": "sɯǝʇI pǝʇuɐɥɔuƎ", - "category.anvilcraft.foods_and_drinks": "sʞuᴉɹᗡ puɐ spooℲ", + "category.anvilcraft.filter": "ʎɹoᵷǝʇɐƆ ʍǝN", + "category.anvilcraft.food_and_drink": "sʞuᴉɹᗡ puɐ spooℲ", "category.anvilcraft.namespace": "sɯǝʇI %s", "category.anvilcraft.namespace.anvilcraft": "ʇɟɐɹƆꞁᴉʌuⱯ", + "category.anvilcraft.namespace.minecraft": "ʇɟɐɹɔǝuᴉW", "category.anvilcraft.redstone": "sɯǝʇI ǝuoʇspǝᴚ", "category.anvilcraft.unknown_namespace": "<%s> uʍouʞu∩", + "category.anvilcraft.unstackable": "sɯǝʇI ǝꞁqɐʞɔɐʇsu∩", "command.anvilcraft.multiBlock.multi_block_pos": " sᴉ sod ʇɹɐd uᴉɐW", "command.anvilcraft.multiBlock.not_multi_block": "ʞɔoꞁq-ᴉʇꞁnɯ ɐ ʇou sᴉ ʞɔoꞁq sᴉɥ⟘", "command.anvilcraft.multiphase.apply.not_player": "ɹǝʎɐꞁd ʇou sᴉ ɹǝuunɹ puɐɯɯoƆ", @@ -1403,6 +1409,15 @@ "screen.anvilcraft.smithing_template.transcendium_upgrade_smithing_template.applies_to": "poᴚ uoᵷɐɹᗡ ɹǝqɯƎ 'ɹǝɯɯɐH ꞁᴉʌuⱯ ɹǝqɯƎ 'ꞁᴉʌuⱯ ꞁɐʇǝW ɹǝqɯƎ", "screen.anvilcraft.smithing_template.transcendium_upgrade_smithing_template.base_slot_description": "ɯǝʇᴉ ǝꞁqɐpɐɹᵷdn ʇnԀ", "screen.anvilcraft.smithing_template.transcendium_upgrade_smithing_template.upgrade_ingredients": "ʞɔoꞁᗺ ɯnᴉpuǝɔsuɐɹ⟘ ɹo ʇoᵷuI ɯnᴉpuǝɔsuɐɹ⟘", + "screen.anvilcraft.storage.category.add": "ʎɹoᵷǝʇɐɔ ɯoʇsnɔ ppɐ oʇ ɹǝʇꞁᴉℲ ᵷuᴉpꞁoɥ uǝɥʍ ʞɔᴉꞁɔ ʇɟǝꞀ", + "screen.anvilcraft.storage.category.alternate.removable": "ʎɹoᵷǝʇɐɔ sᴉɥʇ ǝʇǝꞁǝp oʇ ʞɔᴉꞁɔ ʇɥᵷᴉɹ 'ʇɔǝꞁǝs oʇ ʞɔᴉꞁɔ ʇɟǝꞀ", + "screen.anvilcraft.storage.category.alternate.unremovable": "ʇɔǝꞁǝs oʇ ʞɔᴉꞁɔ ʇɟǝꞀ", + "screen.anvilcraft.storage.category.mode": "%s :ǝpoW", + "screen.anvilcraft.storage.category.mode.allowlist": "ʎɐꞁdsᴉᗡ ʍoꞁꞁⱯ", + "screen.anvilcraft.storage.category.mode.blocklist": "ʎɐꞁdsᴉᗡ ʞɔoꞁᗺ", + "screen.anvilcraft.storage.category.mode.unlimited": "pǝʇᴉɯᴉꞁu∩", + "screen.anvilcraft.storage.category.name": "%s :ǝɯɐN", + "screen.anvilcraft.storage.category.tooltip": "doʇ oʇ uᴉd oʇ ʞɔᴉꞁɔ ʇɥᵷᴉɹ 'sǝʇɐuɹǝʇꞁɐ oʇ ǝʌoɯ oʇ ʞɔᴉꞁɔ ʇɟǝꞀ", "screen.anvilcraft.structure_scanner.info_title": "oɟuI ǝɹnʇɔnɹʇS", "screen.anvilcraft.structure_scanner.ready": "ʎpɐǝɹ uɐɔS", "screen.anvilcraft.structure_scanner.tooltip.large_structure": "pǝʌɐs ǝq ꞁꞁᴉʇs uɐɔ ʇnq 'ɹǝɔɐꞁԀ ʞɔoꞁᗺ ʇɹɐɯS ǝɥʇ ʎq pǝɔɐꞁd ǝq ʇouuɐɔ puɐ ǝᵷɹɐꞁ sᴉ ǝɹnʇɔnɹʇs sᴉɥ⟘", @@ -1802,6 +1817,8 @@ "tooltip.anvilcraft.property.multiphase.suffix.3": "δ-", "tooltip.anvilcraft.property.providence": "sǝɯᴉʇ ǝꞁdᴉʇꞁnɯ sʇuǝɯʇuɐɥɔuǝ [%s pꞁoH] ɹǝᵷᵷᴉɹʇ oʇ ǝɔuɐɥɔ sɐɥ :ǝɔuǝpᴉʌoɹԀ", "tooltip.anvilcraft.property.providence.shifting": "sǝɯᴉʇ ǝꞁdᴉʇꞁnɯ sʇuǝɯʇuɐɥɔuǝ (%s) ɹǝᵷᵷᴉɹʇ oʇ ǝɔuɐɥɔ sɐɥ :ǝɔuǝpᴉʌoɹԀ", + "tooltip.anvilcraft.property.storage.id": "%s :ᗡI ǝᵷɐɹoʇS", + "tooltip.anvilcraft.property.storage.id.null": "ᵷuᴉɔɐꞁd uǝɥʍ ǝʇɐǝɹɔ ꞁꞁᴉʍ 'ʇǝʎ ǝuoN", "tooltip.anvilcraft.property.stored_energy": "%s :ʎᵷɹǝuƎ ᵷuᴉuᴉɐɯǝᴚ", "tooltip.anvilcraft.redstone.output_mode": "%s :ǝpoW ʇndʇnO ", "tooltip.anvilcraft.redstone.output_mode.compare": "ǝɹɐdɯoƆ", @@ -1816,4 +1833,4 @@ "tooltip.anvilcraft.working_progress.progress": "%%%2$s %1$s ", "tooltip.anvilcraft.working_progress.time": "%2$s / %1$s ", "tooltip.anvilcraft.working_progress.title": ":ssǝɹᵷoɹd ᵷuᴉʞɹoM" -} \ No newline at end of file +} diff --git a/src/generated/resources/assets/anvilcraft/lang/en_us.json b/src/generated/resources/assets/anvilcraft/lang/en_us.json index ae762fbae0..d8848f7792 100644 --- a/src/generated/resources/assets/anvilcraft/lang/en_us.json +++ b/src/generated/resources/assets/anvilcraft/lang/en_us.json @@ -221,6 +221,8 @@ "anvilcraft.configuration.show_multiphase_stored_id.tooltip": "Add a tooltip line that shows multiphase stored ID", "anvilcraft.configuration.sliding_rail_stick_to_each_other": "Sliding Rail Stick To Each Other", "anvilcraft.configuration.sliding_rail_stick_to_each_other.tooltip": "Pushing or pulling a sliding rail will chain to other rails", + "anvilcraft.configuration.storage_recover_max_size": "Storage Recover Max Size", + "anvilcraft.configuration.storage_recover_max_size.tooltip": "The max size of the entries in storages' recover station", "anvilcraft.configuration.title": "Anvilcraft Configuration", "anvilcraft.configuration.transcendence_anvil_beyond_max_level": "Transcendence Anvil Beyond Max Level", "anvilcraft.configuration.transcendence_anvil_beyond_max_level.tooltip": "Combining items with Enchanted Books beyond max level in Transcendence Anvil", @@ -567,12 +569,16 @@ "block.anvilcraft.zinc_block": "Block of Zinc", "block.anvilcraft.zinc_pressure_plate": "Zinc Pressure Plate", "book.anvilcraft.material_list.missing_header": "Missing:", + "category.anvilcraft.block": "Block Items", "category.anvilcraft.enchanted": "Enchanted Items", - "category.anvilcraft.foods_and_drinks": "Foods and Drinks", + "category.anvilcraft.filter": "New Category", + "category.anvilcraft.food_and_drink": "Foods and Drinks", "category.anvilcraft.namespace": "%s Items", "category.anvilcraft.namespace.anvilcraft": "AnvilCraft", + "category.anvilcraft.namespace.minecraft": "Minecraft", "category.anvilcraft.redstone": "Redstone Items", "category.anvilcraft.unknown_namespace": "Unknown <%s>", + "category.anvilcraft.unstackable": "Unstackable Items", "command.anvilcraft.multiBlock.multi_block_pos": "Main part pos is ", "command.anvilcraft.multiBlock.not_multi_block": "This block is not a multi-block", "command.anvilcraft.multiphase.apply.not_player": "Command runner is not player", @@ -1403,6 +1409,15 @@ "screen.anvilcraft.smithing_template.transcendium_upgrade_smithing_template.applies_to": "Ember Metal Anvil, Ember Anvil Hammer, Ember Dragon Rod", "screen.anvilcraft.smithing_template.transcendium_upgrade_smithing_template.base_slot_description": "Put upgradable item", "screen.anvilcraft.smithing_template.transcendium_upgrade_smithing_template.upgrade_ingredients": "Transcendium Ingot or Transcendium Block", + "screen.anvilcraft.storage.category.add": "Left click when holding Filter to add custom category", + "screen.anvilcraft.storage.category.alternate.removable": "Left click to select, right click to delete this category", + "screen.anvilcraft.storage.category.alternate.unremovable": "Left click to select", + "screen.anvilcraft.storage.category.mode": "Mode: %s", + "screen.anvilcraft.storage.category.mode.allowlist": "Allow Display", + "screen.anvilcraft.storage.category.mode.blocklist": "Block Display", + "screen.anvilcraft.storage.category.mode.unlimited": "Unlimited", + "screen.anvilcraft.storage.category.name": "Name: %s", + "screen.anvilcraft.storage.category.tooltip": "Left click to move to alternates, right click to pin to top", "screen.anvilcraft.structure_scanner.info_title": "Structure Info", "screen.anvilcraft.structure_scanner.ready": "Scan ready", "screen.anvilcraft.structure_scanner.tooltip.large_structure": "This structure is large and cannot be placed by the Smart Block Placer, but can still be saved", @@ -1802,6 +1817,8 @@ "tooltip.anvilcraft.property.multiphase.suffix.3": "-δ", "tooltip.anvilcraft.property.providence": "Providence: has chance to trigger [Hold %s] enchantments multiple times", "tooltip.anvilcraft.property.providence.shifting": "Providence: has chance to trigger (%s) enchantments multiple times", + "tooltip.anvilcraft.property.storage.id": "Storage ID: %s", + "tooltip.anvilcraft.property.storage.id.null": "None yet, will create when placing", "tooltip.anvilcraft.property.stored_energy": "Remaining Energy: %s", "tooltip.anvilcraft.redstone.output_mode": " Output Mode: %s", "tooltip.anvilcraft.redstone.output_mode.compare": "Compare", @@ -1816,4 +1833,4 @@ "tooltip.anvilcraft.working_progress.progress": " %1$s %2$s%%", "tooltip.anvilcraft.working_progress.time": " %1$s / %2$s", "tooltip.anvilcraft.working_progress.title": "Working progress:" -} \ No newline at end of file +} diff --git a/src/generated/resources/data/anvilcraft/anvilcraft/category/block.json b/src/generated/resources/data/anvilcraft/anvilcraft/category/block.json new file mode 100644 index 0000000000..681a9f1170 --- /dev/null +++ b/src/generated/resources/data/anvilcraft/anvilcraft/category/block.json @@ -0,0 +1,3 @@ +{ + "type": "anvilcraft:block" +} \ No newline at end of file diff --git a/src/generated/resources/data/anvilcraft/anvilcraft/category/enchanted.json b/src/generated/resources/data/anvilcraft/anvilcraft/category/enchanted.json index a4da14b7d1..54caf3d024 100644 --- a/src/generated/resources/data/anvilcraft/anvilcraft/category/enchanted.json +++ b/src/generated/resources/data/anvilcraft/anvilcraft/category/enchanted.json @@ -7,8 +7,8 @@ "translate": "category.anvilcraft.enchanted" }, "predicates": { - "anvilcraft:disabled_enchantments": {}, - "anvilcraft:merciless_enchantments": {}, + "anvilcraft:disabled_enchantment": [], + "anvilcraft:merciless_enchantment": [], "minecraft:enchantments": [], "minecraft:stored_enchantments": [] } diff --git a/src/generated/resources/data/anvilcraft/anvilcraft/category/foods_and_drinks.json b/src/generated/resources/data/anvilcraft/anvilcraft/category/food_and_drink.json similarity index 79% rename from src/generated/resources/data/anvilcraft/anvilcraft/category/foods_and_drinks.json rename to src/generated/resources/data/anvilcraft/anvilcraft/category/food_and_drink.json index b95864ec8d..541239da5a 100644 --- a/src/generated/resources/data/anvilcraft/anvilcraft/category/foods_and_drinks.json +++ b/src/generated/resources/data/anvilcraft/anvilcraft/category/food_and_drink.json @@ -4,7 +4,7 @@ "id": "minecraft:apple" }, "name": { - "translate": "category.anvilcraft.foods_and_drinks" + "translate": "category.anvilcraft.food_and_drink" }, "predicates": { "minecraft:food": {}, diff --git a/src/generated/resources/data/anvilcraft/anvilcraft/category/minecraft.json b/src/generated/resources/data/anvilcraft/anvilcraft/category/minecraft.json new file mode 100644 index 0000000000..fddde2e0e6 --- /dev/null +++ b/src/generated/resources/data/anvilcraft/anvilcraft/category/minecraft.json @@ -0,0 +1,15 @@ +{ + "type": "anvilcraft:namespace", + "icon": { + "id": "minecraft:grass_block" + }, + "name": { + "translate": "category.anvilcraft.namespace", + "with": [ + { + "mod_id": "minecraft" + } + ] + }, + "namespace": "minecraft" +} \ No newline at end of file diff --git a/src/generated/resources/data/anvilcraft/anvilcraft/category/unstackable.json b/src/generated/resources/data/anvilcraft/anvilcraft/category/unstackable.json new file mode 100644 index 0000000000..2027192606 --- /dev/null +++ b/src/generated/resources/data/anvilcraft/anvilcraft/category/unstackable.json @@ -0,0 +1,3 @@ +{ + "type": "anvilcraft:unstackable" +} \ No newline at end of file diff --git a/src/main/java/dev/anvilcraft/lib/v2/util1/stack/UnlimitedItemStack.java b/src/main/java/dev/anvilcraft/lib/v2/util1/stack/UnlimitedItemStack.java index 9e45ea7c59..b8f4705edb 100644 --- a/src/main/java/dev/anvilcraft/lib/v2/util1/stack/UnlimitedItemStack.java +++ b/src/main/java/dev/anvilcraft/lib/v2/util1/stack/UnlimitedItemStack.java @@ -173,6 +173,10 @@ public int getMaxStackSize() { return this.getItem().getMaxStackSize(this.toStack()); } + public boolean isStackable() { + return this.getMaxStackSize() > 1 && (!this.stack.isDamageableItem() || !this.stack.isDamaged()); + } + public UnlimitedItemStack copy() { return new UnlimitedItemStack(this.stack, this.count); } diff --git a/src/main/java/dev/dubhe/anvilcraft/AnvilCraft.java b/src/main/java/dev/dubhe/anvilcraft/AnvilCraft.java index f2af5319a0..aee926003d 100644 --- a/src/main/java/dev/dubhe/anvilcraft/AnvilCraft.java +++ b/src/main/java/dev/dubhe/anvilcraft/AnvilCraft.java @@ -48,7 +48,9 @@ import dev.dubhe.anvilcraft.init.recipe.ModRecipeTypes; import dev.dubhe.anvilcraft.init.recipe.ModResultModifierTypes; import dev.dubhe.anvilcraft.init.storage.ModCategoryTypes; +import dev.dubhe.anvilcraft.mixin.invoker.BaseMappedRegistryInvoker; import lombok.Getter; +import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.resources.Identifier; import net.neoforged.bus.api.IEventBus; import net.neoforged.fml.ModContainer; @@ -174,6 +176,7 @@ public static void loadComplete(FMLLoadCompleteEvent event) { CONFIG.emberAnvilBeyondMaxLevel = true; CONFIG.transcendenceAnvilBeyondMaxLevel = true; } + Util.cast(BuiltInRegistries.DATA_COMPONENT_PREDICATE_TYPE).invokeSetSync(true); }); } } diff --git a/src/main/java/dev/dubhe/anvilcraft/api/component/ModNameContents.java b/src/main/java/dev/dubhe/anvilcraft/api/component/ModNameContents.java index 3678e14ca9..fd7bfebbe5 100644 --- a/src/main/java/dev/dubhe/anvilcraft/api/component/ModNameContents.java +++ b/src/main/java/dev/dubhe/anvilcraft/api/component/ModNameContents.java @@ -18,6 +18,7 @@ import org.jspecify.annotations.Nullable; import java.util.List; +import java.util.Objects; import java.util.Optional; @Getter(AccessLevel.PROTECTED) @@ -94,4 +95,15 @@ public MutableComponent resolve(ResolutionContext context, int recursionDepth) t public MapCodec codec() { return ModNameContents.CODEC; } + + @Override + public boolean equals(Object o) { + if (!(o instanceof ModNameContents that)) return false; + return Objects.equals(this.getId(), that.getId()); + } + + @Override + public int hashCode() { + return Objects.hashCode(this.getId()); + } } diff --git a/src/main/java/dev/dubhe/anvilcraft/api/energy/IEnergyHandlerHolder.java b/src/main/java/dev/dubhe/anvilcraft/api/energy/IEnergyHandlerHolder.java new file mode 100644 index 0000000000..ea48d3c0eb --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/api/energy/IEnergyHandlerHolder.java @@ -0,0 +1,10 @@ +package dev.dubhe.anvilcraft.api.energy; + +import net.minecraft.core.Direction; +import net.neoforged.neoforge.transfer.energy.EnergyHandler; +import org.jspecify.annotations.Nullable; + +/// 持有 EnergyHandler 的 +public interface IEnergyHandlerHolder { + @Nullable EnergyHandler getEnergyHandler(@Nullable Direction direction); +} diff --git a/src/main/java/dev/dubhe/anvilcraft/api/fluid/IFluidHandlerHolder.java b/src/main/java/dev/dubhe/anvilcraft/api/fluid/IFluidHandlerHolder.java index 8bb1560e7a..84ce373a7b 100644 --- a/src/main/java/dev/dubhe/anvilcraft/api/fluid/IFluidHandlerHolder.java +++ b/src/main/java/dev/dubhe/anvilcraft/api/fluid/IFluidHandlerHolder.java @@ -3,7 +3,7 @@ import net.neoforged.neoforge.transfer.ResourceHandler; import net.neoforged.neoforge.transfer.fluid.FluidResource; -/// 持有FluidTank的 +/// 持有 FluidTank 的 public interface IFluidHandlerHolder { ResourceHandler getFluidHandler(); } diff --git a/src/main/java/dev/dubhe/anvilcraft/api/itemhandler/TypeLimitItemStacksResourceHandler.java b/src/main/java/dev/dubhe/anvilcraft/api/itemhandler/TypeLimitItemStacksResourceHandler.java index 4e503d2151..13bd9f02da 100644 --- a/src/main/java/dev/dubhe/anvilcraft/api/itemhandler/TypeLimitItemStacksResourceHandler.java +++ b/src/main/java/dev/dubhe/anvilcraft/api/itemhandler/TypeLimitItemStacksResourceHandler.java @@ -1,9 +1,16 @@ package dev.dubhe.anvilcraft.api.itemhandler; import com.mojang.serialization.Codec; +import com.mojang.serialization.MapCodec; +import dev.anvilcraft.lib.v2.codec.CodecUtil; import dev.anvilcraft.lib.v2.network.util.BoolAndInt; import dev.anvilcraft.lib.v2.util1.stack.UnlimitedItemStack; +import lombok.AccessLevel; +import lombok.Getter; import net.minecraft.core.NonNullList; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; import net.minecraft.world.item.ItemInstance; import net.minecraft.world.level.storage.ValueInput; import net.minecraft.world.level.storage.ValueOutput; @@ -18,15 +25,43 @@ import java.util.List; import java.util.Objects; import java.util.Optional; -import java.util.function.Function; +import java.util.function.IntUnaryOperator; +@Getter(AccessLevel.PRIVATE) public class TypeLimitItemStacksResourceHandler implements ResourceHandler, ValueIOSerializable { - public static final String VALUE_IO_KEY = "stacks"; - public static final Codec> STACKS_CODEC = UnlimitedItemStack.CODEC + public static final String STACKS_KEY = "stacks"; + public static final String TYPE_LIMIT_KEY = "type_limit"; + public static final String SPACE_SIZE_KEY = "space_size"; + public static final Codec> STACKS_CODEC = UnlimitedItemStack.OPTIONAL_CODEC .listOf() - .xmap(TypeLimitItemStacksResourceHandler::constructStackList, Function.identity()); - private final int typeLimit; - private final int spaceSize; + .xmap(TypeLimitItemStacksResourceHandler::constructStackList, TypeLimitItemStacksResourceHandler::trim); + public static final MapCodec CODEC = CodecUtil.mapCodec( + TypeLimitItemStacksResourceHandler.STACKS_CODEC + .fieldOf(TypeLimitItemStacksResourceHandler.STACKS_KEY) + .forGetter(TypeLimitItemStacksResourceHandler::getStacks), + Codec.INT + .fieldOf(TypeLimitItemStacksResourceHandler.TYPE_LIMIT_KEY) + .forGetter(TypeLimitItemStacksResourceHandler::getTypeLimit), + Codec.INT + .fieldOf(TypeLimitItemStacksResourceHandler.SPACE_SIZE_KEY) + .forGetter(TypeLimitItemStacksResourceHandler::getSpaceSize), + TypeLimitItemStacksResourceHandler::new + ); + public static final StreamCodec> STACKS_STREAM_CODEC = UnlimitedItemStack + .STREAM_CODEC + .apply(ByteBufCodecs.list()) + .map(TypeLimitItemStacksResourceHandler::constructStackList, TypeLimitItemStacksResourceHandler::trim); + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + TypeLimitItemStacksResourceHandler.STACKS_STREAM_CODEC, + TypeLimitItemStacksResourceHandler::getStacks, + ByteBufCodecs.VAR_INT, + TypeLimitItemStacksResourceHandler::getTypeLimit, + ByteBufCodecs.VAR_INT, + TypeLimitItemStacksResourceHandler::getSpaceSize, + TypeLimitItemStacksResourceHandler::new + ); + private int typeLimit; + private int spaceSize; private final NonNullList stacks = TypeLimitItemStacksResourceHandler.constructStackList(); private int space = 0; @@ -41,6 +76,70 @@ public TypeLimitItemStacksResourceHandler(int typeLimit, int spaceSize) { this.spaceSize = spaceSize; } + private TypeLimitItemStacksResourceHandler(NonNullList stacks, int typeLimit, int spaceSize) { + this.typeLimit = typeLimit; + this.spaceSize = spaceSize; + this.initStacks(stacks); + } + + private void initStacks(NonNullList stacks) { + this.space = 0; + MAIN: + for (UnlimitedItemStack stack : stacks) { + // 剩余空间连一个都塞不下,直接下一个 + if (this.computeEmptySize(stack) < 1) { + continue; + } + + int space = TypeLimitItemStacksResourceHandler.computeSpace(stack, stack.count()); + + // 塞得下整个栈,塞完下一个 + if (this.spaceSize == Integer.MAX_VALUE || this.space + space <= this.spaceSize) { + this.space += space; + for (int i = 0; i < this.stacks.size(); i++) { + UnlimitedItemStack exist = this.stacks.get(i); + if (exist.isSameItemSameComponents(stack)) { + UnlimitedItemStack original = exist.copy(); + exist.setCount(stack.count()); + this.onContentsChanged(i, original); + continue MAIN; + } + } + this.stacks.add(stack); + continue; + } + + // 塞不下整个栈,尝试找到能塞下的数量 + for (int i = stack.count() - 1; i >= 0; i--) { + space = TypeLimitItemStacksResourceHandler.computeSpace(stack, i); + // 找到了,塞完下一个 + if (this.space + space <= this.spaceSize) { + this.space += space; + for (int index = 0; index < this.stacks.size(); index++) { + UnlimitedItemStack exist = this.stacks.get(index); + if (exist.isSameItemSameComponents(stack)) { + UnlimitedItemStack original = exist.copy(); + exist.setCount(stack.count()); + this.onContentsChanged(index, original); + continue MAIN; + } + } + this.stacks.add(stack); + continue MAIN; + } + } + // 找不到不塞,下一个 + } + } + + public void addSpaceSize(IntUnaryOperator adder) { + int spaceSize = adder.applyAsInt(this.spaceSize); + if (spaceSize < this.spaceSize) { + return; + } + this.spaceSize = spaceSize; + } + private static NonNullList constructStackList() { return new NonNullList<>(new ArrayList<>(), UnlimitedItemStack.EMPTY); } @@ -66,9 +165,13 @@ public long getAmountAsLong(int index) { return this.stacks.get(index).getCount(); } + public double getFullness() { + return (double) this.space / this.spaceSize; + } + @Override public long getCapacityAsLong(int index, ItemResource resource) { - return Integer.MAX_VALUE; + return TypeLimitItemStacksResourceHandler.computeSpace(resource, this.spaceSize); } @Override @@ -77,15 +180,21 @@ public boolean isValid(int index, ItemResource resource) { } protected int computeEmptySize(ItemResource resource) { + if (this.spaceSize == Integer.MAX_VALUE) { + return Integer.MAX_VALUE; + } return TypeLimitItemStacksResourceHandler.computeCount(resource, this.spaceSize - this.space); } - protected int findEmptySlot() { - for (int i = 0; i < this.stacks.size(); i++) { - if (this.stacks.get(i).isEmpty()) { - return i; - } + protected int computeEmptySize(ItemInstance instance) { + if (this.spaceSize == Integer.MAX_VALUE) { + return Integer.MAX_VALUE; } + return TypeLimitItemStacksResourceHandler.computeCount(instance, this.spaceSize - this.space); + } + + protected int findNewSlot() { + // 虽然种类限制为 Integer.MAX_VALUE 时视为无上限,但 Java 底层限制不允许 ArrayList 的大小超过 Integer.MAX_VALUE return this.stacks.size() >= this.typeLimit ? -1 : this.stacks.size(); } @@ -101,7 +210,7 @@ public int insert(ItemResource resource, int amount, TransactionContext transact } } - int index = this.findEmptySlot(); + int index = this.findNewSlot(); if (index < 0) { return 0; } @@ -127,7 +236,7 @@ private BoolAndInt insertInternal(int index, ItemResource resource, int amount, TransferPreconditions.checkNonEmptyNonNegative(resource, amount); UnlimitedItemStack stack = this.stacks.get(index); - if (!stack.isSameItemSameComponents(resource)) { + if (!stack.isEmpty() && !stack.isSameItemSameComponents(resource)) { return new BoolAndInt(false, 0); } @@ -149,11 +258,11 @@ public int extract(int index, ItemResource resource, int amount, TransactionCont TransferPreconditions.checkNonEmptyNonNegative(resource, amount); UnlimitedItemStack stack = this.stacks.get(index); - int count = stack.count(); if (!stack.isSameItemSameComponents(resource)) { return 0; } + int count = stack.count(); int extracted = Math.min(amount, count); if (extracted <= 0) { return 0; @@ -162,7 +271,7 @@ public int extract(int index, ItemResource resource, int amount, TransactionCont this.snapshotJournals.get(index).updateSnapshots(transaction); this.stacks.set(index, new UnlimitedItemStack(resource, count - extracted)); this.space -= TypeLimitItemStacksResourceHandler.computeSpace(resource, extracted); - return amount; + return extracted; } private void updateStacksSize() { @@ -178,22 +287,37 @@ private void updateStacksSize() { } } - @Override - public void serialize(ValueOutput output) { - NonNullList saving = TypeLimitItemStacksResourceHandler.constructStackList(); - for (UnlimitedItemStack stack : this.stacks) { + public void sync(TypeLimitItemStacksResourceHandler items) { + this.typeLimit = items.typeLimit; + this.spaceSize = items.spaceSize; + this.initStacks(items.stacks); + } + + private static NonNullList trim(NonNullList list) { + NonNullList result = TypeLimitItemStacksResourceHandler.constructStackList(); + for (UnlimitedItemStack stack : list) { if (stack.isEmpty()) { continue; } - saving.add(stack); + result.add(stack); } - output.store(TypeLimitItemStacksResourceHandler.VALUE_IO_KEY, TypeLimitItemStacksResourceHandler.STACKS_CODEC, saving); + return result; + } + + @Override + public void serialize(ValueOutput output) { + NonNullList saving = TypeLimitItemStacksResourceHandler.trim(this.stacks); + output.store(TypeLimitItemStacksResourceHandler.STACKS_KEY, TypeLimitItemStacksResourceHandler.STACKS_CODEC, saving); + output.putInt(TypeLimitItemStacksResourceHandler.TYPE_LIMIT_KEY, this.typeLimit); + output.putInt(TypeLimitItemStacksResourceHandler.SPACE_SIZE_KEY, this.spaceSize); } @Override public void deserialize(ValueInput input) { + this.typeLimit = input.getIntOr(TypeLimitItemStacksResourceHandler.TYPE_LIMIT_KEY, Integer.MAX_VALUE); + input.getInt(TypeLimitItemStacksResourceHandler.SPACE_SIZE_KEY).ifPresent(size -> this.spaceSize = size); Optional> stacksOp = input.read( - TypeLimitItemStacksResourceHandler.VALUE_IO_KEY, + TypeLimitItemStacksResourceHandler.STACKS_KEY, TypeLimitItemStacksResourceHandler.STACKS_CODEC ); if (stacksOp.isEmpty()) { diff --git a/src/main/java/dev/dubhe/anvilcraft/block/container/CrateBlock.java b/src/main/java/dev/dubhe/anvilcraft/block/container/CrateBlock.java deleted file mode 100644 index 5996721da3..0000000000 --- a/src/main/java/dev/dubhe/anvilcraft/block/container/CrateBlock.java +++ /dev/null @@ -1,9 +0,0 @@ -package dev.dubhe.anvilcraft.block.container; - -import net.minecraft.world.level.block.Block; - -public class CrateBlock extends Block { - public CrateBlock(Properties properties) { - super(properties); - } -} diff --git a/src/main/java/dev/dubhe/anvilcraft/block/container/storage/CrateBlock.java b/src/main/java/dev/dubhe/anvilcraft/block/container/storage/CrateBlock.java new file mode 100644 index 0000000000..a6c30970b9 --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/block/container/storage/CrateBlock.java @@ -0,0 +1,65 @@ +package dev.dubhe.anvilcraft.block.container.storage; + +import dev.dubhe.anvilcraft.block.entity.storage.CrateBlockEntity; +import dev.dubhe.anvilcraft.init.ModMenuTypes; +import dev.dubhe.anvilcraft.init.block.ModBlockEntities; +import dev.dubhe.anvilcraft.network.multiple.StoragePackets; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.GameType; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.EntityBlock; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.BlockHitResult; +import org.jspecify.annotations.Nullable; + +public class CrateBlock extends Block implements EntityBlock { + public CrateBlock(Properties properties) { + super(properties); + } + + @Override + public @Nullable BlockEntity newBlockEntity(BlockPos pos, BlockState state) { + return ModBlockEntities.CRATE.create(pos, state); + } + + @Override + public BlockState playerWillDestroy(Level level, BlockPos pos, BlockState state, Player player) { + BlockEntity blockEntity = level.getBlockEntity(pos); + if (blockEntity instanceof CrateBlockEntity be) { + be.playerWillDestroy(level, pos, state, player); + } + + return super.playerWillDestroy(level, pos, state, player); + } + + @Override + protected InteractionResult useItemOn( + ItemStack itemStack, + BlockState state, + Level level, + BlockPos pos, + Player player, + InteractionHand hand, + BlockHitResult hitResult + ) { + BlockEntity blockEntity = level.getBlockEntity(pos); + if (blockEntity instanceof CrateBlockEntity entity) { + if (player instanceof ServerPlayer serverPlayer) { + if (serverPlayer.gameMode.getGameModeForPlayer() == GameType.SPECTATOR) return InteractionResult.PASS; + StoragePackets.sync(serverPlayer, entity.getId(), serverPlayer.registryAccess()); + ModMenuTypes.open(serverPlayer, entity, pos); + return InteractionResult.SUCCESS_SERVER; + } else { + return InteractionResult.SUCCESS; + } + } + return super.useItemOn(itemStack, state, level, pos, player, hand, hitResult); + } +} diff --git a/src/main/java/dev/dubhe/anvilcraft/block/container/HyperdimensionStorageStationBlock.java b/src/main/java/dev/dubhe/anvilcraft/block/container/storage/HyperdimensionStorageStationBlock.java similarity index 74% rename from src/main/java/dev/dubhe/anvilcraft/block/container/HyperdimensionStorageStationBlock.java rename to src/main/java/dev/dubhe/anvilcraft/block/container/storage/HyperdimensionStorageStationBlock.java index bd048a193d..8753fc0dec 100644 --- a/src/main/java/dev/dubhe/anvilcraft/block/container/HyperdimensionStorageStationBlock.java +++ b/src/main/java/dev/dubhe/anvilcraft/block/container/storage/HyperdimensionStorageStationBlock.java @@ -1,14 +1,24 @@ -package dev.dubhe.anvilcraft.block.container; +package dev.dubhe.anvilcraft.block.container.storage; import dev.anvilcraft.lib.v2.util.ShapeUtil; import dev.dubhe.anvilcraft.api.hammer.IHammerRemovable; +import dev.dubhe.anvilcraft.block.entity.storage.HyperdimensionStorageStationBlockEntity; import dev.dubhe.anvilcraft.block.multipart.MultiPartBlockEntity; import dev.dubhe.anvilcraft.block.multipart.SimpleMultiPartBlock; import dev.dubhe.anvilcraft.block.state.Cube3x3PartHalf; +import dev.dubhe.anvilcraft.init.ModMenuTypes; import dev.dubhe.anvilcraft.init.block.ModBlockEntities; +import dev.dubhe.anvilcraft.network.multiple.StoragePackets; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.GameType; +import net.minecraft.world.level.Level; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; @@ -16,6 +26,7 @@ import net.minecraft.world.level.block.state.properties.EnumProperty; import net.minecraft.world.level.block.state.properties.Property; import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.shapes.CollisionContext; import net.minecraft.world.phys.shapes.Shapes; import net.minecraft.world.phys.shapes.VoxelShape; @@ -64,6 +75,40 @@ public BlockEntity createBlockEntity(BlockPos pos, BlockState state) { return ModBlockEntities.HYPERDIMENSION_STORAGE_STATION.create(pos, state); } + @Override + public BlockState playerWillDestroy(Level level, BlockPos pos, BlockState state, Player player) { + BlockEntity blockEntity = level.getBlockEntity(pos); + if (blockEntity instanceof HyperdimensionStorageStationBlockEntity be) { + be.playerWillDestroy(level, pos, state, player); + } + + return super.playerWillDestroy(level, pos, state, player); + } + + @Override + protected InteractionResult useItemOn( + ItemStack itemStack, + BlockState state, + Level level, + BlockPos pos, + Player player, + InteractionHand hand, + BlockHitResult hitResult + ) { + BlockEntity blockEntity = level.getBlockEntity(pos); + if (blockEntity instanceof HyperdimensionStorageStationBlockEntity entity) { + if (player instanceof ServerPlayer serverPlayer) { + if (serverPlayer.gameMode.getGameModeForPlayer() == GameType.SPECTATOR) return InteractionResult.PASS; + StoragePackets.sync(serverPlayer, entity.getId(), serverPlayer.registryAccess()); + ModMenuTypes.open(serverPlayer, entity, pos); + return InteractionResult.SUCCESS_SERVER; + } else { + return InteractionResult.SUCCESS; + } + } + return super.useItemOn(itemStack, state, level, pos, player, hand, hitResult); + } + // region VoxelShapes @Override protected VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) { diff --git a/src/main/java/dev/dubhe/anvilcraft/block/container/LargeCrateBlock.java b/src/main/java/dev/dubhe/anvilcraft/block/container/storage/LargeCrateBlock.java similarity index 76% rename from src/main/java/dev/dubhe/anvilcraft/block/container/LargeCrateBlock.java rename to src/main/java/dev/dubhe/anvilcraft/block/container/storage/LargeCrateBlock.java index 09fb079c86..0d9f380ecf 100644 --- a/src/main/java/dev/dubhe/anvilcraft/block/container/LargeCrateBlock.java +++ b/src/main/java/dev/dubhe/anvilcraft/block/container/storage/LargeCrateBlock.java @@ -1,14 +1,24 @@ -package dev.dubhe.anvilcraft.block.container; +package dev.dubhe.anvilcraft.block.container.storage; import dev.anvilcraft.lib.v2.util.ShapeUtil; import dev.dubhe.anvilcraft.api.hammer.IHammerRemovable; +import dev.dubhe.anvilcraft.block.entity.storage.LargeCrateBlockEntity; import dev.dubhe.anvilcraft.block.multipart.MultiPartBlockEntity; import dev.dubhe.anvilcraft.block.multipart.SimpleMultiPartBlock; import dev.dubhe.anvilcraft.block.state.Cube3x3PartHalf; +import dev.dubhe.anvilcraft.init.ModMenuTypes; import dev.dubhe.anvilcraft.init.block.ModBlockEntities; +import dev.dubhe.anvilcraft.network.multiple.StoragePackets; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.GameType; +import net.minecraft.world.level.Level; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; @@ -16,6 +26,7 @@ import net.minecraft.world.level.block.state.properties.EnumProperty; import net.minecraft.world.level.block.state.properties.Property; import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.shapes.CollisionContext; import net.minecraft.world.phys.shapes.Shapes; import net.minecraft.world.phys.shapes.VoxelShape; @@ -64,6 +75,40 @@ public BlockEntity createBlockEntity(BlockPos pos, BlockState state) { return ModBlockEntities.LARGE_CRATE.create(pos, state); } + @Override + public BlockState playerWillDestroy(Level level, BlockPos pos, BlockState state, Player player) { + BlockEntity blockEntity = level.getBlockEntity(pos); + if (blockEntity instanceof LargeCrateBlockEntity be) { + be.playerWillDestroy(level, pos, state, player); + } + + return super.playerWillDestroy(level, pos, state, player); + } + + @Override + protected InteractionResult useItemOn( + ItemStack itemStack, + BlockState state, + Level level, + BlockPos pos, + Player player, + InteractionHand hand, + BlockHitResult hitResult + ) { + BlockEntity blockEntity = level.getBlockEntity(pos); + if (blockEntity instanceof LargeCrateBlockEntity entity) { + if (player instanceof ServerPlayer serverPlayer) { + if (serverPlayer.gameMode.getGameModeForPlayer() == GameType.SPECTATOR) return InteractionResult.PASS; + StoragePackets.sync(serverPlayer, entity.getId(), serverPlayer.registryAccess()); + ModMenuTypes.open(serverPlayer, entity, pos); + return InteractionResult.SUCCESS_SERVER; + } else { + return InteractionResult.SUCCESS; + } + } + return super.useItemOn(itemStack, state, level, pos, player, hand, hitResult); + } + // region VoxelShapes @Override protected VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) { diff --git a/src/main/java/dev/dubhe/anvilcraft/block/container/ShulkerContainerBlock.java b/src/main/java/dev/dubhe/anvilcraft/block/container/storage/ShulkerContainerBlock.java similarity index 84% rename from src/main/java/dev/dubhe/anvilcraft/block/container/ShulkerContainerBlock.java rename to src/main/java/dev/dubhe/anvilcraft/block/container/storage/ShulkerContainerBlock.java index cdca495d9f..1f2475d584 100644 --- a/src/main/java/dev/dubhe/anvilcraft/block/container/ShulkerContainerBlock.java +++ b/src/main/java/dev/dubhe/anvilcraft/block/container/storage/ShulkerContainerBlock.java @@ -1,17 +1,26 @@ -package dev.dubhe.anvilcraft.block.container; +package dev.dubhe.anvilcraft.block.container.storage; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import dev.anvilcraft.lib.v2.util.ShapeUtil; import dev.dubhe.anvilcraft.api.hammer.IHammerRemovable; +import dev.dubhe.anvilcraft.block.entity.storage.ShulkerContainerBlockEntity; import dev.dubhe.anvilcraft.block.multipart.FlexibleMultiPartBlock; import dev.dubhe.anvilcraft.block.multipart.MultiPartBlockEntity; import dev.dubhe.anvilcraft.block.state.OpenedCube3x3PartHalf; +import dev.dubhe.anvilcraft.init.ModMenuTypes; import dev.dubhe.anvilcraft.init.block.ModBlockEntities; +import dev.dubhe.anvilcraft.network.multiple.StoragePackets; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.core.Vec3i; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.GameType; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Mirror; @@ -23,6 +32,7 @@ import net.minecraft.world.level.block.state.properties.Property; import net.minecraft.world.level.pathfinder.PathComputationType; import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.shapes.CollisionContext; import net.minecraft.world.phys.shapes.Shapes; import net.minecraft.world.phys.shapes.VoxelShape; @@ -190,6 +200,40 @@ public BlockEntity createBlockEntity(BlockPos pos, BlockState state) { return ModBlockEntities.SHULKER_CONTAINER.create(pos, state); } + @Override + public BlockState playerWillDestroy(Level level, BlockPos pos, BlockState state, Player player) { + BlockEntity blockEntity = level.getBlockEntity(pos); + if (blockEntity instanceof ShulkerContainerBlockEntity be) { + be.playerWillDestroy(level, pos, state, player); + } + + return super.playerWillDestroy(level, pos, state, player); + } + + @Override + protected InteractionResult useItemOn( + ItemStack itemStack, + BlockState state, + Level level, + BlockPos pos, + Player player, + InteractionHand hand, + BlockHitResult hitResult + ) { + BlockEntity blockEntity = level.getBlockEntity(pos); + if (blockEntity instanceof ShulkerContainerBlockEntity entity) { + if (player instanceof ServerPlayer serverPlayer) { + if (serverPlayer.gameMode.getGameModeForPlayer() == GameType.SPECTATOR) return InteractionResult.PASS; + StoragePackets.sync(serverPlayer, entity.getId(), serverPlayer.registryAccess()); + ModMenuTypes.open(serverPlayer, entity, pos); + return InteractionResult.SUCCESS_SERVER; + } else { + return InteractionResult.SUCCESS; + } + } + return super.useItemOn(itemStack, state, level, pos, player, hand, hitResult); + } + // region VoxelShapes @Override protected VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) { diff --git a/src/main/java/dev/dubhe/anvilcraft/block/container/storage/package-info.java b/src/main/java/dev/dubhe/anvilcraft/block/container/storage/package-info.java new file mode 100644 index 0000000000..0edfe95ac2 --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/block/container/storage/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package dev.dubhe.anvilcraft.block.container.storage; + +import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/dev/dubhe/anvilcraft/block/entity/FeCollectorBlockEntity.java b/src/main/java/dev/dubhe/anvilcraft/block/entity/FeCollectorBlockEntity.java index e53d78c0df..4246835f93 100644 --- a/src/main/java/dev/dubhe/anvilcraft/block/entity/FeCollectorBlockEntity.java +++ b/src/main/java/dev/dubhe/anvilcraft/block/entity/FeCollectorBlockEntity.java @@ -1,6 +1,7 @@ package dev.dubhe.anvilcraft.block.entity; import dev.dubhe.anvilcraft.AnvilCraft; +import dev.dubhe.anvilcraft.api.energy.IEnergyHandlerHolder; import dev.dubhe.anvilcraft.api.power.IPowerProducer; import dev.dubhe.anvilcraft.api.power.PowerGrid; import dev.dubhe.anvilcraft.api.tooltip.providers.IHasAffectRange; @@ -30,7 +31,7 @@ import net.neoforged.neoforge.transfer.transaction.TransactionContext; import org.jspecify.annotations.Nullable; -public class FeCollectorBlockEntity extends BlockEntity implements IPowerProducer, IHasAffectRange { +public class FeCollectorBlockEntity extends BlockEntity implements IPowerProducer, IHasAffectRange, IEnergyHandlerHolder { public static final int MAX_ENERGY = 1_000_000; static final int FE_PER_TICK = 10_000; public static final int PRODUCE_THRESHOLD = 400_000; @@ -115,8 +116,9 @@ Direction[] getConnectedSides() { : new Direction[]{Direction.NORTH, Direction.SOUTH}; } + @Override @Nullable - public EnergyHandler getEnergyStorage(@Nullable Direction side) { + public EnergyHandler getEnergyHandler(@Nullable Direction side) { if (side == null) return new FeEnergyStore(null); for (Direction d : this.getConnectedSides()) { if (d == side) return new FeEnergyStore(side); diff --git a/src/main/java/dev/dubhe/anvilcraft/block/entity/PowerConverterBlockEntity.java b/src/main/java/dev/dubhe/anvilcraft/block/entity/PowerConverterBlockEntity.java index 9562ebbec4..04c4d01f93 100644 --- a/src/main/java/dev/dubhe/anvilcraft/block/entity/PowerConverterBlockEntity.java +++ b/src/main/java/dev/dubhe/anvilcraft/block/entity/PowerConverterBlockEntity.java @@ -1,6 +1,7 @@ package dev.dubhe.anvilcraft.block.entity; import dev.dubhe.anvilcraft.AnvilCraft; +import dev.dubhe.anvilcraft.api.energy.IEnergyHandlerHolder; import dev.dubhe.anvilcraft.api.power.IPowerConsumer; import dev.dubhe.anvilcraft.api.power.PowerGrid; import dev.dubhe.anvilcraft.block.power.converter.BasePowerConverterBlock; @@ -27,7 +28,7 @@ import net.neoforged.neoforge.transfer.transaction.TransactionContext; import org.jspecify.annotations.Nullable; -public class PowerConverterBlockEntity extends BlockEntity implements IPowerConsumer { +public class PowerConverterBlockEntity extends BlockEntity implements IPowerConsumer, IEnergyHandlerHolder { @Getter @Setter private @Nullable PowerGrid grid = null; @@ -54,7 +55,8 @@ int getMaxEnergy() { return this.inputPower * 10000; } - public @Nullable EnergyHandler getEnergyStorage(@Nullable Direction side) { + @Override + public @Nullable EnergyHandler getEnergyHandler(@Nullable Direction side) { if (side == null) return new PowerConverterEnergyStore(); if (side == getBlockState().getValue(BasePowerConverterBlock.FACING)) return new PowerConverterEnergyStore(); return null; diff --git a/src/main/java/dev/dubhe/anvilcraft/block/entity/container/HyperdimensionStorageStationBlockEntity.java b/src/main/java/dev/dubhe/anvilcraft/block/entity/container/HyperdimensionStorageStationBlockEntity.java deleted file mode 100644 index 83b6e94adf..0000000000 --- a/src/main/java/dev/dubhe/anvilcraft/block/entity/container/HyperdimensionStorageStationBlockEntity.java +++ /dev/null @@ -1,12 +0,0 @@ -package dev.dubhe.anvilcraft.block.entity.container; - -import net.minecraft.core.BlockPos; -import net.minecraft.world.level.block.entity.BlockEntity; -import net.minecraft.world.level.block.entity.BlockEntityType; -import net.minecraft.world.level.block.state.BlockState; - -public class HyperdimensionStorageStationBlockEntity extends BlockEntity { - public HyperdimensionStorageStationBlockEntity(BlockEntityType type, BlockPos worldPosition, BlockState blockState) { - super(type, worldPosition, blockState); - } -} diff --git a/src/main/java/dev/dubhe/anvilcraft/block/entity/container/LargeCrateBlockEntity.java b/src/main/java/dev/dubhe/anvilcraft/block/entity/container/LargeCrateBlockEntity.java deleted file mode 100644 index d356a4d400..0000000000 --- a/src/main/java/dev/dubhe/anvilcraft/block/entity/container/LargeCrateBlockEntity.java +++ /dev/null @@ -1,12 +0,0 @@ -package dev.dubhe.anvilcraft.block.entity.container; - -import net.minecraft.core.BlockPos; -import net.minecraft.world.level.block.entity.BlockEntity; -import net.minecraft.world.level.block.entity.BlockEntityType; -import net.minecraft.world.level.block.state.BlockState; - -public class LargeCrateBlockEntity extends BlockEntity { - public LargeCrateBlockEntity(BlockEntityType type, BlockPos worldPosition, BlockState blockState) { - super(type, worldPosition, blockState); - } -} diff --git a/src/main/java/dev/dubhe/anvilcraft/block/entity/container/ShulkerContainerBlockEntity.java b/src/main/java/dev/dubhe/anvilcraft/block/entity/container/ShulkerContainerBlockEntity.java deleted file mode 100644 index ae2f256735..0000000000 --- a/src/main/java/dev/dubhe/anvilcraft/block/entity/container/ShulkerContainerBlockEntity.java +++ /dev/null @@ -1,12 +0,0 @@ -package dev.dubhe.anvilcraft.block.entity.container; - -import net.minecraft.core.BlockPos; -import net.minecraft.world.level.block.entity.BlockEntity; -import net.minecraft.world.level.block.entity.BlockEntityType; -import net.minecraft.world.level.block.state.BlockState; - -public class ShulkerContainerBlockEntity extends BlockEntity { // TODO: 实现潜影集装箱功能 - public ShulkerContainerBlockEntity(BlockEntityType type, BlockPos pos, BlockState state) { - super(type, pos, state); - } -} diff --git a/src/main/java/dev/dubhe/anvilcraft/block/entity/storage/CrateBlockEntity.java b/src/main/java/dev/dubhe/anvilcraft/block/entity/storage/CrateBlockEntity.java new file mode 100644 index 0000000000..4644f8c60c --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/block/entity/storage/CrateBlockEntity.java @@ -0,0 +1,12 @@ +package dev.dubhe.anvilcraft.block.entity.storage; + +import dev.dubhe.anvilcraft.saved.storage.StorageType; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.state.BlockState; + +public class CrateBlockEntity extends StorageBlockEntity { + public CrateBlockEntity(BlockEntityType type, BlockPos pos, BlockState state) { + super(type, pos, state, StorageType.CRATE); + } +} diff --git a/src/main/java/dev/dubhe/anvilcraft/block/entity/storage/HyperdimensionStorageStationBlockEntity.java b/src/main/java/dev/dubhe/anvilcraft/block/entity/storage/HyperdimensionStorageStationBlockEntity.java new file mode 100644 index 0000000000..0423d16688 --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/block/entity/storage/HyperdimensionStorageStationBlockEntity.java @@ -0,0 +1,12 @@ +package dev.dubhe.anvilcraft.block.entity.storage; + +import dev.dubhe.anvilcraft.saved.storage.StorageType; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.state.BlockState; + +public class HyperdimensionStorageStationBlockEntity extends StorageBlockEntity { + public HyperdimensionStorageStationBlockEntity(BlockEntityType type, BlockPos pos, BlockState state) { + super(type, pos, state, StorageType.HYPERDIMENSION); + } +} diff --git a/src/main/java/dev/dubhe/anvilcraft/block/entity/storage/LargeCrateBlockEntity.java b/src/main/java/dev/dubhe/anvilcraft/block/entity/storage/LargeCrateBlockEntity.java new file mode 100644 index 0000000000..14c86fb438 --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/block/entity/storage/LargeCrateBlockEntity.java @@ -0,0 +1,12 @@ +package dev.dubhe.anvilcraft.block.entity.storage; + +import dev.dubhe.anvilcraft.saved.storage.StorageType; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.state.BlockState; + +public class LargeCrateBlockEntity extends StorageBlockEntity { + public LargeCrateBlockEntity(BlockEntityType type, BlockPos pos, BlockState state) { + super(type, pos, state, StorageType.LARGE_CRATE); + } +} diff --git a/src/main/java/dev/dubhe/anvilcraft/block/entity/storage/ShulkerContainerBlockEntity.java b/src/main/java/dev/dubhe/anvilcraft/block/entity/storage/ShulkerContainerBlockEntity.java new file mode 100644 index 0000000000..61ddac9a23 --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/block/entity/storage/ShulkerContainerBlockEntity.java @@ -0,0 +1,12 @@ +package dev.dubhe.anvilcraft.block.entity.storage; + +import dev.dubhe.anvilcraft.saved.storage.StorageType; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.state.BlockState; + +public class ShulkerContainerBlockEntity extends StorageBlockEntity { // TODO: 实现潜影集装箱功能 + public ShulkerContainerBlockEntity(BlockEntityType type, BlockPos pos, BlockState state) { + super(type, pos, state, StorageType.SHULKER_CONTAINER); + } +} diff --git a/src/main/java/dev/dubhe/anvilcraft/block/entity/storage/StorageBlockEntity.java b/src/main/java/dev/dubhe/anvilcraft/block/entity/storage/StorageBlockEntity.java new file mode 100644 index 0000000000..6830243847 --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/block/entity/storage/StorageBlockEntity.java @@ -0,0 +1,131 @@ +package dev.dubhe.anvilcraft.block.entity.storage; + +import dev.dubhe.anvilcraft.init.ModMenuTypes; +import dev.dubhe.anvilcraft.init.item.ModComponents; +import dev.dubhe.anvilcraft.inventory.StorageMenu; +import dev.dubhe.anvilcraft.inventory.state.StorageMenuState; +import dev.dubhe.anvilcraft.item.property.component.StorageRef; +import dev.dubhe.anvilcraft.saved.storage.StorageType; +import lombok.Getter; +import net.minecraft.core.BlockPos; +import net.minecraft.core.HolderLookup; +import net.minecraft.core.UUIDUtil; +import net.minecraft.core.component.DataComponentGetter; +import net.minecraft.core.component.DataComponentMap; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.Connection; +import net.minecraft.network.chat.Component; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.game.ClientGamePacketListener; +import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.entity.item.ItemEntity; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.storage.ValueInput; +import net.minecraft.world.level.storage.ValueOutput; +import org.jspecify.annotations.Nullable; + +import java.util.UUID; + +@Getter +public class StorageBlockEntity extends BlockEntity implements MenuProvider { + private final StorageType storageType; + private @Nullable UUID id; + + public StorageBlockEntity(BlockEntityType type, BlockPos pos, BlockState state, StorageType storageType) { + super(type, pos, state); + this.storageType = storageType; + } + + public void setId(UUID id) { + if (this.id != null) { + return; + } + this.id = id; + if (this.level != null) { + BlockState state = this.getBlockState(); + this.level.sendBlockUpdated(this.getBlockPos(), state, state, Block.UPDATE_ALL); + } + } + + @Override + protected void saveAdditional(ValueOutput output) { + if (this.id != null) { + output.store("storage_id", UUIDUtil.CODEC, this.id); + } + } + + @Override + protected void loadAdditional(ValueInput input) { + // 信任加载的数据 + input.read("storage_id", UUIDUtil.CODEC).ifPresent(id -> this.id = id); + } + + @Override + public void onDataPacket(Connection net, ValueInput input) { + // 信任服务端传来的数据 + input.read("storage_id", UUIDUtil.CODEC).ifPresent(id -> this.id = id); + } + + @Override + public CompoundTag getUpdateTag(HolderLookup.Provider registries) { + CompoundTag tag = new CompoundTag(); + tag.store("storage_id", UUIDUtil.CODEC, this.id); + return tag; + } + + @Override + public Packet getUpdatePacket() { + return ClientboundBlockEntityDataPacket.create(this); + } + + @Override + protected void applyImplicitComponents(DataComponentGetter components) { + StorageRef ref = components.get(ModComponents.STORAGE); + if (ref.type() != this.storageType) { + return; + } + this.setId(ref.id().orElse(UUID.randomUUID())); + } + + @Override + protected void collectImplicitComponents(DataComponentMap.Builder components) { + components.set(ModComponents.STORAGE, new StorageRef(this.storageType, this.id)); + } + + @Override + public Component getDisplayName() { + return this.getBlockState().getBlock().getName(); + } + + @Override + public @Nullable AbstractContainerMenu createMenu(int containerId, Inventory inventory, Player player) { + return new StorageMenu(ModMenuTypes.STORAGE.get(), containerId, inventory, this); + } + + @Override + public void setRemoved() { + super.setRemoved(); + if (this.id != null) { + StorageMenuState.clear(this.id); + } + } + + public void playerWillDestroy(Level level, BlockPos pos, BlockState state, Player player) { + if (!level.isClientSide() && player.preventsBlockDrops() && this.getId() != null) { + ItemStack itemStack = new ItemStack(state.getBlock()); + itemStack.applyComponents(this.collectComponents()); + ItemEntity entity = new ItemEntity(level, pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5, itemStack); + entity.setDefaultPickUpDelay(); + level.addFreshEntity(entity); + } + } +} diff --git a/src/main/java/dev/dubhe/anvilcraft/block/entity/storage/package-info.java b/src/main/java/dev/dubhe/anvilcraft/block/entity/storage/package-info.java new file mode 100644 index 0000000000..f3e611dc73 --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/block/entity/storage/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package dev.dubhe.anvilcraft.block.entity.storage; + +import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/dev/dubhe/anvilcraft/client/event/ClientEventListener.java b/src/main/java/dev/dubhe/anvilcraft/client/event/ClientEventListener.java index 0ee2ff337b..26f2ea28bb 100644 --- a/src/main/java/dev/dubhe/anvilcraft/client/event/ClientEventListener.java +++ b/src/main/java/dev/dubhe/anvilcraft/client/event/ClientEventListener.java @@ -16,6 +16,7 @@ import dev.dubhe.anvilcraft.client.support.StructureDiskPreviewSupport; import dev.dubhe.anvilcraft.init.block.ModBlocks; import dev.dubhe.anvilcraft.init.item.ModItems; +import dev.dubhe.anvilcraft.inventory.state.StorageMenuState; import dev.dubhe.anvilcraft.item.tool.AnvilHammerItem; import dev.dubhe.anvilcraft.network.UsePillBoxPacket; import dev.dubhe.anvilcraft.recipe.sync.RecipesRecord; @@ -94,6 +95,7 @@ public static void onRenderBlockOverlay(RenderBlockScreenEffectEvent event) { public static void onClientPlayerDisconnect(ClientPlayerNetworkEvent.LoggingOut event) { SoundHelper.INSTANCE.clear(); RecipesRecord.CLIENTSIDE = null; + StorageMenuState.clear(); } @SubscribeEvent diff --git a/src/main/java/dev/dubhe/anvilcraft/client/gui/component/RenderableWidgetAdder.java b/src/main/java/dev/dubhe/anvilcraft/client/gui/component/RenderableWidgetAdder.java new file mode 100644 index 0000000000..3519a18059 --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/client/gui/component/RenderableWidgetAdder.java @@ -0,0 +1,9 @@ +package dev.dubhe.anvilcraft.client.gui.component; + +import net.minecraft.client.gui.components.Renderable; +import net.minecraft.client.gui.components.events.GuiEventListener; +import net.minecraft.client.gui.narration.NarratableEntry; + +public interface RenderableWidgetAdder { + T addRenderableWidget(T widget); +} diff --git a/src/main/java/dev/dubhe/anvilcraft/client/gui/component/category/CategoryButton.java b/src/main/java/dev/dubhe/anvilcraft/client/gui/component/category/CategoryButton.java new file mode 100644 index 0000000000..a9c450096f --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/client/gui/component/category/CategoryButton.java @@ -0,0 +1,103 @@ +package dev.dubhe.anvilcraft.client.gui.component.category; + +import dev.anvilcraft.lib.v2.util.component.MultilineComponentHelper; +import dev.dubhe.anvilcraft.client.support.GuiRenderSupport; +import dev.dubhe.anvilcraft.constant.SharedTextures; +import dev.dubhe.anvilcraft.saved.setting.PlayerSetting; +import dev.dubhe.anvilcraft.saved.storage.category.ICategory; +import dev.dubhe.anvilcraft.saved.storage.category.store.CategoryEntry; +import dev.dubhe.anvilcraft.saved.storage.category.store.CategoryMode; +import lombok.Getter; +import lombok.Setter; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.GuiGraphicsExtractor; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.components.Tooltip; +import net.minecraft.client.renderer.RenderPipelines; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.Identifier; +import net.minecraft.world.item.ItemStack; +import org.joml.Matrix3x2fStack; + +@Getter +@Setter +public class CategoryButton extends Button { + public static final Identifier BACKGROUND = SharedTextures.textureGui("misc/storage_station/category"); + private final Font font = Minecraft.getInstance().font; + private final PlayerSetting setting; + private final int index; + private CategoryMode mode; + + public CategoryButton(int x, PlayerSetting setting, int index, CategoryMode mode, OnPress onPress) { + super( + x, + 0, + 86, + 20, + Component.empty(), + button -> { + if (!(button instanceof CategoryButton category)) return; + category.mode = category.entry().changeMode(); + onPress.onPress(button); + }, + DEFAULT_NARRATION + ); + this.setting = setting; + this.index = index; + this.mode = mode; + } + + protected CategoryEntry entry() { + return this.setting.listed().get(this.index); + } + + @Override + protected void extractContents(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float a) { + this.isHovered = this.isMouseOver(mouseX, mouseY); + int offsetV = this.mode.getTexYDiff(); + if (this.isHovered) { + offsetV = 20; // Hovered part + } + + graphics.blit( + RenderPipelines.GUI_TEXTURED, + CategoryButton.BACKGROUND, + this.getX(), + this.getY(), + 0, + offsetV, + this.width, + this.height, + 86, + 80 + ); + + Matrix3x2fStack pose = graphics.pose(); + pose.pushMatrix(); + pose.translate(this.getX(), this.getY()); + pose.scale(0.75f, 0.75f); + + ICategory category = this.entry().getCategory(); + + ItemStack icon = category.icon().create(); + int x = 4; + int y = 4; + graphics.fakeItem(icon, x, y); + graphics.itemDecorations(this.font, icon, x, y); + + pose.popMatrix(); + + Component name = category.name(); + this.setTooltip(Tooltip.create( + MultilineComponentHelper.create() + .addln("screen.anvilcraft.storage.category.name", name) + .addln("screen.anvilcraft.storage.category.mode", this.mode.getModeName()) + .build() + )); + + int left = this.getX() + 17; + int top = this.getY() + 5; + GuiRenderSupport.centeredEllipsisText(graphics, this.font, name, left, top, 65); + } +} diff --git a/src/main/java/dev/dubhe/anvilcraft/client/gui/component/category/CategoryList.java b/src/main/java/dev/dubhe/anvilcraft/client/gui/component/category/CategoryList.java new file mode 100644 index 0000000000..1671c0900e --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/client/gui/component/category/CategoryList.java @@ -0,0 +1,184 @@ +package dev.dubhe.anvilcraft.client.gui.component.category; + +import dev.anvilcraft.lib.v2.util.ListUtil; +import dev.anvilcraft.lib.v2.util.MathUtil; +import dev.anvilcraft.lib.v2.util.Scrollable; +import dev.dubhe.anvilcraft.client.gui.component.TexturedButton; +import dev.dubhe.anvilcraft.constant.SharedTextures; +import dev.dubhe.anvilcraft.saved.setting.PlayerSetting; +import dev.dubhe.anvilcraft.saved.storage.category.store.CategoryEntry; +import net.minecraft.client.gui.GuiGraphicsExtractor; +import net.minecraft.client.gui.components.AbstractContainerWidget; +import net.minecraft.client.gui.components.AbstractScrollArea; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.narration.NarrationElementOutput; +import net.minecraft.client.input.MouseButtonEvent; +import net.minecraft.client.renderer.RenderPipelines; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.Identifier; + +import java.util.ArrayList; +import java.util.List; + +public class CategoryList extends AbstractContainerWidget { + private static final Identifier SETTING_BUTTON_BACKGROUND = SharedTextures.textureGui("misc/storage_station/category_setting"); + public static final Identifier SMALL_SLIDER = SharedTextures.textureGui("misc/storage_station/slider_small"); + private final List categoryButtons; + private final Button.OnPress categoryOnPress; + private final TexturedButton settingButton; + + private final Scrollable scrollable = new Scrollable() { + @Override + public int row() { + return 8; + } + + @Override + public int column() { + return 1; + } + + @Override + public int size() { + return CategoryList.this.children().size() + 1; + } + + @Override + public void setHead(int head) { + CategoryList.this.head = head; + } + }; + private int head = 0; + + public CategoryList(int x, int y, PlayerSetting setting, Button.OnPress categoryOnPress, Button.OnPress openSetting) { + super(x, y, 92, 160, Component.empty(), AbstractScrollArea.defaultSettings(1)); + + this.categoryButtons = new ArrayList<>(); + this.categoryOnPress = categoryOnPress; + + this.settingButton = new TexturedButton( + x, + 0, + 86, + 20, + CategoryList.SETTING_BUTTON_BACKGROUND, + 20, + 86, + 40, + openSetting + ); + + this.rebuild(setting); + } + + public void rebuild(PlayerSetting setting) { + this.categoryButtons.clear(); + List listed = setting.listed(); + for (int i = 0; i < listed.size(); i++) { + CategoryEntry entry = listed.get(i); + this.categoryButtons.add(new CategoryButton(this.getX(), setting, i, entry.getMode(), this.categoryOnPress)); + } + this.scrollable.scrollTo(); + } + + @Override + public boolean mouseClicked(MouseButtonEvent event, boolean doubleClick) { + if (event.button() == 0 && this.insideScrollbar(event.x(), event.y())) { + this.scrollable.scrolling(); + return true; + } + return super.mouseClicked(event, doubleClick); + } + + @Override + public boolean mouseReleased(MouseButtonEvent event) { + if (event.button() == 0 && this.scrollable.isScrolling()) { + this.scrollable.notScrolling(); + return true; + } + + return super.mouseReleased(event); + } + + @Override + public boolean mouseDragged(MouseButtonEvent event, double dx, double dy) { + if (this.scrollable.isScrolling()) { + int top = this.getY() + 18; + this.scrollable.scrollOnDrag(10, event.y(), top, top + 112); + return true; + } + return super.mouseDragged(event, dx, dy); + } + + @Override + public boolean mouseScrolled(double mouseX, double mouseY, double scrollX, double scrollY) { + if (!this.scrollable.canScroll()) { + return false; + } else { + this.scrollable.scrollOnScroll(scrollY / 1.2); + return true; + } + } + + @Override + protected int contentHeight() { + return 0; + } + + @Override + protected void extractWidgetRenderState(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float a) { + int areaSize = this.scrollable.column() * this.scrollable.row(); + int end = this.head + Math.min(this.scrollable.size() - this.head, areaSize); + for (int i = this.head; i < end; i++) { + Button button = ListUtil.safelyGet(this.children(), i).orElse(null); + if (button == null) continue; + button.active = true; + button.setPosition(this.getX(), this.getY() + i * 20); + button.extractRenderState(graphics, mouseX, mouseY, a); + } + } + + @Override + protected void extractScrollbar(GuiGraphicsExtractor graphics, int mouseX, int mouseY) { + if (this.scrollable.canScroll()) { + int top = this.getY(); + int bottom = top + this.getHeight(); + graphics.blit( + RenderPipelines.GUI_TEXTURED, + CategoryList.SMALL_SLIDER, + this.getX() + 88, + top + (int) ((float) (bottom - top - 10) * this.scrollable.getScrollOffs()), + 0, + 0, + 4, + 10, + 4, + 10 + ); + } + } + + @Override + protected void updateWidgetNarration(NarrationElementOutput narrationElementOutput) { + } + + private final List