Skip to content

Commit

Permalink
Rework sounds to allow custom sounds and better inspector
Browse files Browse the repository at this point in the history
  • Loading branch information
gabber235 committed Dec 1, 2023
1 parent 1b96f8d commit bb09583
Show file tree
Hide file tree
Showing 22 changed files with 444 additions and 131 deletions.
Original file line number Diff line number Diff line change
@@ -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)
/**
Expand All @@ -28,26 +28,34 @@ class PlaySoundActionEntry(
override val modifiers: List<Modifier> = emptyList(),
override val triggers: List<String> = 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<Location> = 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<String>("sound", context.gson).optional
val location = json.getAndParse<Optional<Location>>("location", context.gson).optional
val volume = json.getAndParse<Float>("volume", context.gson).optional
val pitch = json.getAndParse<Float>("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
}
Original file line number Diff line number Diff line change
@@ -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)
/**
Expand All @@ -28,8 +32,6 @@ class SoundCinematicEntry(
override val criteria: List<Criteria>,
@Segments(icon = Icons.MUSIC)
val segments: List<SoundSegment>,
@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(
Expand All @@ -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(
Expand All @@ -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<AdventureSound.Source>("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<Int>("startFrame", context.gson).optional
val endFrame = segmentJson.getAndParse<Int>("endFrame", context.gson).optional
val sound = segmentJson.getAndParse<String>("sound", context.gson).optional
val volume = segmentJson.getAndParse<Float>("volume", context.gson).optional
val pitch = segmentJson.getAndParse<Float>("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
}
Original file line number Diff line number Diff line change
@@ -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)
/**
Expand All @@ -17,5 +21,25 @@ class SimpleSpeakerEntry(
override val id: String = "",
override val name: String = "",
override val displayName: String = "",
override val sound: String = "",
) : SpeakerEntry
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<String>("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
}
Original file line number Diff line number Diff line change
@@ -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

Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -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
) : 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<String>("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
}
4 changes: 2 additions & 2 deletions app/lib/models/sound_id.dart → app/lib/models/sound.dart
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 5 additions & 3 deletions app/lib/models/sounds.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -61,11 +62,12 @@ Future<Map<String, List<SoundData>>> minecraftSounds(

@riverpod
Future<MinecraftSound?> 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 {
Expand Down
2 changes: 2 additions & 0 deletions app/lib/widgets/inspector/editors.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand All @@ -44,6 +45,7 @@ List<EditorFilter> editorFilters(EditorFiltersRef ref) => [
PotionEffectEditorFilter(),
ItemEditorFilter(),
SoundIdEditorFilter(),
SoundSourceEditorFilter(),

// Default filters
StringEditorFilter(),
Expand Down
2 changes: 1 addition & 1 deletion app/lib/widgets/inspector/editors.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion app/lib/widgets/inspector/editors/sound_id.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
Loading

0 comments on commit bb09583

Please sign in to comment.