diff --git a/assets/images/editors/ui/context-icons.png b/assets/images/editors/ui/context-icons.png index b7411d02d..68f8976e5 100644 Binary files a/assets/images/editors/ui/context-icons.png and b/assets/images/editors/ui/context-icons.png differ diff --git a/assets/languages/en/Editors.xml b/assets/languages/en/Editors.xml index ae4c27e16..b2cad46c5 100644 --- a/assets/languages/en/Editors.xml +++ b/assets/languages/en/Editors.xml @@ -223,6 +223,7 @@ Reset Zoom Show Sections Separator Show Beats Separator + Show Camera Highlights Rainbow Waveforms Low Detail Waveforms Scroll Left @@ -236,8 +237,9 @@ Go forward to the end Add camera on Opponent Add camera on Player - Mute instrumental - Mute voices + Instrumental + Voices + Hitsounds @@ -250,6 +252,7 @@ Bookmark List Edit Bookmarks New Bookmark + Bookmark List Note @@ -258,6 +261,7 @@ Subtract sustain length Select all Select measure + Note Types List Edit Note Types List Snap @@ -308,8 +312,9 @@ Options ↓ + Waveforms Hitsounds - Mute Vocals + Vocals Edit Delete diff --git a/source/funkin/backend/chart/Chart.hx b/source/funkin/backend/chart/Chart.hx index 3dbeceb82..fd82ec4be 100644 --- a/source/funkin/backend/chart/Chart.hx +++ b/source/funkin/backend/chart/Chart.hx @@ -278,9 +278,13 @@ class Chart { var filteredChart = filterChartForSaving(chart, saveSettings.saveMetaInChart, saveSettings.saveLocalEvents, saveSettings.saveGlobalEvents && saveSettings.seperateGlobalEvents != true); #if sys - var songPath = saveSettings.songFolder == null ? 'songs/${chart.meta.name}' : saveSettings.songFolder, variantSuffix = variant != null && variant != "" ? '-$variant' : ""; - var metaPath = 'meta$variantSuffix.json', prettyPrint = saveSettings.prettyPrint == true ? Flags.JSON_PRETTY_PRINT : null, temp:String; - if ((temp = Paths.assetsTree.getPath('assets/$songPath/$metaPath')) != null) { + var songPath = saveSettings.songFolder == null ? 'songs/${chart.meta.name}' : saveSettings.songFolder, variantSuffix = variant != null && variant != "" ? '-$variant' : "", difficultySuffix = difficulty != null && difficulty != "" ? '-$difficulty' : ""; + var metaPath = 'meta$variantSuffix.json', altMetaPath = 'meta${variantSuffix}${difficultySuffix}.json', prettyPrint = saveSettings.prettyPrint == true ? Flags.JSON_PRETTY_PRINT : null, temp:String; + if ((temp = Paths.assetsTree.getPath('assets/$songPath/$altMetaPath')) != null) { //check for difficulty specific + songPath = temp.substr(0, temp.length - altMetaPath.length - 1); + metaPath = temp; + } + else if ((temp = Paths.assetsTree.getPath('assets/$songPath/$metaPath')) != null) { songPath = temp.substr(0, temp.length - metaPath.length - 1); metaPath = temp; } diff --git a/source/funkin/editors/character/CharacterAnimButton.hx b/source/funkin/editors/character/CharacterAnimButton.hx index a9b184a9e..fd400fa99 100644 --- a/source/funkin/editors/character/CharacterAnimButton.hx +++ b/source/funkin/editors/character/CharacterAnimButton.hx @@ -449,12 +449,12 @@ class CharacterAnimButton extends UIButton { public function toggleGhost() { if (valid && parent.ghosts.indexOf(anim) == -1) { - FlxG.sound.play(Paths.sound(Flags.DEFAULT_CHARACTER_GHOSTENABLE_SOUND)); + UIState.playEditorSound(Flags.DEFAULT_CHARACTER_GHOSTENABLE_SOUND); parent.ghosts.push(anim); ghostIcon.animation.play("alive", true); ghostIcon.color = 0xFFFFFFFF; } else { - FlxG.sound.play(Paths.sound(Flags.DEFAULT_CHARACTER_GHOSTDISABLE_SOUND)); + UIState.playEditorSound(Flags.DEFAULT_CHARACTER_GHOSTDISABLE_SOUND); parent.ghosts.remove(anim); ghostIcon.animation.play("dead", true); ghostIcon.color = 0xFFADADAD; diff --git a/source/funkin/editors/character/CharacterAnimsWindow.hx b/source/funkin/editors/character/CharacterAnimsWindow.hx index ae6822eac..6e9c5a015 100644 --- a/source/funkin/editors/character/CharacterAnimsWindow.hx +++ b/source/funkin/editors/character/CharacterAnimsWindow.hx @@ -99,7 +99,7 @@ class CharacterAnimsWindow extends UIButtonList { displayAnimsFramesList.remove(name); public function deleteAnimation(button:CharacterAnimButton, addToUndo:Bool = true) { - FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_DELETE_SOUND)); + UIState.playEditorSound(Flags.DEFAULT_EDITOR_DELETE_SOUND); if (buttons.members.length <= 1) return; if (character.getAnimName() == button.anim) @:privateAccess CharacterEditor.instance._animation_down(null); diff --git a/source/funkin/editors/character/CharacterEditor.hx b/source/funkin/editors/character/CharacterEditor.hx index b040d9bd3..efdeb30d8 100644 --- a/source/funkin/editors/character/CharacterEditor.hx +++ b/source/funkin/editors/character/CharacterEditor.hx @@ -453,7 +453,7 @@ class CharacterEditor extends UIState { function _file_save(_) { #if sys - FlxG.sound.play(Paths.sound('editors/save')); + UIState.playEditorSound(Flags.DEFAULT_EDITOR_SAVE_SOUND); CoolUtil.safeSaveFile( '${Paths.getAssetsRoot()}/data/characters/${character.curCharacter}.xml', buildCharacter() @@ -465,7 +465,7 @@ class CharacterEditor extends UIState { } function _file_saveas(_) { - FlxG.sound.play(Paths.sound('editors/save')); + UIState.playEditorSound(Flags.DEFAULT_EDITOR_SAVE_SOUND); openSubState(new SaveSubstate(buildCharacter(), { defaultSaveFile: '${character.curCharacter}.xml' })); @@ -499,7 +499,7 @@ class CharacterEditor extends UIState { } function _undo(undo:CharacterEditorChange) { - FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_UNDO_SOUND)); + UIState.playEditorSound(Flags.DEFAULT_EDITOR_UNDO_SOUND); switch (undo) { case null: // do nothing case CCharEditPosition(oldPos, newPos): @@ -570,7 +570,7 @@ class CharacterEditor extends UIState { } function _redo(redo:CharacterEditorChange) { - FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_REDO_SOUND)); + UIState.playEditorSound(Flags.DEFAULT_EDITOR_REDO_SOUND); switch (redo) { case null: // do nothing case CCharEditPosition(oldPos, newPos): diff --git a/source/funkin/editors/charter/Charter.hx b/source/funkin/editors/charter/Charter.hx index 952fdacac..e776bd781 100644 --- a/source/funkin/editors/charter/Charter.hx +++ b/source/funkin/editors/charter/Charter.hx @@ -77,6 +77,7 @@ class Charter extends UIState { public var strumlineLockButton:CharterStrumlineButton; public var hitsound:FlxSound; + public var hitsoundGlobalVolume:Float = 1.0; public var metronome:FlxSound; public var vocals:FlxSound; @@ -95,6 +96,7 @@ class Charter extends UIState { public var rightEventRowText:UIText; public var leftEventsGroup:CharterEventGroup = new CharterEventGroup(); public var rightEventsGroup:CharterEventGroup = new CharterEventGroup(); + public var cameraMovementChanges:Array = []; public var charterCamera:FlxCamera; public var uiCamera:FlxCamera; @@ -307,6 +309,11 @@ class Charter extends UIState { onSelect: _view_showeventBeatSeparator, icon: Options.charterShowBeats ? 1 : 0 }, + { + label: translate("view.showCameraHighlights"), + onSelect: _view_showeventCameraHighlights, + icon: Options.charterShowCameraHighlights ? 1 : 0 + }, null, { label: translate("view.rainbowWaveforms"), @@ -514,7 +521,7 @@ class Charter extends UIState { strumlineLockButton = new CharterStrumlineButton("editors/charter/lock-strumline", translate("lock-unlock")); strumlineLockButton.onClick = function () { - FlxG.sound.play(Paths.sound(!strumLines.draggable ? Flags.DEFAULT_CHARTER_STRUMUNLOCK_SOUND : Flags.DEFAULT_CHARTER_STRUMLOCK_SOUND)); + UIState.playEditorSound(!strumLines.draggable ? Flags.DEFAULT_CHARTER_STRUMUNLOCK_SOUND : Flags.DEFAULT_CHARTER_STRUMLOCK_SOUND); if (strumLines != null) { strumLines.draggable = !strumLines.draggable; strumlineLockButton.textTweenColor.color = strumLines.draggable ? 0xFF5C95CA : 0xFFE16565; @@ -684,6 +691,7 @@ class Charter extends UIState { CharterGridSeperatorBase.lastConductorSprY = Math.NEGATIVE_INFINITY; updateWaveforms(); + updateCameraChanges(); } public function getWavesToGenerate():Array<{name:String, sound:FlxSound}> { @@ -739,6 +747,38 @@ class Charter extends UIState { } } + public function updateCameraChanges() { + if (!Options.charterShowCameraHighlights) return; + + cameraMovementChanges = []; + for (grp in [leftEventsGroup, rightEventsGroup]) { + grp.filterEvents(); + grp.sortEvents(); + for(e in grp.members) { + for(event in e.events) { + if (event.name == "Camera Movement") { + cameraMovementChanges.push({ + strumLineID: event.params[0], + step: e.step, + endStep: __endStep + }); + } + } + } + } + + //need to sort again for both local and global events to be used + cameraMovementChanges.sort(function(e1, e2) { + return FlxSort.byValues(FlxSort.ASCENDING, e1.step, e2.step); + }); + //update previous change + if (cameraMovementChanges.length > 0) { + for (i in 1...cameraMovementChanges.length) { + cameraMovementChanges[i-1].endStep = cameraMovementChanges[i].step; + } + } + } + public override function beatHit(curBeat:Int) { super.beatHit(curBeat); if (FlxG.sound.music.playing) { @@ -801,7 +841,7 @@ class Charter extends UIState { else Chart.save(PlayState.SONG, __diff.toLowerCase(), __variant, {saveMetaInChart: true, saveLocalEvents: true, seperateGlobalEvents: true, prettyPrint: Options.editorCharterPrettyPrint}); - FlxG.sound.play(Paths.sound('editors/save')); + UIState.playEditorSound(Flags.DEFAULT_EDITOR_SAVE_SOUND); undos.save(); } autoSaveNotif.cancelled = false; @@ -961,7 +1001,7 @@ class Charter extends UIState { notesGroup.add(note); selection = [note]; undos.addToUndo(CCreateSelection([note])); - FlxG.sound.play(Paths.sound(Flags.DEFAULT_CHARTER_NOTEPLACE_SOUND)); + UIState.playEditorSound(Flags.DEFAULT_CHARTER_NOTEPLACE_SOUND); } isSelecting = false; } @@ -1090,7 +1130,7 @@ class Charter extends UIState { if (selected == null) return selected; if (selected is CharterNote) { - FlxG.sound.play(Paths.sound(Flags.DEFAULT_CHARTER_NOTEDELETE_SOUND)); + UIState.playEditorSound(Flags.DEFAULT_CHARTER_NOTEDELETE_SOUND); var note:CharterNote = cast selected; note.strumLineID = strumLines.members.indexOf(note.strumLine); note.strumLine = null; // For static undos :D @@ -1570,20 +1610,20 @@ class Charter extends UIState { else {undos = null; FlxG.switchState(new CharterSelection()); Charter.instance.__clearStatics();} } - function _file_save_all(_) {saveEverything(); FlxG.sound.play(Paths.sound('editors/save'));} - function _file_save(_) {saveChart(); FlxG.sound.play(Paths.sound('editors/save'));} - function _file_saveas(_) {saveChartAs(); FlxG.sound.play(Paths.sound('editors/save'));} - function _file_events_save(_) {saveEvents(); FlxG.sound.play(Paths.sound('editors/save'));} - function _file_events_saveas(_) {saveEventsAs(); FlxG.sound.play(Paths.sound('editors/save'));} - function _file_save_no_events(_) {saveChart(true, false); FlxG.sound.play(Paths.sound('editors/save'));} - function _file_saveas_no_events(_) {saveChartAs(true, false); FlxG.sound.play(Paths.sound('editors/save'));} - function _file_meta_save(_) {saveMeta(); FlxG.sound.play(Paths.sound('editors/save'));} - function _file_meta_saveas(_) {saveMetaAs(); FlxG.sound.play(Paths.sound('editors/save'));} - function _file_saveas_fnflegacy(_) {saveLegacyChartAs(); FlxG.sound.play(Paths.sound('editors/save'));} - function _file_saveas_psych(_) {savePsychChartAs(); FlxG.sound.play(Paths.sound('editors/save'));} + function _file_save_all(_) {saveEverything(); UIState.playEditorSound(Flags.DEFAULT_EDITOR_SAVE_SOUND);} + function _file_save(_) {saveChart(); UIState.playEditorSound(Flags.DEFAULT_EDITOR_SAVE_SOUND);} + function _file_saveas(_) {saveChartAs(); UIState.playEditorSound(Flags.DEFAULT_EDITOR_SAVE_SOUND);} + function _file_events_save(_) {saveEvents(); UIState.playEditorSound(Flags.DEFAULT_EDITOR_SAVE_SOUND);} + function _file_events_saveas(_) {saveEventsAs(); UIState.playEditorSound(Flags.DEFAULT_EDITOR_SAVE_SOUND);} + function _file_save_no_events(_) {saveChart(true, false); UIState.playEditorSound(Flags.DEFAULT_EDITOR_SAVE_SOUND);} + function _file_saveas_no_events(_) {saveChartAs(true, false); UIState.playEditorSound(Flags.DEFAULT_EDITOR_SAVE_SOUND);} + function _file_meta_save(_) {saveMeta(); UIState.playEditorSound(Flags.DEFAULT_EDITOR_SAVE_SOUND);} + function _file_meta_saveas(_) {saveMetaAs(); UIState.playEditorSound(Flags.DEFAULT_EDITOR_SAVE_SOUND);} + function _file_saveas_fnflegacy(_) {saveLegacyChartAs(); UIState.playEditorSound(Flags.DEFAULT_EDITOR_SAVE_SOUND);} + function _file_saveas_psych(_) {savePsychChartAs(); UIState.playEditorSound(Flags.DEFAULT_EDITOR_SAVE_SOUND);} function _edit_copy(_, playSFX=true) { - if (playSFX) FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_COPY_SOUND)); + if (playSFX) UIState.playEditorSound(Flags.DEFAULT_EDITOR_COPY_SOUND); if(selection.length == 0) return; var minStep:Float = selection[0].step; @@ -1602,7 +1642,7 @@ class Charter extends UIState { ]; } function _edit_paste(_) { - FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_PASTE_SOUND)); + UIState.playEditorSound(Flags.DEFAULT_EDITOR_PASTE_SOUND); if (clipboard.length <= 0) return; var minStep = curStep; @@ -1630,7 +1670,7 @@ class Charter extends UIState { } function _edit_cut(_) { - FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_CUT_SOUND)); + UIState.playEditorSound(Flags.DEFAULT_EDITOR_CUT_SOUND); if (selection == null || selection.length == 0) return; _edit_copy(_, false); @@ -1638,7 +1678,7 @@ class Charter extends UIState { } function _edit_delete(_) { - FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_DELETE_SOUND)); + UIState.playEditorSound(Flags.DEFAULT_EDITOR_DELETE_SOUND); if (selection == null || selection.length == 0) return; selection.loop((n:CharterNote) -> { noteDeleteAnims.deleteNotes.push({note: n, time: noteDeleteAnims.deleteTime}); @@ -1647,7 +1687,7 @@ class Charter extends UIState { } function _undo(undo:CharterChange) { - FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_UNDO_SOUND)); + UIState.playEditorSound(Flags.DEFAULT_EDITOR_UNDO_SOUND); switch(undo) { case null: // do nothing case CDeleteStrumLine(strumLineID, strumLine): @@ -1708,7 +1748,7 @@ class Charter extends UIState { } function _redo(redo:CharterChange) { - FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_REDO_SOUND)); + UIState.playEditorSound(Flags.DEFAULT_EDITOR_REDO_SOUND); switch(redo) { case null: // do nothing case CDeleteStrumLine(strumLineID, strumLine): @@ -1807,14 +1847,24 @@ class Charter extends UIState { function _playback_metronome(t) { t.icon = (Options.charterMetronomeEnabled = !Options.charterMetronomeEnabled) ? 1 : 0; } - function _song_muteinst(t) { - FlxG.sound.music.volume = FlxG.sound.music.volume > 0 ? 0 : 1; - t.icon = 1 - Std.int(Math.ceil(FlxG.sound.music.volume)); + + public function _slider_mutetoggle(t:UIContextMenuOption) { + if (t.slider == null) return; + t.button.slider.value = t.button.slider.value > 0 ? 0 : 1; + } + + function _song_instvolume(t) { + FlxG.sound.music.volume = t.slider.value; + t.icon = t.slider.value > 0.5 ? 7 : (t.slider.value > 0 ? 8 : 9); } - function _song_mutevoices(t) { - vocals.volume = vocals.volume > 0 ? 0 : 1; - for (strumLine in strumLines.members) strumLine.vocals.volume = strumLine.vocals.volume > 0 ? 0 : 1; - t.icon = 1 - Std.int(Math.ceil(vocals.volume)); + function _song_voicesvolume(t) { + vocals.volume = t.slider.value; + for (strumLine in strumLines.members) strumLine.vocals.volume = t.slider.value * strumLine.vocalsVolume; + t.icon = t.slider.value > 0.5 ? 7 : (t.slider.value > 0 ? 8 : 9); + } + function _song_hitsoundvolume(t) { + hitsoundGlobalVolume = t.slider.value; + t.icon = t.slider.value > 0.5 ? 7 : (t.slider.value > 0 ? 8 : 9); } function _playback_back(_) { if (FlxG.sound.music.playing) return; @@ -1859,6 +1909,7 @@ class Charter extends UIState { __event.refreshEventIcons(); (__event.global ? rightEventsGroup : leftEventsGroup).add(__event); undos.addToUndo(CEditEvent(__event, [], __event.events)); + updateCameraChanges(); } public function getBookmarkList():Array { @@ -2010,26 +2061,56 @@ class Charter extends UIState { if (bookmarks.length > 0) { + var bookmarkOptions:Array = []; var goToBookmark = TU.getRaw("charter.bookmarks.goTo"); for (b in bookmarks) { - newChilds.push({ + bookmarkOptions.push({ label: goToBookmark.format([b.name]), onSelect: function(_) { Conductor.songPosition = Conductor.getTimeForStep(b.time); } }); } + newChilds.push({ + label: translate("bookmarks.bookmarkList"), + childs: bookmarkOptions + }); newChilds.push(null); } - newChilds.push({ - label: translate("song.muteInst"), - onSelect: _song_muteinst + label: translate("song.inst"), + slider: { + min: 0, + max: 1, + value: 1, + onChange: _song_instvolume + }, + onIconClick: _slider_mutetoggle, + icon: 7 + }); + + newChilds.push({ + label: translate("song.voices"), + slider: { + min: 0, + max: 1, + value: 1, + onChange: _song_voicesvolume + }, + onIconClick: _slider_mutetoggle, + icon: 7 }); newChilds.push({ - label: translate("song.muteVoices"), - onSelect: _song_mutevoices + label: translate("song.hitsounds"), + slider: { + min: 0, + max: 1, + value: 1, + onChange: _song_hitsoundvolume + }, + onIconClick: _slider_mutetoggle, + icon: 7 }); if (songTopButton != null) songTopButton.contextMenu = newChilds; @@ -2054,6 +2135,10 @@ class Charter extends UIState { function _view_showeventBeatSeparator(t) { t.icon = (Options.charterShowBeats = !Options.charterShowBeats) ? 1 : 0; } + function _view_showeventCameraHighlights(t) { + t.icon = (Options.charterShowCameraHighlights = !Options.charterShowCameraHighlights) ? 1 : 0; + updateCameraChanges(); + } function _view_switchWaveformRainbow(t) { t.icon = (Options.charterRainbowWaveforms = !Options.charterRainbowWaveforms) ? 1 : 0; @@ -2079,8 +2164,8 @@ class Charter extends UIState { inline function _snap_decreasesnap(_) changequant(-1); inline function _snap_resetsnap(_) setquant(16); - inline function changequant(change:Int) {FlxG.sound.play(Paths.sound(Flags.DEFAULT_CHARTER_SNAPPINGCHANGE_SOUND)); quant = quants[FlxMath.wrap(quants.indexOf(quant) + change, 0, quants.length-1)]; buildSnapsUI();}; - inline function setquant(newQuant:Int) {FlxG.sound.play(Paths.sound(Flags.DEFAULT_CHARTER_SNAPPINGCHANGE_SOUND)); quant = newQuant; buildSnapsUI();} + inline function changequant(change:Int) {UIState.playEditorSound(Flags.DEFAULT_CHARTER_SNAPPINGCHANGE_SOUND); quant = quants[FlxMath.wrap(quants.indexOf(quant) + change, 0, quants.length-1)]; buildSnapsUI();}; + inline function setquant(newQuant:Int) {UIState.playEditorSound(Flags.DEFAULT_CHARTER_SNAPPINGCHANGE_SOUND); quant = newQuant; buildSnapsUI();} function buildSnapsUI():Array { var snapsTopButton:UITopMenuButton = topMenuSpr == null ? null : cast topMenuSpr.members[snapIndex]; @@ -2116,12 +2201,12 @@ class Charter extends UIState { } inline function _note_addsustain(t) { - FlxG.sound.play(Paths.sound(Flags.DEFAULT_CHARTER_SUSTAINADD_SOUND)); + UIState.playEditorSound(Flags.DEFAULT_CHARTER_SUSTAINADD_SOUND); changeNoteSustain(1); } inline function _note_subtractsustain(t) { - FlxG.sound.play(Paths.sound(Flags.DEFAULT_CHARTER_SUSTAINDELETE_SOUND)); + UIState.playEditorSound(Flags.DEFAULT_CHARTER_SUSTAINDELETE_SOUND); changeNoteSustain(-1); } @@ -2198,16 +2283,18 @@ class Charter extends UIState { keybind: [CONTROL, SHIFT, A], onSelect: _note_selectmeasure }, - null, - { - label: "(0) " + translate("noteTypes.default"), - keybind: [ZERO], - onSelect: (_) -> {changeNoteType(0);}, - icon: this.noteType == 0 ? 1 : 0 - } + null ]; - var noteKeys:Array = [ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE]; + var noteTypeOptions:Array = [{ + label: "(0) " + translate("noteTypes.default"), + keybind: [ZERO], + onSelect: (_) -> {changeNoteType(0);}, + icon: this.noteType == 0 ? 1 : 0 + }]; + + final noteKeys:Array>> = [[[ZERO], [NUMPADZERO]], [[ONE], [NUMPADONE]], [[TWO], [NUMPADTWO]], [[THREE], [NUMPADTHREE]], [[FOUR], [NUMPADFOUR]], [[FIVE], [NUMPADFIVE]], + [[SIX], [NUMPADSIX]], [[SEVEN], [NUMPADSEVEN]], [[EIGHT], [NUMPADEIGHT]], [[NINE], [NUMPADNINE]]]; for (i=>type in noteTypes) { var realNoteID:Int = i+1; // Default Note not stored var newChild:UIContextMenuOption = { @@ -2216,9 +2303,13 @@ class Charter extends UIState { onSelect: (_) -> {changeNoteType(realNoteID);}, icon: this.noteType == realNoteID ? 1 : 0 }; - if (realNoteID <= 9) newChild.keybind = [noteKeys[realNoteID]]; - newChilds.push(newChild); + if (realNoteID <= 9) newChild.keybinds = noteKeys[realNoteID]; + noteTypeOptions.push(newChild); } + newChilds.push({ + label: translate("note.noteTypesList"), + childs: noteTypeOptions + }); newChilds.push({ label: translate("note.editNoteTypesList"), color: 0xFF959829, icon: 4, @@ -2226,6 +2317,7 @@ class Charter extends UIState { onSelect: editNoteTypesList }); if (noteTopButton != null) noteTopButton.contextMenu = newChilds; + if (topMenu != null && topMenu[noteIndex] != null) topMenu[noteIndex].childs = newChilds; return newChilds; } @@ -2313,8 +2405,12 @@ class Charter extends UIState { } } - public inline function hitsoundsEnabled(id:Int) - return strumLines.members[id] != null && strumLines.members[id].hitsounds; + public inline function playHitsound(id:Int) { + if (strumLines.members[id] != null && strumLines.members[id].hitsoundVolume > 0 && hitsoundGlobalVolume > 0) { + hitsound.volume = hitsoundGlobalVolume * strumLines.members[id].hitsoundVolume; + hitsound.replay(); + } + } public inline function __fixSelection(selection:Selection):Selection { var newSelection:Selection = new Selection(); @@ -2405,7 +2501,7 @@ class Charter extends UIState { quantSelected: quant, noteTypeSelected: noteType, strumlinesDraggable: strumLines.draggable, - hitSounds: [for (strumLine in strumLines.members) strumLine.hitsounds], + hitSounds: [for (strumLine in strumLines.members) strumLine.hitsoundVolume > 0], mutedVocals: [for (strumLine in strumLines.members) !(strumLine.vocals.volume > 0)], waveforms: [for (strumLine in strumLines.members) strumLine.selectedWaveform] } @@ -2421,7 +2517,7 @@ class Charter extends UIState { strumLines.draggable = playtestInfo.strumlinesDraggable; for (i => strumLine in strumLines.members) - strumLine.hitsounds = playtestInfo.hitSounds[i]; + strumLine.hitsoundVolume = playtestInfo.hitSounds[i] ? 1 : 0; for (i => strumLine in strumLines.members) strumLine.vocals.volume = playtestInfo.mutedVocals[i] ? 0 : 1; for (i => strumLine in strumLines.members) @@ -2513,3 +2609,9 @@ typedef PlaytestInfo = { var mutedVocals:Array; var waveforms:Array; } + +typedef CameraChange = { + var strumLineID:Int; + var step:Float; + var endStep:Float; +} diff --git a/source/funkin/editors/charter/CharterAutoSaveUI.hx b/source/funkin/editors/charter/CharterAutoSaveUI.hx index 901cba9a7..8ed1c177e 100644 --- a/source/funkin/editors/charter/CharterAutoSaveUI.hx +++ b/source/funkin/editors/charter/CharterAutoSaveUI.hx @@ -82,7 +82,7 @@ class CharterAutoSaveUI extends UISliceSprite { icon.animation.curAnim.curFrame = 1; for (member in [this, progressBar, progressBarBack, autosavingText]) member.color = 0xFFA3EC95; - FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_AUTOSAVE_SOUND)); + UIState.playEditorSound(Flags.DEFAULT_EDITOR_AUTOSAVE_SOUND); (new FlxTimer()).start(1, (_) -> {disappearAnimation();}); }); diff --git a/source/funkin/editors/charter/CharterBackdropGroup.hx b/source/funkin/editors/charter/CharterBackdropGroup.hx index a4e35ce64..d021c66bc 100644 --- a/source/funkin/editors/charter/CharterBackdropGroup.hx +++ b/source/funkin/editors/charter/CharterBackdropGroup.hx @@ -178,6 +178,8 @@ class CharterBackdrop extends FlxTypedGroup { public var gridShader:CustomShader = new CustomShader("engine/charterGrid"); var __lastKeyCount:Int = 4; + public var cameraHighlight:CameraHighlight; + public function new() { super(); @@ -187,6 +189,10 @@ class CharterBackdrop extends FlxTypedGroup { add(gridBackDrop); gridShader.hset("segments", 4); + cameraHighlight = new CameraHighlight(this); + cameraHighlight.makeSolid(1, 1, 0xFFFFFFFF); + add(cameraHighlight); + waveformSprite = new FlxSprite().makeSolid(1, 1, 0xFF000000); waveformSprite.scale.set(160, 1); waveformSprite.updateHitbox(); @@ -247,10 +253,11 @@ class CharterBackdrop extends FlxTypedGroup { x = strumLine.x; alpha = strumLine.strumLine.visible ? 0.9 : 0.4; keyCount = strumLine.keyCount; + cameraHighlight.color = strumLine.highlightColor; } else alpha = 0.9; for (spr in [gridBackDrop, beatSeparator, topLimit, bottomLimit, - topSeparator, bottomSeparator, conductorFollowerSpr, waveformSprite]) { + topSeparator, bottomSeparator, conductorFollowerSpr, waveformSprite, cameraHighlight]) { spr.x = x; if (spr != waveformSprite) spr.alpha = alpha; spr.cameras = this.cameras; } @@ -272,6 +279,9 @@ class CharterBackdrop extends FlxTypedGroup { spr.updateHitbox(); } + cameraHighlight.scale.x = (keyCount * 40)-2; + cameraHighlight.x += 1; + waveformSprite.visible = waveformSprite.shader != null; if (waveformSprite.shader == null) return; @@ -311,16 +321,19 @@ class CharterGridSeperatorBase extends FlxSprite { private static var lastMaxMeasure:Float = -1; public static var lastConductorSprY:Float = Math.NEGATIVE_INFINITY; + public static var lastCameraZoom:Float = -1; private static var beatStepTimes:Array = []; private static var measureStepTimes:Array = []; private static var timeSignatureChangeGaps:Array = []; private function recalculateBeats() { + @:privateAccess + var currentZoom = Charter.instance.__camZoom; var conductorSprY = Charter.instance.gridBackdrops.conductorSprY; - if (conductorSprY == lastConductorSprY) return; + if (conductorSprY == lastConductorSprY && currentZoom == lastCameraZoom) return; //only update if song pos or camera zoom has changed - var zoomOffset = ((FlxG.height * (1/cameras[0].zoom)) * 0.5); + var zoomOffset = ((FlxG.height * (1/currentZoom)) * 0.5); minStep = (conductorSprY - zoomOffset)/40; maxStep = (conductorSprY + zoomOffset)/40; @@ -359,6 +372,7 @@ class CharterGridSeperatorBase extends FlxSprite { } lastConductorSprY = conductorSprY; + lastCameraZoom = currentZoom; } private inline function calculateTimeSignatureGaps() { @@ -446,6 +460,77 @@ class CharterGridSeperator extends CharterGridSeperatorBase { } } +class CameraHighlight extends FlxSprite { + + private var grid:CharterBackdrop; + private var _currentCameraMovementIndex:Int = 0; + public function new(grid:CharterBackdrop) { + this.grid = grid; + super(); + } + + override public function draw() { + if (!Options.charterShowCameraHighlights || grid.strumLine == null) return; + + @:privateAccess + var minStep = CharterGridSeperatorBase.minStep; + @:privateAccess + var maxStep = CharterGridSeperatorBase.maxStep; + @:privateAccess + var strumLineID = Charter.instance.strumLines.isDragging ? Charter.instance.strumLines.__pastStrumlines.indexOf(grid.strumLine) : Charter.instance.gridBackdrops.members.indexOf(grid); + + //first default camera change + if ((Charter.instance.cameraMovementChanges[0] == null || Charter.instance.cameraMovementChanges[0].step != 0) && strumLineID == 0) { + var endStep = Charter.instance.cameraMovementChanges[0] != null ? Charter.instance.cameraMovementChanges[0].step : Charter.instance.__endStep; + + if (endStep >= minStep) { + setupHighlight(0, endStep); super.draw(); + setupLine(endStep); super.draw(); + } + } + + //update index if gone backwards + while(_currentCameraMovementIndex > 0) { + if (Charter.instance.cameraMovementChanges[_currentCameraMovementIndex] == null || Charter.instance.cameraMovementChanges[_currentCameraMovementIndex].endStep >= minStep) { + _currentCameraMovementIndex--; + } else { + break; + } + } + + var seenFirstVisible = false; + for (i in _currentCameraMovementIndex...Charter.instance.cameraMovementChanges.length) { + var change = Charter.instance.cameraMovementChanges[i]; + if (change.endStep >= minStep) { + if (!seenFirstVisible) { + _currentCameraMovementIndex = i; //remember the index for the next frame, so we dont need to loop through everything every time + seenFirstVisible = true; + } + if (change.strumLineID == strumLineID) { + setupHighlight(change.step, change.endStep); super.draw(); + setupLine(change.step); super.draw(); + setupLine(change.endStep); super.draw(); + } + } + if (change.endStep > maxStep) break; + } + } + + private inline function setupHighlight(step:Float, endStep:Float) { + y = step * 40; + alpha = 0.15; + scale.y = (endStep -step) * 40; + updateHitbox(); + } + + private inline function setupLine(step:Float) { + y = (step * 40)-1; + alpha = 0.8; + scale.y = 2; + updateHitbox(); + } +} + class EventBackdrop extends FlxBackdrop { public var eventBeatSeparator:CharterEventGridSeperator; diff --git a/source/funkin/editors/charter/CharterNote.hx b/source/funkin/editors/charter/CharterNote.hx index 2376392f9..8bece2b4a 100644 --- a/source/funkin/editors/charter/CharterNote.hx +++ b/source/funkin/editors/charter/CharterNote.hx @@ -161,8 +161,9 @@ class CharterNote extends UISprite implements ICharterSelectable { } if (__passed != (__passed = step < Conductor.curStepFloat + (Options.songOffsetAffectEditors ? (Conductor.songOffset / Conductor.stepCrochet) : 0))) { - if (__passed && FlxG.sound.music.playing && Charter.instance.hitsoundsEnabled(strumLineID)) - Charter.instance.hitsound.replay(); + if (__passed && FlxG.sound.music.playing) { + Charter.instance.playHitsound(strumLineID); + } } if (strumLine != null) { diff --git a/source/funkin/editors/charter/CharterPreviewStrumLine.hx b/source/funkin/editors/charter/CharterPreviewStrumLine.hx index 91aa5ccad..d2d26edd8 100644 --- a/source/funkin/editors/charter/CharterPreviewStrumLine.hx +++ b/source/funkin/editors/charter/CharterPreviewStrumLine.hx @@ -21,7 +21,7 @@ class CharterPreviewStrumLine extends FlxTypedGroup for (i in 0...keyCount){ var strum = new FlxSprite(); strum.frames = Paths.getFrames("game/notes/default"); - strum.setGraphicSize(Std.int((strum.width * 0.7) * scale)); + strum.setGraphicSize(Std.int((strum.width * Flags.DEFAULT_NOTE_SCALE) * scale)); strum.updateHitbox(); var animPrefix = strumAnimPrefix[i % 4]; @@ -33,7 +33,7 @@ class CharterPreviewStrumLine extends FlxTypedGroup note = new FlxSprite(); note.frames = Paths.getFrames("game/notes/default"); - note.setGraphicSize(Std.int((note.width * 0.7) * scale)); + note.setGraphicSize(Std.int((note.width * Flags.DEFAULT_NOTE_SCALE) * scale)); note.updateHitbox(); note.animation.addByPrefix('purple', 'purple0'); note.animation.play('purple'); @@ -41,7 +41,7 @@ class CharterPreviewStrumLine extends FlxTypedGroup add(note); } - var noteTime:Float = FlxG.height; + var noteTime:Float = FlxG.initialHeight; var scroll:Float = 1.0; public function updatePos(x:Float, y:Float, scale:Float, spacing:Float, keyCount:Int, scrollSpeed:Float){ @@ -53,7 +53,7 @@ class CharterPreviewStrumLine extends FlxTypedGroup strum.x = CoolUtil.fpsLerp(strum.x, x + (Note.swagWidth * scale * spacing * i), 0.2); strum.y = CoolUtil.fpsLerp(strum.y, y + (Note.swagWidth*0.5) - (Note.swagWidth * scale * 0.5), 0.2); - strum.scale.x = strum.scale.y = CoolUtil.fpsLerp(strum.scale.x, 0.7 * scale, 0.2); + strum.scale.x = strum.scale.y = CoolUtil.fpsLerp(strum.scale.x, Flags.DEFAULT_NOTE_SCALE * scale, 0.2); strum.updateHitbox(); } @@ -61,7 +61,7 @@ class CharterPreviewStrumLine extends FlxTypedGroup scroll = CoolUtil.fpsLerp(scroll, scrollSpeed, 0.2); noteTime -= FlxG.elapsed * scroll * 1000 * 0.45; if (noteTime <= 0.0) - noteTime = FlxG.height; + noteTime = FlxG.initialHeight; note.x = members[0].x; note.y = members[0].y + noteTime; diff --git a/source/funkin/editors/charter/CharterStrumLineGroup.hx b/source/funkin/editors/charter/CharterStrumLineGroup.hx index 5b1633a65..2a0f42b61 100644 --- a/source/funkin/editors/charter/CharterStrumLineGroup.hx +++ b/source/funkin/editors/charter/CharterStrumLineGroup.hx @@ -105,6 +105,7 @@ class CharterStrumLineGroup extends FlxTypedGroup { draggingObj = null; fixEvents(); refreshStrumlineIDs(); + Charter.instance.updateCameraChanges(); } public inline function fixEvents() { diff --git a/source/funkin/editors/charter/CharterStrumline.hx b/source/funkin/editors/charter/CharterStrumline.hx index aea5b6790..40f2bf369 100644 --- a/source/funkin/editors/charter/CharterStrumline.hx +++ b/source/funkin/editors/charter/CharterStrumline.hx @@ -1,5 +1,7 @@ package funkin.editors.charter; +import funkin.editors.ui.UIContextMenu.UIContextMenuOption; +import flixel.util.FlxColor; import flixel.group.FlxSpriteGroup; import flixel.sound.FlxSound; import funkin.backend.chart.ChartData.ChartStrumLine; @@ -10,11 +12,11 @@ import funkin.game.HealthIcon; class CharterStrumline extends UISprite { public var strumLine:ChartStrumLine; - public var hitsounds:Bool = true; public var draggingSprite:UISprite; public var healthIcons:FlxSpriteGroup; public var button:CharterStrumlineOptions; + public var highlightColor:FlxColor; public var draggable:Bool = false; public var dragging:Bool = false; @@ -22,6 +24,9 @@ class CharterStrumline extends UISprite { public var curMenu:UIContextMenu = null; public var vocals:FlxSound; + public var hasVocals:Bool = false; + public var vocalsVolume:Float = 1; + public var hitsoundVolume:Float = 1; public var keyCount:Int = 4; public var startingID(get, null):Int; @@ -141,11 +146,22 @@ class CharterStrumline extends UISprite { if (asset != null) { vocals.reset(); vocals.loadEmbedded(asset); + hasVocals = true; } else { vocals.destroy(); + hasVocals = false; } vocals.group = FlxG.sound.defaultMusicGroup; + + highlightColor = 0xFFFFFFFF; + if (icons[0] != null) { + var characterXML = Character.getXMLFromCharName(icons[0]); + if (characterXML != null && characterXML.x.exists("color")) highlightColor = FlxColor.fromString(characterXML.x.get("color")); + + //make darker colors more visible for the highlight + highlightColor.brightness = Math.max(highlightColor.brightness, 0.65); + } } } @@ -168,17 +184,17 @@ class CharterStrumlineOptions extends UITopMenuButton { contextMenu = [ { label: TU.translate("charter.strumLine.hitsounds"), - onSelect: function(_) { - strLine.hitsounds = !strLine.hitsounds; + slider: { + min: 0, + max: 1, + value: strLine.hitsoundVolume, + onChange: function(t) { + strLine.hitsoundVolume = t.slider.value; + t.icon = t.slider.value > 0.5 ? 7 : (t.slider.value > 0 ? 8 : 9); + } }, - icon: strLine.hitsounds ? 1 : 0 - }, - { - label: TU.translate("charter.strumLine.muteVocals"), - onSelect: function(_) { - strLine.vocals.volume = strLine.vocals.volume > 0 ? 0 : 1; - }, - icon: strLine.vocals.volume > 0 ? 0 : 1 + onIconClick: Charter.instance._slider_mutetoggle, + icon: 7 }, null, { @@ -199,20 +215,44 @@ class CharterStrumlineOptions extends UITopMenuButton { } ]; - contextMenu.insert(0, { - label: TU.translate("charter.strumLine.noWaveform"), - onSelect: function(_) {strLine.selectedWaveform = -1;}, - icon: strLine.selectedWaveform == -1 ? 1 : 0 - }); + if (strLine.hasVocals) { + contextMenu.insert(1, { + label: TU.translate("charter.strumLine.vocals"), + slider: { + min: 0, + max: 1, + value: strLine.vocalsVolume, + onChange: function(t) { + strLine.vocalsVolume = t.slider.value; + strLine.vocals.volume = Charter.instance.vocals.volume * strLine.vocalsVolume; + t.icon = t.slider.value > 0.5 ? 7 : (t.slider.value > 0 ? 8 : 9); + } + }, + onIconClick: Charter.instance._slider_mutetoggle, + icon: 7 + }); + } + + var waveformOptions:Array = [ + { + label: TU.translate("charter.strumLine.noWaveform"), + onSelect: function(_) {strLine.selectedWaveform = -1;}, + icon: strLine.selectedWaveform == -1 ? 1 : 0 + } + ]; for (i => name in Charter.waveformHandler.waveformList) - contextMenu.insert(1+i, { + waveformOptions.push({ label: name, onSelect: function(_) {strLine.selectedWaveform = i;}, icon: strLine.selectedWaveform == i ? 6 : 5 }); - contextMenu.insert(1+Charter.waveformHandler.waveformList.length, null); + contextMenu.insert(0, { + label: TU.translate("charter.strumLine.waveforms"), + childs: waveformOptions + }); + contextMenu.insert(1, null); var cam = Charter.instance.charterCamera; var point = CoolUtil.worldToScreenPosition(this, cam); diff --git a/source/funkin/editors/charter/CharterStrumlineScreen.hx b/source/funkin/editors/charter/CharterStrumlineScreen.hx index a6beb4ad7..501317c64 100644 --- a/source/funkin/editors/charter/CharterStrumlineScreen.hx +++ b/source/funkin/editors/charter/CharterStrumlineScreen.hx @@ -33,6 +33,8 @@ class CharterStrumlineScreen extends UISubstateWindow { public var strumLineCam:HudCamera; public var previewStrumLine:CharterPreviewStrumLine; + public var previewBorder:Array = []; + private final borderGap:Int = 5; private var onSave:ChartStrumLine -> Void = null; @@ -176,20 +178,30 @@ class CharterStrumlineScreen extends UISubstateWindow { addLabelOn(vocalsSuffixDropDown, TU.translate("charterStrumLine.vocalSuffix")); keyCountStepper = new UINumericStepper(stagePositionDropdown.x, vocalsSuffixDropDown.y, strumLine.keyCount != null ? strumLine.keyCount : 4, 1, 0, 1, 1000, 84); - // if (Flags.CHARTER_ADVANCED_SETTINGS) { - add(keyCountStepper); - addLabelOn(keyCountStepper, TU.translate("charterStrumLine.keyCount")); - // } + add(keyCountStepper); + addLabelOn(keyCountStepper, TU.translate("charterStrumLine.keyCount")); strumLineCam = new HudCamera(); strumLineCam.downscroll = Options.downscroll; strumLineCam.bgColor = 0; strumLineCam.alpha = 0; FlxG.cameras.add(strumLineCam, false); + updateStrumlineCam(FlxG.width, FlxG.height); + previewStrumLine = new CharterPreviewStrumLine(0, 0, 0, 1, 4, 0); previewStrumLine.camera = strumLineCam; add(previewStrumLine); FlxTween.tween(strumLineCam, {alpha: 1}, 0.25, {ease: FlxEase.cubeOut}); + + //preview border + previewBorder.push(new FlxSprite(-borderGap, -borderGap).makeSolid(FlxG.initialWidth + (borderGap*2), borderGap, 0x88FFFFFF)); + previewBorder.push(new FlxSprite(-borderGap, 0).makeSolid(borderGap, FlxG.initialHeight, 0x88FFFFFF)); + previewBorder.push(new FlxSprite(FlxG.initialWidth, 0).makeSolid(borderGap, FlxG.initialHeight, 0x88FFFFFF)); + previewBorder.push(new FlxSprite(-borderGap, FlxG.initialHeight).makeSolid(FlxG.initialWidth + (borderGap*2), borderGap, 0x88FFFFFF)); + for (border in previewBorder) { + border.camera = strumLineCam; + add(border); + } } function saveStrumline() { @@ -222,7 +234,7 @@ class CharterStrumlineScreen extends UISubstateWindow { previewStrumLine.visible = visibleCheckbox.checked; - var xOffset:Float = StrumLine.calculateStartingXPos(hudXStepper.value, hudScaleStepper.value, hudSpacingStepper.value, Std.int(keyCountStepper.value)); + var xOffset:Float = StrumLine.calculateStartingXPosFromInitialWidth(hudXStepper.value, hudScaleStepper.value, hudSpacingStepper.value, Std.int(keyCountStepper.value)); previewStrumLine.updatePos(xOffset, hudYStepper.value, hudScaleStepper.value, hudSpacingStepper.value, Std.int(keyCountStepper.value), scrollSpeed); super.update(elapsed); @@ -233,6 +245,26 @@ class CharterStrumlineScreen extends UISubstateWindow { FlxTween.cancelTweensOf(strumLineCam); FlxG.cameras.remove(strumLineCam); } + + public override function onResize(width:Int, height:Int) { + super.onResize(width, height); + if (!UIState.resolutionAware) return; + + if ((width < FlxG.initialWidth || height < FlxG.initialHeight) && !Options.bypassEditorsResize) { + width = FlxG.initialWidth; height = FlxG.initialHeight; + } + + updateStrumlineCam(width, height); + } + + private function updateStrumlineCam(windowWidth:Int, windowHeight:Int) { + strumLineCam.width = FlxG.initialWidth + (borderGap*2); + strumLineCam.height = FlxG.initialHeight + (borderGap*2); + strumLineCam.x = (windowWidth/2) - (strumLineCam.width/2); + strumLineCam.y = (windowHeight/2) - (strumLineCam.height/2); + strumLineCam.scroll.x = -borderGap; + strumLineCam.scroll.y = -borderGap; + } } class CharacterButton extends UIButton { diff --git a/source/funkin/editors/stage/StageEditor.hx b/source/funkin/editors/stage/StageEditor.hx index aa637834c..0c91a9d39 100644 --- a/source/funkin/editors/stage/StageEditor.hx +++ b/source/funkin/editors/stage/StageEditor.hx @@ -510,7 +510,7 @@ class StageEditor extends UIState { function _file_save(_) { #if sys - FlxG.sound.play(Paths.sound('editors/save')); + UIState.playEditorSound(Flags.DEFAULT_EDITOR_SAVE_SOUND); CoolUtil.safeSaveFile( '${Paths.getAssetsRoot()}/data/stages/${__stage}.xml', buildStage() @@ -522,7 +522,7 @@ class StageEditor extends UIState { } function _file_saveas(_) { - FlxG.sound.play(Paths.sound('editors/save')); + UIState.playEditorSound(Flags.DEFAULT_EDITOR_SAVE_SOUND); openSubState(new SaveSubstate(buildStage(), { defaultSaveFile: '${__stage}.xml' })); @@ -734,7 +734,7 @@ class StageEditor extends UIState { } function _edit_undo(_) { - FlxG.sound.play(Flags.DEFAULT_EDITOR_UNDO_SOUND); + UIState.playEditorSound(Flags.DEFAULT_EDITOR_UNDO_SOUND); var undo = undos.undo(); switch(undo) { case null: @@ -760,7 +760,7 @@ class StageEditor extends UIState { } function _edit_redo(_) { - FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_REDO_SOUND)); + UIState.playEditorSound(Flags.DEFAULT_EDITOR_REDO_SOUND); var redo = undos.redo(); switch(redo) { case null: diff --git a/source/funkin/editors/ui/UIButton.hx b/source/funkin/editors/ui/UIButton.hx index 2064e3d7c..017c53855 100644 --- a/source/funkin/editors/ui/UIButton.hx +++ b/source/funkin/editors/ui/UIButton.hx @@ -30,7 +30,7 @@ class UIButton extends UISliceSprite { super.onHovered(); if (FlxG.mouse.justPressed) { hasBeenPressed = true; - FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_BUTTONCLICK_SOUND)); + UIState.playEditorSound(Flags.DEFAULT_EDITOR_BUTTONCLICK_SOUND); } if (FlxG.mouse.justReleased && callback != null && shouldPress && hasBeenPressed) { callback(); diff --git a/source/funkin/editors/ui/UIContextMenu.hx b/source/funkin/editors/ui/UIContextMenu.hx index dcd8d0af3..14b3db0bc 100644 --- a/source/funkin/editors/ui/UIContextMenu.hx +++ b/source/funkin/editors/ui/UIContextMenu.hx @@ -16,6 +16,12 @@ class UIContextMenu extends MusicBeatSubstate { public var contextMenuOptions:Array = []; public var separators:Array = []; + public var childContextMenu:UIContextMenu = null; + public var parentContextMenu:UIContextMenu = null; + private var childContextMenuOptionIndex:Int = -1; + @:allow(funkin.editors.ui.UIContextMenu) + private var lastHoveredOptionIndex:Int = -1; + var scroll:Float = 0.0; var flipped:Bool = false; @@ -96,6 +102,10 @@ class UIContextMenu extends MusicBeatSubstate { for(o in separators) o.x -= bg.bWidth; } + + for(o in contextMenuOptions) { + o.postCreate(); + } } public function select(option:UIContextMenuOption) { @@ -105,12 +115,12 @@ class UIContextMenu extends MusicBeatSubstate { if (callback != null) callback(this, index, option); if (option.closeOnSelect == null ? true : option.closeOnSelect) - close(); + closeWithParents(); } public override function update(elapsed:Float) { - if (__oobDeletion && FlxG.mouse.justPressed && !bg.hoveredByChild) - close(); + if (__oobDeletion && FlxG.mouse.justPressed && !bg.hoveredByChild && !hoveringAnyChildren()) + closeWithParents(); __oobDeletion = true; @@ -121,6 +131,14 @@ class UIContextMenu extends MusicBeatSubstate { contextCam.scroll.y = CoolUtil.fpsLerp(contextCam.scroll.y, scroll, 0.5); contextCam.alpha = CoolUtil.fpsLerp(contextCam.alpha, 1, 0.25); + + if (parentContextMenu != null) { + if (hoveringAnyParents() && parentContextMenu.lastHoveredOptionIndex != parentContextMenu.childContextMenuOptionIndex) { + closeWithChildren(); + parentContextMenu.childContextMenuOptionIndex = -1; + } + } + } public override function destroy() { @@ -129,6 +147,56 @@ class UIContextMenu extends MusicBeatSubstate { if (UIState.state.curContextMenu == this) UIState.state.curContextMenu = null; } + + public function openChildContextMenu(optionSpr:UIContextMenuOptionSpr) { + var index = contextMenuOptions.indexOf(optionSpr); + if (index != childContextMenuOptionIndex) { + childContextMenuOptionIndex = index; + var child = new UIContextMenu(optionSpr.option.childs, null, optionSpr.x + optionSpr.bWidth + 4, optionSpr.y - 4); + persistentDraw = true; + persistentUpdate = true; + child.parentContextMenu = this; + childContextMenu = child; + openSubState(child); + } + } + public function closeWithParents() { + close(); + if (parentContextMenu != null) { + parentContextMenu.closeWithParents(); + } + } + public function closeWithChildren() { + if (childContextMenu != null) { + childContextMenu.closeWithChildren(); + } + close(); + } + public function hoveringAnyParents() { + if (parentContextMenu != null) { + return parentContextMenu.bg.hoveredByChild || parentContextMenu.hoveringAnyParents(); + } + return false; + } + public function hoveringAnyChildren() { + if (childContextMenu != null) { + return childContextMenu.bg.hoveredByChild || childContextMenu.hoveringAnyChildren(); + } + return false; + } +} + +typedef UIContextMenuSliderOptionData = { + var min:Float; + var max:Float; + var value:Float; + var ?onChange:UIContextMenuOption->Void; + //default = 120, ignored if sameLine = false + var ?width:Float; + //disables stepper and text if false, default = false + var ?showValues:Bool; + //if true, the slider will be on the same line as the label text, otherwise it will be on the next line below the label + var ?sameLine:Bool; } typedef UIContextMenuCallback = UIContextMenu->Int->UIContextMenuOption->Void; @@ -144,13 +212,24 @@ typedef UIContextMenuOption = { var ?button:UIContextMenuOptionSpr; var ?onCreate:UIContextMenuOptionSpr->Void; var ?childs:Array; + var ?slider:UIContextMenuSliderOptionData; + var ?onIconClick:UIContextMenuOption->Void; +} + +enum abstract UIContextMenuOptionType(Int) from Int { + var DEFAULT = 0; + var SUBMENU = 1; + var SLIDER = 2; } class UIContextMenuOptionSpr extends UISliceSprite { public var label:UIText; public var labelKeybind:UIText; - public var icon:FlxSprite; + public var icon:UIContextMenuOptionIcon; public var option:UIContextMenuOption; + public var optionType:UIContextMenuOptionType = DEFAULT; + + public var slider:UISlider = null; var parent:UIContextMenu; @@ -160,40 +239,96 @@ class UIContextMenuOptionSpr extends UISliceSprite { this.parent = parent; this.color = option.color; - if (option.icon != null && option.icon > 0) { - icon = new FlxSprite(0, 0).loadGraphic(Paths.image('editors/ui/context-icons'), true, 20, 20); - icon.animation.add('icon', [option.icon-1], 0, true); - icon.animation.play('icon'); - } + var w:Int = label.frameWidth + 22; + var h:Int = label.frameHeight; - if (option.keybinds == null) { - if (option.keybind != null) { - option.keybinds = [option.keybind]; - } - } + if (option.childs != null) optionType = SUBMENU; + if (option.slider != null) optionType = SLIDER; + + switch(optionType) { + + case SUBMENU: + labelKeybind = new UIText(label.x + label.frameWidth + 10, 2, 0, ">"); + case SLIDER: + labelKeybind = new UIText(label.x + label.frameWidth + 10, 2, 0, ""); + //slider needs to be created after so that it can match the menu width (when not on the same line) + if (option.slider.sameLine != null && option.slider.sameLine) { + var sliderWidth = option.slider.width != null ? Std.int(option.slider.width) : 120; + w += 120 + slider.barWidth; + } else { + h *= 2; + } + + default: + if (option.keybinds == null) { + if (option.keybind != null) { + option.keybinds = [option.keybind]; + } + } - if (option.keybinds != null || option.keybindText != null) { - var text = if(option.keybindText == null) { - var textKeys:Array = []; - for (o in option.keybinds[0]) { - if (Std.int(o) > 0) { - textKeys.push(o.toUIString()); + if (option.keybinds != null || option.keybindText != null) { + var text = if(option.keybindText == null) { + var textKeys:Array = []; + for (o in option.keybinds[0]) { + if (Std.int(o) > 0) { + textKeys.push(o.toUIString()); + } + } + textKeys.join("+"); + } else { + option.keybindText; } + labelKeybind = new UIText(label.x + label.frameWidth + 10, 2, 0, text); + labelKeybind.alpha = 0.75; + + w = Std.int(labelKeybind.x + labelKeybind.frameWidth + 10); } - textKeys.join("+"); - } else { - option.keybindText; - } - labelKeybind = new UIText(label.x + label.frameWidth + 10, 2, 0, text); - labelKeybind.alpha = 0.75; } - super(x, y, labelKeybind != null ? Std.int(labelKeybind.x + labelKeybind.frameWidth + 10) : (label.frameWidth + 22), label.frameHeight, 'editors/ui/menu-item'); + super(x, y, w, h, 'editors/ui/menu-item'); + members.push(label); - if (icon != null) - members.push(icon); + updateIcon(); + if (labelKeybind != null) - members.push(labelKeybind); + members.push(labelKeybind); + } + + //Called after all options are created and the context menu width/height is final + public function postCreate() { + switch(optionType) { + case SLIDER: + + var sliderWidth = bWidth-50; + if (option.slider.sameLine != null && option.slider.sameLine) { + option.slider.width != null ? Std.int(option.slider.width) : 120; + } + + slider = new UISlider(0, 0, sliderWidth, option.slider.value, + [{start: option.slider.min, end: option.slider.max, size: option.slider.max-option.slider.min}], false); + + slider.onChange = function(v) { + option.slider.value = v; + if (option.slider.onChange != null) option.slider.onChange(option); + updateIcon(); //check if icon has changed + @:privateAccess + labelKeybind.text = '${CoolUtil.quantize(slider.__barProgress * 100, 1)}%'; + }; + slider.value = option.slider.value; + + if (option.slider.showValues == null || !option.slider.showValues) { + slider.startText.visible = false; + slider.endText.visible = false; + slider.valueStepper.visible = false; + slider.valueStepper.selectable = false; + } + + members.push(slider); + case SUBMENU: + + default: + + } } public override function draw() { @@ -205,12 +340,71 @@ class UIContextMenuOptionSpr extends UISliceSprite { icon.follow(this, 0, 0); if (labelKeybind != null) labelKeybind.follow(this, bWidth - 10 - labelKeybind.frameWidth, 2); + if (slider != null) { + if (option.slider.sameLine != null && option.slider.sameLine) { + slider.follow(this, bWidth - 18 - slider.barWidth - (slider.endText.visible ? slider.endText.width : 0), 5); + } else { + slider.follow(this, 20, 5 + label.frameHeight); + } + } super.draw(); } public override function onHovered() { super.onHovered(); - if (FlxG.mouse.justReleased) - parent.select(option); + + parent.lastHoveredOptionIndex = parent.contextMenuOptions.indexOf(this); + + switch(optionType) { + case SUBMENU: + parent.openChildContextMenu(this); + case SLIDER: + + default: + if (FlxG.mouse.justReleased) + parent.select(option); + } + } + + public function updateIcon() { + var currentIcon = option.icon != null ? option.icon : 0; + + if (icon == null && currentIcon > 0) { + members.push(icon = new UIContextMenuOptionIcon(option)); + } + if (icon != null) { + icon.updateIconState(currentIcon); + } + } +} + +class UIContextMenuOptionIcon extends UISprite { + private var option:UIContextMenuOption; + private var _lastState:Int = 0; + override public function new(option:UIContextMenuOption) { + super(); + this.option = option; + loadGraphic(Paths.image('editors/ui/context-icons'), true, 20, 20); + selectable = option.onIconClick != null; + cursor = option.onIconClick != null ? CLICK : ARROW; + } + + public function updateIconState(state:Int) { + if (_lastState == state) return; + _lastState = state; + + visible = state > 0; + if (state > 0) { + animation.add('icon', [state-1], 0, true); + animation.play('icon'); + } + } + + public override function onHovered() { + super.onHovered(); + + if (FlxG.mouse.justReleased && option.onIconClick != null) { + option.onIconClick(option); + } } } \ No newline at end of file diff --git a/source/funkin/editors/ui/UIDropDown.hx b/source/funkin/editors/ui/UIDropDown.hx index d90322494..1603f43a7 100644 --- a/source/funkin/editors/ui/UIDropDown.hx +++ b/source/funkin/editors/ui/UIDropDown.hx @@ -87,7 +87,7 @@ class UIDropDown extends UISliceSprite { } public function openContextMenu() { - FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_DROPDOWNAPPEAR_SOUND)); + UIState.playEditorSound(Flags.DEFAULT_EDITOR_DROPDOWNAPPEAR_SOUND); var screenPos = getScreenPosition(null, __lastDrawCameras[0] == null ? FlxG.camera : __lastDrawCameras[0]); curMenu = UIState.state.openContextMenu([ for(k=>o in items) { diff --git a/source/funkin/editors/ui/UIState.hx b/source/funkin/editors/ui/UIState.hx index 97db4580a..f7db06b15 100644 --- a/source/funkin/editors/ui/UIState.hx +++ b/source/funkin/editors/ui/UIState.hx @@ -106,7 +106,7 @@ class UIState extends MusicBeatState { } if (FlxG.mouse.justPressed) { - FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_CLICK_SOUND)); + playEditorSound(Flags.DEFAULT_EDITOR_CLICK_SOUND); } if (FlxG.mouse.justReleased) @@ -147,7 +147,7 @@ class UIState extends MusicBeatState { } public function closeCurrentContextMenu() { - FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_WINDOWCLOSE_SOUND)); + playEditorSound(Flags.DEFAULT_EDITOR_WINDOWCLOSE_SOUND); if(curContextMenu != null) { curContextMenu.close(); curContextMenu = null; @@ -155,7 +155,7 @@ class UIState extends MusicBeatState { } public function openContextMenu(options:Array, ?callback:UIContextMenuCallback, ?x:Float, ?y:Float, ?w:Int) { - FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_WINDOWAPPEAR_SOUND)); + playEditorSound(Flags.DEFAULT_EDITOR_WINDOWAPPEAR_SOUND); var state = FlxG.state; while(state.subState != null && !(state._requestSubStateReset && state._requestedSubState == null)) state = state.subState; @@ -191,4 +191,9 @@ class UIState extends MusicBeatState { resolutionAware = true; FlxG.scaleMode = uiScaleMode; } + + public static function playEditorSound(path:String) { + if (!Options.editorSFX) return; + FlxG.sound.play(Paths.sound(path)); + } } \ No newline at end of file diff --git a/source/funkin/editors/ui/UISubstateWindow.hx b/source/funkin/editors/ui/UISubstateWindow.hx index 61c9f5cae..e7e990760 100644 --- a/source/funkin/editors/ui/UISubstateWindow.hx +++ b/source/funkin/editors/ui/UISubstateWindow.hx @@ -86,7 +86,7 @@ class UISubstateWindow extends MusicBeatSubstate { } public override function destroy() { - FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_WINDOWCLOSE_SOUND)); + UIState.playEditorSound(Flags.DEFAULT_EDITOR_WINDOWCLOSE_SOUND); super.destroy(); for(e in camShaders) e.removeShader(blurShader); diff --git a/source/funkin/editors/ui/UITextBox.hx b/source/funkin/editors/ui/UITextBox.hx index 371297f29..da4341b42 100644 --- a/source/funkin/editors/ui/UITextBox.hx +++ b/source/funkin/editors/ui/UITextBox.hx @@ -110,7 +110,7 @@ class UITextBox extends UISliceSprite implements IUIFocusable { case RIGHT: changeSelection(1); case BACKSPACE: - FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_TEXTREMOVE_SOUND)); + UIState.playEditorSound(Flags.DEFAULT_EDITOR_TEXTREMOVE_SOUND); if (position > 0) { label.text = label.text.substr(0, position-1) + label.text.substr(position); changeSelection(-1); @@ -120,7 +120,7 @@ class UITextBox extends UISliceSprite implements IUIFocusable { case END: position = label.text.length; case V: - FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_TEXTTYPE_SOUND)); + UIState.playEditorSound(Flags.DEFAULT_EDITOR_TEXTTYPE_SOUND); // Hey lj here, fixed copying because before we checked if the modifier was left or right ctrl // but somehow it gave a int outside of the KeyModifier's range :sob: // apparently there is a boolean that just checks for you. yw :D @@ -131,14 +131,14 @@ class UITextBox extends UISliceSprite implements IUIFocusable { var data:String = Clipboard.generalClipboard.getData(TEXT_FORMAT); if (data != null) onTextInput(data); case C: - FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_TEXTTYPE_SOUND)); + UIState.playEditorSound(Flags.DEFAULT_EDITOR_TEXTTYPE_SOUND); // if we are not holding ctrl, ignore if (!modifier.ctrlKey) return; // copying Clipboard.generalClipboard.setData(TEXT_FORMAT, label.text); default: - FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_TEXTTYPE_SOUND)); + UIState.playEditorSound(Flags.DEFAULT_EDITOR_TEXTTYPE_SOUND); } } diff --git a/source/funkin/editors/ui/UIWindow.hx b/source/funkin/editors/ui/UIWindow.hx index e7b994e94..92f885e44 100644 --- a/source/funkin/editors/ui/UIWindow.hx +++ b/source/funkin/editors/ui/UIWindow.hx @@ -15,7 +15,7 @@ class UIWindow extends UISliceSprite { content = new FlxTypedGroup(); members.push(content); - FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_WINDOWAPPEAR_SOUND)); + UIState.playEditorSound(Flags.DEFAULT_EDITOR_WINDOWAPPEAR_SOUND); } public override function update(elapsed:Float) { diff --git a/source/funkin/game/StrumLine.hx b/source/funkin/game/StrumLine.hx index ae5303b8c..f814cdec3 100644 --- a/source/funkin/game/StrumLine.hx +++ b/source/funkin/game/StrumLine.hx @@ -420,6 +420,9 @@ class StrumLine extends FlxTypedGroup { public static inline function calculateStartingXPos(hudXRatio:Float, scale:Float, spacing:Float, keyCount:Int) { return (FlxG.width * hudXRatio) - ((Note.swagWidth * scale * ((keyCount/2)-0.5) * spacing) + Note.swagWidth * 0.5 * scale); } + public static inline function calculateStartingXPosFromInitialWidth(hudXRatio:Float, scale:Float, spacing:Float, keyCount:Int) { + return (FlxG.initialWidth * hudXRatio) - ((Note.swagWidth * scale * ((keyCount/2)-0.5) * spacing) + Note.swagWidth * 0.5 * scale); + } /** * SETTERS & GETTERS diff --git a/source/funkin/options/Options.hx b/source/funkin/options/Options.hx index ac5c87acd..38993d328 100644 --- a/source/funkin/options/Options.hx +++ b/source/funkin/options/Options.hx @@ -84,6 +84,7 @@ class Options public static var charterMetronomeEnabled:Bool = false; public static var charterShowSections:Bool = true; public static var charterShowBeats:Bool = true; + public static var charterShowCameraHighlights:Bool = true; public static var charterEnablePlaytestScripts:Bool = true; public static var charterRainbowWaveforms:Bool = false; public static var charterLowDetailWaveforms:Bool = false; diff --git a/source/funkin/options/categories/GameplayOptions.hx b/source/funkin/options/categories/GameplayOptions.hx index 7d66f3511..868c3c4c9 100644 --- a/source/funkin/options/categories/GameplayOptions.hx +++ b/source/funkin/options/categories/GameplayOptions.hx @@ -4,7 +4,7 @@ import flixel.util.FlxTimer; import funkin.backend.system.Conductor; class GameplayOptions extends TreeMenuScreen { - var __metronome = FlxG.sound.load(Paths.sound('editors/charter/metronome')); + var __metronome = FlxG.sound.load(Paths.sound(Flags.DEFAULT_CHARTER_METRONOME_SOUND)); var offsetSetting:NumOption; public function new() {