Horses can be ridden without saddle, pigs without carrot on a stick
The bug
Horses can be ridden even if they have no saddle equipped.
How to reproduce (vanilla)
The following uses the fact that because the steering is done client-side the forward movement is set client-side only as well. Since the method AbstractHorse.canBeSteered() does not test for the saddle (described below) the horse is moved with this set forward movement.
- Summon a horse
/summon horse ~ ~ ~ {NoAI:1b,Tame:1b,SaddleItem:{id:"saddle",Count:1b}} - Mount it
- Unequip the saddle
→ You will notice that the horse moves forward
How to reproduce (modded)
- Change the condition of the method net.minecraft.entity.passive.AbstractHorse.moveEntityWithHeading(float, float)
// if (this.isBeingRidden() && this.canBeSteered() && this.isHorseSaddled()) if (this.isBeingRidden() && this.canBeSteered() && (world.isRemote || this.isHorseSaddled()))
- Summon a horse
/summon horse ~ ~ ~ {NoAI:1b,Tame:1b} - Mount it and move
Note: To verify that the player and horse move server-side as well you can open the world to LAN and have a second player join or reproduce it on a server.
Code analysis
Based on 1.11.2 decompiled using MCP 9.35 rc1
The method net.minecraft.entity.passive.AbstractHorse.canBeSteered() should probably test if the horse has a saddle.
The method net.minecraft.network.NetHandlerPlayServer.processVehicleMove(CPacketVehicleMove) should then probably use net.minecraft.entity.Entity.canPassengerSteer() as additional condition.
This requires the method net.minecraft.entity.Entity.canPassengerSteer() to be adjusted:
public boolean canPassengerSteer() { Entity entity = this.getControllingPassenger(); /* Comment: * Replaced this, now some calls to this method require require an additional condition to only * allow a client player to control the movement of an entity */ // return entity instanceof EntityPlayer ? ((EntityPlayer)entity).isUser() : !this.world.isRemote; return !this.world.isRemote || (entity instanceof EntityPlayer && ((EntityPlayer) entity).isUser()); }
This adjustment causes some problems where controlling a ridden entity is only done client-side, therefor a new method called for example boolean shouldSteer() could be added which returns !world.isRemote and is overridden by EntityPlayer to return false and for EntityPlayerSP return true. Then all server-side calls to the old method Entity.canPassengerSteer() respectively EntityLiving.canPassengerSteer() can be replaced with entity.canPassengerSteer() && entity.getControllingPassenger().shouldSteer().
Note: Maybe add a null check to Entity.canPassengerSteer() in case it is called for entities with no (valid) passengers.