将拴绳的一些工具类修改放入到该库中,并且添加了网络同步数据类型,及其网络系统搭建
This commit is contained in:
parent
d15da0e4d6
commit
38e52ac38a
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 <T extends ISyncData<?>> void registerSyncManager(
|
||||
ResourceLocation id,
|
||||
ISyncManager<T> syncManager,
|
||||
Capability<T> 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 <T extends ISyncData<?>> void bindCapability(ResourceLocation id, Capability<T> capability) {
|
||||
syncs2Manager.bindCapability(id, capability);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解绑能力
|
||||
*/
|
||||
public void unbindCapability(ResourceLocation id) {
|
||||
syncs2Manager.unbindCapability(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 完整的类型安全注册
|
||||
*/
|
||||
public <T extends ISyncData<?>> void registerComplete(
|
||||
ResourceLocation id,
|
||||
ISyncManager<T> syncManager,
|
||||
Capability<T> capability,
|
||||
Class<?>... allowedEntityClasses
|
||||
) {
|
||||
registerSyncManager(id, syncManager, capability);
|
||||
if (allowedEntityClasses.length > 0) {
|
||||
addAllowEntityClass(id, allowedEntityClasses);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
package top.r3944realms.lib39.core.event;
|
||||
|
||||
public class ClientHandler {
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
package top.r3944realms.lib39.core.event;
|
||||
|
||||
public class ServerHandler {
|
||||
}
|
||||
|
|
@ -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 <MSG> void sendToPlayer(MSG message, ServerPlayer player){
|
||||
INSTANCE.send(PacketDistributor.PLAYER.with(() -> player), message);
|
||||
}
|
||||
public static <MSG, T> void sendToPlayer(MSG message, T entity, @NotNull PacketDistributor<T> packetDistributor){
|
||||
INSTANCE.send(packetDistributor.with(() -> entity), message);
|
||||
}
|
||||
}
|
||||
|
|
@ -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<NetworkEvent.Context> 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<ISyncData<?>>> 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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<ILocaleEntry> allValues() {
|
||||
public static @NotNull @UnmodifiableView Collection<ILocaleEntry> 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 + "]";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
14
src/main/java/top/r3944realms/lib39/core/sync/ISyncData.java
Normal file
14
src/main/java/top/r3944realms/lib39/core/sync/ISyncData.java
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
package top.r3944realms.lib39.core.sync;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
public interface ISyncData<T> {
|
||||
ResourceLocation id();
|
||||
boolean isDirty();
|
||||
void setDirty(boolean dirty);
|
||||
default void makeDirty() {
|
||||
setDirty(true);
|
||||
}
|
||||
void copyFrom(T src);
|
||||
void checkIfDirtyThenUpdate();
|
||||
}
|
||||
|
|
@ -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<T extends ISyncData<?>> {
|
||||
Set<T> getSyncSet();
|
||||
default void track(T instance) {
|
||||
Set<T> syncSet = checkAndGetSet();
|
||||
syncSet.add(instance);
|
||||
}
|
||||
default void untrack(T instance) {
|
||||
Set<T> syncSet = checkAndGetSet();
|
||||
syncSet.remove(instance);
|
||||
}
|
||||
default void foreach(Consumer<T> consumer) {
|
||||
Set<T> syncSet = checkAndGetSet();
|
||||
syncSet.forEach(consumer);
|
||||
}
|
||||
|
||||
private @NotNull Set<T> checkAndGetSet() throws IllegalArgumentException {
|
||||
Set<T> syncSet = getSyncSet();
|
||||
if (syncSet == null) {
|
||||
throw new IllegalStateException("SyncSet is not initialized");
|
||||
}
|
||||
return syncSet;
|
||||
}
|
||||
}
|
||||
|
|
@ -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<NBTSyncData>, INBTSerializable<CompoundTag> {
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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<ResourceLocation, TypedSyncEntry<?>> typedEntries = Maps.newConcurrentMap();
|
||||
|
||||
private static class TypedSyncEntry<T extends ISyncData<?>> {
|
||||
final ISyncManager<T> manager;
|
||||
final Capability<T> capability;
|
||||
final Set<Class<?>> allowedClasses;
|
||||
|
||||
TypedSyncEntry(ISyncManager<T> manager, Capability<T> capability) {
|
||||
this.manager = manager;
|
||||
this.capability = capability;
|
||||
this.allowedClasses = Sets.newConcurrentHashSet();
|
||||
}
|
||||
}
|
||||
|
||||
public <T extends ISyncData<?>> void registerManager(
|
||||
ResourceLocation key,
|
||||
ISyncManager<T> manager,
|
||||
Capability<T> 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<? extends ISyncData<?>> 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<ISyncData<?>>) manager,
|
||||
null
|
||||
));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends ISyncData<?>> Optional<ISyncManager<T>> getManager(ResourceLocation key) {
|
||||
TypedSyncEntry<?> entry = typedEntries.get(key);
|
||||
return entry != null ? Optional.of((ISyncManager<T>) entry.manager) : Optional.empty();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends ISyncData<?>> Optional<Capability<T>> getCapability(ResourceLocation key) {
|
||||
TypedSyncEntry<?> entry = typedEntries.get(key);
|
||||
if (entry != null && entry.capability != null) {
|
||||
return Optional.of((Capability<T>) 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 <T extends ISyncData<?>> void bindCapability(ResourceLocation key, Capability<T> 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 <T extends ISyncData<?>> void trackEntityWithTypedEntry(Entity entity, @NotNull TypedSyncEntry<T> 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 <T extends ISyncData<?>> void untrackEntityWithTypedEntry(Entity entity, @NotNull TypedSyncEntry<T> 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<Entity> entities, ResourceLocation managerId) {
|
||||
for (Entity entity : entities) {
|
||||
untrackEntityForManager(entity, managerId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从所有管理器中批量移除实体跟踪
|
||||
*/
|
||||
public void untrackEntitiesFromAllManagers(@NotNull Iterable<Entity> entities) {
|
||||
for (Entity entity : entities) {
|
||||
untrackEntityFromAllManagers(entity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 强制清理管理器中的所有跟踪数据
|
||||
*/
|
||||
public void clearAllTrackedData(ResourceLocation managerId) {
|
||||
TypedSyncEntry<?> entry = typedEntries.get(managerId);
|
||||
if (entry != null) {
|
||||
clearTrackedDataForEntry(entry);
|
||||
}
|
||||
}
|
||||
|
||||
private <T extends ISyncData<?>> void clearTrackedDataForEntry(@NotNull TypedSyncEntry<T> entry) {
|
||||
// 获取当前跟踪的集合并清空
|
||||
Set<T> syncSet = entry.manager.getSyncSet();
|
||||
if (syncSet != null) {
|
||||
syncSet.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理所有管理器的跟踪数据
|
||||
*/
|
||||
public void clearAllTrackedData() {
|
||||
for (ResourceLocation id : getRegisteredKeys()) {
|
||||
clearAllTrackedData(id);
|
||||
}
|
||||
}
|
||||
|
||||
// 辅助方法:更新条目的能力
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T extends ISyncData<?>> void updateCapabilityInEntry(TypedSyncEntry<?> entry, Capability<T> newCapability) {
|
||||
TypedSyncEntry<T> typedEntry = (TypedSyncEntry<T>) entry;
|
||||
// 由于 capability 是 final,需要替换整个 entry
|
||||
// 在实际实现中,可能需要将 capability 改为非 final 或使用不同的设计
|
||||
// 这里假设重构了 TypedSyncEntry 使 capability 可变
|
||||
}
|
||||
|
||||
|
||||
|
||||
public Set<ResourceLocation> getRegisteredKeys() {
|
||||
return Collections.unmodifiableSet(typedEntries.keySet());
|
||||
}
|
||||
|
||||
public void forEach(BiConsumer<ResourceLocation, ISyncManager<?>> 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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<String, String> lanKeyMap;
|
||||
private static final List<String> 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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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<ILangKeyValue> getValues() {
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
||||
/**
|
||||
45
src/main/java/top/r3944realms/lib39/util/lang/Pair.java
Normal file
45
src/main/java/top/r3944realms/lib39/util/lang/Pair.java
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
package top.r3944realms.lib39.util.lang;
|
||||
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public final class Pair<F, S> {
|
||||
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 <F, S> @NotNull Pair<F, S> 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 +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
48
src/main/java/top/r3944realms/lib39/util/lang/Triple.java
Normal file
48
src/main/java/top/r3944realms/lib39/util/lang/Triple.java
Normal file
|
|
@ -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<A, B, C> {
|
||||
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 <A, B, C> @NotNull Triple<A, B, C> 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 +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
85
src/main/java/top/r3944realms/lib39/util/lang/Tuple.java
Normal file
85
src/main/java/top/r3944realms/lib39/util/lang/Tuple.java
Normal file
|
|
@ -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<Object> 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> 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> T first() {
|
||||
return get(0);
|
||||
}
|
||||
|
||||
public <T> T second() {
|
||||
return get(1);
|
||||
}
|
||||
|
||||
public <T> T third() {
|
||||
return get(2);
|
||||
}
|
||||
|
||||
public <T> T last() {
|
||||
return get(elements.size() - 1);
|
||||
}
|
||||
|
||||
public List<Object> 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<Object> iterator() {
|
||||
return elements.iterator();
|
||||
}
|
||||
|
||||
public java.util.stream.Stream<Object> stream() {
|
||||
return elements.stream();
|
||||
}
|
||||
}
|
||||
38
src/main/java/top/r3944realms/lib39/util/nbt/NBTReader.java
Normal file
38
src/main/java/top/r3944realms/lib39/util/nbt/NBTReader.java
Normal file
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
36
src/main/java/top/r3944realms/lib39/util/nbt/NBTWriter.java
Normal file
36
src/main/java/top/r3944realms/lib39/util/nbt/NBTWriter.java
Normal file
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<UUID, Entity> entityProvider) {
|
||||
if (relationship == null || entityProvider == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int appliedCount = 0;
|
||||
Queue<RidingRelationship> 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<RidingRelationship> relationships,
|
||||
Function<UUID, Entity> 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<UUID, Entity> entityProvider) {
|
||||
RidingRelationship relationship = RidingSerializer.deserialize(json);
|
||||
return applyRidingRelationship(relationship, entityProvider);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<Entity> queue = new LinkedList<>();
|
||||
queue.offer(entity);
|
||||
|
||||
while (!queue.isEmpty()) {
|
||||
Entity current = queue.poll();
|
||||
|
||||
// 让当前实体的所有乘客下车
|
||||
List<Entity> 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<Entity> queue = new LinkedList<>();
|
||||
queue.offer(entity);
|
||||
|
||||
while (!queue.isEmpty() && iteration < maxIterations) {
|
||||
Entity current = queue.poll();
|
||||
iteration++;
|
||||
|
||||
// 让当前实体下车(如果是乘客)
|
||||
if (current.isPassenger()) {
|
||||
current.stopRiding();
|
||||
}
|
||||
|
||||
// 处理当前实体的乘客
|
||||
List<Entity> passengers = new ArrayList<>(current.getPassengers());
|
||||
for (Entity passenger : passengers) {
|
||||
passenger.stopRiding();
|
||||
queue.offer(passenger);
|
||||
}
|
||||
}
|
||||
|
||||
return queue.isEmpty(); // 如果队列为空表示全部解除成功
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量解除多个实体的骑乘关系
|
||||
*/
|
||||
public static void dismountEntities(Collection<Entity> entities) {
|
||||
if (entities == null || entities.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Set<Entity> processed = new HashSet<>();
|
||||
Queue<Entity> 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<Entity> passengers = new ArrayList<>(current.getPassengers());
|
||||
for (Entity passenger : passengers) {
|
||||
if (!processed.contains(passenger)) {
|
||||
queue.offer(passenger);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据骑乘关系数据结构解除骑乘
|
||||
*/
|
||||
public static void dismountByRelationship(RidingRelationship relationship,
|
||||
Function<UUID, Entity> entityProvider) {
|
||||
if (relationship == null || entityProvider == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 使用栈进行深度优先遍历解除
|
||||
Deque<RidingRelationship> 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<RidingRelationship> 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<Entity> allPassengers = RidingFinder.getAllPassengers(entity, false);
|
||||
for (Entity passenger : allPassengers) {
|
||||
if (passenger.isPassenger()) {
|
||||
passenger.stopRiding();
|
||||
}
|
||||
}
|
||||
|
||||
// 再次检查并清理(确保完全解除)
|
||||
if (!entity.getPassengers().isEmpty()) {
|
||||
entity.ejectPassengers();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<Entity> getEntityFromRidingShip(RidingRelationship ship,
|
||||
Function<UUID, Entity> entityProvider) {
|
||||
List<Entity> ret = new ArrayList<>();
|
||||
Queue<RidingRelationship> queue = new LinkedList<>();
|
||||
queue.offer(ship);
|
||||
while (!queue.isEmpty()) {
|
||||
RidingRelationship poll = queue.poll();
|
||||
ret.add(entityProvider.apply(ship.getEntityId()));
|
||||
List<RidingRelationship> 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<Entity> getAllPassengers(@Nullable Entity entity) {
|
||||
return getAllPassengers(entity, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有乘客(包括嵌套乘客)
|
||||
*/
|
||||
public static List<Entity> 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<Entity> result = new ArrayList<>();
|
||||
Queue<Entity> queue = new LinkedList<>();
|
||||
queue.offer(rootEntity);
|
||||
|
||||
while (!queue.isEmpty()) {
|
||||
Entity current = queue.poll();
|
||||
result.add(current); // 把当前实体加入列表
|
||||
|
||||
List<Entity> passengers = current.getPassengers();
|
||||
if (!passengers.isEmpty()) {
|
||||
queue.addAll(passengers);
|
||||
}
|
||||
}
|
||||
|
||||
return Collections.unmodifiableList(result);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package top.r3944realms.lib39.util.riding;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 骑乘关系数据结构
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class RidingRelationship {
|
||||
private UUID entityId;
|
||||
private UUID vehicleId;
|
||||
private List<RidingRelationship> passengers;
|
||||
|
||||
public RidingRelationship() {
|
||||
this.passengers = new ArrayList<>();
|
||||
}
|
||||
|
||||
public RidingRelationship(List<RidingRelationship> 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<RidingRelationship> getPassengers() {
|
||||
return Collections.unmodifiableList(passengers);
|
||||
}
|
||||
|
||||
public void setPassengers(List<RidingRelationship> 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() +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
114
src/main/java/top/r3944realms/lib39/util/riding/RidingSaver.java
Normal file
114
src/main/java/top/r3944realms/lib39/util/riding/RidingSaver.java
Normal file
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<Pair<Entity, RidingRelationship>> queue = new LinkedList<>();
|
||||
queue.offer(Pair.of(rootEntity, rootRelationship));
|
||||
|
||||
Set<UUID> processedEntities = new HashSet<>();
|
||||
processedEntities.add(rootEntity.getUUID());
|
||||
|
||||
while (!queue.isEmpty()) {
|
||||
Pair<Entity, RidingRelationship> current = queue.poll();
|
||||
Entity currentEntity = current.first;
|
||||
RidingRelationship currentRelation = current.second;
|
||||
|
||||
List<Entity> 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<UUID, Entity>,通常在服务器侧就是 level::getEntity
|
||||
private static Function<UUID, Entity> entityProvider;
|
||||
|
||||
public static void setEntityProvider(Function<UUID, Entity> 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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<Entity> 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;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user