Fix door stuck nudge, double door priority, shelter detection, and debug stick

This commit is contained in:
2026-06-18 16:32:42 +03:00
parent a93660d5e5
commit e6f0c841fe
5 changed files with 208 additions and 91 deletions
@@ -15,14 +15,16 @@ import java.util.EnumSet;
public class EllieDoorInteractGoal extends Goal { public class EllieDoorInteractGoal extends Goal {
private static final int CLOSE_DELAY = 25; private static final int CLOSE_DELAY = 25;
private static final double CLOSE_DIST_SQ = 2.0 * 2.0; private static final double CLOSE_DIST_SQ = 2.0 * 2.0;
private static final double WAYPOINT_REACH_DIST = 0.4; private static final double STUCK_DELTA = 0.005;
private final EllieEntity ellie; private final EllieEntity ellie;
private BlockPos doorPos; private BlockPos doorPos;
private BlockPos pairedDoorPos; private BlockPos pairedDoorPos;
private Vec3 passageCenter;
private boolean hasPassed; private boolean hasPassed;
private int closeTimer; private int closeTimer;
private Vec3 doorStuckPos;
private int doorStuckTicks;
private boolean stuckNudgeCw;
public EllieDoorInteractGoal(EllieEntity ellie) { public EllieDoorInteractGoal(EllieEntity ellie) {
this.ellie = ellie; this.ellie = ellie;
@@ -49,21 +51,13 @@ public class EllieDoorInteractGoal extends Goal {
return; return;
} }
if (passageCenter != null) {
boolean reached = ellie.distanceToSqr(passageCenter) < WAYPOINT_REACH_DIST * WAYPOINT_REACH_DIST
|| ellie.getNavigation().isDone();
if (reached) {
passageCenter = null;
} else {
ellie.getNavigation().moveTo(passageCenter.x, passageCenter.y, passageCenter.z, 0.6);
}
}
if (!hasPassed) { if (!hasPassed) {
double distSq = ellie.distanceToSqr(Vec3.atCenterOf(doorPos)); double distSq = ellie.distanceToSqr(Vec3.atCenterOf(doorPos));
if (distSq > CLOSE_DIST_SQ) { if (distSq > CLOSE_DIST_SQ) {
hasPassed = true; hasPassed = true;
closeTimer = CLOSE_DELAY; closeTimer = CLOSE_DELAY;
} else {
checkStuck();
} }
} else if (--closeTimer <= 0) { } else if (--closeTimer <= 0) {
closeDoor(doorPos); closeDoor(doorPos);
@@ -93,11 +87,42 @@ public class EllieDoorInteractGoal extends Goal {
hasPassed = false; hasPassed = false;
closeTimer = 0; closeTimer = 0;
passageCenter = calcPassageCenter();
ellie.getNavigation().moveTo(passageCenter.x, passageCenter.y, passageCenter.z, 0.6);
} }
} }
private void checkStuck() {
Vec3 current = ellie.position();
if (doorStuckPos != null) {
if (current.distanceToSqr(doorStuckPos) < STUCK_DELTA) {
doorStuckTicks++;
if (doorStuckTicks > 40) {
nudgeDoor();
doorStuckTicks = 0;
}
} else {
doorStuckTicks = 0;
}
}
doorStuckPos = current;
}
private void nudgeDoor() {
BlockState state = ellie.level().getBlockState(doorPos);
if (!(state.getBlock() instanceof DoorBlock)) return;
Direction facing = state.getValue(DoorBlock.FACING);
Direction nudgeDir = stuckNudgeCw
? facing.getClockWise()
: facing.getCounterClockWise();
stuckNudgeCw = !stuckNudgeCw;
ellie.setPos(
ellie.getX() + nudgeDir.getStepX() * 0.2,
ellie.getY(),
ellie.getZ() + nudgeDir.getStepZ() * 0.2
);
}
private void openDoor(BlockPos pos, BlockState state) { private void openDoor(BlockPos pos, BlockState state) {
if (state.getBlock() instanceof DoorBlock door && !state.getValue(DoorBlock.OPEN)) { if (state.getBlock() instanceof DoorBlock door && !state.getValue(DoorBlock.OPEN)) {
door.setOpen(ellie, ellie.level(), state, pos, true); door.setOpen(ellie, ellie.level(), state, pos, true);
@@ -115,22 +140,43 @@ public class EllieDoorInteractGoal extends Goal {
Path path = ellie.getNavigation().getPath(); Path path = ellie.getNavigation().getPath();
if (path != null && !path.isDone()) { if (path != null && !path.isDone()) {
BlockPos entityPos = ellie.blockPosition(); BlockPos entityPos = ellie.blockPosition();
for (int i = 0; i < Math.min(8, path.getNodeCount()); i++) { for (int i = 0; i < Math.min(16, path.getNodeCount()); i++) {
Node node = path.getNode(i); Node node = path.getNode(i);
if (node == null) continue; if (node == null) continue;
BlockPos checkPos = new BlockPos(node.x, node.y, node.z); BlockPos checkPos = new BlockPos(node.x, node.y, node.z);
if (checkPos.distSqr(entityPos) > 64) continue; if (checkPos.distSqr(entityPos) > 64) continue;
if (checkForDoor(checkPos)) return checkPos; BlockPos door = resolveDoor(checkPos);
if (checkForDoor(checkPos.above())) return checkPos.above(); if (door != null) return door;
} }
} }
BlockPos center = ellie.blockPosition(); BlockPos center = ellie.blockPosition();
for (int dx = -1; dx <= 1; dx++) { BlockPos singleDoor = null;
for (int dz = -1; dz <= 1; dz++) { for (int dx = -2; dx <= 2; dx++) {
BlockPos checkPos = center.offset(dx, 0, dz); for (int dz = -2; dz <= 2; dz++) {
if (checkForDoor(checkPos)) return checkPos; for (int dy = -1; dy <= 1; dy++) {
if (checkForDoor(checkPos.above())) return checkPos.above(); BlockPos checkPos = center.offset(dx, dy, dz);
if (!checkForDoor(checkPos)) continue;
BlockState state = ellie.level().getBlockState(checkPos);
if (findPairedDoor(checkPos, state) != null) {
return checkPos;
} }
if (singleDoor == null) {
singleDoor = checkPos;
}
}
}
}
return singleDoor;
}
private BlockPos resolveDoor(BlockPos pos) {
if (checkForDoor(pos)) return pos;
if (checkForDoor(pos.above())) return pos.above();
if (checkForDoor(pos.below())) return pos.below();
for (Direction dir : Direction.Plane.HORIZONTAL) {
BlockPos adj = pos.relative(dir);
if (checkForDoor(adj)) return adj;
if (checkForDoor(adj.above())) return adj.above();
} }
return null; return null;
} }
@@ -155,19 +201,13 @@ public class EllieDoorInteractGoal extends Goal {
return null; return null;
} }
private Vec3 calcPassageCenter() {
if (pairedDoorPos != null) {
return Vec3.atCenterOf(doorPos).add(Vec3.atCenterOf(pairedDoorPos)).scale(0.5);
}
return Vec3.atCenterOf(doorPos);
}
private void clear() { private void clear() {
doorPos = null; doorPos = null;
pairedDoorPos = null; pairedDoorPos = null;
passageCenter = null;
hasPassed = false; hasPassed = false;
closeTimer = 0; closeTimer = 0;
doorStuckPos = null;
doorStuckTicks = 0;
} }
@Override @Override
@@ -14,9 +14,17 @@ public class EllieNodeEvaluator extends WalkNodeEvaluator {
if (node != null) return node; if (node != null) return node;
BlockPathTypes head1 = this.getCachedBlockType(this.mob, x, y + 1, z); BlockPathTypes head1 = this.getCachedBlockType(this.mob, x, y + 1, z);
if (head1 == BlockPathTypes.BLOCKED) return null; float head1Malus = this.mob.getPathfindingMalus(head1);
if (head1Malus < 0.0f) {
BlockPathTypes body = this.getCachedBlockType(this.mob, x, y, z);
BlockPathTypes below = this.getCachedBlockType(this.mob, x, y - 1, z);
if (this.mob.getPathfindingMalus(body) < 0.0f || this.mob.getPathfindingMalus(below) < 0.0f) {
return null;
}
}
BlockPathTypes head2 = this.getCachedBlockType(this.mob, x, y + 2, z); BlockPathTypes head2 = this.getCachedBlockType(this.mob, x, y + 2, z);
if (head2 != BlockPathTypes.BLOCKED) return null; if (this.mob.getPathfindingMalus(head2) >= 0.0f) return null;
BlockPathTypes footType = this.getCachedBlockType(this.mob, x, y, z); BlockPathTypes footType = this.getCachedBlockType(this.mob, x, y, z);
float costMalus = this.mob.getPathfindingMalus(footType); float costMalus = this.mob.getPathfindingMalus(footType);
@@ -3,8 +3,6 @@ package me.sashegdev.fabled_hearts.ai;
import me.sashegdev.fabled_hearts.entity.ellie.EllieEntity; import me.sashegdev.fabled_hearts.entity.ellie.EllieEntity;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.world.entity.ai.goal.Goal; import net.minecraft.world.entity.ai.goal.Goal;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.Vec3;
import java.util.EnumSet; import java.util.EnumSet;
@@ -40,10 +38,11 @@ public class ShelterGoal extends Goal {
@Override @Override
public void tick() { public void tick() {
if (shelterPos == null) return; if (shelterPos == null) return;
if (ellie.distanceToSqr(Vec3.atCenterOf(shelterPos)) < REACH_DIST_SQR) { Vec3 target = new Vec3(shelterPos.getX() + 0.5, shelterPos.getY(), shelterPos.getZ() + 0.5);
if (ellie.distanceToSqr(target) < REACH_DIST_SQR) {
ellie.getNavigation().stop(); ellie.getNavigation().stop();
} else if (ellie.getNavigation().isDone()) { } else if (ellie.getNavigation().isDone()) {
ellie.getNavigation().moveTo(shelterPos.getX() + 0.5, shelterPos.getY(), shelterPos.getZ() + 0.5, 0.6); ellie.getNavigation().moveTo(target.x, target.y, target.z, 0.6);
} }
} }
@@ -59,16 +58,6 @@ public class ShelterGoal extends Goal {
shelterPos = null; shelterPos = null;
} }
private boolean hasShelter(BlockPos pos) {
Level level = ellie.level();
for (int y = 1; y <= 3; y++) {
if (level.getBlockState(pos.above(y)).blocksMotion()) {
return true;
}
}
return false;
}
private BlockPos findShelter() { private BlockPos findShelter() {
BlockPos entityPos = ellie.blockPosition(); BlockPos entityPos = ellie.blockPosition();
BlockPos.MutableBlockPos mutable = new BlockPos.MutableBlockPos(); BlockPos.MutableBlockPos mutable = new BlockPos.MutableBlockPos();
@@ -77,14 +66,18 @@ public class ShelterGoal extends Goal {
for (int x = -SEARCH_RADIUS; x <= SEARCH_RADIUS; x++) { for (int x = -SEARCH_RADIUS; x <= SEARCH_RADIUS; x++) {
for (int z = -SEARCH_RADIUS; z <= SEARCH_RADIUS; z++) { for (int z = -SEARCH_RADIUS; z <= SEARCH_RADIUS; z++) {
mutable.set(entityPos.getX() + x, entityPos.getY(), entityPos.getZ() + z); for (int dy = 2; dy >= -4; dy--) {
int topY = ellie.level().getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, mutable).getY(); mutable.set(entityPos.getX() + x, entityPos.getY() + dy, entityPos.getZ() + z);
mutable.setY(topY); BlockPos standPos = mutable.above();
if (hasShelter(mutable)) { if (!ellie.level().isRainingAt(standPos)
&& ellie.level().getBlockState(standPos).isAir()
&& !ellie.level().getBlockState(mutable).isAir()) {
double dist = entityPos.distSqr(mutable); double dist = entityPos.distSqr(mutable);
if (dist < nearestDist) { if (dist < nearestDist) {
nearestDist = dist; nearestDist = dist;
nearest = mutable.immutable(); nearest = standPos;
}
break;
} }
} }
} }
@@ -77,7 +77,11 @@ public class EllieEntity extends Animal implements GeoEntity {
SynchedEntityData.defineId(EllieEntity.class, EntityDataSerializers.INT); SynchedEntityData.defineId(EllieEntity.class, EntityDataSerializers.INT);
private static final List<String> DEBUG_ANIMS = List.of( private static final List<String> DEBUG_ANIMS = List.of(
"idle1", "idle2", "idle3", "turn", "eat1", "fall" "idle1", "idle2", "idle3", "turn", "eat1", "fall",
"sleep", "walkingsleepy", "idlesleepy",
"walkingrain", "idleconrain",
"shiftwalking", "shiftidle",
"walkingsimple", "idleins"
); );
private final AnimatableInstanceCache cache = GeckoLibUtil.createInstanceCache(this); private final AnimatableInstanceCache cache = GeckoLibUtil.createInstanceCache(this);
@@ -423,7 +427,7 @@ public class EllieEntity extends Animal implements GeoEntity {
BlockPos footPos = headPos.relative(facing.getOpposite()); BlockPos footPos = headPos.relative(facing.getOpposite());
double footX = footPos.getX() + 0.5 - facing.getStepX() * 0.5; double footX = footPos.getX() + 0.5 - facing.getStepX() * 0.5;
double footZ = footPos.getZ() + 0.5 - facing.getStepZ() * 0.5; double footZ = footPos.getZ() + 0.5 - facing.getStepZ() * 0.5;
setPos(footX, footPos.getY() + 0.6875, footZ); setPos(footX, footPos.getY() + 1.0625, footZ);
setSleepingPos(headPos); setSleepingPos(headPos);
float yRot = facing.getOpposite().toYRot(); float yRot = facing.getOpposite().toYRot();
setYRot(yRot); setYRot(yRot);
@@ -530,7 +534,7 @@ public class EllieEntity extends Animal implements GeoEntity {
@Override @Override
public InteractionResult mobInteract(Player player, InteractionHand hand) { public InteractionResult mobInteract(Player player, InteractionHand hand) {
ItemStack stack = player.getItemInHand(hand); ItemStack stack = player.getItemInHand(hand);
boolean holdingStick = stack.is(Items.STICK); boolean holdingStick = stack.is(Items.DEBUG_STICK);
if (holdingStick && player.isShiftKeyDown()) { if (holdingStick && player.isShiftKeyDown()) {
if (!level().isClientSide) { if (!level().isClientSide) {
@@ -547,7 +551,7 @@ public class EllieEntity extends Animal implements GeoEntity {
if (holdingStick) { if (holdingStick) {
if (!level().isClientSide) { if (!level().isClientSide) {
int current = entityData.get(DATA_DEBUG_ANIM); int current = entityData.get(DATA_DEBUG_ANIM);
int next = (current % DEBUG_ANIMS.size()) + 1; int next = (current + 1) % (DEBUG_ANIMS.size() + 1);
entityData.set(DATA_DEBUG_ANIM, next); entityData.set(DATA_DEBUG_ANIM, next);
debugAnimTimer = 200; debugAnimTimer = 200;
} }
@@ -575,7 +579,7 @@ public class EllieEntity extends Animal implements GeoEntity {
public boolean hurt(DamageSource source, float amount) { public boolean hurt(DamageSource source, float amount) {
if (source.getEntity() instanceof Player player) { if (source.getEntity() instanceof Player player) {
ItemStack weapon = player.getMainHandItem(); ItemStack weapon = player.getMainHandItem();
if (weapon.is(Items.STICK)) { if (weapon.is(Items.DEBUG_STICK)) {
if (!level().isClientSide) { if (!level().isClientSide) {
int current = entityData.get(DATA_DEBUG_ANIM); int current = entityData.get(DATA_DEBUG_ANIM);
if (current > 0) { if (current > 0) {
@@ -13899,53 +13899,97 @@
"bones": { "bones": {
"root": { "root": {
"rotation": { "rotation": {
"vector": [0, 0, 0] "vector": [90, 0, -180]
}, },
"position": { "position": {
"vector": [0, 0, 0] "vector": [0, 9.1, 9]
} }
}, },
"torso": { "torso": {
"rotation": { "rotation": {
"0.0833": { "vector": [0, 0, -5] }, "0.0833": {
"1.0833": { "vector": [2, 0, -5] }, "vector": [0, 0, -5]
"1.3333": { "vector": [2.25, 0, -5] }, },
"2.0833": { "vector": [0, 0, -5] } "1.0833": {
"vector": [2, 0, -5]
},
"1.3333": {
"vector": [2.25, 0, -5]
},
"2.0833": {
"vector": [0, 0, -5]
}
}, },
"position": { "position": {
"0.0": { "vector": [0, -0.4, 0] }, "0.0": {
"1.0": { "vector": [0, -0.3, 0] }, "vector": [0, -0.4, 0]
"1.25": { "vector": [0, -0.3, 0] }, },
"2.0": { "vector": [0, -0.4, 0] } "1.0": {
"vector": [0, -0.3, 0]
},
"1.25": {
"vector": [0, -0.3, 0]
},
"2.0": {
"vector": [0, -0.4, 0]
}
} }
}, },
"head": { "head": {
"rotation": { "rotation": {
"0.0": { "vector": [0, 0, -5] }, "0.0": {
"0.1667": { "vector": [0, 0, -5] }, "vector": [0, 0, -5]
"1.1667": { "vector": [-3, 0, -5] }, },
"1.4167": { "vector": [-3, 0, -5] }, "0.1667": {
"2.1667": { "vector": [0, 0, -5] } "vector": [0, 0, -5]
},
"1.1667": {
"vector": [-3, 0, -5]
},
"1.4167": {
"vector": [-3, 0, -5]
},
"2.1667": {
"vector": [0, 0, -5]
}
} }
}, },
"righteye": { "righteye": {
"position": { "vector": [0, 0, 1] } "position": {
"vector": [0, 0, 1]
}
}, },
"frown": { "frown": {
"position": { "vector": [0, 0, 1] } "position": {
"vector": [0, 0, 1]
}
}, },
"rightarm": { "rightarm": {
"rotation": { "rotation": {
"0.0": { "vector": [0, 0, -10] }, "0.0": {
"0.2083": { "vector": [0, 0, -10] }, "vector": [0, 0, -10]
"1.2083": { "vector": [2, 0, -10] }, },
"1.4583": { "vector": [3, 0, -10] }, "0.2083": {
"2.2083": { "vector": [0, 0, -10] } "vector": [0, 0, -10]
},
"1.2083": {
"vector": [2, 0, -10]
},
"1.4583": {
"vector": [3, 0, -10]
},
"2.2083": {
"vector": [0, 0, -10]
}
} }
}, },
"rightleg": { "rightleg": {
"rotation": { "vector": [0, 0, 25] }, "rotation": {
"position": { "vector": [0, 0.7, 0] } "vector": [0, 0, 25]
},
"position": {
"vector": [0, 0.7, 0]
}
}, },
"openmouth]2": { "openmouth]2": {
"position": { "position": {
@@ -13985,19 +14029,19 @@
"leftarm": { "leftarm": {
"rotation": { "rotation": {
"0.0": { "0.0": {
"vector": [0, -82.5, -80] "vector": [-17.1045, 0.5398, 22.7077]
}, },
"0.2083": { "0.2083": {
"vector": [0, -82.5, -80] "vector": [-17.1045, 0.5398, 22.7077]
}, },
"1.2083": { "1.2083": {
"vector": [18.3312, -82.0966, -98.4951] "vector": [-14.6045, 0.5398, 22.7077]
}, },
"1.4583": { "1.4583": {
"vector": [24.8777, -81.7278, -105.1071] "vector": [-14.6045, 0.5398, 22.7077]
}, },
"2.2083": { "2.2083": {
"vector": [0, -82.5, -80] "vector": [-17.1045, 0.5398, 22.7077]
} }
}, },
"position": { "position": {
@@ -14007,6 +14051,12 @@
"0.2083": { "0.2083": {
"vector": [-1, 0, 0] "vector": [-1, 0, 0]
}, },
"1.2083": {
"vector": [-1, 0, 0]
},
"1.4583": {
"vector": [-1, 0, 0]
},
"2.2083": { "2.2083": {
"vector": [-1, 0, 0] "vector": [-1, 0, 0]
} }
@@ -14014,17 +14064,31 @@
}, },
"loverleftarm": { "loverleftarm": {
"rotation": { "rotation": {
"vector": [0, 0, 12.5] "0.0": {
"vector": [0, 0, 85]
},
"0.3333": {
"vector": [0, 0, 85]
},
"1.3333": {
"vector": [0, 0, 87.5]
},
"1.5833": {
"vector": [0, 0, 87.5]
},
"2.25": {
"vector": [0, 0, 85]
}
} }
}, },
"leftleg": { "leftleg": {
"rotation": { "rotation": {
"vector": [-30, 0, 0] "vector": [-15, 0, 0]
} }
}, },
"loverleg_L": { "loverleg_L": {
"rotation": { "rotation": {
"vector": [70, 0, 0] "vector": [47.5, 0, 0]
} }
}, },
"closed_L": { "closed_L": {
@@ -14036,6 +14100,14 @@
"position": { "position": {
"vector": [0, 0, 1] "vector": [0, 0, 1]
} }
},
"clothleg_lower_L": {
"rotation": {
"vector": [42.5, 0, 0]
},
"position": {
"vector": [0, -0.5, -0.3]
}
} }
} }
}, },