【问题标题】:How to change what block is at certain coordinates?如何更改特定坐标处的块?
【发布时间】:2021-04-30 08:02:18
【问题描述】:

我正在尝试在 YouTuber TommyInnit 的视频“Minecraft’s Laser Eye Mod Is Hilarious”中重新创建该模组,因为我和我的朋友希望使用它,但我们在互联网上找不到它,我已从 here 获取代码raycasting 并设置了一个键绑定,但我无法弄清楚如何设置您正在查看的块。我试图设法获取块并设置它,但我只能找到如何制作尚不存在的新块。我的代码如下,块代码在第 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
        ));
    }



    }


【问题讨论】:

    标签: minecraft minecraft-fabric


    【解决方案1】:

    首先,我强烈建议您关注the standard Java naming conventions,因为它会让您的代码更容易被其他人理解。

    世界上特定位置的方块的技术名称是“方块状态”,由BlockState 类表示。

    您只能在服务器端的特定位置更改块状态。您的光线投射代码在客户端上运行,因此您需要使用 Fabric Networking API。您可以看到服务器端 Javadoc here 和客户端 Javadoc here

    谢天谢地,Fabric Wikia networking tutorial,因此您不必阅读所有 Javadoc。您感兴趣的部分是“向服务器发送数据包并在服务器上接收数据包”。

    以下是针对您的用例的指南:

    网络简介

    Minecraft 在两个不同的组件中运行;客户端和服务器。

    客户端负责渲染和GUI等工作,而服务器负责处理世界存储、实体AI等(这里说逻辑客户端和服务器)

    物理服务器和物理客户端是实际运行的 JAR 文件。

    物理(专用)服务器仅包含逻辑服务器,而物理客户端包含逻辑(集成)服务器和逻辑客户端。

    可以在here找到解释它的图表。

    因此,逻辑客户端无法更改逻辑服务器的状态(例如,世界中的方块状态),因此必须将数据包从客户端发送到服务器,以便服务器响应。

    以下代码仅为示例代码,请勿复制!您应该考虑安全预防措施,例如防止作弊客户端更改每个块。可能是网络中最重要的规则之一:假设客户在撒谎。

    Fabric 网络 API

    您的起点是ServerPlayNetworkingClientPlayNetworking。它们是帮助您发送和接收数据包的类。

    使用registerGlobalReceiver注册监听器,使用send发送数据包。

    您首先需要一个Identifier 以将您的数据包与其他数据包分开并确保它被正确解释。建议将这样的 Identifier 放在您的 ModInitializer 或实用程序类的静态字段中。

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

    (别忘了用你的模组 ID 替换 modid

    您通常希望通过数据包传递数据(例如,块位置和要更改的块),您可以使用 PacketByteBuf 来实现。

    让我们一起把这一切拼凑起来

    所以,我们有一个Identifier。让我们发送一个数据包!

    客户端

    我们将首先创建一个PacketByteBuf 并写入正确的数据:

    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"*/));
    }
    

    现在发送数据包

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

    服务器端

    一个ID为SET_BLOCK_PACKET的数据包已经发送,但是我们还需要在服务器端监听和接收它。我们可以使用ServerPlayNetworking.registerGlobalReceiver

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

    我们在这里使用 lambda 表达式。有关 lambdas 的更多信息,Google 是您的朋友。

    当接收到一个数据包时,您的 lambda 中的代码将在 网络线程 上执行。此代码不允许修改与游戏内逻辑(即世界)相关的任何内容。为此,我们将使用server.execute(Runnable)

    不过,您应该阅读网络线程上的 buf。

    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
        });
    });
    

    再一次,您应该防止客户端发送无效位置

    【讨论】:

    • 谢谢!这非常有帮助,但是在尝试跟进时,您需要导入一些东西,例如它如何说 Identifier cannot be resolve to a type,我不知道我需要为此导入什么。
    • 您的 IDE 应该具有自动导入功能。对于Identifier,它是net.minecraft.util.IdentifierRegistrynet.minecraft.uti.registry.Registry
    • 对不起!由于某种原因,自动导入不起作用,但我现在明白了!
    猜你喜欢
    • 1970-01-01
    • 2021-09-24
    • 2021-10-15
    • 1970-01-01
    • 2015-02-06
    • 2011-07-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多