diff --git a/gui/src/main/kotlin/io/github/chrislo27/rhre3/sfxdb/gui/RSDE.kt b/gui/src/main/kotlin/io/github/chrislo27/rhre3/sfxdb/gui/RSDE.kt index b22246b..a64a3f2 100644 --- a/gui/src/main/kotlin/io/github/chrislo27/rhre3/sfxdb/gui/RSDE.kt +++ b/gui/src/main/kotlin/io/github/chrislo27/rhre3/sfxdb/gui/RSDE.kt @@ -33,7 +33,7 @@ class RSDE : Application() { companion object { const val TITLE = "RHRE SFX Database Editor" - val VERSION = Version(1, 0, 7, "") + val VERSION = Version(1, 1, 0, "DEVELOPMENT") val rootFolder: File = File(System.getProperty("user.home")).resolve(".rsde/").apply { mkdirs() } val rhreRoot: File = File(System.getProperty("user.home")).resolve(".rhre3/").apply { mkdirs() diff --git a/gui/src/main/kotlin/io/github/chrislo27/rhre3/sfxdb/gui/editor/panes/CueObjPane.kt b/gui/src/main/kotlin/io/github/chrislo27/rhre3/sfxdb/gui/editor/panes/CueObjPane.kt index b9ec874..45539c8 100644 --- a/gui/src/main/kotlin/io/github/chrislo27/rhre3/sfxdb/gui/editor/panes/CueObjPane.kt +++ b/gui/src/main/kotlin/io/github/chrislo27/rhre3/sfxdb/gui/editor/panes/CueObjPane.kt @@ -40,6 +40,9 @@ class CueObjPane(editor: Editor, struct: Cue) : DatamodelPane(editor, struc val fileExtField = TextField(struct.endingSound).apply { this.promptText = SoundFileExtensions.DEFAULT.fileExt } + val earlinessField = doubleSpinnerFactory(0.0, Float.MAX_VALUE.toDouble(), 0.0, 0.1) + val loopStartField = doubleSpinnerFactory(0.0, Float.MAX_VALUE.toDouble(), 0.0, 0.1) + val loopEndField = doubleSpinnerFactory(-1.0, Float.MAX_VALUE.toDouble(), -1.0, 0.1) val responseIDsField = ChipPane(FXCollections.observableArrayList((struct.responseIDs ?: mutableListOf()).map { Chip(it) })) init { @@ -66,6 +69,15 @@ class CueObjPane(editor: Editor, struct: Cue) : DatamodelPane(editor, struc tooltip = Tooltip().bindLocalized("cueObject.endingSound.tooltip") }, endingSoundField) addProperty(Label().bindLocalized("cueObject.fileExtension"), fileExtField) + addProperty(Label().bindLocalized("cueObject.earliness").apply { + tooltip = Tooltip().bindLocalized("cueObject.earliness.tooltip") + }, earlinessField) + addProperty(Label().bindLocalized("cueObject.loopStart").apply { + tooltip = Tooltip().bindLocalized("cueObject.loopStart.tooltip") + }, loopStartField) + addProperty(Label().bindLocalized("cueObject.loopEnd").apply { + tooltip = Tooltip().bindLocalized("cueObject.loopEnd.tooltip") + }, loopEndField) addProperty(Label().bindLocalized("datamodel.responseIDs").apply { tooltip = Tooltip().bindLocalized("datamodel.responseIDs.tooltip") }, responseIDsField) @@ -88,6 +100,7 @@ class CueObjPane(editor: Editor, struct: Cue) : DatamodelPane(editor, struc loopsField.selectedProperty().addListener { _, _, newValue -> struct.loops = newValue editor.markDirty() + forceUpdate() } baseBpmField.valueProperty().addListener { _, _, newValue -> struct.baseBpm = newValue.toFloat() @@ -113,6 +126,18 @@ class CueObjPane(editor: Editor, struct: Cue) : DatamodelPane(editor, struc struct.responseIDs = list.takeUnless { it.isEmpty() } editor.markDirty() }) + earlinessField.valueProperty().addListener { _, _, n -> + struct.earliness = n.toFloat() + editor.markDirty() + } + loopStartField.valueProperty().addListener { _, _, n -> + struct.loopStart = n.toFloat() + editor.markDirty() + } + loopEndField.valueProperty().addListener { _, _, n -> + struct.loopEnd = n.toFloat() + editor.markDirty() + } fileExtField.textProperty().addListener { _, _, _ -> editor.refreshLists() @@ -130,6 +155,15 @@ class CueObjPane(editor: Editor, struct: Cue) : DatamodelPane(editor, struc durationField.valueProperty().addListener { _, _, _ -> editor.refreshLists() } + earlinessField.valueProperty().addListener { _, _, _ -> + editor.refreshLists() + } + loopStartField.valueProperty().addListener { _, _, _ -> + editor.refreshLists() + } + loopEndField.valueProperty().addListener { _, _, _ -> + editor.refreshLists() + } } init { @@ -141,6 +175,8 @@ class CueObjPane(editor: Editor, struct: Cue) : DatamodelPane(editor, struc validation.registerValidators(endingSoundField, Validators.EXTERNAL_CUE_POINTER, Validators.cuePointerPointsNowhere(editor.gameObject)) validation.registerValidators(responseIDsField, Validators.EXTERNAL_RESPONSE_IDS, Validators.responseIDsPointsNowhere(editor.gameObject)) validation.registerValidators(durationField, Validators.ZERO_DURATION) + validation.registerValidators(loopStartField, Validators.loopStartAheadOfEnd(this), Validators.loopStartWithoutLooping(this)) + validation.registerValidators(loopEndField, Validators.loopEndWithoutLooping(this)) } } \ No newline at end of file diff --git a/gui/src/main/kotlin/io/github/chrislo27/rhre3/sfxdb/gui/validation/Validators.kt b/gui/src/main/kotlin/io/github/chrislo27/rhre3/sfxdb/gui/validation/Validators.kt index ee67dbb..6c101a7 100644 --- a/gui/src/main/kotlin/io/github/chrislo27/rhre3/sfxdb/gui/validation/Validators.kt +++ b/gui/src/main/kotlin/io/github/chrislo27/rhre3/sfxdb/gui/validation/Validators.kt @@ -6,6 +6,7 @@ import io.github.chrislo27.rhre3.sfxdb.SoundFileExtensions import io.github.chrislo27.rhre3.sfxdb.adt.Cue import io.github.chrislo27.rhre3.sfxdb.adt.Datamodel import io.github.chrislo27.rhre3.sfxdb.adt.Game +import io.github.chrislo27.rhre3.sfxdb.gui.editor.panes.CueObjPane import io.github.chrislo27.rhre3.sfxdb.gui.editor.panes.GameObjPane import io.github.chrislo27.rhre3.sfxdb.gui.util.UiLocalization import io.github.chrislo27.rhre3.sfxdb.validation.Transformers @@ -145,6 +146,19 @@ object Validators { fromErrorIf(t, UiLocalization["validation.zeroDuration"], u <= 0.0) } + fun loopStartWithoutLooping(cueObjPane: CueObjPane): Validator = Validator { t, u -> + Validators.fromWarningIf(t, UiLocalization["validation.loopPointsWithoutLooping"]) { !cueObjPane.loopsField.isSelected && u > 0.0 } + } + + fun loopEndWithoutLooping(cueObjPane: CueObjPane): Validator = Validator { t, u -> + Validators.fromWarningIf(t, UiLocalization["validation.loopPointsWithoutLooping"]) { !cueObjPane.loopsField.isSelected && u >= 0.0 } + } + + fun loopStartAheadOfEnd(cueObjPane: CueObjPane): Validator = Validator { t, u -> + val loopEndValue = cueObjPane.loopEndField.value + Validators.fromErrorIf(t, UiLocalization["validation.loopStartAheadOfEnd"]) { loopEndValue >= 0.0 && u > loopEndValue } + } + // MultipartObject val ZERO_DISTANCE: Validator = Validator { t, u -> fromErrorIf(t, UiLocalization["validation.zeroDistance"], u <= 0.0) diff --git a/gui/src/main/resources/localization/default.properties b/gui/src/main/resources/localization/default.properties index 45699b0..22a5703 100644 --- a/gui/src/main/resources/localization/default.properties +++ b/gui/src/main/resources/localization/default.properties @@ -127,6 +127,12 @@ cueObject.introSound.tooltip=The pointer ID for another sound to play when this cueObject.endingSound=Ending sound: cueObject.endingSound.tooltip=The pointer ID for another sound to play when this cue ends cueObject.fileExtension=File extension: +cueObject.earliness=Earliness: +cueObject.earliness.tooltip=The amount of seconds early to play this cue +cueObject.loopStart=Loop start: +cueObject.loopStart.tooltip=Where to start the loop in seconds +cueObject.loopEnd=Loop end: +cueObject.loopEnd.tooltip=Where to end the loop in seconds. Negative values indicate the entire sound file length equidistantObj.distance=Distance equidistantObj.distance.tooltip=The distance between each cue (end-to-end) @@ -165,3 +171,5 @@ validation.abnormalSemitone=The semitone value is out of the standard RHRE range validation.zeroDuration=The duration must be greater than zero validation.zeroDistance=The distance must be greater than zero validation.unsupportedFileExt=File extension is not supported. Use {0} or leave blank +validation.loopPointsWithoutLooping=The "loops" field isn't true, but this field is positive +validation.loopStartAheadOfEnd=The loop start is after the loop end. Move the loop start back, or set the loop end to -1.0 diff --git a/main/src/main/kotlin/io/github/chrislo27/rhre3/sfxdb/adt/ADT.kt b/main/src/main/kotlin/io/github/chrislo27/rhre3/sfxdb/adt/ADT.kt index fd11f06..dce948e 100644 --- a/main/src/main/kotlin/io/github/chrislo27/rhre3/sfxdb/adt/ADT.kt +++ b/main/src/main/kotlin/io/github/chrislo27/rhre3/sfxdb/adt/ADT.kt @@ -58,10 +58,13 @@ class Cue( @JsonInclude(JsonInclude.Include.NON_EMPTY) var endingSound: String? = null, @JsonInclude(JsonInclude.Include.NON_EMPTY) var responseIDs: MutableList? = null, @JsonInclude(JsonInclude.Include.NON_DEFAULT) var baseBpm: Float = 0f, - @JsonInclude(JsonInclude.Include.NON_DEFAULT) var loops: Boolean = false + @JsonInclude(JsonInclude.Include.NON_DEFAULT) var loops: Boolean = false, + @JsonInclude(JsonInclude.Include.NON_DEFAULT) var earliness: Float = 0f, + @JsonInclude(JsonInclude.Include.NON_DEFAULT) var loopStart: Float = 0f, + @JsonInclude(JsonInclude.Include.NON_DEFAULT) var loopEnd: Float = -1f ) : Datamodel("cue", id, name, deprecatedIDs) { override fun copy(): Datamodel { - return Cue(id, name, deprecatedIDs, duration, stretchable, repitchable, fileExtension, introSound, endingSound, responseIDs?.toMutableList(), baseBpm, loops) + return Cue(id, name, deprecatedIDs, duration, stretchable, repitchable, fileExtension, introSound, endingSound, responseIDs?.toMutableList(), baseBpm, loops, earliness, loopStart, loopEnd) } } diff --git a/main/src/main/kotlin/io/github/chrislo27/rhre3/sfxdb/validation/ADT.kt b/main/src/main/kotlin/io/github/chrislo27/rhre3/sfxdb/validation/ADT.kt index 35dd899..5365104 100644 --- a/main/src/main/kotlin/io/github/chrislo27/rhre3/sfxdb/validation/ADT.kt +++ b/main/src/main/kotlin/io/github/chrislo27/rhre3/sfxdb/validation/ADT.kt @@ -86,12 +86,16 @@ class CueObject : DatamodelObject() { var introSound: Result by Property(Transformers.idTransformer, "") var endingSound: Result by Property(Transformers.idTransformer, "") var responseIDs: Result> by Property(Transformers.responseIDsTransformer, mutableListOf()) + var earliness: Result by Property(Transformers.positiveFloatTransformer("Earliness must be positive"), 0f) + var loopStart: Result by Property(Transformers.positiveFloatTransformer("Loop start must be positive"), 0f) + var loopEnd: Result by Property(Transformers.floatTransformer, -1f) override fun producePerfectADT(): Cue { return Cue( id.orException(), name.orException(), deprecatedIDs.orException(), duration.orException(), stretchable.orException(), repitchable.orException(), fileExtension.orException(), introSound.orException(), - endingSound.orException(), responseIDs.orException(), baseBpm.orException(), loops.orException() + endingSound.orException(), responseIDs.orException(), baseBpm.orException(), loops.orException(), + earliness.orException(), loopStart.orException(), loopEnd.orException() ) } @@ -99,7 +103,8 @@ class CueObject : DatamodelObject() { return Cue( id.orElse(""), name.orElse(""), deprecatedIDs.orElse(mutableListOf()), duration.orElse(0f), stretchable.orElse(false), repitchable.orElse(false), fileExtension.orElse(""), introSound.orNull(), - endingSound.orNull(), responseIDs.orNull(), baseBpm.orElse(0f), loops.orElse(false) + endingSound.orNull(), responseIDs.orNull(), baseBpm.orElse(0f), loops.orElse(false), + earliness.orElse(0f), loopStart.orElse(0f), loopEnd.orElse(-1f) ) } }