fix: 修正部分渲染问题

This commit is contained in:
叁玖领域 2025-09-20 18:47:15 +08:00
parent 32ac85d04e
commit c878df45e9
21 changed files with 197 additions and 151 deletions

View File

@ -148,6 +148,7 @@ configurations {
dependencies {
annotationProcessor ('org.spongepowered:mixin:0.8.5:processor')
modRuntimeOnly("curse.maven:debug-utils-forge-783008:5337491")
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}")
modRuntimeOnly("mezz.jei:jei-${minecraft_version}-forge:${jei_version}")

View File

@ -55,12 +55,12 @@ mod_name=Super Lead Rope
# The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default.
mod_license=GPLv3
# The mod version. See https://semver.org/
mod_version=0.0.0.1
mod_version=0.0.0.2
# The group ID for the mod. It is only important when publishing as an artifact to a Maven repository.
# This should match the base package used for the mod sources.
# See https://maven.apache.org/guides/mini/guide-naming-conventions.html
mod_group_id=top.r3944realms.superleadrope
# The authors of the mod. This is a simple text string that is used for display purposes in the mod list.
mod_authors=R3944realms
mod_authors=R3944Realms
# The description of the mod. This is a simple multiline text string that is used for display purposes in the mod list.
mod_description= A Lead Rope which is better than Bugjump.

View File

@ -1,2 +1,2 @@
// 1.20.1 2025-09-11T20:29:48.0546389 Languages: zh_tw
852af450b5a31ef7436f17cb9e0d92d282e6831a assets/superleadrope/lang/zh_tw.json
// 1.20.1 2025-09-20T18:20:23.4290773 Languages: zh_tw
0237411659cf392d87e4f866cec642396bc91382 assets/superleadrope/lang/zh_tw.json

View File

@ -1,2 +1,2 @@
// 1.20.1 2025-09-11T20:29:48.04512 Languages: zh_cn
78b1f4779544658700e3bf6e8f5f53ef15db7531 assets/superleadrope/lang/zh_cn.json
// 1.20.1 2025-09-20T18:20:23.4270438 Languages: zh_cn
c09eff43c9dc28c3fe516d4637ec6302af4c6656 assets/superleadrope/lang/zh_cn.json

View File

@ -1,2 +1,2 @@
// 1.20.1 2025-09-11T20:29:48.0526255 Languages: lzh
f20f2b37e9d7bfc332b5c579573e6de9f4504996 assets/superleadrope/lang/lzh.json
// 1.20.1 2025-09-20T18:15:03.8019547 Languages: lzh
f2ebd8aedd40b511793d238ff726c86f95693613 assets/superleadrope/lang/lzh.json

View File

@ -1,2 +1,2 @@
// 1.20.1 2025-09-11T20:29:48.0501199 Languages: en_us
d0fa9a0b01c9a2057af32d116fc8fb4e63192fa8 assets/superleadrope/lang/en_us.json
// 1.20.1 2025-09-20T18:20:23.4290773 Languages: en_us
0b7f741121c31dd391168341de47844cd4a5a03c assets/superleadrope/lang/en_us.json

View File

@ -1,9 +1,15 @@
{
"command.leashdata.addApplyEntity.success": "§bAdded leash successfully. §a%s §7→ §e%s",
"command.leashdata.removeApplyEntity.success": "§bRemoved leash successfully. §a%s §7- §e%s",
"command.leashdata.setApplyEntity.success": "§bSet leash property successfully. §a%s §7: §e%s §7= §6%.1f",
"command.leashdata.transfer.success": "§bTransferred leash successfully. §a%s §7→ §e%s §7→ §6%s",
"death.attack.eternal_potato_not_complete": "§c%1$s was not the rightful owner, struck by lightning!",
"death.attack.eternal_potato_not_owner": "§c%1$s was not the rightful owner, struck by lightning!",
"entity.superleadrope.super_lead_knot": "Super Lead Knot",
"gamerule.slp.TeleportWithLeashedEntities": "Teleport leashed player with player holder",
"gamerule.slp.TeleportWithLeashedEntities.description": "Holder will teleport with their leashed players ",
"gamerule.SLP.CreateSuperLeashKnotEntityIfAbsent": "Create Leash Fence Knot Entity if absent",
"gamerule.SLP.CreateSuperLeashKnotEntityIfAbsent.description": "Create LeashKnot Entity if it's absent on fence or other supported positions",
"gamerule.SLP.TeleportWithLeashedEntities": "Teleport leashed player with holder",
"gamerule.SLP.TeleportWithLeashedEntities.description": "Holder will teleport with their leashed players ",
"item.eternal_potato.msg.bind_msg": "§6Bound to you as the server-wide shared person.",
"item.eternal_potato.msg.cannot_drop": "§cThe Eternal Potato cannot be dropped! +%d punishments.",
"item.eternal_potato.msg.obligation_countdown": "Punish Countdown: §a%d §fseconds remaining",
@ -25,6 +31,14 @@
"sound.superleadrope.subtitle.lead_break": "Lead Break",
"sound.superleadrope.subtitle.lead_tied": "Lead Tied",
"sound.superleadrope.subtitle.lead_untied": "Lead Untie",
"superleadrope.command.leash.message..get.block": "§7Block: §e%s",
"superleadrope.command.leash.message..get.elastic": "§7Elastic: §6%.1f",
"superleadrope.command.leash.message..get.keep": "§7Keep: §c%d§7/§c%d",
"superleadrope.command.leash.message..get.max": "§7Max: §a%.1f",
"superleadrope.command.leash.message..get.reserved": "§7Reserved: §d%s",
"superleadrope.command.leash.message..get.title": "=== Leash Data for %s ===",
"superleadrope.command.leash.message..get.total": "Total leashes: %d",
"superleadrope.command.leash.message..get.uuid": "§7UUID: §b%s",
"superleadrope.command.motion.message.adder.successful": "§bAdd Successfully.§a%s§7:§f[§eVec§7:§a(§f%.2f§7,§f%.2f§7,§f%.2f§7)§f]§r",
"superleadrope.command.motion.message.multiply.successful": "§bMultiply Successfully.§a%s§7:§f[§eVec§7:§a(§f%.2f§7,§f%.2f§7,§f%.2f§7)§f]§r",
"superleadrope.command.motion.message.setter.successful": "§bSet Successfully.§a%s§7:§f[§eVec§7:§a(§f%.2f§7,§f%.2f§7,§f%.2f§7)§f]§r"

View File

@ -1,9 +1,15 @@
{
"command.leashdata.addApplyEntity.success": "§b繫繩既添. §a%s §7→ §e%s",
"command.leashdata.removeApplyEntity.success": "§b繫繩既除. §a%s §7- §e%s",
"command.leashdata.setApplyEntity.success": "§b繫繩性既定. §a%s §7: §e%s §7= §6%.1f",
"command.leashdata.transfer.success": "§b繫繩既移. §a%s §7→ §e%s §7→ §6%s",
"death.attack.eternal_potato_not_complete": "§c%1$s 非汝所主,雷霆降身!",
"death.attack.eternal_potato_not_owner": "§c%1$s 非汝所主,雷霆降身!",
"entity.superleadrope.super_lead_knot": "神駒羈縻索結",
"gamerule.slp.TeleportWithLeashedEntities": "繫畜隨持者傳送",
"gamerule.slp.TeleportWithLeashedEntities.description": "傳送時繫畜隨持者同傳",
"gamerule.SLP.CreateSuperLeashKnotEntityIfAbsent": "若阙则创超级繫绳结",
"gamerule.SLP.CreateSuperLeashKnotEntityIfAbsent.description": "若栅等支处阙超级繫绳结,则创之",
"gamerule.SLP.TeleportWithLeashedEntities": "繫畜隨持者傳送",
"gamerule.SLP.TeleportWithLeashedEntities.description": "傳送時繫畜隨持者同傳",
"item.eternal_potato.msg.bind_msg": "§6已与汝绑定为全服共享之人。",
"item.eternal_potato.msg.cannot_drop": "§c永恒土豆不可丟棄懲罰數增加%d",
"item.eternal_potato.msg.obligation_countdown": "受罚倒数§a%d §f瞬",
@ -25,6 +31,14 @@
"sound.superleadrope.subtitle.lead_break": "索絕",
"sound.superleadrope.subtitle.lead_tied": "繫索",
"sound.superleadrope.subtitle.lead_untied": "解索",
"superleadrope.command.leash.message..get.block": "§7磚石: §e%s",
"superleadrope.command.leash.message..get.elastic": "§7彈距: §6%.1f",
"superleadrope.command.leash.message..get.keep": "§7持時: §c%d§7/§c%d",
"superleadrope.command.leash.message..get.max": "§7極距: §a%.1f",
"superleadrope.command.leash.message..get.reserved": "§7備註: §d%s",
"superleadrope.command.leash.message..get.title": "=== %s 之繫繩數據 ===",
"superleadrope.command.leash.message..get.total": "繫繩總數: %d",
"superleadrope.command.leash.message..get.uuid": "§7UUID: §b%s",
"superleadrope.command.motion.message.adder.successful": "§b增益既成.§a%s§7:§f[§e速勢§7:(§a%.2f§7,§a%.2f§7,§a%.2f§7)§f]§r",
"superleadrope.command.motion.message.multiply.successful": "§b倍乘既成.§a%s§7:§f[§e速勢§7:(§a%.2f§7,§a%.2f§7,§a%.2f§7)§f]§r",
"superleadrope.command.motion.message.setter.successful": "§b定值既成.§a%s§7:§f[§e速勢§7:(§a%.2f§7,§a%.2f§7,§a%.2f§7)§f]§r"

View File

@ -1,9 +1,15 @@
{
"command.leashdata.addApplyEntity.success": "§b添加拴绳成功. §a%s §7→ §e%s",
"command.leashdata.removeApplyEntity.success": "§b移除拴绳成功. §a%s §7- §e%s",
"command.leashdata.setApplyEntity.success": "§b设置拴绳属性成功. §a%s §7: §e%s §7= §6%.1f",
"command.leashdata.transfer.success": "§b转移拴绳成功. §a%s §7→ §e%s §7→ §6%s",
"death.attack.eternal_potato_not_complete": "§c%1$s 因使用非自己绑定物品,受到闪电惩罚!",
"death.attack.eternal_potato_not_owner": "§c%1$s 因使用非自己绑定物品,受到闪电惩罚!",
"entity.superleadrope.super_lead_knot": "超级拴绳结",
"gamerule.slp.TeleportWithLeashedEntities": "被拴实体随玩家持有者传送",
"gamerule.slp.TeleportWithLeashedEntities.description": "传送时将被拴实体与持有者一起传送",
"gamerule.SLP.CreateSuperLeashKnotEntityIfAbsent": "如果缺失则创建超级拴绳结",
"gamerule.SLP.CreateSuperLeashKnotEntityIfAbsent.description": "如果在栅栏等支持处缺失超级拴绳结,则创建它",
"gamerule.SLP.TeleportWithLeashedEntities": "被拴实体随持有者传送",
"gamerule.SLP.TeleportWithLeashedEntities.description": "传送时将被拴实体与持有者一起传送",
"item.eternal_potato.msg.bind_msg": "§6已与你绑定成为全服共有之人。",
"item.eternal_potato.msg.cannot_drop": "§c永恒土豆是不可丢弃的惩罚数加%d",
"item.eternal_potato.msg.obligation_countdown": "惩罚倒计时: §a%d §f秒",
@ -25,6 +31,14 @@
"sound.superleadrope.subtitle.lead_break": "拴绳断裂",
"sound.superleadrope.subtitle.lead_tied": "拴绳系上",
"sound.superleadrope.subtitle.lead_untied": "拴绳解开",
"superleadrope.command.leash.message..get.block": "§7方块: §e%s",
"superleadrope.command.leash.message..get.elastic": "§7弹性距离: §6%.1f",
"superleadrope.command.leash.message..get.keep": "§7保持: §c%d§7/§c%d",
"superleadrope.command.leash.message..get.max": "§7最大距离: §a%.1f",
"superleadrope.command.leash.message..get.reserved": "§7保留字段: §d%s",
"superleadrope.command.leash.message..get.title": "=== %s 的拴绳数据 ===",
"superleadrope.command.leash.message..get.total": "总拴绳数: %d",
"superleadrope.command.leash.message..get.uuid": "§7UUID: §b%s",
"superleadrope.command.motion.message.adder.successful": "§b添加成功.§a%s§7:§f[§e加速§7:(§a%.2f§7,§a%.2f§7,§a%.2f§7)§f]§r",
"superleadrope.command.motion.message.multiply.successful": "§b倍乘成功.§a%s§7:§f[§e加速§7:(§a%.2f§7,§a%.2f§7,§a%.2f§7)§f]§r",
"superleadrope.command.motion.message.setter.successful": "§b设置成功.§a%s§7:§f[§e加速§7:(§a%.2f§7,§a%.2f§7,§a%.2f§7)§f]§r"

View File

@ -1,9 +1,15 @@
{
"command.leashdata.addApplyEntity.success": "§b添加拴繩成功. §a%s §7→ §e%s",
"command.leashdata.removeApplyEntity.success": "§b移除拴繩成功. §a%s §7- §e%s",
"command.leashdata.setApplyEntity.success": "§b設置拴繩屬性成功. §a%s §7: §e%s §7= §6%.1f",
"command.leashdata.transfer.success": "§b轉移拴繩成功. §a%s §7→ §e%s §7→ §6%s",
"death.attack.eternal_potato_not_complete": "§c%1$s 因使用非自己綁定物品,受到閃電懲罰!",
"death.attack.eternal_potato_not_owner": "§c%1$s 因使用非自己綁定物品,受到閃電懲罰!",
"entity.superleadrope.super_lead_knot": "超級拴繩結",
"gamerule.slp.TeleportWithLeashedEntities": "被拴实体随玩家持有者傳送",
"gamerule.slp.TeleportWithLeashedEntities.description": "將被拴实体將隨持有者一起傳送",
"gamerule.SLP.CreateSuperLeashKnotEntityIfAbsent": "如果缺失則創建超級拴繩結",
"gamerule.SLP.CreateSuperLeashKnotEntityIfAbsent.description": "如果在柵欄等支持處缺失超級拴繩結,則創建它",
"gamerule.SLP.TeleportWithLeashedEntities": "被拴实体随持有者傳送",
"gamerule.SLP.TeleportWithLeashedEntities.description": "將被拴实体將隨持有者一起傳送",
"item.eternal_potato.msg.bind_msg": "§6已與你綁定成為全服共有之人。",
"item.eternal_potato.msg.cannot_drop": "§c永恆土豆不可丟棄懲罰數加%d",
"item.eternal_potato.msg.obligation_countdown": "懲罰倒計時: §a%d §f秒",
@ -25,6 +31,14 @@
"sound.superleadrope.subtitle.lead_break": "拴繩斷裂",
"sound.superleadrope.subtitle.lead_tied": "拴繩係上",
"sound.superleadrope.subtitle.lead_untied": "拴繩解開",
"superleadrope.command.leash.message..get.block": "§7方塊: §e%s",
"superleadrope.command.leash.message..get.elastic": "§7彈性距離: §6%.1f",
"superleadrope.command.leash.message..get.keep": "§7保持: §c%d§7/§c%d",
"superleadrope.command.leash.message..get.max": "§7最大距離: §a%.1f",
"superleadrope.command.leash.message..get.reserved": "§7保留字段: §d%s",
"superleadrope.command.leash.message..get.title": "=== %s 的拴繩數據 ===",
"superleadrope.command.leash.message..get.total": "總拴繩數: %d",
"superleadrope.command.leash.message..get.uuid": "§7UUID: §b%s",
"superleadrope.command.motion.message.adder.successful": "§b添加成功.§a%s§7:§f[§e加速§7:(§a%.2f§7,§a%.2f§7,§a%.2f§7)§f]§r",
"superleadrope.command.motion.message.multiply.successful": "§b倍乘成功.§a%s§7:§f[§e加速§7:(§a%.2f§7,§a%.2f§7,§a%.2f§7)§f]§r",
"superleadrope.command.motion.message.setter.successful": "§b設置成功.§a%s§7:§f[§e加速§7:(§a%.2f§7,§a%.2f§7,§a%.2f§7)§f]§r"

View File

@ -47,9 +47,9 @@ public class SuperLeashRenderer {
Matrix4f matrix = poseStack.last().pose();
int blockLightStart = getBlockLight(BlockPos.containing(startWorld));
int blockLightEnd = getBlockLight(BlockPos.containing(endWorld));
int skyLightStart = getSkyLight(BlockPos.containing(startWorld));
int skyLightEnd = getSkyLight(BlockPos.containing(endWorld));
int blockLightEnd = getBlockLight(state.isFirstPerson() ? state.belowBlockPos() : (BlockPos.containing(endWorld)));
int skyLightStart = 15; //getSkyLight(BlockPos.containing(startWorld));
int skyLightEnd = 15; //getSkyLight(BlockPos.containing(endWorld));
// 差向量 + 偏移
Offsets offsets = computeOffsets(startWorld, endWorld);

View File

@ -21,11 +21,9 @@ import net.minecraft.util.Mth;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.HumanoidArm;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec3;
import top.r3944realms.superleadrope.SuperLeadRope;
import org.jetbrains.annotations.NotNull;
import top.r3944realms.superleadrope.client.renderer.state.SuperLeashRenderState;
import top.r3944realms.superleadrope.content.capability.impi.LeashDataImpl;
import top.r3944realms.superleadrope.content.capability.inter.ILeashData;
import top.r3944realms.superleadrope.content.capability.inter.ILeashState;
import top.r3944realms.superleadrope.content.entity.SuperLeashKnotEntity;
@ -64,19 +62,19 @@ public class SuperLeashStateResolver {
ILeashState.LeashState leashState = leashStateOpt.get();
// 计算实体端偏移
Vec3 currentEntityOffset = getEntityLeashOffset(leashedEntity, leashState, partialTicks);
Vec3 currentEntityOffset = getEntityLeashOffset(leashedEntity, leashState);
// 计算持有者端偏移
Vec3 holderOffset = getHolderOffset(holder, leashState);
boolean isFirstPerson = Minecraft.getInstance().options.getCameraType() == CameraType.FIRST_PERSON;
// 获取插值后的位置
Vec3 currentHolderPos;
if (holder instanceof Player player) {
// 玩家手部偏移 + 旋转矩阵第一人称/第三人称都适用
if (Minecraft.getInstance().options.getCameraType() == CameraType.FIRST_PERSON) {
// 玩家手部偏移 + 旋转矩阵第一人称适用
if (isFirstPerson && player == Minecraft.getInstance().player) {
currentHolderPos = getFirstPersonLeashPos(player, partialTicks);
} else {
currentHolderPos = getEntityLeashHolderPos(player, holderOffset, partialTicks);
currentHolderPos = getEntityLeashHolderPos(player, (Minecraft.getInstance().player == holder) ? holderOffset : holderOffset.add(0,0,0.4), partialTicks);
}
} else if (holder instanceof SuperLeashKnotEntity) {
@ -85,8 +83,7 @@ public class SuperLeashStateResolver {
// 普通实体偏移 + 插值 + 旋转
currentHolderPos = getEntityLeashHolderPos(holder, holderOffset, partialTicks);
}
Vec3 currentEntityPos = getInterpolatedPosition(leashedEntity, partialTicks)
.add(currentEntityOffset);
Vec3 currentEntityPos = applyOffsetWithRotation(leashedEntity, currentEntityOffset, partialTicks);
// 上一帧缓存
FrameCache cache = frameCacheMap.get(leashedEntity.getUUID());
@ -123,42 +120,21 @@ public class SuperLeashStateResolver {
selectColor(tension, isCritical),
THICKNESS_BASE + tension * THICKNESS_TENSION,
swing.angle(), swing.speed(),
(float) leashInfo.maxDistance()
(float) leashInfo.maxDistance(),
isFirstPerson, holder.blockPosition()
));
}
/* ------------------------ 实体偏移计算 ------------------------ */
private static Vec3 getEntityLeashOffset(Entity entity, ILeashState.LeashState leashState, float partialTicks) {
// 获取实体自身的 LeashState 偏移
private static @NotNull Vec3 getEntityLeashOffset(Entity entity, ILeashState.@NotNull LeashState leashState) {
Optional<ILeashState> entityStateOpt = LeashStateAPI.getLeashState(entity);
Vec3 baseOffset = entityStateOpt
.map(eState -> eState.getLeashApplyEntityLocationOffset()
.orElse(eState.getDefaultLeashApplyEntityLocationOffset()))
.orElse(Vec3.ZERO);
// 加上单条绳子的额外偏移
baseOffset = baseOffset.add(leashState.applyEntityLocationOffset());
// 获取旋转角度
float pitch = Mth.lerp(partialTicks, entity.getXRot(), entity.xRotO) * ((float)Math.PI / 180F);
float yaw = Mth.lerp(partialTicks, entity.getYRot(), entity.yRotO) * ((float)Math.PI / 180F);
float roll = 0f;
// 玩家特殊处理飞行/旋转攻击可能需要 roll
if (entity instanceof Player player && (player.isFallFlying() || player.isAutoSpinAttack())) {
Vec3 view = player.getViewVector(partialTicks);
Vec3 motion = player.getDeltaMovement();
double motionLenSq = motion.horizontalDistanceSqr();
double viewLenSq = view.horizontalDistanceSqr();
if (motionLenSq > 0 && viewLenSq > 0) {
double dot = (motion.x * view.x + motion.z * view.z) / Math.sqrt(motionLenSq * viewLenSq);
double cross = motion.x * view.z - motion.z * view.x;
roll = (float)(Math.signum(cross) * Math.acos(dot));
}
}
// 将偏移从局部坐标转换到世界坐标
return baseOffset.xRot(-pitch).yRot(-yaw).zRot(-roll);
return baseOffset.add(leashState.applyEntityLocationOffset());
}
private static Vec3 getHolderOffset(Entity holder, ILeashState.LeashState leashState) {
if (LeashStateAPI.Query.hasLeashState(holder)) {
@ -172,94 +148,91 @@ public class SuperLeashStateResolver {
return Optional.ofNullable(leashState.holderLocationOffset())
.orElse(leashState.defaultHolderLocationOffset());
}
private static boolean holderHasLeashState(Entity holder) {
if (LeashStateAPI.Query.hasLeashState(holder)) {
Optional<ILeashState> holderStateOpt = LeashStateAPI.getLeashState(holder);
return holderStateOpt.isPresent();
/**
* 将局部偏移向量应用到实体旋转返回世界坐标位置
* @param entity 实体
* @param localOffset 局部偏移相对于实体局部坐标系
* @param partialTicks 插值参数
* @return 偏移旋转后的世界坐标位置
*/
public static @NotNull Vec3 applyOffsetWithRotation(Entity entity, Vec3 localOffset, float partialTicks) {
// 实体中心位置
Vec3 centerPos = getInterpolatedPosition(entity, partialTicks);
// 旋转角度
// float pitch = Mth.lerp(partialTicks, entity.getXRot(), entity.xRotO) * ((float)Math.PI / 180F);
float yaw = Mth.lerp(partialTicks, entity.getYRot(), entity.yRotO) * ((float)Math.PI / 180F);
float roll = 0f;
if (entity instanceof Player player && (player.isFallFlying() || player.isAutoSpinAttack())) {
roll = getRoll(player, partialTicks, roll);
}
return false;
// 应用旋转到局部偏移
Vec3 rotatedOffset = localOffset.yRot(-yaw).zRot(-roll);
// 返回世界坐标
return centerPos.add(rotatedOffset);
}
/**
* 获取玩家挂点位置支持旋转偏移
*/
private static Vec3 getFirstPersonLeashPos(Player player, float partialTicks) {
private static @NotNull Vec3 getFirstPersonLeashPos(@NotNull Player player, float partialTicks) {
// 插值旋转角度
float yaw = Mth.lerp(partialTicks, player.yRotO, player.getYRot()) * ((float)Math.PI / 180F);
float pitch = Mth.lerp(partialTicks, player.xRotO, player.getXRot()) * ((float)Math.PI / 180F);
// float pitch = Mth.lerp(partialTicks, player.xRotO, player.getXRot()) * ((float)Math.PI / 180F);
float roll = 0f;
// 计算 roll飞行或旋转攻击时
if (player.isFallFlying() || player.isAutoSpinAttack()) {
Vec3 view = player.getViewVector(partialTicks);
Vec3 motion = player.getDeltaMovement();
double motionLenSq = motion.horizontalDistanceSqr();
double viewLenSq = view.horizontalDistanceSqr();
if (motionLenSq > 0 && viewLenSq > 0) {
double dot = (motion.x*view.x + motion.z*view.z) / Math.sqrt(motionLenSq*viewLenSq);
double cross = motion.x*view.z - motion.z*view.x;
roll = (float)(Math.signum(cross) * Math.acos(dot));
}
roll = getRoll(player, partialTicks, roll);
}
// 手部偏移
double side = player.getMainArm() == HumanoidArm.RIGHT ? -1.0D : 1.0D;
Vec3 localPos = new Vec3(0.39D * side, -0.6D, 0.3D);
Vec3 localPos = new Vec3(0.39D * side, -0.6D, -0.5D);
// 局部坐标旋转到世界坐标
Vec3 rotatedPos = localPos.xRot(-pitch).yRot(-yaw).zRot(-roll);
Vec3 rotatedPos = localPos.yRot(-yaw).zRot(-roll);
// 返回世界坐标玩家眼睛位置 + 手部旋转偏移
return player.getEyePosition(partialTicks).add(rotatedPos);
}
private static float getRoll(@NotNull Player player, float partialTicks, float roll) {
Vec3 view = player.getViewVector(partialTicks);
Vec3 motion = player.getDeltaMovement();
double motionLenSq = motion.horizontalDistanceSqr();
double viewLenSq = view.horizontalDistanceSqr();
if (motionLenSq > 0 && viewLenSq > 0) {
double dot = (motion.x*view.x + motion.z*view.z) / Math.sqrt(motionLenSq*viewLenSq);
double cross = motion.x*view.z - motion.z*view.x;
roll = (float)(Math.signum(cross) * Math.acos(dot));
}
return roll;
}
/**
* 获取实体挂点位置支持旋转偏移
*/
public static Vec3 getEntityLeashHolderPos(Entity entity, Vec3 baseOffset, float partialTicks) {
// 从眼睛位置头部开始
Vec3 pos = entity.getEyePosition(partialTicks);
Vec3 pos = entity.getPosition(partialTicks);
double xOffset = baseOffset.x();
double yOffset = baseOffset.y();
double zOffset = baseOffset.z();
float pitch = Mth.lerp(partialTicks, entity.getXRot(), entity.xRotO) * ((float)Math.PI / 180F);
float yaw = Mth.lerp(partialTicks, entity.getYRot(), entity.yRotO) * ((float)Math.PI / 180F);
if (entity instanceof Player player) {
xOffset *= player.getMainArm() == HumanoidArm.RIGHT ? -1.0D : 1.0D;
if (!player.isFallFlying() && !player.isAutoSpinAttack()) {
if (player.isVisuallySwimming()) {
pos = pos.add(new Vec3(xOffset, 0.2D, -0.15D).xRot(-pitch).yRot(-yaw));
} else {
double yBase = player.getBoundingBox().getYsize() - 1.0D;
double yAdjust = player.isCrouching() ? -0.2D : 0.07D;
pos = pos.add(new Vec3(xOffset, yBase, yAdjust).yRot(-yaw));
}
} else {
Vec3 view = player.getViewVector(partialTicks);
Vec3 motion = player.getDeltaMovement();
float roll = computeRoll(view, motion);
pos = pos.add(new Vec3(xOffset, -0.11D, 0.85D).zRot(-roll).xRot(-pitch).yRot(-yaw));
}
} else {
double yBase = entity.getBbHeight() * 0.5 + yOffset;
pos = pos.add(new Vec3(xOffset, yBase, zOffset).yRot(-yaw).xRot(-pitch));
xOffset += 0.19D * (player.getMainArm() == HumanoidArm.RIGHT ? -1.0D : 1.0D);
}
// 直接在头部位置应用偏移不管手飞行游泳
pos = pos.add(new Vec3(xOffset, yOffset, zOffset).yRot(-yaw));
return pos;
}
private static float computeRoll(Vec3 view, Vec3 motion) {
double d1 = motion.horizontalDistanceSqr();
double d2 = view.horizontalDistanceSqr();
if (d1 > 0.0 && d2 > 0.0) {
double dot = (motion.x * view.x + motion.z * view.z) / Math.sqrt(d1 * d2);
double cross = motion.x * view.z - motion.z * view.x;
return (float)(Math.signum(cross) * Math.acos(dot));
}
return 0.0F;
}
private static Vec3 getInterpolatedPosition(Entity entity, float partialTicks) {
return entity.getEyePosition(partialTicks);
}
@ -276,13 +249,13 @@ public class SuperLeashStateResolver {
Vec3 cross = lastDir.cross(currentDir);
float angleChange = (float) Math.acos(Math.min(1.0, lastDir.dot(currentDir)));
angleChange *= Math.signum(cross.y);
angleChange *= (float) Math.signum(cross.y);
float newSpeed = lastSpeed * 0.9f + angleChange * 0.5f * tension;
float newAngle = lastAngle + newSpeed;
if (tension > 0.3f) {
newSpeed += (Math.random() - 0.5) * 0.05 * tension;
newSpeed += (float) ((Math.random() - 0.5) * 0.05 * tension);
}
return new SwingDynamics(newAngle, newSpeed);
@ -301,27 +274,4 @@ public class SuperLeashStateResolver {
return tension > 0.7f ? SuperLeashRenderState.COLOR_TENSION : SuperLeashRenderState.COLOR_NORMAL;
}
/* ------------------------ 批量解析 ------------------------ */
public static List<SuperLeashRenderState> resolveAll(Entity leashedEntity,
LeashDataImpl leashData, float partialTicks) {
List<SuperLeashRenderState> states = new ArrayList<>();
Level level = leashedEntity.level();
for (ILeashData.LeashInfo leashInfo : leashData.getAllLeashes()) {
Entity holder = null;
if (leashInfo.blockPosOpt().isEmpty() && leashInfo.holderIdOpt().isPresent()){
holder = level.getEntity(leashInfo.holderIdOpt().get());
} else if (leashInfo.blockPosOpt().isPresent()) {
holder = SuperLeashKnotEntity.getOrCreateKnot(level, leashInfo.blockPosOpt().get());
}
if (holder != null) {
resolve(holder, leashedEntity, leashInfo, partialTicks)
.ifPresent(states::add);
} else SuperLeadRope.logger.error("Holder not found for leash of " + leashedEntity);
}
return states;
}
}

View File

@ -15,6 +15,7 @@
package top.r3944realms.superleadrope.client.renderer.state;
import net.minecraft.core.BlockPos;
import net.minecraft.world.phys.Vec3;
public record SuperLeashRenderState(
@ -30,7 +31,9 @@ public record SuperLeashRenderState(
float thickness, // 线宽(根据张力变化)
float swingAngle, // 当前摆动角度(弧度)
float swingSpeed, // 摆动速度(弧度/tick)
float maxDistance // 最大距离
float maxDistance, // 最大距离
boolean isFirstPerson, // 是否是第一人称
BlockPos belowBlockPos // 持有者位置
) {
// 预定义颜色常量
public static final int COLOR_NORMAL = 0xFF6B4E2E; // 深棕色木绳色温暖自然

View File

@ -0,0 +1,30 @@
/*
* 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;
import dev.dubhe.curtain.features.player.patches.EntityPlayerMPFake;
import net.minecraft.world.entity.player.Player;
import net.minecraftforge.fml.ModList;
public class CurtainCompat {
public final static boolean isModLoaded = ModList.get().isLoaded("curtain");
public static boolean isNotFakePlayer(Player player) {
if (isModLoaded) {
return !(player instanceof EntityPlayerMPFake);
}
return true;
}
}

View File

@ -113,7 +113,8 @@ public class LeashCommonConfig {
.defineListAllowEmpty(
List.of("defaultApplyEntityLocationOffset"),
List.of(
"vec3(0,-0.5,0) : [*]", "vec3(0,-0.42,0) : [minecraft:player]"
"vec3(0,-0.2,0) : [*]",
"vec3(0,-0.22,-0.2) : [minecraft:player]"
),
o -> o instanceof String s && isValidOffsetRefFormat(s)
);
@ -130,13 +131,14 @@ public class LeashCommonConfig {
" - #modid : all entities from a mod (e.g. #minecraft)",
" - * : all entities",
"Multiple entries can be separated by commas",
"Example: vec3(0,1.5,0) : [minecraft:player]",
"Example: vec3(0,-0.5,0) : [minecraft:player]",
"Priority order: specific entity > tag > mod > *"
)
.defineListAllowEmpty(
List.of("defaultHolderLocationOffset"),
List.of(
"vec3(0,-0.5,0) : [*]"
"vec3(0,-0.2,0) : [*]",
"vec3(0,-0.6,0) : [minecraft:player]"
),
o -> o instanceof String s && isValidOffsetRefFormat(s)
);

View File

@ -38,6 +38,7 @@ import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import top.r3944realms.superleadrope.CommonEventHandler;
import top.r3944realms.superleadrope.SuperLeadRope;
import top.r3944realms.superleadrope.compat.CurtainCompat;
import top.r3944realms.superleadrope.content.capability.inter.ILeashData;
import top.r3944realms.superleadrope.content.entity.SuperLeashKnotEntity;
import top.r3944realms.superleadrope.core.register.SLPSoundEvents;
@ -489,7 +490,7 @@ public class LeashDataImpl implements ILeashData {
if (hasForce) {
// 处理玩家和其他实体
if (finalApplyEntity instanceof ServerPlayer player) {
if (finalApplyEntity instanceof ServerPlayer player && CurtainCompat.isNotFakePlayer(player)) {
RindingLeash.applyForceToPlayer(player, combinedForce);
} else {
finalApplyEntity.setDeltaMovement(finalApplyEntity.getDeltaMovement().add(combinedForce));

View File

@ -337,7 +337,7 @@ public class LeashStateImpl implements ILeashState {
@Override
public void copy(ILeashState other, Entity newEntity) {
this.entity = newEntity;
this.defaultApplyEntityLocationOffset = other.getDefaultLeashApplyEntityLocationOffset();
this.defaultApplyEntityLocationOffset = CommonEventHandler.leashConfigManager.getDefaultEntityOffset(newEntity);
this.staticApplyEntityLocationOffset = other.getLeashApplyEntityLocationOffset().orElse(null);
}

View File

@ -282,7 +282,7 @@ public class LeashDataCommand {
}
}
}
public static final String SET_MAX_DISTANCE = SLP_LEASH_MESSAGE_ + "setApplyEntity.max_distance";
public static final String SET_MAX_DISTANCE = SLP_LEASH_MESSAGE_ + "set_apply_entity.max_distance";
private static int setMaxDistance(CommandContext<CommandSourceStack> context) throws CommandSyntaxException {
return setMaxDistance(context, CommonEventHandler.leashConfigManager.getMaxLeashLength(), "");
}
@ -297,11 +297,11 @@ public class LeashDataCommand {
}
return -1;
}
public static final String REMOVE_ALL_BLOCK_LEASHES = SLP_LEASH_MESSAGE_ + "removeApplyEntity.all_block_leashes";
public static final String REMOVE_ALL_BLOCK_LEASHES = SLP_LEASH_MESSAGE_ + "remove_apply_entity.all_block_leashes";
private static int removeAllBlockLeashes(CommandContext<CommandSourceStack> context) throws CommandSyntaxException {
return -1;
}
public static final String REMOVE_ALL_HOLDER_LEASHES = SLP_LEASH_MESSAGE_ + "removeApplyEntity.all_holder_leashes";
public static final String REMOVE_ALL_HOLDER_LEASHES = SLP_LEASH_MESSAGE_ + "remove_apply_entity.all_holder_leashes";
private static int removeAllHolderLeashes(CommandContext<CommandSourceStack> context) throws CommandSyntaxException {
return -1;
}
@ -312,7 +312,7 @@ public class LeashDataCommand {
private static int transferFromBlock(CommandContext<CommandSourceStack> context, String reserved) throws CommandSyntaxException {
return -1;
}
public static final String SET_ELASTIC_DISTANCE = SLP_LEASH_MESSAGE_ + "setApplyEntity.elastic_distance";
public static final String SET_ELASTIC_DISTANCE = SLP_LEASH_MESSAGE_ + "set_apply_entity.elastic_distance";
private static int setElasticDistance(CommandContext<CommandSourceStack> context) throws CommandSyntaxException {
return setElasticDistance(context, 0 ,"");
}
@ -322,7 +322,7 @@ public class LeashDataCommand {
private static int setElasticDistance(CommandContext<CommandSourceStack> context, int keepTicks, String reserved) throws CommandSyntaxException {
return -1;
}
public static final String SET_BLOCK_MAX_DISTANCE = SLP_LEASH_MESSAGE_ + "setApplyEntity.block_max_distance";
public static final String SET_BLOCK_MAX_DISTANCE = SLP_LEASH_MESSAGE_ + "set_apply_entity.block_max_distance";
private static int setBlockMaxDistance(CommandContext<CommandSourceStack> context) throws CommandSyntaxException {
return setBlockMaxDistance(context, 0 ,"");
}
@ -332,7 +332,7 @@ public class LeashDataCommand {
private static int setBlockMaxDistance(CommandContext<CommandSourceStack> context, int keepTicks, String reserved) throws CommandSyntaxException {
return -1;
}
public static final String SET_BLOCK_ELASTIC_DISTANCE = SLP_LEASH_MESSAGE_ + "setApplyEntity.block_elastic_distance";
public static final String SET_BLOCK_ELASTIC_DISTANCE = SLP_LEASH_MESSAGE_ + "set_apply_entity.block_elastic_distance";
private static int setBlockElasticDistance(CommandContext<CommandSourceStack> context) throws CommandSyntaxException {
return setBlockElasticDistance(context, 0 ,"");
}
@ -351,6 +351,7 @@ public class LeashDataCommand {
for (Entity target : targets) {
Collection<ILeashData.LeashInfo> leashes = LeashDataAPI.QueryOperations.getAllLeashes(target);
// +++
source.sendSuccess(() -> Component.literal("=== Leash Data for " + target.getName().getString() + " ==="), false);
source.sendSuccess(() -> Component.literal("Total leashes: " + leashes.size()), false);

View File

@ -24,7 +24,7 @@ public class SLPGamerules {
public static final SLPGameruleRegistry GAMERULE_REGISTRY = SLPGameruleRegistry.INSTANCE;
public static final HashMap<String, Boolean> gamerulesBooleanValuesClient = new HashMap<>();
public static final HashMap<String, Integer> gameruleIntegerValuesClient = new HashMap<>();
public static final String RULE_KEY_PERFiX_ = "gamerule." + GAMERULE_PREFIX.toLowerCase();
public static final String RULE_KEY_PERFiX_ = "gamerule." + GAMERULE_PREFIX;
public static String getDescriptionKey(Class<?> gameRuleClass) {
return RULE_KEY_PERFiX_ + gameRuleClass.getSimpleName() + ".description";
}

View File

@ -19,6 +19,7 @@ import net.minecraft.server.MinecraftServer;
import net.minecraft.world.level.GameRules;
import net.minecraft.world.level.Level;
import top.r3944realms.superleadrope.content.gamerule.SLPGamerules;
import top.r3944realms.superleadrope.content.gamerule.server.CreateSuperLeashKnotEntityIfAbsent;
import top.r3944realms.superleadrope.content.gamerule.server.TeleportWithLeashedEntities;
import java.util.HashMap;
@ -63,7 +64,7 @@ public enum SLPGameruleRegistry {
gameruleDataTypes.put(gameruleName, RuleDataType.BOOLEAN);
}
public void registerGamerule(String gameruleName, GameRules.Category category, int pDefault) {
registerGamerule(gameruleName, category, pDefault, (BiConsumer<MinecraftServer, GameRules.IntegerValue>) (s, i)->{});//最后一个仅占位无用
registerGamerule(gameruleName, category, pDefault, (s, i)->{});//最后一个仅占位无用
}
public void registerGamerule(String gameruleName, GameRules.Category category, int pDefault, BiConsumer<MinecraftServer, GameRules.IntegerValue> pChangeListener) {
gamerules.put(gameruleName, GameRules.register(gameruleName, category, GameRules.IntegerValue.create(pDefault, pChangeListener)));
@ -71,6 +72,7 @@ public enum SLPGameruleRegistry {
}
public static void register() {
TeleportWithLeashedEntities.register();
CreateSuperLeashKnotEntityIfAbsent.register();
}
}

View File

@ -206,9 +206,9 @@ public enum SLPLangKeyValue {
),
TELEPORT_WITH_LEASHED_ENTITIES_NAME(
TeleportWithLeashedEntities.NAME_KEY, ModPartEnum.GAME_RULE,
"Teleport leashed player with player holder",
"被拴实体随玩家持有者传送",
"被拴实体随玩家持有者傳送",
"Teleport leashed player with holder",
"被拴实体随持有者传送",
"被拴实体随持有者傳送",
"繫畜隨持者傳送"
),
CREATE_SUPER_LEASH_KNOT_ENTITY_IF_ABSENT_NAME(