/* * Super Lead rope mod * Copyright (C) 2025 R3944Realms * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package top.r3944realms.superleadrope.content.capability; import net.minecraft.core.BlockPos; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.LivingEntity; 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; 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.inter.ILeashDataCapability; import top.r3944realms.superleadrope.content.entity.SuperLeashKnotEntity; import top.r3944realms.superleadrope.network.NetworkHandler; import top.r3944realms.superleadrope.network.toClient.LeashDataSyncPacket; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Predicate; /** * 预期行为 * * * * * * * * * * * * * * * * * * * * * * * * * *
场景行为
距离 ≤ maxDistance正常弹性拉力,重置 keepLeashTicks 为最大值
maxDistance < distance ≤ 2*maxDistance增强拉力,并减少 keepLeashTicks(每Tick减1)
distance > 2*maxDistance && keepLeashTicks > 0施加更强拉力并减少Tick
distance > 2*maxDistance && keepLeashTicks == 0立即断裂
*/ 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 = 3;//一个实体最多链接多少个拴绳 //TODO:未来可配置 private final Entity entity; private boolean needsSync = false; private long lastSyncTime; private final Map leashHolders = new ConcurrentHashMap<>(); // 引入解决 绳结不保存导致第二进入持有者不存在的问题 private final Map leashKnots = new ConcurrentHashMap<>(); private CompoundTag lastSyncedData = new CompoundTag(); public LeashDataImpl(Entity entity) { this.entity = entity; } private void markForSync() { if (!entity.level().isClientSide) { needsSync = true; immediateSync(); } } private void immediateSync() { NetworkHandler.INSTANCE.send( PacketDistributor.TRACKING_ENTITY_AND_SELF.with(() -> entity), new LeashDataSyncPacket(entity.getId(), serializeNBT()) ); lastSyncTime = System.currentTimeMillis(); needsSync = false; } public void sync() { if (!needsSync || entity.level().isClientSide) return; CompoundTag currentData = serializeNBT(); if (!currentData.equals(lastSyncedData)) { NetworkHandler.INSTANCE.send( PacketDistributor.TRACKING_ENTITY_AND_SELF.with(() -> entity), new LeashDataSyncPacket(entity.getId(), currentData) ); lastSyncTime = System.currentTimeMillis(); lastSyncedData = currentData; needsSync = false; } } // 定期同步检查 public void checkSync() { if (!needsSync) return; // 距离上次同步超过0.1秒才同步 if (System.currentTimeMillis() - lastSyncTime > 100) { sync(); } } // 添加拴绳(支持自定义最大长度和弹性距离) @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; } @Override public boolean addLeash(Entity holder, ItemStack leashStack, double maxDistance, double elasticDistance, int maxKeepLeashTicks) { boolean isSuperKnot = holder instanceof SuperLeashKnotEntity; if (!canBeLeashed() || (!isSuperKnot && leashHolders.containsKey(holder.getUUID()) || (isSuperKnot && leashKnots.containsKey(((SuperLeashKnotEntity) holder).getPos())))) { return false; } LeashInfo info = LeashInfo.CreateLeashInfo( holder, leashStack.getItem().getDescription().toString(), calculateAttachOffset(entity), maxDistance, elasticDistance, maxKeepLeashTicks, maxKeepLeashTicks ); if (holder instanceof SuperLeashKnotEntity s) { leashKnots.put(s.getPos(), info); } else leashHolders.put(holder.getUUID(), info); markForSync(); return true; } @Override public boolean setMaxDistance(Entity holder, double newMaxDistance) { return holder instanceof SuperLeashKnotEntity superLeashKnotEntity ? setMaxDistance(superLeashKnotEntity.getPos(), newMaxDistance) : setMaxDistance(holder.getUUID(), newMaxDistance); } @Override public boolean setMaxDistance(Entity holder, double newMaxDistance, int newMaxKeepLeashTicks) { return holder instanceof SuperLeashKnotEntity superLeashKnotEntity ? setMaxDistance(superLeashKnotEntity.getPos(), newMaxDistance, newMaxKeepLeashTicks) : setMaxDistance(holder.getUUID(), newMaxDistance, newMaxKeepLeashTicks); } // 动态修改最大拴绳长度 @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(), newMaxDistance, info.elasticDistance(), // 保持原有弹性距离 info.keepLeashTicks(), info.maxKeepLeashTicks() )); markForSync(); return true; } @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(), 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(), newMaxKeepLeashTicks )); markForSync(); return true; } @Override public boolean setElasticDistance(Entity holder, double newElasticDistance) { return holder instanceof SuperLeashKnotEntity superLeashKnotEntity ? setElasticDistance(superLeashKnotEntity.getPos(), newElasticDistance) : setElasticDistance(holder.getUUID(), newElasticDistance); } // 动态修改弹性距离 @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(), newElasticDistance, info.keepLeashTicks(), info.maxKeepLeashTicks() )); markForSync(); return true; } @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(), newElasticDistance, info.keepLeashTicks(), info.maxKeepLeashTicks() )); markForSync(); return true; } @Override public boolean setElasticDistance(Entity holder, double newElasticDistance, int newMaxKeepLeashTicks) { return holder instanceof SuperLeashKnotEntity superLeashKnotEntity ? setElasticDistance(superLeashKnotEntity.getPos(), newElasticDistance, newMaxKeepLeashTicks) : 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; } @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(), newElasticDistance, Math.min(info.keepLeashTicks(), newMaxKeepLeashTicks), // 限制剩余Tick不超过新最大值 newMaxKeepLeashTicks )); return true; } // 计算拴绳拉力(防抖动逻辑) @Override public void applyLeashForces() { for (Map.Entry uuidLeashInfoEntry : leashHolders.entrySet()) { internalUUIDApplyLeashForces(uuidLeashInfoEntry); } for (Map.Entry blockPosLeashInfoEntry : leashKnots.entrySet()) { internalBlockPosApplyLeashForce(blockPosLeashInfoEntry); } } private void internalUUIDApplyLeashForces(Map.Entry entry) { UUID uuid = entry.getKey(); Entity uuidHolder = ((ServerLevel) entity.level()).getEntity(uuid); if (uuidHolder != null) { internalApplyLeashForces(uuidHolder, entry); } else { SuperLeadRope.logger.error("Could not apply leash forces for {}, because it is not found.", uuid); } } private void internalBlockPosApplyLeashForce(Map.Entry entry) { SuperLeashKnotEntity orCreateKnot = SuperLeashKnotEntity.getOrCreateKnot(entity.level(), entry.getKey()); internalApplyLeashForces(orCreateKnot, entry); } private void internalApplyLeashForces(Entity holder, Map.Entry entry) { Vec3 holderPos = holder.position().add(0, holder.getBbHeight() * 0.7, 0); 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; // 1. 检查是否超出断裂距离 if (distance > extremeSnapDist) { // 如果还有剩余缓冲Tick,施加更强拉力并减少计数 if (info.keepLeashTicks() > 0) { // 计算临界拉力(距离越远,拉力越强) Vec3 pullForce = calculateCriticalPullForce(holderPos, entityPos, distance, info); entity.setDeltaMovement(entity.getDeltaMovement().add(pullForce)); entity.hurtMarked = true; entry.setValue(info.decrementKeepLeashTicks()); return; } // 否则立即断裂 removeLeash(holder); return; } // 2. 正常弹性拉力逻辑(保持不变) if (distance > info.elasticDistance()) { Vec3 pullForce = calculatePullForce(holderPos, entityPos, distance, info); entity.setDeltaMovement(entity.getDeltaMovement().add(pullForce)); entity.hurtMarked = true; } // 3. 重置缓冲Tick(如果回到安全距离) if (distance <= info.maxDistance() && info.keepLeashTicks() < info.maxKeepLeashTicks()) { entry.setValue(info.resetKeepLeashTicks()); } } @Contract("_, _, _, _ -> new") private @NotNull Vec3 calculatePullForce(@NotNull Vec3 holderPos, Vec3 entityPos, double distance, @NotNull LeashInfo info) { Vec3 pullDirection = holderPos.subtract(entityPos).normalize(); double pullStrength = 0.2; // 增强拉力(如果超出maxDistance但未达断裂距离) if (distance > info.maxDistance()) { double excessRatio = (distance - info.maxDistance()) / (info.maxDistance()); pullStrength += excessRatio * 0.8; // 最高1.0倍基础拉力 } Vec3 pullForce = pullDirection.scale( (distance - info.elasticDistance()) * pullStrength * SPRING_DAMPENING ); return new Vec3( pullForce.x * AXIS_SPECIFIC_ELASTICITY.x, pullForce.y * AXIS_SPECIFIC_ELASTICITY.y, pullForce.z * AXIS_SPECIFIC_ELASTICITY.z ); } private @NotNull Vec3 calculateCriticalPullForce(@NotNull Vec3 holderPos, Vec3 entityPos, double distance, @NotNull LeashInfo info) { Vec3 pullDirection = holderPos.subtract(entityPos).normalize(); // 非线性增强拉力(距离越远,拉力越强) double excessRatio = (distance - info.maxDistance()) / (info.maxDistance()); double pullStrength = 1.0 + excessRatio * 2.0; // 基础1.0 + 额外增强(最高3.0倍) Vec3 pullForce = pullDirection.scale( (distance - info.elasticDistance()) * pullStrength * SPRING_DAMPENING ); // 应用轴向弹性系数(减少Y轴抖动) return new Vec3( pullForce.x * AXIS_SPECIFIC_ELASTICITY.x, pullForce.y * AXIS_SPECIFIC_ELASTICITY.y, pullForce.z * AXIS_SPECIFIC_ELASTICITY.z ); } // 移除拴绳绑定 @Override public boolean removeLeash(Entity holder) { return holder instanceof SuperLeashKnotEntity superLeashKnotEntity ? removeLeash(superLeashKnotEntity.getPos()) : removeLeash(holder.getUUID()); } @Override public boolean removeLeash(UUID holderUUID) { boolean removed = leashHolders.remove(holderUUID) != null; if (removed) markForSync(); return removed; } @Override public boolean removeLeash(BlockPos knotPos) { boolean removed = leashKnots.remove(knotPos) != null; if (removed) markForSync(); return removed; } @Override public boolean transferLeash(Entity holder, Entity newHolder) { return holder instanceof SuperLeashKnotEntity superLeashKnotEntity ? transferLeash(superLeashKnotEntity.getPos(), newHolder) : transferLeash(holder.getUUID(), newHolder); } @Override public boolean transferLeash(Entity holder, Entity newHolder, ItemStack stack) { return holder instanceof SuperLeashKnotEntity superLeashKnotEntity ? transferLeash(superLeashKnotEntity.getPos(), newHolder, stack) : transferLeash(holder.getUUID(), newHolder, stack); } // 将拴绳持有者转移到新实体(非拴绳结 -> 任意) @Override public boolean transferLeash(UUID oldHolderUUID, Entity newHolder) { LeashInfo info = leashHolders.remove(oldHolderUUID); if (info == null || newHolder == null) return false; if(newHolder instanceof SuperLeashKnotEntity superLeashKnotEntity) { LeashInfo leashInfo = info.transferHolder(superLeashKnotEntity); leashKnots.put(superLeashKnotEntity.getPos(), leashInfo); } else { LeashInfo leashInfo = info.transferHolder(newHolder); leashHolders.put(newHolder.getUUID(), leashInfo); } markForSync(); return true; } @Override public boolean transferLeash(UUID oldHolderUUID, Entity newHolder, ItemStack stack) { LeashInfo info = leashHolders.remove(oldHolderUUID); if (info == null || newHolder == null) return false; if(newHolder instanceof SuperLeashKnotEntity superLeashKnotEntity) { LeashInfo leashInfo = info.transferHolder(superLeashKnotEntity, stack.getDescriptionId()); leashKnots.put(superLeashKnotEntity.getPos(), leashInfo); } else { LeashInfo leashInfo = info.transferHolder(newHolder, stack.getDescriptionId()); leashHolders.put(newHolder.getUUID(), leashInfo); } markForSync(); return true; } @Override public boolean transferLeash(BlockPos knotPos, Entity newHolder) { LeashInfo info = leashKnots.remove(knotPos); if (info == null || newHolder == null) return false; if(newHolder instanceof SuperLeashKnotEntity superLeashKnotEntity) { LeashInfo leashInfo = info.transferHolder(superLeashKnotEntity); leashKnots.put(superLeashKnotEntity.getPos(), leashInfo); } else { LeashInfo leashInfo = info.transferHolder(newHolder); leashHolders.put(newHolder.getUUID(), leashInfo); } markForSync(); return true; } @Override public boolean transferLeash(BlockPos knotPos, Entity newHolder, ItemStack stack) { LeashInfo info = leashKnots.remove(knotPos); if (info == null || newHolder == null) return false; if(newHolder instanceof SuperLeashKnotEntity superLeashKnotEntity) { LeashInfo leashInfo = info.transferHolder(superLeashKnotEntity, stack.getDescriptionId()); leashKnots.put(superLeashKnotEntity.getPos(), leashInfo); } else { LeashInfo leashInfo = info.transferHolder(newHolder, stack.getDescriptionId()); leashHolders.put(newHolder.getUUID(), leashInfo); } markForSync(); return true; } //只能系在这些实体上,在这里,其它情况一律忽略 //TODO: 标签支持控制 public static boolean isLeashable(Entity entity) { return entity instanceof LivingEntity || entity instanceof Boat || entity instanceof Minecart; } // 获取所有拴绳信息 @Override public Collection getAllLeashes() { Collection values = leashHolders.values(); values.addAll(leashKnots.values()); return values; } @Override public boolean isLeashedBy(Entity holder) { return holder instanceof SuperLeashKnotEntity superLeashKnotEntity ? isLeashedBy(superLeashKnotEntity.getPos()) : isLeashedBy(holder.getUUID()); } // 检查是否被特定实体拴住 @Override public boolean isLeashedBy(UUID holderUUID) { return leashHolders.containsKey(holderUUID); } @Override public boolean isLeashedBy(BlockPos knotPos) { return leashKnots.containsKey(knotPos); } @Override public Optional getLeashInfo(Entity holder) { return holder instanceof SuperLeashKnotEntity superLeashKnotEntity ? getLeashInfo(superLeashKnotEntity.getPos()) : getLeashInfo(holder.getUUID()); } @Override public Optional getLeashInfo(UUID holderUUID) { return Optional.ofNullable(leashHolders.get(holderUUID)); } @Override public Optional getLeashInfo(BlockPos knotPos) { return Optional.ofNullable(leashKnots.get(knotPos)); } @Override public CompoundTag serializeNBT() { CompoundTag tag = new CompoundTag(); ListTag holdersList = new ListTag(); for (LeashInfo info : leashHolders.values()) { CompoundTag infoTag = generateCompoundTagFromUUIDLeashInfo(info); holdersList.add(infoTag); } for (LeashInfo info : leashKnots.values()) { CompoundTag infoTag = generateCompoundTagFromBlockPosLeashInfo(info); holdersList.add(infoTag); } tag.put("LeashHolders", holdersList); return tag; } private static @NotNull CompoundTag generateCompoundTagFromUUIDLeashInfo(@NotNull LeashInfo info) { CompoundTag infoTag = new CompoundTag(); if (info.holderUUIDOpt().isEmpty()) { throw new IllegalArgumentException("LeashInfo.holderUUIDOpt is empty"); } infoTag.putUUID("HolderUUID", info.holderUUIDOpt().get()); return getCommonCompoundTag(info, infoTag); } private static @NotNull CompoundTag generateCompoundTagFromBlockPosLeashInfo(@NotNull LeashInfo info) { CompoundTag infoTag = new CompoundTag(); if (info.blockPosOpt().isEmpty()) { 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()); return getCommonCompoundTag(info, infoTag); } private static @NotNull CompoundTag getCommonCompoundTag(@NotNull LeashInfo info, CompoundTag infoTag) { if(info.holderIdOpt().isEmpty()) { throw new IllegalArgumentException("LeashInfo.intId is empty"); } 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.putDouble("MaxDistance", info.maxDistance()); infoTag.putDouble("ElasticDistance", info.elasticDistance()); infoTag.putInt("KeepLeashTicks", info.keepLeashTicks()); infoTag.putInt("MaxKeepLeashTicks", info.maxKeepLeashTicks()); 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); for (int i = 0; i < holdersList.size(); i++) { CompoundTag infoTag = holdersList.getCompound(i); if (infoTag.contains("HolderUUID")) { LeashInfo uuidLeashDataFormListTag = getUUIDLeashDataFormListTag(infoTag); leashHolders.put(uuidLeashDataFormListTag.holderUUIDOpt().orElseThrow(), uuidLeashDataFormListTag); } else { LeashInfo blockPosLeashDataFormListTag = getBlockPosLeashDataFormListTag(infoTag); leashKnots.put(blockPosLeashDataFormListTag.blockPosOpt().orElseThrow(), blockPosLeashDataFormListTag); } } } } @Override public boolean canBeLeashed() { return leashHolders.size() <= MAX_LEASHES_PER_ENTITY; } @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 leashableInArea(Level pLevel, Vec3 pPos, Predicate filter) { return leashableInArea(pLevel, pPos, filter, 1024D); } public static @NotNull List leashableInArea(@NotNull Level pLevel, Vec3 pPos, Predicate filter, double fetchDistance) { AABB box = AABB.ofSize(pPos, fetchDistance, fetchDistance, fetchDistance); return pLevel.getEntitiesOfClass(Entity.class, box, e -> LeashDataImpl.isLeashable(e) && filter.test(e)); } public static @NotNull List leashableInArea(@NotNull Entity entity, Predicate filter, double fetchDistance) { return leashableInArea(entity.level(), entity.getBoundingBox().getCenter(), filter, fetchDistance); } public static @NotNull List leashableInArea(Entity entity, Predicate filter) { return leashableInArea(entity, filter, 1024D); } public boolean canBeAttachedTo(Entity pEntity) { if(pEntity == entity) { return false; } else { Optional leashInfo = getLeashInfo(pEntity.getUUID()); return leashInfo.isEmpty() && (entity.distanceTo(pEntity) <= LEASH_ELASTIC_DIST * LEASH_EXTREME_SNAP_DIST_FACTOR) && canBeLeashed();//距离最大,则不可以被固定或转移 } } public static boolean isLeashHolder(@NotNull Entity pEntity, UUID pHolderUUID) { AtomicBoolean isTarget = new AtomicBoolean(false); pEntity.getCapability(CapabilityHandler.LEASH_DATA_CAP).ifPresent(i -> { if (i instanceof LeashDataImpl li) { isTarget.set(li.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 -> { if (i instanceof LeashDataImpl li) { isTarget.set(li.isLeashedBy(pKnotPos)); } }); return isTarget.get(); } public static boolean isLeashHolder(@NotNull Entity pEntity, Entity pTestHolder) { return pTestHolder instanceof SuperLeashKnotEntity superLeashKnotEntity ? isLeashHolder(pEntity, superLeashKnotEntity.getPos()) : isLeashHolder(pEntity, pTestHolder.getUUID()); } // 计算拴绳附着点 @Contract("_ -> new") private @NotNull Vec3 calculateAttachOffset(@NotNull Entity entity) { EntityType type = entity.getType(); if (type == EntityType.HORSE || type == EntityType.DONKEY) { return new Vec3(0, 1.4, 0.3); } else if (type == EntityType.IRON_GOLEM) { return new Vec3(0, 1.8, 0); } //TODO: 未来自定义配置 return new Vec3(0, entity.getBbHeight() * 0.8, 0); } }