Skip to content

Commit

Permalink
Reworked stuck handling, nonsafe destinations are now eligible for pa…
Browse files Browse the repository at this point in the history
…rtials stuck handling. (#10588)

Reworked stuck handling, nonsafe destinations are now eligible for partial stuck handling.
Calling .stop on the navigator signals no further entity movement is desired, also reset stuck handling.
Fixed a bug causing the stuck handler to not progress further than random moving(outside of global timeout tp)
Fixed a bug where citizens started from a wrong position, when they were standing on the edge of a block
Citizen info command no longer prints duplicate information, nor sends it to all OP. Now also displays stuck level.
Citizen info command now allows teleporting to the displayed coordinates on click
  • Loading branch information
someaddons authored Jan 12, 2025
1 parent ebe99bb commit abbb0d9
Show file tree
Hide file tree
Showing 12 changed files with 355 additions and 222 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.minecolonies.core.entity.pathfinding.navigation;
package com.minecolonies.api.entity.pathfinding;

/**
* Interface for navigators which keep an internal heuristic mod
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package com.minecolonies.api.entity.pathfinding;

import com.minecolonies.core.entity.pathfinding.navigation.MinecoloniesAdvancedPathNavigate;
import com.minecolonies.core.entity.pathfinding.pathjobs.AbstractPathJob;
import com.minecolonies.core.entity.pathfinding.pathresults.PathResult;
import net.minecraft.core.BlockPos;
import net.minecraft.world.entity.Mob;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

/**
* Describes the Navigator used by minecolonies entities
*/
public interface IMinecoloniesNavigator
{
/**
* Sets a new pathjob to execute
*
* @param job to run
* @param dest
* @param speedFactor
* @param safeDestination
* @param <T>
* @return null or new pathresult
*/
@Nullable
<T extends AbstractPathJob> PathResult<T> setPathJob(
@NotNull AbstractPathJob job,
BlockPos dest,
double speedFactor, boolean safeDestination);

/**
* Indirectly triggers a recalulation, by marking the navigator as done
*/
void recalc();

/**
* Returns the pathresult holding the current pathing task and result
*
* @return
*/
PathResult getPathResult();

/**
* Gets the safe destination the entity wants to travel to
*
* @return
*/
BlockPos getSafeDestination();

/**
* Gets the entity of the navigator
*
* @return
*/
Mob getOurEntity();

/**
* Pauses the navigator for X ticks from starting any new pathing tasks
*
* @param pauseTicks
*/
void setPauseTicks(int pauseTicks);

/**
* Returns the stuck handler used by the navigator
*
* @return
*/
IStuckHandler<MinecoloniesAdvancedPathNavigate> getStuckHandler();
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,25 @@
package com.minecolonies.api.entity.pathfinding;

import com.minecolonies.core.entity.pathfinding.navigation.AbstractAdvancedPathNavigate;
import net.minecraft.world.entity.ai.navigation.PathNavigation;

/**
* Stuck handler for pathing, gets called to check/deal with stuck status
*/
public interface IStuckHandler
public interface IStuckHandler<NAV extends PathNavigation & IMinecoloniesNavigator>
{
/**
* Checks if the navigator is stuck
*
* @param navigator navigator to check
*/
void checkStuck(final AbstractAdvancedPathNavigate navigator);
void checkStuck(final NAV navigator);

void resetGlobalStuckTimers();

/**
* Returns the stuck level (0-9) indicating how long the entity is stuck and which stuck actions got used
*
* @return
*/
public int getStuckLevel();
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import com.minecolonies.api.colony.IColony;
import com.minecolonies.api.colony.IColonyManager;
import com.minecolonies.api.entity.citizen.AbstractEntityCitizen;
import com.minecolonies.api.entity.citizen.Skill;
import com.minecolonies.api.entity.pathfinding.IMinecoloniesNavigator;
import com.minecolonies.api.util.constant.translation.CommandTranslationConstants;
import com.minecolonies.core.colony.buildings.modules.WorkerBuildingModule;
import com.minecolonies.core.commands.commandTypes.IMCColonyOfficerCommand;
Expand All @@ -15,7 +15,9 @@
import com.mojang.brigadier.context.CommandContext;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.ClickEvent;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.Style;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.Level;

Expand Down Expand Up @@ -44,18 +46,19 @@ public int onExecute(final CommandContext<CommandSourceStack> context)
final IColony colony = IColonyManager.getInstance().getColonyByDimension(colonyID, sender == null ? Level.OVERWORLD : context.getSource().getLevel().dimension());
if (colony == null)
{
context.getSource().sendSuccess(() -> Component.translatable(CommandTranslationConstants.COMMAND_COLONY_ID_NOT_FOUND, colonyID), true);
context.getSource().sendSuccess(() -> Component.translatable(CommandTranslationConstants.COMMAND_COLONY_ID_NOT_FOUND, colonyID), false);
return 0;
}

final ICitizenData citizenData = colony.getCitizenManager().getCivilian(IntegerArgumentType.getInteger(context, CITIZENID_ARG));

if (citizenData == null)
{
context.getSource().sendSuccess(() -> Component.translatable(CommandTranslationConstants.COMMAND_CITIZEN_NOT_FOUND), true);
context.getSource().sendSuccess(() -> Component.translatable(CommandTranslationConstants.COMMAND_CITIZEN_NOT_FOUND), false);
return 0;
}

context.getSource().sendSuccess(() -> Component.translatable(CommandTranslationConstants.COMMAND_CITIZEN_INFO, citizenData.getId(), citizenData.getName()), false);
final Optional<AbstractEntityCitizen> optionalEntityCitizen = citizenData.getEntity();

if (optionalEntityCitizen.isPresent())
Expand All @@ -67,45 +70,31 @@ public int onExecute(final CommandContext<CommandSourceStack> context)
.sendSuccess(() -> Component.translatable(CommandTranslationConstants.COMMAND_CITIZEN_INFO_POSITION,
citizenPosition.getX(),
citizenPosition.getY(),
citizenPosition.getZ()), true);
final BlockPos homePosition = citizenData.getHomePosition();
context.getSource()
.sendSuccess(() -> Component.translatable(CommandTranslationConstants.COMMAND_CITIZEN_INFO_HOME_POSITION,
homePosition.getX(),
homePosition.getY(),
homePosition.getZ()), true);
citizenPosition.getZ()).withStyle(styleWithTeleport(citizenPosition)), false);

context.getSource()
.sendSuccess(() -> Component.translatable(CommandTranslationConstants.COMMAND_CITIZEN_INFO_HEALTH, entityCitizen.getHealth(), entityCitizen.getMaxHealth()), true);
.sendSuccess(() -> Component.translatable(CommandTranslationConstants.COMMAND_CITIZEN_INFO_HEALTH, entityCitizen.getHealth(), entityCitizen.getMaxHealth()), false);
}
else
{
context.getSource().sendSuccess(() -> Component.translatable(CommandTranslationConstants.COMMAND_CITIZEN_INFO_POSITION,
citizenData.getLastPosition().getX(),
citizenData.getLastPosition().getY(),
citizenData.getLastPosition().getZ()), true);
citizenData.getLastPosition().getZ()).withStyle(styleWithTeleport(citizenData.getLastPosition())), false);

context.getSource().sendSuccess(() -> Component.translatable(CommandTranslationConstants.COMMAND_CITIZEN_NOT_LOADED), true);
context.getSource().sendSuccess(() -> Component.translatable(CommandTranslationConstants.COMMAND_CITIZEN_NOT_LOADED), false);
}

context.getSource().sendSuccess(() -> Component.translatable(CommandTranslationConstants.COMMAND_CITIZEN_INFO, citizenData.getId(), citizenData.getName()), true);

final AbstractEntityCitizen entityCitizen = optionalEntityCitizen.get();

final BlockPos citizenPosition = entityCitizen.blockPosition();
context.getSource()
.sendSuccess(() -> Component.translatable(CommandTranslationConstants.COMMAND_CITIZEN_INFO_POSITION,
citizenPosition.getX(),
citizenPosition.getY(),
citizenPosition.getZ()), true);
final BlockPos homePosition = citizenData.getHomePosition();
context.getSource()
.sendSuccess(() -> Component.translatable(CommandTranslationConstants.COMMAND_CITIZEN_INFO_HOME_POSITION, homePosition.getX(), homePosition.getY(), homePosition.getZ()),
true);
.sendSuccess(() -> Component.translatable(CommandTranslationConstants.COMMAND_CITIZEN_INFO_HOME_POSITION,
homePosition.getX(),
homePosition.getY(),
homePosition.getZ()).withStyle(styleWithTeleport(homePosition)), false);

if (citizenData.getWorkBuilding() == null)
{
context.getSource().sendSuccess(() -> Component.translatable(CommandTranslationConstants.COMMAND_CITIZEN_INFO_NO_WORKING_POSITION), true);
context.getSource().sendSuccess(() -> Component.translatable(CommandTranslationConstants.COMMAND_CITIZEN_INFO_NO_WORKING_POSITION), false);
}
else
{
Expand All @@ -114,45 +103,52 @@ public int onExecute(final CommandContext<CommandSourceStack> context)
.sendSuccess(() -> Component.translatable(CommandTranslationConstants.COMMAND_CITIZEN_INFO_WORKING_POSITION,
workingPosition.getX(),
workingPosition.getY(),
workingPosition.getZ()), true);
workingPosition.getZ()).withStyle(styleWithTeleport(workingPosition)), false);
}


Object[] skills =
new Object[] {citizenData.getCitizenSkillHandler().getSkills().get(Skill.Athletics).getLevel(),
citizenData.getCitizenSkillHandler().getSkills().get(Skill.Dexterity).getLevel(),
citizenData.getCitizenSkillHandler().getSkills().get(Skill.Strength).getLevel(),
citizenData.getCitizenSkillHandler().getSkills().get(Skill.Agility).getLevel(),
citizenData.getCitizenSkillHandler().getSkills().get(Skill.Stamina).getLevel(),
citizenData.getCitizenSkillHandler().getSkills().get(Skill.Mana).getLevel(),
citizenData.getCitizenSkillHandler().getSkills().get(Skill.Adaptability).getLevel(),
citizenData.getCitizenSkillHandler().getSkills().get(Skill.Focus).getLevel(),
citizenData.getCitizenSkillHandler().getSkills().get(Skill.Creativity).getLevel(),
citizenData.getCitizenSkillHandler().getSkills().get(Skill.Knowledge).getLevel(),
citizenData.getCitizenSkillHandler().getSkills().get(Skill.Intelligence).getLevel()};
context.getSource().sendSuccess(() -> Component.translatable(CommandTranslationConstants.COMMAND_CITIZEN_INFO_SKILLS, skills), true);

if (citizenData.getJob() == null)
{
context.getSource().sendSuccess(() -> Component.translatable(CommandTranslationConstants.COMMAND_CITIZEN_INFO_NO_JOB), true);
context.getSource().sendSuccess(() -> Component.translatable(CommandTranslationConstants.COMMAND_CITIZEN_INFO_NO_ACTIVITY), true);
context.getSource().sendSuccess(() -> Component.translatable(CommandTranslationConstants.COMMAND_CITIZEN_INFO_NO_JOB), false);
context.getSource().sendSuccess(() -> Component.translatable(CommandTranslationConstants.COMMAND_CITIZEN_INFO_NO_ACTIVITY), false);
}
else if (citizenData.getWorkBuilding() != null && citizenData.getWorkBuilding().hasModule(WorkerBuildingModule.class))
{
context.getSource()
.sendSuccess(() -> Component.translatable(CommandTranslationConstants.COMMAND_CITIZEN_INFO_JOB,
citizenData.getWorkBuilding().getFirstModuleOccurance(WorkerBuildingModule.class).getJobEntry().getTranslationKey()), true);
citizenData.getWorkBuilding().getFirstModuleOccurance(WorkerBuildingModule.class).getJobEntry().getTranslationKey()), false);

if (optionalEntityCitizen.isPresent())
{
final AbstractEntityCitizen entityCitizen = optionalEntityCitizen.get();
context.getSource()
.sendSuccess(() -> Component.translatable(CommandTranslationConstants.COMMAND_CITIZEN_INFO_ACTIVITY,
((EntityCitizen) entityCitizen).getCitizenAI().getState(),
entityCitizen.getCitizenJobHandler().getColonyJob().getNameTagDescription(),
entityCitizen.getCitizenJobHandler().getWorkAI().getState()), false);
}
}

if (optionalEntityCitizen.isPresent())
{
final AbstractEntityCitizen entityCitizen = optionalEntityCitizen.get();
context.getSource()
.sendSuccess(() -> Component.translatable(CommandTranslationConstants.COMMAND_CITIZEN_INFO_ACTIVITY,
((EntityCitizen) entityCitizen).getCitizenAI().getState(),
entityCitizen.getCitizenJobHandler().getColonyJob().getNameTagDescription(),
entityCitizen.getCitizenJobHandler().getWorkAI().getState()), true);
.sendSuccess(() -> Component.literal("Stuck level: " + ((IMinecoloniesNavigator) entityCitizen.getNavigation()).getStuckHandler().getStuckLevel()), false);
}

return 1;
}

/**
* Creates a style with clickable teleport
*
* @param pos
* @return
*/
private static Style styleWithTeleport(final BlockPos pos)
{
return Style.EMPTY.withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/tp " + pos.getX() + " " + pos.getY() + " " + pos.getZ()));
}

/**
* Name string of the command.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ private void findBedAndTryToSleep()
usedBed = homePos;
}

if (EntityNavigationUtils.walkToPosInBuilding(citizen, usedBed, citizen.getCitizenData().getHomeBuilding(), 5))
if (EntityNavigationUtils.walkToPosInBuilding(citizen, usedBed, citizen.getCitizenData().getHomeBuilding(), 12))
{
bedTicks++;
if (!citizen.getCitizenSleepHandler().trySleep(usedBed))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,10 @@
import com.minecolonies.core.colony.jobs.JobDeliveryman;
import com.minecolonies.core.colony.requestsystem.resolvers.StationRequestResolver;
import com.minecolonies.core.entity.pathfinding.navigation.EntityNavigationUtils;
import com.minecolonies.core.util.citizenutils.CitizenItemUtils;
import com.minecolonies.core.entity.pathfinding.proxy.EntityCitizenWalkToProxy;
import com.minecolonies.core.tileentities.TileEntityRack;
import com.minecolonies.core.util.WorkerUtil;
import com.minecolonies.core.util.citizenutils.CitizenItemUtils;
import com.mojang.authlib.GameProfile;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
Expand Down Expand Up @@ -286,7 +286,7 @@ public void setWalkTo(final BlockPos walkto)
*/
private IAIState walkToState()
{
if (!walkWithProxy(walkTo, DEFAULT_RANGE_FOR_DELAY))
if (!walkToSafePos(walkTo))
{
return getState();
}
Expand Down Expand Up @@ -900,6 +900,8 @@ protected final boolean walkWithProxy(@NotNull final BlockPos stand, final int r
workOnBlock(null, DELAY_RECHECK);
return true;
}

worker.getNavigation().stop();
return false;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ else if (raider.blockPosition().distSqr(targetBlock) < 25)
walkTimer = raider.level.getGameTime() + TICKS_SECOND * 30;
walkInBuilding = raider.getColony().getBuildingManager().getBuilding(targetBlock);
}
else if (raider.getNavigation().isDone() || raider.getNavigation().getDesiredPos() == null)
else if (raider.getNavigation().isDone())
{
final List<BlockPos> wayPoints = ((IColonyRaidEvent) event).getWayPoints();
final BlockPos moveToPos = ShipBasedRaiderUtils.chooseWaypointFor(wayPoints, raider.blockPosition(), targetBlock);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,40 @@ public static void syncDebugReachedPositions(final HashSet<BlockPos> reached, fi
*/
public static BlockPos prepareStart(@NotNull final LivingEntity entity)
{
@NotNull BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos(Mth.floor(entity.getX()),
final BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos(Mth.floor(entity.getX()),
Mth.floor(entity.getY()),
Mth.floor(entity.getZ()));
final Level level = entity.level;
BlockState bs = level.getBlockState(pos);

// Check if the entity is standing ontop of another block with part of its bb
final BlockPos.MutableBlockPos below = new BlockPos.MutableBlockPos(pos.getX(), pos.getY() - 1, pos.getZ());
if (entity.onGround() && SurfaceType.getSurfaceType(level, level.getBlockState(below), below) != SurfaceType.WALKABLE)
{
int minX = Mth.floor(entity.getBoundingBox().minX);
int minZ = Mth.floor(entity.getBoundingBox().minZ);
int maxX = Mth.floor(entity.getBoundingBox().maxX);
int maxZ = Mth.floor(entity.getBoundingBox().maxZ);

for (int x = minX; x <= maxX; x++)
{
for (int z = minZ; z <= maxZ; z++)
{
final BlockPos toCheck = new BlockPos(x, below.getY(), z);
// Only check other positions than the current
if ((x != pos.getX() || z != pos.getZ())
&& SurfaceType.getSurfaceType(level, level.getBlockState(toCheck), toCheck) == SurfaceType.WALKABLE
&& Math.abs(ShapeUtil.max(level.getBlockState(toCheck).getCollisionShape(level, toCheck), Direction.Axis.Y) + toCheck.getY() - entity.getY()) < 0.1)
{
pos.setX(x);
pos.setZ(z);
below.setX(x);
below.setZ(z);
}
}
}
}

// 1 Up when we're standing within this collision shape
final VoxelShape collisionShape = bs.getCollisionShape(level, pos);
final boolean isFineToStandIn = canStandInSolidBlock(bs);
Expand Down
Loading

0 comments on commit abbb0d9

Please sign in to comment.