Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| beaaef421c | |||
| da7e65d32e | |||
| e6f0c841fe | |||
| a93660d5e5 | |||
| ff6046e89c | |||
| a43adb0de2 | |||
| dcd402eebf |
@@ -0,0 +1,39 @@
|
|||||||
|
--- a/net/minecraft/client/renderer/entity/LivingEntityRenderer.java
|
||||||
|
+++ b/net/minecraft/client/renderer/entity/LivingEntityRenderer.java
|
||||||
|
@@ -49,14 +_,17 @@
|
||||||
|
}
|
||||||
|
|
||||||
|
public void m_7392_(T p_115308_, float p_115309_, float p_115310_, PoseStack p_115311_, MultiBufferSource p_115312_, int p_115313_) {
|
||||||
|
+ if (net.minecraftforge.common.MinecraftForge.EVENT_BUS.post(new net.minecraftforge.client.event.RenderLivingEvent.Pre<T, M>(p_115308_, this, p_115310_, p_115311_, p_115312_, p_115313_))) return;
|
||||||
|
p_115311_.m_85836_();
|
||||||
|
this.f_115290_.f_102608_ = this.m_115342_(p_115308_, p_115310_);
|
||||||
|
- this.f_115290_.f_102609_ = p_115308_.m_20159_();
|
||||||
|
+
|
||||||
|
+ boolean shouldSit = p_115308_.m_20159_() && (p_115308_.m_20202_() != null && p_115308_.m_20202_().shouldRiderSit());
|
||||||
|
+ this.f_115290_.f_102609_ = shouldSit;
|
||||||
|
this.f_115290_.f_102610_ = p_115308_.m_6162_();
|
||||||
|
float f = Mth.m_14189_(p_115310_, p_115308_.f_20884_, p_115308_.f_20883_);
|
||||||
|
float f1 = Mth.m_14189_(p_115310_, p_115308_.f_20886_, p_115308_.f_20885_);
|
||||||
|
float f2 = f1 - f;
|
||||||
|
- if (p_115308_.m_20159_() && p_115308_.m_20202_() instanceof LivingEntity) {
|
||||||
|
+ if (shouldSit && p_115308_.m_20202_() instanceof LivingEntity) {
|
||||||
|
LivingEntity livingentity = (LivingEntity)p_115308_.m_20202_();
|
||||||
|
f = Mth.m_14189_(p_115310_, livingentity.f_20884_, livingentity.f_20883_);
|
||||||
|
f2 = f1 - f;
|
||||||
|
@@ -98,7 +_,7 @@
|
||||||
|
p_115311_.m_252880_(0.0F, -1.501F, 0.0F);
|
||||||
|
float f8 = 0.0F;
|
||||||
|
float f5 = 0.0F;
|
||||||
|
- if (!p_115308_.m_20159_() && p_115308_.m_6084_()) {
|
||||||
|
+ if (!shouldSit && p_115308_.m_6084_()) {
|
||||||
|
f8 = p_115308_.f_267362_.m_267711_(p_115310_);
|
||||||
|
f5 = p_115308_.f_267362_.m_267590_(p_115310_);
|
||||||
|
if (p_115308_.m_6162_()) {
|
||||||
|
@@ -131,6 +_,7 @@
|
||||||
|
|
||||||
|
p_115311_.m_85849_();
|
||||||
|
super.m_7392_(p_115308_, p_115309_, p_115310_, p_115311_, p_115312_, p_115313_);
|
||||||
|
+ net.minecraftforge.common.MinecraftForge.EVENT_BUS.post(new net.minecraftforge.client.event.RenderLivingEvent.Post<T, M>(p_115308_, this, p_115310_, p_115311_, p_115312_, p_115313_));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
--- a/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java
|
||||||
|
+++ b/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java
|
||||||
|
@@ -112,7 +_,7 @@
|
||||||
|
BlockPathTypes blockpathtypes = this.m_77567_(this.f_77313_, p_77641_.f_77271_, p_77641_.f_77272_ + 1, p_77641_.f_77273_);
|
||||||
|
BlockPathTypes blockpathtypes1 = this.m_77567_(this.f_77313_, p_77641_.f_77271_, p_77641_.f_77272_, p_77641_.f_77273_);
|
||||||
|
if (this.f_77313_.m_21439_(blockpathtypes) >= 0.0F && blockpathtypes1 != BlockPathTypes.STICKY_HONEY) {
|
||||||
|
- j = Mth.m_14143_(Math.max(1.0F, this.f_77313_.m_274421_()));
|
||||||
|
+ j = Mth.m_14143_(Math.max(1.0F, this.f_77313_.getStepHeight()));
|
||||||
|
}
|
||||||
|
|
||||||
|
double d0 = this.m_142213_(new BlockPos(p_77641_.f_77271_, p_77641_.f_77272_, p_77641_.f_77273_));
|
||||||
|
@@ -306,7 +_,7 @@
|
||||||
|
}
|
||||||
|
|
||||||
|
private double m_255203_() {
|
||||||
|
- return Math.max(1.125D, (double)this.f_77313_.m_274421_());
|
||||||
|
+ return Math.max(1.125D, (double)this.f_77313_.getStepHeight());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Node m_230619_(int p_230620_, int p_230621_, int p_230622_, BlockPathTypes p_230623_, float p_230624_) {
|
||||||
|
@@ -453,6 +_,11 @@
|
||||||
|
if (l != 0 || j1 != 0) {
|
||||||
|
p_77609_.m_122178_(i + l, j + i1, k + j1);
|
||||||
|
BlockState blockstate = p_77608_.m_8055_(p_77609_);
|
||||||
|
+ BlockPathTypes blockPathType = blockstate.getAdjacentBlockPathType(p_77608_, p_77609_, null, p_77610_);
|
||||||
|
+ if (blockPathType != null) return blockPathType;
|
||||||
|
+ FluidState fluidState = blockstate.m_60819_();
|
||||||
|
+ BlockPathTypes fluidPathType = fluidState.getAdjacentBlockPathType(p_77608_, p_77609_, null, p_77610_);
|
||||||
|
+ if (fluidPathType != null) return fluidPathType;
|
||||||
|
if (blockstate.m_60713_(Blocks.f_50128_) || blockstate.m_60713_(Blocks.f_50685_)) {
|
||||||
|
return BlockPathTypes.DANGER_OTHER;
|
||||||
|
}
|
||||||
|
@@ -478,6 +_,8 @@
|
||||||
|
|
||||||
|
protected static BlockPathTypes m_77643_(BlockGetter p_77644_, BlockPos p_77645_) {
|
||||||
|
BlockState blockstate = p_77644_.m_8055_(p_77645_);
|
||||||
|
+ BlockPathTypes type = blockstate.getBlockPathType(p_77644_, p_77645_, null);
|
||||||
|
+ if (type != null) return type;
|
||||||
|
Block block = blockstate.m_60734_();
|
||||||
|
if (blockstate.m_60795_()) {
|
||||||
|
return BlockPathTypes.OPEN;
|
||||||
|
@@ -491,6 +_,8 @@
|
||||||
|
return BlockPathTypes.COCOA;
|
||||||
|
} else if (!blockstate.m_60713_(Blocks.f_50070_) && !blockstate.m_60713_(Blocks.f_152588_)) {
|
||||||
|
FluidState fluidstate = p_77644_.m_6425_(p_77645_);
|
||||||
|
+ BlockPathTypes nonLoggableFluidPathType = fluidstate.getBlockPathType(p_77644_, p_77645_, null, false);
|
||||||
|
+ if (nonLoggableFluidPathType != null) return nonLoggableFluidPathType;
|
||||||
|
if (fluidstate.m_205070_(FluidTags.f_13132_)) {
|
||||||
|
return BlockPathTypes.LAVA;
|
||||||
|
} else if (m_77622_(blockstate)) {
|
||||||
|
@@ -510,6 +_,8 @@
|
||||||
|
if (!blockstate.m_60647_(p_77644_, p_77645_, PathComputationType.LAND)) {
|
||||||
|
return BlockPathTypes.BLOCKED;
|
||||||
|
} else {
|
||||||
|
+ BlockPathTypes loggableFluidPathType = fluidstate.getBlockPathType(p_77644_, p_77645_, null, true);
|
||||||
|
+ if (loggableFluidPathType != null) return loggableFluidPathType;
|
||||||
|
return fluidstate.m_205070_(FluidTags.f_13131_) ? BlockPathTypes.WATER : BlockPathTypes.OPEN;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package me.sashegdev.fabled_hearts;
|
package me.sashegdev.fabled_hearts;
|
||||||
|
|
||||||
import com.mojang.logging.LogUtils;
|
import com.mojang.logging.LogUtils;
|
||||||
|
import me.sashegdev.fabled_hearts.client.DebugClientHandler;
|
||||||
import me.sashegdev.fabled_hearts.dialog.DialogLoader;
|
import me.sashegdev.fabled_hearts.dialog.DialogLoader;
|
||||||
import me.sashegdev.fabled_hearts.dialog.DialogManager;
|
import me.sashegdev.fabled_hearts.dialog.DialogManager;
|
||||||
import me.sashegdev.fabled_hearts.entity.ellie.EllieEntity;
|
import me.sashegdev.fabled_hearts.entity.ellie.EllieEntity;
|
||||||
@@ -10,6 +11,7 @@ import me.sashegdev.fabled_hearts.registry.ModEntities;
|
|||||||
import me.sashegdev.fabled_hearts.registry.ModItems;
|
import me.sashegdev.fabled_hearts.registry.ModItems;
|
||||||
import net.minecraftforge.api.distmarker.Dist;
|
import net.minecraftforge.api.distmarker.Dist;
|
||||||
import net.minecraftforge.client.event.EntityRenderersEvent;
|
import net.minecraftforge.client.event.EntityRenderersEvent;
|
||||||
|
import net.minecraftforge.client.event.RegisterKeyMappingsEvent;
|
||||||
import net.minecraftforge.common.MinecraftForge;
|
import net.minecraftforge.common.MinecraftForge;
|
||||||
import net.minecraftforge.event.AddReloadListenerEvent;
|
import net.minecraftforge.event.AddReloadListenerEvent;
|
||||||
import net.minecraftforge.event.entity.EntityAttributeCreationEvent;
|
import net.minecraftforge.event.entity.EntityAttributeCreationEvent;
|
||||||
@@ -17,6 +19,7 @@ import net.minecraftforge.event.level.BlockEvent;
|
|||||||
import net.minecraftforge.eventbus.api.IEventBus;
|
import net.minecraftforge.eventbus.api.IEventBus;
|
||||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||||
import net.minecraftforge.fml.common.Mod;
|
import net.minecraftforge.fml.common.Mod;
|
||||||
|
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
|
||||||
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
|
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
|
||||||
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
|
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@@ -62,6 +65,16 @@ public class Main {
|
|||||||
public static void registerRenderers(EntityRenderersEvent.RegisterRenderers event) {
|
public static void registerRenderers(EntityRenderersEvent.RegisterRenderers event) {
|
||||||
event.registerEntityRenderer(ModEntities.ELLIE.get(), EllieRenderer::new);
|
event.registerEntityRenderer(ModEntities.ELLIE.get(), EllieRenderer::new);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SubscribeEvent
|
||||||
|
public static void onClientSetup(FMLClientSetupEvent event) {
|
||||||
|
DebugClientHandler.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SubscribeEvent
|
||||||
|
public static void registerKeyMappings(RegisterKeyMappingsEvent event) {
|
||||||
|
DebugClientHandler.registerKeyMapping(event);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class ForgeHandler {
|
public static class ForgeHandler {
|
||||||
@@ -76,10 +89,10 @@ public class Main {
|
|||||||
var pos = event.getPos();
|
var pos = event.getPos();
|
||||||
event.getLevel().getEntitiesOfClass(
|
event.getLevel().getEntitiesOfClass(
|
||||||
EllieEntity.class,
|
EllieEntity.class,
|
||||||
new net.minecraft.world.phys.AABB(pos).inflate(2),
|
new net.minecraft.world.phys.AABB(pos).inflate(3),
|
||||||
e -> e.isSleeping() && pos.equals(e.getBedPos())
|
e -> e.isBedAtOrAdjacent(pos) || (e.getBedPos() != null && e.getBedPos().equals(pos))
|
||||||
).forEach(e -> {
|
).forEach(e -> {
|
||||||
e.wakeUp();
|
if (e.isSleeping()) e.wakeUp();
|
||||||
e.forgetBed();
|
e.forgetBed();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,224 @@
|
|||||||
|
package me.sashegdev.fabled_hearts.ai;
|
||||||
|
|
||||||
|
import me.sashegdev.fabled_hearts.entity.ellie.EllieEntity;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.core.Direction;
|
||||||
|
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.level.block.state.properties.DoorHingeSide;
|
||||||
|
import net.minecraft.world.level.pathfinder.Node;
|
||||||
|
import net.minecraft.world.level.pathfinder.Path;
|
||||||
|
import net.minecraft.world.phys.Vec3;
|
||||||
|
|
||||||
|
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 int STUCK_TICK_LIMIT = 40;
|
||||||
|
|
||||||
|
private final EllieEntity ellie;
|
||||||
|
private BlockPos doorPos;
|
||||||
|
private BlockPos pairedDoorPos;
|
||||||
|
private boolean hasPassed;
|
||||||
|
private int closeTimer;
|
||||||
|
private Vec3 doorStuckPos;
|
||||||
|
private int doorStuckTicks;
|
||||||
|
private boolean lastNudgeWasCw;
|
||||||
|
|
||||||
|
public EllieDoorInteractGoal(EllieEntity ellie) {
|
||||||
|
this.ellie = ellie;
|
||||||
|
this.setFlags(EnumSet.noneOf(Flag.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canUse() {
|
||||||
|
if (ellie.tickCount < 20) return false;
|
||||||
|
return !ellie.isSleeping();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean requiresUpdateEveryTick() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void tick() {
|
||||||
|
if (doorPos != null) {
|
||||||
|
BlockState state = ellie.level().getBlockState(doorPos);
|
||||||
|
if (!(state.getBlock() instanceof DoorBlock)) {
|
||||||
|
clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasPassed) {
|
||||||
|
double distSq = ellie.distanceToSqr(Vec3.atCenterOf(doorPos));
|
||||||
|
if (distSq > CLOSE_DIST_SQ) {
|
||||||
|
hasPassed = true;
|
||||||
|
closeTimer = CLOSE_DELAY;
|
||||||
|
} else {
|
||||||
|
checkDoorStuck();
|
||||||
|
}
|
||||||
|
} else if (--closeTimer <= 0) {
|
||||||
|
closeDoor(doorPos);
|
||||||
|
if (pairedDoorPos != null) {
|
||||||
|
closeDoor(pairedDoorPos);
|
||||||
|
}
|
||||||
|
clear();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
BlockPos door = findDoor();
|
||||||
|
if (door != null) {
|
||||||
|
BlockState state = ellie.level().getBlockState(door);
|
||||||
|
openDoor(door, state);
|
||||||
|
|
||||||
|
doorPos = door;
|
||||||
|
pairedDoorPos = findPairedDoor(door, state);
|
||||||
|
if (pairedDoorPos != null) {
|
||||||
|
BlockState pairedState = ellie.level().getBlockState(pairedDoorPos);
|
||||||
|
if (pairedState.getBlock() instanceof DoorBlock) {
|
||||||
|
if (!pairedState.getValue(DoorBlock.OPEN)) {
|
||||||
|
openDoor(pairedDoorPos, pairedState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hasPassed = false;
|
||||||
|
closeTimer = 0;
|
||||||
|
doorStuckPos = null;
|
||||||
|
doorStuckTicks = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkDoorStuck() {
|
||||||
|
Vec3 current = ellie.position();
|
||||||
|
if (doorStuckPos != null && current.distanceToSqr(doorStuckPos) < 0.005) {
|
||||||
|
doorStuckTicks++;
|
||||||
|
if (doorStuckTicks > STUCK_TICK_LIMIT) {
|
||||||
|
nudgeFromDoor();
|
||||||
|
doorStuckTicks = 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
doorStuckTicks = 0;
|
||||||
|
}
|
||||||
|
doorStuckPos = current;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void nudgeFromDoor() {
|
||||||
|
BlockState state = ellie.level().getBlockState(doorPos);
|
||||||
|
if (!(state.getBlock() instanceof DoorBlock)) return;
|
||||||
|
|
||||||
|
Direction facing = state.getValue(DoorBlock.FACING);
|
||||||
|
Direction nudgeDir = lastNudgeWasCw ? facing.getCounterClockWise() : facing.getClockWise();
|
||||||
|
lastNudgeWasCw = !lastNudgeWasCw;
|
||||||
|
|
||||||
|
double nx = nudgeDir.getStepX() * 0.125;
|
||||||
|
double nz = nudgeDir.getStepZ() * 0.125;
|
||||||
|
ellie.setPos(ellie.getX() + nx, ellie.getY(), ellie.getZ() + nz);
|
||||||
|
|
||||||
|
Vec3 ahead = Vec3.atBottomCenterOf(doorPos.relative(facing));
|
||||||
|
ellie.getNavigation().moveTo(ahead.x, ahead.y, ahead.z, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void closeDoor(BlockPos pos) {
|
||||||
|
BlockState state = ellie.level().getBlockState(pos);
|
||||||
|
if (state.getBlock() instanceof DoorBlock door && state.getValue(DoorBlock.OPEN)) {
|
||||||
|
door.setOpen(null, ellie.level(), state, pos, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private BlockPos findDoor() {
|
||||||
|
Path path = ellie.getNavigation().getPath();
|
||||||
|
if (path != null && !path.isDone()) {
|
||||||
|
BlockPos entityPos = ellie.blockPosition();
|
||||||
|
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;
|
||||||
|
BlockPos door = resolveDoor(checkPos);
|
||||||
|
if (door != null) return door;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BlockPos center = ellie.blockPosition();
|
||||||
|
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)) {
|
||||||
|
if (isDoubleDoor(checkPos)) {
|
||||||
|
return checkPos;
|
||||||
|
}
|
||||||
|
if (singleDoor == null) {
|
||||||
|
singleDoor = checkPos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return singleDoor;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isDoubleDoor(BlockPos pos) {
|
||||||
|
BlockState state = ellie.level().getBlockState(pos);
|
||||||
|
return findPairedDoor(pos, state) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean checkForDoor(BlockPos pos) {
|
||||||
|
BlockState state = ellie.level().getBlockState(pos);
|
||||||
|
return state.getBlock() instanceof DoorBlock && !state.getValue(DoorBlock.OPEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
private BlockPos findPairedDoor(BlockPos pos, BlockState state) {
|
||||||
|
Direction facing = state.getValue(DoorBlock.FACING);
|
||||||
|
DoorHingeSide hinge = state.getValue(DoorBlock.HINGE);
|
||||||
|
for (Direction adj : Direction.Plane.HORIZONTAL) {
|
||||||
|
BlockPos adjPos = pos.relative(adj);
|
||||||
|
BlockState adjState = ellie.level().getBlockState(adjPos);
|
||||||
|
if (adjState.getBlock() instanceof DoorBlock) {
|
||||||
|
if (adjState.getValue(DoorBlock.FACING) == facing
|
||||||
|
&& adjState.getValue(DoorBlock.HINGE) != hinge) {
|
||||||
|
return adjPos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clear() {
|
||||||
|
doorPos = null;
|
||||||
|
pairedDoorPos = null;
|
||||||
|
hasPassed = false;
|
||||||
|
closeTimer = 0;
|
||||||
|
doorStuckPos = null;
|
||||||
|
doorStuckTicks = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stop() {
|
||||||
|
clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package me.sashegdev.fabled_hearts.ai;
|
||||||
|
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.core.Direction;
|
||||||
|
import net.minecraft.world.level.pathfinder.BlockPathTypes;
|
||||||
|
import net.minecraft.world.level.pathfinder.Node;
|
||||||
|
import net.minecraft.world.level.pathfinder.WalkNodeEvaluator;
|
||||||
|
|
||||||
|
public class EllieNodeEvaluator extends WalkNodeEvaluator {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Node findAcceptedNode(int x, int y, int z, int depth, double floorY, Direction direction, BlockPathTypes expectedType) {
|
||||||
|
Node node = super.findAcceptedNode(x, y, z, depth, floorY, direction, expectedType);
|
||||||
|
if (node != null) return node;
|
||||||
|
|
||||||
|
BlockPathTypes head1 = this.getCachedBlockType(this.mob, x, y + 1, z);
|
||||||
|
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 (this.mob.getPathfindingMalus(head2) >= 0.0f) return null;
|
||||||
|
|
||||||
|
BlockPathTypes footType = this.getCachedBlockType(this.mob, x, y, z);
|
||||||
|
float costMalus = this.mob.getPathfindingMalus(footType);
|
||||||
|
if (costMalus < 0.0f) return null;
|
||||||
|
|
||||||
|
double floorLevel = getFloorLevel(new BlockPos(x, y, z));
|
||||||
|
double maxJump = Math.max(1.125, this.mob.getStepHeight());
|
||||||
|
if (floorLevel - floorY > maxJump) return null;
|
||||||
|
|
||||||
|
node = this.getNode(x, y, z);
|
||||||
|
if (node != null) {
|
||||||
|
node.type = footType;
|
||||||
|
node.costMalus = costMalus;
|
||||||
|
}
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
package me.sashegdev.fabled_hearts.ai;
|
||||||
|
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.world.entity.Mob;
|
||||||
|
import net.minecraft.world.entity.ai.navigation.GroundPathNavigation;
|
||||||
|
import net.minecraft.world.level.Level;
|
||||||
|
import net.minecraft.world.level.pathfinder.BlockPathTypes;
|
||||||
|
import net.minecraft.world.level.pathfinder.PathFinder;
|
||||||
|
import net.minecraft.world.phys.Vec3;
|
||||||
|
|
||||||
|
public class ElliePathNavigation extends GroundPathNavigation {
|
||||||
|
|
||||||
|
private Vec3 lastDest;
|
||||||
|
private double lastSpeed;
|
||||||
|
private BlockPos lastStuckCheck;
|
||||||
|
private int stuckTicks;
|
||||||
|
private int repathTimer;
|
||||||
|
|
||||||
|
private static final int REPATH_INTERVAL = 60;
|
||||||
|
private static final int STUCK_THRESHOLD = 40;
|
||||||
|
private static final int CROUCH_STUCK_THRESHOLD = 120;
|
||||||
|
private static final double PROGRESS_MIN_SQ = 0.2 * 0.2;
|
||||||
|
|
||||||
|
public ElliePathNavigation(Mob mob, Level level) {
|
||||||
|
super(mob, level);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected PathFinder createPathFinder(int maxVisitedNodes) {
|
||||||
|
mob.setPathfindingMalus(BlockPathTypes.DOOR_WOOD_CLOSED, 0.0F);
|
||||||
|
mob.setPathfindingMalus(BlockPathTypes.DOOR_IRON_CLOSED, 5.0F);
|
||||||
|
mob.setPathfindingMalus(BlockPathTypes.DANGER_FIRE, -1.0F);
|
||||||
|
mob.setPathfindingMalus(BlockPathTypes.DAMAGE_FIRE, -1.0F);
|
||||||
|
mob.setPathfindingMalus(BlockPathTypes.LAVA, -1.0F);
|
||||||
|
mob.setPathfindingMalus(BlockPathTypes.WATER, 3.0F);
|
||||||
|
mob.setPathfindingMalus(BlockPathTypes.WATER_BORDER, 1.0F);
|
||||||
|
mob.setPathfindingMalus(BlockPathTypes.FENCE, -1.0F);
|
||||||
|
|
||||||
|
EllieNodeEvaluator evaluator = new EllieNodeEvaluator();
|
||||||
|
evaluator.setCanPassDoors(true);
|
||||||
|
evaluator.setCanOpenDoors(true);
|
||||||
|
this.nodeEvaluator = evaluator;
|
||||||
|
return new PathFinder(evaluator, maxVisitedNodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void tick() {
|
||||||
|
if (isInProgress() && lastDest != null) {
|
||||||
|
BlockPos currentPos = mob.blockPosition();
|
||||||
|
|
||||||
|
boolean crouching = mob.getPose() == net.minecraft.world.entity.Pose.CROUCHING;
|
||||||
|
|
||||||
|
if (lastStuckCheck != null) {
|
||||||
|
double movedSq = currentPos.distSqr(lastStuckCheck);
|
||||||
|
if (movedSq < PROGRESS_MIN_SQ) {
|
||||||
|
stuckTicks++;
|
||||||
|
} else {
|
||||||
|
stuckTicks = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lastStuckCheck = currentPos;
|
||||||
|
|
||||||
|
int threshold = crouching ? CROUCH_STUCK_THRESHOLD : STUCK_THRESHOLD;
|
||||||
|
if (stuckTicks > threshold) {
|
||||||
|
moveTo(lastDest.x, lastDest.y, lastDest.z, lastSpeed);
|
||||||
|
stuckTicks = 0;
|
||||||
|
repathTimer = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!crouching && repathTimer++ > REPATH_INTERVAL) {
|
||||||
|
moveTo(lastDest.x, lastDest.y, lastDest.z, lastSpeed);
|
||||||
|
repathTimer = 0;
|
||||||
|
}
|
||||||
|
} else if (isDone() && lastDest != null) {
|
||||||
|
if (mob.distanceToSqr(lastDest) > 4.0) {
|
||||||
|
if (repathTimer++ > REPATH_INTERVAL) {
|
||||||
|
moveTo(lastDest.x, lastDest.y, lastDest.z, lastSpeed);
|
||||||
|
repathTimer = 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
lastDest = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
super.tick();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean moveTo(double x, double y, double z, double speed) {
|
||||||
|
lastDest = new Vec3(x, y, z);
|
||||||
|
lastSpeed = speed;
|
||||||
|
stuckTicks = 0;
|
||||||
|
repathTimer = 0;
|
||||||
|
lastStuckCheck = mob.blockPosition();
|
||||||
|
return super.moveTo(x, y, z, speed);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stop() {
|
||||||
|
super.stop();
|
||||||
|
lastDest = null;
|
||||||
|
stuckTicks = 0;
|
||||||
|
repathTimer = 0;
|
||||||
|
lastStuckCheck = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,7 +4,6 @@ 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.block.BedBlock;
|
import net.minecraft.world.level.block.BedBlock;
|
||||||
import net.minecraft.world.level.block.DoorBlock;
|
|
||||||
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.phys.Vec3;
|
||||||
|
|
||||||
@@ -54,7 +53,11 @@ public class EllieSleepGoal extends Goal {
|
|||||||
public void tick() {
|
public void tick() {
|
||||||
if (bedPos == null) return;
|
if (bedPos == null) return;
|
||||||
|
|
||||||
tryOpenDoor();
|
BlockState currentState = ellie.level().getBlockState(bedPos);
|
||||||
|
if (!(currentState.getBlock() instanceof BedBlock)) {
|
||||||
|
bedPos = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
double distSqr = ellie.distanceToSqr(Vec3.atCenterOf(bedPos));
|
double distSqr = ellie.distanceToSqr(Vec3.atCenterOf(bedPos));
|
||||||
|
|
||||||
@@ -79,19 +82,6 @@ public class EllieSleepGoal extends Goal {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void tryOpenDoor() {
|
|
||||||
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
|
@Override
|
||||||
public void stop() {
|
public void stop() {
|
||||||
if (ellie.isSleeping()) {
|
if (ellie.isSleeping()) {
|
||||||
@@ -104,16 +94,21 @@ public class EllieSleepGoal extends Goal {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean canContinueToUse() {
|
public boolean canContinueToUse() {
|
||||||
if (ellie.isSleeping()) {
|
|
||||||
if (sleepTimer >= MAX_SLEEP_TICKS) return false;
|
|
||||||
if (bedPos != null) {
|
if (bedPos != null) {
|
||||||
BlockState state = ellie.level().getBlockState(bedPos);
|
BlockState state = ellie.level().getBlockState(bedPos);
|
||||||
if (!state.isBed(ellie.level(), bedPos, null)) return false;
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
if (bedPos == null) return false;
|
if (bedPos == null) return false;
|
||||||
if (ellie.hurtTime > 10) return false;
|
if (ellie.hurtTime > 10) return false;
|
||||||
|
if (ellie.level().isDay() && !ellie.isTired()) return false;
|
||||||
if (!ellie.level().isNight() && !ellie.isTired()) return false;
|
if (!ellie.level().isNight() && !ellie.isTired()) return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package me.sashegdev.fabled_hearts.ai;
|
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.world.entity.Entity;
|
||||||
import net.minecraft.world.entity.ai.goal.Goal;
|
import net.minecraft.world.entity.ai.goal.Goal;
|
||||||
import net.minecraft.world.entity.player.Player;
|
import net.minecraft.world.entity.player.Player;
|
||||||
import net.minecraft.world.phys.Vec3;
|
import net.minecraft.world.phys.Vec3;
|
||||||
@@ -13,6 +15,11 @@ public class FollowPlayerGoal extends Goal {
|
|||||||
private int cooldown;
|
private int cooldown;
|
||||||
private static final double FOLLOW_RANGE = 16.0;
|
private static final double FOLLOW_RANGE = 16.0;
|
||||||
private static final double FOLLOW_RANGE_SQR = FOLLOW_RANGE * FOLLOW_RANGE;
|
private static final double FOLLOW_RANGE_SQR = FOLLOW_RANGE * FOLLOW_RANGE;
|
||||||
|
private static final int RECALC_COOLDOWN = 5;
|
||||||
|
private static final int STUCK_THRESHOLD = 40;
|
||||||
|
private BlockPos lastStuckPos;
|
||||||
|
private int stuckTicks;
|
||||||
|
private int stuckCooldown;
|
||||||
|
|
||||||
public FollowPlayerGoal(EllieEntity ellie) {
|
public FollowPlayerGoal(EllieEntity ellie) {
|
||||||
this.ellie = ellie;
|
this.ellie = ellie;
|
||||||
@@ -21,8 +28,20 @@ public class FollowPlayerGoal extends Goal {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean canUse() {
|
public boolean canUse() {
|
||||||
|
if (ellie.tickCount < 20) return false;
|
||||||
if (ellie.isSleeping()) return false;
|
if (ellie.isSleeping()) return false;
|
||||||
if (ellie.hurtTime > 0) return false;
|
if (ellie.hurtTime > 0) return false;
|
||||||
|
|
||||||
|
int followId = ellie.getFollowTargetId();
|
||||||
|
if (followId > 0) {
|
||||||
|
Entity e = ellie.level().getEntity(followId);
|
||||||
|
if (e instanceof Player p && p.isAlive()) {
|
||||||
|
target = p;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
float rel = ellie.getRelationshipPoints();
|
float rel = ellie.getRelationshipPoints();
|
||||||
if (rel < 50) return false;
|
if (rel < 50) return false;
|
||||||
|
|
||||||
@@ -35,6 +54,9 @@ public class FollowPlayerGoal extends Goal {
|
|||||||
@Override
|
@Override
|
||||||
public void start() {
|
public void start() {
|
||||||
cooldown = 0;
|
cooldown = 0;
|
||||||
|
stuckTicks = 0;
|
||||||
|
stuckCooldown = 0;
|
||||||
|
lastStuckPos = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -42,9 +64,12 @@ public class FollowPlayerGoal extends Goal {
|
|||||||
if (target == null) return;
|
if (target == null) return;
|
||||||
|
|
||||||
double distSqr = ellie.distanceToSqr(target);
|
double distSqr = ellie.distanceToSqr(target);
|
||||||
float rel = ellie.getRelationshipPoints();
|
|
||||||
|
|
||||||
double minDist, maxDist;
|
double minDist, maxDist;
|
||||||
|
if (ellie.getFollowTargetId() == target.getId()) {
|
||||||
|
minDist = 2.0; maxDist = 4.0;
|
||||||
|
} else {
|
||||||
|
float rel = ellie.getRelationshipPoints();
|
||||||
if (rel >= 100) {
|
if (rel >= 100) {
|
||||||
minDist = 1.5; maxDist = 3.0;
|
minDist = 1.5; maxDist = 3.0;
|
||||||
} else if (rel >= 75) {
|
} else if (rel >= 75) {
|
||||||
@@ -52,16 +77,17 @@ public class FollowPlayerGoal extends Goal {
|
|||||||
} else {
|
} else {
|
||||||
minDist = 6.0; maxDist = 10.0;
|
minDist = 6.0; maxDist = 10.0;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (distSqr > maxDist * maxDist) {
|
if (distSqr > maxDist * maxDist) {
|
||||||
if (--cooldown <= 0) {
|
if (--cooldown <= 0) {
|
||||||
cooldown = 10;
|
cooldown = RECALC_COOLDOWN;
|
||||||
ellie.getNavigation().moveTo(target, 0.6);
|
checkStuck();
|
||||||
}
|
}
|
||||||
} else if (distSqr < minDist * minDist) {
|
} else if (distSqr < minDist * minDist) {
|
||||||
ellie.getNavigation().stop();
|
ellie.getNavigation().stop();
|
||||||
if (--cooldown <= 0) {
|
if (--cooldown <= 0) {
|
||||||
cooldown = 10;
|
cooldown = RECALC_COOLDOWN;
|
||||||
Vec3 away = new Vec3(
|
Vec3 away = new Vec3(
|
||||||
ellie.getX() - target.getX(),
|
ellie.getX() - target.getX(),
|
||||||
0,
|
0,
|
||||||
@@ -77,15 +103,47 @@ public class FollowPlayerGoal extends Goal {
|
|||||||
} else {
|
} else {
|
||||||
ellie.getNavigation().stop();
|
ellie.getNavigation().stop();
|
||||||
cooldown = 0;
|
cooldown = 0;
|
||||||
|
stuckTicks = 0;
|
||||||
|
lastStuckPos = null;
|
||||||
ellie.getLookControl().setLookAt(target, 30, 30);
|
ellie.getLookControl().setLookAt(target, 30, 30);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void checkStuck() {
|
||||||
|
if (stuckCooldown > 0) {
|
||||||
|
stuckCooldown--;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean found = ellie.getNavigation().moveTo(target, 0.6);
|
||||||
|
if (!found) {
|
||||||
|
stuckCooldown = 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ellie.getNavigation().isInProgress()) {
|
||||||
|
BlockPos currentPos = ellie.blockPosition();
|
||||||
|
if (currentPos.equals(lastStuckPos)) {
|
||||||
|
stuckTicks++;
|
||||||
|
if (stuckTicks > STUCK_THRESHOLD) {
|
||||||
|
ellie.getNavigation().stop();
|
||||||
|
stuckTicks = 0;
|
||||||
|
stuckCooldown = 20;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
stuckTicks = 0;
|
||||||
|
}
|
||||||
|
lastStuckPos = currentPos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean canContinueToUse() {
|
public boolean canContinueToUse() {
|
||||||
if (target == null || !target.isAlive()) return false;
|
if (target == null || !target.isAlive()) return false;
|
||||||
if (ellie.isSleeping()) return false;
|
if (ellie.isSleeping()) return false;
|
||||||
if (ellie.hurtTime > 0) return false;
|
if (ellie.hurtTime > 0) return false;
|
||||||
|
if (ellie.getFollowTargetId() == target.getId()) {
|
||||||
|
return ellie.isFollowingPlayer();
|
||||||
|
}
|
||||||
double distSqr = ellie.distanceToSqr(target);
|
double distSqr = ellie.distanceToSqr(target);
|
||||||
return distSqr <= FOLLOW_RANGE_SQR;
|
return distSqr <= FOLLOW_RANGE_SQR;
|
||||||
}
|
}
|
||||||
@@ -94,5 +152,8 @@ public class FollowPlayerGoal extends Goal {
|
|||||||
public void stop() {
|
public void stop() {
|
||||||
target = null;
|
target = null;
|
||||||
ellie.getNavigation().stop();
|
ellie.getNavigation().stop();
|
||||||
|
stuckTicks = 0;
|
||||||
|
stuckCooldown = 0;
|
||||||
|
lastStuckPos = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,123 @@
|
|||||||
|
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.phys.Vec3;
|
||||||
|
|
||||||
|
import java.util.EnumSet;
|
||||||
|
|
||||||
|
public class ShelterGoal extends Goal {
|
||||||
|
private final EllieEntity ellie;
|
||||||
|
private BlockPos shelterPos;
|
||||||
|
private static final int SEARCH_RADIUS = 12;
|
||||||
|
private static final double REACH_DIST_SQR = 0.75 * 0.75;
|
||||||
|
|
||||||
|
public ShelterGoal(EllieEntity ellie) {
|
||||||
|
this.ellie = ellie;
|
||||||
|
this.setFlags(EnumSet.of(Flag.MOVE));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canUse() {
|
||||||
|
if (ellie.tickCount < 20) return false;
|
||||||
|
if (ellie.isSleeping()) return false;
|
||||||
|
if (!ellie.level().isRaining() && !ellie.level().isThundering()) return false;
|
||||||
|
return ellie.level().isRainingAt(ellie.blockPosition().above());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start() {
|
||||||
|
shelterPos = findShelter();
|
||||||
|
if (shelterPos != null) {
|
||||||
|
ellie.getNavigation().moveTo(shelterPos.getX() + 0.5, shelterPos.getY(), shelterPos.getZ() + 0.5, 0.6);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void tick() {
|
||||||
|
if (shelterPos == null) return;
|
||||||
|
|
||||||
|
if (!ellie.level().isRainingAt(ellie.blockPosition().above())) {
|
||||||
|
ellie.getNavigation().stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec3 target = new Vec3(shelterPos.getX() + 0.5, shelterPos.getY(), shelterPos.getZ() + 0.5);
|
||||||
|
|
||||||
|
if (ellie.distanceToSqr(target) < REACH_DIST_SQR && ellie.getNavigation().isDone()) {
|
||||||
|
BlockPos deeper = findDeeperShelter();
|
||||||
|
if (deeper != null) {
|
||||||
|
shelterPos = deeper;
|
||||||
|
target = new Vec3(shelterPos.getX() + 0.5, shelterPos.getY(), shelterPos.getZ() + 0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ellie.getNavigation().isDone()) {
|
||||||
|
ellie.getNavigation().moveTo(target.x, target.y, target.z, 0.6);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canContinueToUse() {
|
||||||
|
if (shelterPos == null) return false;
|
||||||
|
if (ellie.isSleeping()) return false;
|
||||||
|
return ellie.level().isRaining() || ellie.level().isThundering();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stop() {
|
||||||
|
shelterPos = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private BlockPos findShelter() {
|
||||||
|
BlockPos entityPos = ellie.blockPosition();
|
||||||
|
BlockPos.MutableBlockPos mutable = new BlockPos.MutableBlockPos();
|
||||||
|
BlockPos nearest = null;
|
||||||
|
double nearestDist = Double.MAX_VALUE;
|
||||||
|
|
||||||
|
for (int dx = -SEARCH_RADIUS; dx <= SEARCH_RADIUS; dx++) {
|
||||||
|
for (int dz = -SEARCH_RADIUS; dz <= SEARCH_RADIUS; dz++) {
|
||||||
|
for (int dy = 1; dy >= -4; dy--) {
|
||||||
|
mutable.set(entityPos.getX() + dx, entityPos.getY() + dy, entityPos.getZ() + dz);
|
||||||
|
BlockPos standPos = mutable.above();
|
||||||
|
if (ellie.level().isEmptyBlock(standPos)
|
||||||
|
&& !ellie.level().getBlockState(mutable).isAir()
|
||||||
|
&& !ellie.level().isRainingAt(standPos)) {
|
||||||
|
double dist = entityPos.distSqr(standPos);
|
||||||
|
if (dist < nearestDist) {
|
||||||
|
nearestDist = dist;
|
||||||
|
nearest = standPos;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nearest;
|
||||||
|
}
|
||||||
|
|
||||||
|
private BlockPos findDeeperShelter() {
|
||||||
|
BlockPos.MutableBlockPos mutable = new BlockPos.MutableBlockPos();
|
||||||
|
BlockPos best = null;
|
||||||
|
double bestScore = 0;
|
||||||
|
|
||||||
|
for (int dx = -2; dx <= 2; dx++) {
|
||||||
|
for (int dz = -2; dz <= 2; dz++) {
|
||||||
|
if (dx == 0 && dz == 0) continue;
|
||||||
|
mutable.set(shelterPos.getX() + dx, shelterPos.getY(), shelterPos.getZ() + dz);
|
||||||
|
BlockPos standPos = mutable.below();
|
||||||
|
if (ellie.level().isEmptyBlock(mutable)
|
||||||
|
&& !ellie.level().getBlockState(standPos).isAir()
|
||||||
|
&& !ellie.level().isRainingAt(mutable)) {
|
||||||
|
double score = Math.abs(dx) + Math.abs(dz);
|
||||||
|
if (score > bestScore) {
|
||||||
|
bestScore = score;
|
||||||
|
best = mutable.immutable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return best;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
package me.sashegdev.fabled_hearts.client;
|
||||||
|
|
||||||
|
import com.mojang.blaze3d.vertex.PoseStack;
|
||||||
|
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||||
|
import me.sashegdev.fabled_hearts.Main;
|
||||||
|
import me.sashegdev.fabled_hearts.entity.ellie.EllieEntity;
|
||||||
|
import me.sashegdev.fabled_hearts.network.DebugTogglePacket;
|
||||||
|
import me.sashegdev.fabled_hearts.network.ModNetworking;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.client.player.LocalPlayer;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.world.entity.Entity;
|
||||||
|
import net.minecraft.world.phys.AABB;
|
||||||
|
import net.minecraft.world.phys.EntityHitResult;
|
||||||
|
import net.minecraft.world.phys.HitResult;
|
||||||
|
import net.minecraft.world.phys.Vec3;
|
||||||
|
import net.minecraftforge.api.distmarker.Dist;
|
||||||
|
import net.minecraftforge.client.event.InputEvent;
|
||||||
|
import net.minecraftforge.client.event.RenderLevelStageEvent;
|
||||||
|
import net.minecraftforge.client.event.RegisterKeyMappingsEvent;
|
||||||
|
import net.minecraftforge.common.MinecraftForge;
|
||||||
|
import net.minecraftforge.event.TickEvent;
|
||||||
|
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||||
|
import net.minecraftforge.fml.common.Mod;
|
||||||
|
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
|
||||||
|
import org.lwjgl.glfw.GLFW;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
public class DebugClientHandler {
|
||||||
|
public static final DebugClientHandler INSTANCE = new DebugClientHandler();
|
||||||
|
|
||||||
|
private static net.minecraft.client.KeyMapping debugKey;
|
||||||
|
|
||||||
|
private final Map<Integer, DebugData> debugDataMap = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
public static class DebugData {
|
||||||
|
public final List<BlockPos> waypoints;
|
||||||
|
public final long lastUpdate;
|
||||||
|
|
||||||
|
public DebugData(List<BlockPos> waypoints) {
|
||||||
|
this.waypoints = waypoints;
|
||||||
|
this.lastUpdate = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void init() {
|
||||||
|
MinecraftForge.EVENT_BUS.register(INSTANCE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SubscribeEvent
|
||||||
|
public void onClientTick(TickEvent.ClientTickEvent event) {
|
||||||
|
if (event.phase != TickEvent.Phase.END) return;
|
||||||
|
if (debugKey == null) return;
|
||||||
|
|
||||||
|
while (debugKey.consumeClick()) {
|
||||||
|
Minecraft mc = Minecraft.getInstance();
|
||||||
|
LocalPlayer player = mc.player;
|
||||||
|
if (player == null) return;
|
||||||
|
|
||||||
|
EllieEntity target = findTargetEntity(player);
|
||||||
|
if (target != null) {
|
||||||
|
ModNetworking.CHANNEL.sendToServer(new DebugTogglePacket(target.getId()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private EllieEntity findTargetEntity(LocalPlayer player) {
|
||||||
|
Minecraft mc = Minecraft.getInstance();
|
||||||
|
if (mc.hitResult != null && mc.hitResult.getType() == HitResult.Type.ENTITY) {
|
||||||
|
EntityHitResult entityHit = (EntityHitResult) mc.hitResult;
|
||||||
|
if (entityHit.getEntity() instanceof EllieEntity ellie) {
|
||||||
|
return ellie;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
double nearestDist = 25.0;
|
||||||
|
EllieEntity nearest = null;
|
||||||
|
Vec3 eyePos = player.getEyePosition();
|
||||||
|
AABB searchBox = player.getBoundingBox().inflate(8);
|
||||||
|
for (EllieEntity ellie : player.level().getEntitiesOfClass(EllieEntity.class, searchBox)) {
|
||||||
|
double dist = ellie.distanceToSqr(eyePos);
|
||||||
|
if (dist < nearestDist) {
|
||||||
|
nearestDist = dist;
|
||||||
|
nearest = ellie;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nearest;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SubscribeEvent
|
||||||
|
public void onRenderLevelStage(RenderLevelStageEvent event) {
|
||||||
|
if (event.getStage() != RenderLevelStageEvent.Stage.AFTER_LEVEL) return;
|
||||||
|
|
||||||
|
Minecraft mc = Minecraft.getInstance();
|
||||||
|
if (mc.player == null) return;
|
||||||
|
|
||||||
|
PoseStack poseStack = event.getPoseStack();
|
||||||
|
var pose = poseStack.last();
|
||||||
|
VertexConsumer consumer = mc.renderBuffers().bufferSource().getBuffer(
|
||||||
|
net.minecraft.client.renderer.RenderType.LINES);
|
||||||
|
|
||||||
|
for (Map.Entry<Integer, DebugData> entry : debugDataMap.entrySet()) {
|
||||||
|
Entity entity = mc.player.level().getEntity(entry.getKey());
|
||||||
|
if (!(entity instanceof EllieEntity ellie) || !ellie.isDebugMode()) continue;
|
||||||
|
|
||||||
|
DebugData data = entry.getValue();
|
||||||
|
if (data.waypoints.size() < 2) continue;
|
||||||
|
|
||||||
|
Vec3 prev = null;
|
||||||
|
for (BlockPos wp : data.waypoints) {
|
||||||
|
Vec3 current = new Vec3(wp.getX() + 0.5, wp.getY() + 0.2, wp.getZ() + 0.5);
|
||||||
|
if (prev != null) {
|
||||||
|
drawLine(consumer, pose, prev, current, 0x00FF00);
|
||||||
|
}
|
||||||
|
prev = current;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void drawLine(VertexConsumer consumer, PoseStack.Pose pose,
|
||||||
|
Vec3 from, Vec3 to, int color) {
|
||||||
|
float r = ((color >> 16) & 0xFF) / 255f;
|
||||||
|
float g = ((color >> 8) & 0xFF) / 255f;
|
||||||
|
float b = (color & 0xFF) / 255f;
|
||||||
|
|
||||||
|
consumer.vertex(pose.pose(), (float) from.x, (float) from.y, (float) from.z)
|
||||||
|
.color(r, g, b, 1.0f)
|
||||||
|
.normal(pose.normal(), 0, 1, 0)
|
||||||
|
.endVertex();
|
||||||
|
consumer.vertex(pose.pose(), (float) to.x, (float) to.y, (float) to.z)
|
||||||
|
.color(r, g, b, 1.0f)
|
||||||
|
.normal(pose.normal(), 0, 1, 0)
|
||||||
|
.endVertex();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onDebugSync(int entityId, List<BlockPos> waypoints, String brainState) {
|
||||||
|
debugDataMap.put(entityId, new DebugData(waypoints));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeDebug(int entityId) {
|
||||||
|
debugDataMap.remove(entityId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void registerKeyMapping(RegisterKeyMappingsEvent event) {
|
||||||
|
debugKey = new net.minecraft.client.KeyMapping(
|
||||||
|
"key.fabled_hearts.debug",
|
||||||
|
GLFW.GLFW_KEY_F6,
|
||||||
|
"key.categories.fabled_hearts"
|
||||||
|
);
|
||||||
|
event.register(debugKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,11 @@
|
|||||||
package me.sashegdev.fabled_hearts.entity.ellie;
|
package me.sashegdev.fabled_hearts.entity.ellie;
|
||||||
|
|
||||||
import me.sashegdev.fabled_hearts.ai.BasicAI;
|
import me.sashegdev.fabled_hearts.ai.BasicAI;
|
||||||
|
import me.sashegdev.fabled_hearts.ai.EllieDoorInteractGoal;
|
||||||
|
import me.sashegdev.fabled_hearts.ai.ElliePathNavigation;
|
||||||
import me.sashegdev.fabled_hearts.ai.EllieSleepGoal;
|
import me.sashegdev.fabled_hearts.ai.EllieSleepGoal;
|
||||||
import me.sashegdev.fabled_hearts.ai.IdleAnimationGoal;
|
import me.sashegdev.fabled_hearts.ai.IdleAnimationGoal;
|
||||||
|
import me.sashegdev.fabled_hearts.ai.ShelterGoal;
|
||||||
import me.sashegdev.fabled_hearts.network.ModNetworking;
|
import me.sashegdev.fabled_hearts.network.ModNetworking;
|
||||||
import me.sashegdev.fabled_hearts.network.OpenDialogPacket;
|
import me.sashegdev.fabled_hearts.network.OpenDialogPacket;
|
||||||
import net.minecraft.core.BlockPos;
|
import net.minecraft.core.BlockPos;
|
||||||
@@ -11,20 +14,31 @@ import net.minecraft.nbt.CompoundTag;
|
|||||||
import net.minecraft.network.syncher.EntityDataAccessor;
|
import net.minecraft.network.syncher.EntityDataAccessor;
|
||||||
import net.minecraft.network.syncher.EntityDataSerializers;
|
import net.minecraft.network.syncher.EntityDataSerializers;
|
||||||
import net.minecraft.network.syncher.SynchedEntityData;
|
import net.minecraft.network.syncher.SynchedEntityData;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
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.damagesource.DamageSource;
|
||||||
import net.minecraft.world.entity.*;
|
import net.minecraft.world.entity.*;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
import net.minecraft.world.item.Items;
|
||||||
import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
|
import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
|
||||||
import net.minecraft.world.entity.ai.attributes.Attributes;
|
import net.minecraft.world.entity.ai.attributes.Attributes;
|
||||||
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
import net.minecraft.world.entity.ai.navigation.GroundPathNavigation;
|
import net.minecraft.world.entity.ai.navigation.GroundPathNavigation;
|
||||||
|
import net.minecraft.world.entity.ai.navigation.PathNavigation;
|
||||||
import net.minecraft.world.entity.animal.Animal;
|
import net.minecraft.world.entity.animal.Animal;
|
||||||
import net.minecraft.world.entity.player.Player;
|
import net.minecraft.world.entity.player.Player;
|
||||||
import net.minecraft.world.level.Level;
|
import net.minecraft.world.level.Level;
|
||||||
import net.minecraft.world.level.block.BedBlock;
|
import net.minecraft.world.level.block.BedBlock;
|
||||||
import net.minecraft.world.level.block.DoorBlock;
|
|
||||||
import net.minecraft.world.level.block.state.BlockState;
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
import net.minecraft.world.level.block.state.properties.BedPart;
|
import net.minecraft.world.level.block.state.properties.BedPart;
|
||||||
import net.minecraft.server.level.ServerLevel;
|
import net.minecraft.server.level.ServerLevel;
|
||||||
|
import net.minecraft.world.level.pathfinder.Node;
|
||||||
|
import net.minecraft.world.level.pathfinder.Path;
|
||||||
|
import net.minecraftforge.network.PacketDistributor;
|
||||||
|
import me.sashegdev.fabled_hearts.network.DebugSyncPacket;
|
||||||
|
import me.sashegdev.fabled_hearts.network.ModNetworking;
|
||||||
import software.bernie.geckolib.animatable.GeoEntity;
|
import software.bernie.geckolib.animatable.GeoEntity;
|
||||||
import software.bernie.geckolib.core.animatable.instance.AnimatableInstanceCache;
|
import software.bernie.geckolib.core.animatable.instance.AnimatableInstanceCache;
|
||||||
import software.bernie.geckolib.core.animation.AnimatableManager;
|
import software.bernie.geckolib.core.animation.AnimatableManager;
|
||||||
@@ -51,22 +65,49 @@ public class EllieEntity extends Animal implements GeoEntity {
|
|||||||
SynchedEntityData.defineId(EllieEntity.class, EntityDataSerializers.INT);
|
SynchedEntityData.defineId(EllieEntity.class, EntityDataSerializers.INT);
|
||||||
private static final EntityDataAccessor<Boolean> DATA_MOVING =
|
private static final EntityDataAccessor<Boolean> DATA_MOVING =
|
||||||
SynchedEntityData.defineId(EllieEntity.class, EntityDataSerializers.BOOLEAN);
|
SynchedEntityData.defineId(EllieEntity.class, EntityDataSerializers.BOOLEAN);
|
||||||
|
private static final EntityDataAccessor<Boolean> DATA_DEBUG =
|
||||||
|
SynchedEntityData.defineId(EllieEntity.class, EntityDataSerializers.BOOLEAN);
|
||||||
|
private static final EntityDataAccessor<String> DATA_DEBUG_BRAIN =
|
||||||
|
SynchedEntityData.defineId(EllieEntity.class, EntityDataSerializers.STRING);
|
||||||
|
private static final EntityDataAccessor<Integer> DATA_TURNING_TIMER =
|
||||||
|
SynchedEntityData.defineId(EllieEntity.class, EntityDataSerializers.INT);
|
||||||
|
private static final EntityDataAccessor<Integer> DATA_DEBUG_ANIM =
|
||||||
|
SynchedEntityData.defineId(EllieEntity.class, EntityDataSerializers.INT);
|
||||||
|
private static final EntityDataAccessor<Integer> DATA_FOLLOW_TARGET =
|
||||||
|
SynchedEntityData.defineId(EllieEntity.class, EntityDataSerializers.INT);
|
||||||
|
|
||||||
|
private static final List<String> DEBUG_ANIMS = List.of(
|
||||||
|
"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);
|
||||||
|
|
||||||
private int ticksWithoutSleep;
|
private int ticksWithoutSleep;
|
||||||
private float relationshipPoints;
|
private float relationshipPoints;
|
||||||
|
private float prevYRot;
|
||||||
|
private float yawDeltaAccumulator;
|
||||||
private BlockPos bedPos;
|
private BlockPos bedPos;
|
||||||
private double lastX, lastZ;
|
private double lastX, lastZ;
|
||||||
private int moveDetectCooldown;
|
|
||||||
private BlockPos lastOpenedDoor;
|
|
||||||
private int doorCloseTimer;
|
|
||||||
private int crouchTimer;
|
private int crouchTimer;
|
||||||
|
private int debugAnimTimer;
|
||||||
|
private boolean needsSleepRestore;
|
||||||
|
|
||||||
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();
|
||||||
|
lastX = getX();
|
||||||
|
lastZ = getZ();
|
||||||
|
prevYRot = getYRot();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PathNavigation createNavigation(Level level) {
|
||||||
|
return new ElliePathNavigation(this, level);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static AttributeSupplier.Builder createAttributes() {
|
public static AttributeSupplier.Builder createAttributes() {
|
||||||
@@ -87,68 +128,61 @@ public class EllieEntity extends Animal implements GeoEntity {
|
|||||||
this.entityData.define(DATA_EATING, false);
|
this.entityData.define(DATA_EATING, false);
|
||||||
this.entityData.define(DATA_FALL_TICKS, 0);
|
this.entityData.define(DATA_FALL_TICKS, 0);
|
||||||
this.entityData.define(DATA_MOVING, false);
|
this.entityData.define(DATA_MOVING, false);
|
||||||
|
this.entityData.define(DATA_DEBUG, false);
|
||||||
|
this.entityData.define(DATA_DEBUG_BRAIN, "");
|
||||||
|
this.entityData.define(DATA_TURNING_TIMER, 0);
|
||||||
|
this.entityData.define(DATA_DEBUG_ANIM, 0);
|
||||||
|
this.entityData.define(DATA_FOLLOW_TARGET, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void registerGoals() {
|
protected void registerGoals() {
|
||||||
BasicAI.addBasicGoals(this, this.goalSelector, 2);
|
BasicAI.addBasicGoals(this, this.goalSelector, 4);
|
||||||
this.goalSelector.addGoal(1, new EllieSleepGoal(this, 30));
|
this.goalSelector.addGoal(1, new EllieSleepGoal(this, 30));
|
||||||
|
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(3, new me.sashegdev.fabled_hearts.ai.FollowPlayerGoal(this));
|
this.goalSelector.addGoal(3, new me.sashegdev.fabled_hearts.ai.FollowPlayerGoal(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void aiStep() {
|
public void aiStep() {
|
||||||
|
if (!level().isClientSide && !entityData.get(DATA_SLEEPING)) {
|
||||||
|
boolean aheadLow = checkLowCeiling();
|
||||||
|
if (aheadLow) {
|
||||||
|
crouchTimer = Math.max(crouchTimer, 20);
|
||||||
|
if (onGround()) {
|
||||||
|
setPose(Pose.CROUCHING);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
super.aiStep();
|
super.aiStep();
|
||||||
tickMovementDetection();
|
|
||||||
tickDoors();
|
|
||||||
if (!level().isClientSide) {
|
if (!level().isClientSide) {
|
||||||
tickServer();
|
tickServer();
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void tickMovementDetection() {
|
|
||||||
if (moveDetectCooldown-- <= 0) {
|
|
||||||
moveDetectCooldown = 2;
|
|
||||||
double dx = getX() - lastX;
|
|
||||||
double dz = getZ() - lastZ;
|
|
||||||
lastX = getX();
|
lastX = getX();
|
||||||
lastZ = getZ();
|
lastZ = getZ();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void tickDoors() {
|
|
||||||
if (lastOpenedDoor != null) {
|
|
||||||
BlockState state = level().getBlockState(lastOpenedDoor);
|
|
||||||
if (state.getBlock() instanceof DoorBlock && state.getValue(DoorBlock.OPEN)) {
|
|
||||||
double dist = distanceToSqr(lastOpenedDoor.getX() + 0.5, lastOpenedDoor.getY(), lastOpenedDoor.getZ() + 0.5);
|
|
||||||
if (dist > 0.5 * 0.5) {
|
|
||||||
doorCloseTimer++;
|
|
||||||
if (doorCloseTimer > 10) {
|
|
||||||
level().setBlock(lastOpenedDoor, state.setValue(DoorBlock.OPEN, false), 3);
|
|
||||||
lastOpenedDoor = null;
|
|
||||||
doorCloseTimer = 0;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
doorCloseTimer = 0;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
lastOpenedDoor = null;
|
|
||||||
doorCloseTimer = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isMoving() {
|
public boolean isMoving() {
|
||||||
double dx = getX() - lastX;
|
|
||||||
double dz = getZ() - lastZ;
|
|
||||||
boolean posChanged = dx * dx + dz * dz > 1.0e-8;
|
|
||||||
boolean navBusy = navigation.isInProgress() && !navigation.isDone();
|
boolean navBusy = navigation.isInProgress() && !navigation.isDone();
|
||||||
boolean hasVelocity = getDeltaMovement().horizontalDistanceSqr() > 0.001;
|
boolean hasVelocity = getDeltaMovement().horizontalDistanceSqr() > 0.001;
|
||||||
return posChanged || navBusy || hasVelocity;
|
return navBusy || hasVelocity;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void tickServer() {
|
private void tickServer() {
|
||||||
|
if (needsSleepRestore && bedPos != null && entityData.get(DATA_SLEEPING)) {
|
||||||
|
setBedSleepPos(bedPos);
|
||||||
|
needsSleepRestore = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entityData.get(DATA_SLEEPING) && bedPos != null) {
|
||||||
|
BlockState bedState = level().getBlockState(bedPos);
|
||||||
|
if (!(bedState.getBlock() instanceof BedBlock)) {
|
||||||
|
wakeUp();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
boolean isNight = level().isNight();
|
boolean isNight = level().isNight();
|
||||||
boolean raining = level().isRainingAt(blockPosition());
|
boolean raining = level().isRainingAt(blockPosition());
|
||||||
boolean lowCeiling = checkLowCeiling();
|
boolean lowCeiling = checkLowCeiling();
|
||||||
@@ -169,7 +203,10 @@ public class EllieEntity extends Animal implements GeoEntity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
boolean wasCrouching = getPose() == Pose.CROUCHING;
|
boolean wasCrouching = getPose() == Pose.CROUCHING;
|
||||||
if (wasCrouching) {
|
|
||||||
|
if (lowCeiling) {
|
||||||
|
crouchTimer = Math.max(crouchTimer, 20);
|
||||||
|
} else if (wasCrouching) {
|
||||||
crouchTimer = Math.max(0, crouchTimer - 1);
|
crouchTimer = Math.max(0, crouchTimer - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,7 +216,6 @@ public class EllieEntity extends Animal implements GeoEntity {
|
|||||||
} else if (lowCeiling) {
|
} else if (lowCeiling) {
|
||||||
if (onGround() || wasCrouching) {
|
if (onGround() || wasCrouching) {
|
||||||
setPose(Pose.CROUCHING);
|
setPose(Pose.CROUCHING);
|
||||||
crouchTimer = 20;
|
|
||||||
}
|
}
|
||||||
} else if (wasCrouching && crouchTimer > 0) {
|
} else if (wasCrouching && crouchTimer > 0) {
|
||||||
setPose(Pose.CROUCHING);
|
setPose(Pose.CROUCHING);
|
||||||
@@ -189,6 +225,20 @@ public class EllieEntity extends Animal implements GeoEntity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (entityData.get(DATA_DEBUG) && tickCount % 10 == 0) {
|
||||||
|
updateDebugBrainString();
|
||||||
|
if (!level().isClientSide) {
|
||||||
|
List<BlockPos> wps = getPathWaypoints();
|
||||||
|
for (Player player : level().players()) {
|
||||||
|
if (player.distanceToSqr(this) < 64 * 64) {
|
||||||
|
ModNetworking.CHANNEL.send(
|
||||||
|
PacketDistributor.PLAYER.with(() -> (ServerPlayer) player),
|
||||||
|
new DebugSyncPacket(this.getId(), wps));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!sleeping) {
|
if (!sleeping) {
|
||||||
if (isNight) {
|
if (isNight) {
|
||||||
ticksWithoutSleep++;
|
ticksWithoutSleep++;
|
||||||
@@ -196,32 +246,128 @@ public class EllieEntity extends Animal implements GeoEntity {
|
|||||||
} else {
|
} else {
|
||||||
ticksWithoutSleep = Math.max(0, ticksWithoutSleep - 10);
|
ticksWithoutSleep = Math.max(0, ticksWithoutSleep - 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int turnTimer = entityData.get(DATA_TURNING_TIMER);
|
||||||
|
if (turnTimer > 0) {
|
||||||
|
entityData.set(DATA_TURNING_TIMER, turnTimer - 1);
|
||||||
|
if (turnTimer == 1) yawDeltaAccumulator = 0;
|
||||||
|
} else if (!sleeping && !falling) {
|
||||||
|
float yawDelta = Math.abs(getYRot() - prevYRot);
|
||||||
|
yawDeltaAccumulator += yawDelta;
|
||||||
|
if (yawDeltaAccumulator > 30 && !moving) {
|
||||||
|
entityData.set(DATA_TURNING_TIMER, 17);
|
||||||
|
yawDeltaAccumulator = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
prevYRot = getYRot();
|
||||||
|
|
||||||
|
if (tickCount > 20) {
|
||||||
|
int debugAnim = entityData.get(DATA_DEBUG_ANIM);
|
||||||
|
if (debugAnim > 0) {
|
||||||
|
debugAnimTimer--;
|
||||||
|
if (debugAnimTimer <= 0) {
|
||||||
|
entityData.set(DATA_DEBUG_ANIM, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int followTarget = entityData.get(DATA_FOLLOW_TARGET);
|
||||||
|
if (followTarget > 0) {
|
||||||
|
Entity target = level().getEntity(followTarget);
|
||||||
|
if (!(target instanceof Player) || !target.isAlive() || target.distanceToSqr(this) > 64 * 64) {
|
||||||
|
entityData.set(DATA_FOLLOW_TARGET, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateDebugBrainString() {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
String goalName = goalSelector.getRunningGoals()
|
||||||
|
.findFirst()
|
||||||
|
.map(g -> g.getGoal().getClass().getSimpleName())
|
||||||
|
.orElse("none");
|
||||||
|
sb.append("Goal:").append(goalName).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("Move:").append(entityData.get(DATA_MOVING)).append("|");
|
||||||
|
sb.append("Pose:").append(getPose()).append("|");
|
||||||
|
sb.append("LowCeil:").append(entityData.get(DATA_UNDER_LOW_CEILING)).append("|");
|
||||||
|
sb.append("Rel:").append(String.format("%.0f", relationshipPoints)).append("|");
|
||||||
|
sb.append("CrouchT:").append(crouchTimer).append("|");
|
||||||
|
sb.append("TurnT:").append(entityData.get(DATA_TURNING_TIMER)).append("|");
|
||||||
|
if (navigation.isInProgress() && !navigation.isDone()) {
|
||||||
|
sb.append("NavTo:").append(navigation.getTargetPos());
|
||||||
|
} else {
|
||||||
|
sb.append("NavTo:-");
|
||||||
|
}
|
||||||
|
entityData.set(DATA_DEBUG_BRAIN, sb.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void toggleDebug() {
|
||||||
|
entityData.set(DATA_DEBUG, !entityData.get(DATA_DEBUG));
|
||||||
|
if (!entityData.get(DATA_DEBUG)) {
|
||||||
|
entityData.set(DATA_DEBUG_BRAIN, "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<BlockPos> getPathWaypoints() {
|
||||||
|
List<BlockPos> result = new ArrayList<>();
|
||||||
|
Path path = navigation.getPath();
|
||||||
|
if (path != null) {
|
||||||
|
for (int i = 0; i < path.getNodeCount(); i++) {
|
||||||
|
Node node = path.getNode(i);
|
||||||
|
result.add(new BlockPos(node.x, node.y, node.z));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean checkLowCeiling() {
|
private boolean checkLowCeiling() {
|
||||||
return isSpaceLow(blockPosition());
|
BlockPos pos = blockPosition();
|
||||||
|
if (isSpaceLow(pos)) return true;
|
||||||
|
if (isMoving() && navigation.isInProgress()) {
|
||||||
|
Direction dir = getDirection();
|
||||||
|
if (isSpaceLow(pos.relative(dir, 1))) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isSpaceLow(BlockPos pos) {
|
public boolean isSpaceLow(BlockPos pos) {
|
||||||
return level().getBlockState(pos.above(1)).blocksMotion();
|
for (int dy = 1; dy < 3; dy++) {
|
||||||
|
if (level().getBlockState(pos.above(dy)).blocksMotion()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public EntityDimensions getDimensions(Pose pose) {
|
public EntityDimensions getDimensions(Pose pose) {
|
||||||
if (pose == Pose.SLEEPING) return EntityDimensions.fixed(0.4f, 0.3f);
|
if (pose == Pose.SLEEPING) return EntityDimensions.fixed(0.15f, 0.15f);
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float getEyeHeight(Pose pose) {
|
||||||
|
if (pose == Pose.STANDING && getPose() == Pose.SLEEPING) return 0.1f;
|
||||||
|
return super.getEyeHeight(pose);
|
||||||
|
}
|
||||||
|
|
||||||
public boolean occupyBed(BlockPos pos) {
|
public boolean occupyBed(BlockPos pos) {
|
||||||
BlockState state = level().getBlockState(pos);
|
BlockState state = level().getBlockState(pos);
|
||||||
if (state.getBlock() instanceof BedBlock) {
|
if (state.getBlock() instanceof BedBlock) {
|
||||||
if (state.getValue(BedBlock.OCCUPIED)) return false;
|
if (state.getValue(BedBlock.OCCUPIED)) return false;
|
||||||
|
Direction facing = state.getValue(BedBlock.FACING);
|
||||||
BlockPos foot = state.getValue(BedBlock.PART) == BedPart.HEAD
|
BlockPos foot = state.getValue(BedBlock.PART) == BedPart.HEAD
|
||||||
? pos.relative(state.getValue(BedBlock.FACING).getOpposite())
|
? pos.relative(facing.getOpposite())
|
||||||
: pos;
|
: pos;
|
||||||
level().setBlock(foot, state.setValue(BedBlock.OCCUPIED, true), 3);
|
BlockState footState = level().getBlockState(foot);
|
||||||
BlockPos head = foot.relative(state.getValue(BedBlock.FACING));
|
if (footState.getBlock() instanceof BedBlock) {
|
||||||
|
level().setBlock(foot, footState.setValue(BedBlock.OCCUPIED, true), 3);
|
||||||
|
}
|
||||||
|
BlockPos head = foot.relative(facing);
|
||||||
BlockState headState = level().getBlockState(head);
|
BlockState headState = level().getBlockState(head);
|
||||||
if (headState.getBlock() instanceof BedBlock) {
|
if (headState.getBlock() instanceof BedBlock) {
|
||||||
level().setBlock(head, headState.setValue(BedBlock.OCCUPIED, true), 3);
|
level().setBlock(head, headState.setValue(BedBlock.OCCUPIED, true), 3);
|
||||||
@@ -255,53 +401,57 @@ public class EllieEntity extends Animal implements GeoEntity {
|
|||||||
return level().getBlockState(pos).isBed(level(), pos, null);
|
return level().getBlockState(pos).isBed(level(), pos, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isBedAtOrAdjacent(BlockPos pos) {
|
||||||
|
if (pos.equals(bedPos)) return true;
|
||||||
|
if (bedPos != null) {
|
||||||
|
BlockState state = level().getBlockState(bedPos);
|
||||||
|
if (state.getBlock() instanceof BedBlock) {
|
||||||
|
BlockPos head = bedPos.relative(state.getValue(BedBlock.FACING));
|
||||||
|
if (pos.equals(head)) return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public void setBedSleepPos(BlockPos pos) {
|
public void setBedSleepPos(BlockPos pos) {
|
||||||
BlockState state = level().getBlockState(pos);
|
BlockState state = level().getBlockState(pos);
|
||||||
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 footPos = state.getValue(BedBlock.PART) == BedPart.HEAD
|
BlockPos headPos = state.getValue(BedBlock.PART) == BedPart.HEAD
|
||||||
? pos.relative(facing.getOpposite())
|
? pos
|
||||||
: pos;
|
: pos.relative(facing);
|
||||||
|
|
||||||
|
setPose(Pose.SLEEPING);
|
||||||
|
setPos(headPos.getX() + 0.5, pos.getY() + 0.6875, headPos.getZ() + 0.5);
|
||||||
|
setSleepingPos(headPos);
|
||||||
float yRot = facing.toYRot();
|
float yRot = facing.toYRot();
|
||||||
setPos(footPos.getX() + 0.5, footPos.getY() + 0.5625, footPos.getZ() + 0.5);
|
|
||||||
setYRot(yRot);
|
setYRot(yRot);
|
||||||
yRotO = yRot;
|
yRotO = yRot;
|
||||||
setYHeadRot(yRot);
|
setYHeadRot(yRot);
|
||||||
setSleeping(true);
|
setSleeping(true);
|
||||||
setPose(Pose.SLEEPING);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void wakeUp() {
|
public void wakeUp() {
|
||||||
releaseBed();
|
releaseBed();
|
||||||
|
forgetBed();
|
||||||
setSleeping(false);
|
setSleeping(false);
|
||||||
setPose(Pose.STANDING);
|
setPose(Pose.STANDING);
|
||||||
navigation.stop();
|
navigation.stop();
|
||||||
setDeltaMovement(getDeltaMovement().add(0, 0.15, 0));
|
setDeltaMovement(getDeltaMovement().add(0, 0.15, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void openDoorInFront() {
|
|
||||||
for (int i = 0; i < 3; i++) {
|
|
||||||
BlockPos checkPos = blockPosition().relative(getDirection(), 2 + i);
|
|
||||||
BlockState state = level().getBlockState(checkPos);
|
|
||||||
if (state.getBlock() instanceof DoorBlock) {
|
|
||||||
if (!state.getValue(DoorBlock.OPEN)) {
|
|
||||||
level().setBlock(checkPos, state.setValue(DoorBlock.OPEN, true), 3);
|
|
||||||
lastOpenedDoor = checkPos;
|
|
||||||
doorCloseTimer = 0;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void registerControllers(AnimatableManager.ControllerRegistrar controllers) {
|
public void registerControllers(AnimatableManager.ControllerRegistrar controllers) {
|
||||||
controllers.add(new AnimationController<>(this, "main", 4, this::animationHandler));
|
controllers.add(new AnimationController<>(this, "main", 4, this::animationHandler));
|
||||||
}
|
}
|
||||||
|
|
||||||
private <E extends GeoEntity> PlayState animationHandler(AnimationState<E> state) {
|
private <E extends GeoEntity> PlayState animationHandler(AnimationState<E> state) {
|
||||||
|
int debugAnim = entityData.get(DATA_DEBUG_ANIM);
|
||||||
|
if (debugAnim > 0 && debugAnim <= DEBUG_ANIMS.size()) {
|
||||||
|
return state.setAndContinue(RawAnimation.begin().thenPlay(DEBUG_ANIMS.get(debugAnim - 1)));
|
||||||
|
}
|
||||||
|
|
||||||
int idleAnim = entityData.get(DATA_IDLE_ANIM);
|
int idleAnim = entityData.get(DATA_IDLE_ANIM);
|
||||||
if (idleAnim > 0) {
|
if (idleAnim > 0) {
|
||||||
String animName = switch (idleAnim) {
|
String animName = switch (idleAnim) {
|
||||||
@@ -342,6 +492,10 @@ public class EllieEntity extends Animal implements GeoEntity {
|
|||||||
if (fallTicks > 5) return RawAnimation.begin().thenPlay("fall");
|
if (fallTicks > 5) return RawAnimation.begin().thenPlay("fall");
|
||||||
if (eating) return RawAnimation.begin().thenPlay("eat1");
|
if (eating) return RawAnimation.begin().thenPlay("eat1");
|
||||||
|
|
||||||
|
if (entityData.get(DATA_TURNING_TIMER) > 0) {
|
||||||
|
return RawAnimation.begin().thenPlay("turn").thenLoop(resolveTurnLoopName());
|
||||||
|
}
|
||||||
|
|
||||||
if (tired) return moving
|
if (tired) return moving
|
||||||
? RawAnimation.begin().thenLoop("walkingsleepy")
|
? RawAnimation.begin().thenLoop("walkingsleepy")
|
||||||
: RawAnimation.begin().thenLoop("idlesleepy");
|
: RawAnimation.begin().thenLoop("idlesleepy");
|
||||||
@@ -355,6 +509,19 @@ public class EllieEntity extends Animal implements GeoEntity {
|
|||||||
return RawAnimation.begin().thenLoop("idleins");
|
return RawAnimation.begin().thenLoop("idleins");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String resolveTurnLoopName() {
|
||||||
|
boolean tired = entityData.get(DATA_TIRED);
|
||||||
|
boolean inRain = entityData.get(DATA_IN_RAIN);
|
||||||
|
boolean lowCeiling = entityData.get(DATA_UNDER_LOW_CEILING);
|
||||||
|
boolean moving = entityData.get(DATA_MOVING);
|
||||||
|
|
||||||
|
if (tired) return moving ? "walkingsleepy" : "idlesleepy";
|
||||||
|
if (inRain) return moving ? "walkingrain" : "idleconrain";
|
||||||
|
if (lowCeiling) return moving ? "shiftwalking" : "shiftidle";
|
||||||
|
if (moving) return "walkingsimple";
|
||||||
|
return "idleins";
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AgeableMob getBreedOffspring(ServerLevel level, AgeableMob other) {
|
public AgeableMob getBreedOffspring(ServerLevel level, AgeableMob other) {
|
||||||
return null;
|
return null;
|
||||||
@@ -362,6 +529,31 @@ 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);
|
||||||
|
boolean holdingStick = stack.is(Items.DEBUG_STICK);
|
||||||
|
|
||||||
|
if (holdingStick && player.isShiftKeyDown()) {
|
||||||
|
if (!level().isClientSide) {
|
||||||
|
int current = entityData.get(DATA_FOLLOW_TARGET);
|
||||||
|
if (current == player.getId()) {
|
||||||
|
entityData.set(DATA_FOLLOW_TARGET, 0);
|
||||||
|
} else {
|
||||||
|
entityData.set(DATA_FOLLOW_TARGET, player.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return InteractionResult.SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (holdingStick) {
|
||||||
|
if (!level().isClientSide) {
|
||||||
|
int current = entityData.get(DATA_DEBUG_ANIM);
|
||||||
|
int next = (current + 1) % (DEBUG_ANIMS.size() + 1);
|
||||||
|
entityData.set(DATA_DEBUG_ANIM, next);
|
||||||
|
debugAnimTimer = 200;
|
||||||
|
}
|
||||||
|
return InteractionResult.SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
if (player.isShiftKeyDown()) return InteractionResult.PASS;
|
if (player.isShiftKeyDown()) return InteractionResult.PASS;
|
||||||
|
|
||||||
if (entityData.get(DATA_SLEEPING)) {
|
if (entityData.get(DATA_SLEEPING)) {
|
||||||
@@ -379,6 +571,24 @@ public class EllieEntity extends Animal implements GeoEntity {
|
|||||||
return InteractionResult.SUCCESS;
|
return InteractionResult.SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hurt(DamageSource source, float amount) {
|
||||||
|
if (source.getEntity() instanceof Player player) {
|
||||||
|
ItemStack weapon = player.getMainHandItem();
|
||||||
|
if (weapon.is(Items.DEBUG_STICK)) {
|
||||||
|
if (!level().isClientSide) {
|
||||||
|
int current = entityData.get(DATA_DEBUG_ANIM);
|
||||||
|
if (current > 0) {
|
||||||
|
entityData.set(DATA_DEBUG_ANIM, current);
|
||||||
|
debugAnimTimer = 200;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.hurt(source, amount);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addAdditionalSaveData(CompoundTag tag) {
|
public void addAdditionalSaveData(CompoundTag tag) {
|
||||||
super.addAdditionalSaveData(tag);
|
super.addAdditionalSaveData(tag);
|
||||||
@@ -392,7 +602,12 @@ public class EllieEntity extends Animal implements GeoEntity {
|
|||||||
super.readAdditionalSaveData(tag);
|
super.readAdditionalSaveData(tag);
|
||||||
ticksWithoutSleep = tag.getInt("TicksWithoutSleep");
|
ticksWithoutSleep = tag.getInt("TicksWithoutSleep");
|
||||||
relationshipPoints = tag.getFloat("Relationship");
|
relationshipPoints = tag.getFloat("Relationship");
|
||||||
if (tag.contains("BedPos")) bedPos = BlockPos.of(tag.getLong("BedPos"));
|
if (tag.contains("BedPos")) {
|
||||||
|
bedPos = BlockPos.of(tag.getLong("BedPos"));
|
||||||
|
if (entityData.get(DATA_SLEEPING)) {
|
||||||
|
needsSleepRestore = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -403,6 +618,8 @@ public class EllieEntity extends Animal implements GeoEntity {
|
|||||||
public boolean isTired() { return entityData.get(DATA_TIRED); }
|
public boolean isTired() { return entityData.get(DATA_TIRED); }
|
||||||
public boolean isInRain() { return entityData.get(DATA_IN_RAIN); }
|
public boolean isInRain() { return entityData.get(DATA_IN_RAIN); }
|
||||||
public boolean isUnderLowCeiling() { return entityData.get(DATA_UNDER_LOW_CEILING); }
|
public boolean isUnderLowCeiling() { return entityData.get(DATA_UNDER_LOW_CEILING); }
|
||||||
|
public boolean isDebugMode() { return entityData.get(DATA_DEBUG); }
|
||||||
|
public String getDebugBrainString() { return entityData.get(DATA_DEBUG_BRAIN); }
|
||||||
public void setBedPos(BlockPos pos) { this.bedPos = pos; }
|
public void setBedPos(BlockPos pos) { this.bedPos = pos; }
|
||||||
public BlockPos getBedPos() { return bedPos; }
|
public BlockPos getBedPos() { return bedPos; }
|
||||||
public float getRelationshipPoints() { return relationshipPoints; }
|
public float getRelationshipPoints() { return relationshipPoints; }
|
||||||
@@ -410,4 +627,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 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); }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,18 @@
|
|||||||
package me.sashegdev.fabled_hearts.entity.ellie;
|
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.math.Axis;
|
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.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;
|
||||||
|
import org.joml.Matrix4f;
|
||||||
import software.bernie.geckolib.renderer.GeoEntityRenderer;
|
import software.bernie.geckolib.renderer.GeoEntityRenderer;
|
||||||
|
|
||||||
public class EllieRenderer extends GeoEntityRenderer<EllieEntity> {
|
public class EllieRenderer extends GeoEntityRenderer<EllieEntity> {
|
||||||
@@ -16,6 +21,25 @@ 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) {
|
||||||
@@ -25,14 +49,54 @@ public class EllieRenderer extends GeoEntityRenderer<EllieEntity> {
|
|||||||
@Override
|
@Override
|
||||||
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) {
|
||||||
if (entity.getPose() == Pose.SLEEPING) {
|
|
||||||
poseStack.pushPose();
|
|
||||||
poseStack.translate(0, 0.5625f, 0);
|
|
||||||
poseStack.mulPose(Axis.XP.rotationDegrees(-90));
|
|
||||||
super.render(entity, entityYaw, partialTick, poseStack, bufferSource, packedLight);
|
|
||||||
poseStack.popPose();
|
|
||||||
} else {
|
|
||||||
super.render(entity, entityYaw, partialTick, poseStack, bufferSource, packedLight);
|
super.render(entity, entityYaw, partialTick, poseStack, bufferSource, packedLight);
|
||||||
|
|
||||||
|
if (entity.isDebugMode()) {
|
||||||
|
renderBrainText(entity, poseStack, bufferSource, packedLight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void renderBrainText(EllieEntity entity, PoseStack poseStack,
|
||||||
|
MultiBufferSource bufferSource, int packedLight) {
|
||||||
|
String brain = entity.getDebugBrainString();
|
||||||
|
if (brain == null || brain.isEmpty()) return;
|
||||||
|
|
||||||
|
Font font = Minecraft.getInstance().font;
|
||||||
|
String[] lines = brain.split("\\|");
|
||||||
|
|
||||||
|
poseStack.pushPose();
|
||||||
|
float heightOffset = entity.getPose() == Pose.SLEEPING ? 1.8f : entity.getBbHeight() + 0.7f;
|
||||||
|
poseStack.translate(0, heightOffset, 0);
|
||||||
|
poseStack.mulPose(Minecraft.getInstance().getEntityRenderDispatcher().cameraOrientation());
|
||||||
|
poseStack.scale(-0.025f, -0.025f, 0.025f);
|
||||||
|
|
||||||
|
int totalHeight = lines.length * (font.lineHeight + 2);
|
||||||
|
int startY = -totalHeight / 2;
|
||||||
|
|
||||||
|
for (String line : lines) {
|
||||||
|
int color = 0xFFFFFF;
|
||||||
|
if (line.startsWith("Goal:")) color = 0x55FF55;
|
||||||
|
else if (line.startsWith("Bed:")) color = 0xAAAAAA;
|
||||||
|
else if (line.startsWith("Sleep:")) color = 0xFFFF55;
|
||||||
|
else if (line.startsWith("Tired:")) color = 0xFFAA00;
|
||||||
|
else if (line.startsWith("Move:")) color = 0x55FFFF;
|
||||||
|
else if (line.startsWith("Pose:")) color = 0xFF55FF;
|
||||||
|
else if (line.startsWith("LowCeil:")) color = 0xFF5555;
|
||||||
|
else if (line.startsWith("Rel:")) color = 0xFF69B4;
|
||||||
|
else if (line.startsWith("CrouchT:")) color = 0xAAAAAA;
|
||||||
|
else if (line.startsWith("NavTo:")) color = 0x55FFAA;
|
||||||
|
else if (line.startsWith("TurnT:")) color = 0xFFAAFF;
|
||||||
|
|
||||||
|
int width = font.width(line);
|
||||||
|
int x = -width / 2;
|
||||||
|
|
||||||
|
Matrix4f matrix = poseStack.last().pose();
|
||||||
|
font.drawInBatch(line, x, startY, color, false,
|
||||||
|
matrix, bufferSource, Font.DisplayMode.SEE_THROUGH,
|
||||||
|
0x40000000, packedLight);
|
||||||
|
startY += font.lineHeight + 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
poseStack.popPose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package me.sashegdev.fabled_hearts.network;
|
||||||
|
|
||||||
|
import me.sashegdev.fabled_hearts.client.DebugClientHandler;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.network.FriendlyByteBuf;
|
||||||
|
import net.minecraftforge.network.NetworkEvent;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
public class DebugSyncPacket {
|
||||||
|
private final int entityId;
|
||||||
|
private final List<BlockPos> waypoints;
|
||||||
|
private final String brainState;
|
||||||
|
|
||||||
|
public DebugSyncPacket(int entityId, List<BlockPos> waypoints, String brainState) {
|
||||||
|
this.entityId = entityId;
|
||||||
|
this.waypoints = waypoints;
|
||||||
|
this.brainState = brainState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void encode(FriendlyByteBuf buf) {
|
||||||
|
buf.writeInt(entityId);
|
||||||
|
buf.writeInt(waypoints.size());
|
||||||
|
for (BlockPos wp : waypoints) {
|
||||||
|
buf.writeInt(wp.getX());
|
||||||
|
buf.writeInt(wp.getY());
|
||||||
|
buf.writeInt(wp.getZ());
|
||||||
|
}
|
||||||
|
buf.writeUtf(brainState);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DebugSyncPacket decode(FriendlyByteBuf buf) {
|
||||||
|
int entityId = buf.readInt();
|
||||||
|
int size = buf.readInt();
|
||||||
|
List<BlockPos> waypoints = new ArrayList<>(size);
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
waypoints.add(new BlockPos(buf.readInt(), buf.readInt(), buf.readInt()));
|
||||||
|
}
|
||||||
|
String brainState = buf.readUtf();
|
||||||
|
return new DebugSyncPacket(entityId, waypoints, brainState);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handle(Supplier<NetworkEvent.Context> ctx) {
|
||||||
|
ctx.get().enqueueWork(() -> {
|
||||||
|
DebugClientHandler.INSTANCE.onDebugSync(entityId, waypoints, brainState);
|
||||||
|
});
|
||||||
|
ctx.get().setPacketHandled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package me.sashegdev.fabled_hearts.network;
|
||||||
|
|
||||||
|
import me.sashegdev.fabled_hearts.entity.ellie.EllieEntity;
|
||||||
|
import net.minecraft.network.FriendlyByteBuf;
|
||||||
|
import net.minecraft.world.entity.player.Player;
|
||||||
|
import net.minecraftforge.network.NetworkEvent;
|
||||||
|
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
public class DebugTogglePacket {
|
||||||
|
private final int entityId;
|
||||||
|
|
||||||
|
public DebugTogglePacket(int entityId) {
|
||||||
|
this.entityId = entityId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void encode(FriendlyByteBuf buf) {
|
||||||
|
buf.writeInt(entityId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DebugTogglePacket decode(FriendlyByteBuf buf) {
|
||||||
|
return new DebugTogglePacket(buf.readInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handle(Supplier<NetworkEvent.Context> ctx) {
|
||||||
|
ctx.get().enqueueWork(() -> {
|
||||||
|
Player player = ctx.get().getSender();
|
||||||
|
if (player != null && player.level().getEntity(entityId) instanceof EllieEntity ellie) {
|
||||||
|
ellie.toggleDebug();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ctx.get().setPacketHandled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,5 +25,9 @@ public class ModNetworking {
|
|||||||
DialogNodePacket::encode, DialogNodePacket::decode, DialogNodePacket::handle);
|
DialogNodePacket::encode, DialogNodePacket::decode, DialogNodePacket::handle);
|
||||||
CHANNEL.registerMessage(id++, RelationshipSyncPacket.class,
|
CHANNEL.registerMessage(id++, RelationshipSyncPacket.class,
|
||||||
RelationshipSyncPacket::encode, RelationshipSyncPacket::decode, RelationshipSyncPacket::handle);
|
RelationshipSyncPacket::encode, RelationshipSyncPacket::decode, RelationshipSyncPacket::handle);
|
||||||
|
CHANNEL.registerMessage(id++, DebugTogglePacket.class,
|
||||||
|
DebugTogglePacket::encode, DebugTogglePacket::decode, DebugTogglePacket::handle);
|
||||||
|
CHANNEL.registerMessage(id++, DebugSyncPacket.class,
|
||||||
|
DebugSyncPacket::encode, DebugSyncPacket::decode, DebugSyncPacket::handle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ public class ModEntities {
|
|||||||
|
|
||||||
public static final RegistryObject<EntityType<EllieEntity>> ELLIE =
|
public static final RegistryObject<EntityType<EllieEntity>> ELLIE =
|
||||||
ENTITIES.register("ellie", () -> EntityType.Builder.of(EllieEntity::new, MobCategory.CREATURE)
|
ENTITIES.register("ellie", () -> EntityType.Builder.of(EllieEntity::new, MobCategory.CREATURE)
|
||||||
.sized(0.9f, 2.5f)
|
.sized(0.6f, 2.5f)
|
||||||
.clientTrackingRange(64)
|
.clientTrackingRange(64)
|
||||||
.build("ellie"));
|
.build("ellie"));
|
||||||
|
|
||||||
|
|||||||
@@ -13897,14 +13897,6 @@
|
|||||||
"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": {
|
||||||
@@ -14029,19 +14021,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": {
|
||||||
@@ -14051,6 +14043,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]
|
||||||
}
|
}
|
||||||
@@ -14058,17 +14056,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": {
|
||||||
@@ -14080,6 +14092,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]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user