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() {