feat: nPC AI 编写(未完成)
This commit is contained in:
parent
ea11aa3895
commit
c80bd9dc75
|
|
@ -16,5 +16,14 @@
|
|||
|
||||
package top.r3944realms.eroticdungeongame.content.entity.npc;
|
||||
|
||||
public interface INPCPlayer {
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.entity.Targeting;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public interface INPCPlayer extends Targeting {
|
||||
@Nullable
|
||||
@Override
|
||||
default LivingEntity getTarget() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package top.r3944realms.eroticdungeongame.content.entity.npc;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import dev.dubhe.curtain.utils.Messenger;
|
||||
import net.minecraft.core.BlockPos;
|
||||
|
|
@ -35,20 +36,35 @@ import net.minecraft.server.players.GameProfileCache;
|
|||
import net.minecraft.world.damagesource.DamageSource;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.EquipmentSlot;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.entity.Mob;
|
||||
import net.minecraft.world.entity.ai.goal.Goal;
|
||||
import net.minecraft.world.entity.ai.goal.GoalSelector;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.entity.vehicle.Boat;
|
||||
import net.minecraft.world.food.FoodData;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.GameType;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.entity.SkullBlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.pathfinder.BlockPathTypes;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import top.r3944realms.eroticdungeongame.EroticDungeon;
|
||||
import top.r3944realms.eroticdungeongame.content.entity.npc.ai.control.NPCBodyRotationControl;
|
||||
import top.r3944realms.eroticdungeongame.content.entity.npc.ai.control.NPCJumpControl;
|
||||
import top.r3944realms.eroticdungeongame.content.entity.npc.ai.control.NPCLookControl;
|
||||
import top.r3944realms.eroticdungeongame.content.entity.npc.ai.control.NPCMoveControl;
|
||||
import top.r3944realms.eroticdungeongame.content.entity.npc.ai.goal.NPCFindTargetGoal;
|
||||
import top.r3944realms.eroticdungeongame.content.entity.npc.ai.goal.NPCFollowGoal;
|
||||
import top.r3944realms.eroticdungeongame.content.entity.npc.ai.navigation.NPCGroupGroundPathNavigation;
|
||||
import top.r3944realms.eroticdungeongame.content.entity.npc.ai.navigation.NPCPathNavigation;
|
||||
import top.r3944realms.eroticdungeongame.core.network.NPCEmptyClientConnection;
|
||||
import top.r3944realms.eroticdungeongame.util.IEDGMinecraftServer;
|
||||
import top.r3944realms.lib39.util.nbt.NBTWriter;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
|
|
@ -56,6 +72,20 @@ public class NPCServerPlayer extends ServerPlayer implements INPCPlayer {
|
|||
public static final String PREFIX = "[NPC]";
|
||||
public String npcNameWithoutPrefix;
|
||||
|
||||
@Nullable
|
||||
private LivingEntity target;
|
||||
private final GoalSelector goalSelector;
|
||||
private final GoalSelector targetSelector;
|
||||
|
||||
protected NPCLookControl lookControl;
|
||||
protected NPCMoveControl moveControl;
|
||||
protected NPCJumpControl jumpControl;
|
||||
private final NPCBodyRotationControl bodyRotationControl;
|
||||
|
||||
private final Map<BlockPathTypes, Float> pathfindingMalus = Maps.newEnumMap(BlockPathTypes.class);
|
||||
|
||||
protected NPCPathNavigation navigation;
|
||||
|
||||
public String getNpcNameWithoutPrefix() {
|
||||
return npcNameWithoutPrefix;
|
||||
}
|
||||
|
|
@ -66,55 +96,115 @@ public class NPCServerPlayer extends ServerPlayer implements INPCPlayer {
|
|||
|
||||
public Runnable fixStartingPosition = () -> {
|
||||
};
|
||||
|
||||
public NPCServerPlayer(MinecraftServer server, ServerLevel level, GameProfile gameProfile) {
|
||||
super(server, level, gameProfile);
|
||||
this.goalSelector = new GoalSelector(server::getProfiler);
|
||||
this.targetSelector = new GoalSelector(server::getProfiler);
|
||||
this.navigation = this.createNavigation(level);
|
||||
this.lookControl = new NPCLookControl(this);
|
||||
this.moveControl = new NPCMoveControl(this);
|
||||
this.jumpControl = new NPCJumpControl(this);
|
||||
this.bodyRotationControl = this.createBodyControl();
|
||||
getAdvancements().stopListening();
|
||||
registerGoals();
|
||||
}
|
||||
|
||||
protected NPCPathNavigation createNavigation(Level level) {
|
||||
return new NPCGroupGroundPathNavigation(this, level);
|
||||
}
|
||||
|
||||
protected boolean shouldPassengersInheritMalus() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public float getPathfindingMalus(BlockPathTypes nodeType) {
|
||||
Float f = this.pathfindingMalus.get(nodeType);
|
||||
return f == null ? nodeType.getMalus() : f;
|
||||
}
|
||||
|
||||
public void setPathfindingMalus(BlockPathTypes nodeType, float malus) {
|
||||
this.pathfindingMalus.put(nodeType, malus);
|
||||
}
|
||||
|
||||
public void onPathfindingStart() {
|
||||
}
|
||||
|
||||
public void onPathfindingDone() {
|
||||
}
|
||||
|
||||
|
||||
protected void registerGoals() {
|
||||
// 优先级 0 最高
|
||||
this.goalSelector.addGoal(1, new NPCFindTargetGoal(this, 16, 100));
|
||||
this.goalSelector.addGoal(2, new NPCFollowGoal(this, 1.0, 3, 2));
|
||||
}
|
||||
|
||||
protected NPCBodyRotationControl createBodyControl() {
|
||||
return new NPCBodyRotationControl(this);
|
||||
}
|
||||
|
||||
public double getFollowDistance() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
public int getMaxHeadXRot() {
|
||||
return 40;
|
||||
}
|
||||
|
||||
public int getMaxHeadYRot() {
|
||||
return 75;
|
||||
}
|
||||
|
||||
public int getHeadRotSpeed() {
|
||||
return 10;
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnusedReturnValue")
|
||||
public static @Nullable NPCServerPlayer createNPC(String username, @NotNull MinecraftServer server, double x, double y, double z, double yaw, double pitch, ResourceKey<Level> dimensionId, GameType gamemode, boolean isflying) {
|
||||
ServerLevel worldIn = server.getLevel(dimensionId);
|
||||
if (worldIn != null) {
|
||||
GameProfileCache.setUsesAuthentication(false);
|
||||
GameProfile gameProfile = null;
|
||||
try {
|
||||
GameProfileCache profileCache = server.getProfileCache();
|
||||
if (profileCache != null) {
|
||||
gameProfile = profileCache.get(username).orElse(null);
|
||||
}
|
||||
} finally {
|
||||
GameProfileCache.setUsesAuthentication(server.isDedicatedServer() && server.usesAuthentication());
|
||||
if (worldIn != null) {
|
||||
GameProfileCache.setUsesAuthentication(false);
|
||||
GameProfile gameProfile = null;
|
||||
try {
|
||||
GameProfileCache profileCache = server.getProfileCache();
|
||||
if (profileCache != null) {
|
||||
gameProfile = profileCache.get(username).orElse(null);
|
||||
}
|
||||
} finally {
|
||||
GameProfileCache.setUsesAuthentication(server.isDedicatedServer() && server.usesAuthentication());
|
||||
}
|
||||
try {
|
||||
if (gameProfile == null) {
|
||||
gameProfile = new GameProfile(UUIDUtil.createOfflinePlayerUUID(username), username);
|
||||
}
|
||||
try {
|
||||
if (gameProfile == null) {
|
||||
gameProfile = new GameProfile(UUIDUtil.createOfflinePlayerUUID(username), username);
|
||||
}
|
||||
|
||||
if (gameProfile.getProperties().containsKey("textures")) {
|
||||
AtomicReference<GameProfile> result = new AtomicReference<>();
|
||||
Objects.requireNonNull(result);
|
||||
SkullBlockEntity.updateGameprofile(gameProfile, result::set);
|
||||
gameProfile = result.get();
|
||||
}
|
||||
NPCServerPlayer instance = new NPCServerPlayer(server, worldIn, gameProfile);
|
||||
instance.fixStartingPosition = () -> {
|
||||
instance.moveTo(x, y, z, (float) yaw, (float) pitch);
|
||||
};
|
||||
((IEDGMinecraftServer)server).getNPCPlayerList().placeNewNPC(new NPCEmptyClientConnection(PacketFlow.SERVERBOUND), instance);
|
||||
instance.teleportTo(worldIn, x, y, z, (float) yaw, (float) pitch);
|
||||
instance.setHealth(20.0F);
|
||||
instance.unsetRemoved();
|
||||
instance.setMaxUpStep(0.6F);
|
||||
instance.gameMode.changeGameModeForPlayer(gamemode);
|
||||
((IEDGMinecraftServer)server).getNPCPlayerList().broadcastAll(new ClientboundRotateHeadPacket(instance, (byte)((int)(instance.yHeadRot * 256.0F / 360.0F))), dimensionId);
|
||||
((IEDGMinecraftServer)server).getNPCPlayerList().broadcastAll(new ClientboundTeleportEntityPacket(instance), dimensionId);
|
||||
instance.entityData.set(DATA_PLAYER_MODE_CUSTOMISATION, (byte)127);
|
||||
instance.getAbilities().flying = isflying;
|
||||
return instance;
|
||||
} catch (Exception e) {
|
||||
EroticDungeon.getLogger().error("Failed to create NPC", e);
|
||||
if (gameProfile.getProperties().containsKey("textures")) {
|
||||
AtomicReference<GameProfile> result = new AtomicReference<>();
|
||||
Objects.requireNonNull(result);
|
||||
SkullBlockEntity.updateGameprofile(gameProfile, result::set);
|
||||
gameProfile = result.get();
|
||||
}
|
||||
} else EroticDungeon.getLogger().error("Failed to create NPC because server({}) is null!", dimensionId);
|
||||
return null;
|
||||
NPCServerPlayer instance = new NPCServerPlayer(server, worldIn, gameProfile);
|
||||
instance.fixStartingPosition = () -> {
|
||||
instance.moveTo(x, y, z, (float) yaw, (float) pitch);
|
||||
};
|
||||
((IEDGMinecraftServer) server).getNPCPlayerList().placeNewNPC(new NPCEmptyClientConnection(PacketFlow.SERVERBOUND), instance);
|
||||
instance.teleportTo(worldIn, x, y, z, (float) yaw, (float) pitch);
|
||||
instance.setHealth(20.0F);
|
||||
instance.unsetRemoved();
|
||||
instance.setMaxUpStep(0.6F);
|
||||
instance.gameMode.changeGameModeForPlayer(gamemode);
|
||||
((IEDGMinecraftServer) server).getNPCPlayerList().broadcastAll(new ClientboundRotateHeadPacket(instance, (byte) ((int) (instance.yHeadRot * 256.0F / 360.0F))), dimensionId);
|
||||
((IEDGMinecraftServer) server).getNPCPlayerList().broadcastAll(new ClientboundTeleportEntityPacket(instance), dimensionId);
|
||||
instance.entityData.set(DATA_PLAYER_MODE_CUSTOMISATION, (byte) 127);
|
||||
instance.getAbilities().flying = isflying;
|
||||
return instance;
|
||||
} catch (Exception e) {
|
||||
EroticDungeon.getLogger().error("Failed to create NPC", e);
|
||||
}
|
||||
} else EroticDungeon.getLogger().error("Failed to create NPC because server({}) is null!", dimensionId);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -125,6 +215,70 @@ public class NPCServerPlayer extends ServerPlayer implements INPCPlayer {
|
|||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void serverAiStep() {
|
||||
super.serverAiStep();
|
||||
int i = Objects.requireNonNull(this.level().getServer()).getTickCount() + this.getId();
|
||||
if (i % 2 != 0 && this.tickCount > 1) {
|
||||
this.level().getProfiler().push("targetSelector");
|
||||
this.targetSelector.tickRunningGoals(false);
|
||||
this.level().getProfiler().pop();
|
||||
this.level().getProfiler().push("goalSelector");
|
||||
this.goalSelector.tickRunningGoals(false);
|
||||
this.level().getProfiler().pop();
|
||||
} else {
|
||||
this.level().getProfiler().push("targetSelector");
|
||||
this.targetSelector.tick();
|
||||
this.level().getProfiler().pop();
|
||||
this.level().getProfiler().push("goalSelector");
|
||||
this.goalSelector.tick();
|
||||
this.level().getProfiler().pop();
|
||||
}
|
||||
this.level().getProfiler().push("navigation");
|
||||
this.navigation.tick();
|
||||
this.level().getProfiler().pop();
|
||||
this.level().getProfiler().push("mob tick");
|
||||
|
||||
this.level().getProfiler().pop();
|
||||
this.level().getProfiler().push("controls");
|
||||
this.level().getProfiler().push("move");
|
||||
this.moveControl.tick();
|
||||
this.level().getProfiler().popPush("look");
|
||||
this.lookControl.tick();
|
||||
this.level().getProfiler().popPush("jump");
|
||||
this.jumpControl.tick();
|
||||
this.level().getProfiler().pop();
|
||||
this.level().getProfiler().pop();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected float tickHeadTurn(float yRot, float animStep) {
|
||||
this.bodyRotationControl.clientTick();
|
||||
return animStep;
|
||||
}
|
||||
|
||||
|
||||
protected void updateControlFlags() {
|
||||
boolean flag = !(this.getControllingPassenger() instanceof Mob);
|
||||
boolean flag1 = !(this.getVehicle() instanceof Boat);
|
||||
this.goalSelector.setControlFlag(Goal.Flag.MOVE, flag);
|
||||
this.goalSelector.setControlFlag(Goal.Flag.JUMP, flag && flag1);
|
||||
this.goalSelector.setControlFlag(Goal.Flag.LOOK, flag);
|
||||
}
|
||||
|
||||
public void setZza(float amount) {
|
||||
this.zza = amount;
|
||||
}
|
||||
|
||||
public void setYya(float amount) {
|
||||
this.yya = amount;
|
||||
}
|
||||
|
||||
public void setXxa(float amount) {
|
||||
this.xxa = amount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void kill() {
|
||||
this.kill(Messenger.s("Killed"));
|
||||
|
|
@ -166,9 +320,7 @@ public class NPCServerPlayer extends ServerPlayer implements INPCPlayer {
|
|||
super.addAdditionalSaveData(compound);
|
||||
String npcName = getName().getString();
|
||||
String npcNameWithoutPrefix = npcName.substring(PREFIX.length());
|
||||
NBTWriter.of(compound)
|
||||
.string("NpcName", npcNameWithoutPrefix)
|
||||
.compound("NpcGameProfile", NbtUtils.writeGameProfile(new CompoundTag(), this.getGameProfile()));
|
||||
NBTWriter.of(compound).string("NpcName", npcNameWithoutPrefix).compound("NpcGameProfile", NbtUtils.writeGameProfile(new CompoundTag(), this.getGameProfile()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -191,9 +343,13 @@ public class NPCServerPlayer extends ServerPlayer implements INPCPlayer {
|
|||
public void tick() {
|
||||
if (this.getServer() != null && this.getServer().getTickCount() % 10 == 0) {
|
||||
this.connection.resetPosition();
|
||||
((ServerLevel)this.level()).getChunkSource().move(this);
|
||||
((ServerLevel) this.level()).getChunkSource().move(this);
|
||||
}
|
||||
if (!this.level().isClientSide) {
|
||||
if (this.tickCount % 5 == 0) {
|
||||
this.updateControlFlags();
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
super.tick();
|
||||
this.doTick();
|
||||
|
|
@ -229,4 +385,31 @@ public class NPCServerPlayer extends ServerPlayer implements INPCPlayer {
|
|||
return clientboundAddPlayerPacket;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public LivingEntity getTarget() {
|
||||
return target;
|
||||
}
|
||||
|
||||
public void setTarget(@Nullable LivingEntity entity) {
|
||||
this.target = entity;
|
||||
}
|
||||
|
||||
public NPCLookControl getLookControl() {
|
||||
return lookControl;
|
||||
}
|
||||
|
||||
public NPCMoveControl getMoveControl() {
|
||||
return moveControl;
|
||||
}
|
||||
|
||||
public NPCJumpControl getJumpControl() {
|
||||
return jumpControl;
|
||||
}
|
||||
|
||||
public NPCPathNavigation getNavigation() {
|
||||
return navigation;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* Copyright 2025-2026 R3944Realms
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.r3944realms.eroticdungeongame.content.entity.npc.ai.control;
|
||||
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.entity.Mob;
|
||||
import net.minecraft.world.entity.ai.control.Control;
|
||||
import top.r3944realms.eroticdungeongame.content.entity.npc.NPCServerPlayer;
|
||||
|
||||
public class NPCBodyRotationControl implements Control {
|
||||
private final NPCServerPlayer npc;
|
||||
private static final int HEAD_STABLE_ANGLE = 15;
|
||||
private static final int DELAY_UNTIL_STARTING_TO_FACE_FORWARD = 10;
|
||||
private static final int HOW_LONG_IT_TAKES_TO_FACE_FORWARD = 10;
|
||||
private int headStableTime;
|
||||
private float lastStableYHeadRot;
|
||||
|
||||
public NPCBodyRotationControl(NPCServerPlayer npc) {
|
||||
this.npc = npc;
|
||||
}
|
||||
public void clientTick() {
|
||||
if (this.isMoving()) {
|
||||
this.npc.yBodyRot = this.npc.getYRot();
|
||||
this.rotateHeadIfNecessary();
|
||||
this.lastStableYHeadRot = this.npc.yHeadRot;
|
||||
this.headStableTime = 0;
|
||||
} else {
|
||||
if (notCarryingMobPassengers()) {
|
||||
if (Math.abs(this.npc.yHeadRot - this.lastStableYHeadRot) > 15.0F) {
|
||||
this.headStableTime = 0;
|
||||
this.lastStableYHeadRot = this.npc.yHeadRot;
|
||||
this.rotateBodyIfNecessary();
|
||||
} else {
|
||||
++this.headStableTime;
|
||||
if (this.headStableTime > 10) {
|
||||
this.rotateHeadTowardsFront();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void rotateBodyIfNecessary() {
|
||||
this.npc.yBodyRot = Mth.rotateIfNecessary(this.npc.yBodyRot, this.npc.yHeadRot, (float)this.npc.getMaxHeadYRot());
|
||||
}
|
||||
|
||||
private void rotateHeadIfNecessary() {
|
||||
this.npc.yHeadRot = Mth.rotateIfNecessary(this.npc.yHeadRot, this.npc.yBodyRot, (float)this.npc.getMaxHeadYRot());
|
||||
}
|
||||
|
||||
private void rotateHeadTowardsFront() {
|
||||
int i = this.headStableTime - 10;
|
||||
float f = Mth.clamp((float)i / 10.0F, 0.0F, 1.0F);
|
||||
float f1 = (float)this.npc.getMaxHeadYRot() * (1.0F - f);
|
||||
this.npc.yBodyRot = Mth.rotateIfNecessary(this.npc.yBodyRot, this.npc.yHeadRot, f1);
|
||||
}
|
||||
|
||||
private boolean notCarryingMobPassengers() {
|
||||
return !(this.npc.getFirstPassenger() instanceof Mob);
|
||||
}
|
||||
|
||||
private boolean isMoving() {
|
||||
double d0 = this.npc.getX() - this.npc.xo;
|
||||
double d1 = this.npc.getZ() - this.npc.zo;
|
||||
return d0 * d0 + d1 * d1 > (double)2.5000003E-7F;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright 2025-2026 R3944Realms
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.r3944realms.eroticdungeongame.content.entity.npc.ai.control;
|
||||
|
||||
import top.r3944realms.eroticdungeongame.content.entity.npc.NPCServerPlayer;
|
||||
|
||||
public class NPCJumpControl {
|
||||
private final NPCServerPlayer npc;
|
||||
protected boolean jump;
|
||||
|
||||
public NPCJumpControl(NPCServerPlayer npc) {
|
||||
this.npc = npc;
|
||||
}
|
||||
|
||||
public void jump() {
|
||||
this.jump = true;
|
||||
}
|
||||
|
||||
public void tick() {
|
||||
this.npc.setJumping(this.jump);
|
||||
this.jump = false;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,145 @@
|
|||
/*
|
||||
* Copyright 2025-2026 R3944Realms
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.r3944realms.eroticdungeongame.content.entity.npc.ai.control;
|
||||
|
||||
import net.minecraft.util.Mth;
|
||||
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 top.r3944realms.eroticdungeongame.content.entity.npc.NPCServerPlayer;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public class NPCLookControl implements Control {
|
||||
protected final NPCServerPlayer npc;
|
||||
protected float yMaxRotSpeed;
|
||||
protected float xMaxRotAngle;
|
||||
protected int lookAtCooldown;
|
||||
protected double wantedX;
|
||||
protected double wantedY;
|
||||
protected double wantedZ;
|
||||
|
||||
public NPCLookControl(NPCServerPlayer npc) {
|
||||
this.npc = npc;
|
||||
}
|
||||
|
||||
|
||||
public void setLookAt(Vec3 lookVector) {
|
||||
this.setLookAt(lookVector.x, lookVector.y, lookVector.z);
|
||||
}
|
||||
|
||||
|
||||
public void setLookAt(Entity entity) {
|
||||
this.setLookAt(entity.getX(), getWantedY(entity), entity.getZ());
|
||||
}
|
||||
|
||||
|
||||
public void setLookAt(Entity entity, float deltaYaw, float deltaPitch) {
|
||||
this.setLookAt(entity.getX(), getWantedY(entity), entity.getZ(), deltaYaw, deltaPitch);
|
||||
}
|
||||
|
||||
public void setLookAt(double x, double y, double z) {
|
||||
this.setLookAt(x, y, z, (float)this.npc.getHeadRotSpeed(), (float)this.npc.getMaxHeadXRot());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets position to look at
|
||||
*/
|
||||
public void setLookAt(double x, double y, double z, float deltaYaw, float deltaPitch) {
|
||||
this.wantedX = x;
|
||||
this.wantedY = y;
|
||||
this.wantedZ = z;
|
||||
this.yMaxRotSpeed = deltaYaw;
|
||||
this.xMaxRotAngle = deltaPitch;
|
||||
this.lookAtCooldown = 2;
|
||||
}
|
||||
|
||||
public void tick() {
|
||||
if (this.resetXRotOnTick()) {
|
||||
this.npc.setXRot(0.0F);
|
||||
}
|
||||
|
||||
if (this.lookAtCooldown > 0) {
|
||||
--this.lookAtCooldown;
|
||||
this.getYRotD().ifPresent((p_287447_) -> {
|
||||
this.npc.yHeadRot = this.rotateTowards(this.npc.yHeadRot, p_287447_, this.yMaxRotSpeed);
|
||||
});
|
||||
this.getXRotD().ifPresent((p_289400_) -> {
|
||||
this.npc.setXRot(this.rotateTowards(this.npc.getXRot(), p_289400_, this.xMaxRotAngle));
|
||||
});
|
||||
} else {
|
||||
this.npc.yHeadRot = this.rotateTowards(this.npc.yHeadRot, this.npc.yBodyRot, 10.0F);
|
||||
}
|
||||
|
||||
this.clampHeadRotationToBody();
|
||||
}
|
||||
|
||||
protected void clampHeadRotationToBody() {
|
||||
if (!this.npc.getNavigation().isDone()) {
|
||||
this.npc.yHeadRot = Mth.rotateIfNecessary(this.npc.yHeadRot, this.npc.yBodyRot, (float)this.npc.getMaxHeadYRot());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected boolean resetXRotOnTick() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean isLookingAtTarget() {
|
||||
return this.lookAtCooldown > 0;
|
||||
}
|
||||
|
||||
public double getWantedX() {
|
||||
return this.wantedX;
|
||||
}
|
||||
|
||||
public double getWantedY() {
|
||||
return this.wantedY;
|
||||
}
|
||||
|
||||
public double getWantedZ() {
|
||||
return this.wantedZ;
|
||||
}
|
||||
|
||||
protected Optional<Float> getXRotD() {
|
||||
double d0 = this.wantedX - this.npc.getX();
|
||||
double d1 = this.wantedY - this.npc.getEyeY();
|
||||
double d2 = this.wantedZ - this.npc.getZ();
|
||||
double d3 = Math.sqrt(d0 * d0 + d2 * d2);
|
||||
return !(Math.abs(d1) > (double)1.0E-5F) && !(Math.abs(d3) > (double)1.0E-5F) ? Optional.empty() : Optional.of((float)(-(Mth.atan2(d1, d3) * (double)(180F / (float)Math.PI))));
|
||||
}
|
||||
|
||||
protected Optional<Float> getYRotD() {
|
||||
double d0 = this.wantedX - this.npc.getX();
|
||||
double d1 = this.wantedZ - this.npc.getZ();
|
||||
return !(Math.abs(d1) > (double)1.0E-5F) && !(Math.abs(d0) > (double)1.0E-5F) ? Optional.empty() : Optional.of((float)(Mth.atan2(d1, d0) * (double)(180F / (float)Math.PI)) - 90.0F);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rotate as much as possible from {@code from} to {@code to} within the bounds of {@code maxDelta}
|
||||
*/
|
||||
protected float rotateTowards(float from, float to, float maxDelta) {
|
||||
float f = Mth.degreesDifference(from, to);
|
||||
float f1 = Mth.clamp(f, -maxDelta, maxDelta);
|
||||
return from + f1;
|
||||
}
|
||||
|
||||
private static double getWantedY(Entity entity) {
|
||||
return entity instanceof LivingEntity ? entity.getEyeY() : (entity.getBoundingBox().minY + entity.getBoundingBox().maxY) / 2.0D;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,193 @@
|
|||
/*
|
||||
* Copyright 2025-2026 R3944Realms
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.r3944realms.eroticdungeongame.content.entity.npc.ai.control;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.tags.BlockTags;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.entity.ai.attributes.Attributes;
|
||||
import net.minecraft.world.entity.ai.control.Control;
|
||||
import net.minecraft.world.entity.ai.navigation.PathNavigation;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.pathfinder.BlockPathTypes;
|
||||
import net.minecraft.world.level.pathfinder.NodeEvaluator;
|
||||
import net.minecraft.world.phys.shapes.VoxelShape;
|
||||
import top.r3944realms.eroticdungeongame.content.entity.npc.NPCServerPlayer;
|
||||
import top.r3944realms.eroticdungeongame.content.entity.npc.ai.navigation.NPCPathNavigation;
|
||||
import top.r3944realms.eroticdungeongame.content.entity.npc.ai.navigation.pathfinder.NPCNodeEvaluator;
|
||||
|
||||
public class NPCMoveControl implements Control {
|
||||
public static final float MIN_SPEED = 5.0E-4F;
|
||||
public static final float MIN_SPEED_SQR = 2.5000003E-7F;
|
||||
protected static final int MAX_TURN = 90;
|
||||
protected final NPCServerPlayer npc;
|
||||
protected double wantedX;
|
||||
protected double wantedY;
|
||||
protected double wantedZ;
|
||||
protected double speedModifier;
|
||||
protected float strafeForwards;
|
||||
protected float strafeRight;
|
||||
protected Operation operation = Operation.WAIT;
|
||||
|
||||
public NPCMoveControl(NPCServerPlayer npc) {
|
||||
this.npc = npc;
|
||||
}
|
||||
|
||||
public boolean hasWanted() {
|
||||
return this.operation == Operation.MOVE_TO;
|
||||
}
|
||||
|
||||
public double getSpeedModifier() {
|
||||
return this.speedModifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the speed and location to move to
|
||||
*/
|
||||
public void setWantedPosition(double x, double y, double z, double speed) {
|
||||
this.wantedX = x;
|
||||
this.wantedY = y;
|
||||
this.wantedZ = z;
|
||||
this.speedModifier = speed;
|
||||
if (this.operation != Operation.JUMPING) {
|
||||
this.operation = Operation.MOVE_TO;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void strafe(float forward, float strafe) {
|
||||
this.operation = Operation.STRAFE;
|
||||
this.strafeForwards = forward;
|
||||
this.strafeRight = strafe;
|
||||
this.speedModifier = 0.25D;
|
||||
}
|
||||
|
||||
public void tick() {
|
||||
if (this.operation == Operation.STRAFE) {
|
||||
float f = (float)this.npc.getAttributeValue(Attributes.MOVEMENT_SPEED);
|
||||
float f1 = (float)this.speedModifier * f;
|
||||
float f2 = this.strafeForwards;
|
||||
float f3 = this.strafeRight;
|
||||
float f4 = Mth.sqrt(f2 * f2 + f3 * f3);
|
||||
if (f4 < 1.0F) {
|
||||
f4 = 1.0F;
|
||||
}
|
||||
|
||||
f4 = f1 / f4;
|
||||
f2 *= f4;
|
||||
f3 *= f4;
|
||||
float f5 = Mth.sin(this.npc.getYRot() * ((float)Math.PI / 180F));
|
||||
float f6 = Mth.cos(this.npc.getYRot() * ((float)Math.PI / 180F));
|
||||
float f7 = f2 * f6 - f3 * f5;
|
||||
float f8 = f3 * f6 + f2 * f5;
|
||||
if (!this.isWalkable(f7, f8)) {
|
||||
this.strafeForwards = 1.0F;
|
||||
this.strafeRight = 0.0F;
|
||||
}
|
||||
|
||||
this.npc.setSpeed(f1);
|
||||
this.npc.setZza(this.strafeForwards);
|
||||
this.npc.setXxa(this.strafeRight);
|
||||
this.operation = Operation.WAIT;
|
||||
} else if (this.operation == Operation.MOVE_TO) {
|
||||
this.operation = Operation.WAIT;
|
||||
double d0 = this.wantedX - this.npc.getX();
|
||||
double d1 = this.wantedZ - this.npc.getZ();
|
||||
double d2 = this.wantedY - this.npc.getY();
|
||||
double d3 = d0 * d0 + d2 * d2 + d1 * d1;
|
||||
if (d3 < (double)2.5000003E-7F) {
|
||||
this.npc.setZza(0.0F);
|
||||
return;
|
||||
}
|
||||
|
||||
float f9 = (float)(Mth.atan2(d1, d0) * (double)(180F / (float)Math.PI)) - 90.0F;
|
||||
this.npc.setYRot(this.rotlerp(this.npc.getYRot(), f9, 90.0F));
|
||||
this.npc.setSpeed((float)(this.speedModifier * this.npc.getAttributeValue(Attributes.MOVEMENT_SPEED)));
|
||||
BlockPos blockpos = this.npc.blockPosition();
|
||||
BlockState blockstate = this.npc.level().getBlockState(blockpos);
|
||||
VoxelShape voxelshape = blockstate.getCollisionShape(this.npc.level(), blockpos);
|
||||
if (d2 > (double)this.npc.getStepHeight() && d0 * d0 + d1 * d1 < (double)Math.max(1.0F, this.npc.getBbWidth()) || !voxelshape.isEmpty() && this.npc.getY() < voxelshape.max(Direction.Axis.Y) + (double)blockpos.getY() && !blockstate.is(BlockTags.DOORS) && !blockstate.is(BlockTags.FENCES)) {
|
||||
this.npc.getJumpControl().jump();
|
||||
this.operation = Operation.JUMPING;
|
||||
}
|
||||
} else if (this.operation == Operation.JUMPING) {
|
||||
this.npc.setSpeed((float)(this.speedModifier * this.npc.getAttributeValue(Attributes.MOVEMENT_SPEED)));
|
||||
if (this.npc.onGround()) {
|
||||
this.operation = Operation.WAIT;
|
||||
}
|
||||
} else {
|
||||
this.npc.setZza(0.0F);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the mob can walk successfully to a given X and Z
|
||||
*/
|
||||
private boolean isWalkable(float relativeX, float relativeZ) {
|
||||
NPCPathNavigation pathnavigation = this.npc.getNavigation();
|
||||
if (pathnavigation != null) {
|
||||
NPCNodeEvaluator nodeevaluator = pathnavigation.getNodeEvaluator();
|
||||
return nodeevaluator == null || nodeevaluator.getBlockPathType(this.npc.level(), Mth.floor(this.npc.getX() + (double) relativeX), this.npc.getBlockY(), Mth.floor(this.npc.getZ() + (double) relativeZ)) == BlockPathTypes.WALKABLE;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to rotate the first angle to become the second angle, but only allow overall direction change to at max be third parameter
|
||||
*/
|
||||
protected float rotlerp(float sourceAngle, float targetAngle, float maximumChange) {
|
||||
float f = Mth.wrapDegrees(targetAngle - sourceAngle);
|
||||
if (f > maximumChange) {
|
||||
f = maximumChange;
|
||||
}
|
||||
|
||||
if (f < -maximumChange) {
|
||||
f = -maximumChange;
|
||||
}
|
||||
|
||||
float f1 = sourceAngle + f;
|
||||
if (f1 < 0.0F) {
|
||||
f1 += 360.0F;
|
||||
} else if (f1 > 360.0F) {
|
||||
f1 -= 360.0F;
|
||||
}
|
||||
|
||||
return f1;
|
||||
}
|
||||
|
||||
public double getWantedX() {
|
||||
return this.wantedX;
|
||||
}
|
||||
|
||||
public double getWantedY() {
|
||||
return this.wantedY;
|
||||
}
|
||||
|
||||
public double getWantedZ() {
|
||||
return this.wantedZ;
|
||||
}
|
||||
|
||||
protected enum Operation {
|
||||
WAIT,
|
||||
MOVE_TO,
|
||||
STRAFE,
|
||||
JUMPING;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,150 @@
|
|||
/*
|
||||
* Copyright 2025-2026 R3944Realms
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.r3944realms.eroticdungeongame.content.entity.npc.ai.goal;
|
||||
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.entity.ai.goal.Goal;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import top.r3944realms.eroticdungeongame.content.entity.npc.NPCServerPlayer;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.EnumSet;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public class NPCFindTargetGoal extends Goal {
|
||||
private final NPCServerPlayer npc;
|
||||
private LivingEntity target;
|
||||
private final double searchRadius;
|
||||
private final int cooldown;
|
||||
private int cooldownTicks;
|
||||
|
||||
// 目标过滤条件
|
||||
private Predicate<LivingEntity> targetFilter;
|
||||
|
||||
// 优先级排序器
|
||||
private Comparator<LivingEntity> targetSorter;
|
||||
|
||||
public NPCFindTargetGoal(NPCServerPlayer npc, double searchRadius, int cooldown) {
|
||||
this.npc = npc;
|
||||
this.searchRadius = searchRadius;
|
||||
this.cooldown = cooldown;
|
||||
this.setFlags(EnumSet.of(Flag.LOOK));
|
||||
|
||||
// 默认过滤器:排除自己、死亡、创造模式玩家
|
||||
this.targetFilter = entity ->
|
||||
entity != npc &&
|
||||
entity.isAlive() &&
|
||||
entity instanceof Player player && !player.isCreative();
|
||||
|
||||
// 默认排序:最近优先
|
||||
this.targetSorter = Comparator.comparingDouble(npc::distanceToSqr);
|
||||
}
|
||||
|
||||
// 允许自定义过滤和排序
|
||||
public NPCFindTargetGoal withFilter(Predicate<LivingEntity> filter) {
|
||||
this.targetFilter = filter;
|
||||
return this;
|
||||
}
|
||||
|
||||
public NPCFindTargetGoal withSorter(Comparator<LivingEntity> sorter) {
|
||||
this.targetSorter = sorter;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canUse() {
|
||||
if (npc.getTarget() != null) return false;
|
||||
if (cooldownTicks > 0) return false;
|
||||
|
||||
// 寻找有效目标
|
||||
this.target = findNearestTarget();
|
||||
return this.target != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canContinueToUse() {
|
||||
// 继续条件:目标存在且存活,且没有更紧急的目标
|
||||
return target != null &&
|
||||
target.isAlive() &&
|
||||
npc.getTarget() == target &&
|
||||
!hasHigherPriorityTarget();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
if (target != null) {
|
||||
npc.setTarget(target);
|
||||
cooldownTicks = cooldown;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
// 不清除 target,让其他 Goal 决定是否保留
|
||||
cooldownTicks = cooldown;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void 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)
|
||||
.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; // 可配置
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Copyright 2025-2026 R3944Realms
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.r3944realms.eroticdungeongame.content.entity.npc.ai.goal;
|
||||
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.entity.ai.goal.Goal;
|
||||
import top.r3944realms.eroticdungeongame.content.entity.npc.NPCServerPlayer;
|
||||
|
||||
import java.util.EnumSet;
|
||||
|
||||
public class NPCFollowGoal extends Goal {
|
||||
private final NPCServerPlayer npc;
|
||||
private LivingEntity target;
|
||||
private final double speedModifier;
|
||||
private final int followDistance;
|
||||
private final int stopDistance;
|
||||
public NPCFollowGoal(NPCServerPlayer npc, double speed, int followDist, int stopDist) {
|
||||
this.npc = npc;
|
||||
this.speedModifier = speed;
|
||||
this.followDistance = followDist;
|
||||
this.stopDistance = stopDist;
|
||||
this.setFlags(EnumSet.of(Flag.MOVE, Flag.LOOK));
|
||||
}
|
||||
@Override
|
||||
public boolean canUse() {
|
||||
this.target = npc.getTarget();
|
||||
return target != null && npc.distanceToSqr(target) > stopDistance;
|
||||
}
|
||||
@Override
|
||||
public boolean canContinueToUse() {
|
||||
return target != null && npc.distanceToSqr(target) > followDistance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
npc.getNavigation().moveTo(target, speedModifier);
|
||||
}
|
||||
@Override
|
||||
public void tick() {
|
||||
npc.getLookControl().setLookAt(target, 30.0f, 30.0f);
|
||||
if (npc.distanceToSqr(target) > stopDistance) {
|
||||
npc.getNavigation().moveTo(target, speedModifier);
|
||||
} else {
|
||||
npc.getNavigation().stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,165 @@
|
|||
/*
|
||||
* Copyright 2025-2026 R3944Realms
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.r3944realms.eroticdungeongame.content.entity.npc.ai.navigation;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.level.Level;
|
||||
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 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;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public class NPCGroupGroundPathNavigation extends NPCPathNavigation {
|
||||
private boolean avoidSun;
|
||||
|
||||
public NPCGroupGroundPathNavigation(NPCServerPlayer npc, Level level) {
|
||||
super(npc, level);
|
||||
}
|
||||
|
||||
protected NPCPathFinder createPathFinder(int maxVisitedNodes) {
|
||||
this.nodeEvaluator = new NPCWalkNodeEvaluator();
|
||||
this.nodeEvaluator.setCanPassDoors(true);
|
||||
return new NPCPathFinder(this.nodeEvaluator, maxVisitedNodes);
|
||||
}
|
||||
|
||||
protected boolean canUpdatePath() {
|
||||
return this.npc.onGround() || this.isInLiquid() || this.npc.isPassenger();
|
||||
}
|
||||
|
||||
protected Vec3 getTempMobPos() {
|
||||
return new Vec3(this.npc.getX(), this.getSurfaceY(), this.npc.getZ());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns path to given BlockPos
|
||||
*/
|
||||
public Path createPath(BlockPos pos, int accuracy) {
|
||||
if (this.level.getBlockState(pos).isAir()) {
|
||||
BlockPos blockpos;
|
||||
for(blockpos = pos.below(); blockpos.getY() > this.level.getMinBuildHeight() && this.level.getBlockState(blockpos).isAir(); blockpos = blockpos.below()) {
|
||||
}
|
||||
|
||||
if (blockpos.getY() > this.level.getMinBuildHeight()) {
|
||||
return super.createPath(blockpos.above(), accuracy);
|
||||
}
|
||||
|
||||
while(blockpos.getY() < this.level.getMaxBuildHeight() && this.level.getBlockState(blockpos).isAir()) {
|
||||
blockpos = blockpos.above();
|
||||
}
|
||||
|
||||
pos = blockpos;
|
||||
}
|
||||
|
||||
if (!this.level.getBlockState(pos).isSolid()) {
|
||||
return super.createPath(pos, accuracy);
|
||||
} else {
|
||||
BlockPos blockpos1;
|
||||
for(blockpos1 = pos.above(); blockpos1.getY() < this.level.getMaxBuildHeight() && this.level.getBlockState(blockpos1).isSolid(); blockpos1 = blockpos1.above()) {
|
||||
}
|
||||
|
||||
return super.createPath(blockpos1, accuracy);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a path to the given entity or null
|
||||
*/
|
||||
public Path createPath(Entity entity, int accuracy) {
|
||||
return this.createPath(entity.blockPosition(), accuracy);
|
||||
}
|
||||
|
||||
private int getSurfaceY() {
|
||||
if (this.npc.isInWater() && this.canFloat()) {
|
||||
int i = this.npc.getBlockY();
|
||||
BlockState blockstate = this.level.getBlockState(BlockPos.containing(this.npc.getX(), i, this.npc.getZ()));
|
||||
int j = 0;
|
||||
|
||||
while(blockstate.is(Blocks.WATER)) {
|
||||
++i;
|
||||
blockstate = this.level.getBlockState(BlockPos.containing(this.npc.getX(), i, this.npc.getZ()));
|
||||
++j;
|
||||
if (j > 16) {
|
||||
return this.npc.getBlockY();
|
||||
}
|
||||
}
|
||||
|
||||
return i;
|
||||
} else {
|
||||
return Mth.floor(this.npc.getY() + 0.5D);
|
||||
}
|
||||
}
|
||||
|
||||
protected void trimPath() {
|
||||
super.trimPath();
|
||||
if (this.avoidSun) {
|
||||
if (this.level.canSeeSky(BlockPos.containing(this.npc.getX(), this.npc.getY() + 0.5D, this.npc.getZ()))) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.path != null) {
|
||||
for(int i = 0; i < this.path.getNodeCount(); ++i) {
|
||||
Node node = this.path.getNode(i);
|
||||
if (this.level.canSeeSky(new BlockPos(node.x, node.y, node.z))) {
|
||||
this.path.truncateNodes(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected boolean hasValidPathType(BlockPathTypes pathType) {
|
||||
if (pathType == BlockPathTypes.WATER) {
|
||||
return false;
|
||||
} else if (pathType == BlockPathTypes.LAVA) {
|
||||
return false;
|
||||
} else {
|
||||
return pathType != BlockPathTypes.OPEN;
|
||||
}
|
||||
}
|
||||
|
||||
public void setCanOpenDoors(boolean canOpenDoors) {
|
||||
this.nodeEvaluator.setCanOpenDoors(canOpenDoors);
|
||||
}
|
||||
|
||||
public boolean canPassDoors() {
|
||||
return this.nodeEvaluator.canPassDoors();
|
||||
}
|
||||
|
||||
public void setCanPassDoors(boolean canPassDoors) {
|
||||
this.nodeEvaluator.setCanPassDoors(canPassDoors);
|
||||
}
|
||||
|
||||
public boolean canOpenDoors() {
|
||||
return this.nodeEvaluator.canPassDoors();
|
||||
}
|
||||
|
||||
public void setAvoidSun(boolean avoidSun) {
|
||||
this.avoidSun = avoidSun;
|
||||
}
|
||||
|
||||
public void setCanWalkOverFences(boolean canWalkOverFences) {
|
||||
this.nodeEvaluator.setCanWalkOverFences(canWalkOverFences);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,463 @@
|
|||
/*
|
||||
* Copyright 2025-2026 R3944Realms
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.r3944realms.eroticdungeongame.content.entity.npc.ai.navigation;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Vec3i;
|
||||
import net.minecraft.tags.BlockTags;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.level.ClipContext;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.PathNavigationRegion;
|
||||
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.Nullable;
|
||||
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.NPCPathFinder;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public abstract class NPCPathNavigation {
|
||||
private static final int MAX_TIME_RECOMPUTE = 20;
|
||||
private static final int STUCK_CHECK_INTERVAL = 100;
|
||||
private static final float STUCK_THRESHOLD_DISTANCE_FACTOR = 0.25F;
|
||||
protected final NPCServerPlayer npc;
|
||||
protected final Level level;
|
||||
@Nullable
|
||||
protected Path path;
|
||||
protected double speedModifier;
|
||||
protected int tick;
|
||||
protected int lastStuckCheck;
|
||||
protected Vec3 lastStuckCheckPos = Vec3.ZERO;
|
||||
protected Vec3i timeoutCachedNode = Vec3i.ZERO;
|
||||
protected long timeoutTimer;
|
||||
protected long lastTimeoutCheck;
|
||||
protected double timeoutLimit;
|
||||
protected float maxDistanceToWaypoint = 0.5F;
|
||||
|
||||
protected boolean hasDelayedRecomputation;
|
||||
protected long timeLastRecompute;
|
||||
protected NPCNodeEvaluator nodeEvaluator;
|
||||
@Nullable
|
||||
private BlockPos targetPos;
|
||||
/**
|
||||
* Distance in which a path point counts as target-reaching
|
||||
*/
|
||||
private int reachRange;
|
||||
private float maxVisitedNodesMultiplier = 1.0F;
|
||||
private final NPCPathFinder pathFinder;
|
||||
private boolean isStuck;
|
||||
|
||||
public NPCPathNavigation(NPCServerPlayer npc, Level level) {
|
||||
this.npc = npc;
|
||||
this.level = level;
|
||||
// int i = Mth.floor(npc.getAttributeValue(Attributes.FOLLOW_RANGE) * 16.0D);
|
||||
// 不要使用属性
|
||||
int i = Mth.floor(npc.getFollowDistance() * 16.0D);
|
||||
this.pathFinder = this.createPathFinder(i);
|
||||
}
|
||||
|
||||
public void resetMaxVisitedNodesMultiplier() {
|
||||
this.maxVisitedNodesMultiplier = 1.0F;
|
||||
}
|
||||
|
||||
public void setMaxVisitedNodesMultiplier(float multiplier) {
|
||||
this.maxVisitedNodesMultiplier = multiplier;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public BlockPos getTargetPos() {
|
||||
return this.targetPos;
|
||||
}
|
||||
|
||||
protected abstract NPCPathFinder createPathFinder(int maxVisitedNodes);
|
||||
|
||||
/**
|
||||
* Sets the speed
|
||||
*/
|
||||
public void setSpeedModifier(double speed) {
|
||||
this.speedModifier = speed;
|
||||
}
|
||||
|
||||
public void recomputePath() {
|
||||
if (this.level.getGameTime() - this.timeLastRecompute > 20L) {
|
||||
if (this.targetPos != null) {
|
||||
this.path = null;
|
||||
this.path = this.createPath(this.targetPos, this.reachRange);
|
||||
this.timeLastRecompute = this.level.getGameTime();
|
||||
this.hasDelayedRecomputation = false;
|
||||
}
|
||||
} else {
|
||||
this.hasDelayedRecomputation = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns path to given BlockPos
|
||||
*/
|
||||
@Nullable
|
||||
public final Path createPath(double x, double y, double z, int accuracy) {
|
||||
return this.createPath(BlockPos.containing(x, y, z), accuracy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a path to one of the elements of the stream or null
|
||||
*/
|
||||
@Nullable
|
||||
public Path createPath(Stream<BlockPos> targets, int accuracy) {
|
||||
return this.createPath(targets.collect(Collectors.toSet()), 8, false, accuracy);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Path createPath(Set<BlockPos> positions, int distance) {
|
||||
return this.createPath(positions, 8, false, distance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns path to given BlockPos
|
||||
*/
|
||||
@Nullable
|
||||
public Path createPath(BlockPos pos, int accuracy) {
|
||||
return this.createPath(ImmutableSet.of(pos), 8, false, accuracy);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Path createPath(BlockPos pos, int regionOffset, int accuracy) {
|
||||
return this.createPath(ImmutableSet.of(pos), 8, false, regionOffset, (float)accuracy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a path to the given entity or null
|
||||
*/
|
||||
@Nullable
|
||||
public Path createPath(Entity entity, int accuracy) {
|
||||
return this.createPath(ImmutableSet.of(entity.blockPosition()), 16, true, accuracy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a path to one of the given targets or null
|
||||
*/
|
||||
@Nullable
|
||||
protected Path createPath(Set<BlockPos> targets, int regionOffset, boolean offsetUpward, int accuracy) {
|
||||
return this.createPath(targets, regionOffset, offsetUpward, accuracy, (float)this.npc.getFollowDistance());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected Path createPath(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()) {
|
||||
return null;
|
||||
} else if (!this.canUpdatePath()) {
|
||||
return null;
|
||||
} else if (this.path != null && !this.path.isDone() && targets.contains(this.targetPos)) {
|
||||
return this.path;
|
||||
} else {
|
||||
this.level.getProfiler().push("pathfind");
|
||||
BlockPos blockpos = offsetUpward ? this.npc.blockPosition().above() : this.npc.blockPosition();
|
||||
int i = (int)(followRange + (float)regionOffset);
|
||||
PathNavigationRegion pathnavigationregion = new PathNavigationRegion(this.level, blockpos.offset(-i, -i, -i), blockpos.offset(i, i, i));
|
||||
Path path = this.pathFinder.findPath(pathnavigationregion, this.npc, targets, followRange, accuracy, this.maxVisitedNodesMultiplier);
|
||||
this.level.getProfiler().pop();
|
||||
if (path != null && path.getTarget() != null) {
|
||||
this.targetPos = path.getTarget();
|
||||
this.reachRange = accuracy;
|
||||
this.resetStuckTimeout();
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to find and set a path to XYZ. Returns {@code true} if successful.
|
||||
*/
|
||||
public boolean moveTo(double x, double y, double z, double speed) {
|
||||
return this.moveTo(this.createPath(x, y, z, 1), speed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to find and set a path to EntityLiving. Returns {@code true} if successful.
|
||||
*/
|
||||
public boolean moveTo(Entity entity, double speed) {
|
||||
Path path = this.createPath(entity, 1);
|
||||
return path != null && this.moveTo(path, speed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a new path. If it's different from the old path. Checks to adjust path for sun avoiding, and stores start coords.
|
||||
*/
|
||||
public boolean moveTo(@Nullable Path pathentity, double speed) {
|
||||
if (pathentity == null) {
|
||||
this.path = null;
|
||||
return false;
|
||||
} else {
|
||||
if (!pathentity.sameAs(this.path)) {
|
||||
this.path = pathentity;
|
||||
}
|
||||
|
||||
if (this.isDone()) {
|
||||
return false;
|
||||
} else {
|
||||
this.trimPath();
|
||||
if (this.path.getNodeCount() <= 0) {
|
||||
return false;
|
||||
} else {
|
||||
this.speedModifier = speed;
|
||||
Vec3 vec3 = this.getTempMobPos();
|
||||
this.lastStuckCheck = this.tick;
|
||||
this.lastStuckCheckPos = vec3;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Path getPath() {
|
||||
return this.path;
|
||||
}
|
||||
|
||||
public void tick() {
|
||||
++this.tick;
|
||||
if (this.hasDelayedRecomputation) {
|
||||
this.recomputePath();
|
||||
}
|
||||
|
||||
if (!this.isDone()) {
|
||||
if (this.canUpdatePath()) {
|
||||
this.followThePath();
|
||||
} else if (this.path != null && !this.path.isDone()) {
|
||||
Vec3 vec3 = this.getTempMobPos();
|
||||
Vec3 vec31 = this.path.getNextEntityPos(this.npc);
|
||||
if (vec3.y > vec31.y && !this.npc.onGround() && Mth.floor(vec3.x) == Mth.floor(vec31.x) && Mth.floor(vec3.z) == Mth.floor(vec31.z)) {
|
||||
this.path.advance();
|
||||
}
|
||||
}
|
||||
if (!this.isDone()) {
|
||||
assert this.path != null;
|
||||
Vec3 vec32 = this.path.getNextEntityPos(this.npc);
|
||||
this.npc.getMoveControl().setWantedPosition(vec32.x, this.getGroundY(vec32), vec32.z, this.speedModifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected double getGroundY(Vec3 vec) {
|
||||
BlockPos blockpos = BlockPos.containing(vec);
|
||||
return this.level.getBlockState(blockpos.below()).isAir() ? vec.y : WalkNodeEvaluator.getFloorLevel(this.level, blockpos);
|
||||
}
|
||||
|
||||
protected void followThePath() {
|
||||
Vec3 vec3 = this.getTempMobPos();
|
||||
this.maxDistanceToWaypoint = this.npc.getBbWidth() > 0.75F ? this.npc.getBbWidth() / 2.0F : 0.75F - this.npc.getBbWidth() / 2.0F;
|
||||
boolean flag = isFlag();
|
||||
if (flag || this.canCutCorner(this.path != null ? this.path.getNextNode().type : null) && this.shouldTargetNextNodeInDirection(vec3)) {
|
||||
assert this.path != null;
|
||||
this.path.advance();
|
||||
}
|
||||
|
||||
this.doStuckDetection(vec3);
|
||||
}
|
||||
|
||||
private boolean isFlag() {
|
||||
assert this.path != null;
|
||||
Vec3i vec3i = this.path.getNextNodePos();
|
||||
double d0 = Math.abs(this.npc.getX() - ((double)vec3i.getX() + (this.npc.getBbWidth() + 1) / 2D)); //Forge: Fix MC-94054
|
||||
double d1 = Math.abs(this.npc.getY() - (double)vec3i.getY());
|
||||
double d2 = Math.abs(this.npc.getZ() - ((double)vec3i.getZ() + (this.npc.getBbWidth() + 1) / 2D)); //Forge: Fix MC-94054
|
||||
boolean flag = d0 <= (double)this.maxDistanceToWaypoint && d2 <= (double)this.maxDistanceToWaypoint && d1 < 1.0D; //Forge: Fix MC-94054
|
||||
return flag;
|
||||
}
|
||||
|
||||
private boolean shouldTargetNextNodeInDirection(Vec3 vec) {
|
||||
assert this.path != null;
|
||||
if (this.path.getNextNodeIndex() + 1 >= this.path.getNodeCount()) {
|
||||
return false;
|
||||
} else {
|
||||
Vec3 vec3 = Vec3.atBottomCenterOf(this.path.getNextNodePos());
|
||||
if (!vec.closerThan(vec3, 2.0D)) {
|
||||
return false;
|
||||
} else if (this.canMoveDirectly(vec, this.path.getNextEntityPos(this.npc))) {
|
||||
return true;
|
||||
} else {
|
||||
Vec3 vec31 = Vec3.atBottomCenterOf(this.path.getNodePos(this.path.getNextNodeIndex() + 1));
|
||||
Vec3 vec32 = vec3.subtract(vec);
|
||||
Vec3 vec33 = vec31.subtract(vec);
|
||||
double d0 = vec32.lengthSqr();
|
||||
double d1 = vec33.lengthSqr();
|
||||
boolean flag = d1 < d0;
|
||||
boolean flag1 = d0 < 0.5D;
|
||||
if (!flag && !flag1) {
|
||||
return false;
|
||||
} else {
|
||||
Vec3 vec34 = vec32.normalize();
|
||||
Vec3 vec35 = vec33.normalize();
|
||||
return vec35.dot(vec34) < 0.0D;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if entity haven't been moved when last checked and if so, stops the current navigation.
|
||||
*/
|
||||
protected void doStuckDetection(Vec3 positionVec3) {
|
||||
if (this.tick - this.lastStuckCheck > 100) {
|
||||
float f = this.npc.getSpeed() >= 1.0F ? this.npc.getSpeed() : this.npc.getSpeed() * this.npc.getSpeed();
|
||||
float f1 = f * 100.0F * 0.25F;
|
||||
if (positionVec3.distanceToSqr(this.lastStuckCheckPos) < (double)(f1 * f1)) {
|
||||
this.isStuck = true;
|
||||
this.stop();
|
||||
} else {
|
||||
this.isStuck = false;
|
||||
}
|
||||
|
||||
this.lastStuckCheck = this.tick;
|
||||
this.lastStuckCheckPos = positionVec3;
|
||||
}
|
||||
|
||||
if (this.path != null && !this.path.isDone()) {
|
||||
Vec3i vec3i = this.path.getNextNodePos();
|
||||
long i = this.level.getGameTime();
|
||||
if (vec3i.equals(this.timeoutCachedNode)) {
|
||||
this.timeoutTimer += i - this.lastTimeoutCheck;
|
||||
} else {
|
||||
this.timeoutCachedNode = vec3i;
|
||||
double d0 = positionVec3.distanceTo(Vec3.atBottomCenterOf(this.timeoutCachedNode));
|
||||
this.timeoutLimit = this.npc.getSpeed() > 0.0F ? d0 / (double)this.npc.getSpeed() * 20.0D : 0.0D;
|
||||
}
|
||||
|
||||
if (this.timeoutLimit > 0.0D && (double)this.timeoutTimer > this.timeoutLimit * 3.0D) {
|
||||
this.timeoutPath();
|
||||
}
|
||||
|
||||
this.lastTimeoutCheck = i;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void timeoutPath() {
|
||||
this.resetStuckTimeout();
|
||||
this.stop();
|
||||
}
|
||||
|
||||
private void resetStuckTimeout() {
|
||||
this.timeoutCachedNode = Vec3i.ZERO;
|
||||
this.timeoutTimer = 0L;
|
||||
this.timeoutLimit = 0.0D;
|
||||
this.isStuck = false;
|
||||
}
|
||||
|
||||
public boolean isDone() {
|
||||
return this.path == null || this.path.isDone();
|
||||
}
|
||||
|
||||
public boolean isInProgress() {
|
||||
return !this.isDone();
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
this.path = null;
|
||||
}
|
||||
|
||||
protected abstract Vec3 getTempMobPos();
|
||||
|
||||
protected abstract boolean canUpdatePath();
|
||||
|
||||
protected boolean isInLiquid() {
|
||||
return this.npc.isInWaterOrBubble() || this.npc.isInLava();
|
||||
}
|
||||
|
||||
protected void trimPath() {
|
||||
if (this.path != null) {
|
||||
for(int i = 0; i < this.path.getNodeCount(); ++i) {
|
||||
Node node = this.path.getNode(i);
|
||||
Node node1 = i + 1 < this.path.getNodeCount() ? this.path.getNode(i + 1) : null;
|
||||
BlockState blockstate = this.level.getBlockState(new BlockPos(node.x, node.y, node.z));
|
||||
if (blockstate.is(BlockTags.CAULDRONS)) {
|
||||
this.path.replaceNode(i, node.cloneAndMove(node.x, node.y + 1, node.z));
|
||||
if (node1 != null && node.y >= node1.y) {
|
||||
this.path.replaceNode(i + 1, node.cloneAndMove(node1.x, node.y + 1, node1.z));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the specified entity can safely walk to the specified location.
|
||||
*/
|
||||
protected boolean canMoveDirectly(Vec3 posVec31, Vec3 posVec32) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean canCutCorner(BlockPathTypes pathType) {
|
||||
return pathType != BlockPathTypes.DANGER_FIRE && pathType != BlockPathTypes.DANGER_OTHER && pathType != BlockPathTypes.WALKABLE_DOOR;
|
||||
}
|
||||
|
||||
protected static boolean isClearForMovementBetween(NPCServerPlayer npc, Vec3 pos1, Vec3 pos2, boolean allowSwimming) {
|
||||
Vec3 vec3 = new Vec3(pos2.x, pos2.y + (double)npc.getBbHeight() * 0.5D, pos2.z);
|
||||
return npc.level().clip(new ClipContext(pos1, vec3, ClipContext.Block.COLLIDER, allowSwimming ? ClipContext.Fluid.ANY : ClipContext.Fluid.NONE, npc)).getType() == HitResult.Type.MISS;
|
||||
}
|
||||
|
||||
public boolean isStableDestination(BlockPos pos) {
|
||||
BlockPos blockpos = pos.below();
|
||||
return this.level.getBlockState(blockpos).isSolidRender(this.level, blockpos);
|
||||
}
|
||||
|
||||
public NPCNodeEvaluator getNodeEvaluator() {
|
||||
return this.nodeEvaluator;
|
||||
}
|
||||
|
||||
public void setCanFloat(boolean canSwim) {
|
||||
this.nodeEvaluator.setCanFloat(canSwim);
|
||||
}
|
||||
|
||||
public boolean canFloat() {
|
||||
return this.nodeEvaluator.canFloat();
|
||||
}
|
||||
|
||||
public boolean shouldRecomputePath(BlockPos pos) {
|
||||
if (this.hasDelayedRecomputation) {
|
||||
return false;
|
||||
} else if (this.path != null && !this.path.isDone() && this.path.getNodeCount() != 0) {
|
||||
Node node = this.path.getEndNode();
|
||||
assert node != null;
|
||||
Vec3 vec3 = new Vec3(((double)node.x + this.npc.getX()) / 2.0D, ((double)node.y + this.npc.getY()) / 2.0D, ((double)node.z + this.npc.getZ()) / 2.0D);
|
||||
return pos.closerToCenterThan(vec3, this.path.getNodeCount() - this.path.getNextNodeIndex());
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public float getMaxDistanceToWaypoint() {
|
||||
return this.maxDistanceToWaypoint;
|
||||
}
|
||||
|
||||
public boolean isStuck() {
|
||||
return this.isStuck;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* Copyright 2025-2026 R3944Realms
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.r3944realms.eroticdungeongame.content.entity.npc.ai.navigation;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
import net.minecraft.world.entity.Mob;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.pathfinder.PathFinder;
|
||||
import net.minecraft.world.level.pathfinder.SwimNodeEvaluator;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
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.NPCSwimNodeEvaluator;
|
||||
|
||||
public class NPCWaterBoundPathNavigation extends NPCPathNavigation {
|
||||
private boolean allowBreaching;
|
||||
|
||||
public NPCWaterBoundPathNavigation(NPCServerPlayer npc, Level level) {
|
||||
super(npc, level);
|
||||
}
|
||||
|
||||
protected NPCPathFinder createPathFinder(int maxVisitedNodes) {
|
||||
this.allowBreaching = false; //todo 考虑下这里修改适配NPC
|
||||
this.nodeEvaluator = new NPCSwimNodeEvaluator(false);
|
||||
return new NPCPathFinder(this.nodeEvaluator, maxVisitedNodes);
|
||||
}
|
||||
|
||||
protected boolean canUpdatePath() {
|
||||
return this.allowBreaching || this.isInLiquid();
|
||||
}
|
||||
|
||||
protected Vec3 getTempMobPos() {
|
||||
return new Vec3(this.npc.getX(), this.npc.getY(0.5D), this.npc.getZ());
|
||||
}
|
||||
|
||||
protected double getGroundY(Vec3 vec) {
|
||||
return vec.y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the specified entity can safely walk to the specified location.
|
||||
*/
|
||||
protected boolean canMoveDirectly(Vec3 posVec31, Vec3 posVec32) {
|
||||
return isClearForMovementBetween(this.npc, posVec31, posVec32, false);
|
||||
}
|
||||
|
||||
public boolean isStableDestination(BlockPos pos) {
|
||||
return !this.level.getBlockState(pos).isSolidRender(this.level, pos);
|
||||
}
|
||||
|
||||
public void setCanFloat(boolean canSwim) {
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,325 @@
|
|||
/*
|
||||
* Copyright 2025-2026 R3944Realms
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.r3944realms.eroticdungeongame.content.entity.npc.ai.navigation.pathfinder;
|
||||
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.level.BlockGetter;
|
||||
import net.minecraft.world.level.PathNavigationRegion;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.pathfinder.BlockPathTypes;
|
||||
import net.minecraft.world.level.pathfinder.Node;
|
||||
import net.minecraft.world.level.pathfinder.Target;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import top.r3944realms.eroticdungeongame.content.entity.npc.NPCServerPlayer;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
|
||||
public class NPCFlyNodeEvaluator extends NPCWalkNodeEvaluator {
|
||||
private final Long2ObjectMap<BlockPathTypes> pathTypeByPosCache = new Long2ObjectOpenHashMap<>();
|
||||
private static final float SMALL_MOB_INFLATED_START_NODE_BOUNDING_BOX = 1.5F;
|
||||
private static final int MAX_START_NODE_CANDIDATES = 10;
|
||||
|
||||
public void prepare(PathNavigationRegion level, NPCServerPlayer npc) {
|
||||
super.prepare(level, npc);
|
||||
this.pathTypeByPosCache.clear();
|
||||
npc.onPathfindingStart();
|
||||
}
|
||||
|
||||
public void done() {
|
||||
this.npc.onPathfindingDone();
|
||||
this.pathTypeByPosCache.clear();
|
||||
super.done();
|
||||
}
|
||||
|
||||
public Node getStart() {
|
||||
int i;
|
||||
if (this.canFloat() && this.npc.isInWater()) {
|
||||
i = this.npc.getBlockY();
|
||||
BlockPos.MutableBlockPos blockpos$mutableblockpos = new BlockPos.MutableBlockPos(this.npc.getX(), (double)i, this.npc.getZ());
|
||||
|
||||
for(BlockState blockstate = this.level.getBlockState(blockpos$mutableblockpos); blockstate.is(Blocks.WATER); blockstate = this.level.getBlockState(blockpos$mutableblockpos)) {
|
||||
++i;
|
||||
blockpos$mutableblockpos.set(this.npc.getX(), (double)i, this.npc.getZ());
|
||||
}
|
||||
} else {
|
||||
i = Mth.floor(this.npc.getY() + 0.5D);
|
||||
}
|
||||
|
||||
BlockPos blockpos1 = BlockPos.containing(this.npc.getX(), (double)i, this.npc.getZ());
|
||||
if (!this.canStartAt(blockpos1)) {
|
||||
for(BlockPos blockpos : this.iteratePathfindingStartNodeCandidatePositions(this.npc)) {
|
||||
if (this.canStartAt(blockpos)) {
|
||||
return super.getStartNode(blockpos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return super.getStartNode(blockpos1);
|
||||
}
|
||||
|
||||
protected boolean canStartAt(BlockPos pos) {
|
||||
BlockPathTypes blockpathtypes = this.getBlockPathType(this.npc, pos);
|
||||
return this.npc.getPathfindingMalus(blockpathtypes) >= 0.0F;
|
||||
}
|
||||
|
||||
public Target getGoal(double x, double y, double z) {
|
||||
return this.getTargetFromNode(this.getNode(Mth.floor(x), Mth.floor(y), Mth.floor(z)));
|
||||
}
|
||||
|
||||
public int getNeighbors(Node[] outputArray, Node p_node) {
|
||||
int i = 0;
|
||||
Node node = this.findAcceptedNode(p_node.x, p_node.y, p_node.z + 1);
|
||||
if (this.isOpen(node)) {
|
||||
outputArray[i++] = node;
|
||||
}
|
||||
|
||||
Node node1 = this.findAcceptedNode(p_node.x - 1, p_node.y, p_node.z);
|
||||
if (this.isOpen(node1)) {
|
||||
outputArray[i++] = node1;
|
||||
}
|
||||
|
||||
Node node2 = this.findAcceptedNode(p_node.x + 1, p_node.y, p_node.z);
|
||||
if (this.isOpen(node2)) {
|
||||
outputArray[i++] = node2;
|
||||
}
|
||||
|
||||
Node node3 = this.findAcceptedNode(p_node.x, p_node.y, p_node.z - 1);
|
||||
if (this.isOpen(node3)) {
|
||||
outputArray[i++] = node3;
|
||||
}
|
||||
|
||||
Node node4 = this.findAcceptedNode(p_node.x, p_node.y + 1, p_node.z);
|
||||
if (this.isOpen(node4)) {
|
||||
outputArray[i++] = node4;
|
||||
}
|
||||
|
||||
Node node5 = this.findAcceptedNode(p_node.x, p_node.y - 1, p_node.z);
|
||||
if (this.isOpen(node5)) {
|
||||
outputArray[i++] = node5;
|
||||
}
|
||||
|
||||
Node node6 = this.findAcceptedNode(p_node.x, p_node.y + 1, p_node.z + 1);
|
||||
if (this.isOpen(node6) && this.hasMalus(node) && this.hasMalus(node4)) {
|
||||
outputArray[i++] = node6;
|
||||
}
|
||||
|
||||
Node node7 = this.findAcceptedNode(p_node.x - 1, p_node.y + 1, p_node.z);
|
||||
if (this.isOpen(node7) && this.hasMalus(node1) && this.hasMalus(node4)) {
|
||||
outputArray[i++] = node7;
|
||||
}
|
||||
|
||||
Node node8 = this.findAcceptedNode(p_node.x + 1, p_node.y + 1, p_node.z);
|
||||
if (this.isOpen(node8) && this.hasMalus(node2) && this.hasMalus(node4)) {
|
||||
outputArray[i++] = node8;
|
||||
}
|
||||
|
||||
Node node9 = this.findAcceptedNode(p_node.x, p_node.y + 1, p_node.z - 1);
|
||||
if (this.isOpen(node9) && this.hasMalus(node3) && this.hasMalus(node4)) {
|
||||
outputArray[i++] = node9;
|
||||
}
|
||||
|
||||
Node node10 = this.findAcceptedNode(p_node.x, p_node.y - 1, p_node.z + 1);
|
||||
if (this.isOpen(node10) && this.hasMalus(node) && this.hasMalus(node5)) {
|
||||
outputArray[i++] = node10;
|
||||
}
|
||||
|
||||
Node node11 = this.findAcceptedNode(p_node.x - 1, p_node.y - 1, p_node.z);
|
||||
if (this.isOpen(node11) && this.hasMalus(node1) && this.hasMalus(node5)) {
|
||||
outputArray[i++] = node11;
|
||||
}
|
||||
|
||||
Node node12 = this.findAcceptedNode(p_node.x + 1, p_node.y - 1, p_node.z);
|
||||
if (this.isOpen(node12) && this.hasMalus(node2) && this.hasMalus(node5)) {
|
||||
outputArray[i++] = node12;
|
||||
}
|
||||
|
||||
Node node13 = this.findAcceptedNode(p_node.x, p_node.y - 1, p_node.z - 1);
|
||||
if (this.isOpen(node13) && this.hasMalus(node3) && this.hasMalus(node5)) {
|
||||
outputArray[i++] = node13;
|
||||
}
|
||||
|
||||
Node node14 = this.findAcceptedNode(p_node.x + 1, p_node.y, p_node.z - 1);
|
||||
if (this.isOpen(node14) && this.hasMalus(node3) && this.hasMalus(node2)) {
|
||||
outputArray[i++] = node14;
|
||||
}
|
||||
|
||||
Node node15 = this.findAcceptedNode(p_node.x + 1, p_node.y, p_node.z + 1);
|
||||
if (this.isOpen(node15) && this.hasMalus(node) && this.hasMalus(node2)) {
|
||||
outputArray[i++] = node15;
|
||||
}
|
||||
|
||||
Node node16 = this.findAcceptedNode(p_node.x - 1, p_node.y, p_node.z - 1);
|
||||
if (this.isOpen(node16) && this.hasMalus(node3) && this.hasMalus(node1)) {
|
||||
outputArray[i++] = node16;
|
||||
}
|
||||
|
||||
Node node17 = this.findAcceptedNode(p_node.x - 1, p_node.y, p_node.z + 1);
|
||||
if (this.isOpen(node17) && this.hasMalus(node) && this.hasMalus(node1)) {
|
||||
outputArray[i++] = node17;
|
||||
}
|
||||
|
||||
Node node18 = this.findAcceptedNode(p_node.x + 1, p_node.y + 1, p_node.z - 1);
|
||||
if (this.isOpen(node18) && this.hasMalus(node14) && this.hasMalus(node3) && this.hasMalus(node2) && this.hasMalus(node4) && this.hasMalus(node9) && this.hasMalus(node8)) {
|
||||
outputArray[i++] = node18;
|
||||
}
|
||||
|
||||
Node node19 = this.findAcceptedNode(p_node.x + 1, p_node.y + 1, p_node.z + 1);
|
||||
if (this.isOpen(node19) && this.hasMalus(node15) && this.hasMalus(node) && this.hasMalus(node2) && this.hasMalus(node4) && this.hasMalus(node6) && this.hasMalus(node8)) {
|
||||
outputArray[i++] = node19;
|
||||
}
|
||||
|
||||
Node node20 = this.findAcceptedNode(p_node.x - 1, p_node.y + 1, p_node.z - 1);
|
||||
if (this.isOpen(node20) && this.hasMalus(node16) && this.hasMalus(node3) && this.hasMalus(node1) && this.hasMalus(node4) && this.hasMalus(node9) && this.hasMalus(node7)) {
|
||||
outputArray[i++] = node20;
|
||||
}
|
||||
|
||||
Node node21 = this.findAcceptedNode(p_node.x - 1, p_node.y + 1, p_node.z + 1);
|
||||
if (this.isOpen(node21) && this.hasMalus(node17) && this.hasMalus(node) && this.hasMalus(node1) && this.hasMalus(node4) && this.hasMalus(node6) && this.hasMalus(node7)) {
|
||||
outputArray[i++] = node21;
|
||||
}
|
||||
|
||||
Node node22 = this.findAcceptedNode(p_node.x + 1, p_node.y - 1, p_node.z - 1);
|
||||
if (this.isOpen(node22) && this.hasMalus(node14) && this.hasMalus(node3) && this.hasMalus(node2) && this.hasMalus(node5) && this.hasMalus(node13) && this.hasMalus(node12)) {
|
||||
outputArray[i++] = node22;
|
||||
}
|
||||
|
||||
Node node23 = this.findAcceptedNode(p_node.x + 1, p_node.y - 1, p_node.z + 1);
|
||||
if (this.isOpen(node23) && this.hasMalus(node15) && this.hasMalus(node) && this.hasMalus(node2) && this.hasMalus(node5) && this.hasMalus(node10) && this.hasMalus(node12)) {
|
||||
outputArray[i++] = node23;
|
||||
}
|
||||
|
||||
Node node24 = this.findAcceptedNode(p_node.x - 1, p_node.y - 1, p_node.z - 1);
|
||||
if (this.isOpen(node24) && this.hasMalus(node16) && this.hasMalus(node3) && this.hasMalus(node1) && this.hasMalus(node5) && this.hasMalus(node13) && this.hasMalus(node11)) {
|
||||
outputArray[i++] = node24;
|
||||
}
|
||||
|
||||
Node node25 = this.findAcceptedNode(p_node.x - 1, p_node.y - 1, p_node.z + 1);
|
||||
if (this.isOpen(node25) && this.hasMalus(node17) && this.hasMalus(node) && this.hasMalus(node1) && this.hasMalus(node5) && this.hasMalus(node10) && this.hasMalus(node11)) {
|
||||
outputArray[i++] = node25;
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
private boolean hasMalus(@Nullable Node node) {
|
||||
return node != null && node.costMalus >= 0.0F;
|
||||
}
|
||||
|
||||
private boolean isOpen(@Nullable Node node) {
|
||||
return node != null && !node.closed;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected Node findAcceptedNode(int x, int y, int z) {
|
||||
Node node = null;
|
||||
BlockPathTypes blockpathtypes = this.getCachedBlockPathType(x, y, z);
|
||||
float f = this.npc.getPathfindingMalus(blockpathtypes);
|
||||
if (f >= 0.0F) {
|
||||
node = this.getNode(x, y, z);
|
||||
node.type = blockpathtypes;
|
||||
node.costMalus = Math.max(node.costMalus, f);
|
||||
if (blockpathtypes == BlockPathTypes.WALKABLE) {
|
||||
++node.costMalus;
|
||||
}
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
private BlockPathTypes getCachedBlockPathType(int x, int y, int z) {
|
||||
return this.pathTypeByPosCache.computeIfAbsent(BlockPos.asLong(x, y, z), (p_265010_) -> {
|
||||
return this.getBlockPathType(this.level, x, y, z, this.npc);
|
||||
});
|
||||
}
|
||||
|
||||
public BlockPathTypes getBlockPathType(BlockGetter level, int x, int y, int z, NPCServerPlayer npc) {
|
||||
EnumSet<BlockPathTypes> enumset = EnumSet.noneOf(BlockPathTypes.class);
|
||||
BlockPathTypes blockpathtypes = BlockPathTypes.BLOCKED;
|
||||
BlockPos blockpos = npc.blockPosition();
|
||||
blockpathtypes = super.getBlockPathTypes(level, x, y, z, enumset, blockpathtypes, blockpos);
|
||||
if (enumset.contains(BlockPathTypes.FENCE)) {
|
||||
return BlockPathTypes.FENCE;
|
||||
} else {
|
||||
BlockPathTypes blockpathtypes1 = BlockPathTypes.BLOCKED;
|
||||
|
||||
for(BlockPathTypes blockpathtypes2 : enumset) {
|
||||
if (npc.getPathfindingMalus(blockpathtypes2) < 0.0F) {
|
||||
return blockpathtypes2;
|
||||
}
|
||||
|
||||
if (npc.getPathfindingMalus(blockpathtypes2) >= npc.getPathfindingMalus(blockpathtypes1)) {
|
||||
blockpathtypes1 = blockpathtypes2;
|
||||
}
|
||||
}
|
||||
|
||||
return blockpathtypes == BlockPathTypes.OPEN && npc.getPathfindingMalus(blockpathtypes1) == 0.0F ? BlockPathTypes.OPEN : blockpathtypes1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the node type at the specified postion taking the block below into account
|
||||
*/
|
||||
public BlockPathTypes getBlockPathType(BlockGetter level, int x, int y, int z) {
|
||||
BlockPos.MutableBlockPos blockpos$mutableblockpos = new BlockPos.MutableBlockPos();
|
||||
BlockPathTypes blockpathtypes = getBlockPathTypeRaw(level, blockpos$mutableblockpos.set(x, y, z));
|
||||
if (blockpathtypes == BlockPathTypes.OPEN && y >= level.getMinBuildHeight() + 1) {
|
||||
BlockPathTypes blockpathtypes1 = getBlockPathTypeRaw(level, blockpos$mutableblockpos.set(x, y - 1, z));
|
||||
if (blockpathtypes1 != BlockPathTypes.DAMAGE_FIRE && blockpathtypes1 != BlockPathTypes.LAVA) {
|
||||
if (blockpathtypes1 == BlockPathTypes.DAMAGE_OTHER) {
|
||||
blockpathtypes = BlockPathTypes.DAMAGE_OTHER;
|
||||
} else if (blockpathtypes1 == BlockPathTypes.COCOA) {
|
||||
blockpathtypes = BlockPathTypes.COCOA;
|
||||
} else if (blockpathtypes1 == BlockPathTypes.FENCE) {
|
||||
if (!blockpos$mutableblockpos.equals(this.npc.blockPosition())) {
|
||||
blockpathtypes = BlockPathTypes.FENCE;
|
||||
}
|
||||
} else {
|
||||
blockpathtypes = blockpathtypes1 != BlockPathTypes.WALKABLE && blockpathtypes1 != BlockPathTypes.OPEN && blockpathtypes1 != BlockPathTypes.WATER ? BlockPathTypes.WALKABLE : BlockPathTypes.OPEN;
|
||||
}
|
||||
} else {
|
||||
blockpathtypes = BlockPathTypes.DAMAGE_FIRE;
|
||||
}
|
||||
}
|
||||
|
||||
if (blockpathtypes == BlockPathTypes.WALKABLE || blockpathtypes == BlockPathTypes.OPEN) {
|
||||
blockpathtypes = checkNeighbourBlocks(level, blockpos$mutableblockpos.set(x, y, z), blockpathtypes);
|
||||
}
|
||||
|
||||
return blockpathtypes;
|
||||
}
|
||||
|
||||
private Iterable<BlockPos> iteratePathfindingStartNodeCandidatePositions(NPCServerPlayer npc) {
|
||||
float f = 1.0F;
|
||||
AABB aabb = npc.getBoundingBox();
|
||||
boolean flag = aabb.getSize() < 1.0D;
|
||||
if (!flag) {
|
||||
return List.of(BlockPos.containing(aabb.minX, (double)npc.getBlockY(), aabb.minZ), BlockPos.containing(aabb.minX, (double)npc.getBlockY(), aabb.maxZ), BlockPos.containing(aabb.maxX, (double)npc.getBlockY(), aabb.minZ), BlockPos.containing(aabb.maxX, (double)npc.getBlockY(), aabb.maxZ));
|
||||
} else {
|
||||
double d0 = Math.max(0.0D, (1.5D - aabb.getZsize()) / 2.0D);
|
||||
double d1 = Math.max(0.0D, (1.5D - aabb.getXsize()) / 2.0D);
|
||||
double d2 = Math.max(0.0D, (1.5D - aabb.getYsize()) / 2.0D);
|
||||
AABB aabb1 = aabb.inflate(d1, d2, d0);
|
||||
return BlockPos.randomBetweenClosed(npc.getRandom(), 10, Mth.floor(aabb1.minX), Mth.floor(aabb1.minY), Mth.floor(aabb1.minZ), Mth.floor(aabb1.maxX), Mth.floor(aabb1.maxY), Mth.floor(aabb1.maxZ));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
/*
|
||||
* Copyright 2025-2026 R3944Realms
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.r3944realms.eroticdungeongame.content.entity.npc.ai.navigation.pathfinder;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.level.BlockGetter;
|
||||
import net.minecraft.world.level.PathNavigationRegion;
|
||||
import net.minecraft.world.level.pathfinder.BlockPathTypes;
|
||||
import net.minecraft.world.level.pathfinder.Node;
|
||||
import net.minecraft.world.level.pathfinder.Target;
|
||||
import top.r3944realms.eroticdungeongame.content.entity.npc.NPCServerPlayer;
|
||||
|
||||
public abstract class NPCNodeEvaluator {
|
||||
protected PathNavigationRegion level;
|
||||
protected NPCServerPlayer npc;
|
||||
protected final Int2ObjectMap<Node> nodes = new Int2ObjectOpenHashMap<>();
|
||||
protected int entityWidth;
|
||||
protected int entityHeight;
|
||||
protected int entityDepth;
|
||||
protected boolean canPassDoors;
|
||||
protected boolean canOpenDoors;
|
||||
protected boolean canFloat;
|
||||
protected boolean canWalkOverFences;
|
||||
|
||||
public void prepare(PathNavigationRegion level, NPCServerPlayer npc) {
|
||||
this.level = level;
|
||||
this.npc = npc;
|
||||
this.nodes.clear();
|
||||
this.entityWidth = Mth.floor(npc.getBbWidth() + 1.0F);
|
||||
this.entityHeight = Mth.floor(npc.getBbHeight() + 1.0F);
|
||||
this.entityDepth = Mth.floor(npc.getBbWidth() + 1.0F);
|
||||
}
|
||||
|
||||
public void done() {
|
||||
this.level = null;
|
||||
this.npc = null;
|
||||
}
|
||||
|
||||
protected Node getNode(BlockPos pos) {
|
||||
return this.getNode(pos.getX(), pos.getY(), pos.getZ());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a mapped point or creates and adds one
|
||||
*/
|
||||
protected Node getNode(int x, int y, int z) {
|
||||
return this.nodes.computeIfAbsent(Node.createHash(x, y, z), (p_77332_) -> {
|
||||
return new Node(x, y, z);
|
||||
});
|
||||
}
|
||||
|
||||
public abstract Node getStart();
|
||||
|
||||
public abstract Target getGoal(double x, double y, double z);
|
||||
|
||||
protected Target getTargetFromNode(Node node) {
|
||||
return new Target(node);
|
||||
}
|
||||
|
||||
public abstract int getNeighbors(Node[] outputArray, Node node);
|
||||
|
||||
public abstract BlockPathTypes getBlockPathType(BlockGetter level, int x, int y, int z, NPCServerPlayer npc);
|
||||
|
||||
/**
|
||||
* Returns the node type at the specified postion taking the block below into account
|
||||
*/
|
||||
public abstract BlockPathTypes getBlockPathType(BlockGetter level, int x, int y, int z);
|
||||
|
||||
public void setCanPassDoors(boolean canEnterDoors) {
|
||||
this.canPassDoors = canEnterDoors;
|
||||
}
|
||||
|
||||
public void setCanOpenDoors(boolean canOpenDoors) {
|
||||
this.canOpenDoors = canOpenDoors;
|
||||
}
|
||||
|
||||
public void setCanFloat(boolean canFloat) {
|
||||
this.canFloat = canFloat;
|
||||
}
|
||||
|
||||
public void setCanWalkOverFences(boolean canWalkOverFences) {
|
||||
this.canWalkOverFences = canWalkOverFences;
|
||||
}
|
||||
|
||||
public boolean canPassDoors() {
|
||||
return this.canPassDoors;
|
||||
}
|
||||
|
||||
public boolean canOpenDoors() {
|
||||
return this.canOpenDoors;
|
||||
}
|
||||
|
||||
public boolean canFloat() {
|
||||
return this.canFloat;
|
||||
}
|
||||
|
||||
public boolean canWalkOverFences() {
|
||||
return this.canWalkOverFences;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,159 @@
|
|||
/*
|
||||
* Copyright 2025-2026 R3944Realms
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.r3944realms.eroticdungeongame.content.entity.npc.ai.navigation.pathfinder;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Sets;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.util.profiling.ProfilerFiller;
|
||||
import net.minecraft.util.profiling.metrics.MetricCategory;
|
||||
import net.minecraft.world.level.PathNavigationRegion;
|
||||
import net.minecraft.world.level.pathfinder.*;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import top.r3944realms.eroticdungeongame.content.entity.npc.NPCServerPlayer;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class NPCPathFinder{
|
||||
private static final float FUDGING = 1.5F;
|
||||
private final Node[] neighbors = new Node[32];
|
||||
private final int maxVisitedNodes;
|
||||
private final NPCNodeEvaluator nodeEvaluator;
|
||||
private static final boolean DEBUG = false;
|
||||
private final BinaryHeap openSet = new BinaryHeap();
|
||||
|
||||
public NPCPathFinder(NPCNodeEvaluator nodeEvaluator, int maxVisitedNodes) {
|
||||
this.nodeEvaluator = nodeEvaluator;
|
||||
this.maxVisitedNodes = maxVisitedNodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a path to one of the specified positions and post-processes it or returns null if no path could be found within given accuracy
|
||||
*/
|
||||
@Nullable
|
||||
public Path findPath(PathNavigationRegion region, NPCServerPlayer npc, Set<BlockPos> targetPositions, float maxRange, int accuracy, float searchDepthMultiplier) {
|
||||
this.openSet.clear();
|
||||
this.nodeEvaluator.prepare(region, npc);
|
||||
Node node = this.nodeEvaluator.getStart();
|
||||
if (node == null) {
|
||||
return null;
|
||||
} else {
|
||||
Map<Target, BlockPos> map = targetPositions.stream().collect(Collectors.toMap((pos) -> this.nodeEvaluator.getGoal(pos.getX(), pos.getY(), pos.getZ()), Function.identity()));
|
||||
Path path = this.findPath(region.getProfiler(), node, map, maxRange, accuracy, searchDepthMultiplier);
|
||||
this.nodeEvaluator.done();
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Path findPath(ProfilerFiller profiler, Node p_node, Map<Target, BlockPos> targetPos, float maxRange, int accuracy, float searchDepthMultiplier) {
|
||||
profiler.push("find_path");
|
||||
profiler.markForCharting(MetricCategory.PATH_FINDING);
|
||||
Set<Target> set = targetPos.keySet();
|
||||
p_node.g = 0.0F;
|
||||
p_node.h = this.getBestH(p_node, set);
|
||||
p_node.f = p_node.h;
|
||||
this.openSet.clear();
|
||||
this.openSet.insert(p_node);
|
||||
Set<Node> set1 = ImmutableSet.of();
|
||||
int i = 0;
|
||||
Set<Target> set2 = Sets.newHashSetWithExpectedSize(set.size());
|
||||
int j = (int)((float)this.maxVisitedNodes * searchDepthMultiplier);
|
||||
|
||||
while(!this.openSet.isEmpty()) {
|
||||
++i;
|
||||
if (i >= j) {
|
||||
break;
|
||||
}
|
||||
|
||||
Node node = this.openSet.pop();
|
||||
node.closed = true;
|
||||
|
||||
for(Target target : set) {
|
||||
if (node.distanceManhattan(target) <= (float)accuracy) {
|
||||
target.setReached();
|
||||
set2.add(target);
|
||||
}
|
||||
}
|
||||
|
||||
if (!set2.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!(node.distanceTo(p_node) >= maxRange)) {
|
||||
int k = this.nodeEvaluator.getNeighbors(this.neighbors, node);
|
||||
|
||||
for(int l = 0; l < k; ++l) {
|
||||
Node node1 = this.neighbors[l];
|
||||
float f = this.distance(node, node1);
|
||||
node1.walkedDistance = node.walkedDistance + f;
|
||||
float f1 = node.g + f + node1.costMalus;
|
||||
if (node1.walkedDistance < maxRange && (!node1.inOpenSet() || f1 < node1.g)) {
|
||||
node1.cameFrom = node;
|
||||
node1.g = f1;
|
||||
node1.h = this.getBestH(node1, set) * 1.5F;
|
||||
if (node1.inOpenSet()) {
|
||||
this.openSet.changeCost(node1, node1.g + node1.h);
|
||||
} else {
|
||||
node1.f = node1.g + node1.h;
|
||||
this.openSet.insert(node1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Optional<Path> optional = !set2.isEmpty() ? set2.stream().map((p_77454_) -> this.reconstructPath(p_77454_.getBestNode(), targetPos.get(p_77454_), true)).min(Comparator.comparingInt(Path::getNodeCount)) : set.stream().map((p_77451_) -> this.reconstructPath(p_77451_.getBestNode(), targetPos.get(p_77451_), false)).min(Comparator.comparingDouble(Path::getDistToTarget).thenComparingInt(Path::getNodeCount));
|
||||
profiler.pop();
|
||||
return optional.orElse(null);
|
||||
}
|
||||
|
||||
protected float distance(Node first, Node second) {
|
||||
return first.distanceTo(second);
|
||||
}
|
||||
|
||||
private float getBestH(Node node, Set<Target> targets) {
|
||||
float f = Float.MAX_VALUE;
|
||||
|
||||
for(Target target : targets) {
|
||||
float f1 = node.distanceTo(target);
|
||||
target.updateBest(f1, node);
|
||||
f = Math.min(f1, f);
|
||||
}
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a recursive path point structure into a path
|
||||
*/
|
||||
private Path reconstructPath(Node point, BlockPos targetPos, boolean reachesTarget) {
|
||||
List<Node> list = Lists.newArrayList();
|
||||
Node node = point;
|
||||
list.add(0, point);
|
||||
|
||||
while(node.cameFrom != null) {
|
||||
node = node.cameFrom;
|
||||
list.add(0, node);
|
||||
}
|
||||
|
||||
return new Path(list, targetPos, reachesTarget);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,147 @@
|
|||
/*
|
||||
* Copyright 2025-2026 R3944Realms
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.r3944realms.eroticdungeongame.content.entity.npc.ai.navigation.pathfinder;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.tags.FluidTags;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.level.BlockGetter;
|
||||
import net.minecraft.world.level.PathNavigationRegion;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.material.FluidState;
|
||||
import net.minecraft.world.level.pathfinder.*;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import top.r3944realms.eroticdungeongame.content.entity.npc.NPCServerPlayer;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class NPCSwimNodeEvaluator extends NPCNodeEvaluator {
|
||||
private final boolean allowBreaching;
|
||||
private final Long2ObjectMap<BlockPathTypes> pathTypesByPosCache = new Long2ObjectOpenHashMap<>();
|
||||
|
||||
public NPCSwimNodeEvaluator(boolean allowBreaching) {
|
||||
this.allowBreaching = allowBreaching;
|
||||
}
|
||||
|
||||
public void prepare(PathNavigationRegion level, NPCServerPlayer npc) {
|
||||
super.prepare(level, npc);
|
||||
this.pathTypesByPosCache.clear();
|
||||
}
|
||||
|
||||
public void done() {
|
||||
super.done();
|
||||
this.pathTypesByPosCache.clear();
|
||||
}
|
||||
|
||||
public Node getStart() {
|
||||
return this.getNode(Mth.floor(this.npc.getBoundingBox().minX), Mth.floor(this.npc.getBoundingBox().minY + 0.5D), Mth.floor(this.npc.getBoundingBox().minZ));
|
||||
}
|
||||
|
||||
public Target getGoal(double x, double y, double z) {
|
||||
return this.getTargetFromNode(this.getNode(Mth.floor(x), Mth.floor(y), Mth.floor(z)));
|
||||
}
|
||||
|
||||
public int getNeighbors(Node[] outputArray, Node p_node) {
|
||||
int i = 0;
|
||||
Map<Direction, Node> map = Maps.newEnumMap(Direction.class);
|
||||
|
||||
for(Direction direction : Direction.values()) {
|
||||
Node node = this.findAcceptedNode(p_node.x + direction.getStepX(), p_node.y + direction.getStepY(), p_node.z + direction.getStepZ());
|
||||
map.put(direction, node);
|
||||
if (this.isNodeValid(node)) {
|
||||
outputArray[i++] = node;
|
||||
}
|
||||
}
|
||||
|
||||
for(Direction direction1 : Direction.Plane.HORIZONTAL) {
|
||||
Direction direction2 = direction1.getClockWise();
|
||||
Node node1 = this.findAcceptedNode(p_node.x + direction1.getStepX() + direction2.getStepX(), p_node.y, p_node.z + direction1.getStepZ() + direction2.getStepZ());
|
||||
if (this.isDiagonalNodeValid(node1, map.get(direction1), map.get(direction2))) {
|
||||
outputArray[i++] = node1;
|
||||
}
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
protected boolean isNodeValid(@Nullable Node node) {
|
||||
return node != null && !node.closed;
|
||||
}
|
||||
|
||||
protected boolean isDiagonalNodeValid(@Nullable Node root, @Nullable Node horizontal, @Nullable Node clockwise) {
|
||||
return this.isNodeValid(root) && horizontal != null && horizontal.costMalus >= 0.0F && clockwise != null && clockwise.costMalus >= 0.0F;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected Node findAcceptedNode(int x, int y, int z) {
|
||||
Node node = null;
|
||||
BlockPathTypes blockpathtypes = this.getCachedBlockType(x, y, z);
|
||||
if (this.allowBreaching && blockpathtypes == BlockPathTypes.BREACH || blockpathtypes == BlockPathTypes.WATER) {
|
||||
float f = this.npc.getPathfindingMalus(blockpathtypes);
|
||||
if (f >= 0.0F) {
|
||||
node = this.getNode(x, y, z);
|
||||
node.type = blockpathtypes;
|
||||
node.costMalus = Math.max(node.costMalus, f);
|
||||
if (this.level.getFluidState(new BlockPos(x, y, z)).isEmpty()) {
|
||||
node.costMalus += 8.0F;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
protected BlockPathTypes getCachedBlockType(int x, int y, int z) {
|
||||
return this.pathTypesByPosCache.computeIfAbsent(BlockPos.asLong(x, y, z), (p_192957_) -> {
|
||||
return this.getBlockPathType(this.level, x, y, z);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the node type at the specified postion taking the block below into account
|
||||
*/
|
||||
public BlockPathTypes getBlockPathType(BlockGetter level, int x, int y, int z) {
|
||||
return this.getBlockPathType(level, x, y, z, this.npc);
|
||||
}
|
||||
|
||||
public BlockPathTypes getBlockPathType(BlockGetter level, int x, int y, int z, NPCServerPlayer mob) {
|
||||
BlockPos.MutableBlockPos blockpos$mutableblockpos = new BlockPos.MutableBlockPos();
|
||||
|
||||
for(int i = x; i < x + this.entityWidth; ++i) {
|
||||
for(int j = y; j < y + this.entityHeight; ++j) {
|
||||
for(int k = z; k < z + this.entityDepth; ++k) {
|
||||
FluidState fluidstate = level.getFluidState(blockpos$mutableblockpos.set(i, j, k));
|
||||
BlockState blockstate = level.getBlockState(blockpos$mutableblockpos.set(i, j, k));
|
||||
if (fluidstate.isEmpty() && blockstate.isPathfindable(level, blockpos$mutableblockpos.below(), PathComputationType.WATER) && blockstate.isAir()) {
|
||||
return BlockPathTypes.BREACH;
|
||||
}
|
||||
|
||||
if (!fluidstate.is(FluidTags.WATER)) {
|
||||
return BlockPathTypes.BLOCKED;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BlockState blockstate1 = level.getBlockState(blockpos$mutableblockpos);
|
||||
return blockstate1.isPathfindable(level, blockpos$mutableblockpos, PathComputationType.WATER) ? BlockPathTypes.WATER : BlockPathTypes.BLOCKED;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,574 @@
|
|||
/*
|
||||
* Copyright 2025-2026 R3944Realms
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.r3944realms.eroticdungeongame.content.entity.npc.ai.navigation.pathfinder;
|
||||
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2BooleanMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2BooleanOpenHashMap;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.tags.BlockTags;
|
||||
import net.minecraft.tags.FluidTags;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.level.BlockGetter;
|
||||
import net.minecraft.world.level.PathNavigationRegion;
|
||||
import net.minecraft.world.level.block.*;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.material.FluidState;
|
||||
import net.minecraft.world.level.material.Fluids;
|
||||
import net.minecraft.world.level.pathfinder.BlockPathTypes;
|
||||
import net.minecraft.world.level.pathfinder.Node;
|
||||
import net.minecraft.world.level.pathfinder.PathComputationType;
|
||||
import net.minecraft.world.level.pathfinder.Target;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import net.minecraft.world.phys.shapes.VoxelShape;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import top.r3944realms.eroticdungeongame.content.entity.npc.NPCServerPlayer;
|
||||
|
||||
import java.util.EnumSet;
|
||||
|
||||
public class NPCWalkNodeEvaluator extends NPCNodeEvaluator {
|
||||
public static final double SPACE_BETWEEN_WALL_POSTS = 0.5D;
|
||||
private static final double DEFAULT_npc_JUMP_HEIGHT = 1.125D;
|
||||
private final Long2ObjectMap<BlockPathTypes> pathTypesByPosCache = new Long2ObjectOpenHashMap<>();
|
||||
private final Object2BooleanMap<AABB> collisionCache = new Object2BooleanOpenHashMap<>();
|
||||
|
||||
public void prepare(PathNavigationRegion level, NPCServerPlayer npc) {
|
||||
super.prepare(level, npc);
|
||||
npc.onPathfindingStart();
|
||||
}
|
||||
|
||||
public void done() {
|
||||
this.npc.onPathfindingDone();
|
||||
this.pathTypesByPosCache.clear();
|
||||
this.collisionCache.clear();
|
||||
super.done();
|
||||
}
|
||||
|
||||
public Node getStart() {
|
||||
BlockPos.MutableBlockPos blockpos$mutableblockpos = new BlockPos.MutableBlockPos();
|
||||
int i = this.npc.getBlockY();
|
||||
BlockState blockstate = this.level.getBlockState(blockpos$mutableblockpos.set(this.npc.getX(), i, this.npc.getZ()));
|
||||
if (!this.npc.canStandOnFluid(blockstate.getFluidState())) {
|
||||
if (this.canFloat() && this.npc.isInWater()) {
|
||||
while(true) {
|
||||
if (!blockstate.is(Blocks.WATER) && blockstate.getFluidState() != Fluids.WATER.getSource(false)) {
|
||||
--i;
|
||||
break;
|
||||
}
|
||||
|
||||
++i;
|
||||
blockstate = this.level.getBlockState(blockpos$mutableblockpos.set(this.npc.getX(), i, this.npc.getZ()));
|
||||
}
|
||||
} else if (this.npc.onGround()) {
|
||||
i = Mth.floor(this.npc.getY() + 0.5D);
|
||||
} else {
|
||||
BlockPos blockpos;
|
||||
for(blockpos = this.npc.blockPosition(); (this.level.getBlockState(blockpos).isAir() || this.level.getBlockState(blockpos).isPathfindable(this.level, blockpos, PathComputationType.LAND)) && blockpos.getY() > this.npc.level().getMinBuildHeight(); blockpos = blockpos.below()) {
|
||||
}
|
||||
|
||||
i = blockpos.above().getY();
|
||||
}
|
||||
} else {
|
||||
while(this.npc.canStandOnFluid(blockstate.getFluidState())) {
|
||||
++i;
|
||||
blockstate = this.level.getBlockState(blockpos$mutableblockpos.set(this.npc.getX(), i, this.npc.getZ()));
|
||||
}
|
||||
|
||||
--i;
|
||||
}
|
||||
|
||||
BlockPos blockpos1 = this.npc.blockPosition();
|
||||
if (!this.canStartAt(blockpos$mutableblockpos.set(blockpos1.getX(), i, blockpos1.getZ()))) {
|
||||
AABB aabb = this.npc.getBoundingBox();
|
||||
if (this.canStartAt(blockpos$mutableblockpos.set(aabb.minX, i, aabb.minZ)) || this.canStartAt(blockpos$mutableblockpos.set(aabb.minX, i, aabb.maxZ)) || this.canStartAt(blockpos$mutableblockpos.set(aabb.maxX, i, aabb.minZ)) || this.canStartAt(blockpos$mutableblockpos.set(aabb.maxX, i, aabb.maxZ))) {
|
||||
return this.getStartNode(blockpos$mutableblockpos);
|
||||
}
|
||||
}
|
||||
|
||||
return this.getStartNode(new BlockPos(blockpos1.getX(), i, blockpos1.getZ()));
|
||||
}
|
||||
|
||||
protected Node getStartNode(BlockPos pos) {
|
||||
Node node = this.getNode(pos);
|
||||
node.type = this.getBlockPathType(this.npc, node.asBlockPos());
|
||||
node.costMalus = this.npc.getPathfindingMalus(node.type);
|
||||
return node;
|
||||
}
|
||||
|
||||
protected boolean canStartAt(BlockPos pos) {
|
||||
BlockPathTypes blockpathtypes = this.getBlockPathType(this.npc, pos);
|
||||
return blockpathtypes != BlockPathTypes.OPEN && this.npc.getPathfindingMalus(blockpathtypes) >= 0.0F;
|
||||
}
|
||||
|
||||
public Target getGoal(double x, double y, double z) {
|
||||
return this.getTargetFromNode(this.getNode(Mth.floor(x), Mth.floor(y), Mth.floor(z)));
|
||||
}
|
||||
|
||||
public int getNeighbors(Node[] outputArray, Node p_node) {
|
||||
int i = 0;
|
||||
int j = 0;
|
||||
BlockPathTypes blockpathtypes = this.getCachedBlockType(this.npc, p_node.x, p_node.y + 1, p_node.z);
|
||||
BlockPathTypes blockpathtypes1 = this.getCachedBlockType(this.npc, p_node.x, p_node.y, p_node.z);
|
||||
if (this.npc.getPathfindingMalus(blockpathtypes) >= 0.0F && blockpathtypes1 != BlockPathTypes.STICKY_HONEY) {
|
||||
j = Mth.floor(Math.max(1.0F, this.npc.getStepHeight()));
|
||||
}
|
||||
|
||||
double d0 = this.getFloorLevel(new BlockPos(p_node.x, p_node.y, p_node.z));
|
||||
Node node = this.findAcceptedNode(p_node.x, p_node.y, p_node.z + 1, j, d0, Direction.SOUTH, blockpathtypes1);
|
||||
if (this.isNeighborValid(node, p_node)) {
|
||||
outputArray[i++] = node;
|
||||
}
|
||||
|
||||
Node node1 = this.findAcceptedNode(p_node.x - 1, p_node.y, p_node.z, j, d0, Direction.WEST, blockpathtypes1);
|
||||
if (this.isNeighborValid(node1, p_node)) {
|
||||
outputArray[i++] = node1;
|
||||
}
|
||||
|
||||
Node node2 = this.findAcceptedNode(p_node.x + 1, p_node.y, p_node.z, j, d0, Direction.EAST, blockpathtypes1);
|
||||
if (this.isNeighborValid(node2, p_node)) {
|
||||
outputArray[i++] = node2;
|
||||
}
|
||||
|
||||
Node node3 = this.findAcceptedNode(p_node.x, p_node.y, p_node.z - 1, j, d0, Direction.NORTH, blockpathtypes1);
|
||||
if (this.isNeighborValid(node3, p_node)) {
|
||||
outputArray[i++] = node3;
|
||||
}
|
||||
|
||||
Node node4 = this.findAcceptedNode(p_node.x - 1, p_node.y, p_node.z - 1, j, d0, Direction.NORTH, blockpathtypes1);
|
||||
if (this.isDiagonalValid(p_node, node1, node3, node4)) {
|
||||
outputArray[i++] = node4;
|
||||
}
|
||||
|
||||
Node node5 = this.findAcceptedNode(p_node.x + 1, p_node.y, p_node.z - 1, j, d0, Direction.NORTH, blockpathtypes1);
|
||||
if (this.isDiagonalValid(p_node, node2, node3, node5)) {
|
||||
outputArray[i++] = node5;
|
||||
}
|
||||
|
||||
Node node6 = this.findAcceptedNode(p_node.x - 1, p_node.y, p_node.z + 1, j, d0, Direction.SOUTH, blockpathtypes1);
|
||||
if (this.isDiagonalValid(p_node, node1, node, node6)) {
|
||||
outputArray[i++] = node6;
|
||||
}
|
||||
|
||||
Node node7 = this.findAcceptedNode(p_node.x + 1, p_node.y, p_node.z + 1, j, d0, Direction.SOUTH, blockpathtypes1);
|
||||
if (this.isDiagonalValid(p_node, node2, node, node7)) {
|
||||
outputArray[i++] = node7;
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
protected boolean isNeighborValid(@Nullable Node neighbor, Node node) {
|
||||
return neighbor != null && !neighbor.closed && (neighbor.costMalus >= 0.0F || node.costMalus < 0.0F);
|
||||
}
|
||||
|
||||
protected boolean isDiagonalValid(Node root, @Nullable Node xNode, @Nullable Node zNode, @Nullable Node diagonal) {
|
||||
if (diagonal != null && zNode != null && xNode != null) {
|
||||
if (diagonal.closed) {
|
||||
return false;
|
||||
} else if (zNode.y <= root.y && xNode.y <= root.y) {
|
||||
if (xNode.type != BlockPathTypes.WALKABLE_DOOR && zNode.type != BlockPathTypes.WALKABLE_DOOR && diagonal.type != BlockPathTypes.WALKABLE_DOOR) {
|
||||
boolean flag = zNode.type == BlockPathTypes.FENCE && xNode.type == BlockPathTypes.FENCE && (double)this.npc.getBbWidth() < 0.5D;
|
||||
return diagonal.costMalus >= 0.0F && (zNode.y < root.y || zNode.costMalus >= 0.0F || flag) && (xNode.y < root.y || xNode.costMalus >= 0.0F || flag);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean doesBlockHavePartialCollision(BlockPathTypes blockPathType) {
|
||||
return blockPathType == BlockPathTypes.FENCE || blockPathType == BlockPathTypes.DOOR_WOOD_CLOSED || blockPathType == BlockPathTypes.DOOR_IRON_CLOSED;
|
||||
}
|
||||
|
||||
private boolean canReachWithoutCollision(Node node) {
|
||||
AABB aabb = this.npc.getBoundingBox();
|
||||
Vec3 vec3 = new Vec3((double)node.x - this.npc.getX() + aabb.getXsize() / 2.0D, (double)node.y - this.npc.getY() + aabb.getYsize() / 2.0D, (double)node.z - this.npc.getZ() + aabb.getZsize() / 2.0D);
|
||||
int i = Mth.ceil(vec3.length() / aabb.getSize());
|
||||
vec3 = vec3.scale(1.0F / (float)i);
|
||||
|
||||
for(int j = 1; j <= i; ++j) {
|
||||
aabb = aabb.move(vec3);
|
||||
if (this.hasCollisions(aabb)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected double getFloorLevel(BlockPos pos) {
|
||||
return (this.canFloat() || this.isAmphibious()) && this.level.getFluidState(pos).is(FluidTags.WATER) ? (double)pos.getY() + 0.5D : getFloorLevel(this.level, pos);
|
||||
}
|
||||
|
||||
public static double getFloorLevel(BlockGetter level, BlockPos pos) {
|
||||
BlockPos blockpos = pos.below();
|
||||
VoxelShape voxelshape = level.getBlockState(blockpos).getCollisionShape(level, blockpos);
|
||||
return (double)blockpos.getY() + (voxelshape.isEmpty() ? 0.0D : voxelshape.max(Direction.Axis.Y));
|
||||
}
|
||||
|
||||
protected boolean isAmphibious() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected Node findAcceptedNode(int x, int y, int z, int verticalDeltaLimit, double nodeFloorLevel, Direction direction, BlockPathTypes pathType) {
|
||||
Node node = null;
|
||||
BlockPos.MutableBlockPos blockpos$mutableblockpos = new BlockPos.MutableBlockPos();
|
||||
double d0 = this.getFloorLevel(blockpos$mutableblockpos.set(x, y, z));
|
||||
if (d0 - nodeFloorLevel > this.getnpcJumpHeight()) {
|
||||
return null;
|
||||
} else {
|
||||
BlockPathTypes blockpathtypes = this.getCachedBlockType(this.npc, x, y, z);
|
||||
float f = this.npc.getPathfindingMalus(blockpathtypes);
|
||||
double d1 = (double)this.npc.getBbWidth() / 2.0D;
|
||||
if (f >= 0.0F) {
|
||||
node = this.getNodeAndUpdateCostToMax(x, y, z, blockpathtypes, f);
|
||||
}
|
||||
|
||||
if (doesBlockHavePartialCollision(pathType) && node != null && node.costMalus >= 0.0F && !this.canReachWithoutCollision(node)) {
|
||||
node = null;
|
||||
}
|
||||
|
||||
if (blockpathtypes != BlockPathTypes.WALKABLE && (!this.isAmphibious() || blockpathtypes != BlockPathTypes.WATER)) {
|
||||
if ((node == null || node.costMalus < 0.0F) && verticalDeltaLimit > 0 && (blockpathtypes != BlockPathTypes.FENCE || this.canWalkOverFences()) && blockpathtypes != BlockPathTypes.UNPASSABLE_RAIL && blockpathtypes != BlockPathTypes.TRAPDOOR && blockpathtypes != BlockPathTypes.POWDER_SNOW) {
|
||||
node = this.findAcceptedNode(x, y + 1, z, verticalDeltaLimit - 1, nodeFloorLevel, direction, pathType);
|
||||
if (node != null && (node.type == BlockPathTypes.OPEN || node.type == BlockPathTypes.WALKABLE) && this.npc.getBbWidth() < 1.0F) {
|
||||
double d2 = (double)(x - direction.getStepX()) + 0.5D;
|
||||
double d3 = (double)(z - direction.getStepZ()) + 0.5D;
|
||||
AABB aabb = new AABB(d2 - d1, this.getFloorLevel(blockpos$mutableblockpos.set(d2, y + 1, d3)) + 0.001D, d3 - d1, d2 + d1, (double)this.npc.getBbHeight() + this.getFloorLevel(blockpos$mutableblockpos.set(node.x, node.y, (double)node.z)) - 0.002D, d3 + d1);
|
||||
if (this.hasCollisions(aabb)) {
|
||||
node = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.isAmphibious() && blockpathtypes == BlockPathTypes.WATER && !this.canFloat()) {
|
||||
if (this.getCachedBlockType(this.npc, x, y - 1, z) != BlockPathTypes.WATER) {
|
||||
return node;
|
||||
}
|
||||
|
||||
while(y > this.npc.level().getMinBuildHeight()) {
|
||||
--y;
|
||||
blockpathtypes = this.getCachedBlockType(this.npc, x, y, z);
|
||||
if (blockpathtypes != BlockPathTypes.WATER) {
|
||||
return node;
|
||||
}
|
||||
|
||||
node = this.getNodeAndUpdateCostToMax(x, y, z, blockpathtypes, this.npc.getPathfindingMalus(blockpathtypes));
|
||||
}
|
||||
}
|
||||
|
||||
if (blockpathtypes == BlockPathTypes.OPEN) {
|
||||
int j = 0;
|
||||
int i = y;
|
||||
|
||||
while(blockpathtypes == BlockPathTypes.OPEN) {
|
||||
--y;
|
||||
if (y < this.npc.level().getMinBuildHeight()) {
|
||||
return this.getBlockedNode(x, i, z);
|
||||
}
|
||||
|
||||
if (j++ >= this.npc.getMaxFallDistance()) {
|
||||
return this.getBlockedNode(x, y, z);
|
||||
}
|
||||
|
||||
blockpathtypes = this.getCachedBlockType(this.npc, x, y, z);
|
||||
f = this.npc.getPathfindingMalus(blockpathtypes);
|
||||
if (blockpathtypes != BlockPathTypes.OPEN && f >= 0.0F) {
|
||||
node = this.getNodeAndUpdateCostToMax(x, y, z, blockpathtypes, f);
|
||||
break;
|
||||
}
|
||||
|
||||
if (f < 0.0F) {
|
||||
return this.getBlockedNode(x, y, z);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (doesBlockHavePartialCollision(blockpathtypes) && node == null) {
|
||||
node = this.getNode(x, y, z);
|
||||
node.closed = true;
|
||||
node.type = blockpathtypes;
|
||||
node.costMalus = blockpathtypes.getMalus();
|
||||
}
|
||||
|
||||
return node;
|
||||
} else {
|
||||
return node;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private double getnpcJumpHeight() {
|
||||
return Math.max(1.125D, this.npc.getStepHeight());
|
||||
}
|
||||
|
||||
private Node getNodeAndUpdateCostToMax(int x, int y, int z, BlockPathTypes type, float costMalus) {
|
||||
Node node = this.getNode(x, y, z);
|
||||
node.type = type;
|
||||
node.costMalus = Math.max(node.costMalus, costMalus);
|
||||
return node;
|
||||
}
|
||||
|
||||
private Node getBlockedNode(int x, int y, int z) {
|
||||
Node node = this.getNode(x, y, z);
|
||||
node.type = BlockPathTypes.BLOCKED;
|
||||
node.costMalus = -1.0F;
|
||||
return node;
|
||||
}
|
||||
|
||||
private boolean hasCollisions(AABB boundingBox) {
|
||||
return this.collisionCache.computeIfAbsent(boundingBox, (p_192973_) -> {
|
||||
return !this.level.noCollision(this.npc, boundingBox);
|
||||
});
|
||||
}
|
||||
|
||||
public BlockPathTypes getBlockPathType(BlockGetter level, int x, int y, int z, @NotNull NPCServerPlayer npc) {
|
||||
EnumSet<BlockPathTypes> enumset = EnumSet.noneOf(BlockPathTypes.class);
|
||||
BlockPathTypes blockpathtypes = BlockPathTypes.BLOCKED;
|
||||
blockpathtypes = this.getBlockPathTypes(level, x, y, z, enumset, blockpathtypes, npc.blockPosition());
|
||||
if (enumset.contains(BlockPathTypes.FENCE)) {
|
||||
return BlockPathTypes.FENCE;
|
||||
} else if (enumset.contains(BlockPathTypes.UNPASSABLE_RAIL)) {
|
||||
return BlockPathTypes.UNPASSABLE_RAIL;
|
||||
} else {
|
||||
BlockPathTypes blockpathtypes1 = BlockPathTypes.BLOCKED;
|
||||
|
||||
for(BlockPathTypes blockpathtypes2 : enumset) {
|
||||
if (npc.getPathfindingMalus(blockpathtypes2) < 0.0F) {
|
||||
return blockpathtypes2;
|
||||
}
|
||||
|
||||
if (npc.getPathfindingMalus(blockpathtypes2) >= npc.getPathfindingMalus(blockpathtypes1)) {
|
||||
blockpathtypes1 = blockpathtypes2;
|
||||
}
|
||||
}
|
||||
|
||||
return blockpathtypes == BlockPathTypes.OPEN && npc.getPathfindingMalus(blockpathtypes1) == 0.0F && this.entityWidth <= 1 ? BlockPathTypes.OPEN : blockpathtypes1;
|
||||
}
|
||||
}
|
||||
|
||||
public BlockPathTypes getBlockPathTypes(BlockGetter level, int xOffset, int yOffset, int zOffset, EnumSet<BlockPathTypes> output, BlockPathTypes fallbackPathType, BlockPos pos) {
|
||||
for(int i = 0; i < this.entityWidth; ++i) {
|
||||
for(int j = 0; j < this.entityHeight; ++j) {
|
||||
for(int k = 0; k < this.entityDepth; ++k) {
|
||||
int l = i + xOffset;
|
||||
int i1 = j + yOffset;
|
||||
int j1 = k + zOffset;
|
||||
BlockPathTypes blockpathtypes = this.getBlockPathType(level, l, i1, j1);
|
||||
blockpathtypes = this.evaluateBlockPathType(level, pos, blockpathtypes);
|
||||
if (i == 0 && j == 0 && k == 0) {
|
||||
fallbackPathType = blockpathtypes;
|
||||
}
|
||||
|
||||
output.add(blockpathtypes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fallbackPathType;
|
||||
}
|
||||
|
||||
protected BlockPathTypes evaluateBlockPathType(BlockGetter level, BlockPos pos, BlockPathTypes pathTypes) {
|
||||
boolean flag = this.canPassDoors();
|
||||
if (pathTypes == BlockPathTypes.DOOR_WOOD_CLOSED && this.canOpenDoors() && flag) {
|
||||
pathTypes = BlockPathTypes.WALKABLE_DOOR;
|
||||
}
|
||||
|
||||
if (pathTypes == BlockPathTypes.DOOR_OPEN && !flag) {
|
||||
pathTypes = BlockPathTypes.BLOCKED;
|
||||
}
|
||||
|
||||
if (pathTypes == BlockPathTypes.RAIL && !(level.getBlockState(pos).getBlock() instanceof BaseRailBlock) && !(level.getBlockState(pos.below()).getBlock() instanceof BaseRailBlock)) {
|
||||
pathTypes = BlockPathTypes.UNPASSABLE_RAIL;
|
||||
}
|
||||
|
||||
return pathTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a significant cached path node type for specified position or calculates it
|
||||
*/
|
||||
protected BlockPathTypes getBlockPathType(NPCServerPlayer entityliving, @NotNull BlockPos pos) {
|
||||
return this.getCachedBlockType(entityliving, pos.getX(), pos.getY(), pos.getZ());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a cached path node type for specified position or calculates it
|
||||
*/
|
||||
protected BlockPathTypes getCachedBlockType(NPCServerPlayer entity, int x, int y, int z) {
|
||||
return this.pathTypesByPosCache.computeIfAbsent(BlockPos.asLong(x, y, z), (l) -> this.getBlockPathType(this.level, x, y, z, entity));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the node type at the specified postion taking the block below into account
|
||||
*/
|
||||
public BlockPathTypes getBlockPathType(BlockGetter level, int x, int y, int z) {
|
||||
return getBlockPathTypeStatic(level, new BlockPos.MutableBlockPos(x, y, z));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the node type at the specified postion taking the block below into account
|
||||
*/
|
||||
public static BlockPathTypes getBlockPathTypeStatic(BlockGetter level, BlockPos.@NotNull MutableBlockPos pos) {
|
||||
int i = pos.getX();
|
||||
int j = pos.getY();
|
||||
int k = pos.getZ();
|
||||
BlockPathTypes blockpathtypes = getBlockPathTypeRaw(level, pos);
|
||||
if (blockpathtypes == BlockPathTypes.OPEN && j >= level.getMinBuildHeight() + 1) {
|
||||
BlockPathTypes blockpathtypes1 = getBlockPathTypeRaw(level, pos.set(i, j - 1, k));
|
||||
blockpathtypes = blockpathtypes1 != BlockPathTypes.WALKABLE && blockpathtypes1 != BlockPathTypes.OPEN && blockpathtypes1 != BlockPathTypes.WATER && blockpathtypes1 != BlockPathTypes.LAVA ? BlockPathTypes.WALKABLE : BlockPathTypes.OPEN;
|
||||
if (blockpathtypes1 == BlockPathTypes.DAMAGE_FIRE) {
|
||||
blockpathtypes = BlockPathTypes.DAMAGE_FIRE;
|
||||
}
|
||||
|
||||
if (blockpathtypes1 == BlockPathTypes.DAMAGE_OTHER) {
|
||||
blockpathtypes = BlockPathTypes.DAMAGE_OTHER;
|
||||
}
|
||||
|
||||
if (blockpathtypes1 == BlockPathTypes.STICKY_HONEY) {
|
||||
blockpathtypes = BlockPathTypes.STICKY_HONEY;
|
||||
}
|
||||
|
||||
if (blockpathtypes1 == BlockPathTypes.POWDER_SNOW) {
|
||||
blockpathtypes = BlockPathTypes.DANGER_POWDER_SNOW;
|
||||
}
|
||||
|
||||
if (blockpathtypes1 == BlockPathTypes.DAMAGE_CAUTIOUS) {
|
||||
blockpathtypes = BlockPathTypes.DAMAGE_CAUTIOUS;
|
||||
}
|
||||
}
|
||||
|
||||
if (blockpathtypes == BlockPathTypes.WALKABLE) {
|
||||
blockpathtypes = checkNeighbourBlocks(level, pos.set(i, j, k), blockpathtypes);
|
||||
}
|
||||
|
||||
return blockpathtypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns possible dangers in a 3x3 cube, otherwise nodeType
|
||||
*/
|
||||
public static BlockPathTypes checkNeighbourBlocks(BlockGetter level, BlockPos.@NotNull MutableBlockPos centerPos, BlockPathTypes nodeType) {
|
||||
int i = centerPos.getX();
|
||||
int j = centerPos.getY();
|
||||
int k = centerPos.getZ();
|
||||
|
||||
for(int l = -1; l <= 1; ++l) {
|
||||
for(int i1 = -1; i1 <= 1; ++i1) {
|
||||
for(int j1 = -1; j1 <= 1; ++j1) {
|
||||
if (l != 0 || j1 != 0) {
|
||||
centerPos.set(i + l, j + i1, k + j1);
|
||||
BlockState blockstate = level.getBlockState(centerPos);
|
||||
BlockPathTypes blockPathType = blockstate.getAdjacentBlockPathType(level, centerPos, null, nodeType);
|
||||
if (blockPathType != null) return blockPathType;
|
||||
FluidState fluidState = blockstate.getFluidState();
|
||||
BlockPathTypes fluidPathType = fluidState.getAdjacentBlockPathType(level, centerPos, null, nodeType);
|
||||
if (fluidPathType != null) return fluidPathType;
|
||||
if (blockstate.is(Blocks.CACTUS) || blockstate.is(Blocks.SWEET_BERRY_BUSH)) {
|
||||
return BlockPathTypes.DANGER_OTHER;
|
||||
}
|
||||
|
||||
if (isBurningBlock(blockstate)) {
|
||||
return BlockPathTypes.DANGER_FIRE;
|
||||
}
|
||||
|
||||
if (level.getFluidState(centerPos).is(FluidTags.WATER)) {
|
||||
return BlockPathTypes.WATER_BORDER;
|
||||
}
|
||||
|
||||
if (blockstate.is(Blocks.WITHER_ROSE) || blockstate.is(Blocks.POINTED_DRIPSTONE)) {
|
||||
return BlockPathTypes.DAMAGE_CAUTIOUS;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nodeType;
|
||||
}
|
||||
|
||||
protected static BlockPathTypes getBlockPathTypeRaw(@NotNull BlockGetter level, BlockPos pos) {
|
||||
BlockState blockstate = level.getBlockState(pos);
|
||||
BlockPathTypes type = blockstate.getBlockPathType(level, pos, null);
|
||||
if (type != null) return type;
|
||||
Block block = blockstate.getBlock();
|
||||
if (blockstate.isAir()) {
|
||||
return BlockPathTypes.OPEN;
|
||||
} else if (!blockstate.is(BlockTags.TRAPDOORS) && !blockstate.is(Blocks.LILY_PAD) && !blockstate.is(Blocks.BIG_DRIPLEAF)) {
|
||||
if (blockstate.is(Blocks.POWDER_SNOW)) {
|
||||
return BlockPathTypes.POWDER_SNOW;
|
||||
} else if (!blockstate.is(Blocks.CACTUS) && !blockstate.is(Blocks.SWEET_BERRY_BUSH)) {
|
||||
if (blockstate.is(Blocks.HONEY_BLOCK)) {
|
||||
return BlockPathTypes.STICKY_HONEY;
|
||||
} else if (blockstate.is(Blocks.COCOA)) {
|
||||
return BlockPathTypes.COCOA;
|
||||
} else if (!blockstate.is(Blocks.WITHER_ROSE) && !blockstate.is(Blocks.POINTED_DRIPSTONE)) {
|
||||
FluidState fluidstate = level.getFluidState(pos);
|
||||
BlockPathTypes nonLoggableFluidPathType = fluidstate.getBlockPathType(level, pos, null, false);
|
||||
if (nonLoggableFluidPathType != null) return nonLoggableFluidPathType;
|
||||
if (fluidstate.is(FluidTags.LAVA)) {
|
||||
return BlockPathTypes.LAVA;
|
||||
} else if (isBurningBlock(blockstate)) {
|
||||
return BlockPathTypes.DAMAGE_FIRE;
|
||||
} else if (block instanceof DoorBlock doorblock) {
|
||||
if (blockstate.getValue(DoorBlock.OPEN)) {
|
||||
return BlockPathTypes.DOOR_OPEN;
|
||||
} else {
|
||||
return doorblock.type().canOpenByHand() ? BlockPathTypes.DOOR_WOOD_CLOSED : BlockPathTypes.DOOR_IRON_CLOSED;
|
||||
}
|
||||
} else if (block instanceof BaseRailBlock) {
|
||||
return BlockPathTypes.RAIL;
|
||||
} else if (block instanceof LeavesBlock) {
|
||||
return BlockPathTypes.LEAVES;
|
||||
} else if (!blockstate.is(BlockTags.FENCES) && !blockstate.is(BlockTags.WALLS) && (!(block instanceof FenceGateBlock) || blockstate.getValue(FenceGateBlock.OPEN))) {
|
||||
if (!blockstate.isPathfindable(level, pos, PathComputationType.LAND)) {
|
||||
return BlockPathTypes.BLOCKED;
|
||||
} else {
|
||||
BlockPathTypes loggableFluidPathType = fluidstate.getBlockPathType(level, pos, null, true);
|
||||
if (loggableFluidPathType != null) return loggableFluidPathType;
|
||||
return fluidstate.is(FluidTags.WATER) ? BlockPathTypes.WATER : BlockPathTypes.OPEN;
|
||||
}
|
||||
} else {
|
||||
return BlockPathTypes.FENCE;
|
||||
}
|
||||
} else {
|
||||
return BlockPathTypes.DAMAGE_CAUTIOUS;
|
||||
}
|
||||
} else {
|
||||
return BlockPathTypes.DAMAGE_OTHER;
|
||||
}
|
||||
} else {
|
||||
return BlockPathTypes.TRAPDOOR;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the specified block state can cause burn damage
|
||||
*/
|
||||
public static boolean isBurningBlock(@NotNull BlockState state) {
|
||||
return state.is(BlockTags.FIRE) || state.is(Blocks.LAVA) || state.is(Blocks.MAGMA_BLOCK) || CampfireBlock.isLitCampfire(state) || state.is(Blocks.LAVA_CAULDRON);
|
||||
}
|
||||
}
|
||||
|
|
@ -24,6 +24,7 @@ import net.minecraft.sounds.SoundEvents;
|
|||
import net.minecraft.sounds.SoundSource;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.InteractionResult;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
import net.minecraft.world.entity.npc.VillagerTrades;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.Items;
|
||||
|
|
@ -55,6 +56,7 @@ import top.r3944realms.eroticdungeongame.content.command.EDGCommand;
|
|||
import top.r3944realms.eroticdungeongame.content.entity.IAnchorableEntity;
|
||||
import top.r3944realms.eroticdungeongame.content.entity.IronBallEntity;
|
||||
import top.r3944realms.eroticdungeongame.content.entity.npc.INPCPlayer;
|
||||
import top.r3944realms.eroticdungeongame.content.entity.npc.NPCServerPlayer;
|
||||
import top.r3944realms.eroticdungeongame.content.recipe.DungeonCraftingBookCategory;
|
||||
import top.r3944realms.eroticdungeongame.content.recipe.DungeonRecipe;
|
||||
import top.r3944realms.eroticdungeongame.content.recipe.EDGRecipeBookTypes;
|
||||
|
|
|
|||
|
|
@ -1,64 +0,0 @@
|
|||
{
|
||||
"name": "x_corss_pose_01",
|
||||
"author": "R3944Realms",
|
||||
"description": "X Cross pose type 01",
|
||||
"emote":{
|
||||
"isLoop": "false",
|
||||
"returnTick": 2,
|
||||
"beginTick":0,
|
||||
"endTick":60,
|
||||
"stopTick":2147483647,
|
||||
"degrees":false,
|
||||
"moves":[
|
||||
|
||||
{
|
||||
"tick":5,
|
||||
"easing": "EASEINOUTQUAD",
|
||||
"turn": 0,
|
||||
"rightArm":{
|
||||
"roll":2.7488934993743896
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick":5,
|
||||
"easing": "EASEINOUTQUAD",
|
||||
"turn": 0,
|
||||
"rightArm":{
|
||||
"z":0.4000000059604645
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick":5,
|
||||
"easing": "EASEINOUTQUAD",
|
||||
"turn": 0,
|
||||
"leftArm":{
|
||||
"roll":-2.7488934993743896
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick":5,
|
||||
"easing": "EASEINOUTQUAD",
|
||||
"turn": 0,
|
||||
"leftArm":{
|
||||
"z":0.40000003576278687
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick":5,
|
||||
"easing": "EASEINOUTQUAD",
|
||||
"turn": 0,
|
||||
"rightLeg":{
|
||||
"roll":0.39269909262657166
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick":5,
|
||||
"easing": "EASEINOUTQUAD",
|
||||
"turn": 0,
|
||||
"leftLeg":{
|
||||
"roll":-0.39269909262657166
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user