Initial commit: Forge 1.20.1 Ellie companion mod

- EllieEntity with GeckoLib animations, sleep AI, pathfinding with crouching
- Dialog system with conditions and effects
- Relationship system with milestones
- OpenDoor and bed occupation pathfinding
- 15 animations: idle1/2/3, sleep, walkingsimple, shiftwalking/shiftidle, etc.
This commit is contained in:
2026-06-09 21:18:04 +03:00
commit f5d318f02e
48 changed files with 21932 additions and 0 deletions
@@ -0,0 +1,143 @@
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.block.DoorBlock;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.Vec3;
import java.util.EnumSet;
public class EllieSleepGoal extends Goal {
private final EllieEntity ellie;
private final int searchRadius;
private BlockPos bedPos;
private int sleepTimer;
private boolean claimed;
private static final int MAX_SLEEP_TICKS = 6000;
private static final double REACH_DIST_SQR = 1.8 * 1.8;
public EllieSleepGoal(EllieEntity ellie, int searchRadius) {
this.ellie = ellie;
this.searchRadius = searchRadius;
this.setFlags(EnumSet.of(Flag.MOVE, Flag.LOOK));
}
@Override
public boolean canUse() {
if (!ellie.level().isNight() && !ellie.isTired()) return false;
if (ellie.isSleeping()) return false;
if (ellie.hurtTime > 0) return false;
bedPos = findNearestBed();
return bedPos != null;
}
@Override
public void start() {
claimed = false;
if (bedPos != null) {
if (ellie.isSpaceLow(bedPos)) {
ellie.setPose(net.minecraft.world.entity.Pose.CROUCHING);
}
ellie.getNavigation().moveTo(bedPos.getX() + 0.5, bedPos.getY(), bedPos.getZ() + 0.5, 0.6);
}
}
@Override
public void tick() {
if (bedPos == null) return;
// Open doors in path
tryOpenDoor();
double distSqr = ellie.distanceToSqr(Vec3.atCenterOf(bedPos));
if (distSqr < REACH_DIST_SQR) {
ellie.getNavigation().stop();
if (!claimed) {
claimed = ellie.occupyBed(bedPos);
}
if (claimed) {
if (!ellie.isSleeping()) {
ellie.setBedSleepPos(bedPos);
}
sleepTimer++;
}
} else {
if (ellie.getNavigation().isDone()) {
ellie.getNavigation().moveTo(bedPos.getX() + 0.5, bedPos.getY(), bedPos.getZ() + 0.5, 0.6);
}
}
}
private void tryOpenDoor() {
BlockPos inFront = ellie.blockPosition().relative(ellie.getDirection());
for (int i = 0; i < 3; i++) {
BlockPos checkPos = ellie.blockPosition().relative(ellie.getDirection(), i);
BlockState state = ellie.level().getBlockState(checkPos);
if (state.getBlock() instanceof DoorBlock) {
if (!state.getValue(DoorBlock.OPEN)) {
ellie.level().setBlock(checkPos, state.setValue(DoorBlock.OPEN, true), 3);
}
return;
}
}
}
@Override
public void stop() {
if (ellie.isSleeping()) {
ellie.wakeUp();
}
sleepTimer = 0;
bedPos = null;
claimed = false;
}
@Override
public boolean canContinueToUse() {
if (ellie.hurtTime > 0) return false;
if (ellie.isSleeping()) {
if (sleepTimer >= MAX_SLEEP_TICKS) return false;
if (bedPos != null && !ellie.level().getBlockState(bedPos).isBed(ellie.level(), bedPos, null)) {
return false;
}
return true;
}
if (!ellie.level().isNight() && !ellie.isTired()) return false;
return true;
}
private BlockPos findNearestBed() {
BlockPos entityPos = ellie.blockPosition();
BlockPos.MutableBlockPos mutable = new BlockPos.MutableBlockPos();
BlockPos nearest = null;
double nearestDist = Double.MAX_VALUE;
for (int x = -searchRadius; x <= searchRadius; x++) {
for (int z = -searchRadius; z <= searchRadius; z++) {
for (int y = -2; y <= 2; y++) {
mutable.set(entityPos.getX() + x, entityPos.getY() + y, entityPos.getZ() + z);
if (isFreeBed(mutable)) {
double dist = entityPos.distSqr(mutable);
if (dist < nearestDist) {
nearestDist = dist;
nearest = mutable.immutable();
}
}
}
}
}
return nearest;
}
private boolean isFreeBed(BlockPos pos) {
BlockState state = ellie.level().getBlockState(pos);
if (!state.isBed(ellie.level(), pos, null)) return false;
if (state.hasProperty(DoorBlock.OPEN) && state.getValue(DoorBlock.OPEN)) return false;
return !state.getValue(net.minecraft.world.level.block.BedBlock.OCCUPIED);
}
}