From 38e52ac38a79885a43b3470262c18b1444c1f1a9 Mon Sep 17 00:00:00 2001 From: 3944Realms Date: Tue, 14 Oct 2025 10:54:12 +0800 Subject: [PATCH] =?UTF-8?q?=E5=B0=86=E6=8B=B4=E7=BB=B3=E7=9A=84=E4=B8=80?= =?UTF-8?q?=E4=BA=9B=E5=B7=A5=E5=85=B7=E7=B1=BB=E4=BF=AE=E6=94=B9=E6=94=BE?= =?UTF-8?q?=E5=85=A5=E5=88=B0=E8=AF=A5=E5=BA=93=E4=B8=AD=EF=BC=8C=E5=B9=B6?= =?UTF-8?q?=E4=B8=94=E6=B7=BB=E5=8A=A0=E4=BA=86=E7=BD=91=E7=BB=9C=E5=90=8C?= =?UTF-8?q?=E6=AD=A5=E6=95=B0=E6=8D=AE=E7=B1=BB=E5=9E=8B=EF=BC=8C=E5=8F=8A?= =?UTF-8?q?=E5=85=B6=E7=BD=91=E7=BB=9C=E7=B3=BB=E7=BB=9F=E6=90=AD=E5=BB=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gradle.properties | 2 +- .../java/top/r3944realms/lib39/Lib39.java | 18 ++ .../api/event/SyncManagerRegisterEvent.java | 82 ++++++ .../lib39/core/event/ClientHandler.java | 4 + .../lib39/core/event/CommonHandler.java | 64 ++++ .../lib39/core/event/ServerHandler.java | 4 + .../lib39/core/network/NetworkHandler.java | 34 +++ .../network/toClient/SyncNBTDataS2CPack.java | 60 ++++ .../lib39/core/registry/LocaleRegistry.java | 12 +- .../lib39/core/sync/ISyncData.java | 14 + .../lib39/core/sync/ISyncManager.java | 30 ++ .../lib39/core/sync/NBTSyncData.java | 36 +++ .../lib39/core/sync/SyncData2Manager.java | 276 ++++++++++++++++++ .../provider/SimpleLanguageProvider.java | 6 +- .../lib39/datagen/value/ILangKeyValue.java | 4 +- .../lib39/datagen/value/LangKeyValue.java | 85 +++--- .../lib39/datagen/value/ModPartEnum.java | 9 +- .../command/CommandAliasHelper.java | 3 +- .../top/r3944realms/lib39/util/lang/Pair.java | 45 +++ .../r3944realms/lib39/util/lang/Triple.java | 48 +++ .../r3944realms/lib39/util/lang/Tuple.java | 85 ++++++ .../r3944realms/lib39/util/nbt/NBTReader.java | 38 +++ .../r3944realms/lib39/util/nbt/NBTWriter.java | 36 +++ .../lib39/util/riding/RidingApplier.java | 118 ++++++++ .../util/riding/RidingCycleException.java | 41 +++ .../lib39/util/riding/RidingDismounts.java | 208 +++++++++++++ .../lib39/util/riding/RidingFinder.java | 102 +++++++ .../lib39/util/riding/RidingRelationship.java | 101 +++++++ .../lib39/util/riding/RidingSaver.java | 114 ++++++++ .../lib39/util/riding/RidingSerializer.java | 36 +++ .../lib39/util/riding/RidingValidator.java | 59 ++++ 31 files changed, 1730 insertions(+), 44 deletions(-) create mode 100644 src/main/java/top/r3944realms/lib39/api/event/SyncManagerRegisterEvent.java create mode 100644 src/main/java/top/r3944realms/lib39/core/event/ClientHandler.java create mode 100644 src/main/java/top/r3944realms/lib39/core/event/CommonHandler.java create mode 100644 src/main/java/top/r3944realms/lib39/core/event/ServerHandler.java create mode 100644 src/main/java/top/r3944realms/lib39/core/network/NetworkHandler.java create mode 100644 src/main/java/top/r3944realms/lib39/core/network/toClient/SyncNBTDataS2CPack.java create mode 100644 src/main/java/top/r3944realms/lib39/core/sync/ISyncData.java create mode 100644 src/main/java/top/r3944realms/lib39/core/sync/ISyncManager.java create mode 100644 src/main/java/top/r3944realms/lib39/core/sync/NBTSyncData.java create mode 100644 src/main/java/top/r3944realms/lib39/core/sync/SyncData2Manager.java rename src/main/java/top/r3944realms/lib39/{utils => util}/command/CommandAliasHelper.java (98%) create mode 100644 src/main/java/top/r3944realms/lib39/util/lang/Pair.java create mode 100644 src/main/java/top/r3944realms/lib39/util/lang/Triple.java create mode 100644 src/main/java/top/r3944realms/lib39/util/lang/Tuple.java create mode 100644 src/main/java/top/r3944realms/lib39/util/nbt/NBTReader.java create mode 100644 src/main/java/top/r3944realms/lib39/util/nbt/NBTWriter.java create mode 100644 src/main/java/top/r3944realms/lib39/util/riding/RidingApplier.java create mode 100644 src/main/java/top/r3944realms/lib39/util/riding/RidingCycleException.java create mode 100644 src/main/java/top/r3944realms/lib39/util/riding/RidingDismounts.java create mode 100644 src/main/java/top/r3944realms/lib39/util/riding/RidingFinder.java create mode 100644 src/main/java/top/r3944realms/lib39/util/riding/RidingRelationship.java create mode 100644 src/main/java/top/r3944realms/lib39/util/riding/RidingSaver.java create mode 100644 src/main/java/top/r3944realms/lib39/util/riding/RidingSerializer.java create mode 100644 src/main/java/top/r3944realms/lib39/util/riding/RidingValidator.java diff --git a/gradle.properties b/gradle.properties index 7f3c1c3..2985816 100644 --- a/gradle.properties +++ b/gradle.properties @@ -33,7 +33,7 @@ mod_name=3944Realms 's Lib Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=MIT # The mod version. See https://semver.org/ -mod_version=0.0.1 +mod_version=0.0.2 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html diff --git a/src/main/java/top/r3944realms/lib39/Lib39.java b/src/main/java/top/r3944realms/lib39/Lib39.java index 96bb90b..7f91b39 100644 --- a/src/main/java/top/r3944realms/lib39/Lib39.java +++ b/src/main/java/top/r3944realms/lib39/Lib39.java @@ -1,14 +1,32 @@ package top.r3944realms.lib39; +import net.minecraftforge.fml.ModList; import net.minecraftforge.fml.common.Mod; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import top.r3944realms.lib39.core.network.NetworkHandler; @Mod(Lib39.MOD_ID) public class Lib39 { public static final String MOD_ID = "lib39"; public static final Logger LOGGER = LoggerFactory.getLogger(Lib39.class); public Lib39() { + initialize(); + } + public static void initialize() { LOGGER.info("[Lib39] Initializing Lib39"); + NetworkHandler.register(); + LOGGER.info("[Lib39] Initialized Lib39"); + + } + public static class ModInfo { + public static final String VERSION; + static { + // 从 ModList 获取当前 ModContainer 的元数据 + VERSION = ModList.get() + .getModContainerById(MOD_ID) + .map(c -> c.getModInfo().getVersion().toString()) + .orElse("UNKNOWN"); + } } } diff --git a/src/main/java/top/r3944realms/lib39/api/event/SyncManagerRegisterEvent.java b/src/main/java/top/r3944realms/lib39/api/event/SyncManagerRegisterEvent.java new file mode 100644 index 0000000..e46627f --- /dev/null +++ b/src/main/java/top/r3944realms/lib39/api/event/SyncManagerRegisterEvent.java @@ -0,0 +1,82 @@ +package top.r3944realms.lib39.api.event; + +import net.minecraft.resources.ResourceLocation; +import net.minecraftforge.common.capabilities.Capability; +import net.minecraftforge.eventbus.api.Event; +import top.r3944realms.lib39.core.sync.ISyncData; +import top.r3944realms.lib39.core.sync.ISyncManager; +import top.r3944realms.lib39.core.sync.SyncData2Manager; + +@SuppressWarnings("unused") +public class SyncManagerRegisterEvent extends Event { + protected final SyncData2Manager syncs2Manager; + + public SyncManagerRegisterEvent(SyncData2Manager syncsManager) { + this.syncs2Manager = syncsManager; + } + + public SyncData2Manager getSyncsManager() { + return syncs2Manager; + } + + /** + * 类型安全的同步管理器注册 + */ + public > void registerSyncManager( + ResourceLocation id, + ISyncManager syncManager, + Capability capability + ) { + syncs2Manager.registerManager(id, syncManager, capability); + } + + + + public void unregisterSyncManager(ResourceLocation id) { + syncs2Manager.removeManager(id); + } + + /** + * 允许实体类 + */ + public final void addAllowEntityClass(ResourceLocation id, Class... entityClasses) { + syncs2Manager.allowEntityClass(id, entityClasses); + } + + /** + * 移除允许的实体类 + */ + public final void removeAllowEntityClass(ResourceLocation id, Class... entityClasses) { + syncs2Manager.disallowEntityClass(id, entityClasses); + } + + /** + * 绑定能力(用于分离注册的情况) + * @param id 必须先注册安全同步管理器,再绑定Cap,否则会抛出{@link IllegalStateException 未找到对应安全同步管理器} + */ + public > void bindCapability(ResourceLocation id, Capability capability) { + syncs2Manager.bindCapability(id, capability); + } + + /** + * 解绑能力 + */ + public void unbindCapability(ResourceLocation id) { + syncs2Manager.unbindCapability(id); + } + + /** + * 完整的类型安全注册 + */ + public > void registerComplete( + ResourceLocation id, + ISyncManager syncManager, + Capability capability, + Class... allowedEntityClasses + ) { + registerSyncManager(id, syncManager, capability); + if (allowedEntityClasses.length > 0) { + addAllowEntityClass(id, allowedEntityClasses); + } + } +} \ No newline at end of file diff --git a/src/main/java/top/r3944realms/lib39/core/event/ClientHandler.java b/src/main/java/top/r3944realms/lib39/core/event/ClientHandler.java new file mode 100644 index 0000000..da0dc08 --- /dev/null +++ b/src/main/java/top/r3944realms/lib39/core/event/ClientHandler.java @@ -0,0 +1,4 @@ +package top.r3944realms.lib39.core.event; + +public class ClientHandler { +} diff --git a/src/main/java/top/r3944realms/lib39/core/event/CommonHandler.java b/src/main/java/top/r3944realms/lib39/core/event/CommonHandler.java new file mode 100644 index 0000000..c0ef88c --- /dev/null +++ b/src/main/java/top/r3944realms/lib39/core/event/CommonHandler.java @@ -0,0 +1,64 @@ +package top.r3944realms.lib39.core.event; + +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.Entity; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.event.TickEvent; +import net.minecraftforge.event.entity.EntityJoinLevelEvent; +import net.minecraftforge.event.entity.EntityLeaveLevelEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; +import top.r3944realms.lib39.Lib39; +import top.r3944realms.lib39.api.event.SyncManagerRegisterEvent; +import top.r3944realms.lib39.core.sync.ISyncData; +import top.r3944realms.lib39.core.sync.SyncData2Manager; + +public class CommonHandler { + @Mod.EventBusSubscriber(modid = Lib39.MOD_ID, bus = Mod.EventBusSubscriber.Bus.FORGE) + public static class Game extends CommonHandler { + static volatile SyncData2Manager syncData2Manager; + public static SyncData2Manager getSyncData2Manager() { + return syncData2Manager; + } + + @SubscribeEvent + public static void onServerTick(TickEvent.ServerTickEvent event) { + if (event.phase == TickEvent.Phase.END) { + if (syncData2Manager == null) { + synchronized (Game.class){ + if (syncData2Manager == null) { + syncData2Manager = new SyncData2Manager(); + MinecraftForge.EVENT_BUS.post(new SyncManagerRegisterEvent(syncData2Manager)); + } + } + } + if (event.getServer().getTickCount() % 10 == 0) { + syncData2Manager.forEach(((resourceLocation, iSyncManager) -> iSyncManager.foreach(ISyncData::makeDirty))); + } + syncData2Manager.forEach(((resourceLocation, iSyncManager) -> iSyncManager.foreach(ISyncData::checkIfDirtyThenUpdate))); + } + } + @SubscribeEvent + public static void onEntityJoinWorld(EntityJoinLevelEvent event) { + Entity entity = event.getEntity(); + if (entity.level().isClientSide) return; + + for (ResourceLocation id : syncData2Manager.getRegisteredKeys()) { + if (syncData2Manager.isEntityClassAllowed(id, entity.getClass())) { + syncData2Manager.trackEntityForManager(entity, id); + } + } + } + @SubscribeEvent + public static void onEntityLeaveWorld(EntityLeaveLevelEvent event) { + Entity entity = event.getEntity(); + if (entity.level().isClientSide) return; + + for (ResourceLocation id : syncData2Manager.getRegisteredKeys()) { + if (syncData2Manager.isEntityClassAllowed(id, entity.getClass())) { + syncData2Manager.untrackEntityForManager(entity, id); + } + } + } + } +} diff --git a/src/main/java/top/r3944realms/lib39/core/event/ServerHandler.java b/src/main/java/top/r3944realms/lib39/core/event/ServerHandler.java new file mode 100644 index 0000000..e1a33bc --- /dev/null +++ b/src/main/java/top/r3944realms/lib39/core/event/ServerHandler.java @@ -0,0 +1,4 @@ +package top.r3944realms.lib39.core.event; + +public class ServerHandler { +} diff --git a/src/main/java/top/r3944realms/lib39/core/network/NetworkHandler.java b/src/main/java/top/r3944realms/lib39/core/network/NetworkHandler.java new file mode 100644 index 0000000..9af0765 --- /dev/null +++ b/src/main/java/top/r3944realms/lib39/core/network/NetworkHandler.java @@ -0,0 +1,34 @@ +package top.r3944realms.lib39.core.network; + +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; +import net.minecraftforge.network.NetworkDirection; +import net.minecraftforge.network.NetworkRegistry; +import net.minecraftforge.network.PacketDistributor; +import net.minecraftforge.network.simple.SimpleChannel; +import org.jetbrains.annotations.NotNull; +import top.r3944realms.lib39.Lib39; +import top.r3944realms.lib39.core.network.toClient.SyncNBTDataS2CPack; + +public class NetworkHandler { + private static int cid = 0; + public static final SimpleChannel INSTANCE = NetworkRegistry.newSimpleChannel( + new ResourceLocation(Lib39.MOD_ID, "main"), + () -> Lib39.ModInfo.VERSION, + Lib39.ModInfo.VERSION::equals, + Lib39.ModInfo.VERSION::equals + ); + public static void register() { + INSTANCE.messageBuilder(SyncNBTDataS2CPack.class, cid++, NetworkDirection.PLAY_TO_CLIENT) + .encoder(SyncNBTDataS2CPack::encode) + .decoder(SyncNBTDataS2CPack::decode) + .consumerNetworkThread(SyncNBTDataS2CPack::handle) + .add(); + } + public static void sendToPlayer(MSG message, ServerPlayer player){ + INSTANCE.send(PacketDistributor.PLAYER.with(() -> player), message); + } + public static void sendToPlayer(MSG message, T entity, @NotNull PacketDistributor packetDistributor){ + INSTANCE.send(packetDistributor.with(() -> entity), message); + } +} diff --git a/src/main/java/top/r3944realms/lib39/core/network/toClient/SyncNBTDataS2CPack.java b/src/main/java/top/r3944realms/lib39/core/network/toClient/SyncNBTDataS2CPack.java new file mode 100644 index 0000000..64ab01d --- /dev/null +++ b/src/main/java/top/r3944realms/lib39/core/network/toClient/SyncNBTDataS2CPack.java @@ -0,0 +1,60 @@ +package top.r3944realms.lib39.core.network.toClient; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.Entity; +import net.minecraftforge.common.capabilities.Capability; +import net.minecraftforge.network.NetworkEvent; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import top.r3944realms.lib39.Lib39; +import top.r3944realms.lib39.core.event.CommonHandler; +import top.r3944realms.lib39.core.sync.ISyncData; +import top.r3944realms.lib39.core.sync.NBTSyncData; + +import java.util.Optional; +import java.util.function.Supplier; + +public record SyncNBTDataS2CPack(int entityId, ResourceLocation id, CompoundTag data) { + + public SyncNBTDataS2CPack(int entityId, ResourceLocation id, @NotNull NBTSyncData data) { + this(entityId, data.id(), data.serializeNBT()); + } + + public static void encode(@NotNull SyncNBTDataS2CPack msg, @NotNull FriendlyByteBuf buffer) { + buffer.writeInt(msg.entityId); + buffer.writeResourceLocation(msg.id); + buffer.writeNbt(msg.data); + } + + @Contract("_ -> new") + public static @NotNull SyncNBTDataS2CPack decode(@NotNull FriendlyByteBuf buffer) { + return new SyncNBTDataS2CPack(buffer.readInt(), buffer.readResourceLocation(), buffer.readNbt()); + } + + public static void handle(SyncNBTDataS2CPack msg, @NotNull Supplier ctx) { + NetworkEvent.Context context = ctx.get(); + context.enqueueWork(() -> { + ClientLevel level = Minecraft.getInstance().level; + if (level != null) { + Entity entity = level.getEntity(msg.entityId); + if (entity != null) { + Optional>> capability = CommonHandler.Game.getSyncData2Manager().getCapability(msg.id); + capability.ifPresent(dataCapability -> entity.getCapability(dataCapability).ifPresent(cap -> { + if (cap instanceof NBTSyncData nbtCap){ + CompoundTag current = nbtCap.serializeNBT(); + if (!current.equals(msg.data)) { + nbtCap.deserializeNBT(msg.data); + } + } else Lib39.LOGGER.debug("Unhandled sync data: {}", msg.data); + })); + } + } + }); + context.setPacketHandled(true); + } + +} diff --git a/src/main/java/top/r3944realms/lib39/core/registry/LocaleRegistry.java b/src/main/java/top/r3944realms/lib39/core/registry/LocaleRegistry.java index bf53324..0695dec 100644 --- a/src/main/java/top/r3944realms/lib39/core/registry/LocaleRegistry.java +++ b/src/main/java/top/r3944realms/lib39/core/registry/LocaleRegistry.java @@ -1,5 +1,8 @@ package top.r3944realms.lib39.core.registry; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.UnmodifiableView; import top.r3944realms.lib39.datagen.value.ILocaleEntry; import top.r3944realms.lib39.datagen.value.McLocale; @@ -23,17 +26,17 @@ public class LocaleRegistry { } /** 通过 Minecraft 代码查找 */ - public static ILocaleEntry fromMcCode(String code) { + public static ILocaleEntry fromMcCode(@NotNull String code) { return REGISTRY.get(code.toLowerCase()); } /** 列出所有 */ - public static Collection allValues() { + public static @NotNull @UnmodifiableView Collection allValues() { return Collections.unmodifiableCollection(REGISTRY.values()); } /** 动态注册一个扩展 Locale */ - public static ILocaleEntry registerDynamic(String mcCode, Locale locale) { + public static ILocaleEntry registerDynamic(@NotNull String mcCode, Locale locale) { return REGISTRY.computeIfAbsent(mcCode.toLowerCase(), k -> new ExtendedLocale(mcCode.toLowerCase(), locale)); } @@ -43,8 +46,9 @@ public class LocaleRegistry { */ private record ExtendedLocale(String mcCode, Locale javaLocale) implements ILocaleEntry { + @Contract(pure = true) @Override - public String toString() { + public @NotNull String toString() { return "ExtendedLocale[" + mcCode + "]"; } } diff --git a/src/main/java/top/r3944realms/lib39/core/sync/ISyncData.java b/src/main/java/top/r3944realms/lib39/core/sync/ISyncData.java new file mode 100644 index 0000000..ad3b577 --- /dev/null +++ b/src/main/java/top/r3944realms/lib39/core/sync/ISyncData.java @@ -0,0 +1,14 @@ +package top.r3944realms.lib39.core.sync; + +import net.minecraft.resources.ResourceLocation; + +public interface ISyncData { + ResourceLocation id(); + boolean isDirty(); + void setDirty(boolean dirty); + default void makeDirty() { + setDirty(true); + } + void copyFrom(T src); + void checkIfDirtyThenUpdate(); +} diff --git a/src/main/java/top/r3944realms/lib39/core/sync/ISyncManager.java b/src/main/java/top/r3944realms/lib39/core/sync/ISyncManager.java new file mode 100644 index 0000000..f014e45 --- /dev/null +++ b/src/main/java/top/r3944realms/lib39/core/sync/ISyncManager.java @@ -0,0 +1,30 @@ +package top.r3944realms.lib39.core.sync; + +import org.jetbrains.annotations.NotNull; + +import java.util.Set; +import java.util.function.Consumer; + +public interface ISyncManager> { + Set getSyncSet(); + default void track(T instance) { + Set syncSet = checkAndGetSet(); + syncSet.add(instance); + } + default void untrack(T instance) { + Set syncSet = checkAndGetSet(); + syncSet.remove(instance); + } + default void foreach(Consumer consumer) { + Set syncSet = checkAndGetSet(); + syncSet.forEach(consumer); + } + + private @NotNull Set checkAndGetSet() throws IllegalArgumentException { + Set syncSet = getSyncSet(); + if (syncSet == null) { + throw new IllegalStateException("SyncSet is not initialized"); + } + return syncSet; + } +} diff --git a/src/main/java/top/r3944realms/lib39/core/sync/NBTSyncData.java b/src/main/java/top/r3944realms/lib39/core/sync/NBTSyncData.java new file mode 100644 index 0000000..05841e9 --- /dev/null +++ b/src/main/java/top/r3944realms/lib39/core/sync/NBTSyncData.java @@ -0,0 +1,36 @@ +package top.r3944realms.lib39.core.sync; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.resources.ResourceLocation; +import net.minecraftforge.common.util.INBTSerializable; +import org.jetbrains.annotations.NotNull; + +public abstract class NBTSyncData implements ISyncData, INBTSerializable { + protected boolean dirty; + protected final ResourceLocation id; + + protected NBTSyncData(ResourceLocation id) { + this.id = id; + } + + @Override + public ResourceLocation id() { + return id; + } + + @Override + public boolean isDirty() { + return dirty; + } + + @Override + public void setDirty(boolean dirty) { + this.dirty = dirty; + } + + @Override + public void copyFrom(@NotNull NBTSyncData src) { + this.dirty = src.isDirty(); + } + +} diff --git a/src/main/java/top/r3944realms/lib39/core/sync/SyncData2Manager.java b/src/main/java/top/r3944realms/lib39/core/sync/SyncData2Manager.java new file mode 100644 index 0000000..a3d5c13 --- /dev/null +++ b/src/main/java/top/r3944realms/lib39/core/sync/SyncData2Manager.java @@ -0,0 +1,276 @@ +package top.r3944realms.lib39.core.sync; + +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.Entity; +import net.minecraftforge.common.capabilities.Capability; +import org.jetbrains.annotations.NotNull; + +import java.util.*; +import java.util.function.BiConsumer; + +@SuppressWarnings("unused") +public class SyncData2Manager { + private final Map> typedEntries = Maps.newConcurrentMap(); + + private static class TypedSyncEntry> { + final ISyncManager manager; + final Capability capability; + final Set> allowedClasses; + + TypedSyncEntry(ISyncManager manager, Capability capability) { + this.manager = manager; + this.capability = capability; + this.allowedClasses = Sets.newConcurrentHashSet(); + } + } + + public > void registerManager( + ResourceLocation key, + ISyncManager manager, + Capability capability + ) { + Objects.requireNonNull(key, "ResourceLocation key cannot be null"); + Objects.requireNonNull(manager, "Sync manager cannot be null"); + Objects.requireNonNull(capability, "Capability cannot be null"); + + typedEntries.put(key, new TypedSyncEntry<>(manager, capability)); + } + + /** + * 向后兼容的注册方法(只注册管理器,不注册能力) + */ + @SuppressWarnings("unchecked") + public void registerManager(ResourceLocation key, ISyncManager> manager) { + Objects.requireNonNull(key, "ResourceLocation key cannot be null"); + Objects.requireNonNull(manager, "Sync manager cannot be null"); + + // 创建一个虚拟的 TypedSyncEntry,但 capability 为 null + // 注意:这种方法会限制类型安全的功能 + typedEntries.put(key, new TypedSyncEntry<>( + (ISyncManager>) manager, + null + )); + } + + @SuppressWarnings("unchecked") + public > Optional> getManager(ResourceLocation key) { + TypedSyncEntry entry = typedEntries.get(key); + return entry != null ? Optional.of((ISyncManager) entry.manager) : Optional.empty(); + } + + @SuppressWarnings("unchecked") + public > Optional> getCapability(ResourceLocation key) { + TypedSyncEntry entry = typedEntries.get(key); + if (entry != null && entry.capability != null) { + return Optional.of((Capability) entry.capability); + } + return Optional.empty(); + } + + public final void allowEntityClass(ResourceLocation key, Class... classes) { + Objects.requireNonNull(key, "ResourceLocation key cannot be null"); + Objects.requireNonNull(classes, "Classes array cannot be null"); + + if (classes.length == 0) { + return; + } + + TypedSyncEntry entry = typedEntries.get(key); + if (entry != null) { + entry.allowedClasses.addAll(Arrays.asList(classes)); + } + } + + /** + * 移除允许的实体类 + */ + public final void disallowEntityClass(ResourceLocation key, Class... classes) { + Objects.requireNonNull(key, "ResourceLocation key cannot be null"); + Objects.requireNonNull(classes, "Classes array cannot be null"); + + TypedSyncEntry entry = typedEntries.get(key); + if (entry != null && classes.length > 0) { + Arrays.asList(classes).forEach(entry.allowedClasses::remove); + + } + } + + /** + * 绑定能力(用于分离注册的情况) + */ + public > void bindCapability(ResourceLocation key, Capability capability) { + Objects.requireNonNull(key, "ResourceLocation key cannot be null"); + Objects.requireNonNull(capability, "Capability cannot be null"); + + TypedSyncEntry entry = typedEntries.get(key); + if (entry != null) { + // 更新现有条目的能力 + updateCapabilityInEntry(entry, capability); + } else throw new IllegalArgumentException("No manager found for " + key); + } + + /** + * 解绑能力 + */ + public void unbindCapability(ResourceLocation key) { + Objects.requireNonNull(key, "ResourceLocation key cannot be null"); + + TypedSyncEntry entry = typedEntries.get(key); + if (entry != null) { + // 将能力设置为null,但保留管理器和其他配置 + updateCapabilityInEntry(entry, null); + } + } + + /** + * 清除允许的实体类 + */ + public void clearAllowedEntityClasses(ResourceLocation key) { + Objects.requireNonNull(key, "ResourceLocation key cannot be null"); + + TypedSyncEntry entry = typedEntries.get(key); + if (entry != null) { + entry.allowedClasses.clear(); + } + } + + public boolean isEntityClassAllowed(ResourceLocation key, Class entityClass) { + Objects.requireNonNull(key, "ResourceLocation key cannot be null"); + Objects.requireNonNull(entityClass, "Entity class cannot be null"); + + TypedSyncEntry entry = typedEntries.get(key); + boolean isAllowed = false; + if (entry != null) { + for (Class allowedClass : entry.allowedClasses) { + if (entityClass.isAssignableFrom(allowedClass)) { + isAllowed = true; + break; + } + } + } + return entry != null && isAllowed ; + } + + // 类型安全的事件处理 + public void trackEntityForManager(Entity entity, ResourceLocation managerId) { + TypedSyncEntry entry = typedEntries.get(managerId); + if (entry != null) { + trackEntityWithTypedEntry(entity, entry); + } + } + + private > void trackEntityWithTypedEntry(Entity entity, @NotNull TypedSyncEntry entry) { + if (entry.capability != null) { + entity.getCapability(entry.capability) + .ifPresent(entry.manager::track); + } + } + // 类型安全的事件处理 - 取消跟踪实体 + public void untrackEntityForManager(Entity entity, ResourceLocation managerId) { + TypedSyncEntry entry = typedEntries.get(managerId); + if (entry != null) { + untrackEntityWithTypedEntry(entity, entry); + } + } + + private > void untrackEntityWithTypedEntry(Entity entity, @NotNull TypedSyncEntry entry) { + if (entry.capability != null) { + entity.getCapability(entry.capability) + .ifPresent(entry.manager::untrack); + } + } + + /** + * 从所有管理器中移除实体跟踪 + */ + public void untrackEntityFromAllManagers(Entity entity) { + for (ResourceLocation id : getRegisteredKeys()) { + if (isEntityClassAllowed(id, entity.getClass())) { + untrackEntityForManager(entity, id); + } + } + } + + /** + * 批量从管理器中移除实体跟踪 + */ + public void untrackEntitiesForManager(@NotNull Iterable entities, ResourceLocation managerId) { + for (Entity entity : entities) { + untrackEntityForManager(entity, managerId); + } + } + + /** + * 从所有管理器中批量移除实体跟踪 + */ + public void untrackEntitiesFromAllManagers(@NotNull Iterable entities) { + for (Entity entity : entities) { + untrackEntityFromAllManagers(entity); + } + } + + /** + * 强制清理管理器中的所有跟踪数据 + */ + public void clearAllTrackedData(ResourceLocation managerId) { + TypedSyncEntry entry = typedEntries.get(managerId); + if (entry != null) { + clearTrackedDataForEntry(entry); + } + } + + private > void clearTrackedDataForEntry(@NotNull TypedSyncEntry entry) { + // 获取当前跟踪的集合并清空 + Set syncSet = entry.manager.getSyncSet(); + if (syncSet != null) { + syncSet.clear(); + } + } + + /** + * 清理所有管理器的跟踪数据 + */ + public void clearAllTrackedData() { + for (ResourceLocation id : getRegisteredKeys()) { + clearAllTrackedData(id); + } + } + + // 辅助方法:更新条目的能力 + @SuppressWarnings("unchecked") + private > void updateCapabilityInEntry(TypedSyncEntry entry, Capability newCapability) { + TypedSyncEntry typedEntry = (TypedSyncEntry) entry; + // 由于 capability 是 final,需要替换整个 entry + // 在实际实现中,可能需要将 capability 改为非 final 或使用不同的设计 + // 这里假设重构了 TypedSyncEntry 使 capability 可变 + } + + + + public Set getRegisteredKeys() { + return Collections.unmodifiableSet(typedEntries.keySet()); + } + + public void forEach(BiConsumer> consumer) { + Objects.requireNonNull(consumer, "Consumer cannot be null"); + typedEntries.forEach((key, entry) -> consumer.accept(key, entry.manager)); + } + + public int getManagerCount() { + return typedEntries.size(); + } + + public void clearAll() { + typedEntries.clear(); + } + + /** + * 移除管理器(包括所有相关配置) + */ + public void removeManager(ResourceLocation key) { + Objects.requireNonNull(key, "ResourceLocation key cannot be null"); + typedEntries.remove(key); + } +} \ No newline at end of file diff --git a/src/main/java/top/r3944realms/lib39/datagen/provider/SimpleLanguageProvider.java b/src/main/java/top/r3944realms/lib39/datagen/provider/SimpleLanguageProvider.java index 08892af..d561032 100644 --- a/src/main/java/top/r3944realms/lib39/datagen/provider/SimpleLanguageProvider.java +++ b/src/main/java/top/r3944realms/lib39/datagen/provider/SimpleLanguageProvider.java @@ -2,6 +2,7 @@ package top.r3944realms.lib39.datagen.provider; import net.minecraft.data.PackOutput; import net.minecraftforge.common.data.LanguageProvider; +import org.jetbrains.annotations.NotNull; import top.r3944realms.lib39.datagen.value.ILangKeyValue; import top.r3944realms.lib39.datagen.value.McLocale; @@ -10,12 +11,13 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +@SuppressWarnings("unused") public class SimpleLanguageProvider extends LanguageProvider { private final McLocale language; private final ILangKeyValue langKeyValue; private final Map lanKeyMap; private static final List objects = new ArrayList<>(); - public SimpleLanguageProvider(PackOutput output, String modId, McLocale Lan, ILangKeyValue langKeyValue) { + public SimpleLanguageProvider(PackOutput output, String modId, @NotNull McLocale Lan, ILangKeyValue langKeyValue) { super(output, modId, Lan.mcCode()); this.language = Lan; this.langKeyValue = langKeyValue; @@ -28,7 +30,7 @@ public class SimpleLanguageProvider extends LanguageProvider { } } private void addLang(String Key, String value) { - if(!objects.contains(Key)) objects.add(Key); + if (!objects.contains(Key)) objects.add(Key); lanKeyMap.put(Key, value); } diff --git a/src/main/java/top/r3944realms/lib39/datagen/value/ILangKeyValue.java b/src/main/java/top/r3944realms/lib39/datagen/value/ILangKeyValue.java index cec70f5..5621f18 100644 --- a/src/main/java/top/r3944realms/lib39/datagen/value/ILangKeyValue.java +++ b/src/main/java/top/r3944realms/lib39/datagen/value/ILangKeyValue.java @@ -1,9 +1,11 @@ package top.r3944realms.lib39.datagen.value; +import org.jetbrains.annotations.NotNull; + import java.util.List; public interface ILangKeyValue { - static String getLang(McLocale locale, ILangKeyValue key) { + static String getLang(McLocale locale, @NotNull ILangKeyValue key) { return key.getLang(locale); } String getLang(McLocale locale); diff --git a/src/main/java/top/r3944realms/lib39/datagen/value/LangKeyValue.java b/src/main/java/top/r3944realms/lib39/datagen/value/LangKeyValue.java index c84dd17..46dabc8 100644 --- a/src/main/java/top/r3944realms/lib39/datagen/value/LangKeyValue.java +++ b/src/main/java/top/r3944realms/lib39/datagen/value/LangKeyValue.java @@ -1,20 +1,25 @@ package top.r3944realms.lib39.datagen.value; +import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; +import java.util.List; import java.util.function.Supplier; -public abstract class LangKeyValue implements ILangKeyValue { - private final Supplier supplier; - private String key; - private final String US_EN; - private final String SIM_CN; - private final String TRA_CN; - private final String LZH; - private final Boolean Default; - private final ModPartEnum MPE; - LangKeyValue(Supplier Supplier, ModPartEnum MPE, String US_EN, String SIM_CN, String TRA_CN, String LZH, Boolean isDefault) { - this.supplier = Supplier; +@SuppressWarnings("unused") +public class LangKeyValue implements ILangKeyValue { + protected final Supplier supplier; + protected String key; + protected final String US_EN; + protected final String SIM_CN; + protected final String TRA_CN; + protected final String LZH; + protected final Boolean Default; + protected final ModPartEnum MPE; + private LangKeyValue(Supplier supplier, String key, ModPartEnum MPE, + String US_EN, String SIM_CN, String TRA_CN, String LZH, Boolean isDefault) { + this.supplier = supplier; + this.key = key; this.MPE = MPE; this.US_EN = US_EN; this.SIM_CN = SIM_CN; @@ -22,34 +27,43 @@ public abstract class LangKeyValue implements ILangKeyValue { this.LZH = LZH; this.Default = isDefault; } - LangKeyValue(@NotNull String ResourceKey, ModPartEnum MPE, String US_EN, String SIM_CN, String TRA_CN, String LZH, Boolean isDefault) { - this.supplier = null; - this.key = ResourceKey; - this.MPE = MPE; - this.US_EN = US_EN; - this.SIM_CN = SIM_CN; - this.TRA_CN = TRA_CN; - this.LZH = LZH; - this.Default = isDefault; + + @Contract(value = "_, _, _, _, _ -> new", pure = true) + public static @NotNull LangKeyValue ofSupplier(Supplier supplier, ModPartEnum MPE, + String US_EN, String SIM_CN, String TRA_CN) { + return new LangKeyValue(supplier, null, MPE, US_EN, SIM_CN, TRA_CN, null, false); } - LangKeyValue(Supplier Supplier, ModPartEnum MPE, String US_EN, String SIM_CN, String TRA_CN, String LZH) { - this(Supplier, MPE, US_EN, SIM_CN, TRA_CN, LZH, false); + + @Contract(value = "_, _, _, _, _, _ -> new", pure = true) + public static @NotNull LangKeyValue ofSupplier(Supplier supplier, ModPartEnum MPE, + String US_EN, String SIM_CN, String TRA_CN, String LZH) { + return new LangKeyValue(supplier, null, MPE, US_EN, SIM_CN, TRA_CN, LZH, false); } - LangKeyValue(Supplier Supplier, ModPartEnum MPE, String US_EN, String SIM_CN, String TRA_CN, Boolean isDefault) { - this(Supplier, MPE, US_EN, SIM_CN, TRA_CN, null, isDefault); + + @Contract(value = "_, _, _, _, _, _ -> new", pure = true) + public static @NotNull LangKeyValue ofSupplier(Supplier supplier, ModPartEnum MPE, + String US_EN, String SIM_CN, String TRA_CN, Boolean isDefault) { + return new LangKeyValue(supplier, null, MPE, US_EN, SIM_CN, TRA_CN, null, isDefault); } - LangKeyValue(@NotNull String ResourceKey, ModPartEnum MPE, String US_EN, String SIM_CN, String TRA_CN, Boolean isDefault) { - this(ResourceKey, MPE, US_EN, SIM_CN, TRA_CN, null, isDefault); + + @Contract(value = "_, _, _, _, _ -> new", pure = true) + public static @NotNull LangKeyValue ofKey(String key, ModPartEnum MPE, + String US_EN, String SIM_CN, String TRA_CN) { + return new LangKeyValue(null, key, MPE, US_EN, SIM_CN, TRA_CN, null, false); } - LangKeyValue(@NotNull String ResourceKey, ModPartEnum MPE, String US_EN, String SIM_CN, String TRA_CN, String LZH) { - this(ResourceKey, MPE, US_EN, SIM_CN, TRA_CN, LZH, false); + + @Contract(value = "_, _, _, _, _, _ -> new", pure = true) + public static @NotNull LangKeyValue ofKey(String key, ModPartEnum MPE, + String US_EN, String SIM_CN, String TRA_CN, String LZH) { + return new LangKeyValue(null, key, MPE, US_EN, SIM_CN, TRA_CN, LZH, false); } - LangKeyValue(Supplier Supplier, ModPartEnum MPE, String US_EN, String SIM_CN, String TRA_CN) { - this(Supplier, MPE, US_EN, SIM_CN, TRA_CN, null, false); - } - LangKeyValue(@NotNull String ResourceKey, ModPartEnum MPE, String US_EN, String SIM_CN, String TRA_CN) { - this(ResourceKey, MPE, US_EN, SIM_CN, TRA_CN, null, false); + + @Contract(value = "_, _, _, _, _, _ -> new", pure = true) + public static @NotNull LangKeyValue ofKey(String key, ModPartEnum MPE, + String US_EN, String SIM_CN, String TRA_CN, Boolean isDefault) { + return new LangKeyValue(null, key, MPE, US_EN, SIM_CN, TRA_CN, null, isDefault); } + public String getKey() { return key; } @@ -62,4 +76,9 @@ public abstract class LangKeyValue implements ILangKeyValue { case LZH -> LZH; }; } + + @Override + public List getValues() { + return List.of(); + } } diff --git a/src/main/java/top/r3944realms/lib39/datagen/value/ModPartEnum.java b/src/main/java/top/r3944realms/lib39/datagen/value/ModPartEnum.java index 61aeaeb..0ddc759 100644 --- a/src/main/java/top/r3944realms/lib39/datagen/value/ModPartEnum.java +++ b/src/main/java/top/r3944realms/lib39/datagen/value/ModPartEnum.java @@ -1,5 +1,8 @@ package top.r3944realms.lib39.datagen.value; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + /** * 模组各部分的类型枚举,用于数据生成与分类。 */ @@ -61,7 +64,8 @@ public enum ModPartEnum { * 根据枚举类型生成标准化 key 前缀 * 例如 ITEM -> "item.", BLOCK -> "block." */ - public String getKeyPrefix() { + @Contract(pure = true) + public @NotNull String getKeyPrefix() { return switch (this) { case ITEM -> "item."; case BLOCK -> "block."; @@ -88,7 +92,8 @@ public enum ModPartEnum { * 根据枚举类型和具体名称生成完整 key * 例如 ITEM + "example_item" -> "item.example_item" */ - public String getFullKey(String name) { + @Contract(pure = true) + public @NotNull String getFullKey(String name) { return getKeyPrefix() + name; } } diff --git a/src/main/java/top/r3944realms/lib39/utils/command/CommandAliasHelper.java b/src/main/java/top/r3944realms/lib39/util/command/CommandAliasHelper.java similarity index 98% rename from src/main/java/top/r3944realms/lib39/utils/command/CommandAliasHelper.java rename to src/main/java/top/r3944realms/lib39/util/command/CommandAliasHelper.java index f3e7323..4c05005 100644 --- a/src/main/java/top/r3944realms/lib39/utils/command/CommandAliasHelper.java +++ b/src/main/java/top/r3944realms/lib39/util/command/CommandAliasHelper.java @@ -1,4 +1,4 @@ -package top.r3944realms.lib39.utils.command; +package top.r3944realms.lib39.util.command; import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.builder.ArgumentBuilder; @@ -12,6 +12,7 @@ import net.minecraft.commands.Commands; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +@SuppressWarnings("unused") public class CommandAliasHelper { /** diff --git a/src/main/java/top/r3944realms/lib39/util/lang/Pair.java b/src/main/java/top/r3944realms/lib39/util/lang/Pair.java new file mode 100644 index 0000000..53116d3 --- /dev/null +++ b/src/main/java/top/r3944realms/lib39/util/lang/Pair.java @@ -0,0 +1,45 @@ +package top.r3944realms.lib39.util.lang; + +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +public final class Pair { + public F first; + public S second; + + private Pair(F first, S second) { + this.first = first; + this.second = second; + } + + @Contract("null, _ -> fail; !null, null -> fail; !null, !null -> new") + public static @NotNull Pair of(F first, S second) { + if (first == null || second == null) { + throw new IllegalArgumentException("Pair.of requires non-null argument"); + } + return new Pair<>(first, second); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof Pair rhs)) { + return false; + } + return first.equals(rhs.first) && second.equals(rhs.second); + } + @Override + public int hashCode() { + return first.hashCode() * 37 + second.hashCode(); + } + + @Override + public String toString() { + return "Pair{" + + "first=" + first + + ", second=" + second + + '}'; + } +} diff --git a/src/main/java/top/r3944realms/lib39/util/lang/Triple.java b/src/main/java/top/r3944realms/lib39/util/lang/Triple.java new file mode 100644 index 0000000..8843589 --- /dev/null +++ b/src/main/java/top/r3944realms/lib39/util/lang/Triple.java @@ -0,0 +1,48 @@ +package top.r3944realms.lib39.util.lang; + +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; + +@SuppressWarnings("unused") +public final class Triple { + public A first; + public B second; + public C third; + + private Triple(A first, B second, C third) { + this.first = first; + this.second = second; + this.third = third; + } + + @Contract(value = "_, _, _ -> new", pure = true) + public static @NotNull Triple of(A first, B second, C third) { + return new Triple<>(first, second, third); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Triple triple = (Triple) o; + return Objects.equals(first, triple.first) && + Objects.equals(second, triple.second) && + Objects.equals(third, triple.third); + } + + @Override + public int hashCode() { + return Objects.hash(first, second, third); + } + + @Override + public String toString() { + return "Triple{" + + "first=" + first + + ", second=" + second + + ", third=" + third + + '}'; + } +} \ No newline at end of file diff --git a/src/main/java/top/r3944realms/lib39/util/lang/Tuple.java b/src/main/java/top/r3944realms/lib39/util/lang/Tuple.java new file mode 100644 index 0000000..2fd3bc3 --- /dev/null +++ b/src/main/java/top/r3944realms/lib39/util/lang/Tuple.java @@ -0,0 +1,85 @@ +package top.r3944realms.lib39.util.lang; + +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; + +@SuppressWarnings("unused") +public final class Tuple { + private final List elements; + + private Tuple(Object... elements) { + this.elements = List.of(elements); + } + + @Contract(value = "_ -> new", pure = true) + public static @NotNull Tuple of(Object... elements) { + return new Tuple(elements); + } + + public int size() { + return elements.size(); + } + + @SuppressWarnings("unchecked") + public T get(int index) { + if (index < 0 || index >= elements.size()) { + throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + elements.size()); + } + return (T) elements.get(index); + } + + public T first() { + return get(0); + } + + public T second() { + return get(1); + } + + public T third() { + return get(2); + } + + public T last() { + return get(elements.size() - 1); + } + + public List toList() { + return new ArrayList<>(elements); + } + + public Object[] toArray() { + return elements.toArray(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Tuple tuple = (Tuple) o; + return Objects.equals(elements, tuple.elements); + } + + @Override + public int hashCode() { + return Objects.hash(elements); + } + + @Override + public String toString() { + return "Tuple" + elements; + } + + public Iterator iterator() { + return elements.iterator(); + } + + public java.util.stream.Stream stream() { + return elements.stream(); + } +} diff --git a/src/main/java/top/r3944realms/lib39/util/nbt/NBTReader.java b/src/main/java/top/r3944realms/lib39/util/nbt/NBTReader.java new file mode 100644 index 0000000..f3a5ec0 --- /dev/null +++ b/src/main/java/top/r3944realms/lib39/util/nbt/NBTReader.java @@ -0,0 +1,38 @@ +/* + * Super Lead rope mod + * Copyright (C) 2025 R3944Realms + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package top.r3944realms.lib39.util.nbt; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.phys.Vec3; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +@SuppressWarnings("unused") +public class NBTReader { + private NBTReader() {} + @Contract("_ -> new") + public static @NotNull Vec3 readVec3(@NotNull CompoundTag nbt) { + if (nbt.contains("X") && nbt.contains("Y") && nbt.contains("Z")) { + return new Vec3( + nbt.getDouble("X"), + nbt.getDouble("Y"), + nbt.getDouble("Z") + ); + } else { + throw new IllegalArgumentException("NBT is missing X, Y, or Z value for Vec3"); + } + } +} diff --git a/src/main/java/top/r3944realms/lib39/util/nbt/NBTWriter.java b/src/main/java/top/r3944realms/lib39/util/nbt/NBTWriter.java new file mode 100644 index 0000000..ba9752c --- /dev/null +++ b/src/main/java/top/r3944realms/lib39/util/nbt/NBTWriter.java @@ -0,0 +1,36 @@ +/* + * Super Lead rope mod + * Copyright (C) 2025 R3944Realms + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package top.r3944realms.lib39.util.nbt; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.phys.Vec3; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +@SuppressWarnings("unused") +public class NBTWriter { + private NBTWriter() {} + @Contract("null -> fail") + public static @NotNull CompoundTag writeVec3(Vec3 vec) { + CompoundTag nbt = new CompoundTag(); + if (vec == null) throw new IllegalArgumentException("Vec3 cannot be null"); + + nbt.putDouble("X", vec.x); + nbt.putDouble("Y", vec.y); + nbt.putDouble("Z", vec.z); + return nbt; + } +} diff --git a/src/main/java/top/r3944realms/lib39/util/riding/RidingApplier.java b/src/main/java/top/r3944realms/lib39/util/riding/RidingApplier.java new file mode 100644 index 0000000..84f6822 --- /dev/null +++ b/src/main/java/top/r3944realms/lib39/util/riding/RidingApplier.java @@ -0,0 +1,118 @@ +/* + * Super Lead rope mod + * Copyright (C) 2025 R3944Realms + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package top.r3944realms.lib39.util.riding; + +import net.minecraft.world.entity.Entity; +import top.r3944realms.lib39.Lib39; + +import java.util.Collection; +import java.util.LinkedList; +import java.util.Queue; +import java.util.UUID; +import java.util.function.Function; + +@SuppressWarnings("unused") +public class RidingApplier { + /** + * 应用骑乘关系(在服务器端调用) + * @param relationship 骑乘关系 + * @param entityProvider 实体提供器(根据UUID获取实体) + * @return 应用成功的实体数量 + */ + public static int applyRidingRelationship(RidingRelationship relationship, + Function entityProvider) { + if (relationship == null || entityProvider == null) { + return 0; + } + + int appliedCount = 0; + Queue queue = new LinkedList<>(); + queue.offer(relationship); + + while (!queue.isEmpty()) { + RidingRelationship current = queue.poll(); + UUID entityId = current.getEntityId(); + UUID vehicleId = current.getVehicleId(); + + // 获取实体和载具 + Entity entity = entityProvider.apply(entityId); + Entity vehicle = vehicleId != null ? entityProvider.apply(vehicleId) : null; + + if (entity == null) continue; + + // ---------- 白名单保护 ---------- + + if (vehicle != null) { + // 将当前节点的乘客挂回上层载具 + for (RidingRelationship child : current.getPassengers()) { + child.setVehicleId(vehicle.getUUID()); + queue.offer(child); + } + } + + appliedCount++; + + // 如果实体已经有载具,先下车 + if (entity.getVehicle() != null) { + entity.stopRiding(); + } + + // 如果有指定的载具,尝试上车 + if (vehicle != null) { + if (RidingValidator.wouldCreateCycle(entity, vehicle)) { + throw new RidingCycleException(entityId, vehicleId); + } + boolean success = entity.startRiding(vehicle, true); + if (!success) { + Lib39.LOGGER.error("Failed to mount entity {} to vehicle {}", entityId, vehicleId); + } + } + + // 处理子乘客 + queue.addAll(current.getPassengers()); + } + + return appliedCount; + } + /** + * 批量应用骑乘关系(适用于世界加载时) + */ + public static void applyRidingRelationships(Collection relationships, + Function entityProvider) { + if (relationships == null || relationships.isEmpty()) { + return; + } + + for (RidingRelationship relationship : relationships) { + try { + applyRidingRelationship(relationship, entityProvider); + } catch (RidingCycleException e) { + // 记录循环引用错误,但继续处理其他关系 + Lib39.LOGGER.warn("Cyclic riding reference detected and skipped: {}", e.getMessage()); + } + } + } + + /** + * 从JSON字符串应用骑乘关系 + */ + public static int applyRidingRelationshipFromJson(String json, + Function entityProvider) { + RidingRelationship relationship = RidingSerializer.deserialize(json); + return applyRidingRelationship(relationship, entityProvider); + } + +} diff --git a/src/main/java/top/r3944realms/lib39/util/riding/RidingCycleException.java b/src/main/java/top/r3944realms/lib39/util/riding/RidingCycleException.java new file mode 100644 index 0000000..87e7d43 --- /dev/null +++ b/src/main/java/top/r3944realms/lib39/util/riding/RidingCycleException.java @@ -0,0 +1,41 @@ +/* + * Super Lead rope mod + * Copyright (C) 2025 R3944Realms + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package top.r3944realms.lib39.util.riding; + +import java.util.UUID; + +@SuppressWarnings("unused") +public class RidingCycleException extends IllegalStateException { + private final UUID entityId; + private final UUID vehicleId; + + public RidingCycleException(UUID entityId, UUID vehicleId) { + super(String.format("Cyclic riding reference detected. " + + "Entity %s cannot be added as passenger to vehicle %s " + + "as it would create a circular dependency.", + entityId, vehicleId)); + this.entityId = entityId; + this.vehicleId = vehicleId; + } + + public UUID getEntityId() { + return entityId; + } + + public UUID getVehicleId() { + return vehicleId; + } +} diff --git a/src/main/java/top/r3944realms/lib39/util/riding/RidingDismounts.java b/src/main/java/top/r3944realms/lib39/util/riding/RidingDismounts.java new file mode 100644 index 0000000..1fe0995 --- /dev/null +++ b/src/main/java/top/r3944realms/lib39/util/riding/RidingDismounts.java @@ -0,0 +1,208 @@ +/* + * Super Lead rope mod + * Copyright (C) 2025 R3944Realms + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package top.r3944realms.lib39.util.riding; + +import net.minecraft.world.entity.Entity; + +import java.util.*; +import java.util.function.Function; + +@SuppressWarnings("unused") +public class RidingDismounts { + /** + * 解除单个实体的骑乘关系 + */ + public static void dismountEntity(Entity entity) { + if (entity == null) { + return; + } + + // 如果实体正在骑乘,先下车 + if (entity.isPassenger()) { + entity.stopRiding(); + } + + // 让所有乘客下车 + dismountAllPassengers(entity); + } + + /** + * 解除实体及其所有乘客的骑乘关系(非递归) + */ + public static void dismountAllPassengers(Entity entity) { + if (entity == null) { + return; + } + + // 使用队列进行广度优先遍历 + Queue queue = new LinkedList<>(); + queue.offer(entity); + + while (!queue.isEmpty()) { + Entity current = queue.poll(); + + // 让当前实体的所有乘客下车 + List passengers = new ArrayList<>(current.getPassengers()); + for (Entity passenger : passengers) { + passenger.stopRiding(); + queue.offer(passenger); + } + } + } + + /** + * 解除根实体的骑乘关系(包括从载具下车) + */ + public static void dismountRootEntity(Entity entity) { + if (entity == null) { + return; + } + + // 找到根载具 + Entity rootVehicle = RidingFinder.findRootVehicle(entity); + if (rootVehicle != null) { + // 让根载具的所有乘客下车 + dismountAllPassengers(rootVehicle); + + // 根载具本身也下车(如果有载具的话) + if (rootVehicle.isPassenger()) { + rootVehicle.stopRiding(); + } + } + } + + /** + * 安全解除骑乘关系(带超时保护) + */ + public static boolean safeDismountAll(Entity entity, int maxIterations) { + if (entity == null) { + return true; + } + + int iteration = 0; + Queue queue = new LinkedList<>(); + queue.offer(entity); + + while (!queue.isEmpty() && iteration < maxIterations) { + Entity current = queue.poll(); + iteration++; + + // 让当前实体下车(如果是乘客) + if (current.isPassenger()) { + current.stopRiding(); + } + + // 处理当前实体的乘客 + List passengers = new ArrayList<>(current.getPassengers()); + for (Entity passenger : passengers) { + passenger.stopRiding(); + queue.offer(passenger); + } + } + + return queue.isEmpty(); // 如果队列为空表示全部解除成功 + } + + /** + * 批量解除多个实体的骑乘关系 + */ + public static void dismountEntities(Collection entities) { + if (entities == null || entities.isEmpty()) { + return; + } + + Set processed = new HashSet<>(); + Queue queue = new LinkedList<>(entities); + + while (!queue.isEmpty()) { + Entity current = queue.poll(); + + if (current != null && !processed.contains(current)) { + processed.add(current); + + // 让当前实体下车 + if (current.isPassenger()) { + current.stopRiding(); + } + + // 处理乘客 + List passengers = new ArrayList<>(current.getPassengers()); + for (Entity passenger : passengers) { + if (!processed.contains(passenger)) { + queue.offer(passenger); + } + } + } + } + } + + /** + * 根据骑乘关系数据结构解除骑乘 + */ + public static void dismountByRelationship(RidingRelationship relationship, + Function entityProvider) { + if (relationship == null || entityProvider == null) { + return; + } + + // 使用栈进行深度优先遍历解除 + Deque stack = new ArrayDeque<>(); + stack.push(relationship); + + while (!stack.isEmpty()) { + RidingRelationship current = stack.pop(); + + // 解除当前实体的骑乘 + Entity entity = entityProvider.apply(current.getEntityId()); + if (entity != null && entity.isPassenger()) { + entity.stopRiding(); + } + + // 将子乘客加入栈中(后进先出,深度优先) + List passengers = current.getPassengers(); + for (int i = passengers.size() - 1; i >= 0; i--) { + stack.push(passengers.get(i)); + } + } + } + + /** + * 立即解除所有骑乘关系(强制方式) + */ + public static void forceDismountAll(Entity entity) { + if (entity == null) { + return; + } + + // 先让自己下车 + if (entity.isPassenger()) { + entity.stopRiding(); + } + + // 使用广度优先让所有乘客下车 + List allPassengers = RidingFinder.getAllPassengers(entity, false); + for (Entity passenger : allPassengers) { + if (passenger.isPassenger()) { + passenger.stopRiding(); + } + } + + // 再次检查并清理(确保完全解除) + if (!entity.getPassengers().isEmpty()) { + entity.ejectPassengers(); + } + } +} diff --git a/src/main/java/top/r3944realms/lib39/util/riding/RidingFinder.java b/src/main/java/top/r3944realms/lib39/util/riding/RidingFinder.java new file mode 100644 index 0000000..6ff5201 --- /dev/null +++ b/src/main/java/top/r3944realms/lib39/util/riding/RidingFinder.java @@ -0,0 +1,102 @@ +/* + * Super Lead rope mod + * Copyright (C) 2025 R3944Realms + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package top.r3944realms.lib39.util.riding; + +import net.minecraft.world.entity.Entity; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; +import java.util.function.Function; + +@SuppressWarnings("unused") +public class RidingFinder { + /** + * 从JSON字符串应用骑乘关系 + */ + public static @NotNull List getEntityFromRidingShip(RidingRelationship ship, + Function entityProvider) { + List ret = new ArrayList<>(); + Queue queue = new LinkedList<>(); + queue.offer(ship); + while (!queue.isEmpty()) { + RidingRelationship poll = queue.poll(); + ret.add(entityProvider.apply(ship.getEntityId())); + List passengers = poll.getPassengers(); + if (!passengers.isEmpty()) { + queue.addAll(passengers); + } + } + return ret; + } + /** + * 查找根载具 + */ + @Nullable + public static Entity findRootVehicle(@Nullable Entity entity) { + if (entity == null) { + return null; + } + + Entity current = entity; + while (current.getVehicle() != null) { + current = current.getVehicle(); + // 安全保护,防止意外循环 + if (current == entity) { + break; + } + } + return current; + } + + /** + * 获取所有乘客(包括嵌套乘客) + */ + public static List getAllPassengers(@Nullable Entity entity) { + return getAllPassengers(entity, true); + } + + /** + * 获取所有乘客(包括嵌套乘客) + */ + public static List getAllPassengers(@Nullable Entity entity, boolean findRoot) { + if (entity == null) { + return Collections.emptyList(); + } + + Entity rootEntity = findRoot ? findRootVehicle(entity) : entity; + if (rootEntity == null) { + return Collections.emptyList(); + } + + List result = new ArrayList<>(); + Queue queue = new LinkedList<>(); + queue.offer(rootEntity); + + while (!queue.isEmpty()) { + Entity current = queue.poll(); + result.add(current); // 把当前实体加入列表 + + List passengers = current.getPassengers(); + if (!passengers.isEmpty()) { + queue.addAll(passengers); + } + } + + return Collections.unmodifiableList(result); + } + +} diff --git a/src/main/java/top/r3944realms/lib39/util/riding/RidingRelationship.java b/src/main/java/top/r3944realms/lib39/util/riding/RidingRelationship.java new file mode 100644 index 0000000..94b2f66 --- /dev/null +++ b/src/main/java/top/r3944realms/lib39/util/riding/RidingRelationship.java @@ -0,0 +1,101 @@ +/* + * Super Lead rope mod + * Copyright (C) 2025 R3944Realms + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package top.r3944realms.lib39.util.riding; + +import java.util.*; + +/** + * 骑乘关系数据结构 + */ +@SuppressWarnings("unused") +public class RidingRelationship { + private UUID entityId; + private UUID vehicleId; + private List passengers; + + public RidingRelationship() { + this.passengers = new ArrayList<>(); + } + + public RidingRelationship(List passengers, UUID vehicleId, UUID entityId) { + this.passengers = passengers != null ? passengers : new ArrayList<>(); + this.vehicleId = vehicleId; + this.entityId = entityId; + } + + public UUID getEntityId() { + return entityId; + } + + public void setEntityId(UUID entityId) { + this.entityId = entityId; + } + + public List getPassengers() { + return Collections.unmodifiableList(passengers); + } + + public void setPassengers(List passengers) { + this.passengers = passengers != null ? passengers : new ArrayList<>(); + } + + public void addPassenger(RidingRelationship passenger) { + this.passengers.add(passenger); + } + + public UUID getVehicleId() { + return vehicleId; + } + + public void setVehicleId(UUID vehicleId) { + this.vehicleId = vehicleId; + } + + /** + * 获取所有嵌套乘客的数量 + */ + public int getTotalPassengerCount() { + int count = passengers.size(); + for (RidingRelationship passenger : passengers) { + count += passenger.getTotalPassengerCount(); + } + return count; + } + + /** + * 检查是否包含特定实体 + */ + public boolean containsEntity(UUID entityId) { + if (Objects.equals(this.entityId, entityId)) { + return true; + } + for (RidingRelationship passenger : passengers) { + if (passenger.containsEntity(entityId)) { + return true; + } + } + return false; + } + + @Override + public String toString() { + return "RidingRelationship{" + + "entityId=" + entityId + + ", vehicleId=" + vehicleId + + ", passengers=" + passengers.size() + + '}'; + } +} \ No newline at end of file diff --git a/src/main/java/top/r3944realms/lib39/util/riding/RidingSaver.java b/src/main/java/top/r3944realms/lib39/util/riding/RidingSaver.java new file mode 100644 index 0000000..3002084 --- /dev/null +++ b/src/main/java/top/r3944realms/lib39/util/riding/RidingSaver.java @@ -0,0 +1,114 @@ +/* + * Super Lead rope mod + * Copyright (C) 2025 R3944Realms + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package top.r3944realms.lib39.util.riding; + +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityType; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import top.r3944realms.lib39.util.lang.Pair; + +import java.util.*; +import java.util.function.Function; + +@SuppressWarnings("unused") +public class RidingSaver { + /** + * 保存骑乘关系 + */ + @Contract("null -> new") + public static @NotNull RidingRelationship save(@Nullable Entity entity) { + return save(entity, true); + } + + /** + * 保存骑乘关系 + */ + @Contract("null, _ -> new") + public static @NotNull RidingRelationship save(@Nullable Entity entity, boolean findRoot) { + if (entity == null) { + return new RidingRelationship(Collections.emptyList(), null, null); + } + + Entity rootEntity = findRoot ? RidingFinder.findRootVehicle(entity) : entity; + if (rootEntity == null) { + return new RidingRelationship(Collections.emptyList(), null, null); + } + + RidingRelationship rootRelationship = new RidingRelationship(); + rootRelationship.setEntityId(rootEntity.getUUID()); + rootRelationship.setVehicleId(null); + rootRelationship.setPassengers(new ArrayList<>()); + + Queue> queue = new LinkedList<>(); + queue.offer(Pair.of(rootEntity, rootRelationship)); + + Set processedEntities = new HashSet<>(); + processedEntities.add(rootEntity.getUUID()); + + while (!queue.isEmpty()) { + Pair current = queue.poll(); + Entity currentEntity = current.first; + RidingRelationship currentRelation = current.second; + + List passengers = currentEntity.getPassengers(); + + if (!passengers.isEmpty()) { + for (Entity passenger : passengers) { + UUID passengerId = passenger.getUUID(); + + if (!processedEntities.contains(passengerId)) { + processedEntities.add(passengerId); + + // ⬇ 构建子关系 + RidingRelationship passengerRelation = new RidingRelationship(); + passengerRelation.setEntityId(passengerId); + passengerRelation.setVehicleId(currentEntity.getUUID()); + passengerRelation.setPassengers(new ArrayList<>()); + + currentRelation.addPassenger(passengerRelation); + queue.offer(Pair.of(passenger, passengerRelation)); + } else { + throw new RidingCycleException( + passengerId, + currentEntity.getUUID() + ); + } + } + } + } + + return rootRelationship; + } + + // 传入一个实体提供器 Function,通常在服务器侧就是 level::getEntity + private static Function entityProvider; + + public static void setEntityProvider(Function provider) { + entityProvider = provider; + } + + /** + * 根据UUID获取EntityType + */ + private static @Nullable EntityType getEntityType(UUID entityId) { + if (entityProvider == null) return null; + Entity entity = entityProvider.apply(entityId); + if (entity == null) return null; + return entity.getType(); + } +} diff --git a/src/main/java/top/r3944realms/lib39/util/riding/RidingSerializer.java b/src/main/java/top/r3944realms/lib39/util/riding/RidingSerializer.java new file mode 100644 index 0000000..a50505d --- /dev/null +++ b/src/main/java/top/r3944realms/lib39/util/riding/RidingSerializer.java @@ -0,0 +1,36 @@ +/* + * Super Lead rope mod + * Copyright (C) 2025 R3944Realms + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package top.r3944realms.lib39.util.riding; + +import com.google.gson.Gson; + +@SuppressWarnings("unused") +public class RidingSerializer { + private static final Gson GSON = new Gson(); + /** + * 序列化骑乘关系 + */ + public static String serialize(RidingRelationship relationship) { + return GSON.toJson(relationship); + } + + /** + * 反序列化骑乘关系 + */ + public static RidingRelationship deserialize(String json) { + return GSON.fromJson(json, RidingRelationship.class); + } +} diff --git a/src/main/java/top/r3944realms/lib39/util/riding/RidingValidator.java b/src/main/java/top/r3944realms/lib39/util/riding/RidingValidator.java new file mode 100644 index 0000000..e9a2db1 --- /dev/null +++ b/src/main/java/top/r3944realms/lib39/util/riding/RidingValidator.java @@ -0,0 +1,59 @@ +/* + * Super Lead rope mod + * Copyright (C) 2025 R3944Realms + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package top.r3944realms.lib39.util.riding; + +import net.minecraft.world.entity.Entity; + +import java.util.LinkedList; +import java.util.Queue; + +@SuppressWarnings("unused") +public class RidingValidator { + /** + * 检查骑乘是否会产生循环引用 + */ + public static boolean wouldCreateCycle(Entity entity, Entity vehicle) { + // 如果实体就是载具本身,直接产生循环 + if (entity == vehicle) { + return true; + } + + // 检查载具是否已经是实体的乘客(直接或间接) + return isIndirectPassenger(vehicle, entity); + } + + /** + * 检查target是否是entity的间接乘客 + */ + public static boolean isIndirectPassenger(Entity target, Entity entity) { + Queue queue = new LinkedList<>(); + queue.offer(entity); + + while (!queue.isEmpty()) { + Entity current = queue.poll(); + if (current == target) { + return true; + } + + // 检查当前实体的所有乘客 + for (Entity passenger : current.getPassengers()) { + queue.offer(passenger); + } + } + + return false; + } +}