mojira.dev
MC-157464

Villagers can pathfind and move towards items while sleeping in a bed

When a villager detects a desired item while sleeping (such as carrots, bread, or beetroot), it will move towards that item before entirely waking up. This causes an incorrect visual for the player where the villager appears to still be asleep (laying down horizontally) while it pathfinds towards any applicable item(s).

Steps to Reproduce:

  1. Place down one or more beds

  2. Spawn in one or more villagers

  3. Drop an item on the ground that entice villagers

    /give @p minecraft:carrot 1

Observed Behavior:

The villager will move across the ground as if it is still asleep to pick up items.

Expected Behavior:

The villager should first wake up entirely (standing upright) before walking to the dropped item, or not detect items while asleep in the first place.

Video:

[media]

Code Analysis:

Here in the create() method of the GoToWantedItem class (which handles a villagers ai behavior to pathfind to desired items) it checks the following conditions for the villager and/or the desired item:

  1. The item pickup cooldown is empty

  2. It can start to pickup the item

  3. The desired item is close enough to the villager

  4. The villager and desired item are both within the level's world border

However, it does not check for whether the villager is asleep. The villager waking up is only ever executed from create() in WakeUp.

public static <E extends LivingEntity> BehaviorControl<E> create(Predicate<E> startCondition, float speed, boolean requiresWalkTarget, int radius) {
      return BehaviorBuilder.create((context) -> {
         BehaviorBuilder<E, ? extends MemoryAccessor<? extends K1, WalkTarget>> behaviorbuilder = requiresWalkTarget ? context.registered(MemoryModuleType.WALK_TARGET) : context.absent(MemoryModuleType.WALK_TARGET);
         return context.group(context.registered(MemoryModuleType.LOOK_TARGET), behaviorbuilder, context.present(MemoryModuleType.NEAREST_VISIBLE_WANTED_ITEM), context.registered(MemoryModuleType.ITEM_PICKUP_COOLDOWN_TICKS)).apply(context, (lookTarget, walkTarget, nearestVisibleWantedItem, itemPickupCooldownTicks) -> {
            return (level, entity, time) -> {
               ItemEntity itementity = context.get(nearestVisibleWantedItem);
               if (
                       context.tryGet(itemPickupCooldownTicks).isEmpty()
                               && startCondition.test(entity)
                               && itementity.closerThan(entity, (double)radius)
                               && entity.level().getWorldBorder().isWithinBounds(itementity.blockPosition())
               ) {
                  WalkTarget walktarget = new WalkTarget(new EntityTracker(itementity, false), speed, 0);
                  lookTarget.set(new EntityTracker(itementity, true));
                  walkTarget.set(walktarget);
                  return true;
               } else {
                  return false;
               }
            };
         });
      });
   }
}

Two Possible Solutions:

  1. If the villager should not be able to pathfind/collect the desired item until it has been woken up via the time turning to day, or being manually woken by a player input on it's bed, a check could be added for whether the villager is currently sleeping. If it is, do not try and pathfind. Like so:

    if (
            context.tryGet(itemPickupCooldownTicks).isEmpty()
                    && startCondition.test(entity)
                    && itementity.closerThan(entity, (double)radius)
                    && entity.level().getWorldBorder().isWithinBounds(itementity.blockPosition())
    //Fix
                    && !entity.isSleeping()
    //Fix end
    )
  2. If the villager should still be able to wake up as it does currently while sleeping, the villager should first properly and entirely wake up before pathfinding to collect the item. This could be done by adding a check to see if the villager is sleeping. If it is, wake up the villager first before executing the rest of the code.

    //Fix
    if (entity.isSleeping()) {
       entity.stopSleeping();
    }
    //Fix end
    
    WalkTarget walktarget = new WalkTarget(new EntityTracker(itementity, false), speed, 0);
    lookTarget.set(new EntityTracker(itementity, true));
    walkTarget.set(walktarget);
    return true;

This is how it looks compared with the new behavior in suggestion 2:

[media]

Linked issues

Attachments

Comments 22

Pelle Reinke

Still present in 20w16a

Ian

Still a problem in the latest snapshot...

 

Pelle Reinke

Still present in 20w17a

Pelle Reinke

still present in 20w18a

Pelle Reinke

still present in 20w19a

12 more comments
[Mod] Jingy

Issue description
When a villager detects a desired item while sleeping (such as carrots, bread, or beetroot), it will move towards the item before properly waking up, which causes an incorrect visual of the villager appearing to still be asleep while it pathfinds to items it wants to pick up.

Steps to Reproduce:
1. Place down one or more beds
2. Spawn in one or more villagers
3. Drop an item on the ground that entise villagers

/give @p minecraft:carrot 1

Observed Results:
The villager will move across the ground as if it is still asleep to pick up items.

Expected Results:
The villager should first wake up entirely (standing upright) before walking to the dropped item.

Screenshots/Videos:

[media]

Code analysis & Suggested fixes:
Here in the create() method of the GoToWantedItem class (which handles a villagers ai behavior to pathfind to desired items) it checks the following conditions for the villager and/or the desired item:
1. The item pickup cooldown is empty
2. It can start to pickup the item
3. The desired item is close enough to the villager
4. The villager and desired item are both within the level's world border

However, it does not check for whether the villager is asleep. The villager waking up is only ever executed from create() in WakeUp.

public static <E extends LivingEntity> BehaviorControl<E> create(Predicate<E> startCondition, float speed, boolean requiresWalkTarget, int radius) {
      return BehaviorBuilder.create((context) -> {
         BehaviorBuilder<E, ? extends MemoryAccessor<? extends K1, WalkTarget>> behaviorbuilder = requiresWalkTarget ? context.registered(MemoryModuleType.WALK_TARGET) : context.absent(MemoryModuleType.WALK_TARGET);
         return context.group(context.registered(MemoryModuleType.LOOK_TARGET), behaviorbuilder, context.present(MemoryModuleType.NEAREST_VISIBLE_WANTED_ITEM), context.registered(MemoryModuleType.ITEM_PICKUP_COOLDOWN_TICKS)).apply(context, (lookTarget, walkTarget, nearestVisibleWantedItem, itemPickupCooldownTicks) -> {
            return (level, entity, time) -> {
               ItemEntity itementity = context.get(nearestVisibleWantedItem);
               if (
                       context.tryGet(itemPickupCooldownTicks).isEmpty()
                               && startCondition.test(entity)
                               && itementity.closerThan(entity, (double)radius)
                               && entity.level().getWorldBorder().isWithinBounds(itementity.blockPosition())
               ) {
                  WalkTarget walktarget = new WalkTarget(new EntityTracker(itementity, false), speed, 0);
                  lookTarget.set(new EntityTracker(itementity, true));
                  walkTarget.set(walktarget);
                  return true;
               } else {
                  return false;
               }
            };
         });
      });
   }
}

Two possible solutions:
1. If the villager should not be able to pathfind/collect the desired item until it has been woken up via the time turning to day, or being manually woken by a player input on it's bed, a check could be added for wether the villager is currently sleeping. If it is, do not try and pathfind. Like so:

if (
        context.tryGet(itemPickupCooldownTicks).isEmpty()
                && startCondition.test(entity)
                && itementity.closerThan(entity, (double)radius)
                && entity.level().getWorldBorder().isWithinBounds(itementity.blockPosition())
//Fix
                && !entity.isSleeping()
//Fix end
)

2. If the villager should still be able to wake up as it does currently while sleeping, the villager should first properly and entirely wake up before pathfinding to collect the item. This could be done by adding a check to see if the villager is sleeping. If it is, wake up the villager first before executing the rest of the code.

//Fix
if (entity.isSleeping()) {
   entity.stopSleeping();
}
//Fix end

WalkTarget walktarget = new WalkTarget(new EntityTracker(itementity, false), speed, 0);
lookTarget.set(new EntityTracker(itementity, true));
walkTarget.set(walktarget);
return true;

This is how it looks compared with the new behavior in suggestion 2:

[media]
[Mod] Jingy

Requesting ownership of this issue as the original poster "Jack Davies" has not updated the issue in 4 years, and this is their only post.

danegraphics

Can confirm in 1.20.1.

When I saw it happen I laughed so hard.

[media]
auits

my sollution is get brightness

Junhior2

el aldeano se mueve estando dormido.

 

Jack Davies

[Mod] Jingy

(Unassigned)

Confirmed

Gameplay

Normal

Mob behaviour

ai, mob, sleeping, villager

1.14.4, 1.15, 1.15.2, 20w09a, 20w13b, ..., 1.21, 1.21 Release Candidate 1, 24w38a, 1.21.3, 1.21.5

Retrieved