Kludge - Extension library for Kotlin


All of my Kotlin plugins use a library I’ve made called Kludge. It’s essentially a large collection of utilities, designed to take advantage of Kotlin’s extension, operator, and property support to simplify a few things. I’ve decided to release it since it could be useful to people.

Feature list

  • A global plugin variable, so you don’t have to pass it to things like task builders.
  • Text manipulation has never been so easy.
    • A rich replace function. Never again mess about with TextTemplate for on-the-fly replacement! Replace a string or regex with a text.
      • Formatting in the original text is perfectly preserved.
      • Optionally enable lossy mode to allow some formatting to be lost in the event that the string to be replaced is spread out over multiple text children (use this for e.g. swear filtering color-code-enabled chat).
      • You can’t use regex $var references in the replacement text, but I’m working on it!
    • Convert to text anything that can be converted by using the ! operator or the text() function, and convert to builder with textBuilder().
    • Format any text convertibles into text immediately using provided color, format, and style functions, along with individual functions for each color and style, as well as onClick, onHover, and onShiftClick.
      • This includes Text itself!
    • Use + to construct text or a Text.Builder, and - to remove from a Text.Builder. You can even use + on Text with a receiver closure.
    • ALL of this preserves types as much as possible. If you call !"Text", you get a LiteralText. If you call ScoreText#red(), you get a ScoreText. Etc.
    • String has two extra functions - textByJson and textByFormat.
    • Join arrays or iterables to a text with joinToText.
  • Builders. Every builder in the API has a whichamajiggerOf style function , which you call with a receiver lambda, for Kotlin-style builders. Yes, every builder.
    • dataRegistrationOf, taskOf, and other similar builders support the global plugin.
  • Service properties. Why call the ServiceManager when you can just delegate a property to it? Just write your property like this: val svc: EconomyService? by Service and when you call it, it’ll call the ServiceManager for you.
    • There’s also UncheckedService for non-nullable properties.
    • Use it like a constructor instead of an object to cache the result.
  • A couple of Optional helpers. Use unwrap() or the ! operator on an Optional to convert it to a nullable type, and .optional() on a nullable type to convert to Optional.
  • Call command() on your CommandExecutor-applicable function to convert it into one, or pass it into CommandSpec.Builder directly.
  • Operator all the things!.
    • The Data API:
      • Use index getters and setters and the in operator for Keys, classes, Values, etc.
      • += and -= add and remove data from holders
      • % and %= work with raw data or fill
      • -= rolls back a data transaction
      • Use the bare operator (i.e. without =) to get the DataTransactionResult
    • Scoreboards:
      • Score has full numerical functionality
      • Team has +=, -=, and in for members
      • Scoreboard has +=, -=, and in for objectives, and indexers for DisplaySlots
      • Objective has +=, -=, and in for Scores, -= and in for names, an indexer to get a score, and an invoker to getOrCreate a score.
    • Advancements:
      • ScoreCriterionProgress has full numerical functionality
      • AdvancementCriterion's and and or functions are now infix
      • TreeLayout has an indexer
      • Advancement now has a toastText property
    • Inventories:
      • += to add items to an inventory
      • Indexers for properties, queries, getting slots at indexes or positions, and setting items at indexes or positions.
      • Invokers for peeking at items at indexes or positions
      • Inventory now has a slots property, which fixes ducktyping on the slots() function
      • In general just pretend the API was written with Kotlin in mind, and write anything that you think makes sense, and it’ll probably work
    • Flow Math:
      • All the vectory and matrixy types now support all the math operators they can
  • All the static singletons you get from Sponge and GameRegistry now act like objects (you could call CommandManager.register directly, etc.)
  • UUID has player(), user(), and profile() functions
  • Player now has a storageInventory property which gets the MainPlayerInventory directly
  • MessageReceiver now has all the functions of ChatTypeMessageReceiver. If the receiver happens to be a ChatTypeMessageReceiver, then its functions get called normally; otherwise, the extra ChatType argument is ignored and the MessageReceiver functions are called instead. Because even a smart cast is too much boilerplate for message sending. If you care if the ChatType was actually used, the methods return true if it was and false if it wasn’t.
  • Vector3d now has eulerToDirection and directionToEuler. In addition, Transform now has a .direction property.
  • Command arguments can now be done with a closure. This removes the need to put GenericArguments. before your arguments.
    • Note that any custom CommandElements you use must be prefixed with a + in the closure.
    • You can also just generate a CommandElement directly, with commandArgumentsOf.
  • Less Task noise.
    *sync(plugin) { doThing() } to run on the main server thread, async(plugin) { doThing() } to run off the main server thread. If you’re already on the main server thread, sync just executes immediately.
    • delay(plugin, 30) { doThing() } to delay by a certain number of ticks.
    • Coroutines! Use task(plugin) { } to begin a coroutine that uses Tasks for continuations.
      • Now you can write what you mean. desync() to move off the server thread, resync() to move back on the server thread, delay and delayTicks to wait for a period of time. All of these backend to Task jumping.
      • Fully compatible with all other methods of starting or stopping coroutines.
    • Supports the global plugin, letting you remove the plugin argument.
  • Less Item noise. Player has a give function which deposits the item into their inventory and drops whatever didn’t fit on the ground next to them.
  • Less ItemStack noise.
    • ItemType, BlockType, BlockState, BlockSnapshot and Location now have toStack functions which turn them into ItemStacks. Optionally, open out a closure to keep customizing the builder.
    • ItemStack.Builder has displayName and lore functions.
  • Other little things like String#toUUID() and typeTokenOf.

Feel free to suggest more features!

How to include in your plugin

First, enable the shadow plugin if you haven’t already.

How to enable the shadow plugin
  1. Add to your plugins block id 'com.github.johnrengelman.shadow' version '1.2.4'.
  2. Set jar.enabled = false and build.dependsOn shadowJar. If you have the signing plugin enabled, add signArchives.dependsOn shadowJar.
  3. Add to your configurations block compile.extendsFrom shadow.
  4. Create a shadowJar block:
shadowJar {
    classifier = null
    configurations = [project.configurations.shadow]

Add the Maven repository https://jitpack.io/ and the shadow dependency com.github.pie-flavor:Kludge.

  • The ‘latest’ version is master-SNAPSHOT, but I would advise against using this as the API could break, and this isn’t going to be numerically versioned. Instead, for the version use the shortened commit hash you get from GitHub (eecb6d2 at the time of writing).

To prevent version conflicts with anyone else using it, add relocate 'flavor.pie.kludge', 'my.plugin.package.util' to your shadowJar block.

A recommended setting for IntelliJ IDEA is to go to Editor > Code Style > Kotlin > Imports, and add flavor.pie.kludge to Packages to Use Import with '*'.

  • Added math operators to Flow Math.
  • Added angle conversion functions.
  • Added a couple more inventory operators.
  • Changed all the builders from constructor-style to function-style.
  • Added synchronization functions.
  • Added the give function.
  • Changed the behavior of cached services.
  • Added item utility functions.
  • Added typeTokenOf function.
  • Added the global plugin variable.
  • Added easy command arguments.
  • Added more text utilities.
  • Other miscellaneous things.
  • Lots of bugfixes. PLEASE UPDATE
  • Added Task coroutines.

Interesting. How’s building Text looks like? With color, boldness, colored hint and stuff.

"It ".red() + "looks ".green().bold() + "like " + "this".gold().onHover("This!".aqua()) + "!"

Works for everything - TextRepresentable, Score, Translatable, etc.

val item = ItemTypes.DIAMOND.toStack()
val text = (!"[" + item + "]").green().onHover(item).onClick { (it as? Player)?.give(item) }

I don’t think it could get any more fluent without custom interpolation.

1 Like

Really cool! Thanks :smiley:

1 Like