9
0
mirror of https://github.com/Xiao-MoMi/craft-engine.git synced 2025-12-28 11:29:17 +00:00

Merge pull request #174 from jhqwqmc/dev

feat(client-mod): 更新客户端模组
This commit is contained in:
XiaoMoMi
2025-05-12 20:41:08 +08:00
committed by GitHub
17 changed files with 444 additions and 53 deletions

View File

@@ -0,0 +1,64 @@
package net.momirealms.craftengine.bukkit.plugin.network;
import net.momirealms.craftengine.core.util.FriendlyByteBuf;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.Function;
public class NetWorkDataTypes<T> {
private static final Map<Integer, NetWorkDataTypes<?>> id2NetWorkDataTypes = new HashMap<>();
public static final NetWorkDataTypes<Integer> CLIENT_CUSTOM_BLOCK =
new NetWorkDataTypes<>(0, FriendlyByteBuf::readInt, FriendlyByteBuf::writeInt);
public static final NetWorkDataTypes<Boolean> CANCEL_BLOCK_UPDATE =
new NetWorkDataTypes<>(1, FriendlyByteBuf::readBoolean, FriendlyByteBuf::writeBoolean);
static {
register(CLIENT_CUSTOM_BLOCK);
register(CANCEL_BLOCK_UPDATE);
}
private static void register(NetWorkDataTypes<?> type) {
id2NetWorkDataTypes.put(type.id, type);
}
private final int id;
private final Function<FriendlyByteBuf, T> decoder;
private final BiConsumer<FriendlyByteBuf, T> encoder;
public NetWorkDataTypes(int id, Function<FriendlyByteBuf, T> decoder, BiConsumer<FriendlyByteBuf, T> encoder) {
this.id = id;
this.decoder = decoder;
this.encoder = encoder;
}
public T decode(FriendlyByteBuf buf) {
return decoder.apply(buf);
}
public void encode(FriendlyByteBuf buf, T data) {
encoder.accept(buf, data);
}
public int id() {
return id;
}
public void writeType(FriendlyByteBuf buf) {
buf.writeVarInt(id);
}
public static NetWorkDataTypes<?> readType(FriendlyByteBuf buf) {
int id = buf.readVarInt();
return id2NetWorkDataTypes.get(id);
}
@SuppressWarnings("unchecked")
public <R> NetWorkDataTypes<R> as(Class<R> clazz) {
return (NetWorkDataTypes<R>) this;
}
}

View File

@@ -1961,29 +1961,38 @@ public class PacketConsumers {
} else {
data = (byte[]) Reflections.method$DiscardedPayload$dataByteArray.invoke(payload);
}
String decodeData = new String(data, StandardCharsets.UTF_8);
if (!decodeData.endsWith("init")) return;
int firstColon = decodeData.indexOf(':');
if (firstColon == -1) return;
int secondColon = decodeData.indexOf(':', firstColon + 1);
if (secondColon == -1) return;
int clientBlockRegistrySize = Integer.parseInt(decodeData.substring(firstColon + 1, secondColon));
int serverBlockRegistrySize = RegistryUtils.currentBlockRegistrySize();
if (clientBlockRegistrySize != serverBlockRegistrySize) {
Object kickPacket = Reflections.constructor$ClientboundDisconnectPacket.newInstance(
ComponentUtils.adventureToMinecraft(
Component.translatable(
"disconnect.craftengine.block_registry_mismatch",
TranslationArgument.numeric(clientBlockRegistrySize),
TranslationArgument.numeric(serverBlockRegistrySize)
)
)
);
user.nettyChannel().writeAndFlush(kickPacket);
user.nettyChannel().disconnect();
return;
FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.wrappedBuffer(data));
NetWorkDataTypes<?> dataType = NetWorkDataTypes.readType(buf);
if (dataType == NetWorkDataTypes.CLIENT_CUSTOM_BLOCK) {
int clientBlockRegistrySize = dataType.as(Integer.class).decode(buf);
int serverBlockRegistrySize = RegistryUtils.currentBlockRegistrySize();
if (clientBlockRegistrySize != serverBlockRegistrySize) {
Object kickPacket = Reflections.constructor$ClientboundDisconnectPacket.newInstance(
ComponentUtils.adventureToMinecraft(
Component.translatable(
"disconnect.craftengine.block_registry_mismatch",
TranslationArgument.numeric(clientBlockRegistrySize),
TranslationArgument.numeric(serverBlockRegistrySize)
)
)
);
user.nettyChannel().writeAndFlush(kickPacket);
user.nettyChannel().disconnect();
return;
}
user.setClientModState(true);
} else if (dataType == NetWorkDataTypes.CANCEL_BLOCK_UPDATE) {
if (!VersionHelper.isOrAbove1_20_2()) return;
if (dataType.as(Boolean.class).decode(buf)) {
FriendlyByteBuf bufPayload = new FriendlyByteBuf(Unpooled.buffer());
dataType.writeType(bufPayload);
dataType.as(Boolean.class).encode(bufPayload, true);
Object channelKey = KeyUtils.toResourceLocation(Key.of(NetworkManager.MOD_CHANNEL));
Object dataPayload = Reflections.constructor$DiscardedPayload.newInstance(channelKey, bufPayload.array());
Object responsePacket = Reflections.constructor$ClientboundCustomPayloadPacket.newInstance(dataPayload);
user.nettyChannel().writeAndFlush(responsePacket);
}
}
user.setClientModState(true);
}
} catch (Exception e) {
CraftEngine.instance().logger().warn("Failed to handle ServerboundCustomPayloadPacket", e);

View File

@@ -6659,4 +6659,20 @@ public class Reflections {
"world.entity.projectile.AbstractArrow"
)
);
public static final Class<?> clazz$ClientboundCustomPayloadPacket = requireNonNull(
BukkitReflectionUtils.findReobfOrMojmapClass(
List.of("network.protocol.game.PacketPlayOutCustomPayload", "network.protocol.common.ClientboundCustomPayloadPacket"),
List.of("network.protocol.game.ClientboundCustomPayloadPacket", "network.protocol.common.ClientboundCustomPayloadPacket")
)
);
public static final Constructor<?> constructor$ClientboundCustomPayloadPacket = requireNonNull(
ReflectionUtils.getTheOnlyConstructor(clazz$ClientboundCustomPayloadPacket)
);
// 1.20.2+
public static final Constructor<?> constructor$DiscardedPayload = Optional.ofNullable(clazz$DiscardedPayload)
.map(clazz -> ReflectionUtils.getTheOnlyConstructor(clazz))
.orElse(null);
}

View File

@@ -1,9 +1,13 @@
package net.momirealms.craftengine.fabric.client;
import io.netty.buffer.Unpooled;
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.blockrenderlayer.v1.BlockRenderLayerMap;
import net.fabricmc.fabric.api.client.networking.v1.ClientConfigurationConnectionEvents;
import net.fabricmc.fabric.api.client.networking.v1.ClientConfigurationNetworking;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents;
import net.fabricmc.fabric.api.client.rendering.v1.ColorProviderRegistry;
import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry;
import net.minecraft.block.Block;
@@ -11,17 +15,19 @@ import net.minecraft.client.MinecraftClient;
import net.minecraft.client.color.world.BiomeColors;
import net.minecraft.client.network.ClientConfigurationNetworkHandler;
import net.minecraft.client.render.RenderLayer;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.registry.Registries;
import net.minecraft.util.Identifier;
import net.minecraft.world.biome.FoliageColors;
import net.momirealms.craftengine.fabric.client.blocks.CustomBlock;
import net.momirealms.craftengine.fabric.client.config.ModConfig;
import net.momirealms.craftengine.fabric.client.network.CraftEnginePayload;
import net.momirealms.craftengine.fabric.client.util.NetWorkDataTypes;
import java.nio.charset.StandardCharsets;
@Environment(EnvType.CLIENT)
public class CraftEngineFabricModClient implements ClientModInitializer {
public static final String MOD_ID = "craftengine";
public static boolean serverInstalled = false;
@Override
public void onInitializeClient() {
@@ -29,6 +35,8 @@ public class CraftEngineFabricModClient implements ClientModInitializer {
PayloadTypeRegistry.configurationC2S().register(CraftEnginePayload.ID, CraftEnginePayload.CODEC);
registerRenderLayer();
ClientConfigurationConnectionEvents.START.register(CraftEngineFabricModClient::initChannel);
ClientConfigurationNetworking.registerGlobalReceiver(CraftEnginePayload.ID, CraftEngineFabricModClient::handleReceiver);
ClientPlayConnectionEvents.DISCONNECT.register((client, handler) -> serverInstalled = false);
}
public static void registerRenderLayer() {
@@ -58,14 +66,32 @@ public class CraftEngineFabricModClient implements ClientModInitializer {
}
private static void initChannel(ClientConfigurationNetworkHandler handler, MinecraftClient client) {
if (ModConfig.enableNetwork) {
registerChannel(handler);
} else {
if (!ModConfig.enableNetwork && !ModConfig.enableCancelBlockUpdate) {
ClientConfigurationNetworking.unregisterGlobalReceiver(CraftEnginePayload.ID);
return;
}
PacketByteBuf buf = new PacketByteBuf(Unpooled.buffer());
if (ModConfig.enableNetwork) {
NetWorkDataTypes<Integer> type = NetWorkDataTypes.CLIENT_CUSTOM_BLOCK;
type.writeType(buf);
type.encode(buf, Block.STATE_IDS.size());
} else if (ModConfig.enableCancelBlockUpdate) {
NetWorkDataTypes<Boolean> type = NetWorkDataTypes.CANCEL_BLOCK_UPDATE;
type.writeType(buf);
type.encode(buf, true);
}
ClientConfigurationNetworking.send(new CraftEnginePayload(buf.array()));
}
private static void registerChannel(ClientConfigurationNetworkHandler handler) {
ClientConfigurationNetworking.send(new CraftEnginePayload((":" + Block.STATE_IDS.size() + ":init").getBytes(StandardCharsets.UTF_8)));
private static void handleReceiver(CraftEnginePayload payload, ClientConfigurationNetworking.Context context) {
byte[] data = payload.data();
PacketByteBuf buf = new PacketByteBuf(Unpooled.wrappedBuffer(data));
NetWorkDataTypes<?> type = NetWorkDataTypes.readType(buf);
if (type == NetWorkDataTypes.CANCEL_BLOCK_UPDATE) {
serverInstalled = type.as(Boolean.class).decode(buf);
}
}
}

View File

@@ -3,33 +3,69 @@ package net.momirealms.craftengine.fabric.client.config;
import me.shedaniel.clothconfig2.api.ConfigBuilder;
import me.shedaniel.clothconfig2.api.ConfigCategory;
import me.shedaniel.clothconfig2.api.ConfigEntryBuilder;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.Yaml;
import java.io.IOException;
import java.io.Writer;
import java.nio.file.Files;
import java.nio.file.Path;
public class ModConfig {
public static boolean enableNetwork = true;
private static final Path CONFIG_PATH = FabricLoader.getInstance().getConfigDir().resolve("craft-engine-fabric-mod/config.yml");
public static boolean enableNetwork = false;
public static boolean enableCancelBlockUpdate = false;
public static Screen getConfigScreen(Screen parent) {
ConfigBuilder builder = ConfigBuilder.create()
.setParentScreen(parent)
.setSavingRunnable(ModConfig::saveConfig)
.setTitle(Text.translatable("title.craftengine.config"));
ConfigCategory general = builder.getOrCreateCategory(Text.translatable("category.craftengine.general"));
ConfigEntryBuilder entryBuilder = builder.entryBuilder();
general.addEntry(entryBuilder.startBooleanToggle(
Text.translatable("option.craftengine.enable_network")
.formatted(Formatting.WHITE),
Text.translatable("option.craftengine.enable_network")
.formatted(Formatting.WHITE),
enableNetwork)
.setDefaultValue(true)
.setDefaultValue(false)
.setSaveConsumer(newValue -> enableNetwork = newValue)
.setTooltip(
Text.translatable("tooltip.craftengine.enable_network")
.formatted(Formatting.GRAY)
)
.build());
general.addEntry(entryBuilder.startBooleanToggle(
Text.translatable("option.craftengine.enable_cancel_block_update")
.formatted(Formatting.WHITE),
enableCancelBlockUpdate)
.setDefaultValue(false)
.setSaveConsumer(newValue -> enableCancelBlockUpdate = newValue)
.setTooltip(
Text.translatable("tooltip.craftengine.enable_cancel_block_update")
)
.build()
);
return builder.build();
}
private static void saveConfig() {
DumperOptions options = new DumperOptions();
options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
Yaml yaml = new Yaml(options);
var data = new java.util.HashMap<String, Object>();
data.put("enable-network", ModConfig.enableNetwork);
data.put("enable-cancel-block-update", ModConfig.enableCancelBlockUpdate);
try (Writer writer = Files.newBufferedWriter(CONFIG_PATH)) {
yaml.dump(data, writer);
} catch (IOException e) {
e.printStackTrace();
}
}
}

View File

@@ -9,8 +9,13 @@ public record CraftEnginePayload(byte[] data) implements CustomPayload {
public static final Identifier CRAFTENGINE_PAYLOAD = Identifier.of("craftengine", "payload");
public static final Id<CraftEnginePayload> ID = new Id<>(CraftEnginePayload.CRAFTENGINE_PAYLOAD);
public static final PacketCodec<PacketByteBuf, CraftEnginePayload> CODEC = PacketCodec.of(
(payload, byteBuf) -> byteBuf.writeByteArray(payload.data()),
buf -> new CraftEnginePayload(buf.readByteArray()));
(payload, byteBuf) -> byteBuf.writeBytes(payload.data()),
buf -> {
int i = buf.readableBytes();
byte[] data = new byte[i];
buf.readBytes(data);
return new CraftEnginePayload(data);
});
@Override
public Id<? extends CustomPayload> getId() {

View File

@@ -0,0 +1,63 @@
package net.momirealms.craftengine.fabric.client.util;
import net.minecraft.network.PacketByteBuf;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.Function;
public class NetWorkDataTypes<T> {
private static final Map<Integer, NetWorkDataTypes<?>> id2NetWorkDataTypes = new HashMap<>();
public static final NetWorkDataTypes<Integer> CLIENT_CUSTOM_BLOCK =
new NetWorkDataTypes<>(0, PacketByteBuf::readInt, PacketByteBuf::writeInt);
public static final NetWorkDataTypes<Boolean> CANCEL_BLOCK_UPDATE =
new NetWorkDataTypes<>(1, PacketByteBuf::readBoolean, PacketByteBuf::writeBoolean);
static {
register(CLIENT_CUSTOM_BLOCK);
register(CANCEL_BLOCK_UPDATE);
}
private static void register(NetWorkDataTypes<?> type) {
id2NetWorkDataTypes.put(type.id, type);
}
private final int id;
private final Function<PacketByteBuf, T> decoder;
private final BiConsumer<PacketByteBuf, T> encoder;
public NetWorkDataTypes(int id, Function<PacketByteBuf, T> decoder, BiConsumer<PacketByteBuf, T> encoder) {
this.id = id;
this.decoder = decoder;
this.encoder = encoder;
}
public T decode(PacketByteBuf buf) {
return decoder.apply(buf);
}
public void encode(PacketByteBuf buf, T data) {
encoder.accept(buf, data);
}
public int id() {
return id;
}
public void writeType(PacketByteBuf buf) {
buf.writeVarInt(id);
}
public static NetWorkDataTypes<?> readType(PacketByteBuf buf) {
int id = buf.readVarInt();
return id2NetWorkDataTypes.get(id);
}
@SuppressWarnings("unchecked")
public <R> NetWorkDataTypes<R> as(Class<R> clazz) {
return (NetWorkDataTypes<R>) this;
}
}

View File

@@ -1,5 +1,7 @@
package net.momirealms.craftengine.fabric;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.block.BlockState;
@@ -9,16 +11,15 @@ import net.momirealms.craftengine.fabric.util.BlockUtils;
import net.momirealms.craftengine.fabric.util.LoggerFilter;
import net.momirealms.craftengine.fabric.util.RegisterBlocks;
import net.momirealms.craftengine.fabric.util.YamlUtils;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.Yaml;
import java.io.IOException;
import java.io.InputStream;
import java.io.Writer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;
@Environment(EnvType.CLIENT)
public class CraftEngineFabricMod implements ModInitializer {
private static final Path CONFIG_PATH = FabricLoader.getInstance().getConfigDir().resolve("craft-engine-fabric-mod/config.yml");
public static final String MOD_ID = "craftengine";
@@ -47,32 +48,25 @@ public class CraftEngineFabricMod implements ModInitializer {
} catch (IOException e) {
e.printStackTrace();
}
Runtime.getRuntime().addShutdownHook(new Thread(this::saveConfig));
}
@SuppressWarnings("unchecked")
private void loadConfig() {
if (!Files.exists(CONFIG_PATH)) {
ModConfig.enableNetwork = true;
ModConfig.enableNetwork = false;
ModConfig.enableCancelBlockUpdate = false;
return;
}
try (InputStream inputStream = Files.newInputStream(CONFIG_PATH)) {
Yaml yaml = new Yaml();
var config = yaml.loadAs(inputStream, java.util.Map.class);
ModConfig.enableNetwork = (Boolean) config.getOrDefault("enable-network", true);
} catch (IOException e) {
e.printStackTrace();
}
}
private void saveConfig() {
DumperOptions options = new DumperOptions();
options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
Yaml yaml = new Yaml(options);
var data = new java.util.HashMap<String, Object>();
data.put("enable-network", ModConfig.enableNetwork);
try (Writer writer = Files.newBufferedWriter(CONFIG_PATH)) {
yaml.dump(data, writer);
if (config == null) {
ModConfig.enableNetwork = false;
ModConfig.enableCancelBlockUpdate = false;
return;
}
ModConfig.enableNetwork = (Boolean) config.getOrDefault("enable-network", false);
ModConfig.enableCancelBlockUpdate = (Boolean) config.getOrDefault("enable-cancel-block-update", false);
} catch (IOException e) {
e.printStackTrace();
}

View File

@@ -0,0 +1,65 @@
package net.momirealms.craftengine.fabric.mixin;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.block.AbstractBlock;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.random.Random;
import net.minecraft.world.World;
import net.minecraft.world.WorldAccess;
import net.minecraft.world.WorldView;
import net.minecraft.world.block.WireOrientation;
import net.minecraft.world.tick.ScheduledTickView;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import static net.momirealms.craftengine.fabric.client.CraftEngineFabricModClient.serverInstalled;
import static net.momirealms.craftengine.fabric.client.config.ModConfig.enableCancelBlockUpdate;
@Environment(EnvType.CLIENT)
@Mixin(AbstractBlock.AbstractBlockState.class)
public abstract class AbstractBlockStateMixin {
@Inject(method = "getStateForNeighborUpdate", at = @At("HEAD"), cancellable = true)
private void cancelGetStateForNeighborUpdate(WorldView world, ScheduledTickView tickView, BlockPos pos, Direction direction, BlockPos neighborPos, BlockState neighborState, Random random, CallbackInfoReturnable<AbstractBlockStateMixin> cir) {
if (!enableCancelBlockUpdate || !serverInstalled) return;
cir.setReturnValue(this);
}
@Inject(method = "neighborUpdate", at = @At("HEAD"), cancellable = true)
private void cancelNeighborUpdate(World world, BlockPos pos, Block sourceBlock, WireOrientation wireOrientation, boolean notify, CallbackInfo ci) {
if (!enableCancelBlockUpdate || !serverInstalled) return;
ci.cancel();
}
@Inject(method = "updateNeighbors*", at = @At("HEAD"), cancellable = true)
private void cancelUpdateNeighbors(WorldAccess world, BlockPos pos, int flags, CallbackInfo ci) {
if (!enableCancelBlockUpdate || !serverInstalled) return;
ci.cancel();
}
@Inject(method = "scheduledTick", at = @At("HEAD"), cancellable = true)
private void cancelScheduledTick(ServerWorld world, BlockPos pos, Random random, CallbackInfo ci) {
if (!enableCancelBlockUpdate || !serverInstalled) return;
ci.cancel();
}
@Inject(method = "canPlaceAt", at = @At("HEAD"), cancellable = true)
private void passCanPlaceAt(WorldView world, BlockPos pos, CallbackInfoReturnable<Boolean> cir) {
if (!enableCancelBlockUpdate || !serverInstalled) return;
cir.setReturnValue(true);
}
@Inject(method = "randomTick", at = @At("HEAD"), cancellable = true)
private void cancelRandomTick(ServerWorld world, BlockPos pos, Random random, CallbackInfo ci) {
if (!enableCancelBlockUpdate || !serverInstalled) return;
ci.cancel();
}
}

View File

@@ -0,0 +1,26 @@
package net.momirealms.craftengine.fabric.mixin;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.block.AbstractRailBlock;
import net.minecraft.block.BlockState;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import static net.momirealms.craftengine.fabric.client.CraftEngineFabricModClient.serverInstalled;
import static net.momirealms.craftengine.fabric.client.config.ModConfig.enableCancelBlockUpdate;
@Environment(EnvType.CLIENT)
@Mixin(AbstractRailBlock.class)
public abstract class AbstractRailBlockMixin {
@Inject(method = "updateCurves", at = @At("HEAD"), cancellable = true)
private void cancelUpdateCurves(BlockState state, World world, BlockPos pos, boolean notify, CallbackInfoReturnable<BlockState> cir) {
if (!enableCancelBlockUpdate || !serverInstalled) return;
cir.setReturnValue(state);
}
}

View File

@@ -0,0 +1,30 @@
package net.momirealms.craftengine.fabric.mixin;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.component.DataComponentTypes;
import net.minecraft.component.type.NbtComponent;
import net.minecraft.item.BlockItem;
import net.minecraft.item.ItemPlacementContext;
import net.minecraft.item.ItemStack;
import net.minecraft.util.ActionResult;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Environment(EnvType.CLIENT)
@Mixin(BlockItem.class)
public class BlockItemMixin {
@Inject(method = "place*", at = @At("HEAD"), cancellable = true)
private void onPlace(ItemPlacementContext context, CallbackInfoReturnable<ActionResult> cir) {
ItemStack stack = context.getStack();
NbtComponent customData = stack.getComponents().get(DataComponentTypes.CUSTOM_DATA);
if (customData == null) return;
if (customData.contains("craftengine:id")) {
cir.setReturnValue(ActionResult.FAIL);
cir.cancel();
}
}
}

View File

@@ -0,0 +1,26 @@
package net.momirealms.craftengine.fabric.mixin;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.block.BlockState;
import net.minecraft.fluid.FluidState;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.math.BlockPos;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import static net.momirealms.craftengine.fabric.client.CraftEngineFabricModClient.serverInstalled;
import static net.momirealms.craftengine.fabric.client.config.ModConfig.enableCancelBlockUpdate;
@Environment(EnvType.CLIENT)
@Mixin(FluidState.class)
public class FluidStateMixin {
@Inject(method = "onScheduledTick", at = @At("HEAD"), cancellable = true)
private void cancelScheduledTick(ServerWorld world, BlockPos pos, BlockState state, CallbackInfo ci) {
if (!enableCancelBlockUpdate || !serverInstalled) return;
ci.cancel();
}
}

View File

@@ -2,6 +2,8 @@
"title.craftengine.config": "CraftEngine Settings",
"category.craftengine.general": "General",
"option.craftengine.enable_network": "Enable custom blocks in server",
"option.craftengine.enable_cancel_block_update": "Enable cancelling block updates in the server",
"tooltip.craftengine.enable_network": "Changes requires re-entering the server to take effect",
"tooltip.craftengine.enable_cancel_block_update": "Only works on servers with CraftEngine installed.",
"disconnect.craftengine.block_registry_mismatch": "Block registry size mismatch. Current: %s. Expected: %s. \n 1. Make sure that the configs are the same as the server's. \n 2. Do not use any mod that might register new block. \n 3. Do not install ViaVersion."
}

View File

@@ -2,6 +2,8 @@
"title.craftengine.config": "CraftEngine设置",
"category.craftengine.general": "通用",
"option.craftengine.enable_network": "启用服务器内自定义方块",
"option.craftengine.enable_cancel_block_update": "启用服务器内取消方块更新",
"tooltip.craftengine.enable_network": "需要重新进入服务器以应用更改",
"tooltip.craftengine.enable_cancel_block_update": "仅在安装了CraftEngine的服务器内生效",
"disconnect.craftengine.block_registry_mismatch": "方块注册表大小不匹配. 当前: %s. 预期: %s \n 1. 确保客户端mod配置与服务端配置一致. \n 2. 不要安装任何会注册新方块的mod. \n 3. 不要使用ViaVersion."
}

View File

@@ -0,0 +1,14 @@
{
"required": true,
"package": "net.momirealms.craftengine.fabric.mixin",
"compatibilityLevel": "JAVA_21",
"client": [
"AbstractBlockStateMixin",
"AbstractRailBlockMixin",
"BlockItemMixin",
"FluidStateMixin"
],
"injectors": {
"defaultRequire": 1
}
}

View File

@@ -24,6 +24,10 @@
"modmenu": ["net.momirealms.craftengine.fabric.client.config.ModMenuIntegration"]
},
"mixins": [
"craftengine.mixins.json"
],
"depends": {
"fabricloader": ">=${loader_version}",
"fabric": "*",

View File

@@ -458,6 +458,15 @@ public class FriendlyByteBuf extends ByteBuf {
this.writeBytes(Arrays.copyOf(byteArray, MCUtils.positiveCeilDiv(size, 8)));
}
@SuppressWarnings("unchecked")
public <T extends Enum<T>> T readEnumConstant(Class<T> enumClass) {
return (T)((Enum<T>[])enumClass.getEnumConstants())[this.readVarInt()];
}
public FriendlyByteBuf writeEnumConstant(Enum<?> instance) {
return this.writeVarInt(instance.ordinal());
}
@FunctionalInterface
public interface Writer<T> extends BiConsumer<FriendlyByteBuf, T> {