FastWorldReset/fastworldreset.sk

842 lines
35 KiB
Plaintext

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 [<text>] [<text>] [<text>]:
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 <world> <new-world>"
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 <world> [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 <world>"
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 <world>"
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% &centities"
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 <world> <boolean> &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 <world> <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 <world>"
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 <world>"
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 <world> [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 <world> &a- Removes the world from FastWorldReset system, does not unload or delete the world."
send "&f/fwr clone <world> <new-world> &a- Clones a FWR world."
send "&f/fwr reset <world> [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 <world> [generator] &a- loads a world. Optionally with a generator."
send "&f/fwr unload <world> [<true/false>] &a- loads a world. true/false if the world should be saved, defaults to true."
send formatted "<ttp:&f/fwr unload <world>%newline%&7May be useful for unloading worlds which you no longer need.>&f/fwr unload <world> &a- unload a world."
send "&f/fwr unload-chunks <world> <true/false> &a- unloads chunks in the specified world, with saving or without."
send "&f/fwr tp <world> [player] &a- teleport to a world or teleport another player to the world."
send formatted "<ttp:&f/fwr disable-chunk-reset <world>%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 <world> &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 <world> &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 <world> <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 <world> &a- see information about the specified world."
send "&f/fwr resetstats &a- useful for debugging of reset bugs."
send "&f/fwr optimize-template <world> &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%}