/*
* 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, LeashInfo> 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);
}
}