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 3fbb6573fa..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,17 +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.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.SoundId -import net.kyori.adventure.sound.Sound +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) /** @@ -28,26 +28,34 @@ class PlaySoundActionEntry( override val modifiers: List = emptyList(), override val triggers: List = emptyList(), @Help("The sound to play.") - val sound: SoundId = SoundId.EMPTY, - @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) - val key = this.sound.namespacedKey ?: return - val sound = Sound.sound(key, Sound.Source.MASTER, volume, pitch) - - if (location.isPresent) { - val location = location.get() - player.playSound(sound, location.x, location.y, location.z) - } else { - player.playSound(sound) - } + 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/cinematic/SoundCinematicEntry.kt b/adapters/BasicAdapter/src/main/kotlin/me/gabber235/typewriter/entries/cinematic/SoundCinematicEntry.kt index 1a20023a7e..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,18 +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.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 me.gabber235.typewriter.utils.SoundId -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) /** @@ -28,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( @@ -43,11 +45,7 @@ data class SoundSegment( override val startFrame: Int, override val endFrame: Int, @Help("The sound to play") - val sound: SoundId = SoundId.EMPTY, - @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( @@ -59,19 +57,60 @@ class SoundCinematicAction( override suspend fun startSegment(segment: SoundSegment) { super.startSegment(segment) - player.playSound( - net.kyori.adventure.sound.Sound.sound( - segment.sound.namespacedKey ?: return, - entry.channel, - segment.volume, - segment.pitch, - ), - ) + player.playSound(segment.sound) } override suspend fun stopSegment(segment: SoundSegment) { super.stopSegment(segment) - player.stopSound(SoundStop.named(segment.sound.namespacedKey ?: return)) + 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/entity/SimpleSpeakerEntry.kt b/adapters/BasicAdapter/src/main/kotlin/me/gabber235/typewriter/entries/entity/SimpleSpeakerEntry.kt index 71626bbf63..784af69040 100644 --- a/adapters/BasicAdapter/src/main/kotlin/me/gabber235/typewriter/entries/entity/SimpleSpeakerEntry.kt +++ b/adapters/BasicAdapter/src/main/kotlin/me/gabber235/typewriter/entries/entity/SimpleSpeakerEntry.kt @@ -1,9 +1,13 @@ package me.gabber235.typewriter.entries.entity +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.entry.* import me.gabber235.typewriter.entry.entries.SpeakerEntry -import me.gabber235.typewriter.utils.Icons +import me.gabber235.typewriter.utils.* +import kotlin.jvm.optionals.getOrDefault @Entry("simple_speaker", "The most basic speaker", Colors.ORANGE, Icons.PERSON) /** @@ -17,5 +21,25 @@ class SimpleSpeakerEntry( override val id: String = "", override val name: String = "", override val displayName: String = "", - override val sound: String = "", -) : SpeakerEntry \ No newline at end of file + override val sound: Sound = Sound.EMPTY, +) : SpeakerEntry + +@EntryMigration(SimpleSpeakerEntry::class, "0.4.0") +@NeedsMigrationIfNotParsable +fun migrate040SimpleSpeaker(json: JsonObject, context: EntryMigratorContext): JsonObject { + val data = JsonObject() + data.copyAllBut(json, "sound") + + val soundId = json.getAndParse("sound", context.gson).optional + + val sound = Sound( + soundId = soundId.map(::DefaultSoundId).getOrDefault(SoundId.EMPTY), + soundSource = SelfSoundSource, + volume = 1.0f, + pitch = 1.0f, + ) + + data["sound"] = context.gson.toJsonTree(sound) + + return data +} diff --git a/adapters/CitizensAdapter/src/main/kotlin/me/gabber235/typewriter/citizens/entries/entity/Npc.kt b/adapters/CitizensAdapter/src/main/kotlin/me/gabber235/typewriter/citizens/entries/entity/Npc.kt index 0dc843b53e..3cc0eeb5c0 100644 --- a/adapters/CitizensAdapter/src/main/kotlin/me/gabber235/typewriter/citizens/entries/entity/Npc.kt +++ b/adapters/CitizensAdapter/src/main/kotlin/me/gabber235/typewriter/citizens/entries/entity/Npc.kt @@ -1,8 +1,9 @@ package me.gabber235.typewriter.citizens.entries.entity import me.gabber235.typewriter.adapters.Tags +import me.gabber235.typewriter.entry.entries.SoundSourceEntry import me.gabber235.typewriter.entry.entries.SpeakerEntry @Tags("npc") -interface Npc : SpeakerEntry +interface Npc : SpeakerEntry, SoundSourceEntry diff --git a/adapters/CitizensAdapter/src/main/kotlin/me/gabber235/typewriter/citizens/entries/entity/ReferenceNpcEntry.kt b/adapters/CitizensAdapter/src/main/kotlin/me/gabber235/typewriter/citizens/entries/entity/ReferenceNpcEntry.kt index edbf1af9bb..7a2b99e182 100644 --- a/adapters/CitizensAdapter/src/main/kotlin/me/gabber235/typewriter/citizens/entries/entity/ReferenceNpcEntry.kt +++ b/adapters/CitizensAdapter/src/main/kotlin/me/gabber235/typewriter/citizens/entries/entity/ReferenceNpcEntry.kt @@ -1,10 +1,16 @@ package me.gabber235.typewriter.citizens.entries.entity +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.Tags import me.gabber235.typewriter.adapters.modifiers.Help -import me.gabber235.typewriter.utils.Icons +import me.gabber235.typewriter.entry.* +import me.gabber235.typewriter.utils.* +import net.citizensnpcs.api.CitizensAPI +import kotlin.jvm.optionals.getOrDefault +import net.kyori.adventure.sound.Sound as AdventureSound @Tags("reference_npc") @Entry("reference_npc", "When the npc is not managed by TypeWriter", Colors.ORANGE, Icons.PERSON) @@ -19,7 +25,32 @@ class ReferenceNpcEntry( override val id: String = "", override val name: String = "", override val displayName: String = "", - override val sound: String = "", + override val sound: Sound, @Help("The id of the NPC in the Citizens plugin.") val npcId: Int = 0, -) : Npc \ No newline at end of file +) : Npc { + override fun getEmitter(): AdventureSound.Emitter { + val npc = CitizensAPI.getNPCRegistry().getById(npcId) ?: return AdventureSound.Emitter.self() + return npc.entity + } +} + +@EntryMigration(ReferenceNpcEntry::class, "0.4.0") +@NeedsMigrationIfNotParsable +fun migrate040ReferenceNpc(json: JsonObject, context: EntryMigratorContext): JsonObject { + val data = JsonObject() + data.copyAllBut(json, "sound") + + val soundId = json.getAndParse("sound", context.gson).optional + + val sound = Sound( + soundId = soundId.map(::DefaultSoundId).getOrDefault(SoundId.EMPTY), + soundSource = SelfSoundSource, + volume = 1.0f, + pitch = 1.0f, + ) + + data["sound"] = context.gson.toJsonTree(sound) + + return data +} \ No newline at end of file diff --git a/app/lib/models/sound_id.dart b/app/lib/models/sound.dart similarity index 88% rename from app/lib/models/sound_id.dart rename to app/lib/models/sound.dart index 59696b32c1..dbe4221da2 100644 --- a/app/lib/models/sound_id.dart +++ b/app/lib/models/sound.dart @@ -1,7 +1,7 @@ import "package:freezed_annotation/freezed_annotation.dart"; -part "sound_id.freezed.dart"; -part "sound_id.g.dart"; +part "sound.freezed.dart"; +part "sound.g.dart"; @Freezed(unionKey: "type") class SoundId with _$SoundId { diff --git a/app/lib/models/sound_id.freezed.dart b/app/lib/models/sound.freezed.dart similarity index 99% rename from app/lib/models/sound_id.freezed.dart rename to app/lib/models/sound.freezed.dart index 4a1fb1815c..f96e443e39 100644 --- a/app/lib/models/sound_id.freezed.dart +++ b/app/lib/models/sound.freezed.dart @@ -3,7 +3,7 @@ // ignore_for_file: type=lint // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark -part of 'sound_id.dart'; +part of 'sound.dart'; // ************************************************************************** // FreezedGenerator diff --git a/app/lib/models/sound_id.g.dart b/app/lib/models/sound.g.dart similarity index 97% rename from app/lib/models/sound_id.g.dart rename to app/lib/models/sound.g.dart index 94f4863b40..b9d913a052 100644 --- a/app/lib/models/sound_id.g.dart +++ b/app/lib/models/sound.g.dart @@ -1,6 +1,6 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -part of 'sound_id.dart'; +part of 'sound.dart'; // ************************************************************************** // JsonSerializableGenerator diff --git a/app/lib/models/sounds.dart b/app/lib/models/sounds.dart index 28eaae16be..e93bdb6938 100644 --- a/app/lib/models/sounds.dart +++ b/app/lib/models/sounds.dart @@ -5,6 +5,7 @@ import "package:freezed_annotation/freezed_annotation.dart"; import "package:http/http.dart" as http; import "package:riverpod_annotation/riverpod_annotation.dart"; import "package:typewriter/main.dart"; +import "package:typewriter/utils/extensions.dart"; part "sounds.freezed.dart"; part "sounds.g.dart"; @@ -61,11 +62,12 @@ Future>> minecraftSounds( @riverpod Future minecraftSound(MinecraftSoundRef ref, String id) async { + final strippedId = id.replacePrefix("minecraft:", ""); final sounds = await ref.watch(minecraftSoundsProvider.future); - if (!sounds.containsKey(id)) return null; - final sound = sounds[id]; + if (!sounds.containsKey(strippedId)) return null; + final sound = sounds[strippedId]; if (sound == null) return null; - return MapEntry(id, sound); + return MapEntry(strippedId, sound); } extension SoundDataX on SoundData { diff --git a/app/lib/widgets/inspector/editors.dart b/app/lib/widgets/inspector/editors.dart index 5f2dcdf2b6..88551c7dff 100644 --- a/app/lib/widgets/inspector/editors.dart +++ b/app/lib/widgets/inspector/editors.dart @@ -18,6 +18,7 @@ import "package:typewriter/widgets/inspector/editors/optional.dart"; import "package:typewriter/widgets/inspector/editors/page_selector.dart"; import "package:typewriter/widgets/inspector/editors/potion_effect.dart"; import "package:typewriter/widgets/inspector/editors/sound_id.dart"; +import "package:typewriter/widgets/inspector/editors/sound_source.dart"; import "package:typewriter/widgets/inspector/editors/string.dart"; import "package:typewriter/widgets/inspector/inspector.dart"; @@ -44,6 +45,7 @@ List editorFilters(EditorFiltersRef ref) => [ PotionEffectEditorFilter(), ItemEditorFilter(), SoundIdEditorFilter(), + SoundSourceEditorFilter(), // Default filters StringEditorFilter(), diff --git a/app/lib/widgets/inspector/editors.g.dart b/app/lib/widgets/inspector/editors.g.dart index 0fd21e91ab..64a715f2e0 100644 --- a/app/lib/widgets/inspector/editors.g.dart +++ b/app/lib/widgets/inspector/editors.g.dart @@ -172,7 +172,7 @@ class _FieldValueProviderElement extends AutoDisposeProviderElement dynamic get defaultValue => (origin as FieldValueProvider).defaultValue; } -String _$editorFiltersHash() => r'82881dad4fc36c0d7950c7bee132cd91a6e0314a'; +String _$editorFiltersHash() => r'168b4998335c3de63d58cbd8c0bba07b574b53ec'; /// See also [editorFilters]. @ProviderFor(editorFilters) diff --git a/app/lib/widgets/inspector/editors/sound_id.dart b/app/lib/widgets/inspector/editors/sound_id.dart index ccc1acfbb2..0043c0bdb5 100644 --- a/app/lib/widgets/inspector/editors/sound_id.dart +++ b/app/lib/widgets/inspector/editors/sound_id.dart @@ -9,7 +9,7 @@ import "package:fuzzy/fuzzy.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:riverpod_annotation/riverpod_annotation.dart"; import "package:typewriter/models/adapter.dart"; -import "package:typewriter/models/sound_id.dart"; +import "package:typewriter/models/sound.dart"; import "package:typewriter/models/sounds.dart"; import "package:typewriter/utils/audio_player.dart"; import "package:typewriter/utils/extensions.dart"; diff --git a/app/lib/widgets/inspector/editors/sound_source.dart b/app/lib/widgets/inspector/editors/sound_source.dart new file mode 100644 index 0000000000..0d93c6025e --- /dev/null +++ b/app/lib/widgets/inspector/editors/sound_source.dart @@ -0,0 +1,101 @@ +import "package:flutter/cupertino.dart"; +import "package:flutter/material.dart"; +import "package:hooks_riverpod/hooks_riverpod.dart"; +import "package:typewriter/models/adapter.dart"; +import "package:typewriter/utils/passing_reference.dart"; +import "package:typewriter/widgets/inspector/editors.dart"; +import "package:typewriter/widgets/inspector/editors/entry_selector.dart"; +import "package:typewriter/widgets/inspector/editors/location.dart"; +import "package:typewriter/widgets/inspector/inspector.dart"; + +class SoundSourceEditorFilter extends EditorFilter { + @override + bool canEdit(FieldInfo info) => + info is CustomField && info.editor == "soundSource"; + @override + Widget build(String path, FieldInfo info) => + SoundSourceEditor(path, info as CustomField); +} + +class SoundSourceEditor extends HookConsumerWidget { + const SoundSourceEditor(this.path, this.info, {super.key}); + final String path; + final CustomField info; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final type = ref.watch(fieldValueProvider("$path.type", "self")); + + final sourceType = SoundSourceType.fromString(type); + + return SizedBox( + width: double.infinity, + child: Column( + children: [ + CupertinoSlidingSegmentedControl( + children: { + for (final type in SoundSourceType.values) + type: _SegmentSelector(text: type.name), + }, + groupValue: sourceType, + backgroundColor: Theme.of(context).inputDecorationTheme.fillColor ?? + Theme.of(context).canvasColor, + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + onValueChanged: (value) => ref + .read(inspectingEntryDefinitionProvider) + ?.updateField(ref.passing, "$path.type", value?.name ?? "self"), + ), + const SizedBox(height: 8), + if (sourceType == SoundSourceType.emitter) + EntrySelectorEditor( + path: "$path.entryId", + field: const PrimitiveField( + type: PrimitiveFieldType.string, + modifiers: [Modifier(name: "entry", data: "sound_source")], + ), + ), + if (sourceType == SoundSourceType.location) + LocationEditor( + path: "$path.location", + field: CustomField( + editor: "location", + defaultValue: info.defaultValue["location"] ?? {}, + ), + ), + ], + ), + ); + } +} + +class _SegmentSelector extends StatelessWidget { + const _SegmentSelector({ + required this.text, + }); + + final String text; + + @override + Widget build(BuildContext context) { + return SizedBox( + width: double.maxFinite, + child: Center(child: Text(text)), + ); + } +} + +enum SoundSourceType { + self, + emitter, + location, + ; + + String get name => toString().split(".").last; + + static SoundSourceType fromString(String? value) { + return SoundSourceType.values.firstWhere( + (type) => type.name == value, + orElse: () => SoundSourceType.self, + ); + } +} diff --git a/plugin/src/main/kotlin/me/gabber235/typewriter/adapters/CustomEditors.kt b/plugin/src/main/kotlin/me/gabber235/typewriter/adapters/CustomEditors.kt index f2be51685a..20f5cb2ce5 100644 --- a/plugin/src/main/kotlin/me/gabber235/typewriter/adapters/CustomEditors.kt +++ b/plugin/src/main/kotlin/me/gabber235/typewriter/adapters/CustomEditors.kt @@ -7,6 +7,7 @@ import me.gabber235.typewriter.adapters.modifiers.StaticModifierComputer import me.gabber235.typewriter.utils.CronExpression import me.gabber235.typewriter.utils.Item import me.gabber235.typewriter.utils.SoundId +import me.gabber235.typewriter.utils.SoundSource import org.bukkit.Location import org.bukkit.Material import org.bukkit.potion.PotionEffectType @@ -208,6 +209,7 @@ internal val customEditors by lazy { ObjectEditor::potionEffectType, ObjectEditor::item, ObjectEditor::soundId, + ObjectEditor::soundSource, ) .mapNotNull(::objectEditorFromFunction) .associateBy { it.klass } diff --git a/plugin/src/main/kotlin/me/gabber235/typewriter/adapters/FieldModifiers.kt b/plugin/src/main/kotlin/me/gabber235/typewriter/adapters/FieldModifiers.kt index fdb2e7b7b1..9da3990144 100644 --- a/plugin/src/main/kotlin/me/gabber235/typewriter/adapters/FieldModifiers.kt +++ b/plugin/src/main/kotlin/me/gabber235/typewriter/adapters/FieldModifiers.kt @@ -136,7 +136,6 @@ private val computers: List by lazy { MultiLineModifierComputer, MaterialPropertiesModifierComputer, WithRotationModifierComputer, - SoundModifierComputer, SegmentModifierComputer, MinModifierComputer, MaxModifierComputer, diff --git a/plugin/src/main/kotlin/me/gabber235/typewriter/adapters/editors/SoundSourceEditor.kt b/plugin/src/main/kotlin/me/gabber235/typewriter/adapters/editors/SoundSourceEditor.kt new file mode 100644 index 0000000000..1a8f9a16c8 --- /dev/null +++ b/plugin/src/main/kotlin/me/gabber235/typewriter/adapters/editors/SoundSourceEditor.kt @@ -0,0 +1,70 @@ +package me.gabber235.typewriter.adapters.editors + +import com.google.gson.JsonObject +import me.gabber235.typewriter.adapters.CustomEditor +import me.gabber235.typewriter.adapters.ObjectEditor +import me.gabber235.typewriter.utils.EmitterSoundSource +import me.gabber235.typewriter.utils.LocationSoundSource +import me.gabber235.typewriter.utils.SelfSoundSource +import me.gabber235.typewriter.utils.SoundSource +import org.bukkit.Location + +@CustomEditor(SoundSource::class) +fun ObjectEditor.soundSource() = reference { + default { + JsonObject().apply { + addProperty("type", "self") + addProperty("entryId", "") + add("location", JsonObject().apply { + addProperty("world", "") + addProperty("x", 0.0) + addProperty("y", 0.0) + addProperty("z", 0.0) + addProperty("yaw", 0.0) + addProperty("pitch", 0.0) + }) + } + } + + jsonDeserialize { jsonElement, _, context -> + val obj = jsonElement.asJsonObject + val type = obj.get("type").asString + + when (type) { + "self" -> SelfSoundSource + "emitter" -> { + val value = obj.get("entryId").asString + EmitterSoundSource(value) + } + + "location" -> { + val location: Location = context.deserialize(obj.get("location"), Location::class.java) + LocationSoundSource(location) + } + + else -> throw IllegalArgumentException("Invalid sound source type: $type") + } + } + + jsonSerialize { soundSource, _, context -> + val obj = JsonObject() + + when (soundSource) { + is SelfSoundSource -> { + obj.addProperty("type", "self") + } + + is EmitterSoundSource -> { + obj.addProperty("type", "emitter") + obj.addProperty("entryId", soundSource.entryId) + } + + is LocationSoundSource -> { + obj.addProperty("type", "location") + obj.add("location", context.serialize(soundSource.location)) + } + } + + obj + } +} \ No newline at end of file diff --git a/plugin/src/main/kotlin/me/gabber235/typewriter/adapters/modifiers/SoundModifierComputer.kt b/plugin/src/main/kotlin/me/gabber235/typewriter/adapters/modifiers/SoundModifierComputer.kt deleted file mode 100644 index 50c50fc938..0000000000 --- a/plugin/src/main/kotlin/me/gabber235/typewriter/adapters/modifiers/SoundModifierComputer.kt +++ /dev/null @@ -1,30 +0,0 @@ -package me.gabber235.typewriter.adapters.modifiers - -import me.gabber235.typewriter.adapters.FieldInfo -import me.gabber235.typewriter.adapters.FieldModifier -import me.gabber235.typewriter.adapters.PrimitiveField -import me.gabber235.typewriter.adapters.PrimitiveFieldType -import me.gabber235.typewriter.logger - -@Target(AnnotationTarget.PROPERTY, AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER) -annotation class Sound - -object SoundModifierComputer : StaticModifierComputer { - override val annotationClass: Class = Sound::class.java - - override fun computeModifier(annotation: Sound, info: FieldInfo): FieldModifier? { - // If the field is wrapped in a list or other container we try if the inner type can be modified - innerCompute(annotation, info)?.let { return it } - - if (info !is PrimitiveField) { - logger.warning("Sound annotation can only be used on strings") - return null - } - if (info.type != PrimitiveFieldType.STRING) { - logger.warning("Sound annotation can only be used on strings") - return null - } - - return FieldModifier.StaticModifier("sound") - } -} \ No newline at end of file diff --git a/plugin/src/main/kotlin/me/gabber235/typewriter/entry/dialogue/DialogueSequence.kt b/plugin/src/main/kotlin/me/gabber235/typewriter/entry/dialogue/DialogueSequence.kt index f55bbff4c5..c955e9a4be 100644 --- a/plugin/src/main/kotlin/me/gabber235/typewriter/entry/dialogue/DialogueSequence.kt +++ b/plugin/src/main/kotlin/me/gabber235/typewriter/entry/dialogue/DialogueSequence.kt @@ -10,9 +10,7 @@ import me.gabber235.typewriter.interaction.startBlockingActionBar import me.gabber235.typewriter.interaction.startBlockingMessages import me.gabber235.typewriter.interaction.stopBlockingActionBar import me.gabber235.typewriter.interaction.stopBlockingMessages -import org.bukkit.NamespacedKey -import org.bukkit.Sound -import org.bukkit.SoundCategory +import me.gabber235.typewriter.utils.playSound import org.bukkit.entity.Player import org.koin.core.component.KoinComponent import org.koin.core.component.inject @@ -82,9 +80,6 @@ class DialogueSequence(private val player: Player, initialEntry: DialogueEntry) } fun Player.playSpeakerSound(speaker: SpeakerEntry?) { - val soundName = speaker?.sound ?: return - if (soundName.isBlank()) return - val soundNamespace = NamespacedKey.fromString(speaker.sound) - val sound = Sound.values().firstOrNull { it.key == soundNamespace } ?: return - playSound(this, sound, SoundCategory.VOICE, 1f, 1f) + val sound = speaker?.sound ?: return + playSound(sound) } \ No newline at end of file diff --git a/plugin/src/main/kotlin/me/gabber235/typewriter/entry/entries/EntityEntry.kt b/plugin/src/main/kotlin/me/gabber235/typewriter/entry/entries/EntityEntry.kt index 65195528a8..9c5b17782a 100644 --- a/plugin/src/main/kotlin/me/gabber235/typewriter/entry/entries/EntityEntry.kt +++ b/plugin/src/main/kotlin/me/gabber235/typewriter/entry/entries/EntityEntry.kt @@ -2,18 +2,17 @@ package me.gabber235.typewriter.entry.entries import me.gabber235.typewriter.adapters.Tags import me.gabber235.typewriter.adapters.modifiers.Help -import me.gabber235.typewriter.adapters.modifiers.Sound import me.gabber235.typewriter.entry.StaticEntry +import me.gabber235.typewriter.utils.Sound @Tags("entity") interface EntityEntry : StaticEntry @Tags("speaker") interface SpeakerEntry : EntityEntry { - @Help("The name of the entity that will be displayed in the chat (e.g. 'Steve' or 'Alex').") - val displayName: String + @Help("The name of the entity that will be displayed in the chat (e.g. 'Steve' or 'Alex').") + val displayName: String - @Sound - @Help("The sound that will be played when the entity speaks.") - val sound: String + @Help("The sound that will be played when the entity speaks.") + val sound: Sound } \ No newline at end of file diff --git a/plugin/src/main/kotlin/me/gabber235/typewriter/entry/entries/SoundIdEntry.kt b/plugin/src/main/kotlin/me/gabber235/typewriter/entry/entries/SoundEntry.kt similarity index 61% rename from plugin/src/main/kotlin/me/gabber235/typewriter/entry/entries/SoundIdEntry.kt rename to plugin/src/main/kotlin/me/gabber235/typewriter/entry/entries/SoundEntry.kt index e4beb101aa..4b371fc3d2 100644 --- a/plugin/src/main/kotlin/me/gabber235/typewriter/entry/entries/SoundIdEntry.kt +++ b/plugin/src/main/kotlin/me/gabber235/typewriter/entry/entries/SoundEntry.kt @@ -2,8 +2,14 @@ package me.gabber235.typewriter.entry.entries import me.gabber235.typewriter.adapters.Tags import me.gabber235.typewriter.entry.StaticEntry +import net.kyori.adventure.sound.Sound @Tags("sound_id") interface SoundIdEntry : StaticEntry { val soundId: String +} + +@Tags("sound_source") +interface SoundSourceEntry : StaticEntry { + fun getEmitter(): Sound.Emitter } \ No newline at end of file diff --git a/plugin/src/main/kotlin/me/gabber235/typewriter/utils/Sound.kt b/plugin/src/main/kotlin/me/gabber235/typewriter/utils/Sound.kt new file mode 100644 index 0000000000..8e6970001c --- /dev/null +++ b/plugin/src/main/kotlin/me/gabber235/typewriter/utils/Sound.kt @@ -0,0 +1,92 @@ +package me.gabber235.typewriter.utils + +import me.gabber235.typewriter.adapters.modifiers.Help +import me.gabber235.typewriter.entry.Query +import me.gabber235.typewriter.entry.entries.SoundIdEntry +import me.gabber235.typewriter.entry.entries.SoundSourceEntry +import me.gabber235.typewriter.logger +import net.kyori.adventure.audience.Audience +import net.kyori.adventure.sound.SoundStop +import org.bukkit.Location +import org.bukkit.NamespacedKey +import net.kyori.adventure.sound.Sound as AdventureSound + + +data class Sound( + @Help("The sound to play.") + val soundId: SoundId = SoundId.EMPTY, + @Help("The source of the location to play the sound from. (Defaults to player's location)") + val soundSource: SoundSource = SelfSoundSource, + @Help("The track to play the sound on. (Corresponds to the Minecraft sound category)") + val track: AdventureSound.Source = AdventureSound.Source.MASTER, + @Help("The volume of the sound. A value of 1.0 is normal volume.") + val volume: Float = 1.0f, + @Help("The pitch of the sound. A value of 1.0 is normal pitch.") + val pitch: Float = 1.0f, +) { + + companion object { + val EMPTY = Sound() + } + + val soundStop: SoundStop? + get() = soundId.namespacedKey?.let { SoundStop.named(it) } + + fun play(audience: Audience) { + val key = this.soundId.namespacedKey ?: return + val sound = AdventureSound.sound(key, track, volume, pitch) + + when (soundSource) { + is SelfSoundSource -> audience.playSound(sound) + is EmitterSoundSource -> { + val entryId = soundSource.entryId + val entry = Query.findById(entryId) + if (entry == null) { + logger.warning("Could not find sound source entry with id $entryId") + return + } + val emitter = entry.getEmitter() + audience.playSound(sound, emitter) + } + + is LocationSoundSource -> { + val location = soundSource.location + audience.playSound(sound, location.x, location.y, location.z) + } + } + } +} + +fun Audience.playSound(sound: Sound) = sound.play(this) +fun Audience.stopSound(sound: Sound) = sound.soundStop?.let { this.stopSound(it) } + +sealed interface SoundId { + companion object { + val EMPTY = DefaultSoundId(null) + } + + val namespacedKey: NamespacedKey? +} + +class DefaultSoundId(override val namespacedKey: NamespacedKey?) : SoundId { + constructor(key: String) : this(if (key.isEmpty()) null else NamespacedKey.fromString(key)) +} + +class EntrySoundId(val entryId: String) : SoundId { + override val namespacedKey: NamespacedKey? + get() { + val entry = Query.findById(entryId) + if (entry == null) { + logger.warning("Could not find sound entry with id $entryId") + return null + } + return NamespacedKey.fromString(entry.soundId) + } +} + +sealed interface SoundSource + +object SelfSoundSource : SoundSource +class EmitterSoundSource(val entryId: String) : SoundSource + +class LocationSoundSource(val location: Location) : SoundSource diff --git a/plugin/src/main/kotlin/me/gabber235/typewriter/utils/SoundId.kt b/plugin/src/main/kotlin/me/gabber235/typewriter/utils/SoundId.kt deleted file mode 100644 index 9608d2e5a3..0000000000 --- a/plugin/src/main/kotlin/me/gabber235/typewriter/utils/SoundId.kt +++ /dev/null @@ -1,28 +0,0 @@ -package me.gabber235.typewriter.utils - -import me.gabber235.typewriter.entry.Query -import me.gabber235.typewriter.entry.entries.SoundIdEntry -import me.gabber235.typewriter.logger -import org.bukkit.NamespacedKey - -sealed interface SoundId { - companion object { - val EMPTY = DefaultSoundId(null) - } - - val namespacedKey: NamespacedKey? -} - -class DefaultSoundId(override val namespacedKey: NamespacedKey?) : SoundId - -class EntrySoundId(val entryId: String) : SoundId { - override val namespacedKey: NamespacedKey? - get() { - val entry = Query.findById(entryId) - if (entry == null) { - logger.warning("Could not find sound entry with id $entryId") - return null - } - return NamespacedKey.fromString(entry.soundId) - } -} \ No newline at end of file