Scheduler, logging and more
The smaller helper packages under dev.versatia.util. Server-wide calls, such as
finding players, broadcasting or loading a service, are on VersatiaEngine.
Logging
Package util.plugin. Shorthands over Plugin.logger for places that hold a Plugin anyway, such
as a bean that takes the plugin for its data folder or a processor with context.plugin. A class
that only logs takes a Logger instead; see
Beans every plugin has.
plugin.log("Loaded") // INFO
plugin.log(Level.FINE) { "expensive $detail" } // message built only if the level is enabled
plugin.logWarning("Config key missing")
plugin.logSevere("Could not save", cause)
Inside the plugin class itself, logger.info(...) is just as short; the helpers add nothing there.
Scheduler
Repeating work and waits are coroutines: a BackgroundTask runs as long as the
plugin and is restarted by every reload, scope.launch { delayTicks(20) } waits without a
callback. The helpers below are for plain Bukkit tasks, such as a one-off runTaskLater or an API
that wants a BukkitTask.
Package util.scheduler. Wrappers over BukkitScheduler that take the plugin as receiver:
plugin.runTask { /* next tick, main thread */ }
plugin.runTaskLater(20) { /* in one second */ }
plugin.runTaskTimer(periodTicks = 20) { /* every second, from the next tick */ }
plugin.runTaskAsynchronously { /* off the main thread */ }
plugin.callSyncMethod { VersatiaEngine.onlinePlayers.size } // Future<Int> from an async thread
plugin.cancelAllTasks()
The timer variants take the period first and the delay second: runTaskTimer(periodTicks, delayTicks = 0).
This is the reverse of BukkitScheduler, so name both arguments when you pass a delay:
plugin.runTaskTimer(100, 20) { autosave() }
plugin.runTaskTimer(periodTicks = 20, delayTicks = 100) { autosave() }
Each variant has a *WithContext twin whose lambda receives the BukkitTask, so a repeating task
can cancel itself:
plugin.runTaskTimerWithContext(periodTicks = 20, delayTicks = 100) { task ->
if (done) task.cancel()
}
Validation
Package util.validation. validate, validateAtLeast, validateAtMost, validateBetween,
validateIn, validateGreaterThan, validateLessThan, validateZero and validateNotZero wrap
require and return the receiver, so they chain inline:
val size = config.getIntOrThrow("size").validateAtLeast(1) { "size must be positive" }
They are generic over Comparable<T> (or Number for the zero checks). Messages are lambdas and
evaluated only on failure.
Reflection
Package util.reflect, built on kotlin-reflect.
KProperty1.forceGetValue(instance),KMutableProperty1.forceSetValue(instance, value)andKFunction.forceInvoke(args)temporarily make a member accessible and restore the previous accessibility afterwards.- Properties of Kotlin
objects are backed by static fields, whichkotlin-reflectcannot set throughKMutableProperty1.seton the instance. The helpers detect a static backing field and go throughjava.lang.reflect.Fieldinstead. KClass.forceGetObjectInstance()andgetObjectInstanceOrThrow()read anobject's instance even when the object is private.isClassInClasspath(name, classLoader)takes the loader explicitly, because every plugin on a Paper server has its own class loader. The class is loaded without initialization, so no static initializers run as a side effect.
kotlin-reflect is on the server because VersatiaCore lists it under libraries:. Your plugin
reaches it through depend: [VersatiaCore] and must not list it itself.
Inventory
Package util.inventory.
ItemStack?.isNullOrAir().Inventory.getAmountOfSimilarItems(item)and the same onArray<ItemStack?>.ItemStack.loreWithoutDefaultFormat(lore)/ItemMeta.loreWithoutDefaultFormat(lore)andList<Component>.withoutDefaultItemFormat()strip the italic purple default that Minecraft applies to lore lines.PlayerInventorySlotsholds the raw slot indices of the player inventory;Int.toEquipmentSlotOrNull()maps an armour slot index to itsEquipmentSlot.
World
Package util.world.
Location.getCircleAround(radius, points = 100)returns evenly spaced points on a horizontal circle around the location.Location.spawnParticle(...)spawns a particle at the location and does nothing when the world is not loaded.Location.isWithin(distance, other)tells whetherotheris in the same world and at mostdistanceblocks away, the boundary included:player.location.isWithin(1.5, start). It isfalse, never an exception, when the worlds differ or either location has none; like every world access it throws when a world has been unloaded.
Enums
Package util.lang. asEnumIgnoreCase<T>(name), asEnumIgnoreCaseOrThrow<T>(name) and
isEnumIgnoreCase<T>(name) look up enum constants without caring about case; the configuration
getters use them for enum values.
Text
Package util.text. toSubscript() and toSuperscript() on Int, Long, UInt, ULong and
String convert digits and a leading minus to their Unicode sub- and superscript forms
(12.toSuperscript() is ¹², "-3".toSubscript() is ₋₃). A string holding anything else
throws IllegalArgumentException.
Value types
Package util.type. Percent is an inline value class around a Double in 0..100,
comparable. Create it with Percent.of(12.5), Percent.of(current, total),
Percent.ofNormalizedValue(0.125) or Percent.parse("12.5%"); normalizedValue gives the 0..1
form. The configuration getters parse the "12.5%" notation.
Secret is an inline value class around a String that prints as **** ('' when empty), for
passwords, tokens and keys; reveal() returns the text, Secret.EMPTY is the empty one and
isEmpty() tells it, which by convention switches the feature off. The
configuration getters and @Value read it from text,
${env:NAME}, ${file:PATH} or ${pluginfile:PATH}.