diff --git a/README.MD b/README.MD index 3d2ec01..2005c75 100644 --- a/README.MD +++ b/README.MD @@ -13,6 +13,9 @@ We recommend testing on backups or a development server before deploying to prod **Super Lead Rope** is a mod that enhances the vanilla lead, allowing players to manage and leash entities more flexibly. Compared to the original, it is not only more powerful but also supports cross-dimension and even cross-server features. + +Super Lead Rope Commands 超级拴绳指令: +[Leash Data Command](./doc/LeashDataCommandUsage_EN) | [超级拴绳数据指令](./doc/LeashDataCommandUsage_CN) --- ## ✨ 功能特色 / Features diff --git a/build.gradle b/build.gradle index 2f8ce4c..121721d 100644 --- a/build.gradle +++ b/build.gradle @@ -76,6 +76,10 @@ repositories { name = "Curios" url = "https://maven.theillusivec4.top/" } + maven { + name = "Luck perms" + url = "https://repo1.maven.org/maven2/net/luckperms/api/" + } maven { name = "GeckoLib" url = "https://dl.cloudsmith.io/public/geckolib3/geckolib/maven/" @@ -173,13 +177,12 @@ dependencies { modRuntimeOnly("curse.maven:spark-361579:4738952") compileOnly(annotationProcessor("io.github.llamalad7:mixinextras-common:0.4.1")) modImplementation("io.github.llamalad7:mixinextras-forge:0.4.1") + compileOnly("net.luckperms:api:5.4") 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' - modCompileOnly("top.r3944realms.lib39:lib39:1.20.1-0.0.15:all@jar") - } // ===================== 修复 Javadoc 任务 ===================== @@ -445,7 +448,7 @@ publishing { // ===================== 构建任务依赖 ===================== tasks.named('build') { - dependsOn apiJar, apiJavadocJar + dependsOn sourceJar, apiSourceJar, apiJar, apiJavadocJar } tasks.named('clean') { @@ -486,3 +489,30 @@ idea { downloadJavadoc = true } } +tasks.register('showTaskTree') { + doLast { + def showTaskDeps + showTaskDeps = { task, prefix = '', isLast = true -> + // 当前任务节点 + def connector = isLast ? '└── ' : '├── ' + println "${prefix}${connector}${task.name}" + + // 子任务 + def dependencies = task.getTaskDependencies().getDependencies(task).toList() + def newPrefix = prefix + (isLast ? ' ' : '│ ') + + dependencies.eachWithIndex { dep, index -> + def lastChild = index == dependencies.size() - 1 + showTaskDeps(dep, newPrefix, lastChild) + } + } + + def targetTask = tasks.findByName('build') + if (targetTask) { + println "构建任务依赖树:" + showTaskDeps(targetTask, '', true) + } else { + println "未找到 build 任务" + } + } +} diff --git a/doc/LeashDataCommandUsage_CN.MD b/doc/LeashDataCommandUsage_CN.MD new file mode 100644 index 0000000..129b403 --- /dev/null +++ b/doc/LeashDataCommandUsage_CN.MD @@ -0,0 +1,176 @@ +## 基础指令结构 + +### 主要指令 +``` +/slp leashdata [子命令] +``` + +### 指令权限 +需要 OP 权限(权限等级 2) + +## 子命令分类 + +### 1. GET 命令 - 获取信息 + +**获取实体拴绳数据:** +``` +/slp leashdata get data +``` +- ``: 目标实体(选择器) + +**获取单个实体详细信息:** +``` +/slp leashdata get info +``` +- ``: 目标实体(选择器) + +### 2. ADD 命令 - 添加拴绳 + +**添加实体拴绳:** +``` +/slp leashdata add [maxDistance] [elasticDistanceScale] [keepTicks] [reserved] +``` +- ``: 被拴绳实体 +- ``: 持有者实体 +- `[maxDistance]`: 最大距离(默认值范围) +- `[elasticDistanceScale]`: 弹性距离比例(默认值范围) +- `[keepTicks]`: 保持时间(游戏刻,≥0) +- `[reserved]`: 保留字段(字符串) + +**添加方块拴绳:** +``` +/slp leashdata add block [maxDistance] [elasticDistanceScale] [keepTicks] [reserved] +``` +- ``: 方块位置 +- 其他参数同上 + +### 3. REMOVE 命令 - 移除拴绳 + +**移除特定实体拴绳:** +``` +/slp leashdata remove +``` +- ``: 目标实体 +- ``: 持有者实体 + +**移除方块拴绳:** +``` +/slp leashdata remove block +``` +- ``: 方块位置 + +**批量移除:** +``` +/slp leashdata remove all # 移除所有拴绳 +/slp leashdata remove holders # 移除所有实体拴绳 +/slp leashdata remove knots # 移除所有方块拴绳 +``` + +### 4. TRANSFER 命令 - 转移拴绳 + +**实体到实体转移:** +``` +/slp leashdata transfer [reserved] +``` +- ``: 原持有者 +- ``: 新持有者 + +**方块到实体转移:** +``` +/slp leashdata transfer fromBlock [reserved] +``` +- ``: 原方块位置 +- ``: 新持有者实体 + +**方块到方块转移:** +``` +/slp leashdata transfer fromBlock toBlock [reserved] +``` +- ``: 原方块位置 +- ``: 新方块位置 + +### 5. SET 命令 - 设置属性 + +**设置静态属性(适用于所有拴绳):** +``` +/slp leashdata set static maxDistance reset # 重置最大距离 +/slp leashdata set static maxDistance # 设置最大距离 +/slp leashdata set static elasticDistanceScale reset # 重置弹性比例 +/slp leashdata set static elasticDistanceScale # 设置弹性比例 +``` + +**设置实体拴绳属性:** +``` +# 设置最大距离 +/slp leashdata set entity maxDistance [distance] [keepTicks] [reserved] +/slp leashdata set entity maxDistance [distance] [keepTicks] [reserved] # 所有持有者 + +# 设置弹性距离比例 +/slp leashdata set entity elasticDistanceScale [scale] [keepTicks] [reserved] +/slp leashdata set entity elasticDistanceScale [scale] [keepTicks] [reserved] # 所有持有者 +``` + +**设置方块拴绳属性:** +``` +# 设置最大距离 +/slp leashdata set block maxDistance [distance] [keepTicks] [reserved] +/slp leashdata set block maxDistance [distance] [keepTicks] [reserved] # 所有节点 + +# 设置弹性距离比例 +/slp leashdata set block elasticDistanceScale [scale] [keepTicks] [reserved] +/slp leashdata set block elasticDistanceScale [scale] [keepTicks] [reserved] # 所有节点 +``` + +### 6. APPLY FORCES 命令 - 应用物理力 + +**对实体应用拴绳力:** +``` +/slp leashdata applyForces +``` + +## 参数说明 + +### 选择器参数: +- ``: 实体选择器(如 `@e[type=!player]`, `@a`, `@p`) +- ``, ``, ``: 单个实体选择器 + +### 数值参数: +- `maxDistance`: 最大距离(`Double`,范围:LeashConfigManager.MAX_DISTANCE_MIN_VALUE 到 MAX_DISTANCE_MAX_VALUE) +- `elasticDistanceScale`: 弹性距离比例(`Double`,范围:LeashConfigManager.ELASTIC_DISTANCE_MIN_VALUE 到 ELASTIC_DISTANCE_MAX_VALUE) +- `keepTicks`: 保持时间(`Integer`,≥0) + +### 其他参数: +- `reserved`: 保留字段(`String`) +- ``, ``, ``: 方块位置坐标 + +## 使用示例 + +1. **拴住所有羊到玩家:** + ``` + /slp leashdata add @e[type=sheep] @p 10.0 0.8 600 "farm" + ``` + +2. **查看玩家拴绳状态:** + ``` + /slp leashdata get info @p + ``` + +3. **将拴绳从木桩转移到玩家:** + ``` + /slp leashdata transfer @e[type=sheep] fromBlock 100 64 100 @p + ``` + +4. **设置所有拴绳的最大距离:** + ``` + /slp leashdata set @e[type=sheep] static maxDistance 15.0 + ``` + +## 配置默认值 +- 最大距离范围:`LeashConfigManager.MAX_DISTANCE_MIN_VALUE` 到 `MAX_DISTANCE_MAX_VALUE` +- 弹性比例范围:`LeashConfigManager.ELASTIC_DISTANCE_MIN_VALUE` 到 `ELASTIC_DISTANCE_MAX_VALUE` + +## 注意事项 +1. 所有指令需要 OP 权限(权限等级 2) +2. 方块拴绳需要在对应位置存在 SuperLeashKnotEntity +3. 转移操作不会作用于来源实体自身(避免循环) +4. 显示结果会限制最多显示 4 个实体(超过会显示省略号) diff --git a/doc/LeashDataCommandUsage_EN.MD b/doc/LeashDataCommandUsage_EN.MD new file mode 100644 index 0000000..78df804 --- /dev/null +++ b/doc/LeashDataCommandUsage_EN.MD @@ -0,0 +1,176 @@ +## Basic Command Structure + +### Main Command +``` +/slp leashdata [subcommand] +``` + +### Command Permissions +Requires OP permissions (permission level 2) + +## Subcommand Categories + +### 1. GET Commands - Retrieve Information + +**Get leash data for entities:** +``` +/slp leashdata get data +``` +- ``: Target entities (selector) + +**Get detailed info for a single entity:** +``` +/slp leashdata get info +``` +- ``: Target entity (selector) + +### 2. ADD Commands - Add Leashes + +**Add entity leash:** +``` +/slp leashdata add [maxDistance] [elasticDistanceScale] [keepTicks] [reserved] +``` +- ``: Entities to be leashed +- ``: Holder entity +- `[maxDistance]`: Maximum distance (default value range) +- `[elasticDistanceScale]`: Elastic distance scale (default value range) +- `[keepTicks]`: Keep duration (game ticks, ≥0) +- `[reserved]`: Reserved field (string) + +**Add block leash:** +``` +/slp leashdata add block [maxDistance] [elasticDistanceScale] [keepTicks] [reserved] +``` +- ``: Block position +- Other parameters same as above + +### 3. REMOVE Commands - Remove Leashes + +**Remove specific entity leash:** +``` +/slp leashdata remove +``` +- ``: Target entities +- ``: Holder entity + +**Remove block leash:** +``` +/slp leashdata remove block +``` +- ``: Block position + +**Batch removal:** +``` +/slp leashdata remove all # Remove all leashes +/slp leashdata remove holders # Remove all entity leashes +/slp leashdata remove knots # Remove all block leashes +``` + +### 4. TRANSFER Commands - Transfer Leashes + +**Transfer from entity to entity:** +``` +/slp leashdata transfer [reserved] +``` +- ``: Original holder +- ``: New holder + +**Transfer from block to entity:** +``` +/slp leashdata transfer fromBlock [reserved] +``` +- ``: Original block position +- ``: New holder entity + +**Transfer from block to block:** +``` +/slp leashdata transfer fromBlock toBlock [reserved] +``` +- ``: Original block position +- ``: New block position + +### 5. SET Commands - Set Properties + +**Set static properties (applies to all leashes):** +``` +/slp leashdata set static maxDistance reset # Reset max distance +/slp leashdata set static maxDistance # Set max distance +/slp leashdata set static elasticDistanceScale reset # Reset elastic scale +/slp leashdata set static elasticDistanceScale # Set elastic scale +``` + +**Set entity leash properties:** +``` +# Set max distance +/slp leashdata set entity maxDistance [distance] [keepTicks] [reserved] +/slp leashdata set entity maxDistance [distance] [keepTicks] [reserved] # All holders + +# Set elastic distance scale +/slp leashdata set entity elasticDistanceScale [scale] [keepTicks] [reserved] +/slp leashdata set entity elasticDistanceScale [scale] [keepTicks] [reserved] # All holders +``` + +**Set block leash properties:** +``` +# Set max distance +/slp leashdata set block maxDistance [distance] [keepTicks] [reserved] +/slp leashdata set block maxDistance [distance] [keepTicks] [reserved] # All knots + +# Set elastic distance scale +/slp leashdata set block elasticDistanceScale [scale] [keepTicks] [reserved] +/slp leashdata set block elasticDistanceScale [scale] [keepTicks] [reserved] # All knots +``` + +### 6. APPLY FORCES Command - Apply Physics Forces + +**Apply leash forces to entities:** +``` +/slp leashdata applyForces +``` + +## Parameter Descriptions + +### Selector Parameters: +- ``: Entity selector (e.g., `@e[type=!player]`, `@a`, `@p`) +- ``, ``, ``: Single entity selector + +### Numerical Parameters: +- `maxDistance`: Maximum distance (`Double`, range: LeashConfigManager.MAX_DISTANCE_MIN_VALUE to MAX_DISTANCE_MAX_VALUE) +- `elasticDistanceScale`: Elastic distance scale (`Double`, range: LeashConfigManager.ELASTIC_DISTANCE_MIN_VALUE to ELASTIC_DISTANCE_MAX_VALUE) +- `keepTicks`: Keep duration (`Integer`, ≥0) + +### Other Parameters: +- `reserved`: Reserved field (`String`) +- ``, ``, ``: Block position coordinates + +## Usage Examples + +1. **Leash all sheep to player:** + ``` + /slp leashdata add @e[type=sheep] @p 10.0 0.8 600 "farm" + ``` + +2. **Check player's leash status:** + ``` + /slp leashdata get info @p + ``` + +3. **Transfer leash from fence post to player:** + ``` + /slp leashdata transfer @e[type=sheep] fromBlock 100 64 100 @p + ``` + +4. **Set max distance for all leashes:** + ``` + /slp leashdata set @e[type=sheep] static maxDistance 15.0 + ``` + +## Configuration Defaults +- Max distance range: `LeashConfigManager.MAX_DISTANCE_MIN_VALUE` to `MAX_DISTANCE_MAX_VALUE` +- Elastic scale range: `LeashConfigManager.ELASTIC_DISTANCE_MIN_VALUE` to `ELASTIC_DISTANCE_MAX_VALUE` + +## Important Notes +1. All commands require OP permissions (permission level 2) +2. Block leashes require SuperLeashKnotEntity to exist at the target position +3. Transfer operations do not affect the source entity itself (to prevent loops) +4. Display results are limited to showing up to 4 entities (shows ellipsis for more) \ No newline at end of file diff --git a/src/main/java/top/r3944realms/superleadrope/compat/CurtainCompat.java b/src/main/java/top/r3944realms/superleadrope/compat/CurtainCompat.java index e1379cb..e5e834e 100644 --- a/src/main/java/top/r3944realms/superleadrope/compat/CurtainCompat.java +++ b/src/main/java/top/r3944realms/superleadrope/compat/CurtainCompat.java @@ -22,10 +22,7 @@ import net.minecraftforge.fml.ModList; /** * The type Curtain compat. */ -public class CurtainCompat { - /** - * The constant isModLoaded. - */ +public class CurtainCompat{ public final static boolean isModLoaded = ModList.get().isLoaded("curtain"); /** diff --git a/src/main/java/top/r3944realms/superleadrope/compat/LuckPermsCompat.java b/src/main/java/top/r3944realms/superleadrope/compat/LuckPermsCompat.java new file mode 100644 index 0000000..5b41589 --- /dev/null +++ b/src/main/java/top/r3944realms/superleadrope/compat/LuckPermsCompat.java @@ -0,0 +1,95 @@ +package top.r3944realms.superleadrope.compat; + +import net.luckperms.api.LuckPerms; +import net.luckperms.api.LuckPermsProvider; +import net.luckperms.api.context.*; +import net.luckperms.api.node.Node; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.player.Player; +import net.minecraftforge.fml.ModList; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import top.r3944realms.superleadrope.SuperLeadRope; +import top.r3944realms.superleadrope.util.capability.LeashDataInnerAPI; + +import java.util.Objects; + +public class LuckPermsCompat { + public final static boolean isModLoaded = ModList.get().isLoaded("luckperms"); + public static volatile ILPC instance; + public interface ILPC { + void init(); + default boolean isLeashedBypass(Entity player) { return false; } + } + + @Contract(" -> new") + public static @NotNull ILPC getOrCreateLPC() { + + if (instance == null) { + synchronized (LuckPermsCompat.class) { + if (instance == null) { + if (!isModLoaded) { + instance = new DummyLPC(); + } else instance = new RealLPC(); + } + } + } + return instance; + } + + // 空实现 + private static class DummyLPC implements ILPC { + @Override + public void init() {} + } + + // 真实实现(只有在模组加载时才被初始化) + private static class RealLPC implements ILPC { + private boolean isInitialized; + private LuckPerms luckPerms ; + private final Node LeashBypass = Node.builder(SuperLeadRope.MOD_ID + ".leash.bypass").build(); + public RealLPC() { + isInitialized = false; + init(); + } + + @Override + public void init() { + try { + luckPerms = LuckPermsProvider.get(); + luckPerms.getContextManager().registerCalculator(new LeashCalculator()); + isInitialized = true; + } catch (IllegalStateException e) { + SuperLeadRope.logger.error("LuckPermsCompat failed to initialize", e); + } + } + + @Override + public boolean isLeashedBypass(Entity player) { + if (!(player instanceof Player)) return false; + return isInitialized && luckPerms.getUserManager().isLoaded(player.getUUID()) && + Objects.requireNonNull(luckPerms.getUserManager().getUser(player.getUUID())) + .getNodes() + .stream() + .filter(i -> i.equals(LeashBypass)) + .findFirst() + .map(Node::getValue) + .orElse(false); + } + + public static class LeashCalculator implements ContextCalculator { + @Override + public void calculate(@NotNull Player target, ContextConsumer contextConsumer) { + contextConsumer.accept("isLeashed", String.valueOf(LeashDataInnerAPI.QueryOperations.hasLeash(target))); + } + + @Override + public @NotNull ContextSet estimatePotentialContexts() { + ImmutableContextSet.Builder builder = ImmutableContextSet.builder(); + builder.add("isLeashed", "false"); + builder.add("isLeashed", "true"); + return builder.build(); + } + } + } +} diff --git a/src/main/java/top/r3944realms/superleadrope/content/capability/impi/LeashDataImpl.java b/src/main/java/top/r3944realms/superleadrope/content/capability/impi/LeashDataImpl.java index 1e16da3..1486c59 100644 --- a/src/main/java/top/r3944realms/superleadrope/content/capability/impi/LeashDataImpl.java +++ b/src/main/java/top/r3944realms/superleadrope/content/capability/impi/LeashDataImpl.java @@ -44,6 +44,7 @@ import top.r3944realms.superleadrope.api.event.SuperLeadRopeEvent; import top.r3944realms.superleadrope.api.type.capabilty.ILeashData; import top.r3944realms.superleadrope.api.type.capabilty.LeashInfo; import top.r3944realms.superleadrope.compat.CurtainCompat; +import top.r3944realms.superleadrope.compat.LuckPermsCompat; import top.r3944realms.superleadrope.config.LeashConfigManager; import top.r3944realms.superleadrope.content.entity.SuperLeashKnotEntity; import top.r3944realms.superleadrope.core.register.SLPSoundEvents; @@ -1607,7 +1608,7 @@ public class LeashDataImpl implements ILeashData { } public boolean canBeAttachedTo(Entity pEntity) { - if (pEntity == entity) { + if (pEntity == entity && !LuckPermsCompat.getOrCreateLPC().isLeashedBypass(entity)) { return false; } else { Optional leashInfo = getLeashInfo(pEntity); diff --git a/src/main/resources/coremods/morsb_patch.js b/src/main/resources/coremods/morsb_patch.js index 0b79d0e..092e600 100644 --- a/src/main/resources/coremods/morsb_patch.js +++ b/src/main/resources/coremods/morsb_patch.js @@ -13,48 +13,54 @@ * along with this program. If not, see . */ +var ASMAPI = Java.type('net.minecraftforge.coremod.api.ASMAPI'); 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" + 'leash_render': { + 'target': { + 'type': 'METHOD', + 'class': 'net.minecraft.client.renderer.entity.MobRenderer', + 'methodName': ASMAPI.mapMethod('m_5523_'), // shouldRender + 'methodDesc': '(Lnet/minecraft/world/entity/Mob;Lnet/minecraft/client/renderer/culling/Frustum;DDD)Z' }, - "transformer": function(method) { + 'transformer': function(method) { var insns = method.instructions; + // 寻找具体的 ICONST_0 位置 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( + + // 寻找 L4 标签后的 ICONST_0 -> IRETURN 序列 + if (insn.getOpcode() === Opcodes.ICONST_0) { + var nextInsn = insns.get(i + 1); + if (nextInsn && nextInsn.getOpcode() === Opcodes.IRETURN) { + // 找到目标位置,插入我们的钩子调用 + var newInstructions = ASMAPI.listOf( + new VarInsnNode(Opcodes.ALOAD, 1), // 加载 Mob 参数 (livingEntity) + new VarInsnNode(Opcodes.ALOAD, 2), // 加载 Frustum 参数 (camera) + new MethodInsnNode( + Opcodes.INVOKESTATIC, 'top/r3944realms/superleadrope/core/hook/LeashRenderHook', - null, - ASMAPI.MethodType.STATIC, 'shouldRenderExtra', '(Lnet/minecraft/world/entity/Mob;Lnet/minecraft/client/renderer/culling/Frustum;)Z', - ASMAPI.MethodCallMode.STATIC + false ) - )); - // 移除原来的 ICONST_0 - insns.remove(insn); + ); + + // 在 ICONST_0 之前插入新指令,然后移除 ICONST_0 + method.instructions.insertBefore(insn, newInstructions); + method.instructions.remove(insn); + + // 只需要修改这一个地方 break; } } } + return method; } }