feat: API封装

fix: 部分内容
This commit is contained in:
叁玖领域 2025-09-15 00:37:57 +08:00
parent 1a56faad9f
commit e82f84f2a4
39 changed files with 2530 additions and 334 deletions

View File

@ -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 { configurations {
@ -150,8 +153,10 @@ dependencies {
modRuntimeOnly("mezz.jei:jei-${minecraft_version}-forge:${jei_version}") modRuntimeOnly("mezz.jei:jei-${minecraft_version}-forge:${jei_version}")
modRuntimeOnly("curse.maven:spark-361579:4738952") modRuntimeOnly("curse.maven:spark-361579:4738952")
compileOnly ('me.lucko:spark-api:0.1-SNAPSHOT') 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 ========== // ========== ProGuard ==========
tasks.register('proguard', ProGuardTask) { tasks.register('proguard', ProGuardTask) {
@ -231,8 +244,8 @@ tasks.register('proguard', ProGuardTask) {
// JDK jmods // JDK jmods
libraryjars "${System.getProperty('java.home')}/jmods" libraryjars "${System.getProperty('java.home')}/jmods"
// // 使
configurations.compileClasspath.files.each { file -> configurations.proguardLibs.resolve().each { file ->
libraryjars file.absolutePath libraryjars file.absolutePath
} }
@ -298,7 +311,14 @@ tasks.register("runWithRenderDoc", Exec) {
println "Environment MOD_CLASSES: ${environment['MOD_CLASSES']}" 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
idea { idea {

4
proguard.pro vendored
View File

@ -36,6 +36,10 @@
-keepclassmembers class cpw.mods.** { *; } -keepclassmembers class cpw.mods.** { *; }
-dontwarn cpw.mods.** -dontwarn cpw.mods.**
-keep class mezz.jei.**
-keepclassmembers class mezz.jei.**{ *; }
-dontwarn mezz.jei.**
#--------------------------------------- #---------------------------------------
# 保留资源文件 (mods.toml / assets / data / pack.mcmeta) # 保留资源文件 (mods.toml / assets / data / pack.mcmeta)
#--------------------------------------- #---------------------------------------

View File

@ -50,8 +50,11 @@ import net.minecraftforge.event.level.LevelEvent;
import net.minecraftforge.event.server.ServerStartingEvent; import net.minecraftforge.event.server.ServerStartingEvent;
import net.minecraftforge.event.server.ServerStoppingEvent; import net.minecraftforge.event.server.ServerStoppingEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.event.config.ModConfigEvent;
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
import org.jetbrains.annotations.NotNull; 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.CapabilityHandler;
import top.r3944realms.superleadrope.content.capability.CapabilityRemainder; import top.r3944realms.superleadrope.content.capability.CapabilityRemainder;
import top.r3944realms.superleadrope.content.capability.impi.LeashDataImpl; 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.PotatoMode;
import top.r3944realms.superleadrope.core.util.PotatoModeHelper; import top.r3944realms.superleadrope.core.util.PotatoModeHelper;
import top.r3944realms.superleadrope.datagen.data.SLPLangKeyValue; 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.model.RidingRelationship;
import top.r3944realms.superleadrope.util.riding.RidingApplier; import top.r3944realms.superleadrope.util.riding.RidingApplier;
import top.r3944realms.superleadrope.util.riding.RidingDismounts; import top.r3944realms.superleadrope.util.riding.RidingDismounts;
@ -84,6 +89,7 @@ import java.util.UUID;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
public class CommonEventHandler { 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) @net.minecraftforge.fml.common.Mod.EventBusSubscriber(modid = SuperLeadRope.MOD_ID, bus = net.minecraftforge.fml.common.Mod.EventBusSubscriber.Bus.FORGE)
public static class Game { public static class Game {
@SubscribeEvent @SubscribeEvent
@ -91,8 +97,8 @@ public class CommonEventHandler {
Entity entity = event.getEntity(); Entity entity = event.getEntity();
if (entity.level().isClientSide) return; if (entity.level().isClientSide) return;
if (entity instanceof LivingEntity || entity instanceof Boat || entity instanceof Minecart) { if (entity instanceof LivingEntity || entity instanceof Boat || entity instanceof Minecart) {
entity.getCapability(CapabilityHandler.LEASH_DATA_CAP).ifPresent(LeashSyncManager.Data::track); LeashDataAPI.getLeashData(entity).ifPresent(LeashSyncManager.Data::track);
entity.getCapability(CapabilityHandler.LEASH_STATE_CAP).ifPresent(LeashSyncManager.State::track); LeashStateAPI.getLeashState(entity).ifPresent(LeashSyncManager.State::track);
if (entity instanceof ServerPlayer serverPlayer) { if (entity instanceof ServerPlayer serverPlayer) {
LeashSyncManager.Data.forEach(i -> { LeashSyncManager.Data.forEach(i -> {
if (i.isLeashedBy(serverPlayer) && i.isInDelayedLeash(serverPlayer.getUUID())) { 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); LeashDataAPI.getLeashData(entity).ifPresent(LeashSyncManager.Data::untrack);
entity.getCapability(CapabilityHandler.LEASH_STATE_CAP).ifPresent(LeashSyncManager.State::untrack); LeashStateAPI.getLeashState(entity).ifPresent(LeashSyncManager.State::untrack);
} }
} }
@SubscribeEvent @SubscribeEvent
@ -139,9 +145,6 @@ public class CommonEventHandler {
@SubscribeEvent @SubscribeEvent
public static void onPlayerRightHitOnBlock(PlayerInteractEvent.RightClickBlock event) { public static void onPlayerRightHitOnBlock(PlayerInteractEvent.RightClickBlock event) {
Level level = event.getLevel(); Level level = event.getLevel();
if (level.isClientSide) {
return;
}
BlockPos blockPos = event.getHitVec().getBlockPos(); BlockPos blockPos = event.getHitVec().getBlockPos();
BlockState blockState = level.getBlockState(blockPos); BlockState blockState = level.getBlockState(blockPos);
Player player = event.getEntity(); Player player = event.getEntity();
@ -256,7 +259,7 @@ public class CommonEventHandler {
List<Entity> entities = LeashDataImpl.leashableInArea(telEntity); List<Entity> entities = LeashDataImpl.leashableInArea(telEntity);
//规则关闭则禁止 //规则关闭则禁止
if(!SLPGameruleRegistry.getGameruleBoolValue(event.getEntity().level(), TeleportWithLeashedEntities.ID)) { 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; return;
} }
for (Entity beLeashedEntity : entities) { for (Entity beLeashedEntity : entities) {
@ -268,9 +271,9 @@ public class CommonEventHandler {
Vec3 originalDeltaMovement = beLeashedEntity.getDeltaMovement(); Vec3 originalDeltaMovement = beLeashedEntity.getDeltaMovement();
AtomicReference<ILeashData.LeashInfo> originalLeashInfo = new AtomicReference<>(); AtomicReference<ILeashData.LeashInfo> originalLeashInfo = new AtomicReference<>();
beLeashedEntity.getCapability(CapabilityHandler.LEASH_DATA_CAP).ifPresent(cap -> { LeashDataAPI.getLeashData(beLeashedEntity).ifPresent(data -> {
originalLeashInfo.set(cap.getLeashInfo(telEntity).orElse(null)); originalLeashInfo.set(data.getLeashInfo(telEntity).orElse(null));
cap.removeLeash(telEntity); data.removeLeash(telEntity);
}); });
@ -302,14 +305,11 @@ public class CommonEventHandler {
entity.setPose(originalPose); entity.setPose(originalPose);
} }
// --- 恢复拴绳 --- // --- 将holder替换 ---
ILeashData.LeashInfo leashInfo = Optional.ofNullable(originalLeashInfo.get()) ILeashData.LeashInfo leashInfo = Optional.ofNullable(originalLeashInfo.get())
.map(info -> info.transferHolder(telEntity))
.orElse(ILeashData.LeashInfo.EMPTY); .orElse(ILeashData.LeashInfo.EMPTY);
beLeashedEntity.getCapability(CapabilityHandler.LEASH_DATA_CAP).ifPresent( LeashDataAPI.LeashOperations.attachWithInfo(beLeashedEntity, telEntity, leashInfo);
cap -> cap.addLeash(telEntity, leashInfo)
);
// --- 重新应用骑乘关系仅保留白名单根载具 --- // --- 重新应用骑乘关系仅保留白名单根载具 ---
RidingRelationship filteredRelationship = RidingSaver.filterByWhitelistRoot(originalRidingRelationship); 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) @net.minecraftforge.fml.common.Mod.EventBusSubscriber(modid = SuperLeadRope.MOD_ID, bus= net.minecraftforge.fml.common.Mod.EventBusSubscriber.Bus.MOD)
public static class Mod { public static class Mod {
@SubscribeEvent @SubscribeEvent
public static void onCommonInit (FMLCommonSetupEvent event) { public static void onFMLCommonInit(FMLCommonSetupEvent event) {
event.enqueueWork(Mod::checkAndSet);
event.enqueueWork(SLPGameruleRegistry::register);//规则注册 event.enqueueWork(SLPGameruleRegistry::register);//规则注册
} }
@SubscribeEvent @SubscribeEvent
@ -375,6 +376,43 @@ public class CommonEventHandler {
event.accept(SLPItems.SUPER_LEAD_ROPE); 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);
}
}
} }
} }

View File

@ -33,6 +33,7 @@ import top.r3944realms.superleadrope.util.file.ConfigUtil;
@Mod(value = SuperLeadRope.MOD_ID) @Mod(value = SuperLeadRope.MOD_ID)
public class SuperLeadRope { public class SuperLeadRope {
public static final Logger logger = LoggerFactory.getLogger(SuperLeadRope.class); public static final Logger logger = LoggerFactory.getLogger(SuperLeadRope.class);
public static final String MOD_ID = "superleadrope"; public static final String MOD_ID = "superleadrope";
public SuperLeadRope() { public SuperLeadRope() {
IEventBus eventBus = FMLJavaModLoadingContext.get().getModEventBus(); IEventBus eventBus = FMLJavaModLoadingContext.get().getModEventBus();
@ -41,6 +42,7 @@ public class SuperLeadRope {
SLPSoundEvents.register(eventBus); SLPSoundEvents.register(eventBus);
NetworkHandler.register(); NetworkHandler.register();
initialize(); initialize();
} }
public static void initialize() { public static void initialize() {
logger.info("Initializing SuperLeadRope"); logger.info("Initializing SuperLeadRope");

View File

@ -69,7 +69,7 @@ public class ClientEventHandler {
(itemStack, clientLevel, livingEntity, i) -> { (itemStack, clientLevel, livingEntity, i) -> {
if (!itemStack.isDamageableItem()) return 0.0F; 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(); PotatoMode mode = getCurrentMode();

View File

@ -24,9 +24,9 @@ import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import top.r3944realms.superleadrope.SuperLeadRope; import top.r3944realms.superleadrope.SuperLeadRope;
import top.r3944realms.superleadrope.client.renderer.resolver.SuperLeashStateResolver; 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.capability.inter.ILeashData;
import top.r3944realms.superleadrope.content.entity.SuperLeashKnotEntity; import top.r3944realms.superleadrope.content.entity.SuperLeashKnotEntity;
import top.r3944realms.superleadrope.util.capability.LeashDataAPI;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
@ -46,10 +46,9 @@ public class LeashRenderHandler {
// 遍历摄像机附近所有实体 // 遍历摄像机附近所有实体
for (Entity entity : level.getEntitiesOfClass(Entity.class, for (Entity entity : level.getEntitiesOfClass(Entity.class,
cameraEntity.getBoundingBox().inflate(50))) { cameraEntity.getBoundingBox().inflate(100))) {
entity.getCapability(CapabilityHandler.LEASH_DATA_CAP).ifPresent(leashData -> { LeashDataAPI.getLeashData(entity).ifPresent(leashData -> {
if(leashData instanceof ILeashData) {}
for (ILeashData.LeashInfo leashInfo : leashData.getAllLeashes()) { for (ILeashData.LeashInfo leashInfo : leashData.getAllLeashes()) {
renderLeashFromInfo(entity, leashInfo, poseStack, bufferSource, partialTick); renderLeashFromInfo(entity, leashInfo, poseStack, bufferSource, partialTick);
} }

View File

@ -52,7 +52,7 @@ public class SuperLeashRenderer {
int skyLightEnd = getSkyLight(BlockPos.containing(endWorld)); int skyLightEnd = getSkyLight(BlockPos.containing(endWorld));
// 差向量 + 偏移 // 差向量 + 偏移
Offsets offsets = computeOffsets(state, startWorld, endWorld); Offsets offsets = computeOffsets(startWorld, endWorld);
// pass1: 0 N // pass1: 0 N
for (int i = 0; i <= LEASH_STEPS; i++) { 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 dx = (float) (end.x - start.x);
float dy = (float) (end.y - start.y); float dy = (float) (end.y - start.y);
float dz = (float) (end.z - start.z); float dz = (float) (end.z - start.z);

View File

@ -20,11 +20,10 @@ import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.Vec3;
import top.r3944realms.superleadrope.SuperLeadRope; import top.r3944realms.superleadrope.SuperLeadRope;
import top.r3944realms.superleadrope.client.renderer.state.SuperLeashRenderState; 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.impi.LeashDataImpl;
import top.r3944realms.superleadrope.content.capability.inter.ILeashData; import top.r3944realms.superleadrope.content.capability.inter.ILeashData;
import top.r3944realms.superleadrope.content.entity.SuperLeashKnotEntity; 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.*;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
@ -56,7 +55,7 @@ public class SuperLeashStateResolver {
} }
AtomicReference<Vec3> holderOffset = new AtomicReference<>(); AtomicReference<Vec3> holderOffset = new AtomicReference<>();
AtomicReference<Vec3> entityOffset = new AtomicReference<>(); AtomicReference<Vec3> entityOffset = new AtomicReference<>();
LeashUtil.getLeashState(leashedEntity).ifPresent(state -> LeashStateAPI.getLeashState(leashedEntity).ifPresent(state ->
state state
.getLeashState(holder) .getLeashState(holder)
.ifPresent(ls -> { .ifPresent(ls -> {
@ -110,7 +109,6 @@ public class SuperLeashStateResolver {
return Optional.of(new SuperLeashRenderState( return Optional.of(new SuperLeashRenderState(
currentHolderPos, currentHolderPos,
currentEntityPos, currentEntityPos,
leashInfo.attachOffset(),
lastHolderPos, lastHolderPos,
lastEntityPos, lastEntityPos,
tension, tension,

View File

@ -20,7 +20,6 @@ import net.minecraft.world.phys.Vec3;
public record SuperLeashRenderState( public record SuperLeashRenderState(
Vec3 startPos, // 当前帧起点位置 Vec3 startPos, // 当前帧起点位置
Vec3 endPos, // 当前帧终点位置 Vec3 endPos, // 当前帧终点位置
Vec3 attachOffset, // 拴绳附着点偏移
Vec3 lastStartPos, // 上一帧起点位置(用于摆动计算) Vec3 lastStartPos, // 上一帧起点位置(用于摆动计算)
Vec3 lastEndPos, // 上一帧终点位置(用于摆动计算) Vec3 lastEndPos, // 上一帧终点位置(用于摆动计算)
float tension, // 张力强度(0.0-1.0) float tension, // 张力强度(0.0-1.0)

View File

@ -18,6 +18,8 @@ package top.r3944realms.superleadrope.config;
import net.minecraftforge.common.ForgeConfigSpec; import net.minecraftforge.common.ForgeConfigSpec;
import java.util.List; import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class LeashCommonConfig { public class LeashCommonConfig {
public static ForgeConfigSpec.Builder BUILDER = new ForgeConfigSpec.Builder(); public static ForgeConfigSpec.Builder BUILDER = new ForgeConfigSpec.Builder();
@ -38,13 +40,18 @@ public class LeashCommonConfig {
public final ForgeConfigSpec.DoubleValue springDampening; public final ForgeConfigSpec.DoubleValue springDampening;
public final ForgeConfigSpec.ConfigValue<List<? extends Double>> axisSpecificElasticity; public final ForgeConfigSpec.ConfigValue<List<? extends Double>> axisSpecificElasticity;
public final ForgeConfigSpec.IntValue maxLeashesPerEntity; public final ForgeConfigSpec.IntValue maxLeashesPerEntity;
public final ForgeConfigSpec.ConfigValue<List<? extends String>> 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) { public Common(ForgeConfigSpec.Builder builder) {
BUILDER.push("Command"); BUILDER.push("Command");
EnableSLPModCommandPrefix = builder EnableSLPModCommandPrefix = builder
.comment("The prefix of this mod's commands") .comment("The prefix of this mod's commands")
.define("SLPModCommandPrefix", true); .define("SLPModCommandPrefix", true);
SLPModCommandPrefix = builder 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"); .define("EnableSLPModCommandPrefix", "slp");
BUILDER.pop(); BUILDER.pop();
builder.push("Entity"); builder.push("Entity");
@ -59,7 +66,7 @@ public class LeashCommonConfig {
.defineListAllowEmpty( .defineListAllowEmpty(
List.of("allowedTeleportEntities"), List.of("allowedTeleportEntities"),
List.of("#minecraft", "modernlife:bicycle", "modernlife:motorboat"), List.of("#minecraft", "modernlife:bicycle", "modernlife:motorboat"),
o -> o instanceof String s && isValidFormat(s) o -> o instanceof String s && isValidEntityRefFormat(s)
); );
builder.pop(); builder.pop();
builder.push("LeashSettings"); builder.push("LeashSettings");
@ -87,20 +94,70 @@ public class LeashCommonConfig {
.defineInRange("maxLeashesPerEntity", 6, 1, 24); .defineInRange("maxLeashesPerEntity", 6, 1, 24);
builder.pop(); 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("#")) { if (s.startsWith("#")) {
String body = s.substring(1); String body = s.substring(1);
// 支持 #modid 整个模组 // 支持 #modid 整个模组 #modid:tag_name 标签
if (body.matches("[a-z0-9_]+")) { return body.matches("[a-z0-9_]+(:[a-z0-9_/]+)?");
return true;
}
// 支持 #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_/]+"); 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;
}
}
} }
} }

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<String, double[]> entityOffsetMap = new ConcurrentHashMap<>();
private final Map<String, double[]> tagOffsetMap = new ConcurrentHashMap<>();
private final Map<String, double[]> modOffsetMap = new ConcurrentHashMap<>();
// 缓存常用配置值以提高性能
private volatile List<String> 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<Double> axisSpecificElasticityCache;
private volatile int maxLeashesPerEntityCache;
public LeashConfigManager() {
this.reloadAll();
}
/**
* 解析偏移配置线程安全
*/
public void parseOffsetConfig() {
Map<String, double[]> newEntityOffsetMap = new HashMap<>();
Map<String, double[]> newTagOffsetMap = new HashMap<>();
Map<String, double[]> newModOffsetMap = new HashMap<>();
List<? extends String> 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<String> 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<String> 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<String> 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<EntityType<?>> 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<Double> 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();
}
}

View File

@ -18,7 +18,7 @@ package top.r3944realms.superleadrope.content.capability;
import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.player.Player;
import net.minecraftforge.event.entity.player.PlayerEvent; 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 class CapabilityRemainder {
public static void onPlayerClone(PlayerEvent.Clone event) { public static void onPlayerClone(PlayerEvent.Clone event) {
@ -26,9 +26,9 @@ public class CapabilityRemainder {
if(newEntity instanceof ServerPlayer newPlayer) { if(newEntity instanceof ServerPlayer newPlayer) {
Player original = event.getOriginal(); Player original = event.getOriginal();
original.reviveCaps(); original.reviveCaps();
LeashUtil.getLeashState(original) LeashStateAPI.getLeashState(original)
.ifPresent(oldCap -> .ifPresent(oldCap ->
LeashUtil.getLeashState(newPlayer) LeashStateAPI.getLeashState(newPlayer)
.ifPresent(newData -> .ifPresent(newData ->
newData.copy(oldCap, newEntity) newData.copy(oldCap, newEntity)
) )

View File

@ -21,8 +21,8 @@ import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.NbtUtils; import net.minecraft.nbt.NbtUtils;
import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.level.ServerPlayer;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.Mob; import net.minecraft.world.entity.Mob;
import net.minecraft.world.entity.animal.horse.Llama; 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.Boat;
import net.minecraft.world.entity.vehicle.Minecart; import net.minecraft.world.entity.vehicle.Minecart;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import net.minecraft.world.level.pathfinder.Path;
import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.Vec3;
import net.minecraftforge.network.PacketDistributor; import net.minecraftforge.network.PacketDistributor;
import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import top.r3944realms.superleadrope.CommonEventHandler;
import top.r3944realms.superleadrope.SuperLeadRope; import top.r3944realms.superleadrope.SuperLeadRope;
import top.r3944realms.superleadrope.config.LeashCommonConfig;
import top.r3944realms.superleadrope.content.capability.inter.ILeashData; import top.r3944realms.superleadrope.content.capability.inter.ILeashData;
import top.r3944realms.superleadrope.content.entity.SuperLeashKnotEntity; 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.NetworkHandler;
import top.r3944realms.superleadrope.network.toClient.LeashDataSyncPacket; import top.r3944realms.superleadrope.network.toClient.LeashDataSyncPacket;
import top.r3944realms.superleadrope.util.capability.LeashUtil; import top.r3944realms.superleadrope.util.capability.LeashDataAPI;
import top.r3944realms.superleadrope.util.nbt.NBTReader; import top.r3944realms.superleadrope.util.capability.LeashStateAPI;
import top.r3944realms.superleadrope.util.nbt.NBTWriter;
import top.r3944realms.superleadrope.util.riding.RindingLeash; import top.r3944realms.superleadrope.util.riding.RindingLeash;
import java.util.*; import java.util.*;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -85,34 +85,6 @@ import java.util.stream.Stream;
* </table> * </table>
*/ */
public class LeashDataImpl implements ILeashData { 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<? extends Double> 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 final Entity entity;
private boolean needsSync = false; private boolean needsSync = false;
private long lastSyncTime; private long lastSyncTime;
@ -172,18 +144,18 @@ public class LeashDataImpl implements ILeashData {
@Override @Override
public boolean addLeash(Entity holder) { public boolean addLeash(Entity holder) {
return addLeash(holder, Config.maxLeashDistance()); return addLeash(holder, CommonEventHandler.leashConfigManager.getMaxLeashLength());
} }
@Override @Override
public boolean addLeash(Entity holder, String reserved) { public boolean addLeash(Entity holder, String reserved) {
return addLeash(holder, Config.maxLeashDistance(), reserved); return addLeash(holder, CommonEventHandler.leashConfigManager.getMaxLeashLength(), reserved);
} }
// 添加拴绳支持自定义最大长度 // 添加拴绳支持自定义最大长度
@Override @Override
public boolean addLeash(Entity holder, double maxDistance) { 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 字段 // 添加拴绳支持自定义最大长度 + reserved 字段
@Override @Override
public boolean addLeash(Entity holder, double maxDistance, String reserved) { 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);
} }
// 添加拴绳最终实现支持最大长度弹性距离保持 Tickreserved // 添加拴绳最终实现支持最大长度弹性距离保持 Tickreserved
@ -219,7 +191,6 @@ public class LeashDataImpl implements ILeashData {
LeashInfo info = LeashInfo.create( LeashInfo info = LeashInfo.create(
holder, holder,
reserved, reserved,
calculateAttachOffset(entity),
maxDistance, maxDistance,
elasticDistance, elasticDistance,
maxKeepLeashTicks, maxKeepLeashTicks,
@ -231,7 +202,7 @@ public class LeashDataImpl implements ILeashData {
} else { } else {
leashHolders.put(holder.getUUID(), info); leashHolders.put(holder.getUUID(), info);
} }
LeashStateAPI.Operations.attach(entity, holder);
markForSync(); markForSync();
return true; return true;
} }
@ -301,7 +272,6 @@ public class LeashDataImpl implements ILeashData {
old.holderUUIDOpt().get(), old.holderUUIDOpt().get(),
old.holderIdOpt().get(), old.holderIdOpt().get(),
old.reserved(), old.reserved(),
old.attachOffset(),
newMaxDistance, newMaxDistance,
old.elasticDistance(), old.elasticDistance(),
old.keepLeashTicks(), old.keepLeashTicks(),
@ -316,7 +286,6 @@ public class LeashDataImpl implements ILeashData {
old.holderUUIDOpt().get(), old.holderUUIDOpt().get(),
old.holderIdOpt().get(), old.holderIdOpt().get(),
old.reserved(), old.reserved(),
old.attachOffset(),
newMaxDistance, newMaxDistance,
old.elasticDistance(), old.elasticDistance(),
Math.min(old.keepLeashTicks(), newMaxKeepLeashTicks), Math.min(old.keepLeashTicks(), newMaxKeepLeashTicks),
@ -331,7 +300,6 @@ public class LeashDataImpl implements ILeashData {
old.holderUUIDOpt().get(), old.holderUUIDOpt().get(),
old.holderIdOpt().get(), old.holderIdOpt().get(),
reserved, reserved,
old.attachOffset(),
distance, distance,
old.elasticDistance(), old.elasticDistance(),
Math.min(old.keepLeashTicks(), maxKeepTicks), Math.min(old.keepLeashTicks(), maxKeepTicks),
@ -346,7 +314,6 @@ public class LeashDataImpl implements ILeashData {
old.blockPosOpt().get(), old.blockPosOpt().get(),
old.holderIdOpt().get(), old.holderIdOpt().get(),
old.reserved(), old.reserved(),
old.attachOffset(),
newMaxDistance, newMaxDistance,
old.elasticDistance(), old.elasticDistance(),
old.keepLeashTicks(), old.keepLeashTicks(),
@ -361,7 +328,6 @@ public class LeashDataImpl implements ILeashData {
old.blockPosOpt().get(), old.blockPosOpt().get(),
old.holderIdOpt().get(), old.holderIdOpt().get(),
old.reserved(), old.reserved(),
old.attachOffset(),
newMaxDistance, newMaxDistance,
old.elasticDistance(), old.elasticDistance(),
Math.min(old.keepLeashTicks(), newMaxKeepLeashTicks), Math.min(old.keepLeashTicks(), newMaxKeepLeashTicks),
@ -376,7 +342,6 @@ public class LeashDataImpl implements ILeashData {
old.blockPosOpt().get(), old.blockPosOpt().get(),
old.holderIdOpt().get(), old.holderIdOpt().get(),
reserved, reserved,
old.attachOffset(),
distance, distance,
old.elasticDistance(), old.elasticDistance(),
Math.min(old.keepLeashTicks(), maxKeepTicks), Math.min(old.keepLeashTicks(), maxKeepTicks),
@ -399,7 +364,6 @@ public class LeashDataImpl implements ILeashData {
old.holderUUIDOpt().get(), old.holderUUIDOpt().get(),
old.holderIdOpt().get(), old.holderIdOpt().get(),
old.reserved(), old.reserved(),
old.attachOffset(),
old.maxDistance(), old.maxDistance(),
newElasticDistance, newElasticDistance,
old.keepLeashTicks(), old.keepLeashTicks(),
@ -414,7 +378,6 @@ public class LeashDataImpl implements ILeashData {
old.blockPosOpt().get(), old.blockPosOpt().get(),
old.holderIdOpt().get(), old.holderIdOpt().get(),
old.reserved(), old.reserved(),
old.attachOffset(),
old.maxDistance(), old.maxDistance(),
newElasticDistance, newElasticDistance,
old.keepLeashTicks(), old.keepLeashTicks(),
@ -444,7 +407,6 @@ public class LeashDataImpl implements ILeashData {
old.holderUUIDOpt().get(), old.holderUUIDOpt().get(),
old.holderIdOpt().get(), old.holderIdOpt().get(),
old.reserved(), old.reserved(),
old.attachOffset(),
old.maxDistance(), old.maxDistance(),
newElasticDistance, newElasticDistance,
Math.min(old.keepLeashTicks(), newMaxKeepLeashTicks), Math.min(old.keepLeashTicks(), newMaxKeepLeashTicks),
@ -459,7 +421,6 @@ public class LeashDataImpl implements ILeashData {
old.holderUUIDOpt().get(), old.holderUUIDOpt().get(),
old.holderIdOpt().get(), old.holderIdOpt().get(),
reserved, reserved,
old.attachOffset(),
old.maxDistance(), old.maxDistance(),
distance, distance,
Math.min(old.keepLeashTicks(), maxKeepTicks), Math.min(old.keepLeashTicks(), maxKeepTicks),
@ -474,7 +435,6 @@ public class LeashDataImpl implements ILeashData {
old.blockPosOpt().get(), old.blockPosOpt().get(),
old.holderIdOpt().get(), old.holderIdOpt().get(),
old.reserved(), old.reserved(),
old.attachOffset(),
old.maxDistance(), old.maxDistance(),
newElasticDistance, newElasticDistance,
old.keepLeashTicks(), old.keepLeashTicks(),
@ -489,7 +449,6 @@ public class LeashDataImpl implements ILeashData {
old.blockPosOpt().get(), old.blockPosOpt().get(),
old.holderIdOpt().get(), old.holderIdOpt().get(),
reserved, reserved,
old.attachOffset(),
old.maxDistance(), old.maxDistance(),
newElasticDistance, newElasticDistance,
Math.min(old.keepLeashTicks(), newMaxKeepLeashTicks), Math.min(old.keepLeashTicks(), newMaxKeepLeashTicks),
@ -502,13 +461,17 @@ public class LeashDataImpl implements ILeashData {
*/ */
@Override @Override
public void applyLeashForces() { public void applyLeashForces() {
Vec3 combinedForce = Vec3.ZERO; // 初始化合力向量 Vec3 combinedForce = Vec3.ZERO;
Vec3 combinedDirection = Vec3.ZERO;
int validLeashes = 0;
// 计算所有拴绳的合力 // 计算所有拴绳的合力和平均方向
for (Map.Entry<UUID, LeashInfo> entry : leashHolders.entrySet()) { for (Map.Entry<UUID, LeashInfo> entry : leashHolders.entrySet()) {
Vec3 force = calculateLeashForceForUUID(entry); Vec3 force = calculateLeashForceForUUID(entry);
if (force != null) { if (force != null) {
combinedForce = combinedForce.add(force); combinedForce = combinedForce.add(force);
combinedDirection = combinedDirection.add(force.normalize());
validLeashes++;
} }
} }
@ -516,27 +479,152 @@ public class LeashDataImpl implements ILeashData {
Vec3 force = calculateLeashForceForBlockPos(entry); Vec3 force = calculateLeashForceForBlockPos(entry);
if (force != null) { if (force != null) {
combinedForce = combinedForce.add(force); combinedForce = combinedForce.add(force);
combinedDirection = combinedDirection.add(force.normalize());
validLeashes++;
} }
} }
boolean hasForce = !combinedForce.equals(Vec3.ZERO); boolean hasForce = !combinedForce.equals(Vec3.ZERO);
Entity finalApplyEntity = RindingLeash.getFinalEntityForLeashIfForce(entity, hasForce); Entity finalApplyEntity = RindingLeash.getFinalEntityForLeashIfForce(entity, hasForce);
if (hasForce) {
if (hasForce) {
// 处理玩家和其他实体
if (finalApplyEntity instanceof ServerPlayer player) { if (finalApplyEntity instanceof ServerPlayer player) {
RindingLeash.applyForceToPlayer(player, combinedForce); RindingLeash.applyForceToPlayer(player, combinedForce);
return;
} else { } else {
finalApplyEntity.setDeltaMovement(finalApplyEntity.getDeltaMovement().add(combinedForce)); finalApplyEntity.setDeltaMovement(finalApplyEntity.getDeltaMovement().add(combinedForce));
finalApplyEntity.hurtMarked = true; 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); RindingLeash.protectAnimalMovement(finalApplyEntity, true);
} else { } else {
RindingLeash.protectAnimalMovement(finalApplyEntity, false); 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拴绳计算力 * 为UUID拴绳计算力
@ -569,9 +657,9 @@ public class LeashDataImpl implements ILeashData {
private Vec3 calculateLeashForce(Entity holder, Map.Entry<?, LeashInfo> entry) { private Vec3 calculateLeashForce(Entity holder, Map.Entry<?, LeashInfo> entry) {
Vec3 holderPos = holder.position().add(0, holder.getBbHeight() * 0.7, 0); Vec3 holderPos = holder.position().add(0, holder.getBbHeight() * 0.7, 0);
LeashInfo info = entry.getValue(); LeashInfo info = entry.getValue();
Vec3 entityPos = entity.position().add(info.attachOffset()); Vec3 entityPos = entity.position();
double distance = holderPos.distanceTo(entityPos); double distance = holderPos.distanceTo(entityPos);
double extremeSnapDist = info.maxDistance() * Config.leashExtremeSnapDistFactor(); double extremeSnapDist = info.maxDistance() * CommonEventHandler.leashConfigManager.getExtremeSnapFactor();
// 1. 检查是否超出断裂距离 // 1. 检查是否超出断裂距离
if (distance > extremeSnapDist) { if (distance > extremeSnapDist) {
@ -583,6 +671,8 @@ public class LeashDataImpl implements ILeashData {
} }
// 断裂 // 断裂
removeLeash(holder); removeLeash(holder);
//TODO: 是不是应该考虑让断裂统一发出声音还是就这样由断裂发出
entity.level().playSound(null, holder.getOnPos(), SLPSoundEvents.LEAD_BREAK.get(), SoundSource.PLAYERS);
return null; return null;
} }
@ -590,15 +680,6 @@ public class LeashDataImpl implements ILeashData {
Vec3 pullForce = Vec3.ZERO; Vec3 pullForce = Vec3.ZERO;
if (distance > info.elasticDistance()) { if (distance > info.elasticDistance()) {
pullForce = calculatePullForce(holderPos, entityPos, distance, info); 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 // 3. 重置缓冲Tick
@ -621,13 +702,13 @@ public class LeashDataImpl implements ILeashData {
} }
Vec3 pullForce = pullDirection.scale( Vec3 pullForce = pullDirection.scale(
(distance - info.elasticDistance()) * pullStrength * Config.springDampening() (distance - info.elasticDistance()) * pullStrength * CommonEventHandler.leashConfigManager.getSpringDampening()
); );
return new Vec3( return new Vec3(
pullForce.x * Config.axisSpecificElasticity().x, pullForce.x * CommonEventHandler.leashConfigManager.getXElasticity(),
pullForce.y * Config.axisSpecificElasticity().y, pullForce.y * CommonEventHandler.leashConfigManager.getXElasticity(),
pullForce.z * Config.axisSpecificElasticity().z pullForce.z * CommonEventHandler.leashConfigManager.getZElasticity()
); );
} }
@ -638,13 +719,13 @@ public class LeashDataImpl implements ILeashData {
double pullStrength = 1.0 + excessRatio * 2.0; double pullStrength = 1.0 + excessRatio * 2.0;
Vec3 pullForce = pullDirection.scale( Vec3 pullForce = pullDirection.scale(
(distance - info.elasticDistance()) * pullStrength * Config.springDampening() (distance - info.elasticDistance()) * pullStrength * CommonEventHandler.leashConfigManager.getSpringDampening()
); );
return new Vec3( return new Vec3(
pullForce.x * Config.axisSpecificElasticity().x, pullForce.x * CommonEventHandler.leashConfigManager.getXElasticity(),
pullForce.y * Config.axisSpecificElasticity().y, pullForce.y * CommonEventHandler.leashConfigManager.getYElasticity(),
pullForce.z * Config.axisSpecificElasticity().z pullForce.z * CommonEventHandler.leashConfigManager.getZElasticity()
); );
} }
@ -658,16 +739,20 @@ public class LeashDataImpl implements ILeashData {
@Override @Override
public boolean removeLeash(UUID holderUUID) { public boolean removeLeash(UUID holderUUID) {
boolean removed = leashHolders.remove(holderUUID) != null; boolean removed = leashHolders.remove(holderUUID) != null;
if (removed) if (removed) {
LeashStateAPI.Operations.detach(entity, holderUUID);
markForSync(); markForSync();
}
return removed; return removed;
} }
@Override @Override
public boolean removeLeash(BlockPos knotPos) { public boolean removeLeash(BlockPos knotPos) {
boolean removed = leashKnots.remove(knotPos) != null; boolean removed = leashKnots.remove(knotPos) != null;
if (removed) if (removed) {
LeashStateAPI.Operations.detach(entity, knotPos);
markForSync(); markForSync();
}
return removed; return removed;
} }
@ -675,18 +760,21 @@ public class LeashDataImpl implements ILeashData {
public void removeAllLeashes() { public void removeAllLeashes() {
leashHolders.clear(); leashHolders.clear();
leashKnots.clear(); leashKnots.clear();
LeashStateAPI.Offset.removeAll(entity);
markForSync(); markForSync();
} }
@Override @Override
public void removeAllHolderLeashes() { public void removeAllHolderLeashes() {
leashHolders.clear(); leashHolders.clear();
LeashStateAPI.Offset.removeAllUUIDs(entity);
markForSync(); markForSync();
} }
@Override @Override
public void removeAllKnotLeashes() { public void removeAllKnotLeashes() {
leashKnots.clear(); leashKnots.clear();
LeashStateAPI.Offset.removeAllBlockPoses(entity);
markForSync(); markForSync();
} }
@ -716,6 +804,7 @@ public class LeashDataImpl implements ILeashData {
LeashInfo leashInfo = info.transferHolder(newHolder); LeashInfo leashInfo = info.transferHolder(newHolder);
leashHolders.put(newHolder.getUUID(), leashInfo); leashHolders.put(newHolder.getUUID(), leashInfo);
} }
LeashStateAPI.Operations.transfer(entity, oldHolderUUID, newHolder);
markForSync(); markForSync();
return true; return true;
} }
@ -730,6 +819,7 @@ public class LeashDataImpl implements ILeashData {
LeashInfo leashInfo = info.transferHolder(newHolder, reserved); LeashInfo leashInfo = info.transferHolder(newHolder, reserved);
leashHolders.put(newHolder.getUUID(), leashInfo); leashHolders.put(newHolder.getUUID(), leashInfo);
} }
LeashStateAPI.Operations.transfer(entity, oldHolderUUID, newHolder);
markForSync(); markForSync();
return true; return true;
} }
@ -745,6 +835,7 @@ public class LeashDataImpl implements ILeashData {
LeashInfo leashInfo = info.transferHolder(newHolder); LeashInfo leashInfo = info.transferHolder(newHolder);
leashHolders.put(newHolder.getUUID(), leashInfo); leashHolders.put(newHolder.getUUID(), leashInfo);
} }
LeashStateAPI.Operations.transfer(entity, knotPos, newHolder);
markForSync(); markForSync();
return true; return true;
} }
@ -760,6 +851,7 @@ public class LeashDataImpl implements ILeashData {
LeashInfo leashInfo = info.transferHolder(newHolder, reserved); LeashInfo leashInfo = info.transferHolder(newHolder, reserved);
leashHolders.put(newHolder.getUUID(), leashInfo); leashHolders.put(newHolder.getUUID(), leashInfo);
} }
LeashStateAPI.Operations.transfer(entity, knotPos, newHolder);
markForSync(); markForSync();
return true; return true;
} }
@ -885,7 +977,6 @@ public class LeashDataImpl implements ILeashData {
} }
infoTag.putInt("HolderID", info.holderIdOpt().get()); infoTag.putInt("HolderID", info.holderIdOpt().get());
infoTag.putString("LeashItem", info.reserved()); infoTag.putString("LeashItem", info.reserved());
infoTag.put("Offset", NBTWriter.writeVec3(info.attachOffset()));
infoTag.putDouble("MaxDistance", info.maxDistance()); infoTag.putDouble("MaxDistance", info.maxDistance());
infoTag.putDouble("ElasticDistance", info.elasticDistance()); infoTag.putDouble("ElasticDistance", info.elasticDistance());
infoTag.putInt("KeepLeashTicks", info.keepLeashTicks()); infoTag.putInt("KeepLeashTicks", info.keepLeashTicks());
@ -933,7 +1024,6 @@ public class LeashDataImpl implements ILeashData {
infoTag.getUUID("HolderUUID"), infoTag.getUUID("HolderUUID"),
infoTag.getInt("HolderID"), infoTag.getInt("HolderID"),
infoTag.getString("LeashItem"), infoTag.getString("LeashItem"),
NBTReader.readVec3(infoTag.getCompound("Offset")),
infoTag.getDouble("MaxDistance"), infoTag.getDouble("MaxDistance"),
infoTag.contains("ElasticDistance") ? infoTag.getDouble("ElasticDistance") : 6.0, infoTag.contains("ElasticDistance") ? infoTag.getDouble("ElasticDistance") : 6.0,
infoTag.getInt("KeepLeashTicks"), infoTag.getInt("KeepLeashTicks"),
@ -949,7 +1039,6 @@ public class LeashDataImpl implements ILeashData {
NbtUtils.readBlockPos(infoTag.getCompound("KnotBlockPos")), NbtUtils.readBlockPos(infoTag.getCompound("KnotBlockPos")),
infoTag.getInt("HolderID"), infoTag.getInt("HolderID"),
infoTag.getString("LeashItem"), infoTag.getString("LeashItem"),
NBTReader.readVec3(infoTag.getCompound("Offset")),
infoTag.getDouble("MaxDistance"), infoTag.getDouble("MaxDistance"),
infoTag.contains("ElasticDistance") ? infoTag.getDouble("ElasticDistance") : 6.0, infoTag.contains("ElasticDistance") ? infoTag.getDouble("ElasticDistance") : 6.0,
infoTag.getInt("KeepLeashTicks"), infoTag.getInt("KeepLeashTicks"),
@ -961,7 +1050,7 @@ public class LeashDataImpl implements ILeashData {
@Override @Override
public boolean canBeLeashed() { public boolean canBeLeashed() {
return (leashHolders.size() + leashKnots.size()) <= Config.maxLeashesPerEntity(); return (leashHolders.size() + leashKnots.size()) <= CommonEventHandler.leashConfigManager.getMaxLeashesPerEntity();
} }
@Override @Override
@ -1007,48 +1096,28 @@ public class LeashDataImpl implements ILeashData {
return leashableInArea(holder, i -> isLeashHolder(i, holder), 1024D); return leashableInArea(holder, i -> isLeashHolder(i, holder), 1024D);
} }
public boolean canBeAttachedTo(Entity pEntity) { public boolean canBeAttachedTo(Entity pEntity) {
if(pEntity == entity) { if (pEntity == entity) {
return false; return false;
} else { } else {
Optional<LeashInfo> leashInfo = getLeashInfo(pEntity); Optional<LeashInfo> 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) { public static boolean isLeashHolder(@NotNull Entity pEntity, Entity pTestHolder) {
return pTestHolder instanceof SuperLeashKnotEntity superLeashKnotEntity ? return pTestHolder instanceof SuperLeashKnotEntity superLeashKnotEntity ?
isLeashHolder(pEntity, superLeashKnotEntity.getPos()) : isLeashHolder(pEntity, superLeashKnotEntity.getPos()) :
isLeashHolder(pEntity, pTestHolder.getUUID()); isLeashHolder(pEntity, pTestHolder.getUUID());
} }
public static boolean isLeashHolder(@NotNull Entity pEntity, UUID pHolderUUID) {
// 计算拴绳附着点 return LeashDataAPI.getLeashData(pEntity)
@Contract("_ -> new") .map(leashData -> leashData.isLeashedBy(pHolderUUID))
private @NotNull Vec3 calculateAttachOffset(@NotNull Entity entity) { .orElse(false);
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, BlockPos pKnotPos) {
return LeashDataAPI.getLeashData(pEntity)
.map(leashData -> leashData.isLeashedBy(pKnotPos))
.orElse(false);
}
} }

View File

@ -127,7 +127,7 @@ public class LeashStateImpl implements ILeashState {
@Override @Override
public void resetLeashHolderLocationOffset(Entity holder) { public void resetLeashHolderLocationOffset(Entity holder) {
if (entity instanceof SuperLeashKnotEntity leashKnot) { if (holder instanceof SuperLeashKnotEntity leashKnot) {
resetLeashHolderLocationOffset(leashKnot.getPos()); resetLeashHolderLocationOffset(leashKnot.getPos());
} else resetLeashHolderLocationOffset(holder.getUUID()); } else resetLeashHolderLocationOffset(holder.getUUID());
} }
@ -146,45 +146,97 @@ public class LeashStateImpl implements ILeashState {
@Override @Override
public void setLeashHolderLocationOffset(Entity holder, Vec3 offset) { public void setLeashHolderLocationOffset(Entity holder, Vec3 offset) {
if (entity instanceof SuperLeashKnotEntity leashKnot) { if (holder instanceof SuperLeashKnotEntity leashKnot) {
setLeashHolderLocationOffset(leashKnot.getPos(), offset); setLeashHolderLocationOffset(leashKnot.getPos(), offset);
} else setLeashHolderLocationOffset(holder.getUUID(), offset); } else setLeashHolderLocationOffset(holder.getUUID(), offset);
} }
@Override @Override
public void setLeashHolderLocationOffset(UUID holderUUID, Vec3 offset) { 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(); markForSync();
} }
@Override @Override
public void setLeashHolderLocationOffset(BlockPos knotPos, Vec3 leashHolderLocationOffset) { public void setLeashHolderLocationOffset(BlockPos knotPos, Vec3 offset) {
leashKnots.computeIfPresent(knotPos, (blockPos, state) -> state.setHolderLocationOffset(leashHolderLocationOffset)); 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(); markForSync();
} }
@Override @Override
public void addLeashHolderLocationOffset(Entity holder, Vec3 offset) { public void addLeashHolderLocationOffset(Entity holder, Vec3 offset) {
if (entity instanceof SuperLeashKnotEntity leashKnot) { if (holder instanceof SuperLeashKnotEntity leashKnot) {
addLeashHolderLocationOffset(leashKnot.getPos(), offset); addLeashHolderLocationOffset(leashKnot.getPos(), offset);
} else addLeashHolderLocationOffset(holder.getUUID(), offset); } else addLeashHolderLocationOffset(holder.getUUID(), offset);
} }
@Override @Override
public void addLeashHolderLocationOffset(UUID holderUUID, Vec3 offset) { 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(); markForSync();
} }
@Override @Override
public void addLeashHolderLocationOffset(BlockPos knotPos, Vec3 offset) { 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(); markForSync();
} }
@Override @Override
public void removeLeashHolderLocationOffset(Entity holder) { public void removeLeashHolderLocationOffset(Entity holder) {
if (entity instanceof SuperLeashKnotEntity leashKnot) { if (holder instanceof SuperLeashKnotEntity leashKnot) {
removeLeashHolderLocationOffset(leashKnot.getPos()); removeLeashHolderLocationOffset(leashKnot.getPos());
} else removeLeashHolderLocationOffset(holder.getUUID()); } else removeLeashHolderLocationOffset(holder.getUUID());
} }
@ -201,6 +253,25 @@ public class LeashStateImpl implements ILeashState {
markForSync(); 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 @Override
public void resetAllLeashHolderLocationsOffset() { public void resetAllLeashHolderLocationsOffset() {
leashKnots.replaceAll((pos, leashState) -> leashState.resetHolderLocationOffset()); leashKnots.replaceAll((pos, leashState) -> leashState.resetHolderLocationOffset());

View File

@ -19,7 +19,6 @@ import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.player.Player;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.common.util.INBTSerializable; import net.minecraftforge.common.util.INBTSerializable;
import top.r3944realms.superleadrope.content.entity.SuperLeashKnotEntity; import top.r3944realms.superleadrope.content.entity.SuperLeashKnotEntity;
@ -142,7 +141,6 @@ public interface ILeashData extends INBTSerializable<CompoundTag> {
Optional<UUID> holderUUIDOpt, Optional<UUID> holderUUIDOpt,
Optional<Integer> holderIdOpt, // Only for client side use Optional<Integer> holderIdOpt, // Only for client side use
String reserved, // 保留字段 String reserved, // 保留字段
Vec3 attachOffset,
double maxDistance, double maxDistance,
double elasticDistance, double elasticDistance,
int keepLeashTicks, // 剩余 Tick int keepLeashTicks, // 剩余 Tick
@ -150,14 +148,13 @@ public interface ILeashData extends INBTSerializable<CompoundTag> {
) { ) {
public static final LeashInfo EMPTY = new LeashInfo( public static final LeashInfo EMPTY = new LeashInfo(
Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(),
"", Vec3.ZERO, 12.0D, 6.0D, 0, 0 "", 12.0D, 6.0D, 0, 0
); );
/* ---------- Factory ---------- */ /* ---------- Factory ---------- */
public static LeashInfo create( public static LeashInfo create(
Entity entity, Entity entity,
String reserved, String reserved,
Vec3 offset,
double maxDistance, double maxDistance,
double elasticDistance, double elasticDistance,
int keepTicks, int keepTicks,
@ -165,32 +162,31 @@ public interface ILeashData extends INBTSerializable<CompoundTag> {
) { ) {
return entity instanceof SuperLeashKnotEntity knot return entity instanceof SuperLeashKnotEntity knot
? new LeashInfo(knot.getPos(), entity.getId(), reserved, ? new LeashInfo(knot.getPos(), entity.getId(), reserved,
offset, maxDistance, elasticDistance, keepTicks, maxKeepTicks) maxDistance, elasticDistance, keepTicks, maxKeepTicks)
: new LeashInfo(entity.getUUID(), entity.getId(), reserved, : new LeashInfo(entity.getUUID(), entity.getId(), reserved, maxDistance, elasticDistance, keepTicks, maxKeepTicks);
offset, 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) { double maxDistance, double elasticDistance, int keepTicks, int maxKeepTicks) {
this(Optional.empty(), Optional.of(holderUUID), Optional.of(holderId), 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) { double maxDistance, double elasticDistance, int keepTicks, int maxKeepTicks) {
this(Optional.of(knotPos), Optional.empty(), Optional.of(holderId), this(Optional.of(knotPos), Optional.empty(), Optional.of(holderId),
reserved, offset, maxDistance, elasticDistance, keepTicks, maxKeepTicks); reserved, maxDistance, elasticDistance, keepTicks, maxKeepTicks);
} }
/* ---------- State updates ---------- */ /* ---------- State updates ---------- */
public LeashInfo decrementKeepTicks() { public LeashInfo decrementKeepTicks() {
return new LeashInfo(blockPosOpt, holderUUIDOpt, holderIdOpt, reserved, attachOffset, return new LeashInfo(blockPosOpt, holderUUIDOpt, holderIdOpt, reserved,
maxDistance, elasticDistance, maxDistance, elasticDistance,
Math.max(0, keepLeashTicks - 1), maxKeepLeashTicks); Math.max(0, keepLeashTicks - 1), maxKeepLeashTicks);
} }
public LeashInfo resetKeepTicks() { public LeashInfo resetKeepTicks() {
return new LeashInfo(blockPosOpt, holderUUIDOpt, holderIdOpt, reserved, attachOffset, return new LeashInfo(blockPosOpt, holderUUIDOpt, holderIdOpt, reserved,
maxDistance, elasticDistance, maxDistance, elasticDistance,
maxKeepLeashTicks, maxKeepLeashTicks); maxKeepLeashTicks, maxKeepLeashTicks);
} }
@ -205,7 +201,7 @@ public interface ILeashData extends INBTSerializable<CompoundTag> {
isKnot ? Optional.of(((SuperLeashKnotEntity) entity).getPos()) : Optional.empty(), isKnot ? Optional.of(((SuperLeashKnotEntity) entity).getPos()) : Optional.empty(),
!isKnot ? Optional.of(entity.getUUID()) : Optional.empty(), !isKnot ? Optional.of(entity.getUUID()) : Optional.empty(),
Optional.of(entity.getId()), Optional.of(entity.getId()),
newReserved, attachOffset, maxDistance, elasticDistance, newReserved, maxDistance, elasticDistance,
keepLeashTicks, maxKeepLeashTicks keepLeashTicks, maxKeepLeashTicks
); );
} }

View File

@ -67,6 +67,9 @@ public interface ILeashState extends INBTSerializable<CompoundTag> {
void removeLeashHolderLocationOffset(Entity holder); void removeLeashHolderLocationOffset(Entity holder);
void removeLeashHolderLocationOffset(UUID holderUUID); void removeLeashHolderLocationOffset(UUID holderUUID);
void removeLeashHolderLocationOffset(BlockPos knotPos); void removeLeashHolderLocationOffset(BlockPos knotPos);
void removeAllLeashHolderLocationOffset();
void removeAllLeashHolderUUIDLocationOffset();
void removeAllLeashHolderBlockPosLocationOffset();
/* ---------------------- /* ----------------------
* Apply-entity offset * Apply-entity offset

View File

@ -19,13 +19,13 @@ import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands; import net.minecraft.commands.Commands;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import top.r3944realms.superleadrope.config.LeashCommonConfig; import top.r3944realms.superleadrope.CommonEventHandler;
import java.util.List; import java.util.List;
public class Command { public class Command {
public static final String PREFIX = LeashCommonConfig.COMMON.SLPModCommandPrefix.get(); public static final String PREFIX = CommonEventHandler.leashConfigManager.getCommandPrefix();
public static boolean SHOULD_USE_PREFIX = LeashCommonConfig.COMMON.EnableSLPModCommandPrefix.get(); public static boolean SHOULD_USE_PREFIX = CommonEventHandler.leashConfigManager.isCommandPrefixEnabled();
static LiteralArgumentBuilder<CommandSourceStack> getLiterArgumentBuilderOfCSS(String name, boolean shouldAddToList, @Nullable List<LiteralArgumentBuilder<CommandSourceStack>> list) { static LiteralArgumentBuilder<CommandSourceStack> getLiterArgumentBuilderOfCSS(String name, boolean shouldAddToList, @Nullable List<LiteralArgumentBuilder<CommandSourceStack>> list) {
LiteralArgumentBuilder<CommandSourceStack> literal = Commands.literal(name); LiteralArgumentBuilder<CommandSourceStack> literal = Commands.literal(name);
if (shouldAddToList) { if (shouldAddToList) {

View File

@ -16,13 +16,525 @@
package top.r3944realms.superleadrope.content.command; package top.r3944realms.superleadrope.content.command;
import com.mojang.brigadier.CommandDispatcher; 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.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 { public class LeashDataCommand {
// 获取Data public static final String SLP_LEASH_MESSAGE_ = SuperLeadRope.MOD_ID + ".command.leash.message.";
// 设置Data public static final String LEASH_DATA_GET_ = SLP_LEASH_MESSAGE_ + ".get.",
// <add/transfer/remove> Holder<BlockPos/Entity<需判断实体类型>> 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<CommandSourceStack> dispatcher) { public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
@Nullable List<LiteralArgumentBuilder<CommandSourceStack>> nodeList = SHOULD_USE_PREFIX ? null : new ArrayList<>();
LiteralArgumentBuilder<CommandSourceStack> literalArgumentBuilder = Commands.literal(PREFIX);
LiteralArgumentBuilder<CommandSourceStack> $$leashDataRoot = getLiterArgumentBuilderOfCSS("leashdata", !SHOULD_USE_PREFIX, nodeList);
RequiredArgumentBuilder<CommandSourceStack, EntitySelector> $$$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<CommandSourceStack> $$$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<CommandSourceStack> $$$add = Commands.literal("add")
.then(Commands.argument("target", EntityArgument.entities())
// 实体拴绳
.then($$$add$holder)
// 方块拴绳
.then($$$add$pos)
);
LiteralArgumentBuilder<CommandSourceStack> $$$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<CommandSourceStack> $$$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<CommandSourceStack, EntitySelector> $$$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<CommandSourceStack> $$$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<CommandSourceStack> $$$set = Commands.literal("set")
.then(Commands.argument("target", EntityArgument.entities())
// 实体拴绳设置
.then($$$set$holder)
// 方块拴绳设置
.then($$$set$pos)
);
LiteralArgumentBuilder<CommandSourceStack> $$$applayForces = Commands.literal("applyForces")
.then(Commands.argument("target", EntityArgument.entities())
.executes(LeashDataCommand::applyForces)
);
LiteralArgumentBuilder<CommandSourceStack> $$$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<CommandSourceStack> context) throws CommandSyntaxException {
return setMaxDistance(context, CommonEventHandler.leashConfigManager.getMaxLeashLength(), "");
}
private static int setMaxDistance(CommandContext<CommandSourceStack> context, double maxDistance) throws CommandSyntaxException {
return setMaxDistance(context, maxDistance, "");
}
private static int setMaxDistance(CommandContext<CommandSourceStack> context, double maxDistance, String reserved) throws CommandSyntaxException {
Collection<? extends Entity> 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<CommandSourceStack> 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<CommandSourceStack> context) throws CommandSyntaxException {
return -1;
}
public static final String TRANSFER_FROM_BLOCK = SLP_LEASH_MESSAGE_ + "transfer.from_block";
private static int transferFromBlock(CommandContext<CommandSourceStack> context) throws CommandSyntaxException {
return transferFromBlock(context, "");
}
private static int transferFromBlock(CommandContext<CommandSourceStack> 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<CommandSourceStack> context) throws CommandSyntaxException {
return setElasticDistance(context, 0 ,"");
}
private static int setElasticDistance(CommandContext<CommandSourceStack> context, int keepTicks) throws CommandSyntaxException {
return setElasticDistance(context, keepTicks ,"");
}
private static int setElasticDistance(CommandContext<CommandSourceStack> 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<CommandSourceStack> context) throws CommandSyntaxException {
return setBlockMaxDistance(context, 0 ,"");
}
private static int setBlockMaxDistance(CommandContext<CommandSourceStack> context, int keepTicks) throws CommandSyntaxException {
return setBlockMaxDistance(context, keepTicks ,"");
}
private static int setBlockMaxDistance(CommandContext<CommandSourceStack> 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<CommandSourceStack> context) throws CommandSyntaxException {
return setBlockElasticDistance(context, 0 ,"");
}
private static int setBlockElasticDistance(CommandContext<CommandSourceStack> context, int keepTicks) throws CommandSyntaxException {
return setBlockElasticDistance(context, keepTicks ,"");
}
private static int setBlockElasticDistance(CommandContext<CommandSourceStack> context, int keepTicks, String reserved) throws CommandSyntaxException {
return -1;
}
// ==================== 命令执行方法 ====================
private static int getLeashData(CommandContext<CommandSourceStack> context) throws CommandSyntaxException {
Collection<? extends Entity> targets = EntityArgument.getEntities(context, "target");
CommandSourceStack source = context.getSource();
for (Entity target : targets) {
Collection<ILeashData.LeashInfo> 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<CommandSourceStack> context) throws CommandSyntaxException {
return addLeash(context, CommonEventHandler.leashConfigManager.getMaxLeashLength(), CommonEventHandler.leashConfigManager.getElasticDistance(), 0, "");
}
private static int addLeash(CommandContext<CommandSourceStack> context,
double maxDistance) throws CommandSyntaxException {
return addLeash(context, maxDistance, CommonEventHandler.leashConfigManager.getElasticDistance(), 0, "");
}
private static int addLeash(CommandContext<CommandSourceStack> context,
double maxDistance, double elasticDistance) throws CommandSyntaxException {
return addLeash(context, maxDistance, elasticDistance, 0, "");
}
private static int addLeash(CommandContext<CommandSourceStack> context,
double maxDistance, double elasticDistance, int keepTicks) throws CommandSyntaxException {
return addLeash(context, maxDistance, elasticDistance, keepTicks, "");
}
private static int addLeash(CommandContext<CommandSourceStack> context,
double maxDistance, double elasticDistance, int keepTicks, String reserved)
throws CommandSyntaxException {
Collection<? extends Entity> targets = EntityArgument.getEntities(context, "target");
Entity holder = EntityArgument.getEntity(context, "holder");
CommandSourceStack source = context.getSource();
List<Entity> 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<CommandSourceStack> context) throws CommandSyntaxException {
return addBlockLeash(context, CommonEventHandler.leashConfigManager.getMaxLeashLength(), CommonEventHandler.leashConfigManager.getElasticDistance(), 0, "");
}
private static int addBlockLeash(CommandContext<CommandSourceStack> context,
double maxDistance) throws CommandSyntaxException {
return addBlockLeash(context, maxDistance, CommonEventHandler.leashConfigManager.getElasticDistance(), 0, "");
}
private static int addBlockLeash(CommandContext<CommandSourceStack> context,
double maxDistance, double elasticDistance, int keepTicks) throws CommandSyntaxException {
return addBlockLeash(context, maxDistance, elasticDistance, keepTicks, "");
}
private static int addBlockLeash(CommandContext<CommandSourceStack> context,
double maxDistance, double elasticDistance, int keepTicks, String reserved)
throws CommandSyntaxException {
Collection<? extends Entity> 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<Entity> 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<CommandSourceStack> context) throws CommandSyntaxException {
Collection<? extends Entity> 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<CommandSourceStack> context) throws CommandSyntaxException {
Collection<? extends Entity> 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<CommandSourceStack> context) throws CommandSyntaxException {
Collection<? extends Entity> 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<CommandSourceStack> context) throws CommandSyntaxException {
return transferLeash(context, "");
}
private static int transferLeash(CommandContext<CommandSourceStack> context, String reserved)
throws CommandSyntaxException {
Collection<? extends Entity> 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<CommandSourceStack> context) throws CommandSyntaxException {
Collection<? extends Entity> 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();
}
}

View File

@ -34,13 +34,13 @@ import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.gameevent.GameEvent; import net.minecraft.world.level.gameevent.GameEvent;
import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.AABB;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import top.r3944realms.superleadrope.content.capability.CapabilityHandler;
import top.r3944realms.superleadrope.content.capability.impi.LeashDataImpl; import top.r3944realms.superleadrope.content.capability.impi.LeashDataImpl;
import top.r3944realms.superleadrope.core.register.SLPEntityTypes; 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.Arrays;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
public class SuperLeashKnotEntity extends LeashFenceKnotEntity { public class SuperLeashKnotEntity extends LeashFenceKnotEntity {
@ -83,7 +83,7 @@ public class SuperLeashKnotEntity extends LeashFenceKnotEntity {
this.playSound(SoundEvents.LEASH_KNOT_BREAK); this.playSound(SoundEvents.LEASH_KNOT_BREAK);
List<Entity> entities = LeashDataImpl.leashableInArea(this.level(), pos.getCenter(), entity -> LeashDataImpl.isLeashHolder(entity, this)); List<Entity> entities = LeashDataImpl.leashableInArea(this.level(), pos.getCenter(), entity -> LeashDataImpl.isLeashHolder(entity, this));
entities.forEach(entity -> entities.forEach(entity ->
LeashUtil.getLeashData(entity) LeashDataAPI.getLeashData(entity)
.map(iLeashDataCapability -> iLeashDataCapability.removeLeash(this)) .map(iLeashDataCapability -> iLeashDataCapability.removeLeash(this))
); );
} }
@ -120,6 +120,27 @@ public class SuperLeashKnotEntity extends LeashFenceKnotEntity {
pLevel.addFreshEntity(superLeashKnotEntity1); pLevel.addFreshEntity(superLeashKnotEntity1);
return superLeashKnotEntity1; return superLeashKnotEntity1;
} }
public static @NotNull Optional<SuperLeashKnotEntity> 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 @Override
protected void recalculateBoundingBox() { protected void recalculateBoundingBox() {
@ -169,7 +190,7 @@ public class SuperLeashKnotEntity extends LeashFenceKnotEntity {
List<Entity> entities = LeashDataImpl.leashableInArea(player); List<Entity> entities = LeashDataImpl.leashableInArea(player);
for(Entity entity : entities) { for(Entity entity : entities) {
if (LeashDataImpl.isLeashHolder(entity, player.getUUID())) if (LeashDataImpl.isLeashHolder(entity, player.getUUID()))
LeashUtil.getLeashData(entity) LeashDataAPI.getLeashData(entity)
.ifPresent(i -> { .ifPresent(i -> {
i.transferLeash(player.getUUID(), this); i.transferLeash(player.getUUID(), this);
isTransferLeash.set(true); isTransferLeash.set(true);
@ -182,7 +203,7 @@ public class SuperLeashKnotEntity extends LeashFenceKnotEntity {
this.discard(); this.discard();
List<Entity> entities1 = LeashDataImpl.leashableInArea(this); List<Entity> entities1 = LeashDataImpl.leashableInArea(this);
entities1.forEach(entity -> entities1.forEach(entity ->
LeashUtil.getLeashData(entity) LeashDataAPI.getLeashData(entity)
.ifPresent(iLeashDataCapability -> { .ifPresent(iLeashDataCapability -> {
iLeashDataCapability.removeLeash(this); iLeashDataCapability.removeLeash(this);
isRemoveLeashKnot.set(true); isRemoveLeashKnot.set(true);

View File

@ -24,12 +24,12 @@ public class SLPGamerules {
public static final SLPGameruleRegistry GAMERULE_REGISTRY = SLPGameruleRegistry.INSTANCE; public static final SLPGameruleRegistry GAMERULE_REGISTRY = SLPGameruleRegistry.INSTANCE;
public static final HashMap<String, Boolean> gamerulesBooleanValuesClient = new HashMap<>(); public static final HashMap<String, Boolean> gamerulesBooleanValuesClient = new HashMap<>();
public static final HashMap<String, Integer> gameruleIntegerValuesClient = new HashMap<>(); public static final HashMap<String, Integer> 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) { 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) { public static String getDescriptionKey(String gameRuleName) {
return RULE_KEY_PERFix_ + gameRuleName + ".description"; return RULE_KEY_PERFiX_ + gameRuleName + ".description";
} }
public static String getGameruleName(Class<?> clazz) { public static String getGameruleName(Class<?> clazz) {
return SLPGamerules.GAMERULE_PREFIX + clazz.getSimpleName(); return SLPGamerules.GAMERULE_PREFIX + clazz.getSimpleName();
@ -39,7 +39,7 @@ public class SLPGamerules {
} }
public static String getNameKey(Class<?> gameRuleClass) { public static String getNameKey(Class<?> gameRuleClass) {
return RULE_KEY_PERFix_ + gameRuleClass.getSimpleName(); return RULE_KEY_PERFiX_ + gameRuleClass.getSimpleName();
} }
} }

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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);
}
}

View File

@ -29,15 +29,13 @@ import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.gameevent.GameEvent; import net.minecraft.world.level.gameevent.GameEvent;
import net.minecraftforge.common.extensions.IForgeItem; import net.minecraftforge.common.extensions.IForgeItem;
import net.minecraftforge.common.util.LazyOptional;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import top.r3944realms.superleadrope.content.SLPToolTier; 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.impi.LeashDataImpl;
import top.r3944realms.superleadrope.content.capability.inter.ILeashData; import top.r3944realms.superleadrope.content.capability.inter.ILeashData;
import top.r3944realms.superleadrope.content.entity.SuperLeashKnotEntity; import top.r3944realms.superleadrope.content.entity.SuperLeashKnotEntity;
import top.r3944realms.superleadrope.core.register.SLPSoundEvents; 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.List;
import java.util.Optional; import java.util.Optional;
@ -83,7 +81,7 @@ public class SuperLeadRopeItem extends TieredItem implements IForgeItem {
} }
public static boolean canUse(ItemStack itemStack) { public static boolean canUse(ItemStack itemStack) {
return itemStack.getDamageValue() < 974; return itemStack.getDamageValue() < 1200;
} }
@Override @Override
@ -106,11 +104,10 @@ public class SuperLeadRopeItem extends TieredItem implements IForgeItem {
* @param newHolder 新实体 * @param newHolder 新实体
* @param player 明确持有玩家 * @param player 明确持有玩家
* @param level 维度世界 * @param level 维度世界
* @param leashStack 拴绳物品实例
* @return 是否成功 * @return 是否成功
*/ */
public static boolean bindToEntity(Entity newHolder, Player player, Level level, ItemStack leashStack) { public static boolean bindToEntity(Entity newHolder, Player player, Level level) {
return bindToEntity(newHolder, player, level, player.getOnPos(), leashStack); return bindToEntity(newHolder, player, level, player.getOnPos());
} }
/** /**
* 右键蹲下绑定到另一实体上 * 右键蹲下绑定到另一实体上
@ -118,10 +115,9 @@ public class SuperLeadRopeItem extends TieredItem implements IForgeItem {
* @param player 明确持有玩家 * @param player 明确持有玩家
* @param level 维度世界 * @param level 维度世界
* @param pos 坐标一般是明确持有玩家的位置 * @param pos 坐标一般是明确持有玩家的位置
* @param leashStack 拴绳物品实例
* @return 是否成功 * @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; boolean isSuccess = false;
// 查找当前玩家持有的可拴生物 // 查找当前玩家持有的可拴生物
@ -131,7 +127,7 @@ public class SuperLeadRopeItem extends TieredItem implements IForgeItem {
); );
for (Entity e : list) { for (Entity e : list) {
Optional<ILeashData> leashDataOpt = LeashUtil.getLeashData(e); Optional<ILeashData> leashDataOpt = LeashDataAPI.getLeashData(e);
if (leashDataOpt.map(i -> i.canBeAttachedTo(newHolder)).orElse(false)) { if (leashDataOpt.map(i -> i.canBeAttachedTo(newHolder)).orElse(false)) {
leashDataOpt.ifPresent(i -> i.transferLeash(player.getUUID(), newHolder)); leashDataOpt.ifPresent(i -> i.transferLeash(player.getUUID(), newHolder));
@ -182,16 +178,16 @@ public class SuperLeadRopeItem extends TieredItem implements IForgeItem {
if (leashStack.isEmpty() || !canUse(leashStack)) { if (leashStack.isEmpty() || !canUse(leashStack)) {
return false; return false;
} }
knot = SuperLeashKnotEntity.getOrCreateKnot(level, pos); knot = SuperLeashKnotEntity.getOrCreateKnot(level, pos);
knot.playPlacementSound(); knot.playPlacementSound();
SuperLeashKnotEntity finalKnot = knot; SuperLeashKnotEntity finalKnot = knot;
LeashUtil.getLeashData(player).ifPresent(i -> { LeashDataAPI.getLeashData(player).ifPresent(i -> {
if (i.canBeAttachedTo(finalKnot)) { if (i.canBeAttachedTo(finalKnot)) {
i.addLeash(finalKnot); if (!level.isClientSide) i.addLeash(finalKnot);
isSuccess.set(true); isSuccess.set(true);
} }
}); });
} }
// 情况二把已有生物拴到 knot // 情况二把已有生物拴到 knot
else if (!list.isEmpty()) { else if (!list.isEmpty()) {
@ -202,9 +198,9 @@ public class SuperLeadRopeItem extends TieredItem implements IForgeItem {
} }
SuperLeashKnotEntity finalKnot = knot; SuperLeashKnotEntity finalKnot = knot;
LeashUtil.getLeashData(e).ifPresent(i -> { LeashDataAPI.getLeashData(e).ifPresent(i -> {
if (i.canBeAttachedTo(finalKnot)) { if (i.canBeAttachedTo(finalKnot)) {
i.transferLeash(uuid, finalKnot); if (!level.isClientSide) i.transferLeash(uuid, finalKnot);
isSuccess.set(true); isSuccess.set(true);
} }
}); });

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<Integer> 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();
}
}

View File

@ -16,6 +16,8 @@
package top.r3944realms.superleadrope.core.leash; package top.r3944realms.superleadrope.core.leash;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource; import net.minecraft.sounds.SoundSource;
import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult; 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.item.ItemStack;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import net.minecraft.world.level.gameevent.GameEvent; 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.AttackEntityEvent;
import net.minecraftforge.event.entity.player.PlayerInteractEvent; 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.impi.LeashDataImpl;
import top.r3944realms.superleadrope.content.capability.inter.ILeashData; import top.r3944realms.superleadrope.content.capability.inter.ILeashData;
import top.r3944realms.superleadrope.content.item.SuperLeadRopeItem; import top.r3944realms.superleadrope.content.item.SuperLeadRopeItem;
import top.r3944realms.superleadrope.core.register.SLPItems; import top.r3944realms.superleadrope.core.register.SLPItems;
import top.r3944realms.superleadrope.core.register.SLPSoundEvents; import top.r3944realms.superleadrope.core.register.SLPSoundEvents;
import top.r3944realms.superleadrope.util.capability.LeashDataAPI;
import java.util.Optional;
public class LeashInteractHandler { public class LeashInteractHandler {
//只有玩家可以互动触发其它的暂不支持考虑到0 Mixin) //只有玩家可以互动触发其它的暂不支持考虑到0 Mixin)
@ -48,6 +51,7 @@ public class LeashInteractHandler {
event.setCanceled(true); event.setCanceled(true);
event.setCancellationResult(InteractionResult.SUCCESS); event.setCancellationResult(InteractionResult.SUCCESS);
} }
return; return;
} }
if (hand == InteractionHand.OFF_HAND) { if (hand == InteractionHand.OFF_HAND) {
@ -57,8 +61,8 @@ public class LeashInteractHandler {
if (!LeashDataImpl.isLeashable(target)) { if (!LeashDataImpl.isLeashable(target)) {
return; return;
} }
LazyOptional<ILeashData> LeashCap = target.getCapability(CapabilityHandler.LEASH_DATA_CAP); Optional<ILeashData> LeashCap = LeashDataAPI.getLeashData(target);
if (!LeashCap.isPresent()) { if (LeashCap.isEmpty()) {
return; 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) { if (isSuccess) {
event.setCanceled(true); event.setCanceled(true);
event.setCancellationResult(InteractionResult.SUCCESS); event.setCancellationResult(InteractionResult.SUCCESS);
@ -103,7 +107,7 @@ public class LeashInteractHandler {
boolean success = iLeashDataCapability.addLeash(player); boolean success = iLeashDataCapability.addLeash(player);
if (success) { if (success) {
if(!player.isCreative()) 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); level.playSound(null, target.getOnPos(), SLPSoundEvents.LEAD_TIED.get(), SoundSource.PLAYERS);
event.setCanceled(true); event.setCanceled(true);
event.setCancellationResult(InteractionResult.SUCCESS); event.setCancellationResult(InteractionResult.SUCCESS);
@ -123,7 +127,7 @@ public class LeashInteractHandler {
} }
} else { } else {
if (flag) { if (flag) {
target.getCapability(CapabilityHandler.LEASH_DATA_CAP).ifPresent(leashDataCapability -> { LeashDataAPI.getLeashData(target).ifPresent(leashDataCapability -> {
if (leashDataCapability.hasLeash()){ if (leashDataCapability.hasLeash()){
int size = leashDataCapability.getAllLeashes().size(); int size = leashDataCapability.getAllLeashes().size();
if (player.isSecondaryUseActive()) if (player.isSecondaryUseActive())

View File

@ -18,7 +18,9 @@ package top.r3944realms.superleadrope.datagen.data;
import net.minecraft.world.item.Item; import net.minecraft.world.item.Item;
import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Block;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import top.r3944realms.superleadrope.content.command.LeashDataCommand;
import top.r3944realms.superleadrope.content.command.MotionCommand; 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.gamerule.server.TeleportWithLeashedEntities;
import top.r3944realms.superleadrope.content.item.EternalPotatoItem; import top.r3944realms.superleadrope.content.item.EternalPotatoItem;
import top.r3944realms.superleadrope.core.register.SLPEntityTypes; import top.r3944realms.superleadrope.core.register.SLPEntityTypes;
@ -42,21 +44,24 @@ public enum SLPLangKeyValue {
"Eternal Potato", "永恒土豆", "永恆馬鈴薯", "不滅薯", true "Eternal Potato", "永恒土豆", "永恆馬鈴薯", "不滅薯", true
), ),
EP_TOOLTIP_TITLE(EternalPotatoItem.getDescKey("title"), ModPartEnum.DESCRIPTION, EP_TOOLTIP_TITLE(
EternalPotatoItem.getDescKey("title"), ModPartEnum.DESCRIPTION,
"§6Mythical Item §7- §6Eternal Potato", "§6Mythical Item §7- §6Eternal Potato",
"§6神话物品 §7- §6永恒土豆", "§6神话物品 §7- §6永恒土豆",
"§6神話物品 §7- §6永恒土豆", "§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", "§7Symbol of server-wide contract, cannot be discarded",
"§7象征全服契约不可丢弃", "§7象征全服契约不可丢弃",
"§7象徵全服契約不可丟棄", "§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", "§bBound Owner: §f%s",
"§b绑定主人: §f%s", "§b绑定主人: §f%s",
"§b綁定主人: §f%s", "§b綁定主人: §f%s",
@ -70,42 +75,48 @@ public enum SLPLangKeyValue {
"§c尚未绑定主人" "§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)", "§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逾期未完成)", "§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", "§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", "§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[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次。", "§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.", "§aThe power of the Eternal Potato comforts you, it won't disappear.",
"§a永恒土豆的力量抚慰了你但它不会消失。", "§a永恒土豆的力量抚慰了你但它不会消失。",
"§a永恆土豆的力量撫慰了你但它不會消失。", "§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.", "§cThe Eternal Potato cannot be dropped! +%d punishments.",
"§c永恒土豆是不可丢弃的惩罚数加%d", "§c永恒土豆是不可丢弃的惩罚数加%d",
"§c永恆土豆不可丟棄懲罰數加%d", "§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.", "§6Bound to you as the server-wide shared person.",
"§6已与你绑定成为全服共有之人。", "§6已与你绑定成为全服共有之人。",
"§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", "§eObligation completed, remaining: §a%d§e",
"§e义务完成一次剩余 §a%d §e次。", "§e义务完成一次剩余 §a%d §e次。",
"§e義務完成一次剩餘 §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!", "§aAll obligations completed today!",
"§a今日义务已全部完成", "§a今日义务已全部完成",
"§a今日義務已全部完成", "§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!", "§cYesterday obligations incomplete, punished!",
"§c未完成昨日义务受到惩罚", "§c未完成昨日义务受到惩罚",
"§c未完成昨日義務受到懲罰", "§c未完成昨日義務受到懲罰",
@ -142,14 +156,16 @@ public enum SLPLangKeyValue {
"受罚倒数§a%d §f瞬" "受罚倒数§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!", "§cYou are not the rightful owner and cannot pick this up!",
"§c非绑定主人无法拾取此物品", "§c非绑定主人无法拾取此物品",
"§c非綁定主人無法拾取此物品", "§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!", "§cYou are not the rightful owner, punished by lightning!",
"§c非绑定主人使用受到闪电惩罚", "§c非绑定主人使用受到闪电惩罚",
"§c非綁定主人使用受到閃電懲罰", "§c非綁定主人使用受到閃電懲罰",
@ -188,13 +204,29 @@ public enum SLPLangKeyValue {
SLPEntityTypes.getEntityNameKey("super_lead_knot"), ModPartEnum.ENTITY, SLPEntityTypes.getEntityNameKey("super_lead_knot"), ModPartEnum.ENTITY,
"Super Lead Knot", "超级拴绳结", "超級拴繩結", "神駒羈縻索結" "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 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 ", "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",
"§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; private final Supplier<?> supplier;

View File

@ -21,7 +21,7 @@ import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.Entity;
import net.minecraftforge.network.NetworkEvent; import net.minecraftforge.network.NetworkEvent;
import top.r3944realms.superleadrope.content.capability.CapabilityHandler; import top.r3944realms.superleadrope.util.capability.LeashDataAPI;
import java.util.function.Supplier; import java.util.function.Supplier;
@ -42,7 +42,7 @@ public record LeashDataSyncPacket(int entityId, CompoundTag leashData) {
if (level != null) { if (level != null) {
Entity entity = level.getEntity(msg.entityId); Entity entity = level.getEntity(msg.entityId);
if (entity != null) { if (entity != null) {
entity.getCapability(CapabilityHandler.LEASH_DATA_CAP).ifPresent(cap -> { LeashDataAPI.getLeashData(entity).ifPresent(cap -> {
// 只在数据确实变化时更新 // 只在数据确实变化时更新
CompoundTag current = cap.serializeNBT(); CompoundTag current = cap.serializeNBT();
if (!current.equals(msg.leashData)) { if (!current.equals(msg.leashData)) {

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<ILeashData> 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<ILeashData.LeashInfo> 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<ILeashData.LeashInfo> getLeashInfo(Entity entity, Entity holder) {
return getLeashData(entity).flatMap(data -> data.getLeashInfo(holder));
}
public static Optional<ILeashData.LeashInfo> getLeashInfo(Entity entity, UUID holderUUID) {
return getLeashData(entity).flatMap(data -> data.getLeashInfo(holderUUID));
}
public static Optional<ILeashData.LeashInfo> 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<UUID> 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();
}
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<ILeashState> 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<UUID, ILeashState.LeashState> getAllUUIDStates(Entity entity) {
return getLeashState(entity)
.map(ILeashState::getHolderLeashStates)
.orElse(Map.of());
}
public static Map<BlockPos, ILeashState.LeashState> getAllBlockPosStates(Entity entity) {
return getLeashState(entity)
.map(ILeashState::getKnotLeashStates)
.orElse(Map.of());
}
public static Optional<ILeashState.LeashState> getState(Entity entity, Entity holder) {
return getLeashState(entity).flatMap(state -> state.getLeashState(holder));
}
public static Optional<ILeashState.LeashState> getState(Entity entity, UUID holderUUID) {
return getLeashState(entity).flatMap(state -> state.getLeashState(holderUUID));
}
public static Optional<ILeashState.LeashState> 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<Vec3> 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)
)
);
}
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<ILeashData> getLeashData(@NotNull Entity entity) {
Objects.requireNonNull(entity, "Entity cannot be null");
return entity.getCapability(CapabilityHandler.LEASH_DATA_CAP).resolve();
}
public static Optional<ILeashState> getLeashState(@NotNull Entity entity) {
Objects.requireNonNull(entity, "Entity cannot be null");
return entity.getCapability(CapabilityHandler.LEASH_STATE_CAP).resolve();
}
}

View File

@ -16,6 +16,7 @@
package top.r3944realms.superleadrope.util.riding; package top.r3944realms.superleadrope.util.riding;
import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.Entity;
import top.r3944realms.superleadrope.CommonEventHandler;
import top.r3944realms.superleadrope.SuperLeadRope; import top.r3944realms.superleadrope.SuperLeadRope;
import top.r3944realms.superleadrope.core.exception.RidingCycleException; import top.r3944realms.superleadrope.core.exception.RidingCycleException;
import top.r3944realms.superleadrope.util.model.RidingRelationship; import top.r3944realms.superleadrope.util.model.RidingRelationship;
@ -55,7 +56,7 @@ public class RidingApplier {
if (entity == null) continue; if (entity == null) continue;
// ---------- 白名单保护 ---------- // ---------- 白名单保护 ----------
if (!RidingValidator.isInWhitelist(entity.getType())) { if (!CommonEventHandler.leashConfigManager.isEntityTeleportAllowed(entity)) {
// 不在白名单跳过本节点但保留其乘客挂回上层 // 不在白名单跳过本节点但保留其乘客挂回上层
if (vehicle != null) { 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)) { if (RidingValidator.wouldCreateCycle(entity, vehicle)) {
throw new RidingCycleException(entityId, vehicleId); throw new RidingCycleException(entityId, vehicleId);
} }

View File

@ -17,6 +17,7 @@ package top.r3944realms.superleadrope.util.riding;
import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.EntityType;
import top.r3944realms.superleadrope.CommonEventHandler;
import top.r3944realms.superleadrope.core.exception.RidingCycleException; import top.r3944realms.superleadrope.core.exception.RidingCycleException;
import top.r3944realms.superleadrope.core.util.ImmutablePair; import top.r3944realms.superleadrope.core.util.ImmutablePair;
import top.r3944realms.superleadrope.util.model.RidingRelationship; import top.r3944realms.superleadrope.util.model.RidingRelationship;
@ -72,7 +73,7 @@ public class RidingSaver {
processedEntities.add(passengerId); processedEntities.add(passengerId);
// 校验白名单 // 校验白名单
if (!RidingValidator.isInWhitelist(passenger.getType())) { if (!CommonEventHandler.leashConfigManager.isEntityTeleportAllowed(passenger.getType())) {
// 不在白名单直接截断 // 不在白名单直接截断
continue; continue;
} }
@ -105,7 +106,7 @@ public class RidingSaver {
if (relationship == null) return null; 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(); RidingRelationship filtered = new RidingRelationship();
filtered.setEntityId(relationship.getEntityId()); filtered.setEntityId(relationship.getEntityId());
filtered.setVehicleId(relationship.getVehicleId()); filtered.setVehicleId(relationship.getVehicleId());
@ -114,7 +115,7 @@ public class RidingSaver {
} else { } else {
// 根节点不在白名单尝试找到合法的子节点作为新的根 // 根节点不在白名单尝试找到合法的子节点作为新的根
for (RidingRelationship child : relationship.getPassengers()) { 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(); RidingRelationship newRoot = new RidingRelationship();
newRoot.setEntityId(child.getEntityId()); newRoot.setEntityId(child.getEntityId());

View File

@ -15,52 +15,12 @@
package top.r3944realms.superleadrope.util.riding; 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.Entity;
import net.minecraft.world.entity.EntityType;
import top.r3944realms.superleadrope.config.LeashCommonConfig;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.Queue; import java.util.Queue;
public class RidingValidator { 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<EntityType<?>> 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;
}
/** /**
* 检查骑乘是否会产生循环引用 * 检查骑乘是否会产生循环引用
*/ */

View File

@ -21,6 +21,7 @@ import net.minecraft.world.entity.ai.goal.Goal;
import net.minecraft.world.entity.animal.Animal; import net.minecraft.world.entity.animal.Animal;
import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import top.r3944realms.superleadrope.CommonEventHandler;
import top.r3944realms.superleadrope.network.NetworkHandler; import top.r3944realms.superleadrope.network.NetworkHandler;
import top.r3944realms.superleadrope.network.toClient.UpdatePlayerMovementPacket; import top.r3944realms.superleadrope.network.toClient.UpdatePlayerMovementPacket;
@ -37,7 +38,7 @@ public class RindingLeash {
Entity current = root; Entity current = root;
while (current != null) { while (current != null) {
if (RidingValidator.isInWhitelist(current.getType())) { if (CommonEventHandler.leashConfigManager.isEntityTeleportAllowed(current.getType())) {
return current; // 找到白名单载具 return current; // 找到白名单载具
} }
current = current.getVehicle(); current = current.getVehicle();
@ -60,7 +61,7 @@ public class RindingLeash {
Entity current = root; Entity current = root;
while (current != null) { while (current != null) {
if (RidingValidator.isInWhitelist(current.getType())) { if (CommonEventHandler.leashConfigManager.isEntityTeleportAllowed(current.getType())) {
return current; // 找到白名单载具 return current; // 找到白名单载具
} }
current = current.getVehicle(); current = current.getVehicle();

View File

@ -1 +1,3 @@
{} {
"morsb_patch":"coremods/morsb_patch.js"
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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;
}
}
};
}

View File

@ -13,12 +13,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package top.r3944realms.superleadrope.util.coremods; package top.r3944realms.superleadropetest;
import net.minecraftforge.api.distmarker.Dist; import top.r3944realms.superleadrope.util.capability.LeashDataAPI;
import net.minecraftforge.api.distmarker.OnlyIn;
@OnlyIn(Dist.CLIENT) public class Placeholder {
public class InvokerMethod {
} }

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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));
}
}
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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);
// 添加默认构造器 <init>
MethodNode constructor = new MethodNode(Opcodes.ASM9, Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
InsnList initInsns = constructor.instructions;
initInsns.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this
initInsns.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()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);
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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 {
// 组索引 现在坐标在组123实体列表在组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("=== 测试完成 ===");
}
}