9
0
mirror of https://github.com/Xiao-MoMi/craft-engine.git synced 2025-12-25 01:49:30 +00:00

Merge branch 'Xiao-MoMi:dev' into dev

This commit is contained in:
Halogly
2025-07-21 11:22:37 +08:00
committed by GitHub
38 changed files with 609 additions and 313 deletions

View File

@@ -10,7 +10,8 @@ import net.momirealms.craftengine.bukkit.compatibility.leveler.*;
import net.momirealms.craftengine.bukkit.compatibility.model.bettermodel.BetterModelModel;
import net.momirealms.craftengine.bukkit.compatibility.model.modelengine.ModelEngineModel;
import net.momirealms.craftengine.bukkit.compatibility.model.modelengine.ModelEngineUtils;
import net.momirealms.craftengine.bukkit.compatibility.mythicmobs.MythicMobsListener;
import net.momirealms.craftengine.bukkit.compatibility.mythicmobs.MythicItemDropListener;
import net.momirealms.craftengine.bukkit.compatibility.mythicmobs.MythicSkillHelper;
import net.momirealms.craftengine.bukkit.compatibility.papi.PlaceholderAPIUtils;
import net.momirealms.craftengine.bukkit.compatibility.permission.LuckPermsEventListeners;
import net.momirealms.craftengine.bukkit.compatibility.skript.SkriptHook;
@@ -39,6 +40,7 @@ public class BukkitCompatibilityManager implements CompatibilityManager {
private final Map<String, LevelerProvider> levelerProviders;
private boolean hasPlaceholderAPI;
private boolean hasViaVersion;
private MythicSkillHelper skillExecute;
public BukkitCompatibilityManager(BukkitCraftEngine plugin) {
this.plugin = plugin;
@@ -131,11 +133,16 @@ public class BukkitCompatibilityManager implements CompatibilityManager {
}
if (this.isPluginEnabled("MythicMobs")) {
BukkitItemManager.instance().registerExternalItemSource(new MythicMobsSource());
new MythicMobsListener(this.plugin);
new MythicItemDropListener(this.plugin);
logHook("MythicMobs");
}
}
@Override
public void executeMMSkill(String skill, float power, Player player) {
MythicSkillHelper.execute(skill, power, player);
}
@Override
public void registerLevelerProvider(String plugin, LevelerProvider provider) {
this.levelerProviders.put(plugin, provider);

View File

@@ -21,12 +21,12 @@ import org.bukkit.inventory.ItemStack;
import java.lang.reflect.Constructor;
public class CraftEngineItemDrop extends ItemDrop implements IItemDrop {
public class MythicItemDrop extends ItemDrop implements IItemDrop {
private final CustomItem<ItemStack> customItem;
private static final Constructor<?> constructor$BukkitItemStack = ReflectionUtils.getConstructor(BukkitItemStack.class, ItemStack.class);
private static final boolean useReflection = constructor$BukkitItemStack != null;
public CraftEngineItemDrop(String line, MythicLineConfig config, CustomItem<ItemStack> customItem) {
public MythicItemDrop(String line, MythicLineConfig config, CustomItem<ItemStack> customItem) {
super(line, config);
this.customItem = customItem;
}

View File

@@ -8,10 +8,10 @@ import org.bukkit.Bukkit;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
public class MythicMobsListener implements Listener {
public class MythicItemDropListener implements Listener {
private final BukkitCraftEngine plugin;
public MythicMobsListener(BukkitCraftEngine plugin) {
public MythicItemDropListener(BukkitCraftEngine plugin) {
this.plugin = plugin;
Bukkit.getPluginManager().registerEvents(this, plugin.javaPlugin());
}
@@ -24,7 +24,7 @@ public class MythicMobsListener implements Listener {
this.plugin.itemManager().getCustomItem(itemId).ifPresent(customItem -> {
String line = event.getContainer().getConfigLine();
MythicLineConfig config = event.getConfig();
event.register(new CraftEngineItemDrop(line, config, customItem));
event.register(new MythicItemDrop(line, config, customItem));
});
}
}

View File

@@ -0,0 +1,27 @@
package net.momirealms.craftengine.bukkit.compatibility.mythicmobs;
import io.lumine.mythic.bukkit.MythicBukkit;
import io.lumine.mythic.core.utils.MythicUtil;
import net.momirealms.craftengine.core.entity.player.Player;
import org.bukkit.Location;
import org.bukkit.entity.Entity;
import org.bukkit.entity.LivingEntity;
import java.util.ArrayList;
import java.util.List;
public final class MythicSkillHelper {
public static void execute(String skill, float power, Player player) {
org.bukkit.entity.Player casterPlayer = (org.bukkit.entity.Player) player.platformPlayer();
Location location = casterPlayer.getLocation();
LivingEntity target = MythicUtil.getTargetedEntity(casterPlayer);
List<Entity> targets = new ArrayList<>();
List<Location> locations = null;
if (target != null) {
targets.add(target);
locations = List.of(target.getLocation());
}
MythicBukkit.inst().getAPIHelper().castSkill(casterPlayer, skill, casterPlayer, location, targets, locations, power);
}
}

View File

@@ -1,9 +1,12 @@
package net.momirealms.craftengine.bukkit.compatibility.worldedit;
import com.fastasyncworldedit.bukkit.FaweBukkitWorld;
import com.fastasyncworldedit.bukkit.adapter.CachedBukkitAdapter;
import com.fastasyncworldedit.bukkit.adapter.FaweAdapter;
import com.fastasyncworldedit.bukkit.adapter.NMSAdapter;
import com.fastasyncworldedit.core.configuration.Settings;
import com.fastasyncworldedit.core.extent.processor.ExtentBatchProcessorHolder;
import com.fastasyncworldedit.core.math.IntPair;
import com.fastasyncworldedit.core.util.ExtentTraverser;
import com.fastasyncworldedit.core.util.ProcessorTraverser;
import com.sk89q.worldedit.EditSession;
@@ -24,6 +27,7 @@ import com.sk89q.worldedit.world.block.BaseBlock;
import com.sk89q.worldedit.world.block.BlockStateHolder;
import net.momirealms.craftengine.bukkit.block.BukkitBlockManager;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
import net.momirealms.craftengine.bukkit.plugin.injector.WorldStorageInjector;
import net.momirealms.craftengine.bukkit.util.BlockStateUtils;
import net.momirealms.craftengine.core.block.EmptyBlock;
@@ -89,12 +93,14 @@ public class FastAsyncWorldEditDelegate extends AbstractDelegateExtent {
if (levelChunk != null) {
Object[] sections = FastNMS.INSTANCE.method$ChunkAccess$getSections(levelChunk);
CESection[] ceSections = ceChunk.sections();
for (int i = 0; i < ceSections.length; i++) {
CESection ceSection = ceSections[i];
Object section = sections[i];
int finalI = i;
WorldStorageInjector.injectLevelChunkSection(section, ceSection, ceChunk, new SectionPos(pos.x, ceChunk.sectionY(i), pos.z),
(injected) -> sections[finalI] = injected);
synchronized (sections) {
for (int i = 0; i < ceSections.length; i++) {
CESection ceSection = ceSections[i];
Object section = sections[i];
int finalI = i;
WorldStorageInjector.injectLevelChunkSection(section, ceSection, ceChunk, new SectionPos(pos.x, ceChunk.sectionY(i), pos.z),
(injected) -> sections[finalI] = injected);
}
}
}
}
@@ -174,18 +180,18 @@ public class FastAsyncWorldEditDelegate extends AbstractDelegateExtent {
@Override
public @Nullable Operation commit() {
Operation operation = super.commit();
saveAllChunks();
Operation operation = super.commit();
List<ChunkPos> chunks = new ArrayList<>(this.brokenChunks);
this.brokenChunks.clear();
Object worldServer = this.ceWorld.world().serverWorld();
Object chunkSource = FastNMS.INSTANCE.method$ServerLevel$getChunkSource(worldServer);
for (ChunkPos chunk : chunks) {
CEChunk loaded = this.ceWorld.getChunkAtIfLoaded(chunk.longKey());
// only inject loaded chunks
if (loaded == null) continue;
injectLevelChunk(chunkSource, loaded);
}
Object worldServer = this.ceWorld.world().serverWorld();
Object chunkSource = FastNMS.INSTANCE.method$ServerLevel$getChunkSource(worldServer);
for (ChunkPos chunk : chunks) {
CEChunk loaded = this.ceWorld.getChunkAtIfLoaded(chunk.longKey());
// only inject loaded chunks
if (loaded == null) continue;
injectLevelChunk(chunkSource, loaded);
}
return operation;
}
@@ -214,11 +220,12 @@ public class FastAsyncWorldEditDelegate extends AbstractDelegateExtent {
try {
CEChunk ceChunk = Optional.ofNullable(this.ceWorld.getChunkAtIfLoaded(chunkX, chunkZ))
.orElse(this.ceWorld.worldDataStorage().readChunkAt(this.ceWorld, new ChunkPos(chunkX, chunkZ)));
CESection ceSection = ceChunk.sectionById(SectionPos.blockToSectionCoord(blockY));
ImmutableBlockState immutableBlockState = BukkitBlockManager.instance().getImmutableBlockState(newStateId);
if (immutableBlockState == null) {
ceChunk.setBlockState(blockX, blockY, blockZ, EmptyBlock.STATE);
ceSection.setBlockState(blockX & 15, blockY & 15, blockZ & 15, EmptyBlock.STATE);
} else {
ceChunk.setBlockState(blockX, blockY, blockZ, immutableBlockState);
ceSection.setBlockState(blockX & 15, blockY & 15, blockZ & 15, immutableBlockState);
}
this.chunksToSave.add(ceChunk);
} catch (IOException e) {

View File

@@ -1,23 +1,12 @@
package net.momirealms.craftengine.bukkit.plugin.command.feature;
import net.momirealms.craftengine.bukkit.entity.data.HappyGhastData;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.command.BukkitCommandFeature;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MEntityTypes;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.NetworkReflections;
import net.momirealms.craftengine.bukkit.plugin.network.BukkitNetworkManager;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager;
import org.bukkit.Location;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.incendo.cloud.Command;
import org.incendo.cloud.bukkit.parser.location.LocationParser;
import org.incendo.cloud.parser.standard.IntegerParser;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
public class TestCommand extends BukkitCommandFeature<CommandSender> {
@@ -29,35 +18,9 @@ public class TestCommand extends BukkitCommandFeature<CommandSender> {
public Command.Builder<? extends CommandSender> assembleCommand(org.incendo.cloud.CommandManager<CommandSender> manager, Command.Builder<CommandSender> builder) {
return builder
.senderType(Player.class)
.required("location", LocationParser.locationParser())
.required("remove", IntegerParser.integerParser())
.handler(context -> {
Player player = context.sender();
int removeEntityId = context.get("remove");
if (removeEntityId >= 0) {
try {
Object packet = NetworkReflections.constructor$ClientboundRemoveEntitiesPacket.newInstance((Object) new int[]{removeEntityId});
plugin().adapt(player).sendPacket(packet, true);
player.sendMessage("发送成功");
} catch (ReflectiveOperationException e) {
player.sendMessage("发送失败");
}
return;
}
Location location = context.get("location");
int entityId = CoreReflections.instance$Entity$ENTITY_COUNTER.incrementAndGet();
List<Object> packets = new ArrayList<>();
List<Object> cachedShulkerValues = new ArrayList<>();
HappyGhastData.MobFlags.addEntityDataIfNotDefaultValue((byte) 0x01, cachedShulkerValues); // NO AI
// HappyGhastData.SharedFlags.addEntityDataIfNotDefaultValue((byte) 0x20, cachedShulkerValues); // Invisible
HappyGhastData.StaysStill.addEntityDataIfNotDefaultValue(true, cachedShulkerValues);
packets.add(FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket(
entityId, UUID.randomUUID(), location.x(), location.y(), location.z(), 0, location.getYaw(),
MEntityTypes.HAPPY_GHAST, 0, CoreReflections.instance$Vec3$Zero, 0
));
packets.add(FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(entityId, List.copyOf(cachedShulkerValues)));
plugin().adapt(player).sendPackets(packets, true);
player.sendMessage("发送成功 id: " + entityId);
player.sendMessage("客户端模组状态: " + BukkitNetworkManager.instance().getUser(player).clientModEnabled());
});
}

View File

@@ -29,6 +29,7 @@ import net.momirealms.craftengine.bukkit.plugin.network.handler.*;
import net.momirealms.craftengine.bukkit.plugin.network.payload.DiscardedPayload;
import net.momirealms.craftengine.bukkit.plugin.network.payload.NetWorkDataTypes;
import net.momirealms.craftengine.bukkit.plugin.network.payload.Payload;
import net.momirealms.craftengine.bukkit.plugin.network.payload.UnknownPayload;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MBuiltInRegistries;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MEntityTypes;
@@ -105,7 +106,7 @@ public class PacketConsumers {
byte yHeadRot = buf.readByte();
int data = buf.readVarInt();
// Falling blocks
int remapped = remap(data);
int remapped = user.clientModEnabled() ? remapMOD(data) : remap(data);
if (remapped != data) {
int xa = buf.readShort();
int ya = buf.readShort();
@@ -428,7 +429,7 @@ public class PacketConsumers {
if (user.clientModEnabled() && !BlockStateUtils.isVanillaBlock(before)) {
return;
}
int state = remap(before);
int state = user.clientModEnabled() ? remapMOD(before) : remap(before);
if (state == before) {
return;
}
@@ -450,7 +451,7 @@ public class PacketConsumers {
BlockPos blockPos = buf.readBlockPos();
int state = buf.readInt();
boolean global = buf.readBoolean();
int newState = remap(state);
int newState = user.clientModEnabled() ? remapMOD(state) : remap(state);
if (newState == state) {
return;
}
@@ -1006,7 +1007,7 @@ public class PacketConsumers {
if (!CoreReflections.clazz$BlockParticleOption.isInstance(option)) return;
Object blockState = FastNMS.INSTANCE.field$BlockParticleOption$blockState(option);
int id = BlockStateUtils.blockStateToId(blockState);
int remapped = remap(id);
int remapped = user.clientModEnabled() ? remapMOD(id) : remap(id);
if (remapped == id) return;
Object type = FastNMS.INSTANCE.method$BlockParticleOption$getType(option);
Object remappedOption = FastNMS.INSTANCE.constructor$BlockParticleOption(type, BlockStateUtils.idToBlockState(remapped));
@@ -1046,7 +1047,7 @@ public class PacketConsumers {
if (!CoreReflections.clazz$BlockParticleOption.isInstance(option)) return;
Object blockState = FastNMS.INSTANCE.field$BlockParticleOption$blockState(option);
int id = BlockStateUtils.blockStateToId(blockState);
int remapped = remap(id);
int remapped = user.clientModEnabled() ? remapMOD(id) : remap(id);
if (remapped == id) return;
Object type = FastNMS.INSTANCE.method$BlockParticleOption$getType(option);
Object remappedOption = FastNMS.INSTANCE.constructor$BlockParticleOption(type, BlockStateUtils.idToBlockState(remapped));
@@ -1086,7 +1087,7 @@ public class PacketConsumers {
if (!CoreReflections.clazz$BlockParticleOption.isInstance(option)) return;
Object blockState = FastNMS.INSTANCE.field$BlockParticleOption$blockState(option);
int id = BlockStateUtils.blockStateToId(blockState);
int remapped = remap(id);
int remapped = user.clientModEnabled() ? remapMOD(id) : remap(id);
if (remapped == id) return;
Object type = FastNMS.INSTANCE.method$BlockParticleOption$getType(option);
Object remappedOption = FastNMS.INSTANCE.constructor$BlockParticleOption(type, BlockStateUtils.idToBlockState(remapped));
@@ -1884,34 +1885,38 @@ public class PacketConsumers {
public static final TriConsumer<NetWorkUser, NMSPacketEvent, Object> CUSTOM_PAYLOAD = (user, event, packet) -> {
try {
if (!VersionHelper.isOrAbove1_20_5()) return;
if (!VersionHelper.isOrAbove1_20_2()) return;
Object payload = NetworkReflections.methodHandle$ServerboundCustomPayloadPacket$payloadGetter.invokeExact(packet);
Payload clientPayload;
if (NetworkReflections.clazz$DiscardedPayload.isInstance(payload)) {
Payload discardedPayload = DiscardedPayload.from(payload);
if (discardedPayload == null || !discardedPayload.channel().equals(NetworkManager.MOD_CHANNEL_KEY))
clientPayload = DiscardedPayload.from(payload);
} else if (!VersionHelper.isOrAbove1_20_5() && NetworkReflections.clazz$UnknownPayload.isInstance(payload)) {
clientPayload = UnknownPayload.from(payload);
} else {
return;
}
if (clientPayload == null || !clientPayload.channel().equals(NetworkManager.MOD_CHANNEL_KEY))
return;
FriendlyByteBuf buf = clientPayload.toBuffer();
NetWorkDataTypes dataType = buf.readEnumConstant(NetWorkDataTypes.class);
if (dataType == NetWorkDataTypes.CLIENT_CUSTOM_BLOCK) {
int clientBlockRegistrySize = dataType.decode(buf);
int serverBlockRegistrySize = RegistryUtils.currentBlockRegistrySize();
if (clientBlockRegistrySize != serverBlockRegistrySize) {
user.kick(Component.translatable(
"disconnect.craftengine.block_registry_mismatch",
TranslationArgument.numeric(clientBlockRegistrySize),
TranslationArgument.numeric(serverBlockRegistrySize)
));
return;
FriendlyByteBuf buf = discardedPayload.toBuffer();
NetWorkDataTypes<?> dataType = NetWorkDataTypes.readType(buf);
if (dataType == NetWorkDataTypes.CLIENT_CUSTOM_BLOCK) {
int clientBlockRegistrySize = dataType.as(Integer.class).decode(buf);
int serverBlockRegistrySize = RegistryUtils.currentBlockRegistrySize();
if (clientBlockRegistrySize != serverBlockRegistrySize) {
user.kick(Component.translatable(
"disconnect.craftengine.block_registry_mismatch",
TranslationArgument.numeric(clientBlockRegistrySize),
TranslationArgument.numeric(serverBlockRegistrySize)
));
return;
}
user.setClientModState(true);
} else if (dataType == NetWorkDataTypes.CANCEL_BLOCK_UPDATE) {
if (!VersionHelper.isOrAbove1_20_2()) return;
if (dataType.as(Boolean.class).decode(buf)) {
FriendlyByteBuf bufPayload = new FriendlyByteBuf(Unpooled.buffer());
dataType.writeType(bufPayload);
dataType.as(Boolean.class).encode(bufPayload, true);
user.sendCustomPayload(NetworkManager.MOD_CHANNEL_KEY, bufPayload.array());
}
}
user.setClientModState(true);
} else if (dataType == NetWorkDataTypes.CANCEL_BLOCK_UPDATE) {
if (dataType.decode(buf)) {
FriendlyByteBuf bufPayload = new FriendlyByteBuf(Unpooled.buffer());
bufPayload.writeEnumConstant(dataType);
dataType.encode(bufPayload, true);
user.sendCustomPayload(NetworkManager.MOD_CHANNEL_KEY, bufPayload.array());
}
}
} catch (Throwable e) {

View File

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

View File

@@ -0,0 +1,28 @@
package net.momirealms.craftengine.bukkit.plugin.network.payload;
import io.netty.buffer.ByteBuf;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.NetworkReflections;
import net.momirealms.craftengine.bukkit.util.KeyUtils;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.util.FriendlyByteBuf;
import net.momirealms.craftengine.core.util.Key;
public record UnknownPayload(Key channel, ByteBuf rawPayload) implements Payload{
public static UnknownPayload from(Object payload) {
try {
Object id = NetworkReflections.field$UnknownPayload$id.get(payload);
ByteBuf data = (ByteBuf) NetworkReflections.field$UnknownPayload$data.get(payload);
Key channel = KeyUtils.resourceLocationToKey(id);
return new UnknownPayload(channel, data);
} catch (Exception e) {
CraftEngine.instance().logger().warn("Failed to create UnknownPayload", e);
return null;
}
}
@Override
public FriendlyByteBuf toBuffer() {
return new FriendlyByteBuf(rawPayload);
}
}

View File

@@ -3873,4 +3873,27 @@ public final class CoreReflections {
throw new ReflectionInitException("Failed to initialize HashOps", e);
}
}
public static final Class<?> clazz$SnowLayerBlock = requireNonNull(
BukkitReflectionUtils.findReobfOrMojmapClass(
"world.level.block.BlockSnow",
"world.level.block.SnowLayerBlock"
)
);
public static final Field field$SnowLayerBlock$LAYERS = requireNonNull(
ReflectionUtils.getDeclaredField(
clazz$SnowLayerBlock, clazz$IntegerProperty, 0
)
);
public static final Object instance$SnowLayerBlock$LAYERS;
static {
try {
instance$SnowLayerBlock$LAYERS = field$SnowLayerBlock$LAYERS.get(null);
} catch (IllegalAccessException e) {
throw new ReflectionInitException("Failed to initialize SnowLayerBlock$LAYERS", e);
}
}
}

View File

@@ -17,6 +17,7 @@ public final class MBlocks {
public static final Object SHORT_GRASS$defaultState;
public static final Object SHULKER_BOX;
public static final Object COMPOSTER;
public static final Object SNOW;
private static Object getById(String id) {
Object rl = FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", id);
@@ -35,5 +36,6 @@ public final class MBlocks {
SHORT_GRASS$defaultState = FastNMS.INSTANCE.method$Block$defaultState(SHORT_GRASS);
SHULKER_BOX = getById("shulker_box");
COMPOSTER = getById("composter");
SNOW = getById("snow");
}
}

View File

@@ -1248,7 +1248,9 @@ public final class NetworkReflections {
);
public static final Constructor<?> constructor$ClientboundCustomPayloadPacket = requireNonNull(
ReflectionUtils.getConstructor(clazz$ClientboundCustomPayloadPacket, 0)
VersionHelper.isOrAbove1_20_2()
? ReflectionUtils.getConstructor(clazz$ClientboundCustomPayloadPacket, clazz$CustomPacketPayload)
: ReflectionUtils.getConstructor(clazz$ClientboundCustomPayloadPacket, CoreReflections.clazz$ResourceLocation, CoreReflections.clazz$FriendlyByteBuf)
);
// 1.20.2+
@@ -1621,4 +1623,27 @@ public final class NetworkReflections {
throw new ReflectionInitException("Failed to initialize HashedStack$STREAM_CODEC", e);
}
}
// 1.20.2~1.20.4
public static final Class<?> clazz$UnknownPayload = MiscUtils.requireNonNullIf(
ReflectionUtils.getClazz(
BukkitReflectionUtils.assembleMCClass("network.protocol.common.ServerboundCustomPayloadPacket$UnknownPayload")
),
VersionHelper.isOrAbove1_20_2() && !VersionHelper.isOrAbove1_20_5()
);
// 1.20.2~1.20.4
public static final Field field$UnknownPayload$id = Optional.ofNullable(clazz$UnknownPayload)
.map(it -> ReflectionUtils.getDeclaredField(it, CoreReflections.clazz$ResourceLocation, 0))
.orElse(null);
// 1.20.2~1.20.4
public static final Field field$UnknownPayload$data = Optional.ofNullable(clazz$UnknownPayload)
.map(it -> ReflectionUtils.getDeclaredField(it, ByteBuf.class, 0))
.orElse(null);
// 1.20.2~1.20.4
public static final Constructor<?> constructor$UnknownPayload = Optional.ofNullable(clazz$UnknownPayload)
.map(ReflectionUtils::getTheOnlyConstructor)
.orElse(null);
}

View File

@@ -306,13 +306,20 @@ public class BukkitServerPlayer extends Player {
public void sendCustomPayload(Key channel, byte[] data) {
try {
Object channelKey = KeyUtils.toResourceLocation(channel);
Object dataPayload;
if (DiscardedPayload.useNewMethod) {
dataPayload = NetworkReflections.constructor$DiscardedPayload.newInstance(channelKey, data);
Object responsePacket;
if (VersionHelper.isOrAbove1_20_2()) {
Object dataPayload;
if (NetworkReflections.clazz$UnknownPayload != null) {
dataPayload = NetworkReflections.constructor$UnknownPayload.newInstance(channelKey, Unpooled.wrappedBuffer(data));
} else if (DiscardedPayload.useNewMethod) {
dataPayload = NetworkReflections.constructor$DiscardedPayload.newInstance(channelKey, data);
} else {
dataPayload = NetworkReflections.constructor$DiscardedPayload.newInstance(channelKey, Unpooled.wrappedBuffer(data));
}
responsePacket = NetworkReflections.constructor$ClientboundCustomPayloadPacket.newInstance(dataPayload);
} else {
dataPayload = NetworkReflections.constructor$DiscardedPayload.newInstance(channelKey, Unpooled.wrappedBuffer(data));
responsePacket = NetworkReflections.constructor$ClientboundCustomPayloadPacket.newInstance(channelKey, FastNMS.INSTANCE.constructor$FriendlyByteBuf(Unpooled.wrappedBuffer(data)));
}
Object responsePacket = NetworkReflections.constructor$ClientboundCustomPayloadPacket.newInstance(dataPayload);
this.sendPacket(responsePacket, true);
} catch (Exception e) {
CraftEngine.instance().logger().warn("Failed to send custom payload to " + name(), e);

View File

@@ -11,6 +11,7 @@ import net.momirealms.craftengine.core.block.DelegatingBlockState;
import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.util.Key;
import org.bukkit.block.Block;
import org.bukkit.block.data.BlockData;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
@@ -142,4 +143,10 @@ public class BlockStateUtils {
Object blockOwner = getBlockOwner(state);
return IGNITE_ODDS.getOrDefault(blockOwner, 0) > 0;
}
public static Object getBlockState(Block block) {
Object worldServer = FastNMS.INSTANCE.field$CraftWorld$ServerLevel(block.getWorld());
Object blockPos = LocationUtils.toBlockPos(block.getX(), block.getY(), block.getZ());
return FastNMS.INSTANCE.method$BlockGetter$getBlockState(worldServer, blockPos);
}
}

View File

@@ -2,7 +2,10 @@ package net.momirealms.craftengine.bukkit.world;
import net.momirealms.craftengine.bukkit.api.CraftEngineBlocks;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MBlocks;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MFluids;
import net.momirealms.craftengine.bukkit.util.BlockStateUtils;
import net.momirealms.craftengine.bukkit.util.LocationUtils;
import net.momirealms.craftengine.core.block.CustomBlock;
import net.momirealms.craftengine.core.block.ImmutableBlockState;
@@ -21,11 +24,15 @@ public class BukkitBlockInWorld implements BlockInWorld {
@Override
public boolean canBeReplaced(BlockPlaceContext context) {
ImmutableBlockState customState = CraftEngineBlocks.getCustomBlockState(this.block);
Object state = BlockStateUtils.getBlockState(this.block);
ImmutableBlockState customState = BlockStateUtils.getOptionalCustomBlockState(state).orElse(null);
if (customState != null && !customState.isEmpty()) {
return customState.behavior().canBeReplaced(context, customState);
}
return this.block.isReplaceable();
if (BlockStateUtils.getBlockOwner(state) == MBlocks.SNOW) {
return (int) FastNMS.INSTANCE.method$StateHolder$getValue(state, CoreReflections.instance$SnowLayerBlock$LAYERS) == 1;
}
return BlockStateUtils.isReplaceable(state);
}
@Override

View File

@@ -88,6 +88,11 @@ public class BukkitWorldManager implements WorldManager, Listener {
}
}
@Override
public CEWorld[] getWorlds() {
return this.worldArray;
}
private void resetWorldArray() {
this.worldArray = this.worlds.values().toArray(new CEWorld[0]);
}
@@ -218,7 +223,6 @@ public class BukkitWorldManager implements WorldManager, Listener {
this.lastVisitedUUID = null;
}
this.resetWorldArray();
} finally {
this.worldMapLock.writeLock().unlock();
}
@@ -328,74 +332,76 @@ public class BukkitWorldManager implements WorldManager, Listener {
Object chunkSource = FastNMS.INSTANCE.method$ServerLevel$getChunkSource(worldServer);
Object levelChunk = FastNMS.INSTANCE.method$ServerChunkCache$getChunkAtIfLoadedMainThread(chunkSource, chunk.getX(), chunk.getZ());
Object[] sections = FastNMS.INSTANCE.method$ChunkAccess$getSections(levelChunk);
for (int i = 0; i < ceSections.length; i++) {
CESection ceSection = ceSections[i];
Object section = sections[i];
if (Config.syncCustomBlocks()) {
Object statesContainer = FastNMS.INSTANCE.field$LevelChunkSection$states(section);
Object data = CoreReflections.varHandle$PalettedContainer$data.get(statesContainer);
Object palette = CoreReflections.field$PalettedContainer$Data$palette.get(data);
boolean requiresSync = false;
if (CoreReflections.clazz$SingleValuePalette.isInstance(palette)) {
Object onlyBlockState = CoreReflections.field$SingleValuePalette$value.get(palette);
if (BlockStateUtils.isCustomBlock(onlyBlockState)) {
synchronized (sections) {
for (int i = 0; i < ceSections.length; i++) {
CESection ceSection = ceSections[i];
Object section = sections[i];
if (Config.syncCustomBlocks()) {
Object statesContainer = FastNMS.INSTANCE.field$LevelChunkSection$states(section);
Object data = CoreReflections.varHandle$PalettedContainer$data.get(statesContainer);
Object palette = CoreReflections.field$PalettedContainer$Data$palette.get(data);
boolean requiresSync = false;
if (CoreReflections.clazz$SingleValuePalette.isInstance(palette)) {
Object onlyBlockState = CoreReflections.field$SingleValuePalette$value.get(palette);
if (BlockStateUtils.isCustomBlock(onlyBlockState)) {
requiresSync = true;
}
} else if (CoreReflections.clazz$LinearPalette.isInstance(palette)) {
Object[] blockStates = (Object[]) CoreReflections.field$LinearPalette$values.get(palette);
for (Object blockState : blockStates) {
if (blockState != null) {
if (BlockStateUtils.isCustomBlock(blockState)) {
requiresSync = true;
break;
}
}
}
} else if (CoreReflections.clazz$HashMapPalette.isInstance(palette)) {
Object biMap = CoreReflections.field$HashMapPalette$values.get(palette);
Object[] blockStates = (Object[]) CoreReflections.field$CrudeIncrementalIntIdentityHashBiMap$keys.get(biMap);
for (Object blockState : blockStates) {
if (blockState != null) {
if (BlockStateUtils.isCustomBlock(blockState)) {
requiresSync = true;
break;
}
}
}
} else {
requiresSync = true;
}
} else if (CoreReflections.clazz$LinearPalette.isInstance(palette)) {
Object[] blockStates = (Object[]) CoreReflections.field$LinearPalette$values.get(palette);
for (Object blockState : blockStates) {
if (blockState != null) {
if (BlockStateUtils.isCustomBlock(blockState)) {
requiresSync = true;
break;
}
}
}
} else if (CoreReflections.clazz$HashMapPalette.isInstance(palette)) {
Object biMap = CoreReflections.field$HashMapPalette$values.get(palette);
Object[] blockStates = (Object[]) CoreReflections.field$CrudeIncrementalIntIdentityHashBiMap$keys.get(biMap);
for (Object blockState : blockStates) {
if (blockState != null) {
if (BlockStateUtils.isCustomBlock(blockState)) {
requiresSync = true;
break;
}
}
}
} else {
requiresSync = true;
}
if (requiresSync) {
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
for (int y = 0; y < 16; y++) {
Object mcState = FastNMS.INSTANCE.method$LevelChunkSection$getBlockState(section, x, y, z);
Optional<ImmutableBlockState> optionalCustomState = BlockStateUtils.getOptionalCustomBlockState(mcState);
if (optionalCustomState.isPresent()) {
ceSection.setBlockState(x, y, z, optionalCustomState.get());
if (requiresSync) {
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
for (int y = 0; y < 16; y++) {
Object mcState = FastNMS.INSTANCE.method$LevelChunkSection$getBlockState(section, x, y, z);
Optional<ImmutableBlockState> optionalCustomState = BlockStateUtils.getOptionalCustomBlockState(mcState);
if (optionalCustomState.isPresent()) {
ceSection.setBlockState(x, y, z, optionalCustomState.get());
}
}
}
}
}
}
}
if (Config.restoreCustomBlocks()) {
if (!ceSection.statesContainer().isEmpty()) {
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
for (int y = 0; y < 16; y++) {
ImmutableBlockState customState = ceSection.getBlockState(x, y, z);
if (!customState.isEmpty() && customState.customBlockState() != null) {
FastNMS.INSTANCE.method$LevelChunkSection$setBlockState(section, x, y, z, customState.customBlockState().handle(), false);
if (Config.restoreCustomBlocks()) {
if (!ceSection.statesContainer().isEmpty()) {
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
for (int y = 0; y < 16; y++) {
ImmutableBlockState customState = ceSection.getBlockState(x, y, z);
if (!customState.isEmpty() && customState.customBlockState() != null) {
FastNMS.INSTANCE.method$LevelChunkSection$setBlockState(section, x, y, z, customState.customBlockState().handle(), false);
}
}
}
}
}
}
int finalI = i;
WorldStorageInjector.injectLevelChunkSection(section, ceSection, ceChunk, new SectionPos(pos.x, ceChunk.sectionY(i), pos.z),
(injected) -> sections[finalI] = injected);
}
int finalI = i;
WorldStorageInjector.injectLevelChunkSection(section, ceSection, ceChunk, new SectionPos(pos.x, ceChunk.sectionY(i), pos.z),
(injected) -> sections[finalI] = injected);
}
if (Config.enableRecipeSystem()) {
@SuppressWarnings("unchecked")

View File

@@ -84,8 +84,6 @@ resource-pack:
ip: "localhost"
port: 8163
protocol: "http"
# The optional URL must be complete and include a trailing slash / at the end.
#url: "http://localhost:8163/"
deny-non-minecraft-request: true
one-time-token: true
rate-limit:
@@ -380,9 +378,9 @@ chunk-system:
# Settings for injection
injection:
# Requires a restart to apply
# SECTION: Inject the LevelChunkSection (Faster & Experimental) since 0.0.53
# SECTION: Inject the LevelChunkSection
# PALETTE: Inject the PalettedContainer
target: PALETTE
target: SECTION
# Enables faster injection method
# Note: May not work with certain server forks that alter chunk class structure (In most cases it won't conflict)
use-fast-method: false

View File

@@ -393,6 +393,7 @@ warning.config.function.potion_effect.missing_potion_effect: "<yellow>Issue foun
warning.config.function.set_cooldown.missing_time: "<yellow>Issue found in file <arg:0> - The config '<arg:1>' is missing the required 'time' argument for 'set_cooldown' function.</yellow>"
warning.config.function.set_cooldown.missing_id: "<yellow>Issue found in file <arg:0> - The config '<arg:1>' is missing the required 'id' argument for 'set_cooldown' function.</yellow>"
warning.config.function.remove_cooldown.missing_id: "<yellow>Issue found in file <arg:0> - The config '<arg:1>' is missing the required 'id' argument for 'remove_cooldown' function.</yellow>"
warning.config.function.mythic_mobs_skill.missing_skill: "<yellow>Issue found in file <arg:0> - The config '<arg:1>' is missing the required 'skill' argument for 'mythic_mobs_skill' function.</yellow>"
warning.config.selector.missing_type: "<yellow>Issue found in file <arg:0> - The config '<arg:1>' is missing the required 'type' argument for selector.</yellow>"
warning.config.selector.invalid_type: "<yellow>Issue found in file <arg:0> - The config '<arg:1>' is using an invalid selector type '<arg:2>'.</yellow>"
warning.config.selector.invalid_target: "<yellow>Issue found in file <arg:0> - The config '<arg:1>' is using an invalid selector target '<arg:2>'.</yellow>"

View File

@@ -391,6 +391,7 @@ warning.config.function.potion_effect.missing_potion_effect: "<yellow>在文件
warning.config.function.set_cooldown.missing_time: "<yellow>在文件 <arg:0> 中发现问题 - 配置项 '<arg:1>' 缺少 'set_cooldown' 函数必需的 'time' 参数</yellow>"
warning.config.function.set_cooldown.missing_id: "<yellow>在文件 <arg:0> 中发现问题 - 配置项 '<arg:1>' 缺少 'set_cooldown' 函数必需的 'id' 参数</yellow>"
warning.config.function.remove_cooldown.missing_id: "<yellow>在文件 <arg:0> 中发现问题 - 配置项 '<arg:1>' 缺少 'remove_cooldown' 函数必需的 'id' 参数</yellow>"
warning.config.function.mythic_mobs_skill.missing_skill: "<yellow>在文件 <arg:0> 中发现问题 - 配置项 '<arg:1>' 缺少 'mythic_mobs_skill' 函数必需的 'skill' 参数</yellow>"
warning.config.selector.missing_type: "<yellow>在文件 <arg:0> 中发现问题 - 配置项 '<arg:1>' 缺少选择器必需的 'type' 参数</yellow>"
warning.config.selector.invalid_type: "<yellow>在文件 <arg:0> 中发现问题 - 配置项 '<arg:1>' 使用了无效的选择器类型 '<arg:2>'</yellow>"
warning.config.selector.invalid_target: "<yellow>在文件 <arg:0> 中发现问题 - 配置项 '<arg:1>' 使用了无效的选择器目标 '<arg:2>'</yellow>"

View File

@@ -9,7 +9,7 @@ import net.momirealms.craftengine.core.util.*;
import java.util.Map;
public class Properties {
public final class Properties {
public static final Key BOOLEAN = Key.of("craftengine:boolean");
public static final Key INT = Key.of("craftengine:int");
public static final Key STRING = Key.of("craftengine:string");

View File

@@ -9,11 +9,14 @@ import net.momirealms.craftengine.core.item.data.Enchantment;
import net.momirealms.craftengine.core.item.data.JukeboxPlayable;
import net.momirealms.craftengine.core.item.equipment.*;
import net.momirealms.craftengine.core.item.modifier.*;
import net.momirealms.craftengine.core.item.modifier.lore.DynamicLoreModifier;
import net.momirealms.craftengine.core.item.modifier.lore.LoreModifier;
import net.momirealms.craftengine.core.item.setting.EquipmentData;
import net.momirealms.craftengine.core.pack.AbstractPackManager;
import net.momirealms.craftengine.core.pack.LoadingSequence;
import net.momirealms.craftengine.core.pack.Pack;
import net.momirealms.craftengine.core.pack.ResourceLocation;
import net.momirealms.craftengine.core.pack.host.ResourcePackHosts;
import net.momirealms.craftengine.core.pack.model.*;
import net.momirealms.craftengine.core.pack.model.generation.AbstractModelGenerator;
import net.momirealms.craftengine.core.pack.model.generation.ModelGeneration;
@@ -534,15 +537,12 @@ public abstract class AbstractItemManager<I> extends AbstractModelGenerator impl
return new CustomNameModifier<>(name);
}, "custom-name", "item-name", "display-name");
}
registerDataType(LoreModifier::createLoreModifier, "lore", "display-lore", "description");
registerDataType((obj) -> {
List<String> lore = MiscUtils.getAsStringList(obj);
return new LoreModifier<>(lore);
}, "lore", "display-lore", "description");
registerDataType((obj) -> {
Map<String, List<String>> dynamicLore = new LinkedHashMap<>();
Map<String, LoreModifier<I>> dynamicLore = new LinkedHashMap<>();
if (obj instanceof Map<?, ?> map) {
for (Map.Entry<?, ?> entry : map.entrySet()) {
dynamicLore.put(entry.getKey().toString(), MiscUtils.getAsStringList(entry.getValue()));
dynamicLore.put(entry.getKey().toString(), LoreModifier.createLoreModifier(entry.getValue()));
}
}
return new DynamicLoreModifier<>(dynamicLore);

View File

@@ -16,7 +16,6 @@ import net.momirealms.sparrow.nbt.Tag;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
public abstract class ItemFactory<W extends ItemWrapper<I>, I> {
protected final CraftEngine plugin;
@@ -112,7 +111,7 @@ public abstract class ItemFactory<W extends ItemWrapper<I>, I> {
protected void loreComponent(W item, List<Component> component) {
if (component != null && !component.isEmpty()) {
loreJson(item, component.stream().map(AdventureHelper::componentToJson).collect(Collectors.toList()));
loreJson(item, component.stream().map(AdventureHelper::componentToJson).toList());
} else {
loreJson(item, null);
}

View File

@@ -1,65 +0,0 @@
package net.momirealms.craftengine.core.item.modifier;
import net.momirealms.craftengine.core.item.ComponentKeys;
import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.item.ItemBuildContext;
import net.momirealms.craftengine.core.item.NetworkItemHandler;
import net.momirealms.craftengine.core.plugin.config.Config;
import net.momirealms.craftengine.core.util.AdventureHelper;
import net.momirealms.craftengine.core.util.VersionHelper;
import net.momirealms.sparrow.nbt.CompoundTag;
import net.momirealms.sparrow.nbt.Tag;
import java.util.ArrayList;
import java.util.List;
public class LoreModifier<I> implements ItemDataModifier<I> {
private final List<String> argument;
public LoreModifier(List<String> argument) {
if (Config.addNonItalicTag()) {
List<String> processed = new ArrayList<>(argument.size());
for (String arg : argument) {
if (arg.startsWith("<!i>")) {
processed.add(arg);
} else {
processed.add("<!i>" + arg);
}
}
this.argument = processed;
} else {
this.argument = argument;
}
}
@Override
public String name() {
return "lore";
}
@Override
public Item<I> apply(Item<I> item, ItemBuildContext context) {
item.loreComponent(this.argument.stream().map(it -> AdventureHelper.miniMessage().deserialize(it, context.tagResolvers())).toList());
return item;
}
@Override
public Item<I> prepareNetworkItem(Item<I> item, ItemBuildContext context, CompoundTag networkData) {
if (VersionHelper.isOrAbove1_20_5()) {
Tag previous = item.getSparrowNBTComponent(ComponentKeys.LORE);
if (previous != null) {
networkData.put(ComponentKeys.LORE.asString(), NetworkItemHandler.pack(NetworkItemHandler.Operation.ADD, previous));
} else {
networkData.put(ComponentKeys.LORE.asString(), NetworkItemHandler.pack(NetworkItemHandler.Operation.REMOVE));
}
} else {
Tag previous = item.getTag("display", "Lore");
if (previous != null) {
networkData.put("display.Lore", NetworkItemHandler.pack(NetworkItemHandler.Operation.ADD, previous));
} else {
networkData.put("display.Lore", NetworkItemHandler.pack(NetworkItemHandler.Operation.REMOVE));
}
}
return item;
}
}

View File

@@ -1,31 +1,29 @@
package net.momirealms.craftengine.core.item.modifier;
package net.momirealms.craftengine.core.item.modifier.lore;
import net.momirealms.craftengine.core.item.ComponentKeys;
import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.item.ItemBuildContext;
import net.momirealms.craftengine.core.item.NetworkItemHandler;
import net.momirealms.craftengine.core.util.AdventureHelper;
import net.momirealms.craftengine.core.item.modifier.ItemDataModifier;
import net.momirealms.craftengine.core.util.VersionHelper;
import net.momirealms.sparrow.nbt.CompoundTag;
import net.momirealms.sparrow.nbt.Tag;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
public class DynamicLoreModifier<I> implements ItemDataModifier<I> {
public final class DynamicLoreModifier<I> implements ItemDataModifier<I> {
public static final String CONTEXT_TAG_KEY = "craftengine:display_context";
private final Map<String, List<String>> displayContexts;
private final String defaultContext;
private final Map<String, LoreModifier<I>> displayContexts;
private final LoreModifier<I> defaultModifier;
public DynamicLoreModifier(Map<String, List<String>> displayContexts) {
this.defaultContext = displayContexts.keySet().iterator().next();
public DynamicLoreModifier(Map<String, LoreModifier<I>> displayContexts) {
this.displayContexts = displayContexts;
this.defaultModifier = displayContexts.values().iterator().next();
}
public Map<String, List<String>> displayContexts() {
return Collections.unmodifiableMap(this.displayContexts);
public Map<String, LoreModifier<I>> displayContexts() {
return displayContexts;
}
@Override
@@ -35,13 +33,12 @@ public class DynamicLoreModifier<I> implements ItemDataModifier<I> {
@Override
public Item<I> apply(Item<I> item, ItemBuildContext context) {
String displayContext = Optional.ofNullable(item.getJavaTag(CONTEXT_TAG_KEY)).orElse(this.defaultContext).toString();
List<String> lore = this.displayContexts.get(displayContext);
String displayContext = Optional.ofNullable(item.getJavaTag(CONTEXT_TAG_KEY)).orElse(this.defaultModifier).toString();
LoreModifier<I> lore = this.displayContexts.get(displayContext);
if (lore == null) {
lore = this.displayContexts.get(this.defaultContext);
lore = this.defaultModifier;
}
item.loreComponent(lore.stream().map(it -> AdventureHelper.miniMessage().deserialize(it, context.tagResolvers())).toList());
return item;
return lore.apply(item, context);
}
@Override

View File

@@ -0,0 +1,39 @@
package net.momirealms.craftengine.core.item.modifier.lore;
import net.kyori.adventure.text.Component;
import net.momirealms.craftengine.core.item.ItemBuildContext;
import net.momirealms.craftengine.core.util.AdventureHelper;
import net.momirealms.craftengine.core.util.TriFunction;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
// todo 可以考虑未来添加条件系统
public record LoreModification(Operation operation, boolean split, String[] content) {
public Stream<Component> apply(Stream<Component> lore, ItemBuildContext context) {
return this.operation.function.apply(lore, context, this);
}
public Stream<Component> parseAsStream(ItemBuildContext context) {
Stream<Component> parsed = Arrays.stream(this.content).map(string -> AdventureHelper.miniMessage().deserialize(string, context.tagResolvers()));
return this.split ? parsed.map(AdventureHelper::splitLines).flatMap(List::stream) : parsed;
}
public List<Component> parseAsList(ItemBuildContext context) {
return this.parseAsStream(context).collect(Collectors.toList());
}
public enum Operation {
APPEND((s, c, modification) -> Stream.concat(s, modification.parseAsStream(c))),
PREPEND((s, c, modification) -> Stream.concat(modification.parseAsStream(c), s));
private final TriFunction<Stream<Component>, ItemBuildContext, LoreModification, Stream<Component>> function;
Operation(TriFunction<Stream<Component>, ItemBuildContext, LoreModification, Stream<Component>> function) {
this.function = function;
}
}
}

View File

@@ -0,0 +1,11 @@
package net.momirealms.craftengine.core.item.modifier.lore;
import org.jetbrains.annotations.NotNull;
public record LoreModificationHolder(LoreModification modification, int priority) implements Comparable<LoreModificationHolder> {
@Override
public int compareTo(@NotNull LoreModificationHolder o) {
return Integer.compare(priority, o.priority);
}
}

View File

@@ -0,0 +1,126 @@
package net.momirealms.craftengine.core.item.modifier.lore;
import net.kyori.adventure.text.Component;
import net.momirealms.craftengine.core.item.ComponentKeys;
import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.item.ItemBuildContext;
import net.momirealms.craftengine.core.item.NetworkItemHandler;
import net.momirealms.craftengine.core.item.modifier.ItemDataModifier;
import net.momirealms.craftengine.core.util.MiscUtils;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
import net.momirealms.craftengine.core.util.VersionHelper;
import net.momirealms.sparrow.nbt.CompoundTag;
import net.momirealms.sparrow.nbt.Tag;
import java.util.*;
import java.util.stream.Stream;
public sealed interface LoreModifier<I> extends ItemDataModifier<I>
permits LoreModifier.EmptyLoreModifier, LoreModifier.CompositeLoreModifier, LoreModifier.DoubleLoreModifier, LoreModifier.SingleLoreModifier {
@Override
default String name() {
return "lore";
}
@Override
default Item<I> prepareNetworkItem(Item<I> item, ItemBuildContext context, CompoundTag networkData) {
if (VersionHelper.isOrAbove1_20_5()) {
Tag previous = item.getSparrowNBTComponent(ComponentKeys.LORE);
if (previous != null) networkData.put(ComponentKeys.LORE.asString(), NetworkItemHandler.pack(NetworkItemHandler.Operation.ADD, previous));
else networkData.put(ComponentKeys.LORE.asString(), NetworkItemHandler.pack(NetworkItemHandler.Operation.REMOVE));
} else {
Tag previous = item.getTag("display", "Lore");
if (previous != null) networkData.put("display.Lore", NetworkItemHandler.pack(NetworkItemHandler.Operation.ADD, previous));
else networkData.put("display.Lore", NetworkItemHandler.pack(NetworkItemHandler.Operation.REMOVE));
}
return item;
}
static <I> LoreModifier<I> createLoreModifier(Object arg) {
List<Object> rawLoreData = MiscUtils.getAsList(arg, Object.class);
String[] rawLore = new String[rawLoreData.size()];
label_all_string_check: {
for (int i = 0; i < rawLore.length; i++) {
Object o = rawLoreData.get(i);
if (o instanceof Map<?,?>) {
break label_all_string_check;
} else {
rawLore[i] = o.toString();
}
}
return new SingleLoreModifier<>(new LoreModification(LoreModification.Operation.APPEND, false, rawLore));
}
List<LoreModificationHolder> modifications = new ArrayList<>(rawLoreData.size() + 1);
int lastPriority = 0;
for (Object o : rawLoreData) {
if (o instanceof Map<?,?> complexLore) {
String[] content = MiscUtils.getAsStringArray(complexLore.get("content"));
LoreModification.Operation operation = ResourceConfigUtils.getAsEnum(Optional.ofNullable(complexLore.get("operation")).map(String::valueOf).orElse(null), LoreModification.Operation.class, LoreModification.Operation.APPEND);
lastPriority = Optional.ofNullable(complexLore.get("priority")).map(it -> ResourceConfigUtils.getAsInt(it, "priority")).orElse(lastPriority);
boolean split = ResourceConfigUtils.getAsBoolean(complexLore.get("split-lines"), "split-lines");
modifications.add(new LoreModificationHolder(new LoreModification(operation, split, content), lastPriority));
}
}
modifications.sort(LoreModificationHolder::compareTo);
return switch (modifications.size()) {
case 0 -> new EmptyLoreModifier<>();
case 1 -> new SingleLoreModifier<>(modifications.get(0).modification());
case 2 -> new DoubleLoreModifier<>(modifications.get(0).modification(), modifications.get(1).modification());
default -> new CompositeLoreModifier<>(modifications.stream().map(LoreModificationHolder::modification).toArray(LoreModification[]::new));
};
}
non-sealed class EmptyLoreModifier<I> implements LoreModifier<I> {
@Override
public Item<I> apply(Item<I> item, ItemBuildContext context) {
return item;
}
}
non-sealed class SingleLoreModifier<I> implements LoreModifier<I> {
private final LoreModification modification;
public SingleLoreModifier(LoreModification modification) {
this.modification = modification;
}
@Override
public Item<I> apply(Item<I> item, ItemBuildContext context) {
item.loreComponent(this.modification.parseAsList(context));
return item;
}
}
non-sealed class DoubleLoreModifier<I> implements LoreModifier<I> {
private final LoreModification modification1;
private final LoreModification modification2;
public DoubleLoreModifier(LoreModification m1, LoreModification m2) {
this.modification1 = m1;
this.modification2 = m2;
}
@Override
public Item<I> apply(Item<I> item, ItemBuildContext context) {
item.loreComponent(this.modification2.apply(this.modification1.apply(Stream.empty(), context), context).toList());
return item;
}
}
non-sealed class CompositeLoreModifier<I> implements LoreModifier<I> {
private final LoreModification[] modifications;
public CompositeLoreModifier(LoreModification... modifications) {
this.modifications = modifications;
}
@Override
public Item<I> apply(Item<I> item, ItemBuildContext context) {
item.loreComponent(Arrays.stream(this.modifications).reduce(Stream.<Component>empty(), (stream, modification) -> modification.apply(stream, context), Stream::concat).toList());
return item;
}
}
}

View File

@@ -20,7 +20,7 @@ public final class ResourceLocation {
}
public static boolean isValidNamespace(String namespace) {
for(int i = 0; i < namespace.length(); ++i) {
for (int i = 0; i < namespace.length(); ++i) {
if (!validNamespaceChar(namespace.charAt(i))) {
return false;
}

View File

@@ -34,4 +34,6 @@ public interface CompatibilityManager {
String parse(Player player1, Player player2, String text);
int getPlayerProtocolVersion(UUID uuid);
void executeMMSkill(String skill, float power, Player player);
}

View File

@@ -38,6 +38,7 @@ public class EventFunctions {
register(CommonFunctions.LEVELER_EXP, new LevelerExpFunction.FactoryImpl<>(EventConditions::fromMap));
register(CommonFunctions.SET_COOLDOWN, new SetCooldownFunction.FactoryImpl<>(EventConditions::fromMap));
register(CommonFunctions.REMOVE_COOLDOWN, new RemoveCooldownFunction.FactoryImpl<>(EventConditions::fromMap));
register(CommonFunctions.MYTHIC_MOBS_SKILL, new MythicMobsSkillFunction.FactoryImpl<>(EventConditions::fromMap));
}
public static void register(Key key, FunctionFactory<PlayerOptionalContext> factory) {

View File

@@ -27,4 +27,5 @@ public final class CommonFunctions {
public static final Key DROP_LOOT = Key.of("craftengine:drop_loot");
public static final Key SWING_HAND = Key.of("craftengine:swing_hand");
public static final Key LEVELER_EXP = Key.of("craftengine:leveler_exp");
public static final Key MYTHIC_MOBS_SKILL = Key.of("craftengine:mythic_mobs_skill");
}

View File

@@ -0,0 +1,48 @@
package net.momirealms.craftengine.core.plugin.context.function;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.context.Condition;
import net.momirealms.craftengine.core.plugin.context.Context;
import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
import java.util.List;
import java.util.Map;
public class MythicMobsSkillFunction<CTX extends Context> extends AbstractConditionalFunction<CTX> {
private final String skill;
private final float power;
public MythicMobsSkillFunction(String skill, float power, List<Condition<CTX>> predicates) {
super(predicates);
this.skill = skill;
this.power = power;
}
@Override
protected void runInternal(CTX ctx) {
ctx.getOptionalParameter(DirectContextParameters.PLAYER).ifPresent(it -> {
CraftEngine.instance().compatibilityManager().executeMMSkill(this.skill, this.power, it);
});
}
@Override
public Key type() {
return CommonFunctions.MYTHIC_MOBS_SKILL;
}
public static class FactoryImpl<CTX extends Context> extends AbstractFactory<CTX> {
public FactoryImpl(java.util.function.Function<Map<String, Object>, Condition<CTX>> factory) {
super(factory);
}
@Override
public Function<CTX> create(Map<String, Object> args) {
String skill = ResourceConfigUtils.requireNonEmptyStringOrThrow(args.get("skill"), "warning.config.function.mythic_mobs_skill.missing_skill");
float power = ResourceConfigUtils.getAsFloat(args.getOrDefault("power", 1.0), "power");
return new MythicMobsSkillFunction<>(skill, power, getPredicates(args));
}
}
}

View File

@@ -5,7 +5,9 @@ import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.sound.Sound;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentIteratorType;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.TextReplacementConfig;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
@@ -15,7 +17,7 @@ import net.momirealms.sparrow.nbt.Tag;
import net.momirealms.sparrow.nbt.adventure.NBTComponentSerializer;
import net.momirealms.sparrow.nbt.adventure.NBTSerializerOptions;
import java.util.Map;
import java.util.*;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@@ -29,6 +31,21 @@ public class AdventureHelper {
private final MiniMessage miniMessageCustom;
private final GsonComponentSerializer gsonComponentSerializer;
private final NBTComponentSerializer nbtComponentSerializer;
private static final TextReplacementConfig REPLACE_LF = TextReplacementConfig.builder().matchLiteral("\n").replacement(Component.newline()).build();
/**
* This iterator slices a component into individual parts that
* <ul>
* <li>Can be used individually without style loss</li>
* <li>Can be concatenated to form the original component, given that children are dropped</li>
* </ul>
* Any {@link net.kyori.adventure.text.ComponentIteratorFlag}s are ignored.
*/
private static final ComponentIteratorType SLICER = (component, deque, flags) -> {
final List<Component> children = component.children();
for (int i = children.size() - 1; i >= 0; i--) {
deque.addFirst(children.get(i).applyFallbackStyle(component.style()));
}
};
private AdventureHelper() {
this.miniMessage = MiniMessage.builder().build();
@@ -209,6 +226,24 @@ public class AdventureHelper {
return getNBT().deserialize(tag);
}
public static List<Component> splitLines(Component component) {
List<Component> result = new ArrayList<>(1);
Component line = Component.empty();
for (Iterator<Component> it = component.replaceText(REPLACE_LF).iterator(SLICER); it.hasNext(); ) {
Component child = it.next().children(Collections.emptyList());
if (child instanceof TextComponent text && text.content().equals(Component.newline().content())) {
result.add(line.compact());
line = Component.empty();
} else {
line = line.append(child);
}
}
if (Component.IS_NOT_EMPTY.test(line)) {
result.add(line.compact());
}
return result;
}
/**
* Checks if a character is a legacy color code.
*

View File

@@ -51,6 +51,20 @@ public class MiscUtils {
return list;
}
public static String[] getAsStringArray(Object o) {
if (o instanceof List<?> list) {
String[] array = new String[list.size()];
for (int i = 0; i < array.length; i++) {
array[i] = list.get(i).toString();
}
return array;
} else if (o != null) {
return new String[]{o.toString()};
} else {
return new String[0];
}
}
@SuppressWarnings("unchecked")
public static <T> List<T> getAsList(Object o, Class<T> clazz) {
if (o instanceof List<?> list) {

View File

@@ -5,10 +5,7 @@ import net.momirealms.craftengine.core.plugin.locale.LocalizedException;
import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.function.Function;
import java.util.function.Supplier;
@@ -20,6 +17,17 @@ public final class ResourceConfigUtils {
return raw != null ? function.apply(raw) : defaultValue;
}
public static <E extends Enum<E>> E getAsEnum(Object o, Class<E> clazz, E defaultValue) {
if (o == null) {
return defaultValue;
}
try {
return Enum.valueOf(clazz, o.toString().toUpperCase(Locale.ENGLISH));
} catch (IllegalArgumentException e) {
return defaultValue;
}
}
public static <T> T requireNonNullOrThrow(T obj, String node) {
if (obj == null)
throw new LocalizedResourceConfigException(node);

View File

@@ -13,6 +13,8 @@ public interface WorldManager extends Manageable {
CEWorld getWorld(UUID uuid);
CEWorld[] getWorlds();
void loadWorld(World world);
void loadWorld(CEWorld world);

View File

@@ -113,7 +113,7 @@ public class CEChunk {
return this.sections[index];
}
@Nullable
@NotNull
public CESection sectionById(int sectionId) {
return this.sections[sectionIndex(sectionId)];
}

View File

@@ -2,8 +2,8 @@ org.gradle.jvmargs=-Xmx1G
# Project settings
# Rule: [major update].[feature update].[bug fix]
project_version=0.0.60.3
config_version=42
project_version=0.0.60.5
config_version=43
lang_version=22
project_group=net.momirealms
latest_supported_version=1.21.8
@@ -50,7 +50,7 @@ byte_buddy_version=1.17.5
ahocorasick_version=0.6.3
snake_yaml_version=2.4
anti_grief_version=0.18
nms_helper_version=1.0.36
nms_helper_version=1.0.37
evalex_version=3.5.0
reactive_streams_version=1.0.4
amazon_awssdk_version=2.31.23