From 3e13bf50c68d0ef6cce5aef726883beeebb6d1ef Mon Sep 17 00:00:00 2001 From: TheZoroForce240 <86524550+TheZoroForce240@users.noreply.github.com> Date: Sat, 1 Feb 2025 21:13:46 +0000 Subject: [PATCH] Continuous bpm change event, lots of other reworks and fixes --- .../event-icons/components/arrow-down.png | Bin 0 -> 6292 bytes .../event-icons/components/arrow-down.xml | 6 + .../event-icons/components/warning.png | Bin 0 -> 7951 bytes source/funkin/backend/chart/EventsData.hx | 9 +- source/funkin/backend/system/Conductor.hx | 113 +++++++++++--- source/funkin/editors/charter/Charter.hx | 39 +++-- .../editors/charter/CharterBackdropGroup.hx | 6 +- source/funkin/editors/charter/CharterEvent.hx | 138 ++++++++++++++++-- .../editors/charter/CharterEventScreen.hx | 4 +- .../charter/CharterEventTypeSelection.hx | 21 ++- 10 files changed, 277 insertions(+), 59 deletions(-) create mode 100644 assets/images/editors/charter/event-icons/components/arrow-down.png create mode 100644 assets/images/editors/charter/event-icons/components/arrow-down.xml create mode 100644 assets/images/editors/charter/event-icons/components/warning.png diff --git a/assets/images/editors/charter/event-icons/components/arrow-down.png b/assets/images/editors/charter/event-icons/components/arrow-down.png new file mode 100644 index 0000000000000000000000000000000000000000..b5e3ce87f66d9765527b40e8750cf14da1c5bcaf GIT binary patch literal 6292 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 R^$O$-GuAiPE86cC{(m8k8dLxP literal 0 HcmV?d00001 diff --git a/assets/images/editors/charter/event-icons/components/arrow-down.xml b/assets/images/editors/charter/event-icons/components/arrow-down.xml new file mode 100644 index 00000000..1e58cc38 --- /dev/null +++ b/assets/images/editors/charter/event-icons/components/arrow-down.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/assets/images/editors/charter/event-icons/components/warning.png b/assets/images/editors/charter/event-icons/components/warning.png new file mode 100644 index 0000000000000000000000000000000000000000..3558e0a35fd630d2df4465e4af4d6460c861001f GIT binary patch literal 7951 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 literal 0 HcmV?d00001 diff --git a/source/funkin/backend/chart/EventsData.hx b/source/funkin/backend/chart/EventsData.hx index d1cac615..e9f5e79b 100644 --- a/source/funkin/backend/chart/EventsData.hx +++ b/source/funkin/backend/chart/EventsData.hx @@ -10,7 +10,7 @@ import funkin.backend.assets.Paths; using StringTools; class EventsData { - public static var defaultEventsList:Array = ["HScript Call", "Camera Movement", "Add Camera Zoom", "Camera Modulo Change", "Camera Flash", "BPM Change", "Scroll Speed Change", "Alt Animation Toggle", "Play Animation", "Time Signature Change"]; + public static var defaultEventsList:Array = ["HScript Call", "Camera Movement", "Add Camera Zoom", "Camera Modulo Change", "Camera Flash", "BPM Change", "Continuous BPM Change", "Time Signature Change", "Scroll Speed Change", "Alt Animation Toggle", "Play Animation"]; public static var defaultEventsParams:Map> = [ "HScript Call" => [ {name: "Function Name", type: TString, defValue: "myFunc"}, @@ -31,7 +31,9 @@ class EventsData { {name: "Time (Steps)", type: TFloat(0.25, 9999, 0.25, 2), defValue: 4}, {name: "Camera", type: TDropDown(['camGame', 'camHUD']), defValue: "camHUD"} ], - "BPM Change" => [{name: "Target BPM", type: TFloat(1.00, null, 0.001, 3), defValue: 100}], + "BPM Change" => [{name: "Target BPM", type: TFloat(1.00, 9999, 0.001, 3), defValue: 100}], + "Continuous BPM Change" => [{name: "Target BPM", type: TFloat(1.00, 9999, 0.001, 3), defValue: 100}, {name: "Time (steps)", type: TFloat(0.25, 9999, 0.25, 2), defValue: 4}], + "Time Signature Change" => [{name: "Target Beat Count", type: TFloat(1), defValue: 4}, {name: "Target Step Count", type: TFloat(1), defValue: 4}], "Scroll Speed Change" => [ {name: "Tween Speed?", type: TBool, defValue: true}, {name: "New Speed", type: TFloat(0.01, 99, 0.01, 2), defValue: 1.}, @@ -48,8 +50,7 @@ class EventsData { } ], "Alt Animation Toggle" => [{name: "Enable On Sing Poses", type: TBool, defValue: true}, {name: "Enable On Idle", type: TBool, defValue: true}, {name: "Strumline", type: TStrumLine, defValue: 0}], - "Play Animation" => [{name: "Character", type: TStrumLine, defValue: 0}, {name: "Animation", type: TString, defValue: "animation"}, {name: "Is forced?", type: TBool, defValue: true}], - "Time Signature Change" => [{name: "Target Beat Count", type: TFloat(1), defValue: 4}, {name: "Target Step Count", type: TFloat(1), defValue: 4}], + "Play Animation" => [{name: "Character", type: TStrumLine, defValue: 0}, {name: "Animation", type: TString, defValue: "animation"}, {name: "Is forced?", type: TBool, defValue: true}] ]; public static var eventsList:Array = defaultEventsList.copy(); diff --git a/source/funkin/backend/system/Conductor.hx b/source/funkin/backend/system/Conductor.hx index 2fbc6dfe..01281ef8 100644 --- a/source/funkin/backend/system/Conductor.hx +++ b/source/funkin/backend/system/Conductor.hx @@ -4,6 +4,7 @@ import flixel.FlxState; import flixel.util.FlxSignal.FlxTypedSignal; import funkin.backend.chart.ChartData; import funkin.backend.system.interfaces.IBeatReceiver; +import funkin.editors.charter.Charter; @:structInit class BPMChangeEvent @@ -14,6 +15,7 @@ class BPMChangeEvent public var stepsPerBeat:Int = 4; public var endSongTime:Float = 0; + public var endStepTime:Float = 0; public var continuous:Bool = false; public var stepTime:Float; @@ -139,6 +141,11 @@ final class Conductor */ public static var bpmChangeMap:Array; + /** + * Array of all events that have been rejected by the Conductor. + */ + public static var invalidEvents:Array = []; + @:dox(hide) public function new() {} public static function reset() { @@ -154,13 +161,9 @@ final class Conductor mapBPMChanges(SONG); } - private static function mapBPMChange(curChange:BPMChangeEvent, time:Float, bpm:Float, ?endTime:Float, ?prevChange:BPMChangeEvent):BPMChangeEvent { + private static function mapBPMChange(curChange:BPMChangeEvent, time:Float, bpm:Float):BPMChangeEvent { var beatTime:Float, measureTime:Float, stepTime:Float; - if (curChange.continuous) - stepTime = curChange.stepTime + (((curChange.endSongTime - curChange.songTime) * (curChange.bpm - prevChange.bpm)) - / Math.log(curChange.bpm / prevChange.bpm) + (time - curChange.endSongTime) * curChange.bpm) / 15000; - else - stepTime = curChange.stepTime + (time - curChange.songTime) / (15000 / curChange.bpm); + stepTime = (curChange.continuous ? curChange.endStepTime : curChange.stepTime) + (time - (curChange.continuous ? curChange.endSongTime : curChange.songTime)) / (15000 / curChange.bpm); beatTime = curChange.beatTime + (stepTime - curChange.stepTime) / curChange.stepsPerBeat; measureTime = curChange.measureTime + (beatTime - curChange.beatTime) / curChange.beatsPerMeasure; @@ -174,7 +177,6 @@ final class Conductor beatsPerMeasure: curChange.beatsPerMeasure, stepsPerBeat: curChange.stepsPerBeat }); - if (curChange.continuous = (endTime is Float && endTime != 0)) curChange.endSongTime = endTime; return curChange; } @@ -194,32 +196,97 @@ final class Conductor }; curChangeIndex = 0; bpmChangeMap = [curChange]; + invalidEvents = []; if (song.events == null) return; // fix the sort first... var events:Array = []; - for (e in song.events) if (e.params != null && (e.name == "BPM Change" || e.name == "Time Signature Change")) events.push(e); - events.sort(function(a, b) return Std.int(a.time - b.time)); + for (e in song.events) if (e.params != null && (e.name == "BPM Change" || e.name == "Time Signature Change" || e.name == "Continuous BPM Change")) events.push(e); + events.sort(function(a, b) { + if (a.time == b.time) { + if (a.name == "Continuous BPM Change") return 1; + if (b.name == "Continuous BPM Change") return -1; + } + return Std.int(a.time - b.time); + }); - var prevChange:BPMChangeEvent = null; for (e in events) { - if (bpmChangeMap.length > 3) prevChange = bpmChangeMap[bpmChangeMap.length - 2]; - var name = e.name, params = e.params, time = e.time; - if (name == "BPM Change" && params[0] is Float && curChange.bpm != params[0]) - curChange = mapBPMChange(curChange, time, params[0], params[1], prevChange); - else if (name == "Time Signature Change") { - //if (beatsPerMeasure == curChange.beatsPerMeasure && stepsPerBeat == curChange.stepsPerBeat) continue; - /* TODO: make so time sigs doesnt stop the bpm change if its in the duration of bpm change */ + curChange = mapEvent(e, curChange); + } + } - if (curChange.songTime != time) curChange = mapBPMChange(curChange, time, curChange.bpm, null, prevChange); - curChange.beatsPerMeasure = params[0]; - curChange.stepsPerBeat = params[1]; + private static function mapEvent(e:ChartEvent, curChange:BPMChangeEvent) { + var name = e.name, params = e.params, time = e.time; + if (curChange.continuous && time < curChange.endSongTime) { //ensure that you cannot place any conductor events during a continuous change + invalidEvents.push(e); + Logs.trace('Invalid Conductor event "${e.name}" at ${e.time} (Intersecting continuous change!)', WARNING); + return curChange; + } + + if (name == "BPM Change" && params[0] is Float && curChange.bpm != params[0]) + curChange = mapBPMChange(curChange, time, params[0]); + else if (name == "Time Signature Change") { + //if (beatsPerMeasure == curChange.beatsPerMeasure && stepsPerBeat == curChange.stepsPerBeat) continue; + /* TODO: make so time sigs doesnt stop the bpm change if its in the duration of bpm change */ + + if (curChange.songTime != time) curChange = mapBPMChange(curChange, time, curChange.bpm); + curChange.beatsPerMeasure = params[0]; + curChange.stepsPerBeat = params[1]; + + curChange.stepTime = CoolUtil.floorInt(curChange.stepTime + .99998); + curChange.beatTime = CoolUtil.floorInt(curChange.beatTime + .99998); + curChange.measureTime = CoolUtil.floorInt(curChange.measureTime + .99998); + } else if (name == "Continuous BPM Change") { + + var prevBPM = curChange.bpm; + if (curChange.bpm == params[0]) { + invalidEvents.push(e); + return curChange; //DO NOT!!!! + } + curChange = mapBPMChange(curChange, time, params[0]); + var endTime = time + (params[1]) / (curChange.bpm - prevBPM) * Math.log(curChange.bpm / prevBPM) * 15000; + curChange.endStepTime = curChange.stepTime + params[1]; + curChange.continuous = true; + curChange.endSongTime = endTime; + } + return curChange; + } + + public static function mapCharterBPMChanges(song:ChartData) { + var curChange:BPMChangeEvent = { + songTime: 0, + stepTime: 0, + beatTime: 0, + measureTime: 0, + bpm: song.meta.bpm, + beatsPerMeasure: song.meta.beatsPerMeasure.getDefault(4), + stepsPerBeat: CoolUtil.floorInt(song.meta.stepsPerBeat.getDefault(4)) + }; + curChangeIndex = 0; + bpmChangeMap = [curChange]; + invalidEvents = []; + + for(event in Charter.instance.eventsGroup.members) { + event.events.sort(function(a, b) { + if (a.time == b.time) { + if (a.name == "Continuous BPM Change") return 1; + if (b.name == "Continuous BPM Change") return -1; + } + return 0; + }); + + var eventTime = Conductor.getTimeForStep(event.step); + for (e in event.events) { + e.time = eventTime; - curChange.stepTime = CoolUtil.floorInt(curChange.stepTime + .99998); - curChange.beatTime = CoolUtil.floorInt(curChange.beatTime + .99998); - curChange.measureTime = CoolUtil.floorInt(curChange.measureTime + .99998); + if ((e.name == "BPM Change" || e.name == "Time Signature Change" || e.name == "Continuous BPM Change")) { + curChange = mapEvent(e, curChange); + } + } } + + } private static var elapsed:Float; diff --git a/source/funkin/editors/charter/Charter.hx b/source/funkin/editors/charter/Charter.hx index 3760b25a..55f74e70 100644 --- a/source/funkin/editors/charter/Charter.hx +++ b/source/funkin/editors/charter/Charter.hx @@ -843,6 +843,7 @@ class Charter extends UIState { if (s is CharterNote) cast(s, CharterNote).snappedToStrumline = true; if (s is UISprite) cast(s, UISprite).cursor = BUTTON; } + checkSelectionForBPMUpdates(); if (!(verticalChange == 0 && horizontalChange == 0)) { notesGroup.sortNotes(); eventsGroup.sortEvents(); undos.addToUndo(CSelectionDrag(undoDrags)); @@ -1031,11 +1032,7 @@ class Charter extends UIState { notesGroup.sortNotes(); notesGroup.autoSort = true; - for (s in selection) - if (s is CharterEvent) { - Charter.instance.updateBPMEvents(); - break; - } + checkSelectionForBPMUpdates(); if (addToUndo) undos.addToUndo(CCreateSelection(selection)); @@ -1059,11 +1056,7 @@ class Charter extends UIState { notesGroup.sortNotes(); notesGroup.autoSort = true; - for (s in selection) - if (s is CharterEvent) { - Charter.instance.updateBPMEvents(); - break; - } + checkSelectionForBPMUpdates(); if (addToUndo) undos.addToUndo(CDeleteSelection(selection)); @@ -1432,6 +1425,8 @@ class Charter extends UIState { selection = sObjects; _edit_copy(_); // to fix stupid bugs + checkSelectionForBPMUpdates(); + undos.addToUndo(CCreateSelection(sObjects.copy())); } @@ -1477,6 +1472,7 @@ class Charter extends UIState { if (s.selectable.draggable) s.selectable.handleDrag(s.change * -1); selection = [for (s in selectionDrags) s.selectable]; + Charter.instance.updateBPMEvents(); case CEditSustains(changes): for(n in changes) n.note.updatePos(n.note.step, n.note.id, n.before, n.note.type); @@ -1522,6 +1518,7 @@ class Charter extends UIState { for (s in selectionDrags) if (s.selectable.draggable) s.selectable.handleDrag(s.change); //this.selection = selection; + Charter.instance.updateBPMEvents(); case CEditSustains(changes): for(n in changes) n.note.updatePos(n.note.step, n.note.id, n.after, n.note.type); @@ -1836,14 +1833,30 @@ class Charter extends UIState { } public function updateBPMEvents() { + eventsGroup.sortEvents(); + Conductor.mapCharterBPMChanges(PlayState.SONG); buildEvents(); - Conductor.changeBPM(PlayState.SONG.meta.bpm, cast PlayState.SONG.meta.beatsPerMeasure.getDefault(4), cast PlayState.SONG.meta.stepsPerBeat.getDefault(4)); - Conductor.mapBPMChanges(PlayState.SONG); - + for(e in eventsGroup.members) { + for(event in e.events) { + if (event.name == "BPM Change" || event.name == "Time Signature Change" || event.name == "Continuous BPM Change") { + e.refreshEventIcons(); + break; + } + } + } + refreshBPMSensitive(); } + public inline function checkSelectionForBPMUpdates() { + for (s in selection) + if (s is CharterEvent) { + Charter.instance.updateBPMEvents(); + break; + } + } + public inline function hitsoundsEnabled(id:Int) return strumLines.members[id] != null && strumLines.members[id].hitsounds; diff --git a/source/funkin/editors/charter/CharterBackdropGroup.hx b/source/funkin/editors/charter/CharterBackdropGroup.hx index ac58a444..484b0380 100644 --- a/source/funkin/editors/charter/CharterBackdropGroup.hx +++ b/source/funkin/editors/charter/CharterBackdropGroup.hx @@ -294,10 +294,10 @@ class CharterGridSeperatorBase extends FlxSprite { for (i => change in Conductor.bpmChangeMap) { if (change.stepTime >= minStep && change.stepTime <= maxStep) { //get step while ignoring the current change - var index = CoolUtil.boundInt(i-1, 0, Conductor.bpmChangeMap.length - 1); - var step = Conductor.getTimeWithBPMInSteps(change.songTime, index, Conductor.getTimeWithIndexInBPM(change.songTime, index)); + var index:Int = CoolUtil.boundInt(i-1, 0, Conductor.bpmChangeMap.length - 1); + var step:Float = Conductor.getTimeWithBPMInSteps(change.songTime, index, Conductor.getTimeWithIndexInBPM(change.songTime, index)); - if (Math.ceil(step) - step > 0) { //mid step change + if (Math.ceil(step) - step > 0 && (step - Math.floor(step)) > FlxMath.EPSILON) { //mid step change timeSignatureChangeGaps.push(step); } } diff --git a/source/funkin/editors/charter/CharterEvent.hx b/source/funkin/editors/charter/CharterEvent.hx index 0eb0fc81..0e2dc002 100644 --- a/source/funkin/editors/charter/CharterEvent.hx +++ b/source/funkin/editors/charter/CharterEvent.hx @@ -1,5 +1,7 @@ package funkin.editors.charter; +import funkin.backend.shaders.CustomShader; +import funkin.backend.system.Conductor; import flixel.group.FlxSpriteGroup; import flixel.math.FlxPoint; import flixel.system.FlxAssets.FlxGraphicAsset; @@ -176,7 +178,7 @@ class CharterEvent extends UISliceSprite implements ICharterSelectable { return healthIcon; } - public static function generateEventIcon(event:ChartEvent):FlxSprite { + public static function generateEventIcon(event:ChartEvent, inMenu:Bool = true):FlxSprite { var script = getUIScript(event, "event-icon"); if(script != null && !(script is DummyScript)) { if(script.get("generateIcon") != null) { @@ -201,8 +203,43 @@ class CharterEvent extends UISliceSprite implements ICharterSelectable { num.active = false; num; }); + if (Conductor.invalidEvents.contains(event)) generateEventIconWarning(group); return group; } + case "Continuous BPM Change": + if(event.params != null && event.params[1] != null) { + if (inMenu) { + var group = new EventIconGroup(); + group.add(generateDefaultIcon("BPM Change Start")); + if (Conductor.invalidEvents.contains(event)) generateEventIconWarning(group); + return group; + } + + var group = generateEventIconWithDuration(event.params[1], "BPM Change Start"); + group.members[0].y -= 2; + generateEventIconNumbers(group, Std.int(event.params[0]), 15); + if (Conductor.invalidEvents.contains(event)) generateEventIconWarning(group); + return group; + } else { + return generateDefaultIcon("BPM Change Start"); + } + case "BPM Change": + if(event.params != null && event.params[0] != null) { + if (inMenu) { + var group = new EventIconGroup(); + group.add(generateDefaultIcon(event.name)); + if (Conductor.invalidEvents.contains(event)) generateEventIconWarning(group); + return group; + } + + var group = new EventIconGroup(); + group.add(generateDefaultIcon(event.name)); + group.members[0].y -= 2; + generateEventIconNumbers(group, Std.int(event.params[0]), 15); + if (Conductor.invalidEvents.contains(event)) generateEventIconWarning(group); + return group; + } + case "Camera Movement": // camera movement, use health icon if(event.params != null) { @@ -213,6 +250,76 @@ class CharterEvent extends UISliceSprite implements ICharterSelectable { return generateDefaultIcon(event.name); } + private static function generateEventIconNumbers(group:EventIconGroup, number:Int, y:Float) { + var numString = Std.string(number); + var offset = (16 - (numString.length*5))/2; + for (num in 0...numString.length) { + group.add({ + var num = new EventNumber(offset + num*5, y, Std.parseInt(numString.charAt(num)), EventNumber.ALIGN_CENTER); + num.active = false; + num; + }); + } + } + + private static function generateEventIconWithDuration(duration:Float, startIcon:String, endIcon:String = "") { + var group = new EventIconGroup(); + group.add(generateDefaultIcon(startIcon)); + + var xOffset = 4; + var yGap = 24; + var endGap = 7; + if (endIcon == "") endGap = -2; + + if (duration >= 0.65) { //min time for showing arrow + var tail = new FlxSprite(xOffset, yGap); + var arrow = new FlxSprite(xOffset, (duration * 40) + endGap); + var arrowSegment = new FlxSprite(xOffset, yGap); + tail.frames = arrow.frames = arrowSegment.frames = Paths.getSparrowAtlas("editors/charter/event-icons/components/arrow-down"); + + group.add({ + tail.animation.addByPrefix("tail", "tail"); + tail.animation.play("tail"); + tail; + }); + + group.add({ + arrowSegment.animation.addByPrefix("segment", "segment"); + arrowSegment.animation.play("segment"); + arrowSegment.scale.y = (duration * 40) - (tail.height + endGap + yGap); + arrowSegment.updateHitbox(); + arrowSegment.y += tail.height; + arrowSegment; + }); + + group.add({ + arrow.animation.addByPrefix("arrow", "arrow"); + arrow.animation.play("arrow"); + arrow; + }); + } + + if (endIcon != "") { + group.add({ + var icon = generateDefaultIcon(endIcon); + icon.y = duration * 40; + icon; + }); + } + + + return group; + } + + private static function generateEventIconWarning(group:EventIconGroup) { + for (spr in group) { + spr.colorTransform.redMultiplier = spr.colorTransform.greenMultiplier = spr.colorTransform.blueMultiplier = 0.5; + spr.colorTransform.redOffset = 100; + } + group.add(getEventComponent("warning", 16, -8)); + group.copyColorTransformToChildren = false; + } + public override function onHovered() { super.onHovered(); /* @@ -238,26 +345,22 @@ class CharterEvent extends UISliceSprite implements ICharterSelectable { } for(event in events) { - var spr = generateEventIcon(event); + var spr = generateEventIcon(event, false); icons.push(spr); members.push(spr); } draggable = true; - for (event in events) - if (event.name == "BPM Change" || event.name == "Time Signature Change") { - draggable = false; - break; - } x = (snappedToGrid && eventsBackdrop != null ? eventsBackdrop.x : 0) - (bWidth = 37 + (icons.length * 22)); } } class EventIconGroup extends FlxSpriteGroup { - public var forceWidth:Float = 32; - public var forceHeight:Float = 32; + public var forceWidth:Float = 16; + public var forceHeight:Float = 16; public var dontTransformChildren:Bool = true; + public var copyColorTransformToChildren:Bool = true; public function new() { super(); @@ -298,6 +401,12 @@ class EventIconGroup extends FlxSpriteGroup { override function get_height() { return forceHeight; } + + override public function draw() { + @:privateAccess + if (copyColorTransformToChildren && colorTransform != null) for (child in members) child.colorTransform.__copyFrom(colorTransform); + super.draw(); + } } class EventNumber extends FlxSprite { @@ -312,10 +421,15 @@ class EventNumber extends FlxSprite { super(x, y); this.digits = []; this.align = align; - while (number > 0) { - this.digits.insert(0, number % 10); - number = Std.int(number / 10); + if (number == 0) { + this.digits.insert(0, 0); + } else { + while (number > 0) { + this.digits.insert(0, number % 10); + number = Std.int(number / 10); + } } + loadGraphic(Paths.image('editors/charter/event-icons/components/eventNums'), true, 6, 7); } diff --git a/source/funkin/editors/charter/CharterEventScreen.hx b/source/funkin/editors/charter/CharterEventScreen.hx index 307aa907..3c2ad340 100644 --- a/source/funkin/editors/charter/CharterEventScreen.hx +++ b/source/funkin/editors/charter/CharterEventScreen.hx @@ -58,7 +58,7 @@ class CharterEventScreen extends UISubstateWindow { }); eventsList.add(new EventButton(events[events.length-1], CharterEvent.generateEventIcon(events[events.length-1]), events.length-1, this, eventsList)); changeTab(events.length-1); - })); + }, chartEvent.step)); for (k=>i in events) eventsList.add(new EventButton(i, CharterEvent.generateEventIcon(i), k, this, eventsList)); add(eventsList); @@ -83,12 +83,12 @@ class CharterEventScreen extends UISubstateWindow { else { chartEvent.events = [for (i in eventsList.buttons.members) i.event]; chartEvent.refreshEventIcons(); - Charter.instance.updateBPMEvents(); Charter.undos.addToUndo(CEditEvent(chartEvent, oldEvents, [for (event in events) Reflect.copy(event)])); } } + Charter.instance.updateBPMEvents(); close(); }); saveButton.x -= saveButton.bWidth; diff --git a/source/funkin/editors/charter/CharterEventTypeSelection.hx b/source/funkin/editors/charter/CharterEventTypeSelection.hx index bfa6cfe7..0e836e77 100644 --- a/source/funkin/editors/charter/CharterEventTypeSelection.hx +++ b/source/funkin/editors/charter/CharterEventTypeSelection.hx @@ -1,9 +1,11 @@ package funkin.editors.charter; +import funkin.backend.system.Conductor; import funkin.backend.chart.EventsData; class CharterEventTypeSelection extends UISubstateWindow { var callback:String->Void; + var eventStepTime:Float; var buttons:Array = []; @@ -13,9 +15,10 @@ class CharterEventTypeSelection extends UISubstateWindow { var upIndicator:UIText; var downIndicator:UIText; - public function new(callback:String->Void) { + public function new(callback:String->Void, eventStepTime:Float) { super(); this.callback = callback; + this.eventStepTime = eventStepTime; } public override function create() { @@ -32,6 +35,15 @@ class CharterEventTypeSelection extends UISubstateWindow { buttonsBG.frames = Paths.getFrames('editors/ui/inputbox'); add(buttonsBG); + var disableConductorEvents:Bool = false; + var disableOnlyContinuousChanges:Bool = false; + for (change in Conductor.bpmChangeMap) { + if (change.continuous && eventStepTime >= change.stepTime && eventStepTime < change.endStepTime) { + disableOnlyContinuousChanges = eventStepTime == change.stepTime; //allow time sig and instant bpm changes on the same event + disableConductorEvents = !disableOnlyContinuousChanges; + } + } + for(k=>eventName in EventsData.eventsList) { var button = new UIButton(0, (32 * k), eventName, function() { close(); @@ -52,6 +64,11 @@ class CharterEventTypeSelection extends UISubstateWindow { icon.x = button.x + 8; icon.y = button.y + Math.abs(button.bHeight - icon.height) / 2; add(icon); + + if (disableConductorEvents && (eventName == "Time Signature Change" || eventName == "Continuous BPM Change" || eventName == "BPM Change") || (disableOnlyContinuousChanges && eventName == "Continuous BPM Change")) { + button.selectable = button.shouldPress = false; + button.autoAlpha = true; + } } windowSpr.bHeight = 61 + (32 * (17)); @@ -79,7 +96,7 @@ class CharterEventTypeSelection extends UISubstateWindow { sinner += elapsed; for (button in buttons) - button.selectable = buttonsBG.hovered; + if (button.shouldPress) button.selectable = buttonsBG.hovered; buttonCameras.zoom = subCam.zoom;