753 lines
32 KiB
Java
753 lines
32 KiB
Java
/*
|
||
* 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;
|
||
|
||
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;
|
||
|
||
/**
|
||
* 预期行为
|
||
* <table border="1">
|
||
* <thead>
|
||
* <tr>
|
||
* <th>场景</th>
|
||
* <th>行为</th>
|
||
* </tr>
|
||
* </thead>
|
||
* <tbody>
|
||
* <tr>
|
||
* <th>距离 ≤ maxDistance</th>
|
||
* <th>正常弹性拉力,重置 keepLeashTicks 为最大值</th>
|
||
* </tr>
|
||
* <tr>
|
||
* <th>maxDistance < distance ≤ 2*maxDistance</th>
|
||
* <th>增强拉力,并减少 keepLeashTicks(每Tick减1)</th>
|
||
* </tr>
|
||
* <tr>
|
||
* <th>distance > 2*maxDistance && keepLeashTicks > 0</th>
|
||
* <th>施加更强拉力并减少Tick</th>
|
||
* </tr>
|
||
* <tr>
|
||
* <th>distance > 2*maxDistance && keepLeashTicks == 0</th>
|
||
* <th>立即断裂</th>
|
||
* </tr>
|
||
* </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 = 3;//一个实体最多链接多少个拴绳 //TODO:未来可配置
|
||
private final Entity entity;
|
||
private boolean needsSync = false;
|
||
private long lastSyncTime;
|
||
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;
|
||
}
|
||
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<UUID, LeashInfo> uuidLeashInfoEntry : leashHolders.entrySet()) {
|
||
internalUUIDApplyLeashForces(uuidLeashInfoEntry);
|
||
}
|
||
for (Map.Entry<BlockPos, LeashInfo> blockPosLeashInfoEntry : leashKnots.entrySet()) {
|
||
internalBlockPosApplyLeashForce(blockPosLeashInfoEntry);
|
||
}
|
||
}
|
||
|
||
private void internalUUIDApplyLeashForces(Map.Entry<UUID, LeashInfo> 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<BlockPos, LeashInfo> 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<LeashInfo> getAllLeashes() {
|
||
Collection<LeashInfo> 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<LeashInfo> getLeashInfo(Entity holder) {
|
||
return holder instanceof SuperLeashKnotEntity superLeashKnotEntity ?
|
||
getLeashInfo(superLeashKnotEntity.getPos()) :
|
||
getLeashInfo(holder.getUUID());
|
||
}
|
||
|
||
@Override
|
||
public Optional<LeashInfo> getLeashInfo(UUID holderUUID) {
|
||
return Optional.ofNullable(leashHolders.get(holderUUID));
|
||
}
|
||
|
||
@Override
|
||
public Optional<LeashInfo> 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<Entity> leashableInArea(Level pLevel, Vec3 pPos, Predicate<Entity> filter) {
|
||
return leashableInArea(pLevel, pPos, filter, 1024D);
|
||
}
|
||
public static @NotNull List<Entity> leashableInArea(@NotNull Level pLevel, Vec3 pPos, Predicate<Entity> 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<Entity> leashableInArea(@NotNull Entity entity, Predicate<Entity> filter, double fetchDistance) {
|
||
return leashableInArea(entity.level(), entity.getBoundingBox().getCenter(), filter, fetchDistance);
|
||
}
|
||
public static @NotNull List<Entity> leashableInArea(Entity entity, Predicate<Entity> filter) {
|
||
return leashableInArea(entity, filter, 1024D);
|
||
}
|
||
public boolean canBeAttachedTo(Entity pEntity) {
|
||
if(pEntity == entity) {
|
||
return false;
|
||
} else {
|
||
Optional<LeashInfo> 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);
|
||
}
|
||
|
||
|
||
}
|