Compare commits

2 Commits

14 changed files with 22667 additions and 4391 deletions
Vendored Regular → Executable
View File
@@ -5,11 +5,13 @@ import me.sashegdev.fabled_hearts.dialog.DialogLoader;
import me.sashegdev.fabled_hearts.dialog.DialogManager;
import me.sashegdev.fabled_hearts.entity.ellie.EllieEntity;
import me.sashegdev.fabled_hearts.entity.ellie.EllieRenderer;
import me.sashegdev.fabled_hearts.client.DebugClientHandler;
import me.sashegdev.fabled_hearts.network.ModNetworking;
import me.sashegdev.fabled_hearts.registry.ModEntities;
import me.sashegdev.fabled_hearts.registry.ModItems;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.client.event.EntityRenderersEvent;
import net.minecraftforge.client.event.RegisterKeyMappingsEvent;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.AddReloadListenerEvent;
import net.minecraftforge.event.entity.EntityAttributeCreationEvent;
@@ -62,6 +64,16 @@ public class Main {
public static void registerRenderers(EntityRenderersEvent.RegisterRenderers event) {
event.registerEntityRenderer(ModEntities.ELLIE.get(), EllieRenderer::new);
}
@SubscribeEvent
public static void registerKeyMappings(RegisterKeyMappingsEvent event) {
DebugClientHandler.registerKeyMapping(event);
}
@SubscribeEvent
public static void onClientSetup(net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent event) {
DebugClientHandler.init();
}
}
public static class ForgeHandler {
@@ -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;
}
}
@@ -16,7 +16,9 @@ public class EllieSleepGoal extends Goal {
private BlockPos bedPos;
private int sleepTimer;
private boolean claimed;
private int scanCooldown;
private static final int MAX_SLEEP_TICKS = 6000;
private static final int SCAN_INTERVAL = 40;
private static final double REACH_DIST_SQR = 1.8 * 1.8;
public EllieSleepGoal(EllieEntity ellie, int searchRadius) {
@@ -39,6 +41,8 @@ public class EllieSleepGoal extends Goal {
if (stored != null && isFreeBed(stored) && ellie.distanceToSqr(Vec3.atCenterOf(stored)) < 50 * 50) {
return stored;
}
if (scanCooldown-- > 0) return null;
scanCooldown = SCAN_INTERVAL;
return findNearestBed();
}
@@ -108,7 +112,7 @@ public class EllieSleepGoal extends Goal {
if (sleepTimer >= MAX_SLEEP_TICKS) return false;
if (bedPos != null) {
BlockState state = ellie.level().getBlockState(bedPos);
if (!state.isBed(ellie.level(), bedPos, null)) return false;
if (!(state.getBlock() instanceof BedBlock)) return false;
}
return true;
}
@@ -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;
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.IdleAnimationGoal;
import me.sashegdev.fabled_hearts.ai.ShelterGoal;
import me.sashegdev.fabled_hearts.network.ModNetworking;
import me.sashegdev.fabled_hearts.network.OpenDialogPacket;
import net.minecraft.core.BlockPos;
@@ -17,12 +20,14 @@ import net.minecraft.world.entity.*;
import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
import net.minecraft.world.entity.ai.attributes.Attributes;
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.player.Player;
import net.minecraft.world.level.Level;
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.phys.Vec3;
import net.minecraft.world.level.block.state.properties.BedPart;
import net.minecraft.server.level.ServerLevel;
import software.bernie.geckolib.animatable.GeoEntity;
@@ -57,18 +62,24 @@ public class EllieEntity extends Animal implements GeoEntity {
private int ticksWithoutSleep;
private float relationshipPoints;
private BlockPos bedPos;
private Direction lastBedDir;
private double lastX, lastZ;
private int moveDetectCooldown;
private BlockPos lastOpenedDoor;
private int doorCloseTimer;
private int crouchTimer;
private boolean debugMode;
public EllieEntity(EntityType<? extends Animal> type, Level level) {
super(type, level);
((GroundPathNavigation) this.getNavigation()).setCanOpenDoors(true);
this.setPersistenceRequired();
}
@Override
public PathNavigation createNavigation(Level level) {
return new ElliePathNavigation(this, level);
}
public static AttributeSupplier.Builder createAttributes() {
return Animal.createMobAttributes()
.add(Attributes.MAX_HEALTH, 40.0)
@@ -91,9 +102,11 @@ public class EllieEntity extends Animal implements GeoEntity {
@Override
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 EllieDoorInteractGoal(this));
this.goalSelector.addGoal(1, new IdleAnimationGoal(this));
this.goalSelector.addGoal(2, new ShelterGoal(this));
this.goalSelector.addGoal(3, new me.sashegdev.fabled_hearts.ai.FollowPlayerGoal(this));
}
@@ -176,6 +189,9 @@ public class EllieEntity extends Animal implements GeoEntity {
if (sleeping) {
setPose(Pose.SLEEPING);
crouchTimer = 0;
if (bedPos != null && distanceToSqr(Vec3.atCenterOf(bedPos)) > 9.0) {
wakeUp();
}
} else if (lowCeiling) {
if (onGround() || wasCrouching) {
setPose(Pose.CROUCHING);
@@ -215,21 +231,25 @@ public class EllieEntity extends Animal implements GeoEntity {
public boolean occupyBed(BlockPos pos) {
BlockState state = level().getBlockState(pos);
if (state.getBlock() instanceof BedBlock) {
if (state.getValue(BedBlock.OCCUPIED)) return false;
BlockPos foot = state.getValue(BedBlock.PART) == BedPart.HEAD
? pos.relative(state.getValue(BedBlock.FACING).getOpposite())
: pos;
level().setBlock(foot, state.setValue(BedBlock.OCCUPIED, true), 3);
BlockPos head = foot.relative(state.getValue(BedBlock.FACING));
BlockState headState = level().getBlockState(head);
if (headState.getBlock() instanceof BedBlock) {
level().setBlock(head, headState.setValue(BedBlock.OCCUPIED, true), 3);
}
bedPos = foot;
return true;
if (!(state.getBlock() instanceof BedBlock)) return false;
if (state.getValue(BedBlock.OCCUPIED)) return false;
Direction facing = state.getValue(BedBlock.FACING);
BlockPos foot = state.getValue(BedBlock.PART) == BedPart.HEAD
? pos.relative(facing.getOpposite())
: pos;
BlockPos head = foot.relative(facing);
BlockState footState = level().getBlockState(foot);
if (footState.getBlock() instanceof BedBlock) {
level().setBlock(foot, footState.setValue(BedBlock.OCCUPIED, true), 3);
}
return false;
BlockState headState = level().getBlockState(head);
if (headState.getBlock() instanceof BedBlock) {
level().setBlock(head, headState.setValue(BedBlock.OCCUPIED, true), 3);
}
bedPos = foot;
return true;
}
public void releaseBed() {
@@ -260,19 +280,29 @@ public class EllieEntity extends Animal implements GeoEntity {
if (!(state.getBlock() instanceof BedBlock)) return;
Direction facing = state.getValue(BedBlock.FACING);
BlockPos footPos = state.getValue(BedBlock.PART) == BedPart.HEAD
? pos.relative(facing.getOpposite())
: pos;
BlockPos headPos = state.getValue(BedBlock.PART) == BedPart.HEAD
? pos
: pos.relative(facing);
float yRot = facing.toYRot();
setPos(footPos.getX() + 0.5, footPos.getY() + 0.5625, footPos.getZ() + 0.5);
setYRot(yRot);
yRotO = yRot;
setYHeadRot(yRot);
setPos(headPos.getX() + 0.5, pos.getY() + 0.6875, headPos.getZ() + 0.5);
lastBedDir = facing;
setSleeping(true);
setPose(Pose.SLEEPING);
}
@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() {
releaseBed();
setSleeping(false);
@@ -410,4 +440,6 @@ public class EllieEntity extends Animal implements GeoEntity {
this.relationshipPoints = Math.min(100, Math.max(0, this.relationshipPoints + amount));
}
public int getTicksWithoutSleep() { return ticksWithoutSleep; }
public boolean isDebugMode() { return debugMode; }
public void toggleDebug() { this.debugMode = !debugMode; }
}
@@ -2,9 +2,9 @@ package me.sashegdev.fabled_hearts.entity.ellie;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.math.Axis;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import net.minecraft.core.Direction;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.Pose;
import org.jetbrains.annotations.Nullable;
@@ -18,21 +18,33 @@ public class EllieRenderer extends GeoEntityRenderer<EllieEntity> {
@Override
public RenderType getRenderType(EllieEntity animatable, ResourceLocation texture,
@Nullable MultiBufferSource bufferSource, float partialTick) {
@Nullable net.minecraft.client.renderer.MultiBufferSource bufferSource, float partialTick) {
return RenderType.entityTranslucent(texture);
}
@Override
public void render(EllieEntity entity, float entityYaw, float partialTick, PoseStack poseStack,
MultiBufferSource bufferSource, int packedLight) {
protected void applyRotations(EllieEntity entity, PoseStack poseStack, float tick, float yRot, float partialTick) {
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);
Direction bedDir = entity.getBedOrientation();
if (bedDir == null) bedDir = entity.getLastBedDir();
if (bedDir != null) {
float targetYRot = bedDir.toYRot();
// North/South fix: model lies on side without this
if (bedDir == Direction.NORTH || bedDir == Direction.SOUTH) {
targetYRot += 180.0F;
}
float offset = 1.655F;
poseStack.translate(
-bedDir.getStepX() * offset,
0.125F,
-bedDir.getStepZ() * offset
);
poseStack.mulPose(Axis.YP.rotationDegrees(targetYRot));
poseStack.mulPose(Axis.XP.rotationDegrees(90.0F));
poseStack.mulPose(Axis.ZP.rotationDegrees(180.0F));
return;
}
}
super.applyRotations(entity, poseStack, tick, yRot, partialTick);
}
}
@@ -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);
CHANNEL.registerMessage(id++, RelationshipSyncPacket.class,
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);
}
}
File diff suppressed because it is too large Load Diff