Bukkit#unloadWorld() returns false

Discussion in 'Plugin Development' started by eyris, Nov 22, 2022.

  1. Offline

    eyris

    Hello people,

    I just started playing around with Minecraft modding a few days ago.
    Bukkit seemed to be a fairly fleshed out API to build against, so I have tried to do that.
    I am using spigot.

    I currently am working on trying to allow a player to generate a new world and teleport there, discarding the old one completely (unloading it and wiping that world folder). In essence, it is like launching another seed because you f'd up on the last one.

    I know that I can find mods that are already built and just use them, but I am really just trying to develop understanding. I also know that there are better ways to do what I am doing. I am open to see those. Nevertheless, I would like to try to push forward with what I have and get it to work in order to understand why this particular issue I have is occurring so that I can extract some understanding out of it.

    Anyways, here is what I do:

    1. Server starts, generates "world"
    2. Player types /resetworld in console
    3. Player is teleported to "lobby" (a waiting room while the new world is being generated)
    4. Unload "world" and recursively delete the "world" folder so that I can create another World named "world"
    5. Create "world" again and then teleport player to "world"
    6. Unload "lobby"

    Currently, the issue is #4. Here is my code.
    Code:
       @Override
        public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
            this.plugin = Main.getPlugin();
            String carnage_world_name = "world";
            String lobby = "lobby";
            World delete = Bukkit.getWorld(carnage_world_name);
            if (command.getName().equalsIgnoreCase("resetWorld")) {
                // teleport players to lobby
                if (sender instanceof Player) {
                    sendAllPlayersToNewWorld(lobby);
                    clearAll(); // when teleporting players to new world, entities from old world are not cleared, so just clearing them all out
    
                    // Unload old world from memory?
                    boolean status = plugin.getServer().unloadWorld(delete, false); // ISSUE
                    boolean status_bukkit = Bukkit.unloadWorld(delete, false); // ISSUE
    
    
                    // TEST - UNLOAD returning false. Examine why (https://bukkit.org/threads/unloadworld-returning-false.92367/)
                    System.out.println("Status: " + status);
                    System.out.println("Bukkit Status: " + status_bukkit);
    
                    System.out.println("World null? " + plugin.getServer().getWorlds());
                    System.out.println("Players 0? " + delete.getPlayers().size());
                    System.out.println("Entities 0? " + delete.getEntities().size());
    
    //                // Delete old world files. Since freed from memory, should delete everything??
    //                File deleteFolder = delete.getWorldFolder(); // this does not work if unloadworld for carnage is not done. does it work after?
    //                delete = null; // do i have to actually clear references manually?
    //                deleteDirectory(deleteFolder);
    //                sendAllPlayersToNewWorld(carnage_world_name);
    //
    //                // Unload lobby
    //                plugin.getServer().unloadWorld(lobby, false);
                }
            }
            return true;
        }
    
        public void sendAllPlayersToNewWorld(String world_name) {
            if (plugin.getServer().getWorld(world_name) == null) {
                plugin.getServer().createWorld(new WorldCreator(world_name));
            }
            for (Player p : plugin.getServer().getOnlinePlayers()) {
                p.teleport(plugin.getServer().getWorld(world_name).getSpawnLocation());
            }
        }
    
        public void clearAll() {
            World w =  getServer().getWorld("world");
            for(Chunk c : w.getLoadedChunks()) {
                c.load();
                for(Entity e : c.getEntities()) {
                    e.remove();
                }
                c.unload(true);
            }
        }
    Bukkt#unloadWorld returns false.

    I tried to find the source code for Bukkit#unloadWorld(), but only found an interface (https://github.com/Bukkit/Bukkit/blob/master/src/main/java/org/bukkit/Server.java). I really want to see what Bukkit#unloadWorld() is exactly doing so that I can see what is going wrong.

    The closest I came to this was the following thread: https://bukkit.org/threads/unloadworld-returning-false.92367/. This post notes that CraftBukkit has source code for unloadWorld:

    Code:
    public boolean unloadWorld(World world, boolean save) {
            if (world == null) {
                return false;
            }
    
            WorldServer handle = ((CraftWorld) world).getHandle();
    
            if (!(console.worlds.contains(handle))) {
                return false;
            }
    
            if (!(handle.dimension > 1)) {
                return false;
            }
    
            if (handle.players.size() > 0) {
                return false;
            }
    
            WorldUnloadEvent e = new WorldUnloadEvent(handle.getWorld());
            pluginManager.callEvent(e);
    
            if (e.isCancelled()) {
                return false;
            }
    
            if (save) {
                try {
                    handle.save(true, null);
                    handle.saveLevel();
                } catch (ExceptionWorldConflict ex) {
                    getLogger().log(Level.SEVERE, null, ex);
                }
            }
          ....
    }
    I wanted to try to use CraftWorld#getHandle() so that I could also do it this way, but I do not directly have CraftBukkit jar in my directory. I do use Spiggot, which as I have read, is a fork of CraftBukkit. I am unsure if the code above is relevant.

    When I run my code, I check the player count and entity count; they are 0 (the system.outs are shown in the png). I also check for an unloadWorld event in another file (the forum I linked above does this). I am quite confused here because using intuition, unloadWorld seems like it would do all this already (clear entities, chunks,...).

    My directory structure is your normal directory structure for a basic server.
    Code:
    server_folder
        ymls, props, configs,...
        world directory (this is what is being regenerated everytime)
        lobby directory (assume that this is never regenerated from scratch)
    
    
    Perhaps it relates to old references? I am not sure. I will go through and refactor to see if my plugin is holding old references and then update this post with the status (after work).

    I also have seen others say that you cannot unload the "main world", which I assume means the world that is specified in server.properties.

    I can provide more info as requested. I am not sure what else to provide.
     
    Last edited: Nov 22, 2022
  2. Offline

    timtower Administrator Administrator Moderator

    I am gonna go with this one as well.
     
  3. Offline

    eyris

    Easy solution would just to make the lobby main and then they type a command to go to the practice session.
    Then in practice session /resetworld -> teleport to lobby while new world generates -> teleport back to practice session.
     
    timtower likes this.

Share This Page