From e82f84f2a41e30eead1c2cf2a369bdc280b14279 Mon Sep 17 00:00:00 2001 From: 3944Realms Date: Mon, 15 Sep 2025 00:37:57 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20API=E5=B0=81=E8=A3=85=20fix:=20?= =?UTF-8?q?=E9=83=A8=E5=88=86=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 30 +- proguard.pro | 4 + .../superleadrope/CommonEventHandler.java | 72 ++- .../superleadrope/SuperLeadRope.java | 2 + .../client/ClientEventHandler.java | 2 +- .../client/renderer/LeashRenderHandler.java | 7 +- .../client/renderer/SuperLeashRenderer.java | 4 +- .../resolver/SuperLeashStateResolver.java | 6 +- .../renderer/state/SuperLeashRenderState.java | 1 - .../config/LeashCommonConfig.java | 77 ++- .../config/LeashConfigManager.java | 361 ++++++++++++ .../capability/CapabilityRemainder.java | 6 +- .../capability/impi/LeashDataImpl.java | 293 ++++++---- .../capability/impi/LeashStateImpl.java | 89 ++- .../content/capability/inter/ILeashData.java | 24 +- .../content/capability/inter/ILeashState.java | 3 + .../content/command/Command.java | 6 +- .../content/command/LeashDataCommand.java | 522 +++++++++++++++++- .../content/entity/SuperLeashKnotEntity.java | 31 +- .../content/gamerule/SLPGamerules.java | 8 +- .../CreateSuperLeashKnotEntityIfAbsent.java | 33 ++ .../content/item/SuperLeadRopeItem.java | 38 +- .../core/hook/LeashRenderHook.java | 44 ++ .../core/leash/LeashInteractHandler.java | 18 +- .../datagen/data/SLPLangKeyValue.java | 148 ++++- .../network/toClient/LeashDataSyncPacket.java | 4 +- .../util/capability/LeashDataAPI.java | 315 +++++++++++ .../util/capability/LeashStateAPI.java | 273 +++++++++ .../util/capability/LeashUtil.java | 36 -- .../util/riding/RidingApplier.java | 5 +- .../util/riding/RidingSaver.java | 7 +- .../util/riding/RidingValidator.java | 40 -- .../util/riding/RindingLeash.java | 5 +- src/main/resources/META-INF/coremods.json | 4 +- src/main/resources/coremods/morsb_patch.js | 62 +++ .../superleadropetest/Placeholder.java} | 8 +- .../superleadropetest/asm/ASMTest.java | 44 ++ .../asm/CoreModTransformerMemoryTest.java | 100 ++++ .../config/OffsetReadTest.java | 132 +++++ 39 files changed, 2530 insertions(+), 334 deletions(-) create mode 100644 src/main/java/top/r3944realms/superleadrope/config/LeashConfigManager.java create mode 100644 src/main/java/top/r3944realms/superleadrope/content/gamerule/server/CreateSuperLeashKnotEntityIfAbsent.java create mode 100644 src/main/java/top/r3944realms/superleadrope/core/hook/LeashRenderHook.java create mode 100644 src/main/java/top/r3944realms/superleadrope/util/capability/LeashDataAPI.java create mode 100644 src/main/java/top/r3944realms/superleadrope/util/capability/LeashStateAPI.java delete mode 100644 src/main/java/top/r3944realms/superleadrope/util/capability/LeashUtil.java create mode 100644 src/main/resources/coremods/morsb_patch.js rename src/{main/java/top/r3944realms/superleadrope/util/coremods/InvokerMethod.java => test/java/top/r3944realms/superleadropetest/Placeholder.java} (78%) create mode 100644 src/test/java/top/r3944realms/superleadropetest/asm/ASMTest.java create mode 100644 src/test/java/top/r3944realms/superleadropetest/asm/CoreModTransformerMemoryTest.java create mode 100644 src/test/java/top/r3944realms/superleadropetest/config/OffsetReadTest.java diff --git a/build.gradle b/build.gradle index d1cb255..0a7e638 100644 --- a/build.gradle +++ b/build.gradle @@ -135,7 +135,10 @@ legacyForge { } // 源码资源目录 -sourceSets.main.resources { srcDir 'src/generated/resources' } +sourceSets.main.resources { + srcDir 'src/generated/resources' + srcDir 'src/main/resources' // 记得加这一行,把 coremods 包含进去 +} // ========== 依赖 ========== configurations { @@ -150,8 +153,10 @@ dependencies { modRuntimeOnly("mezz.jei:jei-${minecraft_version}-forge:${jei_version}") modRuntimeOnly("curse.maven:spark-361579:4738952") compileOnly ('me.lucko:spark-api:0.1-SNAPSHOT') - - + implementation 'org.ow2.asm:asm:9.6' + implementation 'org.ow2.asm:asm-tree:9.6' + implementation 'org.ow2.asm:asm-commons:9.6' + implementation 'org.ow2.asm:asm-util:9.6' } // ========== 编译配置 ========== @@ -221,6 +226,14 @@ publishing { } } } +// 新建一个可解析配置专门给 ProGuard +configurations { + proguardLibs { + canBeResolved = true + canBeConsumed = false + extendsFrom modCompileOnly + } +} // ========== ProGuard 配置 ========== tasks.register('proguard', ProGuardTask) { @@ -231,8 +244,8 @@ tasks.register('proguard', ProGuardTask) { // JDK jmods 库 libraryjars "${System.getProperty('java.home')}/jmods" - // 项目依赖作为库输入 - configurations.compileClasspath.files.each { file -> + // 使用可解析配置 + configurations.proguardLibs.resolve().each { file -> libraryjars file.absolutePath } @@ -298,7 +311,14 @@ tasks.register("runWithRenderDoc", Exec) { println "Environment MOD_CLASSES: ${environment['MOD_CLASSES']}" } } +tasks.register("copyCoreMods", Copy) { + from("src/main/resources/coremods") // 源目录 + into("$buildDir/classes/java/main/coremods") // 目标目录 +} +tasks.named("classes") { + dependsOn("copyCoreMods") +} // IDEA 支持 idea { diff --git a/proguard.pro b/proguard.pro index 0f27c54..f85cfb4 100644 --- a/proguard.pro +++ b/proguard.pro @@ -36,6 +36,10 @@ -keepclassmembers class cpw.mods.** { *; } -dontwarn cpw.mods.** +-keep class mezz.jei.** +-keepclassmembers class mezz.jei.**{ *; } +-dontwarn mezz.jei.** + #--------------------------------------- # 保留资源文件 (mods.toml / assets / data / pack.mcmeta) #--------------------------------------- diff --git a/src/main/java/top/r3944realms/superleadrope/CommonEventHandler.java b/src/main/java/top/r3944realms/superleadrope/CommonEventHandler.java index da71602..9a42e5c 100644 --- a/src/main/java/top/r3944realms/superleadrope/CommonEventHandler.java +++ b/src/main/java/top/r3944realms/superleadrope/CommonEventHandler.java @@ -50,8 +50,11 @@ import net.minecraftforge.event.level.LevelEvent; import net.minecraftforge.event.server.ServerStartingEvent; import net.minecraftforge.event.server.ServerStoppingEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.event.config.ModConfigEvent; import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; import org.jetbrains.annotations.NotNull; +import top.r3944realms.superleadrope.config.LeashCommonConfig; +import top.r3944realms.superleadrope.config.LeashConfigManager; import top.r3944realms.superleadrope.content.capability.CapabilityHandler; import top.r3944realms.superleadrope.content.capability.CapabilityRemainder; import top.r3944realms.superleadrope.content.capability.impi.LeashDataImpl; @@ -71,6 +74,8 @@ import top.r3944realms.superleadrope.core.register.SLPItems; import top.r3944realms.superleadrope.core.util.PotatoMode; import top.r3944realms.superleadrope.core.util.PotatoModeHelper; import top.r3944realms.superleadrope.datagen.data.SLPLangKeyValue; +import top.r3944realms.superleadrope.util.capability.LeashDataAPI; +import top.r3944realms.superleadrope.util.capability.LeashStateAPI; import top.r3944realms.superleadrope.util.model.RidingRelationship; import top.r3944realms.superleadrope.util.riding.RidingApplier; import top.r3944realms.superleadrope.util.riding.RidingDismounts; @@ -84,6 +89,7 @@ import java.util.UUID; import java.util.concurrent.atomic.AtomicReference; public class CommonEventHandler { + public volatile static LeashConfigManager leashConfigManager; @net.minecraftforge.fml.common.Mod.EventBusSubscriber(modid = SuperLeadRope.MOD_ID, bus = net.minecraftforge.fml.common.Mod.EventBusSubscriber.Bus.FORGE) public static class Game { @SubscribeEvent @@ -91,8 +97,8 @@ public class CommonEventHandler { Entity entity = event.getEntity(); if (entity.level().isClientSide) return; if (entity instanceof LivingEntity || entity instanceof Boat || entity instanceof Minecart) { - entity.getCapability(CapabilityHandler.LEASH_DATA_CAP).ifPresent(LeashSyncManager.Data::track); - entity.getCapability(CapabilityHandler.LEASH_STATE_CAP).ifPresent(LeashSyncManager.State::track); + LeashDataAPI.getLeashData(entity).ifPresent(LeashSyncManager.Data::track); + LeashStateAPI.getLeashState(entity).ifPresent(LeashSyncManager.State::track); if (entity instanceof ServerPlayer serverPlayer) { LeashSyncManager.Data.forEach(i -> { if (i.isLeashedBy(serverPlayer) && i.isInDelayedLeash(serverPlayer.getUUID())) { @@ -115,8 +121,8 @@ public class CommonEventHandler { } }); } - entity.getCapability(CapabilityHandler.LEASH_DATA_CAP).ifPresent(LeashSyncManager.Data::untrack); - entity.getCapability(CapabilityHandler.LEASH_STATE_CAP).ifPresent(LeashSyncManager.State::untrack); + LeashDataAPI.getLeashData(entity).ifPresent(LeashSyncManager.Data::untrack); + LeashStateAPI.getLeashState(entity).ifPresent(LeashSyncManager.State::untrack); } } @SubscribeEvent @@ -139,9 +145,6 @@ public class CommonEventHandler { @SubscribeEvent public static void onPlayerRightHitOnBlock(PlayerInteractEvent.RightClickBlock event) { Level level = event.getLevel(); - if (level.isClientSide) { - return; - } BlockPos blockPos = event.getHitVec().getBlockPos(); BlockState blockState = level.getBlockState(blockPos); Player player = event.getEntity(); @@ -256,7 +259,7 @@ public class CommonEventHandler { List entities = LeashDataImpl.leashableInArea(telEntity); //规则关闭则禁止 if(!SLPGameruleRegistry.getGameruleBoolValue(event.getEntity().level(), TeleportWithLeashedEntities.ID)) { - entities.forEach(i -> i.getCapability(CapabilityHandler.LEASH_DATA_CAP).ifPresent(j -> j.removeLeash(i))); + entities.forEach(entity -> LeashDataAPI.LeashOperations.detach(entity, telEntity)); return; } for (Entity beLeashedEntity : entities) { @@ -268,9 +271,9 @@ public class CommonEventHandler { Vec3 originalDeltaMovement = beLeashedEntity.getDeltaMovement(); AtomicReference originalLeashInfo = new AtomicReference<>(); - beLeashedEntity.getCapability(CapabilityHandler.LEASH_DATA_CAP).ifPresent(cap -> { - originalLeashInfo.set(cap.getLeashInfo(telEntity).orElse(null)); - cap.removeLeash(telEntity); + LeashDataAPI.getLeashData(beLeashedEntity).ifPresent(data -> { + originalLeashInfo.set(data.getLeashInfo(telEntity).orElse(null)); + data.removeLeash(telEntity); }); @@ -302,14 +305,11 @@ public class CommonEventHandler { entity.setPose(originalPose); } - // --- 恢复拴绳 --- + // --- 将holder替换 --- ILeashData.LeashInfo leashInfo = Optional.ofNullable(originalLeashInfo.get()) - .map(info -> info.transferHolder(telEntity)) .orElse(ILeashData.LeashInfo.EMPTY); - beLeashedEntity.getCapability(CapabilityHandler.LEASH_DATA_CAP).ifPresent( - cap -> cap.addLeash(telEntity, leashInfo) - ); + LeashDataAPI.LeashOperations.attachWithInfo(beLeashedEntity, telEntity, leashInfo); // --- 重新应用骑乘关系,仅保留白名单根载具 --- RidingRelationship filteredRelationship = RidingSaver.filterByWhitelistRoot(originalRidingRelationship); @@ -362,7 +362,8 @@ public class CommonEventHandler { @net.minecraftforge.fml.common.Mod.EventBusSubscriber(modid = SuperLeadRope.MOD_ID, bus= net.minecraftforge.fml.common.Mod.EventBusSubscriber.Bus.MOD) public static class Mod { @SubscribeEvent - public static void onCommonInit (FMLCommonSetupEvent event) { + public static void onFMLCommonInit(FMLCommonSetupEvent event) { + event.enqueueWork(Mod::checkAndSet); event.enqueueWork(SLPGameruleRegistry::register);//规则注册 } @SubscribeEvent @@ -375,6 +376,43 @@ public class CommonEventHandler { event.accept(SLPItems.SUPER_LEAD_ROPE); } } + @SubscribeEvent + public void onConfigReloading(ModConfigEvent.Reloading event) { + if (event.getConfig().getSpec() == LeashCommonConfig.SPEC) { + SuperLeadRope.logger.debug("Config reloading detected..."); + } + } + private static void checkAndSet() { + if (leashConfigManager == null) { + synchronized (LeashConfigManager.class) { + if (leashConfigManager == null) { + leashConfigManager = new LeashConfigManager(); + } + } + } + } + @SubscribeEvent + public void onConfigLoaded(ModConfigEvent.Loading event) { + if (event.getConfig().getSpec() == LeashCommonConfig.SPEC) { + checkAndSet(); + LeashConfigManager.loading(leashConfigManager); + } + } + + @SubscribeEvent + public void onConfigReloaded(ModConfigEvent.Reloading event) { + if (event.getConfig().getSpec() == LeashCommonConfig.SPEC) { + checkAndSet(); + LeashConfigManager.reloading(leashConfigManager); + } + } + + @SubscribeEvent + public void onConfigUnloaded(ModConfigEvent.Unloading event) { + if (event.getConfig().getSpec() == LeashCommonConfig.SPEC) { + LeashConfigManager.unloading(leashConfigManager); + } + } } } \ No newline at end of file diff --git a/src/main/java/top/r3944realms/superleadrope/SuperLeadRope.java b/src/main/java/top/r3944realms/superleadrope/SuperLeadRope.java index 72eddf6..fe5853b 100644 --- a/src/main/java/top/r3944realms/superleadrope/SuperLeadRope.java +++ b/src/main/java/top/r3944realms/superleadrope/SuperLeadRope.java @@ -33,6 +33,7 @@ import top.r3944realms.superleadrope.util.file.ConfigUtil; @Mod(value = SuperLeadRope.MOD_ID) public class SuperLeadRope { public static final Logger logger = LoggerFactory.getLogger(SuperLeadRope.class); + public static final String MOD_ID = "superleadrope"; public SuperLeadRope() { IEventBus eventBus = FMLJavaModLoadingContext.get().getModEventBus(); @@ -41,6 +42,7 @@ public class SuperLeadRope { SLPSoundEvents.register(eventBus); NetworkHandler.register(); initialize(); + } public static void initialize() { logger.info("Initializing SuperLeadRope"); diff --git a/src/main/java/top/r3944realms/superleadrope/client/ClientEventHandler.java b/src/main/java/top/r3944realms/superleadrope/client/ClientEventHandler.java index 80e66d1..4af923b 100644 --- a/src/main/java/top/r3944realms/superleadrope/client/ClientEventHandler.java +++ b/src/main/java/top/r3944realms/superleadrope/client/ClientEventHandler.java @@ -69,7 +69,7 @@ public class ClientEventHandler { (itemStack, clientLevel, livingEntity, i) -> { if (!itemStack.isDamageableItem()) return 0.0F; - return itemStack.getDamageValue() > 1024 - 50 ? 1.0F : 0.0F; // 损坏 → 返回 1.0(触发 override) + return itemStack.getDamageValue() > 1024 - 33 ? 1.0F : 0.0F; // 损坏 → 返回 1.0(触发 override) } ); PotatoMode mode = getCurrentMode(); diff --git a/src/main/java/top/r3944realms/superleadrope/client/renderer/LeashRenderHandler.java b/src/main/java/top/r3944realms/superleadrope/client/renderer/LeashRenderHandler.java index 22a692a..de1974a 100644 --- a/src/main/java/top/r3944realms/superleadrope/client/renderer/LeashRenderHandler.java +++ b/src/main/java/top/r3944realms/superleadrope/client/renderer/LeashRenderHandler.java @@ -24,9 +24,9 @@ import net.minecraft.world.entity.Entity; import net.minecraft.world.level.Level; import top.r3944realms.superleadrope.SuperLeadRope; import top.r3944realms.superleadrope.client.renderer.resolver.SuperLeashStateResolver; -import top.r3944realms.superleadrope.content.capability.CapabilityHandler; import top.r3944realms.superleadrope.content.capability.inter.ILeashData; import top.r3944realms.superleadrope.content.entity.SuperLeashKnotEntity; +import top.r3944realms.superleadrope.util.capability.LeashDataAPI; import java.util.Optional; import java.util.UUID; @@ -46,10 +46,9 @@ public class LeashRenderHandler { // 遍历摄像机附近所有实体 for (Entity entity : level.getEntitiesOfClass(Entity.class, - cameraEntity.getBoundingBox().inflate(50))) { + cameraEntity.getBoundingBox().inflate(100))) { - entity.getCapability(CapabilityHandler.LEASH_DATA_CAP).ifPresent(leashData -> { - if(leashData instanceof ILeashData) {} + LeashDataAPI.getLeashData(entity).ifPresent(leashData -> { for (ILeashData.LeashInfo leashInfo : leashData.getAllLeashes()) { renderLeashFromInfo(entity, leashInfo, poseStack, bufferSource, partialTick); } diff --git a/src/main/java/top/r3944realms/superleadrope/client/renderer/SuperLeashRenderer.java b/src/main/java/top/r3944realms/superleadrope/client/renderer/SuperLeashRenderer.java index 7bc829d..2259591 100644 --- a/src/main/java/top/r3944realms/superleadrope/client/renderer/SuperLeashRenderer.java +++ b/src/main/java/top/r3944realms/superleadrope/client/renderer/SuperLeashRenderer.java @@ -52,7 +52,7 @@ public class SuperLeashRenderer { int skyLightEnd = getSkyLight(BlockPos.containing(endWorld)); // 差向量 + 偏移 - Offsets offsets = computeOffsets(state, startWorld, endWorld); + Offsets offsets = computeOffsets(startWorld, endWorld); // pass1: 0 → N for (int i = 0; i <= LEASH_STEPS; i++) { @@ -76,7 +76,7 @@ public class SuperLeashRenderer { /** 计算差向量和偏移 */ - private static Offsets computeOffsets(SuperLeashRenderState state, Vec3 start, Vec3 end) { + private static Offsets computeOffsets(Vec3 start, Vec3 end) { float dx = (float) (end.x - start.x); float dy = (float) (end.y - start.y); float dz = (float) (end.z - start.z); diff --git a/src/main/java/top/r3944realms/superleadrope/client/renderer/resolver/SuperLeashStateResolver.java b/src/main/java/top/r3944realms/superleadrope/client/renderer/resolver/SuperLeashStateResolver.java index dd35424..5ea8f46 100644 --- a/src/main/java/top/r3944realms/superleadrope/client/renderer/resolver/SuperLeashStateResolver.java +++ b/src/main/java/top/r3944realms/superleadrope/client/renderer/resolver/SuperLeashStateResolver.java @@ -20,11 +20,10 @@ import net.minecraft.world.level.Level; import net.minecraft.world.phys.Vec3; import top.r3944realms.superleadrope.SuperLeadRope; import top.r3944realms.superleadrope.client.renderer.state.SuperLeashRenderState; -import top.r3944realms.superleadrope.content.capability.CapabilityHandler; import top.r3944realms.superleadrope.content.capability.impi.LeashDataImpl; import top.r3944realms.superleadrope.content.capability.inter.ILeashData; import top.r3944realms.superleadrope.content.entity.SuperLeashKnotEntity; -import top.r3944realms.superleadrope.util.capability.LeashUtil; +import top.r3944realms.superleadrope.util.capability.LeashStateAPI; import java.util.*; import java.util.concurrent.atomic.AtomicReference; @@ -56,7 +55,7 @@ public class SuperLeashStateResolver { } AtomicReference holderOffset = new AtomicReference<>(); AtomicReference entityOffset = new AtomicReference<>(); - LeashUtil.getLeashState(leashedEntity).ifPresent(state -> + LeashStateAPI.getLeashState(leashedEntity).ifPresent(state -> state .getLeashState(holder) .ifPresent(ls -> { @@ -110,7 +109,6 @@ public class SuperLeashStateResolver { return Optional.of(new SuperLeashRenderState( currentHolderPos, currentEntityPos, - leashInfo.attachOffset(), lastHolderPos, lastEntityPos, tension, diff --git a/src/main/java/top/r3944realms/superleadrope/client/renderer/state/SuperLeashRenderState.java b/src/main/java/top/r3944realms/superleadrope/client/renderer/state/SuperLeashRenderState.java index ce84aec..e91d84b 100644 --- a/src/main/java/top/r3944realms/superleadrope/client/renderer/state/SuperLeashRenderState.java +++ b/src/main/java/top/r3944realms/superleadrope/client/renderer/state/SuperLeashRenderState.java @@ -20,7 +20,6 @@ import net.minecraft.world.phys.Vec3; public record SuperLeashRenderState( Vec3 startPos, // 当前帧起点位置 Vec3 endPos, // 当前帧终点位置 - Vec3 attachOffset, // 拴绳附着点偏移 Vec3 lastStartPos, // 上一帧起点位置(用于摆动计算) Vec3 lastEndPos, // 上一帧终点位置(用于摆动计算) float tension, // 张力强度(0.0-1.0) diff --git a/src/main/java/top/r3944realms/superleadrope/config/LeashCommonConfig.java b/src/main/java/top/r3944realms/superleadrope/config/LeashCommonConfig.java index 3340535..94092e7 100644 --- a/src/main/java/top/r3944realms/superleadrope/config/LeashCommonConfig.java +++ b/src/main/java/top/r3944realms/superleadrope/config/LeashCommonConfig.java @@ -18,6 +18,8 @@ package top.r3944realms.superleadrope.config; import net.minecraftforge.common.ForgeConfigSpec; import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; public class LeashCommonConfig { public static ForgeConfigSpec.Builder BUILDER = new ForgeConfigSpec.Builder(); @@ -38,13 +40,18 @@ public class LeashCommonConfig { public final ForgeConfigSpec.DoubleValue springDampening; public final ForgeConfigSpec.ConfigValue> axisSpecificElasticity; public final ForgeConfigSpec.IntValue maxLeashesPerEntity; + public final ForgeConfigSpec.ConfigValue> defaultApplyEntityLocationOffset; + // 正则表达式模式 + static final Pattern OFFSET_PATTERN = Pattern.compile( + "(?i)(?:vec3|vec3d|vector3|offset)\\s*\\(\\s*([-+]?[0-9]*\\.?[0-9]+)\\s*,\\s*([-+]?[0-9]*\\.?[0-9]+)\\s*,\\s*([-+]?[0-9]*\\.?[0-9]+)\\s*\\)\\s*:\\s*\\[\\s*([^]]+?)\\s*]\\s*" ); + public Common(ForgeConfigSpec.Builder builder) { BUILDER.push("Command"); EnableSLPModCommandPrefix = builder .comment("The prefix of this mod's commands") .define("SLPModCommandPrefix", true); SLPModCommandPrefix = builder - .comment("The prefix of this mod's commands"," [ Default:'slp'] ") + .comment("The prefix of this mod's commands", " [ Default:'slp'] ") .define("EnableSLPModCommandPrefix", "slp"); BUILDER.pop(); builder.push("Entity"); @@ -59,7 +66,7 @@ public class LeashCommonConfig { .defineListAllowEmpty( List.of("allowedTeleportEntities"), List.of("#minecraft", "modernlife:bicycle", "modernlife:motorboat"), - o -> o instanceof String s && isValidFormat(s) + o -> o instanceof String s && isValidEntityRefFormat(s) ); builder.pop(); builder.push("LeashSettings"); @@ -87,20 +94,70 @@ public class LeashCommonConfig { .defineInRange("maxLeashesPerEntity", 6, 1, 24); builder.pop(); + builder.push("LeashStateSettings"); + defaultApplyEntityLocationOffset = builder + .comment( + "Default leash attachment point offsets for entities.", + "Format: vec3(x,y,z) : [entity_list]", + "Optional Name : vector3, vec3d, offset", + "Entity list can contain:", + " - modid:entity_id : specific entity (e.g. minecraft:bee)", + " - #modid:tag_name : entity type tag (e.g. #minecraft:boats)", + " - #modid : mod-wide (e.g. #minecraft)", + "Multiple entities can be separated by commas", + "Example: vec3(0,0.2,0) : [minecraft:bee, minecraft:horse]", + "Priority: specific entity > tag > mod" + ) + .defineListAllowEmpty( + List.of("defaultApplyEntityLocationOffset"), + List.of( + "vec3(0,0.2,0) : [minecraft:bee]", + "vec3(0,1.0,0) : [minecraft:horse, minecraft:donkey]", + "vec3(0,0.5,0) : [#minecraft:boats]", + "vec3(0,0.4,0) : [#minecraft:minecarts]", + "vec3(0,0.3,0) : [#minecraft]", + "vec3(0,0.6,0) : [#modernlife]" + ), + o -> o instanceof String s && isValidOffsetRefFormat(s) + ); + BUILDER.pop(); } - private static boolean isValidFormat(String s) { + private static boolean isValidEntityRefFormat(String s) { if (s.startsWith("#")) { String body = s.substring(1); - // 支持 #modid (整个模组) - if (body.matches("[a-z0-9_]+")) { - return true; - } - // 支持 #modid:tag_name (标签) - return body.matches("[a-z0-9_]+:[a-z0-9_/]+"); + // 支持 #modid (整个模组)或 #modid:tag_name (标签) + return body.matches("[a-z0-9_]+(:[a-z0-9_/]+)?"); } - // 普通实体 ID + // 普通实体 ID: modid:entity_id return s.matches("[a-z0-9_]+:[a-z0-9_/]+"); } + + private static boolean isValidOffsetRefFormat(String s) { + // 匹配格式: vec3(x,y,z) : [entity_list] + Matcher matcher = Common.OFFSET_PATTERN.matcher(s); + if (!matcher.matches()) { + return false; + } + + // 检查坐标值是否有效 + try { + Double.parseDouble(matcher.group(1)); + Double.parseDouble(matcher.group(2)); + Double.parseDouble(matcher.group(3)); + + // 检查实体列表格式 + String entityList = matcher.group(4); + String[] entities = entityList.split(","); + for (String entity : entities) { + if (!isValidEntityRefFormat(entity.trim())) { + return false; + } + } + return true; + } catch (NumberFormatException e) { + return false; + } + } } } diff --git a/src/main/java/top/r3944realms/superleadrope/config/LeashConfigManager.java b/src/main/java/top/r3944realms/superleadrope/config/LeashConfigManager.java new file mode 100644 index 0000000..5ba7655 --- /dev/null +++ b/src/main/java/top/r3944realms/superleadrope/config/LeashConfigManager.java @@ -0,0 +1,361 @@ +/* + * Super Lead rope mod + * Copyright (C) 2025 R3944Realms + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package top.r3944realms.superleadrope.config; + +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.phys.Vec3; +import top.r3944realms.superleadrope.SuperLeadRope; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Matcher; + +import static top.r3944realms.superleadrope.config.LeashCommonConfig.Common.OFFSET_PATTERN; + +public class LeashConfigManager { + private final Map entityOffsetMap = new ConcurrentHashMap<>(); + private final Map tagOffsetMap = new ConcurrentHashMap<>(); + private final Map modOffsetMap = new ConcurrentHashMap<>(); + + // 缓存常用配置值以提高性能 + private volatile List teleportWhitelistCache; + private volatile String commandPrefixCache; + private volatile boolean enableCommandPrefixCache; + private volatile double maxLeashLengthCache; + private volatile double elasticDistanceCache; + private volatile double extremeSnapFactorCache; + private volatile double springDampeningCache; + private volatile List axisSpecificElasticityCache; + private volatile int maxLeashesPerEntityCache; + + public LeashConfigManager() { + this.reloadAll(); + } + + /** + * 解析偏移配置(线程安全) + */ + public void parseOffsetConfig() { + Map newEntityOffsetMap = new HashMap<>(); + Map newTagOffsetMap = new HashMap<>(); + Map newModOffsetMap = new HashMap<>(); + + List offsets = LeashCommonConfig.COMMON.defaultApplyEntityLocationOffset.get(); + for (String offsetConfig : offsets) { + Matcher matcher = OFFSET_PATTERN.matcher(offsetConfig); + if (!matcher.matches()) continue; + + try { + double x = Double.parseDouble(matcher.group(1).trim()); + double y = Double.parseDouble(matcher.group(2).trim()); + double z = Double.parseDouble(matcher.group(3).trim()); + double[] offset = new double[]{x, y, z}; + + String entityList = matcher.group(4); + String[] entities = entityList.split(","); + + for (String entity : entities) { + String trimmed = entity.trim(); + if (trimmed.startsWith("#")) { + String body = trimmed.substring(1).trim(); + if (body.contains(":")) { + newTagOffsetMap.put(body, offset); + } else { + newModOffsetMap.put(body, offset); + } + } else { + newEntityOffsetMap.put(trimmed, offset); + } + } + } catch (NumberFormatException e) { + System.err.println("Invalid offset config: " + offsetConfig); + } + } + + // 原子性更新映射 + entityOffsetMap.clear(); + entityOffsetMap.putAll(newEntityOffsetMap); + + tagOffsetMap.clear(); + tagOffsetMap.putAll(newTagOffsetMap); + + modOffsetMap.clear(); + modOffsetMap.putAll(newModOffsetMap); + } + + /** + * 重新加载所有配置值到缓存 + */ + public void reloadAll() { + // 加载偏移配置 + parseOffsetConfig(); + + // 加载其他配置到缓存 + teleportWhitelistCache = new ArrayList<>(LeashCommonConfig.COMMON.teleportWhitelist.get()); + commandPrefixCache = LeashCommonConfig.COMMON.SLPModCommandPrefix.get(); + enableCommandPrefixCache = LeashCommonConfig.COMMON.EnableSLPModCommandPrefix.get(); + maxLeashLengthCache = LeashCommonConfig.COMMON.maxLeashLength.get(); + elasticDistanceCache = LeashCommonConfig.COMMON.elasticDistance.get(); + extremeSnapFactorCache = LeashCommonConfig.COMMON.extremeSnapFactor.get(); + springDampeningCache = LeashCommonConfig.COMMON.springDampening.get(); + axisSpecificElasticityCache = new ArrayList<>(LeashCommonConfig.COMMON.axisSpecificElasticity.get()); + maxLeashesPerEntityCache = LeashCommonConfig.COMMON.maxLeashesPerEntity.get(); + + SuperLeadRope.logger.debug("All configs reloaded: {}", getStats()); + } + + // ========== 偏移配置相关方法 ========== + + /** + * 获取实体类型的偏移量 + */ + @SuppressWarnings("deprecation") + public Vec3 getEntityOffset(EntityType entityType) { + String entityId = entityType.builtInRegistryHolder().key().location().toString(); + String modId = entityId.split(":")[0]; // 从实体ID提取modId + + // 获取实体的标签 + List tagStrings = new ArrayList<>(); + for (var tag : entityType.builtInRegistryHolder().tags().toList()) { + tagStrings.add(tag.location().toString()); + } + + double[] offset = getEntityOffset(entityId, modId, tagStrings); + return offset != null ? new Vec3(offset[0], offset[1], offset[2]) : Vec3.ZERO; + } + + /** + * 获取实体对象的偏移量(便捷方法) + */ + public Vec3 getEntityOffset(Entity entity) { + return getEntityOffset(entity.getType()); + } + + + /** + * 获取实体偏移量(原始数据) + */ + public double[] getEntityOffset(String entityId, String modId, List tags) { + // 1. 首先检查特定实体 + if (entityOffsetMap.containsKey(entityId)) { + return entityOffsetMap.get(entityId); + } + + // 2. 检查标签 + for (String tag : tags) { + if (tagOffsetMap.containsKey(tag)) { + return tagOffsetMap.get(tag); + } + } + + // 3. 检查模组 + if (modOffsetMap.containsKey(modId)) { + return modOffsetMap.get(modId); + } + + return null; + } + + // ========== 传送白名单相关方法 ========== + + public List getTeleportWhitelist() { + return Collections.unmodifiableList(teleportWhitelistCache); + } + + @SuppressWarnings("deprecation") + public boolean isEntityTeleportAllowed(EntityType entityType) { + String entityId = entityType.builtInRegistryHolder().key().location().toString(); + String modid = entityId.split(":")[0]; + + for (String entry : teleportWhitelistCache) { + if (entry.startsWith("#")) { + String body = entry.substring(1); + + // Case 1: #modid → allow all entities from this mod + if (!body.contains(":")) { + if (modid.equals(body)) { + return true; + } + } + // Case 2: #modid:tag_name → allow all entities under this tag + else { + ResourceLocation tagId = new ResourceLocation(body); + TagKey> tag = TagKey.create(Registries.ENTITY_TYPE, tagId); + if (entityType.builtInRegistryHolder().is(tag)) { + return true; + } + } + } else { + // Case 3: modid:entity_name → allow a specific entity + if (entry.equals(entityId)) { + return true; + } + } + } + return false; + } + + public boolean isEntityTeleportAllowed(String entityId) { + // 对于字符串ID,我们无法检查标签,只能检查模组和特定实体 + String modid = entityId.contains(":") ? entityId.split(":")[0] : "minecraft"; + + for (String entry : teleportWhitelistCache) { + if (entry.startsWith("#")) { + String body = entry.substring(1); + + // Case 1: #modid → allow all entities from this mod + if (!body.contains(":")) { + if (modid.equals(body)) { + return true; + } + } + // Case 2: #modid:tag_name → 字符串ID无法检查标签,跳过 + // 如果需要支持标签检查,需要传入EntityType而不是String + } else { + // Case 3: modid:entity_name → allow a specific entity + if (entry.equals(entityId)) { + return true; + } + } + } + return false; + } + + // 辅助方法:检查实体ID是否匹配模式(用于旧的匹配逻辑) + private boolean matchesTeleportPattern(String pattern, String entityId) { + if (pattern.startsWith("#")) { + String body = pattern.substring(1); + if (body.contains(":")) { + // 标签格式: #modid:tag_name - 字符串ID无法准确匹配标签 + // 返回模组匹配作为近似 + String patternModId = body.split(":")[0]; + String entityModId = entityId.split(":")[0]; + return entityModId.equals(patternModId); + } else { + // 模组格式: #modid + String entityModId = entityId.split(":")[0]; + return entityModId.equals(body); + } + } else { + // 实体格式: modid:entity_id + return entityId.equals(pattern); + } + } + + // 添加一个重载方法,方便使用Entity对象 + public boolean isEntityTeleportAllowed(Entity entity) { + return isEntityTeleportAllowed(entity.getType()); + } + + // ========== 命令配置相关方法 ========== + + public String getCommandPrefix() { + return commandPrefixCache; + } + + public boolean isCommandPrefixEnabled() { + return enableCommandPrefixCache; + } + + public String getFullCommand(String subCommand) { + return isCommandPrefixEnabled() ? + getCommandPrefix() + " " + subCommand : + subCommand; + } + + // ========== 拴绳物理配置相关方法 ========== + + public double getMaxLeashLength() { + return maxLeashLengthCache; + } + + public double getElasticDistance() { + return elasticDistanceCache; + } + + public double getExtremeSnapFactor() { + return extremeSnapFactorCache; + } + + public double getBreakDistance() { + return getMaxLeashLength() * getExtremeSnapFactor(); + } + + public double getSpringDampening() { + return springDampeningCache; + } + + public List getAxisSpecificElasticity() { + return Collections.unmodifiableList(axisSpecificElasticityCache); + } + + public double getXElasticity() { + return !axisSpecificElasticityCache.isEmpty() ? axisSpecificElasticityCache.get(0) : 0.8; + } + + public double getYElasticity() { + return axisSpecificElasticityCache.size() > 1 ? axisSpecificElasticityCache.get(1) : 0.2; + } + + public double getZElasticity() { + return axisSpecificElasticityCache.size() > 2 ? axisSpecificElasticityCache.get(2) : 0.8; + } + + // ========== 实体限制配置相关方法 ========== + + public int getMaxLeashesPerEntity() { + return maxLeashesPerEntityCache; + } + + public boolean canEntityAcceptMoreLeashes(Entity entity, int currentLeashCount) { + return currentLeashCount < getMaxLeashesPerEntity(); + } + + // ========== 管理方法 ========== + + public void reload() { + reloadAll(); + } + + public void clear() { + entityOffsetMap.clear(); + tagOffsetMap.clear(); + modOffsetMap.clear(); + teleportWhitelistCache = Collections.emptyList(); + } + + public String getStats() { + return String.format("Entities: %d, Tags: %d, Mods: %d, TeleportWhitelist: %d", + entityOffsetMap.size(), tagOffsetMap.size(), modOffsetMap.size(), + teleportWhitelistCache.size()); + } + + public static void loading(LeashConfigManager manager) { + manager.reloadAll(); + } + + public static void reloading(LeashConfigManager manager) { + manager.reloadAll(); + } + + public static void unloading(LeashConfigManager manager) { + manager.clear(); + } +} diff --git a/src/main/java/top/r3944realms/superleadrope/content/capability/CapabilityRemainder.java b/src/main/java/top/r3944realms/superleadrope/content/capability/CapabilityRemainder.java index 7e71ac5..b1d2146 100644 --- a/src/main/java/top/r3944realms/superleadrope/content/capability/CapabilityRemainder.java +++ b/src/main/java/top/r3944realms/superleadrope/content/capability/CapabilityRemainder.java @@ -18,7 +18,7 @@ package top.r3944realms.superleadrope.content.capability; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.entity.player.Player; import net.minecraftforge.event.entity.player.PlayerEvent; -import top.r3944realms.superleadrope.util.capability.LeashUtil; +import top.r3944realms.superleadrope.util.capability.LeashStateAPI; public class CapabilityRemainder { public static void onPlayerClone(PlayerEvent.Clone event) { @@ -26,9 +26,9 @@ public class CapabilityRemainder { if(newEntity instanceof ServerPlayer newPlayer) { Player original = event.getOriginal(); original.reviveCaps(); - LeashUtil.getLeashState(original) + LeashStateAPI.getLeashState(original) .ifPresent(oldCap -> - LeashUtil.getLeashState(newPlayer) + LeashStateAPI.getLeashState(newPlayer) .ifPresent(newData -> newData.copy(oldCap, newEntity) ) diff --git a/src/main/java/top/r3944realms/superleadrope/content/capability/impi/LeashDataImpl.java b/src/main/java/top/r3944realms/superleadrope/content/capability/impi/LeashDataImpl.java index 33f6a30..9e2335d 100644 --- a/src/main/java/top/r3944realms/superleadrope/content/capability/impi/LeashDataImpl.java +++ b/src/main/java/top/r3944realms/superleadrope/content/capability/impi/LeashDataImpl.java @@ -21,8 +21,8 @@ import net.minecraft.nbt.ListTag; import net.minecraft.nbt.NbtUtils; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; +import net.minecraft.sounds.SoundSource; import net.minecraft.world.entity.Entity; -import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.Mob; import net.minecraft.world.entity.animal.horse.Llama; @@ -30,26 +30,26 @@ import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.vehicle.Boat; import net.minecraft.world.entity.vehicle.Minecart; import net.minecraft.world.level.Level; +import net.minecraft.world.level.pathfinder.Path; import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.Vec3; import net.minecraftforge.network.PacketDistributor; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; +import top.r3944realms.superleadrope.CommonEventHandler; import top.r3944realms.superleadrope.SuperLeadRope; -import top.r3944realms.superleadrope.config.LeashCommonConfig; import top.r3944realms.superleadrope.content.capability.inter.ILeashData; import top.r3944realms.superleadrope.content.entity.SuperLeashKnotEntity; +import top.r3944realms.superleadrope.core.register.SLPSoundEvents; import top.r3944realms.superleadrope.network.NetworkHandler; import top.r3944realms.superleadrope.network.toClient.LeashDataSyncPacket; -import top.r3944realms.superleadrope.util.capability.LeashUtil; -import top.r3944realms.superleadrope.util.nbt.NBTReader; -import top.r3944realms.superleadrope.util.nbt.NBTWriter; +import top.r3944realms.superleadrope.util.capability.LeashDataAPI; +import top.r3944realms.superleadrope.util.capability.LeashStateAPI; import top.r3944realms.superleadrope.util.riding.RindingLeash; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArraySet; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -85,34 +85,6 @@ import java.util.stream.Stream; * */ public class LeashDataImpl implements ILeashData { - private static final class Config { - private Config() {} // 私有构造防止实例化 - - static double maxLeashDistance() { - return LeashCommonConfig.COMMON.maxLeashLength.get(); - } - - static double leashElasticDist() { - return LeashCommonConfig.COMMON.elasticDistance.get(); - } - - static double leashExtremeSnapDistFactor() { - return LeashCommonConfig.COMMON.extremeSnapFactor.get(); - } - - static double springDampening() { - return LeashCommonConfig.COMMON.springDampening.get(); - } - - static Vec3 axisSpecificElasticity() { - List list = LeashCommonConfig.COMMON.axisSpecificElasticity.get(); - return new Vec3(list.get(0), list.get(1), list.get(2)); - } - - static int maxLeashesPerEntity() { - return LeashCommonConfig.COMMON.maxLeashesPerEntity.get(); - } - } private final Entity entity; private boolean needsSync = false; private long lastSyncTime; @@ -172,18 +144,18 @@ public class LeashDataImpl implements ILeashData { @Override public boolean addLeash(Entity holder) { - return addLeash(holder, Config.maxLeashDistance()); + return addLeash(holder, CommonEventHandler.leashConfigManager.getMaxLeashLength()); } @Override public boolean addLeash(Entity holder, String reserved) { - return addLeash(holder, Config.maxLeashDistance(), reserved); + return addLeash(holder, CommonEventHandler.leashConfigManager.getMaxLeashLength(), reserved); } // 添加拴绳(支持自定义最大长度) @Override public boolean addLeash(Entity holder, double maxDistance) { - return addLeash(holder, maxDistance, Config.leashElasticDist(), 0, ""); + return addLeash(holder, maxDistance, CommonEventHandler.leashConfigManager.getElasticDistance(), 0, ""); } // 添加拴绳(支持自定义最大长度和弹性距离) @@ -195,7 +167,7 @@ public class LeashDataImpl implements ILeashData { // 添加拴绳(支持自定义最大长度 + reserved 字段) @Override public boolean addLeash(Entity holder, double maxDistance, String reserved) { - return addLeash(holder, maxDistance, Config.leashElasticDist(), 0, reserved); + return addLeash(holder, maxDistance, CommonEventHandler.leashConfigManager.getElasticDistance(), 0, reserved); } // 添加拴绳(最终实现:支持最大长度、弹性距离、保持 Tick、reserved) @@ -219,7 +191,6 @@ public class LeashDataImpl implements ILeashData { LeashInfo info = LeashInfo.create( holder, reserved, - calculateAttachOffset(entity), maxDistance, elasticDistance, maxKeepLeashTicks, @@ -231,7 +202,7 @@ public class LeashDataImpl implements ILeashData { } else { leashHolders.put(holder.getUUID(), info); } - + LeashStateAPI.Operations.attach(entity, holder); markForSync(); return true; } @@ -301,7 +272,6 @@ public class LeashDataImpl implements ILeashData { old.holderUUIDOpt().get(), old.holderIdOpt().get(), old.reserved(), - old.attachOffset(), newMaxDistance, old.elasticDistance(), old.keepLeashTicks(), @@ -316,7 +286,6 @@ public class LeashDataImpl implements ILeashData { old.holderUUIDOpt().get(), old.holderIdOpt().get(), old.reserved(), - old.attachOffset(), newMaxDistance, old.elasticDistance(), Math.min(old.keepLeashTicks(), newMaxKeepLeashTicks), @@ -331,7 +300,6 @@ public class LeashDataImpl implements ILeashData { old.holderUUIDOpt().get(), old.holderIdOpt().get(), reserved, - old.attachOffset(), distance, old.elasticDistance(), Math.min(old.keepLeashTicks(), maxKeepTicks), @@ -346,7 +314,6 @@ public class LeashDataImpl implements ILeashData { old.blockPosOpt().get(), old.holderIdOpt().get(), old.reserved(), - old.attachOffset(), newMaxDistance, old.elasticDistance(), old.keepLeashTicks(), @@ -361,7 +328,6 @@ public class LeashDataImpl implements ILeashData { old.blockPosOpt().get(), old.holderIdOpt().get(), old.reserved(), - old.attachOffset(), newMaxDistance, old.elasticDistance(), Math.min(old.keepLeashTicks(), newMaxKeepLeashTicks), @@ -376,7 +342,6 @@ public class LeashDataImpl implements ILeashData { old.blockPosOpt().get(), old.holderIdOpt().get(), reserved, - old.attachOffset(), distance, old.elasticDistance(), Math.min(old.keepLeashTicks(), maxKeepTicks), @@ -399,7 +364,6 @@ public class LeashDataImpl implements ILeashData { old.holderUUIDOpt().get(), old.holderIdOpt().get(), old.reserved(), - old.attachOffset(), old.maxDistance(), newElasticDistance, old.keepLeashTicks(), @@ -414,7 +378,6 @@ public class LeashDataImpl implements ILeashData { old.blockPosOpt().get(), old.holderIdOpt().get(), old.reserved(), - old.attachOffset(), old.maxDistance(), newElasticDistance, old.keepLeashTicks(), @@ -444,7 +407,6 @@ public class LeashDataImpl implements ILeashData { old.holderUUIDOpt().get(), old.holderIdOpt().get(), old.reserved(), - old.attachOffset(), old.maxDistance(), newElasticDistance, Math.min(old.keepLeashTicks(), newMaxKeepLeashTicks), @@ -459,7 +421,6 @@ public class LeashDataImpl implements ILeashData { old.holderUUIDOpt().get(), old.holderIdOpt().get(), reserved, - old.attachOffset(), old.maxDistance(), distance, Math.min(old.keepLeashTicks(), maxKeepTicks), @@ -474,7 +435,6 @@ public class LeashDataImpl implements ILeashData { old.blockPosOpt().get(), old.holderIdOpt().get(), old.reserved(), - old.attachOffset(), old.maxDistance(), newElasticDistance, old.keepLeashTicks(), @@ -489,7 +449,6 @@ public class LeashDataImpl implements ILeashData { old.blockPosOpt().get(), old.holderIdOpt().get(), reserved, - old.attachOffset(), old.maxDistance(), newElasticDistance, Math.min(old.keepLeashTicks(), newMaxKeepLeashTicks), @@ -502,13 +461,17 @@ public class LeashDataImpl implements ILeashData { */ @Override public void applyLeashForces() { - Vec3 combinedForce = Vec3.ZERO; // 初始化合力向量 + Vec3 combinedForce = Vec3.ZERO; + Vec3 combinedDirection = Vec3.ZERO; + int validLeashes = 0; - // 计算所有拴绳的合力 + // 计算所有拴绳的合力和平均方向 for (Map.Entry entry : leashHolders.entrySet()) { Vec3 force = calculateLeashForceForUUID(entry); if (force != null) { combinedForce = combinedForce.add(force); + combinedDirection = combinedDirection.add(force.normalize()); + validLeashes++; } } @@ -516,27 +479,152 @@ public class LeashDataImpl implements ILeashData { Vec3 force = calculateLeashForceForBlockPos(entry); if (force != null) { combinedForce = combinedForce.add(force); + combinedDirection = combinedDirection.add(force.normalize()); + validLeashes++; } } boolean hasForce = !combinedForce.equals(Vec3.ZERO); Entity finalApplyEntity = RindingLeash.getFinalEntityForLeashIfForce(entity, hasForce); - if (hasForce) { + if (hasForce) { + // 处理玩家和其他实体 if (finalApplyEntity instanceof ServerPlayer player) { RindingLeash.applyForceToPlayer(player, combinedForce); - return; } else { finalApplyEntity.setDeltaMovement(finalApplyEntity.getDeltaMovement().add(combinedForce)); finalApplyEntity.hurtMarked = true; + + // 对生物使用合力方向进行移动(只有在能够移动时才执行) + if (finalApplyEntity instanceof Mob mob && validLeashes > 0 && canMobMove(mob)) { + moveMobTowardsCombinedDirection(mob, combinedDirection, validLeashes, combinedForce.length()); + } else if (finalApplyEntity instanceof Mob mob) { + // 无法移动时停止导航 + mob.getNavigation().stop(); + } } RindingLeash.protectAnimalMovement(finalApplyEntity, true); } else { RindingLeash.protectAnimalMovement(finalApplyEntity, false); + + // 没有力时也停止导航 + if (finalApplyEntity instanceof Mob mob) { + mob.getNavigation().stop(); + } } } + /** + * 检查生物是否能够移动 + */ + private boolean canMobMove(Mob mob) { + // 检查各种无法移动的情况 + return !mob.isNoGravity() && // 有重力才能移动 + !mob.isSleeping() && // 没有在睡觉 + !mob.isDeadOrDying() && // 没有死亡或濒死 + !mob.isFreezing() && // 没有被冻结 + mob.canUpdate() && // 可以更新 + mob.isEffectiveAi() && // AI有效 + mob.getDeltaMovement().lengthSqr() < 100.0; // 移动速度不是特别快(防止异常情况) + } + + /** + * 让生物朝着合力方向移动 + */ + private void moveMobTowardsCombinedDirection(Mob mob, Vec3 combinedDirection, int leashCount, double forceMagnitude) { + if (combinedDirection.equals(Vec3.ZERO)) return; + + // 再次检查是否能够移动 + if (!canMobMove(mob)) { + mob.getNavigation().stop(); + return; + } + + // 计算平均方向 + Vec3 averageDirection = combinedDirection.scale(1.0 / leashCount).normalize(); + + // 根据力的大小调整移动速度 + double speed = calculateMobSpeed(mob, forceMagnitude); + + // 计算目标位置(在合力方向上稍微超前一点) + Vec3 targetPos = mob.position().add(averageDirection.scale(3.0)); // 3格距离 + + // 检查目标位置是否可达 + if (isPositionReachable(mob, targetPos)) { + // 设置移动目标 + mob.getNavigation().moveTo(targetPos.x, targetPos.y, targetPos.z, speed); + + // 设置生物朝向合力方向 + mob.getLookControl().setLookAt(targetPos); + } else { + // 位置不可达时停止导航 + mob.getNavigation().stop(); + } + } + + /** + * 检查位置是否可达 + */ + private boolean isPositionReachable(Mob mob, Vec3 targetPos) { + // 简单的距离检查 + double distance = mob.position().distanceTo(targetPos); + if (distance > 20.0) { // 距离太远 + return false; + } + + // 检查是否有导航路径 + Path path = mob.getNavigation().createPath(targetPos.x, targetPos.y, targetPos.z, 0); + return path != null && !path.isDone(); + } + + /** + * 增强的移动能力检查 + */ + private boolean canMobMoveEnhanced(Mob mob) { + // 基础移动检查 + if (!canMobMove(mob)) { + return false; + } + + // 检查导航系统是否可用 + if (mob.getNavigation().isDone() || mob.getNavigation().isStuck()) { + return false; + } + + // 检查生物是否被拴绳过度拉扯(防止无限尝试移动) + Vec3 motion = mob.getDeltaMovement(); + if (motion.lengthSqr() > 4.0 && mob.tickCount % 20 == 0) { + // 如果移动速度过快,偶尔跳过移动尝试 + return false; + } + + // 检查生物是否在尝试移动但实际没有移动(卡住检测) + if (mob.getNavigation().getPath() != null && + !mob.getNavigation().getPath().isDone()) { + + // 获取上一tick的位置进行比较 + double distanceMoved = mob.position().distanceTo(new Vec3(mob.xOld, mob.yOld, mob.zOld)); + + // 生物卡住了(有路径但几乎没有移动) + return !(distanceMoved < 0.1); + } + + return true; + } + /** + * 根据生物类型和力的大小计算移动速度 + */ + private double calculateMobSpeed(Mob mob, double forceMagnitude) { + double baseSpeed = mob instanceof Llama ? 2.0 : 1.0; + + // 力越大,移动速度越快(但有上限) + double forceFactor = Math.min(forceMagnitude * 0.5, 2.0); // 限制最大加速2倍 + + return baseSpeed * (1.0 + forceFactor); + } + + /** * 为UUID拴绳计算力 @@ -569,9 +657,9 @@ public class LeashDataImpl implements ILeashData { private Vec3 calculateLeashForce(Entity holder, Map.Entry entry) { Vec3 holderPos = holder.position().add(0, holder.getBbHeight() * 0.7, 0); LeashInfo info = entry.getValue(); - Vec3 entityPos = entity.position().add(info.attachOffset()); + Vec3 entityPos = entity.position(); double distance = holderPos.distanceTo(entityPos); - double extremeSnapDist = info.maxDistance() * Config.leashExtremeSnapDistFactor(); + double extremeSnapDist = info.maxDistance() * CommonEventHandler.leashConfigManager.getExtremeSnapFactor(); // 1. 检查是否超出断裂距离 if (distance > extremeSnapDist) { @@ -583,6 +671,8 @@ public class LeashDataImpl implements ILeashData { } // 断裂 removeLeash(holder); + //TODO: 是不是应该考虑让断裂统一发出声音,还是就这样由断裂发出 + entity.level().playSound(null, holder.getOnPos(), SLPSoundEvents.LEAD_BREAK.get(), SoundSource.PLAYERS); return null; } @@ -590,15 +680,6 @@ public class LeashDataImpl implements ILeashData { Vec3 pullForce = Vec3.ZERO; if (distance > info.elasticDistance()) { pullForce = calculatePullForce(holderPos, entityPos, distance, info); - - // 生物添加跟随逻辑(保持不变) - if(entity instanceof Mob mob) { - Vec3 vec3 = (new Vec3(holder.getX() - entity.getX(), holder.getY() - entity.getY(), holder.getZ() - entity.getZ())) - .normalize() - .scale(Math.max(distance - 2.0F, 0.0F)); - double speed = mob instanceof Llama ? 2.0 : 1.0; - mob.getNavigation().moveTo(entity.getX() + vec3.x, entity.getY() + vec3.y, entity.getZ() + vec3.z, speed); - } } // 3. 重置缓冲Tick @@ -621,13 +702,13 @@ public class LeashDataImpl implements ILeashData { } Vec3 pullForce = pullDirection.scale( - (distance - info.elasticDistance()) * pullStrength * Config.springDampening() + (distance - info.elasticDistance()) * pullStrength * CommonEventHandler.leashConfigManager.getSpringDampening() ); return new Vec3( - pullForce.x * Config.axisSpecificElasticity().x, - pullForce.y * Config.axisSpecificElasticity().y, - pullForce.z * Config.axisSpecificElasticity().z + pullForce.x * CommonEventHandler.leashConfigManager.getXElasticity(), + pullForce.y * CommonEventHandler.leashConfigManager.getXElasticity(), + pullForce.z * CommonEventHandler.leashConfigManager.getZElasticity() ); } @@ -638,13 +719,13 @@ public class LeashDataImpl implements ILeashData { double pullStrength = 1.0 + excessRatio * 2.0; Vec3 pullForce = pullDirection.scale( - (distance - info.elasticDistance()) * pullStrength * Config.springDampening() + (distance - info.elasticDistance()) * pullStrength * CommonEventHandler.leashConfigManager.getSpringDampening() ); return new Vec3( - pullForce.x * Config.axisSpecificElasticity().x, - pullForce.y * Config.axisSpecificElasticity().y, - pullForce.z * Config.axisSpecificElasticity().z + pullForce.x * CommonEventHandler.leashConfigManager.getXElasticity(), + pullForce.y * CommonEventHandler.leashConfigManager.getYElasticity(), + pullForce.z * CommonEventHandler.leashConfigManager.getZElasticity() ); } @@ -658,16 +739,20 @@ public class LeashDataImpl implements ILeashData { @Override public boolean removeLeash(UUID holderUUID) { boolean removed = leashHolders.remove(holderUUID) != null; - if (removed) + if (removed) { + LeashStateAPI.Operations.detach(entity, holderUUID); markForSync(); + } return removed; } @Override public boolean removeLeash(BlockPos knotPos) { boolean removed = leashKnots.remove(knotPos) != null; - if (removed) + if (removed) { + LeashStateAPI.Operations.detach(entity, knotPos); markForSync(); + } return removed; } @@ -675,18 +760,21 @@ public class LeashDataImpl implements ILeashData { public void removeAllLeashes() { leashHolders.clear(); leashKnots.clear(); + LeashStateAPI.Offset.removeAll(entity); markForSync(); } @Override public void removeAllHolderLeashes() { leashHolders.clear(); + LeashStateAPI.Offset.removeAllUUIDs(entity); markForSync(); } @Override public void removeAllKnotLeashes() { leashKnots.clear(); + LeashStateAPI.Offset.removeAllBlockPoses(entity); markForSync(); } @@ -716,6 +804,7 @@ public class LeashDataImpl implements ILeashData { LeashInfo leashInfo = info.transferHolder(newHolder); leashHolders.put(newHolder.getUUID(), leashInfo); } + LeashStateAPI.Operations.transfer(entity, oldHolderUUID, newHolder); markForSync(); return true; } @@ -730,6 +819,7 @@ public class LeashDataImpl implements ILeashData { LeashInfo leashInfo = info.transferHolder(newHolder, reserved); leashHolders.put(newHolder.getUUID(), leashInfo); } + LeashStateAPI.Operations.transfer(entity, oldHolderUUID, newHolder); markForSync(); return true; } @@ -745,6 +835,7 @@ public class LeashDataImpl implements ILeashData { LeashInfo leashInfo = info.transferHolder(newHolder); leashHolders.put(newHolder.getUUID(), leashInfo); } + LeashStateAPI.Operations.transfer(entity, knotPos, newHolder); markForSync(); return true; } @@ -760,6 +851,7 @@ public class LeashDataImpl implements ILeashData { LeashInfo leashInfo = info.transferHolder(newHolder, reserved); leashHolders.put(newHolder.getUUID(), leashInfo); } + LeashStateAPI.Operations.transfer(entity, knotPos, newHolder); markForSync(); return true; } @@ -885,7 +977,6 @@ public class LeashDataImpl implements ILeashData { } infoTag.putInt("HolderID", info.holderIdOpt().get()); infoTag.putString("LeashItem", info.reserved()); - infoTag.put("Offset", NBTWriter.writeVec3(info.attachOffset())); infoTag.putDouble("MaxDistance", info.maxDistance()); infoTag.putDouble("ElasticDistance", info.elasticDistance()); infoTag.putInt("KeepLeashTicks", info.keepLeashTicks()); @@ -933,7 +1024,6 @@ public class LeashDataImpl implements ILeashData { infoTag.getUUID("HolderUUID"), infoTag.getInt("HolderID"), infoTag.getString("LeashItem"), - NBTReader.readVec3(infoTag.getCompound("Offset")), infoTag.getDouble("MaxDistance"), infoTag.contains("ElasticDistance") ? infoTag.getDouble("ElasticDistance") : 6.0, infoTag.getInt("KeepLeashTicks"), @@ -949,7 +1039,6 @@ public class LeashDataImpl implements ILeashData { NbtUtils.readBlockPos(infoTag.getCompound("KnotBlockPos")), infoTag.getInt("HolderID"), infoTag.getString("LeashItem"), - NBTReader.readVec3(infoTag.getCompound("Offset")), infoTag.getDouble("MaxDistance"), infoTag.contains("ElasticDistance") ? infoTag.getDouble("ElasticDistance") : 6.0, infoTag.getInt("KeepLeashTicks"), @@ -961,7 +1050,7 @@ public class LeashDataImpl implements ILeashData { @Override public boolean canBeLeashed() { - return (leashHolders.size() + leashKnots.size()) <= Config.maxLeashesPerEntity(); + return (leashHolders.size() + leashKnots.size()) <= CommonEventHandler.leashConfigManager.getMaxLeashesPerEntity(); } @Override @@ -1007,48 +1096,28 @@ public class LeashDataImpl implements ILeashData { return leashableInArea(holder, i -> isLeashHolder(i, holder), 1024D); } public boolean canBeAttachedTo(Entity pEntity) { - if(pEntity == entity) { + if (pEntity == entity) { return false; } else { Optional leashInfo = getLeashInfo(pEntity); - return leashInfo.isEmpty() && (entity.distanceTo(pEntity) <= Config.leashElasticDist() * Config.leashExtremeSnapDistFactor()) && canBeLeashed();//距离最大,则不可以被固定或转移 + return leashInfo.isEmpty() && (entity.distanceTo(pEntity) <= CommonEventHandler.leashConfigManager.getElasticDistance() * CommonEventHandler.leashConfigManager.getExtremeSnapFactor()) && canBeLeashed();//距离最大,则不可以被固定或转移 } } - public static boolean isLeashHolder(@NotNull Entity pEntity, UUID pHolderUUID) { - AtomicBoolean isTarget = new AtomicBoolean(false); - LeashUtil.getLeashData(pEntity) - .ifPresent(i -> - isTarget.set(i.isLeashedBy(pHolderUUID)) - ); - return isTarget.get(); - } - public static boolean isLeashHolder(@NotNull Entity pEntity, BlockPos pKnotPos) { - AtomicBoolean isTarget = new AtomicBoolean(false); - LeashUtil.getLeashData(pEntity) - .ifPresent(i -> - isTarget.set(i.isLeashedBy(pKnotPos)) - ); - return isTarget.get(); - } public static boolean isLeashHolder(@NotNull Entity pEntity, Entity pTestHolder) { return pTestHolder instanceof SuperLeashKnotEntity superLeashKnotEntity ? isLeashHolder(pEntity, superLeashKnotEntity.getPos()) : isLeashHolder(pEntity, pTestHolder.getUUID()); } - - // 计算拴绳附着点 - @Contract("_ -> new") - private @NotNull Vec3 calculateAttachOffset(@NotNull Entity entity) { - EntityType type = entity.getType(); - if (type == EntityType.HORSE || type == EntityType.DONKEY) { - return new Vec3(0, 1.4, 0.3); - } else if (type == EntityType.IRON_GOLEM) { - return new Vec3(0, 1.8, 0); - } - //TODO: 未来自定义配置 - return new Vec3(0, entity.getBbHeight() * 0.8, 0); + public static boolean isLeashHolder(@NotNull Entity pEntity, UUID pHolderUUID) { + return LeashDataAPI.getLeashData(pEntity) + .map(leashData -> leashData.isLeashedBy(pHolderUUID)) + .orElse(false); } - + public static boolean isLeashHolder(@NotNull Entity pEntity, BlockPos pKnotPos) { + return LeashDataAPI.getLeashData(pEntity) + .map(leashData -> leashData.isLeashedBy(pKnotPos)) + .orElse(false); + } } diff --git a/src/main/java/top/r3944realms/superleadrope/content/capability/impi/LeashStateImpl.java b/src/main/java/top/r3944realms/superleadrope/content/capability/impi/LeashStateImpl.java index f61fb3e..cf3490e 100644 --- a/src/main/java/top/r3944realms/superleadrope/content/capability/impi/LeashStateImpl.java +++ b/src/main/java/top/r3944realms/superleadrope/content/capability/impi/LeashStateImpl.java @@ -127,7 +127,7 @@ public class LeashStateImpl implements ILeashState { @Override public void resetLeashHolderLocationOffset(Entity holder) { - if (entity instanceof SuperLeashKnotEntity leashKnot) { + if (holder instanceof SuperLeashKnotEntity leashKnot) { resetLeashHolderLocationOffset(leashKnot.getPos()); } else resetLeashHolderLocationOffset(holder.getUUID()); } @@ -146,45 +146,97 @@ public class LeashStateImpl implements ILeashState { @Override public void setLeashHolderLocationOffset(Entity holder, Vec3 offset) { - if (entity instanceof SuperLeashKnotEntity leashKnot) { + if (holder instanceof SuperLeashKnotEntity leashKnot) { setLeashHolderLocationOffset(leashKnot.getPos(), offset); } else setLeashHolderLocationOffset(holder.getUUID(), offset); } @Override public void setLeashHolderLocationOffset(UUID holderUUID, Vec3 offset) { - leashHolders.computeIfPresent(holderUUID, (uuid, state) -> state.setHolderLocationOffset(offset)); + LeashState currentState = leashHolders.get(holderUUID); + if (currentState == null) { + // 创建新的状态,使用默认的应用实体偏移量 + leashHolders.put(holderUUID, new LeashState( + offset, + getDefaultLeashApplyEntityLocationOffset(), + Vec3.ZERO // 或者合适的默认值 + )); + } else { + // 更新现有状态 + leashHolders.put(holderUUID, + currentState.setHolderLocationOffset(offset) + ); + } markForSync(); } @Override - public void setLeashHolderLocationOffset(BlockPos knotPos, Vec3 leashHolderLocationOffset) { - leashKnots.computeIfPresent(knotPos, (blockPos, state) -> state.setHolderLocationOffset(leashHolderLocationOffset)); + public void setLeashHolderLocationOffset(BlockPos knotPos, Vec3 offset) { + LeashState currentState = leashKnots.get(knotPos); + if (currentState == null) { + // 创建新的状态 + leashKnots.put(knotPos, new LeashState( + offset, + getDefaultLeashApplyEntityLocationOffset(), + Vec3.ZERO + )); + } else { + // 更新现有状态 + leashKnots.put(knotPos, + currentState.setHolderLocationOffset(offset) + ); + } markForSync(); } @Override public void addLeashHolderLocationOffset(Entity holder, Vec3 offset) { - if (entity instanceof SuperLeashKnotEntity leashKnot) { + if (holder instanceof SuperLeashKnotEntity leashKnot) { addLeashHolderLocationOffset(leashKnot.getPos(), offset); } else addLeashHolderLocationOffset(holder.getUUID(), offset); } @Override public void addLeashHolderLocationOffset(UUID holderUUID, Vec3 offset) { - leashHolders.computeIfPresent(holderUUID, (uuid, state) -> state.setHolderLocationOffset(state.holderLocationOffset().add(offset))); + LeashState currentState = leashHolders.get(holderUUID); + if (currentState == null) { + // 创建新的状态,使用默认的应用实体偏移量 + leashHolders.put(holderUUID, new LeashState( + offset, + getDefaultLeashApplyEntityLocationOffset(), + Vec3.ZERO // 或者合适的默认值 + )); + } else { + // 更新现有状态 + leashHolders.put(holderUUID, + currentState.setHolderLocationOffset(currentState.holderLocationOffset().add(offset)) + ); + } markForSync(); } @Override public void addLeashHolderLocationOffset(BlockPos knotPos, Vec3 offset) { - leashKnots.computeIfPresent(knotPos, (blockPos, state) -> state.setHolderLocationOffset(state.holderLocationOffset().add(offset))); + LeashState currentState = leashKnots.get(knotPos); + if (currentState == null) { + // 创建新的状态 + leashKnots.put(knotPos, new LeashState( + offset, + getDefaultLeashApplyEntityLocationOffset(), + Vec3.ZERO + )); + } else { + // 更新现有状态 + leashKnots.put(knotPos, + currentState.setHolderLocationOffset(currentState.holderLocationOffset().add(offset)) + ); + } markForSync(); } @Override public void removeLeashHolderLocationOffset(Entity holder) { - if (entity instanceof SuperLeashKnotEntity leashKnot) { + if (holder instanceof SuperLeashKnotEntity leashKnot) { removeLeashHolderLocationOffset(leashKnot.getPos()); } else removeLeashHolderLocationOffset(holder.getUUID()); } @@ -201,6 +253,25 @@ public class LeashStateImpl implements ILeashState { markForSync(); } + @Override + public void removeAllLeashHolderLocationOffset() { + leashKnots.clear(); + leashHolders.clear(); + markForSync(); + } + + @Override + public void removeAllLeashHolderUUIDLocationOffset() { + leashHolders.clear(); + markForSync(); + } + + @Override + public void removeAllLeashHolderBlockPosLocationOffset() { + leashKnots.clear(); + markForSync(); + } + @Override public void resetAllLeashHolderLocationsOffset() { leashKnots.replaceAll((pos, leashState) -> leashState.resetHolderLocationOffset()); diff --git a/src/main/java/top/r3944realms/superleadrope/content/capability/inter/ILeashData.java b/src/main/java/top/r3944realms/superleadrope/content/capability/inter/ILeashData.java index da7a05e..c225d06 100644 --- a/src/main/java/top/r3944realms/superleadrope/content/capability/inter/ILeashData.java +++ b/src/main/java/top/r3944realms/superleadrope/content/capability/inter/ILeashData.java @@ -19,7 +19,6 @@ import net.minecraft.core.BlockPos; import net.minecraft.nbt.CompoundTag; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.player.Player; -import net.minecraft.world.phys.Vec3; import net.minecraftforge.common.util.INBTSerializable; import top.r3944realms.superleadrope.content.entity.SuperLeashKnotEntity; @@ -142,7 +141,6 @@ public interface ILeashData extends INBTSerializable { Optional holderUUIDOpt, Optional holderIdOpt, // Only for client side use String reserved, // 保留字段 - Vec3 attachOffset, double maxDistance, double elasticDistance, int keepLeashTicks, // 剩余 Tick 数 @@ -150,14 +148,13 @@ public interface ILeashData extends INBTSerializable { ) { public static final LeashInfo EMPTY = new LeashInfo( Optional.empty(), Optional.empty(), Optional.empty(), - "", Vec3.ZERO, 12.0D, 6.0D, 0, 0 + "", 12.0D, 6.0D, 0, 0 ); /* ---------- Factory ---------- */ public static LeashInfo create( Entity entity, String reserved, - Vec3 offset, double maxDistance, double elasticDistance, int keepTicks, @@ -165,32 +162,31 @@ public interface ILeashData extends INBTSerializable { ) { return entity instanceof SuperLeashKnotEntity knot ? new LeashInfo(knot.getPos(), entity.getId(), reserved, - offset, maxDistance, elasticDistance, keepTicks, maxKeepTicks) - : new LeashInfo(entity.getUUID(), entity.getId(), reserved, - offset, maxDistance, elasticDistance, keepTicks, maxKeepTicks); + maxDistance, elasticDistance, keepTicks, maxKeepTicks) + : new LeashInfo(entity.getUUID(), entity.getId(), reserved, maxDistance, elasticDistance, keepTicks, maxKeepTicks); } - public LeashInfo(UUID holderUUID, int holderId, String reserved, Vec3 offset, + public LeashInfo(UUID holderUUID, int holderId, String reserved, double maxDistance, double elasticDistance, int keepTicks, int maxKeepTicks) { this(Optional.empty(), Optional.of(holderUUID), Optional.of(holderId), - reserved, offset, maxDistance, elasticDistance, keepTicks, maxKeepTicks); + reserved, maxDistance, elasticDistance, keepTicks, maxKeepTicks); } - public LeashInfo(BlockPos knotPos, int holderId, String reserved, Vec3 offset, + public LeashInfo(BlockPos knotPos, int holderId, String reserved, double maxDistance, double elasticDistance, int keepTicks, int maxKeepTicks) { this(Optional.of(knotPos), Optional.empty(), Optional.of(holderId), - reserved, offset, maxDistance, elasticDistance, keepTicks, maxKeepTicks); + reserved, maxDistance, elasticDistance, keepTicks, maxKeepTicks); } /* ---------- State updates ---------- */ public LeashInfo decrementKeepTicks() { - return new LeashInfo(blockPosOpt, holderUUIDOpt, holderIdOpt, reserved, attachOffset, + return new LeashInfo(blockPosOpt, holderUUIDOpt, holderIdOpt, reserved, maxDistance, elasticDistance, Math.max(0, keepLeashTicks - 1), maxKeepLeashTicks); } public LeashInfo resetKeepTicks() { - return new LeashInfo(blockPosOpt, holderUUIDOpt, holderIdOpt, reserved, attachOffset, + return new LeashInfo(blockPosOpt, holderUUIDOpt, holderIdOpt, reserved, maxDistance, elasticDistance, maxKeepLeashTicks, maxKeepLeashTicks); } @@ -205,7 +201,7 @@ public interface ILeashData extends INBTSerializable { isKnot ? Optional.of(((SuperLeashKnotEntity) entity).getPos()) : Optional.empty(), !isKnot ? Optional.of(entity.getUUID()) : Optional.empty(), Optional.of(entity.getId()), - newReserved, attachOffset, maxDistance, elasticDistance, + newReserved, maxDistance, elasticDistance, keepLeashTicks, maxKeepLeashTicks ); } diff --git a/src/main/java/top/r3944realms/superleadrope/content/capability/inter/ILeashState.java b/src/main/java/top/r3944realms/superleadrope/content/capability/inter/ILeashState.java index 7a0aaf5..ce2fa29 100644 --- a/src/main/java/top/r3944realms/superleadrope/content/capability/inter/ILeashState.java +++ b/src/main/java/top/r3944realms/superleadrope/content/capability/inter/ILeashState.java @@ -67,6 +67,9 @@ public interface ILeashState extends INBTSerializable { void removeLeashHolderLocationOffset(Entity holder); void removeLeashHolderLocationOffset(UUID holderUUID); void removeLeashHolderLocationOffset(BlockPos knotPos); + void removeAllLeashHolderLocationOffset(); + void removeAllLeashHolderUUIDLocationOffset(); + void removeAllLeashHolderBlockPosLocationOffset(); /* ---------------------- * Apply-entity offset diff --git a/src/main/java/top/r3944realms/superleadrope/content/command/Command.java b/src/main/java/top/r3944realms/superleadrope/content/command/Command.java index 105af0f..3ecc7f3 100644 --- a/src/main/java/top/r3944realms/superleadrope/content/command/Command.java +++ b/src/main/java/top/r3944realms/superleadrope/content/command/Command.java @@ -19,13 +19,13 @@ import com.mojang.brigadier.builder.LiteralArgumentBuilder; import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.Commands; import org.jetbrains.annotations.Nullable; -import top.r3944realms.superleadrope.config.LeashCommonConfig; +import top.r3944realms.superleadrope.CommonEventHandler; import java.util.List; public class Command { - public static final String PREFIX = LeashCommonConfig.COMMON.SLPModCommandPrefix.get(); - public static boolean SHOULD_USE_PREFIX = LeashCommonConfig.COMMON.EnableSLPModCommandPrefix.get(); + public static final String PREFIX = CommonEventHandler.leashConfigManager.getCommandPrefix(); + public static boolean SHOULD_USE_PREFIX = CommonEventHandler.leashConfigManager.isCommandPrefixEnabled(); static LiteralArgumentBuilder getLiterArgumentBuilderOfCSS(String name, boolean shouldAddToList, @Nullable List> list) { LiteralArgumentBuilder literal = Commands.literal(name); if (shouldAddToList) { diff --git a/src/main/java/top/r3944realms/superleadrope/content/command/LeashDataCommand.java b/src/main/java/top/r3944realms/superleadrope/content/command/LeashDataCommand.java index a9366bb..fb1837e 100644 --- a/src/main/java/top/r3944realms/superleadrope/content/command/LeashDataCommand.java +++ b/src/main/java/top/r3944realms/superleadrope/content/command/LeashDataCommand.java @@ -16,13 +16,525 @@ package top.r3944realms.superleadrope.content.command; import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.arguments.*; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.builder.RequiredArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.commands.arguments.*; +import net.minecraft.commands.arguments.coordinates.BlockPosArgument; +import net.minecraft.commands.arguments.selector.EntitySelector; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.Entity; +import org.jetbrains.annotations.Nullable; +import top.r3944realms.superleadrope.CommonEventHandler; +import top.r3944realms.superleadrope.SuperLeadRope; +import top.r3944realms.superleadrope.content.capability.inter.ILeashData; +import top.r3944realms.superleadrope.content.entity.SuperLeashKnotEntity; +import top.r3944realms.superleadrope.content.gamerule.server.CreateSuperLeashKnotEntityIfAbsent; +import top.r3944realms.superleadrope.core.register.SLPGameruleRegistry; +import top.r3944realms.superleadrope.util.capability.LeashDataAPI; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +import static top.r3944realms.superleadrope.content.command.Command.*; +import static top.r3944realms.superleadrope.content.command.Command.SHOULD_USE_PREFIX; public class LeashDataCommand { - // 获取Data - // 设置Data - // Holder> - // 设置对应目标的 最大长度 最长断裂距离 保持不断裂时间刻 + public static final String SLP_LEASH_MESSAGE_ = SuperLeadRope.MOD_ID + ".command.leash.message."; + public static final String LEASH_DATA_GET_ = SLP_LEASH_MESSAGE_ + ".get.", + TITLE = LEASH_DATA_GET_ + "title", + TOTAL = LEASH_DATA_GET_ + "total", + BLOCK = LEASH_DATA_GET_ + "block", + UUID = LEASH_DATA_GET_ + "uuid", + MAX = LEASH_DATA_GET_ + "max", + ELASTIC = LEASH_DATA_GET_ + "elastic", + KEEP = LEASH_DATA_GET_ + "keep", + RESERVED = LEASH_DATA_GET_ + "reserved" + ; public static void register(CommandDispatcher dispatcher) { + @Nullable List> nodeList = SHOULD_USE_PREFIX ? null : new ArrayList<>(); + LiteralArgumentBuilder literalArgumentBuilder = Commands.literal(PREFIX); + LiteralArgumentBuilder $$leashDataRoot = getLiterArgumentBuilderOfCSS("leashdata", !SHOULD_USE_PREFIX, nodeList); + RequiredArgumentBuilder $$$add$holder = Commands.argument("holder", EntityArgument.entity()) + .executes(LeashDataCommand::addLeash) + .then(Commands.argument("maxDistance", DoubleArgumentType.doubleArg(1.0, 256.0)) + .executes(context -> addLeash(context, + DoubleArgumentType.getDouble(context, "maxDistance"))) + .then(Commands.argument("elasticDistance", DoubleArgumentType.doubleArg(1.0, 128.0)) + .executes(context -> addLeash(context, + DoubleArgumentType.getDouble(context, "maxDistance"), + DoubleArgumentType.getDouble(context, "elasticDistance"))) + .then(Commands.argument("keepTicks", IntegerArgumentType.integer(0)) + .executes(context -> addLeash(context, + DoubleArgumentType.getDouble(context, "maxDistance"), + DoubleArgumentType.getDouble(context, "elasticDistance"), + IntegerArgumentType.getInteger(context, "keepTicks"))) + .then(Commands.argument("reserved", StringArgumentType.string()) + .executes(context -> addLeash(context, + DoubleArgumentType.getDouble(context, "maxDistance"), + DoubleArgumentType.getDouble(context, "elasticDistance"), + IntegerArgumentType.getInteger(context, "keepTicks"), + StringArgumentType.getString(context, "reserved"))) + ) + ) + ) + ); + LiteralArgumentBuilder $$$add$pos = Commands.literal("block") + .then(Commands.argument("pos", BlockPosArgument.blockPos()) + .executes(LeashDataCommand::addBlockLeash) + .then(Commands.argument("maxDistance", DoubleArgumentType.doubleArg(1.0, 256.0)) + .executes(context -> addBlockLeash(context, + DoubleArgumentType.getDouble(context, "maxDistance"))) + .then(Commands.argument("elasticDistance", DoubleArgumentType.doubleArg(1.0, 128.0)) + .executes(context -> addBlockLeash(context, + DoubleArgumentType.getDouble(context, "maxDistance"), + DoubleArgumentType.getDouble(context, "elasticDistance"), 0, "")) + .then(Commands.argument("keepTicks", IntegerArgumentType.integer(0)) + .executes(context -> addBlockLeash(context, + DoubleArgumentType.getDouble(context, "maxDistance"), + DoubleArgumentType.getDouble(context, "elasticDistance"), + IntegerArgumentType.getInteger(context, "keepTicks"))) + .then(Commands.argument("reserved", StringArgumentType.string()) + .executes(context -> addBlockLeash(context, + DoubleArgumentType.getDouble(context, "maxDistance"), + DoubleArgumentType.getDouble(context, "elasticDistance"), + IntegerArgumentType.getInteger(context, "keepTicks"), + StringArgumentType.getString(context, "reserved"))) + ) + ) + ) + ) + ); + LiteralArgumentBuilder $$$add = Commands.literal("add") + .then(Commands.argument("target", EntityArgument.entities()) + // 实体拴绳 + .then($$$add$holder) + + // 方块拴绳 + .then($$$add$pos) + ); + LiteralArgumentBuilder $$$remove = Commands.literal("remove") + .then(Commands.argument("target", EntityArgument.entities()) + // 移除特定实体拴绳 + .then(Commands.argument("holder", EntityArgument.entity()) + .executes(LeashDataCommand::removeLeash) + ) + + // 移除方块拴绳 + .then(Commands.literal("block") + .then(Commands.argument("pos", BlockPosArgument.blockPos()) + .executes(LeashDataCommand::removeBlockLeash) + ) + ) + + // 批量移除 + .then(Commands.literal("all") + .executes(LeashDataCommand::removeAllLeashes) + ) + .then(Commands.literal("holders") + .executes(LeashDataCommand::removeAllHolderLeashes) + ) + .then(Commands.literal("blocks") + .executes(LeashDataCommand::removeAllBlockLeashes) + ) + ); + LiteralArgumentBuilder $$$transfer = Commands.literal("transfer") + .then(Commands.argument("target", EntityArgument.entities()) + // 实体到实体转移 + .then(Commands.argument("from", EntityArgument.entity()) + .then(Commands.argument("to", EntityArgument.entity()) + .executes(LeashDataCommand::transferLeash) + .then(Commands.argument("reserved", StringArgumentType.string()) + .executes(context -> transferLeash(context, + StringArgumentType.getString(context, "reserved"))) + ) + ) + ) + + // 方块到实体转移 + .then(Commands.literal("fromBlock") + .then(Commands.argument("fromPos", BlockPosArgument.blockPos()) + .then(Commands.argument("to", EntityArgument.entity()) + .executes(LeashDataCommand::transferFromBlock) + .then(Commands.argument("reserved", StringArgumentType.string()) + .executes(context -> transferFromBlock(context, + StringArgumentType.getString(context, "reserved"))) + ) + ) + ) + ) + ); + RequiredArgumentBuilder $$$set$holder = Commands.argument("holder", EntityArgument.entity()) + // 设置最大距离 + .then(Commands.literal("maxDistance") + .then(Commands.argument("distance", DoubleArgumentType.doubleArg(1.0, 256.0)) + .executes(LeashDataCommand::setMaxDistance) + .then(Commands.argument("keepTicks", IntegerArgumentType.integer(0)) + .executes(context -> setMaxDistance(context, + IntegerArgumentType.getInteger(context, "keepTicks"))) + .then(Commands.argument("reserved", StringArgumentType.string()) + .executes(context -> setMaxDistance(context, + IntegerArgumentType.getInteger(context, "keepTicks"), + StringArgumentType.getString(context, "reserved"))) + ) + ) + ) + ) + + // 设置弹性距离 + .then(Commands.literal("elasticDistance") + .then(Commands.argument("distance", DoubleArgumentType.doubleArg(1.0, 128.0)) + .executes(context -> setElasticDistance(context, 0, "")) + .then(Commands.argument("keepTicks", IntegerArgumentType.integer(0)) + .executes(context -> setElasticDistance(context, + IntegerArgumentType.getInteger(context, "keepTicks"), "")) + .then(Commands.argument("reserved", StringArgumentType.string()) + .executes(context -> setElasticDistance(context, + IntegerArgumentType.getInteger(context, "keepTicks"), + StringArgumentType.getString(context, "reserved"))) + ) + ) + ) + ); + LiteralArgumentBuilder $$$set$pos = Commands.literal("block") + .then(Commands.argument("pos", BlockPosArgument.blockPos()) + // 设置最大距离 + .then(Commands.literal("maxDistance") + .then(Commands.argument("distance", DoubleArgumentType.doubleArg(1.0, 256.0)) + .executes(LeashDataCommand::setBlockMaxDistance) + .then(Commands.argument("keepTicks", IntegerArgumentType.integer(0)) + .executes(context -> setBlockMaxDistance(context, + IntegerArgumentType.getInteger(context, "keepTicks"))) + .then(Commands.argument("reserved", StringArgumentType.string()) + .executes(context -> setBlockMaxDistance(context, + IntegerArgumentType.getInteger(context, "keepTicks"), + StringArgumentType.getString(context, "reserved"))) + ) + ) + ) + ) + + // 设置弹性距离 + .then(Commands.literal("elasticDistance") + .then(Commands.argument("distance", DoubleArgumentType.doubleArg(1.0, 128.0)) + .executes(LeashDataCommand::setBlockElasticDistance) + .then(Commands.argument("keepTicks", IntegerArgumentType.integer(0)) + .executes(context -> setBlockElasticDistance(context, + IntegerArgumentType.getInteger(context, "keepTicks"))) + .then(Commands.argument("reserved", StringArgumentType.string()) + .executes(context -> setBlockElasticDistance(context, + IntegerArgumentType.getInteger(context, "keepTicks"), + StringArgumentType.getString(context, "reserved"))) + ) + ) + ) + ) + ); + LiteralArgumentBuilder $$$set = Commands.literal("set") + .then(Commands.argument("target", EntityArgument.entities()) + // 实体拴绳设置 + .then($$$set$holder) + + // 方块拴绳设置 + .then($$$set$pos) + ); + LiteralArgumentBuilder $$$applayForces = Commands.literal("applyForces") + .then(Commands.argument("target", EntityArgument.entities()) + .executes(LeashDataCommand::applyForces) + ); + LiteralArgumentBuilder $$$get = Commands.literal("get") + .then(Commands.argument("target", EntityArgument.entities()) + .executes(LeashDataCommand::getLeashData) + ); + $$leashDataRoot + .requires(source -> source.hasPermission(2)) // 需要OP权限 + + // ==================== GET 命令 ==================== + .then($$$get) + + // ==================== ADD 命令 ==================== + .then($$$add) + + // ==================== REMOVE 命令 ==================== + .then($$$remove) + + // ==================== TRANSFER 命令 ==================== + .then($$$transfer) + + // ==================== SET 命令 ==================== + .then($$$set) + + // ==================== APPLY FORCES 命令 ==================== + .then($$$applayForces); + if(SHOULD_USE_PREFIX){ + literalArgumentBuilder.then($$leashDataRoot); + dispatcher.register(literalArgumentBuilder); + } else { + if (nodeList != null) { + nodeList.forEach(dispatcher::register); + } + } } -} + public static final String SET_MAX_DISTANCE = SLP_LEASH_MESSAGE_ + "set.max_distance"; + private static int setMaxDistance(CommandContext context) throws CommandSyntaxException { + return setMaxDistance(context, CommonEventHandler.leashConfigManager.getMaxLeashLength(), ""); + } + private static int setMaxDistance(CommandContext context, double maxDistance) throws CommandSyntaxException { + return setMaxDistance(context, maxDistance, ""); + } + private static int setMaxDistance(CommandContext context, double maxDistance, String reserved) throws CommandSyntaxException { + Collection targets = EntityArgument.getEntities(context, "target"); + Entity holder = EntityArgument.getEntity(context, "holder"); + for (Entity target : targets) { + + } + return -1; + } + public static final String REMOVE_ALL_BLOCK_LEASHES = SLP_LEASH_MESSAGE_ + "remove.all_block_leashes"; + private static int removeAllBlockLeashes(CommandContext context) throws CommandSyntaxException { + return -1; + } + public static final String REMOVE_ALL_HOLDER_LEASHES = SLP_LEASH_MESSAGE_ + "remove.all_holder_leashes"; + private static int removeAllHolderLeashes(CommandContext context) throws CommandSyntaxException { + return -1; + } + public static final String TRANSFER_FROM_BLOCK = SLP_LEASH_MESSAGE_ + "transfer.from_block"; + private static int transferFromBlock(CommandContext context) throws CommandSyntaxException { + return transferFromBlock(context, ""); + } + private static int transferFromBlock(CommandContext context, String reserved) throws CommandSyntaxException { + return -1; + } + public static final String SET_ELASTIC_DISTANCE = SLP_LEASH_MESSAGE_ + "set.elastic_distance"; + private static int setElasticDistance(CommandContext context) throws CommandSyntaxException { + return setElasticDistance(context, 0 ,""); + } + private static int setElasticDistance(CommandContext context, int keepTicks) throws CommandSyntaxException { + return setElasticDistance(context, keepTicks ,""); + } + private static int setElasticDistance(CommandContext context, int keepTicks, String reserved) throws CommandSyntaxException { + return -1; + } + public static final String SET_BLOCK_MAX_DISTANCE = SLP_LEASH_MESSAGE_ + "set.block_max_distance"; + private static int setBlockMaxDistance(CommandContext context) throws CommandSyntaxException { + return setBlockMaxDistance(context, 0 ,""); + } + private static int setBlockMaxDistance(CommandContext context, int keepTicks) throws CommandSyntaxException { + return setBlockMaxDistance(context, keepTicks ,""); + } + private static int setBlockMaxDistance(CommandContext context, int keepTicks, String reserved) throws CommandSyntaxException { + return -1; + } + public static final String SET_BLOCK_ELASTIC_DISTANCE = SLP_LEASH_MESSAGE_ + "set.block_elastic_distance"; + private static int setBlockElasticDistance(CommandContext context) throws CommandSyntaxException { + return setBlockElasticDistance(context, 0 ,""); + } + private static int setBlockElasticDistance(CommandContext context, int keepTicks) throws CommandSyntaxException { + return setBlockElasticDistance(context, keepTicks ,""); + } + private static int setBlockElasticDistance(CommandContext context, int keepTicks, String reserved) throws CommandSyntaxException { + return -1; + } + + // ==================== 命令执行方法 ==================== + + private static int getLeashData(CommandContext context) throws CommandSyntaxException { + Collection targets = EntityArgument.getEntities(context, "target"); + CommandSourceStack source = context.getSource(); + + for (Entity target : targets) { + Collection leashes = LeashDataAPI.QueryOperations.getAllLeashes(target); + + source.sendSuccess(() -> Component.literal("=== Leash Data for " + target.getName().getString() + " ==="), false); + source.sendSuccess(() -> Component.literal("Total leashes: " + leashes.size()), false); + // TODO:翻译支持 HoverTip实现部分信息简化显示 + for (ILeashData.LeashInfo leash : leashes) { + StringBuilder info = new StringBuilder(); + leash.blockPosOpt().ifPresent(pos -> info.append("Block: ").append(pos.toShortString()).append(" ")); + leash.holderUUIDOpt().ifPresent(uuid -> info.append("UUID: ").append(uuid).append(" ")); + info.append("Max: ").append(leash.maxDistance()).append(" "); + info.append("Elastic: ").append(leash.elasticDistance()).append(" "); + info.append("Keep: ").append(leash.keepLeashTicks()).append("/").append(leash.maxKeepLeashTicks()); + if (!leash.reserved().isEmpty()) { + info.append(" Reserved: ").append(leash.reserved()); + } + + source.sendSuccess(() -> Component.literal(info.toString()), false); + } + } + + return targets.size(); + } + private static int addLeash(CommandContext context) throws CommandSyntaxException { + return addLeash(context, CommonEventHandler.leashConfigManager.getMaxLeashLength(), CommonEventHandler.leashConfigManager.getElasticDistance(), 0, ""); + } + + private static int addLeash(CommandContext context, + double maxDistance) throws CommandSyntaxException { + return addLeash(context, maxDistance, CommonEventHandler.leashConfigManager.getElasticDistance(), 0, ""); + } + + private static int addLeash(CommandContext context, + double maxDistance, double elasticDistance) throws CommandSyntaxException { + return addLeash(context, maxDistance, elasticDistance, 0, ""); + } + private static int addLeash(CommandContext context, + double maxDistance, double elasticDistance, int keepTicks) throws CommandSyntaxException { + return addLeash(context, maxDistance, elasticDistance, keepTicks, ""); + } + private static int addLeash(CommandContext context, + double maxDistance, double elasticDistance, int keepTicks, String reserved) + throws CommandSyntaxException { + Collection targets = EntityArgument.getEntities(context, "target"); + Entity holder = EntityArgument.getEntity(context, "holder"); + CommandSourceStack source = context.getSource(); + List successful = new ArrayList<>(), failed = new ArrayList<>(); + for (Entity target : targets) { + if(LeashDataAPI.LeashOperations.attach(target, holder, maxDistance, elasticDistance, keepTicks, reserved)) { + successful.add(target); + } else failed.add(target); + } +// todo: source.sendSuccess(() -> Component.translatable(/*成功{},失败{}*/), true); + return successful.size(); + } + private static int addBlockLeash(CommandContext context) throws CommandSyntaxException { + return addBlockLeash(context, CommonEventHandler.leashConfigManager.getMaxLeashLength(), CommonEventHandler.leashConfigManager.getElasticDistance(), 0, ""); + } + private static int addBlockLeash(CommandContext context, + double maxDistance) throws CommandSyntaxException { + return addBlockLeash(context, maxDistance, CommonEventHandler.leashConfigManager.getElasticDistance(), 0, ""); + } + private static int addBlockLeash(CommandContext context, + double maxDistance, double elasticDistance, int keepTicks) throws CommandSyntaxException { + return addBlockLeash(context, maxDistance, elasticDistance, keepTicks, ""); + } + private static int addBlockLeash(CommandContext context, + double maxDistance, double elasticDistance, int keepTicks, String reserved) + throws CommandSyntaxException { + Collection targets = EntityArgument.getEntities(context, "target"); + BlockPos pos = BlockPosArgument.getBlockPos(context, "pos"); + CommandSourceStack source = context.getSource(); + ServerLevel level = source.getLevel(); + SuperLeashKnotEntity knotEntity = SuperLeashKnotEntity.get(level, pos) + .or(() -> { + if (SLPGameruleRegistry.getGameruleBoolValue(level, CreateSuperLeashKnotEntityIfAbsent.NAME_KEY)) + return Optional.of(SuperLeashKnotEntity.createKnot(level, pos, true)); + else return Optional.empty(); + }).orElse(null); + if (knotEntity == null) { +// todo: source.sendFailure(Component.translatable(/*失败,目标上无拴绳结*/)); + return -1; + } + List successful = new ArrayList<>(), failed = new ArrayList<>(); + for (Entity target : targets) { + if(LeashDataAPI.LeashOperations.attach(target, knotEntity, maxDistance, elasticDistance, keepTicks, reserved)) { + successful.add(target); + } else failed.add(target); + } +// todo: source.sendSuccess(() -> Component.translatable(/*成功{},失败{}*/), true); + return successful.size(); + } + + private static int removeLeash(CommandContext context) throws CommandSyntaxException { + Collection targets = EntityArgument.getEntities(context, "target"); + Entity holder = EntityArgument.getEntity(context, "holder"); + CommandSourceStack source = context.getSource(); + int successCount = 0; + + for (Entity target : targets) { + boolean success = LeashDataAPI.LeashOperations.detach(target, holder); + + if (success) { + successCount++; + source.sendSuccess(() -> Component.literal("Removed leash from " + target.getName().getString() + + " held by " + holder.getName().getString()), false); + } else { + source.sendFailure(Component.literal("No leash found for " + holder.getName().getString() + + " on " + target.getName().getString())); + } + } + + return successCount; + } + + private static int removeBlockLeash(CommandContext context) throws CommandSyntaxException { + Collection targets = EntityArgument.getEntities(context, "target"); + BlockPos pos = BlockPosArgument.getBlockPos(context, "pos"); + CommandSourceStack source = context.getSource(); + int successCount = 0; + + for (Entity target : targets) { + boolean success = LeashDataAPI.LeashOperations.detach(target, pos); + + if (success) { + successCount++; + source.sendSuccess(() -> Component.literal("Removed block leash from " + target.getName().getString() + + " at " + pos.toShortString()), false); + } else { + source.sendFailure(Component.literal("No block leash found at " + pos.toShortString() + + " on " + target.getName().getString())); + } + } + + return successCount; + } + + private static int removeAllLeashes(CommandContext context) throws CommandSyntaxException { + Collection targets = EntityArgument.getEntities(context, "target"); + CommandSourceStack source = context.getSource(); + + for (Entity target : targets) { + LeashDataAPI.LeashOperations.detachAll(target); + source.sendSuccess(() -> Component.literal("Removed all leashes from " + target.getName().getString()), false); + } + + return targets.size(); + } + + private static int transferLeash(CommandContext context) throws CommandSyntaxException { + return transferLeash(context, ""); + } + private static int transferLeash(CommandContext context, String reserved) + throws CommandSyntaxException { + Collection targets = EntityArgument.getEntities(context, "target"); + Entity from = EntityArgument.getEntity(context, "from"); + Entity to = EntityArgument.getEntity(context, "to"); + CommandSourceStack source = context.getSource(); + int successCount = 0; + + for (Entity target : targets) { + boolean success = reserved.isEmpty() ? + LeashDataAPI.TransferOperations.transfer(target, from, to) : + LeashDataAPI.TransferOperations.transfer(target, from, to, reserved); + + if (success) { + successCount++; + source.sendSuccess(() -> Component.literal("Transferred leash from " + from.getName().getString() + + " to " + to.getName().getString() + " for " + target.getName().getString()), false); + } else { + source.sendFailure(Component.literal("Failed to transfer leash for " + target.getName().getString())); + } + } + + return successCount; + } + + private static int applyForces(CommandContext context) throws CommandSyntaxException { + Collection targets = EntityArgument.getEntities(context, "target"); + CommandSourceStack source = context.getSource(); + + for (Entity target : targets) { + LeashDataAPI.PhysicsOperations.applyForces(target); + source.sendSuccess(() -> Component.literal("Applied leash forces to " + target.getName().getString()), false); + } + + return targets.size(); + } +} \ No newline at end of file diff --git a/src/main/java/top/r3944realms/superleadrope/content/entity/SuperLeashKnotEntity.java b/src/main/java/top/r3944realms/superleadrope/content/entity/SuperLeashKnotEntity.java index f7d9fa4..e62a298 100644 --- a/src/main/java/top/r3944realms/superleadrope/content/entity/SuperLeashKnotEntity.java +++ b/src/main/java/top/r3944realms/superleadrope/content/entity/SuperLeashKnotEntity.java @@ -34,13 +34,13 @@ import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.gameevent.GameEvent; import net.minecraft.world.phys.AABB; import org.jetbrains.annotations.NotNull; -import top.r3944realms.superleadrope.content.capability.CapabilityHandler; import top.r3944realms.superleadrope.content.capability.impi.LeashDataImpl; import top.r3944realms.superleadrope.core.register.SLPEntityTypes; -import top.r3944realms.superleadrope.util.capability.LeashUtil; +import top.r3944realms.superleadrope.util.capability.LeashDataAPI; import java.util.Arrays; import java.util.List; +import java.util.Optional; import java.util.concurrent.atomic.AtomicBoolean; public class SuperLeashKnotEntity extends LeashFenceKnotEntity { @@ -83,7 +83,7 @@ public class SuperLeashKnotEntity extends LeashFenceKnotEntity { this.playSound(SoundEvents.LEASH_KNOT_BREAK); List entities = LeashDataImpl.leashableInArea(this.level(), pos.getCenter(), entity -> LeashDataImpl.isLeashHolder(entity, this)); entities.forEach(entity -> - LeashUtil.getLeashData(entity) + LeashDataAPI.getLeashData(entity) .map(iLeashDataCapability -> iLeashDataCapability.removeLeash(this)) ); } @@ -120,6 +120,27 @@ public class SuperLeashKnotEntity extends LeashFenceKnotEntity { pLevel.addFreshEntity(superLeashKnotEntity1); return superLeashKnotEntity1; } + public static @NotNull Optional get(@NotNull Level level, @NotNull BlockPos pos) { + AABB searchArea = new AABB(pos).inflate(1.0D); + + return level.getEntitiesOfClass(SuperLeashKnotEntity.class, searchArea) + .stream() + .filter(knot -> knot.getPos().equals(pos)) + .findFirst(); + } + + /** + * 创建拴绳结,请不用直接调用这个,除非你知道自己在干上面 + * @return 拴绳结 + */ + public static @NotNull SuperLeashKnotEntity createKnot(@NotNull Level pLevel, @NotNull BlockPos pPos, boolean isEmpty) { + if(isEmpty) { + SuperLeashKnotEntity superLeashKnotEntity1 = new SuperLeashKnotEntity(pLevel, pPos); + pLevel.addFreshEntity(superLeashKnotEntity1); + return superLeashKnotEntity1; + } + throw new IllegalArgumentException("Cannot create Knot Entity of type " + SuperLeashKnotEntity.class.getSimpleName()); + } @Override protected void recalculateBoundingBox() { @@ -169,7 +190,7 @@ public class SuperLeashKnotEntity extends LeashFenceKnotEntity { List entities = LeashDataImpl.leashableInArea(player); for(Entity entity : entities) { if (LeashDataImpl.isLeashHolder(entity, player.getUUID())) - LeashUtil.getLeashData(entity) + LeashDataAPI.getLeashData(entity) .ifPresent(i -> { i.transferLeash(player.getUUID(), this); isTransferLeash.set(true); @@ -182,7 +203,7 @@ public class SuperLeashKnotEntity extends LeashFenceKnotEntity { this.discard(); List entities1 = LeashDataImpl.leashableInArea(this); entities1.forEach(entity -> - LeashUtil.getLeashData(entity) + LeashDataAPI.getLeashData(entity) .ifPresent(iLeashDataCapability -> { iLeashDataCapability.removeLeash(this); isRemoveLeashKnot.set(true); diff --git a/src/main/java/top/r3944realms/superleadrope/content/gamerule/SLPGamerules.java b/src/main/java/top/r3944realms/superleadrope/content/gamerule/SLPGamerules.java index e1941ae..564ea28 100644 --- a/src/main/java/top/r3944realms/superleadrope/content/gamerule/SLPGamerules.java +++ b/src/main/java/top/r3944realms/superleadrope/content/gamerule/SLPGamerules.java @@ -24,12 +24,12 @@ public class SLPGamerules { public static final SLPGameruleRegistry GAMERULE_REGISTRY = SLPGameruleRegistry.INSTANCE; public static final HashMap gamerulesBooleanValuesClient = new HashMap<>(); public static final HashMap gameruleIntegerValuesClient = new HashMap<>(); - public static final String RULE_KEY_PERFix_ = "gamerule." + GAMERULE_PREFIX.toLowerCase(); + public static final String RULE_KEY_PERFiX_ = "gamerule." + GAMERULE_PREFIX.toLowerCase(); public static String getDescriptionKey(Class gameRuleClass) { - return RULE_KEY_PERFix_ + gameRuleClass.getSimpleName() + ".description"; + return RULE_KEY_PERFiX_ + gameRuleClass.getSimpleName() + ".description"; } public static String getDescriptionKey(String gameRuleName) { - return RULE_KEY_PERFix_ + gameRuleName + ".description"; + return RULE_KEY_PERFiX_ + gameRuleName + ".description"; } public static String getGameruleName(Class clazz) { return SLPGamerules.GAMERULE_PREFIX + clazz.getSimpleName(); @@ -39,7 +39,7 @@ public class SLPGamerules { } public static String getNameKey(Class gameRuleClass) { - return RULE_KEY_PERFix_ + gameRuleClass.getSimpleName(); + return RULE_KEY_PERFiX_ + gameRuleClass.getSimpleName(); } } diff --git a/src/main/java/top/r3944realms/superleadrope/content/gamerule/server/CreateSuperLeashKnotEntityIfAbsent.java b/src/main/java/top/r3944realms/superleadrope/content/gamerule/server/CreateSuperLeashKnotEntityIfAbsent.java new file mode 100644 index 0000000..f983a94 --- /dev/null +++ b/src/main/java/top/r3944realms/superleadrope/content/gamerule/server/CreateSuperLeashKnotEntityIfAbsent.java @@ -0,0 +1,33 @@ +/* + * Super Lead rope mod + * Copyright (C) 2025 R3944Realms + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package top.r3944realms.superleadrope.content.gamerule.server; + +import net.minecraft.world.level.GameRules; +import top.r3944realms.superleadrope.content.gamerule.SLPGamerules; + +import static top.r3944realms.superleadrope.content.gamerule.SLPGamerules.GAMERULE_REGISTRY; + +public class CreateSuperLeashKnotEntityIfAbsent { + public static final boolean DEFAULT_VALUE = true; + public static final String ID = SLPGamerules.getGameruleName(CreateSuperLeashKnotEntityIfAbsent.class); + public static final String DESCRIPTION_KEY = SLPGamerules.getDescriptionKey(CreateSuperLeashKnotEntityIfAbsent.class); + public static final String NAME_KEY = SLPGamerules.getNameKey(CreateSuperLeashKnotEntityIfAbsent.class); + public static final GameRules.Category CATEGORY = GameRules.Category.PLAYER; + + public static void register() { + GAMERULE_REGISTRY.registerGamerule(ID, CATEGORY, DEFAULT_VALUE); + } +} diff --git a/src/main/java/top/r3944realms/superleadrope/content/item/SuperLeadRopeItem.java b/src/main/java/top/r3944realms/superleadrope/content/item/SuperLeadRopeItem.java index 02d6911..f4341e2 100644 --- a/src/main/java/top/r3944realms/superleadrope/content/item/SuperLeadRopeItem.java +++ b/src/main/java/top/r3944realms/superleadrope/content/item/SuperLeadRopeItem.java @@ -29,15 +29,13 @@ import net.minecraft.world.level.Level; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.gameevent.GameEvent; import net.minecraftforge.common.extensions.IForgeItem; -import net.minecraftforge.common.util.LazyOptional; import org.jetbrains.annotations.NotNull; import top.r3944realms.superleadrope.content.SLPToolTier; -import top.r3944realms.superleadrope.content.capability.CapabilityHandler; import top.r3944realms.superleadrope.content.capability.impi.LeashDataImpl; import top.r3944realms.superleadrope.content.capability.inter.ILeashData; import top.r3944realms.superleadrope.content.entity.SuperLeashKnotEntity; import top.r3944realms.superleadrope.core.register.SLPSoundEvents; -import top.r3944realms.superleadrope.util.capability.LeashUtil; +import top.r3944realms.superleadrope.util.capability.LeashDataAPI; import java.util.List; import java.util.Optional; @@ -83,7 +81,7 @@ public class SuperLeadRopeItem extends TieredItem implements IForgeItem { } public static boolean canUse(ItemStack itemStack) { - return itemStack.getDamageValue() < 974; + return itemStack.getDamageValue() < 1200; } @Override @@ -106,11 +104,10 @@ public class SuperLeadRopeItem extends TieredItem implements IForgeItem { * @param newHolder 新实体 * @param player 明确持有玩家 * @param level 维度世界 - * @param leashStack 拴绳物品实例 * @return 是否成功 */ - public static boolean bindToEntity(Entity newHolder, Player player, Level level, ItemStack leashStack) { - return bindToEntity(newHolder, player, level, player.getOnPos(), leashStack); + public static boolean bindToEntity(Entity newHolder, Player player, Level level) { + return bindToEntity(newHolder, player, level, player.getOnPos()); } /** * 右键蹲下绑定到另一实体上 @@ -118,10 +115,9 @@ public class SuperLeadRopeItem extends TieredItem implements IForgeItem { * @param player 明确持有玩家 * @param level 维度世界 * @param pos 坐标(一般是明确持有玩家的位置) - * @param leashStack 拴绳物品实例 * @return 是否成功 */ - public static boolean bindToEntity(Entity newHolder, Player player, Level level, BlockPos pos, ItemStack leashStack) { + public static boolean bindToEntity(Entity newHolder, Player player, Level level, BlockPos pos) { boolean isSuccess = false; // 查找当前玩家持有的可拴生物 @@ -131,7 +127,7 @@ public class SuperLeadRopeItem extends TieredItem implements IForgeItem { ); for (Entity e : list) { - Optional leashDataOpt = LeashUtil.getLeashData(e); + Optional leashDataOpt = LeashDataAPI.getLeashData(e); if (leashDataOpt.map(i -> i.canBeAttachedTo(newHolder)).orElse(false)) { leashDataOpt.ifPresent(i -> i.transferLeash(player.getUUID(), newHolder)); @@ -182,16 +178,16 @@ public class SuperLeadRopeItem extends TieredItem implements IForgeItem { if (leashStack.isEmpty() || !canUse(leashStack)) { return false; } - knot = SuperLeashKnotEntity.getOrCreateKnot(level, pos); - knot.playPlacementSound(); + knot = SuperLeashKnotEntity.getOrCreateKnot(level, pos); + knot.playPlacementSound(); - SuperLeashKnotEntity finalKnot = knot; - LeashUtil.getLeashData(player).ifPresent(i -> { - if (i.canBeAttachedTo(finalKnot)) { - i.addLeash(finalKnot); - isSuccess.set(true); - } - }); + SuperLeashKnotEntity finalKnot = knot; + LeashDataAPI.getLeashData(player).ifPresent(i -> { + if (i.canBeAttachedTo(finalKnot)) { + if (!level.isClientSide) i.addLeash(finalKnot); + isSuccess.set(true); + } + }); } // 情况二:把已有生物拴到 knot else if (!list.isEmpty()) { @@ -202,9 +198,9 @@ public class SuperLeadRopeItem extends TieredItem implements IForgeItem { } SuperLeashKnotEntity finalKnot = knot; - LeashUtil.getLeashData(e).ifPresent(i -> { + LeashDataAPI.getLeashData(e).ifPresent(i -> { if (i.canBeAttachedTo(finalKnot)) { - i.transferLeash(uuid, finalKnot); + if (!level.isClientSide) i.transferLeash(uuid, finalKnot); isSuccess.set(true); } }); diff --git a/src/main/java/top/r3944realms/superleadrope/core/hook/LeashRenderHook.java b/src/main/java/top/r3944realms/superleadrope/core/hook/LeashRenderHook.java new file mode 100644 index 0000000..6173335 --- /dev/null +++ b/src/main/java/top/r3944realms/superleadrope/core/hook/LeashRenderHook.java @@ -0,0 +1,44 @@ +/* + * Super Lead rope mod + * Copyright (C) 2025 R3944Realms + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package top.r3944realms.superleadrope.core.hook; + +import net.minecraft.client.renderer.culling.Frustum; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.Mob; +import top.r3944realms.superleadrope.SuperLeadRope; +import top.r3944realms.superleadrope.util.capability.LeashDataAPI; + +import java.util.Optional; +import java.util.concurrent.atomic.AtomicBoolean; + +public class LeashRenderHook { + public static boolean shouldRenderExtra(Mob mob, Frustum camera) { + SuperLeadRope.logger.debug("[SuperLeash] Checking entity: {} at position: {}, {}, {}", mob.getName().getString(), mob.getX(), mob.getY(), mob.getZ()); + AtomicBoolean flag = new AtomicBoolean(false); + LeashDataAPI.getLeashData(mob).ifPresent(i -> { + i.getAllLeashes().forEach(j -> { + Optional i1 = j.holderIdOpt(); + if (i1.isPresent()) { + Entity entity = mob.level().getEntity(i1.get()); + if (entity != null) { + flag.set(camera.isVisible(entity.getBoundingBoxForCulling())); + } + } + }); + }); + return flag.get(); + } +} \ No newline at end of file diff --git a/src/main/java/top/r3944realms/superleadrope/core/leash/LeashInteractHandler.java b/src/main/java/top/r3944realms/superleadrope/core/leash/LeashInteractHandler.java index 0d2d455..1f98b1f 100644 --- a/src/main/java/top/r3944realms/superleadrope/core/leash/LeashInteractHandler.java +++ b/src/main/java/top/r3944realms/superleadrope/core/leash/LeashInteractHandler.java @@ -16,6 +16,8 @@ package top.r3944realms.superleadrope.core.leash; +import net.minecraft.sounds.SoundEvent; +import net.minecraft.sounds.SoundEvents; import net.minecraft.sounds.SoundSource; import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; @@ -24,15 +26,16 @@ import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.Level; import net.minecraft.world.level.gameevent.GameEvent; -import net.minecraftforge.common.util.LazyOptional; import net.minecraftforge.event.entity.player.AttackEntityEvent; import net.minecraftforge.event.entity.player.PlayerInteractEvent; -import top.r3944realms.superleadrope.content.capability.CapabilityHandler; import top.r3944realms.superleadrope.content.capability.impi.LeashDataImpl; import top.r3944realms.superleadrope.content.capability.inter.ILeashData; import top.r3944realms.superleadrope.content.item.SuperLeadRopeItem; import top.r3944realms.superleadrope.core.register.SLPItems; import top.r3944realms.superleadrope.core.register.SLPSoundEvents; +import top.r3944realms.superleadrope.util.capability.LeashDataAPI; + +import java.util.Optional; public class LeashInteractHandler { //只有玩家可以互动触发(其它的暂不支持(考虑到0 Mixin) @@ -48,6 +51,7 @@ public class LeashInteractHandler { event.setCanceled(true); event.setCancellationResult(InteractionResult.SUCCESS); } + return; } if (hand == InteractionHand.OFF_HAND) { @@ -57,8 +61,8 @@ public class LeashInteractHandler { if (!LeashDataImpl.isLeashable(target)) { return; } - LazyOptional LeashCap = target.getCapability(CapabilityHandler.LEASH_DATA_CAP); - if (!LeashCap.isPresent()) { + Optional LeashCap = LeashDataAPI.getLeashData(target); + if (LeashCap.isEmpty()) { return; } @@ -72,7 +76,7 @@ public class LeashInteractHandler { ) { - boolean isSuccess = SuperLeadRopeItem.bindToEntity(target, player, player.level(), player.getOnPos(), ItemStack.EMPTY); + boolean isSuccess = SuperLeadRopeItem.bindToEntity(target, player, player.level(), player.getOnPos()); if (isSuccess) { event.setCanceled(true); event.setCancellationResult(InteractionResult.SUCCESS); @@ -103,7 +107,7 @@ public class LeashInteractHandler { boolean success = iLeashDataCapability.addLeash(player); if (success) { if(!player.isCreative()) - itemStack.hurtAndBreak(24, player, e->{}); + itemStack.hurtAndBreak(24, player, e-> e.level().playSound(null, player.getOnPos(), SoundEvents.ITEM_BREAK, SoundSource.PLAYERS)); level.playSound(null, target.getOnPos(), SLPSoundEvents.LEAD_TIED.get(), SoundSource.PLAYERS); event.setCanceled(true); event.setCancellationResult(InteractionResult.SUCCESS); @@ -123,7 +127,7 @@ public class LeashInteractHandler { } } else { if (flag) { - target.getCapability(CapabilityHandler.LEASH_DATA_CAP).ifPresent(leashDataCapability -> { + LeashDataAPI.getLeashData(target).ifPresent(leashDataCapability -> { if (leashDataCapability.hasLeash()){ int size = leashDataCapability.getAllLeashes().size(); if (player.isSecondaryUseActive()) diff --git a/src/main/java/top/r3944realms/superleadrope/datagen/data/SLPLangKeyValue.java b/src/main/java/top/r3944realms/superleadrope/datagen/data/SLPLangKeyValue.java index 32ab891..55460f7 100644 --- a/src/main/java/top/r3944realms/superleadrope/datagen/data/SLPLangKeyValue.java +++ b/src/main/java/top/r3944realms/superleadrope/datagen/data/SLPLangKeyValue.java @@ -18,7 +18,9 @@ package top.r3944realms.superleadrope.datagen.data; import net.minecraft.world.item.Item; import net.minecraft.world.level.block.Block; import org.jetbrains.annotations.NotNull; +import top.r3944realms.superleadrope.content.command.LeashDataCommand; import top.r3944realms.superleadrope.content.command.MotionCommand; +import top.r3944realms.superleadrope.content.gamerule.server.CreateSuperLeashKnotEntityIfAbsent; import top.r3944realms.superleadrope.content.gamerule.server.TeleportWithLeashedEntities; import top.r3944realms.superleadrope.content.item.EternalPotatoItem; import top.r3944realms.superleadrope.core.register.SLPEntityTypes; @@ -42,21 +44,24 @@ public enum SLPLangKeyValue { "Eternal Potato", "永恒土豆", "永恆馬鈴薯", "不滅薯", true ), - EP_TOOLTIP_TITLE(EternalPotatoItem.getDescKey("title"), ModPartEnum.DESCRIPTION, + EP_TOOLTIP_TITLE( + EternalPotatoItem.getDescKey("title"), ModPartEnum.DESCRIPTION, "§6Mythical Item §7- §6Eternal Potato", "§6神话物品 §7- §6永恒土豆", "§6神話物品 §7- §6永恒土豆", "§6永恒土豆 §7- §6传奇之物" ), - EP_DESC_TOOLTIP(EternalPotatoItem.getDescKey("desc"), ModPartEnum.DESCRIPTION, + EP_DESC_TOOLTIP( + EternalPotatoItem.getDescKey("desc"), ModPartEnum.DESCRIPTION, "§7Symbol of server-wide contract, cannot be discarded", "§7象征全服契约,不可丢弃", "§7象徵全服契約,不可丟棄", "§7象征全服契约,绝不可弃" ), - EP_BIND_OWNER(EternalPotatoItem.getDescKey("bind_owner"), ModPartEnum.DESCRIPTION, + EP_BIND_OWNER( + EternalPotatoItem.getDescKey("bind_owner"), ModPartEnum.DESCRIPTION, "§bBound Owner: §f%s", "§b绑定主人: §f%s", "§b綁定主人: §f%s", @@ -70,42 +75,48 @@ public enum SLPLangKeyValue { "§c尚未绑定主人" ), - EP_OBLIGATION_TOOLTIP(EternalPotatoItem.getDescKey("obligation"), ModPartEnum.DESCRIPTION, + EP_OBLIGATION_TOOLTIP( + EternalPotatoItem.getDescKey("obligation"), ModPartEnum.DESCRIPTION, "§7Daily obligations remaining: §a%d §c(+%d§c overdue)", "§7今日剩余义务: §a%d §c(+%d §c逾期未完成)", "§7今日剩餘義務: §a%d §c(+%d §c逾期未完成)", "§7今日责务尚余: §a%d §c(+%d §c逾期未尽)" ), - EP_PUNISH_TOOLTIP(EternalPotatoItem.getDescKey("punish"), ModPartEnum.DESCRIPTION, + EP_PUNISH_TOOLTIP( + EternalPotatoItem.getDescKey("punish"), ModPartEnum.DESCRIPTION, "§cOverdue punishments: §4%d §7(will be applied), grace exceeded: §4%d", "§c逾期未完成责务: §4%d §7(将会受罚),超出宽限数: §4%d", "§c逾期未完成责務: §4%d §7(將會受罰),超出寬限數: §4%d", "§c逾期责务尚未完成: §4%d §7(將受懲罰),超出寬限數: §4%d" ), - EP_OBLIGATION_INFO(EternalPotatoItem.getMsgKey("obligation_info"), ModPartEnum.MESSAGE, + EP_OBLIGATION_INFO( + EternalPotatoItem.getMsgKey("obligation_info"), ModPartEnum.MESSAGE, "§e[Eternal Potato] §fThis is the server-wide shared person, remaining obligations today: §a%d§f.", "§e[永恒土豆] §f这是全服共有之人,今日义务剩余:§a%d§f次。", "§e[永恒土豆] §f這是全服共有之人,今日義務剩餘:§a%d§f次。", "§e[永恒土豆] §f此为全服共享之人,今日责务尚余:§a%d§f次。" ), - EP_POTATO_HEAL(EternalPotatoItem.getMsgKey("potato_heal"), ModPartEnum.MESSAGE, + EP_POTATO_HEAL( + EternalPotatoItem.getMsgKey("potato_heal"), ModPartEnum.MESSAGE, "§aThe power of the Eternal Potato comforts you, it won't disappear.", "§a永恒土豆的力量抚慰了你,但它不会消失。", "§a永恆土豆的力量撫慰了你,但它不會消失。", "§a永恒土豆之力慰心,永不消逝。" ), - EP_CANNOT_DROP(EternalPotatoItem.getMsgKey("cannot_drop"), ModPartEnum.MESSAGE, + EP_CANNOT_DROP( + EternalPotatoItem.getMsgKey("cannot_drop"), ModPartEnum.MESSAGE, "§cThe Eternal Potato cannot be dropped! +%d punishments.", "§c永恒土豆是不可丢弃的,惩罚数加%d!", "§c永恆土豆不可丟棄,懲罰數加%d!", "§c永恒土豆不可丟棄,懲罰數增加%d!" ), - EP_BIND_MSG(EternalPotatoItem.getMsgKey("bind_msg"), ModPartEnum.MESSAGE, + EP_BIND_MSG( + EternalPotatoItem.getMsgKey("bind_msg"), ModPartEnum.MESSAGE, "§6Bound to you as the server-wide shared person.", "§6已与你绑定,成为全服共有之人。", "§6已與你綁定,成為全服共有之人。", @@ -114,21 +125,24 @@ public enum SLPLangKeyValue { - EP_OBLIGATION_DONE(EternalPotatoItem.getMsgKey("obligation_done"), ModPartEnum.MESSAGE, + EP_OBLIGATION_DONE( + EternalPotatoItem.getMsgKey("obligation_done"), ModPartEnum.MESSAGE, "§eObligation completed, remaining: §a%d§e", "§e义务完成一次,剩余 §a%d §e次。", "§e義務完成一次,剩餘 §a%d §e次。", "§e责务完成,尚余 §a%d §e次。" ), - EP_OBLIGATION_FULL(EternalPotatoItem.getMsgKey("obligation_full"), ModPartEnum.MESSAGE, + EP_OBLIGATION_FULL( + EternalPotatoItem.getMsgKey("obligation_full"), ModPartEnum.MESSAGE, "§aAll obligations completed today!", "§a今日义务已全部完成!", "§a今日義務已全部完成!", "§a今日责务尽矣!" ), - EP_PUNISH_MSG(EternalPotatoItem.getMsgKey("punish_msg"), ModPartEnum.MESSAGE, + EP_PUNISH_MSG( + EternalPotatoItem.getMsgKey("punish_msg"), ModPartEnum.MESSAGE, "§cYesterday obligations incomplete, punished!", "§c未完成昨日义务,受到惩罚!", "§c未完成昨日義務,受到懲罰!", @@ -142,14 +156,16 @@ public enum SLPLangKeyValue { "受罚倒数:§a%d §f瞬" ), - EP_PICKUP_NOT_OWNER(EternalPotatoItem.getMsgKey("pickup_not_owner"), ModPartEnum.MESSAGE, + EP_PICKUP_NOT_OWNER( + EternalPotatoItem.getMsgKey("pickup_not_owner"), ModPartEnum.MESSAGE, "§cYou are not the rightful owner and cannot pick this up!", "§c非绑定主人无法拾取此物品!", "§c非綁定主人無法拾取此物品!", "§c非汝所主,勿取!" ), - EP_PUNISH_NOT_OWNER(EternalPotatoItem.getMsgKey("punish_not_owner"), ModPartEnum.MESSAGE, + EP_PUNISH_NOT_OWNER( + EternalPotatoItem.getMsgKey("punish_not_owner"), ModPartEnum.MESSAGE, "§cYou are not the rightful owner, punished by lightning!", "§c非绑定主人使用,受到闪电惩罚!", "§c非綁定主人使用,受到閃電懲罰!", @@ -188,13 +204,29 @@ public enum SLPLangKeyValue { SLPEntityTypes.getEntityNameKey("super_lead_knot"), ModPartEnum.ENTITY, "Super Lead Knot", "超级拴绳结", "超級拴繩結", "神駒羈縻索結" ), - TELEPORT_WITH_LEASHED_ENTITIES_NAME(TeleportWithLeashedEntities.NAME_KEY, ModPartEnum.GAME_RULE, + TELEPORT_WITH_LEASHED_ENTITIES_NAME( + TeleportWithLeashedEntities.NAME_KEY, ModPartEnum.GAME_RULE, "Teleport leashed player with player holder", "被拴实体随玩家持有者传送", "被拴实体随玩家持有者傳送", "繫畜隨持者傳送" ), - TELEPORT_WITH_LEASHED_DESCRIPTION(TeleportWithLeashedEntities.DESCRIPTION_KEY, ModPartEnum.DESCRIPTION, + CREATE_SUPER_LEASH_KNOT_ENTITY_IF_ABSENT_NAME( + CreateSuperLeashKnotEntityIfAbsent.NAME_KEY, ModPartEnum.NAME, + "Create Leash Fence Knot Entity if absent", + "如果缺失则创建超级拴绳结", + "如果缺失則創建超級拴繩結", + "若阙则创超级繫绳结" + ), + CREATE_SUPER_LEASH_KNOT_ENTITY_IF_ABSENT_DESCRIPTION( + CreateSuperLeashKnotEntityIfAbsent.DESCRIPTION_KEY, ModPartEnum.DESCRIPTION, + "Create LeashKnot Entity if it's absent on fence or other supported positions", + "如果在栅栏等支持处缺失超级拴绳结,则创建它", + "如果在柵欄等支持處缺失超級拴繩結,則創建它", + "若栅等支处阙超级繫绳结,则创之" + ), + TELEPORT_WITH_LEASHED_DESCRIPTION( + TeleportWithLeashedEntities.DESCRIPTION_KEY, ModPartEnum.DESCRIPTION, "Holder will teleport with their leashed players ", "传送时将被拴实体与持有者一起传送", "將被拴实体將隨持有者一起傳送", @@ -221,6 +253,90 @@ public enum SLPLangKeyValue { "§b倍乘成功.§a%s§7:§f[§e加速§7:(§a%.2f§7,§a%.2f§7,§a%.2f§7)§f]§r", "§b倍乘既成.§a%s§7:§f[§e速勢§7:(§a%.2f§7,§a%.2f§7,§a%.2f§7)§f]§r" ), + MESSAGE_LEASHDATA_GET_TITLE( + LeashDataCommand.TITLE, ModPartEnum.COMMAND, + "=== Leash Data for %s ===", + "=== %s 的拴绳数据 ===", + "=== %s 的拴繩數據 ===", + "=== %s 之繫繩數據 ===" + ), + MESSAGE_LEASHDATA_GET_TOTAL( + LeashDataCommand.TOTAL, ModPartEnum.COMMAND, + "Total leashes: %d", + "总拴绳数: %d", + "總拴繩數: %d", + "繫繩總數: %d" + ), + MESSAGE_LEASHDATA_GET_BLOCK( + LeashDataCommand.BLOCK, ModPartEnum.COMMAND, + "§7Block: §e%s", + "§7方块: §e%s", + "§7方塊: §e%s", + "§7磚石: §e%s" + ), + MESSAGE_LEASHDATA_GET_UUID( + LeashDataCommand.UUID, ModPartEnum.COMMAND, + "§7UUID: §b%s", + "§7UUID: §b%s", + "§7UUID: §b%s", + "§7UUID: §b%s" + ), + MESSAGE_LEASHDATA_GET_MAX( + LeashDataCommand.MAX, ModPartEnum.COMMAND, + "§7Max: §a%.1f", + "§7最大距离: §a%.1f", + "§7最大距離: §a%.1f", + "§7極距: §a%.1f" + ), + MESSAGE_LEASHDATA_GET_ELASTIC( + LeashDataCommand.ELASTIC, ModPartEnum.COMMAND, + "§7Elastic: §6%.1f", + "§7弹性距离: §6%.1f", + "§7彈性距離: §6%.1f", + "§7彈距: §6%.1f" + ), + MESSAGE_LEASHDATA_GET_KEEP( + LeashDataCommand.KEEP, ModPartEnum.COMMAND, + "§7Keep: §c%d§7/§c%d", + "§7保持: §c%d§7/§c%d", + "§7保持: §c%d§7/§c%d", + "§7持時: §c%d§7/§c%d" + ), + MESSAGE_LEASHDATA_GET_RESERVED( + LeashDataCommand.RESERVED, ModPartEnum.COMMAND, + "§7Reserved: §d%s", + "§7保留字段: §d%s", + "§7保留字段: §d%s", + "§7備註: §d%s" + ), + MESSAGE_LEASHDATA_ADD_SUCCESS( + "command.leashdata.add.success", ModPartEnum.COMMAND, + "§bAdded leash successfully. §a%s §7→ §e%s", + "§b添加拴绳成功. §a%s §7→ §e%s", + "§b添加拴繩成功. §a%s §7→ §e%s", + "§b繫繩既添. §a%s §7→ §e%s" + ), + MESSAGE_LEASHDATA_REMOVE_SUCCESS( + "command.leashdata.remove.success", ModPartEnum.COMMAND, + "§bRemoved leash successfully. §a%s §7- §e%s", + "§b移除拴绳成功. §a%s §7- §e%s", + "§b移除拴繩成功. §a%s §7- §e%s", + "§b繫繩既除. §a%s §7- §e%s" + ), + MESSAGE_LEASHDATA_TRANSFER_SUCCESS( + "command.leashdata.transfer.success", ModPartEnum.COMMAND, + "§bTransferred leash successfully. §a%s §7→ §e%s §7→ §6%s", + "§b转移拴绳成功. §a%s §7→ §e%s §7→ §6%s", + "§b轉移拴繩成功. §a%s §7→ §e%s §7→ §6%s", + "§b繫繩既移. §a%s §7→ §e%s §7→ §6%s" + ), + MESSAGE_LEASHDATA_SET_SUCCESS( + "command.leashdata.set.success", ModPartEnum.COMMAND, + "§bSet leash property successfully. §a%s §7: §e%s §7= §6%.1f", + "§b设置拴绳属性成功. §a%s §7: §e%s §7= §6%.1f", + "§b設置拴繩屬性成功. §a%s §7: §e%s §7= §6%.1f", + "§b繫繩性既定. §a%s §7: §e%s §7= §6%.1f" + ); ; private final Supplier supplier; diff --git a/src/main/java/top/r3944realms/superleadrope/network/toClient/LeashDataSyncPacket.java b/src/main/java/top/r3944realms/superleadrope/network/toClient/LeashDataSyncPacket.java index 4038d93..990ff18 100644 --- a/src/main/java/top/r3944realms/superleadrope/network/toClient/LeashDataSyncPacket.java +++ b/src/main/java/top/r3944realms/superleadrope/network/toClient/LeashDataSyncPacket.java @@ -21,7 +21,7 @@ import net.minecraft.nbt.CompoundTag; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.world.entity.Entity; import net.minecraftforge.network.NetworkEvent; -import top.r3944realms.superleadrope.content.capability.CapabilityHandler; +import top.r3944realms.superleadrope.util.capability.LeashDataAPI; import java.util.function.Supplier; @@ -42,7 +42,7 @@ public record LeashDataSyncPacket(int entityId, CompoundTag leashData) { if (level != null) { Entity entity = level.getEntity(msg.entityId); if (entity != null) { - entity.getCapability(CapabilityHandler.LEASH_DATA_CAP).ifPresent(cap -> { + LeashDataAPI.getLeashData(entity).ifPresent(cap -> { // 只在数据确实变化时更新 CompoundTag current = cap.serializeNBT(); if (!current.equals(msg.leashData)) { diff --git a/src/main/java/top/r3944realms/superleadrope/util/capability/LeashDataAPI.java b/src/main/java/top/r3944realms/superleadrope/util/capability/LeashDataAPI.java new file mode 100644 index 0000000..020049b --- /dev/null +++ b/src/main/java/top/r3944realms/superleadrope/util/capability/LeashDataAPI.java @@ -0,0 +1,315 @@ +/* + * Super Lead rope mod + * Copyright (C) 2025 R3944Realms + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package top.r3944realms.superleadrope.util.capability; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.player.Player; +import org.jetbrains.annotations.NotNull; +import top.r3944realms.superleadrope.content.capability.CapabilityHandler; +import top.r3944realms.superleadrope.content.capability.inter.ILeashData; + +import java.util.*; + +/** + * 拴绳数据API - 提供统一的API接口操作拴绳数据能力 + */ +@SuppressWarnings("unused") +public final class LeashDataAPI { + // ==================== 基础能力获取 ==================== + public static Optional getLeashData(@NotNull Entity entity) { + Objects.requireNonNull(entity, "Entity cannot be null"); + return entity.getCapability(CapabilityHandler.LEASH_DATA_CAP).resolve(); + } + // ==================== 拴绳数据管理 API ==================== + + public static final class LeashOperations { + private LeashOperations() {} + + // ---------------------- 添加拴绳 ---------------------- + public static boolean attach(Entity entity, Entity holder) { + return getLeashData(entity).map(data -> data.addLeash(holder)).orElse(false); + } + + public static boolean attach(Entity entity, Entity holder, String reserved) { + return getLeashData(entity).map(data -> data.addLeash(holder, reserved)).orElse(false); + } + + public static boolean attach(Entity entity, Entity holder, double maxDistance) { + return getLeashData(entity).map(data -> data.addLeash(holder, maxDistance)).orElse(false); + } + + public static boolean attach(Entity entity, Entity holder, double maxDistance, double elasticDistance, int maxKeepTicks) { + return getLeashData(entity).map(data -> data.addLeash(holder, maxDistance, elasticDistance, maxKeepTicks)).orElse(false); + } + + public static boolean attach(Entity entity, Entity holder, double maxDistance, String reserved) { + return getLeashData(entity).map(data -> data.addLeash(holder, maxDistance, reserved)).orElse(false); + } + + public static boolean attach(Entity entity, Entity holder, double maxDistance, double elasticDistance, int maxKeepTicks, String reserved) { + return getLeashData(entity).map(data -> data.addLeash(holder, maxDistance, elasticDistance, maxKeepTicks, reserved)).orElse(false); + } + + public static void attachWithInfo(Entity entity, Entity holder, ILeashData.LeashInfo info) { + getLeashData(entity).ifPresent(data -> data.addLeash(holder, info)); + } + + // ---------------------- 延迟拴绳 ---------------------- + public static void attachDelayed(Entity entity, Player holderPlayer) { + getLeashData(entity).ifPresent(data -> data.addDelayedLeash(holderPlayer)); + } + + public static void removeDelayed(Entity entity, UUID onceHolderPlayerUUID) { + getLeashData(entity).ifPresent(data -> data.removeDelayedLeash(onceHolderPlayerUUID)); + } + + // ---------------------- 移除拴绳 ---------------------- + public static boolean detach(Entity entity, Entity holder) { + return getLeashData(entity).map(data -> data.removeLeash(holder)).orElse(false); + } + + public static boolean detach(Entity entity, UUID holderUUID) { + return getLeashData(entity).map(data -> data.removeLeash(holderUUID)).orElse(false); + } + + public static boolean detach(Entity entity, BlockPos knotPos) { + return getLeashData(entity).map(data -> data.removeLeash(knotPos)).orElse(false); + } + + public static void detachAll(Entity entity) { + getLeashData(entity).ifPresent(ILeashData::removeAllLeashes); + } + + public static void detachAllHolders(Entity entity) { + getLeashData(entity).ifPresent(ILeashData::removeAllHolderLeashes); + } + + public static void detachAllKnots(Entity entity) { + getLeashData(entity).ifPresent(ILeashData::removeAllKnotLeashes); + } + } + + // ==================== 拴绳属性修改 API ==================== + + public static final class PropertyOperations { + private PropertyOperations() {} + + // ---------------------- 设置最大距离 ---------------------- + public static boolean setMaxDistance(Entity entity, Entity holder, double distance) { + return getLeashData(entity).map(data -> data.setMaxDistance(holder, distance)).orElse(false); + } + + public static boolean setMaxDistance(Entity entity, Entity holder, double distance, int maxKeepTicks) { + return getLeashData(entity).map(data -> data.setMaxDistance(holder, distance, maxKeepTicks)).orElse(false); + } + + public static boolean setMaxDistance(Entity entity, Entity holder, double distance, int maxKeepTicks, String reserved) { + return getLeashData(entity).map(data -> data.setMaxDistance(holder, distance, maxKeepTicks, reserved)).orElse(false); + } + + public static boolean setMaxDistance(Entity entity, UUID holderUUID, double distance) { + return getLeashData(entity).map(data -> data.setMaxDistance(holderUUID, distance)).orElse(false); + } + + public static boolean setMaxDistance(Entity entity, UUID holderUUID, double distance, int maxKeepTicks) { + return getLeashData(entity).map(data -> data.setMaxDistance(holderUUID, distance, maxKeepTicks)).orElse(false); + } + + public static boolean setMaxDistance(Entity entity, UUID holderUUID, double distance, int maxKeepTicks, String reserved) { + return getLeashData(entity).map(data -> data.setMaxDistance(holderUUID, distance, maxKeepTicks, reserved)).orElse(false); + } + + public static boolean setMaxDistance(Entity entity, BlockPos knotPos, double distance) { + return getLeashData(entity).map(data -> data.setMaxDistance(knotPos, distance)).orElse(false); + } + + public static boolean setMaxDistance(Entity entity, BlockPos knotPos, double distance, int maxKeepTicks) { + return getLeashData(entity).map(data -> data.setMaxDistance(knotPos, distance, maxKeepTicks)).orElse(false); + } + + public static boolean setMaxDistance(Entity entity, BlockPos knotPos, double distance, int maxKeepTicks, String reserved) { + return getLeashData(entity).map(data -> data.setMaxDistance(knotPos, distance, maxKeepTicks, reserved)).orElse(false); + } + + // ---------------------- 设置弹性距离 ---------------------- + public static boolean setElasticDistance(Entity entity, Entity holder, double distance) { + return getLeashData(entity).map(data -> data.setElasticDistance(holder, distance)).orElse(false); + } + + public static boolean setElasticDistance(Entity entity, Entity holder, double distance, int maxKeepTicks) { + return getLeashData(entity).map(data -> data.setElasticDistance(holder, distance, maxKeepTicks)).orElse(false); + } + + public static boolean setElasticDistance(Entity entity, Entity holder, double distance, int maxKeepTicks, String reserved) { + return getLeashData(entity).map(data -> data.setElasticDistance(holder, distance, maxKeepTicks, reserved)).orElse(false); + } + + public static boolean setElasticDistance(Entity entity, UUID holderUUID, double distance) { + return getLeashData(entity).map(data -> data.setElasticDistance(holderUUID, distance)).orElse(false); + } + + public static boolean setElasticDistance(Entity entity, UUID holderUUID, double distance, int maxKeepTicks) { + return getLeashData(entity).map(data -> data.setElasticDistance(holderUUID, distance, maxKeepTicks)).orElse(false); + } + + public static boolean setElasticDistance(Entity entity, UUID holderUUID, double distance, int maxKeepTicks, String reserved) { + return getLeashData(entity).map(data -> data.setElasticDistance(holderUUID, distance, maxKeepTicks, reserved)).orElse(false); + } + + public static boolean setElasticDistance(Entity entity, BlockPos knotPos, double distance) { + return getLeashData(entity).map(data -> data.setElasticDistance(knotPos, distance)).orElse(false); + } + + public static boolean setElasticDistance(Entity entity, BlockPos knotPos, double distance, int maxKeepTicks) { + return getLeashData(entity).map(data -> data.setElasticDistance(knotPos, distance, maxKeepTicks)).orElse(false); + } + + public static boolean setElasticDistance(Entity entity, BlockPos knotPos, double distance, int maxKeepTicks, String reserved) { + return getLeashData(entity).map(data -> data.setElasticDistance(knotPos, distance, maxKeepTicks, reserved)).orElse(false); + } + } + + // ==================== 物理应用 API ==================== + + public static final class PhysicsOperations { + private PhysicsOperations() {} + + public static void applyForces(Entity entity) { + getLeashData(entity).ifPresent(ILeashData::applyLeashForces); + } + } + + // ==================== 拴绳转移 API ==================== + + public static final class TransferOperations { + private TransferOperations() {} + + public static boolean transfer(Entity entity, Entity holder, Entity newHolder) { + return getLeashData(entity).map(data -> data.transferLeash(holder, newHolder)).orElse(false); + } + + public static boolean transfer(Entity entity, Entity holder, Entity newHolder, String reserved) { + return getLeashData(entity).map(data -> data.transferLeash(holder, newHolder, reserved)).orElse(false); + } + + public static boolean transfer(Entity entity, UUID holderUUID, Entity newHolder) { + return getLeashData(entity).map(data -> data.transferLeash(holderUUID, newHolder)).orElse(false); + } + + public static boolean transfer(Entity entity, UUID holderUUID, Entity newHolder, String reserved) { + return getLeashData(entity).map(data -> data.transferLeash(holderUUID, newHolder, reserved)).orElse(false); + } + + public static boolean transfer(Entity entity, BlockPos knotPos, Entity newHolder) { + return getLeashData(entity).map(data -> data.transferLeash(knotPos, newHolder)).orElse(false); + } + + public static boolean transfer(Entity entity, BlockPos knotPos, Entity newHolder, String reserved) { + return getLeashData(entity).map(data -> data.transferLeash(knotPos, newHolder, reserved)).orElse(false); + } + } + + // ==================== 查询操作 API ==================== + + public static final class QueryOperations { + private QueryOperations() {} + + public static boolean hasLeash(Entity entity) { + return getLeashData(entity).map(ILeashData::hasLeash).orElse(false); + } + + public static boolean hasKnotLeash(Entity entity) { + return getLeashData(entity).map(ILeashData::hasKnotLeash).orElse(false); + } + + public static boolean hasHolderLeash(Entity entity) { + return getLeashData(entity).map(ILeashData::hasHolderLeash).orElse(false); + } + + public static Collection getAllLeashes(Entity entity) { + return getLeashData(entity).map(ILeashData::getAllLeashes).orElse(Collections.emptyList()); + } + + public static boolean isLeashedBy(Entity entity, Entity holder) { + return getLeashData(entity).map(data -> data.isLeashedBy(holder)).orElse(false); + } + + public static boolean isLeashedBy(Entity entity, UUID holderUUID) { + return getLeashData(entity).map(data -> data.isLeashedBy(holderUUID)).orElse(false); + } + + public static boolean isLeashedBy(Entity entity, BlockPos knotPos) { + return getLeashData(entity).map(data -> data.isLeashedBy(knotPos)).orElse(false); + } + + public static boolean isInDelayedLeash(Entity entity, UUID holderUUID) { + return getLeashData(entity).map(data -> data.isInDelayedLeash(holderUUID)).orElse(false); + } + + public static Optional getLeashInfo(Entity entity, Entity holder) { + return getLeashData(entity).flatMap(data -> data.getLeashInfo(holder)); + } + + public static Optional getLeashInfo(Entity entity, UUID holderUUID) { + return getLeashData(entity).flatMap(data -> data.getLeashInfo(holderUUID)); + } + + public static Optional getLeashInfo(Entity entity, BlockPos knotPos) { + return getLeashData(entity).flatMap(data -> data.getLeashInfo(knotPos)); + } + + public static boolean canBeLeashed(Entity entity) { + return getLeashData(entity).map(ILeashData::canBeLeashed).orElse(false); + } + + public static boolean canBeAttachedTo(Entity entity, Entity target) { + return getLeashData(entity).map(data -> data.canBeAttachedTo(target)).orElse(false); + } + } + + // ==================== 占用和同步 API ==================== + + public static final class ManagementOperations { + private ManagementOperations() {} + + public static Optional occupyLeash(Entity entity) { + return getLeashData(entity).flatMap(ILeashData::occupyLeash); + } + + public static void markForSync(Entity entity) { + getLeashData(entity).ifPresent(ILeashData::markForSync); + } + + public static void immediateSync(Entity entity) { + getLeashData(entity).ifPresent(ILeashData::immediateSync); + } + + public static void checkSync(Entity entity) { + getLeashData(entity).ifPresent(ILeashData::checkSync); + } + } + + // ==================== 工具方法 ==================== + + public static final class Utils { + private Utils() {} + + public static boolean hasLeashData(Entity entity) { + return getLeashData(entity).isPresent(); + } + } +} \ No newline at end of file diff --git a/src/main/java/top/r3944realms/superleadrope/util/capability/LeashStateAPI.java b/src/main/java/top/r3944realms/superleadrope/util/capability/LeashStateAPI.java new file mode 100644 index 0000000..88bae1a --- /dev/null +++ b/src/main/java/top/r3944realms/superleadrope/util/capability/LeashStateAPI.java @@ -0,0 +1,273 @@ +/* + * Super Lead rope mod + * Copyright (C) 2025 R3944Realms + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package top.r3944realms.superleadrope.util.capability; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.phys.Vec3; +import org.jetbrains.annotations.NotNull; +import top.r3944realms.superleadrope.CommonEventHandler; +import top.r3944realms.superleadrope.content.capability.CapabilityHandler; +import top.r3944realms.superleadrope.content.capability.inter.ILeashState; + +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; + +@SuppressWarnings("unused") +public final class LeashStateAPI { + + private LeashStateAPI() { + } // 防止实例化 + + public static Optional getLeashState(@NotNull Entity entity) { + Objects.requireNonNull(entity, "Entity cannot be null"); + return entity.getCapability(CapabilityHandler.LEASH_STATE_CAP).resolve(); + } + + // ==================== 查询操作 ==================== + + public static final class Query { + private Query() { + } + + public static Map getAllUUIDStates(Entity entity) { + return getLeashState(entity) + .map(ILeashState::getHolderLeashStates) + .orElse(Map.of()); + } + + public static Map getAllBlockPosStates(Entity entity) { + return getLeashState(entity) + .map(ILeashState::getKnotLeashStates) + .orElse(Map.of()); + } + + public static Optional getState(Entity entity, Entity holder) { + return getLeashState(entity).flatMap(state -> state.getLeashState(holder)); + } + + public static Optional getState(Entity entity, UUID holderUUID) { + return getLeashState(entity).flatMap(state -> state.getLeashState(holderUUID)); + } + + public static Optional getState(Entity entity, BlockPos knotPos) { + return getLeashState(entity).flatMap(state -> state.getLeashState(knotPos)); + } + + public static boolean hasState(Entity entity) { + return getLeashState(entity).isPresent(); + } + + public static boolean hasStateFor(Entity entity, Entity holder) { + return getState(entity, holder).isPresent(); + } + + public static boolean hasStateFor(Entity entity, UUID holderUUID) { + return getState(entity, holderUUID).isPresent(); + } + + public static boolean hasStateFor(Entity entity, BlockPos knotPos) { + return getState(entity, knotPos).isPresent(); + } + } + + // ==================== 偏移量操作 ==================== + + public static final class Offset { + private Offset() { + } + + // ---------------------- 重置操作 ---------------------- + public static void resetAll(Entity entity) { + getLeashState(entity).ifPresent(ILeashState::resetAllLeashHolderLocationsOffset); + } + + public static void resetFor(Entity entity, Entity holder) { + getLeashState(entity).ifPresent(state -> state.resetLeashHolderLocationOffset(holder)); + } + + public static void resetFor(Entity entity, UUID holderUUID) { + getLeashState(entity).ifPresent(state -> state.resetLeashHolderLocationOffset(holderUUID)); + } + + public static void resetFor(Entity entity, BlockPos knotPos) { + getLeashState(entity).ifPresent(state -> state.resetLeashHolderLocationOffset(knotPos)); + } + + // ---------------------- 设置操作 ---------------------- + public static void setFor(Entity entity, Entity holder, Vec3 offset) { + getLeashState(entity).ifPresent(state -> state.setLeashHolderLocationOffset(holder, offset)); + } + + public static void setFor(Entity entity, UUID holderUUID, Vec3 offset) { + getLeashState(entity).ifPresent(state -> state.setLeashHolderLocationOffset(holderUUID, offset)); + } + + public static void setFor(Entity entity, BlockPos knotPos, Vec3 offset) { + getLeashState(entity).ifPresent(state -> state.setLeashHolderLocationOffset(knotPos, offset)); + } + + // ---------------------- 添加操作 ---------------------- + public static void addTo(Entity entity, Entity holder, Vec3 offset) { + getLeashState(entity).ifPresent(state -> state.addLeashHolderLocationOffset(holder, offset)); + } + + public static void addTo(Entity entity, UUID holderUUID, Vec3 offset) { + getLeashState(entity).ifPresent(state -> state.addLeashHolderLocationOffset(holderUUID, offset)); + } + + public static void addTo(Entity entity, BlockPos knotPos, Vec3 offset) { + getLeashState(entity).ifPresent(state -> state.addLeashHolderLocationOffset(knotPos, offset)); + } + + // ---------------------- 移除操作 ---------------------- + public static void removeFor(Entity entity, Entity holder) { + getLeashState(entity).ifPresent(state -> state.removeLeashHolderLocationOffset(holder)); + } + + public static void removeFor(Entity entity, UUID holderUUID) { + getLeashState(entity).ifPresent(state -> state.removeLeashHolderLocationOffset(holderUUID)); + } + + public static void removeFor(Entity entity, BlockPos knotPos) { + getLeashState(entity).ifPresent(state -> state.removeLeashHolderLocationOffset(knotPos)); + } + + public static void removeAll(Entity entity) { + getLeashState(entity).ifPresent(ILeashState::removeAllLeashHolderLocationOffset); + } + + public static void removeAllUUIDs(Entity entity) { + getLeashState(entity).ifPresent(ILeashState::removeAllLeashHolderUUIDLocationOffset); + } + + public static void removeAllBlockPoses(Entity entity) { + getLeashState(entity).ifPresent(ILeashState::removeAllLeashHolderBlockPosLocationOffset); + } + } + + // ==================== 应用实体偏移量操作 ==================== + + public static final class ApplyEntity { + private ApplyEntity() { + } + + public static Optional getOffset(Entity entity) { + return getLeashState(entity).flatMap(ILeashState::getLeashApplyEntityLocationOffset); + } + + public static Vec3 getDefaultOffset(Entity entity) { + return getLeashState(entity) + .map(ILeashState::getDefaultLeashApplyEntityLocationOffset) + .orElse(Vec3.ZERO); + } + + public static void resetAll(Entity entity) { + getLeashState(entity).ifPresent(ILeashState::resetAllLeashApplyEntityLocationsOffset); + } + + public static void remove(Entity entity) { + getLeashState(entity).ifPresent(ILeashState::removeLeashApplyEntityLocationOffset); + } + + public static void set(Entity entity, Vec3 offset) { + getLeashState(entity).ifPresent(state -> state.setLeashApplyEntityLocationOffset(offset)); + } + + public static void add(Entity entity, Vec3 offset) { + getLeashState(entity).ifPresent(state -> state.addLeashApplyEntityLocationOffset(offset)); + } + } + + // ==================== 同步操作 ==================== + + public static final class Sync { + private Sync() { + } + + public static void mark(Entity entity) { + getLeashState(entity).ifPresent(ILeashState::markForSync); + } + + public static void immediate(Entity entity) { + getLeashState(entity).ifPresent(ILeashState::immediateSync); + } + + public static void check(Entity entity) { + getLeashState(entity).ifPresent(ILeashState::checkSync); + } + } + + // ==================== 高级操作 ==================== + + public static final class Operations { + private Operations() { + } + + public static void attach(Entity leashed, Entity holder) { + getLeashState(leashed).ifPresent(state -> + state.addLeashHolderLocationOffset(holder, + CommonEventHandler.leashConfigManager.getEntityOffset(holder)) + ); + } + + public static void detach(Entity leashed, Entity holder) { + Offset.removeFor(leashed, holder); + } + + public static void detach(Entity leashed, UUID holderUUID) { + Offset.removeFor(leashed, holderUUID); + } + + public static void detach(Entity leashed, BlockPos knotPos) { + Offset.removeFor(leashed, knotPos); + } + + public static void transfer(Entity leashed, Entity oldHolder, Entity newHolder) { + getLeashState(leashed).ifPresent(state -> { + state.removeLeashHolderLocationOffset(oldHolder); + state.addLeashHolderLocationOffset(newHolder, + CommonEventHandler.leashConfigManager.getEntityOffset(newHolder)); + }); + } + + public static void transfer(Entity leashed, UUID oldHolderUUID, Entity newHolder) { + getLeashState(leashed).ifPresent(state -> { + state.removeLeashHolderLocationOffset(oldHolderUUID); + state.addLeashHolderLocationOffset(newHolder, + CommonEventHandler.leashConfigManager.getEntityOffset(newHolder)); + }); + } + + public static void transfer(Entity leashed, BlockPos oldKnotPos, Entity newHolder) { + getLeashState(leashed).ifPresent(state -> { + state.removeLeashHolderLocationOffset(oldKnotPos); + state.addLeashHolderLocationOffset(newHolder, + CommonEventHandler.leashConfigManager.getEntityOffset(newHolder)); + }); + } + + public static void copy(Entity source, Entity target) { + getLeashState(source).ifPresent(sourceState -> + getLeashState(target).ifPresent(targetState -> + targetState.copy(sourceState, target) + ) + ); + } + } +} diff --git a/src/main/java/top/r3944realms/superleadrope/util/capability/LeashUtil.java b/src/main/java/top/r3944realms/superleadrope/util/capability/LeashUtil.java deleted file mode 100644 index 1190330..0000000 --- a/src/main/java/top/r3944realms/superleadrope/util/capability/LeashUtil.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Super Lead rope mod - * Copyright (C) 2025 R3944Realms - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package top.r3944realms.superleadrope.util.capability; - -import net.minecraft.world.entity.Entity; -import org.jetbrains.annotations.NotNull; -import top.r3944realms.superleadrope.content.capability.CapabilityHandler; -import top.r3944realms.superleadrope.content.capability.inter.ILeashData; -import top.r3944realms.superleadrope.content.capability.inter.ILeashState; - -import java.util.Objects; -import java.util.Optional; - -public class LeashUtil { - public static Optional getLeashData(@NotNull Entity entity) { - Objects.requireNonNull(entity, "Entity cannot be null"); - return entity.getCapability(CapabilityHandler.LEASH_DATA_CAP).resolve(); - } - public static Optional getLeashState(@NotNull Entity entity) { - Objects.requireNonNull(entity, "Entity cannot be null"); - return entity.getCapability(CapabilityHandler.LEASH_STATE_CAP).resolve(); - } -} diff --git a/src/main/java/top/r3944realms/superleadrope/util/riding/RidingApplier.java b/src/main/java/top/r3944realms/superleadrope/util/riding/RidingApplier.java index 21daee5..7078e6b 100644 --- a/src/main/java/top/r3944realms/superleadrope/util/riding/RidingApplier.java +++ b/src/main/java/top/r3944realms/superleadrope/util/riding/RidingApplier.java @@ -16,6 +16,7 @@ package top.r3944realms.superleadrope.util.riding; import net.minecraft.world.entity.Entity; +import top.r3944realms.superleadrope.CommonEventHandler; import top.r3944realms.superleadrope.SuperLeadRope; import top.r3944realms.superleadrope.core.exception.RidingCycleException; import top.r3944realms.superleadrope.util.model.RidingRelationship; @@ -55,7 +56,7 @@ public class RidingApplier { if (entity == null) continue; // ---------- 白名单保护 ---------- - if (!RidingValidator.isInWhitelist(entity.getType())) { + if (!CommonEventHandler.leashConfigManager.isEntityTeleportAllowed(entity)) { // 不在白名单,跳过本节点,但保留其乘客挂回上层 if (vehicle != null) { // 将当前节点的乘客挂回上层载具 @@ -75,7 +76,7 @@ public class RidingApplier { } // 如果有指定的载具,尝试上车 - if (vehicle != null && RidingValidator.isInWhitelist(vehicle.getType())) { + if (vehicle != null && CommonEventHandler.leashConfigManager.isEntityTeleportAllowed(vehicle)) { if (RidingValidator.wouldCreateCycle(entity, vehicle)) { throw new RidingCycleException(entityId, vehicleId); } diff --git a/src/main/java/top/r3944realms/superleadrope/util/riding/RidingSaver.java b/src/main/java/top/r3944realms/superleadrope/util/riding/RidingSaver.java index 184a9e3..d4fd663 100644 --- a/src/main/java/top/r3944realms/superleadrope/util/riding/RidingSaver.java +++ b/src/main/java/top/r3944realms/superleadrope/util/riding/RidingSaver.java @@ -17,6 +17,7 @@ package top.r3944realms.superleadrope.util.riding; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntityType; +import top.r3944realms.superleadrope.CommonEventHandler; import top.r3944realms.superleadrope.core.exception.RidingCycleException; import top.r3944realms.superleadrope.core.util.ImmutablePair; import top.r3944realms.superleadrope.util.model.RidingRelationship; @@ -72,7 +73,7 @@ public class RidingSaver { processedEntities.add(passengerId); // ✅ 校验白名单 - if (!RidingValidator.isInWhitelist(passenger.getType())) { + if (!CommonEventHandler.leashConfigManager.isEntityTeleportAllowed(passenger.getType())) { // ❌ 不在白名单,直接截断 continue; } @@ -105,7 +106,7 @@ public class RidingSaver { if (relationship == null) return null; // 如果当前根节点在白名单,则直接处理子节点 - if (RidingValidator.isInWhitelist(Objects.requireNonNull(getEntityType(relationship.getEntityId())))) { + if (CommonEventHandler.leashConfigManager.isEntityTeleportAllowed(Objects.requireNonNull(getEntityType(relationship.getEntityId())))) { RidingRelationship filtered = new RidingRelationship(); filtered.setEntityId(relationship.getEntityId()); filtered.setVehicleId(relationship.getVehicleId()); @@ -114,7 +115,7 @@ public class RidingSaver { } else { // 根节点不在白名单,尝试找到合法的子节点作为新的根 for (RidingRelationship child : relationship.getPassengers()) { - if (RidingValidator.isInWhitelist(Objects.requireNonNull(getEntityType(child.getEntityId())))) { + if (CommonEventHandler.leashConfigManager.isEntityTeleportAllowed(Objects.requireNonNull(getEntityType(child.getEntityId())))) { // 设置父节点为当前节点的父(倒二叉逻辑) RidingRelationship newRoot = new RidingRelationship(); newRoot.setEntityId(child.getEntityId()); diff --git a/src/main/java/top/r3944realms/superleadrope/util/riding/RidingValidator.java b/src/main/java/top/r3944realms/superleadrope/util/riding/RidingValidator.java index 0445487..7c20369 100644 --- a/src/main/java/top/r3944realms/superleadrope/util/riding/RidingValidator.java +++ b/src/main/java/top/r3944realms/superleadrope/util/riding/RidingValidator.java @@ -15,52 +15,12 @@ package top.r3944realms.superleadrope.util.riding; -import net.minecraft.core.registries.Registries; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.tags.TagKey; import net.minecraft.world.entity.Entity; -import net.minecraft.world.entity.EntityType; -import top.r3944realms.superleadrope.config.LeashCommonConfig; import java.util.LinkedList; import java.util.Queue; public class RidingValidator { - /** - * 是否在配置白名单里 - */ - @SuppressWarnings("deprecation") - public static boolean isInWhitelist(EntityType type) { - String key = type.builtInRegistryHolder().key().location().toString(); - String modid = key.split(":")[0]; - - for (String entry : LeashCommonConfig.COMMON.teleportWhitelist.get()) { - if (entry.startsWith("#")) { - String body = entry.substring(1); - - // Case 1: #modid → allow all entities from this mod - if (!body.contains(":")) { - if (modid.equals(body)) { - return true; - } - } - // Case 2: #modid:tag_name → allow all entities under this tag - else { - ResourceLocation tagId = new ResourceLocation(body); - TagKey> tag = TagKey.create(Registries.ENTITY_TYPE, tagId); - if (type.builtInRegistryHolder().is(tag)) { - return true; - } - } - } else { - // Case 3: modid:entity_name → allow a specific entity - if (entry.equals(key)) { - return true; - } - } - } - return false; - } /** * 检查骑乘是否会产生循环引用 */ diff --git a/src/main/java/top/r3944realms/superleadrope/util/riding/RindingLeash.java b/src/main/java/top/r3944realms/superleadrope/util/riding/RindingLeash.java index 511ee12..6746785 100644 --- a/src/main/java/top/r3944realms/superleadrope/util/riding/RindingLeash.java +++ b/src/main/java/top/r3944realms/superleadrope/util/riding/RindingLeash.java @@ -21,6 +21,7 @@ import net.minecraft.world.entity.ai.goal.Goal; import net.minecraft.world.entity.animal.Animal; import net.minecraft.world.phys.Vec3; import org.jetbrains.annotations.Nullable; +import top.r3944realms.superleadrope.CommonEventHandler; import top.r3944realms.superleadrope.network.NetworkHandler; import top.r3944realms.superleadrope.network.toClient.UpdatePlayerMovementPacket; @@ -37,7 +38,7 @@ public class RindingLeash { Entity current = root; while (current != null) { - if (RidingValidator.isInWhitelist(current.getType())) { + if (CommonEventHandler.leashConfigManager.isEntityTeleportAllowed(current.getType())) { return current; // 找到白名单载具 } current = current.getVehicle(); @@ -60,7 +61,7 @@ public class RindingLeash { Entity current = root; while (current != null) { - if (RidingValidator.isInWhitelist(current.getType())) { + if (CommonEventHandler.leashConfigManager.isEntityTeleportAllowed(current.getType())) { return current; // 找到白名单载具 } current = current.getVehicle(); diff --git a/src/main/resources/META-INF/coremods.json b/src/main/resources/META-INF/coremods.json index 9e26dfe..3a8f394 100644 --- a/src/main/resources/META-INF/coremods.json +++ b/src/main/resources/META-INF/coremods.json @@ -1 +1,3 @@ -{} \ No newline at end of file +{ + "morsb_patch":"coremods/morsb_patch.js" +} \ No newline at end of file diff --git a/src/main/resources/coremods/morsb_patch.js b/src/main/resources/coremods/morsb_patch.js new file mode 100644 index 0000000..cc12b44 --- /dev/null +++ b/src/main/resources/coremods/morsb_patch.js @@ -0,0 +1,62 @@ +/* + * 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 . + */ + +var Opcodes = Java.type("org.objectweb.asm.Opcodes"); +var ASMAPI = Java.type("net.minecraftforge.coremod.api.ASMAPI"); +var VarInsnNode = Java.type("org.objectweb.asm.tree.VarInsnNode"); +var MethodInsnNode = Java.type("org.objectweb.asm.tree.MethodInsnNode"); +var InsnNode = Java.type("org.objectweb.asm.tree.InsnNode"); + +function initializeCoreMod() { + return { + "leash_render_patch": { + "target": { + "type": "METHOD", + "class": "net.minecraft.client.renderer.entity.MobRenderer", + "methodName": "m_5523_", + "methodDesc": "(Lnet/minecraft/world/entity/Mob;Lnet/minecraft/client/renderer/culling/Frustum;DDD)Z" + }, + "transformer": function(method) { + var insns = method.instructions; + + for (var i = 0; i < insns.size(); i++) { + var insn = insns.get(i); + if (insn.getOpcode && insn.getOpcode() === Opcodes.ICONST_0) { + var next = insns.get(i + 1); + if (next && next.getOpcode() === Opcodes.IRETURN) { + // 插入调试日志和方法调用 + insns.insertBefore(insn, ASMAPI.listOf( + new VarInsnNode(Opcodes.ALOAD, 1), // Mob + new VarInsnNode(Opcodes.ALOAD, 2), // Frustum + ASMAPI.buildMethodCall( + 'your/package/LeashRenderHook', + null, + ASMAPI.MethodType.STATIC, + 'shouldRenderExtraWithLog', + '(Lnet/minecraft/world/entity/Mob;Lnet/minecraft/client/renderer/culling/Frustum;)Z', + ASMAPI.MethodCallMode.STATIC + ) + )); + // 移除原来的 ICONST_0 + insns.remove(insn); + break; + } + } + } + return method; + } + } + }; +} \ No newline at end of file diff --git a/src/main/java/top/r3944realms/superleadrope/util/coremods/InvokerMethod.java b/src/test/java/top/r3944realms/superleadropetest/Placeholder.java similarity index 78% rename from src/main/java/top/r3944realms/superleadrope/util/coremods/InvokerMethod.java rename to src/test/java/top/r3944realms/superleadropetest/Placeholder.java index cb322d8..537557d 100644 --- a/src/main/java/top/r3944realms/superleadrope/util/coremods/InvokerMethod.java +++ b/src/test/java/top/r3944realms/superleadropetest/Placeholder.java @@ -13,12 +13,10 @@ * along with this program. If not, see . */ -package top.r3944realms.superleadrope.util.coremods; +package top.r3944realms.superleadropetest; -import net.minecraftforge.api.distmarker.Dist; -import net.minecraftforge.api.distmarker.OnlyIn; +import top.r3944realms.superleadrope.util.capability.LeashDataAPI; -@OnlyIn(Dist.CLIENT) -public class InvokerMethod { +public class Placeholder { } diff --git a/src/test/java/top/r3944realms/superleadropetest/asm/ASMTest.java b/src/test/java/top/r3944realms/superleadropetest/asm/ASMTest.java new file mode 100644 index 0000000..3b38439 --- /dev/null +++ b/src/test/java/top/r3944realms/superleadropetest/asm/ASMTest.java @@ -0,0 +1,44 @@ +/* + * Super Lead rope mod + * Copyright (C) 2025 R3944Realms + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package top.r3944realms.superleadropetest.asm; + +import org.objectweb.asm.*; +import org.objectweb.asm.tree.*; + +import java.io.InputStream; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +public class ASMTest { + public static void main(String[] args) throws Exception { + Path jarPath = Paths.get("G:\\WhimsyMod\\SuperLeadRope\\build\\moddev\\artifacts\\forge-1.20.1-47.3.4-merged.jar"); + try (JarFile jar = new JarFile(jarPath.toFile())) { + JarEntry entry = jar.getJarEntry("net/minecraft/client/renderer/entity/MobRenderer.class"); + try (InputStream in = jar.getInputStream(entry)) { + byte[] classBytes = in.readAllBytes(); + + ClassReader reader = new ClassReader(classBytes); + ClassNode classNode = new ClassNode(); + reader.accept(classNode, 0); + + System.out.println("Methods in MobRenderer:"); + classNode.methods.forEach(m -> System.out.println(" - " + m.name + m.desc)); + } + } + } +} diff --git a/src/test/java/top/r3944realms/superleadropetest/asm/CoreModTransformerMemoryTest.java b/src/test/java/top/r3944realms/superleadropetest/asm/CoreModTransformerMemoryTest.java new file mode 100644 index 0000000..9cf06e3 --- /dev/null +++ b/src/test/java/top/r3944realms/superleadropetest/asm/CoreModTransformerMemoryTest.java @@ -0,0 +1,100 @@ +/* + * Super Lead rope mod + * Copyright (C) 2025 R3944Realms + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package top.r3944realms.superleadropetest.asm; + +import org.objectweb.asm.*; +import org.objectweb.asm.tree.*; + +import java.lang.reflect.Method; + +public class CoreModTransformerMemoryTest { + + // 1️⃣ 静态类替代 Minecraft 类 + public static class TestMob {} + public static class TestFrustum {} + + // Hook 模拟 + public static class LeashRenderHook { + public static boolean shouldRenderExtra(TestMob mob, TestFrustum frustum) { + System.out.println("[Hook] shouldRenderExtra called with Mob=" + mob + ", Frustum=" + frustum); + return true; + } + } + + public static void main(String[] args) throws Exception { + // 2️⃣ 构造假的 shouldRender 方法 + MethodNode methodNode = new MethodNode(Opcodes.ASM9, Opcodes.ACC_PUBLIC, "shouldRender", + "(Ltop/r3944realms/superleadrope/core/test/CoreModTransformerMemoryTest$TestMob;" + + "Ltop/r3944realms/superleadrope/core/test/CoreModTransformerMemoryTest$TestFrustum;)Z", + null, null); + + InsnList insns = methodNode.instructions; + insns.clear(); // 清空原有指令 + // 插入 Hook 调用 + InsnList patch = new InsnList(); + patch.add(new VarInsnNode(Opcodes.ALOAD, 1)); + patch.add(new VarInsnNode(Opcodes.ALOAD, 2)); + patch.add(new MethodInsnNode( + Opcodes.INVOKESTATIC, + "top/r3944realms/superleadrope/core/test/CoreModTransformerMemoryTest$LeashRenderHook", + "shouldRenderExtra", + "(Ltop/r3944realms/superleadrope/core/test/CoreModTransformerMemoryTest$TestMob;" + + "Ltop/r3944realms/superleadrope/core/test/CoreModTransformerMemoryTest$TestFrustum;)Z", + false + )); + patch.add(new InsnNode(Opcodes.IRETURN)); + insns.add(patch); + + // 4️⃣ 创建假的 ClassNode + ClassNode classNode = new ClassNode(); + classNode.version = Opcodes.V1_8; + classNode.access = Opcodes.ACC_PUBLIC; + classNode.name = "FakeMobRenderer"; + classNode.superName = "java/lang/Object"; + classNode.methods.add(methodNode); + + // 添加默认构造器 + MethodNode constructor = new MethodNode(Opcodes.ASM9, Opcodes.ACC_PUBLIC, "", "()V", null, null); + InsnList initInsns = constructor.instructions; + initInsns.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this + initInsns.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, "java/lang/Object", "", "()V", false)); + initInsns.add(new InsnNode(Opcodes.RETURN)); + constructor.maxStack = 1; + constructor.maxLocals = 1; + classNode.methods.add(constructor); + + // 5️⃣ 写入字节码 + ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); + classNode.accept(cw); + byte[] bytes = cw.toByteArray(); + + // 6️⃣ 自定义 ClassLoader,直接返回 Class 对象 + ClassLoader loader = new ClassLoader(CoreModTransformerMemoryTest.class.getClassLoader()) { + public Class defineClassFromBytes(byte[] b) { + return defineClass("FakeMobRenderer", b, 0, b.length); + } + }; + Class clazz = ((Class) loader.getClass().getMethod("defineClassFromBytes", byte[].class).invoke(loader, bytes)); + + // 7️⃣ 实例化 + Object rendererInstance = clazz.getDeclaredConstructor().newInstance(); + Method shouldRender = clazz.getMethod("shouldRender", TestMob.class, TestFrustum.class); + + boolean result = (boolean) shouldRender.invoke(rendererInstance, new TestMob(), new TestFrustum()); + System.out.println("[Test] Result of shouldRender (after patch): " + result); + + } +} \ No newline at end of file diff --git a/src/test/java/top/r3944realms/superleadropetest/config/OffsetReadTest.java b/src/test/java/top/r3944realms/superleadropetest/config/OffsetReadTest.java new file mode 100644 index 0000000..4b846b2 --- /dev/null +++ b/src/test/java/top/r3944realms/superleadropetest/config/OffsetReadTest.java @@ -0,0 +1,132 @@ +/* + * Super Lead rope mod + * Copyright (C) 2025 R3944Realms + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package top.r3944realms.superleadropetest.config; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class OffsetReadTest { + private static final Pattern OFFSET_PATTERN = Pattern.compile( + "(?i)(?:vec3|vec3d|vector3|offset)\\s*\\(\\s*([-+]?[0-9]*\\.?[0-9]+)\\s*,\\s*([-+]?[0-9]*\\.?[0-9]+)\\s*,\\s*([-+]?[0-9]*\\.?[0-9]+)\\s*\\)\\s*:\\s*\\[\\s*([^]]+?)\\s*]\\s*" + ); + private static boolean isValidEntityRefFormat(String s) { + if (s.startsWith("#")) { + String body = s.substring(1); + // 支持 #modid (整个模组)或 #modid:tag_name (标签) + return body.matches("[a-z0-9_]+(:[a-z0-9_/]+)?"); + } + // 普通实体 ID: modid:entity_id + return s.matches("[a-z0-9_]+:[a-z0-9_/]+"); + } + private static boolean isValidOffsetRefFormat(String s) { + // 匹配格式: function_name(x,y,z) : [entity_list] + Matcher matcher = OFFSET_PATTERN.matcher(s); + if (!matcher.matches()) { + return false; + } + + // 检查坐标值是否有效 + try { + // 组索引 现在坐标在组1、2、3,实体列表在组4 + Double.parseDouble(matcher.group(1)); + Double.parseDouble(matcher.group(2)); + Double.parseDouble(matcher.group(3)); + + // 检查实体列表格式 + String entityList = matcher.group(4); + String[] entities = entityList.split(","); + for (String entity : entities) { + if (!isValidEntityRefFormat(entity.trim())) { + return false; + } + } + return true; + } catch (NumberFormatException e) { + return false; + } + } + private static void testCase(String testString, boolean expected) { + boolean result = isValidOffsetRefFormat(testString); + String status = result == expected ? "✓ PASS" : "✗ FAIL"; + System.out.printf("%s: %s -> %s (expected: %s)%n", + status, testString, result, expected); + + if (result) { + Matcher matcher = OFFSET_PATTERN.matcher(testString); + if (matcher.matches()) { + System.out.printf(" 解析结果: X=%s, Y=%s, Z=%s, Entities=%s%n%n", + matcher.group(1), matcher.group(2), matcher.group(3), matcher.group(4)); + } + } else { + System.out.println(); + } + } + + public static void main(String[] args) { + System.out.println("=== 有效测试用例 ==="); + + // 基本格式测试 + testCase("vec3(0,0.2,0) : [minecraft:bee]", true); + testCase("Vec3(0, 0.2, 0) : [minecraft:bee]", true); + testCase("VEC3(0,0.2,0) : [minecraft:bee]", true); + + // 不同函数名测试 + testCase("vec3d(0,0.2,0) : [minecraft:bee]", true); + testCase("Vector3(0,0.2,0) : [minecraft:bee]", true); + testCase("offset(0,0.2,0) : [minecraft:bee]", true); + + // 空格兼容测试 + testCase("vec3( 0 , 0.2 , 0 ) : [ minecraft:bee ]", true); + testCase("vec3(0,0.2,0) : [minecraft:bee] ", true); + testCase("vec3(0, 0.2, 0) : [ minecraft:bee ] ", true); + + // 多实体测试 + testCase("vec3(0,1.0,0) : [minecraft:horse, minecraft:donkey]", true); + testCase("vec3(0,0.5,0) : [#minecraft:boats, #minecraft:minecarts]", true); + + // 标签和模组测试 + testCase("vec3(0,0.5,0) : [#minecraft:boats]", true); + testCase("vec3(0,0.3,0) : [#minecraft]", true); + + // 负数和小数测试 + testCase("vec3(-1, 1.5, 2.8) : [minecraft:horse]", true); + testCase("vec3(0.0, -0.5, 1.23) : [minecraft:bee]", true); + + System.out.println("=== 无效测试用例 ==="); + + // 错误函数名 + testCase("vector(0,0.2,0) : [minecraft:bee]", false); + testCase("pos(0,0.2,0) : [minecraft:bee]", false); + + // 格式错误 + testCase("vec3(0,0.2,0) : minecraft:bee]", false); // 缺少左括号 + testCase("vec3(0,0.2,0) : [minecraft:bee", false); // 缺少右括号 + testCase("vec3(0,0.2) : [minecraft:bee]", false); // 缺少Z坐标 + + // 无效坐标 + testCase("vec3(a,b,c) : [minecraft:bee]", false); + testCase("vec3(0,0.2,invalid) : [minecraft:bee]", false); + + // 无效实体引用 + testCase("vec3(0,0.2,0) : [invalid_entity]", false); + testCase("vec3(0,0.2,0) : [minecraft:bee, invalid]", false); + + // 缺少冒号 + testCase("vec3(0,0.2,0) [minecraft:bee]", false); + + System.out.println("=== 测试完成 ==="); + } +}