Minecarts...won't...turn

Discussion in 'Plugin Development' started by azazad, Jul 25, 2012.

Thread Status:
Not open for further replies.
  1. Offline

    azazad

    I saw a Bukkit plugin called MineCars (which lets you drive minecarts) and thought it was great, except for one small detail. The minecarts always stay facing the same direction and never turn to match the direction of movement, looking weird scooting all over the place. I tried to see if I could fix that.

    I made a simple plugin that spawns a minecart and tries to set its yaw to 45 degrees or so. No dice, the minecart just sits there aligned to a multiple of 90 degrees. Same luck with setting pitch. This seems to be specific to minecarts only, as animals and arrows can be turned.

    Is there some way to pitch and yaw minecarts using just Bukkit API, or will I have to dig into NMS/CraftBukkit stuff? I would like to avoid having to rewrite my plugin every time CraftBukkit updates.
     
  2. Offline

    nisovin

    If setting yaw doesn't work, I doubt it will be possible even if you dig through NMS stuff.
     
  3. I suppose bergerkiller has a story to tell for you (he's the expert with minecarts).
    Basically, trying to access minecart physics is a huge hassle, they just glitch around and don't do what you want. You have to dig reaaaally deep into NMS to gain control over how they behave.
    MineCars probably did that anyway to achieve what it does, so if you want to change the functionality in there and it's open source, take a look at how the rest of the plugin looks, you might find some hooks pre-built for you.
     
  4. Offline

    bergerkiller

    Generally minecart yaw works as expected, but in the physics function (onTick) it recalculates the yaw of the minecart to look into the motion direction (motX/motZ look-at)

    So, you can set the yaw, but it will instantly get invalidated after the next tick. Your best bet is to override the yaw in the physics function (S_ or X_ or something, look up the native source). You can also override the 'setYawPitch' function, which is called to set the new yaw/pitch. This is all taking into account that you use your own nms EntityMinecart extension.

    Without and extension your are probably forced to re-set yaw in a tick task, or set it in the 'vehicle move' event.

    Note that the yaw when on rails is client-defined and can't be overridden.
     
  5. Offline

    azazad

    Thanks bergerkiller! You are indeed quite the expert on minecarts (saw the source of your TrainCarts plugin). I already figured it out though. I ended up extending net.minecraft.server.EntityMinecart and overriding the F_() method to run my own custom code every tick. Yaw and pitch are individually controllable, and as an added plus, I can apply rotationally transformed "friction" (friction in the forward axis is much less than friction in the sideways axis) to make my cars even more realistic.

    Here are the relevant tidbits of code I had to tear out:
    Code:
                if (d23 * d23 + d24 * d24 > 0.0010D) {
                    this.yaw = (float) (Math.atan2(d24, d23) * 180.0D / 3.141592653589793D);
                    if (this.f) {
                        this.yaw += 180.0F;
                    }
                }
    
    and... (this one was easy)
    Code:
     this.pitch = 0.0F;
    
    I also nuked all code relating to minecart tracks. This allows my cars to travel over railroad crossings without acting like minecarts. :D

    Just one problem though. Is there any way to spawn a rotated minecart? When I create an instance of the modified EntityMinecart, set the yaw, then add it to the world, the minecart always faces a specific cardinal direction. Even if I set yaw or pitch, the minecart always spawns facing the same way. Only after waiting at least one tick before setting yaw will the minecart turn. I hope you can help with this!

    bergerkiller

    Had to uninstall TrainCarts from my test server, due to a small technicality. I was testing to see whether my cars could coexist with your plugin's trains, and they can't, unfortunately. Your plugin seems to conflict with mine, because as soon as I spawn a car, your plugin converts it into a linkable minecart. I would hate to have to install only one plugin or the other :(

    UPDATE: I have modified my own copy of TrainCarts to ignore my plugin's cars (not convert them). This is not a very clean solution, and I would like to find another. There are probably other plugins that depend on custom extensions of EntityMinecart and need to be accommodated.

    EDIT by Moderator: merged posts, please use the edit button instead of double posting.
     
    Last edited by a moderator: May 27, 2016
  6. Offline

    bergerkiller

    azazad I can ignore your type of minecarts from within TrainCarts by adding it as a dependency. Let me know when the plugin is out so I can look up the class you use.

    When exactly should TrainCarts stop replacing your minecart extension? Always? Because if you, too, replace all minecarts, it would render either plugin useless.
     
  7. Offline

    azazad

    Nah, my plugin only replaces minecarts that people get into using a command, and turns them back into vanilla minecarts when the driver gets out. No mass minecart conversions here :D

    The only problem comes when I try to add the EntityDrivableMinecart to the world. The addEntity() method of nms.World broadcasts a VehicleCreateEvent that your plugin happens to listen to. By the time I do getBukkitEntity(), the minecart belongs to your plugin.
     
  8. Offline

    bergerkiller

    azazad Hmm I think I know a simple workaround to fix this. I will make sure it <only> converts minecarts of type 'EntityMinecart', not all instances extending it. That should resolve the issue.
     
  9. Offline

    azazad

    bergerkiller

    Something like...
    Code:
    if(((CraftMinecart)minecart).getHandle().getClass() == EntityMinecart.class){
       convert(minecart);
    }
    
    would work great.

    Also, something else: I am thinking about replacing players' NetServerHandlers at runtime so that minecart positions are updated every tick instead of every 3 ticks for smoother movement. I have to do it, or otherwise driving the minecarts is a glitchy spastic affair. Let's keep in touch on how to resolve this conflict too :D
     
  10. Offline

    bergerkiller

    azazad do not do that. Instead, simply set the 'EntityTracker' in the world. That is what I do too, and you have absolute control then.

    Do note that the client smooths movement, so increasing the amount of updates per tick won't necessarily make it look better. You can, though, decrease the minimum movement delta from 4 to less than that to have a more precise motion...but it's not always useful.
     
  11. Offline

    azazad

    bergerkiller

    Aww... I was hoping that sending more frequent movement updates would solve the problems with steering.

    Players are supposed to steer the minecarts by yawing their cursor left and right. The plugin calculates the difference between driver and vehicle yaw and turns the minecart appropriately. Unfortunately, the steering seems to get into a vicious positive feedback loop and spins the minecart in one direction forever until I eject. Moving the cursor ever so slightly to the right causes an irreversable clockwise spin, and vice versa. Even if you quickly swing your mouse in the opposite direction, there is no stopping the spin once it starts.

    I think it has something to do with the fact that entities spin with their vehicles. I really hope that this isn't client-side and can be nuked with some net.minecraft.server magic :D

    Do you know any way to keep a player facing the same absolute direction even when their vehicle spins?

    EDIT: Of course I can always send a Packet32EntityLook. It will be a bit too jerky, though.
     
  12. Offline

    bergerkiller

    azazad make sure you calculate the vehicle yaw correctly. When it hits a certain yaw, add 360 degrees. There was also a slightly confusing matter with the direction, which adds/subtracts 180 degrees.
     
  13. Offline

    azazad

    bergerkiller

    I already normalize the yaw to between 0 and 360 degrees anyways. What do you mean by add/subtract 180 degrees? When the minecart turns around?
     
  14. Offline

    bergerkiller

    azazad exactly. There is this 'reverse' idea the minecart uses. If you don't take account of that, it can get nasty. Check the source code near the rotation calculation, there is a property (boolean) that checks if it is reversing.
     
  15. Offline

    azazad

    bergerkiller

    Yep, already saw that reversing code:
    Code:
                if (d23 * d23 + d24 * d24 > 0.0010D) {
                    this.yaw = (float) (Math.atan2(d24, d23) * 180.0D / 3.141592653589793D);
                    if (this.f) {
                        this.yaw += 180.0F;
                    }
                }
    
    Took it out along with the rest of the enclosing if statement. :D
    Right now, this is the code for the extended EntityMinecart class:
    Code:
    package mk.drivable;
     
    import java.util.List;
    import net.minecraft.server.EntityPlayer;
    import org.bukkit.Location;
    import org.bukkit.World;
    import org.bukkit.craftbukkit.CraftWorld;
    import org.bukkit.entity.Vehicle;
     
    public class EntityDrivableMinecart extends net.minecraft.server.EntityMinecart{
        private int ticksNoTurn = 0;
       
        public EntityDrivableMinecart(World world){
            super(((CraftWorld)world).getHandle());
        }
       
        @Override
        public void F_(){
            // CraftBukkit start
            double prevX = this.locX;
            double prevY = this.locY;
            double prevZ = this.locZ;
            float prevYaw = this.yaw;
            float prevPitch = this.pitch;
            // CraftBukkit end
     
            if (this.m() > 0) {//Wobble animation decay
                this.d(this.m() - 1);
            }
     
            if (this.getDamage() > 0) {//Heal
                this.setDamage(this.getDamage() - 1);
            }
     
            if (this.locY < -64.0D) {//Kill if in the void
                this.aI();
            }
     
            this.lastX = this.locX;
            this.lastY = this.locY;
            this.lastZ = this.locZ;
           
            double thrustFwd = 0.0d;
            double thrustSide = 0.0d;
            float turn = 0.0f;
           
            if(this.passenger != null && this.passenger instanceof EntityPlayer){
                EntityPlayer driver = (EntityPlayer)this.passenger;
               
                //Acceleration
                boolean accelerate = driver.isSneaking();
                if(accelerate) thrustFwd += 0.05;
               
                //Turning
                if(ticksNoTurn == 0){
                    float yawDiff = driver.yaw - this.yaw + 90;
                    turn = (float)(20.0f * Math.sin(yawDiff * Math.PI / 360));
                    driver.getBukkitEntity().sendMessage("driver.yaw="+Math.round(driver.yaw)+", this.yaw="+Math.round(this.yaw)+", yawDiff="+yawDiff+", turn="+turn);
                    ticksNoTurn = 10;
                }
               
                if(ticksNoTurn > 0) ticksNoTurn--;
            }
         
            //Apply the rotationally transformed thrust
            double rYaw = Math.toRadians(this.yaw);
            this.motX += thrustFwd * Math.cos(rYaw) + thrustSide * Math.sin(rYaw);
            this.motZ += thrustFwd * Math.sin(rYaw) + thrustSide * Math.cos(rYaw);
           
            this.motY -= 0.03999999910593033D;//"Gravity"
           
            if(this.onGround){//Ground friction
                this.motX *= 0.95f;
                this.motY *= 0.95f;
                this.motZ *= 0.95f;
            }
           
            this.move(this.motX, this.motY, this.motZ);//Attempt the movement, check for collision
           
            //Air resistance
            this.motX *= 0.99f;
            this.motY *= 0.99f;
            this.motZ *= 0.99f;
           
            //Turning
            this.yaw += turn;
            this.c(this.yaw,this.pitch);
           
            // CraftBukkit start
            org.bukkit.World bworld = this.world.getWorld();
            Location from = new Location(bworld, prevX, prevY, prevZ, prevYaw, prevPitch);
            Location to = new Location(bworld, this.locX, this.locY, this.locZ, this.yaw, this.pitch);
            Vehicle bukkitVehicle = (Vehicle) this.getBukkitEntity();
           
            this.world.getServer().getPluginManager().callEvent(new org.bukkit.event.vehicle.VehicleUpdateEvent(bukkitVehicle));
           
            if (!from.equals(to)) {
                this.world.getServer().getPluginManager().callEvent(new org.bukkit.event.vehicle.VehicleMoveEvent(bukkitVehicle, from, to));
            }
            // CraftBukkit end
         
     
            List list = this.world.getEntities(this, this.boundingBox.grow(0.20000000298023224D, 0.0D, 0.20000000298023224D));
           
            if (list != null && list.size() > 0) {
                for (int l1 = 0; l1 < list.size(); ++l1) {
                    net.minecraft.server.Entity entity = (net.minecraft.server.Entity) list.get(l1);
     
                    if (entity != this.passenger && entity.e_() && entity instanceof net.minecraft.server.EntityMinecart) {
                        entity.collide(this);
                    }
                }
            }
     
            if (this.passenger != null && this.passenger.dead) {
                if (this.passenger.vehicle == this) {
                    this.passenger.vehicle = null;
                }
     
                this.passenger = null;
            }
        }
       
        @Override
        public DrivableMinecart getBukkitEntity(){
            if(this.bukkitEntity == null) this.bukkitEntity = new DrivableMinecart(this.world.getServer(),this);
            return (DrivableMinecart)this.bukkitEntity;
        }
    }
    
    As you can see, it is very minimalistic and pared down to only the essentials. All track following code has been removed. Unless "this.f" is accessed in some other class, I see no reason it would cause any problems.
    I will be downloading CraftBukkit's source so I can open it up in my IDE and find usages.
     
  16. Offline

    bergerkiller

  17. Offline

    azazad

    bergerkiller

    This is becoming a one-on-one conversation, so we should probably use PMs. I will send you one shortly :D
     
  18. Offline

    LucasEmanuel

    Aw, but i find this thread very interesting :)
     
  19. To set the direction get the minecarts location .getLocation(). Then .setYaw() to whatever you like. Then teleport the minecart to that location .teleport(newloc) !!!
     
Thread Status:
Not open for further replies.

Share This Page