feature: 1.添加了拴绳相关属性的配置 2.创建ILeashState实现自定义拴绳渲染位置(待关联)

refactor: 1.调整和优化两Cap的Nbt序列化实现
todo:1.渲染器相关代码并未清除(暂时) 2.永恒土豆待分离逻辑
This commit is contained in:
叁玖领域 2025-09-14 00:50:35 +08:00
parent 0b5bde6047
commit 1a56faad9f
40 changed files with 1834 additions and 556 deletions

View File

@ -1,2 +1,2 @@
// 1.20.1 2025-09-08T23:59:30.214676 Languages: zh_tw
01bf653cea7c3be88445a248753b4cc75e5ac45e assets/superleadrope/lang/zh_tw.json
// 1.20.1 2025-09-11T20:29:48.0546389 Languages: zh_tw
852af450b5a31ef7436f17cb9e0d92d282e6831a assets/superleadrope/lang/zh_tw.json

View File

@ -1,2 +1,2 @@
// 1.20.1 2025-09-08T23:59:30.214676 Languages: zh_cn
6c4c5affb9dae3253eae9155ee4fb58deb5c4b92 assets/superleadrope/lang/zh_cn.json
// 1.20.1 2025-09-11T20:29:48.04512 Languages: zh_cn
78b1f4779544658700e3bf6e8f5f53ef15db7531 assets/superleadrope/lang/zh_cn.json

View File

@ -1,2 +1,2 @@
// 1.20.1 2025-09-08T23:59:30.214676 Languages: lzh
e7d897e78d2a100c249948780b7f1c07ad94e9b9 assets/superleadrope/lang/lzh.json
// 1.20.1 2025-09-11T20:29:48.0526255 Languages: lzh
f20f2b37e9d7bfc332b5c579573e6de9f4504996 assets/superleadrope/lang/lzh.json

View File

@ -1,2 +1,2 @@
// 1.20.1 2025-09-08T23:59:30.214676 Languages: en_us
9055e381f25bb65f46b6c4489800326812fd3659 assets/superleadrope/lang/en_us.json
// 1.20.1 2025-09-11T20:29:48.0501199 Languages: en_us
d0fa9a0b01c9a2057af32d116fc8fb4e63192fa8 assets/superleadrope/lang/en_us.json

View File

@ -2,6 +2,8 @@
"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 ",
"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",
@ -22,5 +24,8 @@
"item.superleadrope.super_lead_rope": "Super Lead Rope",
"sound.superleadrope.subtitle.lead_break": "Lead Break",
"sound.superleadrope.subtitle.lead_tied": "Lead Tied",
"sound.superleadrope.subtitle.lead_untied": "Lead Untie"
"sound.superleadrope.subtitle.lead_untied": "Lead Untie",
"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

@ -2,6 +2,8 @@
"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": "傳送時繫畜隨持者同傳",
"item.eternal_potato.msg.bind_msg": "§6已与汝绑定为全服共享之人。",
"item.eternal_potato.msg.cannot_drop": "§c永恒土豆不可丟棄懲罰數增加%d",
"item.eternal_potato.msg.obligation_countdown": "受罚倒数§a%d §f瞬",
@ -22,5 +24,8 @@
"item.superleadrope.super_lead_rope": "神駒羈縻索",
"sound.superleadrope.subtitle.lead_break": "索絕",
"sound.superleadrope.subtitle.lead_tied": "繫索",
"sound.superleadrope.subtitle.lead_untied": "解索"
"sound.superleadrope.subtitle.lead_untied": "解索",
"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

@ -2,6 +2,8 @@
"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": "传送时将被拴实体与持有者一起传送",
"item.eternal_potato.msg.bind_msg": "§6已与你绑定成为全服共有之人。",
"item.eternal_potato.msg.cannot_drop": "§c永恒土豆是不可丢弃的惩罚数加%d",
"item.eternal_potato.msg.obligation_countdown": "惩罚倒计时: §a%d §f秒",
@ -22,5 +24,8 @@
"item.superleadrope.super_lead_rope": "超级拴绳",
"sound.superleadrope.subtitle.lead_break": "拴绳断裂",
"sound.superleadrope.subtitle.lead_tied": "拴绳系上",
"sound.superleadrope.subtitle.lead_untied": "拴绳解开"
"sound.superleadrope.subtitle.lead_untied": "拴绳解开",
"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

@ -2,6 +2,8 @@
"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": "將被拴实体將隨持有者一起傳送",
"item.eternal_potato.msg.bind_msg": "§6已與你綁定成為全服共有之人。",
"item.eternal_potato.msg.cannot_drop": "§c永恆土豆不可丟棄懲罰數加%d",
"item.eternal_potato.msg.obligation_countdown": "懲罰倒計時: §a%d §f秒",
@ -22,5 +24,8 @@
"item.superleadrope.super_lead_rope": "超級拴繩",
"sound.superleadrope.subtitle.lead_break": "拴繩斷裂",
"sound.superleadrope.subtitle.lead_tied": "拴繩係上",
"sound.superleadrope.subtitle.lead_untied": "拴繩解開"
"sound.superleadrope.subtitle.lead_untied": "拴繩解開",
"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

@ -15,6 +15,8 @@
package top.r3944realms.superleadrope;
import com.mojang.brigadier.CommandDispatcher;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
@ -35,6 +37,7 @@ import net.minecraft.world.phys.Vec3;
import net.minecraftforge.common.capabilities.RegisterCapabilitiesEvent;
import net.minecraftforge.event.AttachCapabilitiesEvent;
import net.minecraftforge.event.BuildCreativeModeTabContentsEvent;
import net.minecraftforge.event.RegisterCommandsEvent;
import net.minecraftforge.event.TickEvent;
import net.minecraftforge.event.entity.EntityJoinLevelEvent;
import net.minecraftforge.event.entity.EntityLeaveLevelEvent;
@ -53,9 +56,11 @@ import top.r3944realms.superleadrope.content.capability.CapabilityHandler;
import top.r3944realms.superleadrope.content.capability.CapabilityRemainder;
import top.r3944realms.superleadrope.content.capability.impi.LeashDataImpl;
import top.r3944realms.superleadrope.content.capability.inter.IEternalPotato;
import top.r3944realms.superleadrope.content.capability.inter.ILeashDataCapability;
import top.r3944realms.superleadrope.content.capability.inter.ILeashData;
import top.r3944realms.superleadrope.content.capability.inter.ILeashState;
import top.r3944realms.superleadrope.content.command.MotionCommand;
import top.r3944realms.superleadrope.content.entity.SuperLeashKnotEntity;
import top.r3944realms.superleadrope.content.gamerule.server.TeleportWithLeashedPlayers;
import top.r3944realms.superleadrope.content.gamerule.server.TeleportWithLeashedEntities;
import top.r3944realms.superleadrope.content.item.EternalPotatoItem;
import top.r3944realms.superleadrope.content.item.SuperLeadRopeItem;
import top.r3944realms.superleadrope.core.leash.LeashInteractHandler;
@ -86,9 +91,10 @@ public class CommonEventHandler {
Entity entity = event.getEntity();
if (entity.level().isClientSide) return;
if (entity instanceof LivingEntity || entity instanceof Boat || entity instanceof Minecart) {
entity.getCapability(CapabilityHandler.LEASH_DATA_CAP).ifPresent(LeashSyncManager::track);
entity.getCapability(CapabilityHandler.LEASH_DATA_CAP).ifPresent(LeashSyncManager.Data::track);
entity.getCapability(CapabilityHandler.LEASH_STATE_CAP).ifPresent(LeashSyncManager.State::track);
if (entity instanceof ServerPlayer serverPlayer) {
LeashSyncManager.forEach(i -> {
LeashSyncManager.Data.forEach(i -> {
if (i.isLeashedBy(serverPlayer) && i.isInDelayedLeash(serverPlayer.getUUID())) {
i.removeDelayedLeash(serverPlayer.getUUID());//重新加入去除延迟
}
@ -103,13 +109,14 @@ public class CommonEventHandler {
if (entity.level().isClientSide) return;
if (entity instanceof LivingEntity || entity instanceof Boat || entity instanceof Minecart) {
if (entity instanceof ServerPlayer serverPlayer) {
LeashSyncManager.forEach(i -> {
LeashSyncManager.Data.forEach(i -> {
if(i.isLeashedBy(serverPlayer)) {
i.addDelayedLeash(serverPlayer); //添加延迟
}
});
}
entity.getCapability(CapabilityHandler.LEASH_DATA_CAP).ifPresent(LeashSyncManager::untrack);
entity.getCapability(CapabilityHandler.LEASH_DATA_CAP).ifPresent(LeashSyncManager.Data::untrack);
entity.getCapability(CapabilityHandler.LEASH_STATE_CAP).ifPresent(LeashSyncManager.State::untrack);
}
}
@SubscribeEvent
@ -142,7 +149,7 @@ public class CommonEventHandler {
if (SuperLeashKnotEntity.isSupportBlock(blockState)) {
boolean shouldConsume = SuperLeadRopeItem.bindToBlock(player, level, blockPos, event.getItemStack(), itemInHand.is(SLPItems.SUPER_LEAD_ROPE.get()));
if (shouldConsume) {
event.setCancellationResult(InteractionResult.CONSUME);
event.setCancellationResult(InteractionResult.SUCCESS);
event.setCanceled(true);
}
}
@ -248,7 +255,7 @@ public class CommonEventHandler {
// 获取范围内可被拴住实体
List<Entity> entities = LeashDataImpl.leashableInArea(telEntity);
//规则关闭则禁止
if(!SLPGameruleRegistry.getGameruleBoolValue(event.getEntity().level(), TeleportWithLeashedPlayers.ID)) {
if(!SLPGameruleRegistry.getGameruleBoolValue(event.getEntity().level(), TeleportWithLeashedEntities.ID)) {
entities.forEach(i -> i.getCapability(CapabilityHandler.LEASH_DATA_CAP).ifPresent(j -> j.removeLeash(i)));
return;
}
@ -260,7 +267,7 @@ public class CommonEventHandler {
float originalPitch = beLeashedEntity.getXRot();
Vec3 originalDeltaMovement = beLeashedEntity.getDeltaMovement();
AtomicReference<ILeashDataCapability.LeashInfo> originalLeashInfo = new AtomicReference<>();
AtomicReference<ILeashData.LeashInfo> originalLeashInfo = new AtomicReference<>();
beLeashedEntity.getCapability(CapabilityHandler.LEASH_DATA_CAP).ifPresent(cap -> {
originalLeashInfo.set(cap.getLeashInfo(telEntity).orElse(null));
cap.removeLeash(telEntity);
@ -296,9 +303,9 @@ public class CommonEventHandler {
}
// --- 恢复拴绳 ---
ILeashDataCapability.LeashInfo leashInfo = Optional.ofNullable(originalLeashInfo.get())
ILeashData.LeashInfo leashInfo = Optional.ofNullable(originalLeashInfo.get())
.map(info -> info.transferHolder(telEntity))
.orElse(ILeashDataCapability.LeashInfo.EMPTY);
.orElse(ILeashData.LeashInfo.EMPTY);
beLeashedEntity.getCapability(CapabilityHandler.LEASH_DATA_CAP).ifPresent(
cap -> cap.addLeash(telEntity, leashInfo)
@ -321,14 +328,16 @@ public class CommonEventHandler {
// 每10 tick标记为脏needsSync
if (tickCounter % 10 == 0) {
LeashSyncManager.forEach(ILeashDataCapability::markForSync);
LeashSyncManager.Data.forEach(ILeashData::markForSync);
LeashSyncManager.State.forEach(ILeashState::markForSync);
}
// 定期同步检查
LeashSyncManager.forEach(ILeashDataCapability::checkSync);
LeashSyncManager.Data.forEach(ILeashData::checkSync);
LeashSyncManager.State.forEach(ILeashState::checkSync);
// 应用物理拉力/效果
LeashSyncManager.forEach(ILeashDataCapability::applyLeashForces);
LeashSyncManager.Data.forEach(ILeashData::applyLeashForces);
}
}
@SubscribeEvent
@ -344,6 +353,11 @@ public class CommonEventHandler {
public static void attachCapability(AttachCapabilitiesEvent<?> event) {
CapabilityHandler.attachCapability(event);
}
@SubscribeEvent
public static void onRegisterCommand (RegisterCommandsEvent event) {
CommandDispatcher<CommandSourceStack> dispatcher = event.getDispatcher();
MotionCommand.register(dispatcher);
}
}
@net.minecraftforge.fml.common.Mod.EventBusSubscriber(modid = SuperLeadRope.MOD_ID, bus= net.minecraftforge.fml.common.Mod.EventBusSubscriber.Bus.MOD)
public static class Mod {
@ -363,4 +377,4 @@ public class CommonEventHandler {
}
}
}
}

View File

@ -16,6 +16,7 @@
package top.r3944realms.superleadrope;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.fml.ModList;
import net.minecraftforge.fml.ModLoadingContext;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.config.ModConfig;
@ -48,4 +49,15 @@ public class SuperLeadRope {
ModLoadingContext modLoadingContext = ModLoadingContext.get();
ConfigUtil.registerConfig(modLoadingContext, ModConfig.Type.COMMON, LeashCommonConfig.SPEC, c, "leash");
}
public static class ModInfo {
public static final String VERSION;
static {
// ModList 获取当前 ModContainer 的元数据
VERSION = ModList.get()
.getModContainerById(MOD_ID)
.map(c -> c.getModInfo().getVersion().toString())
.orElse("UNKNOWN");
}
}
}

View File

@ -25,7 +25,7 @@ import net.minecraft.world.level.Level;
import top.r3944realms.superleadrope.SuperLeadRope;
import top.r3944realms.superleadrope.client.renderer.resolver.SuperLeashStateResolver;
import top.r3944realms.superleadrope.content.capability.CapabilityHandler;
import top.r3944realms.superleadrope.content.capability.inter.ILeashDataCapability;
import top.r3944realms.superleadrope.content.capability.inter.ILeashData;
import top.r3944realms.superleadrope.content.entity.SuperLeashKnotEntity;
import java.util.Optional;
@ -49,15 +49,15 @@ public class LeashRenderHandler {
cameraEntity.getBoundingBox().inflate(50))) {
entity.getCapability(CapabilityHandler.LEASH_DATA_CAP).ifPresent(leashData -> {
if(leashData instanceof ILeashDataCapability) {}
for (ILeashDataCapability.LeashInfo leashInfo : leashData.getAllLeashes()) {
if(leashData instanceof ILeashData) {}
for (ILeashData.LeashInfo leashInfo : leashData.getAllLeashes()) {
renderLeashFromInfo(entity, leashInfo, poseStack, bufferSource, partialTick);
}
});
}
}
private static void renderLeashFromInfo(Entity entity, ILeashDataCapability.LeashInfo leashInfo,
private static void renderLeashFromInfo(Entity entity, ILeashData.LeashInfo leashInfo,
PoseStack poseStack, MultiBufferSource bufferSource,
float partialTick) {
try {
@ -67,7 +67,7 @@ public class LeashRenderHandler {
Entity holder = holderOpt.get();
// 构建渲染状态
SuperLeashStateResolver.resolve(entity, holder, leashInfo, partialTick).ifPresent(
SuperLeashStateResolver.resolve(holder, entity, leashInfo, partialTick).ifPresent(
leashRenderState -> SuperLeashRenderer.renderLeash(leashRenderState, poseStack, bufferSource)
);
@ -77,7 +77,7 @@ public class LeashRenderHandler {
}
}
private static Optional<Entity> getHolderFromLeashInfo(Level level, ILeashDataCapability.LeashInfo leashInfo) {
private static Optional<Entity> getHolderFromLeashInfo(Level level, ILeashData.LeashInfo leashInfo) {
if (leashInfo.blockPosOpt().isPresent()) {
BlockPos pos = leashInfo.blockPosOpt().get();
return Optional.of(SuperLeashKnotEntity.getOrCreateKnot(level, pos));

View File

@ -20,11 +20,15 @@ import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec3;
import top.r3944realms.superleadrope.SuperLeadRope;
import top.r3944realms.superleadrope.client.renderer.state.SuperLeashRenderState;
import top.r3944realms.superleadrope.content.capability.CapabilityHandler;
import top.r3944realms.superleadrope.content.capability.impi.LeashDataImpl;
import top.r3944realms.superleadrope.content.capability.inter.ILeashDataCapability;
import top.r3944realms.superleadrope.content.capability.inter.ILeashData;
import top.r3944realms.superleadrope.content.entity.SuperLeashKnotEntity;
import top.r3944realms.superleadrope.util.capability.LeashUtil;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
//TODO: 未来实现更高级的渲染
public class SuperLeashStateResolver {
private static final float MAX_TENSION = 1.5f;
@ -44,18 +48,37 @@ public class SuperLeashStateResolver {
public static Optional<SuperLeashRenderState> resolve(
Entity holder,
Entity leashedEntity,
ILeashDataCapability.LeashInfo leashInfo,
ILeashData.LeashInfo leashInfo,
float partialTicks) {
if (holder == null || leashedEntity == null) {
return Optional.empty();
}
AtomicReference<Vec3> holderOffset = new AtomicReference<>();
AtomicReference<Vec3> entityOffset = new AtomicReference<>();
LeashUtil.getLeashState(leashedEntity).ifPresent(state ->
state
.getLeashState(holder)
.ifPresent(ls -> {
holderOffset.set(
Optional.ofNullable(ls.holderLocationOffset())
.orElse(ls.defaultHolderLocationOffset())
);
entityOffset.set(
Optional.ofNullable(ls.applyEntityLocationOffset())
.orElse(
state
.getLeashApplyEntityLocationOffset()
.orElse(state.getDefaultLeashApplyEntityLocationOffset())
)
);
}
));
// 获取当前帧位置(带插值)
Vec3 currentHolderPos = getInterpolatedPosition(holder, partialTicks)
.add(0, holder.getBbHeight() * 0.7, 0);
Vec3 currentEntityPos = getInterpolatedPosition(leashedEntity, partialTicks)
.add(leashInfo.attachOffset());
Vec3 currentHolderPos = getInterpolatedPosition(holder, partialTicks).add(holderOffset.get());
Vec3 currentEntityPos = getInterpolatedPosition(leashedEntity, partialTicks).add(entityOffset.get());
// 获取上一帧数据
FrameCache cache = frameCacheMap.get(leashedEntity.getUUID());
@ -77,7 +100,7 @@ public class SuperLeashStateResolver {
currentHolderPos, currentEntityPos,
lastHolderPos, lastEntityPos,
lastAngle, lastSpeed,
tension, partialTicks);
tension);
// 更新缓存
frameCacheMap.put(leashedEntity.getUUID(),
@ -108,7 +131,7 @@ public class SuperLeashStateResolver {
Vec3 currentStart, Vec3 currentEnd,
Vec3 lastStart, Vec3 lastEnd,
float lastAngle, float lastSpeed,
float tension, float partialTicks) {
float tension) {
// 计算当前方向向量
Vec3 currentDir = currentEnd.subtract(currentStart).normalize();
@ -182,7 +205,7 @@ public class SuperLeashStateResolver {
List<SuperLeashRenderState> states = new ArrayList<>();
Level level = leashedEntity.level();
for (ILeashDataCapability.LeashInfo leashInfo : leashData.getAllLeashes()) {
for (ILeashData.LeashInfo leashInfo : leashData.getAllLeashes()) {
Entity holder = null;
if (leashInfo.blockPosOpt().isEmpty() && leashInfo.holderIdOpt().isPresent()){
holder = level.getEntity(leashInfo.holderIdOpt().get());

View File

@ -30,27 +30,76 @@ public class LeashCommonConfig {
}
public static class Common {
public final ForgeConfigSpec.ConfigValue<List<? extends String>> teleportWhitelist;
public final ForgeConfigSpec.ConfigValue<String> SLPModCommandPrefix;
public final ForgeConfigSpec.BooleanValue EnableSLPModCommandPrefix;
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 Common(ForgeConfigSpec.Builder builder) {
builder.push("leash");
BUILDER.push("Command");
EnableSLPModCommandPrefix = builder
.comment("The prefix of this mod's commands")
.define("SLPModCommandPrefix", true);
SLPModCommandPrefix = builder
.comment("The prefix of this mod's commands"," [ Default:'slp'] ")
.define("EnableSLPModCommandPrefix", "slp");
BUILDER.pop();
builder.push("Entity");
teleportWhitelist = builder
.comment("Entity teleport whitelist.",
"Use `#modid` to allow teleporting to all entities from a mod.",
"Use `modid:entity_name` to allow teleporting to a specific entity.")
.comment(
"Entity teleport whitelist.",
"Accepted formats:",
" - #modid : allow teleporting to all entities from a specific mod",
" - modid:entity_name : allow teleporting to a specific entity",
" - #modid:tag_name : allow teleporting to all entities under a given entity type tag"
)
.defineListAllowEmpty(
List.of("allowedTeleportEntities"),
List.of("#minecraft", "modernlife:bicycle", "modernlife:motorboat"),
o -> o instanceof String s && isValidFormat(s)
);
builder.pop();
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);
extremeSnapFactor = builder
.comment("Leash break factor = maxDistance * factor")
.defineInRange("extremeSnapFactor", 2.0, 1.0, 4.0);
springDampening = builder
.comment("Spring dampening coefficient")
.defineInRange("springDampening", 0.7, 0.0, 1.0);
axisSpecificElasticity = builder
.comment("Axis-specific elasticity coefficients for X,Y,Z axes")
.defineList("axisSpecificElasticity", List.of(0.8, 0.2, 0.8), o -> o instanceof Double);
maxLeashesPerEntity = builder
.comment("Maximum number of leashes per entity")
.defineInRange("maxLeashesPerEntity", 6, 1, 24);
builder.pop();
}
private static boolean isValidFormat(String s) {
if (s.startsWith("#")) {
return s.length() > 1 && s.substring(1).matches("[a-z0-9_]+");
String body = s.substring(1);
// 支持 #modid 整个模组
if (body.matches("[a-z0-9_]+")) {
return true;
}
// 支持 #modid:tag_name 标签
return body.matches("[a-z0-9_]+:[a-z0-9_/]+");
}
// 普通实体 ID
return s.matches("[a-z0-9_]+:[a-z0-9_/]+");
}
}

View File

@ -24,17 +24,21 @@ import net.minecraftforge.common.capabilities.RegisterCapabilitiesEvent;
import net.minecraftforge.event.AttachCapabilitiesEvent;
import top.r3944realms.superleadrope.content.capability.impi.LeashDataImpl;
import top.r3944realms.superleadrope.content.capability.inter.IEternalPotato;
import top.r3944realms.superleadrope.content.capability.inter.ILeashDataCapability;
import top.r3944realms.superleadrope.content.capability.inter.ILeashData;
import top.r3944realms.superleadrope.content.capability.inter.ILeashState;
import top.r3944realms.superleadrope.content.capability.provider.EternalPotatoProvider;
import top.r3944realms.superleadrope.content.capability.provider.LeashDataProvider;
import top.r3944realms.superleadrope.content.capability.provider.LeashStateProvider;
import top.r3944realms.superleadrope.content.item.EternalPotatoItem;
public class CapabilityHandler {
public static final Capability<ILeashDataCapability> LEASH_DATA_CAP = CapabilityManager.get(new CapabilityToken<>(){});
public static final Capability<ILeashData> LEASH_DATA_CAP = CapabilityManager.get(new CapabilityToken<>(){});
public static Capability<ILeashState> LEASH_STATE_CAP = CapabilityManager.get(new CapabilityToken<>() {});
public static Capability<IEternalPotato> ETERNAL_POTATO_CAP = CapabilityManager.get(new CapabilityToken<>() {});
public static void registerCapability(RegisterCapabilitiesEvent event) {
event.register(ILeashDataCapability.class);
event.register(ILeashData.class);
event.register(IEternalPotato.class);
event.register(ILeashState.class);
}
public static void attachCapability(AttachCapabilitiesEvent<?> event) {
@ -43,6 +47,7 @@ public class CapabilityHandler {
(LeashDataImpl.isLeashable(entity))//只对活体 矿车添加CAP
) {
event.addCapability(LeashDataProvider.LEASH_DATA_REL, new LeashDataProvider(entity));
event.addCapability(LeashStateProvider.LEASH_STATE_REL, new LeashStateProvider(entity));
} else if (object instanceof ItemStack stack && stack.getItem() instanceof EternalPotatoItem) {
event.addCapability(EternalPotatoProvider.ETERNAL_POTATO_DATA_REL, new EternalPotatoProvider(stack));
}

View File

@ -15,12 +15,25 @@
package top.r3944realms.superleadrope.content.capability;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.player.Player;
import net.minecraftforge.event.entity.player.PlayerEvent;
import top.r3944realms.superleadrope.util.capability.LeashUtil;
public class CapabilityRemainder {
public static void onPlayerClone(PlayerEvent.Clone event) {
if (event.isWasDeath()) {
//
Player newEntity = event.getEntity();
if(newEntity instanceof ServerPlayer newPlayer) {
Player original = event.getOriginal();
original.reviveCaps();
LeashUtil.getLeashState(original)
.ifPresent(oldCap ->
LeashUtil.getLeashState(newPlayer)
.ifPresent(newData ->
newData.copy(oldCap, newEntity)
)
);
original.invalidateCaps();
}
}
}

View File

@ -18,6 +18,7 @@ package top.r3944realms.superleadrope.content.capability.impi;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.NbtUtils;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.Entity;
@ -28,7 +29,6 @@ import net.minecraft.world.entity.animal.horse.Llama;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.vehicle.Boat;
import net.minecraft.world.entity.vehicle.Minecart;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
@ -36,17 +36,21 @@ import net.minecraftforge.network.PacketDistributor;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import top.r3944realms.superleadrope.SuperLeadRope;
import top.r3944realms.superleadrope.content.capability.CapabilityHandler;
import top.r3944realms.superleadrope.content.capability.inter.ILeashDataCapability;
import top.r3944realms.superleadrope.config.LeashCommonConfig;
import top.r3944realms.superleadrope.content.capability.inter.ILeashData;
import top.r3944realms.superleadrope.content.entity.SuperLeashKnotEntity;
import top.r3944realms.superleadrope.network.NetworkHandler;
import top.r3944realms.superleadrope.network.toClient.LeashDataSyncPacket;
import top.r3944realms.superleadrope.util.capability.LeashUtil;
import top.r3944realms.superleadrope.util.nbt.NBTReader;
import top.r3944realms.superleadrope.util.nbt.NBTWriter;
import top.r3944realms.superleadrope.util.riding.RindingLeash;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@ -80,12 +84,35 @@ import java.util.stream.Stream;
* </tbody>
* </table>
*/
public class LeashDataImpl implements ILeashDataCapability {
private static final double LEASH_ELASTIC_DIST = 6.0; // 弹性距离
private static final double LEASH_EXTREME_SNAP_DIST_FACTOR = 2.0; // 断裂距离 = 最大距离 * 2 //TODO:未来可配置
private static final float SPRING_DAMPENING = 0.7f; // 阻尼系数
private static final Vec3 AXIS_SPECIFIC_ELASTICITY = new Vec3(0.8, 0.2, 0.8); // 轴向弹性系数Y轴较弱
private static final int MAX_LEASHES_PER_ENTITY = 6;//一个实体最多链接多少个拴绳 //TODO:未来可配置
public class LeashDataImpl implements ILeashData {
private static final class Config {
private Config() {} // 私有构造防止实例化
static double maxLeashDistance() {
return LeashCommonConfig.COMMON.maxLeashLength.get();
}
static double leashElasticDist() {
return LeashCommonConfig.COMMON.elasticDistance.get();
}
static double leashExtremeSnapDistFactor() {
return LeashCommonConfig.COMMON.extremeSnapFactor.get();
}
static double springDampening() {
return LeashCommonConfig.COMMON.springDampening.get();
}
static Vec3 axisSpecificElasticity() {
List<? extends Double> list = LeashCommonConfig.COMMON.axisSpecificElasticity.get();
return new Vec3(list.get(0), list.get(1), list.get(2));
}
static int maxLeashesPerEntity() {
return LeashCommonConfig.COMMON.maxLeashesPerEntity.get();
}
}
private final Entity entity;
private boolean needsSync = false;
private long lastSyncTime;
@ -93,11 +120,11 @@ public class LeashDataImpl implements ILeashDataCapability {
private final Map<UUID, LeashInfo> leashHolders = new ConcurrentHashMap<>();
// 引入解决 绳结不保存导致第二进入持有者不存在的问题
private final Map<BlockPos, LeashInfo> leashKnots = new ConcurrentHashMap<>();
// private CompoundTag lastSyncedData = new CompoundTag();
public LeashDataImpl(Entity entity) {
this.entity = entity;
}
@Override
public void markForSync() {
if (!entity.level().isClientSide) {
@ -106,13 +133,17 @@ public class LeashDataImpl implements ILeashDataCapability {
}
}
/** 立即同步,无视时间间隔 */
/**
* 立即同步无视时间间隔
*/
@Override
public void immediateSync() {
syncNow();
}
/** 定期调用,每 tick 或每几秒检测 */
/**
* 定期调用 tick 或每几秒检测
*/
@Override
public void checkSync() {
if (!needsSync || entity.level().isClientSide) return;
@ -124,7 +155,9 @@ public class LeashDataImpl implements ILeashDataCapability {
}
}
/** 内部统一同步方法,避免重复逻辑 */
/**
* 内部统一同步方法避免重复逻辑
*/
private void syncNow() {
CompoundTag currentData = serializeNBT();
@ -137,19 +170,44 @@ public class LeashDataImpl implements ILeashDataCapability {
needsSync = false;
}
@Override
public boolean addLeash(Entity holder) {
return addLeash(holder, Config.maxLeashDistance());
}
@Override
public boolean addLeash(Entity holder, String reserved) {
return addLeash(holder, Config.maxLeashDistance(), reserved);
}
// 添加拴绳支持自定义最大长度
@Override
public boolean addLeash(Entity holder, double maxDistance) {
return addLeash(holder, maxDistance, Config.leashElasticDist(), 0, "");
}
// 添加拴绳支持自定义最大长度和弹性距离
@Override
public boolean addLeash(Entity holder, ItemStack leashStack, double maxDistance) {
boolean result = addLeash(holder, leashStack, maxDistance, LEASH_ELASTIC_DIST, 0);
if (result) markForSync();
return result;
public boolean addLeash(Entity holder, double maxDistance, double elasticDistance, int maxKeepLeashTicks) {
return addLeash(holder, maxDistance, elasticDistance, maxKeepLeashTicks, "");
}
// 添加拴绳支持自定义最大长度 + reserved 字段
@Override
public boolean addLeash(Entity holder, ItemStack leashStack, double maxDistance, double elasticDistance, int maxKeepLeashTicks) {
public boolean addLeash(Entity holder, double maxDistance, String reserved) {
return addLeash(holder, maxDistance, Config.leashElasticDist(), 0, reserved);
}
// 添加拴绳最终实现支持最大长度弹性距离保持 Tickreserved
@Override
public boolean addLeash(Entity holder, double maxDistance,
double elasticDistance, int maxKeepLeashTicks, String reserved) {
boolean isSuperKnot = holder instanceof SuperLeashKnotEntity;
if ((!isSuperKnot && leashHolders.containsKey(holder.getUUID()) || (isSuperKnot && leashKnots.containsKey(((SuperLeashKnotEntity) holder).getPos())))) {
if ((!isSuperKnot && leashHolders.containsKey(holder.getUUID()))
|| (isSuperKnot && leashKnots.containsKey(((SuperLeashKnotEntity) holder).getPos()))) {
return false;
}
if (!canBeLeashed()) {
Optional<UUID> uuidOptional = occupyLeash();
if (uuidOptional.isEmpty()) {
@ -157,36 +215,62 @@ public class LeashDataImpl implements ILeashDataCapability {
}
removeLeash(uuidOptional.get());
}
LeashInfo info = LeashInfo.CreateLeashInfo(
LeashInfo info = LeashInfo.create(
holder,
leashStack.getItem().getDescription().toString(),
reserved,
calculateAttachOffset(entity),
maxDistance,
elasticDistance,
maxKeepLeashTicks,
maxKeepLeashTicks
);
if (holder instanceof SuperLeashKnotEntity s) {
leashKnots.put(s.getPos(), info);
if (isSuperKnot) {
leashKnots.put(((SuperLeashKnotEntity) holder).getPos(), info);
} else {
leashHolders.put(holder.getUUID(), info);
}
else leashHolders.put(holder.getUUID(), info);
markForSync();
return true;
}
// 使用已有的 LeashInfo 添加拴绳直接走最终实现
@Override
public boolean addLeash(Entity holder, LeashInfo leashInfo) {
return addLeash(holder, ItemStack.EMPTY, leashInfo.maxDistance(), leashInfo.elasticDistance(), leashInfo.maxKeepLeashTicks());
public void addLeash(Entity holder, LeashInfo leashInfo) {
addLeash(holder,
leashInfo.maxDistance(),
leashInfo.elasticDistance(),
leashInfo.maxKeepLeashTicks(),
leashInfo.reserved()
);
}
@Override
public boolean addDelayedLeash(Player holderPlayer) {
return delayedHolders.add(holderPlayer.getUUID());
public void addDelayedLeash(Player holderPlayer) {
delayedHolders.add(holderPlayer.getUUID());
}
@Override
public boolean removeDelayedLeash(UUID onceHolderUUID) {
return delayedHolders.remove(onceHolderUUID);
public void removeDelayedLeash(UUID onceHolderUUID) {
delayedHolders.remove(onceHolderUUID);
}
private <K> boolean updateLeashInfo(
Map<K, LeashInfo> map,
K key,
Function<LeashInfo, LeashInfo> updater
) {
LeashInfo old = map.get(key);
if (old == null || old.holderIdOpt().isEmpty()) return false;
LeashInfo updated = updater.apply(old);
if (updated == null) return false;
map.put(key, updated);
markForSync();
return true;
}
@Override
@ -203,76 +287,101 @@ public class LeashDataImpl implements ILeashDataCapability {
setMaxDistance(holder.getUUID(), newMaxDistance, newMaxKeepLeashTicks);
}
// 动态修改最大拴绳长度
@Override
public boolean setMaxDistance(Entity holder, double distance, int maxKeepTicks, String reserved) {
return holder instanceof SuperLeashKnotEntity superLeashKnotEntity ?
setMaxDistance(superLeashKnotEntity.getPos(), distance, maxKeepTicks, reserved) :
setMaxDistance(holder.getUUID(), distance, maxKeepTicks, reserved);
}
@SuppressWarnings("OptionalGetWithoutIsPresent")
@Override
public boolean setMaxDistance(UUID holderUUID, double newMaxDistance) {
LeashInfo info = leashHolders.get(holderUUID);
if (info == null || info.holderUUIDOpt().isEmpty() || info.holderIdOpt().isEmpty()) return false;
leashHolders.put(holderUUID, new LeashInfo(
info.holderUUIDOpt().get(),
info.holderIdOpt().get(),
info.reserved(),
info.attachOffset(),
return updateLeashInfo(leashHolders, holderUUID, old -> new LeashInfo(
old.holderUUIDOpt().get(),
old.holderIdOpt().get(),
old.reserved(),
old.attachOffset(),
newMaxDistance,
info.elasticDistance(), // 保持原有弹性距离
info.keepLeashTicks(),
info.maxKeepLeashTicks()
old.elasticDistance(),
old.keepLeashTicks(),
old.maxKeepLeashTicks()
));
markForSync();
return true;
}
@SuppressWarnings("OptionalGetWithoutIsPresent")
@Override
public boolean setMaxDistance(UUID holderUUID, double newMaxDistance, int newMaxKeepLeashTicks) {
LeashInfo info = leashHolders.get(holderUUID);
if (info == null || info.holderUUIDOpt().isEmpty() || info.holderIdOpt().isEmpty()) return false;
leashHolders.put(holderUUID, new LeashInfo(
info.holderUUIDOpt().get(),
info.holderIdOpt().get(),
info.reserved(),
info.attachOffset(),
return updateLeashInfo(leashHolders, holderUUID, old -> new LeashInfo(
old.holderUUIDOpt().get(),
old.holderIdOpt().get(),
old.reserved(),
old.attachOffset(),
newMaxDistance,
info.elasticDistance(), // 保持原有弹性距离
newMaxKeepLeashTicks,
info.maxKeepLeashTicks()
));
markForSync();
return true;
}
@Override
public boolean setMaxDistance(BlockPos knotPos, double newMaxDistance) {
LeashInfo info = leashKnots.get(knotPos);
if (info == null || info.blockPosOpt().isEmpty() || info.holderIdOpt().isEmpty()) return false;
leashKnots.put(knotPos, new LeashInfo(
info.blockPosOpt().get(),
info.holderIdOpt().get(),
info.reserved(),
info.attachOffset(),
newMaxDistance,
info.elasticDistance(), // 保持原有弹性距离
info.keepLeashTicks(),
info.maxKeepLeashTicks()
));
markForSync();
return true;
}
@Override
public boolean setMaxDistance(BlockPos knotPos, double newMaxDistance, int newMaxKeepLeashTicks) {
LeashInfo info = leashKnots.get(knotPos);
if (info == null || info.blockPosOpt().isEmpty() || info.holderIdOpt().isEmpty()) return false;
leashKnots.put(knotPos, new LeashInfo(
info.blockPosOpt().get(),
info.holderIdOpt().get(),
info.reserved(),
info.attachOffset(),
newMaxDistance,
info.elasticDistance(), // 保持原有弹性距离
info.keepLeashTicks(),
old.elasticDistance(),
Math.min(old.keepLeashTicks(), newMaxKeepLeashTicks),
newMaxKeepLeashTicks
));
markForSync();
return true;
}
@SuppressWarnings("OptionalGetWithoutIsPresent")
@Override
public boolean setMaxDistance(UUID holderUUID, double distance, int maxKeepTicks, String reserved) {
return updateLeashInfo(leashHolders, holderUUID, old -> new LeashInfo(
old.holderUUIDOpt().get(),
old.holderIdOpt().get(),
reserved,
old.attachOffset(),
distance,
old.elasticDistance(),
Math.min(old.keepLeashTicks(), maxKeepTicks),
maxKeepTicks
));
}
@SuppressWarnings("OptionalGetWithoutIsPresent")
@Override
public boolean setMaxDistance(BlockPos knotPos, double newMaxDistance) {
return updateLeashInfo(leashKnots, knotPos, old -> new LeashInfo(
old.blockPosOpt().get(),
old.holderIdOpt().get(),
old.reserved(),
old.attachOffset(),
newMaxDistance,
old.elasticDistance(),
old.keepLeashTicks(),
old.maxKeepLeashTicks()
));
}
@SuppressWarnings("OptionalGetWithoutIsPresent")
@Override
public boolean setMaxDistance(BlockPos knotPos, double newMaxDistance, int newMaxKeepLeashTicks) {
return updateLeashInfo(leashKnots, knotPos, old -> new LeashInfo(
old.blockPosOpt().get(),
old.holderIdOpt().get(),
old.reserved(),
old.attachOffset(),
newMaxDistance,
old.elasticDistance(),
Math.min(old.keepLeashTicks(), newMaxKeepLeashTicks),
newMaxKeepLeashTicks
));
}
@SuppressWarnings("OptionalGetWithoutIsPresent")
@Override
public boolean setMaxDistance(BlockPos knotPos, double distance, int maxKeepTicks, String reserved) {
return updateLeashInfo(leashKnots, knotPos, old -> new LeashInfo(
old.blockPosOpt().get(),
old.holderIdOpt().get(),
reserved,
old.attachOffset(),
distance,
old.elasticDistance(),
Math.min(old.keepLeashTicks(), maxKeepTicks),
maxKeepTicks
));
}
@Override
@ -283,42 +392,34 @@ public class LeashDataImpl implements ILeashDataCapability {
}
// 动态修改弹性距离
@SuppressWarnings("OptionalGetWithoutIsPresent")
@Override
public boolean setElasticDistance(UUID holderUUID, double newElasticDistance) {
LeashInfo info = leashHolders.get(holderUUID);
if (info == null || info.holderUUIDOpt().isEmpty() || info.holderIdOpt().isEmpty()) return false;
leashHolders.put(holderUUID, new LeashInfo(
info.holderUUIDOpt().get(),
info.holderIdOpt().get(),
info.reserved(),
info.attachOffset(),
info.maxDistance(),
return updateLeashInfo(leashHolders, holderUUID, old -> new LeashInfo(
old.holderUUIDOpt().get(),
old.holderIdOpt().get(),
old.reserved(),
old.attachOffset(),
old.maxDistance(),
newElasticDistance,
info.keepLeashTicks(),
info.maxKeepLeashTicks()
old.keepLeashTicks(),
old.maxKeepLeashTicks()
));
markForSync();
return true;
}
@SuppressWarnings("OptionalGetWithoutIsPresent")
@Override
public boolean setElasticDistance(BlockPos knotPos, double newElasticDistance) {
LeashInfo info = leashKnots.get(knotPos);
if (info == null || info.blockPosOpt().isEmpty() || info.holderIdOpt().isEmpty()) return false;
leashKnots.put(knotPos, new LeashInfo(
info.blockPosOpt().get(),
info.holderIdOpt().get(),
info.reserved(),
info.attachOffset(),
info.maxDistance(),
return updateLeashInfo(leashKnots, knotPos, old -> new LeashInfo(
old.blockPosOpt().get(),
old.holderIdOpt().get(),
old.reserved(),
old.attachOffset(),
old.maxDistance(),
newElasticDistance,
info.keepLeashTicks(),
info.maxKeepLeashTicks()
old.keepLeashTicks(),
old.maxKeepLeashTicks()
));
markForSync();
return true;
}
@Override
@ -328,41 +429,72 @@ public class LeashDataImpl implements ILeashDataCapability {
setElasticDistance(holder.getUUID(), newElasticDistance, newMaxKeepLeashTicks);
}
// 动态修改弹性距离
@Override
public boolean setElasticDistance(UUID holderUUID, double newElasticDistance, int newMaxKeepLeashTicks) {
LeashInfo info = leashHolders.get(holderUUID);
if (info == null || info.holderUUIDOpt().isEmpty() || info.holderIdOpt().isEmpty()) return false;
leashHolders.put(holderUUID, new LeashInfo(
info.holderUUIDOpt().get(),
info.holderIdOpt().get(),
info.reserved(),
info.attachOffset(),
info.maxDistance(),
newElasticDistance,
Math.min(info.keepLeashTicks(), newMaxKeepLeashTicks), // 限制剩余Tick不超过新最大值
newMaxKeepLeashTicks
));
return true;
public boolean setElasticDistance(Entity holder, double distance, int maxKeepTicks, String reserved) {
return holder instanceof SuperLeashKnotEntity superLeashKnotEntity ?
setElasticDistance(superLeashKnotEntity.getPos(), distance, maxKeepTicks, reserved) :
setElasticDistance(holder.getUUID(), distance, maxKeepTicks, reserved);
}
// 动态修改弹性距离
@SuppressWarnings("OptionalGetWithoutIsPresent")
@Override
public boolean setElasticDistance(BlockPos knotPos, double newElasticDistance, int newMaxKeepLeashTicks) {
LeashInfo info = leashKnots.get(knotPos);
if (info == null || info.blockPosOpt().isEmpty() || info.holderIdOpt().isEmpty()) return false;
leashKnots.put(knotPos, new LeashInfo(
info.blockPosOpt().get(),
info.holderIdOpt().get(),
info.reserved(),
info.attachOffset(),
info.maxDistance(),
public boolean setElasticDistance(UUID holderUUID, double newElasticDistance, int newMaxKeepLeashTicks) {
return updateLeashInfo(leashHolders, holderUUID, old -> new LeashInfo(
old.holderUUIDOpt().get(),
old.holderIdOpt().get(),
old.reserved(),
old.attachOffset(),
old.maxDistance(),
newElasticDistance,
Math.min(info.keepLeashTicks(), newMaxKeepLeashTicks), // 限制剩余Tick不超过新最大值
Math.min(old.keepLeashTicks(), newMaxKeepLeashTicks),
newMaxKeepLeashTicks
));
}
@SuppressWarnings("OptionalGetWithoutIsPresent")
@Override
public boolean setElasticDistance(UUID holderUUID, double distance, int maxKeepTicks, String reserved) {
return updateLeashInfo(leashHolders, holderUUID, old -> new LeashInfo(
old.holderUUIDOpt().get(),
old.holderIdOpt().get(),
reserved,
old.attachOffset(),
old.maxDistance(),
distance,
Math.min(old.keepLeashTicks(), maxKeepTicks),
maxKeepTicks
));
}
@SuppressWarnings("OptionalGetWithoutIsPresent")
@Override
public boolean setElasticDistance(BlockPos knotPos, double newElasticDistance, int newMaxKeepLeashTicks) {
return updateLeashInfo(leashKnots, knotPos, old -> new LeashInfo(
old.blockPosOpt().get(),
old.holderIdOpt().get(),
old.reserved(),
old.attachOffset(),
old.maxDistance(),
newElasticDistance,
old.keepLeashTicks(),
old.maxKeepLeashTicks()
));
}
@SuppressWarnings("OptionalGetWithoutIsPresent")
@Override
public boolean setElasticDistance(BlockPos knotPos, double newElasticDistance, int newMaxKeepLeashTicks, String reserved) {
return updateLeashInfo(leashKnots, knotPos, old -> new LeashInfo(
old.blockPosOpt().get(),
old.holderIdOpt().get(),
reserved,
old.attachOffset(),
old.maxDistance(),
newElasticDistance,
Math.min(old.keepLeashTicks(), newMaxKeepLeashTicks),
newMaxKeepLeashTicks
));
return true;
}
/**
@ -439,14 +571,14 @@ public class LeashDataImpl implements ILeashDataCapability {
LeashInfo info = entry.getValue();
Vec3 entityPos = entity.position().add(info.attachOffset());
double distance = holderPos.distanceTo(entityPos);
double extremeSnapDist = info.maxDistance() * LEASH_EXTREME_SNAP_DIST_FACTOR;
double extremeSnapDist = info.maxDistance() * Config.leashExtremeSnapDistFactor();
// 1. 检查是否超出断裂距离
if (distance > extremeSnapDist) {
if (info.keepLeashTicks() > 0) {
// 计算临界拉力
Vec3 pullForce = calculateCriticalPullForce(holderPos, entityPos, distance, info);
entry.setValue(info.decrementKeepLeashTicks());
entry.setValue(info.decrementKeepTicks());
return pullForce;
}
// 断裂
@ -471,7 +603,7 @@ public class LeashDataImpl implements ILeashDataCapability {
// 3. 重置缓冲Tick
if (distance <= info.maxDistance() && info.keepLeashTicks() < info.maxKeepLeashTicks()) {
entry.setValue(info.resetKeepLeashTicks());
entry.setValue(info.resetKeepTicks());
}
return pullForce;
@ -489,13 +621,13 @@ public class LeashDataImpl implements ILeashDataCapability {
}
Vec3 pullForce = pullDirection.scale(
(distance - info.elasticDistance()) * pullStrength * SPRING_DAMPENING
(distance - info.elasticDistance()) * pullStrength * Config.springDampening()
);
return new Vec3(
pullForce.x * AXIS_SPECIFIC_ELASTICITY.x,
pullForce.y * AXIS_SPECIFIC_ELASTICITY.y,
pullForce.z * AXIS_SPECIFIC_ELASTICITY.z
pullForce.x * Config.axisSpecificElasticity().x,
pullForce.y * Config.axisSpecificElasticity().y,
pullForce.z * Config.axisSpecificElasticity().z
);
}
@ -506,13 +638,13 @@ public class LeashDataImpl implements ILeashDataCapability {
double pullStrength = 1.0 + excessRatio * 2.0;
Vec3 pullForce = pullDirection.scale(
(distance - info.elasticDistance()) * pullStrength * SPRING_DAMPENING
(distance - info.elasticDistance()) * pullStrength * Config.springDampening()
);
return new Vec3(
pullForce.x * AXIS_SPECIFIC_ELASTICITY.x,
pullForce.y * AXIS_SPECIFIC_ELASTICITY.y,
pullForce.z * AXIS_SPECIFIC_ELASTICITY.z
pullForce.x * Config.axisSpecificElasticity().x,
pullForce.y * Config.axisSpecificElasticity().y,
pullForce.z * Config.axisSpecificElasticity().z
);
}
@ -566,10 +698,10 @@ public class LeashDataImpl implements ILeashDataCapability {
}
@Override
public boolean transferLeash(Entity holder, Entity newHolder, ItemStack stack) {
public boolean transferLeash(Entity holder, Entity newHolder, String reserved) {
return holder instanceof SuperLeashKnotEntity superLeashKnotEntity ?
transferLeash(superLeashKnotEntity.getPos(), newHolder, stack) :
transferLeash(holder.getUUID(), newHolder, stack);
transferLeash(superLeashKnotEntity.getPos(), newHolder, reserved) :
transferLeash(holder.getUUID(), newHolder, reserved);
}
// 将拴绳持有者转移到新实体(非拴绳结 -> 任意)
@ -588,14 +720,14 @@ public class LeashDataImpl implements ILeashDataCapability {
return true;
}
@Override
public boolean transferLeash(UUID oldHolderUUID, Entity newHolder, ItemStack stack) {
public boolean transferLeash(UUID oldHolderUUID, Entity newHolder, String reserved) {
LeashInfo info = leashHolders.remove(oldHolderUUID);
if (info == null || newHolder == null) return false;
if(newHolder instanceof SuperLeashKnotEntity superLeashKnotEntity) {
LeashInfo leashInfo = info.transferHolder(superLeashKnotEntity, stack.getDescriptionId());
LeashInfo leashInfo = info.transferHolder(superLeashKnotEntity, reserved);
leashKnots.put(superLeashKnotEntity.getPos(), leashInfo);
} else {
LeashInfo leashInfo = info.transferHolder(newHolder, stack.getDescriptionId());
LeashInfo leashInfo = info.transferHolder(newHolder, reserved);
leashHolders.put(newHolder.getUUID(), leashInfo);
}
markForSync();
@ -618,14 +750,14 @@ public class LeashDataImpl implements ILeashDataCapability {
}
@Override
public boolean transferLeash(BlockPos knotPos, Entity newHolder, ItemStack stack) {
public boolean transferLeash(BlockPos knotPos, Entity newHolder, String reserved) {
LeashInfo info = leashKnots.remove(knotPos);
if (info == null || newHolder == null) return false;
if(newHolder instanceof SuperLeashKnotEntity superLeashKnotEntity) {
LeashInfo leashInfo = info.transferHolder(superLeashKnotEntity, stack.getDescriptionId());
LeashInfo leashInfo = info.transferHolder(superLeashKnotEntity, reserved);
leashKnots.put(superLeashKnotEntity.getPos(), leashInfo);
} else {
LeashInfo leashInfo = info.transferHolder(newHolder, stack.getDescriptionId());
LeashInfo leashInfo = info.transferHolder(newHolder, reserved);
leashHolders.put(newHolder.getUUID(), leashInfo);
}
markForSync();
@ -743,9 +875,7 @@ public class LeashDataImpl implements ILeashDataCapability {
throw new IllegalArgumentException("LeashInfo.blockPos is empty");
}
BlockPos blockPos = info.blockPosOpt().get();
infoTag.putInt("HolderX", blockPos.getX());
infoTag.putInt("HolderY", blockPos.getY());
infoTag.putInt("HolderZ", blockPos.getZ());
infoTag.put("KnotBlockPos", NbtUtils.writeBlockPos(blockPos));
return getCommonCompoundTag(info, infoTag);
}
@ -755,9 +885,7 @@ public class LeashDataImpl implements ILeashDataCapability {
}
infoTag.putInt("HolderID", info.holderIdOpt().get());
infoTag.putString("LeashItem", info.reserved());
infoTag.putDouble("OffsetX", info.attachOffset().x);
infoTag.putDouble("OffsetY", info.attachOffset().y);
infoTag.putDouble("OffsetZ", info.attachOffset().z);
infoTag.put("Offset", NBTWriter.writeVec3(info.attachOffset()));
infoTag.putDouble("MaxDistance", info.maxDistance());
infoTag.putDouble("ElasticDistance", info.elasticDistance());
infoTag.putInt("KeepLeashTicks", info.keepLeashTicks());
@ -793,10 +921,47 @@ public class LeashDataImpl implements ILeashDataCapability {
}
}
}
private static @NotNull UUID getDelayedUUIDFormListTag(@NotNull CompoundTag infoTag) {
if (infoTag.contains("DelayHolderUUID"))
return infoTag.getUUID("DelayHolderUUID");
throw new IllegalArgumentException("LeashInfo.intId is empty");
}
@Contract("_ -> new")
private static @NotNull LeashInfo getUUIDLeashDataFormListTag(@NotNull CompoundTag infoTag) {
if (infoTag.contains("HolderUUID")){
return new LeashInfo(
infoTag.getUUID("HolderUUID"),
infoTag.getInt("HolderID"),
infoTag.getString("LeashItem"),
NBTReader.readVec3(infoTag.getCompound("Offset")),
infoTag.getDouble("MaxDistance"),
infoTag.contains("ElasticDistance") ? infoTag.getDouble("ElasticDistance") : 6.0,
infoTag.getInt("KeepLeashTicks"),
infoTag.contains("MaxKeepLeashTicks") ? infoTag.getInt("MaxKeepLeashTicks") : 20
);
} else
throw new IllegalArgumentException("Unknown LeashInfo");
}
@Contract("_ -> new")
private static @NotNull LeashInfo getBlockPosLeashDataFormListTag(@NotNull CompoundTag infoTag) {
if (infoTag.contains("KnotBlockPos")) {
return new LeashInfo(
NbtUtils.readBlockPos(infoTag.getCompound("KnotBlockPos")),
infoTag.getInt("HolderID"),
infoTag.getString("LeashItem"),
NBTReader.readVec3(infoTag.getCompound("Offset")),
infoTag.getDouble("MaxDistance"),
infoTag.contains("ElasticDistance") ? infoTag.getDouble("ElasticDistance") : 6.0,
infoTag.getInt("KeepLeashTicks"),
infoTag.contains("MaxKeepLeashTicks") ? infoTag.getInt("MaxKeepLeashTicks") : 20
);
} else
throw new IllegalArgumentException("Unknown LeashInfo");
}
@Override
public boolean canBeLeashed() {
return (leashHolders.size() + leashKnots.size()) <= MAX_LEASHES_PER_ENTITY;
return (leashHolders.size() + leashKnots.size()) <= Config.maxLeashesPerEntity();
}
@Override
@ -823,43 +988,7 @@ public class LeashDataImpl implements ILeashDataCapability {
return Optional.empty(); // 理论上不会到这里
}
private static @NotNull UUID getDelayedUUIDFormListTag(@NotNull CompoundTag infoTag) {
if (infoTag.contains("DelayHolderUUID"))
return infoTag.getUUID("DelayHolderUUID");
throw new IllegalArgumentException("LeashInfo.intId is empty");
}
@Contract("_ -> new")
private static @NotNull LeashInfo getUUIDLeashDataFormListTag(@NotNull CompoundTag infoTag) {
if (infoTag.contains("HolderUUID")){
return new LeashInfo(
infoTag.getUUID("HolderUUID"),
infoTag.getInt("HolderID"),
infoTag.getString("LeashItem"),
new Vec3(infoTag.getDouble("OffsetX"), infoTag.getDouble("OffsetY"), infoTag.getDouble("OffsetZ")),
infoTag.getDouble("MaxDistance"),
infoTag.contains("ElasticDistance") ? infoTag.getDouble("ElasticDistance") : 6.0,
infoTag.getInt("KeepLeashTicks"),
infoTag.contains("MaxKeepLeashTicks") ? infoTag.getInt("MaxKeepLeashTicks") : 20
);
} else
throw new IllegalArgumentException("Unknown LeashInfo");
}
@Contract("_ -> new")
private static @NotNull LeashInfo getBlockPosLeashDataFormListTag(@NotNull CompoundTag infoTag) {
if (infoTag.contains("HolderX")) {
return new LeashInfo(
new BlockPos(infoTag.getInt("HolderX"), infoTag.getInt("HolderY"), infoTag.getInt("HolderZ")),
infoTag.getInt("HolderID"),
infoTag.getString("LeashItem"),
new Vec3(infoTag.getDouble("OffsetX"), infoTag.getDouble("OffsetY"), infoTag.getDouble("OffsetZ")),
infoTag.getDouble("MaxDistance"),
infoTag.contains("ElasticDistance") ? infoTag.getDouble("ElasticDistance") : 6.0,
infoTag.getInt("KeepLeashTicks"),
infoTag.contains("MaxKeepLeashTicks") ? infoTag.getInt("MaxKeepLeashTicks") : 20
);
} else
throw new IllegalArgumentException("Unknown LeashInfo");
}
public static @NotNull List<Entity> leashableInArea(Level pLevel, Vec3 pPos, Predicate<Entity> filter) {
return leashableInArea(pLevel, pPos, filter, 1024D);
@ -882,17 +1011,23 @@ public class LeashDataImpl implements ILeashDataCapability {
return false;
} else {
Optional<LeashInfo> leashInfo = getLeashInfo(pEntity);
return leashInfo.isEmpty() && (entity.distanceTo(pEntity) <= LEASH_ELASTIC_DIST * LEASH_EXTREME_SNAP_DIST_FACTOR) && canBeLeashed();//距离最大,则不可以被固定或转移
return leashInfo.isEmpty() && (entity.distanceTo(pEntity) <= Config.leashElasticDist() * Config.leashExtremeSnapDistFactor()) && canBeLeashed();//距离最大,则不可以被固定或转移
}
}
public static boolean isLeashHolder(@NotNull Entity pEntity, UUID pHolderUUID) {
AtomicBoolean isTarget = new AtomicBoolean(false);
pEntity.getCapability(CapabilityHandler.LEASH_DATA_CAP).ifPresent(i -> isTarget.set(i.isLeashedBy(pHolderUUID)));
LeashUtil.getLeashData(pEntity)
.ifPresent(i ->
isTarget.set(i.isLeashedBy(pHolderUUID))
);
return isTarget.get();
}
public static boolean isLeashHolder(@NotNull Entity pEntity, BlockPos pKnotPos) {
AtomicBoolean isTarget = new AtomicBoolean(false);
pEntity.getCapability(CapabilityHandler.LEASH_DATA_CAP).ifPresent(i -> isTarget.set(i.isLeashedBy(pKnotPos)));
LeashUtil.getLeashData(pEntity)
.ifPresent(i ->
isTarget.set(i.isLeashedBy(pKnotPos))
);
return isTarget.get();
}
public static boolean isLeashHolder(@NotNull Entity pEntity, Entity pTestHolder) {

View File

@ -0,0 +1,315 @@
/*
* 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.content.capability.impi;
import com.google.common.collect.Maps;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.NbtUtils;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.network.PacketDistributor;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import top.r3944realms.superleadrope.content.capability.inter.ILeashState;
import top.r3944realms.superleadrope.content.entity.SuperLeashKnotEntity;
import top.r3944realms.superleadrope.network.NetworkHandler;
import top.r3944realms.superleadrope.network.toClient.LeashStateSyncPacket;
import top.r3944realms.superleadrope.util.nbt.NBTReader;
import top.r3944realms.superleadrope.util.nbt.NBTWriter;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
//TODO: 将拴绳状态与数据联系在一起
public class LeashStateImpl implements ILeashState {
private Entity entity;
private boolean needsSync = false;
private long lastSyncTime;
private final Map<UUID, ILeashState.LeashState> leashHolders = new ConcurrentHashMap<>();
private final Map<BlockPos, ILeashState.LeashState> leashKnots = new ConcurrentHashMap<>();
private volatile Vec3 staticApplyEntityLocationOffset;
private volatile Vec3 defaultApplyEntityLocationOffset = new Vec3(0 , 0.45, 0);
public LeashStateImpl(Entity entity) {
this.entity = entity;
}
@Override
public void markForSync() {
if (!entity.level().isClientSide) {
needsSync = true;
immediateSync(); // 立即同步一次
}
}
@Override
public void immediateSync() {
syncNow();
}
@Override
public void checkSync() {
if (!needsSync || entity.level().isClientSide) return;
long now = System.currentTimeMillis();
// 每隔 2 秒同步一次
if (now - lastSyncTime > 2000) {
syncNow();
}
}
private void syncNow() {
CompoundTag currentData = serializeNBT();
NetworkHandler.sendToPlayer(
new LeashStateSyncPacket(entity.getId(), currentData),
entity,
PacketDistributor.TRACKING_ENTITY_AND_SELF
);
lastSyncTime = System.currentTimeMillis();
needsSync = false;
}
@Override
public Map<UUID, LeashState> getHolderLeashStates() {
ConcurrentMap<UUID, LeashState> retMap = Maps.newConcurrentMap();
retMap.putAll(leashHolders);
return retMap;
}
@Override
public Map<BlockPos, LeashState> getKnotLeashStates() {
ConcurrentMap<BlockPos, LeashState> retMap = Maps.newConcurrentMap();
retMap.putAll(leashKnots);
return retMap;
}
@Override
public Optional<LeashState> getLeashState(Entity holder) {
return holder instanceof SuperLeashKnotEntity leashKnot ? getLeashState(leashKnot.getPos())
: getLeashState(holder.getUUID());
}
@Override
public Optional<LeashState> getLeashState(UUID uuid) {
return leashHolders.containsKey(uuid) ? Optional.of(leashHolders.get(uuid)) : Optional.empty();
}
@Override
public Optional<LeashState> getLeashState(BlockPos pos) {
return leashKnots.containsKey(pos) ? Optional.of(leashKnots.get(pos)) : Optional.empty();
}
@Override
public Vec3 getDefaultLeashApplyEntityLocationOffset() {
return defaultApplyEntityLocationOffset;
}
@Override
public Optional<Vec3> getLeashApplyEntityLocationOffset() {
return Optional.ofNullable(staticApplyEntityLocationOffset);
}
@Override
public void resetLeashHolderLocationOffset(Entity holder) {
if (entity instanceof SuperLeashKnotEntity leashKnot) {
resetLeashHolderLocationOffset(leashKnot.getPos());
} else resetLeashHolderLocationOffset(holder.getUUID());
}
@Override
public void resetLeashHolderLocationOffset(UUID holderUUID) {
leashHolders.computeIfPresent(holderUUID, (uuid, state) -> state.resetHolderLocationOffset());
markForSync();
}
@Override
public void resetLeashHolderLocationOffset(BlockPos knotPos) {
leashKnots.computeIfPresent(knotPos, (blockPos, state) -> state.resetHolderLocationOffset());
markForSync();
}
@Override
public void setLeashHolderLocationOffset(Entity holder, Vec3 offset) {
if (entity instanceof SuperLeashKnotEntity leashKnot) {
setLeashHolderLocationOffset(leashKnot.getPos(), offset);
} else setLeashHolderLocationOffset(holder.getUUID(), offset);
}
@Override
public void setLeashHolderLocationOffset(UUID holderUUID, Vec3 offset) {
leashHolders.computeIfPresent(holderUUID, (uuid, state) -> state.setHolderLocationOffset(offset));
markForSync();
}
@Override
public void setLeashHolderLocationOffset(BlockPos knotPos, Vec3 leashHolderLocationOffset) {
leashKnots.computeIfPresent(knotPos, (blockPos, state) -> state.setHolderLocationOffset(leashHolderLocationOffset));
markForSync();
}
@Override
public void addLeashHolderLocationOffset(Entity holder, Vec3 offset) {
if (entity instanceof SuperLeashKnotEntity leashKnot) {
addLeashHolderLocationOffset(leashKnot.getPos(), offset);
} else addLeashHolderLocationOffset(holder.getUUID(), offset);
}
@Override
public void addLeashHolderLocationOffset(UUID holderUUID, Vec3 offset) {
leashHolders.computeIfPresent(holderUUID, (uuid, state) -> state.setHolderLocationOffset(state.holderLocationOffset().add(offset)));
markForSync();
}
@Override
public void addLeashHolderLocationOffset(BlockPos knotPos, Vec3 offset) {
leashKnots.computeIfPresent(knotPos, (blockPos, state) -> state.setHolderLocationOffset(state.holderLocationOffset().add(offset)));
markForSync();
}
@Override
public void removeLeashHolderLocationOffset(Entity holder) {
if (entity instanceof SuperLeashKnotEntity leashKnot) {
removeLeashHolderLocationOffset(leashKnot.getPos());
} else removeLeashHolderLocationOffset(holder.getUUID());
}
@Override
public void removeLeashHolderLocationOffset(UUID holderUUID) {
leashHolders.remove(holderUUID);
markForSync();
}
@Override
public void removeLeashHolderLocationOffset(BlockPos knotPos) {
leashKnots.remove(knotPos);
markForSync();
}
@Override
public void resetAllLeashHolderLocationsOffset() {
leashKnots.replaceAll((pos, leashState) -> leashState.resetHolderLocationOffset());
leashHolders.replaceAll((uuid, leashState) -> leashState.resetHolderLocationOffset());
markForSync();
}
@Override
public void resetAllLeashApplyEntityLocationsOffset() {
leashKnots.replaceAll((pos, leashState) -> leashState.setApplyEntityLocationOffset(defaultApplyEntityLocationOffset));
leashHolders.replaceAll((uuid, leashState) -> leashState.setApplyEntityLocationOffset(defaultApplyEntityLocationOffset));
markForSync();
}
@Override
public void removeLeashApplyEntityLocationOffset() {
staticApplyEntityLocationOffset = null;
markForSync();
}
@Override
public void setLeashApplyEntityLocationOffset(Vec3 leashHolderLocationOffset) {
staticApplyEntityLocationOffset = leashHolderLocationOffset;
markForSync();
}
@Override
public void addLeashApplyEntityLocationOffset(Vec3 offset) {
Optional.ofNullable(this.staticApplyEntityLocationOffset)
.ifPresentOrElse(vec3 -> vec3.add(offset), () -> this.staticApplyEntityLocationOffset = offset);
markForSync();
}
@Override
public void copy(ILeashState other, Entity newEntity) {
this.entity = newEntity;
this.defaultApplyEntityLocationOffset = other.getDefaultLeashApplyEntityLocationOffset();
this.staticApplyEntityLocationOffset = other.getLeashApplyEntityLocationOffset().orElse(null);
}
@Override
public CompoundTag serializeNBT() {
CompoundTag tag = new CompoundTag();
ListTag holdersList = new ListTag();
leashHolders.forEach((uuid, state ) -> {
CompoundTag infoTag = generateCompoundTagFromUUIDLeashInfo(uuid, state);
holdersList.add(infoTag);
});
leashKnots.forEach((blockPos, state ) -> {
CompoundTag infoTag = generateCompoundTagFromBlockPosLeashState(blockPos, state);
holdersList.add(infoTag);
});
tag.put("LeashHolders", holdersList);
if (staticApplyEntityLocationOffset != null) {
tag.put("StaticApplyEntityLocationOffset", NBTWriter.writeVec3(staticApplyEntityLocationOffset));
}
tag.put("DefaultApplyEntityLocationOffset", NBTWriter.writeVec3(defaultApplyEntityLocationOffset));
return tag;
}
private static @NotNull CompoundTag generateCompoundTagFromUUIDLeashInfo(@NotNull UUID uuid, @NotNull ILeashState.LeashState info) {
CompoundTag infoTag = new CompoundTag();
infoTag.putUUID("HolderUUID", uuid);
return getCommonCompoundTag(info ,infoTag);
}
private static @NotNull CompoundTag generateCompoundTagFromBlockPosLeashState(@NotNull BlockPos blockpos, @NotNull ILeashState.LeashState info) {
CompoundTag infoTag = new CompoundTag();
infoTag.put("KnotBlockPos", NbtUtils.writeBlockPos(blockpos));
return getCommonCompoundTag(info, infoTag);
}
private static @NotNull CompoundTag getCommonCompoundTag(@NotNull ILeashState.LeashState info, CompoundTag infoTag) {
infoTag.put("ApplyEntityLocationOffset", NBTWriter.writeVec3(info.applyEntityLocationOffset()));
infoTag.put("HolderEntityLocationOffset", NBTWriter.writeVec3(info.holderLocationOffset()));
infoTag.put("DefaultHolderLocationOffset", NBTWriter.writeVec3(info.defaultHolderLocationOffset()));
return infoTag;
}
@Override
public void deserializeNBT(@NotNull CompoundTag nbt) {
leashHolders.clear();
leashKnots.clear();
if (nbt.contains("LeashHolders", ListTag.TAG_LIST)) {
ListTag holdersList = nbt.getList("LeashHolders", ListTag.TAG_COMPOUND);
if(nbt.contains("StaticApplyEntityLocationOffset")) {
staticApplyEntityLocationOffset = NBTReader.readVec3(nbt.getCompound("StaticApplyEntityLocationOffset"));
}
if (nbt.contains("DefaultApplyEntityLocationOffset")) {
defaultApplyEntityLocationOffset = NBTReader.readVec3(nbt.getCompound("DefaultApplyEntityLocationOffset"));
} else throw new IllegalArgumentException("Nbt Lost DefaultApplyEntityLocationOffset Value");
for (int i = 0; i < holdersList.size(); i++) {
CompoundTag infoTag = holdersList.getCompound(i);
if (infoTag.contains("HolderUUID")) {
ILeashState.LeashState uuidLeashDataFormListTag = getUUIDLeashStateForm(infoTag);
leashHolders.put(infoTag.getUUID("HolderUUID"), uuidLeashDataFormListTag);
} else {
ILeashState.LeashState blockPosLeashDataFormListTag = getUUIDLeashStateForm(infoTag);
leashKnots.put(NbtUtils.readBlockPos(infoTag.getCompound("KnotBlockPos")), blockPosLeashDataFormListTag);
}
}
}
}
@Contract("_ -> new")
private static @NotNull ILeashState.LeashState getUUIDLeashStateForm(@NotNull CompoundTag infoTag) {
return new ILeashState.LeashState(
NBTReader.readVec3(infoTag.getCompound("HolderEntityLocationOffset")),
NBTReader.readVec3(infoTag.getCompound("ApplyEntityLocationOffset")),
NBTReader.readVec3(infoTag.getCompound("DefaultHolderLocationOffset"))
);
}
}

View File

@ -0,0 +1,213 @@
/*
* 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.content.capability.inter;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.common.util.INBTSerializable;
import top.r3944realms.superleadrope.content.entity.SuperLeashKnotEntity;
import java.util.Collection;
import java.util.Optional;
import java.util.UUID;
/**
* Capability interface for managing leash data of entities and knots.
*/
public interface ILeashData extends INBTSerializable<CompoundTag> {
/* ----------------------
* Add / remove leashes
* ---------------------- */
boolean addLeash(Entity holder);
boolean addLeash(Entity holder, String reserved);
boolean addLeash(Entity holder, double maxDistance);
boolean addLeash(Entity holder, double maxDistance, double elasticDistance, int maxKeepTicks);
boolean addLeash(Entity holder, double maxDistance, String reserved);
boolean addLeash(Entity holder, double maxDistance, double elasticDistance, int maxKeepTicks, String reserved);
void addLeash(Entity holder, LeashInfo info);
void addDelayedLeash(Player holderPlayer);
void removeDelayedLeash(UUID onceHolderPlayerUUID);
boolean removeLeash(Entity holder);
boolean removeLeash(UUID holderUUID);
boolean removeLeash(BlockPos knotPos);
void removeAllLeashes();
void removeAllHolderLeashes();
void removeAllKnotLeashes();
/* ----------------------
* Modify leash properties
* ---------------------- */
boolean setMaxDistance(Entity holder, double distance);
boolean setMaxDistance(Entity holder, double distance, int maxKeepTicks);
boolean setMaxDistance(Entity holder, double distance, int maxKeepTicks, String reserved);
boolean setMaxDistance(UUID holderUUID, double distance);
boolean setMaxDistance(UUID holderUUID, double distance, int maxKeepTicks);
boolean setMaxDistance(UUID holderUUID, double distance, int maxKeepTicks, String reserved);
boolean setMaxDistance(BlockPos knotPos, double distance);
boolean setMaxDistance(BlockPos knotPos, double distance, int maxKeepTicks);
boolean setMaxDistance(BlockPos knotPos, double distance, int maxKeepTicks, String reserved);
boolean setElasticDistance(Entity holder, double distance);
boolean setElasticDistance(Entity holder, double distance, int maxKeepTicks);
boolean setElasticDistance(Entity holder, double distance, int maxKeepTicks, String reserved);
boolean setElasticDistance(UUID holderUUID, double distance);
boolean setElasticDistance(UUID holderUUID, double distance, int maxKeepTicks);
boolean setElasticDistance(UUID holderUUID, double distance, int maxKeepTicks, String reserved);
boolean setElasticDistance(BlockPos knotPos, double distance);
boolean setElasticDistance(BlockPos knotPos, double distance, int maxKeepTicks);
boolean setElasticDistance(BlockPos knotPos, double distance, int maxKeepTicks, String reserved);
/* ----------------------
* Apply physics
* ---------------------- */
void applyLeashForces();
/* ----------------------
* Transfer leash holders
* ---------------------- */
boolean transferLeash(Entity holder, Entity newHolder);
boolean transferLeash(Entity holder, Entity newHolder, String reserved);
boolean transferLeash(UUID holderUUID, Entity newHolder);
boolean transferLeash(UUID holderUUID, Entity newHolder, String reserved);
boolean transferLeash(BlockPos knotPos, Entity newHolder);
boolean transferLeash(BlockPos knotPos, Entity newHolder, String reserved);
/* ----------------------
* Query state
* ---------------------- */
boolean hasLeash();
boolean hasKnotLeash();
boolean hasHolderLeash();
Collection<LeashInfo> getAllLeashes();
boolean isLeashedBy(Entity holder);
boolean isLeashedBy(UUID holderUUID);
boolean isLeashedBy(BlockPos knotPos);
boolean isInDelayedLeash(UUID holderUUID);
Optional<LeashInfo> getLeashInfo(Entity holder);
Optional<LeashInfo> getLeashInfo(UUID holderUUID);
Optional<LeashInfo> getLeashInfo(BlockPos knotPos);
boolean canBeLeashed();
boolean canBeAttachedTo(Entity entity);
/* ----------------------
* Occupy / sync
* ---------------------- */
/**
* 抢占位已离线玩家
* 用于解决玩家下线后所持有对象会移除持有者的问题实际上是占用个弱集合
*/
Optional<UUID> occupyLeash();
void markForSync();
void immediateSync();
void checkSync();
/* ----------------------
* Data record
* ---------------------- */
record LeashInfo(
Optional<BlockPos> blockPosOpt,
Optional<UUID> holderUUIDOpt,
Optional<Integer> holderIdOpt, // Only for client side use
String reserved, // 保留字段
Vec3 attachOffset,
double maxDistance,
double elasticDistance,
int keepLeashTicks, // 剩余 Tick
int maxKeepLeashTicks // 最大保持 Tick
) {
public static final LeashInfo EMPTY = new LeashInfo(
Optional.empty(), Optional.empty(), Optional.empty(),
"", Vec3.ZERO, 12.0D, 6.0D, 0, 0
);
/* ---------- Factory ---------- */
public static LeashInfo create(
Entity entity,
String reserved,
Vec3 offset,
double maxDistance,
double elasticDistance,
int keepTicks,
int maxKeepTicks
) {
return entity instanceof SuperLeashKnotEntity knot
? new LeashInfo(knot.getPos(), entity.getId(), reserved,
offset, maxDistance, elasticDistance, keepTicks, maxKeepTicks)
: new LeashInfo(entity.getUUID(), entity.getId(), reserved,
offset, maxDistance, elasticDistance, keepTicks, maxKeepTicks);
}
public LeashInfo(UUID holderUUID, int holderId, String reserved, Vec3 offset,
double maxDistance, double elasticDistance, int keepTicks, int maxKeepTicks) {
this(Optional.empty(), Optional.of(holderUUID), Optional.of(holderId),
reserved, offset, maxDistance, elasticDistance, keepTicks, maxKeepTicks);
}
public LeashInfo(BlockPos knotPos, int holderId, String reserved, Vec3 offset,
double maxDistance, double elasticDistance, int keepTicks, int maxKeepTicks) {
this(Optional.of(knotPos), Optional.empty(), Optional.of(holderId),
reserved, offset, maxDistance, elasticDistance, keepTicks, maxKeepTicks);
}
/* ---------- State updates ---------- */
public LeashInfo decrementKeepTicks() {
return new LeashInfo(blockPosOpt, holderUUIDOpt, holderIdOpt, reserved, attachOffset,
maxDistance, elasticDistance,
Math.max(0, keepLeashTicks - 1), maxKeepLeashTicks);
}
public LeashInfo resetKeepTicks() {
return new LeashInfo(blockPosOpt, holderUUIDOpt, holderIdOpt, reserved, attachOffset,
maxDistance, elasticDistance,
maxKeepLeashTicks, maxKeepLeashTicks);
}
public LeashInfo transferHolder(Entity entity) {
return transferHolder(entity, reserved);
}
public LeashInfo transferHolder(Entity entity, String newReserved) {
boolean isKnot = entity instanceof SuperLeashKnotEntity;
return new LeashInfo(
isKnot ? Optional.of(((SuperLeashKnotEntity) entity).getPos()) : Optional.empty(),
!isKnot ? Optional.of(entity.getUUID()) : Optional.empty(),
Optional.of(entity.getId()),
newReserved, attachOffset, maxDistance, elasticDistance,
keepLeashTicks, maxKeepLeashTicks
);
}
}
}

View File

@ -1,184 +0,0 @@
/*
* 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.content.capability.inter;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.common.util.INBTSerializable;
import top.r3944realms.superleadrope.content.entity.SuperLeashKnotEntity;
import java.util.Collection;
import java.util.Optional;
import java.util.UUID;
public interface ILeashDataCapability extends INBTSerializable<CompoundTag> {
// 原LeashData的方法接口
boolean addLeash(Entity holder, ItemStack leashStack, double maxDistance);
boolean addLeash(Entity holder, ItemStack leashStack, double maxDistance, double elasticDistance, int maxKeepLeashTicks);
boolean addLeash(Entity holder, LeashInfo leashInfo);
boolean addDelayedLeash(Player holderPlayer);
boolean removeDelayedLeash(UUID onceHolderPlayerUUID);
boolean setMaxDistance(Entity holder, double newMaxDistance);
boolean setMaxDistance(Entity holder,double newMaxDistance, int newMaxKeepLeashTicks);
boolean setMaxDistance(UUID holderUUID, double newMaxDistance);
boolean setMaxDistance(UUID holderUUID, double newMaxDistance, int newMaxKeepLeashTicks);
boolean setMaxDistance(BlockPos knotPos, double newMaxDistance);
boolean setMaxDistance(BlockPos knotPos, double newMaxDistance, int newMaxKeepLeashTicks);
boolean setElasticDistance(Entity holder, double newElasticDistance);
boolean setElasticDistance(UUID holderUUID, double newElasticDistance);
boolean setElasticDistance(BlockPos knotPos, double newElasticDistance);
// 动态修改弹性距离
boolean setElasticDistance(Entity holder, double newElasticDistance, int newMaxKeepLeashTicks);
boolean setElasticDistance(UUID holderUUID, double newElasticDistance, int newMaxKeepLeashTicks);
boolean setElasticDistance(BlockPos knotPos, double newElasticDistance, int newMaxKeepLeashTicks);
void applyLeashForces();
boolean removeLeash(Entity holder);
boolean removeLeash(UUID holderUUID);
boolean removeLeash(BlockPos knotPos);
void removeAllLeashes();
void removeAllHolderLeashes();
void removeAllKnotLeashes();
boolean transferLeash(Entity holder, Entity newHolder);
boolean transferLeash(Entity holder, Entity newHolder, ItemStack stack);
boolean transferLeash(UUID holderUUID, Entity newHolder);
boolean transferLeash(UUID holderUUID, Entity newHolder, ItemStack stack);
boolean transferLeash(BlockPos knotPos, Entity newHolder);
boolean transferLeash(BlockPos knotPos, Entity newHolder, ItemStack stack);
// 查询方法
boolean hasLeash();
boolean hasKnotLeash();
boolean hasHolderLeash();
Collection<LeashInfo> getAllLeashes();
boolean isLeashedBy(Entity holder);
boolean isLeashedBy(UUID holderUUID);
boolean isLeashedBy(BlockPos knotPos);
boolean isInDelayedLeash(UUID holderUUID);
Optional<LeashInfo> getLeashInfo(Entity holder);
Optional<LeashInfo> getLeashInfo(UUID holderUUID);
Optional<LeashInfo> getLeashInfo(BlockPos knotPos);
boolean canBeLeashed();
/**
* 抢占位已离线玩家
* 用于解决玩家下线后所持有我对象会移除持有者的问题实际上是占用个弱集合
*/
Optional<UUID> occupyLeash();
boolean canBeAttachedTo(Entity pEntity);
void markForSync();
void immediateSync();
void checkSync();
record LeashInfo(
Optional<BlockPos> blockPosOpt,
Optional<UUID> holderUUIDOpt,
Optional<Integer> holderIdOpt,//Only for client side use
String reserved, //保留字段
Vec3 attachOffset,
double maxDistance,
double elasticDistance,
int keepLeashTicks, // 新增保持拴绳的剩余Tick数
int maxKeepLeashTicks // 新增最大保持Tick数可配置
) {
public static final LeashInfo EMPTY = new LeashInfo(
Optional.empty(), Optional.empty(), Optional.empty(),
"", Vec3.ZERO, 12.0D, 6.0D, 0, 0
);
public static LeashInfo CreateLeashInfo(
Entity entity,
String reserved,
Vec3 attachOffset,
double maxDistance,
double elasticDistance,
int keepLeashTicks,
int maxKeepLeashTicks
) {
return entity instanceof SuperLeashKnotEntity superLeashKnot ?
new LeashInfo(superLeashKnot.getPos(), entity.getId(), reserved, attachOffset, maxDistance, elasticDistance, keepLeashTicks, maxKeepLeashTicks) :
new LeashInfo(entity.getUUID(), entity.getId(), reserved, attachOffset, maxDistance, elasticDistance, keepLeashTicks, maxKeepLeashTicks);
}
public LeashInfo(
UUID holderUUID,
int holderId,
String reserved,
Vec3 attachOffset,
double maxDistance,
double elasticDistance,
int keepLeashTicks,
int maxKeepLeashTicks
) {
this(Optional.empty() ,Optional.of(holderUUID), Optional.of(holderId), reserved, attachOffset, maxDistance, elasticDistance, keepLeashTicks, maxKeepLeashTicks);
}
public LeashInfo(
BlockPos knotPos,
int holderId,
String reserved,
Vec3 attachOffset,
double maxDistance,
double elasticDistance,
int keepLeashTicks,
int maxKeepLeashTicks
) {
this(Optional.of(knotPos), Optional.empty(), Optional.of(holderId), reserved, attachOffset, maxDistance, elasticDistance, keepLeashTicks, maxKeepLeashTicks);
}
// 返回一个减少剩余Tick的新实例
public LeashInfo decrementKeepLeashTicks() {
return new LeashInfo(
blockPosOpt, holderUUIDOpt, holderIdOpt, reserved, attachOffset,
maxDistance, elasticDistance,
Math.max(0, keepLeashTicks - 1),
maxKeepLeashTicks
);
}
// 重置Tick为最大值
public LeashInfo resetKeepLeashTicks() {
return new LeashInfo(
blockPosOpt, holderUUIDOpt, holderIdOpt, reserved, attachOffset,
maxDistance, elasticDistance,
maxKeepLeashTicks,
maxKeepLeashTicks
);
}
public LeashInfo transferHolder (Entity entity) {
boolean isSuperKnot = entity instanceof SuperLeashKnotEntity;
return new LeashInfo(
isSuperKnot ? Optional.of(((SuperLeashKnotEntity) entity).getPos()) : Optional.empty(),
!isSuperKnot ? Optional.of(entity.getUUID()) : Optional.empty(),
Optional.of(entity.getId()),
reserved, attachOffset, maxDistance,elasticDistance, keepLeashTicks, maxKeepLeashTicks
);
}
public LeashInfo transferHolder (Entity entity, String reserved) {
boolean isSuperKnot = entity instanceof SuperLeashKnotEntity;
return new LeashInfo(
isSuperKnot ? Optional.of(((SuperLeashKnotEntity) entity).getPos()) : Optional.empty(),
!isSuperKnot ? Optional.of(entity.getUUID()) : Optional.empty(),
Optional.of(entity.getId()),
reserved, attachOffset, maxDistance,elasticDistance, keepLeashTicks, maxKeepLeashTicks
);
}
}
}

View File

@ -0,0 +1,111 @@
/*
* 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.content.capability.inter;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.common.util.INBTSerializable;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
/**
* Capability interface for managing leash states of entities and knots.
*/
public interface ILeashState extends INBTSerializable<CompoundTag> {
/* ----------------------
* Query leash states
* ---------------------- */
Map<UUID, LeashState> getHolderLeashStates();
Map<BlockPos, LeashState> getKnotLeashStates();
Optional<LeashState> getLeashState(Entity entity);
Optional<LeashState> getLeashState(UUID uuid);
Optional<LeashState> getLeashState(BlockPos pos);
/* ----------------------
* Reset holder offsets
* ---------------------- */
void resetAllLeashHolderLocationsOffset();
void resetLeashHolderLocationOffset(Entity holder);
void resetLeashHolderLocationOffset(UUID holderUUID);
void resetLeashHolderLocationOffset(BlockPos knotPos);
/* ----------------------
* Set holder offsets
* ---------------------- */
void setLeashHolderLocationOffset(Entity holder, Vec3 offset);
void setLeashHolderLocationOffset(UUID holderUUID, Vec3 offset);
void setLeashHolderLocationOffset(BlockPos knotPos, Vec3 offset);
/* ----------------------
* Add holder offsets
* ---------------------- */
void addLeashHolderLocationOffset(Entity holder, Vec3 offset);
void addLeashHolderLocationOffset(UUID holderUUID, Vec3 offset);
void addLeashHolderLocationOffset(BlockPos knotPos, Vec3 offset);
/* ----------------------
* Remove holder offsets
* ---------------------- */
void removeLeashHolderLocationOffset(Entity holder);
void removeLeashHolderLocationOffset(UUID holderUUID);
void removeLeashHolderLocationOffset(BlockPos knotPos);
/* ----------------------
* Apply-entity offset
* ---------------------- */
Optional<Vec3> getLeashApplyEntityLocationOffset();
Vec3 getDefaultLeashApplyEntityLocationOffset();
void resetAllLeashApplyEntityLocationsOffset();
void removeLeashApplyEntityLocationOffset();
void setLeashApplyEntityLocationOffset(Vec3 offset);
void addLeashApplyEntityLocationOffset(Vec3 offset);
/* ----------------------
* Utility & sync
* ---------------------- */
void copy(ILeashState other, Entity newEntity);
void markForSync();
void immediateSync();
void checkSync();
/* ----------------------
* Data record
* ---------------------- */
record LeashState(
Vec3 holderLocationOffset,
Vec3 applyEntityLocationOffset,
Vec3 defaultHolderLocationOffset
) {
public LeashState resetHolderLocationOffset() {
return new LeashState(defaultHolderLocationOffset, applyEntityLocationOffset, defaultHolderLocationOffset);
}
public LeashState setHolderLocationOffset(Vec3 holderLocationOffset) {
return new LeashState(holderLocationOffset, applyEntityLocationOffset, defaultHolderLocationOffset);
}
public LeashState setApplyEntityLocationOffset(Vec3 applyEntityLocationOffset) {
return new LeashState(holderLocationOffset, applyEntityLocationOffset, defaultHolderLocationOffset);
}
}
}

View File

@ -27,12 +27,12 @@ import org.jetbrains.annotations.Nullable;
import top.r3944realms.superleadrope.SuperLeadRope;
import top.r3944realms.superleadrope.content.capability.CapabilityHandler;
import top.r3944realms.superleadrope.content.capability.impi.LeashDataImpl;
import top.r3944realms.superleadrope.content.capability.inter.ILeashDataCapability;
import top.r3944realms.superleadrope.content.capability.inter.ILeashData;
public class LeashDataProvider implements ICapabilitySerializable<CompoundTag> {
public static final ResourceLocation LEASH_DATA_REL = new ResourceLocation(SuperLeadRope.MOD_ID, "leash_data");
private final ILeashDataCapability instance;
private final LazyOptional<ILeashDataCapability> optional;
private final ILeashData instance;
private final LazyOptional<ILeashData> optional;
public LeashDataProvider(Entity entity) {
this.instance = new LeashDataImpl(entity);
this.optional = LazyOptional.of(() -> instance);

View File

@ -0,0 +1,54 @@
/*
* 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.content.capability.provider;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.Entity;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.capabilities.ICapabilitySerializable;
import net.minecraftforge.common.util.LazyOptional;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import top.r3944realms.superleadrope.SuperLeadRope;
import top.r3944realms.superleadrope.content.capability.CapabilityHandler;
import top.r3944realms.superleadrope.content.capability.impi.LeashStateImpl;
import top.r3944realms.superleadrope.content.capability.inter.ILeashState;
public class LeashStateProvider implements ICapabilitySerializable<CompoundTag> {
public static final ResourceLocation LEASH_STATE_REL = new ResourceLocation(SuperLeadRope.MOD_ID, "leash_state");
private final ILeashState instance;
private final LazyOptional<ILeashState> optional;
public LeashStateProvider(Entity entity) {
this.instance = new LeashStateImpl(entity);
this.optional = LazyOptional.of(() -> instance);
}
@Override
public @NotNull <T> LazyOptional<T> getCapability(@NotNull Capability<T> cap, @Nullable Direction side) {
return CapabilityHandler.LEASH_STATE_CAP.orEmpty(cap, optional);
}
@Override
public CompoundTag serializeNBT() {
return instance.serializeNBT();
}
@Override
public void deserializeNBT(CompoundTag nbt) {
instance.deserializeNBT(nbt);
}
}

View File

@ -0,0 +1,37 @@
/*
* 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.content.command;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import org.jetbrains.annotations.Nullable;
import top.r3944realms.superleadrope.config.LeashCommonConfig;
import java.util.List;
public class Command {
public static final String PREFIX = LeashCommonConfig.COMMON.SLPModCommandPrefix.get();
public static boolean SHOULD_USE_PREFIX = LeashCommonConfig.COMMON.EnableSLPModCommandPrefix.get();
static LiteralArgumentBuilder<CommandSourceStack> getLiterArgumentBuilderOfCSS(String name, boolean shouldAddToList, @Nullable List<LiteralArgumentBuilder<CommandSourceStack>> list) {
LiteralArgumentBuilder<CommandSourceStack> literal = Commands.literal(name);
if (shouldAddToList) {
assert list != null;
list.add(literal);
}
return literal;
}
}

View File

@ -0,0 +1,28 @@
/*
* 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.content.command;
import com.mojang.brigadier.CommandDispatcher;
import net.minecraft.commands.CommandSourceStack;
public class LeashDataCommand {
// 获取Data
// 设置Data
// <add/transfer/remove> Holder<BlockPos/Entity<需判断实体类型>>
// 设置对应目标的 最大长度 最长断裂距离 保持不断裂时间刻
public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
}
}

View File

@ -0,0 +1,29 @@
/*
* 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.content.command;
import com.mojang.brigadier.CommandDispatcher;
import net.minecraft.commands.CommandSourceStack;
public class LeashStateCommand {
// 获取State
// 设置State
// <add/set/reset> Holder<BlockPos/Entity<需判断实体类型>> <Holder/Entity> <x> <y> <z>
// 设置对应目标的 拴绳偏移
public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
}
}

View File

@ -0,0 +1,173 @@
/*
* 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.content.command;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.DoubleArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.minecraft.commands.arguments.EntityArgument;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
import top.r3944realms.superleadrope.SuperLeadRope;
import top.r3944realms.superleadrope.network.NetworkHandler;
import top.r3944realms.superleadrope.network.toClient.UpdatePlayerMovementPacket;
import java.util.ArrayList;
import java.util.List;
import static top.r3944realms.superleadrope.content.command.Command.*;
public class MotionCommand {
private final static String SLP_MOTION_MESSAGE_ = SuperLeadRope.MOD_ID + ".command.motion.message.";
public final static String MOTION_SETTER_SUCCESSFUL = SLP_MOTION_MESSAGE_ + "setter.successful",
MOTION_ADDER_SUCCESSFUL = SLP_MOTION_MESSAGE_ + "adder.successful",
MOTION_MULTIPLY_SUCCESSFUL = SLP_MOTION_MESSAGE_ + "multiply.successful";
public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
@Nullable List<LiteralArgumentBuilder<CommandSourceStack>> nodeList = SHOULD_USE_PREFIX ? null : new ArrayList<>();
LiteralArgumentBuilder<CommandSourceStack> literalArgumentBuilder = Commands.literal(PREFIX);
LiteralArgumentBuilder<CommandSourceStack> $$motionRoot = getLiterArgumentBuilderOfCSS("motion", !SHOULD_USE_PREFIX, nodeList);
com.mojang.brigadier.Command<CommandSourceStack> motionVecAdder = context -> {
CommandSourceStack source = context.getSource();
for(Entity entity : EntityArgument.getEntities(context, "targets")){
Vec3 motionVec = new Vec3(
DoubleArgumentType.getDouble(context, "vecX"),
DoubleArgumentType.getDouble(context, "vecY"),
DoubleArgumentType.getDouble(context, "vecZ")
);
boolean flag = entity instanceof ServerPlayer;
if(entity instanceof ServerPlayer player) {
NetworkHandler.sendToPlayer(new UpdatePlayerMovementPacket(UpdatePlayerMovementPacket.Operation.ADD, motionVec.x, motionVec.y, motionVec.z), player);
} else {
entity.addDeltaMovement(motionVec);
}
Vec3 deltaMovement = entity.getDeltaMovement();
double vecX = deltaMovement.x, vecY = deltaMovement.y, vecZ = deltaMovement.z;
source.sendSuccess(() ->
Component.translatable(
MOTION_ADDER_SUCCESSFUL,
entity.getDisplayName(),
flag ? vecX + motionVec.x : vecX,
flag ? vecY + motionVec.y : vecY,
flag ? vecZ + motionVec.z : vecZ
), true
);
}
return 0;
};
Command<CommandSourceStack> motionVecSetter = context -> {
CommandSourceStack source = context.getSource();
for(Entity entity : EntityArgument.getEntities(context, "targets")){
Vec3 motionVec = new Vec3(
DoubleArgumentType.getDouble(context, "vecX"),
DoubleArgumentType.getDouble(context, "vecY"),
DoubleArgumentType.getDouble(context, "vecZ")
);
boolean flag = entity instanceof ServerPlayer;
if(entity instanceof ServerPlayer player) {
NetworkHandler.sendToPlayer(new UpdatePlayerMovementPacket(UpdatePlayerMovementPacket.Operation.SET, motionVec.x, motionVec.y, motionVec.z), player);
} else {
entity.setDeltaMovement(motionVec);
}
double vecX = entity.getDeltaMovement().x, vecY = entity.getDeltaMovement().y, vecZ = entity.getDeltaMovement().z;
source.sendSuccess(() ->
Component.translatable(
MOTION_SETTER_SUCCESSFUL,
entity.getDisplayName(),
flag ? motionVec.x : vecX,
flag ? motionVec.y : vecY,
flag ? motionVec.z : vecZ
), true
);
}
return 0;
};
Command<CommandSourceStack> motionVecMultiply = context -> {
CommandSourceStack source = context.getSource();
for(Entity entity : EntityArgument.getEntities(context, "targets")){
Vec3 motionFactorVec = new Vec3(
DoubleArgumentType.getDouble(context, "vecXFactor"),
DoubleArgumentType.getDouble(context, "vecYFactor"),
DoubleArgumentType.getDouble(context, "vecZFactor")
);
boolean flag = entity instanceof ServerPlayer;
Vec3 deltaMovement = entity.getDeltaMovement();
if(entity instanceof ServerPlayer player) {
NetworkHandler.sendToPlayer(new UpdatePlayerMovementPacket(UpdatePlayerMovementPacket.Operation.MULTIPLY, motionFactorVec.x, motionFactorVec.y, motionFactorVec.z), player);
} else {
entity.setDeltaMovement(deltaMovement.multiply(motionFactorVec));
}
double vecX = deltaMovement.x, vecY = deltaMovement.y, vecZ = deltaMovement.z;
source.sendSuccess(() ->
Component.translatable(
MOTION_MULTIPLY_SUCCESSFUL,
entity.getDisplayName(),
flag ? vecX * motionFactorVec.x : vecX,
flag ? vecY * motionFactorVec.y : vecY,
flag ? vecZ * motionFactorVec.z : vecZ
), true
);
}
return 0;
};
LiteralArgumentBuilder<CommandSourceStack> Motion = $$motionRoot.requires(cs -> cs.hasPermission(2))
.then(Commands.argument("targets", EntityArgument.entities())
.then(Commands.literal("add")
.then(Commands.argument("vecX", DoubleArgumentType.doubleArg())
.then(Commands.argument("vecY", DoubleArgumentType.doubleArg())
.then(Commands.argument("vecZ", DoubleArgumentType.doubleArg())
.executes(motionVecAdder)
)
)
)
)
.then(Commands.literal("set")
.then(Commands.argument("vecX", DoubleArgumentType.doubleArg())
.then(Commands.argument("vecY", DoubleArgumentType.doubleArg())
.then(Commands.argument("vecZ", DoubleArgumentType.doubleArg())
.executes(motionVecSetter)
)
)
)
)
.then(Commands.literal("multiply")
.then(Commands.argument("vecXFactor", DoubleArgumentType.doubleArg())
.then(Commands.argument("vecYFactor", DoubleArgumentType.doubleArg())
.then(Commands.argument("vecZFactor", DoubleArgumentType.doubleArg())
.executes(motionVecMultiply)
)
)
)
)
);
if(SHOULD_USE_PREFIX){
literalArgumentBuilder.then(Motion);
dispatcher.register(literalArgumentBuilder);
} else {
if (nodeList != null) {
nodeList.forEach(dispatcher::register);
}
}
}
}

View File

@ -37,6 +37,7 @@ import org.jetbrains.annotations.NotNull;
import top.r3944realms.superleadrope.content.capability.CapabilityHandler;
import top.r3944realms.superleadrope.content.capability.impi.LeashDataImpl;
import top.r3944realms.superleadrope.core.register.SLPEntityTypes;
import top.r3944realms.superleadrope.util.capability.LeashUtil;
import java.util.Arrays;
import java.util.List;
@ -81,9 +82,9 @@ public class SuperLeashKnotEntity extends LeashFenceKnotEntity {
this.markHurt();
this.playSound(SoundEvents.LEASH_KNOT_BREAK);
List<Entity> entities = LeashDataImpl.leashableInArea(this.level(), pos.getCenter(), entity -> LeashDataImpl.isLeashHolder(entity, this));
entities.forEach(entity -> entity
.getCapability(CapabilityHandler.LEASH_DATA_CAP)
.map(iLeashDataCapability -> iLeashDataCapability.removeLeash(this))
entities.forEach(entity ->
LeashUtil.getLeashData(entity)
.map(iLeashDataCapability -> iLeashDataCapability.removeLeash(this))
);
}
@ -168,11 +169,11 @@ public class SuperLeashKnotEntity extends LeashFenceKnotEntity {
List<Entity> entities = LeashDataImpl.leashableInArea(player);
for(Entity entity : entities) {
if (LeashDataImpl.isLeashHolder(entity, player.getUUID()))
entity.getCapability(CapabilityHandler.LEASH_DATA_CAP).ifPresent(i -> {
i.transferLeash(player.getUUID(), this);
isTransferLeash.set(true);
});
LeashUtil.getLeashData(entity)
.ifPresent(i -> {
i.transferLeash(player.getUUID(), this);
isTransferLeash.set(true);
});
}
AtomicBoolean isRemoveLeashKnot = new AtomicBoolean(false);
if (!isTransferLeash.get()) {
@ -180,14 +181,14 @@ public class SuperLeashKnotEntity extends LeashFenceKnotEntity {
this.playSound(SoundEvents.LEASH_KNOT_BREAK);
this.discard();
List<Entity> entities1 = LeashDataImpl.leashableInArea(this);
entities1.forEach(
entity -> entity
.getCapability(CapabilityHandler.LEASH_DATA_CAP)
.ifPresent(iLeashDataCapability -> {
iLeashDataCapability.removeLeash(this);
isRemoveLeashKnot.set(true);
}
));
entities1.forEach(entity ->
LeashUtil.getLeashData(entity)
.ifPresent(iLeashDataCapability -> {
iLeashDataCapability.removeLeash(this);
isRemoveLeashKnot.set(true);
}
)
);
}
} else {
this.playSound(SoundEvents.LEASH_KNOT_PLACE);

View File

@ -24,8 +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 HashMap<String, Float> gameruleFloatValuesClient = new HashMap<>();
public static final String RULE_KEY_PERFix_ = "gamerule." + GAMERULE_PREFIX;
public static final String RULE_KEY_PERFix_ = "gamerule." + GAMERULE_PREFIX.toLowerCase();
public static String getDescriptionKey(Class<?> gameRuleClass) {
return RULE_KEY_PERFix_ + gameRuleClass.getSimpleName() + ".description";
}

View File

@ -20,11 +20,11 @@ import top.r3944realms.superleadrope.content.gamerule.SLPGamerules;
import static top.r3944realms.superleadrope.content.gamerule.SLPGamerules.GAMERULE_REGISTRY;
public class TeleportWithLeashedPlayers {
public class TeleportWithLeashedEntities {
public static final boolean DEFAULT_VALUE = true;
public static final String ID = SLPGamerules.getGameruleName(TeleportWithLeashedPlayers.class);
public static final String DESCRIPTION_KEY = SLPGamerules.getDescriptionKey(TeleportWithLeashedPlayers.class);
public static final String NAME_KEY = SLPGamerules.getNameKey(TeleportWithLeashedPlayers.class);
public static final String ID = SLPGamerules.getGameruleName(TeleportWithLeashedEntities.class);
public static final String DESCRIPTION_KEY = SLPGamerules.getDescriptionKey(TeleportWithLeashedEntities.class);
public static final String NAME_KEY = SLPGamerules.getNameKey(TeleportWithLeashedEntities.class);
public static final GameRules.Category CATEGORY = GameRules.Category.PLAYER;
public static void register() {

View File

@ -16,6 +16,7 @@
package top.r3944realms.superleadrope.content.item;
import net.minecraft.core.BlockPos;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.InteractionResultHolder;
@ -33,11 +34,13 @@ import org.jetbrains.annotations.NotNull;
import top.r3944realms.superleadrope.content.SLPToolTier;
import top.r3944realms.superleadrope.content.capability.CapabilityHandler;
import top.r3944realms.superleadrope.content.capability.impi.LeashDataImpl;
import top.r3944realms.superleadrope.content.capability.inter.ILeashDataCapability;
import top.r3944realms.superleadrope.content.capability.inter.ILeashData;
import top.r3944realms.superleadrope.content.entity.SuperLeashKnotEntity;
import top.r3944realms.superleadrope.core.register.SLPSoundEvents;
import top.r3944realms.superleadrope.util.capability.LeashUtil;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
@ -76,7 +79,7 @@ public class SuperLeadRopeItem extends TieredItem implements IForgeItem {
return super.use(pLevel, pPlayer, pUsedHand);
}
return InteractionResultHolder.pass(lead);
return InteractionResultHolder.success(lead);
}
public static boolean canUse(ItemStack itemStack) {
@ -89,14 +92,14 @@ public class SuperLeadRopeItem extends TieredItem implements IForgeItem {
BlockPos pos = context.getClickedPos();
BlockState state = level.getBlockState(pos);
ItemStack itemStack = context.getItemInHand();
if (canUse(itemStack)) return InteractionResult.PASS;
if (canUse(itemStack)) return InteractionResult.SUCCESS;
if(SuperLeashKnotEntity.isSupportBlock(state)) {
Player player = context.getPlayer();
if(!level.isClientSide && player != null) {
return bindToBlock(player, level, pos, itemStack, false) ? InteractionResult.CONSUME : InteractionResult.PASS;
return bindToBlock(player, level, pos, itemStack, false) ? InteractionResult.CONSUME : InteractionResult.SUCCESS;
}
}
return InteractionResult.PASS;
return InteractionResult.SUCCESS;
}
/**
* 右键蹲下绑定到另一实体上
@ -119,25 +122,28 @@ public class SuperLeadRopeItem extends TieredItem implements IForgeItem {
* @return 是否成功
*/
public static boolean bindToEntity(Entity newHolder, Player player, Level level, BlockPos pos, ItemStack leashStack) {
AtomicBoolean isSuccess = new AtomicBoolean(false);
List<Entity> list = LeashDataImpl.leashableInArea(level, pos.getCenter(), entity -> LeashDataImpl.isLeashHolder(entity, player.getUUID()));
for(Entity e : list) {
AtomicBoolean canBeAttachedTo = new AtomicBoolean(false);
LazyOptional<ILeashDataCapability> iLeashDataCapability = e.getCapability(CapabilityHandler.LEASH_DATA_CAP);
iLeashDataCapability.ifPresent(i -> canBeAttachedTo.set(i.canBeAttachedTo(newHolder)));
if(canBeAttachedTo.get()) {//canBeAttachedTo
iLeashDataCapability.ifPresent(i -> {
i.transferLeash(player.getUUID(), newHolder, leashStack);
isSuccess.set(true);
});
boolean isSuccess = false;
// 查找当前玩家持有的可拴生物
List<Entity> list = LeashDataImpl.leashableInArea(
level, pos.getCenter(),
entity -> LeashDataImpl.isLeashHolder(entity, player.getUUID())
);
for (Entity e : list) {
Optional<ILeashData> leashDataOpt = LeashUtil.getLeashData(e);
if (leashDataOpt.map(i -> i.canBeAttachedTo(newHolder)).orElse(false)) {
leashDataOpt.ifPresent(i -> i.transferLeash(player.getUUID(), newHolder));
isSuccess = true;
}
}
if(!isSuccess.get()) {
if (!isSuccess) {
return false;
}
else {
} else {
level.gameEvent(GameEvent.ENTITY_INTERACT, pos, GameEvent.Context.of(player));
newHolder.playSound(SLPSoundEvents.LEAD_TIED.get());
level.playSound(null, newHolder.getOnPos(), SLPSoundEvents.LEAD_UNTIED.get(), SoundSource.PLAYERS);
return true;
}
}
@ -155,48 +161,56 @@ public class SuperLeadRopeItem extends TieredItem implements IForgeItem {
}
/**
* 右键蹲下绑定到支持方块上
* @param player 明确持有玩家
* @param level 维度世界
* @param pos 坐标一般是明确持有玩家的位置
* @param leashStack 拴绳物品实例
*
* @param player 明确持有玩家
* @param level 维度世界
* @param pos 坐标一般是明确持有玩家的位置
* @param leashStack 拴绳物品实例
* @param shouldBindSelf 是否应该触发拴自己逻辑检查
* @return 是否成功
*/
public static boolean bindToBlock(Player player, Level level, BlockPos pos, ItemStack leashStack, boolean shouldBindSelf) {
//实现个加强绳结实体
SuperLeashKnotEntity knot = null;
AtomicBoolean isSuccess = new AtomicBoolean(false);
UUID uuid = player.getUUID();
List<Entity> list = LeashDataImpl.leashableInArea(level, pos.getCenter(), entity -> LeashDataImpl.isLeashHolder(entity, uuid));
if (shouldBindSelf && list.isEmpty()) {//拴自己 to new knot
if (leashStack.isEmpty() || !canUse(leashStack)) return false;
knot = SuperLeashKnotEntity.getOrCreateKnot(level, pos);;
List<Entity> list = LeashDataImpl.leashableInArea(level, pos.getCenter(),
entity -> LeashDataImpl.isLeashHolder(entity, uuid));
// 情况一拴自己到新 knot
if (shouldBindSelf && list.isEmpty()) {
if (leashStack.isEmpty() || !canUse(leashStack)) {
return false;
}
knot = SuperLeashKnotEntity.getOrCreateKnot(level, pos);
knot.playPlacementSound();
SuperLeashKnotEntity finalKnot1 = knot;
player.getCapability(CapabilityHandler.LEASH_DATA_CAP).ifPresent(i -> {
i.addLeash(finalKnot1, leashStack, 8D);
SuperLeashKnotEntity finalKnot = knot;
LeashUtil.getLeashData(player).ifPresent(i -> {
if (i.canBeAttachedTo(finalKnot)) {
i.addLeash(finalKnot);
isSuccess.set(true);
}
});
}
// 情况二把已有生物拴到 knot
else if (!list.isEmpty()) {
for(Entity e : list) {
if(knot == null) {
for (Entity e : list) {
if (knot == null) {
knot = SuperLeashKnotEntity.getOrCreateKnot(level, pos);
knot.playPlacementSound();
}
SuperLeashKnotEntity finalKnot = knot;
LazyOptional<ILeashDataCapability> iLeashDataCapability = e.getCapability(CapabilityHandler.LEASH_DATA_CAP);
iLeashDataCapability.ifPresent(i -> {
boolean flag = i.canBeAttachedTo(finalKnot);
if (flag) {
LeashUtil.getLeashData(e).ifPresent(i -> {
if (i.canBeAttachedTo(finalKnot)) {
i.transferLeash(uuid, finalKnot);
isSuccess.set(true);
}
});
}
}
if (isSuccess.get()) {
level.gameEvent(GameEvent.BLOCK_ATTACH, pos, GameEvent.Context.of(player));
return true;

View File

@ -16,6 +16,7 @@
package top.r3944realms.superleadrope.core.leash;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.Entity;
@ -28,7 +29,7 @@ import net.minecraftforge.event.entity.player.AttackEntityEvent;
import net.minecraftforge.event.entity.player.PlayerInteractEvent;
import top.r3944realms.superleadrope.content.capability.CapabilityHandler;
import top.r3944realms.superleadrope.content.capability.impi.LeashDataImpl;
import top.r3944realms.superleadrope.content.capability.inter.ILeashDataCapability;
import top.r3944realms.superleadrope.content.capability.inter.ILeashData;
import top.r3944realms.superleadrope.content.item.SuperLeadRopeItem;
import top.r3944realms.superleadrope.core.register.SLPItems;
import top.r3944realms.superleadrope.core.register.SLPSoundEvents;
@ -45,7 +46,7 @@ public class LeashInteractHandler {
player.getItemInHand(InteractionHand.OFF_HAND).is(SLPItems.SUPER_LEAD_ROPE.get()))
) {
event.setCanceled(true);
event.setCancellationResult(InteractionResult.CONSUME);
event.setCancellationResult(InteractionResult.SUCCESS);
}
return;
}
@ -56,7 +57,7 @@ public class LeashInteractHandler {
if (!LeashDataImpl.isLeashable(target)) {
return;
}
LazyOptional<ILeashDataCapability> LeashCap = target.getCapability(CapabilityHandler.LEASH_DATA_CAP);
LazyOptional<ILeashData> LeashCap = target.getCapability(CapabilityHandler.LEASH_DATA_CAP);
if (!LeashCap.isPresent()) {
return;
}
@ -67,14 +68,14 @@ public class LeashInteractHandler {
if (
mainHandItem.isEmpty() && offHandItem.isEmpty() &&
target.isAlive() && player.isSecondaryUseActive() &&
LeashCap.map(ILeashDataCapability::canBeLeashed).orElse(false)
LeashCap.map(ILeashData::canBeLeashed).orElse(false)
) {
boolean isSuccess = SuperLeadRopeItem.bindToEntity(target, player, player.level(), player.getOnPos(), ItemStack.EMPTY);
if (isSuccess) {
event.setCanceled(true);
event.setCancellationResult(InteractionResult.CONSUME);
event.setCancellationResult(InteractionResult.SUCCESS);
}
} else {
if (LeashDataImpl.isLeashHolder(target, player)) {
@ -82,9 +83,9 @@ public class LeashInteractHandler {
iLeashDataCapability -> iLeashDataCapability.removeLeash(player.getUUID())
);
target.gameEvent(GameEvent.ENTITY_INTERACT, player);
target.playSound(SLPSoundEvents.LEAD_UNTIED.get());
level.playSound(null, target.getOnPos(), SLPSoundEvents.LEAD_UNTIED.get(), SoundSource.PLAYERS);
event.setCanceled(true);
event.setCancellationResult(InteractionResult.CONSUME);
event.setCancellationResult(InteractionResult.SUCCESS);
return;
}
ItemStack itemStack;
@ -99,13 +100,13 @@ public class LeashInteractHandler {
if (itemStack.getItem() == SLPItems.SUPER_LEAD_ROPE.get()) {
LeashCap.ifPresent(iLeashDataCapability -> {
if (iLeashDataCapability.canBeAttachedTo(player)) {
boolean success = iLeashDataCapability.addLeash(player, itemStack, 12);
boolean success = iLeashDataCapability.addLeash(player);
if (success) {
if(!player.isCreative())
itemStack.hurtAndBreak(24, player, e->{});
target.playSound(SLPSoundEvents.LEAD_TIED.get());
event.setCanceled(true);
event.setCancellationResult(InteractionResult.CONSUME);
level.playSound(null, target.getOnPos(), SLPSoundEvents.LEAD_TIED.get(), SoundSource.PLAYERS);
event.setCanceled(true);
event.setCancellationResult(InteractionResult.SUCCESS);
}
}
});
@ -124,11 +125,12 @@ public class LeashInteractHandler {
if (flag) {
target.getCapability(CapabilityHandler.LEASH_DATA_CAP).ifPresent(leashDataCapability -> {
if (leashDataCapability.hasLeash()){
int size = leashDataCapability.getAllLeashes().size();
if (player.isSecondaryUseActive())
leashDataCapability.removeAllLeashes();
else
leashDataCapability.removeAllHolderLeashes();
target.playSound(SLPSoundEvents.LEAD_UNTIED.get());
leashDataCapability.removeAllKnotLeashes();
if(size > leashDataCapability.getAllLeashes().size()) level.playSound(null, target.getOnPos(), SLPSoundEvents.LEAD_UNTIED.get(), SoundSource.PLAYERS);
}
event.setCanceled(true);
});

View File

@ -15,7 +15,8 @@
package top.r3944realms.superleadrope.core.leash;
import top.r3944realms.superleadrope.content.capability.inter.ILeashDataCapability;
import top.r3944realms.superleadrope.content.capability.inter.ILeashData;
import top.r3944realms.superleadrope.content.capability.inter.ILeashState;
import java.util.Collections;
import java.util.Set;
@ -24,16 +25,29 @@ import java.util.function.Consumer;
// 全局LeashData同步管理器
public class LeashSyncManager {
static final Set<ILeashDataCapability> INSTANCES = Collections.newSetFromMap(new WeakHashMap<>());
public static void track(ILeashDataCapability instance) {
INSTANCES.add(instance);
static final Set<ILeashData> LEASH_DATA = Collections.newSetFromMap(new WeakHashMap<>());
static final Set<ILeashState> LEASH_STATES = Collections.newSetFromMap(new WeakHashMap<>());
public static class Data {
public static void track(ILeashData instance) {
LEASH_DATA.add(instance);
}
public static void untrack(ILeashData instance) {
LEASH_DATA.remove(instance);
}
public static void forEach(Consumer<ILeashData> consumer) {
LEASH_DATA.forEach(consumer);
}
}
public static void untrack(ILeashDataCapability instance) {
INSTANCES.remove(instance);
}
public static void forEach(Consumer<ILeashDataCapability> consumer) {
INSTANCES.forEach(consumer);
public static class State {
public static void track(ILeashState instance) {
LEASH_STATES.add(instance);
}
public static void untrack(ILeashState instance) {
LEASH_STATES.remove(instance);
}
public static void forEach(Consumer<ILeashState> consumer) {
LEASH_STATES.forEach(consumer);
}
}
}

View File

@ -19,7 +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.TeleportWithLeashedPlayers;
import top.r3944realms.superleadrope.content.gamerule.server.TeleportWithLeashedEntities;
import java.util.HashMap;
import java.util.Map;
@ -70,7 +70,7 @@ public enum SLPGameruleRegistry {
gameruleDataTypes.put(gameruleName, RuleDataType.INTEGER);
}
public static void register() {
TeleportWithLeashedPlayers.register();
TeleportWithLeashedEntities.register();
}
}

View File

@ -18,7 +18,8 @@ package top.r3944realms.superleadrope.datagen.data;
import net.minecraft.world.item.Item;
import net.minecraft.world.level.block.Block;
import org.jetbrains.annotations.NotNull;
import top.r3944realms.superleadrope.content.gamerule.server.TeleportWithLeashedPlayers;
import top.r3944realms.superleadrope.content.command.MotionCommand;
import top.r3944realms.superleadrope.content.gamerule.server.TeleportWithLeashedEntities;
import top.r3944realms.superleadrope.content.item.EternalPotatoItem;
import top.r3944realms.superleadrope.core.register.SLPEntityTypes;
import top.r3944realms.superleadrope.core.register.SLPItems;
@ -187,15 +188,38 @@ public enum SLPLangKeyValue {
SLPEntityTypes.getEntityNameKey("super_lead_knot"), ModPartEnum.ENTITY,
"Super Lead Knot", "超级拴绳结", "超級拴繩結", "神駒羈縻索結"
),
TELEPORT_WITH_LEASHED_PLAYERS_NAME(TeleportWithLeashedPlayers.NAME_KEY, ModPartEnum.GAME_RULE,
TELEPORT_WITH_LEASHED_ENTITIES_NAME(TeleportWithLeashedEntities.NAME_KEY, ModPartEnum.GAME_RULE,
"Teleport leashed player with player holder",
"被拴实体随玩家持有者传送",
"被拴实体随玩家持有者傳送"
"被拴实体随玩家持有者傳送",
"繫畜隨持者傳送"
),
TELEPORT_WITH_LEASHED_DESCRIPTION(TeleportWithLeashedPlayers.DESCRIPTION_KEY, ModPartEnum.DESCRIPTION,
TELEPORT_WITH_LEASHED_DESCRIPTION(TeleportWithLeashedEntities.DESCRIPTION_KEY, ModPartEnum.DESCRIPTION,
"Holder will teleport with their leashed players ",
"传送时将被拴玩家与持有者一起传送",
"將被拴玩家將隨持有者一起傳送"
"传送时将被拴实体与持有者一起传送",
"將被拴实体將隨持有者一起傳送",
"傳送時繫畜隨持者同傳"
),
MESSAGE_MOTION_ADDER_SUCCESSFUL(
MotionCommand.MOTION_ADDER_SUCCESSFUL, ModPartEnum.COMMAND,
"§bAdd Successfully.§a%s§7:§f[§eVec§7:§a(§f%.2f§7,§f%.2f§7,§f%.2f§7)§f]§r",
"§b添加成功.§a%s§7:§f[§e加速§7:(§a%.2f§7,§a%.2f§7,§a%.2f§7)§f]§r",
"§b添加成功.§a%s§7:§f[§e加速§7:(§a%.2f§7,§a%.2f§7,§a%.2f§7)§f]§r",
"§b增益既成.§a%s§7:§f[§e速勢§7:(§a%.2f§7,§a%.2f§7,§a%.2f§7)§f]§r"
),
MESSAGE_MOTION_SETTER_SUCCESSFUL(
MotionCommand.MOTION_SETTER_SUCCESSFUL, ModPartEnum.COMMAND,
"§bSet Successfully.§a%s§7:§f[§eVec§7:§a(§f%.2f§7,§f%.2f§7,§f%.2f§7)§f]§r",
"§b设置成功.§a%s§7:§f[§e加速§7:(§a%.2f§7,§a%.2f§7,§a%.2f§7)§f]§r",
"§b設置成功.§a%s§7:§f[§e加速§7:(§a%.2f§7,§a%.2f§7,§a%.2f§7)§f]§r",
"§b定值既成.§a%s§7:§f[§e速勢§7:(§a%.2f§7,§a%.2f§7,§a%.2f§7)§f]§r"
),
MESSAGE_MOTION_MULTIPLY_SUCCESSFUL(
MotionCommand.MOTION_MULTIPLY_SUCCESSFUL, ModPartEnum.COMMAND,
"§bMultiply Successfully.§a%s§7:§f[§eVec§7:§a(§f%.2f§7,§f%.2f§7,§f%.2f§7)§f]§r",
"§b倍乘成功.§a%s§7:§f[§e加速§7:(§a%.2f§7,§a%.2f§7,§a%.2f§7)§f]§r",
"§b倍乘成功.§a%s§7:§f[§e加速§7:(§a%.2f§7,§a%.2f§7,§a%.2f§7)§f]§r",
"§b倍乘既成.§a%s§7:§f[§e速勢§7:(§a%.2f§7,§a%.2f§7,§a%.2f§7)§f]§r"
),
;

View File

@ -22,20 +22,16 @@ import net.minecraftforge.network.NetworkRegistry;
import net.minecraftforge.network.PacketDistributor;
import net.minecraftforge.network.simple.SimpleChannel;
import top.r3944realms.superleadrope.SuperLeadRope;
import top.r3944realms.superleadrope.network.toClient.EternalPotatoSyncCapPacket;
import top.r3944realms.superleadrope.network.toClient.LeashDataSyncPacket;
import top.r3944realms.superleadrope.network.toClient.PacketEternalPotatoRemovePacket;
import top.r3944realms.superleadrope.network.toClient.UpdatePlayerMovementPacket;
import top.r3944realms.superleadrope.network.toClient.*;
public class NetworkHandler {
private static final String PROTOCOL_VERSION = "1";
private static int cid = 0;
public static final SimpleChannel INSTANCE = NetworkRegistry.newSimpleChannel(
new ResourceLocation(SuperLeadRope.MOD_ID, "main"),
() -> PROTOCOL_VERSION,
PROTOCOL_VERSION::equals,
PROTOCOL_VERSION::equals
() -> SuperLeadRope.ModInfo.VERSION,
SuperLeadRope.ModInfo.VERSION::equals,
SuperLeadRope.ModInfo.VERSION::equals
);
public static void register() {
INSTANCE.messageBuilder(LeashDataSyncPacket.class, cid++, NetworkDirection.PLAY_TO_CLIENT)
@ -58,6 +54,11 @@ public class NetworkHandler {
.encoder(PacketEternalPotatoRemovePacket::encode)
.consumerNetworkThread(PacketEternalPotatoRemovePacket::handle)
.add();
INSTANCE.messageBuilder(LeashStateSyncPacket.class, cid++, NetworkDirection.PLAY_TO_CLIENT)
.decoder(LeashStateSyncPacket::decode)
.encoder(LeashStateSyncPacket::encode)
.consumerNetworkThread(LeashStateSyncPacket::handle)
.add();
}
public static <MSG> void sendToPlayer(MSG message, ServerPlayer player){
INSTANCE.send(PacketDistributor.PLAYER.with(() -> player), message);

View File

@ -0,0 +1,56 @@
/*
* 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.network.toClient;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.world.entity.Entity;
import net.minecraftforge.network.NetworkEvent;
import top.r3944realms.superleadrope.content.capability.CapabilityHandler;
import java.util.function.Supplier;
public record LeashStateSyncPacket(int entityId, CompoundTag leashState) {
public static void encode(LeashStateSyncPacket msg, FriendlyByteBuf buffer) {
buffer.writeInt(msg.entityId);
buffer.writeNbt(msg.leashState);
}
public static LeashStateSyncPacket decode(FriendlyByteBuf buffer) {
return new LeashStateSyncPacket(buffer.readInt(), buffer.readNbt());
}
public static void handle(LeashStateSyncPacket msg, Supplier<NetworkEvent.Context> ctx) {
ctx.get().enqueueWork(() -> {
ClientLevel level = Minecraft.getInstance().level;
if (level != null) {
Entity entity = level.getEntity(msg.entityId);
if (entity != null) {
entity.getCapability(CapabilityHandler.LEASH_STATE_CAP).ifPresent(cap -> {
// 只在数据确实变化时更新
CompoundTag current = cap.serializeNBT();
if (!current.equals(msg.leashState)) {
cap.deserializeNBT(msg.leashState);//更新
}
});
}
}
});
ctx.get().setPacketHandled(true);
}
}

View File

@ -0,0 +1,36 @@
/*
* 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.util.capability;
import net.minecraft.world.entity.Entity;
import org.jetbrains.annotations.NotNull;
import top.r3944realms.superleadrope.content.capability.CapabilityHandler;
import top.r3944realms.superleadrope.content.capability.inter.ILeashData;
import top.r3944realms.superleadrope.content.capability.inter.ILeashState;
import java.util.Objects;
import java.util.Optional;
public class LeashUtil {
public static Optional<ILeashData> getLeashData(@NotNull Entity entity) {
Objects.requireNonNull(entity, "Entity cannot be null");
return entity.getCapability(CapabilityHandler.LEASH_DATA_CAP).resolve();
}
public static Optional<ILeashState> getLeashState(@NotNull Entity entity) {
Objects.requireNonNull(entity, "Entity cannot be null");
return entity.getCapability(CapabilityHandler.LEASH_STATE_CAP).resolve();
}
}

View File

@ -0,0 +1,33 @@
/*
* 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.util.nbt;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.phys.Vec3;
public class NBTReader {
public static Vec3 readVec3(CompoundTag nbt) {
if (nbt.contains("X") && nbt.contains("Y") && nbt.contains("Z")) {
return new Vec3(
nbt.getDouble("X"),
nbt.getDouble("Y"),
nbt.getDouble("Z")
);
} else {
throw new IllegalArgumentException("NBT is missing X, Y, or Z value for Vec3");
}
}
}

View File

@ -0,0 +1,31 @@
/*
* 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.util.nbt;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.phys.Vec3;
public class NBTWriter {
public static CompoundTag writeVec3(Vec3 vec) {
CompoundTag nbt = new CompoundTag();
if (vec == null) throw new IllegalArgumentException("Vec3 cannot be null");
nbt.putDouble("X", vec.x);
nbt.putDouble("Y", vec.y);
nbt.putDouble("Z", vec.z);
return nbt;
}
}

View File

@ -15,6 +15,9 @@
package top.r3944realms.superleadrope.util.riding;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.TagKey;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import top.r3944realms.superleadrope.config.LeashCommonConfig;
@ -26,17 +29,34 @@ public class RidingValidator {
/**
* 是否在配置白名单里
*/
@SuppressWarnings("deprecation")
public static boolean isInWhitelist(EntityType<?> type) {
//noinspection deprecation
String key = type.builtInRegistryHolder().key().location().toString();
String modid = key.split(":")[0];
for (String entry : LeashCommonConfig.COMMON.teleportWhitelist.get()) {
if (entry.startsWith("#")) {
if (modid.equals(entry.substring(1))) {
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 (type.builtInRegistryHolder().is(tag)) {
return true;
}
}
} else {
// Case 3: modid:entity_name allow a specific entity
if (entry.equals(key)) {
return true;
}
} else if (entry.equals(key)) {
return true;
}
}
return false;