commit 61cb6159d292c5a87e7e15eb9f20c4c6e59141da Author: Govindas Date: Wed Dec 16 12:59:58 2020 +0200 Add files via upload diff --git a/fastworldreset.sk b/fastworldreset.sk new file mode 100644 index 0000000..67aa36e --- /dev/null +++ b/fastworldreset.sk @@ -0,0 +1,727 @@ +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 /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: 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 +on script load: + 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() + delete {-govindask} + if {-fwrcache::bukkitgetserver}.getPluginManager().getPlugin("GovindaSK") is set: + send "[FastWorldReset] GovindaSK found! Enabling faster chunk unloads." to console + set {-govindask} to true + else: + send "[FastWorldReset] GovindaSK not found, falling back to a slower, reflection way of unloading chunks. (2 errors related to chunk unloading will appear, but you can safely ignore them)" to console + + wait 5 seconds + #get list of world generators for /fwr generator command + + delete {-worldgenerators::*} + + set {_plugins::*} to ...{-fwrcache::bukkitgetserver}.getPluginManager().getPlugins() + + loop {_plugins::*}: + + + + set {_value} to loop-value.getDefaultWorldGenerator("%{-fwrcache::mainworld}%", "") + + if {_value} is set: + delete {_value} + + add 1 to {_i} + set {_name} to loop-value.getDescription().getName() + set {-worldgenerators::%{_name}%} to {_name} + +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 + + if {-govindask} is set: + 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) :: boolean: + 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 loop-player to {_spawnpoint} + return true +function unloadWorld(world: world, saving: boolean = true) :: boolean: + set {_n} to now + #teleport players out + + teleportOut({_world}) = true + + #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): + #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} + + if {fastworldresetclone::%{_input}%} is set: + set {_template} to {fastworldresetclone::%{_input}%} + else: + set {_template} to {_input} + + set {_source} to new File("%{_worlddir}%/FastWorldReset/%{_template}%/region") + set {_target} to new File("%{_worlddir}%/%{_input}%/region") + + create new section stored in {_section}: + FileUtils.copyDirectory({_source}, {_target}) + + run section {_section} async and wait + + if {worldgenerator::%{_template}%} is set: + {-fwrcache::bukkitgetserver}.createWorld(new WorldCreator({_input}).generator({worldgenerator::%{_template}%})) + else: + {-fwrcache::bukkitgetserver}.createWorld(new WorldCreator({_input})) + + 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 + if {_notmissing} is true: + set {_w} to arg 2 + set {_source} to new File("%{_worlddir}%/%arg 2%/region") + set {_target} to new File("%{_worlddir}%/FastWorldReset/%arg 2%/region") + create new section stored in {_section}: + FileUtils.deleteDirectory(new File("%{_worlddir}%/FastWorldReset/%{_w}%/region")) + FileUtils.copyDirectory({_source}, {_target}) + run section {_section} async and wait + else: + 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 + 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." + + 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 + + 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 + + set {_ev::world} to {_world} + set {_w} to arg 2 + call event (custom event "world_reset_start" with {_ev::*}) + {_worldreset} is not set: + + #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}) = true + + #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 + + + set {_w} to arg 3 + set {_templatedir} to "%{_worlddir}%/FastWorldReset/%arg 2%" + 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}%/%{_w}%")) is true: + send formatted "&a&lFastWorldReset&2&l> &6Hm... There's already %arg 3% world folder, I'll just load it then." to {_p} + else: + FileUtils.copyDirectory((new File("%{_templatedir}%")), (new File("%{_worlddir}%/%{_w}%"))) + return false + run section {_section} async and store the result in {_stop} and wait + stop if {_stop} is true + set {-fastworldresetworld::%arg 3%} to true + set {fastworldresetclone::%arg 3%} to arg 2 + set {fastworldresetclonename::%arg 3%} to arg 3 + loadWorld(arg 3, {worldgenerator::%arg 2%} ? "") + + + + if (arg 3 parsed as a world) is a world: + send formatted "&a&lFastWorldReset&2&l> &aSuccessfully cloned &e%arg 2% &aworld into &e%arg 3% &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::%arg 3%} 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 + 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 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) + 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> &cChunk-based reset is already disabled in world &e%arg 2%&c!" + else if arg 1 is "enable-chunk-reset": + 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% &aagain! Yay, faster world reset!" + (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 " + send "&cList of World Generators: &eDefault, %{-worldgenerators::*} ? """"%" + 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%" + + if {-worldgenerators::%arg 3%} is not set: + send colored "&cWARNING: The specified generator (%arg 3%) has not been found on the server, but this may be a mistake." + else if arg 1 is "generators": + send colored "&a&lFastWorldReset&2&l> &cList of World Generators: &eDefault, %{-worldgenerators::*} ? """"%" + 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::%{_world}%} 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: + + 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." + 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 &a- to reset a world. (Must be in FastWorldReset system)" + 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 "%new line%&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- disables chunk-based resetting of the specified world, which makes the chunks unload normally." + send "&f/fwr enable-chunk-reset &a- enables chunk-based reset, this is enabled by default 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 "&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::*} 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) +function deleteClones(): + 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: + {-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 + {fastworldreset::chunkresetdisabled::%{_options}%} is not 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 + + #needed, because if it is set as true... entity ticking may totally bug out with the way FastWorldReset's chunk-based reset mehod works + #it may sometimes cause behavior like projectiles constantly teleporting back and never landing if set to true + set {_config}.skipEntityTickingInChunksScheduledForUnload to false + + event-world.setKeepSpawnInMemory(false) +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) \ No newline at end of file