Teleport entity including passenger

Discussion in 'Plugin Development' started by bergerkiller, Jan 21, 2012.

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

    bergerkiller

    Working on TrainCarts a bit more, and I am stuck at insta-teleporting entities cross-worlds.

    Since the native Bukkit teleport version fails for my case, I wrote my own:
    Code:
        public static void teleport(net.minecraft.server.Entity entity, Location to) {
            WorldServer newworld = ((CraftWorld) to.getWorld()).getHandle();
            Util.loadChunks(to);
            if (entity.world != newworld) {           
                //transfer entity cross-worlds
     
                //transfer passenger
                if (entity.passenger != null) {
                    //set out of vehicle?
                    net.minecraft.server.Entity passenger = entity.passenger;
                    entity.passenger = null;
                    passenger.vehicle = null;
                    teleport(passenger, to);
                    passenger.vehicle = entity;
                    entity.passenger = passenger;
                }
               
                //teleport this entity
                entity.world.removeEntity(entity);
                entity.dead = false;
                entity.world = newworld;
                entity.setLocation(to.getX(), to.getY(), to.getZ(), to.getYaw(), to.getPitch());
                entity.world.addEntity(entity);
                if (entity instanceof EntityPlayer) {
                    Util.getCraftServer().getHandle().moveToWorld((EntityPlayer) entity, newworld.dimension, true, to);
                }
               
            } else {
                entity.getBukkitEntity().teleport(to);           
            }
        }
    But with this coding the passenger is not sent to the other world correctly and is 'bugged': it can't interact but isn't in the vehicle either. It also doesn't see the vehicle he is in, move.

    How can I solve this issue? How can I [teleport AND enter] the vehicle to another world?

    I previously solved this by adding a scheduled task which made the passenger enter the vehicle, but right now it is important all of this is 100% instant. Anyone has an idea?

    For now I'm using:
    Code:
        public static void teleport(net.minecraft.server.Entity entity, Location to) {
            WorldServer newworld = ((CraftWorld) to.getWorld()).getHandle();
            Util.loadChunks(to);
            if (entity.world != newworld) {           
                //transfer entity cross-worlds
                if (entity.passenger != null) {
                    //set out of vehicle?
                    net.minecraft.server.Entity passenger = entity.passenger;
                    if (passenger instanceof EntityPlayer) {
                        new Task(TrainCarts.plugin, passenger, entity) {
                            public void run() {
                                EntityPlayer entity = (EntityPlayer) getArg(0);
                                net.minecraft.server.Entity vehicle = (net.minecraft.server.Entity) getArg(1);
                                entity.setPassengerOf(vehicle);
                            }
                        }.startDelayed(0);
                    }
                   
                    entity.passenger = null;
                    passenger.vehicle = null;
                    teleport(passenger, to);
                }
               
                //teleport this entity
                entity.world.removeEntity(entity);
                entity.dead = false;
                entity.world = newworld;
                entity.setLocation(to.getX(), to.getY(), to.getZ(), to.getYaw(), to.getPitch());
                entity.world.addEntity(entity);
                ((WorldServer) entity.world).tracker.track(entity);
               
                if (entity instanceof EntityPlayer) {
                    Util.getCraftServer().getHandle().moveToWorld((EntityPlayer) entity, newworld.dimension, true, to);
                }
               
            } else {
                entity.getBukkitEntity().teleport(to);           
            }
        }
    The task thing is my custom implementation to allow quick scheduling. You can replace it with a scheduled task instead.

    EDIT by Moderator: merged posts, please use the edit button instead of double posting.
     
    Last edited by a moderator: May 23, 2016
  2. Thanks a lot. I use a modified version of this in V10verlap now. :)
     
  3. Offline

    bergerkiller

    V10lator glad I could be of help for you :)
     
    V10lator likes this.
  4. Offline

    bergerkiller

    For anyone still interested; here is the updated function I use right now:
    Code:
        public static void teleport(final net.minecraft.server.Entity entity, final Location to) {
            WorldServer newworld = ((CraftWorld) to.getWorld()).getHandle();
            Util.loadChunks(to);
            if (entity.world != newworld) {   
                if (entity.passenger != null) {
                    final net.minecraft.server.Entity passenger = entity.passenger;
                    passenger.vehicle = null;
                    entity.passenger = null;
                    teleport(passenger, to);
                    new Task(TrainCarts.plugin) {
                        public void run() {
                            passenger.setPassengerOf(entity);
                        }
                    }.start();
                }
     
                //teleport this entity
                if (entity instanceof EntityPlayer) {
                    Util.getCraftServer().getHandle().moveToWorld((EntityPlayer) entity, newworld.dimension, true, to);
                } else {
                    ((WorldServer) entity.world).tracker.untrackEntity(entity);
                    entity.world.removeEntity(entity);
                    entity.dead = false;
                    entity.world = newworld;
                    entity.setLocation(to.getX(), to.getY(), to.getZ(), to.getYaw(), to.getPitch());
                    entity.world.addEntity(entity);
                    ((WorldServer) entity.world).tracker.track(entity);
                }
            } else {
                entity.getBukkitEntity().teleport(to);           
            }
        }
     
  5. bergerkiller I have a problem with a modified version of this: For performance and compatiblity reasons I added a check which checks if the entity is inside of a vehicle. If so it doesn't teleport as the modified version of your code should handle this already. This is the check I added:
    Code:java
    1. if(e instanceof LivingEntity && ((LivingEntity)e).isInsideVehicle())
    2. return;

    and this is the whole teleporting task:
    Code:java
    1. public void run()
    2. {
    3. Server s = plugin.getServer();
    4. World w = s.getWorld(world);
    5. Location loc;
    6. World to;
    7. int y;
    8. for(Chunk c: w.getLoadedChunks())
    9. {
    10. for(Entity e: c.getEntities())
    11. {
    12. if(e instanceof LivingEntity && ((LivingEntity)e).isInsideVehicle())
    13. return;
    14. UUID uuid = e.getUniqueId();
    15. if(plugin.cooldown.contains(uuid))
    16. return;
    17. loc = e.getLocation();
    18. y = loc.getBlockY();
    19. if(lowerWorld != null && y < minY)
    20. {
    21. to = s.getWorld(lowerWorld);
    22. if(to == null)
    23. continue;
    24. loc.setWorld(to);
    25. loc.setY(plugin.api.getMaxY(to) - 1);
    26. plugin.teleport(e, loc);
    27. plugin.cooldown.add(uuid);
    28. s.getScheduler().scheduleSyncDelayedTask(plugin, new CooldownTask(plugin.cooldown, uuid), 100L);
    29. }
    30. else if(upperWorld != null && y > maxY)
    31. {
    32. to = s.getWorld(upperWorld);
    33. if(to == null)
    34. continue;
    35. loc.setWorld(to);
    36. loc.setY(plugin.api.getMinY(to) + 1);
    37. plugin.teleport(e, loc);
    38. plugin.cooldown.add(uuid);
    39. s.getScheduler().scheduleSyncDelayedTask(plugin, new CooldownTask(plugin.cooldown, uuid), 100L);
    40. }
    41. }
    42. }
    43. }

    So if entity A is inside of vehicle B A isn't handled, but B calls the modified teleport function to teleport both. The telport function itself:
    Code:java
    1. void teleport(Entity e, Location to)
    2. {
    3. World w = to.getWorld();
    4. w.getChunkAt(to).load();
    5. if(e instanceof Player)
    6. {
    7. e.teleport(to);
    8. return;
    9. }
    10. net.minecraft.server.Entity entity = ((CraftEntity)e).getHandle();
    11. //transfer entity cross-worlds
    12. if(entity.passenger != null)
    13. {
    14. //set out of vehicle?
    15. net.minecraft.server.Entity passenger = entity.passenger;
    16. entity.passenger = null;
    17. passenger.vehicle = null;
    18. teleport(passenger.getBukkitEntity(), to);
    19. //getServer().getScheduler().scheduleSyncDelayedTask(this, new Teleporter(passenger, entity), 0l);
    20. }
    21. //teleport this entity
    22. ((WorldServer)entity.world).tracker.untrackEntity(entity);
    23. entity.world.removeEntity(entity);
    24. entity.dead = false;
    25. WorldServer newworld = ((CraftWorld)w).getHandle();
    26. entity.world = newworld;
    27. entity.setLocation(to.getX(), to.getY(), to.getZ(), to.getYaw(), to.getPitch());
    28. entity.world.addEntity(entity);
    29. ((WorldServer)entity.world).tracker.track(entity);
    30. }
    31.  
    32. private class Teleporter implements Runnable
    33. {
    34. private final net.minecraft.server.Entity e;
    35. private final net.minecraft.server.Entity v;
    36.  
    37. private Teleporter(net.minecraft.server.Entity e, net.minecraft.server.Entity v)
    38. {
    39. this.e = e;
    40. this.v = v;
    41. }
    42.  
    43. public void run()
    44. {
    45. e.setPassengerOf(v);
    46. }
    47. }

    First off: The scheduler is commented for debugging, but it didn't fix the bug, so it's unrelated.
    The bug itself is: The vehicle gets teleported just fine (from the world to the nether in that example) but the passenger gets teleported to The End...
     
  6. Offline

    bergerkiller

    V10lator Teleporting entities other than players doesn't work cross-world. I tried it with minecarts; instead of teleporting it will just sit there bugged out. That is why my teleport function handles both cases, and only uses the native one if it is on the same world.
     
  7. bergerkiller I know that, look at the code again: It uses the native teleport if the entity is a player and if not it uses a modified version of your code...
    Code:java
    1. if(e instanceof Player)
    2. {
    3. e.teleport(to);
    4. return;
    5. }


    //EDIT: Also note that it worked just fine before I added:
    Code:java
    1. if(e instanceof LivingEntity && ((LivingEntity)e).isInsideVehicle())
    2. return;


    Well, I added some debugging lines and noted that the player was teleported to the nether and from there instantly to the end. Don't know what exactly the problem was but playing a bit with the cooldown fixed it.

    //EDIT: BTW: This makes it possible to fly from one world to another with RideThaDragon + V10verlap, so thanks again. :D

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

    bergerkiller

    V10lator Yup, I use it in TrainCarts to teleport minecarts + passengers cross-world, that was the first reason I made it. It works for other <vehicle>/<passenger> relations too :)

    And, I will adapt my code for a bit more. This is because the player_teleport event is fired when using player.teleport(). If a plugin cancels that, it will cause a lot of issues because it will set the player passenger of a vehicle on another world. Maybe you have suggestions?

    Also, note that loadChunks() loaded a 5x5 chunk area around the entity. This is needed because the entity will be frozen otherwise. (no physics get called when it has insufficient chunks around)

    I now use this, it was a good suggestion to resume the player teleport if this is possible.
    Code:
        public static boolean teleport(final Plugin plugin, final net.minecraft.server.Entity entity, final Location to) {
            WorldServer newworld = ((CraftWorld) to.getWorld()).getHandle();
            WorldUtil.loadChunks(to, 2);
            if (entity.world != newworld && !(entity instanceof EntityPlayer)) {   
                if (entity.passenger != null) {
                    final net.minecraft.server.Entity passenger = entity.passenger;
                    passenger.vehicle = null;
                    entity.passenger = null;
                    if (teleport(plugin, passenger, to)) {
                        Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, new Runnable() {
                            public void run() {
                                passenger.setPassengerOf(entity);
                            }
                        });
                    }
                }
     
                //teleport this entity
                WorldUtil.getTracker(entity.world).untrackEntity(entity);
                entity.world.removeEntity(entity);
                entity.dead = false;
                entity.world = newworld;
                entity.setLocation(to.getX(), to.getY(), to.getZ(), to.getYaw(), to.getPitch());
                entity.world.addEntity(entity);
                WorldUtil.getTracker(entity.world).track(entity);
                return true;
            } else {
                return entity.getBukkitEntity().teleport(to);           
            }
        }
    EDIT by Moderator: merged posts, please use the edit button instead of double posting.
     
    Last edited by a moderator: May 23, 2016
  9. bergerkiller Good idea, even if I will change it slightly again:
    Code:java
    1. if(teleport(passenger.getBukkitEntity(), to))
    2. getServer().getScheduler().scheduleSyncDelayedTask(this, new Teleporter(passenger, entity), 0l);
    3. else
    4. {
    5. entity.passenger = passenger;
    6. passenger.vehicle = entity;
    7. return false;
    8. }

    Would be even nicer if we had a EntityTeleportEvent instead of the PlayerTeleportEvent...

    Are you sure that 5x5 chunks have to be loaded? I think minecraft itself uses just 3x3 and also I noticed no issues with just 1.

    Last but not least: Are you planning to port this directly into CB and to make a pull request? :)

    //EDIT: And another question: Doesn't the loaded chunk(s) has to be unloaded again? Like this:
    Code:java
    1. boolean teleport(Entity e, Location to)
    2. {
    3. World w = to.getWorld();
    4. w.getChunkAt(to).load();
    5. getServer().getScheduler().scheduleSyncDelayedTask(this, new ChunkUnloader(to), 1L);

    Code:java
    1. private class ChunkUnloader implements Runnable
    2. {
    3. private final Location to;
    4.  
    5. private ChunkUnloader(Location to)
    6. {
    7. this.to = to;
    8. }
    9.  
    10. public void run()
    11. {
    12. to.getChunk().unload(true, true);
    13. }
    14. }
     
  10. Offline

    MrMag518

  11. Offline

    bergerkiller

    MrMag518 I just exported a 92 kb library full of functions:
    I previously had these in separate plugins, but I thought it was better to make it a separate library.

    This gives you a nice idea of what is now possible for me to do. This shrinked the size of all of my plugins significantly. :)
    Code:
    package com.bergerkiller.bukkit.mw;
     
    import java.io.File;
     
    import org.bukkit.command.CommandSender;
    import org.bukkit.plugin.Plugin;
     
    import com.bergerkiller.bukkit.common.FileConfiguration;
    import com.bergerkiller.bukkit.common.PluginBase;
     
    public class MyWorlds extends PluginBase {
       
        public MyWorlds() {
            super(1595, 1846);
        }
     
        public static boolean usePermissions;
        public static int teleportInterval;
        public static boolean useWaterTeleport;
        public static int timeLockInterval;
        public static boolean useWorldEnterPermissions;
        public static boolean usePortalEnterPermissions;
        public static boolean useWorldTeleportPermissions;
        public static boolean usePortalTeleportPermissions;
        public static boolean useWorldBuildPermissions;
        public static boolean useWorldUsePermissions;
        public static boolean useWorldChatPermissions;
        public static boolean allowPortalNameOverride;
        public static boolean useWorldOperators;
        public static boolean onlyObsidianPortals = false;
        public static boolean isSpoutEnabled = false;
       
        public static MyWorlds plugin;
           
        public String root() {
            return getDataFolder() + File.separator;
        }
       
        @Override
        public void updateDependency(Plugin plugin, String pluginName, boolean enabled) {
            if (pluginName.equals("Spout")) {
                isSpoutEnabled = enabled;
            }
        }
       
        public void enable() {
            plugin = this;
     
            //Event registering
            this.register(MWListener.class);
            this.register("tpp", "world"); 
           
            FileConfiguration config = new FileConfiguration(this);
            config.load();
            usePermissions = config.get("usePermissions", false);
            teleportInterval = config.get("teleportInterval", 2000);
            useWaterTeleport = config.get("useWaterTeleport", true);
            timeLockInterval = config.get("timeLockInterval", 20);
            useWorldEnterPermissions = config.get("useWorldEnterPermissions", false);
            usePortalEnterPermissions = config.get("usePortalEnterPermissions", false);
            useWorldTeleportPermissions = config.get("useWorldTeleportPermissions", false);
            usePortalTeleportPermissions = config.get("usePortalTeleportPermissions", false);
            useWorldBuildPermissions = config.get("useWorldBuildPermissions", false);
            useWorldUsePermissions = config.get("useWorldUsePermissions", false);
            useWorldChatPermissions = config.get("useWorldChatPermissions", false);
            allowPortalNameOverride = config.get("allowPortalNameOverride", false);
            useWorldOperators = config.get("useWorldOperators", false);
            onlyObsidianPortals = config.get("onlyObsidianPortals", false);
            String locale = config.get("locale", "default");
            config.save();
           
            //Localization
            Localization.init(this, locale);
           
            //Permissions
            Permission.init(this);
           
            //Portals
            Portal.init(root() + "portals.txt");
     
            //World info
            WorldConfig.init(root() + "worlds.yml");
           
            //init chunk loader
            LoadChunksTask.init();
           
            //Chunk cache
            WorldManager.init();
        }
        public void disable() {
            //Portals
            Portal.deinit(root() + "portals.txt");
           
            //World info
            WorldConfig.deinit(root() + "worlds.yml");
           
            WorldManager.deinit();
            Localization.deinit();
            Permission.deinit();
           
            //Abort chunk loader
            LoadChunksTask.deinit();
           
            plugin = null;
        }
       
        public boolean command(CommandSender sender, String cmdLabel, String[] args) {
            com.bergerkiller.bukkit.mw.commands.Command.execute(sender, cmdLabel, args);
            return true;
        }
     
    }
    
    If anyone needs it, I uploaded the entire source code HERE

    I converted all my plugins to use this library instead of internal ones, even Stream Remover because of the configuration. If you want to use it, feel free to do so. :)

    Hehe...

    EDIT by Moderator: merged posts, please use the edit button instead of double posting.
     
    Last edited by a moderator: May 23, 2016
    Don Redhorse and MrMag518 like this.
Thread Status:
Not open for further replies.

Share This Page