Commands blocks can’t create or unload worlds
willkroboth opened this issue · 20 comments
On my server, I'm trying to make a minigame that takes place in an entire survival world. One thing I can't figure out how to do is creating or reloading the world so that each round has a random seed and nothing from previous rounds affects any of the others. Looking through the command reference for Multiverse-Core, I found the mvregen command. This seemed to be exactly what I need to do, so I made a command in my commands.yml like this:
aliases:
regenMinigame:
- mvregen world -s
- mvconfirm
- mvregen world_nether -s
- mvconfirm
- mvreg world_end -s
- mvconfirm
Running the regenMinigame command seems to work perfectly fine when run by a player or even from the console, but when I run the command in a command block (which I need to do for it to be automatic), the sever crashes and it creates this crash log:
crash-2021-02-13_12.40.13-server.txt
When I restart and rejoin the server, I find that the worlds have been regenerated as I wanted. Is there any way to do what I want to do without crashing my sever? If you want any more information about the server, feel free to ask.
Okay, so PaperMC/Paper#6072 addressed the issue where trying to create a world while the worlds were being ticked would cause a crash. Now, when you try to run /mvcreate
in a command block, you are stopped by the IllegalStateException
, and the server does not crash. However, there is a similar issue with unloading a world while the worlds were being ticked. /mvregen
and /mvdelete
unload worlds, so if they are run in a command block, they are still crashing the server. I've opened a new issue (PaperMC/Paper#8080), that should hopefully accomplish the same thing as PaperMC/Paper#6072 but for the worlds being unloaded.
Even after that is fixed, I don't think this issue should be closed. I propose that if a command like /mvcreate
or /mvregen
is called by a command block, the world unloading/deleting should be scheduled to happen once the worlds are no longer being ticked. This would avoid the problems with unloading/deleting worlds while they are being ticked, while still allowing worlds to be managed automatically by command blocks. I think the point of Paper throwing the IllegalStateException
is to document that plugins should not be trying to create or unload worlds while the worlds are being ticked, not just to avoid the server crash that follows.
It seems this issue has gone stale. If it helps, I recently tried doing this again to see if any updates had fixed the issue. Unfortunately, it still crashed, but I noticed this message in the console log: java.lang.IllegalStateException: Cannot create a world while worlds are being ticked
. This suggests that, at least now, the command block causes the crash because it tries to create a world at a bad time. A player or the console reloading the world is fine because it happens outside of the worlds being ticked. Maybe this bug could be fixed by making these problematic commands schedule the world creation to happen at a better time when the worlds are not being ticked?
Here's the console log: latest.log
and crash report: crash-2022-06-27_12.08.05-server.txt
If anyone wants to try to recreate this, here is my server information:
This server is running Paper version git-Paper-379 (MC: 1.18.2) (Implementing API version 1.18.2-R0.1-SNAPSHOT) (Git: 276d830)
Plugins (21): CommandAPI, ConfigCommands, customteleports, dynmap, GrapplingHook, LuckPerms, Multiverse-Core, Multiverse-Inventories, Multiverse-NetherPortals, Multiverse-Portals, NickNames*, OnePlayerSleep, PluginConstructorAPI, ScheduledRestart, ServerRestorer, TpLogin*, TrackingCompass, TreysDoubleJump, VoidGen, WorldEdit, WorldGuard
I think the only plugins that matter are Multiverse, ConfigCommands, and CommandAPI. I don't know if the version matters, but I have Multiverse-Core Version: 4.3.1-b861
and CommandAPI version 7.0.0. ConfigCommands is a plugin I am developing, and you can download the jar I was using here. In the config file for ConfigCommands, put something like this:
debug: true
commands:
regenWorld:
args:
- name: <world>
type: String
commands:
- do <sender>.sendMessage("Regenerating ".join(<world>).join("..."))
- /mvregen <world> -s
- do <sender>.sendMessage(<sender>.dispatchCommand("mvconfirm"))
- do <sender>.sendMessage("Done!")
name: regenworld
Now, if you run /regenworld [name of the world]
as a player, it works, but in a command block, it crashes.
It was concluded as a server software limitation, see: PaperMC/Paper#6072
So should this issue be closed or will the Multiverse code need to change so that it never tries to create a world while the worlds are being ticked? It seems that the server crashes anyways despite the paper fix, so I think this issue is still not resolved.
Ok got this working - Used a BukkitRunnable and cancel it after one iteration after just waiting 10 ticks, shown below.
Obviously doesn't work for command blocks, so for now at least a semi workaround? Could develop a plugin with a custom command to use this code instead.
ConsoleCommandSender console = Bukkit.getServer().getConsoleSender();
String cmdConfirm = "mv confirm";
new BukkitRunnable() {
@Override
public void run() {
//Delete Worlds
Bukkit.dispatchCommand(console, "mvdelete Challenge");
Bukkit.dispatchCommand(console, cmdConfirm);
//Create Worlds
Bukkit.dispatchCommand(console, "mvcreate Challenge normal");
//Cancel BukkitRunnable
cancel();
}
}.runTaskTimer(this, 10L, 100L);
Well, on Paper servers at least the server shouldn't crash anymore when running these commands in a command block if you have the latest version. Spigot servers will still crash, which I'm hoping will be resolved by SPIGOT-7089.
While not accidentally causing the server to crash is good, the command blocks are still not able to unload/create worlds. Since this issue hasn't been closed yet I might assume it is being worked on, though we'll have to see.
I haven't found any way around this yet. It makes sense to not allow removing or creating worlds while they are being looped through, so you would probably need some way to delay command execution until when the worlds are no longer being ticked. The Multiverse code probably needs to change to make this happen.
Obviously doesn't work for command blocks
What do you mean that this doesn't work for command blocks? I tried this out myself and I was able to trigger the world regeneration from a command block. Here's the code I used:
package me.willkroboth.TestMainPlugin;
import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.scheduler.BukkitRunnable;
public class TestMainPlugin extends JavaPlugin {
public void onEnable() {
getCommand("regenfromcommandblock").setExecutor(this);
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (args.length != 1) return false;
String world = args[0];
new BukkitRunnable() {
public void run() {
//Regen World
Bukkit.dispatchCommand(sender, "mvregen " + world + " -s");
Bukkit.dispatchCommand(sender, "mvconfirm");
}
}.runTask(this);
return true;
}
}
Using BukkitRunnable#runTask
, you also don't need to put a delay or worry about canceling the runnable; it will just be run once in the next tick - importantly at a time when the worlds are not being ticked. What's neat is that you can even preserve the sender, even if it is a command block, so all the "blame" for the world regeneration goes to the original sender and not the console.
Oh awesome! Thanks for the information, still relatively new to Spigot plugin coding. 😄
I tried it out and using a BukkitRunnable like this to delay code until the worlds are no longer being ticked looks compatible with the Multiverse code! I think I got mvregen
working with command blocks and should have a pull request done soon.
Actually, I realized that I can't do anything until SPIGOT-7089 resolves. What I was trying to do was this:
try {
attemptWorldRegen(sender, worldName, useSeed, randomSeed, seed, keepGamerules);
} catch (IllegalStateException ignored){
Logging.fine("Worlds were being ticked when attempting to regenerate %s. Trying again in the next tick.", worldName);
// delay second attempt until the worlds are no longer being ticked
new BukkitRunnable() {
public void run() {
attemptWorldRegen(sender, worldName, useSeed, randomSeed, seed, keepGamerules);
}
}.runTask(plugin);
}
If regenerating the world caused an IllegalStateException because the worlds were being ticked, a BukkitRunnable would be created to try regenerating the world again once the worlds were not being ticked. This doesn't change anything if the worlds are not being ticked but successfully triggers the world regeneration at a better time if the worlds are being ticked. The problem is that Spigot servers don't throw this error (or do something else to stop the crash) yet. This code works on a Paper server, but there is still no way to check if the worlds are being ticked on a Spigot server until it is too late and the server is crashing.
If there was some way to tell if the worlds were being ticked on a Spigot server, something like this would work.
if(worldsAreNotBeingTicked){
modifyWorlds();
} else {
new BukkitRunnable() {
public void run() {
modifyWorlds();
}
}.runTask(plugin);
}
Edit: Nevermind I see the issue isn't solved by this, apologies
Afaik, I got around the ticking error in paper by just letting the bukkit runnable use a runTaskTimer that waits 10L instead of running instantly, maybe this could get around the Spigot error?
Ignore the specifics but something like this?
ConsoleCommandSender console = Bukkit.getServer().getConsoleSender();
String cmdConfirm = "mv confirm";
new BukkitRunnable() {
@Override
public void run() {
//Broadcast
Bukkit.getServer().broadcastMessage(ChatColor.GOLD + "[WorldResetter]: " + ChatColor.WHITE + "World recreation starting now... please wait");
inReset = false;
//Delete Worlds
Bukkit.dispatchCommand(console, "mvdelete Challenge");
Bukkit.dispatchCommand(console, cmdConfirm);
Bukkit.dispatchCommand(console, "mvdelete Challenge_nether");
Bukkit.dispatchCommand(console, cmdConfirm);
Bukkit.dispatchCommand(console, "mvdelete Challenge_the_end");
Bukkit.dispatchCommand(console, cmdConfirm);
//Create Worlds
Bukkit.dispatchCommand(console, "mvcreate Challenge normal");
Bukkit.dispatchCommand(console, "mvcreate Challenge_nether nether");
Bukkit.dispatchCommand(console, "mvcreate Challenge_the_end end");
//Cancel BukkitRunnable
Bukkit.getServer().broadcastMessage(ChatColor.GOLD + "[WorldResetter]: " + ChatColor.GREEN + "World recreation finished! Use the command '/start' to begin the challenge!");
cancel();
}
}.runTaskTimer(this, 10L, 0L);
Spigot-7089 has been resolved! On the latest versions of Spigot, command blocks can load and unload worlds with no problems.
Unfortunately, the fixes added by PaperMC/Paper#7653 and PaperMC/Paper#8081 are still in effect, so Paper servers are still trying to avoid the crash by throwing an IllegalStateException. I'll close this issue once that gets resolved and Paper embraces or modifies the Spigot fix (watching PaperMC/Paper#8300).
Update: PaperMC/Paper#8300 has been resolved as: plugins have to deal with this difference between Spigot and Paper.
So Spigot currently allows worlds to be loaded/unloaded while the worlds are being ticked, while Paper does not. For this issue, that means that command blocks can create or unload worlds on Spigot, but not on Paper. I'm currently working on a PR that will fully resolve this issue by switching behavior based on whether or not the Paper patches are active.
(Final?) update: Paper has decided to align with Spigot, at least for now, on the world load/unload issue. In this context, that means that command blocks can now create and unload worlds on the latest versions of both Spigot and Paper without having to modify Multiverse. I'll leave the pull request open since the Paper devs asked to consider not loading/unloading worlds while being ticked, but that's not necessary to resolve this issue.
I have finished dealing with the world load/unload difference between Spigot and Paper. The summary of that is this new method:
private boolean safeToAddOrRemoveWorld(){
Server server = Bukkit.getServer();
Logging.finest("Using reflection to test for Paper build after PR #7653");
try {
// basically doing ((CraftServer) Bukkit.getServer()).getServer().isIteratingOverLevels;
Method getConsole = server.getClass().getMethod("getServer");
Object console = getConsole.invoke(server);
Field isTickingWorlds = console.getClass().getField("isIteratingOverLevels");
boolean isTicking = isTickingWorlds.getBoolean(console);
Logging.finest("Paper fix active");
return !isTicking;
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
Logging.finest("%sUnexpected exception: %s", ChatColor.RED, e.getMessage());
Logging.finest("Assuming Paper fix is inactive");
// If the Paper fix actually is active it should become obvious when Paper complains
// about a world being loaded/unloaded while being ticked
// If that happens, this method needs to be fixed
return true;
} catch (NoSuchFieldException ignored) {
// Expected to fail when field isIteratingOverLevels doesn't exist
// Therefore, Paper fixes aren't active, so it is always considered safe to proceed
Logging.finest("Paper fix inactive");
return true;
}
}
This method uses reflection to check first if Paper's fix is active, and if so whether or not it is currently safe to load or unload worlds. I think this is a good enough solution considering the perspectives of the Paper devs.
With my PR I think this issue should be resolved once the Multiverse devs pull it in. I'm not sure what to expect with that so we'll just have to wait and see.
I'll be looking into this soon. ben pointed out that this is similar to #2167, and I agree with him that this is most likely the same issue.
Here you go: https://j.mp/3pF1hMY
Note: In my first post I called the worlds minigame, but they are actually called manHunt
[08:00:15] [Server thread/INFO]: [Multiverse-Core] World 'world2' was unloaded from Multiverse.
[08:00:35] [Server thread/INFO]: ThreadedAnvilChunkStorage (world2): All chunks are saved
[08:00:35] [Server thread/INFO]: [Multiverse-Core] World 'world2' was unloaded from Bukkit.
[08:00:35] [Server thread/INFO]: CONSOLE: Unloaded world 'world2'!�[m
[08:00:35] [Server thread/INFO]: CONSOLE: Unloaded world 'world2'!�[m
[08:00:35] [Server thread/INFO]: CONSOLE: Unloaded world 'world2'!�[m
[08:00:35] [Server thread/INFO]: CONSOLE: Unloaded world 'world2'!�[m
[08:00:35] [Server thread/INFO]: CONSOLE: Unloaded world 'world2'!�[m
[08:00:35] [Server thread/INFO]: -------- World Settings For [world2] --------
[08:00:35] [Server thread/INFO]: Experience Merge Radius: 3.0
[08:00:35] [Server thread/INFO]: Mob Spawn Range: 6
[08:00:35] [Server thread/INFO]: Cactus Growth Modifier: 100%
[08:00:35] [Server thread/INFO]: Cane Growth Modifier: 100%
[08:00:35] [Server thread/INFO]: Melon Growth Modifier: 100%
[08:00:35] [Server thread/INFO]: Mushroom Growth Modifier: 100%
[08:00:35] [Server thread/INFO]: Pumpkin Growth Modifier: 100%
[08:00:35] [Server thread/INFO]: Sapling Growth Modifier: 100%
[08:00:35] [Server thread/INFO]: Beetroot Growth Modifier: 100%
[08:00:35] [Server thread/INFO]: Carrot Growth Modifier: 100%
[08:00:35] [Server thread/INFO]: Potato Growth Modifier: 100%
[08:00:35] [Server thread/INFO]: Wheat Growth Modifier: 100%
[08:00:35] [Server thread/INFO]: NetherWart Growth Modifier: 100%
[08:00:35] [Server thread/INFO]: Vine Growth Modifier: 100%
[08:00:35] [Server thread/INFO]: Cocoa Growth Modifier: 100%
[08:00:35] [Server thread/INFO]: Bamboo Growth Modifier: 100%
[08:00:35] [Server thread/INFO]: SweetBerry Growth Modifier: 100%
[08:00:35] [Server thread/INFO]: Kelp Growth Modifier: 100%
[08:00:35] [Server thread/INFO]: Entity Activation Range: An 32 / Mo 32 / Ra 48 / Mi 16 / Tiv true
[08:00:35] [Server thread/INFO]: Hopper Transfer: 8 Hopper Check: 1 Hopper Amount: 1
[08:00:35] [Server thread/INFO]: Custom Map Seeds: Village: 10387312 Desert: 14357617 Igloo: 14357618 Jungle: 14357619 Swamp: 14357620 Monument: 10387313 Ocean: 14357621 Shipwreck: 165745295 End City: 10387313 Slime: 987234911 Bastion: 30084232 Fortress: 30084232 Mansion: 10387319 Fossil: 14357921 Portal: 34222645
[08:00:35] [Server thread/INFO]: Max TNT Explosions: 100
[08:00:35] [Server thread/INFO]: Tile Max Tick Time: 50ms Entity max Tick Time: 50ms
[08:00:35] [Server thread/INFO]: Entity Tracking Range: Pl 48 / An 48 / Mo 48 / Mi 32 / Other 64
[08:00:35] [Server thread/INFO]: Allow Zombie Pigmen to spawn from portal blocks: true
[08:00:35] [Server thread/INFO]: Item Despawn Rate: 6000
[08:00:35] [Server thread/INFO]: Item Merge Radius: 2.5
[08:00:35] [Server thread/INFO]: View Distance: 10
[08:00:35] [Server thread/INFO]: Arrow Despawn Rate: 1200 Trident Respawn Rate:1200
[08:00:35] [Server thread/INFO]: Zombie Aggressive Towards Villager: true
[08:00:35] [Server thread/INFO]: Nerfing mobs spawned from spawners: false
[08:00:35] [Server thread/INFO]: Preparing start region for dimension minecraft:world2
[08:00:35] [Worker-Main-17/INFO]: Preparing spawn area: 0%
[08:00:36] [Worker-Main-6/INFO]: Preparing spawn area: 0%
[08:00:36] [Worker-Main-17/INFO]: Preparing spawn area: 0%
[08:00:37] [Worker-Main-17/INFO]: Preparing spawn area: 0%
[08:00:37] [Worker-Main-17/INFO]: Preparing spawn area: 0%
[08:00:38] [Worker-Main-6/INFO]: Preparing spawn area: 1%
[08:00:38] [Worker-Main-6/INFO]: Preparing spawn area: 5%
[08:00:39] [Worker-Main-17/INFO]: Preparing spawn area: 10%
[08:00:39] [Worker-Main-6/INFO]: Preparing spawn area: 12%
[08:00:40] [Worker-Main-6/INFO]: Preparing spawn area: 12%
[08:00:40] [Worker-Main-6/INFO]: Preparing spawn area: 12%
[08:00:41] [Worker-Main-18/INFO]: Preparing spawn area: 12%
[08:00:41] [Worker-Main-18/INFO]: Preparing spawn area: 14%
[08:00:42] [Worker-Main-17/INFO]: Preparing spawn area: 19%
[08:00:42] [Worker-Main-18/INFO]: Preparing spawn area: 26%
[08:00:43] [Worker-Main-18/INFO]: Preparing spawn area: 44%
[08:00:43] [Worker-Main-6/INFO]: Preparing spawn area: 60%
[08:00:44] [Worker-Main-18/INFO]: Preparing spawn area: 79%
[08:00:44] [Worker-Main-17/INFO]: Preparing spawn area: 96%
[08:00:44] [Server thread/INFO]: Time elapsed: 9299 ms
[08:00:44] [Server thread/INFO]: CONSOLE: Loaded world 'world2'!�[m
[08:00:44] [Server thread/INFO]: CONSOLE: Loaded world 'world2'!�[m
[08:00:44] [Server thread/INFO]: CONSOLE: Loaded world 'world2'!�[m
[08:00:44] [Server thread/INFO]: CONSOLE: Loaded world 'world2'!�[m
[08:00:44] [Server thread/INFO]: CONSOLE: Loaded world 'world2'!�[m
[08:00:44] [Server thread/INFO]: CONSOLE: Loaded world 'world2'!�[m
[08:00:44] [Server thread/INFO]: CONSOLE: Loaded world 'world2'!�[m
[08:00:44] [Server thread/INFO]: CONSOLE: Loaded world 'world2'!�[m
[08:00:44] [Server thread/INFO]: CONSOLE: Loaded world 'world2'!�[m
[08:00:44] [Server thread/INFO]: CONSOLE: Loaded world 'world2'!�[m
[08:00:44] [Server thread/INFO]: CONSOLE: Loaded world 'world2'!�[m
Maybe it is running a commad multiple times