diff --git a/build.gradle b/build.gradle
index 1882933..4d29570 100644
--- a/build.gradle
+++ b/build.gradle
@@ -151,6 +151,7 @@ dependencies {
modCompileOnly("blank:curtain-1.20.1:1.3.2")
modRuntimeOnly("blank:curtain-1.20.1:1.3.2")
modCompileOnly("mezz.jei:jei-${minecraft_version}-forge-api:${jei_version}")
+ modCompileOnly("mezz.jei:jei-${minecraft_version}-common-api:${jei_version}")
modRuntimeOnly("mezz.jei:jei-${minecraft_version}-forge:${jei_version}")
modRuntimeOnly("curse.maven:spark-361579:4738952")
compileOnly ('me.lucko:spark-api:0.1-SNAPSHOT')
diff --git a/src/main/java/top/r3944realms/superleadrope/CommonEventHandler.java b/src/main/java/top/r3944realms/superleadrope/CommonEventHandler.java
index 9a42e5c..cbc6763 100644
--- a/src/main/java/top/r3944realms/superleadrope/CommonEventHandler.java
+++ b/src/main/java/top/r3944realms/superleadrope/CommonEventHandler.java
@@ -96,7 +96,7 @@ public class CommonEventHandler {
public static void onEntityJoinWorld(EntityJoinLevelEvent event) {
Entity entity = event.getEntity();
if (entity.level().isClientSide) return;
- if (entity instanceof LivingEntity || entity instanceof Boat || entity instanceof Minecart) {
+ if (LeashDataImpl.isLeashable(entity)) {
LeashDataAPI.getLeashData(entity).ifPresent(LeashSyncManager.Data::track);
LeashStateAPI.getLeashState(entity).ifPresent(LeashSyncManager.State::track);
if (entity instanceof ServerPlayer serverPlayer) {
@@ -113,7 +113,7 @@ public class CommonEventHandler {
public static void onEntityLeaveWorld(EntityLeaveLevelEvent event) {
Entity entity = event.getEntity();
if (entity.level().isClientSide) return;
- if (entity instanceof LivingEntity || entity instanceof Boat || entity instanceof Minecart) {
+ if (LeashDataImpl.isLeashable(entity)) {
if (entity instanceof ServerPlayer serverPlayer) {
LeashSyncManager.Data.forEach(i -> {
if(i.isLeashedBy(serverPlayer)) {
diff --git a/src/main/java/top/r3944realms/superleadrope/client/renderer/resolver/SuperLeashStateResolver.java b/src/main/java/top/r3944realms/superleadrope/client/renderer/resolver/SuperLeashStateResolver.java
index 150b7c4..905d76d 100644
--- a/src/main/java/top/r3944realms/superleadrope/client/renderer/resolver/SuperLeashStateResolver.java
+++ b/src/main/java/top/r3944realms/superleadrope/client/renderer/resolver/SuperLeashStateResolver.java
@@ -167,9 +167,13 @@ public class SuperLeashStateResolver {
if (entity instanceof Player player && (player.isFallFlying() || player.isAutoSpinAttack())) {
roll = getRoll(player, partialTicks, roll);
}
-
+ boolean isFirstPerson = Minecraft.getInstance().options.getCameraType() == CameraType.FIRST_PERSON;
// 应用旋转到局部偏移
- Vec3 rotatedOffset = localOffset.yRot(-yaw).zRot(-roll);
+ Vec3 rotatedOffset = localOffset;
+ if (!isFirstPerson && entity instanceof Player) {
+ rotatedOffset = rotatedOffset.add(0,0,0.2);
+ }
+ rotatedOffset = rotatedOffset.yRot(-yaw).zRot(-roll);
// 返回世界坐标
return centerPos.add(rotatedOffset);
diff --git a/src/main/java/top/r3944realms/superleadrope/compat/jei/JEIPlugin.java b/src/main/java/top/r3944realms/superleadrope/compat/jei/JEIPlugin.java
new file mode 100644
index 0000000..3bb4bc4
--- /dev/null
+++ b/src/main/java/top/r3944realms/superleadrope/compat/jei/JEIPlugin.java
@@ -0,0 +1,32 @@
+/*
+ * Super Lead rope mod
+ * Copyright (C) 2025 R3944Realms
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package top.r3944realms.superleadrope.compat.jei;
+
+import mezz.jei.api.IModPlugin;
+import mezz.jei.api.JeiPlugin;
+import net.minecraft.resources.ResourceLocation;
+import org.jetbrains.annotations.NotNull;
+import top.r3944realms.superleadrope.SuperLeadRope;
+
+@JeiPlugin
+public class JEIPlugin implements IModPlugin {
+ private static final ResourceLocation UID = new ResourceLocation(SuperLeadRope.MOD_ID, "jei_plugin");
+
+ @Override
+ public @NotNull ResourceLocation getPluginUid() {
+ return UID;
+ }
+}
diff --git a/src/main/java/top/r3944realms/superleadrope/config/LeashCommonConfig.java b/src/main/java/top/r3944realms/superleadrope/config/LeashCommonConfig.java
index 8f13fda..ffedb3f 100644
--- a/src/main/java/top/r3944realms/superleadrope/config/LeashCommonConfig.java
+++ b/src/main/java/top/r3944realms/superleadrope/config/LeashCommonConfig.java
@@ -1,18 +1,3 @@
-/*
- * Super Lead rope mod
- * Copyright (C) 2025 R3944Realms
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
package top.r3944realms.superleadrope.config;
import net.minecraftforge.common.ForgeConfigSpec;
@@ -22,38 +7,61 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class LeashCommonConfig {
- public static ForgeConfigSpec.Builder BUILDER = new ForgeConfigSpec.Builder();
- public static ForgeConfigSpec SPEC;
+ public static final ForgeConfigSpec.Builder BUILDER = new ForgeConfigSpec.Builder();
+ public static final ForgeConfigSpec SPEC;
public static final Common COMMON;
+
static {
BUILDER.comment("Leash Common Config");
COMMON = new Common(BUILDER);
SPEC = BUILDER.build();
}
+
public static class Common {
- public final ForgeConfigSpec.ConfigValue> teleportWhitelist;
+ // Command
+ public final ForgeConfigSpec.BooleanValue enableSLPModCommandPrefix;
public final ForgeConfigSpec.ConfigValue SLPModCommandPrefix;
- public final ForgeConfigSpec.BooleanValue EnableSLPModCommandPrefix;
+
+ // Entity
+ public final ForgeConfigSpec.ConfigValue> teleportWhitelist;
+
+ // Leash settings
public final ForgeConfigSpec.DoubleValue maxLeashLength;
public final ForgeConfigSpec.DoubleValue elasticDistance;
public final ForgeConfigSpec.DoubleValue extremeSnapFactor;
public final ForgeConfigSpec.DoubleValue springDampening;
public final ForgeConfigSpec.ConfigValue> axisSpecificElasticity;
public final ForgeConfigSpec.IntValue maxLeashesPerEntity;
- public final ForgeConfigSpec.ConfigValue> defaultApplyEntityLocationOffset, defaultHolderLocationOffset;
+
+ // True damping
+ public final ForgeConfigSpec.BooleanValue enableTrueDamping;
+ public final ForgeConfigSpec.DoubleValue dampingFactor;
+ public final ForgeConfigSpec.DoubleValue maxForce;
+ public final ForgeConfigSpec.DoubleValue playerSpringFactor;
+ public final ForgeConfigSpec.DoubleValue mobSpringFactor;
+
+ // Leash state offsets
+ public final ForgeConfigSpec.ConfigValue> defaultApplyEntityLocationOffset;
+ public final ForgeConfigSpec.ConfigValue> defaultHolderLocationOffset;
+
// 正则表达式模式
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*" );
+ "(?i)(?:vec3|vec3d|vector3|offset)\\s*\\(\\s*([-+]?[0-9]*\\.?[0-9]+)\\s*,\\s*([-+]?[0-9]*\\.?[0-9]+)\\s*,\\s*([-+]?[0-9]*\\.?[0-9]+)\\s*\\)\\s*:\\s*\\[\\s*([^]]+?)\\s*]\\s*"
+ );
public Common(ForgeConfigSpec.Builder builder) {
- BUILDER.push("Command");
- EnableSLPModCommandPrefix = builder
- .comment("The prefix of this mod's commands")
- .define("SLPModCommandPrefix", true);
+ // ===== Command =====
+ builder.push("Command");
+ enableSLPModCommandPrefix = builder
+ .comment("Enable or disable the SLP mod command prefix")
+ .define("enableSLPModCommandPrefix", true);
+
SLPModCommandPrefix = builder
.comment("The prefix of this mod's commands", " [ Default:'slp'] ")
- .define("EnableSLPModCommandPrefix", "slp");
- BUILDER.pop();
+ .define("SLPModCommandPrefix", "slp");
+ builder.pop();
+
+ // ===== Entity =====
builder.push("Entity");
teleportWhitelist = builder
.comment(
@@ -64,15 +72,18 @@ public class LeashCommonConfig {
" - #modid:tag_name : allow teleporting to all entities under a given entity type tag"
)
.defineListAllowEmpty(
- List.of("allowedTeleportEntities"),
+ List.of("teleportWhitelist"),
List.of("#minecraft", "modernlife:bicycle", "modernlife:motorboat"),
o -> o instanceof String s && isValidEntityRefFormat(s)
);
builder.pop();
+
+ // ===== Leash Settings =====
builder.push("LeashSettings");
maxLeashLength = builder
.comment("Maximum leash distance (in blocks) for any entity")
.defineInRange("maxLeashLength", 12.0, 6.0, 256.0);
+
elasticDistance = builder
.comment("Default elastic distance for the Super Lead rope")
.defineInRange("elasticDistance", 6.0, 6.0, 128.0);
@@ -92,8 +103,32 @@ public class LeashCommonConfig {
maxLeashesPerEntity = builder
.comment("Maximum number of leashes per entity")
.defineInRange("maxLeashesPerEntity", 6, 1, 24);
-
builder.pop();
+
+ // ===== True Damping =====
+ builder.push("TrueDamping");
+ enableTrueDamping = builder
+ .comment("Enable true velocity-based damping force (adds -c*v term)")
+ .define("enableTrueDamping", true);
+
+ dampingFactor = builder
+ .comment("Damping factor (resistance against entity velocity)")
+ .defineInRange("dampingFactor", 0.1, 0.0, 2.0);
+
+ maxForce = builder
+ .comment("Maximum leash pulling force (to prevent over-aggressive pulling)")
+ .defineInRange("maxForce", 1.0, 0.1, 10.0);
+
+ playerSpringFactor = builder
+ .comment("Spring stiffness multiplier for players")
+ .defineInRange("playerSpringFactor", 0.3, 0.05, 1.0);
+
+ mobSpringFactor = builder
+ .comment("Spring stiffness multiplier for mobs")
+ .defineInRange("mobSpringFactor", 0.5, 0.05, 2.0);
+ builder.pop();
+
+ // ===== Leash State Offsets =====
builder.push("LeashStateSettings");
defaultApplyEntityLocationOffset = builder
.comment(
@@ -101,12 +136,6 @@ public class LeashCommonConfig {
"Reference point: the entity's eyeHeight (eye / head position).",
"Format: vec3(x,y,z) : [entity_list]",
"Optional names: vector3, vec3d, offset",
- "Entity list may contain:",
- " - modid:entity_id : specific entity (e.g. minecraft:bee)",
- " - #modid:tag_name : entity type tag (e.g. #minecraft:boats)",
- " - #modid : all entities from a mod (e.g. #minecraft)",
- " - * : all entities",
- "Multiple entries can be separated by commas",
"Example: vec3(0,0.2,0) : [minecraft:bee, minecraft:horse]",
"Priority order: specific entity > tag > mod > *"
)
@@ -125,12 +154,6 @@ public class LeashCommonConfig {
"Reference point: the entity's eyeHeight (eye / head position).",
"Format: vec3(x,y,z) : [entity_list]",
"Optional names: vector3, vec3d, offset",
- "Entity list may contain:",
- " - modid:entity_id : specific entity (e.g. minecraft:player)",
- " - #modid:tag_name : entity type tag (e.g. #minecraft:players)",
- " - #modid : all entities from a mod (e.g. #minecraft)",
- " - * : all entities",
- "Multiple entries can be separated by commas",
"Example: vec3(0,-0.5,0) : [minecraft:player]",
"Priority order: specific entity > tag > mod > *"
)
@@ -142,42 +165,28 @@ public class LeashCommonConfig {
),
o -> o instanceof String s && isValidOffsetRefFormat(s)
);
- BUILDER.pop();
+ builder.pop();
}
private static boolean isValidEntityRefFormat(String s) {
- if ("*".equals(s)) {
- return true; // 支持任意实体通配
- }
+ if ("*".equals(s)) return true; // 支持任意实体通配
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) {
- // 匹配格式: vec3(x,y,z) : [entity_list]
- Matcher matcher = Common.OFFSET_PATTERN.matcher(s);
- if (!matcher.matches()) {
- return false;
- }
-
- // 检查坐标值是否有效
+ Matcher matcher = 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(",");
+ String[] entities = matcher.group(4).split(",");
for (String entity : entities) {
- if (!isValidEntityRefFormat(entity.trim())) {
- return false;
- }
+ if (!isValidEntityRefFormat(entity.trim())) return false;
}
return true;
} catch (NumberFormatException e) {
diff --git a/src/main/java/top/r3944realms/superleadrope/config/LeashConfigManager.java b/src/main/java/top/r3944realms/superleadrope/config/LeashConfigManager.java
index 5626f7f..c493728 100644
--- a/src/main/java/top/r3944realms/superleadrope/config/LeashConfigManager.java
+++ b/src/main/java/top/r3944realms/superleadrope/config/LeashConfigManager.java
@@ -30,424 +30,203 @@ import java.util.regex.Matcher;
import static top.r3944realms.superleadrope.config.LeashCommonConfig.Common.OFFSET_PATTERN;
public class LeashConfigManager {
- private final Map entityHolderOffsetMap = new ConcurrentHashMap<>(), entityLeashOffsetMap = new ConcurrentHashMap<>();
- private final Map tagHolderOffsetMap = new ConcurrentHashMap<>(), tagLeashOffsetMap = new ConcurrentHashMap<>();
- private final Map modHolderOffsetMap = new ConcurrentHashMap<>(), modLeashOffsetMap = new ConcurrentHashMap<>();
- // 缓存常用配置值以提高性能
- private volatile List teleportWhitelistCache;
- private volatile String commandPrefixCache;
- private volatile boolean enableCommandPrefixCache;
- private volatile double maxLeashLengthCache;
- private volatile double elasticDistanceCache;
- private volatile double extremeSnapFactorCache;
- private volatile double springDampeningCache;
- private volatile List axisSpecificElasticityCache;
- private volatile int maxLeashesPerEntityCache;
+ // ========== 偏移映射 ==========
+ private final Map entityHolderMap = new ConcurrentHashMap<>();
+ private final Map tagHolderMap = new ConcurrentHashMap<>();
+ private final Map modHolderMap = new ConcurrentHashMap<>();
+
+ private final Map entityLeashMap = new ConcurrentHashMap<>();
+ private final Map tagLeashMap = new ConcurrentHashMap<>();
+ private final Map modLeashMap = new ConcurrentHashMap<>();
+
+ // ========== 缓存配置 ==========
+ private volatile List teleportWhitelistCache = Collections.emptyList();
+ private volatile String commandPrefixCache = "slp";
+ private volatile boolean commandPrefixEnabledCache = true;
+
+ private volatile boolean enableTrueDamping = true;
+ private volatile double maxForce = 1.0;
+ private volatile double playerSpringFactor = 0.3;
+ private volatile double mobSpringFactor = 0.5;
+
+ private volatile double maxLeashLength = 12.0;
+ private volatile double elasticDistance = 6.0;
+ private volatile double extremeSnapFactor = 2.0;
+ private volatile double springDampening = 0.7;
+ private volatile List axisElasticity = List.of(0.8, 0.2, 0.8);
+ private volatile int maxLeashesPerEntity = 6;
public LeashConfigManager() {
- this.reloadAll();
- }
-
- /**
- * 解析偏移配置(线程安全)
- */
- public void parseOffsetConfig() {
- // --- Holder ---
- Map holderEntityMap = new HashMap<>();
- Map holderTagMap = new HashMap<>();
- Map holderModMap = new HashMap<>();
-
- List extends String> holderOffsets = LeashCommonConfig.COMMON.defaultHolderLocationOffset.get();
- for (String offsetConfig : holderOffsets) {
- 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);
- for (String entity : entityList.split(",")) {
- String trimmed = entity.trim();
- if (trimmed.equals("*")) {
- // special case: apply to all entities
- holderModMap.put("*", offset);
- } else if (trimmed.startsWith("#")) {
- String body = trimmed.substring(1).trim();
- if (body.contains(":")) {
- holderTagMap.put(body, offset);
- } else {
- holderModMap.put(body, offset);
- }
- } else {
- holderEntityMap.put(trimmed, offset);
- }
- }
- } catch (NumberFormatException e) {
- SuperLeadRope.logger.error("Invalid holder offset config: {}", offsetConfig);
- }
- }
-
- entityHolderOffsetMap.clear();
- entityHolderOffsetMap.putAll(holderEntityMap);
- tagHolderOffsetMap.clear();
- tagHolderOffsetMap.putAll(holderTagMap);
- modHolderOffsetMap.clear();
- modHolderOffsetMap.putAll(holderModMap);
-
- // --- Leash ---
- Map leashEntityMap = new HashMap<>();
- Map leashTagMap = new HashMap<>();
- Map leashModMap = new HashMap<>();
-
- List extends String> leashOffsets = LeashCommonConfig.COMMON.defaultApplyEntityLocationOffset.get();
- for (String offsetConfig : leashOffsets) {
- 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);
- for (String entity : entityList.split(",")) {
- String trimmed = entity.trim();
- if (trimmed.equals("*")) {
- leashModMap.put("*", offset);
- } else if (trimmed.startsWith("#")) {
- String body = trimmed.substring(1).trim();
- if (body.contains(":")) {
- leashTagMap.put(body, offset);
- } else {
- leashModMap.put(body, offset);
- }
- } else {
- leashEntityMap.put(trimmed, offset);
- }
- }
- } catch (NumberFormatException e) {
- SuperLeadRope.logger.error("Invalid leash offset config: {}", offsetConfig);
- }
- }
-
- entityLeashOffsetMap.clear();
- entityLeashOffsetMap.putAll(leashEntityMap);
- tagLeashOffsetMap.clear();
- tagLeashOffsetMap.putAll(leashTagMap);
- modLeashOffsetMap.clear();
- modLeashOffsetMap.putAll(leashModMap);
- }
-
-
- /**
- * 重新加载所有配置值到缓存
- */
- 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 getDefaultEntityOffset(EntityType> entityType) {
- String entityId = entityType.builtInRegistryHolder().key().location().toString();
- String modId = entityId.split(":")[0]; // 从实体ID提取modId
-
- // 获取实体的标签
- List tagStrings = new ArrayList<>();
- for (var tag : entityType.builtInRegistryHolder().tags().toList()) {
- tagStrings.add(tag.location().toString());
- }
-
- double[] offset = getDefaultEntityOffset(entityId, modId, tagStrings);
- return offset != null ? new Vec3(offset[0], offset[1], offset[2]) : Vec3.ZERO;
- }
-
- /**
- * 获取实体对象的偏移量(便捷方法)
- */
- public Vec3 getDefaultEntityOffset(Entity entity) {
- return getDefaultEntityOffset(entity.getType());
- }
-
-
- /**
- * 获取实体偏移量(原始数据)
- */
- public double[] getDefaultEntityOffset(String entityId, String modId, List tags) {
- // 1. 首先检查特定实体
- if (entityLeashOffsetMap.containsKey(entityId)) {
- return entityLeashOffsetMap.get(entityId);
- }
-
- // 2. 检查标签
- for (String tag : tags) {
- if (tagLeashOffsetMap.containsKey(tag)) {
- return tagHolderOffsetMap.get(tag);
- }
- }
-
- // 3. 检查模组
- if (modLeashOffsetMap.containsKey(modId)) {
- return modLeashOffsetMap.get(modId);
- }
-
- //4. 通配符
- if (modLeashOffsetMap.containsKey("*")) {
- return modLeashOffsetMap.get("*");
- }
- return null;
- }
- /**
- * 获取实体类型的偏移量
- */
- @SuppressWarnings("deprecation")
- public Vec3 getDefaultHolderOffset(EntityType> entityType) {
- String entityId = entityType.builtInRegistryHolder().key().location().toString();
- String modId = entityId.split(":")[0]; // 从实体ID提取modId
-
- // 获取实体的标签
- List tagStrings = new ArrayList<>();
- for (var tag : entityType.builtInRegistryHolder().tags().toList()) {
- tagStrings.add(tag.location().toString());
- }
-
- double[] offset = getDefaultHolderOffset(entityId, modId, tagStrings);
- return offset != null ? new Vec3(offset[0], offset[1], offset[2]) : Vec3.ZERO;
- }
-
- /**
- * 获取实体对象的偏移量(便捷方法)
- */
- public Vec3 getDefaultHolderOffset(Entity entity) {
- return getDefaultHolderOffset(entity.getType());
- }
-
-
- /**
- * 获取实体偏移量(原始数据)
- */
- public double[] getDefaultHolderOffset(String entityId, String modId, List tags) {
- // 1. 首先检查特定实体
- if (entityHolderOffsetMap.containsKey(entityId)) {
- return entityHolderOffsetMap.get(entityId);
- }
-
- // 2. 检查标签
- for (String tag : tags) {
- if (tagHolderOffsetMap.containsKey(tag)) {
- return tagHolderOffsetMap.get(tag);
- }
- }
-
- // 3. 检查模组
- if (modHolderOffsetMap.containsKey(modId)) {
- return modHolderOffsetMap.get(modId);
- }
-
- //4. 通配符
- if (modLeashOffsetMap.containsKey("*")) {
- return modHolderOffsetMap.get("*");
- }
-
- return null;
- }
-
- // ========== 传送白名单相关方法 ==========
-
- public List getTeleportWhitelist() {
- return Collections.unmodifiableList(teleportWhitelistCache);
- }
-
- @SuppressWarnings("deprecation")
- public boolean isEntityTeleportAllowed(EntityType> entityType) {
- String entityId = entityType.builtInRegistryHolder().key().location().toString();
- String modid = entityId.split(":")[0];
-
- for (String entry : teleportWhitelistCache) {
- if (entry.startsWith("#")) {
- String body = entry.substring(1);
-
- // Case 1: #modid → allow all entities from this mod
- if (!body.contains(":")) {
- if (modid.equals(body)) {
- return true;
- }
- }
- // Case 2: #modid:tag_name → allow all entities under this tag
- else {
- ResourceLocation tagId = new ResourceLocation(body);
- TagKey> tag = TagKey.create(Registries.ENTITY_TYPE, tagId);
- if (entityType.builtInRegistryHolder().is(tag)) {
- return true;
- }
- }
- } else {
- // Case 3: modid:entity_name → allow a specific entity
- if (entry.equals(entityId)) {
- return true;
- }
- }
- }
- return false;
- }
-
- public boolean isEntityTeleportAllowed(String entityId) {
- // 对于字符串ID,我们无法检查标签,只能检查模组和特定实体
- String modid = entityId.contains(":") ? entityId.split(":")[0] : "minecraft";
-
- for (String entry : teleportWhitelistCache) {
- if (entry.startsWith("#")) {
- String body = entry.substring(1);
-
- // Case 1: #modid → allow all entities from this mod
- if (!body.contains(":")) {
- if (modid.equals(body)) {
- return true;
- }
- }
- // Case 2: #modid:tag_name → 字符串ID无法检查标签,跳过
- // 如果需要支持标签检查,需要传入EntityType而不是String
- } else {
- // Case 3: modid:entity_name → allow a specific entity
- if (entry.equals(entityId)) {
- return true;
- }
- }
- }
- return false;
- }
-
- // 辅助方法:检查实体ID是否匹配模式(用于旧的匹配逻辑)
- private boolean matchesTeleportPattern(String pattern, String entityId) {
- if (pattern.startsWith("#")) {
- String body = pattern.substring(1);
- if (body.contains(":")) {
- // 标签格式: #modid:tag_name - 字符串ID无法准确匹配标签
- // 返回模组匹配作为近似
- String patternModId = body.split(":")[0];
- String entityModId = entityId.split(":")[0];
- return entityModId.equals(patternModId);
- } else {
- // 模组格式: #modid
- String entityModId = entityId.split(":")[0];
- return entityModId.equals(body);
- }
- } else {
- // 实体格式: modid:entity_id
- return entityId.equals(pattern);
- }
- }
-
- // 添加一个重载方法,方便使用Entity对象
- public boolean isEntityTeleportAllowed(Entity entity) {
- return isEntityTeleportAllowed(entity.getType());
- }
-
- // ========== 命令配置相关方法 ==========
-
- public String getCommandPrefix() {
- return commandPrefixCache;
- }
-
- public boolean isCommandPrefixEnabled() {
- return enableCommandPrefixCache;
- }
-
- public String getFullCommand(String subCommand) {
- return isCommandPrefixEnabled() ?
- getCommandPrefix() + " " + subCommand :
- subCommand;
- }
-
- // ========== 拴绳物理配置相关方法 ==========
-
- public double getMaxLeashLength() {
- return maxLeashLengthCache;
- }
-
- public double getElasticDistance() {
- return elasticDistanceCache;
- }
-
- public double getExtremeSnapFactor() {
- return extremeSnapFactorCache;
- }
-
- public double getBreakDistance() {
- return getMaxLeashLength() * getExtremeSnapFactor();
- }
-
- public double getSpringDampening() {
- return springDampeningCache;
- }
-
- public List getAxisSpecificElasticity() {
- return Collections.unmodifiableList(axisSpecificElasticityCache);
- }
-
- public double getXElasticity() {
- return !axisSpecificElasticityCache.isEmpty() ? axisSpecificElasticityCache.get(0) : 0.8;
- }
-
- public double getYElasticity() {
- return axisSpecificElasticityCache.size() > 1 ? axisSpecificElasticityCache.get(1) : 0.2;
- }
-
- public double getZElasticity() {
- return axisSpecificElasticityCache.size() > 2 ? axisSpecificElasticityCache.get(2) : 0.8;
- }
-
- // ========== 实体限制配置相关方法 ==========
-
- public int getMaxLeashesPerEntity() {
- return maxLeashesPerEntityCache;
- }
-
- public boolean canEntityAcceptMoreLeashes(Entity entity, int currentLeashCount) {
- return currentLeashCount < getMaxLeashesPerEntity();
- }
-
- // ========== 管理方法 ==========
-
- public void reload() {
reloadAll();
}
- public void clear() {
- entityHolderOffsetMap.clear();entityLeashOffsetMap.clear();
- tagHolderOffsetMap.clear();tagLeashOffsetMap.clear();
- modHolderOffsetMap.clear();modLeashOffsetMap.clear();
- teleportWhitelistCache = Collections.emptyList();
+ // ================== 偏移解析 ==================
+ private Map> parseOffsetList(List extends String> offsetConfigs) {
+ Map entityMap = new HashMap<>();
+ Map tagMap = new HashMap<>();
+ Map modMap = new HashMap<>();
+
+ for (String config : offsetConfigs) {
+ Matcher matcher = OFFSET_PATTERN.matcher(config);
+ 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[] entities = matcher.group(4).split(",");
+ for (String e : entities) {
+ String trimmed = e.trim();
+ if (trimmed.equals("*")) modMap.put("*", offset);
+ else if (trimmed.startsWith("#")) {
+ String body = trimmed.substring(1).trim();
+ if (body.contains(":")) tagMap.put(body, offset);
+ else modMap.put(body, offset);
+ } else entityMap.put(trimmed, offset);
+ }
+ } catch (NumberFormatException ex) {
+ SuperLeadRope.logger.error("Invalid offset config: {}", config);
+ }
+ }
+
+ return Map.of(
+ "entity", entityMap,
+ "tag", tagMap,
+ "mod", modMap
+ );
}
- public String getStats() {
- return String.format("Holder: Entities: %d, Tags: %d, Mods: %d \n Leash: Entities: %d, Tags: %d, Mods: %d, TeleportWhitelist: %d",
- entityHolderOffsetMap.size(), tagHolderOffsetMap.size(), modHolderOffsetMap.size(),
- entityLeashOffsetMap.size(), tagLeashOffsetMap.size(), modLeashOffsetMap.size(),
- teleportWhitelistCache.size());
+ public void parseOffsetConfig() {
+ Map> holder = parseOffsetList(LeashCommonConfig.COMMON.defaultHolderLocationOffset.get());
+ entityHolderMap.clear(); entityHolderMap.putAll(holder.get("entity"));
+ tagHolderMap.clear(); tagHolderMap.putAll(holder.get("tag"));
+ modHolderMap.clear(); modHolderMap.putAll(holder.get("mod"));
+
+ Map> leash = parseOffsetList(LeashCommonConfig.COMMON.defaultApplyEntityLocationOffset.get());
+ entityLeashMap.clear(); entityLeashMap.putAll(leash.get("entity"));
+ tagLeashMap.clear(); tagLeashMap.putAll(leash.get("tag"));
+ modLeashMap.clear(); modLeashMap.putAll(leash.get("mod"));
+ }
+
+ // ================== 获取偏移 ==================
+ private double[] getOffset(String entityId, String modId, List tags,
+ Map entityMap,
+ Map tagMap,
+ Map modMap) {
+
+ if (entityMap.containsKey(entityId)) return entityMap.get(entityId);
+ for (String tag : tags) if (tagMap.containsKey(tag)) return tagMap.get(tag);
+ if (modMap.containsKey(modId)) return modMap.get(modId);
+ return modMap.getOrDefault("*", null);
+ }
+
+ @SuppressWarnings({"DuplicatedCode", "deprecation"})
+ public Vec3 getDefaultEntityOffset(EntityType> type) {
+ String entityId = type.builtInRegistryHolder().key().location().toString();
+ String modId = entityId.split(":")[0];
+ List tags = new ArrayList<>();
+ for (var t : type.builtInRegistryHolder().tags().toList()) tags.add(t.location().toString());
+
+ double[] offset = getOffset(entityId, modId, tags, entityLeashMap, tagLeashMap, modLeashMap);
+ return offset != null ? new Vec3(offset[0], offset[1], offset[2]) : Vec3.ZERO;
+ }
+
+ @SuppressWarnings({"DuplicatedCode", "deprecation"})
+ public Vec3 getDefaultHolderOffset(EntityType> type) {
+ String entityId = type.builtInRegistryHolder().key().location().toString();
+ String modId = entityId.split(":")[0];
+ List tags = new ArrayList<>();
+ for (var t : type.builtInRegistryHolder().tags().toList()) tags.add(t.location().toString());
+
+ double[] offset = getOffset(entityId, modId, tags, entityHolderMap, tagHolderMap, modHolderMap);
+ return offset != null ? new Vec3(offset[0], offset[1], offset[2]) : Vec3.ZERO;
+ }
+
+ public Vec3 getDefaultEntityOffset(Entity entity) { return getDefaultEntityOffset(entity.getType()); }
+ public Vec3 getDefaultHolderOffset(Entity entity) { return getDefaultHolderOffset(entity.getType()); }
+
+ // ================== 白名单 ==================
+ public List getTeleportWhitelist() { return Collections.unmodifiableList(teleportWhitelistCache); }
+ @SuppressWarnings({"DuplicatedCode", "deprecation"})
+ public boolean isEntityTeleportAllowed(EntityType> type) {
+ String entityId = type.builtInRegistryHolder().key().location().toString();
+ String modId = entityId.split(":")[0];
+
+ for (String entry : teleportWhitelistCache) {
+ if (entry.startsWith("#")) {
+ String body = entry.substring(1);
+ if (!body.contains(":") && body.equals(modId)) return true;
+
+ if (body.contains(":")) {
+ ResourceLocation tagId = new ResourceLocation(body);
+ TagKey> tag = TagKey.create(Registries.ENTITY_TYPE, tagId);
+ if (type.builtInRegistryHolder().is(tag)) return true;
+ }
+ } else if (entry.equals(entityId)) return true;
+ }
+ return false;
+ }
+
+ public boolean isEntityTeleportAllowed(Entity entity) { return isEntityTeleportAllowed(entity.getType()); }
+
+ // ================== 命令 ==================
+ public String getCommandPrefix() { return commandPrefixCache; }
+ public boolean isCommandPrefixEnabled() { return commandPrefixEnabledCache; }
+ public String getFullCommand(String subCommand) {
+ return isCommandPrefixEnabled() ? getCommandPrefix() + " " + subCommand : subCommand;
+ }
+
+ // ================== 拴绳物理参数 ==================
+ public boolean isEnableTrueDamping() { return enableTrueDamping; }
+ public double getMaxForce() { return maxForce; }
+ public double getPlayerSpringFactor() { return playerSpringFactor; }
+ public double getMobSpringFactor() { return mobSpringFactor; }
+
+ public double getMaxLeashLength() { return maxLeashLength; }
+ public double getElasticDistance() { return elasticDistance; }
+ public double getExtremeSnapFactor() { return extremeSnapFactor; }
+ public double getBreakDistance() { return maxLeashLength * extremeSnapFactor; }
+ public double getSpringDampening() { return springDampening; }
+ public List getAxisElasticity() { return Collections.unmodifiableList(axisElasticity); }
+ public double getXElasticity() { return !axisElasticity.isEmpty() ? axisElasticity.get(0) : 0.8; }
+ public double getYElasticity() { return axisElasticity.size() > 1 ? axisElasticity.get(1) : 0.2; }
+ public double getZElasticity() { return axisElasticity.size() > 2 ? axisElasticity.get(2) : 0.8; }
+
+ public int getMaxLeashesPerEntity() { return maxLeashesPerEntity; }
+ public boolean canEntityAcceptMoreLeashes(Entity entity, int currentCount) {
+ return currentCount < maxLeashesPerEntity;
+ }
+
+ // ================== 管理 ==================
+ public void reloadAll() {
+ parseOffsetConfig();
+
+ teleportWhitelistCache = new ArrayList<>(LeashCommonConfig.COMMON.teleportWhitelist.get());
+ commandPrefixCache = LeashCommonConfig.COMMON.SLPModCommandPrefix.get();
+ commandPrefixEnabledCache = LeashCommonConfig.COMMON.enableSLPModCommandPrefix.get();
+
+ maxLeashLength = LeashCommonConfig.COMMON.maxLeashLength.get();
+ elasticDistance = LeashCommonConfig.COMMON.elasticDistance.get();
+ extremeSnapFactor = LeashCommonConfig.COMMON.extremeSnapFactor.get();
+ springDampening = LeashCommonConfig.COMMON.springDampening.get();
+ axisElasticity = new ArrayList<>(LeashCommonConfig.COMMON.axisSpecificElasticity.get());
+ maxLeashesPerEntity = LeashCommonConfig.COMMON.maxLeashesPerEntity.get();
+
+ enableTrueDamping = LeashCommonConfig.COMMON.enableTrueDamping.get();
+ maxForce = LeashCommonConfig.COMMON.maxForce.get();
+ playerSpringFactor = LeashCommonConfig.COMMON.playerSpringFactor.get();
+ mobSpringFactor = LeashCommonConfig.COMMON.mobSpringFactor.get();
+
+ SuperLeadRope.logger.debug("Configs reloaded: {}", getStats());
+ }
+
+ public void clear() {
+ entityHolderMap.clear(); tagHolderMap.clear(); modHolderMap.clear();
+ entityLeashMap.clear(); tagLeashMap.clear(); modLeashMap.clear();
+ teleportWhitelistCache = Collections.emptyList();
}
public static void loading(LeashConfigManager manager) {
@@ -459,7 +238,15 @@ public class LeashConfigManager {
}
public static void unloading(LeashConfigManager manager) {
- if(manager != null)
- manager.clear();
+ if(manager != null) manager.clear();
}
-}
+
+ public String getStats() {
+ return String.format(
+ "Holder: Entities: %d, Tags: %d, Mods: %d\nLeash: Entities: %d, Tags: %d, Mods: %d, TeleportWhitelist: %d",
+ entityHolderMap.size(), tagHolderMap.size(), modHolderMap.size(),
+ entityLeashMap.size(), tagLeashMap.size(), modLeashMap.size(),
+ teleportWhitelistCache.size()
+ );
+ }
+}
\ No newline at end of file
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 3464469..3ce2797 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
@@ -27,8 +27,8 @@ import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.entity.animal.horse.Llama;
import net.minecraft.world.entity.player.Player;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
import net.minecraft.world.entity.vehicle.Boat;
-import net.minecraft.world.entity.vehicle.Minecart;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.pathfinder.Path;
import net.minecraft.world.phys.AABB;
@@ -466,7 +466,7 @@ public class LeashDataImpl implements ILeashData {
Vec3 combinedDirection = Vec3.ZERO;
int validLeashes = 0;
- // 计算所有拴绳的合力和平均方向
+ // 1. 计算所有拴绳的合力和平均方向
for (Map.Entry entry : leashHolders.entrySet()) {
Vec3 force = calculateLeashForceForUUID(entry);
if (force != null) {
@@ -486,31 +486,40 @@ public class LeashDataImpl implements ILeashData {
}
boolean hasForce = !combinedForce.equals(Vec3.ZERO);
- Entity finalApplyEntity = RindingLeash.getFinalEntityForLeashIfForce(entity, hasForce);
+ Entity targetEntity = RindingLeash.getFinalEntityForLeashIfForce(entity, hasForce);
- if (hasForce) {
- // 处理玩家和其他实体
- if (finalApplyEntity instanceof ServerPlayer player && CurtainCompat.isNotFakePlayer(player)) {
- RindingLeash.applyForceToPlayer(player, combinedForce);
+ if (targetEntity != null && hasForce) {
+ // 玩家与普通实体统一力应用
+ if (targetEntity instanceof ServerPlayer player && CurtainCompat.isNotFakePlayer(player)) {
+ RindingLeash.applyForceToPlayer(
+ player,
+ combinedForce,
+ CommonEventHandler.leashConfigManager.getPlayerSpringFactor(),
+ 0.0, // 阻力取消
+ CommonEventHandler.leashConfigManager.getMaxForce()
+ );
} else {
- finalApplyEntity.setDeltaMovement(finalApplyEntity.getDeltaMovement().add(combinedForce));
- finalApplyEntity.hurtMarked = true;
-
- // 对生物使用合力方向进行移动(只有在能够移动时才执行)
- if (finalApplyEntity instanceof Mob mob && validLeashes > 0 && canMobMove(mob)) {
- moveMobTowardsCombinedDirection(mob, combinedDirection, validLeashes, combinedForce.length());
- } else if (finalApplyEntity instanceof Mob mob) {
- // 无法移动时停止导航
- mob.getNavigation().stop();
- }
+ applyForceToNonPlayerEntity(targetEntity, combinedForce, validLeashes, combinedDirection);
}
+ }
- RindingLeash.protectAnimalMovement(finalApplyEntity, true);
- } else {
- RindingLeash.protectAnimalMovement(finalApplyEntity, false);
+ // 保护动物移动
+ RindingLeash.protectAnimalMovement(targetEntity, hasForce);
+ }
+ /**
+ * 给非玩家实体施加拴绳力(取消阻力)
+ */
+ private void applyForceToNonPlayerEntity(Entity entity, Vec3 combinedForce,
+ int validLeashes, Vec3 combinedDirection) {
+ // 直接施加合力,不再加阻力
+ entity.setDeltaMovement(entity.getDeltaMovement().add(combinedForce));
+ entity.hurtMarked = true;
- // 没有力时也停止导航
- if (finalApplyEntity instanceof Mob mob) {
+ // 如果是生物,处理导航
+ if (entity instanceof Mob mob) {
+ if (validLeashes > 0 && canMobMove(mob)) {
+ moveMobTowardsCombinedDirection(mob, combinedDirection, validLeashes, combinedForce.length());
+ } else {
mob.getNavigation().stop();
}
}
@@ -534,10 +543,7 @@ public class LeashDataImpl implements ILeashData {
* 让生物朝着合力方向移动
*/
private void moveMobTowardsCombinedDirection(Mob mob, Vec3 combinedDirection, int leashCount, double forceMagnitude) {
- if (combinedDirection.equals(Vec3.ZERO)) return;
-
- // 再次检查是否能够移动
- if (!canMobMove(mob)) {
+ if (combinedDirection.equals(Vec3.ZERO) || !canMobMove(mob)) {
mob.getNavigation().stop();
return;
}
@@ -569,11 +575,7 @@ public class LeashDataImpl implements ILeashData {
*/
private boolean isPositionReachable(Mob mob, Vec3 targetPos) {
// 简单的距离检查
- double distance = mob.position().distanceTo(targetPos);
- if (distance > 20.0) { // 距离太远
- return false;
- }
-
+ if (mob.position().distanceTo(targetPos) > 20.0) return false;// 距离太远
// 检查是否有导航路径
Path path = mob.getNavigation().createPath(targetPos.x, targetPos.y, targetPos.z, 0);
return path != null && !path.isDone();
@@ -626,7 +628,7 @@ public class LeashDataImpl implements ILeashData {
LeashInfo info = entry.getValue();
Vec3 entityPos = entity.position();
double distance = holderPos.distanceTo(entityPos);
- double extremeSnapDist = info.maxDistance() * CommonEventHandler.leashConfigManager.getExtremeSnapFactor();
+ double extremeSnapDist = CommonEventHandler.leashConfigManager.getBreakDistance();
// 1. 检查是否超出断裂距离
if (distance > extremeSnapDist) {
@@ -841,7 +843,7 @@ public class LeashDataImpl implements ILeashData {
//只能系在这些实体上,在这里,其它情况一律忽略
//TODO: 标签支持控制
public static boolean isLeashable(Entity entity) {
- return entity instanceof LivingEntity || entity instanceof Boat || entity instanceof Minecart;
+ return entity instanceof LivingEntity || entity instanceof Boat || entity instanceof AbstractMinecart;
}
diff --git a/src/main/java/top/r3944realms/superleadrope/util/riding/RindingLeash.java b/src/main/java/top/r3944realms/superleadrope/util/riding/RindingLeash.java
index 6746785..8750ed3 100644
--- a/src/main/java/top/r3944realms/superleadrope/util/riding/RindingLeash.java
+++ b/src/main/java/top/r3944realms/superleadrope/util/riding/RindingLeash.java
@@ -17,6 +17,7 @@ package top.r3944realms.superleadrope.util.riding;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.Mob;
import net.minecraft.world.entity.ai.goal.Goal;
import net.minecraft.world.entity.animal.Animal;
import net.minecraft.world.phys.Vec3;
@@ -90,13 +91,31 @@ public class RindingLeash {
/**
* 给玩家应用拴绳力前的发包处理
*/
- public static void applyForceToPlayer(ServerPlayer player, Vec3 force) {
+ public static void applyForceToPlayer(ServerPlayer player, Vec3 leashVec, double k, double dampingFactor, double maxForce) {
+ Vec3 velocity = player.getDeltaMovement();
+
+ // 弹簧力
+ Vec3 springForce = leashVec.scale(k);
+
+ // 阻尼力
+ Vec3 dampingForce = velocity.scale(-dampingFactor);
+
+ // 合力
+ Vec3 finalForce = springForce.add(dampingForce);
+
+ // 限幅
+ if (finalForce.length() > maxForce) {
+ finalForce = finalForce.normalize().scale(maxForce);
+ }
+
+ // 发包应用给玩家
NetworkHandler.sendToPlayer(
new UpdatePlayerMovementPacket(
UpdatePlayerMovementPacket.Operation.ADD,
- force
+ finalForce
), player
);
}
+
}