diff --git a/adapters/_DocsAdapter/build.gradle.kts b/adapters/_DocsAdapter/build.gradle.kts new file mode 100644 index 0000000000..dbca3f4b5a --- /dev/null +++ b/adapters/_DocsAdapter/build.gradle.kts @@ -0,0 +1,2 @@ +repositories {} +dependencies {} diff --git a/adapters/_DocsAdapter/settings.gradle.kts b/adapters/_DocsAdapter/settings.gradle.kts new file mode 100644 index 0000000000..2c434271a4 --- /dev/null +++ b/adapters/_DocsAdapter/settings.gradle.kts @@ -0,0 +1,14 @@ +rootProject.name = "DocsAdapter" + +includeBuild("../../plugin") + +plugins { + id("com.gradle.enterprise") version ("3.13.3") +} + +gradleEnterprise { + buildScan { + termsOfServiceUrl = "https://gradle.com/terms-of-service" + termsOfServiceAgree = "yes" + } +} diff --git a/adapters/_DocsAdapter/src/main/kotlin/com/typewritermc/example/ExampleAdapter.kt b/adapters/_DocsAdapter/src/main/kotlin/com/typewritermc/example/ExampleAdapter.kt new file mode 100644 index 0000000000..2300d3e65f --- /dev/null +++ b/adapters/_DocsAdapter/src/main/kotlin/com/typewritermc/example/ExampleAdapter.kt @@ -0,0 +1,17 @@ +package com.typewritermc.example + +// +import me.gabber235.typewriter.adapters.Adapter +import me.gabber235.typewriter.adapters.TypewriterAdapter + +@Adapter("Example", "An example adapter for documentation purposes", "0.0.1") +object ExampleAdapter : TypewriterAdapter() { + override fun initialize() { + // Do something when the adapter is initialized + } + + override fun shutdown() { + // Do something when the adapter is shutdown + } +} +// \ No newline at end of file diff --git a/adapters/_DocsAdapter/src/main/kotlin/com/typewritermc/example/entries/cinematic/ExampleCinematicEntry.kt b/adapters/_DocsAdapter/src/main/kotlin/com/typewritermc/example/entries/cinematic/ExampleCinematicEntry.kt new file mode 100644 index 0000000000..f35d596bba --- /dev/null +++ b/adapters/_DocsAdapter/src/main/kotlin/com/typewritermc/example/entries/cinematic/ExampleCinematicEntry.kt @@ -0,0 +1,143 @@ +package com.typewritermc.example.entries.cinematic + +import me.gabber235.typewriter.adapters.Colors +import me.gabber235.typewriter.adapters.Entry +import me.gabber235.typewriter.adapters.modifiers.* +import me.gabber235.typewriter.entry.Criteria +import me.gabber235.typewriter.entry.cinematic.SimpleCinematicAction +import me.gabber235.typewriter.entry.entries.* +import org.bukkit.entity.Player + +// +@Entry("example_cinematic", "An example cinematic entry", Colors.BLUE, "material-symbols:cinematic-blur") +class ExampleCinematicEntry( + override val id: String = "", + override val name: String = "", + override val criteria: List = emptyList(), + @Segments(Colors.BLUE, "material-symbols:cinematic-blur") + val segments: List = emptyList(), +) : CinematicEntry { + override fun create(player: Player): CinematicAction { + return ExampleCinematicAction(player, this) + } +} +// + +@Entry( + "example_with_segment_sizes", + "An example cinematic entry with segment sizes", + Colors.BLUE, + "material-symbols:cinematic-blur" +) +class ExampleWithSegmentSizesEntry( + override val id: String = "", + override val name: String = "", + override val criteria: List = emptyList(), + // + @Segments(Colors.BLUE, "material-symbols:cinematic-blur") + @InnerMin(Min(10)) + @InnerMax(Max(20)) + val segments: List = emptyList(), + // +) : CinematicEntry { + // + // This will be used when the cinematic is normally displayed to the player. + override fun create(player: Player): CinematicAction { + return DefaultCinematicAction(player, this) + } + + // This is used during content mode to display the cinematic to the player. + // It may be null to not show it during simulation. + override fun createSimulating(player: Player): CinematicAction? { + return SimulatedCinematicAction(player, this) + } + + // This is used during content mode to record the cinematic. + // It may be null to not record it during simulation. + override fun createRecording(player: Player): CinematicAction? { + return RecordingCinematicAction(player, this) + } + // +} + +// +data class ExampleSegment( + override val startFrame: Int = 0, + override val endFrame: Int = 0, +) : Segment +// + +// +class ExampleCinematicAction( + val player: Player, + val entry: ExampleCinematicEntry, +) : CinematicAction { + override suspend fun setup() { + // Initialize variables, spawn entities, etc. + } + + override suspend fun tick(frame: Int) { + val segment = entry.segments activeSegmentAt frame + // Can be null if no segment is active + + // The `frame` parameter is not necessarily next frame: `frame != old(frame)+1` + + // Execute tick logic for the segment + } + + override suspend fun teardown() { + // Remove entities, etc. + } + + override fun canFinish(frame: Int): Boolean = entry.segments canFinishAt frame +} +// + +// +class ExampleSimpleCinematicAction( + val player: Player, + entry: ExampleCinematicEntry, +) : SimpleCinematicAction() { + override val segments: List = entry.segments + + override suspend fun startSegment(segment: ExampleSegment) { + super.startSegment(segment) // Keep this + // Called when a segment starts + } + + override suspend fun tickSegment(segment: ExampleSegment, frame: Int) { + super.tickSegment(segment, frame) // Keep this + // Called every tick while the segment is active + // Will always be called after startSegment and never after stopSegment + + // The `frame` parameter is not necessarily next frame: `frame != old(frame)+1` + } + + override suspend fun stopSegment(segment: ExampleSegment) { + super.stopSegment(segment) // Keep this + // Called when the segment ends + // Will also be called if the cinematic is stopped early + } +} +// + +class DefaultCinematicAction( + val player: Player, + entry: ExampleWithSegmentSizesEntry, +) : SimpleCinematicAction() { + override val segments: List = entry.segments +} + +class SimulatedCinematicAction( + val player: Player, + entry: ExampleWithSegmentSizesEntry, +) : SimpleCinematicAction() { + override val segments: List = entry.segments +} + +class RecordingCinematicAction( + val player: Player, + entry: ExampleWithSegmentSizesEntry, +) : SimpleCinematicAction() { + override val segments: List = entry.segments +} \ No newline at end of file diff --git a/adapters/_DocsAdapter/src/main/kotlin/com/typewritermc/example/entries/manifest/ExampleAudienceEntry.kt b/adapters/_DocsAdapter/src/main/kotlin/com/typewritermc/example/entries/manifest/ExampleAudienceEntry.kt new file mode 100644 index 0000000000..fad71ec8a5 --- /dev/null +++ b/adapters/_DocsAdapter/src/main/kotlin/com/typewritermc/example/entries/manifest/ExampleAudienceEntry.kt @@ -0,0 +1,90 @@ +package com.typewritermc.example.entries.manifest + +import com.typewritermc.example.entries.trigger.SomeBukkitEvent +import me.gabber235.typewriter.adapters.Colors +import me.gabber235.typewriter.adapters.Entry +import me.gabber235.typewriter.entry.entries.AudienceDisplay +import me.gabber235.typewriter.entry.entries.AudienceEntry +import me.gabber235.typewriter.entry.entries.TickableDisplay +import me.gabber235.typewriter.utils.ThreadType +import org.bukkit.entity.Player +import org.bukkit.event.EventHandler + +// +@Entry("example_audience", "An example audience entry.", Colors.GREEN, "material-symbols:chat-rounded") +class ExampleAudienceEntry( + override val id: String, + override val name: String, +) : AudienceEntry { + override fun display(): AudienceDisplay { + return ExampleAudienceDisplay() + } +} +// + +// +class ExampleAudienceDisplay : AudienceDisplay() { + override fun initialize() { + // This is called when the first player is added to the audience. + super.initialize() + // Do something when the audience is initialized + } + + override fun onPlayerAdd(player: Player) { + // Do something when a player gets added to the audience + } + + override fun onPlayerRemove(player: Player) { + // Do something when a player gets removed from the audience + } + + override fun dispose() { + super.dispose() + // Do something when the audience is disposed + // It will always call onPlayerRemove for all players. + // So no player cleanup is needed here. + } +} +// + +// +// highlight-next-line +class TickableAudienceDisplay : AudienceDisplay(), TickableDisplay { + override fun onPlayerAdd(player: Player) {} + override fun onPlayerRemove(player: Player) {} + + // highlight-start + override fun tick() { + // Do something when the audience is ticked + players.forEach { player -> + // Do something with the player + } + + // This is running asynchronously + // If you need to do something on the main thread + ThreadType.SYNC.launch { + // Though this will run a tick later, to sync with the bukkit scheduler. + } + } + // highlight-end +} +// + +// +class AudienceDisplayWithEvents : AudienceDisplay() { + override fun onPlayerAdd(player: Player) {} + override fun onPlayerRemove(player: Player) {} + + // highlight-start + @EventHandler + fun onSomeEvent(event: SomeBukkitEvent) { + // Do something when the event is triggered + // This will trigger for all players, not just the ones in the audience. + // So we need to check if the player is in the audience. + if (event.player in this) { + // Do something with the player + } + } + // highlight-end +} +// \ No newline at end of file diff --git a/adapters/_DocsAdapter/src/main/kotlin/com/typewritermc/example/entries/static/ExampleArtifactEntry.kt b/adapters/_DocsAdapter/src/main/kotlin/com/typewritermc/example/entries/static/ExampleArtifactEntry.kt new file mode 100644 index 0000000000..46dd3eca73 --- /dev/null +++ b/adapters/_DocsAdapter/src/main/kotlin/com/typewritermc/example/entries/static/ExampleArtifactEntry.kt @@ -0,0 +1,26 @@ +package com.typewritermc.example.entries.static + +import me.gabber235.typewriter.adapters.Colors +import me.gabber235.typewriter.adapters.Entry +import me.gabber235.typewriter.entry.AssetManager +import me.gabber235.typewriter.entry.Ref +import me.gabber235.typewriter.entry.entries.ArtifactEntry +import org.koin.java.KoinJavaComponent + +// +@Entry("example_artifact", "An example artifact entry.", Colors.BLUE, "material-symbols:home-storage-rounded") +class ExampleArtifactEntry( + override val id: String = "", + override val name: String = "", + override val artifactId: String = "", +) : ArtifactEntry +// + +// +suspend fun accessArtifactData(ref: Ref) { + val assetManager = KoinJavaComponent.get(AssetManager::class.java) + val entry = ref.get() ?: return + val content: String? = assetManager.fetchAsset(entry) + // Do something with the content +} +// \ No newline at end of file diff --git a/adapters/_DocsAdapter/src/main/kotlin/com/typewritermc/example/entries/static/ExampleAssetEntry.kt b/adapters/_DocsAdapter/src/main/kotlin/com/typewritermc/example/entries/static/ExampleAssetEntry.kt new file mode 100644 index 0000000000..5cd21e0d2f --- /dev/null +++ b/adapters/_DocsAdapter/src/main/kotlin/com/typewritermc/example/entries/static/ExampleAssetEntry.kt @@ -0,0 +1,26 @@ +package com.typewritermc.example.entries.static + +import me.gabber235.typewriter.adapters.Colors +import me.gabber235.typewriter.adapters.Entry +import me.gabber235.typewriter.entry.AssetManager +import me.gabber235.typewriter.entry.Ref +import me.gabber235.typewriter.entry.entries.AssetEntry +import org.koin.java.KoinJavaComponent + +// +@Entry("example_asset", "An example asset entry.", Colors.BLUE, "material-symbols:home-storage-rounded") +class ExampleAssetEntry( + override val id: String = "", + override val name: String = "", + override val path: String = "", +) : AssetEntry +// + +// +suspend fun accessAssetData(ref: Ref) { + val assetManager = KoinJavaComponent.get(AssetManager::class.java) + val entry = ref.get() ?: return + val content: String? = assetManager.fetchAsset(entry) + // Do something with the content +} +// \ No newline at end of file diff --git a/adapters/_DocsAdapter/src/main/kotlin/com/typewritermc/example/entries/static/ExampleSoundIdEntry.kt b/adapters/_DocsAdapter/src/main/kotlin/com/typewritermc/example/entries/static/ExampleSoundIdEntry.kt new file mode 100644 index 0000000000..fd1eecfd12 --- /dev/null +++ b/adapters/_DocsAdapter/src/main/kotlin/com/typewritermc/example/entries/static/ExampleSoundIdEntry.kt @@ -0,0 +1,14 @@ +package com.typewritermc.example.entries.static + +import me.gabber235.typewriter.adapters.Colors +import me.gabber235.typewriter.adapters.Entry +import me.gabber235.typewriter.entry.entries.SoundIdEntry + +// +@Entry("example_sound", "An example sound entry.", Colors.BLUE, "icon-park-solid:volume-up") +class ExampleSoundIdEntry( + override val id: String = "", + override val name: String = "", + override val soundId: String = "", +) : SoundIdEntry +// \ No newline at end of file diff --git a/adapters/_DocsAdapter/src/main/kotlin/com/typewritermc/example/entries/static/ExampleSoundSourceEntry.kt b/adapters/_DocsAdapter/src/main/kotlin/com/typewritermc/example/entries/static/ExampleSoundSourceEntry.kt new file mode 100644 index 0000000000..c1bf10e1eb --- /dev/null +++ b/adapters/_DocsAdapter/src/main/kotlin/com/typewritermc/example/entries/static/ExampleSoundSourceEntry.kt @@ -0,0 +1,20 @@ +package com.typewritermc.example.entries.static + +import me.gabber235.typewriter.adapters.Colors +import me.gabber235.typewriter.adapters.Entry +import me.gabber235.typewriter.entry.entries.SoundSourceEntry +import net.kyori.adventure.sound.Sound + +// +@Entry("example_sound_source", "An example sound source entry.", Colors.BLUE, "ic:round-spatial-audio-off") +class ExampleSoundSourceEntry( + override val id: String = "", + override val name: String = "", +) : SoundSourceEntry { + override fun getEmitter(): Sound.Emitter { + // Return the emitter that should be used for the sound. + // A bukkit entity can be used here. + return Sound.Emitter.self() + } +} +// \ No newline at end of file diff --git a/adapters/_DocsAdapter/src/main/kotlin/com/typewritermc/example/entries/static/ExampleSpeakerEntry.kt b/adapters/_DocsAdapter/src/main/kotlin/com/typewritermc/example/entries/static/ExampleSpeakerEntry.kt new file mode 100644 index 0000000000..6e7a225d86 --- /dev/null +++ b/adapters/_DocsAdapter/src/main/kotlin/com/typewritermc/example/entries/static/ExampleSpeakerEntry.kt @@ -0,0 +1,16 @@ +package com.typewritermc.example.entries.static + +import me.gabber235.typewriter.adapters.Colors +import me.gabber235.typewriter.adapters.Entry +import me.gabber235.typewriter.entry.entries.SpeakerEntry +import me.gabber235.typewriter.utils.Sound + +// +@Entry("example_speaker", "An example speaker entry.", Colors.BLUE, "ic:round-spatial-audio-off") +class ExampleSpeakerEntry( + override val id: String = "", + override val name: String = "", + override val displayName: String = "", + override val sound: Sound = Sound.EMPTY, +) : SpeakerEntry +// \ No newline at end of file diff --git a/adapters/_DocsAdapter/src/main/kotlin/com/typewritermc/example/entries/trigger/ExampleActionEntry.kt b/adapters/_DocsAdapter/src/main/kotlin/com/typewritermc/example/entries/trigger/ExampleActionEntry.kt new file mode 100644 index 0000000000..59f54f04d8 --- /dev/null +++ b/adapters/_DocsAdapter/src/main/kotlin/com/typewritermc/example/entries/trigger/ExampleActionEntry.kt @@ -0,0 +1,26 @@ +package com.typewritermc.example.entries.trigger + +import me.gabber235.typewriter.adapters.Colors +import me.gabber235.typewriter.adapters.Entry +import me.gabber235.typewriter.entry.Criteria +import me.gabber235.typewriter.entry.Modifier +import me.gabber235.typewriter.entry.Ref +import me.gabber235.typewriter.entry.TriggerableEntry +import me.gabber235.typewriter.entry.entries.ActionEntry +import org.bukkit.entity.Player + +// +@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 = emptyList(), + override val modifiers: List = emptyList(), + override val triggers: List> = emptyList(), +) : ActionEntry { + override fun execute(player: Player) { + super.execute(player) // This will apply all the modifiers. + // Do something with the player + } +} +// \ No newline at end of file diff --git a/adapters/_DocsAdapter/src/main/kotlin/com/typewritermc/example/entries/trigger/ExampleCustomTriggeringActionEntry.kt b/adapters/_DocsAdapter/src/main/kotlin/com/typewritermc/example/entries/trigger/ExampleCustomTriggeringActionEntry.kt new file mode 100644 index 0000000000..86fd230935 --- /dev/null +++ b/adapters/_DocsAdapter/src/main/kotlin/com/typewritermc/example/entries/trigger/ExampleCustomTriggeringActionEntry.kt @@ -0,0 +1,29 @@ +package com.typewritermc.example.entries.trigger + +import com.google.gson.annotations.SerializedName +import me.gabber235.typewriter.adapters.Colors +import me.gabber235.typewriter.adapters.Entry +import me.gabber235.typewriter.entry.Criteria +import me.gabber235.typewriter.entry.Modifier +import me.gabber235.typewriter.entry.Ref +import me.gabber235.typewriter.entry.TriggerableEntry +import me.gabber235.typewriter.entry.entries.CustomTriggeringActionEntry +import org.bukkit.entity.Player + +// +@Entry("example_custom_triggering_action", "An example custom triggering entry.", Colors.RED, "material-symbols:touch-app-rounded") +class ExampleCustomTriggeringActionEntry( + override val id: String = "", + override val name: String = "", + override val criteria: List = emptyList(), + override val modifiers: List = emptyList(), + @SerializedName("triggers") + override val customTriggers: List> = 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. + } +} +// \ No newline at end of file diff --git a/adapters/_DocsAdapter/src/main/kotlin/com/typewritermc/example/entries/trigger/ExampleDialogueEntry.kt b/adapters/_DocsAdapter/src/main/kotlin/com/typewritermc/example/entries/trigger/ExampleDialogueEntry.kt new file mode 100644 index 0000000000..a37ad4a02d --- /dev/null +++ b/adapters/_DocsAdapter/src/main/kotlin/com/typewritermc/example/entries/trigger/ExampleDialogueEntry.kt @@ -0,0 +1,59 @@ +package com.typewritermc.example.entries.trigger + +import me.gabber235.typewriter.adapters.Colors +import me.gabber235.typewriter.adapters.Entry +import me.gabber235.typewriter.adapters.Messenger +import me.gabber235.typewriter.adapters.MessengerFilter +import me.gabber235.typewriter.adapters.modifiers.Colored +import me.gabber235.typewriter.adapters.modifiers.Help +import me.gabber235.typewriter.adapters.modifiers.MultiLine +import me.gabber235.typewriter.adapters.modifiers.Placeholder +import me.gabber235.typewriter.entry.* +import me.gabber235.typewriter.entry.dialogue.DialogueMessenger +import me.gabber235.typewriter.entry.dialogue.MessengerState +import me.gabber235.typewriter.entry.entries.DialogueEntry +import me.gabber235.typewriter.entry.entries.SpeakerEntry +import me.gabber235.typewriter.extensions.placeholderapi.parsePlaceholders +import me.gabber235.typewriter.utils.asMini +import org.bukkit.entity.Player +import java.time.Duration + +// +@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 = emptyList(), + override val modifiers: List = emptyList(), + override val triggers: List> = emptyList(), + override val speaker: Ref = emptyRef(), + @MultiLine + @Placeholder + @Colored + @Help("The text to display to the player.") + val text: String = "", +) : DialogueEntry +// + +// +@Messenger(ExampleDialogueEntry::class) +class ExampleDialogueDialogueMessenger(player: Player, entry: ExampleDialogueEntry) : + DialogueMessenger(player, entry) { + + companion object : MessengerFilter { + override fun filter(player: Player, entry: DialogueEntry): Boolean = true + } + + // Called every game tick (20 times per second). + // The cycle is a parameter that is incremented every tick, starting at 0. + override fun tick(playTime: Duration) { + super.tick(playTime) + 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 + } +} +// \ No newline at end of file diff --git a/adapters/_DocsAdapter/src/main/kotlin/com/typewritermc/example/entries/trigger/ExampleEventEntry.kt b/adapters/_DocsAdapter/src/main/kotlin/com/typewritermc/example/entries/trigger/ExampleEventEntry.kt new file mode 100644 index 0000000000..f3284f92b9 --- /dev/null +++ b/adapters/_DocsAdapter/src/main/kotlin/com/typewritermc/example/entries/trigger/ExampleEventEntry.kt @@ -0,0 +1,38 @@ +package com.typewritermc.example.entries.trigger + +import me.gabber235.typewriter.adapters.Colors +import me.gabber235.typewriter.adapters.Entry +import me.gabber235.typewriter.entry.* +import me.gabber235.typewriter.entry.entries.EventEntry +import org.bukkit.entity.Player +import org.bukkit.event.HandlerList +import org.bukkit.event.player.PlayerEvent + +// +@Entry("example_event", "An example event entry.", Colors.YELLOW, "material-symbols:bigtop-updates") +class ExampleEventEntry( + override val id: String = "", + override val name: String = "", + override val triggers: List> = emptyList(), +) : EventEntry +// + +// +// Must be scoped to be public +@EntryListener(ExampleEventEntry::class) +fun onEvent(event: SomeBukkitEvent, query: Query) { + // Do something + val entries = query.find() // Find all the entries of this type, for more information see the Query section + // Do something with the entries, for example trigger them + entries triggerAllFor event.player +} +// + +class SomeBukkitEvent(val player: Player) : PlayerEvent(player) { + override fun getHandlers(): HandlerList = HANDLER_LIST + + companion object { + @JvmStatic + val HANDLER_LIST = HandlerList() + } +} \ No newline at end of file diff --git a/adapters/_DocsAdapter/src/main/templates/App.kt b/adapters/_DocsAdapter/src/main/templates/App.kt new file mode 100644 index 0000000000..c35b69218d --- /dev/null +++ b/adapters/_DocsAdapter/src/main/templates/App.kt @@ -0,0 +1,3 @@ +object App { + const val VERSION = "$version" +} \ No newline at end of file diff --git a/adapters/build.gradle.kts b/adapters/build.gradle.kts index 5999dd5d1c..3015271490 100644 --- a/adapters/build.gradle.kts +++ b/adapters/build.gradle.kts @@ -10,14 +10,8 @@ allprojects { repositories { // Required mavenCentral() - maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots/") - maven("https://oss.sonatype.org/content/groups/public/") - maven("https://libraries.minecraft.net/") - maven { url = uri("https://repo.papermc.io/repository/maven-public/") } - // PacketEvents + maven("https://repo.papermc.io/repository/maven-public/") maven("https://repo.codemc.io/repository/maven-releases/") - // Anvil GUI (Sub dependency of LirandAPI) - maven("https://repo.codemc.io/repository/maven-snapshots/") maven("https://repo.opencollab.dev/maven-snapshots/") maven("https://jitpack.io") } @@ -53,10 +47,8 @@ subprojects { toolchain.languageVersion.set(JavaLanguageVersion.of(targetJavaVersion)) } - tasks.withType().configureEach { - kotlinOptions { - jvmTarget = "$targetJavaVersion" - } + kotlin { + jvmToolchain(targetJavaVersion) } val copyTemplates by tasks.registering(Copy::class) { diff --git a/documentation/docs/develop/02-adapters/02-getting_started.mdx b/documentation/docs/develop/02-adapters/02-getting_started.mdx index a99d61e1fc..cf650eb0d5 100644 --- a/documentation/docs/develop/02-adapters/02-getting_started.mdx +++ b/documentation/docs/develop/02-adapters/02-getting_started.mdx @@ -2,22 +2,28 @@ title: Getting Started --- +import CodeSnippet from "@site/src/components/CodeSnippet"; + + # Creating an Adapter ## Introduction -TypeWriter is a dynamic platform that supports the development of adapters, which are modular components enhancing the overall functionality. Adapters are self-contained, easily shareable, and integrate smoothly into the TypeWriter system. This guide is tailored to guide you through the process of creating an adapter, suitable for both beginners and experienced developers. +Typewriter is a dynamic platform that supports the development of adapters, which are modular components enhancing the overall functionality. +Adapters are self-contained, easily shareable, and integrate smoothly into the TypeWriter system. +They allow you to create custom entries and have them show up in the web panel. +This guide is tailored to guide you through the process of creating an adapter, suitable for both beginners and experienced developers. ## Prerequisites - - Java Development Kit (JDK) 17 or higher. + - Java Development Kit (JDK) 21 or higher. - An Integrated Development Environment (IDE) such as IntelliJ IDEA or Eclipse. - A basic understanding of Gradle and the Spigot API. ## Step 1: Setting Up a Gradle Project -Begin by establishing a Gradle project for your TypeWriter adapter. Below is a comprehensive setup for your `build.gradle.kts`: +Begin by establishing a Gradle project for your Typewriter adapter. Below is a comprehensive setup for your `build.gradle.kts`: ```kotlin title="build.gradle.kts" plugins { - kotlin("jvm") version "1.8.20" - id("com.github.johnrengelman.shadow") version "8.1.1" + kotlin("jvm") version "2.0.0" + id("io.github.goooler.shadow") version "8.1.7" } // Replace with your own information @@ -25,35 +31,33 @@ group = "me.yourusername" version = "0.0.1" repositories { - maven("https://jitpack.io") + // Typewriter required repositories mavenCentral() - maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots/") - maven("https://oss.sonatype.org/content/groups/public/") - maven("https://libraries.minecraft.net/") maven("https://repo.papermc.io/repository/maven-public/") - maven("https://repo.codemc.io/repository/maven-snapshots/") + maven("https://repo.codemc.io/repository/maven-releases/") maven("https://repo.opencollab.dev/maven-snapshots/") + maven("https://jitpack.io") // Add other necessary repositories } dependencies { - compileOnly(kotlin("stdlib")) - compileOnly("io.papermc.paper:paper-api:1.20.1-R0.1-SNAPSHOT") compileOnly("com.github.gabber235:typewriter:main-SNAPSHOT") // Latest release version - // Already included in the TypeWriter plugin but still needed for compilation - compileOnly("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.0-RC") - compileOnly("com.github.dyam0:LirandAPI:96cc59d4fb") - compileOnly("net.kyori:adventure-api:4.13.1") - compileOnly("net.kyori:adventure-text-minimessage:4.13.1") - // Add other dependencies as needed } -java { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 +tasks.withType { + exclude("kotlin/**") + exclude("META-INF/maven/**") + // Important: Use the relocated commandapi which is shadowed by the plugin + relocate("dev.jorel.commandapi", "com.github.gabber235.typewriter.extensions.commandapi") { + include("dev.jorel.commandapi.**") + } +} + +kotlin { + jvmToolchain(21) } ``` @@ -77,19 +81,8 @@ If you need a specific version, visit the [JitPack page](https://jitpack.io/#gab ## Step 2: Creating an Adapter Class After setting up your project, create an adapter class. Here's an example: -kotlin title="ExampleAdapter.kt" -```kotlin -import me.gabber235.typewriter.adapters.Adapter -import me.gabber235.typewriter.adapters.TypewriteAdapter - -@Adapter("Example", "An example adapter for documentation purposes", "0.0.1") -object ExampleAdapter : TypewriteAdapter() { - override fun initialize() { - // Any initializations needed to run the adapter. - } -} -``` + ## Step 3: Building the Adapter After creating the adapter class, build the adapter. This can be done by running the `shadowJar` Gradle task. diff --git a/documentation/docs/develop/02-adapters/03-entries/cinematic/index.mdx b/documentation/docs/develop/02-adapters/03-entries/cinematic/index.mdx index 06f323d9ef..4c8f2f0a34 100644 --- a/documentation/docs/develop/02-adapters/03-entries/cinematic/index.mdx +++ b/documentation/docs/develop/02-adapters/03-entries/cinematic/index.mdx @@ -1,3 +1,5 @@ +import CodeSnippet from "@site/src/components/CodeSnippet"; + # CinematicEntry The `CinematicEntry` does not have any decentends, but is very customizable. When a entry is needed in a cinematic page, it needs to inherid this. @@ -20,39 +22,16 @@ If you need this, reach out to me on [Discord](https://discord.gg/HtbKyuDDBw). As entries are not allowed to have any state, we create a `CinematicAction` everytime a entry is used in a cinematic for a player. ## Usage -```kotlin -@Entry("example_cinematic", "An example cinematic entry.", Colors.CYAN, Icons.TERMINAL) -class ExampleCinematicEntry( - override val id: String, - override val name: String, - override val criteria: List, - @Segments(Colors.CYAN, Icons.TERMINAL) - override val segments: List, -): CinematicEntry { - override fun create(player: Player): CinematicAction { - return ExampleCinematicAction(player, this) - } -} -``` + Segments sometimes need a minimum or maximum duration. This can be done using the `@InnerMin` and `@InnerMax` annotations. -```kotlin -@Segments(Colors.CYAN, Icons.TERMINAL) -@InnerMin(Min(10)) -@InnerMax(Max(20)) -override val segments: List, -``` + This will make sure that the segment will be at least 10 frames long and at most 20 frames long. -### ExampleSegment -```kotlin -data class ExampleSegment( - override val startFrame: Int, - override val endFrame: Int, -): Segment -``` +### ExampleSegment + ### ExampleCinematicAction @@ -66,48 +45,28 @@ There are a few different lifecycle methods that can be used. If you need all the customization, you can can implement the `CinematicAction` directly: -```kotlin -class ExampleCinematicAction( - override val player: Player, - override val entry: ExampleCinematicEntry, -): CinematicAction { - override fun setup() { - // Initialize variables, spawn entities, etc. - } - - override fun tick(frame: Int) { - val segment = entry.segments activeSegmentAt frame - // Can be null if no segment is active + - // Execute segments - } +### SimpleCinematicAction +Sometimes you don't need all the customization and flexiblity. If you only care about 1 segment track, and only need to do something when a segment starts or ends, you can use the `SimpleCinematicAction`. - override fun teardown() { - // Remove entities, etc. - } + - override fun canFinish(frame: Int): Boolean = entry.segments canFinishAt frame -} -``` +## Ticking -### SimpleCinematicAction -Sometimes you don't need all the customization and flexiblity. If you only care about 1 segment track, and only need to do something when a segment starts or ends, you can use the `SimpleCinematicAction`. +One important detail is that the `tick` methods are not necessarily called in order. +It is important that the tick method should show the state of the action at the given frame. -```kotlin -class ExampleCinematicAction( - override val player: Player, - override val entry: ExampleCinematicEntry, -): SimpleCinematicAction() { - override val segments: List = entry.segments +One place where this is definitely the case is when the player is viewing the cinematic in content mode. +As the player is able to scroll through the cinematic, it might be the case that multiple frames are skipped, or rewinded. - override suspend fun startSegment(segment: ExampleSegment) { - // Called when a segment starts - } +## Simulation & Recording - override suspend fun endSegment(segment: ExampleSegment) { - // Called when a segment ends - } -} -``` +Sometimes the cinematic should be different when it is being recorded or simulated. +Like the blinding cinematic, where you don't want to be blinded during simulation/recording. +Or you want to show a different thing during simulation/recording. +Like the camera which displays the camera in the world, instead of setting the player's camera. +To do this, there are 2 additional methods that can be implemented on the `CinematicEntry` that can return a different `CinematicAction` for recording and simulation. + diff --git a/documentation/docs/develop/02-adapters/03-entries/index.mdx b/documentation/docs/develop/02-adapters/03-entries/index.mdx index 1cfd4ab9af..6104f501f5 100644 --- a/documentation/docs/develop/02-adapters/03-entries/index.mdx +++ b/documentation/docs/develop/02-adapters/03-entries/index.mdx @@ -4,10 +4,11 @@ purpose in crafting immersive player experiences. This documentation explains the roles and functionalities of these entry types, providing clear guidance for developers on how to effectively use them. ## Base Entry Interfaces -There are three base interfaces that all entries extend one of. These are: +There are four base interfaces that all entries extend one of. These are: 1. **StaticEntry**: Represents static pages. These are used for content that does not change dynamically or trigger any actions. Examples include static text or images. 2. **TriggerEntry**: Designed for entries that initiate other actions or events. These entries can trigger one or more additional entries, making them crucial for interactive sequences. 3. **CinematicEntry**: Used for cinematic experiences. These entries are ideal for creating immersive story-driven sequences that engage players in a more visually dynamic way. +4. **ManifestEntry**: Used for creating manifest pages, which are used to display statful content to the player. ### 1. StaticEntry @@ -32,6 +33,22 @@ These are entries that can be triggered by other entries. They are the most comm ### 3. CinematicEntry - Primarily used for crafting cinematic experiences in-game, this base interface doesn't have listed specialized interfaces, but it's pivotal for creating story-driven, visually dynamic sequences. +### 4. ManifestEntry +The biggest `ManifestEntry` is the `AudienceEntry`. +It is used to define audiences that can be used to show stateful content to players. +Almost all other `ManifestEntry` are sub-types of `AudienceEntry`. +Though this is not required. +An example of an entry that is not a sub-type of `AudienceEntry` is the `EntityDefinitionEntry`. + +The following are the sub-types of `AudienceEntry`: + - **AudienceFilterEntry**: A variant of AudienceEntry that filters the audience passed to the children of the entry. + - **QuestEntry**: Specifies quest types. + - **ObjectiveEntry**: Base for objectives in quests. + - **LinesEntry**: Displays lines of text in things like sidebars or tablist. + +A whole nother category of `ManifestEntry` is meant for **Entities**. +This has it's seperate page in the documentation. + ## Implementation and Usage Each interface is designed with specific tags and methods to facilitate unique interactions within the TypeWriter plugin. @@ -54,12 +71,13 @@ The `@Entry` annotation is used to define the entry's type, name, and other prop - **name**: The name of the entry. This is used to identify the entry. - **description**: A short description of the entry. - **color**: The color of the entry in the editor. It can be one from the `Colors` class or a hex color code string. -- **icon**: The icon of the entry in the editor. It can be one from the `Icons` class. All icons are from [Font Awesome](https://fontawesome.com/search?o=r&m=free) free collection. +- **icon**: The icon of the entry in the editor. All available icons can be found on [Iconify](https://icones.js.org/). It needs to be the name of the icon. To find out specific requirements for each entry type, check the documentation for the entry's interface. :::caution Enties are not allowed to have any state. This means that there can't be any fields that are not final. +If you need to have state, use a `AudienceEntry`. ::: --- diff --git a/documentation/docs/develop/02-adapters/03-entries/manifest/_category_.yml b/documentation/docs/develop/02-adapters/03-entries/manifest/_category_.yml new file mode 100644 index 0000000000..9e66dcf9ef --- /dev/null +++ b/documentation/docs/develop/02-adapters/03-entries/manifest/_category_.yml @@ -0,0 +1 @@ +label: Manifest Entries diff --git a/documentation/docs/develop/02-adapters/03-entries/manifest/audience.mdx b/documentation/docs/develop/02-adapters/03-entries/manifest/audience.mdx new file mode 100644 index 0000000000..1bfc186e48 --- /dev/null +++ b/documentation/docs/develop/02-adapters/03-entries/manifest/audience.mdx @@ -0,0 +1,76 @@ +import CodeSnippet from "@site/src/components/CodeSnippet"; + +# AudienceEntry + +`AudienceEntry` is a crucial component in Typewriter that allows adapter developers to display content to a group of players. +Typewriter manages the players in the audience, providing hooks that developers can use to show information or interact with the players in the audience. + +The `AudienceEntry` is the most used `ManifestEntry` in Typewriter. + + +## Key Concepts + +1. **Stateful Displays**: In Typewriter, entries are not allowed to have any state. `AudienceEntry` bridges this gap by providing a way to have stateful displays. + +2. **Audience Management**: Typewriter takes care of managing the players in the audience. Developers can focus on defining how to display content and interact with the audience. + +3. **Display Lifecycle**: The `AudienceDisplay` class manages the lifecycle of the audience, including initialization, player addition/removal, and disposal. + +## Implementation + +### Basic AudienceEntry + +Here's an example of a basic `AudienceEntry` implementation: + + + +In this example, we define an `ExampleAudienceEntry` class that implements the `AudienceEntry` interface. +The `display()` function returns an `AudienceDisplay` object, which defines how the content is presented to the audience. + +### AudienceDisplay + +The `AudienceDisplay` class is responsible for displaying content to the audience. +It has lifecycle hooks to accomplish this. +Here's an example implementation: + + + +Key methods in `AudienceDisplay`: + +1. `initialize()`: Called when the first player is added to the audience. Use this method for any setup tasks. +2. `onPlayerAdd(player: Player)`: Invoked when a player joins the audience. Use this to set up player-specific content or effects. +3. `onPlayerRemove(player: Player)`: Called when a player leaves the audience. Use this for cleanup of player-specific resources. +4. `dispose()`: Invoked when the audience is being disposed of. Use this for general cleanup tasks. Note that `onPlayerRemove` will be called for all players, so player-specific cleanup is not needed here. + +### Tickable Audience Display + +For audiences that need to update regularly, you can implement the `TickableDisplay` interface: + + + +The `tick()` method will be called every Minecraft tick (20 times per second, or every 50ms) on an asynchronous thread. +This allows you to update the display or perform regular actions for all players in the audience. + +### Event Handling + +Every `AudienceDisplay` is also a Bukkit listener. +Event listeners will only be active when at least one player is in the audience. +Here's an example of how to handle events: + + + +:::caution Events for All Players +Events will trigger for _**all**_ players, not just those in the audience. + +**Always check if the player is in the audience before performing audience-specific actions.** +::: + +## Best Practices + +1. **State Management**: While state is allowed within the `AudienceDisplay`, ensure that all used state is contained within the display and does not leak outside. + +2. **Resource Management**: Ensure proper resource management to avoid memory leaks, especially when players leave the audience. + +3. **Event Handling**: When handling Bukkit events, always check if the player is in the audience before performing audience-specific actions. + +4. **Asynchronous Operations**: Be mindful that the `tick()` method runs on an asynchronous thread. Ensure thread safety when interacting with Bukkit API or shared resources. diff --git a/documentation/docs/develop/02-adapters/03-entries/static/artifact.mdx b/documentation/docs/develop/02-adapters/03-entries/static/artifact.mdx index 9378e18596..0550aff57f 100644 --- a/documentation/docs/develop/02-adapters/03-entries/static/artifact.mdx +++ b/documentation/docs/develop/02-adapters/03-entries/static/artifact.mdx @@ -1,3 +1,5 @@ +import CodeSnippet from "@site/src/components/CodeSnippet"; + # ArtifactEntry The `ArtifactEntry` is a specialized interface derived from `AssetEntry`. @@ -14,25 +16,11 @@ Here's a generic example of creating and using an `ArtifactEntry`: ### Defining an ArtifactEntry -```kotlin -@Entry("example_artifact", "An example artifact entry.", Colors.BLUE, Icons.ARROW) -class ExampleArtifactEntry( - override val id: String = "", - override val name: String = "", - override val artifactId: String = "", -) : ArtifactEntry -``` + ### Accessing the Artifact's Content -```kotlin -import org.koin.java.KoinJavaComponent.get - -val assetManager = get(AssetManager::class.java) - -val id = // ID of the entry -val entry = Query.findById(id) -val content: String = assetManager.fetchAsset(entry) -``` + -In this example, `ExampleArtifactEntry` is defined as an artifact with a unique identifier. The `assetManager.fetchAsset` method is then used to retrieve the content of the artifact, based on its `artifactId`. +In this example, we get the content of the given artifact reference by using the `AssetManager`. +The `assetManager.fetchAsset` method is then used to retrieve the content of the artifact, based the `entry`. diff --git a/documentation/docs/develop/02-adapters/03-entries/static/asset.mdx b/documentation/docs/develop/02-adapters/03-entries/static/asset.mdx index efa80c3f7e..603e7591a5 100644 --- a/documentation/docs/develop/02-adapters/03-entries/static/asset.mdx +++ b/documentation/docs/develop/02-adapters/03-entries/static/asset.mdx @@ -1,3 +1,5 @@ +import CodeSnippet from "@site/src/components/CodeSnippet"; + # AssetEntry The AssetEntry is a specialized interface that extends the `StaticEntry`. It is primarily used for handling static assets within the game. @@ -8,22 +10,10 @@ The key attribute of AssetEntry is the path, which specifies the location of the Here's a generic example of creating and using an `AssetEntry`: ### Defining an AssetEntry -```kotlin -@Entry("example_asset", "An example asset entry.", Colors.PINK, Icons.PERSON_WALKING) -class ExampleAssetEntry( - override val path: String = "", -): AssetEntry -``` + ### Accessing the Artifact's Content To get the asset from the entry, you can use the following code: -```kotlin -import org.koin.java.KoinJavaComponent.get - -val assetManager = get(AssetManager::class.java) -val id = // ID of the entry -val entry = Query.findById(id) -val content: String = assetManager.fetchAsset(entry) -``` + diff --git a/documentation/docs/develop/02-adapters/03-entries/static/sound_id.mdx b/documentation/docs/develop/02-adapters/03-entries/static/sound_id.mdx index 0a3ce8dc18..2dfb3b6d6e 100644 --- a/documentation/docs/develop/02-adapters/03-entries/static/sound_id.mdx +++ b/documentation/docs/develop/02-adapters/03-entries/static/sound_id.mdx @@ -1,23 +1,11 @@ +import CodeSnippet from "@site/src/components/CodeSnippet"; + # SoundIdEntry The `SoundIdEntry` is an interface derived from `StaticEntry`, specifically designed for managing custom sounds within the TypeWriter Spigot plugin. If a server is using a custom resource pack, the `SoundIdEntry` can be used to add a reference to a custom sound within the resource pack. ## Usage -```kotlin -@Entry("example_sound", "An example sound entry.", Colors.GREEN, Icons.VOLUME_HIGH) -class ExampleSoundIdEntry( - override val id: String, - override val name: String, - override val soundId: String, -) : SoundIdEntry -``` - -Normally a `SoundIdEntry` handled by the interface when an entry needs a sound. -If you ever would need to access the sound ID directly, it can be done like this: + -```kotlin -val id = // ID of the custom sound -val entry = Query.findById(id) -val customSound = entry.soundId -``` +A `SoundIdEntry` handled by the interface when an entry needs a sound. diff --git a/documentation/docs/develop/02-adapters/03-entries/static/sound_source.mdx b/documentation/docs/develop/02-adapters/03-entries/static/sound_source.mdx index c6a71a9368..bed5d7c774 100644 --- a/documentation/docs/develop/02-adapters/03-entries/static/sound_source.mdx +++ b/documentation/docs/develop/02-adapters/03-entries/static/sound_source.mdx @@ -1,19 +1,10 @@ +import CodeSnippet from "@site/src/components/CodeSnippet"; + # SoundSourceEntry The `SoundSourceEntry` class is used to have a sound play at a specific entity in the world. This can be a moving target like an NPC. The sound source can be used in an entry with a `Sound` parameter. ## Usage -```kotlin -@Entry("example_sound_source", "An example sound source entry.", Colors.GREEN, Icons.VOLUME_HIGH) -class ExampleSoundSourceEntry( - override val id: String, - override val name: String, -) : SoundSourceEntry { - fun getEmitter(): net.kyori.adventure.sound.Sound.Emitter { - // Return the emitter that should be used for the sound. - // A bukkit entity can be used here. - } -} -``` + This can be combined with other entry types like `Speaker`. For example, an NPC that speaks can also be a sound emitter. diff --git a/documentation/docs/develop/02-adapters/03-entries/static/speaker.mdx b/documentation/docs/develop/02-adapters/03-entries/static/speaker.mdx index da9b580cdf..e684d91439 100644 --- a/documentation/docs/develop/02-adapters/03-entries/static/speaker.mdx +++ b/documentation/docs/develop/02-adapters/03-entries/static/speaker.mdx @@ -1,3 +1,5 @@ +import CodeSnippet from "@site/src/components/CodeSnippet"; + # SpeakerEntry The `SpeakerEntry` is a specialized interface extending the `EntityEntry`. @@ -5,23 +7,7 @@ It is designed to enhance dialogues in the game by associating non-player charac This feature is pivotal for creating more immersive and interactive storytelling experiences in Minecraft. ## Usage -```kotlin -@Entry("example_speaker", "An example speaker entry.", Colors.BLUE, Icons.PERSON_TALKING) -class ExampleSpeakerEntry( - override val id: String = "", - override val name: String = "", - override val displayName: String = "", - override val sound: Sound = Sound.EMPTY, -) : SpeakerEntry -``` + This speaker can be used by users in various dialogues and interactions within the game. -Normally, you never need to access the `SpeakerEntry` directly, as it is automatically handled by the `DialogueSequence`. -If you ever do need to access the `SpeakerEntry`, you can do so: - -```kotlin -val id = // ID of the speaker -val entry = Query.findById(id) -val name = entry.displayName -val sound = entry.sound -``` +You almost never need to access the `SpeakerEntry` directly, as it is automatically handled by the `DialogueSequence`. diff --git a/documentation/docs/develop/02-adapters/03-entries/trigger/action.mdx b/documentation/docs/develop/02-adapters/03-entries/trigger/action.mdx index 355b02a4d5..76f95faf2d 100644 --- a/documentation/docs/develop/02-adapters/03-entries/trigger/action.mdx +++ b/documentation/docs/develop/02-adapters/03-entries/trigger/action.mdx @@ -1,26 +1,17 @@ +import CodeSnippet from "@site/src/components/CodeSnippet"; + # ActionEntry The `ActionEntry` defines an action to take. When it is triggered, it will run it's `execute` method. After which it will trigger all the next entries in the chain. -## Usage +:::danger Immutable Entry +It is important to stress that the entry must be immutable. +**It cannot have any mutable state.** +::: -```kotlin -@Entry("example_action", "An example action entry.", Colors.RED, Icons.PERSON_RUNNING) -class ExampleActionEntry( - override val id: String = "", - override val name: String = "", - override val criteria: List, - override val modifiers: List, - override val triggers: List = emptyList(), -): ActionEntry { - override fun execute(player: Player) { - // Do something - super.execute(player) // This will apply all the modifiers. - } -} -``` +## Usage + Typewriter will automatically trigger the next entries in the chain after the `execute` method is called. -If you want to call the next entries in the chain manually, you can should the `CustomTriggeringActionEntry`. - +If you want to call the next entries in the chain manually, you can should the [CustomTriggeringActionEntry](./custom_triggering_action.mdx). diff --git a/documentation/docs/develop/02-adapters/03-entries/trigger/custom_triggering_action.mdx b/documentation/docs/develop/02-adapters/03-entries/trigger/custom_triggering_action.mdx index 3793d170d4..0adb38d814 100644 --- a/documentation/docs/develop/02-adapters/03-entries/trigger/custom_triggering_action.mdx +++ b/documentation/docs/develop/02-adapters/03-entries/trigger/custom_triggering_action.mdx @@ -1,23 +1,9 @@ +import CodeSnippet from "@site/src/components/CodeSnippet"; + # CustomTriggeringActionEntry The `CustomTriggeringActionEntry` is a specialised verion of the `ActionEntry` that allows you to trigger the next entries when you want. Or just call a subset of entries. ## Usage -```kotlin -@Entry("example_custom_triggering_action", "An example custom triggering entry.", Colors.RED, Icons.SOLID_HOURGLASS_HALF) -class ExampleCustomTriggeringActionEntry( - override val id: String = "", - override val name: String = "", - override val criteria: List = emptyList(), - override val modifiers: List = emptyList(), - @SerializedName("triggers") - override val customTriggers: List = emptyList(), -) : CustomTriggeringActionEntry { - override fun execute(player: Player) { - super.execute(player) // This will apply the modifiers. - player.triggerCustomTriggers() // Can be called later to trigger the next entries. - } -} -``` - + diff --git a/documentation/docs/develop/02-adapters/03-entries/trigger/dialogue.mdx b/documentation/docs/develop/02-adapters/03-entries/trigger/dialogue.mdx index f11c7bfbaf..630f94185b 100644 --- a/documentation/docs/develop/02-adapters/03-entries/trigger/dialogue.mdx +++ b/documentation/docs/develop/02-adapters/03-entries/trigger/dialogue.mdx @@ -1,3 +1,5 @@ +import CodeSnippet from "@site/src/components/CodeSnippet"; + # DialogueEntry The `DialogueEntry` is used to define a type of dialogue. When a `DialogueEntry` is triggered it's associated `DialogueMessenger` will be used to display the dialogue to the player. @@ -9,47 +11,15 @@ This is automatically handled by Typewriter. ::: ## Usage -```kotlin -@Entry("example_dialogue", "An example dialogue entry.", Colors.BLUE, Icons.MESSAGE) -class ExampleDialogueEntry( -) : SoundIdEntr override val id: String = "", - override val name: String = "", - override val criteria: List = emptyList(), - override val modifiers: List = emptyList(), - override val triggers: List = emptyList(), - override val speaker: String = "", - @MultiLine - @Placeholder - @Colored - @Help("The text to display to the player.") - val text: String = "", -): DialogueEntry -``` + To define the messenger that will be used to display the dialogue to the player, you must create a class that implements the `DialogueMessenger` interface. -```kotlin -@Messenger(ExampleDialogueEntry::class) -class UniversalMessageDialogueDialogueMessenger(player: Player, entry: ExampleDialogueEntry) : - DialogueMessenger(player, entry) { - - companion object : MessengerFilter { - override fun filter(player: Player, entry: DialogueEntry): Boolean = true - } - - // Called every game tick (20 times per second). - // The cycle is a parameter that is incremented every tick, starting at 0. - override fun tick(cycle: Int) { - super.tick(cycle) - if (cycle == 0) { - player.sendMessage("${entry.speakerDisplayName}: ${entry.text}".parsePlaceholders(player).asMini()) - state = MessengerState.FINISHED - } - } -} -``` + +### Multiple Messengers The `DialogueMessenger` has a `MessengerFilter` that is used to determine if the messenger should be used to display the dialogue to the player. When having multiple `MessageFilter`'s make sure that they are deterministic. So if you have some condition, such as if they are bedrock players. One message check that the player is a bedrock player and the other filter check that they are not. +### Lifecycle The `state` of the messenger determines what happens to the messenger. - `MessengerState.FINISHED` - The dialogue is finished and the next dialogue in the chain will be triggered. - `MessengerState.CANCELLED` - The dialogue is cancelled and dialogue chain is stopped, even if there are more dialogues in the chain. diff --git a/documentation/docs/develop/02-adapters/03-entries/trigger/event.mdx b/documentation/docs/develop/02-adapters/03-entries/trigger/event.mdx index c8de928515..f531a32d29 100644 --- a/documentation/docs/develop/02-adapters/03-entries/trigger/event.mdx +++ b/documentation/docs/develop/02-adapters/03-entries/trigger/event.mdx @@ -1,27 +1,21 @@ +import CodeSnippet from "@site/src/components/CodeSnippet"; + # EventEntry The `EventEntry` is used as a starting point for any sequence. It can have external event listeners listening to events and trigger based on that. ## Usage -```kotlin -@Entry("example_event", "An example event entry.", Colors.YELLOW, Icons.CIRCLE) -class ExampleEventEntry( - override val id: String, - override val name: String, - override val triggers: List = emptyList(), -): EventEntry - -// Must be scoped to be public -@EntryListener(ExampleEventEntry::class) -fun onEvent(event: SomeBukkitEvent, query: Query) { - // Do something - val entries = query.find() // Find all the entries of this type, for more information see the Query section - // Do something with the entries, for example trigger them - entries triggerAllFor event.player -} -``` + + +To listen to an event, you must create a function that is annotated with `@EntryListener`. +The great thing about kotlin, is that this can be done in the same file as the entry. + +:::warning Public Function If the function is not scoped to be public, it will not be registered as a listener. -The function will automatically be registered as a listener for the event by Typewriter and be called when the Bukkit event is trigger. An optional `Query` parameter can be added to easily fetch all the different event entries. +::: + +The function will automatically be registered as a listener for the event by Typewriter and be called when the Bukkit event is trigger. +An optional `Query` parameter can be added to easily fetch all the different event entries. diff --git a/documentation/docs/develop/02-adapters/index.mdx b/documentation/docs/develop/02-adapters/index.mdx index bfcd418151..7d94ed4e79 100644 --- a/documentation/docs/develop/02-adapters/index.mdx +++ b/documentation/docs/develop/02-adapters/index.mdx @@ -1,10 +1,10 @@ # Adapters ## Introduction -TypeWriter is a dynamic platform that supports the development of adapters, which are modular components enhancing the overall functionality. Adapters are self-contained, easily shareable, and integrate smoothly into the TypeWriter system. This guide is tailored to guide you through the process of creating an adapter, suitable for both beginners and experienced developers. +TypeWriter is a dynamic platform that supports the development of adapters, which are modular components enhancing the overall functionality. +Adapters are self-contained, easily shareable, and integrate smoothly into the TypeWriter system. +They allow you to create custom entries and have them show up in the web panel. +This guide is tailored to guide you through the process of creating an adapter, suitable for both beginners and experienced developers. :::info -It is highly recommended to write adapters in Kotlin, as it is the primary language of TypeWriter. Though, it is possible to write adapters in Java, not all features are usable in Java. +It is highly recommended to write adapters in Kotlin, as it is the primary language of TypeWriter. **Though, it is currently possible to write adapters in Java, this will no longer be possible in v0.6.** ::: - -## Guides -- [Getting Started](./02-getting_started.mdx) diff --git a/documentation/docs/develop/README.mdx b/documentation/docs/develop/README.mdx index 1a3a383bb5..2079e10db9 100644 --- a/documentation/docs/develop/README.mdx +++ b/documentation/docs/develop/README.mdx @@ -2,9 +2,11 @@ sidebar_position: 1 --- # Development -Typewriter has different parts that can be developed upon. The easiest one are adapters. These are self-containted building blocks that can easaly be shared and added to the system. -Then there are the plugin systems. These are core parts of how Typewriter works. All help is welcome, but please discuss it first as there changes might impact a lot of users. -Lastly there is the UI. This is written in Flutter and is the most complex part of Typewriter. It is also the most fun to work on, as this is where you can really see the results of your work. +Typewriter has different parts that can be developed upon. + +- **Adapters:** These are self-containted building blocks that can easaly be shared and added to the system. +- **Plugin:** These are core parts of how Typewriter works. All help is welcome, but please discuss it first as there changes might impact a lot of users. +- **UI:** This is written in Flutter and is the most complex part of Typewriter. It is also the most fun to work on, as this is where you can really see the results of your work. :::danger important This part of the documentation is still under construction. If you want to help, please contact us on [Discord](https://discord.gg/HtbKyuDDBw). diff --git a/documentation/docusaurus.config.js b/documentation/docusaurus.config.js index 1e256228bf..740dc9f302 100644 --- a/documentation/docusaurus.config.js +++ b/documentation/docusaurus.config.js @@ -173,6 +173,16 @@ const config = { line: 'highlight-green', // For single line block: { start: 'highlight-green-start', end: 'highlight-green-end' }, // For block }, + { + className: 'highlight-blue', + line: 'highlight-blue', // For single line + block: { start: 'highlight-blue-start', end: 'highlight-blue-end' }, // For block + }, + { + className: 'highlight-line', + line: 'highlight-next-line', // For single line + block: { start: 'highlight-start', end: 'highlight-end' }, // For block + }, ] }, algolia: { @@ -194,6 +204,7 @@ const config = { "rive-loader", require.resolve("./plugins/tailwind/index.js"), require.resolve("./plugins/compression/index.js"), + require.resolve("./plugins/code-snippets/index.js"), ] }; diff --git a/documentation/plugins/code-snippets/codeSnippets.js b/documentation/plugins/code-snippets/codeSnippets.js new file mode 100644 index 0000000000..f9790e1eff --- /dev/null +++ b/documentation/plugins/code-snippets/codeSnippets.js @@ -0,0 +1,75 @@ +const fs = require('fs'); +const path = require('path'); + +const codeBockRegex = /\/\/<(?\/)?code-block:(?[a-zA-Z0-9_]*)>$/; + +module.exports = function() { + const snippets = {}; + + function processFile(filePath, relativeFilePath) { + const content = fs.readFileSync(filePath, 'utf8'); + const lines = content.split('\n'); + const blocks = {}; + + lines.forEach((line, index) => { + const match = line.match(codeBockRegex); + if (match) { + const { closing, tag } = match.groups; + if (closing) { + const code = blocks[tag]; + if (!code) { + throw new Error(`Code block not closed: ${tag} (${relativeFilePath}:${index + 1})`); + } + blocks[tag] = null; + snippets[tag] = { + file: relativeFilePath, + content: code.join('\n') + }; + return; + } + + blocks[tag] = []; + } else { + for (const tag in blocks) { + const code = blocks[tag]; + if (!code) continue; + code.push(line); + } + } + }); + + // Handle any unclosed blocks + for (const tag in blocks) { + const code = blocks[tag]; + if (!code) continue; + snippets[tag] = { + file: relativeFilePath, + content: code.join('\n') + }; + } + } + + function traverseDirectory(dir, baseDir) { + const files = fs.readdirSync(dir); + files.forEach(file => { + const filePath = path.join(dir, file); + const relativeFilePath = path.relative(baseDir, filePath); + const stat = fs.statSync(filePath); + if (stat.isDirectory()) { + traverseDirectory(filePath, baseDir); + } else if (path.extname(filePath) === '.kt') { + processFile(filePath, relativeFilePath); + } + }); + } + + // Assuming the adapters directory is at the root of your project + const adaptersDir = path.resolve(__dirname, '../../../adapters/_DocsAdapter'); + traverseDirectory(adaptersDir, adaptersDir); + + // console.log(`Exporting ${Object.keys(snippets).length} snippets: ${Object.keys(snippets)}`); + + this.cacheable(false); + + return `export default ${JSON.stringify(snippets, null, 2)}`; +}; diff --git a/documentation/plugins/code-snippets/index.js b/documentation/plugins/code-snippets/index.js new file mode 100644 index 0000000000..4e56d94bb4 --- /dev/null +++ b/documentation/plugins/code-snippets/index.js @@ -0,0 +1,24 @@ +const path = require('path'); +const crypto = require('crypto'); + +export default function codeSnippets() { + return { + name: 'code-snippets', + configureWebpack(_config, isServer) { + return { + module: { + rules: [ + { + test: /snippets\.ts$/, + use: [ + { + loader: path.resolve(__dirname, 'codeSnippets.js'), + }, + ], + }, + ], + }, + }; + }, + }; +} diff --git a/documentation/src/components/CodeSnippet/index.tsx b/documentation/src/components/CodeSnippet/index.tsx new file mode 100644 index 0000000000..003b006a1f --- /dev/null +++ b/documentation/src/components/CodeSnippet/index.tsx @@ -0,0 +1,23 @@ +import codeSnippets from "./snippets"; +import CodeBlock from '@theme/CodeBlock'; + +interface CodeSnippetProps { + tag: string; +} + +export default function CodeSnippet({ tag }: CodeSnippetProps) { + const codeSnippet = codeSnippets[tag]; + if (codeSnippet == null) { + return
Code snippet not found: {tag}
; + } + const { file, content } = codeSnippet; + if (file == null || content == null) { + return
Code snippet not found: {tag} ({codeSnippet})
; + } + const fileName = file.split('/').pop(); + return ( + + {content} + + ); +} diff --git a/documentation/src/components/CodeSnippet/snippets.ts b/documentation/src/components/CodeSnippet/snippets.ts new file mode 100644 index 0000000000..b4d2f942b4 --- /dev/null +++ b/documentation/src/components/CodeSnippet/snippets.ts @@ -0,0 +1,2 @@ +// This file will be replaced with the processed snippets +export default {}; diff --git a/documentation/src/css/custom.css b/documentation/src/css/custom.css index a63b9787b8..a3717768e4 100644 --- a/documentation/src/css/custom.css +++ b/documentation/src/css/custom.css @@ -108,23 +108,71 @@ border-radius: 0.5rem; } +code { + border-collapse: collapse; +} + code span { margin: 0; } +/* Line Numbers */ +.token-line > span:first-child:not(.token) { + background-color: transparent; +} + +.token-line > span:nth-child(2):not(.token) { + display: table-cell; +} + .highlight-red { background-color: #ff000020; /* Light red background */ - border-left: 3px solid #ff000080; /* Darker red border */ display: block; margin: 0 calc(-1 * var(--ifm-pre-padding)); padding: 0 var(--ifm-pre-padding); + border-left: 3px solid #ff000080; +} + +[data-theme='dark'] .highlight-red { + background-color: #e52e4d20; + border-left: 3px solid #e52e4d80; } .highlight-green { background-color: #00ff0020; /* Light green background */ - border-left: 3px solid #00ff0080; /* Darker green border */ display: block; margin: 0 calc(-1 * var(--ifm-pre-padding)); padding: 0 var(--ifm-pre-padding); + border-left: 3px solid #00ff0080; +} + +[data-theme='dark'] .highlight-green { + background-color: #00ff000d; + border-left: 3px solid #00ff0070; } +.highlight-blue { + background-color: #0000ff20; /* Light blue background */ + display: block; + margin: 0 calc(-1 * var(--ifm-pre-padding)); + padding: 0 var(--ifm-pre-padding); + border-left: 3px solid #0000ff80; +} + +[data-theme='dark'] .highlight-blue { + background-color: #396ce320; + border-left: 3px solid #396ce380; +} + +.highlight-line { + background-color: #0000000d; + display: block; + margin: 0 calc(-1 * var(--ifm-pre-padding)); + padding: 0 var(--ifm-pre-padding); + border-left: 2px solid #00000080; +} + +[data-theme='dark'] .highlight-line { + background-color: #ffffff0d; + border-left: 2px solid #ffffff80; +} diff --git a/plugin/build.gradle.kts b/plugin/build.gradle.kts index 12b43eb70e..7ca0a6bfd8 100644 --- a/plugin/build.gradle.kts +++ b/plugin/build.gradle.kts @@ -28,7 +28,6 @@ repositories { // Floodgate maven("https://repo.opencollab.dev/maven-snapshots/") // PacketEvents, CommandAPI - maven("https://repo.codemc.io/repository/maven-snapshots/") maven("https://repo.codemc.io/repository/maven-releases/") // PlaceholderAPI maven("https://repo.extendedclip.com/content/repositories/placeholderapi/") @@ -97,12 +96,6 @@ kotlin { jvmToolchain(languageJavaVersion) } -tasks.withType().configureEach { - kotlinOptions { - jvmTarget = "$languageJavaVersion" - } -} - tasks.test { useJUnitPlatform() }