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 {
private static final int CLOSE_DELAY = 25;
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 BlockPos doorPos;
private BlockPos pairedDoorPos;
private Vec3 passageCenter;
private boolean hasPassed;
private int closeTimer;
private Vec3 doorStuckPos;
private int doorStuckTicks;
private boolean stuckNudgeCw;
public EllieDoorInteractGoal(EllieEntity ellie) {
this.ellie = ellie;
@@ -49,21 +51,13 @@ public class EllieDoorInteractGoal extends Goal {
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) {
double distSq = ellie.distanceToSqr(Vec3.atCenterOf(doorPos));
if (distSq > CLOSE_DIST_SQ) {
hasPassed = true;
closeTimer = CLOSE_DELAY;
} else {
checkStuck();
}
} else if (--closeTimer <= 0) {
closeDoor(doorPos);
@@ -93,11 +87,42 @@ public class EllieDoorInteractGoal extends Goal {
hasPassed = false;
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) {
if (state.getBlock() instanceof DoorBlock door && !state.getValue(DoorBlock.OPEN)) {
door.setOpen(ellie, ellie.level(), state, pos, true);
@@ -115,22 +140,43 @@ public class EllieDoorInteractGoal extends Goal {
Path path = ellie.getNavigation().getPath();
if (path != null && !path.isDone()) {
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);
if (node == null) continue;
BlockPos checkPos = new BlockPos(node.x, node.y, node.z);
if (checkPos.distSqr(entityPos) > 64) continue;
if (checkForDoor(checkPos)) return checkPos;
if (checkForDoor(checkPos.above())) return checkPos.above();
BlockPos door = resolveDoor(checkPos);
if (door != null) return door;
}
}
BlockPos center = ellie.blockPosition();
for (int dx = -1; dx <= 1; dx++) {
for (int dz = -1; dz <= 1; dz++) {
BlockPos checkPos = center.offset(dx, 0, dz);
if (checkForDoor(checkPos)) return checkPos;
if (checkForDoor(checkPos.above())) return checkPos.above();
BlockPos singleDoor = null;
for (int dx = -2; dx <= 2; dx++) {
for (int dz = -2; dz <= 2; dz++) {
for (int dy = -1; dy <= 1; dy++) {
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;
}
@@ -155,19 +201,13 @@ public class EllieDoorInteractGoal extends Goal {
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() {
doorPos = null;
pairedDoorPos = null;
passageCenter = null;
hasPassed = false;
closeTimer = 0;
doorStuckPos = null;
doorStuckTicks = 0;
}
@Override
@@ -14,9 +14,17 @@ public class EllieNodeEvaluator extends WalkNodeEvaluator {
if (node != null) return node;
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);
if (head2 != BlockPathTypes.BLOCKED) return null;
if (this.mob.getPathfindingMalus(head2) >= 0.0f) return null;
BlockPathTypes footType = this.getCachedBlockType(this.mob, x, y, z);
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 net.minecraft.core.BlockPos;
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 java.util.EnumSet;
@@ -40,10 +38,11 @@ public class ShelterGoal extends Goal {
@Override
public void tick() {
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();
} 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;
}
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() {
BlockPos entityPos = ellie.blockPosition();
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 z = -SEARCH_RADIUS; z <= SEARCH_RADIUS; z++) {
mutable.set(entityPos.getX() + x, entityPos.getY(), entityPos.getZ() + z);
int topY = ellie.level().getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, mutable).getY();
mutable.setY(topY);
if (hasShelter(mutable)) {
for (int dy = 2; dy >= -4; dy--) {
mutable.set(entityPos.getX() + x, entityPos.getY() + dy, entityPos.getZ() + z);
BlockPos standPos = mutable.above();
if (!ellie.level().isRainingAt(standPos)
&& ellie.level().getBlockState(standPos).isAir()
&& !ellie.level().getBlockState(mutable).isAir()) {
double dist = entityPos.distSqr(mutable);
if (dist < nearestDist) {
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);
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);
@@ -423,7 +427,7 @@ public class EllieEntity extends Animal implements GeoEntity {
BlockPos footPos = headPos.relative(facing.getOpposite());
double footX = footPos.getX() + 0.5 - facing.getStepX() * 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);
float yRot = facing.getOpposite().toYRot();
setYRot(yRot);
@@ -530,7 +534,7 @@ public class EllieEntity extends Animal implements GeoEntity {
@Override
public InteractionResult mobInteract(Player player, InteractionHand hand) {
ItemStack stack = player.getItemInHand(hand);
boolean holdingStick = stack.is(Items.STICK);
boolean holdingStick = stack.is(Items.DEBUG_STICK);
if (holdingStick && player.isShiftKeyDown()) {
if (!level().isClientSide) {
@@ -547,7 +551,7 @@ public class EllieEntity extends Animal implements GeoEntity {
if (holdingStick) {
if (!level().isClientSide) {
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);
debugAnimTimer = 200;
}
@@ -575,7 +579,7 @@ public class EllieEntity extends Animal implements GeoEntity {
public boolean hurt(DamageSource source, float amount) {
if (source.getEntity() instanceof Player player) {
ItemStack weapon = player.getMainHandItem();
if (weapon.is(Items.STICK)) {
if (weapon.is(Items.DEBUG_STICK)) {
if (!level().isClientSide) {
int current = entityData.get(DATA_DEBUG_ANIM);
if (current > 0) {
@@ -13899,53 +13899,97 @@
"bones": {
"root": {
"rotation": {
"vector": [0, 0, 0]
"vector": [90, 0, -180]
},
"position": {
"vector": [0, 0, 0]
"vector": [0, 9.1, 9]
}
},
"torso": {
"rotation": {
"0.0833": { "vector": [0, 0, -5] },
"1.0833": { "vector": [2, 0, -5] },
"1.3333": { "vector": [2.25, 0, -5] },
"2.0833": { "vector": [0, 0, -5] }
"0.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": {
"0.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] }
"0.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": {
"rotation": {
"0.0": { "vector": [0, 0, -5] },
"0.1667": { "vector": [0, 0, -5] },
"1.1667": { "vector": [-3, 0, -5] },
"1.4167": { "vector": [-3, 0, -5] },
"2.1667": { "vector": [0, 0, -5] }
"0.0": {
"vector": [0, 0, -5]
},
"0.1667": {
"vector": [0, 0, -5]
},
"1.1667": {
"vector": [-3, 0, -5]
},
"1.4167": {
"vector": [-3, 0, -5]
},
"2.1667": {
"vector": [0, 0, -5]
}
}
},
"righteye": {
"position": { "vector": [0, 0, 1] }
"position": {
"vector": [0, 0, 1]
}
},
"frown": {
"position": { "vector": [0, 0, 1] }
"position": {
"vector": [0, 0, 1]
}
},
"rightarm": {
"rotation": {
"0.0": { "vector": [0, 0, -10] },
"0.2083": { "vector": [0, 0, -10] },
"1.2083": { "vector": [2, 0, -10] },
"1.4583": { "vector": [3, 0, -10] },
"2.2083": { "vector": [0, 0, -10] }
"0.0": {
"vector": [0, 0, -10]
},
"0.2083": {
"vector": [0, 0, -10]
},
"1.2083": {
"vector": [2, 0, -10]
},
"1.4583": {
"vector": [3, 0, -10]
},
"2.2083": {
"vector": [0, 0, -10]
}
}
},
"rightleg": {
"rotation": { "vector": [0, 0, 25] },
"position": { "vector": [0, 0.7, 0] }
"rotation": {
"vector": [0, 0, 25]
},
"position": {
"vector": [0, 0.7, 0]
}
},
"openmouth]2": {
"position": {
@@ -13985,19 +14029,19 @@
"leftarm": {
"rotation": {
"0.0": {
"vector": [0, -82.5, -80]
"vector": [-17.1045, 0.5398, 22.7077]
},
"0.2083": {
"vector": [0, -82.5, -80]
"vector": [-17.1045, 0.5398, 22.7077]
},
"1.2083": {
"vector": [18.3312, -82.0966, -98.4951]
"vector": [-14.6045, 0.5398, 22.7077]
},
"1.4583": {
"vector": [24.8777, -81.7278, -105.1071]
"vector": [-14.6045, 0.5398, 22.7077]
},
"2.2083": {
"vector": [0, -82.5, -80]
"vector": [-17.1045, 0.5398, 22.7077]
}
},
"position": {
@@ -14007,6 +14051,12 @@
"0.2083": {
"vector": [-1, 0, 0]
},
"1.2083": {
"vector": [-1, 0, 0]
},
"1.4583": {
"vector": [-1, 0, 0]
},
"2.2083": {
"vector": [-1, 0, 0]
}
@@ -14014,17 +14064,31 @@
},
"loverleftarm": {
"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": {
"rotation": {
"vector": [-30, 0, 0]
"vector": [-15, 0, 0]
}
},
"loverleg_L": {
"rotation": {
"vector": [70, 0, 0]
"vector": [47.5, 0, 0]
}
},
"closed_L": {
@@ -14036,6 +14100,14 @@
"position": {
"vector": [0, 0, 1]
}
},
"clothleg_lower_L": {
"rotation": {
"vector": [42.5, 0, 0]
},
"position": {
"vector": [0, -0.5, -0.3]
}
}
}
},