9
0
mirror of https://github.com/Xiao-MoMi/craft-engine.git synced 2025-12-26 18:39:20 +00:00

Merge pull request #190 from jhqwqmc/dev

feat(network): 完善客户端侧物品数据处理
This commit is contained in:
XiaoMoMi
2025-05-28 15:17:50 +08:00
committed by GitHub
17 changed files with 750 additions and 69 deletions

View File

@@ -101,11 +101,26 @@ items#topaz_gears:
settings:
tags:
- "default:topaz_tools"
client-bound-data:
components:
can_break:
blocks: minecraft:note_block
state:
"instrument": "hat"
"note":
"min": "0"
"max": "2"
"powered": "false"
data:
item-name: "<!i><#FF8C00><i18n:item.topaz_axe>"
tooltip-style: minecraft:topaz
components:
minecraft:max_damage: 64
can_break:
blocks:
- "craftengine:note_block_0"
- "craftengine:note_block_1"
- "craftengine:note_block_2"
model:
template: default:model/simplified_handheld
arguments:

View File

@@ -9,6 +9,7 @@ import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
import net.momirealms.craftengine.bukkit.plugin.network.id.PacketIdFinder;
import net.momirealms.craftengine.bukkit.plugin.network.id.PacketIds1_20;
import net.momirealms.craftengine.bukkit.plugin.network.id.PacketIds1_20_5;
import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer;
@@ -40,9 +41,8 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
private static BukkitNetworkManager instance;
private static final Map<Class<?>, TriConsumer<NetWorkUser, NMSPacketEvent, Object>> NMS_PACKET_HANDLERS = new HashMap<>();
// only for game stage for the moment
// todo 优化成 数组
private static final Map<Integer, BiConsumer<NetWorkUser, ByteBufPacketEvent>> S2C_BYTE_BUFFER_PACKET_HANDLERS = new HashMap<>();
private static final Map<Integer, BiConsumer<NetWorkUser, ByteBufPacketEvent>> C2S_BYTE_BUFFER_PACKET_HANDLERS = new HashMap<>();
private static BiConsumer<NetWorkUser, ByteBufPacketEvent>[] S2C_BYTE_BUFFER_PACKET_HANDLERS;
private static BiConsumer<NetWorkUser, ByteBufPacketEvent>[] C2S_BYTE_BUFFER_PACKET_HANDLERS;
private static void registerNMSPacketConsumer(final TriConsumer<NetWorkUser, NMSPacketEvent, Object> function, @Nullable Class<?> packet) {
if (packet == null) return;
@@ -51,12 +51,18 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
private static void registerS2CByteBufPacketConsumer(final BiConsumer<NetWorkUser, ByteBufPacketEvent> function, int id) {
if (id == -1) return;
S2C_BYTE_BUFFER_PACKET_HANDLERS.put(id, function);
if (id < 0 || id >= S2C_BYTE_BUFFER_PACKET_HANDLERS.length) {
throw new IllegalArgumentException("Invalid packet id: " + id);
}
S2C_BYTE_BUFFER_PACKET_HANDLERS[id] = function;
}
private static void registerC2SByteBufPacketConsumer(final BiConsumer<NetWorkUser, ByteBufPacketEvent> function, int id) {
if (id == -1) return;
C2S_BYTE_BUFFER_PACKET_HANDLERS.put(id, function);
if (id < 0 || id >= C2S_BYTE_BUFFER_PACKET_HANDLERS.length) {
throw new IllegalArgumentException("Invalid packet id: " + id);
}
C2S_BYTE_BUFFER_PACKET_HANDLERS[id] = function;
}
private final BiConsumer<Object, List<Object>> packetsConsumer;
@@ -81,8 +87,13 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
private static boolean hasModelEngine;
private static boolean hasViaVersion;
@SuppressWarnings("unchecked")
public BukkitNetworkManager(BukkitCraftEngine plugin) {
instance = this;
S2C_BYTE_BUFFER_PACKET_HANDLERS = new BiConsumer[PacketIdFinder.maxS2CPacketId() + 1];
C2S_BYTE_BUFFER_PACKET_HANDLERS = new BiConsumer[PacketIdFinder.maxC2SPacketId() + 1];
Arrays.fill(S2C_BYTE_BUFFER_PACKET_HANDLERS, Handlers.DO_NOTHING);
Arrays.fill(C2S_BYTE_BUFFER_PACKET_HANDLERS, Handlers.DO_NOTHING);
hasModelEngine = Bukkit.getPluginManager().getPlugin("ModelEngine") != null;
hasViaVersion = Bukkit.getPluginManager().getPlugin("ViaVersion") != null;
this.plugin = plugin;
@@ -647,13 +658,13 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
protected void handleS2CByteBufPacket(NetWorkUser user, ByteBufPacketEvent event) {
int packetID = event.packetID();
Optional.ofNullable(S2C_BYTE_BUFFER_PACKET_HANDLERS.get(packetID))
Optional.ofNullable(S2C_BYTE_BUFFER_PACKET_HANDLERS[packetID])
.ifPresent(function -> function.accept(user, event));
}
protected void handleC2SByteBufPacket(NetWorkUser user, ByteBufPacketEvent event) {
int packetID = event.packetID();
Optional.ofNullable(C2S_BYTE_BUFFER_PACKET_HANDLERS.get(packetID))
Optional.ofNullable(C2S_BYTE_BUFFER_PACKET_HANDLERS[packetID])
.ifPresent(function -> function.accept(user, event));
}
@@ -699,4 +710,14 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
}
return output;
}
@FunctionalInterface
public interface Handlers extends BiConsumer<NetWorkUser, ByteBufPacketEvent> {
Handlers DO_NOTHING = doNothing();
static Handlers doNothing() {
return (user, byteBufPacketEvent) -> {
};
}
}
}

View File

@@ -73,15 +73,15 @@ import java.util.*;
import java.util.function.BiConsumer;
public class PacketConsumers {
private static AddEntityHandler[] ADD_ENTITY_HANDLERS;
private static BukkitNetworkManager.Handlers[] ADD_ENTITY_HANDLERS;
private static int[] mappings;
private static int[] mappingsMOD;
private static IntIdentityList BLOCK_LIST;
private static IntIdentityList BIOME_LIST;
public static void initEntities(int registrySize) {
ADD_ENTITY_HANDLERS = new AddEntityHandler[registrySize];
Arrays.fill(ADD_ENTITY_HANDLERS, AddEntityHandler.DO_NOTHING);
ADD_ENTITY_HANDLERS = new BukkitNetworkManager.Handlers[registrySize];
Arrays.fill(ADD_ENTITY_HANDLERS, BukkitNetworkManager.Handlers.DO_NOTHING);
ADD_ENTITY_HANDLERS[Reflections.instance$EntityType$FALLING_BLOCK$registryId] = (user, event) -> {
FriendlyByteBuf buf = event.getBuffer();
int id = buf.readVarInt();
@@ -121,6 +121,21 @@ public class PacketConsumers {
ADD_ENTITY_HANDLERS[Reflections.instance$EntityType$BLOCK_DISPLAY$registryId] = simpleAddEntityHandler(BlockDisplayPacketHandler.INSTANCE);
ADD_ENTITY_HANDLERS[Reflections.instance$EntityType$TEXT_DISPLAY$registryId] = simpleAddEntityHandler(TextDisplayPacketHandler.INSTANCE);
ADD_ENTITY_HANDLERS[Reflections.instance$EntityType$ARMOR_STAND$registryId] = simpleAddEntityHandler(ArmorStandPacketHandler.INSTANCE);
ADD_ENTITY_HANDLERS[Reflections.instance$EntityType$ITEM_DISPLAY$registryId] = simpleAddEntityHandler(ItemDisplayPacketHandler.INSTANCE);
ADD_ENTITY_HANDLERS[Reflections.instance$EntityType$FIREBALL$registryId] = simpleAddEntityHandler(CommonItemPacketHandler.INSTANCE);
ADD_ENTITY_HANDLERS[Reflections.instance$EntityType$EYE_OF_ENDER$registryId] = simpleAddEntityHandler(CommonItemPacketHandler.INSTANCE);
ADD_ENTITY_HANDLERS[Reflections.instance$EntityType$FIREWORK_ROCKET$registryId] = simpleAddEntityHandler(CommonItemPacketHandler.INSTANCE);
ADD_ENTITY_HANDLERS[Reflections.instance$EntityType$ITEM$registryId] = simpleAddEntityHandler(CommonItemPacketHandler.INSTANCE);
ADD_ENTITY_HANDLERS[Reflections.instance$EntityType$ITEM_FRAME$registryId] = simpleAddEntityHandler(CommonItemPacketHandler.INSTANCE);
ADD_ENTITY_HANDLERS[Reflections.instance$EntityType$SMALL_FIREBALL$registryId] = simpleAddEntityHandler(CommonItemPacketHandler.INSTANCE);
ADD_ENTITY_HANDLERS[Reflections.instance$EntityType$EGG$registryId] = simpleAddEntityHandler(CommonItemPacketHandler.INSTANCE);
ADD_ENTITY_HANDLERS[Reflections.instance$EntityType$ENDER_PEARL$registryId] = simpleAddEntityHandler(CommonItemPacketHandler.INSTANCE);
ADD_ENTITY_HANDLERS[Reflections.instance$EntityType$EXPERIENCE_BOTTLE$registryId] = simpleAddEntityHandler(CommonItemPacketHandler.INSTANCE);
ADD_ENTITY_HANDLERS[Reflections.instance$EntityType$SNOWBALL$registryId] = simpleAddEntityHandler(CommonItemPacketHandler.INSTANCE);
ADD_ENTITY_HANDLERS[Reflections.instance$EntityType$POTION$registryId] = simpleAddEntityHandler(CommonItemPacketHandler.INSTANCE);
if (VersionHelper.isOrAbove1_20_5()) {
ADD_ENTITY_HANDLERS[Reflections.instance$EntityType$OMINOUS_ITEM_SPAWNER$registryId] = simpleAddEntityHandler(CommonItemPacketHandler.INSTANCE);
}
}
public static void initBlocks(Map<Integer, Integer> map, int registrySize) {
@@ -2148,11 +2163,12 @@ public class PacketConsumers {
buf.writeByte(buttonNum);
buf.writeVarInt(clickType);
buf.writeVarInt(changedSlots.size());
Object newFriendlyBuf = FastNMS.INSTANCE.constructor$FriendlyByteBuf(buf);
changedSlots.forEach((k, v) -> {
buf.writeShort(k);
FastNMS.INSTANCE.method$FriendlyByteBuf$writeItem(buf, v);
FastNMS.INSTANCE.method$FriendlyByteBuf$writeItem(newFriendlyBuf, v);
});
FastNMS.INSTANCE.method$FriendlyByteBuf$writeItem(buf, carriedItem);
FastNMS.INSTANCE.method$FriendlyByteBuf$writeItem(newFriendlyBuf, carriedItem);
}
} catch (Exception e) {
CraftEngine.instance().logger().warn("Failed to handle ServerboundContainerClickPacket", e);
@@ -2270,17 +2286,7 @@ public class PacketConsumers {
}
};
@FunctionalInterface
public interface AddEntityHandler extends BiConsumer<NetWorkUser, ByteBufPacketEvent> {
AddEntityHandler DO_NOTHING = doNothing();
static AddEntityHandler doNothing() {
return (user, byteBufPacketEvent) -> {
};
}
}
private static AddEntityHandler simpleAddEntityHandler(EntityPacketHandler handler) {
private static BukkitNetworkManager.Handlers simpleAddEntityHandler(EntityPacketHandler handler) {
return (user, event) -> {
FriendlyByteBuf buf = event.getBuffer();
user.entityPacketHandlers().put(buf.readVarInt(), handler);

View File

@@ -0,0 +1,53 @@
package net.momirealms.craftengine.bukkit.plugin.network.handler;
import net.momirealms.craftengine.bukkit.item.BukkitItemManager;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer;
import net.momirealms.craftengine.bukkit.util.EntityDataUtils;
import net.momirealms.craftengine.core.item.ItemBuildContext;
import net.momirealms.craftengine.core.plugin.network.ByteBufPacketEvent;
import net.momirealms.craftengine.core.plugin.network.EntityPacketHandler;
import net.momirealms.craftengine.core.plugin.network.NetWorkUser;
import net.momirealms.craftengine.core.util.FriendlyByteBuf;
import org.bukkit.inventory.ItemStack;
import java.util.List;
import java.util.Optional;
public class CommonItemPacketHandler implements EntityPacketHandler {
public static final CommonItemPacketHandler INSTANCE = new CommonItemPacketHandler();
@Override
public void handleSetEntityData(NetWorkUser user, ByteBufPacketEvent event) {
FriendlyByteBuf buf = event.getBuffer();
ItemBuildContext context = ItemBuildContext.of((BukkitServerPlayer) user);
int id = buf.readVarInt();
boolean isChanged = false;
List<Object> packedItems = FastNMS.INSTANCE.method$ClientboundSetEntityDataPacket$unpack(buf);
for (int i = 0; i < packedItems.size(); i++) {
Object packedItem = packedItems.get(i);
int entityDataId = FastNMS.INSTANCE.field$SynchedEntityData$DataValue$id(packedItem);
if (entityDataId == EntityDataUtils.ITEM_DATA_ID) {
Object nmsItemStack = FastNMS.INSTANCE.field$SynchedEntityData$DataValue$value(packedItem);
ItemStack itemStack = FastNMS.INSTANCE.method$CraftItemStack$asCraftMirror(nmsItemStack);
Optional<ItemStack> optional = BukkitItemManager.instance().s2c(itemStack, context);
if (optional.isPresent()) {
isChanged = true;
itemStack = optional.get();
Object serializer = FastNMS.INSTANCE.field$SynchedEntityData$DataValue$serializer(packedItem);
packedItems.set(i, FastNMS.INSTANCE.constructor$SynchedEntityData$DataValue(
entityDataId, serializer, FastNMS.INSTANCE.method$CraftItemStack$asNMSCopy(itemStack)
));
break;
}
}
}
if (isChanged) {
event.setChanged(true);
buf.clear();
buf.writeVarInt(event.packetID());
buf.writeVarInt(id);
FastNMS.INSTANCE.method$ClientboundSetEntityDataPacket$pack(packedItems, buf);
}
}
}

View File

@@ -0,0 +1,53 @@
package net.momirealms.craftengine.bukkit.plugin.network.handler;
import net.momirealms.craftengine.bukkit.item.BukkitItemManager;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer;
import net.momirealms.craftengine.bukkit.util.EntityDataUtils;
import net.momirealms.craftengine.core.item.ItemBuildContext;
import net.momirealms.craftengine.core.plugin.network.ByteBufPacketEvent;
import net.momirealms.craftengine.core.plugin.network.EntityPacketHandler;
import net.momirealms.craftengine.core.plugin.network.NetWorkUser;
import net.momirealms.craftengine.core.util.FriendlyByteBuf;
import org.bukkit.inventory.ItemStack;
import java.util.List;
import java.util.Optional;
public class ItemDisplayPacketHandler implements EntityPacketHandler {
public static final ItemDisplayPacketHandler INSTANCE = new ItemDisplayPacketHandler();
@Override
public void handleSetEntityData(NetWorkUser user, ByteBufPacketEvent event) {
FriendlyByteBuf buf = event.getBuffer();
ItemBuildContext context = ItemBuildContext.of((BukkitServerPlayer) user);
int id = buf.readVarInt();
boolean isChanged = false;
List<Object> packedItems = FastNMS.INSTANCE.method$ClientboundSetEntityDataPacket$unpack(buf);
for (int i = 0; i < packedItems.size(); i++) {
Object packedItem = packedItems.get(i);
int entityDataId = FastNMS.INSTANCE.field$SynchedEntityData$DataValue$id(packedItem);
if (entityDataId == EntityDataUtils.DISPLAYED_ITEM_DATA_ID) {
Object nmsItemStack = FastNMS.INSTANCE.field$SynchedEntityData$DataValue$value(packedItem);
ItemStack itemStack = FastNMS.INSTANCE.method$CraftItemStack$asCraftMirror(nmsItemStack);
Optional<ItemStack> optional = BukkitItemManager.instance().s2c(itemStack, context);
if (optional.isPresent()) {
isChanged = true;
itemStack = optional.get();
Object serializer = FastNMS.INSTANCE.field$SynchedEntityData$DataValue$serializer(packedItem);
packedItems.set(i, FastNMS.INSTANCE.constructor$SynchedEntityData$DataValue(
entityDataId, serializer, FastNMS.INSTANCE.method$CraftItemStack$asNMSCopy(itemStack)
));
break;
}
}
}
if (isChanged) {
event.setChanged(true);
buf.clear();
buf.writeVarInt(event.packetID());
buf.writeVarInt(id);
FastNMS.INSTANCE.method$ClientboundSetEntityDataPacket$pack(packedItems, buf);
}
}
}

View File

@@ -6,12 +6,15 @@ import net.momirealms.craftengine.bukkit.util.Reflections;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.util.VersionHelper;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
public class PacketIdFinder {
private static final Map<String, Map<String, Integer>> gamePacketIdsByName = new HashMap<>();
private static final Map<String, Map<Class<?>, Integer>> gamePacketIdsByClazz = new HashMap<>();
private static final int maxC2SPacketId;
private static final int maxS2CPacketId;
static {
try {
@@ -34,6 +37,23 @@ public class PacketIdFinder {
} catch (Exception e) {
CraftEngine.instance().logger().warn("Failed to get packets", e);
}
maxS2CPacketId = calculateMaxId("clientbound");
maxC2SPacketId = calculateMaxId("serverbound");
}
private static int calculateMaxId(String direction) {
if (VersionHelper.isOrAbove1_20_5()) {
return gamePacketIdsByName.getOrDefault(direction, Collections.emptyMap()).size();
} else {
return gamePacketIdsByClazz.getOrDefault(direction, Collections.emptyMap()).size();
}
}
public static int maxC2SPacketId() {
return maxC2SPacketId;
}
public static int maxS2CPacketId() {
return maxS2CPacketId;
}
public static int clientboundByName(String packetName) {

View File

@@ -0,0 +1,22 @@
package net.momirealms.craftengine.bukkit.plugin.network.payload;
import io.netty.buffer.ByteBuf;
import java.util.function.Function;
public interface NetWorkCodec<T> extends NetWorkEncoder<T>, NetWorkDecoder<T> {
default <O> NetWorkCodec<O> map(Function<? super T, ? extends O> factory, Function<? super O, ? extends T> getter) {
return new NetWorkCodec<>() {
@Override
public O decode(ByteBuf in) {
return factory.apply(NetWorkCodec.this.decode(in));
}
@Override
public void encode(ByteBuf out, O value) {
NetWorkCodec.this.encode(out, getter.apply(value));
}
};
}
}

View File

@@ -0,0 +1,394 @@
package net.momirealms.craftengine.bukkit.plugin.network.payload;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufInputStream;
import io.netty.buffer.ByteBufOutputStream;
import io.netty.buffer.ByteBufUtil;
import io.netty.handler.codec.DecoderException;
import io.netty.handler.codec.EncoderException;
import net.momirealms.craftengine.core.util.MCUtils;
import net.momirealms.sparrow.nbt.CompoundTag;
import net.momirealms.sparrow.nbt.NBT;
import net.momirealms.sparrow.nbt.Tag;
import org.joml.Quaternionf;
import org.joml.Vector3f;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Optional;
import java.util.OptionalInt;
/**
* 随便写了点方便后面重构和客户端通讯
*/
public interface NetWorkCodecs {
NetWorkCodec<Boolean> BOOLEAN = new NetWorkCodec<>() {
@Override
public Boolean decode(ByteBuf in) {
return in.readBoolean();
}
@Override
public void encode(ByteBuf out, Boolean value) {
out.writeBoolean(value);
}
};
NetWorkCodec<Byte> BYTE = new NetWorkCodec<>() {
@Override
public Byte decode(ByteBuf in) {
return in.readByte();
}
@Override
public void encode(ByteBuf out, Byte value) {
out.writeByte(value);
}
};
NetWorkCodec<Float> ROTATION_BYTE = BYTE.map(MCUtils::unpackDegrees, MCUtils::packDegrees);
NetWorkCodec<Short> SHORT = new NetWorkCodec<>() {
@Override
public Short decode(ByteBuf in) {
return in.readShort();
}
@Override
public void encode(ByteBuf out, Short value) {
out.writeShort(value);
}
};
NetWorkCodec<Integer> UNSIGNED_SHORT = new NetWorkCodec<>() {
@Override
public Integer decode(ByteBuf in) {
return in.readUnsignedShort();
}
@Override
public void encode(ByteBuf out, Integer value) {
out.writeShort(value);
}
};
NetWorkCodec<Integer> INTEGER = new NetWorkCodec<>() {
@Override
public Integer decode(ByteBuf in) {
return in.readInt();
}
@Override
public void encode(ByteBuf out, Integer value) {
out.writeInt(value);
}
};
NetWorkCodec<Integer> VAR_INTEGER = new NetWorkCodec<>() {
@Override
public Integer decode(ByteBuf in) {
int result = 0;
int bytesRead = 0;
byte currentByte;
do {
currentByte = in.readByte();
result |= (currentByte & 127) << bytesRead++ * 7;
if (bytesRead > 5) {
throw new RuntimeException("VarInt too big");
}
} while ((currentByte & 128) == 128);
return result;
}
@Override
public void encode(ByteBuf out, Integer value) {
while ((value & -128) != 0) {
out.writeByte(value & 127 | 128);
value >>>= 7;
}
out.writeByte(value);
}
};
NetWorkCodec<OptionalInt> OPTIONAL_VAR_INTEGER = VAR_INTEGER.map(
integer -> integer == 0 ? OptionalInt.empty() : OptionalInt.of(integer - 1),
optionalInt -> optionalInt.isPresent() ? optionalInt.getAsInt() + 1 : 0
);
NetWorkCodec<Long> LONG = new NetWorkCodec<>() {
@Override
public Long decode(ByteBuf in) {
return in.readLong();
}
@Override
public void encode(ByteBuf out, Long value) {
out.writeLong(value);
}
};
NetWorkCodec<Long> VAR_LONG = new NetWorkCodec<>() {
@Override
public Long decode(ByteBuf in) {
long result = 0L;
int bytesRead = 0;
byte currentByte;
do {
currentByte = in.readByte();
result |= (long)(currentByte & 127) << bytesRead++ * 7;
if (bytesRead > 10) {
throw new RuntimeException("VarLong too big");
}
} while ((currentByte & 128) == 128);
return result;
}
@Override
public void encode(ByteBuf out, Long value) {
while ((value & -128L) != 0L) {
out.writeByte((int)(value & 127L) | 128);
value >>>= 7;
}
out.writeByte(value.intValue());
}
};
NetWorkCodec<Float> FLOAT = new NetWorkCodec<>() {
@Override
public Float decode(ByteBuf in) {
return in.readFloat();
}
@Override
public void encode(ByteBuf out, Float value) {
out.writeFloat(value);
}
};
NetWorkCodec<Double> DOUBLE = new NetWorkCodec<>() {
@Override
public Double decode(ByteBuf in) {
return in.readDouble();
}
@Override
public void encode(ByteBuf out, Double value) {
out.writeDouble(value);
}
};
NetWorkCodec<byte[]> BYTE_ARRAY = new NetWorkCodec<>() {
@Override
public byte[] decode(ByteBuf in) {
int maxSize = in.readableBytes();
int size = VAR_INTEGER.decode(in);
if (size > maxSize) {
throw new DecoderException("ByteArray with size " + size + " is bigger than allowed " + maxSize);
} else {
byte[] bytes = new byte[size];
in.readBytes(bytes);
return bytes;
}
}
@Override
public void encode(ByteBuf out, byte[] value) {
VAR_INTEGER.encode(out, value.length);
out.writeBytes(value);
}
};
NetWorkCodec<long[]> LONG_ARRAY = new NetWorkCodec<>() {
@Override
public long[] decode(ByteBuf in) {
int arrayLength = VAR_INTEGER.decode(in);
int maxPossibleElements = in.readableBytes() / 8;
if (arrayLength > maxPossibleElements) {
throw new DecoderException("LongArray with size " + arrayLength + " is bigger than allowed " + maxPossibleElements);
} else {
long[] longArray = new long[arrayLength];
for (int i = 0; i < longArray.length; i++) {
longArray[i] = in.readLong();
}
return longArray;
}
}
@Override
public void encode(ByteBuf out, long[] value) {
VAR_INTEGER.encode(out, value.length);
for (long element : value) {
out.writeLong(element);
}
}
};
NetWorkCodec<String> STRING_UTF8 = new NetWorkCodec<>() {
private static final int MAX_STRING_LENGTH = 32767;
@Override
public String decode(ByteBuf in) {
int maxEncodedBytes = ByteBufUtil.utf8MaxBytes(MAX_STRING_LENGTH);
int encodedLength = VAR_INTEGER.decode(in);
if (encodedLength > maxEncodedBytes) {
throw new DecoderException("The received encoded string buffer length is longer than maximum allowed (" + encodedLength + " > " + maxEncodedBytes + ")");
} else if (encodedLength < 0) {
throw new DecoderException("The received encoded string buffer length is less than zero! Weird string!");
} else {
int availableBytes = in.readableBytes();
if (encodedLength > availableBytes) {
throw new DecoderException("Not enough bytes in buffer, expected " + encodedLength + ", but got " + availableBytes);
} else {
String decodedString = in.toString(in.readerIndex(), encodedLength, StandardCharsets.UTF_8);
in.readerIndex(in.readerIndex() + encodedLength);
if (decodedString.length() > MAX_STRING_LENGTH) {
throw new DecoderException("The received string length is longer than maximum allowed (" + decodedString.length() + " > " + MAX_STRING_LENGTH + ")");
} else {
return decodedString;
}
}
}
}
@Override
public void encode(ByteBuf out, String value) {
if (value.length() > MAX_STRING_LENGTH) {
throw new EncoderException("String too big (was " + value.length() + " characters, max " + MAX_STRING_LENGTH + ")");
} else {
int maxPossibleBytes = ByteBufUtil.utf8MaxBytes(value);
ByteBuf tempBuffer = out.alloc().buffer(maxPossibleBytes);
try {
int actualEncodedBytes = ByteBufUtil.writeUtf8(tempBuffer, value);
int maxAllowedBytes = ByteBufUtil.utf8MaxBytes(MAX_STRING_LENGTH);
if (actualEncodedBytes > maxAllowedBytes) {
throw new EncoderException("String too big (was " + actualEncodedBytes + " bytes encoded, max " + maxAllowedBytes + ")");
}
VAR_INTEGER.encode(out, actualEncodedBytes);
out.writeBytes(tempBuffer);
} finally {
tempBuffer.release();
}
}
}
};
NetWorkCodec<Tag> TAG = new NetWorkCodec<>() {
@Override
public Tag decode(ByteBuf in) {
int initialIndex = in.readerIndex();
byte marker = in.readByte();
if (marker == 0) {
return null;
} else {
in.readerIndex(initialIndex);
try {
return NBT.readUnnamedTag(new ByteBufInputStream(in), false);
} catch (IOException e) {
throw new EncoderException("Failed to read NBT compound: " + e.getMessage(), e);
}
}
}
@Override
public void encode(ByteBuf out, Tag value) {
if (value == null) {
out.writeByte(0);
} else {
try {
NBT.writeUnnamedTag(value, new ByteBufOutputStream(out), false);
} catch (IOException e) {
throw new EncoderException("Failed to write NBT compound: " + e.getMessage(), e);
}
}
}
};
NetWorkCodec<CompoundTag> COMPOUND_TAG = TAG.map(tag -> {
if (tag instanceof CompoundTag compoundTag) {
return compoundTag;
} else {
throw new DecoderException("Not a compound tag: " + tag);
}
}, tag -> tag);
NetWorkCodec<Optional<CompoundTag>> OPTIONAL_COMPOUND_TAG = new NetWorkCodec<>() {
@Override
public Optional<CompoundTag> decode(ByteBuf in) {
int initialIndex = in.readerIndex();
byte marker = in.readByte();
if (marker == 0) {
return Optional.empty();
} else {
in.readerIndex(initialIndex);
try {
if (NBT.readUnnamedTag(new ByteBufInputStream(in), false) instanceof CompoundTag compoundTag) {
return Optional.of(compoundTag);
}
} catch (IOException e) {
throw new EncoderException("Failed to read NBT compound: " + e.getMessage(), e);
}
}
return Optional.empty();
}
@Override
public void encode(ByteBuf out, Optional<CompoundTag> value) {
CompoundTag compound = value.orElse(null);
if (compound == null) {
out.writeByte(0);
} else {
try {
NBT.writeUnnamedTag(compound, new ByteBufOutputStream(out), false);
} catch (IOException e) {
throw new EncoderException("Failed to write NBT compound: " + e.getMessage(), e);
}
}
}
};
NetWorkCodec<Vector3f> VECTOR3F = new NetWorkCodec<>() {
@Override
public Vector3f decode(ByteBuf in) {
return new Vector3f(in.readFloat(), in.readFloat(), in.readFloat());
}
@Override
public void encode(ByteBuf out, Vector3f value) {
out.writeFloat(value.x());
out.writeFloat(value.y());
out.writeFloat(value.z());
}
};
NetWorkCodec<Quaternionf> QUATERNIONF = new NetWorkCodec<>() {
@Override
public Quaternionf decode(ByteBuf in) {
return new Quaternionf(in.readFloat(), in.readFloat(), in.readFloat(), in.readFloat());
}
@Override
public void encode(ByteBuf out, Quaternionf value) {
out.writeFloat(value.x());
out.writeFloat(value.y());
out.writeFloat(value.z());
out.writeFloat(value.w());
}
};
NetWorkCodec<Integer> CONTAINER_ID = VAR_INTEGER;
NetWorkCodec<Integer> RGB_COLOR = new NetWorkCodec<>() {
@Override
public Integer decode(ByteBuf in) {
return 255 << 24 | in.readByte() & 0xFF << 16 | in.readByte() & 0xFF << 8 | in.readByte() & 0xFF;
}
@Override
public void encode(ByteBuf out, Integer value) {
out.writeByte(value >> 16 & 0xFF);
out.writeByte(value >> 8 & 0xFF);
out.writeByte(value & 0xFF);
}
};
}

View File

@@ -57,7 +57,7 @@ public class NetWorkDataTypes<T> {
return id2NetWorkDataTypes.get(id);
}
@SuppressWarnings("unchecked")
@SuppressWarnings({"unchecked", "unused"})
public <R> NetWorkDataTypes<R> as(Class<R> clazz) {
return (NetWorkDataTypes<R>) this;
}

View File

@@ -0,0 +1,7 @@
package net.momirealms.craftengine.bukkit.plugin.network.payload;
import io.netty.buffer.ByteBuf;
public interface NetWorkDecoder<T> {
T decode(ByteBuf in);
}

View File

@@ -0,0 +1,7 @@
package net.momirealms.craftengine.bukkit.plugin.network.payload;
import io.netty.buffer.ByteBuf;
public interface NetWorkEncoder<T> {
void encode(ByteBuf out, T value);
}

View File

@@ -13,7 +13,9 @@ public class EntityDataUtils {
private static final int RIGHT_ALIGNMENT = 0x10; // 16
public static final int BLOCK_STATE_DATA_ID = VersionHelper.isOrAbove1_20_2() ? 23 : 22;
public static final int TEXT_DATA_ID = VersionHelper.isOrAbove1_20_2() ? 23 : 22;
public static final int DISPLAYED_ITEM_DATA_ID = VersionHelper.isOrAbove1_20_2() ? 23 : 22;
public static final int CUSTOM_NAME_DATA_ID = 2;
public static final int ITEM_DATA_ID = 8;
public static byte encodeTextDisplayMask(boolean hasShadow, boolean isSeeThrough, boolean useDefaultBackground, int alignment) {
int bitMask = 0;

View File

@@ -41,7 +41,7 @@ import java.util.function.Consumer;
import static java.util.Objects.requireNonNull;
@SuppressWarnings("unused")
@SuppressWarnings({"unused", "unchecked"})
public class Reflections {
public static void init() {
@@ -3846,6 +3846,17 @@ public class Reflections {
public static final Object instance$EntityType$OAK_BOAT;
public static final Object instance$EntityType$TRIDENT;
public static final Object instance$EntityType$SNOWBALL;
public static final Object instance$EntityType$FIREBALL;
public static final Object instance$EntityType$EYE_OF_ENDER;
public static final Object instance$EntityType$FIREWORK_ROCKET;
public static final Object instance$EntityType$ITEM;
public static final Object instance$EntityType$ITEM_FRAME;
public static final Object instance$EntityType$OMINOUS_ITEM_SPAWNER;
public static final Object instance$EntityType$SMALL_FIREBALL;
public static final Object instance$EntityType$EGG;
public static final Object instance$EntityType$ENDER_PEARL;
public static final Object instance$EntityType$EXPERIENCE_BOTTLE;
public static final Object instance$EntityType$POTION;
static {
try {
@@ -3869,6 +3880,32 @@ public class Reflections {
instance$EntityType$TRIDENT = Reflections.method$Registry$get.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, trident);
Object snowball = FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", "snowball");
instance$EntityType$SNOWBALL = Reflections.method$Registry$get.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, snowball);
Object fireball = FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", "fireball");
instance$EntityType$FIREBALL = Reflections.method$Registry$get.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, fireball);
Object eyeOfEnder = FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", "eye_of_ender");
instance$EntityType$EYE_OF_ENDER = Reflections.method$Registry$get.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, eyeOfEnder);
Object fireworkRocket = FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", "firework_rocket");
instance$EntityType$FIREWORK_ROCKET = Reflections.method$Registry$get.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, fireworkRocket);
Object item = FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", "item");
instance$EntityType$ITEM = Reflections.method$Registry$get.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, item);
Object itemFrame = FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", "item_frame");
instance$EntityType$ITEM_FRAME = Reflections.method$Registry$get.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, itemFrame);
Object smallFireball = FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", "small_fireball");
instance$EntityType$SMALL_FIREBALL = Reflections.method$Registry$get.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, smallFireball);
Object egg = FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", "egg");
instance$EntityType$EGG = Reflections.method$Registry$get.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, egg);
Object enderPearl = FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", "ender_pearl");
instance$EntityType$ENDER_PEARL = Reflections.method$Registry$get.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, enderPearl);
Object experienceBottle = FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", "experience_bottle");
instance$EntityType$EXPERIENCE_BOTTLE = Reflections.method$Registry$get.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, experienceBottle);
Object potion = FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", "potion");
instance$EntityType$POTION = Reflections.method$Registry$get.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, potion);
if (VersionHelper.isOrAbove1_20_5()) {
Object ominousItemSpawner = FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", "ominous_item_spawner");
instance$EntityType$OMINOUS_ITEM_SPAWNER = Reflections.method$Registry$get.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, ominousItemSpawner);
} else {
instance$EntityType$OMINOUS_ITEM_SPAWNER = null;
}
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
@@ -6451,6 +6488,18 @@ public class Reflections {
public static final int instance$EntityType$FALLING_BLOCK$registryId;
public static final int instance$EntityType$TRIDENT$registryId;
public static final int instance$EntityType$ARMOR_STAND$registryId;
public static final int instance$EntityType$FIREBALL$registryId;
public static final int instance$EntityType$EYE_OF_ENDER$registryId;
public static final int instance$EntityType$FIREWORK_ROCKET$registryId;
public static final int instance$EntityType$ITEM$registryId;
public static final int instance$EntityType$ITEM_FRAME$registryId;
public static final int instance$EntityType$OMINOUS_ITEM_SPAWNER$registryId;
public static final int instance$EntityType$SMALL_FIREBALL$registryId;
public static final int instance$EntityType$EGG$registryId;
public static final int instance$EntityType$ENDER_PEARL$registryId;
public static final int instance$EntityType$EXPERIENCE_BOTTLE$registryId;
public static final int instance$EntityType$SNOWBALL$registryId;
public static final int instance$EntityType$POTION$registryId;
static {
try {
@@ -6460,6 +6509,22 @@ public class Reflections {
instance$EntityType$FALLING_BLOCK$registryId = (int) Reflections.method$Registry$getId.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, instance$EntityType$FALLING_BLOCK);
instance$EntityType$TRIDENT$registryId = (int) Reflections.method$Registry$getId.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, instance$EntityType$TRIDENT);
instance$EntityType$ARMOR_STAND$registryId = (int) Reflections.method$Registry$getId.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, instance$EntityType$ARMOR_STAND);
instance$EntityType$FIREBALL$registryId = (int) Reflections.method$Registry$getId.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, instance$EntityType$FIREBALL);
instance$EntityType$EYE_OF_ENDER$registryId = (int) Reflections.method$Registry$getId.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, instance$EntityType$EYE_OF_ENDER);
instance$EntityType$FIREWORK_ROCKET$registryId = (int) Reflections.method$Registry$getId.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, instance$EntityType$FIREWORK_ROCKET);
instance$EntityType$ITEM$registryId = (int) Reflections.method$Registry$getId.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, instance$EntityType$ITEM);
instance$EntityType$ITEM_FRAME$registryId = (int) Reflections.method$Registry$getId.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, instance$EntityType$ITEM_FRAME);
instance$EntityType$SMALL_FIREBALL$registryId = (int) Reflections.method$Registry$getId.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, instance$EntityType$SMALL_FIREBALL);
instance$EntityType$EGG$registryId = (int) Reflections.method$Registry$getId.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, instance$EntityType$EGG);
instance$EntityType$ENDER_PEARL$registryId = (int) Reflections.method$Registry$getId.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, instance$EntityType$ENDER_PEARL);
instance$EntityType$EXPERIENCE_BOTTLE$registryId = (int) Reflections.method$Registry$getId.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, instance$EntityType$EXPERIENCE_BOTTLE);
instance$EntityType$SNOWBALL$registryId = (int) Reflections.method$Registry$getId.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, instance$EntityType$SNOWBALL);
instance$EntityType$POTION$registryId = (int) Reflections.method$Registry$getId.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, instance$EntityType$POTION);
if (VersionHelper.isOrAbove1_20_5()) {
instance$EntityType$OMINOUS_ITEM_SPAWNER$registryId = (int) Reflections.method$Registry$getId.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, instance$EntityType$OMINOUS_ITEM_SPAWNER);
} else {
instance$EntityType$OMINOUS_ITEM_SPAWNER$registryId = -1;
}
} catch (Exception e) {
throw new RuntimeException(e);
}

View File

@@ -56,7 +56,7 @@ public class NetWorkDataTypes<T> {
return id2NetWorkDataTypes.get(id);
}
@SuppressWarnings("unchecked")
@SuppressWarnings({"unchecked", "unused"})
public <R> NetWorkDataTypes<R> as(Class<R> clazz) {
return (NetWorkDataTypes<R>) this;
}

View File

@@ -197,7 +197,7 @@ public abstract class AbstractPackManager implements PackManager {
Object magicObject = magicConstructor.newInstance(p1, p2);
magicMethod.invoke(magicObject);
} catch (Exception e) {
this.plugin.logger().warn("Failed to generate zip files", e);
this.plugin.logger().warn("Failed to generate zip files\n" + new StringWriter(){{e.printStackTrace(new PrintWriter(this));}}.toString().replaceAll("\\.[Il]{2,}", ""));
}
};
} else {

View File

@@ -9,6 +9,9 @@
<a href="https://momi.gtemc.cn/craftengine" alt="GitBook">
<img src="https://img.shields.io/badge/%E6%96%87%E6%A1%A3-%E7%94%A8%E6%88%B7%E6%89%8B%E5%86%8C-D2691E" alt="Gitbook"/>
</a>
<a href="https://deepwiki.com/Xiao-MoMi/craft-engine">
<img src="https://deepwiki.com/badge.svg" alt="询问DeepWiki">
</a>
<a href="https://github.com/Xiao-MoMi/craft-engine/">
<img src="https://sloc.xyz/github/Xiao-MoMi/craft-engine/?category=codes" alt="SCC数量标识"/>
</a>
@@ -25,37 +28,39 @@
CraftEngine 重新定义了 Minecraft 插件架构,作为下一代自定义内容实现的解决方案。通过 JVM 级别的注入,它提供了前所未有的性能、稳定性和可扩展性。该框架提供了一个代码优先的 API用于注册原生集成的方块行为和物品交互逻辑。
## 构建
只要您安装了 JDK21即可免费获取完整版 JAR。请按照以下指南进行构建。
### 🐚 命令行
1. 安装 JDK 21
2. 打开终端并切换到项目文件夹。
3. 执行 `./gradlew build`,构建产物将生成在 `/target` 文件夹中。
+ 打开终端并切换到项目文件夹
+ 执行 `./gradlew build`,构建产物将生成在 `/target` 文件夹
### 💻 IDE
1. 导入项目并执行 Gradle 构建操作。
2. 构建产物将生成在 `/target` 文件夹中。
+ 导入项目并执行 Gradle 构建操作。
+ 构建产物将生成在 `/target` 文件夹中。
## 安装
### 💻 环境要求
1. 确保您正在运行 [Paper](https://papermc.io/)或其分支1.20.1+ 服务器。CraftEngine 不支持 Spigot且未来也不太可能支持。
2. 使用 JDK 21 来运行服务器。
+ 确保您正在运行 [Paper](https://papermc.io/)或其分支1.20.1+ 服务器。CraftEngine 不支持 Spigot且未来也不太可能支持。
+ 使用 JDK 21 来运行服务器。我相信这对你来说很简单。
### 🔍 安装方式
CraftEngine 提供了两种安装模式:标准安装和 Mod 模式。标准安装与传统插件安装方式相同,即将插件放入插件文件夹中。下面我们将详细介绍 Mod 模式的安装步骤。
### 🔧 安装服务器 Mod
1. 下载最新的 [ignite.jar](https://github.com/vectrix-space/ignite/releases) 到您的服务器根目录
2. 选择以下任一操作:
-您的服务器 JAR 文件重命名为 `paper.jar`
- 添加启动参数:`-Dignite.locator=paper -Dignite.paper.jar=./paper-xxx.jar`
- 示例:`java -Dignite.locator=paper -Dignite.paper.jar=./paper-1.21.4-164.jar -jar ignite.jar`
3. 启动服务器以生成 `/mods` 目录。
4. 将最新的 [mod.jar](https://github.com/Xiao-MoMi/craft-engine/releases) 放入 `/mods` 文件夹。
5. 将插件的 JAR 文件放入 `/plugins` 文件夹进行安装。
6. 执行两次重启:
1. 第一次重启用于文件初始化。
2. 第二次重启以激活所有组件。
### 🔧 安装服务端模组
- 下载最新的 [ignite.jar](https://github.com/vectrix-space/ignite/releases) 到服务器根目录
- 可以:
- 将服务器 JAR 重命名为 `paper.jar` 并修改启动命令为: `-jar ignite.jar`
- 或者:
- 使用高级启动参数
- 对于 paper 或 folia: `-Dignite.locator=paper -Dignite.paper.jar=./server-xxx.jar -jar ignite.jar`
- 对于特殊 Paper 分支 `-Dignite.locator=paper -Dignite.paper.target=cn.dreeam.leaper.QuantumLeaper -Dignite.paper.jar=./leaf-xxx.jar -jar ignite.jar`
- 启动服务器生成 `/mods` 目录
- 将最新的 [mod.jar](https://github.com/Xiao-MoMi/craft-engine/releases) 放入 `/mods` 目录
- 将插件 JAR 放入 `/plugins` 目录
- 最后执行两次重启:
1. 首次重启以进行文件初始化
2. 最后重启以激活所有组件
## 技术概述
@@ -63,19 +68,19 @@ CraftEngine 提供了两种安装模式:标准安装和 Mod 模式。标准安
CraftEngine 使用运行时字节码生成技术,在服务器原生级别注册自定义方块,并结合客户端数据包修改以实现视觉同步。此架构提供了以下功能:
🧱 自定义原生方块
- 动态注册方块,完全可控。
- 物理属性:硬度、引燃几率、亮度等所有标准属性。
- 自定义行为:通过 API 实现树苗、作物、下落的方块等。
- 原版兼容性:完全保留原版方块机制(例如音符盒、绊线)。
+ 动态注册方块,完全可控。
+ 物理属性:硬度、引燃几率、亮度等所有标准属性。
+ 自定义行为:通过 API 实现树苗、作物、下落的方块等。
+ 原版兼容性:完全保留原版方块机制(例如音符盒、绊线)。
📦 数据包集成
- 定义自定义矿脉。
- 生成自定义树木。
- 配置自定义地形生成。
+ 定义自定义矿脉。
+ 生成自定义树木。
+ 配置自定义地形生成。
⚡ 性能优势
- 比传统的 Bukkit 事件监听器更快、更稳定。
- 策略性代码注入以最小化开销。
+ 比传统的 Bukkit 事件监听器更快、更稳定。
+ 策略性代码注入以最小化开销。
### 🥘 配方
CraftEngine 通过底层注入实现完全可定制的合成系统。与传统插件不同,它在处理 NBT 修改时不会失效,确保配方结果仅与唯一的物品标识符绑定。
@@ -89,34 +94,47 @@ CraftEngine 通过底层注入实现完全可定制的合成系统。与传统
### 🛠️ 模型
该插件通过配置实现模型继承和纹理覆盖,同时支持从 1.21.4 版本开始的[所有物品模型](https://misode.github.io/assets/item/)。它包含一个版本迁移系统,可以自动将 1.21.4+ 的物品模型降级为旧格式,以实现最大向后兼容性。
### 您必须了解的破坏性变更及可能与其他插件的不兼容性
- CraftEngine 注入 PalettedContainer 以确保插件方块数据的高效存储和同步。这可能会导致与一些直接修改调色盘的插件冲突。当使用 Spark 分析服务器性能时,调色盘操作开销将在分析结果中划归给 CraftEngine 插件。
- CraftEngine 注入 FurnaceBlockEntity 以修改其配方获取逻辑。
- CraftEngine 使用真服务端侧方块,任何依赖 Bukkit 的 Material 类的插件都将无法正确识别自定义方块类型。正确的方法是使用替代方案,如 BlockState#getBlock (mojmap) 而不是 Material 类。**(译者注: 对于不想直接使用nms的项目可以使用org.bukkit.block.Block#getBlockData来正确获取方块)**
- CraftEngine 通过继承某些 Minecraft 实体实现 0-tick 碰撞实体,确保硬碰撞在服务端侧正常工作(例如,让猪站在椅子上)。然而,一些反作弊插件在检测玩家移动时没有正确检查实体的 AABB轴对齐包围盒这可能导致误报。**(译者注: 还有可能是因为没有正确检查玩家接触的实体是否有硬碰撞箱导致的误报)**
- CraftEngine 的自定义配方处理可能与其他配方管理插件不完全兼容。
## 灵感来源
CraftEngine 从以下开源项目中汲取了灵感:
- [Paper](https://github.com/PaperMC/Paper)
- [LuckPerms](https://github.com/LuckPerms/LuckPerms)
- [Fabric](https://github.com/FabricMC/fabric)
- [packetevents](https://github.com/retrooper/packetevents)
- [NBT](https://github.com/Querz/NBT)
- [DataFixerUpper](https://github.com/Mojang/DataFixerUpper)
- [ViaVersion](https://github.com/ViaVersion/ViaVersion)
+ [Paper](https://github.com/PaperMC/Paper)
+ [LuckPerms](https://github.com/LuckPerms/LuckPerms)
+ [Fabric](https://github.com/FabricMC/fabric)
+ [packetevents](https://github.com/retrooper/packetevents)
+ [DataFixerUpper](https://github.com/Mojang/DataFixerUpper)
+ [ViaVersion](https://github.com/ViaVersion/ViaVersion)
### 核心依赖
CraftEngine 的实现依赖于以下基础库:
- [ignite](https://github.com/vectrix-space/ignite)
- [cloud-minecraft](https://github.com/Incendo/cloud-minecraft)
- [rtag](https://github.com/saicone/rtag)
- [adventure](https://github.com/KyoriPowered/adventure)
- [byte-buddy](https://github.com/raphw/byte-buddy)
+ [ignite](https://github.com/vectrix-space/ignite)
+ [cloud-minecraft](https://github.com/Incendo/cloud-minecraft)
+ [rtag](https://github.com/saicone/rtag)
+ [adventure](https://github.com/KyoriPowered/adventure)
+ [byte-buddy](https://github.com/raphw/byte-buddy)
## 如何贡献
### 🔌 新功能与 Bug 修复
如果您提交的 PR 是关于 Bug 修复的,它很可能会被合并。如果您想提交新功能,请提前在 [Discord](https://discord.com/invite/WVKdaUPR3S) 上联系我。
您贡献的代码将遵循 GPLv3 许可证开源。如果您希望使用更宽松的许可证(例如 MIT可以在文件顶部明确注明。
### 🌍 翻译
1. 克隆此仓库。
2.`/bukkit/loader/src/main/resources/translations` 中创建一个新的语言文件。
3. 完成后,提交 **pull request** 以供审核。我们感谢您的贡献!
## Differences Between Versions
| 版本 | 官方支持 | 最大玩家数 | 开发版本 |
|-----|------|-------|------|
| 社区版 | ❌ 无 | 20 | ❌ 无 |
| 付费版 | ✔️ 有 | 无限制 | ✔️ 有 |
### 💖 支持开发者
如果您喜欢使用 CraftEngine请考虑支持开发者

View File

@@ -129,8 +129,6 @@ CraftEngine 的實現依賴於以下基礎庫:
```kotlin
repositories {
maven("https://repo.momirealms.net/releases/")
// 如果你的網路環境受限可以嘗試下面的存儲庫位址
// maven("https://repo-momi.gtemc.cn/releases/")
}
```
```kotlin