mirror of
https://github.com/Xiao-MoMi/craft-engine.git
synced 2025-12-28 03:19:14 +00:00
@@ -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")
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
108
client-mod/build.gradle.kts
Normal 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)
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 |
@@ -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"
|
||||
}
|
||||
@@ -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连接错误"
|
||||
}
|
||||
34
client-mod/src/main/resources/fabric.mod.json
Normal file
34
client-mod/src/main/resources/fabric.mod.json
Normal 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}"
|
||||
}
|
||||
}
|
||||
@@ -36,4 +36,8 @@ public interface NetWorkUser {
|
||||
Object platformPlayer();
|
||||
|
||||
Map<Integer, List<Integer>> furnitureView();
|
||||
|
||||
boolean usingClientMod();
|
||||
|
||||
void setUsingClientMod(boolean usingClientMod);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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/")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user