问题描述
我正在尝试在 YouTuber TommyInnit 的视频“Minecraft’s Laser Eye Mod Is Hilarious”中重新创建 mod,因为我和我的朋友希望使用它,但我们在互联网上找不到它,我从 {{ 3}} 用于光线投射并设置键绑定,但我不知道如何设置您正在查看的块。我试图设法获取块并设置它,但我只能找到如何制作尚不存在的新块。我的代码如下,块代码在第 142 行:
#include <iostream>
#include <vector>
class base_main {
public:
virtual ~base_main() {}
};
class base_1 : virtual public base_main {};
class base_2 : virtual public base_main {};
class base_3 : virtual public base_main {};
class object : public base_1,public base_2,public base_3 {};
template <typename DestT,typename SrcT,typename T>
void forward_if(SrcT obj,T *o,void (T::*f)(DestT)) noexcept {
if (auto tmp = dynamic_cast<DestT>(obj); tmp != nullptr) {
(o->*f)(tmp);
}
}
struct listener_base {
virtual void object_created(base_main *o) = 0;
};
struct specific_listener : public listener_base {
void object_created(base_main *o) override {
forward_if<base_1 *>(o,this,&specific_listener::object_created);
forward_if<base_2 *>(o,&specific_listener::object_created);
}
void object_created(base_1 *o) {
std::cout << "object created base_1" << std::endl;
}
void object_created(base_2 *o) {
std::cout << "object created base_2" << std::endl;
}
};
int main() {
std::vector<listener_base *> listeners;
listeners.push_back(new specific_listener());
object o;
for (auto listener : listeners) {
listener->object_created(&o);
}
return 0;
}
解决方法
首先,我强烈建议您遵循 the standard Java naming conventions,因为它会让其他人更容易理解您的代码。
存在于世界中特定位置的块的技术名称是“块状态”,由 BlockState
类表示。
您只能在服务器端的特定位置更改块状态。您的光线投射代码在客户端上运行,因此您需要使用 Fabric Networking API。您可以看到服务器端 Javadoc here 和客户端 Javadoc here。
幸运的是,Fabric Wiki 有 a networking tutorial,因此您不必阅读所有 Javadoc。您感兴趣的部分是“向服务器发送数据包并在服务器上接收数据包”。
以下是针对您的用例的指南:
网络简介
Minecraft 在两个不同的组件中运行;客户端和服务器。
客户端负责渲染和 GUI 等工作,而服务器则负责处理世界存储、实体 AI 等(这里讨论逻辑客户端和服务器)
物理服务器和物理客户端是实际运行的 JAR 文件。
物理(专用)服务器仅包含逻辑服务器,而物理客户端同时包含逻辑(集成)服务器和逻辑客户端。
可以在here找到解释它的图表。
因此,逻辑客户端无法更改逻辑服务器的状态(例如,世界中的块状态),因此必须将数据包从客户端发送到服务器,以便服务器做出响应。
以下代码仅为示例代码,请勿复制!您应该考虑安全预防措施,例如防止作弊客户端更改每个块。可能是网络中最重要的规则之一:假设客户在撒谎。
Fabric 网络 API
您的起点是 ServerPlayNetworking
和 ClientPlayNetworking
。它们是帮助您发送和接收数据包的类。
使用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);
服务器端
一个带有 SET_BLOCK_PACKET
ID 的数据包已经发送,但是我们还需要在服务器端监听和接收它。我们可以通过使用 ServerPlayNetworking.registerGlobalReceiver
:
@Override
public void onInitialize() {
// ...
// This code MUST be in onInitialize
ServerPlayNetworking.registerGlobalReceiver(SET_BLOCK_PACKET,(server,player,handler,buf,sender) -> {
});
}
我们在这里使用了 lambda 表达式。有关 lambda 的更多信息,Google 是您的朋友。
接收数据包时,lambda 内部的代码将在网络线程上执行。此代码不允许修改与游戏内逻辑(即世界)相关的任何内容。为此,我们将使用 server.execute(Runnable)
。
不过,您应该阅读网络线程上的 buf。
ServerPlayNetworking.registerGlobalReceiver(SET_BLOCK_PACKET,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
});
});
再次强调,您应该防止客户端发送无效位置