Режим игры решает сервер: клиент больше не ломает блоки сам

This commit is contained in:
sasheg dev
2026-10-20 23:03:54 +03:00
parent 1c93d9ae9a
commit aee8b61128
14 changed files with 367 additions and 33 deletions
@@ -0,0 +1,64 @@
package net.rustnotmc.client.net;
import net.minecraft.client.Minecraft;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.RayTraceResult;
import net.rustnotmc.protocol.Packets;
/**
* What the player is allowed to do to the world, and how the game is told.
*
* In survival the player breaks nothing. Not the blocks, and not the outline
* around the block they are looking at either: a highlight that leads nowhere
* is worse than no highlight, because it says "here" and then nothing happens.
*
* In creative the player breaks the one block they are looking at, and only on
* a deliberate click. The game sends a packet for every frame the button is
* held, which is how a held button ends up breaking a line of blocks across
* the world instead of the one under the crosshair.
*/
public final class RsxClientMode {
private static volatile int mode = Packets.GAMEMODE_SURVIVAL;
private RsxClientMode() {
}
/** The mode the server says the player is in. */
public static int mode() {
return mode;
}
/** The server said the mode changed. */
public static void set(int newMode) {
mode = newMode;
}
/** True when the player may break blocks. */
public static boolean canBreakBlocks() {
return mode == Packets.GAMEMODE_CREATIVE;
}
/**
* The player clicked, and wants the block they are looking at broken.
*
* The game has already worked out what the crosshair is on, so that is what
* gets broken. The server checks that the player was close enough to it.
*/
public static void onLeftClick(Minecraft minecraft) {
if (!canBreakBlocks()) {
return;
}
RayTraceResult hit = minecraft.objectMouseOver;
if (hit == null || hit.typeOfHit != RayTraceResult.Type.BLOCK) {
return;
}
RsxNetworkManager manager = RsxNetworkManager.active();
if (manager == null) {
return;
}
BlockPos pos = hit.getBlockPos();
manager.sendBlockChange(pos.getX(), pos.getY(), pos.getZ(),
net.rustnotmc.protocol.Blocks.AIR);
}
}
@@ -19,7 +19,6 @@ import net.minecraft.network.INetHandler;
import net.minecraft.network.NetworkManager;
import net.minecraft.network.Packet;
import net.minecraft.network.play.INetHandlerPlayClient;import net.minecraft.network.play.client.CPacketPlayer;
import net.minecraft.network.play.client.CPacketPlayerDigging;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.util.text.TextComponentTranslation;
import net.rustnotmc.protocol.Buf;
@@ -245,6 +244,11 @@ public final class RsxNetworkManager extends NetworkManager {
case Packets.WORLD_JOIN:
onWorldJoin(frame);
return;
case Packets.SET_GAME_MODE: {
Packets.GameMode gameMode = Packets.GameMode.read(new Buf(frame.body()));
RsxClientMode.set(gameMode.mode);
return;
}
default:
onPlay(frame);
}
@@ -295,8 +299,9 @@ public final class RsxNetworkManager extends NetworkManager {
if (this.play == null) {
return;
}
RsxClientMode.set(world.gameMode);
List<Packet<INetHandlerPlayClient>> packets = RsxWorldBridge.join(world.entityId, 200,
world.spawnX, world.spawnY, world.spawnZ);
world.spawnX, world.spawnY, world.spawnZ, world.gameMode);
for (int i = 0; i < packets.size(); i++) {
packets.get(i).processPacket(this.play);
}
@@ -364,31 +369,37 @@ public final class RsxNetworkManager extends NetworkManager {
* The game is sending something; the server does not speak the game's
* language, so it is translated.
*
* Most of what the game sends has no counterpart on the server and is
* dropped. The two that matter are where the player is and what the
* player is breaking.
* The dig packets are the one thing that is deliberately not translated.
* The game sends one for every frame the button is held, so a held button
* becomes a stream of block changes and a line of blocks disappears across
* the world instead of the one under the crosshair. The client breaks
* blocks on its own click instead, with the game's own raycast.
*/
@Override
public void sendPacket(Packet<?> packet) {
if (packet instanceof CPacketPlayer) {
if (packet instanceof CPacketPlayer.Position
|| packet instanceof CPacketPlayer.Rotation
|| packet instanceof CPacketPlayer.PositionRotation) {
CPacketPlayer move = (CPacketPlayer) packet;
send(Packets.PLAYER_MOVE, new Packets.PlayerMove(
move.getX(0.0D), move.getY(0.0D), move.getZ(0.0D),
move.getYaw(0.0F), move.getPitch(0.0F), move.isOnGround()));
return;
}
if (packet instanceof CPacketPlayerDigging) {
CPacketPlayerDigging dig = (CPacketPlayerDigging) packet;
if (dig.getAction() == CPacketPlayerDigging.Action.START_DESTROY_BLOCK) {
net.minecraft.util.math.BlockPos pos = dig.getPosition();
send(Packets.BLOCK_CHANGE, new Packets.BlockChange(pos.getX(), pos.getY(),
pos.getZ(), net.rustnotmc.protocol.Blocks.AIR));
}
return;
}
// everything else the game sends has no place in our protocol yet
}
/**
* Asks the server to break a block.
*
* The server decides whether it happens: the player has to be close
* enough to it, and in a survival world the player does not break blocks
* at all.
*/
public void sendBlockChange(int x, int y, int z, byte id) {
send(Packets.BLOCK_CHANGE, new Packets.BlockChange(x, y, z, id));
}
// ------------------------------------------------------------- teardown
private void lost(String reason) {
@@ -55,10 +55,16 @@ public final class RsxWorldBridge {
* world exists, because the player belongs to the world.
*/
public static List<Packet<INetHandlerPlayClient>> join(int entityId, int maxPlayers,
double spawnX, double spawnY, double spawnZ) {
double spawnX, double spawnY, double spawnZ,
int gameMode) {
List<Packet<INetHandlerPlayClient>> packets =
new ArrayList<Packet<INetHandlerPlayClient>>();
packets.add(new SPacketJoinGame(entityId, GameType.SURVIVAL, false, 0,
// the game type in the join packet is what makes the game apply its own
// rules: in creative the player may break blocks, in survival it may
// not, and the outline follows
GameType type = gameMode == net.rustnotmc.protocol.Packets.GAMEMODE_CREATIVE
? GameType.CREATIVE : GameType.SURVIVAL;
packets.add(new SPacketJoinGame(entityId, type, false, 0,
EnumDifficulty.NORMAL, maxPlayers, WorldType.DEFAULT, false));
packets.add(new SPacketSpawnPosition(
new BlockPos((int) Math.floor(spawnX), (int) Math.floor(spawnY),