From f8d7b9a78116079ce507b386fbbf653ca38259e8 Mon Sep 17 00:00:00 2001 From: luk Date: Tue, 3 Feb 2026 16:42:45 +0000 Subject: [PATCH] Add PlaceFluidEvent and patch PlaceFluidInteraction - PlaceFluidEvent: cancellable ECS event for fluid placement - PlaceFluidInteraction: fire event before placing fluid --- .../event/events/ecs/PlaceFluidEvent.java | 33 +++++ .../config/client/PlaceFluidInteraction.java | 140 ++++++++++++++++++ 2 files changed, 173 insertions(+) create mode 100644 src/com/hypixel/hytale/server/core/event/events/ecs/PlaceFluidEvent.java create mode 100644 src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/PlaceFluidInteraction.java diff --git a/src/com/hypixel/hytale/server/core/event/events/ecs/PlaceFluidEvent.java b/src/com/hypixel/hytale/server/core/event/events/ecs/PlaceFluidEvent.java new file mode 100644 index 00000000..06d3618c --- /dev/null +++ b/src/com/hypixel/hytale/server/core/event/events/ecs/PlaceFluidEvent.java @@ -0,0 +1,33 @@ +package com.hypixel.hytale.server.core.event.events.ecs; + +import com.hypixel.hytale.component.system.CancellableEcsEvent; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.asset.type.fluid.Fluid; + +import javax.annotation.Nonnull; + +/** + * Event fired when a player attempts to place a fluid (e.g., water bucket). + * This event is cancellable - if cancelled, the fluid will not be placed. + */ +public class PlaceFluidEvent extends CancellableEcsEvent { + @Nonnull + private final Vector3i targetBlock; + @Nonnull + private final Fluid fluid; + + public PlaceFluidEvent(@Nonnull Vector3i targetBlock, @Nonnull Fluid fluid) { + this.targetBlock = targetBlock; + this.fluid = fluid; + } + + @Nonnull + public Vector3i getTargetBlock() { + return this.targetBlock; + } + + @Nonnull + public Fluid getFluid() { + return this.fluid; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/PlaceFluidInteraction.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/PlaceFluidInteraction.java new file mode 100644 index 00000000..92849582 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/PlaceFluidInteraction.java @@ -0,0 +1,140 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.client; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.WaitForDataFrom; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockFace; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.fluid.Fluid; +import com.hypixel.hytale.server.core.asset.type.fluid.FluidTicker; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.event.events.ecs.PlaceFluidEvent; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.SimpleInteraction; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.section.FluidSection; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PlaceFluidInteraction extends SimpleBlockInteraction { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + PlaceFluidInteraction.class, PlaceFluidInteraction::new, SimpleInteraction.CODEC + ) + .documentation("Places the current or given block.") + .append( + new KeyedCodec<>("FluidToPlace", Codec.STRING), + (placeBlockInteraction, blockTypeKey) -> placeBlockInteraction.fluidKey = blockTypeKey, + placeBlockInteraction -> placeBlockInteraction.fluidKey + ) + .addValidatorLate(() -> Fluid.VALIDATOR_CACHE.getValidator().late()) + .add() + .append( + new KeyedCodec<>("RemoveItemInHand", Codec.BOOLEAN), + (placeBlockInteraction, aBoolean) -> placeBlockInteraction.removeItemInHand = aBoolean, + placeBlockInteraction -> placeBlockInteraction.removeItemInHand + ) + .add() + .build(); + @Nullable + protected String fluidKey; + protected boolean removeItemInHand = true; + + public PlaceFluidInteraction() { + } + + @Nullable + public String getFluidKey() { + return this.fluidKey; + } + + @Nonnull + @Override + public WaitForDataFrom getWaitForDataFrom() { + return WaitForDataFrom.Client; + } + + @Override + protected void interactWithBlock( + @Nonnull World world, + @Nonnull CommandBuffer commandBuffer, + @Nonnull InteractionType type, + @Nonnull InteractionContext context, + @Nullable ItemStack itemInHand, + @Nonnull Vector3i targetBlock, + @Nonnull CooldownHandler cooldownHandler + ) { + Store store = world.getChunkStore().getStore(); + int fluidIndex = Fluid.getFluidIdOrUnknown(this.fluidKey, "Unknown fluid: %s", this.fluidKey); + Fluid fluid = Fluid.getAssetMap().getAsset(fluidIndex); + Vector3i target = targetBlock; + BlockType targetBlockType = world.getBlockType(targetBlock); + if (FluidTicker.isSolid(targetBlockType)) { + target = targetBlock.clone(); + BlockFace face = BlockFace.fromProtocolFace(context.getClientState().blockFace); + target.add(face.getDirection()); + } + + // Fire PlaceFluidEvent and check if cancelled + Ref entityRef = context.getEntity(); + PlaceFluidEvent event = new PlaceFluidEvent(target, fluid); + commandBuffer.invoke(entityRef, event); + if (event.isCancelled()) { + return; + } + + Ref section = world.getChunkStore() + .getChunkSectionReference(ChunkUtil.chunkCoordinate(target.x), ChunkUtil.chunkCoordinate(target.y), ChunkUtil.chunkCoordinate(target.z)); + if (section != null) { + FluidSection fluidSectionComponent = store.getComponent(section, FluidSection.getComponentType()); + if (fluidSectionComponent != null) { + fluidSectionComponent.setFluid(target.x, target.y, target.z, fluid, (byte) fluid.getMaxFluidLevel()); + Ref chunkColumn = world.getChunkStore().getChunkReference(ChunkUtil.indexChunkFromBlock(target.x, target.z)); + if (chunkColumn != null) { + BlockChunk blockChunkComponent = store.getComponent(chunkColumn, BlockChunk.getComponentType()); + blockChunkComponent.setTicking(target.x, target.y, target.z, true); + Ref ref = context.getEntity(); + Player playerComponent = commandBuffer.getComponent(ref, Player.getComponentType()); + PlayerRef playerRefComponent = commandBuffer.getComponent(ref, PlayerRef.getComponentType()); + if ((playerRefComponent == null || playerComponent != null && playerComponent.getGameMode() == GameMode.Adventure) + && itemInHand.getQuantity() == 1 + && this.removeItemInHand) { + context.setHeldItem(null); + } + } + } + } + } + + @Override + protected void simulateInteractWithBlock( + @Nonnull InteractionType type, @Nonnull InteractionContext context, @Nullable ItemStack itemInHand, @Nonnull World world, @Nonnull Vector3i targetBlock + ) { + } + + @Override + public boolean needsRemoteSync() { + return true; + } + + @Nonnull + @Override + public String toString() { + return "PlaceBlockInteraction{blockTypeKey=" + this.fluidKey + ", removeItemInHand=" + this.removeItemInHand + "} " + super.toString(); + } +}