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