Skip to main content
 Warning: Beta Version
Version: Beta ⚠️

All API changes to 0.8.X

There are quite a few changes to the API. Make sure to read these changes before updating your extension.

build.gradle.kts Changes

Now the engine version is not specified in a seperate engine block, but in the extension block.

build.gradle.kts
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
plugins {
kotlin("jvm") version "2.0.21"
id("com.typewritermc.module-plugin") version "1.0.1"
id("com.typewritermc.module-plugin") version "1.1.0"
}
// Replace with your own information
group = "me.yourusername"
version = "0.0.1"
typewriter {
engine {
version = "<latest typewriter version>"
}

namespace = "<a name for the company which all your extensions are published under>"

extension {
name = "<Name of the extension>"
shortDescription = "<Short description of the extension>"
description = "<Long description of the extension>"
engineVersion = "<latest typewriter version>"

paper {
// Optional - If you want to make sure a plugin is required to be installed to use this extension
dependency("<plugin name>")
}
}
}
kotlin {
jvmToolchain(21)
}

Initializable Changes

The way to register a Initializable has changed.

@Initializer
@Singleton
object ExampleInitializer : Initializable {
override suspend fun initialize() {
// Do something when the extension is initialized
}

override suspend fun shutdown() {
// Do something when the extension is shutdown
}
}

Triggering Entries Changes

Since Typewriter now has InteractionContext, this needs to be passed around when triggering entries. As a lot changed, have a look at the Triggering Entries page for more information.

val entries: List<TriggerableEntry> = ...

entries triggerAllFor player
entries.triggerAllFor(player, context())

ActionEntry Changes

As the ActionEntry now needs a InteractionContext, the execute function signature has changed.

@Entry("example_action", "An example action entry.", Colors.RED, "material-symbols:touch-app-rounded")
class ExampleActionEntry(
override val id: String = "",
override val name: String = "",
override val criteria: List<Criteria> = emptyList(),
override val modifiers: List<Modifier> = emptyList(),
override val triggers: List<Ref<TriggerableEntry>> = emptyList(),
) : ActionEntry {
override fun execute(player: Player) {
super.execute(player) // This will apply all the modifiers.
override fun ActionTrigger.execute() {
// Will now automatically apply all the modifiers.
// Do something with the player
}
}

Removal of CustomTriggeringActionEntry

As the action entry now can specify itself how Typewriter should handle the triggering:

@Entry("example_action", "An example action entry.", Colors.RED, "material-symbols:touch-app-rounded")
class ExampleCustomTriggeringActionEntry(
override val id: String = "",
override val name: String = "",
override val criteria: List<Criteria> = emptyList(),
override val modifiers: List<Modifier> = emptyList(),
@SerializedName("triggers")
override val customTriggers: List<Ref<TriggerableEntry>> = emptyList(),
) : CustomTriggeringActionEntry {
override fun execute(player: Player) {
super.execute(player) // This will apply the modifiers.
// Do something with the player
player.triggerCustomTriggers() // Can be called later to trigger the next entries.
override val triggers: List<Ref<TriggerableEntry>> = emptyList(),
) : ActionEntry {
override fun ActionTrigger.execute() {
// This disables Typewriter's automatic triggering of the next entries,
// and disables the automatic apply of the modifiers.
disableAutomaticTriggering()

// Now you can manually trigger the next entries.
triggerManually()

// Or if you want to specify which triggers to trigger, you can do so.
triggers.filterIndexed { index, _ -> index % 2 == 0 }.triggerFor(player)

// You can also manually apply the modifiers.
applyModifiers()
}
}

Dialogue Messenger Changes

As it was the only place in Typewriter where you had to register something with an annotation, we changed it to allow the DialogueEntry to specify the messengers that it uses. Additionally, we now need to forward the context parameter to the messengers.

@Entry("example_dialogue", "An example dialogue entry.", Colors.BLUE, "material-symbols:chat-rounded")
class ExampleDialogueEntry(
override val id: String = "",
override val name: String = "",
override val criteria: List<Criteria> = emptyList(),
override val modifiers: List<Modifier> = emptyList(),
override val triggers: List<Ref<TriggerableEntry>> = emptyList(),
override val speaker: Ref<SpeakerEntry> = emptyRef(),
@MultiLine
@Placeholder
@Colored
@Help("The text to display to the player.")
val text: String = "",
) : DialogueEntry {
// May return null to skip the dialogue
override fun messenger(player: Player, context: InteractionContext): DialogueMessenger<*>? {
// You can use if statements to return a different messenger depending on different conditions
return ExampleDialogueDialogueMessenger(player, context, this)
}
}

@Messenger(ExampleDialogueEntry::class)
class ExampleDialogueDialogueMessenger(player: Player, entry: ExampleDialogueEntry) :
DialogueMessenger<ExampleDialogueEntry>(player, entry) {

companion object : MessengerFilter {
override fun filter(player: Player, entry: DialogueEntry): Boolean = true
}
class ExampleDialogueDialogueMessenger(player: Player, context: InteractionContext, entry: ExampleDialogueEntry) :
DialogueMessenger<ExampleDialogueEntry>(player, context, entry) {


// Called every game tick (20 times per second).
// The cycle is a parameter that is incremented every tick, starting at 0.
override fun tick(context: TickContext) {
super.tick(context)
if (state != MessengerState.RUNNING) return

player.sendMessage("${entry.speakerDisplayName}: ${entry.text}".parsePlaceholders(player).asMini())

// When we want the dialogue to end, we can set the state to FINISHED.
state = MessengerState.FINISHED
}
}