package net.minestom.scratch.tools; import net.kyori.adventure.text.Component; import net.minestom.server.coordinate.Point; import net.minestom.server.coordinate.Pos; import net.minestom.server.instance.block.BlockFace; import net.minestom.server.item.ItemStack; import net.minestom.server.network.packet.client.ClientPacket; import net.minestom.server.network.packet.client.play.*; import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.network.packet.server.play.*; import net.minestom.server.utils.Direction; import net.minestom.server.utils.chunk.ChunkUtils; import net.minestom.server.utils.inventory.PlayerInventoryUtils; import java.util.List; import java.util.function.Consumer; public interface ScratchFeature extends Consumer { record Messaging(Mapping mapping) implements ScratchFeature { @Override public void accept(ClientPacket packet) { if (packet instanceof ClientChatMessagePacket chatMessagePacket) { final String message = chatMessagePacket.message(); final Component formatted = mapping.formatMessage(message); mapping.signal(new SystemChatPacket(formatted, false)); } } public interface Mapping { Component formatMessage(String message); void signal(ServerPacket.Play packet); } } record Movement(Mapping mapping) implements ScratchFeature { @Override public void accept(ClientPacket packet) { final int id = mapping.id(); if (packet instanceof ClientPlayerPositionAndRotationPacket positionAndRotationPacket) { final Pos position = positionAndRotationPacket.position(); mapping.updatePosition(position); mapping.signalMovement(new EntityTeleportPacket(id, position, positionAndRotationPacket.onGround())); mapping.signalMovement(new EntityHeadLookPacket(id, position.yaw())); } else if (packet instanceof ClientPlayerPositionPacket positionPacket) { final Pos position = mapping.position().withCoord(positionPacket.position()); mapping.updatePosition(position); mapping.signalMovement(new EntityTeleportPacket(id, position, positionPacket.onGround())); } else if (packet instanceof ClientPlayerRotationPacket rotationPacket) { final Pos position = mapping.position().withView(rotationPacket.yaw(), rotationPacket.pitch()); mapping.signalMovement(new EntityRotationPacket(id, position.yaw(), position.pitch(), rotationPacket.onGround())); mapping.signalMovement(new EntityHeadLookPacket(id, position.yaw())); } } public interface Mapping { int id(); Pos position(); void updatePosition(Pos position); void signalMovement(ServerPacket.Play packet); } } record ChunkLoading(Mapping mapping) implements ScratchFeature { @Override public void accept(ClientPacket packet) { final Pos oldPosition = mapping.oldPosition(); Pos position = null; if (packet instanceof ClientPlayerPositionAndRotationPacket positionAndRotationPacket) { position = positionAndRotationPacket.position(); } else if (packet instanceof ClientPlayerPositionPacket positionPacket) { position = Pos.fromPoint(positionPacket.position()); } if (position == null || position.sameChunk(oldPosition)) return; final int oldChunkX = oldPosition.chunkX(); final int oldChunkZ = oldPosition.chunkZ(); final int newChunkX = position.chunkX(); final int newChunkZ = position.chunkZ(); mapping.sendPacket(new UpdateViewPositionPacket(newChunkX, newChunkZ)); ChunkUtils.forDifferingChunksInRange(newChunkX, newChunkZ, oldChunkX, oldChunkZ, mapping.viewDistance(), (x, z) -> mapping.sendPacket(mapping.chunkPacket(x, z)), (x, z) -> mapping.sendPacket(new UnloadChunkPacket(x, z))); } public interface Mapping { int viewDistance(); Pos oldPosition(); ChunkDataPacket chunkPacket(int chunkX, int chunkZ); void sendPacket(ServerPacket.Play packet); } } record EntityInteract(Mapping mapping) implements ScratchFeature { @Override public void accept(ClientPacket packet) { if (packet instanceof ClientInteractEntityPacket interactEntityPacket) { final int targetId = interactEntityPacket.targetId(); final ClientInteractEntityPacket.Type type = interactEntityPacket.type(); if (type instanceof ClientInteractEntityPacket.Interact interact) { mapping.right(targetId); } else if (type instanceof ClientInteractEntityPacket.Attack attack) { mapping.left(targetId); } } } public interface Mapping { void left(int id); void right(int id); } } record BlockInteract(Mapping mapping) implements ScratchFeature { @Override public void accept(ClientPacket packet) { if (packet instanceof ClientPlayerDiggingPacket diggingPacket) { final ClientPlayerDiggingPacket.Status status = diggingPacket.status(); final Point blockPosition = diggingPacket.blockPosition(); mapping.acknowledge(new AcknowledgeBlockChangePacket(diggingPacket.sequence())); switch (status) { case STARTED_DIGGING -> { if (mapping.creative()) { mapping.breakBlock(blockPosition); } } } } else if (packet instanceof ClientPlayerBlockPlacementPacket blockPlacementPacket) { final Point blockPosition = blockPlacementPacket.blockPosition(); final BlockFace blockFace = blockPlacementPacket.blockFace(); final Direction direction = blockFace.toDirection(); mapping.placeBlock(blockPosition.add(direction.normalX(), direction.normalY(), direction.normalZ())); } } public interface Mapping { boolean creative(); void breakBlock(Point point); void placeBlock(Point point); void acknowledge(ServerPacket.Play packet); } } record InventoryHandling(Mapping mapping) implements ScratchFeature { @Override public void accept(ClientPacket packet) { if (packet instanceof ClientClickWindowPacket( byte windowId, int stateId, short slot, byte button, ClientClickWindowPacket.ClickType clickType, List changedSlots, ItemStack clickedItem )) { for (ClientClickWindowPacket.ChangedSlot changedSlot : changedSlots) { final int internalSlot = PlayerInventoryUtils.convertPlayerInventorySlot(changedSlot.slot(), PlayerInventoryUtils.OFFSET); mapping.setPlayerItem(internalSlot, changedSlot.item()); } mapping.setCursorItem(clickedItem); } } public interface Mapping { void setPlayerItem(int slot, ItemStack itemStack); void setCursorItem(ItemStack itemStack); } } }