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