KotlinFire is a Kotlin library used to manipulate code templates from the Minecraft server DiamondFire. This library features a clean syntax, making use of Kotlin's lambda functions, extension functions, and operator overloading.
It is encouraged to read this entire README before using this library, as it is the only "tutorial" on its usage.
KotlinFire can be added to your project through Maven Central in whichever build system you use.
Gradle Kotlin DSL
repositories {
mavenCentral()
}
dependencies {
implementation("io.github.flyingpig525:kotlinfire:1.6.3")
}
KotlinFire requires java >=17 and kotlin >=2.1.10 be used.
To begin constructing a template, assign a variable the Template
class. The Template
class contains a lambda function
argument with the type Template.() -> Unit
meaning you can access Template
members inside the function. The Template
class contains one variable for each code block in DiamondFire with its respective name. These variables' member functions
are what will be called in the lambda function. Each member function has another lambda function as an argument. Items
And variables go in this function.
val template = Template {
SetVar.equalTo {
// Items go here
}
}
Example code creating an empty Set Variable>equal
code block
To put an item in a code block, use the unaryPlus operator in the lambda. This library provides a few classes for select
item types, which can be accessed directly through their constructors or through extension functions on Kotlin primitive
types (e.g. String.toTextItem()
)
val template = Template {
SetVar.equalTo {
+"Variable name".toVarItem()
+"Variable value".toTextItem()
}
}
Example code setting a variable with the name Variable name
and Game
scope to the text value Variable value
Items can also be assigned to variables for ease of use.
val template = Template {
val variable = "Very long and annoying to write name".toVarItem()
SetVar.equalTo {
+variable
+(12.toNumItem())
}
}
Example code using a variable with the value of a VarItem
to easily insert the same item in a code block
Each method in a code block category has documentation parsed directly from the df action dump.
You can also provide a name and type, function or process, to the Template
constructor. These default to
"PutNameHere"
and Type.FUNCTION
.
val template = Template(name = "Name", type = Template.Type.PROCESS) {
val variable = "Very long and annoying to write name".toVarItem()
SetVar.equalTo {
+variable
+(12.toNumItem())
}
}
Example code providing a template name ("Name") and type (process)
Event templates can be created using the EventTemplate
class, using the same conventions as the Template
class.
EventTemplate
requires the type of event to be passed as a parameter, these types can be accessed in the PLAYEREVENT
and ENTITYEVENT
enums.
val template = EventTemplate(PLAYEREVENT.Join) {
val variable = "Very long and annoying to write name".toVarItem()
SetVar.equalTo {
+variable
+(12.toNumItem())
}
}
Example code creating a player join event template
Templates can now have parameters, permitting the use of the ParameterItem
.
ParameterItem
can be better constructed using its builder functions.
// Template with a required, non-plural parameter
Template("required", ParameterItem.num("param name").parameter()) {
val numberParam = ParameterItem.num("param name").variable()
// alternative:
// val numberParam = "param name".lineVar
PlayerAction.sendMessage {
+numberParam
}
}
// Template with an optional parameter
Template("optional", ParameterItem.num("name").default((43).numItem).parameter()) {
val numberParam = ParameterItem.num("name").variable()
// alternative:
// val numberParam = "name".lineVar
PlayerAction.sendMessage {
+numberParam
}
// Invoking a template with parameters is the same as adding a codeblock
callFunction("required") {
+(41).numItem
}
}
Code blocks requiring sub actions can now be used, as a result of recreating the file generation for code blocks
EventTemplate(PlayerEvent.Join) {
SetVar.equalTo {
+"%default ticks on ground".gameVar
}
// IsGrounded requires no parameters
Repeat.whileLoop({}, IfPlayerSubAction.IsGrounded) {
SetVar.increment {
+"%default ticks on ground".gameVar
}
Control.wait {
// redundant number
+(1.numItem)
}
}
}
There are now classes given extension functions under the scope of a template. There are only a few of these classes at the time of writing this, being: TextVariable, NumVariable, and VecVariable. These are the only ones I thought of useful functions for at the time of writing this.
Template {
val numVar = NumVariable("number variable", VarItem.Scope.Game)
numVar++ // adds an increment
numVar += 43 // adds an increment with 43 as a parameter
numVar += "12" // adds an increment with a NumItem with the value "12" as a parameter
numVar -= 2 // adds a decrement with 2 as a parameter
numVar-- // adds a decrement
numVar.set(5.numItem) // sets the variable "number variable" to 5
}
While this does add a pretty large amount of functionality, I feel like it is just missing content that should have been in 1.4.0, so this is just marked as a patch.
The above classes can now be compared using the new item comparison infix functions, allowing for a clean comparing
experience. ifVar
can be called while in the scope of a Template
, which takes a new class, ItemComparison
,
as an argument. This class can be constructed through the functions provided on VarClass
extending classes, such as
NumVariable
. This function returns a class that contains an Else
function (capitalized to avoid clashes with
builtin operators), which, quite obviously, allows you to add an else block to the comparison. Every comparison block
also now returns an ElseOperation
to allow easy else addition. Replacing the old Else
function that was a member of
Template
, which could be called at any time.
Template {
val numVar = NumVariable("numvar", VarItem.Scope.Game)
ifVal(numVar equalTo 4.numItem) {
// do something
} Else {
// do another thing
}
val locVar = LocVariable("loc", VarItem.Scope.Game)
ifVal(locVar isNear locOf(14, 52, 23.5)) {
// do something
} // Else is not required
}
Every VarClass
extender has an equalTo
and exists
(not infix) function, but some do not have other comparison
functions.
Game value generation has been implemented in the Generation module,
meaning the format has changed. Game values can now be accessed in their respective category objects: EventValues
,
InformationalValues
, ItemValues
, LocationalValues
, PlotValues
, and StatisticalValues
.
Each of the variables in these objects will create a new GameValue
instance when accessed, and target can be changed
by calling the GameValue#target(GameValue.Target)
method.
Template {
val num = NumVariable("added latency", VarItem.Scope.Line)
// Redundant setting of target, `Default` is the default
num set 41.numItem
num += StatisticalValues.Ping.target(GameValue.Target.Default)
}
More type-safety has also been added to VarClass
extending classes.
Specific classes now have the ability to be "serialized" into DiamondFire dictionaries. This can be done through
extending DiamondFireClass
and delegating properties through the DiamondFireClass#numProp
and
DiamondFireClass#textProp
members (more will be added). More information on usage is found in the example code.
This feature is considered experimental and requires an OptIn
annotation to be used.
Template {
val pastVar = NumVariable("past variable", VarItem.Scope.LINE)
pastVar set 0.numItem
val klass = Serialized(pastVar, "serialized class")
// must initialize variable (creates 3 codeblocks)
klass.init()
// setting mutable properties will create a `SetVariable#setDictValue` codeblock
// they also must be using `Item` inheritors or `VarClass` inheritors
klass.mutableNumber = 12.numItem
PlayerAction.sendMessage {
// will be transformed into a TextItem("%entry(serialized class,immutableTextProperty)")
// should send the player "str"
+klass.immutableTextProperty
// should send the player "0"
+klass.dynamicDefault
}
}
@OptIn(DiamondFireClassOptIn::class)
// scope can be universal between all `Serialized` instances, or defined individually
class Serialized(default: NumVariable, name: String, scope: VarItem.Scope = VarItem.Scope.GAME) : DiamondFireClass(name, scope) {
// a default value must be provided
var mutableNumber by numProp(0)
// immutability is only enforced via the dsl, meaning an immutable property can be set though codeblocks without
// any errors (as there is no sensible way to detect if that is occurring)
val immutableTextProperty by textProp("str")
// properties can also be initialized through `VarClass` inheritors
var dynamicDefault by numProp(default)
}
A new type has been added: DictionaryVariable
!
This type has two comparison functions, containsKey
and valueEquals
. containsKey
takes a key parameter and checks
whether the dictionary has a entry with that value as the key. valueEquals
takes a key parameter and a list of values
and checks if the value of the key is equal to any of the values in the passed list.
DictionaryVariable
also has the get
and set
operator functions defined when in a Template
, and can be used to
get and set values in the dictionary. It also has a getAsVariable
function, which must be used when the value type is
not a number, text, or string. It also inserts a codeblock to create a temporary variable with the value found at the
key.
Template {
val dict = DictionaryVariable("%default data", VarItem.Scope.SAVE)
ifVal(dict containsKey "hasJoined") {
dict["firstJoin"] = 0
} Else {
dict["firstJoin"] = 1
dict["hasJoined"] = 1
}
dict["initialLocation"] = LocationalValues.Location
// later
// if the value at key "firstJoin" equal 1, teleport the player to their initialLocation
ifVal(dict.valueEquals("firstJoin", 1.numItem)) {
// not a number, text, or string, so the value must be read as a VarItem
val initialLocation = dict.getAsVariable("initialLocation", VarItem.Scope.LINE)
PlayerAction.teleport {
+initialLocation
}
}
}
Multiple utility functions exist, allowing the retrieval of encoded template strings and a function to interact with the CodeClient API. This function can be used to send individual or multiple templates to the CodeClient API.
val template = Template(name = "Name", type = Template.Type.PROCESS) {
val variable = "Very long and annoying to write name".toVarItem()
SetVar.equalTo {
+variable
+(12.toNumItem())
}
}
Template.codeClientPlaceTemplate(template)
Example code sending the above constructed template to the CodeClient API
val template = Template(name = "Name", type = Template.Type.PROCESS) {
val variable = "Very long and annoying to write name".toVarItem()
SetVar.equalTo {
+variable
+(12.toNumItem())
}
}
val template2 = Template {
PlayerAction.sendMessage {
+"Hello, world!".toTextItem()
}
}
Template.codeClientPlaceMultipleTemplates(template, template2)
Example code sending multiple templates to the CodeClient API
A wrapper class, TemplateCollection
, is also provided to make sending a large amount of templates easier. When using
TemplateCollection
, ensure you use the provided functions for template collection template
and eventTemplate
instead
of the class constructors.
TemplateCollection {
val template = template(name = "Name", type = Template.Type.PROCESS) {
val variable = "Very long and annoying to write name".toVarItem()
SetVar.equalTo {
+variable
+(12.toNumItem())
}
}
val template2 = template {
PlayerAction.sendMessage {
+"Hello, world!".toTextItem()
}
}
codeClientPlaceTemplates()
}
More fleshed-out examples can be found in the LibraryTest.kt file in KotlinFire/src/test/kotlin/org/example/LibraryTest.kt