options: #------------- Start of Configuration ------------- #recommended to have this as true for things which have to never save changes #NOTE: this will function only if the world has been edited since last reset, if it hasn't been edited, to save performance it will not do useless reset. YAY! ResetResettableWorldsOnRestart: true AutoDeleteClonesOnRestart: true #whether it should only reset "region" world folder contents and not other things like level.dat (It's cheaper to reset only region folder, but it doesn't reset world metadata, only the chunks, which may be not what you wish) ResetOnlyRegionFolder: true #whether /fwr create will look for existing world folder or allow to create a new blank one. #Creating blank world will not function for resetting (as there will be no template) until you update template by running /fwr create on the same world again. #useful for when you want to copypaste a map into a newly created world and then make it resettable #without having to go through another world management plugin to do it AllowCreatingNewWorlds: true #by default saving is only disabled in chunk-based reset worlds, not world-based reset ones. #this option disables world saving in all resettable worlds #NOTE: this option is not fool-proof, the world may still get saved under some conditions, it only helps in some cases DisableSavingInAllResettableWorlds: false #------------- End of Configuration ------------- #The script starts here, only edit if you know what you are doing. import: org.bukkit.Bukkit org.bukkit.WorldCreator java.lang.System java.io.File java.util.Date org.apache.commons.io.FileUtils org.apache.commons.io.comparator.LastModifiedFileComparator java.util.Arrays java.nio.file.Files java.nio.file.Paths ch.njol.skript.Skript on script load: if Skript.isRunningMinecraft(1, 13) is true: set {-fwrcache::modern} to true wait a tick #needed to wait for plugins to load set {-fwrcache::bukkitgetserver} to Bukkit.getServer() set {-fwrcache::mainworld} to {-fwrcache::bukkitgetserver}.getWorlds().get(0) set {-fwrcache::fileseparator} to File.separator set {-fwrcache::worlddir} to {-fwrcache::bukkitgetserver}.getWorldContainer().getPath() #always create FastWorldReset folder on startup if it isn't present #mkdir method checks if folder already exists, so we don't need to check it ourselves create new section stored in {_section}: set {_file} to new File("%{-fwrcache::worlddir}%/FastWorldReset") if {_file}.mkdir() is true: send "[FastWorldReset] Created FastWorldReset directory at %{-fwrcache::worlddir}%%{-fwrcache::fileseparator}%FastWorldReset" to console run section {_section} async delete {-govindask} if {-fwrcache::bukkitgetserver}.getPluginManager().getPlugin("GovindaSK") is set: send "[FastWorldReset] GovindaSK found! Enabling faster chunk unloads in chunk-based reset." to console set {-govindask} to true else: if {-fwrcache::modern} is not set: send "[FastWorldReset] GovindaSK not found, falling back to a slower, reflection way of unloading chunks in chunk-based reset." to console event "world_reset_start": patterns: start of world reset world reset start event-values: world event "world_reset_complete": patterns: complete of world reset world reset complete event-values: world function unloadChunks(world: world, saving: boolean) :: boolean: set {-resetting::%{_world}%} to true set {_n} to now #requires GovindaSK {-govindask} is set: parse if plugin "GovindaSK" is enabled: if {_saving} is true: unload all chunks in {_world} with saving else: unload all chunks in {_world} without saving #if GovindaSK is not found, fall back to skript-reflect way of unloading chunks else: loop ...{_world}.getLoadedChunks(): loop-value.unload({_saving}) #send "[FastWorldReset] &bUnloading chunks of &e%{_world}% &btook %difference between {_n} and now%" to console delete {-resetting::%{_world}%} if amount of ...{_world}.getLoadedChunks() is 0: return true else: return false function teleportOut(world: world): if {fastworldreset::exitpoint} is not a location: delete {fastworldreset::exitpoint} set {_spawnpoint} to {fastworldreset::exitpoint} ? (spawn point of "%{-fwrcache::mainworld}%" parsed as a world) loop all players in {_world}: if passenger of loop-player is set: loop passengers of loop-player: make loop-value-2 dismount teleport (all players in {_world}) to {_spawnpoint} function unloadWorld(world: world, saving: boolean = true) :: boolean: set {_n} to now #unload the world Bukkit.unloadWorld("%{_world}%", {_saving}) send "World unload took %difference between {_n} and now%" to console return true function resetWorld(input: text, sender: object): set {_world} to {_input} parsed as a world {_world} is a world: #teleport out is needed to be able to unload the world amount of players in {_world} is not 0 teleportOut({_world}) #waiting until all players are teleported out, to support asynchronous teleportations while amount of players in {_world} is not 0: wait a tick #mark world save as non-set if {-fastworldreset::shouldreset::%{_input}%} is set: delete {-fastworldreset::shouldreset::%{_input}%} set {_world} to {_input} parsed as a world #stats for problem identification add 1 to {-resetstats::%{_input}%::worldbased} set {-resetstats::%{_input}%::last} to "world" if {_world} is a world: set {_input} to "%{_world}%" unloadWorld({_world}, false) is not true: send "&a&lFastWorldReset&2&l> &cThere was an error in unloading the world &e%{_world}%&c, see console and report any problems to author Govindas." to console stop set {-resetting::%{_input}%} to now set {_worlddir} to {-fwrcache::worlddir} #use original template if this world is a clone set {_template} to {fastworldresetclone::%{_input}%} ? {_input} if {@ResetOnlyRegionFolder} is false: set {_source} to new File("%{_worlddir}%/FastWorldReset/%{_template}%") set {_target} to new File("%{_worlddir}%/%{_input}%") else: set {_source} to new File("%{_worlddir}%/FastWorldReset/%{_template}%/region") set {_target} to new File("%{_worlddir}%/%{_input}%/region") send "&a&lFastWorldReset&2&l> &cStarting to reset &e%{_input}% &cworld..." to {_sender} create new section stored in {_section}: if {_target} is not set: send "&a&lFastWorldReset&2&l> %{_input}% deleteDir somehow was null?" to {_sender} FileUtils.deleteDirectory({_target}) FileUtils.copyDirectory({_source}, {_target}) run section {_section} async and wait if {_input} is not set: send "&a&lFastWorldReset&2&l> &c&lCRITICAL ERROR: &cworld name became null during reset, so it was unable to complete." to console if {worldgenerator::%{_template}%} is set: {-fwrcache::bukkitgetserver}.createWorld(new WorldCreator({_input}).generator({worldgenerator::%{_template}%})) else: {-fwrcache::bukkitgetserver}.createWorld(new WorldCreator({_input})) delete {-resetting::%{_input}%} if ("%{_input}%" parsed as a world) is not a world: send "&c------------------" to console send "&a&lFastWorldReset&2&l> &c&lCRITICAL ERROR: world &e%{_input}% &chas failed to load after a reset." to console send "&c------------------" to console stop set {fastworldreset::lastreset::%{_input}%} to new Date().getTime() #unix time, not using vanilla skript syntax as we need milliseconds support send "&a&lFastWorldReset&2&l> &cReset &e%{_input}% &cvia world-based method." to {_sender} delete {-resetting::%{_input}%} set {_ev::world} to {_input} parsed as a world set {_evt} to custom event "world_reset_complete" with {_ev::*} call event {_evt} import: org.bukkit.World$Environment on world load: {-resetting::%event-world%} is set: delete {-resetting::%event-world%} #mark world as not saved {-fastworldreset::shouldreset::%event-world%} is set: delete {-fastworldreset::shouldreset::%event-world%} #it looks like environment setting makes the world load.. different data, so things disappear on environment change, so I am not releasing it for now, just keeping this function. function loadWorld(world: text, generator: text = "", environment: text = ""): set {-initload::%{_world}%} to true if {_generator} is "": if {worldgenerator::%{_world}%} is set: set {_generator} to {worldgenerator::%{_world}%} if {_environment} is "": set {_environment} to {worldenvironment::%{_world}%} ? "NORMAL" {_generator} is not "": {-fwrcache::bukkitgetserver}.createWorld(new WorldCreator({_world}).generator({_generator}).environment(Environment.valueOf({_environment} in uppercase))) else: {-fwrcache::bukkitgetserver}.createWorld(new WorldCreator({_world})) #blacklist of things that can do bad stuff in windows/linux if used in folder names. function blacklist(input: text) :: boolean: if {_input} contains "/" or "\" or "*" or "." or ":" or "?" or "<" or ">" or "|" or """": return true if {_input} is "plugins" or "logs": "%{-fwrcache::worlddir}%" is "." return true if {_input} is "CON" or "PRN" or "AUX" or "NUL" or "COM1" or "COM2" or "COM3" or "COM4" or "COM5" or "COM6" or "COM7" or "COM8" or "COM9" or "LPT1" or "LPT2" or "LPT3" or "LPT4" or "LPT5" or "LPT6" or "LPT7" or "LPT8" or "LPT9": return true if last character of {_input} = " " or ".": return true return false command /fastworldreset [] [] []: permission: fastworldreset.use aliases: fwr trigger: if arg 1 is "create": if arg 2 is not set: send "&a&lFastWorldReset&2&l> &cPlease specify world name!" stop if blacklist(arg 2) is true: send "&a&lFastWorldReset&2&l> &cYour input cannot use illegal characters!" stop if "%{-fwrcache::mainworld}%" = arg 2: send "&a&lFastWorldReset&2&l> &cYou cannot use your server's main world for this!" stop set {_worlddir} to {-fwrcache::worlddir} set {_w} to arg 2 set {_p} to sender create section stored in {_section}: Files.isDirectory(Paths.get("%{_worlddir}%/FastWorldReset/%{_w}%")) is true: send "&a&lFastWorldReset&2&l> &e&l%{_w}% &6Hm.. This world is already in the system of FastWorldReset, but I'll continue, just in case if you are making a change to existing template." to {_p} return true else: return false run section {_section} async and store the result in {_notmissing} and wait set {_world} to arg 2 parsed as a world if {_world} is a world: send "&a&lFastWorldReset&2&l> &6Hm.. seems like this world is already loaded, saving it!" send "&a&lFastWorldReset&2&l> &eWaiting for world save..." teleport (all players in {_world}) to (spawn point of "%{-fwrcache::mainworld}%" parsed as a world) if {_world}.isAutoSave() is true: {_world}.setAutoSave(false) {_world}.save() wait a tick Bukkit.unloadWorld("%{_world}%", true) while (arg 2 parsed as a world) is a world: add 1 to {_maxtime} if {_maxtime} is higher than 1000: send "&cFailed to unload." stop wait a tick #make reset backup of the world set {_n} to now set {_source} to new File("%{_worlddir}%/%arg 2%") set {_target} to new File("%{_worlddir}%/FastWorldReset/%arg 2%") set {_targetpath} to "%{_worlddir}%/FastWorldReset/%arg 2%" set {_p} to sender create new section stored in {_section}: FileUtils.copyDirectory({_source}, {_target}) if {@AllowCreatingNewWorlds} is false: Files.isDirectory(Paths.get({_targetpath})) is false: send "&a&lFastWorldReset&2&l> &cFailed to copy world! Are file permissions right? Check console for errors." to {_p} return true set {_uid} to new File("%{_targetpath}%/uid.dat") if {_uid}.exists() is true: {_uid}.delete() return false run section {_section} async and store the result in {_failed} and wait stop if {_failed} is true set {fastworldreset::lastreset::%arg 2%} to new Date().getTime() set {-fastworldresetworld::%arg 2%} to true #load world back after reset has been made if arg 3 is set: set {worldgenerator::%arg 2%} to arg 3 if (arg 2 parsed as a world) is not a world: loadWorld(arg 2) send "&a&lFastWorldReset&2&l> &aSuccessfully copied &e&l%arg 2% &aand made it resettable in &e&l%difference between {_n} and now%&e! If it doesn't work check console for errors." send "&a&lFastWorldReset&2&l> &eIf you ever wish to update the template of this world, just run &b/fwr create %arg 2% &eagain!" if {-fwrcache::modern} is set: set {fastworldreset::chunkresetdisabled::%arg 2%} to true send "&a&lFastWorldReset&2&l> &cDisabled chunk-based reset for this world by default. Because you're running 1.13+ where only world-based reset is effective." else if arg 1 is "reset": if arg 2 is not set: send "&a&lFastWorldReset&2&l> &cPlease specify world name!" stop set {_worlddir} to {-fwrcache::worlddir} if {-fastworldresetworld::%arg 2%} is set: set {_world} to arg 2 parsed as a world set {_ev::world} to {_world} call event (custom event "world_reset_start" with {_ev::*}) #this can be used to delay the reset by a specific amount of seconds if arg 3 is set: wait "%arg 3% seconds" parsed as a timespan if {_world} is not a world: set {_worldreset} to true else if {fastworldreset::chunkresetdisabled::%arg 2%} or {-fastworldreset::shouldreset::%arg 2%} is set: set {_worldreset} to true else if {fastworldreset::lastreset::%arg 2%} is not set: set {_worldreset} to true set {_template} to {fastworldresetclone::%arg 2%} ? arg 2 {_worldreset} is not set: set {_w} to arg 2 #detect world save as world save event doesn't always call create section stored in {_section}: if ((new File("%{_worlddir}%/%{_w}%/level.dat")).lastModified()) is higher than {fastworldreset::lastreset::%{_w}%}: return true run section {_section} async and store the result in {_worldreset} and wait {_worldreset} is true: resetWorld(arg 2, sender) else: teleportOut({_world}) #waiting until all players are teleported out, to support asynchronous teleportations while amount of players in {_world} is not 0: wait a tick #needed to detect improperly reset world when a bad plugin is disturbing reset, such as FastAsyncWorldEdit create section stored in {_section}: set {_files} to new File("%{_worlddir}%/%{_world}%/region").listFiles() Arrays.sort({_files}, LastModifiedFileComparator.LASTMODIFIED_REVERSE) return (first element of ...{_files}).lastModified() run section {_section} async and store the result in {_modify} and wait if unloadChunks({_world}, false) is false: send "&a&lFastWorldReset&2&l> &cFailed to unload chunks, falling back to world-based reset method." resetWorld(arg 2, sender) else: wait a tick #needed to detect failed avoidance of chunk save caused by bad plugins #run same section again for comparison run section {_section} async and store the result in {_modify2} and wait difference between {_modify} and {_modify2} is higher than 0: send "&a&lFastWorldReset&2&l> &cFailed to not save chunks. Possibly using a bad plugin which does this? Falling back to world-based reset." resetWorld(arg 2, sender) add 1 to {-resetstats::%arg 2%::worldbased} set {-resetstats::%arg 2%::last} to "world (after failed chunk save avoid, likely caused by bad plugin)" else: send "&a&lFastWorldReset&2&l> &cReset &e%arg 2% &cvia chunks-based method." #stats for problem identification add 1 to {-resetstats::%arg 2%::chunkbased} set {-resetstats::%arg 2%::last} to "chunk" set {_ev::world} to {_world} set {_evt} to custom event "world_reset_complete" with {_ev::*} call event {_evt} else: send "&a&lFastWorldReset&2&l> &e&l%arg 2% &cis not a FastWorldReset world!" else if arg 1 is "load": if blacklist(arg 2) is true: send "&a&lFastWorldReset&2&l> &cYour input cannot use illegal characters!" stop if arg 2 is not set: send "&a&lFastWorldReset&2&l> &cPlease specify world name!" stop if (arg 2 parsed as a world) is a world: send "&a&lFastWorldReset&2&l> &cThe world &e%arg 2% &cis already loaded!" stop loadWorld(arg 2, (arg 3 ? "")) (arg 2 parsed as a world) is a world: send "&a&lFastWorldReset&2&l> &aSuccessfully loaded the world &e%arg 2%&a." else if arg 1 is "clone": if arg 2 or arg 3 is not set: send "&cUsage: &e/fwr clone " stop if blacklist(arg 3) is true: send "&a&lFastWorldReset&2&l> &cYour input cannot use illegal characters!" stop set {_worlddir} to {-fwrcache::worlddir} if (arg 3 parsed as a world) is a world: send formatted "&a&lFastWorldReset&2&l> &cThe specified world is already loaded! (%arg 3%)" stop #if the specified template is a clone in itself, then use the clone's template if {fastworldresetclone::%arg 2%} is set: set {_template} to {fastworldresetclone::%arg 2%} else: set {_template} to arg 2 set {_newworld} to arg 3 set {_templatedir} to "%{_worlddir}%/FastWorldReset/%{_template}%" set {_p} to sender create new section stored in {_section}: Files.isDirectory(Paths.get({_templatedir})) is false: send "&a&lFastWorldReset&2&l> &cThe specified world template is not saved in FastWorldReset folder, thus it cannot be used for cloning." to {_p} return true else if Files.isDirectory(Paths.get("%{_worlddir}%/%{_newworld}%")) is true: send formatted "&a&lFastWorldReset&2&l> &6Hm... There's already %{_newworld}% world folder, I'll just load it then." to {_p} else: FileUtils.copyDirectory((new File("%{_templatedir}%")), (new File("%{_worlddir}%/%{_newworld}%"))) return false run section {_section} async and store the result in {_stop} and wait stop if {_stop} is true set {-fastworldresetworld::%{_newworld}%} to true set {fastworldresetclone::%{_newworld}%} to {_template} #used because index is always lowercased, so we need a way to get real name set {fastworldresetclonename::%{_newworld}%} to {_newworld} loadWorld({_newworld}, ({worldgenerator::%{_template}%} ? "")) if ({_newworld} parsed as a world) is a world: send formatted "&a&lFastWorldReset&2&l> &aSuccessfully cloned &e%{_template}% &aworld into &e%{_newworld}% &aworld!" send "&cNote: the cloned world will not load automatically, it just has been loaded now. You have to use &e/fwr create &con it, if you want it to load automatically." send "&bYou can configure if world folders of clones get deleted on server startup in the script file." set {fastworldreset::lastreset::%{_newworld}%} to new Date().getTime() else if arg 1 is "unload": if arg 2 is not set: send "&a&lFastWorldReset&2&l> &cPlease specify world name!" stop if "%{-fwrcache::mainworld}%" = arg 2: send "&a&lFastWorldReset&2&l> &cYou cannot unload your server's main world!" stop set {_world} to arg 2 parsed as a world {_world} is not a world: send "&a&lFastWorldReset&2&l> &cThere is no world loaded by the name of &e%arg 2%&c." stop #teleport out is needed to be able to unload the world teleportOut({_world}) #waiting until all players are teleported out, to support asynchronous teleportations while amount of players in {_world} is not 0: wait a tick unloadWorld({_world}) = true: send "&a&lFastWorldReset&2&l> &aSuccessfully unloaded the world &e%arg 2%&a." else: send "&a&lFastWorldReset&2&l> &cThere was an error in unloading the world &e%{_world}%&c, see console and report any problems to author Govindas." else if arg 1 is "teleport" or "tp": if arg 2 is not set: send "&a&lFastWorldReset&2&l> &cUsage: &e/fwr teleport [player]" stop if (arg 2 parsed as a world) is not a world: send "&a&lFastWorldReset&2&l> &cThe specified world &e%arg 2% &cis not loaded." stop if arg 3 is not set: set {_player} to player else: if arg 3 parsed as a player is offline: send "&a&lFastWorldReset&2&l> &cThe specified player &e%{_player}% &cis offline." stop set {_player} to arg 3 parsed as a player teleport {_player} to (spawn point of arg 2 parsed as a world) else if arg 1 is "delete": if arg 2 is not set: send "&a&lFastWorldReset&2&l> &cPlease specify world name!" stop if {-fastworldresetworld::%arg 2%} is not set: send "&a&lFastWorldReset&2&l> &cYou must specify existing world name." stop set {_worlddir} to {-fwrcache::worlddir} {-confirmfwrdelete::%sender%::%arg 2%} is set: if difference between {-confirmfwrdelete::%sender%::%arg 2%} and now is higher than 15 seconds: delete {-confirmfwrdelete::%sender%::%arg 2%} if {-confirmfwrdelete::%sender%::%arg 2%} is not set: set {-confirmfwrdelete::%sender%::%arg 2%} to now send colored "&a&lFastWorldReset&2&l> &6Are you sure you want to delete %arg 2%? Type the command again to confirm. (You have 15 seconds)" stop if {worldgenerator::%arg 2%} is set: delete {worldgenerator::%arg 2%} if {worldenvironment::%arg 2%} is set: delete {worldenvironment::%arg 2%} if {fastworldreset::chunkresetdisabled::%arg 2%} is set: delete {fastworldreset::chunkresetdisabled::%arg 2%} if {-resetstats::%arg 2%::*} is set: delete {-resetstats::%arg 2%::*} #just in case if it's a clone if {fastworldresetclone::%arg 2%} is set: delete {fastworldresetclone::%arg 2%} set {_clone} to true if {fastworldresetclonename::%arg 2%} is set: delete {fastworldresetclonename::%arg 2%} # delete {-fastworldresetworld::%arg 2%} if (arg 2 parsed as a world) is a world: send "&a&lFastWorldReset&2&l> &6Hm.. Seems like this world is loaded, unloading it and saving!" if unloadWorld((arg 2 parsed as a world), true) is true: send "&a&lFastWorldReset&2&l> &6Unloaded successfully!" else: send "&a&lFastWorldReset&2&l> &cFailed to unload." send "&a&lFastWorldReset&2&l> &aYou have removed world &e&l%arg 2% &afrom the system of FastWorldReset." set {_w} to arg 2 set {_p} to sender create section stored in {_section}: if {_clone} is set: FileUtils.deleteDirectory(new File("%{_worlddir}%/%{_w}%")) send "&a&lFastWorldReset&2&l> &6Since this world is a clone, deleted it from the world folder." to {_p} else: FileUtils.deleteDirectory(new File("%{_worlddir}%/FastWorldReset/%{_w}%")) send colored "&a&lFastWorldReset&2&l> &6Deleted the world template from %{_worlddir}%/FastWorldReset folder, but still kept the world in your world folder for manual deletion." to {_p} run section {_section} async and wait else if arg 1 is "exit": if sender is not a player: send "&a&lFastWorldReset&2&l> &cThis command is only executable as a player, if you want this to work on console, please contact the author Govindas." stop set {fastworldreset::exitpoint} to location of player send "&a&lFastWorldReset&2&l> &aYou have successfully set exit point at &e&l%{fastworldreset::exitpoint}%" else if arg 1 is "disable-chunk-reset": if arg 2 is not set: send "&a&lFastWorldReset&2&l> &cUsage: &e/fwr disable-chunk-reset " stop if (arg 2 parsed as a world) is not a world: send "&a&lFastWorldReset&2&l> &cThe specified world &e%arg 2% &cis not loaded." stop if {fastworldreset::chunkresetdisabled::%arg 2%} is not set: set {fastworldreset::chunkresetdisabled::%arg 2%} to true (arg 2 parsed as a world).setAutoSave(true) if {-fwrcache::modern} is not set: send "&a&lFastWorldReset&2&l> &aYou have disabled chunk-based reset for the world &e%arg 2% &aplease note that this makes reset slower and more likely to cause lag spikes, but it makes chunks automatically unload in the world as usual, making the world lighter on RAM usage." else: send "&a&lFastWorldReset&2&l> &aYou have disabled chunk-based reset for the world &e%arg 2%&a. This is a good choice for 1.13+ version as chunk-based reset is only helpful below 1.13." else: send "&a&lFastWorldReset&2&l> &cChunk-based reset is already disabled in world &e%arg 2%&c!" else if arg 1 is "enable-chunk-reset": if arg 2 is not set: send "&a&lFastWorldReset&2&l> &cUsage: &e/fwr disable-chunk-reset " stop if (arg 2 parsed as a world) is not a world: send "&a&lFastWorldReset&2&l> &cThe specified world &e%arg 2% &cis not loaded." stop if {fastworldreset::chunkresetdisabled::%arg 2%} is set: delete {fastworldreset::chunkresetdisabled::%arg 2%} send "&a&lFastWorldReset&2&l> &aYou have enabled chunk-based reset for the world &e%arg 2%&a! Yay, faster world reset! (Note chunk-based reset is only effective in 1.8-1.12.2)" (arg 2 parsed as a world).setAutoSave(false) else: send "&a&lFastWorldReset&2&l> &cChunk-based reset is already enabled for the world &e%arg 2%&c!" else if arg 1 is "worlds": send " " send "&6- &a&l Worlds &6-" loop all worlds: send formatted "&e%loop-world% &6- &e&l%amount of ...loop-world.getLoadedChunks()% &cchunks &e&l%amount of entities in loop-world% ¢ities" else if arg 1 is "unload-chunks": if (arg 2 parsed as a world) is not a world: send "&a&lFastWorldReset&2&l> &cThe specified world &e%arg 2% &cis not loaded." stop if (arg 3 parsed as a boolean) is not a boolean: send "&a&lFastWorldReset&2&l> &cUsage: &e/fwr unload-chunks &cThe boolean stands for saving true or saving false." stop set {_saving} to arg 3 parsed as a boolean set {_world} to arg 2 parsed as a world set {-unloading::%{_world}%} to true unloadChunks(({_world}), {_saving}) = true delete {-unloading::%{_world}%} if amount of ...{_world}.getLoadedChunks() is not 0: send "&a&lFastWorldReset&2&l> &cSome chunks have failed to unload, due to some plugin or players keeping chunks loaded." else if arg 1 is "generator": if arg 2 or arg 3 is not set: send "&cUsage: &e/fwr generator " stop set {_worlddir} to {-fwrcache::worlddir} if {-fastworldresetworld::%arg 2%} is not set: send "&a&lFastWorldReset&2&l> &e%arg 2% &cis not a FastWorldReset world." stop else: set {_prev} to "%{worldgenerator::%arg 2%}%" arg 3 is not "Default": set {worldgenerator::%arg 2%} to arg 3 else: delete {worldgenerator::%arg 2%} if "%{worldgenerator::%arg 2%}%" is {_prev}: send formatted "&a&lFastWorldReset&2&l> &e%arg 2% &cis already using &e%arg 3%&c!" stop send "&a&lFastWorldReset&2&l> &aYou have set the world generator of &e%arg 2% &ato &e%arg 3%" else if arg 1 is "generators": send colored "&a&lFastWorldReset&2&l> &cList of World Generators: &eDefault, %{-worldgenerators::*} ? """"%" send "&eDefault" set {_plugins::*} to ...{-fwrcache::bukkitgetserver}.getPluginManager().getPlugins() loop {_plugins::*}: if loop-value.getDefaultWorldGenerator("%{-fwrcache::mainworld}%", "") is set: send "&e%loop-value.getDescription().getName()%" else if arg 1 is "resetstats": if arg 2 is set: set {_world} to arg 2 else: set {_world} to "%player's world%" if {-resetstats::%{_world}%::last} is set: send "&cLast reset type: &e&l%{-resetstats::%{_world}%::last} ? "Unknown"%" send "&cWorld-based resets: &e&l%{-resetstats::%{_world}%::worldbased} ? 0%" send "&cChunk-based resets: &e&l%{-resetstats::%{_world}%::chunkbased} ? 0%" else: send "&a&lFastWorldReset&2&l> &cNo reset data found for this world. Possibly it was never reset since server restart." else if arg 1 is "info": if arg 2 is not set: send "&cUsage: &e/fwr info " stop {-fastworldresetworld::%arg 2%} is set: send formatted "&a&lWorld: &e%{-fastworldresetworld::%arg 2%}%" if {fastworldreset::chunkresetdisabled::%arg 2%} is set: set {_type} to "world-based" else: set {_type} to "chunk-based with fallback to world-based if it fails" send formatted "&a&lReset Type: &e%{_type}%" send formatted "&a&lGenerator: &e%{worldgenerator::%arg 2%} ? "Default"%" if arg 2 parsed as a world is a world: set {_loaded} to true else: set {_loaded} to false send formatted "&a&lIs Loaded: &e%{_loaded}%" else: send formatted "&a&lFastWorldReset&2&l> &cThe specified world (%arg 2%) is not a FastWorldReset world." else if arg 1 is "optimize-template": if arg 2 is not set: send "&cUsage: &e/fwr optimize-template " stop {-fastworldresetworld::%arg 2%} is set: set {_worlddir} to {-fwrcache::worlddir} set {_templatedir} to new File("%{_worlddir}%/FastWorldReset/%{-fastworldresetworld::%arg 2%}%") set {_files::*} to ...{_templatedir}.listFiles() loop {_files::*}: if "%loop-value%" ends with "/region": continue loop else if "%loop-value%" ends with "/level.dat": continue loop add 1 to {_i} set {_unneededfiles::%{_i}%} to "%loop-value%" if amount of {_unneededfiles::*} is not 0: send formatted "&a&lFastWorldReset&2&l> &cFound &e&l%amount of {_unneededfiles::*}% &cfiles that may be not needed for this world to function. It may be a good idea to delete them (unless they're needed for your world, maybe a plugin saved data here? but that's unlikely)" send {_unneededfiles::*} send formatted "&a&lFastWorldReset&2&l> &eYou can delete them manually by going to the directory. Only two things are required for world to function: the region folder and level.dat&e file. Make sure to not delete these two." else: send "&a&lFastWorldReset&2&l> &eThis template is already well optimized, no useless files found!" else: send formatted "&a&lFastWorldReset&2&l> &cThe specified world (%arg 2%) is not a FastWorldReset world." else: send "&2&l--=-= &a&lFastWorldReset Commands &2&l=-=--" send "&f/fwr create [generator] &a- Add a world to FastWorldReset system or create a completely new world into the system. (Or update existing world's template)" send "&f/fwr delete &a- Removes the world from FastWorldReset system, does not unload or delete the world." send "&f/fwr clone &a- Clones a FWR world." send "&f/fwr reset [seconds] &a- to reset a FastWorldReset world, with optional delay." send "&f/fwr exit &a- sets exit point to where players get teleported when their world resets." send "&f/fwr load [generator] &a- loads a world. Optionally with a generator." send "&f/fwr unload [] &a- loads a world. true/false if the world should be saved, defaults to true." send formatted "%newline%&7May be useful for unloading worlds which you no longer need.>&f/fwr unload &a- unload a world." send "&f/fwr unload-chunks &a- unloads chunks in the specified world, with saving or without." send "&f/fwr tp [player] &a- teleport to a world or teleport another player to the world." send formatted "%newline%&7May be useful in situations where the world uses very much RAM and you prefer slower reset than higher RAM usage>&f/fwr disable-chunk-reset &a- [Default on 1.13+] disables chunk-based resetting of the specified world, which makes the chunks unload normally." send "&f/fwr enable-chunk-reset &a- [Only below 1.13] enables chunk-based reset, this is enabled by default on older versions than 1.13 for fastest speed, see above to disable. (This is only meant for things like minigame maps, not where you need changes to persist across server restarts!)" send "&f/fwr worlds &a- shows the list of all worlds and their data." send "&f/fwr generator &a- set a generator for a world." send "&f/fwr generators &a- list all world generators detected on the server." send "&f/fwr info &a- see information about the specified world." send "&f/fwr resetstats &a- useful for debugging of reset bugs." send "&f/fwr optimize-template &a- useful for removing unneeded files from a world reset template." send "&6Hover your mouse on the commands to see extra comments and tips." function initWorlds(): #just in case if the user hasn't configured variables starting with "-" character to not save to database. delete {-fastworldresetworld::*}, {-unloading::*}, {-resetting::*}, {-inworldreset::*} and {-initload::*} set {_worlddir} to {-fwrcache::worlddir} create section stored in {_section}: set {_files::*} to ...new File("%{_worlddir}%/FastWorldReset").listFiles() loop {_files::*}: Files.isDirectory(Paths.get("%loop-value%")) is true: set {_value} to "%loop-value%" set {_r::*} to split "%loop-value%" by "%{-fwrcache::fileseparator}%FastWorldReset%{-fwrcache::fileseparator}%" set {_return::%loop-index%} to {_r::2} return {_return::*} run section {_section} async and store the result in {_worlds::*} and wait loop {_worlds::*}: set {-fastworldresetworld::%loop-value%} to loop-value if {@ResetResettableWorldsOnRestart} is true: make console execute command "/fastworldreset reset %loop-value%" else if (loop-value parsed as a world) is not a world: loadWorld(loop-value) #needed because in rare cases it starts to error if there is no delay ? #which sometimes results in empty chunks. #you'll have to design your code to support worlds that may not be loaded #do not save world object references, no location variables. (save them as text that you parse later) wait a tick function deleteClones(): {fastworldresetclone::*} is set set {_worlddir} to {-fwrcache::worlddir} create section stored in {_section}: loop {fastworldresetclone::*}: FileUtils.deleteDirectory(new File("%{_worlddir}%/%{fastworldresetclonename::%loop-index%}%")) run section {_section} async and wait delete {fastworldresetclone::*} delete {fastworldresetclonename::*} on skript load: wait a second initWorlds() if {@AutoDeleteClonesOnRestart} is true: deleteClones() #chunk-based reset, which resets by not saving chunks and re-loading them instead of reloading the whole world. it reloads the world only if the world got saved by some system. on chunk unload: parse if Skript.isRunningMinecraft(1, 13) is false: {-fastworldresetworld::%event-world%} is set {fastworldreset::chunkresetdisabled::%event-world%} is not set {-resetting::%event-world%} is not set {-unloading::%event-world%} is not set cancel event on world init: if {-initload::%event-world%} is set: delete {-initload::%event-world%} event-world.setKeepSpawnInMemory(false) {fastworldresetclone::%event-world%} is set: set {_options} to {fastworldresetclone::%event-world%} else: set {_options} to "%event-world%" {-fastworldresetworld::%{_options}%} is set #reduces ram usage by disabling spawn chunks event-world.setKeepSpawnInMemory(false) {fastworldreset::chunkresetdisabled::%{_options}%} is not set: set {_disablesaving} to true else if {@DisableSavingInAllResettableWorlds} is true: set {_disablesaving} to true {_disablesaving} is set: event-world.setAutoSave(false) #for Paper servers, to disable saving #setting it to variable for better performance for doing it twice set {_config} to event-world.getHandle().paperConfig set {_config}.autoSavePeriod to 0 set {_config}.maxAutoSaveChunksPerTick to 0 on world saving: {fastworldreset::chunkresetdisabled::%event-world%} is not set set {-fastworldreset::shouldreset::%event-world%} to true on skript unload: loop all worlds: {-fastworldresetworld::%loop-world%} is set {fastworldreset::chunkresetdisabled::%loop-world%} is not set unloadWorld(loop-world, false) #for use of {-inworldreset::worldname} variable in your scripts, this can help to tell whether a world is currently in a reset process on start of world reset: set {-inworldreset::%event-world%} to now on complete of world reset: delete {-inworldreset::%event-world%}