diff --git a/run/server.properties b/run/server.properties deleted file mode 100644 index 5e1ab98..0000000 --- a/run/server.properties +++ /dev/null @@ -1,58 +0,0 @@ -#Minecraft server properties -#Tue Nov 11 20:14:28 EST 2025 -allow-flight=true -allow-nether=true -broadcast-console-to-ops=true -broadcast-rcon-to-ops=true -difficulty=peaceful -enable-command-block=true -enable-jmx-monitoring=false -enable-query=false -enable-rcon=false -enable-status=true -enforce-secure-profile=true -enforce-whitelist=false -entity-broadcast-range-percentage=100 -force-gamemode=false -function-permission-level=2 -gamemode=survival -generate-structures=true -generator-settings={} -hardcore=false -hide-online-players=false -initial-disabled-packs= -initial-enabled-packs=vanilla -level-name=world -level-seed= -level-type=minecraft\:flat -max-chained-neighbor-updates=1000000 -max-players=20 -max-tick-time=60000 -max-world-size=29999984 -motd=A Minecraft Server -network-compression-threshold=256 -online-mode=false -op-permission-level=4 -player-idle-timeout=0 -prevent-proxy-connections=false -pvp=true -query.port=25565 -rate-limit=0 -rcon.password= -rcon.port=25575 -require-resource-pack=false -resource-pack= -resource-pack-prompt= -resource-pack-sha1= -server-ip= -server-port=25565 -simulation-distance=10 -spawn-animals=true -spawn-monsters=true -spawn-npcs=true -spawn-protection=16 -sync-chunk-writes=true -text-filtering-config= -use-native-transport=true -view-distance=10 -white-list=false diff --git a/src/generated/resources/assets/gtmutils/lang/en_ud.json b/src/generated/resources/assets/gtmutils/lang/en_ud.json index ea9022c..f1db64c 100644 --- a/src/generated/resources/assets/gtmutils/lang/en_ud.json +++ b/src/generated/resources/assets/gtmutils/lang/en_ud.json @@ -1,4 +1,13 @@ { + "behaviour.paintspray.infinite.fluid_storage": "ᗺɯ %s / %sɟ§ :ʇuıɐԀq§", + "behaviour.paintspray.infinite.status.color": "%sɟ§ :ǝpoWㄥ§", + "behaviour.paintspray.infinite.status.solvent": "ʇuǝʌןoS", + "behaviour.paintspray.infinite.tooltip.current_color": "%s :ɹoןoƆ ʇuǝɹɹnƆ", + "behaviour.paintspray.infinite.tooltip.info": "˙ʇuıɐd oʇㄥ§ ʞɔıןɔ-ʇɥbıᴚǝ§", + "behaviour.paintspray.infinite.tooltip.info_1": "˙ʇuıɐd uıɐɥɔ oʇㄥ§ ʞɔoןq ǝןqɐʇuıɐd uo ʞɔıןɔ-ʇɥbıᴚ + ʇɟıɥSǝ§", + "behaviour.paintspray.infinite.tooltip.info_2": "˙nuǝW uoıʇɔǝןǝS ɹoןoƆ uǝdo oʇㄥ§ ʞɔıןɔ-ʇɥbıᴚ + ʇɟıɥSǝ§", + "behaviour.paintspray.infinite.tooltip.solvent": ")buıuɐǝןƆ( ʇuǝʌןoSq§ :ǝpoWㄥ§", + "behaviour.paintspray.solvent.short": "ʇuǝʌןoS", "block.gtmutils.enlarged_stocking_bus.desc.0": "ʞɹoʍʇǝu ƎW ǝɥʇ ɯoɹɟ ʎןʇɔǝɹıp sɯǝʇı sǝʌǝıɹʇǝᴚ", "block.gtmutils.enlarged_stocking_bus.desc.1": "ʇsıן buıʞɔoʇs ǝbɹɐן-ɐɹʇxƎ", "block.gtmutils.enlarged_stocking_bus.desc.2": "sɯǝʇı ƎW ʇuɐpunqɐ ʇsoɯ ǝɥʇ ɥʇıʍ ʇsıן ǝɥʇ ןןıɟ uɐɔ ןןnԀ-oʇnⱯ", @@ -72,6 +81,7 @@ "config.gtmutils.option.enlargedStockingSizeRows": "sʍoᴚǝzıSbuıʞɔoʇSpǝbɹɐןuǝ", "config.gtmutils.option.expandedBuffersEnabled": "pǝןqɐuƎsɹǝɟɟnᗺpǝpuɐdxǝ", "config.gtmutils.option.features": "sǝɹnʇɐǝɟ", + "config.gtmutils.option.infiniteSprayCanEnabled": "pǝןqɐuƎuɐƆʎɐɹdSǝʇıuıɟuı", "config.gtmutils.option.omnibreakerEnabled": "pǝןqɐuƎɹǝʞɐǝɹqıuɯo", "config.gtmutils.option.omnibreakerEnergyCapacity": "ʎʇıɔɐdɐƆʎbɹǝuƎɹǝʞɐǝɹqıuɯo", "config.gtmutils.option.omnibreakerTier": "ɹǝı⟘ɹǝʞɐǝɹqıuɯo", @@ -95,6 +105,7 @@ "gtmutils.multiblock.pterb_machine.coolant_usage": "puoɔǝs ɹǝd %s ɟo qɯ%s suıɐɹᗡɔ§", "gtmutils.pterb.current_frequency": "%s :ʎɔuǝnbǝɹɟ ʇuǝɹɹnƆ", "gtmutils.pterb_machine.invalid_frequency": "¡0 ʎɔuǝnbǝɹɟ uo ʞɹoʍ ʇou ןןıʍ s⟘ⱯM", + "gui.gtmutils.color_select.title": "ɹoןoƆ ʇɔǝןǝS", "item.gtceu.tool.ev_buzzsaw": ")ΛƎ( ʍɐszznᗺ %s", "item.gtceu.tool.ev_chainsaw": ")ΛƎ( ʍɐsuıɐɥƆ %s", "item.gtceu.tool.ev_screwdriver": ")ΛƎ( ɹǝʌıɹpʍǝɹɔS ɔıɹʇɔǝןƎ %s", @@ -125,6 +136,7 @@ "item.gtmutils.cupronickel_credit": "ʇıpǝɹƆ ןǝʞɔıuoɹdnƆ", "item.gtmutils.doge_coin": "uıoƆ ǝboᗡ", "item.gtmutils.gold_credit": "ʇıpǝɹƆ pןo⅁", + "item.gtmutils.infinite_spray_can": "uɐƆ ʎɐɹdS ǝʇıuıɟuI", "item.gtmutils.luv_power_unit": "ʇıu∩ ɹǝʍoԀ ΛnꞀ", "item.gtmutils.naquadah_credit": "ʇıpǝɹƆ ɥɐpɐnbɐN", "item.gtmutils.neutronium_credit": "ʇıpǝɹƆ ɯnıuoɹʇnǝN", @@ -134,6 +146,8 @@ "item.gtmutils.silver_credit": "ʇıpǝɹƆ ɹǝʌןıS", "item.gtmutils.zpm_power_unit": "ʇıu∩ ɹǝʍoԀ WԀZ", "itemGroup.gtmutils.creative_tab": "sǝıʇıןıʇ∩ uɹǝpoW ɥɔǝ⟘bǝɹ⅁", + "key.categories.gtmutils": "sןıʇ∩ W⟘⅁", + "key.gtmutils.spray_can_menu": "nuǝW ןɐıpɐᴚ uɐƆ ʎɐɹdS uǝdO", "material.gtmutils.quantum_coolant": "ʇuɐןooƆ ɯnʇuɐnὉ", "tooltip.omnibreaker.can_break_anything": "¡⅁NIH⟘ʎNⱯ ǝuıɯ-ɐʇsuı uɐɔ ɹǝʞɐǝɹq-ıuɯO ǝɥ⟘", "tooltip.omnibreaker.charge_status": "∩Ǝ %s / ∩Ǝ %s :ʎbɹǝuƎ", diff --git a/src/generated/resources/assets/gtmutils/lang/en_us.json b/src/generated/resources/assets/gtmutils/lang/en_us.json index ad78d24..0cd7e35 100644 --- a/src/generated/resources/assets/gtmutils/lang/en_us.json +++ b/src/generated/resources/assets/gtmutils/lang/en_us.json @@ -1,4 +1,13 @@ { + "behaviour.paintspray.infinite.fluid_storage": "§bPaint: §f%s / %s mB", + "behaviour.paintspray.infinite.status.color": "§7Mode: §f%s", + "behaviour.paintspray.infinite.status.solvent": "Solvent", + "behaviour.paintspray.infinite.tooltip.current_color": "Current Color: %s", + "behaviour.paintspray.infinite.tooltip.info": "§eRight-click §7to paint.", + "behaviour.paintspray.infinite.tooltip.info_1": "§eShift + Right-click on paintable block §7to chain paint.", + "behaviour.paintspray.infinite.tooltip.info_2": "§eShift + Right-click §7to open Color Selection Menu.", + "behaviour.paintspray.infinite.tooltip.solvent": "§7Mode: §bSolvent (Cleaning)", + "behaviour.paintspray.solvent.short": "Solvent", "block.gtmutils.enlarged_stocking_bus.desc.0": "Retrieves items directly from the ME network", "block.gtmutils.enlarged_stocking_bus.desc.1": "Extra-large stocking list", "block.gtmutils.enlarged_stocking_bus.desc.2": "Auto-Pull can fill the list with the most abundant ME items", @@ -72,6 +81,7 @@ "config.gtmutils.option.enlargedStockingSizeRows": "enlargedStockingSizeRows", "config.gtmutils.option.expandedBuffersEnabled": "expandedBuffersEnabled", "config.gtmutils.option.features": "features", + "config.gtmutils.option.infiniteSprayCanEnabled": "infiniteSprayCanEnabled", "config.gtmutils.option.omnibreakerEnabled": "omnibreakerEnabled", "config.gtmutils.option.omnibreakerEnergyCapacity": "omnibreakerEnergyCapacity", "config.gtmutils.option.omnibreakerTier": "omnibreakerTier", @@ -95,6 +105,7 @@ "gtmutils.multiblock.pterb_machine.coolant_usage": "§cDrains %smb of %s per second", "gtmutils.pterb.current_frequency": "Current frequency: %s", "gtmutils.pterb_machine.invalid_frequency": "WATs will not work on frequency 0!", + "gui.gtmutils.color_select.title": "Select Color", "item.gtceu.tool.ev_buzzsaw": "%s Buzzsaw (EV)", "item.gtceu.tool.ev_chainsaw": "%s Chainsaw (EV)", "item.gtceu.tool.ev_screwdriver": "%s Electric Screwdriver (EV)", @@ -125,6 +136,7 @@ "item.gtmutils.cupronickel_credit": "Cupronickel Credit", "item.gtmutils.doge_coin": "Doge Coin", "item.gtmutils.gold_credit": "Gold Credit", + "item.gtmutils.infinite_spray_can": "Infinite Spray Can", "item.gtmutils.luv_power_unit": "LuV Power Unit", "item.gtmutils.naquadah_credit": "Naquadah Credit", "item.gtmutils.neutronium_credit": "Neutronium Credit", @@ -134,6 +146,8 @@ "item.gtmutils.silver_credit": "Silver Credit", "item.gtmutils.zpm_power_unit": "ZPM Power Unit", "itemGroup.gtmutils.creative_tab": "GregTech Modern Utilities", + "key.categories.gtmutils": "GTM Utils", + "key.gtmutils.spray_can_menu": "Open Spray Can Radial Menu", "material.gtmutils.quantum_coolant": "Quantum Coolant", "tooltip.omnibreaker.can_break_anything": "The Omni-breaker can insta-mine ANYTHING!", "tooltip.omnibreaker.charge_status": "Energy: %s EU / %s EU", diff --git a/src/generated/resources/assets/gtmutils/models/item/infinite_spray_can.json b/src/generated/resources/assets/gtmutils/models/item/infinite_spray_can.json new file mode 100644 index 0000000..9c558f5 --- /dev/null +++ b/src/generated/resources/assets/gtmutils/models/item/infinite_spray_can.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:item/handheld", + "textures": { + "layer0": "gtmutils:item/tools/infinite_spray_can" + } +} \ No newline at end of file diff --git a/src/main/java/net/neganote/gtutilities/GregTechModernUtilities.java b/src/main/java/net/neganote/gtutilities/GregTechModernUtilities.java index 00346f1..3cbcdc5 100644 --- a/src/main/java/net/neganote/gtutilities/GregTechModernUtilities.java +++ b/src/main/java/net/neganote/gtutilities/GregTechModernUtilities.java @@ -17,6 +17,7 @@ import net.minecraft.world.item.Items; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; +import net.minecraftforge.client.event.RegisterGuiOverlaysEvent; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.eventbus.api.IEventBus; import net.minecraftforge.eventbus.api.SubscribeEvent; @@ -24,6 +25,7 @@ import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; +import net.neganote.gtutilities.client.renderer.SprayCanHudOverlay; import net.neganote.gtutilities.client.renderer.UtilShaders; import net.neganote.gtutilities.common.data.UtilPlaceholders; import net.neganote.gtutilities.common.item.UtilItems; @@ -34,6 +36,7 @@ import net.neganote.gtutilities.common.tools.UtilToolConnection; import net.neganote.gtutilities.config.UtilConfig; import net.neganote.gtutilities.datagen.UtilDatagen; +import net.neganote.gtutilities.network.UtilsNetwork; import com.tterrag.registrate.util.entry.RegistryEntry; import org.apache.logging.log4j.LogManager; @@ -108,6 +111,7 @@ public static ResourceLocation id(String path) { private void commonSetup(final FMLCommonSetupEvent event) { event.enqueueWork(() -> { + UtilsNetwork.init(); LOGGER.info("Hello from common setup! This is *after* registries are done, so we can do this:"); LOGGER.info("Look, I found a {}!", Items.DIAMOND); }); @@ -133,6 +137,17 @@ public void clientSetup(final FMLClientSetupEvent event) { }); } + @Mod.EventBusSubscriber(modid = GregTechModernUtilities.MOD_ID, + bus = Mod.EventBusSubscriber.Bus.MOD, + value = Dist.CLIENT) + public static class ClientModBusEvents { + + @SubscribeEvent + public static void registerGuiOverlays(RegisterGuiOverlaysEvent event) { + event.registerAboveAll("spray_can_info", SprayCanHudOverlay.HUD_SPRAY_CAN); + } + } + // You MUST have this for custom materials. // Remember to register them not to GT's namespace, but your own. private void addMaterialRegistries(MaterialRegistryEvent event) { diff --git a/src/main/java/net/neganote/gtutilities/client/event/ClientTickHandler.java b/src/main/java/net/neganote/gtutilities/client/event/ClientTickHandler.java new file mode 100644 index 0000000..d01db04 --- /dev/null +++ b/src/main/java/net/neganote/gtutilities/client/event/ClientTickHandler.java @@ -0,0 +1,84 @@ +package net.neganote.gtutilities.client.event; + +import net.minecraft.client.Minecraft; +import net.minecraft.sounds.SoundEvents; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.phys.HitResult; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.client.event.InputEvent; +import net.minecraftforge.event.TickEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; +import net.neganote.gtutilities.GregTechModernUtilities; +import net.neganote.gtutilities.client.gui.screen.ColorRadialMenuScreen; +import net.neganote.gtutilities.client.keybind.UtilKeybinds; +import net.neganote.gtutilities.common.item.InfiniteSprayCanItem; +import net.neganote.gtutilities.network.UtilsNetwork; +import net.neganote.gtutilities.network.packet.SelectColorPacket; + +@Mod.EventBusSubscriber(modid = GregTechModernUtilities.MOD_ID, + bus = Mod.EventBusSubscriber.Bus.FORGE, + value = Dist.CLIENT) +public class ClientTickHandler { + + @SubscribeEvent + public static void onClientTick(TickEvent.ClientTickEvent event) { + if (event.phase != TickEvent.Phase.END) return; + + Minecraft mc = Minecraft.getInstance(); + if (mc.player == null || mc.level == null) return; + + if (UtilKeybinds.SPRAY_CAN_MENU.consumeClick()) { + ItemStack stack = mc.player.getMainHandItem(); + if (stack.getItem() instanceof InfiniteSprayCanItem) { + mc.setScreen(new ColorRadialMenuScreen(InteractionHand.MAIN_HAND)); + } + } + } + + @SubscribeEvent + public static void onMouseScroll(InputEvent.MouseScrollingEvent event) { + Minecraft mc = Minecraft.getInstance(); + + if (mc.player == null || mc.screen != null) return; + + if (mc.options.keyShift.isDown()) { + ItemStack stack = mc.player.getMainHandItem(); + if (stack.getItem() instanceof InfiniteSprayCanItem) { + double scrollDelta = event.getScrollDelta(); + + event.setCanceled(true); + + int currentColor = stack.getOrCreateTag().getInt("color"); + if (!stack.getOrCreateTag().contains("color")) currentColor = -1; + + int direction = scrollDelta > 0 ? 1 : -1; + int nextColor = currentColor + direction; + + if (nextColor < -1) nextColor = 15; + if (nextColor > 15) nextColor = -1; + + UtilsNetwork.CHANNEL.sendToServer(new SelectColorPacket(InteractionHand.MAIN_HAND, nextColor)); + + mc.player.playSound(SoundEvents.UI_BUTTON_CLICK.value(), 0.1f, 1.5f + (nextColor * 0.05f)); + } + } + } + + @SubscribeEvent + public static void onRightClick(InputEvent.InteractionKeyMappingTriggered event) { + Minecraft mc = Minecraft.getInstance(); + if (mc.player == null) return; + + if (!event.isUseItem()) return; + ItemStack stack = mc.player.getMainHandItem(); + if (!(stack.getItem() instanceof InfiniteSprayCanItem)) return; + + if (!mc.player.isShiftKeyDown()) return; + if (mc.hitResult == null || mc.hitResult.getType() != HitResult.Type.MISS) return; + + event.setCanceled(true); + mc.setScreen(new ColorRadialMenuScreen(InteractionHand.MAIN_HAND)); + } +} diff --git a/src/main/java/net/neganote/gtutilities/client/gui/screen/ColorRadialMenuScreen.java b/src/main/java/net/neganote/gtutilities/client/gui/screen/ColorRadialMenuScreen.java new file mode 100644 index 0000000..f3ae627 --- /dev/null +++ b/src/main/java/net/neganote/gtutilities/client/gui/screen/ColorRadialMenuScreen.java @@ -0,0 +1,132 @@ +package net.neganote.gtutilities.client.gui.screen; + +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.resources.sounds.SimpleSoundInstance; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.sounds.SoundEvents; +import net.minecraft.util.Mth; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.item.DyeColor; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.neganote.gtutilities.network.UtilsNetwork; +import net.neganote.gtutilities.network.packet.SelectColorPacket; + +import com.mojang.blaze3d.systems.RenderSystem; +import org.jetbrains.annotations.NotNull; + +public class ColorRadialMenuScreen extends Screen { + + private final InteractionHand hand; + private static final int RADIUS = 85; + private static final int INNER_RADIUS = 20; + private static final int ITEM_RADIUS = 60; + + public ColorRadialMenuScreen(InteractionHand hand) { + super(Component.translatable("gui.gtmutils.color_select.title")); + this.hand = hand; + } + + @Override + public void render(@NotNull GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTicks) { + this.renderBackground(guiGraphics); + + int centerX = this.width / 2; + int centerY = this.height / 2; + + DyeColor[] colors = DyeColor.values(); + int numSegments = colors.length; + float segmentAngle = 360.0f / numSegments; + + double distToCenter = Math.sqrt(Math.pow(mouseX - centerX, 2) + Math.pow(mouseY - centerY, 2)); + double mouseAngle = Math.toDegrees(Math.atan2(mouseY - centerY, mouseX - centerX)); + mouseAngle = (mouseAngle + 360 + 90) % 360; + + boolean hoveringSolvent = distToCenter < INNER_RADIUS; + + int solventColor = hoveringSolvent ? 0xFFFF55 : 0xFFFFFF; + Component solventText = Component.translatable("behaviour.paintspray.solvent.short"); + guiGraphics.drawCenteredString(this.font, solventText, centerX, centerY - 4, solventColor); + + for (int i = 0; i < numSegments; i++) { + float startAngleDeg = i * segmentAngle; + float endAngleDeg = (i + 1) * segmentAngle; + boolean hoveringThis = !hoveringSolvent && distToCenter <= RADIUS && distToCenter > INNER_RADIUS && + mouseAngle >= startAngleDeg && mouseAngle < endAngleDeg; + + float midAngleRad = (float) Math.toRadians(startAngleDeg - 90); + float itemAngleRad = (float) Math.toRadians(((startAngleDeg + endAngleDeg) / 2.0f) - 90); + + int x1 = centerX + (int) (Mth.cos(midAngleRad) * INNER_RADIUS); + int y1 = centerY + (int) (Mth.sin(midAngleRad) * INNER_RADIUS); + + guiGraphics.fill(x1, y1, x1 + 1, y1 + 1, 0xAAFFFFFF); + + int itemX = centerX + (int) (Mth.cos(itemAngleRad) * ITEM_RADIUS) - 8; + int itemY = centerY + (int) (Mth.sin(itemAngleRad) * ITEM_RADIUS) - 8; + + if (hoveringThis) { + RenderSystem.setShaderColor(1, 1, 1, 0.2f); + guiGraphics.fill(itemX - 4, itemY - 4, itemX + 20, itemY + 20, 0x44FFFFFF); + RenderSystem.setShaderColor(1, 1, 1, 1); + } + + ItemStack dyeStack = new ItemStack(getDyeItem(colors[i])); + guiGraphics.renderFakeItem(dyeStack, itemX, itemY); + + if (hoveringThis) { + guiGraphics.renderTooltip(this.font, + Component.translatable("color.minecraft." + colors[i].getSerializedName()), mouseX, mouseY); + } + } + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + int centerX = this.width / 2; + int centerY = this.height / 2; + + double distToCenter = Math.sqrt(Math.pow(mouseX - centerX, 2) + Math.pow(mouseY - centerY, 2)); + + if (distToCenter < INNER_RADIUS) { + sendColorSelection(-1); + return true; + } + + if (distToCenter <= RADIUS) { + double angle = Math.toDegrees(Math.atan2(mouseY - centerY, mouseX - centerX)); + angle = (angle + 360 + 90) % 360; + + DyeColor[] colors = DyeColor.values(); + int selectedSegment = (int) (angle / (360.0f / colors.length)); + + if (selectedSegment >= 0 && selectedSegment < colors.length) { + sendColorSelection(selectedSegment); + return true; + } + } + + return super.mouseClicked(mouseX, mouseY, button); + } + + private void sendColorSelection(int id) { + UtilsNetwork.CHANNEL.sendToServer(new SelectColorPacket(hand, id)); + if (this.minecraft != null) { + this.minecraft.getSoundManager().play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK, 1.0F)); + } + this.onClose(); + } + + private Item getDyeItem(DyeColor color) { + ResourceLocation id = new ResourceLocation("minecraft", color.getSerializedName() + "_dye"); + return BuiltInRegistries.ITEM.get(id); + } + + @Override + public boolean isPauseScreen() { + return false; + } +} diff --git a/src/main/java/net/neganote/gtutilities/client/keybind/UtilKeybinds.java b/src/main/java/net/neganote/gtutilities/client/keybind/UtilKeybinds.java new file mode 100644 index 0000000..64d771a --- /dev/null +++ b/src/main/java/net/neganote/gtutilities/client/keybind/UtilKeybinds.java @@ -0,0 +1,22 @@ +package net.neganote.gtutilities.client.keybind; + +import net.minecraft.client.KeyMapping; +import net.minecraftforge.client.event.RegisterKeyMappingsEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; + +import com.mojang.blaze3d.platform.InputConstants; +import org.lwjgl.glfw.GLFW; + +public class UtilKeybinds { + + public static final KeyMapping SPRAY_CAN_MENU = new KeyMapping( + "key.gtmutils.spray_can_menu", + InputConstants.Type.KEYSYM, + GLFW.GLFW_KEY_UNKNOWN, + "key.categories.gtmutils"); + + @SubscribeEvent + public static void register(RegisterKeyMappingsEvent event) { + event.register(SPRAY_CAN_MENU); + } +} diff --git a/src/main/java/net/neganote/gtutilities/client/renderer/SprayCanHudOverlay.java b/src/main/java/net/neganote/gtutilities/client/renderer/SprayCanHudOverlay.java new file mode 100644 index 0000000..6db6e15 --- /dev/null +++ b/src/main/java/net/neganote/gtutilities/client/renderer/SprayCanHudOverlay.java @@ -0,0 +1,40 @@ +package net.neganote.gtutilities.client.renderer; + +import net.minecraft.client.Minecraft; +import net.minecraft.network.chat.Component; +import net.minecraft.world.item.DyeColor; +import net.minecraft.world.item.ItemStack; +import net.minecraftforge.client.gui.overlay.IGuiOverlay; +import net.neganote.gtutilities.common.item.InfiniteSprayCanBehaviour; +import net.neganote.gtutilities.common.item.InfiniteSprayCanItem; + +public class SprayCanHudOverlay { + + public static final IGuiOverlay HUD_SPRAY_CAN = (gui, guiGraphics, partialTick, width, height) -> { + Minecraft mc = Minecraft.getInstance(); + if (mc.player == null) return; + + ItemStack stack = mc.player.getMainHandItem(); + if (!(stack.getItem() instanceof InfiniteSprayCanItem)) { + stack = mc.player.getOffhandItem(); + } + + if (stack.getItem() instanceof InfiniteSprayCanItem) { + DyeColor color = InfiniteSprayCanBehaviour.getColor(stack); + Component text; + + if (color != null) { + Component colorName = Component.translatable("color.minecraft." + color.getSerializedName()); + text = Component.translatable("behaviour.paintspray.infinite.status.color", colorName); + } else { + text = Component.translatable("behaviour.paintspray.infinite.status.solvent"); + } + + int x = width / 2; + int y = height - 53; + + int textWidth = mc.font.width(text); + guiGraphics.drawString(mc.font, text, x - (textWidth / 2), y, 0xFFFFFF); + } + }; +} diff --git a/src/main/java/net/neganote/gtutilities/common/item/InfiniteSprayCanBehaviour.java b/src/main/java/net/neganote/gtutilities/common/item/InfiniteSprayCanBehaviour.java new file mode 100644 index 0000000..4359fd1 --- /dev/null +++ b/src/main/java/net/neganote/gtutilities/common/item/InfiniteSprayCanBehaviour.java @@ -0,0 +1,380 @@ +package net.neganote.gtutilities.common.item; + +import com.gregtechceu.gtceu.GTCEu; +import com.gregtechceu.gtceu.api.blockentity.IPaintable; +import com.gregtechceu.gtceu.api.item.component.IAddInformation; +import com.gregtechceu.gtceu.api.item.component.IInteractionItem; +import com.gregtechceu.gtceu.api.pipenet.IPipeNode; +import com.gregtechceu.gtceu.common.data.GTSoundEntries; +import com.gregtechceu.gtceu.config.ConfigHolder; +import com.gregtechceu.gtceu.data.recipe.CustomTags; +import com.gregtechceu.gtceu.utils.BreadthFirstBlockSearch; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.BlockTags; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.DyeColor; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.TooltipFlag; +import net.minecraft.world.item.context.UseOnContext; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.StainedGlassBlock; +import net.minecraft.world.level.block.StainedGlassPaneBlock; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.ShulkerBoxBlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.Property; +import net.minecraftforge.common.Tags; +import net.minecraftforge.common.util.TriPredicate; + +import appeng.api.implementations.blockentities.IColorableBlockEntity; +import com.google.common.collect.ImmutableMap; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class InfiniteSprayCanBehaviour implements IInteractionItem, IAddInformation { + + private static final ImmutableMap GLASS_MAP; + private static final ImmutableMap GLASS_PANE_MAP; + private static final ImmutableMap TERRACOTTA_MAP; + private static final ImmutableMap WOOL_MAP; + private static final ImmutableMap CARPET_MAP; + private static final ImmutableMap CONCRETE_MAP; + private static final ImmutableMap CONCRETE_POWDER_MAP; + private static final ImmutableMap SHULKER_BOX_MAP; + + private static Block getBlock(DyeColor color, String postfix) { + ResourceLocation id = new ResourceLocation("minecraft", color.getSerializedName() + "_" + postfix); + return BuiltInRegistries.BLOCK.get(id); + } + + static { + ImmutableMap.Builder glassBuilder = ImmutableMap.builder(); + ImmutableMap.Builder glassPaneBuilder = ImmutableMap.builder(); + ImmutableMap.Builder terracottaBuilder = ImmutableMap.builder(); + ImmutableMap.Builder woolBuilder = ImmutableMap.builder(); + ImmutableMap.Builder carpetBuilder = ImmutableMap.builder(); + ImmutableMap.Builder concreteBuilder = ImmutableMap.builder(); + ImmutableMap.Builder concretePowderBuilder = ImmutableMap.builder(); + ImmutableMap.Builder shulkerBoxBuilder = ImmutableMap.builder(); + + for (DyeColor color : DyeColor.values()) { + glassBuilder.put(color, getBlock(color, "stained_glass")); + glassPaneBuilder.put(color, getBlock(color, "stained_glass_pane")); + terracottaBuilder.put(color, getBlock(color, "terracotta")); + woolBuilder.put(color, getBlock(color, "wool")); + carpetBuilder.put(color, getBlock(color, "carpet")); + concreteBuilder.put(color, getBlock(color, "concrete")); + concretePowderBuilder.put(color, getBlock(color, "concrete_powder")); + shulkerBoxBuilder.put(color, getBlock(color, "shulker_box")); + } + GLASS_MAP = glassBuilder.build(); + GLASS_PANE_MAP = glassPaneBuilder.build(); + TERRACOTTA_MAP = terracottaBuilder.build(); + WOOL_MAP = woolBuilder.build(); + CARPET_MAP = carpetBuilder.build(); + CONCRETE_MAP = concreteBuilder.build(); + CONCRETE_POWDER_MAP = concretePowderBuilder.build(); + SHULKER_BOX_MAP = shulkerBoxBuilder.build(); + } + + private static final TriPredicate paintablePredicate = (parent, child, dir) -> { + if (parent == null) return true; + if (!parent.getClass().equals(child.getClass())) { + return false; + } + return parent.getPaintingColor() == child.getPaintingColor(); + }; + + @SuppressWarnings("rawtypes") + private static final TriPredicate gtPipePredicate = (parent, child, direction) -> { + if (parent == null) return true; + if (!paintablePredicate.test(parent, child, direction)) { + return false; + } + return parent.isConnected(direction) && child.isConnected(direction.getOpposite()); + }; + + @Override + public InteractionResult onItemUseFirst(ItemStack stack, UseOnContext context) { + Player player = context.getPlayer(); + Level level = context.getLevel(); + if (player == null) return InteractionResult.PASS; + + DyeColor selectedColor = getColor(stack); + int maxBlocksToRecolor = player.isShiftKeyDown() ? ConfigHolder.INSTANCE.tools.sprayCanChainLength : 1; + + var pos = context.getClickedPos(); + var first = level.getBlockEntity(pos); + + if (first == null || !handleSpecialBlockEntities(first, selectedColor, maxBlocksToRecolor, context)) { + handleBlocks(pos, selectedColor, maxBlocksToRecolor, context); + } + + GTSoundEntries.SPRAY_CAN_TOOL.play(level, null, player.position(), 1.0f, 1.0f); + + return InteractionResult.sidedSuccess(level.isClientSide); + } + + @Override + public void appendHoverText(ItemStack stack, @Nullable Level level, List tooltip, TooltipFlag flag) { + DyeColor currentColor = getColor(stack); + if (currentColor != null) { + tooltip.add(Component.translatable("behaviour.paintspray.infinite.tooltip.current_color", + Component.translatable("color.minecraft." + currentColor.getSerializedName()))); + } else { + tooltip.add(Component.translatable("behaviour.paintspray.infinite.tooltip.solvent")); + } + tooltip.add(Component.translatable("behaviour.paintspray.infinite.tooltip.info")); + tooltip.add(Component.translatable("behaviour.paintspray.infinite.tooltip.info_1")); + tooltip.add(Component.translatable("behaviour.paintspray.infinite.tooltip.info_2")); + } + + public static void setColor(ItemStack stack, @Nullable DyeColor color) { + if (color == null) { + stack.getOrCreateTag().putInt("color", -1); + } else { + stack.getOrCreateTag().putInt("color", color.ordinal()); + } + } + + @Nullable + public static DyeColor getColor(ItemStack stack) { + CompoundTag tag = stack.getTag(); + if (tag == null || !tag.contains("color") || tag.getInt("color") == -1) { + return null; + } + int ordinal = tag.getInt("color"); + DyeColor[] colors = DyeColor.values(); + if (ordinal >= 0 && ordinal < colors.length) { + return colors[ordinal]; + } + return null; + } + + private void handleBlocks(BlockPos start, DyeColor color, int limit, UseOnContext context) { + final var level = context.getLevel(); + var collected = BreadthFirstBlockSearch + .conditionalBlockPosSearch(start, + (parent, child) -> parent == null || + level.getBlockState(child).is(level.getBlockState(parent).getBlock()), + limit, limit * 6); + for (var pos : collected) { + tryPaintBlock(level, pos, color); + } + } + + private boolean handleSpecialBlockEntities(BlockEntity first, DyeColor color, int limit, UseOnContext context) { + var player = context.getPlayer(); + if (player == null) return false; + + if (GTCEu.Mods.isAE2Loaded() && first instanceof IColorableBlockEntity colorable) { + appeng.api.util.AEColor ae2Color = color == null ? + appeng.api.util.AEColor.TRANSPARENT : + appeng.api.util.AEColor.values()[color.ordinal()]; + + if (colorable.getColor() != ae2Color) { + colorable.recolourBlock(null, ae2Color, player); + } + return true; + } + + else if (first instanceof IPipeNode pipe) { + var collected = BreadthFirstBlockSearch.conditionalSearch(IPipeNode.class, pipe, + first.getLevel(), IPipeNode::getPipePos, + gtPipePredicate, limit, limit * 6); + paintPaintables(collected, color); + return true; + } else if (first instanceof IPaintable paintable) { + var collected = BreadthFirstBlockSearch.conditionalSearch(IPaintable.class, paintable, + first.getLevel(), p -> ((BlockEntity) p).getBlockPos(), + paintablePredicate, limit, limit * 6); + paintPaintables(collected, color); + return true; + } + + else if (first instanceof ShulkerBoxBlockEntity shulkerBox) { + var tag = shulkerBox.saveWithoutMetadata(); + var level = first.getLevel(); + var pos = first.getBlockPos(); + recolorBlockNoState(SHULKER_BOX_MAP, color, level, pos, Blocks.SHULKER_BOX); + assert level != null; + if (level.getBlockEntity(pos) instanceof ShulkerBoxBlockEntity newShulker) { + newShulker.load(tag); + } + return true; + } + + return false; + } + + private void paintPaintables(Set paintables, DyeColor color) { + for (var c : paintables) { + paintPaintable(c, color); + } + } + + private void tryPaintBlock(Level level, BlockPos pos, DyeColor color) { + var blockState = level.getBlockState(pos); + var block = blockState.getBlock(); + if (color == null) { + tryStripBlockColor(level, pos, block); + return; + } + if (!recolorBlockState(level, pos, color)) { + tryPaintSpecialBlock(level, pos, block, color); + } + } + + private void tryPaintSpecialBlock(Level world, BlockPos pos, Block block, DyeColor color) { + if (block.defaultBlockState().is(Tags.Blocks.GLASS)) { + if (recolorBlockNoState(GLASS_MAP, color, world, pos, Blocks.GLASS)) { + return; + } + } + if (block.defaultBlockState().is(Tags.Blocks.GLASS_PANES)) { + if (recolorBlockNoState(GLASS_PANE_MAP, color, world, pos, Blocks.GLASS_PANE)) { + return; + } + } + if (block.defaultBlockState().is(BlockTags.TERRACOTTA)) { + if (recolorBlockNoState(TERRACOTTA_MAP, color, world, pos, Blocks.TERRACOTTA)) { + return; + } + } + if (block.defaultBlockState().is(BlockTags.WOOL)) { + if (recolorBlockNoState(WOOL_MAP, color, world, pos, null)) { + return; + } + } + if (block.defaultBlockState().is(BlockTags.WOOL_CARPETS)) { + if (recolorBlockNoState(CARPET_MAP, color, world, pos, null)) { + return; + } + } + if (block.defaultBlockState().is(CustomTags.CONCRETE_BLOCK)) { + if (recolorBlockNoState(CONCRETE_MAP, color, world, pos, null)) { + return; + } + } + if (block.defaultBlockState().is(CustomTags.CONCRETE_POWDER_BLOCK)) { + recolorBlockNoState(CONCRETE_POWDER_MAP, color, world, pos, null); + } + } + + private static void paintPaintable(IPaintable paintable, DyeColor color) { + if (color == null) { + if (!paintable.isPainted()) { + return; + } + paintable.setPaintingColor(IPaintable.UNPAINTED_COLOR); + } else if (paintable.getPaintingColor() != color.getMapColor().col) { + paintable.setPaintingColor(color.getMapColor().col); + } + } + + private static boolean recolorBlockNoState(Map map, @Nullable DyeColor color, + Level level, BlockPos pos, Block defaultBlock) { + Block newBlock = map.getOrDefault(color, defaultBlock); + if (newBlock == Blocks.AIR) newBlock = defaultBlock; + + BlockState old = level.getBlockState(pos); + if (newBlock != null && newBlock != old.getBlock()) { + BlockState state = newBlock.defaultBlockState(); + for (Property property : old.getProperties()) { + if (!state.hasProperty(property)) continue; + state.setValue(property, old.getValue(property)); + } + level.setBlockAndUpdate(pos, state); + return true; + } + return false; + } + + private static void tryStripBlockColor(Level world, BlockPos pos, Block block) { + if (block instanceof StainedGlassBlock) { + world.setBlockAndUpdate(pos, Blocks.GLASS.defaultBlockState()); + return; + } + if (block instanceof StainedGlassPaneBlock) { + world.setBlockAndUpdate(pos, Blocks.GLASS_PANE.defaultBlockState()); + return; + } + if (block.defaultBlockState().is(BlockTags.TERRACOTTA) && block != Blocks.TERRACOTTA) { + world.setBlockAndUpdate(pos, Blocks.TERRACOTTA.defaultBlockState()); + return; + } + if (block.defaultBlockState().is(BlockTags.WOOL) && block != Blocks.WHITE_WOOL) { + world.setBlockAndUpdate(pos, Blocks.WHITE_WOOL.defaultBlockState()); + return; + } + if (block.defaultBlockState().is(BlockTags.WOOL_CARPETS) && block != Blocks.WHITE_CARPET) { + world.setBlockAndUpdate(pos, Blocks.WHITE_CARPET.defaultBlockState()); + return; + } + if (block.defaultBlockState().is(CustomTags.CONCRETE_BLOCK) && block != Blocks.WHITE_CONCRETE) { + world.setBlockAndUpdate(pos, Blocks.WHITE_CONCRETE.defaultBlockState()); + return; + } + if (block.defaultBlockState().is(CustomTags.CONCRETE_POWDER_BLOCK) && block != Blocks.WHITE_CONCRETE_POWDER) { + world.setBlockAndUpdate(pos, Blocks.WHITE_CONCRETE_POWDER.defaultBlockState()); + return; + } + + BlockState state = world.getBlockState(pos); + for (Property prop : state.getProperties()) { + if (prop.getValueClass() == DyeColor.class) { + BlockState defaultState = block.defaultBlockState(); + DyeColor defaultColor = DyeColor.WHITE; + try { + defaultColor = (DyeColor) defaultState.getValue(prop); + } catch (IllegalArgumentException ignored) {} + recolorBlockState(world, pos, defaultColor); + return; + } + } + } + + private static boolean recolorBlockState(Level level, BlockPos pos, DyeColor color) { + BlockState state = level.getBlockState(pos); + for (Property property : state.getProperties()) { + if (property.getValueClass() == DyeColor.class) { + level.setBlockAndUpdate(pos, state.setValue(property, color)); + return true; + } + } + return false; + } + + @Override + public boolean onEntitySwing(@NotNull ItemStack stack, @NotNull LivingEntity entity) { + return false; + } + + @Override + public boolean hurtEnemy(@NotNull ItemStack stack, @NotNull LivingEntity target, @NotNull LivingEntity attacker) { + return false; + } + + @Override + public @NotNull InteractionResult interactLivingEntity(@NotNull ItemStack stack, + @NotNull Player player, + @NotNull LivingEntity interactionTarget, + @NotNull InteractionHand hand) { + return InteractionResult.PASS; + } +} diff --git a/src/main/java/net/neganote/gtutilities/common/item/InfiniteSprayCanItem.java b/src/main/java/net/neganote/gtutilities/common/item/InfiniteSprayCanItem.java new file mode 100644 index 0000000..8f2f6b4 --- /dev/null +++ b/src/main/java/net/neganote/gtutilities/common/item/InfiniteSprayCanItem.java @@ -0,0 +1,33 @@ +package net.neganote.gtutilities.common.item; + +import net.minecraft.network.chat.Component; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.TooltipFlag; +import net.minecraft.world.item.context.UseOnContext; +import net.minecraft.world.level.Level; + +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public class InfiniteSprayCanItem extends Item { + + private final InfiniteSprayCanBehaviour behaviour = new InfiniteSprayCanBehaviour(); + + public InfiniteSprayCanItem(Properties properties) { + super(properties); + } + + @Override + public InteractionResult onItemUseFirst(ItemStack stack, UseOnContext context) { + return this.behaviour.onItemUseFirst(stack, context); + } + + @Override + public void appendHoverText(ItemStack stack, @Nullable Level level, List tooltipComponents, + TooltipFlag isAdvanced) { + this.behaviour.appendHoverText(stack, level, tooltipComponents, isAdvanced); + } +} diff --git a/src/main/java/net/neganote/gtutilities/common/item/UtilItems.java b/src/main/java/net/neganote/gtutilities/common/item/UtilItems.java index 6665032..1f62288 100644 --- a/src/main/java/net/neganote/gtutilities/common/item/UtilItems.java +++ b/src/main/java/net/neganote/gtutilities/common/item/UtilItems.java @@ -26,6 +26,8 @@ public class UtilItems { public static ItemEntry OMNIBREAKER = null; public static int OMNIBREAKER_TIER = UtilConfig.INSTANCE.features.omnibreakerTier; + public static ItemEntry INFINITE_SPRAY_CAN = null; + public static ItemEntry ANCIENT_GOLD_COIN = null; public static ItemEntry CHOCOLATE_COIN = null; public static ItemEntry COPPER_CREDIT = null; @@ -64,6 +66,15 @@ public class UtilItems { OMNIBREAKER_TIER), new PrecisionBreakBehavior(OMNIBREAKER_TIER))) .register(); + + if (UtilConfig.INSTANCE.features.infiniteSprayCanEnabled || GTCEu.isDataGen()) { + INFINITE_SPRAY_CAN = REGISTRATE + .item("infinite_spray_can", InfiniteSprayCanItem::new) + .lang("Infinite Spray Can") + .properties(p -> p.stacksTo(1)) + .model((ctx, prov) -> prov.handheld(ctx, prov.modLoc("item/tools/infinite_spray_can"))) + .register(); + } } if (UtilConfig.INSTANCE.features.coinsEnabled || GTCEu.isDataGen()) { diff --git a/src/main/java/net/neganote/gtutilities/config/UtilConfig.java b/src/main/java/net/neganote/gtutilities/config/UtilConfig.java index 325a9e2..1c7c228 100644 --- a/src/main/java/net/neganote/gtutilities/config/UtilConfig.java +++ b/src/main/java/net/neganote/gtutilities/config/UtilConfig.java @@ -111,6 +111,11 @@ public static class FeatureConfigs { @Configurable.Comment({ "Whether the Tag Stocking Input Bus/Hatch are enabled. If AE2 is not loaded, this config will not load the machines regardless." }) public boolean tagStockingEnabled = true; + + @Configurable + @Configurable.Comment({ + "Whether the Infinite Spray Can should be enabled." }) + public boolean infiniteSprayCanEnabled = true; } public static boolean coolantEnabled() { diff --git a/src/main/java/net/neganote/gtutilities/datagen/lang/UtilLangHandler.java b/src/main/java/net/neganote/gtutilities/datagen/lang/UtilLangHandler.java index e0a509c..3396ad6 100644 --- a/src/main/java/net/neganote/gtutilities/datagen/lang/UtilLangHandler.java +++ b/src/main/java/net/neganote/gtutilities/datagen/lang/UtilLangHandler.java @@ -126,6 +126,25 @@ public static void init(RegistrateLangProvider provider) { provider.add("config.jade.plugin_gtmutils.me_expanded_pattern_buffer_proxy_info.desc", "Shows a snapshot of expanded ME pattern buffer proxy contents in Jade"); + // Keybind + provider.add("key.categories.gtmutils", "GTM Utils"); + provider.add("key.gtmutils.spray_can_menu", "Open Spray Can Radial Menu"); + + // Infinite Spray Can + provider.add("gui.gtmutils.color_select.title", "Select Color"); + provider.add("behaviour.paintspray.solvent.short", "Solvent"); + provider.add("behaviour.paintspray.infinite.status.solvent", "Solvent"); + provider.add("behaviour.paintspray.infinite.tooltip.solvent", "§7Mode: §bSolvent (Cleaning)"); + provider.add("behaviour.paintspray.infinite.tooltip.info", + "§eRight-click §7to paint."); + provider.add("behaviour.paintspray.infinite.tooltip.info_1", + "§eShift + Right-click on paintable block §7to chain paint."); + provider.add("behaviour.paintspray.infinite.tooltip.info_2", + "§eShift + Right-click §7to open Color Selection Menu."); + provider.add("behaviour.paintspray.infinite.fluid_storage", "§bPaint: §f%s / %s mB"); + provider.add("behaviour.paintspray.infinite.status.color", "§7Mode: §f%s"); + provider.add("behaviour.paintspray.infinite.tooltip.current_color", "Current Color: %s"); + dfs(provider, new HashSet<>(), UtilConfig.CONFIG_HOLDER.getValueMap()); } diff --git a/src/main/java/net/neganote/gtutilities/network/UtilsNetwork.java b/src/main/java/net/neganote/gtutilities/network/UtilsNetwork.java new file mode 100644 index 0000000..456f8da --- /dev/null +++ b/src/main/java/net/neganote/gtutilities/network/UtilsNetwork.java @@ -0,0 +1,30 @@ +package net.neganote.gtutilities.network; + +import net.minecraft.resources.ResourceLocation; +import net.minecraftforge.network.NetworkDirection; +import net.minecraftforge.network.NetworkRegistry; +import net.minecraftforge.network.simple.SimpleChannel; +import net.neganote.gtutilities.network.packet.SelectColorPacket; + +import java.util.Optional; + +public class UtilsNetwork { + + private static final String PROTOCOL = "1"; + public static final SimpleChannel CHANNEL = NetworkRegistry.newSimpleChannel( + new ResourceLocation("gtm_utils", "main"), + () -> PROTOCOL, + PROTOCOL::equals, + PROTOCOL::equals); + + private static int id = 0; + + public static void init() { + CHANNEL.registerMessage(id++, + SelectColorPacket.class, + SelectColorPacket::encode, + SelectColorPacket::decode, + SelectColorPacket::handle, + Optional.of(NetworkDirection.PLAY_TO_SERVER)); + } +} diff --git a/src/main/java/net/neganote/gtutilities/network/packet/SelectColorPacket.java b/src/main/java/net/neganote/gtutilities/network/packet/SelectColorPacket.java new file mode 100644 index 0000000..644f603 --- /dev/null +++ b/src/main/java/net/neganote/gtutilities/network/packet/SelectColorPacket.java @@ -0,0 +1,52 @@ +package net.neganote.gtutilities.network.packet; + +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.item.DyeColor; +import net.minecraft.world.item.ItemStack; +import net.minecraftforge.network.NetworkEvent; +import net.neganote.gtutilities.common.item.InfiniteSprayCanBehaviour; +import net.neganote.gtutilities.common.item.InfiniteSprayCanItem; + +import java.util.function.Supplier; + +public class SelectColorPacket { + + private final InteractionHand hand; + private final int selectedIndex; + + public SelectColorPacket(InteractionHand hand, int selectedIndex) { + this.hand = hand; + this.selectedIndex = selectedIndex; + } + + public void encode(FriendlyByteBuf buf) { + buf.writeEnum(hand); + buf.writeVarInt(selectedIndex); + } + + public static SelectColorPacket decode(FriendlyByteBuf buf) { + return new SelectColorPacket(buf.readEnum(InteractionHand.class), buf.readVarInt()); + } + + public static void handle(SelectColorPacket msg, Supplier ctx) { + ctx.get().enqueueWork(() -> { + ServerPlayer player = ctx.get().getSender(); + if (player == null) return; + + ItemStack stack = player.getItemInHand(msg.hand); + if (stack.getItem() instanceof InfiniteSprayCanItem) { + DyeColor[] colors = DyeColor.values(); + DyeColor selectedColor = null; + + if (msg.selectedIndex >= 0 && msg.selectedIndex < colors.length) { + selectedColor = colors[msg.selectedIndex]; + } + + InfiniteSprayCanBehaviour.setColor(stack, selectedColor); + } + }); + ctx.get().setPacketHandled(true); + } +} diff --git a/src/main/resources/assets/gtmutils/textures/item/tools/infinite_spray_can.png b/src/main/resources/assets/gtmutils/textures/item/tools/infinite_spray_can.png new file mode 100644 index 0000000..fa5c968 Binary files /dev/null and b/src/main/resources/assets/gtmutils/textures/item/tools/infinite_spray_can.png differ diff --git a/src/main/resources/assets/gtmutils/textures/item/tools/infinite_spray_can.png.mcmeta b/src/main/resources/assets/gtmutils/textures/item/tools/infinite_spray_can.png.mcmeta new file mode 100644 index 0000000..9261996 --- /dev/null +++ b/src/main/resources/assets/gtmutils/textures/item/tools/infinite_spray_can.png.mcmeta @@ -0,0 +1,6 @@ +{ + "animation": { + "interpolate": true, + "frametime": 3 + } +} \ No newline at end of file