Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
426 views
in Technique[技术] by (71.8m points)

minecraft - How to change what block is at certain coordinates?

I am trying to recreate the mod in the YouTuber TommyInnit's video "Minecraft’s Laser Eye Mod Is Hilarious" as me and my friends wish to use it but we couldn't find it on the internet, and I have taken code from here for raycasting and also set up a keybind, but I cannot figure out how to setb the block you are looking at. I have tried to manage to get the block and set it but I can only find how to make new blocks that don't yet exist. My code is the following, with the block code being on line 142:

package net.laser.eyes;

import org.lwjgl.glfw.GLFW;
import net.minecraft.client.options.KeyBinding;
import net.minecraft.client.util.InputUtil;
import net.minecraft.text.LiteralText;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper;
import net.fabricmc.fabric.api.event.client.ClientTickCallback;
import net.fabricmc.api.*;
import net.fabricmc.fabric.api.client.rendering.v1.*;
import net.minecraft.block.*;
import net.minecraft.client.*;
import net.minecraft.client.gui.*;
import net.minecraft.client.util.math.*;
import net.minecraft.entity.*;
import net.minecraft.entity.decoration.*;
import net.minecraft.entity.projectile.*;
import net.minecraft.text.*;
import net.minecraft.util.hit.*;
import net.minecraft.util.math.*;
import net.minecraft.world.*;

public class main implements ModInitializer {
    @Override
    public void onInitialize() {
        KeyBinding binding1 = KeyBindingHelper.registerKeyBinding(new KeyBinding("key.laser-eyes.shoot", InputUtil.Type.KEYSYM, GLFW.GLFW_KEY_R, "key.category.laser.eyes"));
        HudRenderCallback.EVENT.register(main::displayBoundingBox);
        ClientTickCallback.EVENT.register(client -> {
            while (binding1.wasPressed()) {
                client.player.sendMessage(new LiteralText("Key 1 was pressed!"), false);
            }
        });
    }

    private static long lastCalculationTime = 0;
    private static boolean lastCalculationExists = false;
    private static int lastCalculationMinX = 0;
    private static int lastCalculationMinY = 0;
    private static int lastCalculationWidth = 0;
    private static int lastCalculationHeight = 0;

    private static void displayBoundingBox(MatrixStack matrixStack, float tickDelta) {
        long currentTime = System.currentTimeMillis();
        if(lastCalculationExists && currentTime - lastCalculationTime < 1000/45) {
            drawHollowFill(matrixStack, lastCalculationMinX, lastCalculationMinY,
                    lastCalculationWidth, lastCalculationHeight, 2, 0xffff0000);
            return;
        }

        lastCalculationTime = currentTime;

        MinecraftClient client = MinecraftClient.getInstance();
        int width = client.getWindow().getScaledWidth();
        int height = client.getWindow().getScaledHeight();
        Vec3d cameraDirection = client.cameraEntity.getRotationVec(tickDelta);
        double fov = client.options.fov;
        double angleSize = fov/height;
        Vector3f verticalRotationAxis = new Vector3f(cameraDirection);
        verticalRotationAxis.cross(Vector3f.POSITIVE_Y);
        if(!verticalRotationAxis.normalize()) {
            lastCalculationExists = false;
            return;
        }

        Vector3f horizontalRotationAxis = new Vector3f(cameraDirection);
        horizontalRotationAxis.cross(verticalRotationAxis);
        horizontalRotationAxis.normalize();

        verticalRotationAxis = new Vector3f(cameraDirection);
        verticalRotationAxis.cross(horizontalRotationAxis);

        HitResult hit = client.crosshairTarget;

        if (hit.getType() == HitResult.Type.MISS) {
            lastCalculationExists = false;
            return;
        }

        int minX = width;
        int maxX = 0;
        int minY = height;
        int maxY = 0;

        for(int y = 0; y < height; y +=2) {
            for(int x = 0; x < width; x+=2) {
                if(minX < x && x < maxX && minY < y && y < maxY) {
                    continue;
                }

                Vec3d direction = map(
                        (float) angleSize,
                        cameraDirection,
                        horizontalRotationAxis,
                        verticalRotationAxis,
                        x,
                        y,
                        width,
                        height
                );
                HitResult nextHit = rayTraceInDirection(client, tickDelta, direction);//TODO make less expensive

                if(nextHit == null) {
                    continue;
                }

                if(nextHit.getType() == HitResult.Type.MISS) {
                    continue;
                }

                if(nextHit.getType() != hit.getType()) {
                    continue;
                }

                if (nextHit.getType() == HitResult.Type.BLOCK) {
                    if(!((BlockHitResult) nextHit).getBlockPos().equals(((BlockHitResult) hit).getBlockPos())) {
                        continue;
                    }
                } else if(nextHit.getType() == HitResult.Type.ENTITY) {
                    if(!((EntityHitResult) nextHit).getEntity().equals(((EntityHitResult) hit).getEntity())) {
                        continue;
                    }
                }

                if(minX > x) minX = x;
                if(minY > y) minY = y;
                if(maxX < x) maxX = x;
                if(maxY < y) maxY = y;
            }
        }


        lastCalculationExists = true;
        lastCalculationMinX = minX;
        lastCalculationMinY = minY;
        lastCalculationWidth = maxX - minX;
        lastCalculationHeight = maxY - minY;

        drawHollowFill(matrixStack, minX, minY, maxX - minX, maxY - minY, 2, 0xffff0000);
        LiteralText text = new LiteralText("Bounding " + minX + " " + minY + " " + width + " " + height + ": ");
        client.player.sendMessage(text.append(getLabel(hit)), true);
        //SET THE BLOCK (maybe use hit.getPos(); to find it??)
        
    }

    private static void drawHollowFill(MatrixStack matrixStack, int x, int y, int width, int height, int stroke, int color) {
        matrixStack.push();
        matrixStack.translate(x-stroke, y-stroke, 0);
        width += stroke *2;
        height += stroke *2;
        DrawableHelper.fill(matrixStack, 0, 0, width, stroke, color);
        DrawableHelper.fill(matrixStack, width - stroke, 0, width, height, color);
        DrawableHelper.fill(matrixStack, 0, height - stroke, width, height, color);
        DrawableHelper.fill(matrixStack, 0, 0, stroke, height, color);
        matrixStack.pop();
    }

    private static Text getLabel(HitResult hit) {
        if(hit == null) return new LiteralText("null");

        switch (hit.getType()) {
            case BLOCK:
                return getLabelBlock((BlockHitResult) hit);
            case ENTITY:
                return getLabelEntity((EntityHitResult) hit);
            case MISS:
            default:
                return new LiteralText("null");
        }
    }

    private static Text getLabelEntity(EntityHitResult hit) {
        return hit.getEntity().getDisplayName();
    }

    private static Text getLabelBlock(BlockHitResult hit) {
        BlockPos blockPos = hit.getBlockPos();
        BlockState blockState = MinecraftClient.getInstance().world.getBlockState(blockPos);
        Block block = blockState.getBlock();
        return block.getName();
    }

    private static Vec3d map(float anglePerPixel, Vec3d center, Vector3f horizontalRotationAxis,
                             Vector3f verticalRotationAxis, int x, int y, int width, int height) {
        float horizontalRotation = (x - width/2f) * anglePerPixel;
        float verticalRotation = (y - height/2f) * anglePerPixel;

        final Vector3f temp2 = new Vector3f(center);
        temp2.rotate(verticalRotationAxis.getDegreesQuaternion(verticalRotation));
        temp2.rotate(horizontalRotationAxis.getDegreesQuaternion(horizontalRotation));
        return new Vec3d(temp2);
    }

    private static HitResult rayTraceInDirection(MinecraftClient client, float tickDelta, Vec3d direction) {
        Entity entity = client.getCameraEntity();
        if (entity == null || client.world == null) {
            return null;
        }

        double reachDistance = 5.0F;
        HitResult target = rayTrace(entity, reachDistance, tickDelta, false, direction);
        boolean tooFar = false;
        double extendedReach = 6.0D;
        reachDistance = extendedReach;

        Vec3d cameraPos = entity.getCameraPosVec(tickDelta);

        extendedReach = extendedReach * extendedReach;
        if (target != null) {
            extendedReach = target.getPos().squaredDistanceTo(cameraPos);
        }

        Vec3d vec3d3 = cameraPos.add(direction.multiply(reachDistance));
        Box box = entity
                .getBoundingBox()
                .stretch(entity.getRotationVec(1.0F).multiply(reachDistance))
                .expand(1.0D, 1.0D, 1.0D);
        EntityHitResult entityHitResult = ProjectileUtil.raycast(
                entity,
                cameraPos,
                vec3d3,
                box,
                (entityx) -> !entityx.isSpectator() && entityx.collides(),
                extendedReach
        );

        if (entityHitResult == null) {
            return target;
        }

        Entity entity2 = entityHitResult.getEntity();
        Vec3d hitPos = entityHitResult.getPos();
        if (cameraPos.squaredDistanceTo(hitPos) < extendedReach || target == null) {
            target = entityHitResult;
            if (entity2 instanceof LivingEntity || entity2 instanceof ItemFrameEntity) {
                client.targetedEntity = entity2;
            }
        }

        return target;
    }

    private static HitResult rayTrace(
            Entity entity,
            double maxDistance,
            float tickDelta,
            boolean includeFluids,
            Vec3d direction
    ) {
        Vec3d end = entity.getCameraPosVec(tickDelta).add(direction.multiply(maxDistance));
        return entity.world.raycast(new RaycastContext(
                entity.getCameraPosVec(tickDelta),
                end,
                RaycastContext.ShapeType.OUTLINE,
                includeFluids ? RaycastContext.FluidHandling.ANY : RaycastContext.FluidHandling.NONE,
                entity
        ));
    }



    }


question from:https://stackoverflow.com/questions/65903006/how-to-change-what-block-is-at-certain-coordinates

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

Firstly, I heavily recommend following the standard Java naming conventions as it will make your code more understandable for others.

The technical name for a block present in the world at a specific position is a "Block State", represented by the BlockState class.

You can only change the block state at a specific position on the server-side. Your raycasting code in ran on the client-side, so you need to use the Fabric Networking API. You can see the server-side Javadoc here and the client-side Javadoc here.

Thankfully, Fabric Wiki has a networking tutorial so you don't have to read all that Javadoc. The part that you're interested in is "sending packets to the server and receiving packets on the server".

Here's a guide specific for your use case:

Introduction to Networking

Minecraft operates in two different components; the client and the server.

The client is responsible for doing jobs such as rendering and GUIs, while the server is responsible for handling the world storage, entity AI etc. (talking about logical client and server here)

The physical server and physical client are the actual JAR files that are run.

The physical (dedicated) server contains only the logical server, while a physical client contains both a logical (integrated) server and a logical client.

A diagram that explains it can be found here.

So, the logical client cannot change the state of the logical server (e.g. block states in a world), so packets have to be sent from the client to the server in order for the server to respond.

The following code is only example code, and you shouldn't copy it! You should think about safety precautions like preventing cheat clients from changing every block. Probably one of the most important rules in networking: assume the client is lying.

The Fabric Networking API

Your starting points are ServerPlayNetworking and ClientPlayNetworking. They are the classes that help you send and receive packets.

Register a listener using registerGlobalReceiver, and send a packet by using send.

You first need an Identifier in order to separate your packet from other packets and make sure it is interpreted correctly. An Identifier like this is recommended to be put in a static field in your ModInitializer or a utility class.

public class MyMod implements ModInitializer {
    public static final Identifier SET_BLOCK_PACKET = new Identifier("modid", "setblock");
}

(Don't forget to replace modid with your mod ID)

You usually want to pass data with your packets (e.g. block position and block to change to), and you can do so with a PacketByteBuf.

Let's Piece This all Together

So, we have an Identifier. Let's send a packet!

Client-Side

We will start by creating a PacketByteBuf and writing the correct data:

private static void displayBoundingBox(MatrixStack matrixStack, float tickDelta) {
// ...
    PacketByteBuf data = PacketByteBufs.create();
    buf.writeBlockPos(hit.getPos());
    // Minecraft doesn't have a way to write a Block to a packet, so we will write the registry name instead
    buf.writeIdentifier(new Identifier("minecraft", "someblock" /*for example, "stone"*/));
}

And now sending the packet

// ...
ClientPlayNetworking.send(SET_BLOCK_PACKET, buf);

Server-Side

A packet with the SET_BLOCK_PACKET ID has been sent, But we also need to listen and receive it on the server-side. We can do that by using ServerPlayNetworking.registerGlobalReceiver:

@Override
public void onInitialize() {
    // ...
    // This code MUST be in onInitialize
    ServerPlayNetworking.registerGlobalReceiver(SET_BLOCK_PACKET, (server, player, handler, buf, sender) -> {

    });
}

We are using a lambda expression here. For more info about lambdas, Google is your friend.

When receiving a packet, code inside your lambda will be executed on the network thread. This code is not allowed to modify anything related to in-game logic (i.e. the world). For that, we will use server.execute(Runnable).

You should read the buf on the network thread, though.

ServerPlayNetworking.registerGlobalReceiver(SET_BLOCK_PACKET, (server, player, handler, buf, sender) -> {
    BlockPos pos = buf.readBlockPos(); // reads must be done in the same order
    Block blockToSet = Registry.BLOCK.get(buf.readIdentifier()); // reading using the identifier

    server.execute(() -> { // We are now on the main thread
        // In a normal mod, checks will be done here to prevent the client from setting blocks outside the world etc. but this is only example code

        player.getServerWorld().setBlockState(pos, blockToSet.getDefaultState()); // changing the block state
    });
});

Once again, you should prevent the client from sending invalid locations


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...