├── fabric ├── gradle.properties └── src │ └── main │ ├── resources │ ├── assets │ │ └── shulkerboxtooltip │ │ │ └── icon.png │ ├── shulkerboxtooltip-fabric.mixins.json │ └── fabric.mod.json │ └── java │ └── com │ └── misterpemodder │ └── shulkerboxtooltip │ ├── fabric │ ├── package-info.java │ ├── ShulkerBoxTooltipImpl.java │ └── ShulkerBoxTooltipClientImpl.java │ ├── impl │ ├── fabric │ │ ├── package-info.java │ │ └── PluginManagerImpl.java │ ├── network │ │ └── fabric │ │ │ ├── package-info.java │ │ │ ├── FabricS2CChannel.java │ │ │ ├── FabricChannel.java │ │ │ ├── ClientNetworkingImpl.java │ │ │ ├── ServerNetworkingImpl.java │ │ │ └── FabricC2SChannel.java │ ├── util │ │ └── fabric │ │ │ └── EnvironmentUtilImpl.java │ └── ModMenuCompat.java │ └── mixin │ └── client │ └── fabric │ ├── GuiGraphicsMixin.java │ ├── ItemStackMixin.java │ └── AbstractContainerScreenMixin.java ├── common ├── gradle.properties ├── src │ └── main │ │ ├── resources │ │ ├── architectury.common.json │ │ ├── assets │ │ │ └── shulkerboxtooltip │ │ │ │ └── textures │ │ │ │ └── gui │ │ │ │ └── sprites │ │ │ │ ├── shulker_box_tooltip.png │ │ │ │ └── shulker_box_tooltip.png.mcmeta │ │ ├── shulkerboxtooltip.accesswidener │ │ └── shulkerboxtooltip-common.mixins.json │ │ └── java │ │ └── com │ │ └── misterpemodder │ │ └── shulkerboxtooltip │ │ ├── impl │ │ ├── package-info.java │ │ ├── tree │ │ │ ├── package-info.java │ │ │ └── ConfigNode.java │ │ ├── util │ │ │ ├── package-info.java │ │ │ ├── ServerEnvironmentUtil.java │ │ │ ├── ClientEnvironmentUtil.java │ │ │ ├── NbtType.java │ │ │ ├── NamedLogger.java │ │ │ ├── ItemKey.java │ │ │ ├── EnvironmentUtil.java │ │ │ ├── Key.java │ │ │ ├── ShulkerBoxTooltipUtil.java │ │ │ └── MergedItemStack.java │ │ ├── config │ │ │ ├── package-info.java │ │ │ ├── annotation │ │ │ │ ├── package-info.java │ │ │ │ ├── AutoTooltip.java │ │ │ │ ├── Synchronize.java │ │ │ │ ├── ConfigCategory.java │ │ │ │ ├── RequiresRestart.java │ │ │ │ └── Validator.java │ │ │ ├── validators │ │ │ │ ├── package-info.java │ │ │ │ └── GreaterThanZero.java │ │ │ ├── gui │ │ │ │ ├── package-info.java │ │ │ │ ├── entry │ │ │ │ │ ├── package-info.java │ │ │ │ │ ├── ConfigEntry.java │ │ │ │ │ ├── PrefixTextConfigEntry.java │ │ │ │ │ ├── CategoryTitleConfigEntry.java │ │ │ │ │ ├── EnumValueConfigEntry.java │ │ │ │ │ ├── BooleanValueConfigEntry.java │ │ │ │ │ └── KeyValueConfigEntry.java │ │ │ │ ├── ColorWidget.java │ │ │ │ └── ConfigEntryList.java │ │ │ ├── SerializationException.java │ │ │ ├── JsonHexadecimalInt.java │ │ │ ├── ClientConfiguration.java │ │ │ └── ConfigurationHandler.java │ │ ├── network │ │ │ ├── package-info.java │ │ │ ├── channel │ │ │ │ ├── package-info.java │ │ │ │ ├── Channel.java │ │ │ │ ├── S2CChannel.java │ │ │ │ └── C2SChannel.java │ │ │ ├── context │ │ │ │ ├── package-info.java │ │ │ │ ├── MessageContext.java │ │ │ │ ├── S2CMessageContext.java │ │ │ │ └── C2SMessageContext.java │ │ │ ├── message │ │ │ │ ├── package-info.java │ │ │ │ ├── C2SEnderChestUpdateRequest.java │ │ │ │ ├── S2CMessages.java │ │ │ │ ├── MessageType.java │ │ │ │ ├── C2SMessages.java │ │ │ │ ├── S2CEnderChestUpdate.java │ │ │ │ ├── C2SHandshakeStart.java │ │ │ │ └── S2CHandshakeResponse.java │ │ │ ├── Payload.java │ │ │ ├── ProtocolVersion.java │ │ │ ├── EnderChestInventoryListener.java │ │ │ ├── ClientNetworking.java │ │ │ └── ServerNetworking.java │ │ ├── tooltip │ │ │ ├── package-info.java │ │ │ ├── PreviewTooltipComponent.java │ │ │ └── PreviewClientTooltipComponent.java │ │ ├── hook │ │ │ ├── package-info.java │ │ │ ├── ContainerScreenLockTooltip.java │ │ │ ├── ContainerScreenDrawTooltip.java │ │ │ └── GuiGraphicsExtensions.java │ │ ├── color │ │ │ ├── package-info.java │ │ │ └── ColorKeyImpl.java │ │ ├── provider │ │ │ ├── package-info.java │ │ │ ├── FixedPreviewProviderRegistry.java │ │ │ ├── LecternPreviewProvider.java │ │ │ ├── InventoryAwarePreviewProvider.java │ │ │ └── ShulkerBoxPreviewProvider.java │ │ ├── renderer │ │ │ └── package-info.java │ │ └── PreviewContextImpl.java │ │ ├── api │ │ ├── config │ │ │ ├── package-info.java │ │ │ ├── ItemStackMergingStrategy.java │ │ │ └── PreviewConfiguration.java │ │ ├── provider │ │ │ ├── package-info.java │ │ │ └── EmptyPreviewProvider.java │ │ ├── renderer │ │ │ └── package-info.java │ │ ├── color │ │ │ └── package-info.java │ │ ├── package-info.java │ │ ├── PreviewType.java │ │ └── PreviewContext.java │ │ └── mixin │ │ └── client │ │ ├── KeyboardHandlerMixin.java │ │ ├── ScreenMixin.java │ │ ├── ItemContainerContentsMixin.java │ │ ├── GuiGraphicsMixin.java │ │ └── ItemStackMixin.java └── build.gradle ├── forge ├── gradle.properties └── src │ └── main │ ├── resources │ ├── icon.png │ ├── pack.mcmeta │ ├── shulkerboxtooltip-forge.mixins.json │ └── META-INF │ │ └── mods.toml │ └── java │ └── com │ └── misterpemodder │ └── shulkerboxtooltip │ ├── forge │ ├── package-info.java │ └── ShulkerBoxTooltipImpl.java │ ├── impl │ ├── forge │ │ ├── package-info.java │ │ └── PluginManagerImpl.java │ ├── network │ │ └── forge │ │ │ ├── package-info.java │ │ │ ├── ForgeS2CChannel.java │ │ │ ├── ForgeC2SChannel.java │ │ │ ├── ServerNetworkingImpl.java │ │ │ ├── ClientNetworkingImpl.java │ │ │ └── ForgeChannel.java │ └── util │ │ └── forge │ │ └── EnvironmentUtilImpl.java │ ├── api │ └── forge │ │ ├── package-info.java │ │ └── ShulkerBoxTooltipPlugin.java │ └── mixin │ └── client │ └── forge │ └── AbstractContainerScreenMixin.java ├── neoforge ├── gradle.properties └── src │ └── main │ ├── resources │ ├── icon.png │ ├── pack.mcmeta │ ├── META-INF │ │ ├── accesstransformer.cfg │ │ └── neoforge.mods.toml │ └── shulkerboxtooltip-neoforge.mixins.json │ └── java │ └── com │ └── misterpemodder │ └── shulkerboxtooltip │ ├── neoforge │ ├── package-info.java │ ├── ShulkerBoxTooltipImpl.java │ └── ShulkerBoxTooltipClientImpl.java │ ├── impl │ ├── neoforge │ │ ├── package-info.java │ │ └── PluginManagerImpl.java │ ├── network │ │ └── neoforge │ │ │ ├── package-info.java │ │ │ ├── NeoForgeS2CChannel.java │ │ │ ├── ServerNetworkingImpl.java │ │ │ ├── ClientNetworkingImpl.java │ │ │ ├── NeoForgeC2SChannel.java │ │ │ └── NeoForgeChannel.java │ └── util │ │ └── neoforge │ │ └── EnvironmentUtilImpl.java │ ├── api │ └── neoforge │ │ ├── package-info.java │ │ └── ShulkerBoxTooltipPlugin.java │ └── mixin │ └── client │ └── neoforge │ └── AbstractContainerScreenMixin.java ├── .editorconfig ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── crowdin.yml ├── settings.gradle ├── .github └── ISSUE_TEMPLATE │ └── bug-crash-report.md ├── gradle.properties ├── LICENSE ├── gradlew.bat └── README.md /fabric/gradle.properties: -------------------------------------------------------------------------------- 1 | # Maven Metadata 2 | pomName=Shulker Box Tooltip (Fabric) 3 | -------------------------------------------------------------------------------- /common/gradle.properties: -------------------------------------------------------------------------------- 1 | # Maven Metadata 2 | pomName=Shulker Box Tooltip (Common API) 3 | -------------------------------------------------------------------------------- /forge/gradle.properties: -------------------------------------------------------------------------------- 1 | loom.platform=forge 2 | 3 | # Maven Metadata 4 | pomName=Shulker Box Tooltip (Forge) 5 | -------------------------------------------------------------------------------- /common/src/main/resources/architectury.common.json: -------------------------------------------------------------------------------- 1 | { 2 | "accessWidener": "shulkerboxtooltip.accesswidener" 3 | } 4 | -------------------------------------------------------------------------------- /neoforge/gradle.properties: -------------------------------------------------------------------------------- 1 | loom.platform=neoforge 2 | 3 | # Maven Metadata 4 | pomName=Shulker Box Tooltip (NeoForge) 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | charset=utf-8 3 | end_of_line=lf 4 | insert_final_newline=true 5 | indent_style=space 6 | indent_size=2 7 | -------------------------------------------------------------------------------- /forge/src/main/resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MisterPeModder/ShulkerBoxTooltip/HEAD/forge/src/main/resources/icon.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MisterPeModder/ShulkerBoxTooltip/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /forge/src/main/resources/pack.mcmeta: -------------------------------------------------------------------------------- 1 | { 2 | "pack": { 3 | "description": "ShulkerBoxTooltip", 4 | "pack_format": 8 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /neoforge/src/main/resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MisterPeModder/ShulkerBoxTooltip/HEAD/neoforge/src/main/resources/icon.png -------------------------------------------------------------------------------- /neoforge/src/main/resources/pack.mcmeta: -------------------------------------------------------------------------------- 1 | { 2 | "pack": { 3 | "description": "ShulkerBoxTooltip", 4 | "pack_format": 8 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /fabric/src/main/resources/assets/shulkerboxtooltip/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MisterPeModder/ShulkerBoxTooltip/HEAD/fabric/src/main/resources/assets/shulkerboxtooltip/icon.png -------------------------------------------------------------------------------- /neoforge/src/main/resources/META-INF/accesstransformer.cfg: -------------------------------------------------------------------------------- 1 | public net.minecraft.world.SimpleContainer listeners 2 | public net.minecraft.world.level.block.entity.LecternBlockEntity bookAccess 3 | -------------------------------------------------------------------------------- /forge/src/main/java/com/misterpemodder/shulkerboxtooltip/forge/package-info.java: -------------------------------------------------------------------------------- 1 | @ApiStatus.Internal 2 | package com.misterpemodder.shulkerboxtooltip.forge; 3 | 4 | import org.jetbrains.annotations.ApiStatus; -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/package-info.java: -------------------------------------------------------------------------------- 1 | @ApiStatus.Internal 2 | package com.misterpemodder.shulkerboxtooltip.impl; 3 | 4 | import org.jetbrains.annotations.ApiStatus; 5 | -------------------------------------------------------------------------------- /fabric/src/main/java/com/misterpemodder/shulkerboxtooltip/fabric/package-info.java: -------------------------------------------------------------------------------- 1 | @ApiStatus.Internal 2 | package com.misterpemodder.shulkerboxtooltip.fabric; 3 | 4 | import org.jetbrains.annotations.ApiStatus; -------------------------------------------------------------------------------- /forge/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/forge/package-info.java: -------------------------------------------------------------------------------- 1 | @ApiStatus.Internal 2 | package com.misterpemodder.shulkerboxtooltip.impl.forge; 3 | 4 | import org.jetbrains.annotations.ApiStatus; -------------------------------------------------------------------------------- /neoforge/src/main/java/com/misterpemodder/shulkerboxtooltip/neoforge/package-info.java: -------------------------------------------------------------------------------- 1 | @ApiStatus.Internal 2 | package com.misterpemodder.shulkerboxtooltip.neoforge; 3 | 4 | import org.jetbrains.annotations.ApiStatus; -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/tree/package-info.java: -------------------------------------------------------------------------------- 1 | @ApiStatus.Internal 2 | package com.misterpemodder.shulkerboxtooltip.impl.tree; 3 | 4 | import org.jetbrains.annotations.ApiStatus; 5 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/util/package-info.java: -------------------------------------------------------------------------------- 1 | @ApiStatus.Internal 2 | package com.misterpemodder.shulkerboxtooltip.impl.util; 3 | 4 | import org.jetbrains.annotations.ApiStatus; 5 | -------------------------------------------------------------------------------- /fabric/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/fabric/package-info.java: -------------------------------------------------------------------------------- 1 | @ApiStatus.Internal 2 | package com.misterpemodder.shulkerboxtooltip.impl.fabric; 3 | 4 | import org.jetbrains.annotations.ApiStatus; -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/config/package-info.java: -------------------------------------------------------------------------------- 1 | @ApiStatus.Internal 2 | package com.misterpemodder.shulkerboxtooltip.impl.config; 3 | 4 | import org.jetbrains.annotations.ApiStatus; 5 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/network/package-info.java: -------------------------------------------------------------------------------- 1 | @ApiStatus.Internal 2 | package com.misterpemodder.shulkerboxtooltip.impl.network; 3 | 4 | import org.jetbrains.annotations.ApiStatus; 5 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/tooltip/package-info.java: -------------------------------------------------------------------------------- 1 | @ApiStatus.Internal 2 | package com.misterpemodder.shulkerboxtooltip.impl.tooltip; 3 | 4 | import org.jetbrains.annotations.ApiStatus; 5 | -------------------------------------------------------------------------------- /neoforge/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/neoforge/package-info.java: -------------------------------------------------------------------------------- 1 | @ApiStatus.Internal 2 | package com.misterpemodder.shulkerboxtooltip.impl.neoforge; 3 | 4 | import org.jetbrains.annotations.ApiStatus; -------------------------------------------------------------------------------- /fabric/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/network/fabric/package-info.java: -------------------------------------------------------------------------------- 1 | @ApiStatus.Internal 2 | package com.misterpemodder.shulkerboxtooltip.impl.network.fabric; 3 | 4 | import org.jetbrains.annotations.ApiStatus; -------------------------------------------------------------------------------- /forge/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/network/forge/package-info.java: -------------------------------------------------------------------------------- 1 | @ApiStatus.Internal 2 | package com.misterpemodder.shulkerboxtooltip.impl.network.forge; 3 | 4 | import org.jetbrains.annotations.ApiStatus; -------------------------------------------------------------------------------- /neoforge/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/network/neoforge/package-info.java: -------------------------------------------------------------------------------- 1 | @ApiStatus.Internal 2 | package com.misterpemodder.shulkerboxtooltip.impl.network.neoforge; 3 | 4 | import org.jetbrains.annotations.ApiStatus; -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/config/annotation/package-info.java: -------------------------------------------------------------------------------- 1 | @ApiStatus.Internal 2 | package com.misterpemodder.shulkerboxtooltip.impl.config.annotation; 3 | 4 | import org.jetbrains.annotations.ApiStatus; 5 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/config/validators/package-info.java: -------------------------------------------------------------------------------- 1 | @ApiStatus.Internal 2 | package com.misterpemodder.shulkerboxtooltip.impl.config.validators; 3 | 4 | import org.jetbrains.annotations.ApiStatus; 5 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/network/channel/package-info.java: -------------------------------------------------------------------------------- 1 | @ApiStatus.Internal 2 | package com.misterpemodder.shulkerboxtooltip.impl.network.channel; 3 | 4 | import org.jetbrains.annotations.ApiStatus; 5 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/network/context/package-info.java: -------------------------------------------------------------------------------- 1 | @ApiStatus.Internal 2 | package com.misterpemodder.shulkerboxtooltip.impl.network.context; 3 | 4 | import org.jetbrains.annotations.ApiStatus; 5 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/network/message/package-info.java: -------------------------------------------------------------------------------- 1 | @ApiStatus.Internal 2 | package com.misterpemodder.shulkerboxtooltip.impl.network.message; 3 | 4 | import org.jetbrains.annotations.ApiStatus; 5 | -------------------------------------------------------------------------------- /crowdin.yml: -------------------------------------------------------------------------------- 1 | files: 2 | - source: /common/src/main/resources/assets/shulkerboxtooltip/lang/en_us.json 3 | translation: /common/src/main/resources/assets/shulkerboxtooltip/lang/%locale_with_underscore%.json 4 | commit_message: "[ci skip]" 5 | -------------------------------------------------------------------------------- /common/src/main/resources/assets/shulkerboxtooltip/textures/gui/sprites/shulker_box_tooltip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MisterPeModder/ShulkerBoxTooltip/HEAD/common/src/main/resources/assets/shulkerboxtooltip/textures/gui/sprites/shulker_box_tooltip.png -------------------------------------------------------------------------------- /forge/src/main/resources/shulkerboxtooltip-forge.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "parent": "shulkerboxtooltip-common.mixins.json", 3 | "package": "com.misterpemodder.shulkerboxtooltip.mixin", 4 | "client": [ 5 | "client.forge.AbstractContainerScreenMixin" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /neoforge/src/main/resources/shulkerboxtooltip-neoforge.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "parent": "shulkerboxtooltip-common.mixins.json", 3 | "package": "com.misterpemodder.shulkerboxtooltip.mixin", 4 | "client": [ 5 | "client.neoforge.AbstractContainerScreenMixin" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/hook/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Exposes hooks provided by mixins. 3 | */ 4 | 5 | @ApiStatus.Internal 6 | package com.misterpemodder.shulkerboxtooltip.impl.hook; 7 | 8 | import org.jetbrains.annotations.ApiStatus; 9 | -------------------------------------------------------------------------------- /common/src/main/resources/shulkerboxtooltip.accesswidener: -------------------------------------------------------------------------------- 1 | accessWidener v1 named 2 | accessible field net/minecraft/world/SimpleContainer listeners Ljava/util/List; 3 | accessible field net/minecraft/world/level/block/entity/LecternBlockEntity bookAccess Lnet/minecraft/world/Container; 4 | -------------------------------------------------------------------------------- /common/src/main/resources/assets/shulkerboxtooltip/textures/gui/sprites/shulker_box_tooltip.png.mcmeta: -------------------------------------------------------------------------------- 1 | { 2 | "gui": { 3 | "scaling": { 4 | "type": "nine_slice", 5 | "width": 32, 6 | "height": 32, 7 | "stretch_inner": false, 8 | "border": 7 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/color/package-info.java: -------------------------------------------------------------------------------- 1 | @ApiStatus.Internal @ParametersAreNonnullByDefault 2 | package com.misterpemodder.shulkerboxtooltip.impl.color; 3 | 4 | import org.jetbrains.annotations.ApiStatus; 5 | 6 | import javax.annotation.ParametersAreNonnullByDefault; 7 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/provider/package-info.java: -------------------------------------------------------------------------------- 1 | @ApiStatus.Internal @ParametersAreNonnullByDefault 2 | package com.misterpemodder.shulkerboxtooltip.impl.provider; 3 | 4 | import org.jetbrains.annotations.ApiStatus; 5 | 6 | import javax.annotation.ParametersAreNonnullByDefault; 7 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/renderer/package-info.java: -------------------------------------------------------------------------------- 1 | @ApiStatus.Internal @ParametersAreNonnullByDefault 2 | package com.misterpemodder.shulkerboxtooltip.impl.renderer; 3 | 4 | import org.jetbrains.annotations.ApiStatus; 5 | 6 | import javax.annotation.ParametersAreNonnullByDefault; 7 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/api/config/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Provides a view over the mod's configuration. 3 | * 4 | * @since 3.3.0 5 | */ 6 | @ApiStatus.AvailableSince("3.3.0") 7 | package com.misterpemodder.shulkerboxtooltip.api.config; 8 | 9 | import org.jetbrains.annotations.ApiStatus; -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/api/provider/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Providers for item previews. 3 | * 4 | * @since 1.3.0 5 | */ 6 | @ParametersAreNonnullByDefault 7 | package com.misterpemodder.shulkerboxtooltip.api.provider; 8 | 9 | import javax.annotation.ParametersAreNonnullByDefault; 10 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/api/renderer/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * ShulkerBoxTooltip's rendering API. 3 | * 4 | * @since 1.3.0 5 | */ 6 | @ParametersAreNonnullByDefault 7 | package com.misterpemodder.shulkerboxtooltip.api.renderer; 8 | 9 | import javax.annotation.ParametersAreNonnullByDefault; 10 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/api/color/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * ShulkerBoxTooltip preview colors customization API. 3 | * 4 | * @since 3.2.0 5 | */ 6 | @ParametersAreNonnullByDefault 7 | package com.misterpemodder.shulkerboxtooltip.api.color; 8 | 9 | import javax.annotation.ParametersAreNonnullByDefault; 10 | -------------------------------------------------------------------------------- /fabric/src/main/resources/shulkerboxtooltip-fabric.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "parent": "shulkerboxtooltip-common.mixins.json", 3 | "package": "com.misterpemodder.shulkerboxtooltip.mixin", 4 | "client": [ 5 | "client.fabric.AbstractContainerScreenMixin", 6 | "client.fabric.GuiGraphicsMixin", 7 | "client.fabric.ItemStackMixin" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/config/gui/package-info.java: -------------------------------------------------------------------------------- 1 | @ApiStatus.Internal @Environment(EnvType.CLIENT) @ParametersAreNonnullByDefault 2 | package com.misterpemodder.shulkerboxtooltip.impl.config.gui; 3 | 4 | import net.fabricmc.api.EnvType; 5 | import net.fabricmc.api.Environment; 6 | import org.jetbrains.annotations.ApiStatus; 7 | 8 | import javax.annotation.ParametersAreNonnullByDefault; 9 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/config/gui/entry/package-info.java: -------------------------------------------------------------------------------- 1 | @ApiStatus.Internal @Environment(EnvType.CLIENT) @ParametersAreNonnullByDefault 2 | package com.misterpemodder.shulkerboxtooltip.impl.config.gui.entry; 3 | 4 | import net.fabricmc.api.EnvType; 5 | import net.fabricmc.api.Environment; 6 | import org.jetbrains.annotations.ApiStatus; 7 | 8 | import javax.annotation.ParametersAreNonnullByDefault; 9 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/network/Payload.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.network; 2 | 3 | import net.minecraft.network.protocol.common.custom.CustomPacketPayload; 4 | 5 | public record Payload(CustomPacketPayload.Type id, T value) implements CustomPacketPayload { 6 | @Override 7 | public Type type() { 8 | return this.id; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/config/annotation/AutoTooltip.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.config.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target(ElementType.FIELD) 10 | public @interface AutoTooltip { 11 | } 12 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/config/annotation/Synchronize.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.config.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target({ElementType.FIELD}) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface Synchronize { 11 | } 12 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/tooltip/PreviewTooltipComponent.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.tooltip; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.api.PreviewContext; 4 | import com.misterpemodder.shulkerboxtooltip.api.provider.PreviewProvider; 5 | import net.minecraft.world.inventory.tooltip.TooltipComponent; 6 | 7 | public record PreviewTooltipComponent(PreviewProvider provider, PreviewContext context) implements TooltipComponent { 8 | } 9 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/config/SerializationException.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.config; 2 | 3 | import java.io.Serial; 4 | 5 | public class SerializationException extends Exception { 6 | @Serial 7 | private static final long serialVersionUID = 1L; 8 | 9 | public SerializationException(Throwable cause) { 10 | super(cause); 11 | } 12 | 13 | public SerializationException(String message) { 14 | super(message); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /forge/src/main/java/com/misterpemodder/shulkerboxtooltip/api/forge/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Forge-specific API of ShulkerBoxTooltip 3 | *

4 | * Currently only hosts the {@link com.misterpemodder.shulkerboxtooltip.api.forge.ShulkerBoxTooltipPlugin} class, 5 | * for registering plugins on Forge. 6 | * 7 | * @see com.misterpemodder.shulkerboxtooltip.api Common API. 8 | * @since 3.1.0 9 | */ 10 | @ParametersAreNonnullByDefault 11 | package com.misterpemodder.shulkerboxtooltip.api.forge; 12 | 13 | import javax.annotation.ParametersAreNonnullByDefault; 14 | -------------------------------------------------------------------------------- /common/src/main/resources/shulkerboxtooltip-common.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "package": "com.misterpemodder.shulkerboxtooltip.mixin", 4 | "compatibilityLevel": "JAVA_17", 5 | "minVersion": "0.8", 6 | "client": [ 7 | "client.AbstractContainerScreenMixin", 8 | "client.GuiGraphicsMixin", 9 | "client.ItemContainerContentsMixin", 10 | "client.ItemStackMixin", 11 | "client.KeyboardHandlerMixin", 12 | "client.ScreenMixin" 13 | ], 14 | "mixins": [ 15 | ], 16 | "injectors": { 17 | "defaultRequire": 1 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/config/annotation/ConfigCategory.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.config.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Denotes a field containing a category POJO. 10 | */ 11 | @Target({ElementType.FIELD}) 12 | @Retention(RetentionPolicy.RUNTIME) 13 | public @interface ConfigCategory { 14 | int ordinal() default 0; 15 | } 16 | -------------------------------------------------------------------------------- /neoforge/src/main/java/com/misterpemodder/shulkerboxtooltip/api/neoforge/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * NeoForge-specific API of ShulkerBoxTooltip 3 | *

4 | * Currently only hosts the {@link com.misterpemodder.shulkerboxtooltip.api.neoforge.ShulkerBoxTooltipPlugin} class, 5 | * for registering plugins on NeoForge. 6 | * 7 | * @see com.misterpemodder.shulkerboxtooltip.api Common API. 8 | * @since 4.1.0 9 | */ 10 | @ParametersAreNonnullByDefault 11 | package com.misterpemodder.shulkerboxtooltip.api.neoforge; 12 | 13 | import javax.annotation.ParametersAreNonnullByDefault; 14 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/api/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * The platform independent part of the ShulkerBoxTooltip API. 3 | *

4 | * Most of the API is accessed through the 5 | * {@link com.misterpemodder.shulkerboxtooltip.api.ShulkerBoxTooltipApi} interface. 6 | *

7 | * The Forge-specific API is implemented at {@code com.misterpemodder.shulkerboxtooltip.api.forge}. 8 | * 9 | * @since 1.3.0 10 | */ 11 | @ParametersAreNonnullByDefault 12 | package com.misterpemodder.shulkerboxtooltip.api; 13 | 14 | import javax.annotation.ParametersAreNonnullByDefault; 15 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/config/annotation/RequiresRestart.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.config.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Notifies the user that the game needs to be restarted for a change to the field to take effect. 10 | */ 11 | @Target({ElementType.FIELD}) 12 | @Retention(RetentionPolicy.RUNTIME) 13 | public @interface RequiresRestart { 14 | } 15 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/config/annotation/Validator.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.config.annotation; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.impl.tree.ValueConfigNode; 4 | 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | @Retention(RetentionPolicy.RUNTIME) 11 | @Target(ElementType.FIELD) 12 | public @interface Validator { 13 | Class> value(); 14 | } 15 | -------------------------------------------------------------------------------- /forge/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/util/forge/EnvironmentUtilImpl.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.util.forge; 2 | 3 | import net.minecraftforge.api.distmarker.Dist; 4 | import net.minecraftforge.fml.loading.FMLEnvironment; 5 | 6 | public class EnvironmentUtilImpl { 7 | 8 | private EnvironmentUtilImpl() { 9 | } 10 | 11 | /** 12 | * Implementation of {@link com.misterpemodder.shulkerboxtooltip.impl.util.EnvironmentUtil#isClient()}. 13 | */ 14 | public static boolean isClient() { 15 | return FMLEnvironment.dist == Dist.CLIENT; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /neoforge/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/util/neoforge/EnvironmentUtilImpl.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.util.neoforge; 2 | 3 | import net.neoforged.api.distmarker.Dist; 4 | import net.neoforged.fml.loading.FMLEnvironment; 5 | 6 | public class EnvironmentUtilImpl { 7 | 8 | private EnvironmentUtilImpl() { 9 | } 10 | 11 | /** 12 | * Implementation of {@link com.misterpemodder.shulkerboxtooltip.impl.util.EnvironmentUtil#isClient()}. 13 | */ 14 | public static boolean isClient() { 15 | return FMLEnvironment.getDist() == Dist.CLIENT; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /fabric/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/util/fabric/EnvironmentUtilImpl.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.util.fabric; 2 | 3 | import net.fabricmc.api.EnvType; 4 | import net.fabricmc.loader.api.FabricLoader; 5 | 6 | public class EnvironmentUtilImpl { 7 | 8 | private EnvironmentUtilImpl() { 9 | } 10 | 11 | /** 12 | * Implementation of {@link com.misterpemodder.shulkerboxtooltip.impl.util.EnvironmentUtil#isClient()}. 13 | */ 14 | public static boolean isClient() { 15 | return FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/util/ServerEnvironmentUtil.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.util; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.impl.config.Configuration; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | public final class ServerEnvironmentUtil extends EnvironmentUtil { 7 | @NotNull 8 | @Override 9 | public Configuration makeConfiguration() { 10 | return new Configuration(); 11 | } 12 | 13 | @Override 14 | @NotNull 15 | public Class getConfigurationClass() { 16 | return Configuration.class; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/config/JsonHexadecimalInt.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.config; 2 | 3 | import blue.endless.jankson.JsonGrammar; 4 | import blue.endless.jankson.JsonPrimitive; 5 | 6 | /** 7 | * Helper class to write hex ints to the JSON output, for serialization uses only. 8 | */ 9 | public class JsonHexadecimalInt extends JsonPrimitive { 10 | JsonHexadecimalInt(int value) { 11 | super(value); 12 | } 13 | 14 | @Override 15 | public String toJson(JsonGrammar grammar, int depth) { 16 | return String.format("%#x", ((Number) this.getValue()).intValue()); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /neoforge/src/main/java/com/misterpemodder/shulkerboxtooltip/api/neoforge/ShulkerBoxTooltipPlugin.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.api.neoforge; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.api.ShulkerBoxTooltipApi; 4 | import net.neoforged.fml.IExtensionPoint; 5 | 6 | import java.util.function.Supplier; 7 | 8 | /** 9 | * NeoForge-specific API, do not use on Forge, Fabric, or Quilt! 10 | * 11 | * @param apiImplSupplier A function that returns an instance of {@link ShulkerBoxTooltipApi}. 12 | * @since 4.1.0 13 | */ 14 | public record ShulkerBoxTooltipPlugin(Supplier apiImplSupplier) implements IExtensionPoint { 15 | } 16 | -------------------------------------------------------------------------------- /forge/src/main/java/com/misterpemodder/shulkerboxtooltip/api/forge/ShulkerBoxTooltipPlugin.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.api.forge; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.api.ShulkerBoxTooltipApi; 4 | import net.minecraftforge.fml.IExtensionPoint; 5 | 6 | import java.util.function.Supplier; 7 | 8 | /** 9 | * Forge-specific API, do not use on Fabric/Quilt! 10 | * 11 | * @param apiImplSupplier A function that returns an instance of {@link ShulkerBoxTooltipApi}. 12 | * @since 3.1.0 13 | */ 14 | public record ShulkerBoxTooltipPlugin(Supplier apiImplSupplier) 15 | implements IExtensionPoint { 16 | } 17 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | mavenCentral() 4 | maven { 5 | name = 'Fabric' 6 | url = 'https://maven.fabricmc.net/' 7 | } 8 | maven { 9 | name = 'Forge' 10 | url 'https://maven.minecraftforge.net/' 11 | } 12 | maven { 13 | name = 'Architectury Plugin' 14 | url 'https://maven.architectury.dev/' 15 | } 16 | maven { url "https://jitpack.io" } 17 | gradlePluginPortal() 18 | } 19 | } 20 | 21 | include('common') 22 | include('fabric') 23 | // include('forge') 24 | include('neoforge') 25 | 26 | rootProject.name = 'ShulkerBoxTooltip' 27 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/api/PreviewType.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.api; 2 | 3 | import net.fabricmc.api.EnvType; 4 | import net.fabricmc.api.Environment; 5 | 6 | /** 7 | * The type of preview to draw. 8 | * 9 | * @since 1.3.0 10 | */ 11 | @Environment(EnvType.CLIENT) 12 | public enum PreviewType { 13 | /** 14 | * Preview is not present. 15 | */ 16 | NO_PREVIEW, 17 | 18 | /** 19 | * Compact mode: similar items are grouped together and empty slots are not shown. 20 | */ 21 | COMPACT, 22 | 23 | /** 24 | * Full mode: all stacks are shown in their respective slots, empty slots are also displayed. 25 | */ 26 | FULL 27 | } 28 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/config/gui/entry/ConfigEntry.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.config.gui.entry; 2 | 3 | import net.fabricmc.api.EnvType; 4 | import net.fabricmc.api.Environment; 5 | import net.minecraft.client.gui.components.ContainerObjectSelectionList; 6 | import net.minecraft.util.FormattedCharSequence; 7 | import org.jetbrains.annotations.Nullable; 8 | 9 | import java.util.List; 10 | 11 | @Environment(EnvType.CLIENT) 12 | public abstract class ConfigEntry extends ContainerObjectSelectionList.Entry { 13 | 14 | @Nullable 15 | public List getTooltip() { 16 | return null; 17 | } 18 | 19 | public void refresh() { 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/network/context/MessageContext.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.network.context; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.impl.network.channel.Channel; 4 | import net.minecraft.world.entity.player.Player; 5 | 6 | public sealed interface MessageContext permits C2SMessageContext, S2CMessageContext { 7 | /** 8 | * Executes the given task in the server/client's main thread. 9 | * 10 | * @param task The function to execute. 11 | */ 12 | void execute(Runnable task); 13 | 14 | Player getPlayer(); 15 | 16 | Channel getChannel(); 17 | 18 | Side getReceivingSide(); 19 | 20 | enum Side { 21 | CLIENT, SERVER 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/network/channel/Channel.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.network.channel; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.impl.network.context.MessageContext; 4 | import com.misterpemodder.shulkerboxtooltip.impl.network.message.MessageType; 5 | import net.minecraft.resources.Identifier; 6 | 7 | /** 8 | * Base network channel abstraction. 9 | * 10 | * @param The message data type. 11 | */ 12 | public interface Channel { 13 | Identifier getId(); 14 | 15 | MessageType getMessageType(); 16 | 17 | void registerPayloadType(); 18 | 19 | void onRegister(MessageContext context); 20 | 21 | void onUnregister(MessageContext context); 22 | } 23 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/config/validators/GreaterThanZero.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.config.validators; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.impl.tree.ValueConfigNode; 4 | import net.minecraft.network.chat.Component; 5 | import org.jetbrains.annotations.Nullable; 6 | 7 | public final class GreaterThanZero implements ValueConfigNode.ValueValidator { 8 | @Nullable 9 | @Override 10 | public Component validate(Object value) { 11 | Class valueClass = value.getClass(); 12 | if (valueClass.equals(Integer.class) && (Integer) value <= 0) { 13 | return Component.translatable("shulkerboxtooltip.config.validator.greater_than_zero"); 14 | } 15 | return null; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /fabric/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/ModMenuCompat.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.ShulkerBoxTooltip; 4 | import com.misterpemodder.shulkerboxtooltip.impl.config.ConfigurationHandler; 5 | import com.misterpemodder.shulkerboxtooltip.impl.config.gui.ConfigScreen; 6 | import com.terraformersmc.modmenu.api.ConfigScreenFactory; 7 | import com.terraformersmc.modmenu.api.ModMenuApi; 8 | 9 | public class ModMenuCompat implements ModMenuApi { 10 | @Override 11 | public ConfigScreenFactory getModConfigScreenFactory() { 12 | return parent -> new ConfigScreen<>(parent, ShulkerBoxTooltip.configTree, ShulkerBoxTooltip.savedConfig, 13 | ConfigurationHandler::saveToFile); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-crash-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug/Crash report 3 | about: Post about a bug or crash you have encountered 4 | title: '' 5 | labels: bug 6 | assignees: MisterPeModder 7 | 8 | --- 9 | 10 | #### Issue Description: 11 | 12 | Delete this line after reading it: If this bug occurs in a modpack, please report this to the modpack author first. 13 | 14 | #### What happens: 15 | 16 | 17 | #### What you expected to happen: 18 | 19 | 20 | #### Steps to reproduce: 21 | 22 | 1. 23 | 2. 24 | 3. 25 | ... 26 | 27 | ____ 28 | #### Affected Versions (Do *not* use "latest"): 29 | 30 | - ShulkerBoxTooltip: 31 | - Minecraft: 32 | - Fabric Loader: 33 | - Fabric API: 34 | - Other mods (if relevant): 35 | 36 | #### Your most recent log file where the issue was present: 37 | 38 | [pastebin/gist/etc link here] 39 | -------------------------------------------------------------------------------- /forge/src/main/resources/META-INF/mods.toml: -------------------------------------------------------------------------------- 1 | modLoader = "javafml" 2 | loaderVersion = "[40,)" 3 | issueTrackerURL = "https://github.com/MisterPeModder/ShulkerBoxTooltip/issues" 4 | displayURL = "https://www.curseforge.com/minecraft/mc-mods/shulkerboxtooltip/" 5 | license = "MIT" 6 | 7 | [[mods]] 8 | modId = "shulkerboxtooltip" 9 | version = "${version}" 10 | displayName = "ShulkerBoxTooltip" 11 | authors = "MisterPeModder" 12 | description = "What's in my shulker box?" 13 | logoFile = "icon.png" 14 | 15 | [[dependencies.shulkerboxtooltip]] 16 | modId = "forge" 17 | mandatory = true 18 | versionRange = "[60.0.0,)" 19 | ordering = "NONE" 20 | side = "BOTH" 21 | 22 | [[dependencies.shulkerboxtooltip]] 23 | modId = "minecraft" 24 | mandatory = true 25 | versionRange = "[1.21.10,)" 26 | ordering = "NONE" 27 | side = "BOTH" 28 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/util/ClientEnvironmentUtil.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.util; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.impl.config.ClientConfiguration; 4 | import com.misterpemodder.shulkerboxtooltip.impl.config.Configuration; 5 | import net.fabricmc.api.EnvType; 6 | import net.fabricmc.api.Environment; 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | @Environment(EnvType.CLIENT) 10 | public final class ClientEnvironmentUtil extends EnvironmentUtil { 11 | @NotNull 12 | @Override 13 | public Configuration makeConfiguration() { 14 | return new ClientConfiguration(); 15 | } 16 | 17 | @NotNull 18 | @Override 19 | public Class getConfigurationClass() { 20 | return ClientConfiguration.class; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/util/NbtType.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.util; 2 | 3 | @SuppressWarnings("unused") 4 | public final class NbtType { 5 | public static final int END = 0; 6 | public static final int BYTE = 1; 7 | public static final int SHORT = 2; 8 | public static final int INT = 3; 9 | public static final int LONG = 4; 10 | public static final int FLOAT = 5; 11 | public static final int DOUBLE = 6; 12 | public static final int BYTE_ARRAY = 7; 13 | public static final int STRING = 8; 14 | public static final int LIST = 9; 15 | public static final int COMPOUND = 10; 16 | public static final int INT_ARRAY = 11; 17 | public static final int LONG_ARRAY = 12; 18 | public static final int NUMBER = 99; 19 | 20 | private NbtType() { 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/hook/ContainerScreenLockTooltip.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.hook; 2 | 3 | import net.minecraft.client.gui.Font; 4 | import net.minecraft.client.gui.GuiGraphics; 5 | import net.minecraft.network.chat.Component; 6 | import net.minecraft.resources.Identifier; 7 | import net.minecraft.world.inventory.tooltip.TooltipComponent; 8 | import net.minecraft.world.item.ItemStack; 9 | 10 | import java.util.List; 11 | import java.util.Optional; 12 | 13 | @SuppressWarnings("OptionalUsedAsFieldOrParameterType") 14 | public interface ContainerScreenLockTooltip { 15 | void shulkerboxtooltip$lockTooltipPosition(GuiGraphics graphics, Font font, List text, 16 | Optional data, ItemStack stack, int x, int y, Identifier backgroundTexture); 17 | } 18 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/api/config/ItemStackMergingStrategy.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.api.config; 2 | 3 | /** 4 | * Provides a view over the mod's configuration 5 | * 6 | * @since 3.3.0 7 | */ 8 | public enum ItemStackMergingStrategy { 9 | /** 10 | * Ignore component data when merging item stacks. 11 | * 12 | * @since 3.3.0 13 | */ 14 | IGNORE, 15 | /** 16 | * Merge regardless of component data but use the NBT of the first item stack. 17 | * 18 | * @since 3.3.0 19 | */ 20 | FIRST_ITEM, 21 | /** 22 | * Do not merge stacks that differ in component data. 23 | * 24 | * @since 3.3.0 25 | */ 26 | SEPARATE; 27 | 28 | @Override 29 | public String toString() { 30 | return "shulkerboxtooltip.config.item_stack_merging_strategy." + this.name().toLowerCase(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | projectGroup=com.misterpemodder 2 | projectName=ShulkerBoxTooltip 3 | projectBaseVersion=5.2.14 4 | projectDescription=Adds a display of shulker box contents 5 | projectArchiveBaseName=shulkerboxtooltip 6 | 7 | # Minecraft 8 | mcVersion=1.21.11 9 | mcVersionFull=1.21.11 10 | 11 | # Mod Loaders 12 | # enabledPlatforms=fabric,forge,neoforge 13 | enabledPlatforms=fabric,neoforge 14 | fabricLoaderVersion=0.18.2 15 | fabricApiVersion=0.139.4+1.21.11 16 | forgeVersion=1.21.10-60.1.0 17 | neoForgeVersion=21.11.0-beta 18 | 19 | # Dependencies 20 | modMenuVersion=17.0.0-alpha.1 21 | janksonVersion=1.2.3 22 | 23 | # Maven Metadata 24 | pomName=Shulker Box Tooltip 25 | gitLink=MisterPeModder/ShulkerBoxTooltip 26 | 27 | # Hosting Platforms 28 | hostGameVersions=1.21.11 29 | curseProjectId=315811 30 | modrinthProjectId=2M01OLQq 31 | 32 | # Misc 33 | org.gradle.jvmargs=-Xmx3G 34 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/network/context/S2CMessageContext.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.network.context; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.impl.network.channel.Channel; 4 | import net.fabricmc.api.EnvType; 5 | import net.fabricmc.api.Environment; 6 | import net.minecraft.client.Minecraft; 7 | import net.minecraft.client.player.LocalPlayer; 8 | 9 | @Environment(EnvType.CLIENT) 10 | public record S2CMessageContext(Channel channel) implements MessageContext { 11 | @Override 12 | public void execute(Runnable task) { 13 | Minecraft.getInstance().execute(task); 14 | } 15 | 16 | @Override 17 | public LocalPlayer getPlayer() { 18 | return Minecraft.getInstance().player; 19 | } 20 | 21 | @Override 22 | public Channel getChannel() { 23 | return this.channel; 24 | } 25 | 26 | @Override 27 | public Side getReceivingSide() { 28 | return Side.CLIENT; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/network/channel/S2CChannel.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.network.channel; 2 | 3 | import net.fabricmc.api.EnvType; 4 | import net.fabricmc.api.Environment; 5 | import net.minecraft.server.level.ServerPlayer; 6 | 7 | /** 8 | * Server-to-client channel abstraction. 9 | * 10 | * @param The message data type. 11 | */ 12 | public interface S2CChannel extends Channel { 13 | /** 14 | * Registers handling of messages in this channel from the server. 15 | */ 16 | @Environment(EnvType.CLIENT) 17 | void register(); 18 | 19 | /** 20 | * Unregisters handling of messages in this channel from the server. 21 | */ 22 | @Environment(EnvType.CLIENT) 23 | void unregister(); 24 | 25 | /** 26 | * Sends a message to a specific player. 27 | * 28 | * @param player The target player. 29 | * @param message The message to send. 30 | */ 31 | void sendTo(ServerPlayer player, T message); 32 | } 33 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/hook/ContainerScreenDrawTooltip.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.hook; 2 | 3 | import net.minecraft.client.gui.Font; 4 | import net.minecraft.client.gui.GuiGraphics; 5 | import net.minecraft.network.chat.Component; 6 | import net.minecraft.resources.Identifier; 7 | import net.minecraft.world.inventory.tooltip.TooltipComponent; 8 | import net.minecraft.world.item.ItemStack; 9 | 10 | import java.util.List; 11 | import java.util.Optional; 12 | 13 | @SuppressWarnings("OptionalUsedAsFieldOrParameterType") 14 | public interface ContainerScreenDrawTooltip { 15 | /** 16 | * Adapter over the vanilla GuiGraphics.setTooltipForNextFrame(), 17 | * which does not accept the "backgroundTexture" argument on Forge for some reason. 18 | */ 19 | void shulkerboxtooltip$renderTooltip(GuiGraphics graphics, Font font, List text, 20 | Optional data, ItemStack stack, int x, int y, Identifier backgroundTexture); 21 | } 22 | -------------------------------------------------------------------------------- /fabric/src/main/java/com/misterpemodder/shulkerboxtooltip/fabric/ShulkerBoxTooltipImpl.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.fabric; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.ShulkerBoxTooltip; 4 | import net.fabricmc.api.EnvType; 5 | import net.fabricmc.api.ModInitializer; 6 | import net.fabricmc.loader.api.FabricLoader; 7 | 8 | import java.nio.file.Path; 9 | 10 | @SuppressWarnings("unused") 11 | public class ShulkerBoxTooltipImpl extends ShulkerBoxTooltip implements ModInitializer { 12 | @Override 13 | public void onInitialize() { 14 | ShulkerBoxTooltip.init(); 15 | } 16 | 17 | /** 18 | * Implementation of {@link ShulkerBoxTooltip#isClient()}. 19 | */ 20 | public static boolean isClient() { 21 | return FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT; 22 | } 23 | 24 | /** 25 | * Implementation of {@link ShulkerBoxTooltip#getConfigDir()}. 26 | */ 27 | public static Path getConfigDir() { 28 | return FabricLoader.getInstance().getConfigDir(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/tree/ConfigNode.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.tree; 2 | 3 | import net.minecraft.nbt.CompoundTag; 4 | import net.minecraft.network.chat.Component; 5 | import org.jetbrains.annotations.NotNull; 6 | import org.jetbrains.annotations.Nullable; 7 | 8 | public interface ConfigNode { 9 | @NotNull 10 | String getName(); 11 | 12 | @NotNull 13 | Component getTitle(); 14 | 15 | @Nullable 16 | Component getTooltip(); 17 | 18 | @Nullable 19 | Component getPrefix(); 20 | 21 | void resetToDefault(); 22 | 23 | void resetToActive(C config); 24 | 25 | boolean restartRequired(C config); 26 | 27 | boolean isDefaultValue(C config); 28 | 29 | boolean isActiveValue(C config); 30 | 31 | @Nullable 32 | Component validate(C config); 33 | 34 | void writeToNbt(C config, CompoundTag compound); 35 | 36 | void readFromNbt(C config, CompoundTag compound); 37 | 38 | void copy(C from, C to); 39 | 40 | void writeEditingToConfig(C config); 41 | } 42 | -------------------------------------------------------------------------------- /neoforge/src/main/resources/META-INF/neoforge.mods.toml: -------------------------------------------------------------------------------- 1 | modLoader = "javafml" 2 | loaderVersion = "[1,)" 3 | issueTrackerURL = "https://github.com/MisterPeModder/ShulkerBoxTooltip/issues" 4 | displayURL = "https://www.curseforge.com/minecraft/mc-mods/shulkerboxtooltip/" 5 | license = "MIT" 6 | 7 | [[mods]] 8 | modId = "shulkerboxtooltip" 9 | version = "${version}" 10 | displayName = "ShulkerBoxTooltip" 11 | authors = "MisterPeModder" 12 | description = "What's in my shulker box?" 13 | logoFile = "icon.png" 14 | 15 | [[mixins]] 16 | config = "shulkerboxtooltip-common.mixins.json" 17 | 18 | [[mixins]] 19 | config = "shulkerboxtooltip-neoforge.mixins.json" 20 | 21 | [[accessTransformers]] 22 | file = "META-INF/accesstransformer.cfg" 23 | 24 | [[dependencies.shulkerboxtooltip]] 25 | modId = "neoforge" 26 | mandatory = true 27 | versionRange = "[21.10.0,)" 28 | ordering = "NONE" 29 | side = "BOTH" 30 | 31 | [[dependencies.shulkerboxtooltip]] 32 | modId = "minecraft" 33 | mandatory = true 34 | versionRange = "[1.21.10,)" 35 | ordering = "NONE" 36 | side = "BOTH" 37 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/network/context/C2SMessageContext.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.network.context; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.impl.network.channel.Channel; 4 | import net.minecraft.server.MinecraftServer; 5 | import net.minecraft.server.level.ServerLevel; 6 | import net.minecraft.server.level.ServerPlayer; 7 | 8 | public record C2SMessageContext(ServerPlayer player, Channel channel) implements MessageContext { 9 | @Override 10 | public void execute(Runnable task) { 11 | ServerLevel level = player.level(); 12 | MinecraftServer server = level == null ? null : level.getServer(); 13 | if (server != null) { 14 | server.execute(task); 15 | } 16 | } 17 | 18 | @Override 19 | public ServerPlayer getPlayer() { 20 | return this.player; 21 | } 22 | 23 | @Override 24 | public Channel getChannel() { 25 | return this.channel; 26 | } 27 | 28 | @Override 29 | public Side getReceivingSide() { 30 | return Side.SERVER; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/mixin/client/KeyboardHandlerMixin.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.mixin.client; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.ShulkerBoxTooltipClient; 4 | import net.minecraft.client.KeyboardHandler; 5 | import net.minecraft.client.Minecraft; 6 | import net.minecraft.client.input.KeyEvent; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.injection.At; 9 | import org.spongepowered.asm.mixin.injection.Inject; 10 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 11 | 12 | @Mixin(KeyboardHandler.class) 13 | public class KeyboardHandlerMixin { 14 | @Inject(at = @At("HEAD"), method = "keyPress(JILnet/minecraft/client/input/KeyEvent;)V") 15 | private void onKey(long window, int key, KeyEvent event, CallbackInfo ci) { 16 | // update the pressed preview keys when key event was received on the game window 17 | if (window == Minecraft.getInstance().getWindow().handle()) 18 | ShulkerBoxTooltipClient.updatePreviewKeys(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Yanis Guaye 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/api/provider/EmptyPreviewProvider.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.api.provider; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.api.PreviewContext; 4 | import net.minecraft.world.item.ItemStack; 5 | 6 | import java.util.Collections; 7 | import java.util.List; 8 | 9 | /** 10 | * A PreviewProvider that does nothing. 11 | * 12 | * @since 1.3.0 13 | */ 14 | public class EmptyPreviewProvider implements PreviewProvider { 15 | /** 16 | * The EmptyPreviewProvider instance. 17 | * 18 | * @since 1.3.0 19 | */ 20 | public static final PreviewProvider INSTANCE = new EmptyPreviewProvider(); 21 | 22 | /** 23 | * Default constructor. 24 | */ 25 | protected EmptyPreviewProvider() { 26 | } 27 | 28 | @Override 29 | public int getInventoryMaxSize(PreviewContext context) { 30 | return 0; 31 | } 32 | 33 | @Override 34 | public boolean shouldDisplay(PreviewContext context) { 35 | return false; 36 | } 37 | 38 | @Override 39 | public List getInventory(PreviewContext context) { 40 | return Collections.emptyList(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /fabric/src/main/java/com/misterpemodder/shulkerboxtooltip/fabric/ShulkerBoxTooltipClientImpl.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.fabric; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.ShulkerBoxTooltipClient; 4 | import com.misterpemodder.shulkerboxtooltip.impl.tooltip.PreviewClientTooltipComponent; 5 | import com.misterpemodder.shulkerboxtooltip.impl.tooltip.PreviewTooltipComponent; 6 | import net.fabricmc.api.ClientModInitializer; 7 | import net.fabricmc.api.EnvType; 8 | import net.fabricmc.api.Environment; 9 | import net.fabricmc.fabric.api.client.rendering.v1.TooltipComponentCallback; 10 | 11 | @Environment(EnvType.CLIENT) 12 | public final class ShulkerBoxTooltipClientImpl extends ShulkerBoxTooltipClient implements ClientModInitializer { 13 | @Override 14 | public void onInitializeClient() { 15 | ShulkerBoxTooltipClient.init(); 16 | 17 | // PreviewTooltipData -> PreviewTooltipComponent conversion 18 | TooltipComponentCallback.EVENT.register(data -> { 19 | if (data instanceof PreviewTooltipComponent previewData) 20 | return new PreviewClientTooltipComponent(previewData); 21 | return null; 22 | }); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /fabric/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/fabric/PluginManagerImpl.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.fabric; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.ShulkerBoxTooltip; 4 | import com.misterpemodder.shulkerboxtooltip.api.ShulkerBoxTooltipApi; 5 | import com.misterpemodder.shulkerboxtooltip.impl.PluginManager; 6 | import com.misterpemodder.shulkerboxtooltip.impl.PluginManager.PluginContainer; 7 | import net.fabricmc.loader.api.FabricLoader; 8 | 9 | import java.util.List; 10 | import java.util.stream.Collectors; 11 | 12 | @SuppressWarnings("unused") 13 | public final class PluginManagerImpl { 14 | /** 15 | * Implementation of {@link PluginManager#getPluginContainers()}. 16 | */ 17 | public static List getPluginContainers() { 18 | return FabricLoader.getInstance() 19 | .getEntrypointContainers(ShulkerBoxTooltip.MOD_ID, ShulkerBoxTooltipApi.class) 20 | .stream() 21 | .map(entrypointContainer -> new PluginContainer(entrypointContainer.getProvider().getMetadata().getId(), 22 | entrypointContainer::getEntrypoint)) 23 | .collect(Collectors.toList()); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /forge/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/forge/PluginManagerImpl.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.forge; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.api.forge.ShulkerBoxTooltipPlugin; 4 | import com.misterpemodder.shulkerboxtooltip.impl.PluginManager; 5 | import com.misterpemodder.shulkerboxtooltip.impl.PluginManager.PluginContainer; 6 | import net.minecraftforge.fml.ModList; 7 | import org.jetbrains.annotations.Contract; 8 | 9 | import java.util.List; 10 | import java.util.Optional; 11 | import java.util.stream.Collectors; 12 | 13 | @SuppressWarnings("unused") 14 | public final class PluginManagerImpl { 15 | private PluginManagerImpl() { 16 | } 17 | 18 | /** 19 | * Implementation of {@link PluginManager#getPluginContainers()}. 20 | */ 21 | @Contract(" -> !null") 22 | public static List getPluginContainers() { 23 | return ModList.get().applyForEachModContainer( 24 | modContainer -> modContainer.getCustomExtension(ShulkerBoxTooltipPlugin.class) 25 | .map(extension -> new PluginContainer(modContainer.getModId(), extension.apiImplSupplier()))).flatMap( 26 | Optional::stream).collect(Collectors.toList()); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /neoforge/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/neoforge/PluginManagerImpl.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.neoforge; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.api.neoforge.ShulkerBoxTooltipPlugin; 4 | import com.misterpemodder.shulkerboxtooltip.impl.PluginManager; 5 | import com.misterpemodder.shulkerboxtooltip.impl.PluginManager.PluginContainer; 6 | import net.neoforged.fml.ModList; 7 | import org.jetbrains.annotations.Contract; 8 | 9 | import java.util.List; 10 | import java.util.Optional; 11 | import java.util.stream.Collectors; 12 | 13 | @SuppressWarnings("unused") 14 | public final class PluginManagerImpl { 15 | private PluginManagerImpl() { 16 | } 17 | 18 | /** 19 | * Implementation of {@link PluginManager#getPluginContainers()}. 20 | */ 21 | @Contract(" -> !null") 22 | public static List getPluginContainers() { 23 | return ModList.get().applyForEachModContainer( 24 | modContainer -> modContainer.getCustomExtension(ShulkerBoxTooltipPlugin.class) 25 | .map(extension -> new PluginContainer(modContainer.getModId(), extension.apiImplSupplier()))).flatMap( 26 | Optional::stream).collect(Collectors.toList()); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/network/message/C2SEnderChestUpdateRequest.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.network.message; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.impl.network.context.MessageContext; 4 | import net.minecraft.network.FriendlyByteBuf; 5 | import net.minecraft.server.level.ServerPlayer; 6 | 7 | /** 8 | * Request an update to contents of the client's ender chest to the server. 9 | */ 10 | public record C2SEnderChestUpdateRequest() { 11 | public static class Type implements MessageType { 12 | @Override 13 | public void encode(C2SEnderChestUpdateRequest message, FriendlyByteBuf buf) { 14 | } 15 | 16 | @Override 17 | public C2SEnderChestUpdateRequest decode(FriendlyByteBuf buf) { 18 | return new C2SEnderChestUpdateRequest(); 19 | } 20 | 21 | @Override 22 | public void onReceive(C2SEnderChestUpdateRequest message, MessageContext context) { 23 | var player = (ServerPlayer) context.getPlayer(); 24 | S2CMessages.ENDER_CHEST_UPDATE.sendTo(player, 25 | S2CEnderChestUpdate.create(player.getEnderChestInventory(), player.registryAccess())); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/mixin/client/ScreenMixin.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.mixin.client; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.impl.hook.GuiGraphicsExtensions; 4 | import net.minecraft.client.gui.GuiGraphics; 5 | import net.minecraft.client.gui.screens.Screen; 6 | import org.spongepowered.asm.mixin.Mixin; 7 | import org.spongepowered.asm.mixin.injection.At; 8 | import org.spongepowered.asm.mixin.injection.Inject; 9 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 10 | 11 | @Mixin(Screen.class) 12 | public class ScreenMixin { 13 | 14 | /** 15 | * Makes the current mouse position available via extensions to the GuiGraphics instance. 16 | */ 17 | @Inject(at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screens/Screen;render(Lnet/minecraft/client/gui/GuiGraphics;IIF)V"), method = "renderWithTooltipAndSubtitles(Lnet/minecraft/client/gui/GuiGraphics;IIF)V") 18 | private void captureMousePosition(GuiGraphics graphics, int mouseX, int mouseY, float delta, CallbackInfo ci) { 19 | GuiGraphicsExtensions extensions = (GuiGraphicsExtensions) graphics; 20 | extensions.setMouseY(mouseY); 21 | extensions.setMouseX(mouseX); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/hook/GuiGraphicsExtensions.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.hook; 2 | 3 | import net.minecraft.client.gui.GuiGraphics; 4 | import org.jetbrains.annotations.Nullable; 5 | 6 | /** 7 | * Provides access to the {@link net.minecraft.client.gui.GuiGraphics} methods added by the mod. 8 | */ 9 | public interface GuiGraphicsExtensions { 10 | void setTooltipTopYPosition(int topY); 11 | 12 | int getTooltipTopYPosition(); 13 | 14 | void setMouseX(int mouseX); 15 | 16 | int getMouseX(); 17 | 18 | void setMouseY(int mouseY); 19 | 20 | int getMouseY(); 21 | 22 | @Nullable Runnable getDeferredTooltip(); 23 | 24 | void setDeferredTooltip(@Nullable Runnable deferredTooltip); 25 | 26 | static void renderTooltipImmediate(GuiGraphics graphics, Runnable renderer) { 27 | var extendedGraphics = (GuiGraphicsExtensions) graphics; 28 | 29 | Runnable prevTooltip = extendedGraphics.getDeferredTooltip(); 30 | extendedGraphics.setDeferredTooltip(null); 31 | 32 | renderer.run(); 33 | 34 | Runnable currentTooltip = extendedGraphics.getDeferredTooltip(); 35 | if (currentTooltip != null) { 36 | currentTooltip.run(); 37 | } 38 | extendedGraphics.setDeferredTooltip(prevTooltip); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/provider/FixedPreviewProviderRegistry.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.provider; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.api.provider.PreviewProvider; 4 | import com.misterpemodder.shulkerboxtooltip.api.provider.PreviewProviderRegistry; 5 | import com.misterpemodder.shulkerboxtooltip.impl.util.ShulkerBoxTooltipUtil; 6 | import net.minecraft.core.BlockPos; 7 | import net.minecraft.world.Container; 8 | import net.minecraft.world.level.block.Block; 9 | import net.minecraft.world.level.block.state.BlockState; 10 | 11 | import java.util.function.BiFunction; 12 | import java.util.function.Supplier; 13 | 14 | public record FixedPreviewProviderRegistry(PreviewProviderRegistry registry, 15 | BiFunction, PreviewProvider> providerFactory) { 16 | public FixedPreviewProviderRegistry register(String id, int maxRowSize, 17 | BiFunction inventoryFactory, Block block) { 18 | var provider = providerFactory.apply(maxRowSize, 19 | () -> inventoryFactory.apply(BlockPos.ZERO, block.defaultBlockState())); 20 | registry.register(ShulkerBoxTooltipUtil.id(id), provider, block.asItem()); 21 | return this; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/network/channel/C2SChannel.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.network.channel; 2 | 3 | import net.fabricmc.api.EnvType; 4 | import net.fabricmc.api.Environment; 5 | import net.minecraft.server.level.ServerPlayer; 6 | 7 | /** 8 | * Client-to-server channel abstraction. 9 | * 10 | * @param The message data type. 11 | */ 12 | public interface C2SChannel extends Channel { 13 | /** 14 | * Registers handling of messages in this channel for the given player. 15 | * 16 | * @param player The player. 17 | */ 18 | void registerFor(ServerPlayer player); 19 | 20 | /** 21 | * Unregisters handling of messages in this channel for the given player. 22 | * 23 | * @param player The player. 24 | */ 25 | void unregisterFor(ServerPlayer player); 26 | 27 | /** 28 | * Sends a message to the server. 29 | * 30 | * @param message The message to send. 31 | */ 32 | @Environment(EnvType.CLIENT) 33 | void sendToServer(T message); 34 | 35 | /** 36 | * @return Whether a payload can successfully be sent to the server. 37 | */ 38 | @Environment(EnvType.CLIENT) 39 | boolean canSendToServer(); 40 | 41 | /** 42 | * Called when the client player disconnects from a server. 43 | */ 44 | @Environment(EnvType.CLIENT) 45 | void onDisconnect(); 46 | } 47 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/util/NamedLogger.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.util; 2 | 3 | import org.apache.logging.log4j.Logger; 4 | 5 | /** 6 | * A {@link Logger} wrapper that always prints the name of the logger regardless of configuration. 7 | */ 8 | public final class NamedLogger { 9 | private final Logger inner; 10 | 11 | public NamedLogger(Logger inner) { 12 | this.inner = inner; 13 | } 14 | 15 | public void error(String message) { 16 | this.inner.error('[' + inner.getName() + "] " + message); 17 | } 18 | 19 | public void error(String message, Exception error) { 20 | this.inner.error('[' + inner.getName() + "] " + message, error); 21 | } 22 | 23 | public void debug(String message) { 24 | this.inner.debug('[' + inner.getName() + "] " + message); 25 | } 26 | 27 | public void info(String message) { 28 | this.inner.info('[' + inner.getName() + "] " + message); 29 | } 30 | 31 | public void info(String message, Object arg1) { 32 | this.inner.info('[' + inner.getName() + "] " + message, arg1); 33 | } 34 | 35 | public void info(String message, Object arg1, Object arg2) { 36 | this.inner.info('[' + inner.getName() + "] " + message, arg1, arg2); 37 | } 38 | 39 | public void warn(String message) { 40 | this.inner.warn('[' + inner.getName() + "] " + message); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/util/ItemKey.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.util; 2 | 3 | import net.minecraft.core.component.DataComponentMap; 4 | import net.minecraft.core.registries.BuiltInRegistries; 5 | import net.minecraft.world.item.Item; 6 | import net.minecraft.world.item.ItemStack; 7 | 8 | import java.util.Objects; 9 | 10 | /** 11 | * Used as a key in maps 12 | */ 13 | public class ItemKey { 14 | private final Item item; 15 | private final int id; 16 | private final DataComponentMap components; 17 | private final boolean ignoreComponents; 18 | 19 | public ItemKey(ItemStack stack, boolean ignoreComponents) { 20 | this.item = stack.getItem(); 21 | this.id = BuiltInRegistries.ITEM.getId(this.item); 22 | this.components = stack.getComponents(); 23 | this.ignoreComponents = ignoreComponents; 24 | } 25 | 26 | @Override 27 | public int hashCode() { 28 | return 31 * id + (this.ignoreComponents || components == null ? 0 : components.hashCode()); 29 | } 30 | 31 | @Override 32 | public boolean equals(Object other) { 33 | if (this == other) 34 | return true; 35 | if (!(other instanceof ItemKey key)) 36 | return false; 37 | 38 | return key.item == this.item && key.id == this.id && (this.ignoreComponents || Objects.equals(key.components, 39 | this.components)); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /fabric/src/main/java/com/misterpemodder/shulkerboxtooltip/mixin/client/fabric/GuiGraphicsMixin.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.mixin.client.fabric; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.impl.hook.GuiGraphicsExtensions; 4 | import net.minecraft.client.gui.GuiGraphics; 5 | import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipPositioner; 6 | import org.joml.Vector2ic; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.injection.At; 9 | import org.spongepowered.asm.mixin.injection.Redirect; 10 | 11 | @Mixin(GuiGraphics.class) 12 | public abstract class GuiGraphicsMixin { 13 | 14 | @Redirect(at = @At(value = "INVOKE", target = 15 | "Lnet/minecraft/client/gui/screens/inventory/tooltip/ClientTooltipPositioner;" 16 | + "positionTooltip(IIIIII)Lorg/joml/Vector2ic;"), method = "renderTooltip(Lnet/minecraft/client/gui/Font;Ljava/util/List;IILnet/minecraft/client/gui/screens/inventory/tooltip/ClientTooltipPositioner;Lnet/minecraft/resources/Identifier;)V", require = 0) 17 | private Vector2ic captureTooltipYPosition(ClientTooltipPositioner positioner, int guiWidth, int guiHeight, int x, 18 | int y, int totalWidth, int totalHeight) { 19 | Vector2ic result = positioner.positionTooltip(guiWidth, guiHeight, x, y, totalWidth, totalHeight); 20 | ((GuiGraphicsExtensions) this).setTooltipTopYPosition(result.y()); 21 | return result; 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/api/config/PreviewConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.api.config; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.api.color.ColorKey; 4 | 5 | /** 6 | * A read-only view of the preview configuration. 7 | * Maps to the "preview" section of the config file. 8 | * 9 | * @since 3.3.0 10 | */ 11 | public interface PreviewConfiguration { 12 | /** 13 | * Controls the way items with the same ID but differing components should be merged. 14 | * 15 | * @return the stack merging strategy. 16 | * @since 3.3.0 17 | */ 18 | ItemStackMergingStrategy itemStackMergingStrategy(); 19 | 20 | /** 21 | * The max number of items in a preview row. May not affect modded containers. 22 | * 23 | * @return the default max row size. 24 | * @since 3.3.0 25 | */ 26 | int defaultMaxRowSize(); 27 | 28 | /** 29 | * Whether to shorten large item counts using suffixes. (e.g. 1,000,000 -> 1M). 30 | * 31 | * @return whether to shorten item counts. 32 | * @since 3.3.0 33 | */ 34 | boolean shortItemCounts(); 35 | 36 | /** 37 | * Whether to use colors when rendering the preview decoration, 38 | * when {@code false} the renderer should use the default inventory color. 39 | * 40 | * @return whether to use colors in the preview decoration. 41 | * @see ColorKey#DEFAULT 42 | * @since 3.3.0 43 | */ 44 | boolean useColors(); 45 | } 46 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/network/ProtocolVersion.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.network; 2 | 3 | import net.minecraft.network.FriendlyByteBuf; 4 | import org.jetbrains.annotations.Nullable; 5 | 6 | /** 7 | * ShulkerBoxTooltip's network protocol versioning. 8 | * 9 | * @param major The major revision of the protocol, different major versions are always incompatible. 10 | * @param minor The minor version number, different minor version should be compatible. 11 | */ 12 | public record ProtocolVersion(int major, int minor) { 13 | /** 14 | * The current network protocol version. 15 | */ 16 | public static final ProtocolVersion CURRENT = new ProtocolVersion(2, 0); 17 | 18 | /** 19 | * Attempts to read a version from the given buffer. 20 | * 21 | * @param buf The byte buffer. 22 | * @return The remote version, or null if an error occurred. 23 | */ 24 | @Nullable 25 | public static ProtocolVersion readFromPacketBuf(FriendlyByteBuf buf) { 26 | try { 27 | return new ProtocolVersion(buf.readInt(), buf.readInt()); 28 | } catch (RuntimeException e) { 29 | return null; 30 | } 31 | } 32 | 33 | /** 34 | * Writes a version to the given packet buffer. 35 | * 36 | * @param buf The byte buffer. 37 | */ 38 | public void writeToPacketBuf(FriendlyByteBuf buf) { 39 | buf.writeInt(this.major); 40 | buf.writeInt(this.minor); 41 | } 42 | 43 | public String toString() { 44 | return this.major + "." + this.minor; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/mixin/client/ItemContainerContentsMixin.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.mixin.client; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.ShulkerBoxTooltip; 4 | import com.misterpemodder.shulkerboxtooltip.impl.config.Configuration; 5 | import net.minecraft.core.component.DataComponentGetter; 6 | import net.minecraft.network.chat.Component; 7 | import net.minecraft.world.item.Item; 8 | import net.minecraft.world.item.TooltipFlag; 9 | import net.minecraft.world.item.component.ItemContainerContents; 10 | import org.spongepowered.asm.mixin.Mixin; 11 | import org.spongepowered.asm.mixin.injection.At; 12 | import org.spongepowered.asm.mixin.injection.Inject; 13 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 14 | 15 | import java.util.function.Consumer; 16 | 17 | @Mixin(ItemContainerContents.class) 18 | public class ItemContainerContentsMixin { 19 | @Inject(at = @At("HEAD"), method = "addToTooltip(" 20 | + "Lnet/minecraft/world/item/Item$TooltipContext;Ljava/util/function/Consumer;" 21 | + "Lnet/minecraft/world/item/TooltipFlag;Lnet/minecraft/core/component/DataComponentGetter;)V", cancellable = true) 22 | void addToTooltip(Item.TooltipContext tooltipContext, Consumer consumer, TooltipFlag tooltipFlag, 23 | DataComponentGetter dataComponentGetter, CallbackInfo ci) { 24 | if (ShulkerBoxTooltip.config != null 25 | && ShulkerBoxTooltip.config.tooltip.type != Configuration.ShulkerBoxTooltipType.VANILLA) { 26 | ci.cancel(); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/mixin/client/GuiGraphicsMixin.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.mixin.client; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.impl.hook.GuiGraphicsExtensions; 4 | import net.minecraft.client.gui.GuiGraphics; 5 | import org.jetbrains.annotations.Nullable; 6 | import org.spongepowered.asm.mixin.Intrinsic; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.Unique; 9 | import org.spongepowered.asm.mixin.gen.Accessor; 10 | 11 | @Mixin(GuiGraphics.class) 12 | public abstract class GuiGraphicsMixin implements GuiGraphicsExtensions { 13 | @Unique 14 | private int tooltipTopY = 0; 15 | @Unique 16 | private int mouseX = 0; 17 | @Unique 18 | private int mouseY = 0; 19 | 20 | @Intrinsic 21 | public void setTooltipTopYPosition(int topY) { 22 | this.tooltipTopY = topY; 23 | } 24 | 25 | @Override 26 | @Intrinsic 27 | public int getTooltipTopYPosition() { 28 | return this.tooltipTopY; 29 | } 30 | 31 | @Override 32 | @Intrinsic 33 | public void setMouseX(int mouseX) { 34 | this.mouseX = mouseX; 35 | } 36 | 37 | @Override 38 | @Intrinsic 39 | public int getMouseX() { 40 | return this.mouseX; 41 | } 42 | 43 | @Override 44 | @Intrinsic 45 | public void setMouseY(int mouseY) { 46 | this.mouseY = mouseY; 47 | } 48 | 49 | @Override 50 | @Intrinsic 51 | public int getMouseY() { 52 | return this.mouseY; 53 | } 54 | 55 | @Accessor 56 | @Nullable 57 | public abstract Runnable getDeferredTooltip(); 58 | 59 | @Accessor 60 | public abstract void setDeferredTooltip(@Nullable Runnable deferredTooltip); 61 | } 62 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/network/message/S2CMessages.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.network.message; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.impl.network.ServerNetworking; 4 | import com.misterpemodder.shulkerboxtooltip.impl.network.channel.S2CChannel; 5 | import com.misterpemodder.shulkerboxtooltip.impl.util.ShulkerBoxTooltipUtil; 6 | import net.fabricmc.api.EnvType; 7 | import net.fabricmc.api.Environment; 8 | 9 | /** 10 | * The server to client messages of ShulkerBoxTooltip. 11 | */ 12 | public class S2CMessages { 13 | public static final S2CChannel HANDSHAKE_RESPONSE = ServerNetworking.createS2CChannel( 14 | ShulkerBoxTooltipUtil.id("s2c_handshake"), new S2CHandshakeResponse.Type()); 15 | public static final S2CChannel ENDER_CHEST_UPDATE = ServerNetworking.createS2CChannel( 16 | ShulkerBoxTooltipUtil.id("ec_update"), new S2CEnderChestUpdate.Type()); 17 | 18 | private S2CMessages() { 19 | } 20 | 21 | public static void registerPayloadTypes() { 22 | HANDSHAKE_RESPONSE.registerPayloadType(); 23 | ENDER_CHEST_UPDATE.registerPayloadType(); 24 | } 25 | 26 | /** 27 | * Registers all to server to client messages. 28 | */ 29 | @Environment(EnvType.CLIENT) 30 | public static void registerAll() { 31 | HANDSHAKE_RESPONSE.register(); 32 | ENDER_CHEST_UPDATE.register(); 33 | } 34 | 35 | /** 36 | * Unregisters all to server to client messages. 37 | */ 38 | @Environment(EnvType.CLIENT) 39 | public static void unregisterAll() { 40 | HANDSHAKE_RESPONSE.unregister(); 41 | ENDER_CHEST_UPDATE.unregister(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/provider/LecternPreviewProvider.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.provider; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.api.PreviewContext; 4 | import com.mojang.serialization.MapCodec; 5 | import net.minecraft.core.NonNullList; 6 | import net.minecraft.core.component.DataComponents; 7 | import net.minecraft.world.Container; 8 | import net.minecraft.world.item.ItemStack; 9 | import net.minecraft.world.item.component.TypedEntityData; 10 | import net.minecraft.world.level.block.entity.BlockEntityType; 11 | 12 | import java.util.List; 13 | import java.util.function.Supplier; 14 | 15 | public class LecternPreviewProvider extends InventoryAwarePreviewProvider { 16 | private static final MapCodec CODEC = ItemStack.CODEC.fieldOf("Book"); 17 | 18 | public LecternPreviewProvider(int maxRowSize, Supplier inventoryFactory) { 19 | super(maxRowSize, inventoryFactory); 20 | } 21 | 22 | @SuppressWarnings("deprecation") 23 | @Override 24 | public List getInventory(PreviewContext context) { 25 | int invMaxSize = this.getInventoryMaxSize(context); 26 | List inv = NonNullList.withSize(invMaxSize, ItemStack.EMPTY); 27 | TypedEntityData> beData = context.stack().get(DataComponents.BLOCK_ENTITY_DATA); 28 | 29 | if (beData != null) 30 | beData.getUnsafe().read(CODEC).ifPresent(book -> inv.set(0, book)); 31 | 32 | return inv; 33 | } 34 | 35 | @Override 36 | public boolean showTooltipHints(PreviewContext context) { 37 | return context.stack().has(DataComponents.BLOCK_ENTITY_DATA); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /fabric/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/network/fabric/FabricS2CChannel.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.network.fabric; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.impl.network.Payload; 4 | import com.misterpemodder.shulkerboxtooltip.impl.network.channel.S2CChannel; 5 | import com.misterpemodder.shulkerboxtooltip.impl.network.context.S2CMessageContext; 6 | import com.misterpemodder.shulkerboxtooltip.impl.network.message.MessageType; 7 | import net.fabricmc.api.EnvType; 8 | import net.fabricmc.api.Environment; 9 | import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; 10 | import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; 11 | import net.minecraft.resources.Identifier; 12 | import net.minecraft.server.level.ServerPlayer; 13 | 14 | class FabricS2CChannel extends FabricChannel implements S2CChannel { 15 | public FabricS2CChannel(Identifier id, MessageType type) { 16 | super(id, type); 17 | } 18 | 19 | @Override 20 | @Environment(EnvType.CLIENT) 21 | public void register() { 22 | ClientPlayNetworking.registerReceiver(this.id, this::onReceive); 23 | } 24 | 25 | @Override 26 | @Environment(EnvType.CLIENT) 27 | public void unregister() { 28 | ClientPlayNetworking.unregisterReceiver(this.getId()); 29 | } 30 | 31 | @Override 32 | public void sendTo(ServerPlayer player, T message) { 33 | ServerPlayNetworking.send(player, new Payload<>(this.id, message)); 34 | } 35 | 36 | @Environment(EnvType.CLIENT) 37 | private void onReceive(Payload payload, ClientPlayNetworking.Context context) { 38 | this.type.onReceive(payload.value(), new S2CMessageContext<>(this)); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/provider/InventoryAwarePreviewProvider.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.provider; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.api.PreviewContext; 4 | import com.misterpemodder.shulkerboxtooltip.api.provider.BlockEntityPreviewProvider; 5 | import net.minecraft.world.Container; 6 | import net.minecraft.world.RandomizableContainer; 7 | 8 | import java.util.function.Supplier; 9 | 10 | /** 11 | * A {@link BlockEntityPreviewProvider} that uses a {@link Container} instance to get its information. 12 | */ 13 | public class InventoryAwarePreviewProvider extends BlockEntityPreviewProvider { 14 | 15 | private final Supplier inventoryFactory; 16 | 17 | private final ThreadLocal cachedInventory = ThreadLocal.withInitial(() -> null); 18 | 19 | public InventoryAwarePreviewProvider(int maxRowSize, Supplier inventoryFactory) { 20 | super(27, false, maxRowSize, maxRowSize); 21 | this.inventoryFactory = inventoryFactory; 22 | } 23 | 24 | private I getInventory() { 25 | I inv = this.cachedInventory.get(); 26 | if (inv == null) { 27 | inv = this.inventoryFactory.get(); 28 | this.cachedInventory.set(inv); 29 | } 30 | return inv; 31 | } 32 | 33 | @Override 34 | public boolean showTooltipHints(PreviewContext context) { 35 | return this.shouldDisplay(context); 36 | } 37 | 38 | @Override 39 | public int getInventoryMaxSize(PreviewContext context) { 40 | return this.getInventory().getContainerSize(); 41 | } 42 | 43 | @Override 44 | public boolean canUseLootTables() { 45 | return this.getInventory() instanceof RandomizableContainer; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /forge/src/main/java/com/misterpemodder/shulkerboxtooltip/forge/ShulkerBoxTooltipImpl.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.forge; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.ShulkerBoxTooltip; 4 | import com.misterpemodder.shulkerboxtooltip.api.forge.ShulkerBoxTooltipPlugin; 5 | import net.minecraftforge.eventbus.api.listener.SubscribeEvent; 6 | import net.minecraftforge.fml.common.Mod; 7 | import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; 8 | import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; 9 | import net.minecraftforge.fml.loading.FMLPaths; 10 | import org.jetbrains.annotations.Contract; 11 | 12 | import java.nio.file.Path; 13 | 14 | @Mod(ShulkerBoxTooltip.MOD_ID) 15 | @Mod.EventBusSubscriber(modid = ShulkerBoxTooltip.MOD_ID, bus = Mod.EventBusSubscriber.Bus.MOD) 16 | @SuppressWarnings("unused") 17 | public class ShulkerBoxTooltipImpl extends ShulkerBoxTooltip { 18 | public static ShulkerBoxTooltipImpl INSTANCE = null; 19 | public final FMLJavaModLoadingContext context; 20 | 21 | public ShulkerBoxTooltipImpl(FMLJavaModLoadingContext context) { 22 | INSTANCE = this; 23 | this.context = context; 24 | } 25 | 26 | @SubscribeEvent 27 | public static void onSetup(FMLCommonSetupEvent event) { 28 | event.enqueueWork(() -> { 29 | INSTANCE.context.registerExtensionPoint(ShulkerBoxTooltipPlugin.class, 30 | () -> new ShulkerBoxTooltipPlugin(() -> INSTANCE)); 31 | ShulkerBoxTooltip.init(); 32 | }); 33 | } 34 | 35 | /** 36 | * Implementation of {@link ShulkerBoxTooltip#getConfigDir()}. 37 | */ 38 | @Contract(value = " -> !null", pure = true) 39 | public static Path getConfigDir() { 40 | return FMLPaths.CONFIGDIR.get(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/PreviewContextImpl.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.ShulkerBoxTooltip; 4 | import com.misterpemodder.shulkerboxtooltip.api.PreviewContext; 5 | import com.misterpemodder.shulkerboxtooltip.impl.config.Configuration; 6 | import net.minecraft.core.HolderLookup; 7 | import net.minecraft.world.entity.player.Player; 8 | import net.minecraft.world.item.ItemStack; 9 | 10 | import javax.annotation.Nonnull; 11 | import javax.annotation.Nullable; 12 | 13 | public record PreviewContextImpl(ItemStack stack, @Nullable Player owner, Configuration config, 14 | @Nullable HolderLookup.Provider registryLookup) implements PreviewContext { 15 | public static class Builder implements PreviewContext.Builder { 16 | private final ItemStack stack; 17 | private Player owner; 18 | private HolderLookup.Provider registryLookup; 19 | 20 | public Builder(ItemStack stack) { 21 | this.stack = stack; 22 | } 23 | 24 | @Override 25 | public PreviewContextImpl.Builder withOwner(@Nullable Player owner) { 26 | this.owner = owner; 27 | return this; 28 | } 29 | 30 | @Override 31 | public PreviewContextImpl.Builder withRegistryLookup(@Nullable HolderLookup.Provider registryLookup) { 32 | this.registryLookup = registryLookup; 33 | return this; 34 | } 35 | 36 | @Nonnull 37 | @Override 38 | public PreviewContext build() { 39 | if (this.registryLookup == null && this.owner != null) { 40 | this.registryLookup = this.owner.registryAccess(); 41 | } 42 | return new PreviewContextImpl(this.stack, this.owner, ShulkerBoxTooltip.config, this.registryLookup); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/network/message/MessageType.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.network.message; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.impl.network.context.C2SMessageContext; 4 | import com.misterpemodder.shulkerboxtooltip.impl.network.context.MessageContext; 5 | import com.misterpemodder.shulkerboxtooltip.impl.network.context.S2CMessageContext; 6 | import net.minecraft.network.FriendlyByteBuf; 7 | 8 | /** 9 | * Describes a message. 10 | * 11 | * @param The message data. 12 | */ 13 | public interface MessageType { 14 | /** 15 | * Writes the message to the packet byte buffer. 16 | * 17 | * @param message The message to encode. 18 | * @param buf The buffer. 19 | */ 20 | void encode(T message, FriendlyByteBuf buf); 21 | 22 | /** 23 | * Reads a message from the given buffer. 24 | * 25 | * @param buf The buffer. 26 | * @return The decoded message. 27 | */ 28 | T decode(FriendlyByteBuf buf); 29 | 30 | /** 31 | * Handles the given message. 32 | * 33 | * @param message The message to handle. 34 | * @param context Either an instance of {@link C2SMessageContext} or {@link S2CMessageContext} 35 | */ 36 | void onReceive(T message, MessageContext context); 37 | 38 | /** 39 | * Called when the message is registered. 40 | * 41 | * @param context Either an instance of {@link C2SMessageContext} or {@link S2CMessageContext} 42 | */ 43 | default void onRegister(MessageContext context) { 44 | } 45 | 46 | /** 47 | * Called when the message is unregistered. 48 | * 49 | * @param context Either an instance of {@link C2SMessageContext} or {@link S2CMessageContext} 50 | */ 51 | default void onUnregister(MessageContext context) { 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /forge/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/network/forge/ForgeS2CChannel.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.network.forge; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.impl.network.Payload; 4 | import com.misterpemodder.shulkerboxtooltip.impl.network.channel.S2CChannel; 5 | import com.misterpemodder.shulkerboxtooltip.impl.network.context.S2CMessageContext; 6 | import com.misterpemodder.shulkerboxtooltip.impl.network.message.MessageType; 7 | import net.minecraft.resources.Identifier; 8 | import net.minecraft.server.level.ServerPlayer; 9 | import net.minecraftforge.api.distmarker.Dist; 10 | import net.minecraftforge.api.distmarker.OnlyIn; 11 | import net.minecraftforge.event.network.CustomPayloadEvent; 12 | import net.minecraftforge.network.PacketDistributor; 13 | 14 | public class ForgeS2CChannel extends ForgeChannel implements S2CChannel { 15 | public ForgeS2CChannel(Identifier id, MessageType type) { 16 | super(id, type); 17 | } 18 | 19 | @Override 20 | @OnlyIn(Dist.CLIENT) 21 | public void register() { 22 | // NeoForge does not support dynamic channel registration 23 | } 24 | 25 | @Override 26 | @OnlyIn(Dist.CLIENT) 27 | public void unregister() { 28 | // NeoForge does not support dynamic channel registration 29 | } 30 | 31 | @Override 32 | public void sendTo(ServerPlayer player, T message) { 33 | this.innerChannel.send(new Payload<>(this.id, message), PacketDistributor.PLAYER.with(player)); 34 | } 35 | 36 | @Override 37 | @OnlyIn(Dist.CLIENT) 38 | protected void onReceive(Payload payload, CustomPayloadEvent.Context context) { 39 | if (context.isClientSide()) { 40 | this.type.onReceive(payload.value(), new S2CMessageContext<>(this)); 41 | } 42 | context.setPacketHandled(true); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/config/gui/entry/PrefixTextConfigEntry.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.config.gui.entry; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.impl.config.gui.ConfigCategoryTab; 4 | import net.fabricmc.api.EnvType; 5 | import net.fabricmc.api.Environment; 6 | import net.minecraft.client.gui.GuiGraphics; 7 | import net.minecraft.client.gui.components.MultiLineTextWidget; 8 | import net.minecraft.client.gui.components.events.GuiEventListener; 9 | import net.minecraft.client.gui.narration.NarratableEntry; 10 | import net.minecraft.network.chat.Component; 11 | import org.jetbrains.annotations.NotNull; 12 | 13 | import java.util.List; 14 | 15 | @Environment(EnvType.CLIENT) 16 | public final class PrefixTextConfigEntry extends ConfigEntry { 17 | private final MultiLineTextWidget textWidget; 18 | private final List textWidgetAsList; 19 | 20 | public PrefixTextConfigEntry(ConfigCategoryTab tab, Component text) { 21 | super(); 22 | this.textWidget = new MultiLineTextWidget(text, tab.getMinecraft().font); 23 | this.textWidgetAsList = List.of(this.textWidget); 24 | } 25 | 26 | @NotNull 27 | @Override 28 | public List narratables() { 29 | return this.textWidgetAsList; 30 | } 31 | 32 | @Override 33 | public void renderContent(GuiGraphics guiGraphics, int mouseX, int mouseY, boolean hovered, float delta) { 34 | this.textWidget.setX(this.getContentX()); 35 | this.textWidget.setY(this.getContentY()); 36 | this.textWidget.setMaxWidth(this.getContentWidth()); 37 | this.textWidget.renderWidget(guiGraphics, mouseX, mouseY, delta); 38 | } 39 | 40 | @NotNull 41 | @Override 42 | public List children() { 43 | return this.textWidgetAsList; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /fabric/src/main/resources/fabric.mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": 1, 3 | "id": "shulkerboxtooltip", 4 | "version": "${version}", 5 | "name": "Shulker Box Tooltip", 6 | "icon": "assets/shulkerboxtooltip/icon.png", 7 | "description": "What's in my shulker box?", 8 | "license": "MIT", 9 | "authors": [ 10 | "MisterPeModder" 11 | ], 12 | "contributors": [ 13 | "Samekichi", 14 | "disguys", 15 | "Felix14-v2", 16 | "egeesin", 17 | "gbl", 18 | "gyular", 19 | "notlin4", 20 | "JustEmo", 21 | "ReneMuetti", 22 | "yichifauzi", 23 | "Alexander317", 24 | "TheLegendofSaram", 25 | "yumunet", 26 | "Texaliuz", 27 | "pilahito", 28 | "mpustovoi" 29 | ], 30 | "contact": { 31 | "homepage": "https://minecraft.curseforge.com/projects/shulkerboxtooltip", 32 | "sources": "https://github.com/MisterPeModder/ShulkerBoxTooltip", 33 | "issues": "https://github.com/MisterPeModder/ShulkerBoxTooltip/issues", 34 | "maven": "https://maven.misterpemodder.com/libs-release/com/misterpemodder/shulkerboxtooltip/" 35 | }, 36 | "environment": "*", 37 | "mixins": [ 38 | "shulkerboxtooltip-common.mixins.json", 39 | "shulkerboxtooltip-fabric.mixins.json" 40 | ], 41 | "depends": { 42 | "fabric-resource-loader-v0": "*", 43 | "fabricloader": ">=0.16.0", 44 | "minecraft": ">=1.21.10" 45 | }, 46 | "entrypoints": { 47 | "main": [ 48 | "com.misterpemodder.shulkerboxtooltip.fabric.ShulkerBoxTooltipImpl" 49 | ], 50 | "client": [ 51 | "com.misterpemodder.shulkerboxtooltip.fabric.ShulkerBoxTooltipClientImpl" 52 | ], 53 | "shulkerboxtooltip": [ 54 | "com.misterpemodder.shulkerboxtooltip.fabric.ShulkerBoxTooltipImpl" 55 | ], 56 | "modmenu": [ 57 | "com.misterpemodder.shulkerboxtooltip.impl.ModMenuCompat" 58 | ] 59 | }, 60 | "suggests": { 61 | "modmenu": "*" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/util/EnvironmentUtil.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.util; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.impl.config.Configuration; 4 | import dev.architectury.injectables.annotations.ExpectPlatform; 5 | import org.jetbrains.annotations.Contract; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | import java.lang.reflect.InvocationTargetException; 9 | 10 | public abstract sealed class EnvironmentUtil permits ClientEnvironmentUtil, ServerEnvironmentUtil { 11 | 12 | private static EnvironmentUtil instance; 13 | 14 | private static final String PACKAGE_NAME = EnvironmentUtil.class.getPackageName(); 15 | private static final String CLIENT_ENVIRONMENT_UTIL = PACKAGE_NAME + ".ClientEnvironmentUtil"; 16 | private static final String SERVER_ENVIRONMENT_UTIL = PACKAGE_NAME + ".ServerEnvironmentUtil"; 17 | 18 | public static EnvironmentUtil getInstance() { 19 | if (instance == null) { 20 | String className = isClient() ? CLIENT_ENVIRONMENT_UTIL : SERVER_ENVIRONMENT_UTIL; 21 | try { 22 | Class clazz = Class.forName(className); 23 | instance = (EnvironmentUtil) clazz.getDeclaredConstructor().newInstance(); 24 | } catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException | 25 | InvocationTargetException e) { 26 | throw new IllegalStateException(e); 27 | } 28 | } 29 | return instance; 30 | } 31 | 32 | @NotNull 33 | public abstract Configuration makeConfiguration(); 34 | 35 | @NotNull 36 | public abstract Class getConfigurationClass(); 37 | 38 | /** 39 | * @return Whether the current environment type (or Dist in forge terms) is the client. 40 | */ 41 | @ExpectPlatform 42 | @Contract(value = "-> _", pure = true) 43 | public static boolean isClient() { 44 | //noinspection Contract 45 | throw new AssertionError("Missing implementation of EnvironmentUtil.isClient()"); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /neoforge/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/network/neoforge/NeoForgeS2CChannel.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.network.neoforge; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.impl.network.Payload; 4 | import com.misterpemodder.shulkerboxtooltip.impl.network.channel.S2CChannel; 5 | import com.misterpemodder.shulkerboxtooltip.impl.network.context.S2CMessageContext; 6 | import com.misterpemodder.shulkerboxtooltip.impl.network.message.MessageType; 7 | import net.minecraft.resources.Identifier; 8 | import net.minecraft.server.level.ServerPlayer; 9 | import net.neoforged.api.distmarker.Dist; 10 | import net.neoforged.api.distmarker.OnlyIn; 11 | import net.neoforged.neoforge.network.PacketDistributor; 12 | import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent; 13 | import net.neoforged.neoforge.network.handling.IPayloadContext; 14 | 15 | public class NeoForgeS2CChannel extends NeoForgeChannel implements S2CChannel { 16 | public NeoForgeS2CChannel(Identifier id, MessageType type) { 17 | super(id, type); 18 | } 19 | 20 | @Override 21 | @OnlyIn(Dist.CLIENT) 22 | public void register() { 23 | // NeoForge does not support dynamic channel registration 24 | } 25 | 26 | @Override 27 | @OnlyIn(Dist.CLIENT) 28 | public void unregister() { 29 | // NeoForge does not support dynamic channel registration 30 | } 31 | 32 | @Override 33 | public void sendTo(ServerPlayer player, T message) { 34 | PacketDistributor.sendToPlayer(player, new Payload<>(this.id, message)); 35 | } 36 | 37 | @Override 38 | @OnlyIn(Dist.CLIENT) 39 | protected void onReceive(Payload payload, IPayloadContext context) { 40 | if (context.flow().isClientbound()) { 41 | this.type.onReceive(payload.value(), new S2CMessageContext<>(this)); 42 | } 43 | } 44 | 45 | @Override 46 | protected void registerPayloadTypeInner(RegisterPayloadHandlersEvent event) { 47 | event.registrar("1").optional().commonToClient(this.id, this.codec, this::onReceive); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/config/gui/ColorWidget.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.config.gui; 2 | 3 | import net.fabricmc.api.EnvType; 4 | import net.fabricmc.api.Environment; 5 | import net.minecraft.client.gui.GuiGraphics; 6 | import net.minecraft.client.gui.components.AbstractWidget; 7 | import net.minecraft.client.gui.components.WidgetSprites; 8 | import net.minecraft.client.gui.narration.NarrationElementOutput; 9 | import net.minecraft.client.renderer.RenderPipelines; 10 | import net.minecraft.network.chat.Component; 11 | import net.minecraft.resources.Identifier; 12 | 13 | import java.util.function.IntSupplier; 14 | 15 | @Environment(EnvType.CLIENT) 16 | public class ColorWidget extends AbstractWidget { 17 | private static final WidgetSprites SPRITES = new WidgetSprites( 18 | Identifier.withDefaultNamespace("widget/text_field"), 19 | Identifier.withDefaultNamespace("widget/text_field_highlighted")); 20 | 21 | private final AbstractWidget neighbor; 22 | private final IntSupplier colorSupplier; 23 | 24 | public ColorWidget(Component label, AbstractWidget neighbor, IntSupplier colorSupplier) { 25 | super(0, 0, 18, 18, label); 26 | this.neighbor = neighbor; 27 | this.colorSupplier = colorSupplier; 28 | this.active = false; 29 | } 30 | 31 | @Override 32 | protected void renderWidget(GuiGraphics guiGraphics, int mouseX, int mouseY, float delta) { 33 | if (!this.visible) 34 | return; 35 | Identifier Identifier = SPRITES.get(this.isActive(), this.neighbor.isFocused()); 36 | guiGraphics.blitSprite(RenderPipelines.GUI_TEXTURED, Identifier, this.getX(), this.getY(), this.getWidth(), 37 | this.getHeight()); 38 | guiGraphics.fill(this.getX() + 1, this.getY() + 1, this.getX() + this.getWidth() - 1, 39 | this.getY() + this.getHeight() - 1, 0xFF000000 | this.colorSupplier.getAsInt()); 40 | } 41 | 42 | @Override 43 | protected void updateWidgetNarration(NarrationElementOutput narrationElementOutput) { 44 | // nothing to narrate 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /neoforge/src/main/java/com/misterpemodder/shulkerboxtooltip/neoforge/ShulkerBoxTooltipImpl.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.neoforge; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.ShulkerBoxTooltip; 4 | import com.misterpemodder.shulkerboxtooltip.api.neoforge.ShulkerBoxTooltipPlugin; 5 | import com.misterpemodder.shulkerboxtooltip.impl.network.neoforge.ClientNetworkingImpl; 6 | import com.misterpemodder.shulkerboxtooltip.impl.network.neoforge.ServerNetworkingImpl; 7 | import net.neoforged.bus.api.SubscribeEvent; 8 | import net.neoforged.fml.ModLoadingContext; 9 | import net.neoforged.fml.common.EventBusSubscriber; 10 | import net.neoforged.fml.common.Mod; 11 | import net.neoforged.fml.event.lifecycle.FMLCommonSetupEvent; 12 | import net.neoforged.fml.loading.FMLPaths; 13 | import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent; 14 | import org.jetbrains.annotations.Contract; 15 | 16 | import java.nio.file.Path; 17 | 18 | 19 | @Mod(ShulkerBoxTooltip.MOD_ID) 20 | @EventBusSubscriber(modid = ShulkerBoxTooltip.MOD_ID) 21 | @SuppressWarnings("unused") 22 | public class ShulkerBoxTooltipImpl extends ShulkerBoxTooltip { 23 | @SubscribeEvent 24 | public static void onSetup(FMLCommonSetupEvent event) { 25 | event.enqueueWork(() -> { 26 | ModLoadingContext.get().registerExtensionPoint(ShulkerBoxTooltipPlugin.class, 27 | () -> new ShulkerBoxTooltipPlugin(ShulkerBoxTooltipImpl::new)); 28 | ShulkerBoxTooltip.init(); 29 | }); 30 | } 31 | 32 | @SubscribeEvent 33 | public static void onRegisterPayloads(RegisterPayloadHandlersEvent event) { 34 | ServerNetworkingImpl.S2C_CHANNELS.values().forEach(channel -> channel.registerPayloadTypeDeferred(event)); 35 | ClientNetworkingImpl.C2S_CHANNELS.values().forEach(channel -> channel.registerPayloadTypeDeferred(event)); 36 | } 37 | 38 | /** 39 | * Implementation of {@link ShulkerBoxTooltip#getConfigDir()}. 40 | */ 41 | @Contract(value = " -> !null", pure = true) 42 | public static Path getConfigDir() { 43 | return FMLPaths.CONFIGDIR.get(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/util/Key.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.util; 2 | 3 | import com.mojang.blaze3d.platform.InputConstants; 4 | import net.fabricmc.api.EnvType; 5 | import net.fabricmc.api.Environment; 6 | import org.lwjgl.glfw.GLFW; 7 | 8 | import javax.annotation.Nullable; 9 | import java.util.Objects; 10 | 11 | @Environment(EnvType.CLIENT) 12 | public final class Key { 13 | public static final Key UNKNOWN_KEY = new Key(InputConstants.UNKNOWN); 14 | 15 | InputConstants.Key inner; 16 | 17 | public Key(InputConstants.Key key) { 18 | this.inner = key; 19 | } 20 | 21 | public InputConstants.Key get() { 22 | return this.inner; 23 | } 24 | 25 | public boolean isUnbound() { 26 | return this.inner.equals(InputConstants.UNKNOWN); 27 | } 28 | 29 | public void set(InputConstants.Key key) { 30 | this.inner = key; 31 | } 32 | 33 | @Nullable 34 | public static Key defaultPreviewKey() { 35 | return new Key(InputConstants.Type.KEYSYM.getOrCreate(GLFW.GLFW_KEY_LEFT_SHIFT)); 36 | } 37 | 38 | @Nullable 39 | public static Key defaultFullPreviewKey() { 40 | return new Key(InputConstants.Type.KEYSYM.getOrCreate(GLFW.GLFW_KEY_LEFT_ALT)); 41 | } 42 | 43 | @Nullable 44 | public static Key defaultLockTooltipKey() { 45 | return new Key(InputConstants.Type.KEYSYM.getOrCreate(GLFW.GLFW_KEY_LEFT_CONTROL)); 46 | } 47 | 48 | public static Key fromTranslationKey(@Nullable String translationKey) { 49 | if (translationKey == null) 50 | return UNKNOWN_KEY; 51 | try { 52 | return new Key(InputConstants.getKey(translationKey)); 53 | } catch (Exception e) { 54 | return UNKNOWN_KEY; 55 | } 56 | } 57 | 58 | @Override 59 | public boolean equals(Object o) { 60 | if (this == o) 61 | return true; 62 | if (o == null || getClass() != o.getClass()) 63 | return false; 64 | Key key = (Key) o; 65 | return Objects.equals(this.inner, key.inner); 66 | } 67 | 68 | @Override 69 | public int hashCode() { 70 | return Objects.hashCode(inner); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/mixin/client/ItemStackMixin.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.mixin.client; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.ShulkerBoxTooltip; 4 | import net.minecraft.core.component.DataComponentType; 5 | import net.minecraft.core.component.DataComponents; 6 | import net.minecraft.network.chat.Component; 7 | import net.minecraft.world.item.BlockItem; 8 | import net.minecraft.world.item.Item; 9 | import net.minecraft.world.item.ItemStack; 10 | import net.minecraft.world.item.TooltipFlag; 11 | import net.minecraft.world.item.component.TooltipDisplay; 12 | import net.minecraft.world.item.component.TooltipProvider; 13 | import net.minecraft.world.level.block.ShulkerBoxBlock; 14 | import org.spongepowered.asm.mixin.Mixin; 15 | import org.spongepowered.asm.mixin.injection.At; 16 | import org.spongepowered.asm.mixin.injection.Inject; 17 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 18 | 19 | import java.util.function.Consumer; 20 | 21 | @Mixin(ItemStack.class) 22 | public class ItemStackMixin { 23 | @Inject(at = @At("HEAD"), method = "addToTooltip(" 24 | + "Lnet/minecraft/core/component/DataComponentType;Lnet/minecraft/world/item/Item$TooltipContext;" 25 | + "Lnet/minecraft/world/item/component/TooltipDisplay;Ljava/util/function/Consumer;Lnet/minecraft/world/item/TooltipFlag;)V", cancellable = true) 26 | private void removeLore(DataComponentType componentType, 27 | Item.TooltipContext tooltipContext, TooltipDisplay tooltipDisplay, Consumer consumer, 28 | TooltipFlag tooltipFlag, CallbackInfo ci) { 29 | if (componentType == DataComponents.LORE) { 30 | Item item = ((ItemStack) (Object) this).getItem(); 31 | 32 | //noinspection UnreachableCode 33 | if (ShulkerBoxTooltip.config != null && ShulkerBoxTooltip.config.tooltip.hideShulkerBoxLore 34 | && item instanceof BlockItem blockitem && blockitem.getBlock() instanceof ShulkerBoxBlock) { 35 | ci.cancel(); 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/config/gui/entry/CategoryTitleConfigEntry.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.config.gui.entry; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.impl.config.gui.ConfigCategoryTab; 4 | import net.fabricmc.api.EnvType; 5 | import net.fabricmc.api.Environment; 6 | import net.minecraft.client.Minecraft; 7 | import net.minecraft.client.gui.GuiGraphics; 8 | import net.minecraft.client.gui.components.events.GuiEventListener; 9 | import net.minecraft.client.gui.narration.NarratableEntry; 10 | import net.minecraft.client.gui.narration.NarratedElementType; 11 | import net.minecraft.client.gui.narration.NarrationElementOutput; 12 | import net.minecraft.network.chat.Component; 13 | import org.jetbrains.annotations.NotNull; 14 | 15 | import java.util.List; 16 | 17 | @Environment(EnvType.CLIENT) 18 | public final class CategoryTitleConfigEntry extends ConfigEntry { 19 | private final Minecraft minecraft; 20 | private final Component label; 21 | 22 | public CategoryTitleConfigEntry(ConfigCategoryTab tab, Component label) { 23 | super(); 24 | this.minecraft = tab.getMinecraft(); 25 | this.label = label; 26 | } 27 | 28 | @NotNull 29 | @Override 30 | public List narratables() { 31 | return List.of(new NarratableEntry() { 32 | @NotNull 33 | @Override 34 | public NarratableEntry.NarrationPriority narrationPriority() { 35 | return NarratableEntry.NarrationPriority.HOVERED; 36 | } 37 | 38 | @Override 39 | public void updateNarration(NarrationElementOutput narrationElementOutput) { 40 | narrationElementOutput.add(NarratedElementType.TITLE, CategoryTitleConfigEntry.this.label); 41 | } 42 | }); 43 | } 44 | 45 | @Override 46 | public void renderContent(GuiGraphics guiGraphics, int mouseX, int mouseY, boolean hovered, float delta) { 47 | guiGraphics.drawCenteredString(this.minecraft.font, this.label, this.getContentXMiddle(), this.getContentY() + 5, 48 | -1); 49 | } 50 | 51 | @NotNull 52 | @Override 53 | public List children() { 54 | return List.of(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/config/ClientConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.config; 2 | 3 | import blue.endless.jankson.Comment; 4 | import com.misterpemodder.shulkerboxtooltip.api.color.ColorRegistry; 5 | import com.misterpemodder.shulkerboxtooltip.impl.color.ColorRegistryImpl; 6 | import com.misterpemodder.shulkerboxtooltip.impl.config.annotation.ConfigCategory; 7 | import com.misterpemodder.shulkerboxtooltip.impl.util.Key; 8 | import net.fabricmc.api.EnvType; 9 | import net.fabricmc.api.Environment; 10 | 11 | @Environment(EnvType.CLIENT) 12 | public class ClientConfiguration extends Configuration { 13 | @ConfigCategory(ordinal = 3) 14 | public ColorsCategory colors; 15 | 16 | @ConfigCategory(ordinal = 4) 17 | public ControlsCategory controls; 18 | 19 | 20 | public ClientConfiguration() { 21 | super(); 22 | this.colors = new ColorsCategory(); 23 | this.controls = new ControlsCategory(); 24 | } 25 | 26 | 27 | @Environment(EnvType.CLIENT) 28 | public static class ColorsCategory { 29 | @Comment(""" 30 | Controls whether the preview window should be colored. 31 | (default value: true)""") 32 | public boolean coloredPreview = true; 33 | 34 | public ColorRegistry colors = ColorRegistryImpl.INSTANCE; 35 | } 36 | 37 | 38 | @Environment(EnvType.CLIENT) 39 | public static class ControlsCategory { 40 | @Comment(""" 41 | Press this key when hovering a container stack to open the preview window. 42 | (default value: key.keyboard.left.shift)""") 43 | public Key previewKey = Key.defaultPreviewKey(); 44 | 45 | @Comment(""" 46 | Press this key when hovering a container stack to open the full preview window. 47 | (default value: key.keyboard.left.alt)""") 48 | public Key fullPreviewKey = Key.defaultFullPreviewKey(); 49 | 50 | @Comment(""" 51 | Hold this key when previewing a stack to lock the tooltip. 52 | (default value: key.keyboard.left.control)""") 53 | public Key lockTooltipKey = Key.defaultLockTooltipKey(); 54 | } 55 | 56 | @Override 57 | public boolean useColors() { 58 | return this.colors.coloredPreview; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/config/gui/ConfigEntryList.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.config.gui; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.impl.config.gui.entry.ConfigEntry; 4 | import com.misterpemodder.shulkerboxtooltip.impl.config.gui.entry.ValueConfigEntry; 5 | import net.fabricmc.api.EnvType; 6 | import net.fabricmc.api.Environment; 7 | import net.minecraft.client.Minecraft; 8 | import net.minecraft.client.gui.GuiGraphics; 9 | import net.minecraft.client.gui.components.ContainerObjectSelectionList; 10 | 11 | @Environment(EnvType.CLIENT) 12 | public final class ConfigEntryList extends ContainerObjectSelectionList { 13 | private final ConfigCategoryTab tab; 14 | 15 | public ConfigEntryList(ConfigCategoryTab tab, Minecraft minecraft, int width, int contentHeight, int headerHeight, 16 | int itemSpacing, Iterable entries) { 17 | super(minecraft, width, contentHeight, headerHeight, itemSpacing); 18 | this.tab = tab; 19 | entries.forEach(this::addEntry); 20 | } 21 | 22 | @Override 23 | public void renderWidget(GuiGraphics guiGraphics, int mouseX, int mouseY, float delta) { 24 | super.renderWidget(guiGraphics, mouseX, mouseY, delta); 25 | var entry = this.getHovered(); 26 | if (entry != null) { 27 | if (entry instanceof ValueConfigEntry valueEntry) { 28 | if (valueEntry.resetButton.isHovered()) { 29 | guiGraphics.setTooltipForNextFrame(ValueConfigEntry.RESET_BUTTON_TOOLTIP, mouseX, mouseY); 30 | return; 31 | } else if (valueEntry.undoButton.isHovered()) { 32 | guiGraphics.setTooltipForNextFrame(ValueConfigEntry.UNDO_BUTTON_TOOLTIP, mouseX, mouseY); 33 | return; 34 | } 35 | } 36 | if (entry.getTooltip() != null) { 37 | guiGraphics.setTooltipForNextFrame(entry.getTooltip(), mouseX, mouseY); 38 | } 39 | } 40 | } 41 | 42 | @Override 43 | public int getRowWidth() { 44 | return this.width - 80; 45 | } 46 | 47 | public void refreshEntries() { 48 | this.children().forEach(ConfigEntry::refresh); 49 | } 50 | 51 | @Override 52 | protected void renderListSeparators(GuiGraphics guiGraphics) { 53 | // don't render separators 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /forge/src/main/java/com/misterpemodder/shulkerboxtooltip/mixin/client/forge/AbstractContainerScreenMixin.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.mixin.client.forge; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.impl.hook.ContainerScreenDrawTooltip; 4 | import com.misterpemodder.shulkerboxtooltip.impl.hook.ContainerScreenLockTooltip; 5 | import com.misterpemodder.shulkerboxtooltip.impl.hook.GuiGraphicsExtensions; 6 | import net.minecraft.client.gui.Font; 7 | import net.minecraft.client.gui.GuiGraphics; 8 | import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; 9 | import net.minecraft.core.component.DataComponents; 10 | import net.minecraft.network.chat.Component; 11 | import net.minecraft.resources.Identifier; 12 | import net.minecraft.world.inventory.tooltip.TooltipComponent; 13 | import net.minecraft.world.item.ItemStack; 14 | import org.spongepowered.asm.mixin.Mixin; 15 | import org.spongepowered.asm.mixin.injection.At; 16 | import org.spongepowered.asm.mixin.injection.Redirect; 17 | 18 | import javax.annotation.Nonnull; 19 | import java.util.List; 20 | import java.util.Optional; 21 | 22 | @Mixin(AbstractContainerScreen.class) 23 | @SuppressWarnings("OptionalUsedAsFieldOrParameterType") 24 | public class AbstractContainerScreenMixin implements ContainerScreenDrawTooltip { 25 | @Redirect(at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/GuiGraphics;setTooltipForNextFrame(Lnet/minecraft/client/gui/Font;Ljava/util/List;Ljava/util/Optional;Lnet/minecraft/world/item/ItemStack;II)V"), method = "renderTooltip(Lnet/minecraft/client/gui/GuiGraphics;II)V") 26 | private void lockTooltipPosition(GuiGraphics graphics, Font font, List text, 27 | Optional data, ItemStack stack, int x, int y) { 28 | var self = (ContainerScreenLockTooltip) this; 29 | self.shulkerboxtooltip$lockTooltipPosition(graphics, font, text, data, stack, x, y, 30 | stack.get(DataComponents.TOOLTIP_STYLE)); 31 | } 32 | 33 | @Override 34 | public void shulkerboxtooltip$renderTooltip(@Nonnull GuiGraphics graphics, Font font, List text, 35 | Optional image, ItemStack stack, int x, int y, Identifier backgroundTexture) { 36 | GuiGraphicsExtensions.renderTooltipImmediate(graphics, 37 | () -> graphics.setTooltipForNextFrame(font, text, image, stack, x, y)); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /fabric/src/main/java/com/misterpemodder/shulkerboxtooltip/mixin/client/fabric/ItemStackMixin.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.mixin.client.fabric; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.ShulkerBoxTooltipClient; 4 | import com.misterpemodder.shulkerboxtooltip.api.PreviewContext; 5 | import com.misterpemodder.shulkerboxtooltip.api.ShulkerBoxTooltipApi; 6 | import com.misterpemodder.shulkerboxtooltip.impl.tooltip.PreviewTooltipComponent; 7 | import net.minecraft.network.chat.Component; 8 | import net.minecraft.world.entity.player.Player; 9 | import net.minecraft.world.inventory.tooltip.TooltipComponent; 10 | import net.minecraft.world.item.Item; 11 | import net.minecraft.world.item.ItemStack; 12 | import net.minecraft.world.item.TooltipFlag; 13 | import org.spongepowered.asm.mixin.Mixin; 14 | import org.spongepowered.asm.mixin.injection.At; 15 | import org.spongepowered.asm.mixin.injection.Inject; 16 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 17 | 18 | import java.util.List; 19 | import java.util.Optional; 20 | 21 | @Mixin(ItemStack.class) 22 | public class ItemStackMixin { 23 | @Inject(at = @At("HEAD"), method = "getTooltipImage()Ljava/util/Optional;", cancellable = true) 24 | private void onGetTooltipData(CallbackInfoReturnable> cir) { 25 | PreviewContext context = PreviewContext.builder((ItemStack) (Object) this).withOwner( 26 | ShulkerBoxTooltipClient.client == null ? null : ShulkerBoxTooltipClient.client.player).build(); 27 | 28 | //noinspection UnreachableCode 29 | if (ShulkerBoxTooltipApi.isPreviewAvailable(context)) 30 | cir.setReturnValue(Optional.of( 31 | new PreviewTooltipComponent(ShulkerBoxTooltipApi.getPreviewProviderForStackWithOverrides(context.stack()), 32 | context))); 33 | } 34 | 35 | @Inject(at = @At("RETURN"), method = 36 | "getTooltipLines(Lnet/minecraft/world/item/Item$TooltipContext;Lnet/minecraft/world/entity/player/Player;" 37 | + "Lnet/minecraft/world/item/TooltipFlag;)Ljava/util/List;") 38 | private void onGetTooltip(Item.TooltipContext context, Player player, TooltipFlag type, 39 | CallbackInfoReturnable> cir) { 40 | var tooltip = cir.getReturnValue(); 41 | ShulkerBoxTooltipClient.modifyStackTooltip((ItemStack) (Object) this, tooltip::addAll); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /neoforge/src/main/java/com/misterpemodder/shulkerboxtooltip/mixin/client/neoforge/AbstractContainerScreenMixin.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.mixin.client.neoforge; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.impl.hook.ContainerScreenDrawTooltip; 4 | import com.misterpemodder.shulkerboxtooltip.impl.hook.ContainerScreenLockTooltip; 5 | import com.misterpemodder.shulkerboxtooltip.impl.hook.GuiGraphicsExtensions; 6 | import net.minecraft.client.gui.Font; 7 | import net.minecraft.client.gui.GuiGraphics; 8 | import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; 9 | import net.minecraft.network.chat.Component; 10 | import net.minecraft.resources.Identifier; 11 | import net.minecraft.world.inventory.tooltip.TooltipComponent; 12 | import net.minecraft.world.item.ItemStack; 13 | import org.spongepowered.asm.mixin.Mixin; 14 | import org.spongepowered.asm.mixin.injection.At; 15 | import org.spongepowered.asm.mixin.injection.Redirect; 16 | 17 | import javax.annotation.Nonnull; 18 | import java.util.List; 19 | import java.util.Optional; 20 | 21 | @Mixin(AbstractContainerScreen.class) 22 | @SuppressWarnings("OptionalUsedAsFieldOrParameterType") 23 | public class AbstractContainerScreenMixin implements ContainerScreenDrawTooltip { 24 | @Redirect(at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/GuiGraphics;setTooltipForNextFrame(Lnet/minecraft/client/gui/Font;Ljava/util/List;Ljava/util/Optional;Lnet/minecraft/world/item/ItemStack;IILnet/minecraft/resources/Identifier;)V"), method = "renderTooltip(Lnet/minecraft/client/gui/GuiGraphics;II)V") 25 | private void lockTooltipPosition(GuiGraphics graphics, Font font, List text, 26 | Optional data, ItemStack stack, int x, int y, Identifier backgroundTexture) { 27 | var self = (ContainerScreenLockTooltip) this; 28 | self.shulkerboxtooltip$lockTooltipPosition(graphics, font, text, data, stack, x, y, backgroundTexture); 29 | } 30 | 31 | @Override 32 | public void shulkerboxtooltip$renderTooltip(@Nonnull GuiGraphics graphics, Font font, List text, 33 | Optional image, ItemStack stack, int x, int y, Identifier backgroundTexture) { 34 | GuiGraphicsExtensions.renderTooltipImmediate(graphics, 35 | () -> graphics.setTooltipForNextFrame(font, text, image, stack, x, y, backgroundTexture)); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /fabric/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/network/fabric/FabricChannel.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.network.fabric; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.impl.network.Payload; 4 | import com.misterpemodder.shulkerboxtooltip.impl.network.channel.Channel; 5 | import com.misterpemodder.shulkerboxtooltip.impl.network.context.MessageContext; 6 | import com.misterpemodder.shulkerboxtooltip.impl.network.message.MessageType; 7 | import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry; 8 | import net.minecraft.network.RegistryFriendlyByteBuf; 9 | import net.minecraft.network.codec.StreamCodec; 10 | import net.minecraft.network.protocol.common.custom.CustomPacketPayload; 11 | import net.minecraft.resources.Identifier; 12 | 13 | abstract class FabricChannel implements Channel { 14 | protected final CustomPacketPayload.Type> id; 15 | protected final MessageType type; 16 | protected final StreamCodec> codec; 17 | private boolean payloadTypeRegistered = false; 18 | 19 | 20 | protected FabricChannel(Identifier id, MessageType type) { 21 | this.id = new CustomPacketPayload.Type<>(id); 22 | this.type = type; 23 | this.codec = StreamCodec.of(this::encodePayload, this::decodePayload); 24 | } 25 | 26 | @Override 27 | public Identifier getId() { 28 | return this.id.id(); 29 | } 30 | 31 | @Override 32 | public MessageType getMessageType() { 33 | return this.type; 34 | } 35 | 36 | @Override 37 | public void registerPayloadType() { 38 | if (this.payloadTypeRegistered) { 39 | return; 40 | } 41 | PayloadTypeRegistry.playC2S().register(this.id, this.codec); 42 | PayloadTypeRegistry.playS2C().register(this.id, this.codec); 43 | this.payloadTypeRegistered = true; 44 | } 45 | 46 | @Override 47 | public void onRegister(MessageContext context) { 48 | this.type.onRegister(context); 49 | } 50 | 51 | @Override 52 | public void onUnregister(MessageContext context) { 53 | this.type.onUnregister(context); 54 | } 55 | 56 | private void encodePayload(RegistryFriendlyByteBuf buf, Payload message) { 57 | this.type.encode(message.value(), buf); 58 | } 59 | 60 | private Payload decodePayload(RegistryFriendlyByteBuf buf) { 61 | return new Payload<>(this.id, this.type.decode(buf)); 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /fabric/src/main/java/com/misterpemodder/shulkerboxtooltip/mixin/client/fabric/AbstractContainerScreenMixin.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.mixin.client.fabric; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.impl.hook.ContainerScreenDrawTooltip; 4 | import com.misterpemodder.shulkerboxtooltip.impl.hook.ContainerScreenLockTooltip; 5 | import net.minecraft.client.gui.Font; 6 | import net.minecraft.client.gui.GuiGraphics; 7 | import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; 8 | import net.minecraft.network.chat.Component; 9 | import net.minecraft.resources.Identifier; 10 | import net.minecraft.world.inventory.Slot; 11 | import net.minecraft.world.inventory.tooltip.TooltipComponent; 12 | import net.minecraft.world.item.ItemStack; 13 | import org.spongepowered.asm.mixin.Mixin; 14 | import org.spongepowered.asm.mixin.Shadow; 15 | import org.spongepowered.asm.mixin.injection.At; 16 | import org.spongepowered.asm.mixin.injection.Redirect; 17 | 18 | import javax.annotation.Nonnull; 19 | import javax.annotation.Nullable; 20 | import java.util.List; 21 | import java.util.Optional; 22 | 23 | @Mixin(AbstractContainerScreen.class) 24 | @SuppressWarnings("OptionalUsedAsFieldOrParameterType") 25 | public class AbstractContainerScreenMixin implements ContainerScreenDrawTooltip { 26 | @Shadow 27 | @Nullable 28 | protected Slot hoveredSlot; 29 | 30 | @Redirect(at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/GuiGraphics;setTooltipForNextFrame(Lnet/minecraft/client/gui/Font;Ljava/util/List;Ljava/util/Optional;IILnet/minecraft/resources/Identifier;)V"), method = "renderTooltip(Lnet/minecraft/client/gui/GuiGraphics;II)V") 31 | private void lockTooltipPosition(GuiGraphics graphics, Font font, List text, 32 | Optional data, int x, int y, Identifier backgroundTexture) { 33 | ItemStack stack = this.hoveredSlot == null ? null : this.hoveredSlot.getItem(); 34 | var self = (ContainerScreenLockTooltip) this; 35 | self.shulkerboxtooltip$lockTooltipPosition(graphics, font, text, data, stack, x, y, backgroundTexture); 36 | } 37 | 38 | @Override 39 | public void shulkerboxtooltip$renderTooltip(@Nonnull GuiGraphics graphics, Font font, List text, 40 | Optional image, ItemStack stack, int x, int y, Identifier backgroundTexture) { 41 | graphics.setTooltipForNextFrame(font, text, image, x, y, backgroundTexture); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /forge/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/network/forge/ForgeC2SChannel.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.network.forge; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.impl.network.Payload; 4 | import com.misterpemodder.shulkerboxtooltip.impl.network.channel.C2SChannel; 5 | import com.misterpemodder.shulkerboxtooltip.impl.network.context.C2SMessageContext; 6 | import com.misterpemodder.shulkerboxtooltip.impl.network.message.MessageType; 7 | import net.minecraft.client.Minecraft; 8 | import net.minecraft.client.multiplayer.ClientPacketListener; 9 | import net.minecraft.resources.Identifier; 10 | import net.minecraft.server.level.ServerPlayer; 11 | import net.minecraft.server.network.ServerGamePacketListenerImpl; 12 | import net.minecraftforge.api.distmarker.Dist; 13 | import net.minecraftforge.api.distmarker.OnlyIn; 14 | import net.minecraftforge.event.network.CustomPayloadEvent; 15 | import net.minecraftforge.network.PacketDistributor; 16 | 17 | public class ForgeC2SChannel extends ForgeChannel implements C2SChannel { 18 | public ForgeC2SChannel(Identifier id, MessageType type) { 19 | super(id, type); 20 | } 21 | 22 | @Override 23 | public void registerFor(ServerPlayer player) { 24 | // Forge does not support dynamic channel registration 25 | } 26 | 27 | @Override 28 | public void unregisterFor(ServerPlayer player) { 29 | // Forge does not support dynamic channel registration 30 | } 31 | 32 | @Override 33 | @OnlyIn(Dist.CLIENT) 34 | public void sendToServer(T message) { 35 | this.innerChannel.send(new Payload<>(this.id, message), PacketDistributor.SERVER.noArg()); 36 | } 37 | 38 | @Override 39 | @OnlyIn(Dist.CLIENT) 40 | public boolean canSendToServer() { 41 | ClientPacketListener listener = Minecraft.getInstance().getConnection(); 42 | return listener != null && this.innerChannel.isRemotePresent(listener.getConnection()); 43 | } 44 | 45 | @Override 46 | @OnlyIn(Dist.CLIENT) 47 | public void onDisconnect() { 48 | } 49 | 50 | @Override 51 | protected void onReceive(Payload payload, CustomPayloadEvent.Context context) { 52 | if (context.isServerSide()) { 53 | var listener = (ServerGamePacketListenerImpl) context.getConnection().getPacketListener(); 54 | this.type.onReceive(payload.value(), new C2SMessageContext<>(listener.getPlayer(), this)); 55 | } 56 | context.setPacketHandled(true); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/network/message/C2SMessages.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.network.message; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.ShulkerBoxTooltip; 4 | import com.misterpemodder.shulkerboxtooltip.impl.network.ClientNetworking; 5 | import com.misterpemodder.shulkerboxtooltip.impl.network.ProtocolVersion; 6 | import com.misterpemodder.shulkerboxtooltip.impl.network.channel.C2SChannel; 7 | import com.misterpemodder.shulkerboxtooltip.impl.util.ShulkerBoxTooltipUtil; 8 | import net.fabricmc.api.EnvType; 9 | import net.fabricmc.api.Environment; 10 | import net.minecraft.server.level.ServerPlayer; 11 | 12 | /** 13 | * The client to server messages of ShulkerBoxTooltip. 14 | */ 15 | public final class C2SMessages { 16 | public static final C2SChannel HANDSHAKE_START = ClientNetworking.createC2SChannel( 17 | ShulkerBoxTooltipUtil.id("c2s_handshake"), new C2SHandshakeStart.Type()); 18 | public static final C2SChannel ENDER_CHEST_UPDATE_REQUEST = 19 | ClientNetworking.createC2SChannel(ShulkerBoxTooltipUtil.id("ec_update_req"), 20 | new C2SEnderChestUpdateRequest.Type()); 21 | 22 | private C2SMessages() { 23 | } 24 | 25 | public static void registerPayloadTypes() { 26 | HANDSHAKE_START.registerPayloadType(); 27 | ENDER_CHEST_UPDATE_REQUEST.registerPayloadType(); 28 | } 29 | 30 | @Environment(EnvType.CLIENT) 31 | public static void onDisconnectFromServer() { 32 | HANDSHAKE_START.onDisconnect(); 33 | ENDER_CHEST_UPDATE_REQUEST.onDisconnect(); 34 | } 35 | 36 | /** 37 | * Registers all the client to server messages for the given player. 38 | * 39 | * @param player The player. 40 | */ 41 | public static void registerAllFor(ServerPlayer player) { 42 | HANDSHAKE_START.registerFor(player); 43 | ENDER_CHEST_UPDATE_REQUEST.registerFor(player); 44 | } 45 | 46 | /** 47 | * Sends a handshake packet to the server, if possible. 48 | */ 49 | public static void attemptHandshake() { 50 | if (ShulkerBoxTooltip.config.preview.serverIntegration && ClientNetworking.serverProtocolVersion == null 51 | && C2SMessages.HANDSHAKE_START.canSendToServer()) { 52 | ShulkerBoxTooltip.LOGGER.info("Server integration enabled, attempting handshake..."); 53 | C2SMessages.HANDSHAKE_START.sendToServer(new C2SHandshakeStart(ProtocolVersion.CURRENT)); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /neoforge/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/network/neoforge/ServerNetworkingImpl.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.network.neoforge; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.ShulkerBoxTooltip; 4 | import com.misterpemodder.shulkerboxtooltip.impl.network.ServerNetworking; 5 | import com.misterpemodder.shulkerboxtooltip.impl.network.channel.S2CChannel; 6 | import com.misterpemodder.shulkerboxtooltip.impl.network.message.C2SMessages; 7 | import com.misterpemodder.shulkerboxtooltip.impl.network.message.MessageType; 8 | import com.misterpemodder.shulkerboxtooltip.impl.network.message.S2CMessages; 9 | import net.minecraft.resources.Identifier; 10 | import net.minecraft.server.level.ServerPlayer; 11 | import net.neoforged.bus.api.SubscribeEvent; 12 | import net.neoforged.neoforge.common.NeoForge; 13 | import net.neoforged.neoforge.event.entity.player.PlayerEvent; 14 | 15 | import java.util.HashMap; 16 | import java.util.Map; 17 | 18 | public final class ServerNetworkingImpl { 19 | public static final Map> S2C_CHANNELS = new HashMap<>(); 20 | 21 | private ServerNetworkingImpl() { 22 | } 23 | 24 | @SubscribeEvent 25 | public static void onPlayerJoin(PlayerEvent.PlayerLoggedInEvent event) { 26 | C2SMessages.registerAllFor((ServerPlayer) event.getEntity()); 27 | } 28 | 29 | @SubscribeEvent 30 | public static void onPlayerDisconnect(PlayerEvent.PlayerLoggedOutEvent event) { 31 | ServerNetworking.removeClient((ServerPlayer) event.getEntity()); 32 | } 33 | 34 | @SubscribeEvent 35 | public static void onPlayerChangeDimension(PlayerEvent.PlayerChangedDimensionEvent event) { 36 | ServerNetworking.onPlayerChangeWorld((ServerPlayer) event.getEntity()); 37 | } 38 | 39 | /** 40 | * Implementation of {@link ServerNetworking#init()}. 41 | */ 42 | public static void init() { 43 | if (!ShulkerBoxTooltip.config.server.clientIntegration) 44 | return; 45 | S2CMessages.registerPayloadTypes(); 46 | C2SMessages.registerPayloadTypes(); 47 | NeoForge.EVENT_BUS.register(ServerNetworkingImpl.class); 48 | } 49 | 50 | /** 51 | * Implements {@link ServerNetworking#createS2CChannel(Identifier, MessageType)}. 52 | */ 53 | public static S2CChannel createS2CChannel(Identifier id, MessageType type) { 54 | var channel = new NeoForgeS2CChannel<>(id, type); 55 | S2C_CHANNELS.put(id, channel); 56 | return channel; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /neoforge/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/network/neoforge/ClientNetworkingImpl.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.network.neoforge; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.ShulkerBoxTooltip; 4 | import com.misterpemodder.shulkerboxtooltip.impl.network.ClientNetworking; 5 | import com.misterpemodder.shulkerboxtooltip.impl.network.channel.C2SChannel; 6 | import com.misterpemodder.shulkerboxtooltip.impl.network.message.C2SMessages; 7 | import com.misterpemodder.shulkerboxtooltip.impl.network.message.MessageType; 8 | import com.misterpemodder.shulkerboxtooltip.impl.network.message.S2CMessages; 9 | import net.minecraft.client.Minecraft; 10 | import net.minecraft.resources.Identifier; 11 | import net.neoforged.api.distmarker.Dist; 12 | import net.neoforged.api.distmarker.OnlyIn; 13 | import net.neoforged.bus.api.SubscribeEvent; 14 | import net.neoforged.neoforge.client.event.ClientPlayerNetworkEvent; 15 | import net.neoforged.neoforge.common.NeoForge; 16 | 17 | import java.util.HashMap; 18 | import java.util.Map; 19 | 20 | public final class ClientNetworkingImpl { 21 | public static final Map> C2S_CHANNELS = new HashMap<>(); 22 | 23 | private ClientNetworkingImpl() { 24 | } 25 | 26 | @SubscribeEvent 27 | @OnlyIn(Dist.CLIENT) 28 | public static void onJoinServer(ClientPlayerNetworkEvent.LoggingIn event) { 29 | if (ShulkerBoxTooltip.config.preview.serverIntegration) 30 | S2CMessages.registerAll(); 31 | ClientNetworking.onJoinServer(Minecraft.getInstance()); 32 | } 33 | 34 | @SubscribeEvent 35 | @OnlyIn(Dist.CLIENT) 36 | public static void onLeaveServer(ClientPlayerNetworkEvent.LoggingOut event) { 37 | if (ShulkerBoxTooltip.config.preview.serverIntegration) 38 | C2SMessages.onDisconnectFromServer(); 39 | } 40 | 41 | /** 42 | * Implements {@link ClientNetworking#init()}. 43 | */ 44 | @OnlyIn(Dist.CLIENT) 45 | public static void init() { 46 | S2CMessages.registerPayloadTypes(); 47 | C2SMessages.registerPayloadTypes(); 48 | NeoForge.EVENT_BUS.register(ClientNetworkingImpl.class); 49 | } 50 | 51 | /** 52 | * Implements {@link ClientNetworking#createC2SChannel(Identifier, MessageType)}. 53 | */ 54 | public static C2SChannel createC2SChannel(Identifier id, MessageType type) { 55 | var channel = new NeoForgeC2SChannel<>(id, type); 56 | C2S_CHANNELS.put(id, channel); 57 | return channel; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /forge/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/network/forge/ServerNetworkingImpl.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.network.forge; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.ShulkerBoxTooltip; 4 | import com.misterpemodder.shulkerboxtooltip.impl.network.ServerNetworking; 5 | import com.misterpemodder.shulkerboxtooltip.impl.network.channel.S2CChannel; 6 | import com.misterpemodder.shulkerboxtooltip.impl.network.message.C2SMessages; 7 | import com.misterpemodder.shulkerboxtooltip.impl.network.message.MessageType; 8 | import com.misterpemodder.shulkerboxtooltip.impl.network.message.S2CMessages; 9 | import net.minecraft.resources.Identifier; 10 | import net.minecraft.server.level.ServerPlayer; 11 | import net.minecraftforge.common.MinecraftForge; 12 | import net.minecraftforge.event.entity.player.PlayerEvent; 13 | import net.minecraftforge.eventbus.api.listener.SubscribeEvent; 14 | 15 | import java.util.HashMap; 16 | import java.util.Map; 17 | 18 | public final class ServerNetworkingImpl { 19 | public static final Map> S2C_CHANNELS = new HashMap<>(); 20 | 21 | private ServerNetworkingImpl() { 22 | } 23 | 24 | @SubscribeEvent 25 | public static void onPlayerJoin(PlayerEvent.PlayerLoggedInEvent event) { 26 | C2SMessages.registerAllFor((ServerPlayer) event.getEntity()); 27 | } 28 | 29 | @SubscribeEvent 30 | public static void onPlayerDisconnect(PlayerEvent.PlayerLoggedOutEvent event) { 31 | ServerNetworking.removeClient((ServerPlayer) event.getEntity()); 32 | } 33 | 34 | @SubscribeEvent 35 | public static void onPlayerChangeDimension(PlayerEvent.PlayerChangedDimensionEvent event) { 36 | ServerNetworking.onPlayerChangeWorld((ServerPlayer) event.getEntity()); 37 | } 38 | 39 | /** 40 | * Implementation of {@link ServerNetworking#init()}. 41 | */ 42 | public static void init() { 43 | if (!ShulkerBoxTooltip.config.server.clientIntegration) 44 | return; 45 | S2CMessages.registerPayloadTypes(); 46 | C2SMessages.registerPayloadTypes(); 47 | MinecraftForge.EVENT_BUS.register(ServerNetworkingImpl.class); 48 | } 49 | 50 | /** 51 | * Implements {@link ServerNetworking#createS2CChannel(Identifier, MessageType)}. 52 | */ 53 | public static S2CChannel createS2CChannel(Identifier id, MessageType type) { 54 | var channel = new ForgeS2CChannel<>(id, type); 55 | S2C_CHANNELS.put(id, channel); 56 | return channel; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /forge/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/network/forge/ClientNetworkingImpl.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.network.forge; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.ShulkerBoxTooltip; 4 | import com.misterpemodder.shulkerboxtooltip.impl.network.ClientNetworking; 5 | import com.misterpemodder.shulkerboxtooltip.impl.network.channel.C2SChannel; 6 | import com.misterpemodder.shulkerboxtooltip.impl.network.message.C2SMessages; 7 | import com.misterpemodder.shulkerboxtooltip.impl.network.message.MessageType; 8 | import com.misterpemodder.shulkerboxtooltip.impl.network.message.S2CMessages; 9 | import net.minecraft.client.Minecraft; 10 | import net.minecraft.resources.Identifier; 11 | import net.minecraftforge.api.distmarker.Dist; 12 | import net.minecraftforge.api.distmarker.OnlyIn; 13 | import net.minecraftforge.client.event.ClientPlayerNetworkEvent; 14 | import net.minecraftforge.common.MinecraftForge; 15 | import net.minecraftforge.eventbus.api.listener.SubscribeEvent; 16 | 17 | import java.util.HashMap; 18 | import java.util.Map; 19 | 20 | public final class ClientNetworkingImpl { 21 | public static final Map> C2S_CHANNELS = new HashMap<>(); 22 | 23 | private ClientNetworkingImpl() { 24 | } 25 | 26 | @SubscribeEvent 27 | @OnlyIn(Dist.CLIENT) 28 | public static void onJoinServer(ClientPlayerNetworkEvent.LoggingIn event) { 29 | if (ShulkerBoxTooltip.config.preview.serverIntegration) 30 | S2CMessages.registerAll(); 31 | ClientNetworking.onJoinServer(Minecraft.getInstance()); 32 | } 33 | 34 | @SubscribeEvent 35 | @OnlyIn(Dist.CLIENT) 36 | public static void onLeaveServer(ClientPlayerNetworkEvent.LoggingOut event) { 37 | if (ShulkerBoxTooltip.config.preview.serverIntegration) 38 | C2SMessages.onDisconnectFromServer(); 39 | } 40 | 41 | /** 42 | * Implements {@link ClientNetworking#init()}. 43 | */ 44 | @OnlyIn(Dist.CLIENT) 45 | public static void init() { 46 | S2CMessages.registerPayloadTypes(); 47 | C2SMessages.registerPayloadTypes(); 48 | MinecraftForge.EVENT_BUS.register(ClientNetworkingImpl.class); 49 | } 50 | 51 | /** 52 | * Implements {@link ClientNetworking#createC2SChannel(Identifier, MessageType)}. 53 | */ 54 | public static C2SChannel createC2SChannel(Identifier id, MessageType type) { 55 | var channel = new ForgeC2SChannel<>(id, type); 56 | C2S_CHANNELS.put(id, channel); 57 | return channel; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /neoforge/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/network/neoforge/NeoForgeC2SChannel.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.network.neoforge; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.impl.network.Payload; 4 | import com.misterpemodder.shulkerboxtooltip.impl.network.channel.C2SChannel; 5 | import com.misterpemodder.shulkerboxtooltip.impl.network.context.C2SMessageContext; 6 | import com.misterpemodder.shulkerboxtooltip.impl.network.message.MessageType; 7 | import net.minecraft.client.Minecraft; 8 | import net.minecraft.resources.Identifier; 9 | import net.minecraft.server.level.ServerPlayer; 10 | import net.neoforged.api.distmarker.Dist; 11 | import net.neoforged.api.distmarker.OnlyIn; 12 | import net.neoforged.neoforge.client.network.ClientPacketDistributor; 13 | import net.neoforged.neoforge.common.extensions.ICommonPacketListener; 14 | import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent; 15 | import net.neoforged.neoforge.network.handling.IPayloadContext; 16 | 17 | public class NeoForgeC2SChannel extends NeoForgeChannel implements C2SChannel { 18 | public NeoForgeC2SChannel(Identifier id, MessageType type) { 19 | super(id, type); 20 | } 21 | 22 | @Override 23 | public void registerFor(ServerPlayer player) { 24 | // NeoForge does not support dynamic channel registration 25 | } 26 | 27 | @Override 28 | public void unregisterFor(ServerPlayer player) { 29 | // NeoForge does not support dynamic channel registration 30 | } 31 | 32 | @Override 33 | @OnlyIn(Dist.CLIENT) 34 | public void sendToServer(T message) { 35 | ClientPacketDistributor.sendToServer(new Payload<>(this.id, message)); 36 | } 37 | 38 | @Override 39 | @OnlyIn(Dist.CLIENT) 40 | public boolean canSendToServer() { 41 | ICommonPacketListener listener = Minecraft.getInstance().getConnection(); 42 | return listener != null && listener.hasChannel(this.getId()); 43 | } 44 | 45 | @Override 46 | @OnlyIn(Dist.CLIENT) 47 | public void onDisconnect() { 48 | } 49 | 50 | @Override 51 | protected void onReceive(Payload payload, IPayloadContext context) { 52 | if (context.flow().isServerbound()) { 53 | this.type.onReceive(payload.value(), new C2SMessageContext<>((ServerPlayer) context.player(), this)); 54 | } 55 | } 56 | 57 | @Override 58 | protected void registerPayloadTypeInner(RegisterPayloadHandlersEvent event) { 59 | event.registrar("1").optional().commonToServer(this.id, this.codec, this::onReceive); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/network/EnderChestInventoryListener.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.network; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.api.ShulkerBoxTooltipApi; 4 | import com.misterpemodder.shulkerboxtooltip.impl.network.message.S2CEnderChestUpdate; 5 | import com.misterpemodder.shulkerboxtooltip.impl.network.message.S2CMessages; 6 | import net.minecraft.server.level.ServerPlayer; 7 | import net.minecraft.world.Container; 8 | import net.minecraft.world.ContainerListener; 9 | import net.minecraft.world.inventory.PlayerEnderChestContainer; 10 | 11 | public final class EnderChestInventoryListener implements ContainerListener { 12 | 13 | private final ServerPlayer player; 14 | 15 | private EnderChestInventoryListener(ServerPlayer player) { 16 | this.player = player; 17 | } 18 | 19 | public void containerChanged(Container inv) { 20 | if (!ShulkerBoxTooltipApi.hasModAvailable(this.player)) { 21 | detachFrom(this.player); 22 | return; 23 | } 24 | S2CMessages.ENDER_CHEST_UPDATE.sendTo(this.player, 25 | S2CEnderChestUpdate.create((PlayerEnderChestContainer) inv, this.player.registryAccess())); 26 | } 27 | 28 | /** 29 | * Attempts to attach an ender chest inventory listener to the given player 30 | * if they don't already have one. 31 | * 32 | * @param player The player 33 | */ 34 | public static void attachTo(ServerPlayer player) { 35 | var inventory = player == null ? null : player.getEnderChestInventory(); 36 | var listeners = inventory == null ? null : inventory.listeners; 37 | 38 | // Search for existing listener 39 | if (listeners != null) { 40 | for (ContainerListener listener : listeners) 41 | if (listener instanceof EnderChestInventoryListener) 42 | return; 43 | } 44 | if (inventory != null) 45 | inventory.addListener(new EnderChestInventoryListener(player)); 46 | } 47 | 48 | /** 49 | * Attempts to detach an ender chest inventory listener to the given player if they have one. 50 | * 51 | * @param player The player 52 | */ 53 | public static void detachFrom(ServerPlayer player) { 54 | var inventory = player == null ? null : player.getEnderChestInventory(); 55 | var listeners = inventory == null ? null : inventory.listeners; 56 | 57 | if (listeners == null) 58 | return; 59 | 60 | // Search for existing listener and remove it if found 61 | for (ContainerListener listener : listeners) { 62 | if (listener instanceof EnderChestInventoryListener) { 63 | inventory.removeListener(listener); 64 | return; 65 | } 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /neoforge/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/network/neoforge/NeoForgeChannel.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.network.neoforge; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.impl.network.Payload; 4 | import com.misterpemodder.shulkerboxtooltip.impl.network.channel.Channel; 5 | import com.misterpemodder.shulkerboxtooltip.impl.network.context.MessageContext; 6 | import com.misterpemodder.shulkerboxtooltip.impl.network.message.MessageType; 7 | import net.minecraft.network.FriendlyByteBuf; 8 | import net.minecraft.network.codec.StreamCodec; 9 | import net.minecraft.network.protocol.common.custom.CustomPacketPayload; 10 | import net.minecraft.resources.Identifier; 11 | import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent; 12 | import net.neoforged.neoforge.network.handling.IPayloadContext; 13 | 14 | abstract class NeoForgeChannel implements Channel { 15 | protected final CustomPacketPayload.Type> id; 16 | protected final MessageType type; 17 | protected final StreamCodec> codec; 18 | private boolean payloadTypeRegistered = false; 19 | 20 | 21 | protected NeoForgeChannel(Identifier id, MessageType type) { 22 | this.id = new CustomPacketPayload.Type<>(id); 23 | this.type = type; 24 | this.codec = StreamCodec.of(this::encodePayload, this::decodePayload); 25 | } 26 | 27 | @Override 28 | public Identifier getId() { 29 | return this.id.id(); 30 | } 31 | 32 | @Override 33 | public MessageType getMessageType() { 34 | return this.type; 35 | } 36 | 37 | @Override 38 | public void registerPayloadType() { 39 | // payload registration is handled by the RegisterPayloadHandlersEvent 40 | } 41 | 42 | public void registerPayloadTypeDeferred(RegisterPayloadHandlersEvent event) { 43 | if (this.payloadTypeRegistered) { 44 | return; 45 | } 46 | this.registerPayloadTypeInner(event); 47 | this.payloadTypeRegistered = true; 48 | } 49 | 50 | @Override 51 | public void onRegister(MessageContext context) { 52 | this.type.onRegister(context); 53 | } 54 | 55 | @Override 56 | public void onUnregister(MessageContext context) { 57 | this.type.onUnregister(context); 58 | } 59 | 60 | private void encodePayload(FriendlyByteBuf buf, Payload message) { 61 | this.type.encode(message.value(), buf); 62 | } 63 | 64 | private Payload decodePayload(FriendlyByteBuf buf) { 65 | return new Payload<>(this.id, this.type.decode(buf)); 66 | } 67 | 68 | protected abstract void onReceive(Payload payload, IPayloadContext context); 69 | 70 | protected abstract void registerPayloadTypeInner(RegisterPayloadHandlersEvent event); 71 | } 72 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/color/ColorKeyImpl.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.color; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.api.color.ColorKey; 4 | import com.misterpemodder.shulkerboxtooltip.impl.util.ShulkerBoxTooltipUtil; 5 | import com.mojang.datafixers.util.Pair; 6 | import com.mojang.serialization.Codec; 7 | import com.mojang.serialization.DataResult; 8 | import com.mojang.serialization.codecs.RecordCodecBuilder; 9 | import net.fabricmc.api.EnvType; 10 | import net.fabricmc.api.Environment; 11 | import net.minecraft.resources.Identifier; 12 | import org.jetbrains.annotations.Nullable; 13 | 14 | @Environment(EnvType.CLIENT) 15 | public record ColorKeyImpl(float[] rgbComponents, float[] defaultRgbComponents) implements ColorKey { 16 | private static final Codec> CATEGORY_AND_ID_CODEC = RecordCodecBuilder.create( 17 | instance -> instance.group(Identifier.CODEC.fieldOf("category").forGetter(Pair::getFirst), 18 | Codec.STRING.fieldOf("id").forGetter(Pair::getSecond)).apply(instance, Pair::of)); 19 | 20 | private static final Codec FULL_CODEC = CATEGORY_AND_ID_CODEC.flatXmap( 21 | (Pair categoryAndId) -> { 22 | Identifier category = categoryAndId.getFirst(); 23 | String id = categoryAndId.getSecond(); 24 | @Nullable ColorKey key = ColorRegistryImpl.INSTANCE.category(category).key(id); 25 | 26 | if (key == null) { 27 | return DataResult.error(() -> "Unknown color key " + id + " in category " + category); 28 | } else { 29 | return DataResult.success(key); 30 | } 31 | }, (ColorKey key) -> DataResult.error(() -> "Cannot encode color key " + key + " as a string")); 32 | 33 | private static final Codec INT_CODEC = Codec.INT.xmap(ColorKey::ofRgb, ColorKey::rgb); 34 | public static final Codec CODEC = Codec.withAlternative(FULL_CODEC, INT_CODEC); 35 | 36 | @Override 37 | public int rgb() { 38 | return ShulkerBoxTooltipUtil.componentsToRgb(this.rgbComponents()); 39 | } 40 | 41 | @Override 42 | public int defaultRgb() { 43 | return ShulkerBoxTooltipUtil.componentsToRgb(this.defaultRgbComponents()); 44 | } 45 | 46 | @Override 47 | public void setRgb(int rgb) { 48 | this.setRgb(ShulkerBoxTooltipUtil.rgbToComponents(rgb)); 49 | } 50 | 51 | @Override 52 | public void setRgb(float[] rgb) { 53 | this.rgbComponents[0] = rgb[0]; 54 | this.rgbComponents[1] = rgb[1]; 55 | this.rgbComponents[2] = rgb[2]; 56 | } 57 | 58 | @Override 59 | public String toString() { 60 | return String.format("ColorKey(rgb=#%x, defaultRgb=#%x)", this.rgb(), this.defaultRgb()); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/util/ShulkerBoxTooltipUtil.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.util; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.ShulkerBoxTooltip; 4 | import net.minecraft.resources.Identifier; 5 | 6 | public final class ShulkerBoxTooltipUtil { 7 | private ShulkerBoxTooltipUtil() { 8 | } 9 | 10 | public static Identifier id(String id) { 11 | return Identifier.fromNamespaceAndPath(ShulkerBoxTooltip.MOD_ID, id); 12 | } 13 | 14 | public static String abbreviateInteger(int count) { 15 | if (count == Integer.MIN_VALUE) 16 | return "-2G"; 17 | if (count > -1000 && count < 1000) 18 | return Integer.toString(count); 19 | 20 | var str = new StringBuilder(); 21 | 22 | if (count < 0) { 23 | str.append('-'); 24 | count = -count; 25 | } 26 | char unit; 27 | int integral; 28 | int decimal = 0; 29 | 30 | switch ((int) Math.log10(count)) { 31 | case 3 -> { 32 | integral = count / 1_000; 33 | decimal = (count % 1_000) / 100; 34 | unit = 'k'; 35 | } 36 | case 4, 5 -> { 37 | integral = count / 1_000; 38 | unit = 'k'; 39 | } 40 | case 6 -> { 41 | integral = count / 1_000_000; 42 | decimal = (count % 1_000_000) / 100_000; 43 | unit = 'M'; 44 | } 45 | case 7, 8 -> { 46 | integral = count / 1_000_000; 47 | unit = 'M'; 48 | } 49 | default -> { 50 | integral = count / 1_000_000_000; 51 | decimal = (count % 1_000_000_000) / 100_000_000; 52 | unit = 'G'; 53 | } 54 | } 55 | 56 | str.append(integral); 57 | if (decimal > 0) 58 | str.append('.').append(decimal); 59 | str.append(unit); 60 | return str.toString(); 61 | } 62 | 63 | public static float[] rgbToComponents(int rgb) { 64 | int r = (rgb >> 16) & 0xFF; 65 | int g = (rgb >> 8) & 0xFF; 66 | int b = rgb & 0xFF; 67 | return new float[] {(float) r / 255F, (float) g / 255F, (float) b / 255F}; 68 | } 69 | 70 | public static int componentsToRgb(float[] components) { 71 | int r = (int) (255F * components[0]); 72 | int g = (int) (255F * components[1]); 73 | int b = (int) (255F * components[2]); 74 | return (r << 16) | (g << 8) | b; 75 | } 76 | 77 | public static String snakeCase(String str) { 78 | var sb = new StringBuilder(); 79 | for (int i = 0; i < str.length(); i++) { 80 | char c = str.charAt(i); 81 | if (Character.isUpperCase(c)) { 82 | if (i > 0) 83 | sb.append('_'); 84 | sb.append(Character.toLowerCase(c)); 85 | } else { 86 | sb.append(c); 87 | } 88 | } 89 | return sb.toString(); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/network/ClientNetworking.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.network; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.ShulkerBoxTooltip; 4 | import com.misterpemodder.shulkerboxtooltip.impl.PluginManager; 5 | import com.misterpemodder.shulkerboxtooltip.impl.config.ConfigurationHandler; 6 | import com.misterpemodder.shulkerboxtooltip.impl.network.channel.C2SChannel; 7 | import com.misterpemodder.shulkerboxtooltip.impl.network.message.C2SMessages; 8 | import com.misterpemodder.shulkerboxtooltip.impl.network.message.MessageType; 9 | import dev.architectury.injectables.annotations.ExpectPlatform; 10 | import net.fabricmc.api.EnvType; 11 | import net.fabricmc.api.Environment; 12 | import net.minecraft.client.Minecraft; 13 | import net.minecraft.resources.Identifier; 14 | 15 | import javax.annotation.Nullable; 16 | 17 | /** 18 | * Client-side network handling. 19 | */ 20 | public class ClientNetworking { 21 | /** 22 | * The server's network protocol version, null when not connected or the server is not compatible. 23 | */ 24 | @Nullable 25 | @Environment(EnvType.CLIENT) 26 | public static ProtocolVersion serverProtocolVersion; 27 | 28 | private ClientNetworking() { 29 | } 30 | 31 | /** 32 | * Corresponds to Fabric's ClientPlayConnectionEvents.JOIN and 33 | * Forge's ClientPlayerNetworkEvent.LoggedInEvent events. 34 | */ 35 | @Environment(EnvType.CLIENT) 36 | public static void onJoinServer(Minecraft client) { 37 | client.execute(() -> { 38 | PluginManager.loadColors(); 39 | PluginManager.loadProviders(); 40 | }); 41 | ShulkerBoxTooltip.configTree.copy(ShulkerBoxTooltip.savedConfig, ShulkerBoxTooltip.config); 42 | 43 | // Re-init some config values before syncing 44 | serverProtocolVersion = null; 45 | if (!Minecraft.getInstance().hasSingleplayerServer()) 46 | ConfigurationHandler.reinitClientSideSyncedValues(ShulkerBoxTooltip.config); 47 | C2SMessages.attemptHandshake(); 48 | } 49 | 50 | /** 51 | * Performs registration of messages and events. 52 | */ 53 | @ExpectPlatform 54 | @Environment(EnvType.CLIENT) 55 | public static void init() { 56 | throw new AssertionError("Missing implementation of ClientNetworking.init()"); 57 | } 58 | 59 | /** 60 | * Creates a client-to-server channel compatible with the current mod platform. 61 | * 62 | * @param id The channel's identifier. 63 | * @param type The channel's message type. 64 | * @param The message type. 65 | * @return The newly-created channel. 66 | */ 67 | @ExpectPlatform 68 | public static C2SChannel createC2SChannel(Identifier id, MessageType type) { 69 | throw new AssertionError("Missing implementation of Networking.createC2SChannel()"); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /forge/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/network/forge/ForgeChannel.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.network.forge; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.impl.network.Payload; 4 | import com.misterpemodder.shulkerboxtooltip.impl.network.channel.Channel; 5 | import com.misterpemodder.shulkerboxtooltip.impl.network.context.MessageContext; 6 | import com.misterpemodder.shulkerboxtooltip.impl.network.message.MessageType; 7 | import net.minecraft.network.FriendlyByteBuf; 8 | import net.minecraft.network.codec.StreamCodec; 9 | import net.minecraft.network.protocol.common.custom.CustomPacketPayload; 10 | import net.minecraft.resources.Identifier; 11 | import net.minecraftforge.event.network.CustomPayloadEvent; 12 | import net.minecraftforge.network.ChannelBuilder; 13 | 14 | abstract class ForgeChannel implements Channel { 15 | protected final CustomPacketPayload.Type> id; 16 | protected final MessageType type; 17 | protected final StreamCodec> codec; 18 | protected net.minecraftforge.network.Channel> innerChannel; 19 | private boolean payloadTypeRegistered = false; 20 | 21 | protected ForgeChannel(Identifier id, MessageType type) { 22 | this.id = new CustomPacketPayload.Type<>(id); 23 | this.type = type; 24 | this.codec = StreamCodec.of(this::encodePayload, this::decodePayload); 25 | } 26 | 27 | @Override 28 | public Identifier getId() { 29 | return this.id.id(); 30 | } 31 | 32 | @Override 33 | public MessageType getMessageType() { 34 | return this.type; 35 | } 36 | 37 | @Override 38 | @SuppressWarnings("unchecked") 39 | public void registerPayloadType() { 40 | if (this.payloadTypeRegistered) { 41 | return; 42 | } 43 | var c = ChannelBuilder.named(this.getId()) // 44 | .optional() // 45 | .payloadChannel() // 46 | .any() // 47 | .bidirectional() // 48 | .add(this.id, this.codec, this::onReceive) // 49 | .build(); 50 | this.innerChannel = (net.minecraftforge.network.Channel>) (net.minecraftforge.network.Channel) c; 51 | this.payloadTypeRegistered = true; 52 | } 53 | 54 | @Override 55 | public void onRegister(MessageContext context) { 56 | this.type.onRegister(context); 57 | } 58 | 59 | @Override 60 | public void onUnregister(MessageContext context) { 61 | this.type.onUnregister(context); 62 | } 63 | 64 | private void encodePayload(FriendlyByteBuf buf, Payload message) { 65 | this.type.encode(message.value(), buf); 66 | } 67 | 68 | private Payload decodePayload(FriendlyByteBuf buf) { 69 | return new Payload<>(this.id, this.type.decode(buf)); 70 | } 71 | 72 | protected abstract void onReceive(Payload payload, CustomPayloadEvent.Context context); 73 | } 74 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/config/gui/entry/EnumValueConfigEntry.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.config.gui.entry; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.impl.config.gui.ConfigCategoryTab; 4 | import com.misterpemodder.shulkerboxtooltip.impl.tree.ValueConfigNode; 5 | import net.fabricmc.api.EnvType; 6 | import net.fabricmc.api.Environment; 7 | import net.minecraft.client.gui.GuiGraphics; 8 | import net.minecraft.client.gui.components.CycleButton; 9 | import net.minecraft.network.chat.Component; 10 | 11 | import java.util.List; 12 | 13 | @Environment(EnvType.CLIENT) 14 | public final class EnumValueConfigEntry> extends ValueConfigEntry { 15 | private final CycleButton valueButton; 16 | 17 | public EnumValueConfigEntry(ConfigCategoryTab tab, ValueConfigNode valueNode) { 18 | super(tab, valueNode); 19 | 20 | this.valueButton = CycleButton.builder(value -> Component.translatable(value.toString()), this.getValue()) 21 | .displayOnlyValue() 22 | .withValues(List.of(this.valueNode.getValueType().getEnumConstants())) 23 | .create(0, 0, 160, 20, this.valueNode.getTitle(), (b, value) -> this.setValue(value)); 24 | this.children.addFirst(this.valueButton); 25 | } 26 | 27 | @Override 28 | public void refresh() { 29 | super.refresh(); 30 | var value = this.getValue(); 31 | if (this.valueButton.getValue() != value) { 32 | this.valueButton.setValue(value); 33 | } 34 | } 35 | 36 | @Override 37 | public void renderContent(GuiGraphics guiGraphics, int mouseX, int mouseY, boolean hovered, float delta) { 38 | int x = this.getContentX(); 39 | int y = this.getContentY(); 40 | this.renderLabel(guiGraphics); 41 | 42 | this.valueButton.setWidth(160 - this.resetButton.getWidth() - 2 - this.undoButton.getWidth() - 2); 43 | if (this.tab.getMinecraft().font.isBidirectional()) { 44 | this.undoButton.setX(x); 45 | this.undoButton.setY(y); 46 | 47 | this.resetButton.setX(x + undoButton.getWidth() + 2); 48 | this.resetButton.setY(y); 49 | 50 | this.valueButton.setX(x + undoButton.getWidth() + 2 + resetButton.getWidth() + 2); 51 | this.valueButton.setY(y); 52 | } else { 53 | this.undoButton.setX(this.getContentRight() - this.undoButton.getWidth()); 54 | this.undoButton.setY(y); 55 | 56 | this.resetButton.setX(this.undoButton.getX() - this.resetButton.getWidth() - 2); 57 | this.resetButton.setY(y); 58 | 59 | this.valueButton.setX(this.resetButton.getX() - this.valueButton.getWidth() - 2); 60 | this.valueButton.setY(y); 61 | } 62 | 63 | this.valueButton.render(guiGraphics, mouseX, mouseY, delta); 64 | this.resetButton.render(guiGraphics, mouseX, mouseY, delta); 65 | this.undoButton.render(guiGraphics, mouseX, mouseY, delta); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/config/gui/entry/BooleanValueConfigEntry.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.config.gui.entry; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.impl.config.gui.ConfigCategoryTab; 4 | import com.misterpemodder.shulkerboxtooltip.impl.tree.ValueConfigNode; 5 | import net.fabricmc.api.EnvType; 6 | import net.fabricmc.api.Environment; 7 | import net.minecraft.ChatFormatting; 8 | import net.minecraft.client.gui.GuiGraphics; 9 | import net.minecraft.client.gui.components.CycleButton; 10 | import net.minecraft.network.chat.CommonComponents; 11 | 12 | import java.util.Objects; 13 | 14 | @Environment(EnvType.CLIENT) 15 | public final class BooleanValueConfigEntry extends ValueConfigEntry { 16 | private final CycleButton valueButton; 17 | 18 | public BooleanValueConfigEntry(ConfigCategoryTab tab, ValueConfigNode valueNode) { 19 | super(tab, valueNode); 20 | 21 | this.valueButton = CycleButton.booleanBuilder(CommonComponents.GUI_YES.copy().withStyle(ChatFormatting.GREEN), 22 | CommonComponents.GUI_NO.copy().withStyle(ChatFormatting.RED), this.getValue()).displayOnlyValue().create(0, 0, 23 | 160, 20, valueNode.getTitle(), (b, value) -> this.setValue(value)); 24 | this.children.addFirst(this.valueButton); 25 | } 26 | 27 | @Override 28 | public void refresh() { 29 | super.refresh(); 30 | var value = this.getValue(); 31 | if (!Objects.equals(this.valueButton.getValue(), value)) { 32 | this.valueButton.setValue(value); 33 | } 34 | } 35 | 36 | @Override 37 | public void renderContent(GuiGraphics guiGraphics, int mouseX, int mouseY, boolean hovered, float delta) { 38 | int x = this.getContentX(); 39 | int y = this.getContentY(); 40 | this.renderLabel(guiGraphics); 41 | 42 | this.valueButton.setWidth(160 - this.resetButton.getWidth() - 2 - this.undoButton.getWidth() - 2); 43 | if (this.tab.getMinecraft().font.isBidirectional()) { 44 | this.undoButton.setX(x); 45 | this.undoButton.setY(y); 46 | 47 | this.resetButton.setX(x + undoButton.getWidth() + 2); 48 | this.resetButton.setY(y); 49 | 50 | this.valueButton.setX(x + undoButton.getWidth() + 2 + resetButton.getWidth() + 2); 51 | this.valueButton.setY(y); 52 | } else { 53 | this.undoButton.setX(this.getContentRight() - this.undoButton.getWidth()); 54 | this.undoButton.setY(y); 55 | 56 | this.resetButton.setX(this.undoButton.getX() - this.resetButton.getWidth() - 2); 57 | this.resetButton.setY(y); 58 | 59 | this.valueButton.setX(this.resetButton.getX() - this.valueButton.getWidth() - 2); 60 | this.valueButton.setY(y); 61 | } 62 | 63 | this.valueButton.render(guiGraphics, mouseX, mouseY, delta); 64 | this.resetButton.render(guiGraphics, mouseX, mouseY, delta); 65 | this.undoButton.render(guiGraphics, mouseX, mouseY, delta); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/network/message/S2CEnderChestUpdate.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.network.message; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.impl.network.context.MessageContext; 4 | import net.minecraft.client.Minecraft; 5 | import net.minecraft.core.HolderLookup; 6 | import net.minecraft.nbt.CompoundTag; 7 | import net.minecraft.nbt.ListTag; 8 | import net.minecraft.network.FriendlyByteBuf; 9 | import net.minecraft.util.ProblemReporter; 10 | import net.minecraft.world.ItemStackWithSlot; 11 | import net.minecraft.world.inventory.PlayerEnderChestContainer; 12 | import net.minecraft.world.level.storage.TagValueInput; 13 | import net.minecraft.world.level.storage.TagValueOutput; 14 | 15 | import javax.annotation.Nullable; 16 | import java.util.Objects; 17 | import java.util.Optional; 18 | 19 | /** 20 | * Updates a client's ender chest contents. 21 | * 22 | * @param nbtInventory NBT-serialized ender chest inventory. 23 | */ 24 | public record S2CEnderChestUpdate(@Nullable ListTag nbtInventory) { 25 | private static final S2CEnderChestUpdate EMPTY = new S2CEnderChestUpdate(null); 26 | 27 | public static S2CEnderChestUpdate create(PlayerEnderChestContainer inventory, HolderLookup.Provider registries) { 28 | var valueOutput = TagValueOutput.createWithContext(ProblemReporter.DISCARDING, registries); 29 | inventory.storeAsSlots(valueOutput.list("inv", ItemStackWithSlot.CODEC)); 30 | return new S2CEnderChestUpdate(valueOutput.buildResult().getListOrEmpty("inv")); 31 | } 32 | 33 | public static class Type implements MessageType { 34 | @Override 35 | public void encode(S2CEnderChestUpdate message, FriendlyByteBuf buf) { 36 | CompoundTag compound = new CompoundTag(); 37 | 38 | compound.put("inv", Objects.requireNonNull(message.nbtInventory)); 39 | buf.writeNbt(compound); 40 | } 41 | 42 | @Override 43 | public S2CEnderChestUpdate decode(FriendlyByteBuf buf) { 44 | return Optional.ofNullable(buf.readNbt()) // 45 | .flatMap(compound -> compound.getList("inv")) // 46 | .map(S2CEnderChestUpdate::new) // 47 | .orElse(EMPTY); 48 | } 49 | 50 | @Override 51 | public void onReceive(S2CEnderChestUpdate message, MessageContext context) { 52 | if (message.nbtInventory == null) 53 | return; 54 | 55 | Minecraft.getInstance().execute(() -> { 56 | if (Minecraft.getInstance().player != null) { 57 | var player = Minecraft.getInstance().player; 58 | 59 | var wrappedList = new CompoundTag(); 60 | wrappedList.put("inv", message.nbtInventory); 61 | 62 | var valueInput = TagValueInput.create(ProblemReporter.DISCARDING, player.registryAccess(), wrappedList); 63 | player.getEnderChestInventory().fromSlots(valueInput.listOrEmpty("inv", ItemStackWithSlot.CODEC)); 64 | } 65 | }); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/network/message/C2SHandshakeStart.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.network.message; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.ShulkerBoxTooltip; 4 | import com.misterpemodder.shulkerboxtooltip.impl.network.ProtocolVersion; 5 | import com.misterpemodder.shulkerboxtooltip.impl.network.ServerNetworking; 6 | import com.misterpemodder.shulkerboxtooltip.impl.network.channel.C2SChannel; 7 | import com.misterpemodder.shulkerboxtooltip.impl.network.context.MessageContext; 8 | import net.minecraft.network.FriendlyByteBuf; 9 | import net.minecraft.server.level.ServerPlayer; 10 | 11 | /** 12 | * Initiates a handshake with the server. 13 | *

14 | * Sent by clients as soon as the server registers the capability to handle it. 15 | * 16 | * @param clientVersion The protocol version of the client. 17 | */ 18 | public record C2SHandshakeStart(ProtocolVersion clientVersion) { 19 | public static class Type implements MessageType { 20 | 21 | @Override 22 | public void encode(C2SHandshakeStart message, FriendlyByteBuf buf) { 23 | message.clientVersion.writeToPacketBuf(buf); 24 | } 25 | 26 | @Override 27 | public C2SHandshakeStart decode(FriendlyByteBuf buf) { 28 | return new C2SHandshakeStart(ProtocolVersion.readFromPacketBuf(buf)); 29 | } 30 | 31 | @Override 32 | public void onReceive(C2SHandshakeStart message, MessageContext context) { 33 | var player = (ServerPlayer) context.getPlayer(); 34 | var channel = (C2SChannel) context.getChannel(); 35 | 36 | if (message.clientVersion == null) { 37 | ShulkerBoxTooltip.LOGGER.error(player.getGameProfile().name() + ": received invalid handshake packet"); 38 | channel.unregisterFor(player); 39 | return; 40 | } 41 | 42 | // compatibility check 43 | ShulkerBoxTooltip.LOGGER.info(player.getGameProfile().name() + ": protocol version: " + message.clientVersion); 44 | if (message.clientVersion.major() != ProtocolVersion.CURRENT.major()) { 45 | ShulkerBoxTooltip.LOGGER.error( 46 | player.getGameProfile().name() + ": incompatible client protocol version, expected " 47 | + ProtocolVersion.CURRENT.major() + ", got " + message.clientVersion.major()); 48 | channel.unregisterFor(player); 49 | return; 50 | } 51 | 52 | // client is compatible, send the server's version and register the client 53 | context.execute(() -> { 54 | S2CMessages.HANDSHAKE_RESPONSE.sendTo(player, 55 | new S2CHandshakeResponse(ProtocolVersion.CURRENT, ShulkerBoxTooltip.config)); 56 | ServerNetworking.addClient(player, message.clientVersion); 57 | }); 58 | } 59 | 60 | @Override 61 | public void onRegister(MessageContext context) { 62 | if (context.getReceivingSide() == MessageContext.Side.CLIENT) 63 | C2SMessages.attemptHandshake(); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /fabric/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/network/fabric/ClientNetworkingImpl.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.network.fabric; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.ShulkerBoxTooltip; 4 | import com.misterpemodder.shulkerboxtooltip.impl.network.ClientNetworking; 5 | import com.misterpemodder.shulkerboxtooltip.impl.network.channel.C2SChannel; 6 | import com.misterpemodder.shulkerboxtooltip.impl.network.context.S2CMessageContext; 7 | import com.misterpemodder.shulkerboxtooltip.impl.network.message.C2SMessages; 8 | import com.misterpemodder.shulkerboxtooltip.impl.network.message.MessageType; 9 | import com.misterpemodder.shulkerboxtooltip.impl.network.message.S2CMessages; 10 | import net.fabricmc.api.EnvType; 11 | import net.fabricmc.api.Environment; 12 | import net.fabricmc.fabric.api.client.networking.v1.C2SPlayChannelEvents; 13 | import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; 14 | import net.minecraft.resources.Identifier; 15 | 16 | import java.util.HashMap; 17 | import java.util.Map; 18 | 19 | public final class ClientNetworkingImpl { 20 | private static final Map> C2S_CHANNELS = new HashMap<>(); 21 | 22 | private ClientNetworkingImpl() { 23 | } 24 | 25 | /** 26 | * Implements {@link ClientNetworking#init()}. 27 | */ 28 | @Environment(EnvType.CLIENT) 29 | public static void init() { 30 | if (ShulkerBoxTooltip.config.preview.serverIntegration) { 31 | S2CMessages.registerPayloadTypes(); 32 | C2SMessages.registerPayloadTypes(); 33 | ClientPlayConnectionEvents.INIT.register((handler, client) -> S2CMessages.registerAll()); 34 | ClientPlayConnectionEvents.DISCONNECT.register((handler, client) -> C2SMessages.onDisconnectFromServer()); 35 | 36 | C2SPlayChannelEvents.REGISTER.register( 37 | (handler, sender, server, ids) -> ids.forEach(ClientNetworkingImpl::onRegisterChannel)); 38 | C2SPlayChannelEvents.UNREGISTER.register( 39 | (handler, sender, server, ids) -> ids.forEach(ClientNetworkingImpl::onUnregisterChannel)); 40 | } 41 | ClientPlayConnectionEvents.JOIN.register((handler, sender, client) -> ClientNetworking.onJoinServer(client)); 42 | } 43 | 44 | /** 45 | * Implements {@link ClientNetworking#createC2SChannel(Identifier, MessageType)}. 46 | */ 47 | public static C2SChannel createC2SChannel(Identifier id, MessageType type) { 48 | var channel = new FabricC2SChannel<>(id, type); 49 | C2S_CHANNELS.put(id, channel); 50 | return channel; 51 | } 52 | 53 | @Environment(EnvType.CLIENT) 54 | @SuppressWarnings("unchecked") 55 | private static void onRegisterChannel(Identifier id) { 56 | FabricC2SChannel channel = (FabricC2SChannel) C2S_CHANNELS.get(id); 57 | if (channel != null) 58 | channel.onRegister(new S2CMessageContext<>(channel)); 59 | } 60 | 61 | @Environment(EnvType.CLIENT) 62 | @SuppressWarnings("unchecked") 63 | private static void onUnregisterChannel(Identifier id) { 64 | FabricC2SChannel channel = (FabricC2SChannel) C2S_CHANNELS.get(id); 65 | if (channel != null) 66 | channel.onUnregister(new S2CMessageContext<>(channel)); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/network/message/S2CHandshakeResponse.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.network.message; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.ShulkerBoxTooltip; 4 | import com.misterpemodder.shulkerboxtooltip.impl.config.Configuration; 5 | import com.misterpemodder.shulkerboxtooltip.impl.config.ConfigurationHandler; 6 | import com.misterpemodder.shulkerboxtooltip.impl.network.ClientNetworking; 7 | import com.misterpemodder.shulkerboxtooltip.impl.network.ProtocolVersion; 8 | import com.misterpemodder.shulkerboxtooltip.impl.network.context.MessageContext; 9 | import com.misterpemodder.shulkerboxtooltip.impl.util.EnvironmentUtil; 10 | import net.minecraft.network.FriendlyByteBuf; 11 | 12 | import javax.annotation.Nullable; 13 | import java.util.Objects; 14 | 15 | /** 16 | * Response to a client's handshake attempt. 17 | * 18 | * @param serverVersion The server's protocol version. 19 | * @param config The server's configuration. 20 | */ 21 | public record S2CHandshakeResponse(@Nullable ProtocolVersion serverVersion, Configuration config) { 22 | public static class Type implements MessageType { 23 | @Override 24 | public void encode(S2CHandshakeResponse message, FriendlyByteBuf buf) { 25 | Objects.requireNonNull(message.serverVersion).writeToPacketBuf(buf); 26 | ConfigurationHandler.writeToPacketBuf(message.config, buf); 27 | } 28 | 29 | @Override 30 | public S2CHandshakeResponse decode(FriendlyByteBuf buf) { 31 | ProtocolVersion serverVersion = ProtocolVersion.readFromPacketBuf(buf); 32 | var config = EnvironmentUtil.getInstance().makeConfiguration(); 33 | ShulkerBoxTooltip.configTree.copy(ShulkerBoxTooltip.config, config); 34 | 35 | if (serverVersion != null && serverVersion.major() == ProtocolVersion.CURRENT.major()) { 36 | try { 37 | ConfigurationHandler.readFromPacketBuf(config, buf); 38 | } catch (RuntimeException e) { 39 | ShulkerBoxTooltip.LOGGER.error("failed to read server configuration", e); 40 | } 41 | } 42 | 43 | return new S2CHandshakeResponse(serverVersion, config); 44 | } 45 | 46 | @Override 47 | public void onReceive(S2CHandshakeResponse message, MessageContext context) { 48 | ShulkerBoxTooltip.LOGGER.info("Handshake succeeded"); 49 | if (message.serverVersion != null) { 50 | if (message.serverVersion.major() == ProtocolVersion.CURRENT.major()) { 51 | ShulkerBoxTooltip.LOGGER.info("Server protocol version: " + message.serverVersion); 52 | 53 | ClientNetworking.serverProtocolVersion = message.serverVersion; 54 | ShulkerBoxTooltip.config = message.config; 55 | S2CMessages.HANDSHAKE_RESPONSE.unregister(); 56 | return; 57 | } 58 | ShulkerBoxTooltip.LOGGER.error( 59 | "Incompatible server protocol version, expected " + ProtocolVersion.CURRENT.major() + ", got " 60 | + message.serverVersion.major()); 61 | } else { 62 | ShulkerBoxTooltip.LOGGER.error("Could not read server protocol version"); 63 | } 64 | S2CMessages.unregisterAll(); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/config/ConfigurationHandler.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.config; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.ShulkerBoxTooltip; 4 | import com.misterpemodder.shulkerboxtooltip.impl.config.Configuration.EnderChestSyncType; 5 | import com.misterpemodder.shulkerboxtooltip.impl.util.EnvironmentUtil; 6 | import net.fabricmc.api.EnvType; 7 | import net.fabricmc.api.Environment; 8 | import net.minecraft.nbt.CompoundTag; 9 | import net.minecraft.network.FriendlyByteBuf; 10 | 11 | public final class ConfigurationHandler { 12 | 13 | private static ShulkerBoxTooltipConfigSerializer serializer; 14 | 15 | private ConfigurationHandler() { 16 | } 17 | 18 | public static Configuration register() { 19 | serializer = new ShulkerBoxTooltipConfigSerializer(); 20 | var config = loadFromFile(); 21 | saveToFile(config); 22 | return config; 23 | } 24 | 25 | public static Configuration loadFromFile() { 26 | try { 27 | var config = serializer.deserialize(); 28 | var errorMsg = ShulkerBoxTooltip.configTree.validate(config); 29 | if (errorMsg != null) { 30 | ShulkerBoxTooltip.LOGGER.error("Failed to load configuration, using default values: " + errorMsg); 31 | return EnvironmentUtil.getInstance().makeConfiguration(); 32 | } 33 | return config; 34 | } catch (SerializationException e) { 35 | ShulkerBoxTooltip.LOGGER.error("Failed to load configuration, using default values", e); 36 | return EnvironmentUtil.getInstance().makeConfiguration(); 37 | } 38 | } 39 | 40 | public static void saveToFile(Configuration toSave) { 41 | 42 | if (ShulkerBoxTooltip.savedConfig != null) { 43 | ShulkerBoxTooltip.configTree.copy(toSave, ShulkerBoxTooltip.savedConfig); 44 | } 45 | if (ShulkerBoxTooltip.config != null) { 46 | // Put aside server-synced values 47 | var serverConfigNbt = new CompoundTag(); 48 | ShulkerBoxTooltip.configTree.writeToNbt(ShulkerBoxTooltip.config, serverConfigNbt); 49 | 50 | ShulkerBoxTooltip.configTree.copy(toSave, ShulkerBoxTooltip.config); 51 | 52 | // Restore server-synced values to active config 53 | ShulkerBoxTooltip.configTree.readFromNbt(ShulkerBoxTooltip.config, serverConfigNbt); 54 | } 55 | 56 | try { 57 | serializer.serialize(toSave); 58 | } catch (SerializationException e) { 59 | ShulkerBoxTooltip.LOGGER.error("Failed to save configuration", e); 60 | } 61 | } 62 | 63 | @Environment(EnvType.CLIENT) 64 | public static void reinitClientSideSyncedValues(Configuration config) { 65 | config.server.clientIntegration = false; 66 | config.server.enderChestSyncType = EnderChestSyncType.NONE; 67 | } 68 | 69 | public static void readFromPacketBuf(Configuration config, FriendlyByteBuf buf) { 70 | CompoundTag compound = buf.readNbt(); 71 | if (compound != null) 72 | ShulkerBoxTooltip.configTree.readFromNbt(config, compound); 73 | } 74 | 75 | public static void writeToPacketBuf(Configuration config, FriendlyByteBuf buf) { 76 | CompoundTag compound = new CompoundTag(); 77 | ShulkerBoxTooltip.configTree.writeToNbt(config, compound); 78 | buf.writeNbt(compound); 79 | } 80 | } -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /fabric/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/network/fabric/ServerNetworkingImpl.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.network.fabric; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.ShulkerBoxTooltip; 4 | import com.misterpemodder.shulkerboxtooltip.impl.network.ServerNetworking; 5 | import com.misterpemodder.shulkerboxtooltip.impl.network.channel.S2CChannel; 6 | import com.misterpemodder.shulkerboxtooltip.impl.network.context.C2SMessageContext; 7 | import com.misterpemodder.shulkerboxtooltip.impl.network.message.C2SMessages; 8 | import com.misterpemodder.shulkerboxtooltip.impl.network.message.MessageType; 9 | import com.misterpemodder.shulkerboxtooltip.impl.network.message.S2CMessages; 10 | import net.fabricmc.fabric.api.entity.event.v1.ServerEntityWorldChangeEvents; 11 | import net.fabricmc.fabric.api.networking.v1.S2CPlayChannelEvents; 12 | import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; 13 | import net.minecraft.resources.Identifier; 14 | import net.minecraft.server.level.ServerPlayer; 15 | 16 | import java.util.HashMap; 17 | import java.util.Map; 18 | 19 | public final class ServerNetworkingImpl { 20 | private static final Map> S2C_CHANNELS = new HashMap<>(); 21 | 22 | private ServerNetworkingImpl() { 23 | } 24 | 25 | /** 26 | * Implements {@link ServerNetworking#init()}. 27 | */ 28 | public static void init() { 29 | if (!ShulkerBoxTooltip.config.server.clientIntegration) 30 | return; 31 | S2CMessages.registerPayloadTypes(); 32 | C2SMessages.registerPayloadTypes(); 33 | ServerPlayConnectionEvents.INIT.register((handler, server) -> C2SMessages.registerAllFor(handler.player)); 34 | ServerPlayConnectionEvents.DISCONNECT.register((handler, server) -> ServerNetworking.removeClient(handler.player)); 35 | S2CPlayChannelEvents.REGISTER.register( 36 | (handler, sender, server, ids) -> ids.forEach(id -> onRegisterChannel(id, handler.getPlayer()))); 37 | S2CPlayChannelEvents.UNREGISTER.register( 38 | (handler, sender, server, ids) -> ids.forEach(id -> onUnregisterChannel(id, handler.getPlayer()))); 39 | ServerEntityWorldChangeEvents.AFTER_PLAYER_CHANGE_WORLD.register( 40 | (player, origin, destination) -> ServerNetworking.onPlayerChangeWorld(player)); 41 | } 42 | 43 | /** 44 | * Implements {@link ServerNetworking#createS2CChannel(Identifier, MessageType)}. 45 | */ 46 | public static S2CChannel createS2CChannel(Identifier id, MessageType type) { 47 | var channel = new FabricS2CChannel<>(id, type); 48 | S2C_CHANNELS.put(id, channel); 49 | return channel; 50 | } 51 | 52 | @SuppressWarnings("unchecked") 53 | private static void onRegisterChannel(Identifier id, ServerPlayer player) { 54 | FabricS2CChannel channel = (FabricS2CChannel) S2C_CHANNELS.get(id); 55 | if (channel != null) 56 | channel.onRegister(new C2SMessageContext<>(player, channel)); 57 | } 58 | 59 | @SuppressWarnings("unchecked") 60 | private static void onUnregisterChannel(Identifier id, ServerPlayer player) { 61 | FabricS2CChannel channel = (FabricS2CChannel) S2C_CHANNELS.get(id); 62 | if (channel != null) 63 | channel.onUnregister(new C2SMessageContext<>(player, channel)); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/provider/ShulkerBoxPreviewProvider.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.provider; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.ShulkerBoxTooltip; 4 | import com.misterpemodder.shulkerboxtooltip.api.PreviewContext; 5 | import com.misterpemodder.shulkerboxtooltip.api.color.ColorKey; 6 | import com.misterpemodder.shulkerboxtooltip.impl.config.Configuration; 7 | import net.fabricmc.api.EnvType; 8 | import net.fabricmc.api.Environment; 9 | import net.minecraft.ChatFormatting; 10 | import net.minecraft.core.component.DataComponents; 11 | import net.minecraft.network.chat.Component; 12 | import net.minecraft.network.chat.Style; 13 | import net.minecraft.world.item.DyeColor; 14 | import net.minecraft.world.item.ItemStack; 15 | import net.minecraft.world.level.block.Block; 16 | import net.minecraft.world.level.block.ShulkerBoxBlock; 17 | import net.minecraft.world.level.block.entity.ShulkerBoxBlockEntity; 18 | 19 | import java.util.Collections; 20 | import java.util.List; 21 | import java.util.function.Supplier; 22 | 23 | public class ShulkerBoxPreviewProvider extends InventoryAwarePreviewProvider { 24 | public ShulkerBoxPreviewProvider(int maxRowSize, Supplier blockEntitySupplier) { 25 | super(maxRowSize, blockEntitySupplier); 26 | } 27 | 28 | @Override 29 | public boolean showTooltipHints(PreviewContext context) { 30 | return true; 31 | } 32 | 33 | @Override 34 | @Environment(EnvType.CLIENT) 35 | public ColorKey getWindowColorKey(PreviewContext context) { 36 | DyeColor dye = ((ShulkerBoxBlock) Block.byItem(context.stack().getItem())).getColor(); 37 | 38 | if (dye == null) 39 | return ColorKey.SHULKER_BOX; 40 | return switch (dye) { 41 | case ORANGE -> ColorKey.ORANGE_SHULKER_BOX; 42 | case MAGENTA -> ColorKey.MAGENTA_SHULKER_BOX; 43 | case LIGHT_BLUE -> ColorKey.LIGHT_BLUE_SHULKER_BOX; 44 | case YELLOW -> ColorKey.YELLOW_SHULKER_BOX; 45 | case LIME -> ColorKey.LIME_SHULKER_BOX; 46 | case PINK -> ColorKey.PINK_SHULKER_BOX; 47 | case GRAY -> ColorKey.GRAY_SHULKER_BOX; 48 | case LIGHT_GRAY -> ColorKey.LIGHT_GRAY_SHULKER_BOX; 49 | case CYAN -> ColorKey.CYAN_SHULKER_BOX; 50 | case PURPLE -> ColorKey.PURPLE_SHULKER_BOX; 51 | case BLUE -> ColorKey.BLUE_SHULKER_BOX; 52 | case BROWN -> ColorKey.BROWN_SHULKER_BOX; 53 | case GREEN -> ColorKey.GREEN_SHULKER_BOX; 54 | case RED -> ColorKey.RED_SHULKER_BOX; 55 | case BLACK -> ColorKey.BLACK_SHULKER_BOX; 56 | default -> ColorKey.WHITE_SHULKER_BOX; 57 | }; 58 | } 59 | 60 | @Override 61 | public List addTooltip(PreviewContext context) { 62 | ItemStack stack = context.stack(); 63 | 64 | // Restore the vanilla behavior of adding question marks to the tooltip when the item has a loot table 65 | if (this.canUseLootTables() 66 | && ShulkerBoxTooltip.config.tooltip.lootTableInfoType == Configuration.LootTableInfoType.HIDE && stack.has( 67 | DataComponents.CONTAINER_LOOT)) { 68 | Style style = Style.EMPTY.withColor(ChatFormatting.GRAY); 69 | 70 | return Collections.singletonList(Component.literal("???????").setStyle(style)); 71 | } 72 | return super.addTooltip(context); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /fabric/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/network/fabric/FabricC2SChannel.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.network.fabric; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.ShulkerBoxTooltip; 4 | import com.misterpemodder.shulkerboxtooltip.impl.network.Payload; 5 | import com.misterpemodder.shulkerboxtooltip.impl.network.channel.C2SChannel; 6 | import com.misterpemodder.shulkerboxtooltip.impl.network.context.C2SMessageContext; 7 | import com.misterpemodder.shulkerboxtooltip.impl.network.context.MessageContext; 8 | import com.misterpemodder.shulkerboxtooltip.impl.network.message.MessageType; 9 | import com.misterpemodder.shulkerboxtooltip.impl.util.EnvironmentUtil; 10 | import net.fabricmc.api.EnvType; 11 | import net.fabricmc.api.Environment; 12 | import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; 13 | import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; 14 | import net.minecraft.client.Minecraft; 15 | import net.minecraft.resources.Identifier; 16 | import net.minecraft.server.level.ServerPlayer; 17 | import net.minecraft.server.network.ServerGamePacketListenerImpl; 18 | 19 | class FabricC2SChannel extends FabricChannel implements C2SChannel { 20 | @Environment(EnvType.CLIENT) 21 | private boolean serverRegistered; 22 | 23 | public FabricC2SChannel(Identifier id, MessageType type) { 24 | super(id, type); 25 | if (EnvironmentUtil.isClient()) 26 | this.serverRegistered = false; 27 | } 28 | 29 | @Override 30 | public void registerFor(ServerPlayer player) { 31 | ServerGamePacketListenerImpl handler = player.connection; 32 | 33 | if (handler == null) { 34 | ShulkerBoxTooltip.LOGGER.error("Cannot register packet receiver for " + this.getId() + ", player is not in game"); 35 | return; 36 | } 37 | ServerPlayNetworking.registerReceiver(handler, this.id, this::onReceive); 38 | } 39 | 40 | @Override 41 | public void unregisterFor(ServerPlayer player) { 42 | ServerGamePacketListenerImpl handler = player.connection; 43 | 44 | if (handler != null) { 45 | ServerPlayNetworking.unregisterReceiver(handler, this.getId()); 46 | } 47 | } 48 | 49 | @Override 50 | @Environment(EnvType.CLIENT) 51 | public void sendToServer(T message) { 52 | ClientPlayNetworking.send(new Payload<>(this.id, message)); 53 | } 54 | 55 | @Override 56 | @Environment(EnvType.CLIENT) 57 | public boolean canSendToServer() { 58 | return this.serverRegistered && Minecraft.getInstance().getConnection() != null; 59 | } 60 | 61 | @Override 62 | public void onRegister(MessageContext context) { 63 | if (context.getReceivingSide() == MessageContext.Side.CLIENT) 64 | this.serverRegistered = true; 65 | super.onRegister(context); 66 | } 67 | 68 | @Override 69 | public void onUnregister(MessageContext context) { 70 | if (context.getReceivingSide() == MessageContext.Side.CLIENT) 71 | this.serverRegistered = false; 72 | super.onUnregister(context); 73 | } 74 | 75 | @Override 76 | @Environment(EnvType.CLIENT) 77 | public void onDisconnect() { 78 | this.serverRegistered = false; 79 | } 80 | 81 | private void onReceive(Payload payload, ServerPlayNetworking.Context context) { 82 | this.type.onReceive(payload.value(), new C2SMessageContext<>(context.player(), this)); 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/api/PreviewContext.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.api; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.api.config.PreviewConfiguration; 4 | import com.misterpemodder.shulkerboxtooltip.impl.PreviewContextImpl; 5 | import net.minecraft.core.HolderLookup; 6 | import net.minecraft.world.entity.player.Player; 7 | import net.minecraft.world.item.ItemStack; 8 | import org.jetbrains.annotations.Contract; 9 | 10 | import javax.annotation.Nonnull; 11 | import javax.annotation.Nullable; 12 | 13 | /** 14 | * Provides information for item previews, such as the item stack and player that owns the stack (if present). 15 | * 16 | * @since 2.0.0 17 | */ 18 | public interface PreviewContext { 19 | /** 20 | * Creates a new {@link PreviewContext.Builder} instance. 21 | *

22 | * This is the recommended way to create a {@link PreviewContext} instance as of 4.1.0. 23 | * 24 | * @param stack The stack to create the context with, may not be null. 25 | * @return a new {@link PreviewContext.Builder} instance. 26 | * @since 4.1.0 27 | */ 28 | @Nonnull 29 | @Contract("_ -> new") 30 | static PreviewContext.Builder builder(ItemStack stack) { 31 | return new PreviewContextImpl.Builder(stack); 32 | } 33 | 34 | /** 35 | * Gets the item stack associated with this context. 36 | * 37 | * @return The item stack. 38 | * @since 3.1.0 39 | */ 40 | @Nonnull 41 | ItemStack stack(); 42 | 43 | /** 44 | * Gets the player associated with this context, or null if it does not exist. 45 | * 46 | * @return The owner of this item stack, may be null. 47 | * @since 3.1.0 48 | */ 49 | @Nullable 50 | Player owner(); 51 | 52 | /** 53 | * Provides access to the configuration in use for this context. 54 | * 55 | * @return the configuration. 56 | * @since 3.3.0 57 | */ 58 | @Nonnull 59 | PreviewConfiguration config(); 60 | 61 | /** 62 | * Returns the registry lookup for this context, if available. 63 | * 64 | * @return the registry lookup, or {@code null}. 65 | * @since 4.1.0 66 | */ 67 | @Nullable 68 | HolderLookup.Provider registryLookup(); 69 | 70 | /** 71 | * A builder for creating {@link PreviewContext} instances. 72 | * 73 | * @since 4.1.0 74 | */ 75 | interface Builder { 76 | /** 77 | * Sets the owning player of the item stack. 78 | * 79 | * @param owner The owner of the item stack, may be null. 80 | * @return this builder instance for chaining. 81 | * @since 4.1.0 82 | */ 83 | Builder withOwner(@Nullable Player owner); 84 | 85 | /** 86 | * Sets the registry lookup to use for deserialization of the item stack. 87 | *

88 | * Defaults to the owner's registry if not set. 89 | * 90 | * @param registryLookup The registry lookup for the item stack, may be null. 91 | * @return this builder instance for chaining. 92 | * @since 4.1.0 93 | */ 94 | Builder withRegistryLookup(@Nullable HolderLookup.Provider registryLookup); 95 | 96 | /** 97 | * Builds the {@link PreviewContext} instance. 98 | * 99 | * @return a new {@link PreviewContext} instance. 100 | * @since 4.1.0 101 | */ 102 | @Nonnull 103 | PreviewContext build(); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/tooltip/PreviewClientTooltipComponent.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.tooltip; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.ShulkerBoxTooltip; 4 | import com.misterpemodder.shulkerboxtooltip.api.PreviewContext; 5 | import com.misterpemodder.shulkerboxtooltip.api.ShulkerBoxTooltipApi; 6 | import com.misterpemodder.shulkerboxtooltip.api.provider.PreviewProvider; 7 | import com.misterpemodder.shulkerboxtooltip.api.renderer.PreviewRenderer; 8 | import com.misterpemodder.shulkerboxtooltip.impl.config.Configuration.PreviewPosition; 9 | import com.misterpemodder.shulkerboxtooltip.impl.hook.GuiGraphicsExtensions; 10 | import net.minecraft.client.gui.Font; 11 | import net.minecraft.client.gui.GuiGraphics; 12 | import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipComponent; 13 | import org.jetbrains.annotations.NotNull; 14 | 15 | public class PreviewClientTooltipComponent implements ClientTooltipComponent { 16 | private final PreviewRenderer renderer; 17 | 18 | public PreviewClientTooltipComponent(PreviewTooltipComponent data) { 19 | PreviewRenderer renderer = data.provider().getRenderer(); 20 | 21 | if (renderer == null) 22 | renderer = PreviewRenderer.getDefaultRendererInstance(); 23 | this.renderer = renderer; 24 | PreviewProvider provider = data.provider(); 25 | PreviewContext context = data.context(); 26 | 27 | renderer.setPreview(context, provider); 28 | renderer.setPreviewType(ShulkerBoxTooltipApi.getCurrentPreviewType(provider.isFullPreviewAvailable(context))); 29 | } 30 | 31 | @Override 32 | public int getHeight(@NotNull Font font) { 33 | if (ShulkerBoxTooltip.config.preview.position == PreviewPosition.INSIDE) 34 | return this.renderer.getHeight() + 4; 35 | return 0; 36 | } 37 | 38 | @Override 39 | public int getWidth(@NotNull Font font) { 40 | if (ShulkerBoxTooltip.config.preview.position == PreviewPosition.INSIDE) 41 | return this.renderer.getWidth(); 42 | return 0; 43 | } 44 | 45 | @Override 46 | public void renderImage(@NotNull Font font, int x, int y, int totalWidth, int totalHeight, 47 | @NotNull GuiGraphics graphics) { 48 | var extendedGraphics = (GuiGraphicsExtensions) graphics; 49 | int mouseX = extendedGraphics.getMouseX(); 50 | int mouseY = extendedGraphics.getMouseY(); 51 | int tooltipTopY = extendedGraphics.getTooltipTopYPosition(); 52 | 53 | PreviewPosition position = ShulkerBoxTooltip.config.preview.position; 54 | int viewportHeight = this.renderer.getHeight(); 55 | 56 | if (tooltipTopY == Integer.MIN_VALUE) 57 | // Fall back to "inside" if the tooltip Y position was not captured 58 | position = PreviewPosition.INSIDE; 59 | 60 | if (position == PreviewPosition.OUTSIDE) { 61 | int screenH = graphics.guiHeight(); 62 | position = tooltipTopY + totalHeight + viewportHeight > screenH ? 63 | PreviewPosition.OUTSIDE_TOP : 64 | PreviewPosition.OUTSIDE_BOTTOM; 65 | } 66 | 67 | if (position == PreviewPosition.OUTSIDE_TOP) { 68 | x -= 4; 69 | y = tooltipTopY - viewportHeight - 4; 70 | } else if (position == PreviewPosition.OUTSIDE_BOTTOM) { 71 | x -= 4; 72 | y = tooltipTopY + totalHeight + 4; 73 | } 74 | 75 | this.renderer.draw(x, y, totalWidth, viewportHeight, graphics, font, mouseX, mouseY); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /common/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.github.johnrengelman.shadow' version '8.1.1' 3 | } 4 | 5 | archivesBaseName = "${rootProject.projectArchiveBaseName}-common" 6 | 7 | architectury { 8 | common(rootProject.enabledPlatforms.split(',')) 9 | } 10 | 11 | loom { 12 | accessWidenerPath = file('src/main/resources/shulkerboxtooltip.accesswidener') 13 | } 14 | 15 | configurations { 16 | shadowCommon 17 | } 18 | 19 | dependencies { 20 | // We depend on fabric loader here to use the fabric @Environment annotations and get the mixin dependencies 21 | // Do NOT use other classes from fabric loader 22 | modImplementation "net.fabricmc:fabric-loader:${rootProject.fabricLoaderVersion}" 23 | 24 | // Nullable annotations. 25 | implementation 'com.google.code.findbugs:jsr305:3.0.2' 26 | implementation 'org.jetbrains:annotations:23.0.0' 27 | 28 | // Jankson 29 | compileOnly "blue.endless:jankson:${rootProject.janksonVersion}" 30 | shadowCommon "blue.endless:jankson:${rootProject.janksonVersion}" 31 | } 32 | 33 | javadoc { 34 | exclude 'com/misterpemodder/shulkerboxtooltip/impl/**' 35 | exclude 'com/misterpemodder/shulkerboxtooltip/mixin/**' 36 | exclude 'com/misterpemodder/shulkerboxtooltip/*.java' 37 | } 38 | 39 | task javadocJar(type: Jar, dependsOn: javadoc) { 40 | archiveClassifier.set('javadoc') 41 | from javadoc.destinationDir 42 | } 43 | 44 | shadowJar { 45 | relocate 'blue.endless.jankson', "${rootProject.group}.shulkerboxtooltip.shadowed.blue.endless.jankson" 46 | 47 | configurations = [project.configurations.shadowCommon] 48 | archiveClassifier.set('dev-shadow') 49 | } 50 | 51 | remapJar { 52 | inputFile = shadowJar.archiveFile 53 | dependsOn shadowJar 54 | archiveClassifier.set(null) 55 | } 56 | 57 | publishing { 58 | publications { 59 | mavenCommon(MavenPublication) { 60 | groupId = project.group 61 | artifactId = project.archivesBaseName 62 | version = project.version 63 | 64 | artifact(remapJar) { 65 | builtBy remapJar 66 | } 67 | 68 | artifact(remapSourcesJar) { 69 | builtBy remapSourcesJar 70 | } 71 | 72 | pom rootProject.ext.makePom(project) 73 | } 74 | 75 | mavenCommonMojmap(MavenPublication) { 76 | groupId = project.group 77 | artifactId = project.archivesBaseName + "-mojmap" 78 | version = project.version 79 | 80 | artifact(shadowJar) { 81 | builtBy shadowJar 82 | setClassifier null 83 | } 84 | 85 | artifact(sourcesJar) { 86 | builtBy sourcesJar 87 | } 88 | 89 | artifact(javadocJar) { 90 | builtBy javadocJar 91 | } 92 | 93 | pom rootProject.ext.makePom(project) 94 | } 95 | } 96 | } 97 | 98 | signing { 99 | def signingKey = findProperty('signingKey') 100 | def signingPassword = findProperty('signingPassword') 101 | if (signingKey && signingPassword) { 102 | useInMemoryPgpKeys(signingKey, signingPassword) 103 | sign publishing.publications.mavenCommon 104 | sign publishing.publications.mavenCommonMojmap 105 | } else { 106 | println("${project.name}: skipping jar signing") 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/config/gui/entry/KeyValueConfigEntry.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.config.gui.entry; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.impl.config.gui.ConfigCategoryTab; 4 | import com.misterpemodder.shulkerboxtooltip.impl.tree.ValueConfigNode; 5 | import com.misterpemodder.shulkerboxtooltip.impl.util.Key; 6 | import net.fabricmc.api.EnvType; 7 | import net.fabricmc.api.Environment; 8 | import net.minecraft.ChatFormatting; 9 | import net.minecraft.client.gui.GuiGraphics; 10 | import net.minecraft.client.gui.components.Button; 11 | import net.minecraft.network.chat.Component; 12 | 13 | @Environment(EnvType.CLIENT) 14 | public final class KeyValueConfigEntry extends ValueConfigEntry { 15 | private final Button keyButton; 16 | 17 | public KeyValueConfigEntry(ConfigCategoryTab tab, ValueConfigNode valueNode) { 18 | super(tab, valueNode); 19 | 20 | var label = this.valueNode.getTitle(); 21 | this.keyButton = Button.builder(label, b -> { 22 | this.tab.setSelectedKeyNode(this.valueNode); 23 | this.tab.getScreen().refresh(); 24 | }).bounds(0, 0, 160, 20).createNarration(supplier -> this.getValue().isUnbound() ? 25 | Component.translatable("narrator.controls.unbound", label) : 26 | Component.translatable("narrator.controls.bound", label, supplier.get())).build(); 27 | this.children.addFirst(this.keyButton); 28 | this.refresh(); 29 | } 30 | 31 | @Override 32 | public void resetToDefault() { 33 | super.resetToDefault(); 34 | this.tab.setSelectedKeyNode(null); 35 | this.tab.getScreen().refresh(); 36 | } 37 | 38 | @Override 39 | public void refresh() { 40 | super.refresh(); 41 | this.keyButton.setMessage(this.getValue().get().getDisplayName()); 42 | 43 | if (this.tab.getSelectedKeyNode() == this.valueNode) { 44 | this.keyButton.setMessage(Component.literal("> ") 45 | .append(this.keyButton.getMessage().copy().withStyle(ChatFormatting.WHITE, ChatFormatting.UNDERLINE)) 46 | .append(" <") 47 | .withStyle(ChatFormatting.YELLOW)); 48 | } 49 | } 50 | 51 | @Override 52 | public void renderContent(GuiGraphics guiGraphics, int mouseX, int mouseY, boolean hovered, float delta) { 53 | int x = this.getContentX(); 54 | int y = this.getContentY(); 55 | this.renderLabel(guiGraphics); 56 | 57 | this.keyButton.setWidth(160 - this.resetButton.getWidth() - 2 - this.undoButton.getWidth() - 2); 58 | if (this.tab.getMinecraft().font.isBidirectional()) { 59 | this.undoButton.setX(x); 60 | this.undoButton.setY(y); 61 | 62 | this.resetButton.setX(x + undoButton.getWidth() + 2); 63 | this.resetButton.setY(y); 64 | 65 | this.keyButton.setX(x + undoButton.getWidth() + 2 + resetButton.getWidth() + 2); 66 | this.keyButton.setY(y); 67 | } else { 68 | this.undoButton.setX(this.getContentRight() - this.undoButton.getWidth()); 69 | this.undoButton.setY(y); 70 | 71 | this.resetButton.setX(this.undoButton.getX() - this.resetButton.getWidth() - 2); 72 | this.resetButton.setY(y); 73 | 74 | this.keyButton.setX(this.resetButton.getX() - this.keyButton.getWidth() - 2); 75 | this.keyButton.setY(y); 76 | } 77 | 78 | this.keyButton.render(guiGraphics, mouseX, mouseY, delta); 79 | this.resetButton.render(guiGraphics, mouseX, mouseY, delta); 80 | this.undoButton.render(guiGraphics, mouseX, mouseY, delta); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Shulker Box Tooltip 2 | [![Maven](https://img.shields.io/maven-metadata/v/https/maven.misterpemodder.com/libs-release/com/misterpemodder/shulkerboxtooltip-fabric/maven-metadata.xml.svg)](https://maven.misterpemodder.com/libs-release/com/misterpemodder) 3 | [![CurseForge](http://cf.way2muchnoise.eu/full_315811_downloads.svg)](https://minecraft.curseforge.com/projects/shulkerboxtooltip) 4 | [![Modrinth](https://img.shields.io/modrinth/dt/2M01OLQq?color=1bd96a&label=modrinth%20downloads)](https://modrinth.com/mod/shulkerboxtooltip) 5 | [![CI](https://github.com/MisterPeModder/ShulkerBoxTooltip/workflows/Main/badge.svg)](https://github.com/MisterPeModder/ShulkerBoxTooltip/actions?query=workflow%3AMain) 6 | [![Crowdin](https://badges.crowdin.net/shulkerboxtooltip/localized.svg)](https://crowdin.com/project/shulkerboxtooltip) 7 | ========================= 8 | 9 | This mod allows you to see a preview window of a shulker box contents when hovering above it in an inventory by pressing shift. 10 | 11 | **[Help translate ShulkerBoxTooltip on Crowdin!](https://crowdin.com/project/shulkerboxtooltip)** 12 | [Please vote for this to be included in vanilla!](https://feedback.minecraft.net/hc/en-us/community/posts/360074507051-shulker-boxes-should-have-the-new-bundle-interface) 13 | 14 | Preview Window 15 | 16 | ## Developers 17 | 18 | ### List of artifacts 19 | - **com.misterpemodder:shulkerboxtooltip-common**: Platform-agnostic API (with Yarn/intermediary mappings) 20 | - **com.misterpemodder:shulkerboxtooltip-common-mojmap**: Platform-agnostic API (with official Mojang mappings) 21 | - **com.misterpemodder:shulkerboxtooltip-fabric**: Fabric Implementation 22 | - **com.misterpemodder:shulkerboxtooltip-forge**: Forge-specific API + Implementation 23 | - **com.misterpemodder:shulkerboxtooltip-neoforge**: NeoForge-specific API + Implementation 24 | 25 | ### Declaring the dependency (Fabric Loom/Architectury Loom) 26 | ```gradle 27 | repositories { 28 | maven { url "https://maven.misterpemodder.com/libs-release/" } 29 | } 30 | 31 | dependencies { 32 | // Change to 'shulkerboxtooltip-forge', 'shulkerboxtooltip-neoforge', or 'shulkerboxtooltip-common' depending on the artifact 33 | modImplementation("com.misterpemodder:shulkerboxtooltip-fabric:VERSION") { transitive false } 34 | } 35 | ``` 36 | 37 | ### API 38 | To use the API, implement the `ShulkerBoxTooltipApi` interface on a class and register it as a plugin. 39 | 40 | On Fabric, add your plugin class as an entry point of type `"shulkerboxtooltip"` in your `fabric.mod.json` as such: 41 | ```json 42 | "entrypoints": { 43 | "shulkerboxtooltip": [ 44 | "com.example.mymod.MyShulkerBoxTooltipPlugin" 45 | ] 46 | } 47 | ``` 48 | 49 | On NeoForge, register your plugin by adding an extension point in your mod's initialization code: 50 | ```java 51 | ModLoadingContext.get().registerExtensionPoint(ShulkerBoxTooltipPlugin.class, 52 | () -> new ShulkerBoxTooltipPlugin(MyModShulkerBoxTooltipPlugin::new)); 53 | ``` 54 | 55 | On Forge, register your plugin by adding an extension point in your mod's initialization code: 56 | ```java 57 | FMLJavaModLoadingContext context = /* get instance from your mod's constructor */ 58 | context.registerExtensionPoint(ShulkerBoxTooltipPlugin.class, 59 | () -> new ShulkerBoxTooltipPlugin(MyModShulkerBoxTooltipPlugin::new)); 60 | ``` 61 | 62 | See [api source](https://github.com/MisterPeModder/ShulkerBoxTooltip/blob/1.19/common/src/main/java/com/misterpemodder/shulkerboxtooltip/api/ShulkerBoxTooltipApi.java) for documentation. 63 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/network/ServerNetworking.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.network; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.ShulkerBoxTooltip; 4 | import com.misterpemodder.shulkerboxtooltip.impl.PluginManager; 5 | import com.misterpemodder.shulkerboxtooltip.impl.config.Configuration; 6 | import com.misterpemodder.shulkerboxtooltip.impl.network.channel.S2CChannel; 7 | import com.misterpemodder.shulkerboxtooltip.impl.network.message.MessageType; 8 | import com.misterpemodder.shulkerboxtooltip.impl.network.message.S2CEnderChestUpdate; 9 | import com.misterpemodder.shulkerboxtooltip.impl.network.message.S2CMessages; 10 | import dev.architectury.injectables.annotations.ExpectPlatform; 11 | import net.minecraft.resources.Identifier; 12 | import net.minecraft.server.level.ServerPlayer; 13 | 14 | import java.util.Map; 15 | import java.util.WeakHashMap; 16 | 17 | /** 18 | * Server-side network handling. 19 | */ 20 | public class ServerNetworking { 21 | /** 22 | * A map of current compatible clients along with their protocol version. 23 | */ 24 | private static final Map CLIENTS = new WeakHashMap<>(); 25 | 26 | private ServerNetworking() { 27 | } 28 | 29 | /** 30 | * @param player The player. 31 | * @return true if the player has the mod installed and server integration turned on. 32 | */ 33 | public static boolean hasModAvailable(ServerPlayer player) { 34 | return CLIENTS.containsKey(player); 35 | } 36 | 37 | /** 38 | * @param client The player. 39 | * @param version The client's protocol version. 40 | */ 41 | public static void addClient(ServerPlayer client, ProtocolVersion version) { 42 | CLIENTS.put(client, version); 43 | 44 | // Initialize the providers if not already initialized 45 | PluginManager.loadProviders(); 46 | Configuration.EnderChestSyncType ecSyncType = ShulkerBoxTooltip.config.server.enderChestSyncType; 47 | 48 | if (ecSyncType != Configuration.EnderChestSyncType.NONE) 49 | S2CMessages.ENDER_CHEST_UPDATE.sendTo(client, 50 | S2CEnderChestUpdate.create(client.getEnderChestInventory(), client.registryAccess())); 51 | if (ecSyncType == Configuration.EnderChestSyncType.ACTIVE) 52 | EnderChestInventoryListener.attachTo(client); 53 | } 54 | 55 | public static void removeClient(ServerPlayer client) { 56 | CLIENTS.remove(client); 57 | EnderChestInventoryListener.detachFrom(client); 58 | } 59 | 60 | public static void onPlayerChangeWorld(ServerPlayer player) { 61 | Configuration.EnderChestSyncType ecSyncType = ShulkerBoxTooltip.config.server.enderChestSyncType; 62 | 63 | if (CLIENTS.containsKey(player) && ecSyncType != Configuration.EnderChestSyncType.NONE) { 64 | S2CMessages.ENDER_CHEST_UPDATE.sendTo(player, 65 | S2CEnderChestUpdate.create(player.getEnderChestInventory(), player.registryAccess())); 66 | } 67 | } 68 | 69 | /** 70 | * Performs registration of messages and events. 71 | */ 72 | @ExpectPlatform 73 | public static void init() { 74 | throw new AssertionError("Missing implementation of ServerNetworking.init()"); 75 | } 76 | 77 | /** 78 | * Creates a server-to-client channel compatible with the current mod platform. 79 | * 80 | * @param id The channel's identifier. 81 | * @param type The channel's message type. 82 | * @param The message type. 83 | * @return The newly-created channel. 84 | */ 85 | @ExpectPlatform 86 | public static S2CChannel createS2CChannel(Identifier id, MessageType type) { 87 | throw new AssertionError("Missing implementation of Networking.createS2CChannel()"); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /common/src/main/java/com/misterpemodder/shulkerboxtooltip/impl/util/MergedItemStack.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.impl.util; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.api.config.ItemStackMergingStrategy; 4 | import net.minecraft.core.NonNullList; 5 | import net.minecraft.world.item.ItemStack; 6 | 7 | import java.util.ArrayList; 8 | import java.util.Comparator; 9 | import java.util.HashMap; 10 | import java.util.List; 11 | 12 | public class MergedItemStack implements Comparable { 13 | private ItemStack merged; 14 | private final NonNullList subItems; 15 | private int firstSlot; 16 | 17 | public MergedItemStack(int slotCount) { 18 | this.merged = ItemStack.EMPTY; 19 | this.subItems = NonNullList.withSize(slotCount, ItemStack.EMPTY); 20 | this.firstSlot = Integer.MAX_VALUE; 21 | } 22 | 23 | public ItemStack get() { 24 | return this.merged; 25 | } 26 | 27 | /** 28 | * Add the passed stack into the item list. Does not check if items are equal. 29 | * 30 | * @param stack The stack to add 31 | * @param slot The slot this stack is located in. 32 | */ 33 | public void add(ItemStack stack, int slot, ItemStackMergingStrategy mergingStrategy) { 34 | if (slot < 0 || slot >= this.subItems.size()) 35 | return; 36 | this.subItems.set(slot, stack.copy()); 37 | if (slot < this.firstSlot) 38 | this.firstSlot = slot; 39 | if (this.merged.isEmpty()) { 40 | if (mergingStrategy == ItemStackMergingStrategy.IGNORE) { 41 | this.merged = copyStackWithoutComponents(stack); 42 | } else { 43 | this.merged = stack.copy(); 44 | } 45 | } else { 46 | this.merged.grow(stack.getCount()); 47 | } 48 | } 49 | 50 | private static ItemStack copyStackWithoutComponents(ItemStack stack) { 51 | if (stack.isEmpty()) { 52 | return ItemStack.EMPTY; 53 | } else { 54 | var copy = new ItemStack(stack.getItem(), stack.getCount()); 55 | copy.setPopTime(stack.getPopTime()); 56 | return copy; 57 | } 58 | } 59 | 60 | public ItemStack getSubStack(int slot) { 61 | if (slot < 0 || slot >= this.subItems.size()) 62 | return ItemStack.EMPTY; 63 | return this.subItems.get(slot); 64 | } 65 | 66 | public int size() { 67 | return this.subItems.size(); 68 | } 69 | 70 | @Override 71 | public int compareTo(MergedItemStack other) { 72 | int ret = this.merged.getCount() - other.merged.getCount(); 73 | 74 | if (ret != 0) 75 | return ret; 76 | return other.firstSlot - this.firstSlot; 77 | } 78 | 79 | public static List mergeInventory(List inventory, int maxSize, 80 | ItemStackMergingStrategy mergingStrategy) { 81 | var items = new ArrayList(); 82 | 83 | if (!inventory.isEmpty()) { 84 | var mergedStacks = new HashMap(); 85 | 86 | for (int i = 0, len = inventory.size(); i < len; ++i) { 87 | ItemStack s = inventory.get(i); 88 | 89 | if (s.isEmpty()) 90 | continue; 91 | 92 | ItemKey k = new ItemKey(s, mergingStrategy != ItemStackMergingStrategy.SEPARATE); 93 | MergedItemStack mergedStack = mergedStacks.get(k); 94 | 95 | if (mergedStack == null) { 96 | mergedStack = new MergedItemStack(maxSize); 97 | mergedStacks.put(k, mergedStack); 98 | } 99 | mergedStack.add(s, i, mergingStrategy); 100 | } 101 | 102 | items.addAll(mergedStacks.values()); 103 | items.sort(Comparator.reverseOrder()); 104 | } 105 | return items; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /neoforge/src/main/java/com/misterpemodder/shulkerboxtooltip/neoforge/ShulkerBoxTooltipClientImpl.java: -------------------------------------------------------------------------------- 1 | package com.misterpemodder.shulkerboxtooltip.neoforge; 2 | 3 | import com.misterpemodder.shulkerboxtooltip.ShulkerBoxTooltip; 4 | import com.misterpemodder.shulkerboxtooltip.ShulkerBoxTooltipClient; 5 | import com.misterpemodder.shulkerboxtooltip.api.PreviewContext; 6 | import com.misterpemodder.shulkerboxtooltip.api.ShulkerBoxTooltipApi; 7 | import com.misterpemodder.shulkerboxtooltip.impl.config.ConfigurationHandler; 8 | import com.misterpemodder.shulkerboxtooltip.impl.config.gui.ConfigScreen; 9 | import com.misterpemodder.shulkerboxtooltip.impl.hook.GuiGraphicsExtensions; 10 | import com.misterpemodder.shulkerboxtooltip.impl.tooltip.PreviewClientTooltipComponent; 11 | import com.misterpemodder.shulkerboxtooltip.impl.tooltip.PreviewTooltipComponent; 12 | import com.mojang.datafixers.util.Either; 13 | import net.minecraft.network.chat.FormattedText; 14 | import net.minecraft.world.inventory.tooltip.TooltipComponent; 15 | import net.neoforged.api.distmarker.Dist; 16 | import net.neoforged.api.distmarker.OnlyIn; 17 | import net.neoforged.bus.api.SubscribeEvent; 18 | import net.neoforged.fml.ModLoadingContext; 19 | import net.neoforged.fml.common.EventBusSubscriber; 20 | import net.neoforged.fml.event.lifecycle.FMLClientSetupEvent; 21 | import net.neoforged.neoforge.client.event.RegisterClientTooltipComponentFactoriesEvent; 22 | import net.neoforged.neoforge.client.event.RenderTooltipEvent; 23 | import net.neoforged.neoforge.client.gui.IConfigScreenFactory; 24 | 25 | @OnlyIn(Dist.CLIENT) 26 | @EventBusSubscriber(value = Dist.CLIENT, modid = ShulkerBoxTooltip.MOD_ID) 27 | public final class ShulkerBoxTooltipClientImpl extends ShulkerBoxTooltipClient { 28 | @SubscribeEvent 29 | public static void onClientSetup(FMLClientSetupEvent event) { 30 | event.enqueueWork(() -> { 31 | ShulkerBoxTooltipClient.init(); 32 | 33 | // Register the config screen 34 | ModLoadingContext.get().registerExtensionPoint(IConfigScreenFactory.class, 35 | () -> (client, parent) -> new ConfigScreen<>(parent, ShulkerBoxTooltip.configTree, 36 | ShulkerBoxTooltip.savedConfig, ConfigurationHandler::saveToFile)); 37 | }); 38 | } 39 | 40 | @SubscribeEvent 41 | public static void onRegisterTooltipComponentFactories(RegisterClientTooltipComponentFactoriesEvent event) { 42 | // PreviewTooltipComponent -> PreviewClientTooltipComponent conversion 43 | event.register(PreviewTooltipComponent.class, PreviewClientTooltipComponent::new); 44 | } 45 | 46 | @SubscribeEvent 47 | private static void onRenderTooltipTexture(RenderTooltipEvent.Texture event) { 48 | var extendedGraphics = (GuiGraphicsExtensions) event.getGraphics(); 49 | extendedGraphics.setTooltipTopYPosition(event.getY()); 50 | } 51 | 52 | @SubscribeEvent 53 | private static void onGatherTooltipComponents(RenderTooltipEvent.GatherComponents event) { 54 | var context = PreviewContext.builder(event.getItemStack()).withOwner( 55 | ShulkerBoxTooltipClient.client == null ? null : ShulkerBoxTooltipClient.client.player).build(); 56 | var elements = event.getTooltipElements(); 57 | 58 | // Add the preview window at the beginning of the tooltip 59 | if (ShulkerBoxTooltipApi.isPreviewAvailable(context)) { 60 | var data = new PreviewTooltipComponent( 61 | ShulkerBoxTooltipApi.getPreviewProviderForStackWithOverrides(context.stack()), context); 62 | 63 | elements.add(1, Either.right(data)); 64 | } 65 | 66 | // Add the tooltip hints at the end of the tooltip 67 | ShulkerBoxTooltipClient.modifyStackTooltip(context.stack(), 68 | toAdd -> toAdd.stream().map(Either::left).forEach(elements::add)); 69 | } 70 | } 71 | --------------------------------------------------------------------------------