9
0
mirror of https://github.com/Xiao-MoMi/craft-engine.git synced 2025-12-28 03:19:14 +00:00

Merge pull request #57 from jhqwqmc/dev

feat(client): 添加 Fabric 客户端模组
This commit is contained in:
XiaoMoMi
2025-03-24 19:09:19 +08:00
committed by GitHub
24 changed files with 2822 additions and 43 deletions

View File

@@ -10,6 +10,7 @@ import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
import net.momirealms.craftengine.bukkit.plugin.network.impl.*;
import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer;
import net.momirealms.craftengine.bukkit.util.Reflections;
import net.momirealms.craftengine.bukkit.util.RegistryUtils;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.network.ConnectionState;
import net.momirealms.craftengine.core.plugin.network.NetWorkUser;
@@ -26,9 +27,11 @@ import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.event.player.PlayerRegisterChannelEvent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiConsumer;
@@ -158,6 +161,18 @@ public class BukkitNetworkManager implements NetworkManager, Listener {
this.onlineUsers.remove(player.getUniqueId());
}
@EventHandler
public void onPlayerRegisterChannel(PlayerRegisterChannelEvent event) {
if (!event.getChannel().equals("craftengine:payload")) return;
Player player = event.getPlayer();
NetWorkUser user = getUser(player);
if (user == null) return;
user.setUsingClientMod(true);
int blockRegistrySize = RegistryUtils.currentBlockRegistrySize();
byte[] payload = ("cp:" + blockRegistrySize).getBytes(StandardCharsets.UTF_8);
player.sendPluginMessage(plugin.bootstrap(), "craftengine:payload", payload);
}
@Override
public Collection<BukkitServerPlayer> onlineUsers() {
return onlineUsers.values();
@@ -167,6 +182,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener {
public void init() {
if (init) return;
try {
plugin.bootstrap().getServer().getMessenger().registerOutgoingPluginChannel(plugin.bootstrap(), "craftengine:payload");
Object server = Reflections.method$MinecraftServer$getServer.invoke(null);
Object serverConnection = Reflections.field$MinecraftServer$connection.get(server);
@SuppressWarnings("unchecked")

View File

@@ -39,17 +39,21 @@ import java.util.function.Consumer;
public class PacketConsumers {
private static int[] mappings;
private static int[] mappingsMOD;
private static IntIdentityList BLOCK_LIST;
private static IntIdentityList BIOME_LIST;
public static void init(Map<Integer, Integer> map, int registrySize) {
mappings = new int[registrySize];
Arrays.fill(mappings, -1);
for (int i = 0; i < registrySize; i++) {
mappings[i] = i;
}
mappingsMOD = Arrays.copyOf(mappings, registrySize);
for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
mappings[entry.getKey()] = entry.getValue();
if (BlockStateUtils.isVanillaBlock(entry.getKey())) {
mappingsMOD[entry.getKey()] = entry.getValue();
}
}
BLOCK_LIST = new IntIdentityList(registrySize);
BIOME_LIST = new IntIdentityList(RegistryUtils.currentBiomeRegistrySize());
@@ -59,37 +63,73 @@ public class PacketConsumers {
return mappings[stateId];
}
public static int remapMOD(int stateId) {
return mappingsMOD[stateId];
}
public static final TriConsumer<NetWorkUser, NMSPacketEvent, Object> LEVEL_CHUNK_WITH_LIGHT = (user, event, packet) -> {
try {
BukkitServerPlayer player = (BukkitServerPlayer) user;
Object chunkData = Reflections.field$ClientboundLevelChunkWithLightPacket$chunkData.get(packet);
byte[] buffer = (byte[]) Reflections.field$ClientboundLevelChunkPacketData$buffer.get(chunkData);
ByteBuf buf = Unpooled.copiedBuffer(buffer);
FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(buf);
FriendlyByteBuf newBuf = new FriendlyByteBuf(Unpooled.buffer());
for (int i = 0, count = player.clientSideSectionCount(); i < count; i++) {
try {
MCSection mcSection = new MCSection(BLOCK_LIST, BIOME_LIST);
mcSection.readPacket(friendlyByteBuf);
PalettedContainer<Integer> container = mcSection.blockStateContainer();
Palette<Integer> palette = container.data().palette();
if (palette.canRemap()) {
palette.remap(PacketConsumers::remap);
} else {
for (int j = 0; j < 4096; j ++) {
int state = container.get(j);
int newState = remap(state);
if (newState != state) {
container.set(j, newState);
if (!user.usingClientMod()) {
BukkitServerPlayer player = (BukkitServerPlayer) user;
Object chunkData = Reflections.field$ClientboundLevelChunkWithLightPacket$chunkData.get(packet);
byte[] buffer = (byte[]) Reflections.field$ClientboundLevelChunkPacketData$buffer.get(chunkData);
ByteBuf buf = Unpooled.copiedBuffer(buffer);
FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(buf);
FriendlyByteBuf newBuf = new FriendlyByteBuf(Unpooled.buffer());
for (int i = 0, count = player.clientSideSectionCount(); i < count; i++) {
try {
MCSection mcSection = new MCSection(BLOCK_LIST, BIOME_LIST);
mcSection.readPacket(friendlyByteBuf);
PalettedContainer<Integer> container = mcSection.blockStateContainer();
Palette<Integer> palette = container.data().palette();
if (palette.canRemap()) {
palette.remap(PacketConsumers::remap);
} else {
for (int j = 0; j < 4096; j++) {
int state = container.get(j);
int newState = remap(state);
if (newState != state) {
container.set(j, newState);
}
}
}
mcSection.writePacket(newBuf);
} catch (Exception e) {
break;
}
mcSection.writePacket(newBuf);
} catch (Exception e) {
break;
}
Reflections.field$ClientboundLevelChunkPacketData$buffer.set(chunkData, newBuf.array());
} else {
BukkitServerPlayer player = (BukkitServerPlayer) user;
Object chunkData = Reflections.field$ClientboundLevelChunkWithLightPacket$chunkData.get(packet);
byte[] buffer = (byte[]) Reflections.field$ClientboundLevelChunkPacketData$buffer.get(chunkData);
ByteBuf buf = Unpooled.copiedBuffer(buffer);
FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(buf);
FriendlyByteBuf newBuf = new FriendlyByteBuf(Unpooled.buffer());
for (int i = 0, count = player.clientSideSectionCount(); i < count; i++) {
try {
MCSection mcSection = new MCSection(BLOCK_LIST, BIOME_LIST);
mcSection.readPacket(friendlyByteBuf);
PalettedContainer<Integer> container = mcSection.blockStateContainer();
Palette<Integer> palette = container.data().palette();
if (palette.canRemap()) {
palette.remap(PacketConsumers::remapMOD);
} else {
for (int j = 0; j < 4096; j++) {
int state = container.get(j);
int newState = remapMOD(state);
if (newState != state) {
container.set(j, newState);
}
}
}
mcSection.writePacket(newBuf);
} catch (Exception e) {
break;
}
}
Reflections.field$ClientboundLevelChunkPacketData$buffer.set(chunkData, newBuf.array());
}
Reflections.field$ClientboundLevelChunkPacketData$buffer.set(chunkData, newBuf.array());
} catch (Exception e) {
CraftEngine.instance().logger().warn("Failed to handle ClientboundLevelChunkWithLightPacket", e);
}
@@ -97,24 +137,45 @@ public class PacketConsumers {
public static final BiConsumer<NetWorkUser, ByteBufPacketEvent> SECTION_BLOCK_UPDATE = (user, event) -> {
try {
FriendlyByteBuf buf = event.getBuffer();
long pos = buf.readLong();
int blocks = buf.readVarInt();
short[] positions = new short[blocks];
int[] states = new int[blocks];
for (int i = 0; i < blocks; i++) {
long k = buf.readVarLong();
positions[i] = (short) ((int) (k & 4095L));
states[i] = remap((int) (k >>> 12));
if (!user.usingClientMod()) {
FriendlyByteBuf buf = event.getBuffer();
long pos = buf.readLong();
int blocks = buf.readVarInt();
short[] positions = new short[blocks];
int[] states = new int[blocks];
for (int i = 0; i < blocks; i++) {
long k = buf.readVarLong();
positions[i] = (short) ((int) (k & 4095L));
states[i] = remap((int) (k >>> 12));
}
buf.clear();
buf.writeVarInt(event.packetID());
buf.writeLong(pos);
buf.writeVarInt(blocks);
for (int i = 0; i < blocks; i++) {
buf.writeVarLong((long) states[i] << 12 | positions[i]);
}
event.setChanged(true);
} else {
FriendlyByteBuf buf = event.getBuffer();
long pos = buf.readLong();
int blocks = buf.readVarInt();
short[] positions = new short[blocks];
int[] states = new int[blocks];
for (int i = 0; i < blocks; i++) {
long k = buf.readVarLong();
positions[i] = (short) ((int) (k & 4095L));
states[i] = remapMOD((int) (k >>> 12));
}
buf.clear();
buf.writeVarInt(event.packetID());
buf.writeLong(pos);
buf.writeVarInt(blocks);
for (int i = 0; i < blocks; i++) {
buf.writeVarLong((long) states[i] << 12 | positions[i]);
}
event.setChanged(true);
}
buf.clear();
buf.writeVarInt(event.packetID());
buf.writeLong(pos);
buf.writeVarInt(blocks);
for (int i = 0; i < blocks; i ++) {
buf.writeVarLong((long) states[i] << 12 | positions[i]);
}
event.setChanged(true);
} catch (Exception e) {
CraftEngine.instance().logger().warn("Failed to handle ClientboundSectionBlocksUpdatePacket", e);
}
@@ -125,6 +186,9 @@ public class PacketConsumers {
FriendlyByteBuf buf = event.getBuffer();
BlockPos pos = buf.readBlockPos(buf);
int before = buf.readVarInt();
if (user.usingClientMod() && !BlockStateUtils.isVanillaBlock(before)) {
return;
}
int state = remap(before);
if (state == before) {
return;

View File

@@ -63,6 +63,8 @@ public class BukkitServerPlayer extends Player {
private Key lastUsedRecipe = null;
private boolean usingClientMod = false;
private Map<Integer, List<Integer>> furnitureView = new ConcurrentHashMap<>();
public BukkitServerPlayer(BukkitCraftEngine plugin, Channel channel) {
@@ -616,4 +618,12 @@ public class BukkitServerPlayer extends Player {
public void setLastUsedRecipe(Key lastUsedRecipe) {
this.lastUsedRecipe = lastUsedRecipe;
}
public boolean usingClientMod() {
return this.usingClientMod;
}
public void setUsingClientMod(boolean usingClientMod) {
this.usingClientMod = usingClientMod;
}
}

108
client-mod/build.gradle.kts Normal file
View File

@@ -0,0 +1,108 @@
plugins {
id("fabric-loom") version "1.10-SNAPSHOT"
id("com.gradleup.shadow") version "9.0.0-beta11"
}
version = property("project_version")!!
group = property("project_group")!!
val project_version: String by project
val latest_minecraft_version: String by project
val loader_version: String by project
var modmenu_version = property("modmenu_version")
var cloth_version = property("cloth_version")
base {
archivesName.set("craft-engine-fabric-mod")
}
sourceSets {
create("client") {
compileClasspath += sourceSets.main.get().compileClasspath
runtimeClasspath += sourceSets.main.get().runtimeClasspath
}
main {
compileClasspath += sourceSets["client"].output
runtimeClasspath += sourceSets["client"].output
output.dir(sourceSets["client"].output)
}
}
tasks.shadowJar {
relocate("org.yaml", "net.momirealms.craftengine.libraries.org.yaml")
configurations = listOf(project.configurations.getByName("shadow"))
archiveFileName.set("${base.archivesName.get()}-${project.version}-shadow.jar")
from(sourceSets.main.get().output)
from(sourceSets["client"].output)
}
tasks.remapJar {
dependsOn(tasks.shadowJar)
inputFile.set(tasks.shadowJar.get().archiveFile)
destinationDirectory.set(file("$rootDir/target"))
archiveFileName.set("${base.archivesName.get()}-${project.version}.jar")
}
loom {
mods {
create("craft-engine-fabric-mod") {
sourceSet(sourceSets.main.get())
sourceSet(sourceSets["client"])
}
}
}
repositories {
maven("https://maven.shedaniel.me/")
maven("https://maven.terraformersmc.com/releases/")
}
dependencies {
minecraft("com.mojang:minecraft:${property("latest_minecraft_version")}")
mappings("net.fabricmc:yarn:${property("yarn_mappings")}:v2")
modImplementation("net.fabricmc:fabric-loader:${property("loader_version")}")
modImplementation("net.fabricmc.fabric-api:fabric-api:${property("fabric_version")}")
modApi("me.shedaniel.cloth:cloth-config-fabric:${property("cloth_version")}")
modApi("com.terraformersmc:modmenu:${property("modmenu_version")}")
add("shadow", "org.yaml:snakeyaml:2.4")
}
tasks.processResources {
inputs.property("version", project_version)
inputs.property("minecraft_version", latest_minecraft_version)
inputs.property("loader_version", loader_version)
filteringCharset = "UTF-8"
filesMatching("fabric.mod.json") {
expand(
"version" to project_version,
"minecraft_version" to latest_minecraft_version,
"loader_version" to loader_version,
"modmenu_version" to modmenu_version,
"cloth_version" to cloth_version
)
}
}
val targetJavaVersion = 21
tasks.withType<JavaCompile>().configureEach {
options.encoding = "UTF-8"
if (targetJavaVersion >= 10 || JavaVersion.current().isJava10Compatible) {
options.release.set(targetJavaVersion)
}
}
java {
val javaVersion = JavaVersion.toVersion(targetJavaVersion)
if (JavaVersion.current() < javaVersion) {
toolchain {
languageVersion.set(JavaLanguageVersion.of(targetJavaVersion))
}
}
withSourcesJar()
}
tasks.build {
dependsOn(tasks.shadowJar)
}

View File

@@ -0,0 +1,60 @@
package net.momirealms.craftEngineFabricMod.client;
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.api.blockrenderlayer.v1.BlockRenderLayerMap;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry;
import net.minecraft.block.Block;
import net.minecraft.client.gui.screen.DisconnectedScreen;
import net.minecraft.client.render.RenderLayer;
import net.minecraft.registry.Registries;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
import net.momirealms.craftEngineFabricMod.client.config.ModConfig;
import net.momirealms.craftEngineFabricMod.client.network.CraftEnginePayload;
import net.momirealms.craftEngineFabricMod.client.util.BlockUtils;
import java.nio.charset.StandardCharsets;
public class CraftEngineFabricModClient implements ClientModInitializer {
public static final String MOD_ID = "craftengine";
@Override
public void onInitializeClient() {
PayloadTypeRegistry.playS2C().register(CraftEnginePayload.ID, CraftEnginePayload.CODEC);
Registries.BLOCK.forEach(block -> {
Identifier id = Registries.BLOCK.getId(block);
if (id.getNamespace().equals(CraftEngineFabricModClient.MOD_ID)) {
BlockRenderLayerMap.INSTANCE.putBlock(block, RenderLayer.getCutoutMipped());
if (id.getPath().contains("leaves")) {
BlockUtils.registerColor(block);
}
}
});
ClientTickEvents.START_CLIENT_TICK.register(client -> {
if (!ModConfig.enableNetwork) {
ClientPlayNetworking.unregisterGlobalReceiver(CraftEnginePayload.ID.id());
return;
}
ClientPlayNetworking.registerGlobalReceiver(CraftEnginePayload.ID, (payload, context) -> {
try {
byte[] data = payload.data();
String decoded = new String(data, StandardCharsets.UTF_8);
if (decoded.startsWith("cp:")) {
int blockRegistrySize = Integer.parseInt(decoded.substring(3));
if (Block.STATE_IDS.size() != blockRegistrySize) {
client.disconnect(new DisconnectedScreen(
client.currentScreen,
Text.translatable("disconnect.craftengine.title"),
Text.translatable("disconnect.craftengine.block_registry_mismatch"))
);
}
}
} catch (Exception e) {
e.printStackTrace();
}
});
});
}
}

View File

@@ -0,0 +1,35 @@
package net.momirealms.craftEngineFabricMod.client.config;
import me.shedaniel.clothconfig2.api.ConfigBuilder;
import me.shedaniel.clothconfig2.api.ConfigCategory;
import me.shedaniel.clothconfig2.api.ConfigEntryBuilder;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
public class ModConfig {
public static boolean enableNetwork = true;
public static Screen getConfigScreen(Screen parent) {
ConfigBuilder builder = ConfigBuilder.create()
.setParentScreen(parent)
.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),
enableNetwork)
.setDefaultValue(true)
.setSaveConsumer(newValue -> enableNetwork = newValue)
.setTooltip(
Text.translatable("tooltip.craftengine.enable_network")
.formatted(Formatting.GRAY)
)
.build());
return builder.build();
}
}

View File

@@ -0,0 +1,11 @@
package net.momirealms.craftEngineFabricMod.client.config;
import com.terraformersmc.modmenu.api.ConfigScreenFactory;
import com.terraformersmc.modmenu.api.ModMenuApi;
public class ModMenuIntegration implements ModMenuApi {
@Override
public ConfigScreenFactory<?> getModConfigScreenFactory() {
return ModConfig::getConfigScreen;
}
}

View File

@@ -0,0 +1,20 @@
package net.momirealms.craftEngineFabricMod.client.network;
import net.minecraft.network.RegistryByteBuf;
import net.minecraft.network.codec.PacketCodec;
public class ByteArrayCodec implements PacketCodec<RegistryByteBuf, byte[]> {
@Override
public void encode(RegistryByteBuf buf, byte[] value) {
buf.writeBytes(value);
}
@Override
public byte[] decode(RegistryByteBuf buf) {
int length = buf.readableBytes();
byte[] data = new byte[length];
buf.readBytes(data);
return data;
}
}

View File

@@ -0,0 +1,20 @@
package net.momirealms.craftEngineFabricMod.client.network;
import net.minecraft.network.RegistryByteBuf;
import net.minecraft.network.codec.PacketCodec;
import net.minecraft.network.packet.CustomPayload;
import net.minecraft.util.Identifier;
public record CraftEnginePayload(byte[] data) implements CustomPayload {
public static final Identifier ADD_CRAFTENGINE_BLOCK = Identifier.of("craftengine", "payload");
public static final Id<CraftEnginePayload> ID = new Id<>(CraftEnginePayload.ADD_CRAFTENGINE_BLOCK);
public static final PacketCodec<RegistryByteBuf, CraftEnginePayload> CODEC = PacketCodec.tuple(
new ByteArrayCodec(), CraftEnginePayload::data,
CraftEnginePayload::new
);
@Override
public Id<? extends CustomPayload> getId() {
return ID;
}
}

View File

@@ -0,0 +1,21 @@
package net.momirealms.craftEngineFabricMod.client.util;
import net.fabricmc.fabric.api.client.rendering.v1.ColorProviderRegistry;
import net.minecraft.block.Block;
import net.minecraft.client.color.world.BiomeColors;
import net.minecraft.world.biome.FoliageColors;
public class BlockUtils {
public static void registerColor(Block block) {
ColorProviderRegistry.BLOCK.register(
(state, world, pos, tintIndex) -> {
if (world != null && pos != null) {
return BiomeColors.getFoliageColor(world, pos);
}
return FoliageColors.DEFAULT;
},
block
);
}
}

View File

@@ -0,0 +1,117 @@
package net.momirealms.craftEngineFabricMod.util;
import com.mojang.brigadier.StringReader;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.command.argument.BlockArgumentParser;
import net.minecraft.registry.BuiltinRegistries;
import net.minecraft.registry.Registries;
import net.minecraft.registry.RegistryKeys;
import net.minecraft.registry.RegistryWrapper;
import net.minecraft.util.Identifier;
import org.yaml.snakeyaml.Yaml;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
public class YamlUtils {
public static final String CONFIG_DIR = "config/craft-engine-fabric-mod/";
private static final Yaml yaml = new Yaml();
private static final RegistryWrapper<Block> registryWrapper = BuiltinRegistries.createWrapperLookup().getOrThrow(RegistryKeys.BLOCK);
public static <T> T loadConfig(Path filePath) throws IOException {
try (InputStream inputStream = Files.newInputStream(filePath)) {
return yaml.load(inputStream);
}
}
public static void ensureConfigFile(String fileName) throws IOException {
Path configDir = Path.of(CONFIG_DIR);
if (!Files.exists(configDir)) {
Files.createDirectories(configDir);
}
Path targetPath = configDir.resolve(fileName);
if (Files.exists(targetPath)) return;
String resourcePath = "assets/craft-engine-fabric-mod/config/" + fileName;
try (InputStream inputStream = YamlUtils.class.getClassLoader().getResourceAsStream(resourcePath)) {
if (inputStream == null) {
throw new IOException("Default config file not found: " + resourcePath);
}
Files.copy(inputStream, targetPath, StandardCopyOption.REPLACE_EXISTING);
}
}
public static Map<Identifier, Integer> loadMappingsAndAdditionalBlocks() throws IOException {
Path mappingPath = Path.of(CONFIG_DIR + "mappings.yml");
Path additionalYamlPath = Path.of(CONFIG_DIR + "additional-real-blocks.yml");
Map<String, String> blockStateMappings = loadConfig(mappingPath);
validateBlockStateMappings(blockStateMappings);
Map<Identifier, Integer> blockTypeCounter = new LinkedHashMap<>();
Map<Integer, Integer> appearanceMapper = new HashMap<>();
for (Map.Entry<String, String> entry : blockStateMappings.entrySet()) {
processBlockStateMapping(entry, appearanceMapper, blockTypeCounter);
}
Map<String, Integer> additionalYaml = loadConfig(additionalYamlPath);
return buildRegisteredRealBlockSlots(blockTypeCounter, additionalYaml);
}
private static void validateBlockStateMappings(Map<String, String> blockStateMappings) {
Map<String, String> temp = new HashMap<>(blockStateMappings);
for (Map.Entry<String, String> entry : temp.entrySet()) {
String state = entry.getValue();
blockStateMappings.remove(state);
}
}
private static void processBlockStateMapping(
Map.Entry<String, String> entry,
Map<Integer, Integer> stateIdMapper,
Map<Identifier, Integer> blockUsageCounter
) {
final BlockState sourceState = createBlockData(entry.getKey());
final BlockState targetState = createBlockData(entry.getValue());
if (sourceState == null || targetState == null) {
return;
}
final int sourceStateId = Block.STATE_IDS.getRawId(sourceState);
final int targetStateId = Block.STATE_IDS.getRawId(targetState);
if (stateIdMapper.putIfAbsent(sourceStateId, targetStateId) == null) {
final Block sourceBlock = sourceState.getBlock();
final Identifier blockId = Registries.BLOCK.getId(sourceBlock);
blockUsageCounter.merge(blockId, 1, Integer::sum);
}
}
public static BlockState createBlockData(String blockState) {
try {
StringReader reader = new StringReader(blockState);
BlockArgumentParser.BlockResult arg = BlockArgumentParser.block(registryWrapper, reader, true);
return arg.blockState();
} catch (Exception e) {
return null;
}
}
private static LinkedHashMap<Identifier, Integer> buildRegisteredRealBlockSlots(Map<Identifier, Integer> counter, Map<String, Integer> additionalYaml) {
LinkedHashMap<Identifier, Integer> map = new LinkedHashMap<>();
for (Map.Entry<Identifier, Integer> entry : counter.entrySet()) {
String id = entry.getKey().toString();
Integer additionalStates = additionalYaml.get(id);
int internalIds = entry.getValue() + (additionalStates != null ? additionalStates : 0);
map.put(entry.getKey(), internalIds);
}
return map;
}
}

View File

@@ -0,0 +1,81 @@
package net.momirealms.craftEngineFabricMod;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.block.BlockState;
import net.minecraft.util.Identifier;
import net.momirealms.craftEngineFabricMod.client.config.ModConfig;
import net.momirealms.craftEngineFabricMod.util.BlockUtils;
import net.momirealms.craftEngineFabricMod.util.LoggerFilter;
import net.momirealms.craftEngineFabricMod.util.RegisterBlocks;
import net.momirealms.craftEngineFabricMod.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;
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";
@Override
public void onInitialize() {
loadConfig();
Runtime.getRuntime().addShutdownHook(new Thread(this::saveConfig));
LoggerFilter.filter();
try {
YamlUtils.ensureConfigFile("additional-real-blocks.yml");
YamlUtils.ensureConfigFile("mappings.yml");
YamlUtils.ensureConfigFile("config.yml");
Map<Identifier, Integer> map = YamlUtils.loadMappingsAndAdditionalBlocks();
for (Map.Entry<Identifier, Integer> entry : map.entrySet()) {
Identifier replacedBlockId = entry.getKey();
for (int i = 0; i < entry.getValue(); i++) {
BlockState blockState = YamlUtils.createBlockData("minecraft:" + replacedBlockId.getPath());
RegisterBlocks.register(
replacedBlockId.getPath() + "_" + i,
BlockUtils.canPassThrough(blockState)
);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
@SuppressWarnings("unchecked")
private void loadConfig() {
if (!Files.exists(CONFIG_PATH)) {
saveConfig();
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);
} catch (IOException e) {
e.printStackTrace();
}
}
}

View File

@@ -0,0 +1,45 @@
package net.momirealms.craftEngineFabricMod.util;
import net.minecraft.block.AbstractBlock;
import net.minecraft.block.BlockState;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import static java.util.Objects.requireNonNull;
public class BlockUtils {
private final static Field COLLIDABLE_FIELD = requireNonNull(getDeclaredField(AbstractBlock.Settings.class, boolean.class, 0));
@Nullable
public static Field getDeclaredField(final Class<?> clazz, final Class<?> type, int index) {
int i = 0;
for (final Field field : clazz.getDeclaredFields()) {
if (field.getType() == type) {
if (index == i) {
return setAccessible(field);
}
i++;
}
}
return null;
}
@NotNull
public static <T extends AccessibleObject> T setAccessible(@NotNull final T o) {
o.setAccessible(true);
return o;
}
public static boolean canPassThrough(BlockState state) {
try {
if (state == null) return false;
AbstractBlock.Settings settings = state.getBlock().getSettings();
boolean collidable = COLLIDABLE_FIELD.getBoolean(settings);
return !collidable;
} catch (IllegalAccessException e) {
throw new RuntimeException("Failed to access 'collidable' field", e);
}
}
}

View File

@@ -0,0 +1,21 @@
package net.momirealms.craftEngineFabricMod.util;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.Logger;
import org.apache.logging.log4j.core.filter.AbstractFilter;
public class LoggerFilter {
public static void filter() {
Logger rootLogger = (Logger) LogManager.getRootLogger();
rootLogger.addFilter(new AbstractFilter() {
@Override
public Result filter(LogEvent event) {
if (event.getMessage().getFormattedMessage().contains("Missing model for variant: 'craftengine:")) {
return Result.DENY;
}
return Result.NEUTRAL;
}
});
}
}

View File

@@ -0,0 +1,33 @@
package net.momirealms.craftEngineFabricMod.util;
import net.minecraft.block.AbstractBlock;
import net.minecraft.block.Block;
import net.minecraft.registry.Registries;
import net.minecraft.registry.Registry;
import net.minecraft.registry.RegistryKey;
import net.minecraft.registry.RegistryKeys;
import net.minecraft.util.Identifier;
import net.momirealms.craftEngineFabricMod.CraftEngineFabricMod;
import java.util.function.Function;
public class RegisterBlocks {
@SuppressWarnings("UnusedReturnValue")
public static Block register(String name, boolean canPassThrough) {
AbstractBlock.Settings settings = Block.Settings.create().nonOpaque().strength(-1.0F, 3600000.0F);
if (canPassThrough) settings.noCollision();
return register(name, Block::new, settings);
}
public static Block register(String name, Function<AbstractBlock.Settings, Block> blockFactory, AbstractBlock.Settings settings) {
RegistryKey<Block> blockKey = keyOfBlock(name);
Block block = blockFactory.apply(settings.registryKey(blockKey));
return Registry.register(Registries.BLOCK, blockKey, block);
}
private static RegistryKey<Block> keyOfBlock(String name) {
return RegistryKey.of(RegistryKeys.BLOCK, Identifier.of(CraftEngineFabricMod.MOD_ID, name));
}
}

View File

@@ -0,0 +1,26 @@
# This file will register an additional number of block states to the server, based on the mappings defined in mappings.yml.
# If you're unsure what this means, you can read the following explanation below.
# Suppose you create a new type of leaf, but its appearance has only two states (waterlogged and normal).
# However, because of the defined properties such as distance, persistent, and waterlogged, it requires at least 2x2x7 = 28 different block states.
# By default, the plugin only registers the same number of block states as those defined in the mappings.yml file.
# Therefore, during actual configuration, you will notice that the internal IDs are insufficient
# (without configuring additional-real-block, one type of leaf can only provide 26 states, whereas creating a new leaf requires 28 states).
# The purpose of this file is to register additional block states with the server when starting it, ensuring the correct mapping between real blocks and the visual appearance of fake blocks on the server.
# Some common questions:
# Q: Do I need to restart the server for the changes to take effect?
# A: Yes! Modifying the block registry while the server is running is extremely risky.
# Q: When do I need to configure this file?
# A: When the number of real block IDs is insufficient, but there are still available appearances.
# By default, the plugin only registers an additional 112 oak leaf block states (for the default configuration needs [>=28 states]).
minecraft:oak_leaves: 112
minecraft:oak_sapling: 1
minecraft:birch_sapling: 1
minecraft:spruce_sapling: 1
minecraft:jungle_sapling: 1
minecraft:dark_oak_sapling: 1
minecraft:acacia_sapling: 1
minecraft:cherry_sapling: 1

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@@ -0,0 +1,8 @@
{
"title.craftengine.config": "CraftEngine Settings",
"category.craftengine.general": "General",
"option.craftengine.enable_network": "Enable custom blocks in server",
"tooltip.craftengine.enable_network": "You need to re-enter the server to take effect",
"disconnect.craftengine.block_registry_mismatch": "Block registry size mismatch!",
"disconnect.craftengine.title": "CraftEngine Connection Error"
}

View File

@@ -0,0 +1,8 @@
{
"title.craftengine.config": "CraftEngine设置",
"category.craftengine.general": "通用",
"option.craftengine.enable_network": "启用服务器内自定义方块",
"tooltip.craftengine.enable_network": "需要重新进入服务器即可生效",
"disconnect.craftengine.block_registry_mismatch": "方块注册表大小不匹配!",
"disconnect.craftengine.title": "CraftEngine连接错误"
}

View File

@@ -0,0 +1,34 @@
{
"schemaVersion": 1,
"id": "craft-engine-fabric-mod",
"version": "${version}",
"name": "CraftEngine Fabric Mod",
"description": "CraftEngine Fabric Mod",
"authors": [
"jhqwqmc"
],
"contact": {
"name": "CraftEngine",
"homepage": "https://github.com/Xiao-MoMi/craft-engine",
"issues": "https://github.com/Xiao-MoMi/craft-engine/issues"
},
"license": "GPL-3.0",
"icon": "assets/craft-engine-fabric-mod/icon.png",
"environment": "client",
"entrypoints": {
"client": ["net.momirealms.craftEngineFabricMod.client.CraftEngineFabricModClient"],
"main": ["net.momirealms.craftEngineFabricMod.CraftEngineFabricMod"],
"modmenu": ["net.momirealms.craftEngineFabricMod.client.config.ModMenuIntegration"]
},
"depends": {
"fabricloader": ">=${loader_version}",
"fabric": "*",
"minecraft": "${minecraft_version}",
"modmenu": ">=${modmenu_version}",
"cloth-config": ">=${cloth_version}"
}
}

View File

@@ -36,4 +36,8 @@ public interface NetWorkUser {
Object platformPlayer();
Map<Integer, List<Integer>> furnitureView();
boolean usingClientMod();
void setUsingClientMod(boolean usingClientMod);
}

View File

@@ -47,7 +47,14 @@ mojang_brigadier_version=1.0.18
byte_buddy_version=1.15.11
snake_yaml_version=2.3
anti_grief_version=0.13
# Fabric Dependencies
fabric_version=0.119.2+1.21.4
yarn_mappings=1.21.4+build.8
loader_version=0.16.10
modmenu_version=13.0.3
cloth_version=17.0.144
org.gradle.jvmargs=-Xmx1G
# Proxy settings
#systemProp.socks.proxyHost=127.0.0.1
#systemProp.socks.proxyPort=7890

View File

@@ -6,6 +6,7 @@ include(":bukkit:legacy")
include(":bukkit:compatibility")
include(":bukkit-loader")
include(":server-mod")
include(":client-mod")
pluginManagement {
plugins {
kotlin("jvm") version "2.0.20"
@@ -13,6 +14,6 @@ pluginManagement {
repositories {
gradlePluginPortal()
maven("https://repo.papermc.io/repository/maven-public/")
maven("https://maven.fabricmc.net/")
}
}