feat: nPC可以正常移动
This commit is contained in:
parent
c80bd9dc75
commit
b50fc8d5e4
|
|
@ -258,6 +258,11 @@ public class NPCServerPlayer extends ServerPlayer implements INPCPlayer {
|
|||
return animStep;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSpeed(float speed) {
|
||||
super.setSpeed(speed);
|
||||
this.setZza(speed);
|
||||
}
|
||||
|
||||
protected void updateControlFlags() {
|
||||
boolean flag = !(this.getControllingPassenger() instanceof Mob);
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import net.minecraft.world.entity.Entity;
|
|||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.entity.ai.control.Control;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import top.r3944realms.eroticdungeongame.content.entity.npc.NPCServerPlayer;
|
||||
|
||||
import java.util.Optional;
|
||||
|
|
@ -39,17 +40,17 @@ public class NPCLookControl implements Control {
|
|||
}
|
||||
|
||||
|
||||
public void setLookAt(Vec3 lookVector) {
|
||||
public void setLookAt(@NotNull Vec3 lookVector) {
|
||||
this.setLookAt(lookVector.x, lookVector.y, lookVector.z);
|
||||
}
|
||||
|
||||
|
||||
public void setLookAt(Entity entity) {
|
||||
public void setLookAt(@NotNull Entity entity) {
|
||||
this.setLookAt(entity.getX(), getWantedY(entity), entity.getZ());
|
||||
}
|
||||
|
||||
|
||||
public void setLookAt(Entity entity, float deltaYaw, float deltaPitch) {
|
||||
public void setLookAt(@NotNull Entity entity, float deltaYaw, float deltaPitch) {
|
||||
this.setLookAt(entity.getX(), getWantedY(entity), entity.getZ(), deltaYaw, deltaPitch);
|
||||
}
|
||||
|
||||
|
|
@ -76,11 +77,11 @@ public class NPCLookControl implements Control {
|
|||
|
||||
if (this.lookAtCooldown > 0) {
|
||||
--this.lookAtCooldown;
|
||||
this.getYRotD().ifPresent((p_287447_) -> {
|
||||
this.npc.yHeadRot = this.rotateTowards(this.npc.yHeadRot, p_287447_, this.yMaxRotSpeed);
|
||||
this.getYRotD().ifPresent((aFloat) -> {
|
||||
this.npc.yHeadRot = this.rotateTowards(this.npc.yHeadRot, aFloat, this.yMaxRotSpeed);
|
||||
});
|
||||
this.getXRotD().ifPresent((p_289400_) -> {
|
||||
this.npc.setXRot(this.rotateTowards(this.npc.getXRot(), p_289400_, this.xMaxRotAngle));
|
||||
this.getXRotD().ifPresent((aFloat) -> {
|
||||
this.npc.setXRot(this.rotateTowards(this.npc.getXRot(), aFloat, this.xMaxRotAngle));
|
||||
});
|
||||
} else {
|
||||
this.npc.yHeadRot = this.rotateTowards(this.npc.yHeadRot, this.npc.yBodyRot, 10.0F);
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import top.r3944realms.eroticdungeongame.content.entity.npc.NPCServerPlayer;
|
|||
|
||||
import java.util.Comparator;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public class NPCFindTargetGoal extends Goal {
|
||||
|
|
@ -35,8 +36,8 @@ public class NPCFindTargetGoal extends Goal {
|
|||
// 目标过滤条件
|
||||
private Predicate<LivingEntity> targetFilter;
|
||||
|
||||
// 优先级排序器
|
||||
private Comparator<LivingEntity> targetSorter;
|
||||
// 优先级排序器(数值越小优先级越高)
|
||||
private Comparator<LivingEntity> priorityComparator;
|
||||
|
||||
public NPCFindTargetGoal(NPCServerPlayer npc, double searchRadius, int cooldown) {
|
||||
this.npc = npc;
|
||||
|
|
@ -50,44 +51,50 @@ public class NPCFindTargetGoal extends Goal {
|
|||
entity.isAlive() &&
|
||||
entity instanceof Player player && !player.isCreative();
|
||||
|
||||
// 默认排序:最近优先
|
||||
this.targetSorter = Comparator.comparingDouble(npc::distanceToSqr);
|
||||
// 默认优先级:距离最近优先级最高(距离越小优先级越高)
|
||||
this.priorityComparator = Comparator.comparingDouble(npc::distanceToSqr);
|
||||
}
|
||||
|
||||
// 允许自定义过滤和排序
|
||||
// 允许自定义过滤条件
|
||||
public NPCFindTargetGoal withFilter(Predicate<LivingEntity> filter) {
|
||||
this.targetFilter = filter;
|
||||
return this;
|
||||
}
|
||||
|
||||
public NPCFindTargetGoal withSorter(Comparator<LivingEntity> sorter) {
|
||||
this.targetSorter = sorter;
|
||||
// 允许自定义优先级排序器(例如:血量最低、威胁值最高等)
|
||||
public NPCFindTargetGoal withPriorityComparator(Comparator<LivingEntity> comparator) {
|
||||
this.priorityComparator = comparator;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canUse() {
|
||||
if (npc.getTarget() != null) return false;
|
||||
if (cooldownTicks > 0) return false;
|
||||
// 冷却中不寻找
|
||||
if (cooldownTicks-- > 0) return false;
|
||||
|
||||
// 寻找有效目标
|
||||
this.target = findNearestTarget();
|
||||
// 寻找当前优先级最高的目标
|
||||
this.target = findHighestPriorityTarget();
|
||||
|
||||
// 找到目标后立即返回 true,准备设置目标
|
||||
return this.target != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 一旦找到目标,启动后立即停止,不持续占用该 Goal。
|
||||
* 这样 NPC 每次只会执行一次“寻找最高优先级目标”的动作,
|
||||
* 然后由其他 Goal(如攻击、跟随)来持续处理该目标。
|
||||
*/
|
||||
@Override
|
||||
public boolean canContinueToUse() {
|
||||
// 继续条件:目标存在且存活,且没有更紧急的目标
|
||||
return target != null &&
|
||||
target.isAlive() &&
|
||||
npc.getTarget() == target &&
|
||||
!hasHigherPriorityTarget();
|
||||
return false; // 找到目标后立即结束,不持续运行
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
if (target != null) {
|
||||
// 设置 NPC 的攻击目标
|
||||
npc.setTarget(target);
|
||||
// 重置冷却
|
||||
cooldownTicks = cooldown;
|
||||
}
|
||||
}
|
||||
|
|
@ -95,56 +102,62 @@ public class NPCFindTargetGoal extends Goal {
|
|||
@Override
|
||||
public void stop() {
|
||||
// 不清除 target,让其他 Goal 决定是否保留
|
||||
cooldownTicks = cooldown;
|
||||
// 只是本 Goal 停止运行
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick() {
|
||||
// 由于 canContinueToUse 返回 false,tick 实际上不会被持续调用
|
||||
// 但为了保险,依然保留冷却递减逻辑
|
||||
if (cooldownTicks > 0) {
|
||||
cooldownTicks--;
|
||||
}
|
||||
|
||||
// 定期验证目标是否仍然有效
|
||||
if (npc.tickCount % 20 == 0 && target != null && !isValidTarget(target)) {
|
||||
npc.setTarget(null);
|
||||
target = null;
|
||||
}
|
||||
}
|
||||
|
||||
private LivingEntity findNearestTarget() {
|
||||
return npc.level().getEntitiesOfClass(
|
||||
LivingEntity.class,
|
||||
npc.getBoundingBox().inflate(searchRadius),
|
||||
this::isValidTarget
|
||||
).stream()
|
||||
.min(targetSorter)
|
||||
/**
|
||||
* 查找当前范围内优先级最高的有效目标。
|
||||
* 使用 priorityComparator 排序,取第一个(优先级最高)。
|
||||
*/
|
||||
private LivingEntity findHighestPriorityTarget() {
|
||||
List<? extends LivingEntity> candidates = npc.level().getEntitiesOfClass(
|
||||
LivingEntity.class,
|
||||
npc.getBoundingBox().inflate(searchRadius),
|
||||
this::isValidTarget
|
||||
);
|
||||
|
||||
if (candidates.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 根据优先级排序器找出优先级最高的目标
|
||||
// 注意:Comparator 定义的是“优先级高”的排序规则(例如距离近在前、血量低在前)
|
||||
return candidates.stream()
|
||||
.min(priorityComparator)
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断实体是否为有效目标。
|
||||
*/
|
||||
private boolean isValidTarget(LivingEntity entity) {
|
||||
// 基础检查
|
||||
if (entity == npc) return false;
|
||||
if (!entity.isAlive()) return false;
|
||||
if (entity.isRemoved()) return false;
|
||||
|
||||
// 视线检查(性能消耗较大)
|
||||
// 视线检查(可选,性能消耗较大)
|
||||
if (requiresLineOfSight() && !npc.hasLineOfSight(entity)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 应用过滤器
|
||||
// 应用自定义过滤器
|
||||
return targetFilter.test(entity);
|
||||
}
|
||||
|
||||
private boolean hasHigherPriorityTarget() {
|
||||
// 检查是否有更高优先级
|
||||
return npc.getLastHurtByMob() != null &&
|
||||
npc.getLastHurtByMob().isAlive() &&
|
||||
npc.distanceToSqr(npc.getLastHurtByMob()) < searchRadius * searchRadius;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否需要进行视线检查。
|
||||
*/
|
||||
private boolean requiresLineOfSight() {
|
||||
return true; // 可配置
|
||||
return true; // 可根据需要配置
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -38,22 +38,28 @@ public class NPCFollowGoal extends Goal {
|
|||
@Override
|
||||
public boolean canUse() {
|
||||
this.target = npc.getTarget();
|
||||
return target != null && npc.distanceToSqr(target) > stopDistance;
|
||||
return target != null && target.isAlive() && npc.distanceToSqr(target) > stopDistance;
|
||||
}
|
||||
@Override
|
||||
public boolean canContinueToUse() {
|
||||
return target != null && npc.distanceToSqr(target) > followDistance;
|
||||
return target != null && target.isAlive() && npc.distanceToSqr(target) > followDistance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
npc.getNavigation().moveTo(target, speedModifier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
npc.setTarget(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick() {
|
||||
npc.getLookControl().setLookAt(target, 30.0f, 30.0f);
|
||||
if (npc.distanceToSqr(target) > stopDistance) {
|
||||
npc.getNavigation().moveTo(target, speedModifier);
|
||||
npc.getNavigation().moveTo(target, speedModifier*5);
|
||||
} else {
|
||||
npc.getNavigation().stop();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import net.minecraft.world.level.block.Blocks;
|
|||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.pathfinder.*;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import top.r3944realms.eroticdungeongame.content.entity.npc.NPCServerPlayer;
|
||||
import top.r3944realms.eroticdungeongame.content.entity.npc.ai.navigation.pathfinder.NPCPathFinder;
|
||||
import top.r3944realms.eroticdungeongame.content.entity.npc.ai.navigation.pathfinder.NPCWalkNodeEvaluator;
|
||||
|
|
@ -84,7 +85,7 @@ public class NPCGroupGroundPathNavigation extends NPCPathNavigation {
|
|||
/**
|
||||
* Returns a path to the given entity or null
|
||||
*/
|
||||
public Path createPath(Entity entity, int accuracy) {
|
||||
public Path createPath(@NotNull Entity entity, int accuracy) {
|
||||
return this.createPath(entity.blockPosition(), accuracy);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import net.minecraft.world.level.block.state.BlockState;
|
|||
import net.minecraft.world.level.pathfinder.*;
|
||||
import net.minecraft.world.phys.HitResult;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import top.r3944realms.eroticdungeongame.content.entity.npc.NPCServerPlayer;
|
||||
import top.r3944realms.eroticdungeongame.content.entity.npc.ai.navigation.pathfinder.NPCNodeEvaluator;
|
||||
|
|
@ -126,7 +127,7 @@ public abstract class NPCPathNavigation {
|
|||
* Returns a path to one of the elements of the stream or null
|
||||
*/
|
||||
@Nullable
|
||||
public Path createPath(Stream<BlockPos> targets, int accuracy) {
|
||||
public Path createPath(@NotNull Stream<BlockPos> targets, int accuracy) {
|
||||
return this.createPath(targets.collect(Collectors.toSet()), 8, false, accuracy);
|
||||
}
|
||||
|
||||
|
|
@ -152,7 +153,7 @@ public abstract class NPCPathNavigation {
|
|||
* Returns a path to the given entity or null
|
||||
*/
|
||||
@Nullable
|
||||
public Path createPath(Entity entity, int accuracy) {
|
||||
public Path createPath(@NotNull Entity entity, int accuracy) {
|
||||
return this.createPath(ImmutableSet.of(entity.blockPosition()), 16, true, accuracy);
|
||||
}
|
||||
|
||||
|
|
@ -165,7 +166,7 @@ public abstract class NPCPathNavigation {
|
|||
}
|
||||
|
||||
@Nullable
|
||||
protected Path createPath(Set<BlockPos> targets, int regionOffset, boolean offsetUpward, int accuracy, float followRange) {
|
||||
protected Path createPath(@NotNull Set<BlockPos> targets, int regionOffset, boolean offsetUpward, int accuracy, float followRange) {
|
||||
if (targets.isEmpty()) {
|
||||
return null;
|
||||
} else if (this.npc.getY() < (double)this.level.getMinBuildHeight()) {
|
||||
|
|
@ -222,7 +223,7 @@ public abstract class NPCPathNavigation {
|
|||
return false;
|
||||
} else {
|
||||
this.trimPath();
|
||||
if (this.path.getNodeCount() <= 0) {
|
||||
if (this.path != null && this.path.getNodeCount() <= 0) {
|
||||
return false;
|
||||
} else {
|
||||
this.speedModifier = speed;
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user