diff --git a/.gitignore b/.gitignore index 4df3c09..7a7c5a7 100644 --- a/.gitignore +++ b/.gitignore @@ -115,6 +115,9 @@ gradle-app.setting run/ runs/ +# IDE build output +bin/ + # Forge Gradle crash-reports/ logs/ diff --git a/gradlew b/gradlew old mode 100644 new mode 100755 diff --git a/src/main/java/me/sashegdev/fabled_hearts/ai/BasicAI.java b/src/main/java/me/sashegdev/fabled_hearts/ai/BasicAI.java index 9c09fb4..1d9a63f 100644 --- a/src/main/java/me/sashegdev/fabled_hearts/ai/BasicAI.java +++ b/src/main/java/me/sashegdev/fabled_hearts/ai/BasicAI.java @@ -11,7 +11,6 @@ public class BasicAI { selector.addGoal(p++, new FloatGoal(mob)); selector.addGoal(p++, new LookAtPlayerGoal(mob, Player.class, 8.0f)); selector.addGoal(p++, new RandomLookAroundGoal(mob)); - selector.addGoal(p++, new WaterAvoidingRandomStrollGoal(mob, 0.8)); selector.addGoal(p++, new OpenDoorGoal(mob, true)); } } diff --git a/src/main/java/me/sashegdev/fabled_hearts/ai/BoundedWanderGoal.java b/src/main/java/me/sashegdev/fabled_hearts/ai/BoundedWanderGoal.java new file mode 100644 index 0000000..bfc2465 --- /dev/null +++ b/src/main/java/me/sashegdev/fabled_hearts/ai/BoundedWanderGoal.java @@ -0,0 +1,84 @@ +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.levelgen.Heightmap; +import net.minecraft.world.phys.Vec3; + +import java.util.EnumSet; + +/** + * Элли бродит свободно, но случайная цель всегда в пределах homeRadius от домашней точки + * (обычно — место спавна Элли / спавн игрока). Если её вынесло за зону — ведёт обратно домой. + */ +public class BoundedWanderGoal extends Goal { + private static final int WANDER_CHANCE = 60; + + private final EllieEntity mob; + private final double speed; + private double targetX; + private double targetY; + private double targetZ; + + public BoundedWanderGoal(EllieEntity mob, double speed) { + this.mob = mob; + this.speed = speed; + this.setFlags(EnumSet.of(Flag.MOVE)); + } + + @Override + public boolean canUse() { + if (this.mob.isSleeping()) { + return false; + } + if (!this.mob.getNavigation().isDone()) { + return false; + } + if (this.mob.getRandom().nextInt(WANDER_CHANCE) != 0) { + return false; + } + + Vec3 target = pickTarget(); + if (target == null) { + return false; + } + this.targetX = target.x; + this.targetY = target.y; + this.targetZ = target.z; + return true; + } + + @Override + public boolean canContinueToUse() { + return !this.mob.isSleeping() && !this.mob.getNavigation().isDone(); + } + + @Override + public void start() { + this.mob.getNavigation().moveTo(this.targetX, this.targetY, this.targetZ, this.speed); + } + + @Override + public void stop() { + this.mob.getNavigation().stop(); + } + + private Vec3 pickTarget() { + BlockPos home = this.mob.getHomePos(); + int radius = this.mob.getHomeRadius(); + + double distSqFromHome = this.mob.blockPosition().distSqr(home); + if (distSqFromHome > (double) radius * radius) { + return Vec3.atBottomCenterOf(home); + } + + double angle = this.mob.getRandom().nextDouble() * Math.PI * 2.0; + double dist = this.mob.getRandom().nextDouble() * radius; + double x = home.getX() + 0.5 + Math.cos(angle) * dist; + double z = home.getZ() + 0.5 + Math.sin(angle) * dist; + + BlockPos top = this.mob.level().getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, new BlockPos((int) x, 0, (int) z)); + return new Vec3(x, top.getY(), z); + } +} diff --git a/src/main/java/me/sashegdev/fabled_hearts/ai/EllieSleepGoal.java b/src/main/java/me/sashegdev/fabled_hearts/ai/EllieSleepGoal.java index c89b64a..c82a295 100644 --- a/src/main/java/me/sashegdev/fabled_hearts/ai/EllieSleepGoal.java +++ b/src/main/java/me/sashegdev/fabled_hearts/ai/EllieSleepGoal.java @@ -5,80 +5,96 @@ import net.minecraft.core.BlockPos; import net.minecraft.world.entity.ai.goal.Goal; import net.minecraft.world.level.block.BedBlock; import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.phys.Vec3; +import net.minecraft.world.level.block.state.properties.BedPart; import java.util.EnumSet; +/** + * Ночью Элли ищет ближайшую свободную кровать в пределах домашней зоны (homePos ± homeRadius) + * и идёт к её ИЗНОЖЬЮ. Спит только ночью. После пробуждения встаёт рядом с кроватью (см. wakeUp). + */ 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; + private static final int MAX_PATHFIND_ATTEMPTS = 3; + private static final int NO_BED_RETRY_COOLDOWN_TICKS = 200; + private static final double REACH_DIST_SQR = 3.0; - public EllieSleepGoal(EllieEntity ellie, int searchRadius) { + private final EllieEntity ellie; + private boolean arrived; + private boolean noBedAvailable; + private int pathAttempts; + private long nextSearchAllowedTime; + + public EllieSleepGoal(EllieEntity ellie) { this.ellie = ellie; - this.searchRadius = searchRadius; - this.setFlags(EnumSet.of(Flag.MOVE, Flag.LOOK)); + this.setFlags(EnumSet.of(Flag.MOVE, Flag.LOOK, Flag.JUMP)); } @Override public boolean canUse() { if (ellie.isSleeping()) return false; - if (!ellie.level().isNight() && !ellie.isTired()) return false; - - bedPos = findBed(); - return bedPos != null; + if (ellie.getWakeUpCooldown() > 0) return false; + if (!ellie.isNightTime()) return false; + return ellie.level().getGameTime() >= this.nextSearchAllowedTime; } - private BlockPos findBed() { - BlockPos stored = ellie.getBedPos(); - if (stored != null && isFreeBed(stored) && ellie.distanceToSqr(Vec3.atCenterOf(stored)) < 50 * 50) { - return stored; - } - return findNearestBed(); + @Override + public boolean canContinueToUse() { + return ellie.isNightTime() && !this.noBedAvailable; } @Override public void start() { - claimed = false; - if (bedPos != null) { - ellie.getNavigation().moveTo(bedPos.getX() + 0.5, bedPos.getY(), bedPos.getZ() + 0.5, 0.6); + this.arrived = false; + this.noBedAvailable = false; + this.pathAttempts = 0; + + if (!isBedValid(ellie.getBedPos())) { + ellie.setBedPos(findNearbyBedFootPos()); + } + + if (ellie.getBedPos() != null) { + moveTowardsBed(); + } else { + giveUpForNow(); } } @Override public void tick() { - if (bedPos == null) return; - - BlockState currentState = ellie.level().getBlockState(bedPos); - if (!(currentState.getBlock() instanceof BedBlock)) { - bedPos = null; + if (this.arrived) { + ellie.getNavigation().stop(); return; } - double distSqr = ellie.distanceToSqr(Vec3.atCenterOf(bedPos)); + if (this.noBedAvailable) { + return; + } - if (distSqr < REACH_DIST_SQR) { - ellie.getNavigation().stop(); + BlockPos bed = ellie.getBedPos(); + if (!isBedValid(bed)) { + BlockPos replacement = findNearbyBedFootPos(); + ellie.setBedPos(replacement); + if (replacement == null) { + giveUpForNow(); + return; + } + moveTowardsBed(); + return; + } - if (!claimed) { - claimed = ellie.occupyBed(bedPos); - } + if (ellie.blockPosition().distSqr(bed) <= REACH_DIST_SQR) { + claimBed(bed); + return; + } - if (claimed && !ellie.isSleeping()) { - ellie.setBedSleepPos(bedPos); - } - - if (ellie.isSleeping()) { - sleepTimer++; - } - } else { - if (ellie.getNavigation().isDone()) { - ellie.getNavigation().moveTo(bedPos.getX() + 0.5, bedPos.getY(), bedPos.getZ() + 0.5, 0.6); + if (ellie.getNavigation().isDone()) { + this.pathAttempts++; + if (this.pathAttempts > MAX_PATHFIND_ATTEMPTS) { + ellie.setBedPos(null); + giveUpForNow(); + return; } + moveTowardsBed(); } } @@ -87,59 +103,70 @@ public class EllieSleepGoal extends Goal { if (ellie.isSleeping()) { ellie.wakeUp(); } - sleepTimer = 0; - bedPos = null; - claimed = false; + ellie.getNavigation().stop(); + this.arrived = false; + this.noBedAvailable = false; } - @Override - public boolean canContinueToUse() { - if (bedPos != null) { - BlockState state = ellie.level().getBlockState(bedPos); - if (!state.isBed(ellie.level(), bedPos, null)) return false; - } - - if (ellie.isSleeping()) { - if (sleepTimer >= MAX_SLEEP_TICKS) return false; - if (ellie.level().isDay()) return false; - if (ellie.hurtTime > 10) return false; - if (ellie.distanceToSqr(Vec3.atCenterOf(bedPos)) > 16.0) return false; - return true; - } - if (bedPos == null) return false; - if (ellie.hurtTime > 10) return false; - if (ellie.level().isDay() && !ellie.isTired()) return false; - if (!ellie.level().isNight() && !ellie.isTired()) return false; - return true; + private void giveUpForNow() { + this.noBedAvailable = true; + this.nextSearchAllowedTime = ellie.level().getGameTime() + NO_BED_RETRY_COOLDOWN_TICKS; } - private BlockPos findNearestBed() { - BlockPos entityPos = ellie.blockPosition(); - BlockPos.MutableBlockPos mutable = new BlockPos.MutableBlockPos(); - BlockPos nearest = null; - double nearestDist = Double.MAX_VALUE; + private void moveTowardsBed() { + BlockPos bed = ellie.getBedPos(); + ellie.getNavigation().moveTo(bed.getX() + 0.5, bed.getY(), bed.getZ() + 0.5, 1.0); + } - 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(); - } - } - } + private void claimBed(BlockPos footPos) { + this.arrived = true; + ellie.getNavigation().stop(); + + if (ellie.occupyBed(footPos)) { + ellie.setBedSleepPos(footPos); + ellie.markSleptTonight(); + } else { + this.arrived = false; + ellie.setBedPos(null); + giveUpForNow(); + } + } + + private boolean isBedValid(BlockPos pos) { + if (pos == null) { + return false; + } + return ellie.level().getBlockState(pos).getBlock() instanceof BedBlock; + } + + private BlockPos findNearbyBedFootPos() { + BlockPos home = ellie.getHomePos(); + int radius = ellie.getHomeRadius(); + BlockPos ownPos = ellie.blockPosition(); + + BlockPos min = home.offset(-radius, -4, -radius); + BlockPos max = home.offset(radius, 4, radius); + + BlockPos best = null; + double bestDistSq = Double.MAX_VALUE; + + for (BlockPos pos : BlockPos.betweenClosed(min, max)) { + BlockState state = ellie.level().getBlockState(pos); + if (!(state.getBlock() instanceof BedBlock) || state.getValue(BedBlock.OCCUPIED)) { + continue; + } + + BedPart part = state.getValue(BedBlock.PART); + BlockPos footPos = (part == BedPart.FOOT) + ? pos + : pos.relative(state.getValue(BedBlock.FACING).getOpposite()); + + double distSq = ownPos.distSqr(footPos); + if (distSq < bestDistSq) { + bestDistSq = distSq; + best = footPos.immutable(); } } - return nearest; - } - - private boolean isFreeBed(BlockPos pos) { - BlockState state = ellie.level().getBlockState(pos); - if (!(state.getBlock() instanceof BedBlock)) return false; - if (state.getValue(BedBlock.OCCUPIED)) return false; - return true; + return best; } } diff --git a/src/main/java/me/sashegdev/fabled_hearts/entity/ellie/EllieEntity.java b/src/main/java/me/sashegdev/fabled_hearts/entity/ellie/EllieEntity.java index f8b43b6..8de4eef 100644 --- a/src/main/java/me/sashegdev/fabled_hearts/entity/ellie/EllieEntity.java +++ b/src/main/java/me/sashegdev/fabled_hearts/entity/ellie/EllieEntity.java @@ -18,6 +18,7 @@ import java.util.ArrayList; import java.util.List; import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; +import net.minecraft.world.phys.Vec3; import net.minecraft.world.damagesource.DamageSource; import net.minecraft.world.entity.*; import net.minecraft.world.item.ItemStack; @@ -91,15 +92,28 @@ public class EllieEntity extends Animal implements GeoEntity { private float prevYRot; private float yawDeltaAccumulator; private BlockPos bedPos; + private Direction lastBedDir; private double lastX, lastZ; private int crouchTimer; private int debugAnimTimer; private boolean needsSleepRestore; + private int wakeUpCooldown; + private BlockPos homePos; + private int homeRadius = 20; + private java.util.UUID ownerUuid; + private int missedNights; + private boolean sleptTonight; + private boolean nightCheckedToday; + private boolean homeCheckedToday; + + private static final int SLEEPY_MISSED_NIGHTS_THRESHOLD = 2; + private static final long MORNING_CHECK_WINDOW = 20L; public EllieEntity(EntityType type, Level level) { super(type, level); ((GroundPathNavigation) this.getNavigation()).setCanOpenDoors(true); this.setPersistenceRequired(); + this.homePos = this.blockPosition(); lastX = getX(); lastZ = getZ(); prevYRot = getYRot(); @@ -138,11 +152,12 @@ public class EllieEntity extends Animal implements GeoEntity { @Override protected void registerGoals() { BasicAI.addBasicGoals(this, this.goalSelector, 4); - this.goalSelector.addGoal(1, new EllieSleepGoal(this, 30)); + this.goalSelector.addGoal(1, new EllieSleepGoal(this)); this.goalSelector.addGoal(1, new EllieDoorInteractGoal(this)); this.goalSelector.addGoal(1, new IdleAnimationGoal(this)); this.goalSelector.addGoal(2, new ShelterGoal(this)); this.goalSelector.addGoal(3, new me.sashegdev.fabled_hearts.ai.FollowPlayerGoal(this)); + this.goalSelector.addGoal(4, new me.sashegdev.fabled_hearts.ai.BoundedWanderGoal(this, 0.8)); } @Override @@ -183,10 +198,16 @@ public class EllieEntity extends Animal implements GeoEntity { } } + if (wakeUpCooldown > 0) wakeUpCooldown--; + + tickNightlySleepTracking(); + tickHomeFollowsOwnerSpawn(); + tickHomeBounds(); + boolean isNight = level().isNight(); boolean raining = level().isRainingAt(blockPosition()); boolean lowCeiling = checkLowCeiling(); - boolean tired = ticksWithoutSleep > 48000; + boolean tired = missedNights >= SLEEPY_MISSED_NIGHTS_THRESHOLD; boolean sleeping = entityData.get(DATA_SLEEPING); boolean falling = !onGround() && getDeltaMovement().y < -0.1; boolean moving = isMoving(); @@ -213,6 +234,28 @@ public class EllieEntity extends Animal implements GeoEntity { if (sleeping) { setPose(Pose.SLEEPING); crouchTimer = 0; + if (bedPos != null && distanceToSqr(Vec3.atCenterOf(bedPos)) > 9.0) { + wakeUp(); + } + Direction bedDir = getBedOrientation(); + if (bedDir != null) { + BlockState bedState = level().getBlockState(bedPos); + BlockPos footPos = bedState.getValue(BedBlock.PART) == BedPart.HEAD + ? bedPos.relative(bedDir.getOpposite()) + : bedPos; + setPos(footPos.getX() + 0.5, bedPos.getY(), footPos.getZ() + 0.5); + setDeltaMovement(Vec3.ZERO); + + float yaw = bedDir.toYRot(); + setYRot(yaw); + yRotO = yaw; + yBodyRot = yaw; + yBodyRotO = yaw; + yHeadRot = yaw; + yHeadRotO = yaw; + setXRot(0); + xRotO = 0; + } } else if (lowCeiling) { if (onGround() || wasCrouching) { setPose(Pose.CROUCHING); @@ -232,8 +275,8 @@ public class EllieEntity extends Animal implements GeoEntity { for (Player player : level().players()) { if (player.distanceToSqr(this) < 64 * 64) { ModNetworking.CHANNEL.send( - PacketDistributor.PLAYER.with(() -> (ServerPlayer) player), - new DebugSyncPacket(this.getId(), wps)); + PacketDistributor.PLAYER.with(() -> (ServerPlayer) player), + new DebugSyncPacket(this.getId(), wps, getDebugBrainString())); } } } @@ -287,9 +330,10 @@ public class EllieEntity extends Animal implements GeoEntity { .map(g -> g.getGoal().getClass().getSimpleName()) .orElse("none"); sb.append("Goal:").append(goalName).append("|"); + sb.append("Home:").append(homePos != null ? homePos : "-").append("|"); sb.append("Bed:").append(bedPos != null ? bedPos : "-").append("|"); sb.append("Sleep:").append(entityData.get(DATA_SLEEPING)).append("|"); - sb.append("Tired:").append(ticksWithoutSleep).append("|"); + sb.append("Missed:").append(missedNights).append("|"); sb.append("Move:").append(entityData.get(DATA_MOVING)).append("|"); sb.append("Pose:").append(getPose()).append("|"); sb.append("LowCeil:").append(entityData.get(DATA_UNDER_LOW_CEILING)).append("|"); @@ -304,6 +348,100 @@ public class EllieEntity extends Animal implements GeoEntity { entityData.set(DATA_DEBUG_BRAIN, sb.toString()); } + public boolean isNightTime() { + long time = level().getDayTime() % 24000L; + return time >= 13000L && time <= 23000L; + } + + public BlockPos getHomePos() { + return homePos == null ? blockPosition() : homePos; + } + + public void setHomePos(BlockPos pos) { + this.homePos = pos; + } + + public int getHomeRadius() { + return homeRadius; + } + + public void setHomeRadius(int radius) { + this.homeRadius = radius; + } + + public void setOwnerUuid(java.util.UUID uuid) { + this.ownerUuid = uuid; + } + + public java.util.UUID getOwnerUuid() { + return ownerUuid; + } + + public void markSleptTonight() { + this.sleptTonight = true; + } + + private void tickHomeFollowsOwnerSpawn() { + long timeOfDay = level().getDayTime() % 24000L; + if (timeOfDay > MORNING_CHECK_WINDOW) { + this.homeCheckedToday = false; + return; + } + if (this.homeCheckedToday || ownerUuid == null) { + return; + } + this.homeCheckedToday = true; + + if (!(level() instanceof ServerLevel serverLevel)) { + return; + } + ServerPlayer owner = serverLevel.getServer().getPlayerList().getPlayer(ownerUuid); + if (owner == null) { + return; + } + BlockPos ownerSpawn = owner.getRespawnPosition(); + if (ownerSpawn == null) { + return; + } + + if (ownerSpawn.distSqr(getHomePos()) > 4.0) { + this.setHomePos(ownerSpawn); + this.forgetBed(); + } + } + + private void tickNightlySleepTracking() { + long timeOfDay = level().getDayTime() % 24000L; + if (timeOfDay > MORNING_CHECK_WINDOW) { + this.nightCheckedToday = false; + return; + } + if (this.nightCheckedToday) { + return; + } + this.nightCheckedToday = true; + + if (this.sleptTonight) { + this.missedNights = 0; + } else { + this.missedNights++; + } + this.sleptTonight = false; + entityData.set(DATA_TIRED, this.missedNights >= SLEEPY_MISSED_NIGHTS_THRESHOLD); + } + + private void tickHomeBounds() { + if (isSleeping()) { + return; + } + BlockPos home = getHomePos(); + double distSq = blockPosition().distSqr(home); + double maxSq = (double) homeRadius * homeRadius; + if (distSq > maxSq * 1.1 && navigation.isDone()) { + navigation.moveTo(home.getX() + 0.5, home.getY(), home.getZ() + 0.5, 1.0); + } + } + public void toggleDebug() { entityData.set(DATA_DEBUG, !entityData.get(DATA_DEBUG)); if (!entityData.get(DATA_DEBUG)) { @@ -344,7 +482,7 @@ public class EllieEntity extends Animal implements GeoEntity { @Override public EntityDimensions getDimensions(Pose pose) { - if (pose == Pose.SLEEPING) return EntityDimensions.fixed(0.15f, 0.15f); + if (pose == Pose.SLEEPING) return EntityDimensions.fixed(0.2f, 0.2f); if (pose == Pose.CROUCHING) return EntityDimensions.fixed(0.6f, 1.0f); return super.getDimensions(pose); } @@ -418,27 +556,80 @@ public class EllieEntity extends Animal implements GeoEntity { if (!(state.getBlock() instanceof BedBlock)) return; Direction facing = state.getValue(BedBlock.FACING); - BlockPos headPos = state.getValue(BedBlock.PART) == BedPart.HEAD - ? pos - : pos.relative(facing); + BlockPos footPos = state.getValue(BedBlock.PART) == BedPart.HEAD + ? pos.relative(facing.getOpposite()) + : pos; setPose(Pose.SLEEPING); - setPos(headPos.getX() + 0.5, pos.getY() + 0.6875, headPos.getZ() + 0.5); - setSleepingPos(headPos); - float yRot = facing.toYRot(); - setYRot(yRot); - yRotO = yRot; - setYHeadRot(yRot); + setPos(footPos.getX() + 0.5, footPos.getY(), footPos.getZ() + 0.5); + float yaw = facing.toYRot(); + setYRot(yaw); + yRotO = yaw; + yBodyRot = yaw; + yBodyRotO = yaw; + yHeadRot = yaw; + yHeadRotO = yaw; + setXRot(0); + xRotO = 0; + lastBedDir = facing; setSleeping(true); } + @Override + public Direction getBedOrientation() { + if (bedPos != null && isSleeping()) { + BlockState state = level().getBlockState(bedPos); + if (state.getBlock() instanceof BedBlock) { + return state.getValue(BedBlock.FACING); + } + } + return null; + } + + public Direction getLastBedDir() { return lastBedDir; } + public void wakeUp() { + BlockPos foot = bedPos; + Direction dir = lastBedDir; releaseBed(); forgetBed(); setSleeping(false); setPose(Pose.STANDING); navigation.stop(); + if (foot != null && dir != null && !level().isClientSide) { + BlockPos stand = foot.relative(dir.getOpposite()); + BlockState standState = level().getBlockState(stand); + boolean safeSpot = !standState.blocksMotion() && level().getBlockState(stand.above()).isAir(); + if (safeSpot) { + setPos(stand.getX() + 0.5, stand.getY(), stand.getZ() + 0.5); + float yaw = dir.toYRot(); + setYRot(yaw); + yRotO = yaw; + yBodyRot = yaw; + yBodyRotO = yaw; + yHeadRot = yaw; + yHeadRotO = yaw; + setXRot(0); + xRotO = 0; + wakeUpCooldown = 200; + return; + } + } setDeltaMovement(getDeltaMovement().add(0, 0.15, 0)); + wakeUpCooldown = 200; + } + + @Override + public boolean isPushable() { + return !isSleeping() && super.isPushable(); + } + + @Override + public void push(Entity entity) { + if (isSleeping()) { + return; + } + super.push(entity); } @Override @@ -561,9 +752,6 @@ public class EllieEntity extends Animal implements GeoEntity { return InteractionResult.SUCCESS; } - if (entityData.get(DATA_TIRED)) { - return InteractionResult.SUCCESS; - } if (this.level().isClientSide) { ModNetworking.CHANNEL.sendToServer(new OpenDialogPacket(this.getId())); return InteractionResult.SUCCESS; @@ -586,6 +774,9 @@ public class EllieEntity extends Animal implements GeoEntity { return false; } } + if (isSleeping() && !level().isClientSide) { + wakeUp(); + } return super.hurt(source, amount); } @@ -595,6 +786,16 @@ public class EllieEntity extends Animal implements GeoEntity { tag.putInt("TicksWithoutSleep", ticksWithoutSleep); tag.putFloat("Relationship", relationshipPoints); if (bedPos != null) tag.putLong("BedPos", bedPos.asLong()); + if (homePos != null) { + tag.putInt("HomeX", homePos.getX()); + tag.putInt("HomeY", homePos.getY()); + tag.putInt("HomeZ", homePos.getZ()); + } + tag.putInt("HomeRadius", homeRadius); + tag.putInt("MissedNights", missedNights); + if (ownerUuid != null) { + tag.putUUID("OwnerUuid", ownerUuid); + } } @Override @@ -608,6 +809,16 @@ public class EllieEntity extends Animal implements GeoEntity { needsSleepRestore = true; } } + if (tag.contains("HomeX")) { + homePos = new BlockPos(tag.getInt("HomeX"), tag.getInt("HomeY"), tag.getInt("HomeZ")); + } + if (tag.contains("HomeRadius")) { + homeRadius = tag.getInt("HomeRadius"); + } + missedNights = tag.getInt("MissedNights"); + if (tag.contains("OwnerUuid")) { + ownerUuid = tag.getUUID("OwnerUuid"); + } } @Override @@ -627,6 +838,7 @@ public class EllieEntity extends Animal implements GeoEntity { this.relationshipPoints = Math.min(100, Math.max(0, this.relationshipPoints + amount)); } public int getTicksWithoutSleep() { return ticksWithoutSleep; } + public int getWakeUpCooldown() { return wakeUpCooldown; } public int getFollowTargetId() { return entityData.get(DATA_FOLLOW_TARGET); } public boolean isFollowingPlayer() { return entityData.get(DATA_FOLLOW_TARGET) > 0; } public int getDebugAnim() { return entityData.get(DATA_DEBUG_ANIM); } diff --git a/src/main/java/me/sashegdev/fabled_hearts/entity/ellie/EllieRenderer.java b/src/main/java/me/sashegdev/fabled_hearts/entity/ellie/EllieRenderer.java index ff755a0..89c231d 100644 --- a/src/main/java/me/sashegdev/fabled_hearts/entity/ellie/EllieRenderer.java +++ b/src/main/java/me/sashegdev/fabled_hearts/entity/ellie/EllieRenderer.java @@ -2,13 +2,11 @@ package me.sashegdev.fabled_hearts.entity.ellie; import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.vertex.VertexConsumer; -import com.mojang.math.Axis; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.Font; import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.client.renderer.RenderType; import net.minecraft.client.renderer.entity.EntityRendererProvider; -import net.minecraft.core.Direction; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.entity.Pose; import org.jetbrains.annotations.Nullable; @@ -21,25 +19,6 @@ public class EllieRenderer extends GeoEntityRenderer { this.shadowRadius = 0.5f; } - @Override - protected void applyRotations(EllieEntity entity, PoseStack poseStack, float tick, float yRot, float partialTick) { - if (entity.getPose() == Pose.SLEEPING) { - Direction bedDir = entity.getBedOrientation(); - if (bedDir != null) { - poseStack.translate( - -bedDir.getStepX() * 1.847, - 0.0F, - -bedDir.getStepZ() * 1.847 - ); - poseStack.mulPose(Axis.YP.rotationDegrees(bedDir.toYRot())); - poseStack.mulPose(Axis.XP.rotationDegrees(90.0F)); - poseStack.mulPose(Axis.ZP.rotationDegrees(180.0F)); - } - return; - } - super.applyRotations(entity, poseStack, tick, yRot, partialTick); - } - @Override public RenderType getRenderType(EllieEntity animatable, ResourceLocation texture, @Nullable MultiBufferSource bufferSource, float partialTick) { @@ -50,7 +29,6 @@ public class EllieRenderer extends GeoEntityRenderer { public void render(EllieEntity entity, float entityYaw, float partialTick, PoseStack poseStack, MultiBufferSource bufferSource, int packedLight) { super.render(entity, entityYaw, partialTick, poseStack, bufferSource, packedLight); - if (entity.isDebugMode()) { renderBrainText(entity, poseStack, bufferSource, packedLight); } diff --git a/src/main/java/me/sashegdev/fabled_hearts/menu/EllieSpawnItem.java b/src/main/java/me/sashegdev/fabled_hearts/menu/EllieSpawnItem.java index 771673f..43b1e05 100644 --- a/src/main/java/me/sashegdev/fabled_hearts/menu/EllieSpawnItem.java +++ b/src/main/java/me/sashegdev/fabled_hearts/menu/EllieSpawnItem.java @@ -40,6 +40,9 @@ public class EllieSpawnItem extends Item { var entity = ModEntities.ELLIE.get().create(serverLevel); if (entity != null) { entity.setPos(player.getX(), player.getY(), player.getZ()); + entity.setHomePos(entity.blockPosition()); + entity.setHomeRadius(20); + entity.setOwnerUuid(player.getUUID()); serverLevel.addFreshEntity(entity); data.setEllieUUID(entity.getUUID()); player.sendSystemMessage(Component.literal("§aEllie появилась!")); diff --git a/src/main/resources/assets/fabled_hearts/animations/ellie.animation.json b/src/main/resources/assets/fabled_hearts/animations/ellie.animation.json index 0b65711..753ea21 100644 --- a/src/main/resources/assets/fabled_hearts/animations/ellie.animation.json +++ b/src/main/resources/assets/fabled_hearts/animations/ellie.animation.json @@ -13897,6 +13897,14 @@ "loop": true, "animation_length": 2.25, "bones": { + "root": { + "rotation": { + "vector": [90, 0, -180] + }, + "position": { + "vector": [0, 9.1, 9] + } + }, "torso": { "rotation": { "0.0833": {