将拴绳的一些工具类修改放入到该库中,并且添加了网络同步数据类型,及其网络系统搭建

This commit is contained in:
叁玖领域 2025-10-14 10:54:12 +08:00
parent d15da0e4d6
commit 38e52ac38a
31 changed files with 1730 additions and 44 deletions

View File

@ -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

View File

@ -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");
}
}
}

View File

@ -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);
}
}
}

View File

@ -0,0 +1,4 @@
package top.r3944realms.lib39.core.event;
public class ClientHandler {
}

View File

@ -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);
}
}
}
}
}

View File

@ -0,0 +1,4 @@
package top.r3944realms.lib39.core.event;
public class ServerHandler {
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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 + "]";
}
}

View 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();
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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);

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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 {
/**

View 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 +
'}';
}
}

View 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 +
'}';
}
}

View 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();
}
}

View 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");
}
}
}

View 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;
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}
}

View File

@ -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);
}
}

View File

@ -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() +
'}';
}
}

View 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();
}
}

View 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.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);
}
}

View File

@ -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;
}
}