diff --git a/src/main/java/top/r3944realms/eroticdungeongame/content/entity/npc/NPCServerPlayer.java b/src/main/java/top/r3944realms/eroticdungeongame/content/entity/npc/NPCServerPlayer.java index c98e7eea..5f15dd90 100644 --- a/src/main/java/top/r3944realms/eroticdungeongame/content/entity/npc/NPCServerPlayer.java +++ b/src/main/java/top/r3944realms/eroticdungeongame/content/entity/npc/NPCServerPlayer.java @@ -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); diff --git a/src/main/java/top/r3944realms/eroticdungeongame/content/entity/npc/ai/control/NPCLookControl.java b/src/main/java/top/r3944realms/eroticdungeongame/content/entity/npc/ai/control/NPCLookControl.java index 8ebe3368..77c31ea0 100644 --- a/src/main/java/top/r3944realms/eroticdungeongame/content/entity/npc/ai/control/NPCLookControl.java +++ b/src/main/java/top/r3944realms/eroticdungeongame/content/entity/npc/ai/control/NPCLookControl.java @@ -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); diff --git a/src/main/java/top/r3944realms/eroticdungeongame/content/entity/npc/ai/goal/NPCFindTargetGoal.java b/src/main/java/top/r3944realms/eroticdungeongame/content/entity/npc/ai/goal/NPCFindTargetGoal.java index 894a05ee..bc563ac6 100644 --- a/src/main/java/top/r3944realms/eroticdungeongame/content/entity/npc/ai/goal/NPCFindTargetGoal.java +++ b/src/main/java/top/r3944realms/eroticdungeongame/content/entity/npc/ai/goal/NPCFindTargetGoal.java @@ -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 targetFilter; - // 优先级排序器 - private Comparator targetSorter; + // 优先级排序器(数值越小优先级越高) + private Comparator 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 filter) { this.targetFilter = filter; return this; } - public NPCFindTargetGoal withSorter(Comparator sorter) { - this.targetSorter = sorter; + // 允许自定义优先级排序器(例如:血量最低、威胁值最高等) + public NPCFindTargetGoal withPriorityComparator(Comparator 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 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; // 可根据需要配置 } - -} +} \ No newline at end of file diff --git a/src/main/java/top/r3944realms/eroticdungeongame/content/entity/npc/ai/goal/NPCFollowGoal.java b/src/main/java/top/r3944realms/eroticdungeongame/content/entity/npc/ai/goal/NPCFollowGoal.java index 3a4a1c07..46e42086 100644 --- a/src/main/java/top/r3944realms/eroticdungeongame/content/entity/npc/ai/goal/NPCFollowGoal.java +++ b/src/main/java/top/r3944realms/eroticdungeongame/content/entity/npc/ai/goal/NPCFollowGoal.java @@ -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(); } diff --git a/src/main/java/top/r3944realms/eroticdungeongame/content/entity/npc/ai/navigation/NPCGroupGroundPathNavigation.java b/src/main/java/top/r3944realms/eroticdungeongame/content/entity/npc/ai/navigation/NPCGroupGroundPathNavigation.java index 9c34b365..5c68cc5d 100644 --- a/src/main/java/top/r3944realms/eroticdungeongame/content/entity/npc/ai/navigation/NPCGroupGroundPathNavigation.java +++ b/src/main/java/top/r3944realms/eroticdungeongame/content/entity/npc/ai/navigation/NPCGroupGroundPathNavigation.java @@ -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); } diff --git a/src/main/java/top/r3944realms/eroticdungeongame/content/entity/npc/ai/navigation/NPCPathNavigation.java b/src/main/java/top/r3944realms/eroticdungeongame/content/entity/npc/ai/navigation/NPCPathNavigation.java index 59ea1923..f59c3864 100644 --- a/src/main/java/top/r3944realms/eroticdungeongame/content/entity/npc/ai/navigation/NPCPathNavigation.java +++ b/src/main/java/top/r3944realms/eroticdungeongame/content/entity/npc/ai/navigation/NPCPathNavigation.java @@ -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 targets, int accuracy) { + public Path createPath(@NotNull Stream 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 targets, int regionOffset, boolean offsetUpward, int accuracy, float followRange) { + protected Path createPath(@NotNull Set 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;