Порт из Claude Code версии: укладывание на кровать, дом, ночной сон, sleepy после 2 ночей
This commit is contained in:
@@ -115,6 +115,9 @@ gradle-app.setting
|
|||||||
run/
|
run/
|
||||||
runs/
|
runs/
|
||||||
|
|
||||||
|
# IDE build output
|
||||||
|
bin/
|
||||||
|
|
||||||
# Forge Gradle
|
# Forge Gradle
|
||||||
crash-reports/
|
crash-reports/
|
||||||
logs/
|
logs/
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ public class BasicAI {
|
|||||||
selector.addGoal(p++, new FloatGoal(mob));
|
selector.addGoal(p++, new FloatGoal(mob));
|
||||||
selector.addGoal(p++, new LookAtPlayerGoal(mob, Player.class, 8.0f));
|
selector.addGoal(p++, new LookAtPlayerGoal(mob, Player.class, 8.0f));
|
||||||
selector.addGoal(p++, new RandomLookAroundGoal(mob));
|
selector.addGoal(p++, new RandomLookAroundGoal(mob));
|
||||||
selector.addGoal(p++, new WaterAvoidingRandomStrollGoal(mob, 0.8));
|
|
||||||
selector.addGoal(p++, new OpenDoorGoal(mob, true));
|
selector.addGoal(p++, new OpenDoorGoal(mob, true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,80 +5,96 @@ 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.block.BedBlock;
|
import net.minecraft.world.level.block.BedBlock;
|
||||||
import net.minecraft.world.level.block.state.BlockState;
|
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;
|
import java.util.EnumSet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ночью Элли ищет ближайшую свободную кровать в пределах домашней зоны (homePos ± homeRadius)
|
||||||
|
* и идёт к её ИЗНОЖЬЮ. Спит только ночью. После пробуждения встаёт рядом с кроватью (см. wakeUp).
|
||||||
|
*/
|
||||||
public class EllieSleepGoal extends Goal {
|
public class EllieSleepGoal extends Goal {
|
||||||
private final EllieEntity ellie;
|
private static final int MAX_PATHFIND_ATTEMPTS = 3;
|
||||||
private final int searchRadius;
|
private static final int NO_BED_RETRY_COOLDOWN_TICKS = 200;
|
||||||
private BlockPos bedPos;
|
private static final double REACH_DIST_SQR = 3.0;
|
||||||
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) {
|
private final EllieEntity ellie;
|
||||||
|
private boolean arrived;
|
||||||
|
private boolean noBedAvailable;
|
||||||
|
private int pathAttempts;
|
||||||
|
private long nextSearchAllowedTime;
|
||||||
|
|
||||||
|
public EllieSleepGoal(EllieEntity ellie) {
|
||||||
this.ellie = ellie;
|
this.ellie = ellie;
|
||||||
this.searchRadius = searchRadius;
|
this.setFlags(EnumSet.of(Flag.MOVE, Flag.LOOK, Flag.JUMP));
|
||||||
this.setFlags(EnumSet.of(Flag.MOVE, Flag.LOOK));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean canUse() {
|
public boolean canUse() {
|
||||||
if (ellie.isSleeping()) return false;
|
if (ellie.isSleeping()) return false;
|
||||||
if (!ellie.level().isNight() && !ellie.isTired()) return false;
|
if (ellie.getWakeUpCooldown() > 0) return false;
|
||||||
|
if (!ellie.isNightTime()) return false;
|
||||||
bedPos = findBed();
|
return ellie.level().getGameTime() >= this.nextSearchAllowedTime;
|
||||||
return bedPos != null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private BlockPos findBed() {
|
@Override
|
||||||
BlockPos stored = ellie.getBedPos();
|
public boolean canContinueToUse() {
|
||||||
if (stored != null && isFreeBed(stored) && ellie.distanceToSqr(Vec3.atCenterOf(stored)) < 50 * 50) {
|
return ellie.isNightTime() && !this.noBedAvailable;
|
||||||
return stored;
|
|
||||||
}
|
|
||||||
return findNearestBed();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void start() {
|
public void start() {
|
||||||
claimed = false;
|
this.arrived = false;
|
||||||
if (bedPos != null) {
|
this.noBedAvailable = false;
|
||||||
ellie.getNavigation().moveTo(bedPos.getX() + 0.5, bedPos.getY(), bedPos.getZ() + 0.5, 0.6);
|
this.pathAttempts = 0;
|
||||||
|
|
||||||
|
if (!isBedValid(ellie.getBedPos())) {
|
||||||
|
ellie.setBedPos(findNearbyBedFootPos());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ellie.getBedPos() != null) {
|
||||||
|
moveTowardsBed();
|
||||||
|
} else {
|
||||||
|
giveUpForNow();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void tick() {
|
public void tick() {
|
||||||
if (bedPos == null) return;
|
if (this.arrived) {
|
||||||
|
ellie.getNavigation().stop();
|
||||||
BlockState currentState = ellie.level().getBlockState(bedPos);
|
|
||||||
if (!(currentState.getBlock() instanceof BedBlock)) {
|
|
||||||
bedPos = null;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
double distSqr = ellie.distanceToSqr(Vec3.atCenterOf(bedPos));
|
if (this.noBedAvailable) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (distSqr < REACH_DIST_SQR) {
|
BlockPos bed = ellie.getBedPos();
|
||||||
ellie.getNavigation().stop();
|
if (!isBedValid(bed)) {
|
||||||
|
BlockPos replacement = findNearbyBedFootPos();
|
||||||
|
ellie.setBedPos(replacement);
|
||||||
|
if (replacement == null) {
|
||||||
|
giveUpForNow();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
moveTowardsBed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!claimed) {
|
if (ellie.blockPosition().distSqr(bed) <= REACH_DIST_SQR) {
|
||||||
claimed = ellie.occupyBed(bedPos);
|
claimBed(bed);
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (claimed && !ellie.isSleeping()) {
|
if (ellie.getNavigation().isDone()) {
|
||||||
ellie.setBedSleepPos(bedPos);
|
this.pathAttempts++;
|
||||||
}
|
if (this.pathAttempts > MAX_PATHFIND_ATTEMPTS) {
|
||||||
|
ellie.setBedPos(null);
|
||||||
if (ellie.isSleeping()) {
|
giveUpForNow();
|
||||||
sleepTimer++;
|
return;
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (ellie.getNavigation().isDone()) {
|
|
||||||
ellie.getNavigation().moveTo(bedPos.getX() + 0.5, bedPos.getY(), bedPos.getZ() + 0.5, 0.6);
|
|
||||||
}
|
}
|
||||||
|
moveTowardsBed();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,59 +103,70 @@ public class EllieSleepGoal extends Goal {
|
|||||||
if (ellie.isSleeping()) {
|
if (ellie.isSleeping()) {
|
||||||
ellie.wakeUp();
|
ellie.wakeUp();
|
||||||
}
|
}
|
||||||
sleepTimer = 0;
|
ellie.getNavigation().stop();
|
||||||
bedPos = null;
|
this.arrived = false;
|
||||||
claimed = false;
|
this.noBedAvailable = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void giveUpForNow() {
|
||||||
public boolean canContinueToUse() {
|
this.noBedAvailable = true;
|
||||||
if (bedPos != null) {
|
this.nextSearchAllowedTime = ellie.level().getGameTime() + NO_BED_RETRY_COOLDOWN_TICKS;
|
||||||
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 BlockPos findNearestBed() {
|
private void moveTowardsBed() {
|
||||||
BlockPos entityPos = ellie.blockPosition();
|
BlockPos bed = ellie.getBedPos();
|
||||||
BlockPos.MutableBlockPos mutable = new BlockPos.MutableBlockPos();
|
ellie.getNavigation().moveTo(bed.getX() + 0.5, bed.getY(), bed.getZ() + 0.5, 1.0);
|
||||||
BlockPos nearest = null;
|
}
|
||||||
double nearestDist = Double.MAX_VALUE;
|
|
||||||
|
|
||||||
for (int x = -searchRadius; x <= searchRadius; x++) {
|
private void claimBed(BlockPos footPos) {
|
||||||
for (int z = -searchRadius; z <= searchRadius; z++) {
|
this.arrived = true;
|
||||||
for (int y = -2; y <= 2; y++) {
|
ellie.getNavigation().stop();
|
||||||
mutable.set(entityPos.getX() + x, entityPos.getY() + y, entityPos.getZ() + z);
|
|
||||||
if (isFreeBed(mutable)) {
|
if (ellie.occupyBed(footPos)) {
|
||||||
double dist = entityPos.distSqr(mutable);
|
ellie.setBedSleepPos(footPos);
|
||||||
if (dist < nearestDist) {
|
ellie.markSleptTonight();
|
||||||
nearestDist = dist;
|
} else {
|
||||||
nearest = mutable.immutable();
|
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;
|
return best;
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import net.minecraft.world.InteractionHand;
|
import net.minecraft.world.InteractionHand;
|
||||||
import net.minecraft.world.InteractionResult;
|
import net.minecraft.world.InteractionResult;
|
||||||
|
import net.minecraft.world.phys.Vec3;
|
||||||
import net.minecraft.world.damagesource.DamageSource;
|
import net.minecraft.world.damagesource.DamageSource;
|
||||||
import net.minecraft.world.entity.*;
|
import net.minecraft.world.entity.*;
|
||||||
import net.minecraft.world.item.ItemStack;
|
import net.minecraft.world.item.ItemStack;
|
||||||
@@ -91,15 +92,28 @@ public class EllieEntity extends Animal implements GeoEntity {
|
|||||||
private float prevYRot;
|
private float prevYRot;
|
||||||
private float yawDeltaAccumulator;
|
private float yawDeltaAccumulator;
|
||||||
private BlockPos bedPos;
|
private BlockPos bedPos;
|
||||||
|
private Direction lastBedDir;
|
||||||
private double lastX, lastZ;
|
private double lastX, lastZ;
|
||||||
private int crouchTimer;
|
private int crouchTimer;
|
||||||
private int debugAnimTimer;
|
private int debugAnimTimer;
|
||||||
private boolean needsSleepRestore;
|
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<? extends Animal> type, Level level) {
|
public EllieEntity(EntityType<? extends Animal> type, Level level) {
|
||||||
super(type, level);
|
super(type, level);
|
||||||
((GroundPathNavigation) this.getNavigation()).setCanOpenDoors(true);
|
((GroundPathNavigation) this.getNavigation()).setCanOpenDoors(true);
|
||||||
this.setPersistenceRequired();
|
this.setPersistenceRequired();
|
||||||
|
this.homePos = this.blockPosition();
|
||||||
lastX = getX();
|
lastX = getX();
|
||||||
lastZ = getZ();
|
lastZ = getZ();
|
||||||
prevYRot = getYRot();
|
prevYRot = getYRot();
|
||||||
@@ -138,11 +152,12 @@ public class EllieEntity extends Animal implements GeoEntity {
|
|||||||
@Override
|
@Override
|
||||||
protected void registerGoals() {
|
protected void registerGoals() {
|
||||||
BasicAI.addBasicGoals(this, this.goalSelector, 4);
|
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 EllieDoorInteractGoal(this));
|
||||||
this.goalSelector.addGoal(1, new IdleAnimationGoal(this));
|
this.goalSelector.addGoal(1, new IdleAnimationGoal(this));
|
||||||
this.goalSelector.addGoal(2, new ShelterGoal(this));
|
this.goalSelector.addGoal(2, new ShelterGoal(this));
|
||||||
this.goalSelector.addGoal(3, new me.sashegdev.fabled_hearts.ai.FollowPlayerGoal(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
|
@Override
|
||||||
@@ -183,10 +198,16 @@ public class EllieEntity extends Animal implements GeoEntity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (wakeUpCooldown > 0) wakeUpCooldown--;
|
||||||
|
|
||||||
|
tickNightlySleepTracking();
|
||||||
|
tickHomeFollowsOwnerSpawn();
|
||||||
|
tickHomeBounds();
|
||||||
|
|
||||||
boolean isNight = level().isNight();
|
boolean isNight = level().isNight();
|
||||||
boolean raining = level().isRainingAt(blockPosition());
|
boolean raining = level().isRainingAt(blockPosition());
|
||||||
boolean lowCeiling = checkLowCeiling();
|
boolean lowCeiling = checkLowCeiling();
|
||||||
boolean tired = ticksWithoutSleep > 48000;
|
boolean tired = missedNights >= SLEEPY_MISSED_NIGHTS_THRESHOLD;
|
||||||
boolean sleeping = entityData.get(DATA_SLEEPING);
|
boolean sleeping = entityData.get(DATA_SLEEPING);
|
||||||
boolean falling = !onGround() && getDeltaMovement().y < -0.1;
|
boolean falling = !onGround() && getDeltaMovement().y < -0.1;
|
||||||
boolean moving = isMoving();
|
boolean moving = isMoving();
|
||||||
@@ -213,6 +234,28 @@ public class EllieEntity extends Animal implements GeoEntity {
|
|||||||
if (sleeping) {
|
if (sleeping) {
|
||||||
setPose(Pose.SLEEPING);
|
setPose(Pose.SLEEPING);
|
||||||
crouchTimer = 0;
|
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) {
|
} else if (lowCeiling) {
|
||||||
if (onGround() || wasCrouching) {
|
if (onGround() || wasCrouching) {
|
||||||
setPose(Pose.CROUCHING);
|
setPose(Pose.CROUCHING);
|
||||||
@@ -232,8 +275,8 @@ public class EllieEntity extends Animal implements GeoEntity {
|
|||||||
for (Player player : level().players()) {
|
for (Player player : level().players()) {
|
||||||
if (player.distanceToSqr(this) < 64 * 64) {
|
if (player.distanceToSqr(this) < 64 * 64) {
|
||||||
ModNetworking.CHANNEL.send(
|
ModNetworking.CHANNEL.send(
|
||||||
PacketDistributor.PLAYER.with(() -> (ServerPlayer) player),
|
PacketDistributor.PLAYER.with(() -> (ServerPlayer) player),
|
||||||
new DebugSyncPacket(this.getId(), wps));
|
new DebugSyncPacket(this.getId(), wps, getDebugBrainString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -287,9 +330,10 @@ public class EllieEntity extends Animal implements GeoEntity {
|
|||||||
.map(g -> g.getGoal().getClass().getSimpleName())
|
.map(g -> g.getGoal().getClass().getSimpleName())
|
||||||
.orElse("none");
|
.orElse("none");
|
||||||
sb.append("Goal:").append(goalName).append("|");
|
sb.append("Goal:").append(goalName).append("|");
|
||||||
|
sb.append("Home:").append(homePos != null ? homePos : "-").append("|");
|
||||||
sb.append("Bed:").append(bedPos != null ? bedPos : "-").append("|");
|
sb.append("Bed:").append(bedPos != null ? bedPos : "-").append("|");
|
||||||
sb.append("Sleep:").append(entityData.get(DATA_SLEEPING)).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("Move:").append(entityData.get(DATA_MOVING)).append("|");
|
||||||
sb.append("Pose:").append(getPose()).append("|");
|
sb.append("Pose:").append(getPose()).append("|");
|
||||||
sb.append("LowCeil:").append(entityData.get(DATA_UNDER_LOW_CEILING)).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());
|
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() {
|
public void toggleDebug() {
|
||||||
entityData.set(DATA_DEBUG, !entityData.get(DATA_DEBUG));
|
entityData.set(DATA_DEBUG, !entityData.get(DATA_DEBUG));
|
||||||
if (!entityData.get(DATA_DEBUG)) {
|
if (!entityData.get(DATA_DEBUG)) {
|
||||||
@@ -344,7 +482,7 @@ public class EllieEntity extends Animal implements GeoEntity {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public EntityDimensions getDimensions(Pose pose) {
|
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);
|
if (pose == Pose.CROUCHING) return EntityDimensions.fixed(0.6f, 1.0f);
|
||||||
return super.getDimensions(pose);
|
return super.getDimensions(pose);
|
||||||
}
|
}
|
||||||
@@ -418,27 +556,80 @@ public class EllieEntity extends Animal implements GeoEntity {
|
|||||||
if (!(state.getBlock() instanceof BedBlock)) return;
|
if (!(state.getBlock() instanceof BedBlock)) return;
|
||||||
|
|
||||||
Direction facing = state.getValue(BedBlock.FACING);
|
Direction facing = state.getValue(BedBlock.FACING);
|
||||||
BlockPos headPos = state.getValue(BedBlock.PART) == BedPart.HEAD
|
BlockPos footPos = state.getValue(BedBlock.PART) == BedPart.HEAD
|
||||||
? pos
|
? pos.relative(facing.getOpposite())
|
||||||
: pos.relative(facing);
|
: pos;
|
||||||
|
|
||||||
setPose(Pose.SLEEPING);
|
setPose(Pose.SLEEPING);
|
||||||
setPos(headPos.getX() + 0.5, pos.getY() + 0.6875, headPos.getZ() + 0.5);
|
setPos(footPos.getX() + 0.5, footPos.getY(), footPos.getZ() + 0.5);
|
||||||
setSleepingPos(headPos);
|
float yaw = facing.toYRot();
|
||||||
float yRot = facing.toYRot();
|
setYRot(yaw);
|
||||||
setYRot(yRot);
|
yRotO = yaw;
|
||||||
yRotO = yRot;
|
yBodyRot = yaw;
|
||||||
setYHeadRot(yRot);
|
yBodyRotO = yaw;
|
||||||
|
yHeadRot = yaw;
|
||||||
|
yHeadRotO = yaw;
|
||||||
|
setXRot(0);
|
||||||
|
xRotO = 0;
|
||||||
|
lastBedDir = facing;
|
||||||
setSleeping(true);
|
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() {
|
public void wakeUp() {
|
||||||
|
BlockPos foot = bedPos;
|
||||||
|
Direction dir = lastBedDir;
|
||||||
releaseBed();
|
releaseBed();
|
||||||
forgetBed();
|
forgetBed();
|
||||||
setSleeping(false);
|
setSleeping(false);
|
||||||
setPose(Pose.STANDING);
|
setPose(Pose.STANDING);
|
||||||
navigation.stop();
|
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));
|
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
|
@Override
|
||||||
@@ -561,9 +752,6 @@ public class EllieEntity extends Animal implements GeoEntity {
|
|||||||
return InteractionResult.SUCCESS;
|
return InteractionResult.SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entityData.get(DATA_TIRED)) {
|
|
||||||
return InteractionResult.SUCCESS;
|
|
||||||
}
|
|
||||||
if (this.level().isClientSide) {
|
if (this.level().isClientSide) {
|
||||||
ModNetworking.CHANNEL.sendToServer(new OpenDialogPacket(this.getId()));
|
ModNetworking.CHANNEL.sendToServer(new OpenDialogPacket(this.getId()));
|
||||||
return InteractionResult.SUCCESS;
|
return InteractionResult.SUCCESS;
|
||||||
@@ -586,6 +774,9 @@ public class EllieEntity extends Animal implements GeoEntity {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (isSleeping() && !level().isClientSide) {
|
||||||
|
wakeUp();
|
||||||
|
}
|
||||||
return super.hurt(source, amount);
|
return super.hurt(source, amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -595,6 +786,16 @@ public class EllieEntity extends Animal implements GeoEntity {
|
|||||||
tag.putInt("TicksWithoutSleep", ticksWithoutSleep);
|
tag.putInt("TicksWithoutSleep", ticksWithoutSleep);
|
||||||
tag.putFloat("Relationship", relationshipPoints);
|
tag.putFloat("Relationship", relationshipPoints);
|
||||||
if (bedPos != null) tag.putLong("BedPos", bedPos.asLong());
|
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
|
@Override
|
||||||
@@ -608,6 +809,16 @@ public class EllieEntity extends Animal implements GeoEntity {
|
|||||||
needsSleepRestore = true;
|
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
|
@Override
|
||||||
@@ -627,6 +838,7 @@ public class EllieEntity extends Animal implements GeoEntity {
|
|||||||
this.relationshipPoints = Math.min(100, Math.max(0, this.relationshipPoints + amount));
|
this.relationshipPoints = Math.min(100, Math.max(0, this.relationshipPoints + amount));
|
||||||
}
|
}
|
||||||
public int getTicksWithoutSleep() { return ticksWithoutSleep; }
|
public int getTicksWithoutSleep() { return ticksWithoutSleep; }
|
||||||
|
public int getWakeUpCooldown() { return wakeUpCooldown; }
|
||||||
public int getFollowTargetId() { return entityData.get(DATA_FOLLOW_TARGET); }
|
public int getFollowTargetId() { return entityData.get(DATA_FOLLOW_TARGET); }
|
||||||
public boolean isFollowingPlayer() { return entityData.get(DATA_FOLLOW_TARGET) > 0; }
|
public boolean isFollowingPlayer() { return entityData.get(DATA_FOLLOW_TARGET) > 0; }
|
||||||
public int getDebugAnim() { return entityData.get(DATA_DEBUG_ANIM); }
|
public int getDebugAnim() { return entityData.get(DATA_DEBUG_ANIM); }
|
||||||
|
|||||||
@@ -2,13 +2,11 @@ package me.sashegdev.fabled_hearts.entity.ellie;
|
|||||||
|
|
||||||
import com.mojang.blaze3d.vertex.PoseStack;
|
import com.mojang.blaze3d.vertex.PoseStack;
|
||||||
import com.mojang.blaze3d.vertex.VertexConsumer;
|
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||||
import com.mojang.math.Axis;
|
|
||||||
import net.minecraft.client.Minecraft;
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.client.gui.Font;
|
import net.minecraft.client.gui.Font;
|
||||||
import net.minecraft.client.renderer.MultiBufferSource;
|
import net.minecraft.client.renderer.MultiBufferSource;
|
||||||
import net.minecraft.client.renderer.RenderType;
|
import net.minecraft.client.renderer.RenderType;
|
||||||
import net.minecraft.client.renderer.entity.EntityRendererProvider;
|
import net.minecraft.client.renderer.entity.EntityRendererProvider;
|
||||||
import net.minecraft.core.Direction;
|
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
import net.minecraft.world.entity.Pose;
|
import net.minecraft.world.entity.Pose;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
@@ -21,25 +19,6 @@ public class EllieRenderer extends GeoEntityRenderer<EllieEntity> {
|
|||||||
this.shadowRadius = 0.5f;
|
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
|
@Override
|
||||||
public RenderType getRenderType(EllieEntity animatable, ResourceLocation texture,
|
public RenderType getRenderType(EllieEntity animatable, ResourceLocation texture,
|
||||||
@Nullable MultiBufferSource bufferSource, float partialTick) {
|
@Nullable MultiBufferSource bufferSource, float partialTick) {
|
||||||
@@ -50,7 +29,6 @@ public class EllieRenderer extends GeoEntityRenderer<EllieEntity> {
|
|||||||
public void render(EllieEntity entity, float entityYaw, float partialTick, PoseStack poseStack,
|
public void render(EllieEntity entity, float entityYaw, float partialTick, PoseStack poseStack,
|
||||||
MultiBufferSource bufferSource, int packedLight) {
|
MultiBufferSource bufferSource, int packedLight) {
|
||||||
super.render(entity, entityYaw, partialTick, poseStack, bufferSource, packedLight);
|
super.render(entity, entityYaw, partialTick, poseStack, bufferSource, packedLight);
|
||||||
|
|
||||||
if (entity.isDebugMode()) {
|
if (entity.isDebugMode()) {
|
||||||
renderBrainText(entity, poseStack, bufferSource, packedLight);
|
renderBrainText(entity, poseStack, bufferSource, packedLight);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,9 @@ public class EllieSpawnItem extends Item {
|
|||||||
var entity = ModEntities.ELLIE.get().create(serverLevel);
|
var entity = ModEntities.ELLIE.get().create(serverLevel);
|
||||||
if (entity != null) {
|
if (entity != null) {
|
||||||
entity.setPos(player.getX(), player.getY(), player.getZ());
|
entity.setPos(player.getX(), player.getY(), player.getZ());
|
||||||
|
entity.setHomePos(entity.blockPosition());
|
||||||
|
entity.setHomeRadius(20);
|
||||||
|
entity.setOwnerUuid(player.getUUID());
|
||||||
serverLevel.addFreshEntity(entity);
|
serverLevel.addFreshEntity(entity);
|
||||||
data.setEllieUUID(entity.getUUID());
|
data.setEllieUUID(entity.getUUID());
|
||||||
player.sendSystemMessage(Component.literal("§aEllie появилась!"));
|
player.sendSystemMessage(Component.literal("§aEllie появилась!"));
|
||||||
|
|||||||
@@ -13897,6 +13897,14 @@
|
|||||||
"loop": true,
|
"loop": true,
|
||||||
"animation_length": 2.25,
|
"animation_length": 2.25,
|
||||||
"bones": {
|
"bones": {
|
||||||
|
"root": {
|
||||||
|
"rotation": {
|
||||||
|
"vector": [90, 0, -180]
|
||||||
|
},
|
||||||
|
"position": {
|
||||||
|
"vector": [0, 9.1, 9]
|
||||||
|
}
|
||||||
|
},
|
||||||
"torso": {
|
"torso": {
|
||||||
"rotation": {
|
"rotation": {
|
||||||
"0.0833": {
|
"0.0833": {
|
||||||
|
|||||||
Reference in New Issue
Block a user