diff --git a/.github/actions/build_adapter/action.yml b/.github/actions/build-adapter/action.yml similarity index 100% rename from .github/actions/build_adapter/action.yml rename to .github/actions/build-adapter/action.yml diff --git a/.github/actions/build_plugin/action.yml b/.github/actions/build-plugin/action.yml similarity index 96% rename from .github/actions/build_plugin/action.yml rename to .github/actions/build-plugin/action.yml index 7ecf405dfa..9825211e45 100644 --- a/.github/actions/build_plugin/action.yml +++ b/.github/actions/build-plugin/action.yml @@ -4,7 +4,6 @@ description: "Builds the plugin and runs tests" runs: using: "composite" steps: - - uses: actions/checkout@v2 - name: Setup Flutter uses: subosito/flutter-action@v2 with: diff --git a/.github/workflows/build-adapter.yml b/.github/workflows/build-adapter.yml index 66a9b12a9e..818ae59b2f 100644 --- a/.github/workflows/build-adapter.yml +++ b/.github/workflows/build-adapter.yml @@ -19,16 +19,10 @@ jobs: with: distribution: temurin java-version: 17 - - name: Test Adapter - uses: gradle/gradle-build-action@v2 - with: - arguments: test --scan - build-root-directory: ./adapters/${{ inputs.adapter }} - name: Build Adapter - uses: gradle/gradle-build-action@v2 + uses: ./.github/actions/build-adapter with: - arguments: buildRelease --scan - build-root-directory: ./adapters/${{ inputs.adapter }} + adapter: ${{ inputs.adapter }} - name: Upload Adapter uses: actions/upload-artifact@v2 with: diff --git a/.github/workflows/build-development-jars-and-publish.yml b/.github/workflows/build-development-jars-and-publish.yml index 718dad088e..03da31f7e5 100644 --- a/.github/workflows/build-development-jars-and-publish.yml +++ b/.github/workflows/build-development-jars-and-publish.yml @@ -3,7 +3,7 @@ name: Build Development Jars and Publish on: pull_request: branches: - - develop + - main paths: - 'plugins/**' - 'adapters/**' @@ -19,6 +19,14 @@ jobs: with: distribution: temurin java-version: 17 + - name: Get version + id: vars + run: | + version=$(cat version.txt) + short_sha=$(echo $GITHUB_SHA | cut -c1-7) + full_version="${version}-dev-${short_sha}" + echo $full_version > version.txt + echo "version=$full_version" >> $GITHUB_OUTPUT - name: Build Plugin uses: ./.github/actions/build-plugin - name: Build Basic Adapter @@ -53,3 +61,34 @@ jobs: uses: ./.github/actions/build-adapter with: adapter: WorldGuardAdapter + - name: Publish Development Jars + uses: Kir-Antipov/mc-publish@v3.3 + id: publish + with: + modrinth-id: "Vm7B3ymm" + modrinth-token: ${{ secrets.MODRINTH_TOKEN }} + modrinth-featured: false + files: | + plugin/build/libs/typewriter.jar + adapters/**/build/libs/*.jar + name: "Typewriter v${{ steps.vars.outputs.version }} Development Build" + version: "${{ steps.vars.outputs.version }}" + version-type: "beta" + loaders: | + paper + purpur + game-versions: | + 1.19.4 + [1.20, 1.20.2] + - name: Notify Discord + uses: sarisia/actions-status-discord@v1 + with: + webhook: ${{ secrets.DISCORD_WEBHOOK }} + nodetail: true + title: Published Development Build + description: | + I have published a development build of Typewriter. + Version: ${{ steps.vars.outputs.version }} + [Download](https://modrinth.com/plugin/typewriter/version/${{ steps.publish.outputs.modrinth-version }}) + + diff --git a/.github/workflows/build-plugin.yml b/.github/workflows/build-plugin.yml index 79bb9fa092..69a9fad7ef 100644 --- a/.github/workflows/build-plugin.yml +++ b/.github/workflows/build-plugin.yml @@ -6,35 +6,14 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - name: Setup Flutter - uses: subosito/flutter-action@v2 - with: - channel: "master" - - name: Get Flutter dependencies - run: flutter pub get - working-directory: ./app - - name: Run tests - run: flutter test - working-directory: ./app - - name: Build web app - run: flutter build web --release --no-tree-shake-icons - working-directory: ./app + - uses: actions/checkout@v3 - name: Setup Java uses: actions/setup-java@v3 with: distribution: temurin java-version: 17 - - name: Test Plugin - uses: gradle/gradle-build-action@v2 - with: - arguments: test --scan - build-root-directory: ./plugin - name: Build Plugin - uses: gradle/gradle-build-action@v2 - with: - arguments: buildRelease --scan - build-root-directory: ./plugin + uses: ./.github/actions/build-plugin - name: Upload Plugin uses: actions/upload-artifact@v2 with: diff --git a/adapters/BasicAdapter/build.gradle.kts b/adapters/BasicAdapter/build.gradle.kts index 0b5cc5ee79..75a674ce32 100644 --- a/adapters/BasicAdapter/build.gradle.kts +++ b/adapters/BasicAdapter/build.gradle.kts @@ -22,9 +22,9 @@ repositories { dependencies { compileOnly(kotlin("stdlib")) - compileOnly("io.papermc.paper:paper-api:1.20.1-R0.1-SNAPSHOT") + compileOnly("io.papermc.paper:paper-api:1.20.4-R0.1-SNAPSHOT") - compileOnly("me.gabber235:typewriter:$version") + compileOnly("com.github.gabber235:typewriter:$version") // Already included in the TypeWriter plugin compileOnly("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.0-RC") @@ -90,5 +90,6 @@ task("buildRelease") // Rename the jar to remove the version and -all val jar = file("build/libs/%s-%s-all.jar".format(project.name, project.version)) jar.renameTo(file("build/libs/%s.jar".format(project.name))) + file("build/libs/%s-%s.jar".format(project.name, project.version)).delete() } } \ No newline at end of file diff --git a/adapters/BasicAdapter/src/main/kotlin/me/gabber235/typewriter/entries/action/ConsoleCommandActionEntry.kt b/adapters/BasicAdapter/src/main/kotlin/me/gabber235/typewriter/entries/action/ConsoleCommandActionEntry.kt index d897bab5cd..5e63da30ab 100644 --- a/adapters/BasicAdapter/src/main/kotlin/me/gabber235/typewriter/entries/action/ConsoleCommandActionEntry.kt +++ b/adapters/BasicAdapter/src/main/kotlin/me/gabber235/typewriter/entries/action/ConsoleCommandActionEntry.kt @@ -5,6 +5,7 @@ import lirand.api.extensions.server.commands.dispatchCommand import me.gabber235.typewriter.adapters.Colors import me.gabber235.typewriter.adapters.Entry 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.Criteria import me.gabber235.typewriter.entry.Modifier @@ -30,15 +31,19 @@ class ConsoleCommandActionEntry( override val modifiers: List = emptyList(), override val triggers: List = emptyList(), @Placeholder - @Help("The command to run. (Use %player_name% for the player's name)") - // The command that the console will run. + @MultiLine + @Help("The command(s) to run.") + // Every line is a different command. Commands should not be prefixed with /. private val command: String = "", ) : ActionEntry { override fun execute(player: Player) { super.execute(player) // Run in the main thread plugin.launch { - Bukkit.getConsoleSender().dispatchCommand(command.parsePlaceholders(player)) + val commands = command.parsePlaceholders(player).lines() + for (cmd in commands) { + Bukkit.getConsoleSender().dispatchCommand(cmd) + } } } } \ No newline at end of file diff --git a/adapters/BasicAdapter/src/main/kotlin/me/gabber235/typewriter/entries/action/DropItemActionEntry.kt b/adapters/BasicAdapter/src/main/kotlin/me/gabber235/typewriter/entries/action/DropItemActionEntry.kt index 67440899b4..420bca7a9b 100644 --- a/adapters/BasicAdapter/src/main/kotlin/me/gabber235/typewriter/entries/action/DropItemActionEntry.kt +++ b/adapters/BasicAdapter/src/main/kotlin/me/gabber235/typewriter/entries/action/DropItemActionEntry.kt @@ -1,24 +1,20 @@ package me.gabber235.typewriter.entries.action import com.github.shynixn.mccoroutine.bukkit.launch -import lirand.api.extensions.inventory.meta +import com.google.gson.JsonObject +import lirand.api.extensions.other.set import me.gabber235.typewriter.adapters.Colors import me.gabber235.typewriter.adapters.Entry -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.Criteria -import me.gabber235.typewriter.entry.Modifier +import me.gabber235.typewriter.entry.* import me.gabber235.typewriter.entry.entries.ActionEntry import me.gabber235.typewriter.plugin import me.gabber235.typewriter.utils.Icons -import me.gabber235.typewriter.utils.asMini +import me.gabber235.typewriter.utils.Item +import me.gabber235.typewriter.utils.optional import org.bukkit.Location import org.bukkit.Material import org.bukkit.entity.Player -import org.bukkit.inventory.ItemStack -import org.bukkit.inventory.meta.ItemMeta import java.util.* @Entry("drop_item", "Drop an item at location, or on player", Colors.RED, Icons.DROPBOX) @@ -39,39 +35,42 @@ class DropItemActionEntry( override val modifiers: List, override val triggers: List = emptyList(), @Help("The item to drop.") - private val material: Material = Material.AIR, - @Help("The amount of items to drop.") - private val amount: Int = 1, - @Colored - @Placeholder - @Help("The display name of the item. (Defaults to the material's display name)") - // The display name of the item to drop. If not specified, the item will have its default display name. - private val displayName: String = "", - @MultiLine - @Colored - @Placeholder - @Help("The lore of the item. (Defaults to the item's lore)") - // The lore of the item to drop. If not specified, the item will have its default lore. - private val lore: String, + val item: Item = Item.Empty, @Help("The location to drop the item. (Defaults to the player's location)") // The location to drop the item at. If this field is left blank, the item will be dropped at the location of the player triggering the action. private val location: Optional = Optional.empty(), ) : ActionEntry { override fun execute(player: Player) { super.execute(player) - val item = ItemStack(material, amount).meta { - if (this@DropItemActionEntry.displayName.isNotBlank()) displayName(this@DropItemActionEntry.displayName.asMini()) - if (this@DropItemActionEntry.lore.isNotBlank()) { - lore(this@DropItemActionEntry.lore.split("\n").map { "$it".asMini() }) - } - } // Run on main thread plugin.launch { if (location.isPresent) { - location.get().world.dropItem(location.get(), item) + location.get().world.dropItem(location.get(), item.build(player)) } else { - player.location.world.dropItem(player.location, item) + player.location.world.dropItem(player.location, item.build(player)) } } } -} \ No newline at end of file +} + +@EntryMigration(DropItemActionEntry::class, "0.4.0") +@NeedsMigrationIfContainsAny(["material", "amount", "displayName", "lore"]) +fun migrate040DropItemAction(json: JsonObject, context: EntryMigratorContext): JsonObject { + val data = JsonObject() + data.copyAllBut(json, "material", "amount", "displayName", "lore") + + val material = json.getAndParse("material", context.gson).optional + val amount = json.getAndParse("amount", context.gson).optional + val displayName = json.getAndParse("displayName", context.gson).optional + val lore = json.getAndParse("lore", context.gson).optional + + val item = Item( + material = material, + amount = amount, + name = displayName, + lore = lore, + ) + data["item"] = context.gson.toJsonTree(item) + + return data +} diff --git a/adapters/BasicAdapter/src/main/kotlin/me/gabber235/typewriter/entries/action/GiveItemActionEntry.kt b/adapters/BasicAdapter/src/main/kotlin/me/gabber235/typewriter/entries/action/GiveItemActionEntry.kt index c9da0f4ec7..5a14025a37 100644 --- a/adapters/BasicAdapter/src/main/kotlin/me/gabber235/typewriter/entries/action/GiveItemActionEntry.kt +++ b/adapters/BasicAdapter/src/main/kotlin/me/gabber235/typewriter/entries/action/GiveItemActionEntry.kt @@ -1,21 +1,17 @@ package me.gabber235.typewriter.entries.action -import lirand.api.extensions.inventory.meta +import com.google.gson.JsonObject +import lirand.api.extensions.other.set import me.gabber235.typewriter.adapters.Colors import me.gabber235.typewriter.adapters.Entry -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.Criteria -import me.gabber235.typewriter.entry.Modifier +import me.gabber235.typewriter.entry.* import me.gabber235.typewriter.entry.entries.ActionEntry import me.gabber235.typewriter.utils.Icons -import me.gabber235.typewriter.utils.asMini +import me.gabber235.typewriter.utils.Item +import me.gabber235.typewriter.utils.optional import org.bukkit.Material import org.bukkit.entity.Player -import org.bukkit.inventory.ItemStack -import org.bukkit.inventory.meta.ItemMeta @Entry("give_item", "Give an item to the player", Colors.RED, Icons.WAND_SPARKLES) /** @@ -32,34 +28,33 @@ class GiveItemActionEntry( override val modifiers: List, override val triggers: List = emptyList(), @Help("The item to give.") - // The Minecraft material of the item to give. - private val material: Material = Material.AIR, - @Help("The amount of items to give.") - private val amount: Int = 1, - @Colored - @Placeholder - @Help("The display name of the item. (Defaults to the item's display name)") - // The display name of the item to give. If not specified, the item will have it's default display name. - private val displayName: String = "", - @MultiLine - @Colored - @Placeholder - @Help("The lore of the item. (Defaults to the item's lore)") - // The lore of the item to give. If not specified, the item will have it's default lore. - private val lore: String, + val item: Item = Item.Empty, ) : ActionEntry { override fun execute(player: Player) { super.execute(player) - val item = ItemStack(material, amount).meta { - if (this@GiveItemActionEntry.displayName.isNotBlank()) displayName(this@GiveItemActionEntry.displayName.asMini()) - if (this@GiveItemActionEntry.lore.isNotBlank()) { - lore( - this@GiveItemActionEntry.lore.split("\n").map { "$it".asMini() }) + player.inventory.addItem(item.build(player)) + } +} - } - } +@EntryMigration(GiveItemActionEntry::class, "0.4.0") +@NeedsMigrationIfContainsAny(["material", "amount", "displayName", "lore"]) +fun migrate040GiveItemAction(json: JsonObject, context: EntryMigratorContext): JsonObject { + val data = JsonObject() + data.copyAllBut(json, "material", "amount", "displayName", "lore") - player.inventory.addItem(item) - } + val material = json.getAndParse("material", context.gson).optional + val amount = json.getAndParse("amount", context.gson).optional + val displayName = json.getAndParse("displayName", context.gson).optional + val lore = json.getAndParse("lore", context.gson).optional + + val item = Item( + material = material, + amount = amount, + name = displayName, + lore = lore, + ) + data["item"] = context.gson.toJsonTree(item) + + return data } \ No newline at end of file diff --git a/adapters/BasicAdapter/src/main/kotlin/me/gabber235/typewriter/entries/action/PlaySoundActionEntry.kt b/adapters/BasicAdapter/src/main/kotlin/me/gabber235/typewriter/entries/action/PlaySoundActionEntry.kt index efa1c1d820..4ebb4ee78c 100644 --- a/adapters/BasicAdapter/src/main/kotlin/me/gabber235/typewriter/entries/action/PlaySoundActionEntry.kt +++ b/adapters/BasicAdapter/src/main/kotlin/me/gabber235/typewriter/entries/action/PlaySoundActionEntry.kt @@ -1,16 +1,17 @@ package me.gabber235.typewriter.entries.action +import com.google.gson.JsonObject +import lirand.api.extensions.other.set import me.gabber235.typewriter.adapters.Colors import me.gabber235.typewriter.adapters.Entry import me.gabber235.typewriter.adapters.modifiers.Help -import me.gabber235.typewriter.adapters.modifiers.Sound -import me.gabber235.typewriter.entry.Criteria -import me.gabber235.typewriter.entry.Modifier +import me.gabber235.typewriter.entry.* import me.gabber235.typewriter.entry.entries.ActionEntry -import me.gabber235.typewriter.utils.Icons +import me.gabber235.typewriter.utils.* import org.bukkit.Location import org.bukkit.entity.Player import java.util.* +import kotlin.jvm.optionals.getOrDefault @Entry("play_sound", "Play sound at player, or location", Colors.RED, Icons.MUSIC) /** @@ -26,24 +27,35 @@ class PlaySoundActionEntry( override val criteria: List = emptyList(), override val modifiers: List = emptyList(), override val triggers: List = emptyList(), - @Sound @Help("The sound to play.") - val sound: String = "", - @Help("The location to play the sound from. (Defaults to player's location)") - // The location to play the sound at. If this field is left blank, the sound will be played at the location of the player triggering the action. - val location: Optional = Optional.empty(), - @Help("The volume of the sound.") - val volume: Float = 1.0f, - @Help("The pitch of the sound.") - val pitch: Float = 1.0f, + val sound: Sound = Sound.EMPTY, ) : ActionEntry { override fun execute(player: Player) { super.execute(player) - if (location.isPresent) { - location.get().world?.playSound(location.get(), sound, volume, pitch) - } else { - player.playSound(player, sound, volume, pitch) - } + player.playSound(sound) } +} + +@EntryMigration(PlaySoundActionEntry::class, "0.4.0") +@NeedsMigrationIfNotParsable +fun migrate040PlaySoundAction(json: JsonObject, context: EntryMigratorContext): JsonObject { + val data = JsonObject() + data.copyAllBut(json, "sound", "location", "volume", "pitch") + + val soundString = json.getAndParse("sound", context.gson).optional + val location = json.getAndParse>("location", context.gson).optional + val volume = json.getAndParse("volume", context.gson).optional + val pitch = json.getAndParse("pitch", context.gson).optional + + val sound = Sound( + soundId = soundString.map(::DefaultSoundId).getOrDefault(SoundId.EMPTY), + soundSource = location.map(::LocationSoundSource).getOrDefault(SelfSoundSource), + volume = volume.orElse(1.0f), + pitch = pitch.orElse(1.0f), + ) + + data["sound"] = context.gson.toJsonTree(sound) + + return data } \ No newline at end of file diff --git a/adapters/BasicAdapter/src/main/kotlin/me/gabber235/typewriter/entries/action/PlayerCommandActionEntry.kt b/adapters/BasicAdapter/src/main/kotlin/me/gabber235/typewriter/entries/action/PlayerCommandActionEntry.kt index d7279ece26..f8b4a2274d 100644 --- a/adapters/BasicAdapter/src/main/kotlin/me/gabber235/typewriter/entries/action/PlayerCommandActionEntry.kt +++ b/adapters/BasicAdapter/src/main/kotlin/me/gabber235/typewriter/entries/action/PlayerCommandActionEntry.kt @@ -5,6 +5,7 @@ import lirand.api.extensions.server.commands.dispatchCommand import me.gabber235.typewriter.adapters.Colors import me.gabber235.typewriter.adapters.Entry 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.Criteria import me.gabber235.typewriter.entry.Modifier @@ -35,15 +36,19 @@ class PlayerCommandActionEntry( override val modifiers: List = emptyList(), override val triggers: List = emptyList(), @Placeholder - @Help("The command to run. (Use %player_name% for the player's name)") - // The command that the player will run. + @MultiLine + @Help("The command(s) to run.") + // Every line is a different command. Commands should not be prefixed with /. private val command: String = "", ) : ActionEntry { override fun execute(player: Player) { super.execute(player) // Run in main thread plugin.launch { - player.dispatchCommand(command.parsePlaceholders(player)) + val commands = command.parsePlaceholders(player).lines() + for (cmd in commands) { + player.dispatchCommand(cmd) + } } } } \ No newline at end of file diff --git a/adapters/BasicAdapter/src/main/kotlin/me/gabber235/typewriter/entries/action/RemoveItemActionEntry.kt b/adapters/BasicAdapter/src/main/kotlin/me/gabber235/typewriter/entries/action/RemoveItemActionEntry.kt index 7feef99c6d..545e5ac8e1 100644 --- a/adapters/BasicAdapter/src/main/kotlin/me/gabber235/typewriter/entries/action/RemoveItemActionEntry.kt +++ b/adapters/BasicAdapter/src/main/kotlin/me/gabber235/typewriter/entries/action/RemoveItemActionEntry.kt @@ -1,30 +1,33 @@ package me.gabber235.typewriter.entries.action -import lirand.api.extensions.inventory.meta +import com.google.gson.JsonObject +import lirand.api.extensions.other.set import me.gabber235.typewriter.adapters.Colors import me.gabber235.typewriter.adapters.Entry -import me.gabber235.typewriter.adapters.modifiers.Colored import me.gabber235.typewriter.adapters.modifiers.Help -import me.gabber235.typewriter.adapters.modifiers.Placeholder -import me.gabber235.typewriter.entry.Criteria -import me.gabber235.typewriter.entry.Modifier +import me.gabber235.typewriter.entry.* import me.gabber235.typewriter.entry.entries.ActionEntry import me.gabber235.typewriter.utils.Icons -import me.gabber235.typewriter.utils.asMini +import me.gabber235.typewriter.utils.Item +import me.gabber235.typewriter.utils.optional import org.bukkit.Material import org.bukkit.entity.Player -import org.bukkit.inventory.ItemStack import java.util.* @Entry("remove_item", "Remove an item from the players inventory", Colors.RED, Icons.WAND_SPARKLES) /** * The `Remove Item Action` is an action that removes an item from the player's inventory. * This action provides you with the ability to remove items from the player's inventory in response to specific events. + * + * This action will try to remove "as much as possible" but does not verify if the player has enough items in their inventory. + * If you want to guarantee that the player has enough items in their inventory, add an + * Inventory Item Count Fact to the criteria. + * * * ## How could this be used? * * This can be used when `giving` an NPC an item, and you want to remove the item from the player's inventory. - * Or when you want to remove an item from the player's inventory when they complete a quest or achievement. + * Or when you want to remove a key from the player's inventory after they use it to unlock a door. */ class RemoveItemActionEntry( override val id: String = "", @@ -33,32 +36,33 @@ class RemoveItemActionEntry( override val modifiers: List, override val triggers: List = emptyList(), @Help("The item to remove.") - private val material: Material = Material.AIR, - @Help("The amount of items to remove.") - private val amount: Int = 1, - @Help("Does the player need to have the exact amount of items?") - private val exactAmount: Boolean = false, - @Placeholder - @Colored - @Help("The name of the item.") - // If the name is given, the item must have the same name to be removed. - private val itemName: Optional = Optional.empty(), + val item: Item = Item.Empty, ) : ActionEntry { override fun execute(player: Player) { super.execute(player) - val item = ItemStack(material, amount).meta { - itemName.ifPresent { - displayName(it.asMini()) - } - } - if (exactAmount) { - if (player.inventory.containsAtLeast(item, amount)) { - player.inventory.removeItemAnySlot(item) - } - } else { - player.inventory.removeItemAnySlot(item) - } + player.inventory.removeItemAnySlot(item.build(player)) } } +@EntryMigration(RemoveItemActionEntry::class, "0.4.0") +@NeedsMigrationIfContainsAny(["material", "amount", "itemName"]) +fun migrate040RemoveItemAction(json: JsonObject, context: EntryMigratorContext): JsonObject { + val data = JsonObject() + data.copyAllBut(json, "material", "amount", "itemName") + + val material = json.getAndParse("material", context.gson).optional + val amount = json.getAndParse("amount", context.gson).optional + val displayName = json.getAndParse>("itemName", context.gson).optional + + val item = Item( + material = material, + amount = amount, + name = displayName, + ) + data["item"] = context.gson.toJsonTree(item) + + return data +} + + diff --git a/adapters/BasicAdapter/src/main/kotlin/me/gabber235/typewriter/entries/action/StopSoundActionEntry.kt b/adapters/BasicAdapter/src/main/kotlin/me/gabber235/typewriter/entries/action/StopSoundActionEntry.kt index 7ee43f4978..4556d4e9db 100644 --- a/adapters/BasicAdapter/src/main/kotlin/me/gabber235/typewriter/entries/action/StopSoundActionEntry.kt +++ b/adapters/BasicAdapter/src/main/kotlin/me/gabber235/typewriter/entries/action/StopSoundActionEntry.kt @@ -3,11 +3,12 @@ package me.gabber235.typewriter.entries.action import me.gabber235.typewriter.adapters.Colors import me.gabber235.typewriter.adapters.Entry import me.gabber235.typewriter.adapters.modifiers.Help -import me.gabber235.typewriter.adapters.modifiers.Sound import me.gabber235.typewriter.entry.Criteria import me.gabber235.typewriter.entry.Modifier import me.gabber235.typewriter.entry.entries.ActionEntry import me.gabber235.typewriter.utils.Icons +import me.gabber235.typewriter.utils.SoundId +import net.kyori.adventure.sound.SoundStop import org.bukkit.entity.Player import java.util.* @@ -26,15 +27,18 @@ class StopSoundActionEntry( override val criteria: List = emptyList(), override val modifiers: List = emptyList(), override val triggers: List = emptyList(), - @Sound @Help("The sound to stop.") - val sound: Optional = Optional.empty(), + // The sound to stop. If this field is left blank, all sounds will be stopped. + val sound: Optional = Optional.empty(), ) : ActionEntry { override fun execute(player: Player) { super.execute(player) if (sound.isPresent) { - player.stopSound(sound.get()) + val sound = sound.get() + val soundStop = sound.namespacedKey?.let { SoundStop.named(it) } ?: return + + player.stopSound(soundStop) } else { player.stopAllSounds() } diff --git a/adapters/BasicAdapter/src/main/kotlin/me/gabber235/typewriter/entries/cinematic/BlindingCinematicEntry.kt b/adapters/BasicAdapter/src/main/kotlin/me/gabber235/typewriter/entries/cinematic/BlindingCinematicEntry.kt index f8a95c7bec..4de0b6b166 100644 --- a/adapters/BasicAdapter/src/main/kotlin/me/gabber235/typewriter/entries/cinematic/BlindingCinematicEntry.kt +++ b/adapters/BasicAdapter/src/main/kotlin/me/gabber235/typewriter/entries/cinematic/BlindingCinematicEntry.kt @@ -18,7 +18,6 @@ import me.gabber235.typewriter.utils.GenericPlayerStateProvider.LOCATION import org.bukkit.GameMode import org.bukkit.entity.Player import org.bukkit.potion.PotionEffect -import org.bukkit.potion.PotionEffect.INFINITE_DURATION import org.bukkit.potion.PotionEffectType.BLINDNESS @Entry("blinding_cinematic", "Blind the player so the screen looks black", Colors.CYAN, Icons.SOLID_EYE_SLASH) @@ -36,7 +35,7 @@ class BlindingCinematicEntry( @Segments(icon = Icons.SOLID_EYE_SLASH) val segments: List, ) : CinematicEntry { - override fun shouldSimulate(): Boolean = false + override fun createSimulated(player: Player): CinematicAction? = null override fun create(player: Player): CinematicAction { return BlindingCinematicAction( player, @@ -68,7 +67,7 @@ class BlindingCinematicAction( state = player.state(LOCATION, GAME_MODE, EffectStateProvider(BLINDNESS)) withContext(plugin.minecraftDispatcher) { - player.addPotionEffect(PotionEffect(BLINDNESS, INFINITE_DURATION, 0, false, false, false)) + player.addPotionEffect(PotionEffect(BLINDNESS, 10000000, 1, false, false, false)) if (segment.teleport) { /// Teleport the player high up to prevent them from seeing the world diff --git a/adapters/BasicAdapter/src/main/kotlin/me/gabber235/typewriter/entries/cinematic/CameraCinematicEntry.kt b/adapters/BasicAdapter/src/main/kotlin/me/gabber235/typewriter/entries/cinematic/CameraCinematicEntry.kt index 1a579ff42f..d46368896d 100644 --- a/adapters/BasicAdapter/src/main/kotlin/me/gabber235/typewriter/entries/cinematic/CameraCinematicEntry.kt +++ b/adapters/BasicAdapter/src/main/kotlin/me/gabber235/typewriter/entries/cinematic/CameraCinematicEntry.kt @@ -19,6 +19,7 @@ import me.gabber235.typewriter.plugin import me.gabber235.typewriter.utils.* import me.gabber235.typewriter.utils.GenericPlayerStateProvider.* import org.bukkit.Location +import org.bukkit.Particle import org.bukkit.entity.EntityType import org.bukkit.entity.Player import org.bukkit.potion.PotionEffect @@ -47,13 +48,20 @@ class CameraCinematicEntry( @InnerMin(Min(10)) val segments: List = emptyList(), ) : CinematicEntry { - override fun shouldSimulate(): Boolean = false override fun create(player: Player): CinematicAction { return CameraCinematicAction( player, this, ) } + + override fun createSimulated(player: Player): CinematicAction { + return SimulatedCameraCinematicAction( + player, + this, + ) + } + } data class CameraSegment( @@ -327,6 +335,25 @@ private class TeleportCameraSegmentAction( } } +class SimulatedCameraCinematicAction( + private val player: Player, + private val entry: CameraCinematicEntry, +) : CinematicAction { + + override suspend fun tick(frame: Int) { + super.tick(frame) + + val segment = (entry.segments activeSegmentAt frame) ?: return + val percentage = segment.percentageAt(frame) + val location = segment.path.interpolate(percentage) + + // Display a particle at the location + player.spawnParticle(Particle.SCRAPE, location, 1) + } + + override fun canFinish(frame: Int): Boolean = entry.segments canFinishAt frame +} + /** * Use catmull-rom interpolation to get a point between a list of points. */ diff --git a/adapters/BasicAdapter/src/main/kotlin/me/gabber235/typewriter/entries/cinematic/CinematicConsoleCommandEntry.kt b/adapters/BasicAdapter/src/main/kotlin/me/gabber235/typewriter/entries/cinematic/CinematicCommandEntry.kt similarity index 92% rename from adapters/BasicAdapter/src/main/kotlin/me/gabber235/typewriter/entries/cinematic/CinematicConsoleCommandEntry.kt rename to adapters/BasicAdapter/src/main/kotlin/me/gabber235/typewriter/entries/cinematic/CinematicCommandEntry.kt index bf46726096..27b3117348 100644 --- a/adapters/BasicAdapter/src/main/kotlin/me/gabber235/typewriter/entries/cinematic/CinematicConsoleCommandEntry.kt +++ b/adapters/BasicAdapter/src/main/kotlin/me/gabber235/typewriter/entries/cinematic/CinematicCommandEntry.kt @@ -32,7 +32,6 @@ class CinematicConsoleCommandEntry( override val name: String, override val criteria: List, @Segments(Colors.YELLOW, Icons.TERMINAL) - @Placeholder @InnerMax(Max(1)) // Run commands on different segments override val segments: List, @@ -60,7 +59,6 @@ class CinematicPlayerCommandEntry( override val name: String, override val criteria: List, @Segments(Colors.YELLOW, Icons.TERMINAL) - @Placeholder @InnerMax(Max(1)) // Run commands on different segments override val segments: List, @@ -78,7 +76,10 @@ class CinematicPlayerCommandEntry( data class CommandSegment( override val startFrame: Int, override val endFrame: Int, - @Help("The command to run") + @Help("The command(s) to run.") + @Placeholder + @MultiLine + // Every line is a different command. Commands should not be prefixed with /. val command: String, ) : Segment @@ -92,7 +93,8 @@ class CommandAction( override suspend fun startSegment(segment: CommandSegment) { super.startSegment(segment) withContext(plugin.minecraftDispatcher) { - run(segment.command.parsePlaceholders(player)) + val commands = segment.command.parsePlaceholders(player).lines() + commands.forEach(run) } } } \ No newline at end of file diff --git a/adapters/BasicAdapter/src/main/kotlin/me/gabber235/typewriter/entries/cinematic/DisplayDialogueCinematicAction.kt b/adapters/BasicAdapter/src/main/kotlin/me/gabber235/typewriter/entries/cinematic/DisplayDialogueCinematicAction.kt index 70a04bab59..f77dd397a0 100644 --- a/adapters/BasicAdapter/src/main/kotlin/me/gabber235/typewriter/entries/cinematic/DisplayDialogueCinematicAction.kt +++ b/adapters/BasicAdapter/src/main/kotlin/me/gabber235/typewriter/entries/cinematic/DisplayDialogueCinematicAction.kt @@ -96,7 +96,7 @@ class DisplayDialogueCinematicAction( val text = segment.text.parsePlaceholders(player) - display(player, speaker?.displayName ?: "", text, displayPercentage) + display(player, speaker?.displayName?.parsePlaceholders(player) ?: "", text, displayPercentage) } override suspend fun teardown() { diff --git a/adapters/BasicAdapter/src/main/kotlin/me/gabber235/typewriter/entries/cinematic/SoundCinematicEntry.kt b/adapters/BasicAdapter/src/main/kotlin/me/gabber235/typewriter/entries/cinematic/SoundCinematicEntry.kt index b10c100b61..7c22f5bca8 100644 --- a/adapters/BasicAdapter/src/main/kotlin/me/gabber235/typewriter/entries/cinematic/SoundCinematicEntry.kt +++ b/adapters/BasicAdapter/src/main/kotlin/me/gabber235/typewriter/entries/cinematic/SoundCinematicEntry.kt @@ -1,19 +1,22 @@ package me.gabber235.typewriter.entries.cinematic +import com.google.gson.JsonArray +import com.google.gson.JsonObject +import lirand.api.extensions.other.set import me.gabber235.typewriter.adapters.Colors import me.gabber235.typewriter.adapters.Entry import me.gabber235.typewriter.adapters.modifiers.Help import me.gabber235.typewriter.adapters.modifiers.Segments -import me.gabber235.typewriter.adapters.modifiers.Sound -import me.gabber235.typewriter.entry.Criteria +import me.gabber235.typewriter.entry.* import me.gabber235.typewriter.entry.cinematic.SimpleCinematicAction import me.gabber235.typewriter.entry.entries.CinematicAction import me.gabber235.typewriter.entry.entries.CinematicEntry import me.gabber235.typewriter.entry.entries.Segment -import me.gabber235.typewriter.utils.Icons -import net.kyori.adventure.key.Key -import net.kyori.adventure.sound.SoundStop +import me.gabber235.typewriter.logger +import me.gabber235.typewriter.utils.* import org.bukkit.entity.Player +import kotlin.jvm.optionals.getOrDefault +import net.kyori.adventure.sound.Sound as AdventureSound @Entry("sound_cinematic", "Play a sound during a cinematic", Colors.YELLOW, Icons.MUSIC) /** @@ -29,8 +32,6 @@ class SoundCinematicEntry( override val criteria: List, @Segments(icon = Icons.MUSIC) val segments: List, - @Help("The channel to play the sound in") - val channel: net.kyori.adventure.sound.Sound.Source = net.kyori.adventure.sound.Sound.Source.MASTER, ) : CinematicEntry { override fun create(player: Player): CinematicAction { return SoundCinematicAction( @@ -44,12 +45,7 @@ data class SoundSegment( override val startFrame: Int, override val endFrame: Int, @Help("The sound to play") - @Sound - val sound: String = "", - @Help("The volume of the sound") - val volume: Float = 1f, - @Help("The pitch of the sound") - val pitch: Float = 1f, + val sound: Sound, ) : Segment class SoundCinematicAction( @@ -61,20 +57,60 @@ class SoundCinematicAction( override suspend fun startSegment(segment: SoundSegment) { super.startSegment(segment) - player.playSound( - net.kyori.adventure.sound.Sound.sound( - Key.key(segment.sound), - entry.channel, - segment.volume, - segment.pitch, - ), - net.kyori.adventure.sound.Sound.Emitter.self(), - ) + player.playSound(segment.sound) } override suspend fun stopSegment(segment: SoundSegment) { super.stopSegment(segment) - player.stopSound(SoundStop.named(Key.key(segment.sound))) + player.stopSound(segment.sound) + } + +} + +@EntryMigration(SoundCinematicEntry::class, "0.4.0") +@NeedsMigrationIfNotParsable +fun migrate040SoundCinematic(json: JsonObject, context: EntryMigratorContext): JsonObject { + val data = JsonObject() + data.copyAllBut(json, "segments", "channel") + + val channel = json.getAndParse("channel", context.gson).optional + + val segmentsJson = json["segments"] + if (segmentsJson?.isJsonArray == true) { + logger.severe("Tried migrating sound cinematic entry ${json["name"]}, but segments were not an array.") + return data } + val segmentsArray = segmentsJson?.asJsonArray ?: JsonArray() + + val segments = segmentsArray.mapNotNull { + if (!it.isJsonObject) { + logger.severe("Tried migrating sound cinematic entry ${json["name"]}, but segment was not an object.") + return@mapNotNull null + } + + val segmentJson = it.asJsonObject + val startFrame = segmentJson.getAndParse("startFrame", context.gson).optional + val endFrame = segmentJson.getAndParse("endFrame", context.gson).optional + val sound = segmentJson.getAndParse("sound", context.gson).optional + val volume = segmentJson.getAndParse("volume", context.gson).optional + val pitch = segmentJson.getAndParse("pitch", context.gson).optional + + SoundSegment( + startFrame = startFrame.getOrDefault(0), + endFrame = endFrame.getOrDefault(0), + sound = Sound( + soundId = sound.map(::DefaultSoundId).getOrDefault(SoundId.EMPTY), + soundSource = SelfSoundSource, + track = channel.getOrDefault(AdventureSound.Source.MASTER), + volume = volume.getOrDefault(1.0f), + pitch = pitch.getOrDefault(1.0f), + ) + ) + } + + data["segments"] = context.gson.toJsonTree(segments) + + + return data } \ No newline at end of file diff --git a/adapters/BasicAdapter/src/main/kotlin/me/gabber235/typewriter/entries/cinematic/SpokenDialogueCinematicEntry.kt b/adapters/BasicAdapter/src/main/kotlin/me/gabber235/typewriter/entries/cinematic/SpokenDialogueCinematicEntry.kt index 7c7f44089e..90556504e4 100644 --- a/adapters/BasicAdapter/src/main/kotlin/me/gabber235/typewriter/entries/cinematic/SpokenDialogueCinematicEntry.kt +++ b/adapters/BasicAdapter/src/main/kotlin/me/gabber235/typewriter/entries/cinematic/SpokenDialogueCinematicEntry.kt @@ -12,7 +12,6 @@ import me.gabber235.typewriter.entry.entries.CinematicEntry import me.gabber235.typewriter.entry.entries.SpeakerEntry import me.gabber235.typewriter.entry.entries.SystemTrigger.DIALOGUE_END import me.gabber235.typewriter.entry.triggerFor -import me.gabber235.typewriter.extensions.placeholderapi.parsePlaceholders import me.gabber235.typewriter.interaction.acceptActionBarMessage import me.gabber235.typewriter.interaction.chatHistory import me.gabber235.typewriter.snippets.snippet @@ -114,7 +113,7 @@ val spokenPercentage: Double by snippet("cinematic.dialogue.spoken.percentage", private fun displaySpokenDialogue(player: Player, speakerName: String, text: String, displayPercentage: Double) { - val message = text.parsePlaceholders(player).asPartialFormattedMini( + val message = text.asPartialFormattedMini( displayPercentage, padding = spokenPadding, minLines = spokenMinLines, diff --git a/adapters/BasicAdapter/src/main/kotlin/me/gabber235/typewriter/entries/dialogue/messengers/message/UniversalMessageDialogueDialogueMessenger.kt b/adapters/BasicAdapter/src/main/kotlin/me/gabber235/typewriter/entries/dialogue/messengers/message/UniversalMessageDialogueDialogueMessenger.kt index b771a5f39a..e15cfc0aa1 100644 --- a/adapters/BasicAdapter/src/main/kotlin/me/gabber235/typewriter/entries/dialogue/messengers/message/UniversalMessageDialogueDialogueMessenger.kt +++ b/adapters/BasicAdapter/src/main/kotlin/me/gabber235/typewriter/entries/dialogue/messengers/message/UniversalMessageDialogueDialogueMessenger.kt @@ -3,40 +3,41 @@ package me.gabber235.typewriter.entries.dialogue.messengers.message import me.gabber235.typewriter.adapters.Messenger import me.gabber235.typewriter.adapters.MessengerFilter import me.gabber235.typewriter.entries.dialogue.MessageDialogueEntry -import me.gabber235.typewriter.entry.dialogue.* +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.extensions.placeholderapi.parsePlaceholders import me.gabber235.typewriter.snippets.snippet -import me.gabber235.typewriter.utils.* +import me.gabber235.typewriter.utils.sendMiniWithResolvers import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder import org.bukkit.entity.Player val messageFormat: String by snippet( - "dialogue.message.format", - "\n [ ]\n \n" + "dialogue.message.format", + "\n [ ]\n \n" ) @Messenger(MessageDialogueEntry::class) class UniversalMessageDialogueDialogueMessenger(player: Player, entry: MessageDialogueEntry) : - DialogueMessenger(player, entry) { + DialogueMessenger(player, entry) { - companion object : MessengerFilter { - override fun filter(player: Player, entry: DialogueEntry): Boolean = true - } + companion object : MessengerFilter { + override fun filter(player: Player, entry: DialogueEntry): Boolean = true + } - override fun tick(cycle: Int) { - super.tick(cycle) - if (cycle == 0) { - player.sendMessageDialogue(entry.text, entry.speakerDisplayName) - state = MessengerState.FINISHED - } - } + override fun tick(cycle: Int) { + super.tick(cycle) + if (cycle == 0) { + player.sendMessageDialogue(entry.text, entry.speakerDisplayName) + state = MessengerState.FINISHED + } + } } fun Player.sendMessageDialogue(text: String, speakerDisplayName: String) { - sendMiniWithResolvers( - messageFormat, - Placeholder.parsed("speaker", speakerDisplayName), - Placeholder.parsed("message", text.parsePlaceholders(player).replace("\n", "\n ")) - ) + sendMiniWithResolvers( + messageFormat, + Placeholder.parsed("speaker", speakerDisplayName.parsePlaceholders(this)), + Placeholder.parsed("message", text.parsePlaceholders(this).replace("\n", "\n ")) + ) } \ No newline at end of file diff --git a/adapters/BasicAdapter/src/main/kotlin/me/gabber235/typewriter/entries/dialogue/messengers/option/BedrockOptionDialogueDialogueMessenger.kt b/adapters/BasicAdapter/src/main/kotlin/me/gabber235/typewriter/entries/dialogue/messengers/option/BedrockOptionDialogueDialogueMessenger.kt index dc1be041fd..ca128e0a0d 100644 --- a/adapters/BasicAdapter/src/main/kotlin/me/gabber235/typewriter/entries/dialogue/messengers/option/BedrockOptionDialogueDialogueMessenger.kt +++ b/adapters/BasicAdapter/src/main/kotlin/me/gabber235/typewriter/entries/dialogue/messengers/option/BedrockOptionDialogueDialogueMessenger.kt @@ -13,53 +13,51 @@ import me.gabber235.typewriter.extensions.placeholderapi.parsePlaceholders import me.gabber235.typewriter.utils.isFloodgate import me.gabber235.typewriter.utils.legacy import org.bukkit.entity.Player -import org.geysermc.cumulus.form.CustomForm -import org.geysermc.floodgate.api.FloodgateApi @Messenger(OptionDialogueEntry::class, priority = 5) class BedrockOptionDialogueDialogueMessenger(player: Player, entry: OptionDialogueEntry) : - DialogueMessenger(player, entry) { - - companion object : MessengerFilter { - override fun filter(player: Player, entry: DialogueEntry): Boolean = player.isFloodgate - } - - private var selectedIndex = 0 - private val selected get() = usableOptions[selectedIndex] - - private var usableOptions: List