feat:调整配置 & fix: 修正渲染问题

This commit is contained in:
叁玖领域 2025-09-20 22:56:30 +08:00
parent c878df45e9
commit 0ecebeba88
8 changed files with 367 additions and 513 deletions

View File

@ -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')

View File

@ -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)) {

View File

@ -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);

View File

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

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<List<? extends String>> teleportWhitelist;
// Command
public final ForgeConfigSpec.BooleanValue enableSLPModCommandPrefix;
public final ForgeConfigSpec.ConfigValue<String> SLPModCommandPrefix;
public final ForgeConfigSpec.BooleanValue EnableSLPModCommandPrefix;
// Entity
public final ForgeConfigSpec.ConfigValue<List<? extends String>> 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<List<? extends Double>> axisSpecificElasticity;
public final ForgeConfigSpec.IntValue maxLeashesPerEntity;
public final ForgeConfigSpec.ConfigValue<List<? extends String>> 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<List<? extends String>> defaultApplyEntityLocationOffset;
public final ForgeConfigSpec.ConfigValue<List<? extends String>> 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) {

View File

@ -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<String, double[]> entityHolderOffsetMap = new ConcurrentHashMap<>(), entityLeashOffsetMap = new ConcurrentHashMap<>();
private final Map<String, double[]> tagHolderOffsetMap = new ConcurrentHashMap<>(), tagLeashOffsetMap = new ConcurrentHashMap<>();
private final Map<String, double[]> modHolderOffsetMap = new ConcurrentHashMap<>(), modLeashOffsetMap = new ConcurrentHashMap<>();
// 缓存常用配置值以提高性能
private volatile List<String> teleportWhitelistCache;
private volatile String commandPrefixCache;
private volatile boolean enableCommandPrefixCache;
private volatile double maxLeashLengthCache;
private volatile double elasticDistanceCache;
private volatile double extremeSnapFactorCache;
private volatile double springDampeningCache;
private volatile List<Double> axisSpecificElasticityCache;
private volatile int maxLeashesPerEntityCache;
// ========== 偏移映射 ==========
private final Map<String, double[]> entityHolderMap = new ConcurrentHashMap<>();
private final Map<String, double[]> tagHolderMap = new ConcurrentHashMap<>();
private final Map<String, double[]> modHolderMap = new ConcurrentHashMap<>();
private final Map<String, double[]> entityLeashMap = new ConcurrentHashMap<>();
private final Map<String, double[]> tagLeashMap = new ConcurrentHashMap<>();
private final Map<String, double[]> modLeashMap = new ConcurrentHashMap<>();
// ========== 缓存配置 ==========
private volatile List<String> 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<Double> axisElasticity = List.of(0.8, 0.2, 0.8);
private volatile int maxLeashesPerEntity = 6;
public LeashConfigManager() {
this.reloadAll();
}
/**
* 解析偏移配置线程安全
*/
public void parseOffsetConfig() {
// --- Holder ---
Map<String, double[]> holderEntityMap = new HashMap<>();
Map<String, double[]> holderTagMap = new HashMap<>();
Map<String, double[]> 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<String, double[]> leashEntityMap = new HashMap<>();
Map<String, double[]> leashTagMap = new HashMap<>();
Map<String, double[]> 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<String> 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<String> 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<String> 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<String> 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<String> getTeleportWhitelist() {
return Collections.unmodifiableList(teleportWhitelistCache);
}
@SuppressWarnings("deprecation")
public boolean isEntityTeleportAllowed(EntityType<?> entityType) {
String entityId = entityType.builtInRegistryHolder().key().location().toString();
String modid = entityId.split(":")[0];
for (String entry : teleportWhitelistCache) {
if (entry.startsWith("#")) {
String body = entry.substring(1);
// Case 1: #modid allow all entities from this mod
if (!body.contains(":")) {
if (modid.equals(body)) {
return true;
}
}
// Case 2: #modid:tag_name allow all entities under this tag
else {
ResourceLocation tagId = new ResourceLocation(body);
TagKey<EntityType<?>> tag = TagKey.create(Registries.ENTITY_TYPE, tagId);
if (entityType.builtInRegistryHolder().is(tag)) {
return true;
}
}
} else {
// Case 3: modid:entity_name allow a specific entity
if (entry.equals(entityId)) {
return true;
}
}
}
return false;
}
public boolean isEntityTeleportAllowed(String entityId) {
// 对于字符串ID我们无法检查标签只能检查模组和特定实体
String modid = entityId.contains(":") ? entityId.split(":")[0] : "minecraft";
for (String entry : teleportWhitelistCache) {
if (entry.startsWith("#")) {
String body = entry.substring(1);
// Case 1: #modid allow all entities from this mod
if (!body.contains(":")) {
if (modid.equals(body)) {
return true;
}
}
// Case 2: #modid:tag_name 字符串ID无法检查标签跳过
// 如果需要支持标签检查需要传入EntityType而不是String
} else {
// Case 3: modid:entity_name allow a specific entity
if (entry.equals(entityId)) {
return true;
}
}
}
return false;
}
// 辅助方法检查实体ID是否匹配模式用于旧的匹配逻辑
private boolean matchesTeleportPattern(String pattern, String entityId) {
if (pattern.startsWith("#")) {
String body = pattern.substring(1);
if (body.contains(":")) {
// 标签格式: #modid:tag_name - 字符串ID无法准确匹配标签
// 返回模组匹配作为近似
String patternModId = body.split(":")[0];
String entityModId = entityId.split(":")[0];
return entityModId.equals(patternModId);
} else {
// 模组格式: #modid
String entityModId = entityId.split(":")[0];
return entityModId.equals(body);
}
} else {
// 实体格式: modid:entity_id
return entityId.equals(pattern);
}
}
// 添加一个重载方法方便使用Entity对象
public boolean isEntityTeleportAllowed(Entity entity) {
return isEntityTeleportAllowed(entity.getType());
}
// ========== 命令配置相关方法 ==========
public String getCommandPrefix() {
return commandPrefixCache;
}
public boolean isCommandPrefixEnabled() {
return enableCommandPrefixCache;
}
public String getFullCommand(String subCommand) {
return isCommandPrefixEnabled() ?
getCommandPrefix() + " " + subCommand :
subCommand;
}
// ========== 拴绳物理配置相关方法 ==========
public double getMaxLeashLength() {
return maxLeashLengthCache;
}
public double getElasticDistance() {
return elasticDistanceCache;
}
public double getExtremeSnapFactor() {
return extremeSnapFactorCache;
}
public double getBreakDistance() {
return getMaxLeashLength() * getExtremeSnapFactor();
}
public double getSpringDampening() {
return springDampeningCache;
}
public List<Double> getAxisSpecificElasticity() {
return Collections.unmodifiableList(axisSpecificElasticityCache);
}
public double getXElasticity() {
return !axisSpecificElasticityCache.isEmpty() ? axisSpecificElasticityCache.get(0) : 0.8;
}
public double getYElasticity() {
return axisSpecificElasticityCache.size() > 1 ? axisSpecificElasticityCache.get(1) : 0.2;
}
public double getZElasticity() {
return axisSpecificElasticityCache.size() > 2 ? axisSpecificElasticityCache.get(2) : 0.8;
}
// ========== 实体限制配置相关方法 ==========
public int getMaxLeashesPerEntity() {
return maxLeashesPerEntityCache;
}
public boolean canEntityAcceptMoreLeashes(Entity entity, int currentLeashCount) {
return currentLeashCount < getMaxLeashesPerEntity();
}
// ========== 管理方法 ==========
public void reload() {
reloadAll();
}
public void clear() {
entityHolderOffsetMap.clear();entityLeashOffsetMap.clear();
tagHolderOffsetMap.clear();tagLeashOffsetMap.clear();
modHolderOffsetMap.clear();modLeashOffsetMap.clear();
teleportWhitelistCache = Collections.emptyList();
// ================== 偏移解析 ==================
private Map<String, Map<String, double[]>> parseOffsetList(List<? extends String> offsetConfigs) {
Map<String, double[]> entityMap = new HashMap<>();
Map<String, double[]> tagMap = new HashMap<>();
Map<String, double[]> 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<String, Map<String, double[]>> 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<String, Map<String, double[]>> 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<String> tags,
Map<String,double[]> entityMap,
Map<String,double[]> tagMap,
Map<String,double[]> 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<String> 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<String> 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<String> 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<EntityType<?>> 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<Double> 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()
);
}
}

View File

@ -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<UUID, LeashInfo> 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;
}

View File

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