Merge branch 'Time-Signature-Changes' into new-conductor
# Conflicts: # .gitignore # source/funkin/backend/FunkinSprite.hx # source/funkin/backend/chart/ChartData.hx # source/funkin/backend/chart/EventsData.hx # source/funkin/backend/system/Conductor.hx # source/funkin/backend/utils/CoolUtil.hx # source/funkin/editors/charter/Charter.hx # source/funkin/editors/charter/CharterEvent.hx # source/funkin/editors/charter/CharterEventScreen.hx # source/funkin/editors/charter/CharterMetaDataScreen.hx # source/funkin/editors/charter/SongCreationScreen.hx # source/funkin/game/PlayState.hx
This commit is contained in:
@@ -145,7 +145,6 @@ class FunkinSprite extends FlxSkewedSprite implements IBeatReceiver implements I
|
||||
{
|
||||
if (lastAnimContext != LOCK && beatAnims.length > 0 && (curBeat + beatOffset) % beatInterval == 0)
|
||||
{
|
||||
if(skipNegativeBeats && curBeat < 0) return;
|
||||
// TODO: find a solution without countedBeat
|
||||
var anim = beatAnims[FlxMath.wrap(countedBeat++, 0, beatAnims.length - 1)];
|
||||
if (anim.name != null && anim.name != "null" && anim.name != "none")
|
||||
|
||||
@@ -10,7 +10,7 @@ import openfl.Assets;
|
||||
using StringTools;
|
||||
|
||||
class EventsData {
|
||||
public static var defaultEventsList:Array<String> = ["HScript Call", "Camera Movement", "Camera Position", "Add Camera Zoom", "Camera Zoom", "Camera Modulo Change", "Camera Flash", "BPM Change", "Scroll Speed Change", "Alt Animation Toggle", "Play Animation"];
|
||||
public static var defaultEventsList:Array<String> = ["HScript Call", "Camera Movement", "Camera Position", "Add Camera Zoom", "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<String, Array<EventParamInfo>> = [
|
||||
"HScript Call" => [
|
||||
{name: "Function Name", type: TString, defValue: "myFunc"},
|
||||
@@ -75,8 +75,10 @@ class EventsData {
|
||||
{name: "Multiplicative?", type: TBool, defValue: true}
|
||||
],
|
||||
"Camera Modulo Change" => [
|
||||
{name: "Modulo Interval (Beats)", type: TInt(1, 9999999, 1), defValue: 4},
|
||||
{name: "Bump Strength", type: TFloat(0.1, 10, 0.01, 2), defValue: 1}
|
||||
{name: "Modulo Interval", type: TInt(1, 9999999, 1), defValue: 4},
|
||||
{name: "Bump Strength", type: TFloat(0.1, 10, 0.01, 2), defValue: 1},
|
||||
{name: "Every Beat Type", type: TDropDown(['BEAT', 'MEASURE', 'STEP']), defValue: 'BEAT'},
|
||||
{name: "Beat Offset", type: TFloat(-10, 10, 0.25, 2), defValue: 0}
|
||||
],
|
||||
"Camera Flash" => [
|
||||
{name: "Reversed?", type: TBool, defValue: false},
|
||||
@@ -84,7 +86,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 Numerator", type: TFloat(1), defValue: 4}, {name: "Target Denominator", type: TFloat(1), defValue: 4}, {name: "Denominator is Steps Per Beat", type: TBool, defValue: false}],
|
||||
"Scroll Speed Change" => [
|
||||
{name: "Tween Speed?", type: TBool, defValue: true},
|
||||
{name: "New Speed", type: TFloat(0.01, 99, 0.01, 2), defValue: 1.},
|
||||
|
||||
@@ -55,6 +55,18 @@ class FNFLegacyParser {
|
||||
continue; // Yoshi Engine charts crash fix
|
||||
}
|
||||
|
||||
var newBeatsPerMeasure:Float = section.sectionBeats != null ? section.sectionBeats : data.beatsPerMeasure.getDefault(4); // Default to 4 if sectionBeats is null or undefined (oops :3)
|
||||
|
||||
if (newBeatsPerMeasure != beatsPerMeasure) {
|
||||
beatsPerMeasure = newBeatsPerMeasure;
|
||||
|
||||
result.events.push({
|
||||
time: curTime,
|
||||
name: "Time Signature Change",
|
||||
params: [newBeatsPerMeasure, 4]
|
||||
});
|
||||
}
|
||||
|
||||
if (camFocusedBF != (camFocusedBF = section.mustHitSection)) {
|
||||
result.events.push({
|
||||
time: curTime,
|
||||
@@ -178,7 +190,8 @@ class FNFLegacyParser {
|
||||
mustHitSection: notes[section-1] != null ? notes[section-1].mustHitSection : false,
|
||||
bpm: notes[section-1] != null ? notes[section-1].bpm : chart.meta.bpm,
|
||||
changeBPM: false,
|
||||
altAnim: notes[section-1] != null ? notes[section-1].altAnim : false
|
||||
altAnim: notes[section-1] != null ? notes[section-1].altAnim : false,
|
||||
sectionBeats: notes[section-1] != null ? notes[section-1].sectionBeats : chart.meta.beatsPerMeasure.getDefault(4)
|
||||
};
|
||||
|
||||
var sectionEndTime:Float = Conductor.getTimeForStep(Conductor.getMeasureLength() * (section+1));
|
||||
@@ -192,6 +205,8 @@ class FNFLegacyParser {
|
||||
case "BPM Change":
|
||||
baseSection.changeBPM = true;
|
||||
baseSection.bpm = event.params[0];
|
||||
case "Time Signature Change":
|
||||
baseSection.sectionBeats = event.params[0];
|
||||
}
|
||||
}
|
||||
notes[section] = baseSection;
|
||||
|
||||
@@ -4,56 +4,58 @@ import flixel.FlxState;
|
||||
import flixel.util.FlxSignal.FlxTypedSignal;
|
||||
import funkin.backend.chart.ChartData;
|
||||
import funkin.backend.system.interfaces.IBeatReceiver;
|
||||
import funkin.editors.charter.Charter;
|
||||
|
||||
typedef BPMChangeEvent =
|
||||
{
|
||||
var stepTime:Float;
|
||||
var songTime:Float;
|
||||
var bpm:Float;
|
||||
enum BeatType {
|
||||
BEAT;
|
||||
MEASURE;
|
||||
STEP;
|
||||
}
|
||||
|
||||
class Conductor
|
||||
@:structInit
|
||||
class BPMChangeEvent
|
||||
{
|
||||
public var songTime:Float;
|
||||
public var bpm:Float;
|
||||
public var beatsPerMeasure:Float = 4;
|
||||
public var stepsPerBeat:Int = 4;
|
||||
|
||||
public var endSongTime:Float = 0;
|
||||
public var endStepTime:Float = 0;
|
||||
public var continuous:Bool = false;
|
||||
|
||||
public var stepTime:Float;
|
||||
public var beatTime:Float;
|
||||
public var measureTime:Float;
|
||||
}
|
||||
|
||||
final class Conductor
|
||||
{
|
||||
public static function getBeats(?every:BeatType, interval:Float, offset:Float = 0):Float {
|
||||
final beat = switch(every) {
|
||||
case MEASURE: curMeasureFloat;
|
||||
case STEP: curStepFloat;
|
||||
default: curBeatFloat;
|
||||
}
|
||||
if (interval <= 0) return beat - offset;
|
||||
else return Math.floor((beat - offset) / interval) * interval;
|
||||
}
|
||||
|
||||
/**
|
||||
* FlxSignals
|
||||
*/
|
||||
public static var onMeasureHit:FlxTypedSignal<Int->Void> = new FlxTypedSignal();
|
||||
public static var onBeatHit:FlxTypedSignal<Int->Void> = new FlxTypedSignal();
|
||||
public static var onStepHit:FlxTypedSignal<Int->Void> = new FlxTypedSignal();
|
||||
public static var onBPMChange:FlxTypedSignal<Float->Void> = new FlxTypedSignal();
|
||||
|
||||
/**
|
||||
* Current BPM
|
||||
*/
|
||||
public static var bpm:Float = 100;
|
||||
|
||||
/**
|
||||
* Current Crochet (time per beat), in milliseconds.
|
||||
*/
|
||||
public static var crochet:Float = ((60 / bpm) * 1000); // beats in milliseconds
|
||||
|
||||
/**
|
||||
* Current StepCrochet (time per step), in milliseconds.
|
||||
*/
|
||||
public static var stepCrochet:Float = crochet / 4; // steps in milliseconds
|
||||
|
||||
/**
|
||||
* Number of beats per measure (top number in time signature). Defaults to 4.
|
||||
*/
|
||||
public static var beatsPerMeasure:Float = 4;
|
||||
|
||||
/**
|
||||
* Number of steps per beat (bottom number in time signature). Defaults to 4.
|
||||
*/
|
||||
public static var stepsPerBeat:Int = 4;
|
||||
|
||||
public static var onBPMChange:FlxTypedSignal<(Float,Float)->Void> = new FlxTypedSignal();
|
||||
public static var onTimeSignatureChange:FlxTypedSignal<(Float,Float)->Void> = new FlxTypedSignal();
|
||||
|
||||
/**
|
||||
* Current position of the song, in milliseconds.
|
||||
*/
|
||||
public static var songPosition(get, default):Float;
|
||||
private static function get_songPosition() {
|
||||
if (songOffset != Options.songOffset) trace(songOffset = Options.songOffset);
|
||||
if (songOffset != Options.songOffset) songOffset = Options.songOffset;
|
||||
return songPosition - songOffset;
|
||||
}
|
||||
|
||||
@@ -62,6 +64,88 @@ class Conductor
|
||||
*/
|
||||
public static var songOffset:Float = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Current bpmChangeMap index
|
||||
*/
|
||||
public static var curChangeIndex:Int = 0;
|
||||
|
||||
/**
|
||||
* Current bpmChangeMap
|
||||
*/
|
||||
public static var curChange(get, never):BPMChangeEvent;
|
||||
private static var dummyChange:BPMChangeEvent = {bpm: 100, beatsPerMeasure: 4, stepsPerBeat: 4, songTime: 0, stepTime: 0, beatTime: 0, measureTime: 0};
|
||||
private static function get_curChange()
|
||||
return bpmChangeMap.length == 0 ? dummyChange : bpmChangeMap[curChangeIndex];
|
||||
|
||||
/**
|
||||
* Current BPM
|
||||
*/
|
||||
public static var bpm(get, never):Float;
|
||||
private static function get_bpm()
|
||||
return (curChangeIndex == 0 || bpmChangeMap.length == 0) ? startingBPM : getTimeWithIndexInBPM(songPosition, curChangeIndex);
|
||||
|
||||
/**
|
||||
* Starting BPM
|
||||
*/
|
||||
public static var startingBPM(get, never):Float;
|
||||
private static function get_startingBPM()
|
||||
return (bpmChangeMap.length == 0 ? dummyChange : bpmChangeMap[0]).bpm;
|
||||
|
||||
/**
|
||||
* Current Crochet (time per beat), in milliseconds.
|
||||
* It should be crotchet but ehhh, now it's there for backward compatibility.
|
||||
*/
|
||||
public static var crochet(get, never):Float;
|
||||
private static function get_crochet() return 15000 * stepsPerBeat / bpm;
|
||||
|
||||
/**
|
||||
* Current StepCrochet (time per step), in milliseconds.
|
||||
*/
|
||||
public static var stepCrochet(get, never):Float;
|
||||
private static function get_stepCrochet() return 15000 / bpm;
|
||||
|
||||
/**
|
||||
* Number of beats per mesure (top number in time signature). Defaults to 4.
|
||||
*/
|
||||
public static var beatsPerMeasure(get, never):Float;
|
||||
private static function get_beatsPerMeasure()
|
||||
return (bpmChangeMap.length == 0 ? dummyChange : bpmChangeMap[curChangeIndex]).beatsPerMeasure;
|
||||
|
||||
/**
|
||||
* Number of steps per beat. Defaults to 4.
|
||||
* Not a divisor number for time signature, it does the complete opposite.
|
||||
* It's because CNE Conductor is based in sixteenth note instead of beat.
|
||||
*/
|
||||
public static var stepsPerBeat(get, never):Int;
|
||||
private static function get_stepsPerBeat()
|
||||
return (bpmChangeMap.length == 0 ? dummyChange : bpmChangeMap[curChangeIndex]).stepsPerBeat;
|
||||
|
||||
/**
|
||||
* How much value notes to divide for beat (bottom or divisor number in time signature).
|
||||
* Only for a convinient way to access divisor instead of multiply by steps per beat.
|
||||
*/
|
||||
public static var denominator(get, never):Float;
|
||||
private static function get_denominator() return FlxMath.roundDecimal(16 / stepsPerBeat, 2);
|
||||
|
||||
/**
|
||||
* Last step from BPM Change
|
||||
*/
|
||||
public static var lastStepChange(get, never):Float;
|
||||
private static function get_lastStepChange() return curChange.stepTime;
|
||||
|
||||
/**
|
||||
* Last beat from BPM Change
|
||||
*/
|
||||
public static var lastBeatChange(get, never):Float;
|
||||
private static function get_lastBeatChange() return curChange.beatTime;
|
||||
|
||||
/**
|
||||
* Last measure from BPM Change
|
||||
*/
|
||||
public static var lastMeasureChange(get, never):Float;
|
||||
private static function get_lastMeasureChange() return curChange.measureTime;
|
||||
|
||||
/**
|
||||
* Current step
|
||||
*/
|
||||
@@ -77,7 +161,6 @@ class Conductor
|
||||
*/
|
||||
public static var curMeasure:Int = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Current step, as a `Float` (ex: 4.94, instead of 4)
|
||||
*/
|
||||
@@ -100,53 +183,143 @@ class Conductor
|
||||
/**
|
||||
* Array of all BPM changes that have been mapped.
|
||||
*/
|
||||
public static var bpmChangeMap:Array<BPMChangeEvent> = [];
|
||||
public static var bpmChangeMap:Array<BPMChangeEvent>;
|
||||
|
||||
/**
|
||||
* Array of all events that have been rejected by the Conductor.
|
||||
*/
|
||||
public static var invalidEvents:Array<ChartEvent> = [];
|
||||
|
||||
private static var validEventNames:Array<String> = ["BPM Change", "Time Signature Change", "Continuous BPM Change"];
|
||||
|
||||
@:dox(hide) public function new() {}
|
||||
|
||||
public static function reset() {
|
||||
songPosition = lastSongPos = curBeatFloat = curStepFloat = curBeat = curStep = 0;
|
||||
bpmChangeMap = [];
|
||||
changeBPM(0);
|
||||
curChangeIndex = 0;
|
||||
changeBPM();
|
||||
}
|
||||
|
||||
public static function changeBPM(bpm:Float = 100, beatsPerMeasure:Float = 4, stepsPerBeat:Int = 4)
|
||||
bpmChangeMap = [{bpm: bpm, beatsPerMeasure: beatsPerMeasure, stepsPerBeat: stepsPerBeat, songTime: 0, stepTime: 0, beatTime: 0, measureTime: 0}];
|
||||
|
||||
public static function setupSong(SONG:ChartData) {
|
||||
reset();
|
||||
mapBPMChanges(SONG);
|
||||
changeBPM(SONG.meta.bpm, cast SONG.meta.beatsPerMeasure.getDefault(4), cast SONG.meta.stepsPerBeat.getDefault(4));
|
||||
}
|
||||
|
||||
private static function mapBPMChange(curChange:BPMChangeEvent, time:Float, bpm:Float):BPMChangeEvent {
|
||||
var beatTime:Float, measureTime:Float, stepTime:Float;
|
||||
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;
|
||||
|
||||
bpmChangeMap.push(curChange = {
|
||||
songTime: time,
|
||||
stepTime: stepTime,
|
||||
beatTime: beatTime,
|
||||
measureTime: measureTime,
|
||||
bpm: bpm,
|
||||
beatsPerMeasure: curChange.beatsPerMeasure,
|
||||
stepsPerBeat: curChange.stepsPerBeat
|
||||
});
|
||||
return curChange;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps BPM changes from a song.
|
||||
* @param song Song to map BPM changes from.
|
||||
*/
|
||||
public static function mapBPMChanges(song:ChartData)
|
||||
{
|
||||
bpmChangeMap = [
|
||||
{
|
||||
stepTime: 0,
|
||||
songTime: 0,
|
||||
bpm: song.meta.bpm
|
||||
}
|
||||
];
|
||||
|
||||
public static function mapBPMChanges(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 = [];
|
||||
if (song.events == null) return;
|
||||
|
||||
var curBPM:Float = song.meta.bpm;
|
||||
var songTime:Float = 0;
|
||||
var stepTime:Float = 0;
|
||||
// fix the sort first...
|
||||
var events:Array<ChartEvent> = [for (e in song.events) if (e.params != null && validEventNames.contains(e.name)) e];
|
||||
events.sort((a, b) -> {
|
||||
if (MathUtil.equal(a.time, b.time)) return a.name == "Continuous BPM Change" ? 1 : -1;
|
||||
return Std.int(a.time - b.time);
|
||||
});
|
||||
|
||||
for(e in song.events) if (e.name == "BPM Change" && e.params != null && e.params[0] is Float) {
|
||||
if (e.params[0] == curBPM) continue;
|
||||
var steps = (e.time - songTime) / ((60 / curBPM) * 1000 / 4);
|
||||
stepTime += steps;
|
||||
songTime = e.time;
|
||||
curBPM = e.params[0];
|
||||
for (e in events) curChange = mapEvent(e, curChange);
|
||||
}
|
||||
|
||||
bpmChangeMap.push({
|
||||
stepTime: stepTime,
|
||||
songTime: songTime,
|
||||
bpm: curBPM
|
||||
private static function mapEvent(e:ChartEvent, curChange:BPMChangeEvent) {
|
||||
var name = e.name, params = e.params, time = e.time;
|
||||
if (curChange.continuous && MathUtil.lessThan(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];
|
||||
|
||||
if (params[2]) curChange.stepsPerBeat = params[1];
|
||||
else curChange.stepsPerBeat = Math.floor(16 / params[1]); // convert from denominator to stepsPerBeat
|
||||
|
||||
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]);
|
||||
curChange.endSongTime = time + (params[1]) / (curChange.bpm - prevBPM) * Math.log(curChange.bpm / prevBPM) * 15000;
|
||||
curChange.endStepTime = curChange.stepTime + params[1];
|
||||
curChange.continuous = true;
|
||||
}
|
||||
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((a, b) -> {
|
||||
if (MathUtil.equal(a.time, b.time)) return a.name == "Continuous BPM Change" ? 1 : -1;
|
||||
return 0;
|
||||
});
|
||||
|
||||
var eventTime = Conductor.getStepsInTime(event.step);
|
||||
for (e in event.events) {
|
||||
if (!Math.isNaN(eventTime)) e.time = eventTime;
|
||||
if (validEventNames.contains(e.name)) curChange = mapEvent(e, curChange);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,12 +337,10 @@ class Conductor
|
||||
return;
|
||||
}
|
||||
|
||||
if (lastSongPos != (lastSongPos = FlxG.sound.music.time - songOffset)) {
|
||||
// update conductor
|
||||
if (lastSongPos != (lastSongPos = FlxG.sound.music.time - songOffset))
|
||||
songPosition = lastSongPos;
|
||||
} else {
|
||||
else
|
||||
songPosition += songOffset + elapsed * 1000;
|
||||
}
|
||||
}
|
||||
|
||||
private static function onStateSwitch(newState:FlxState) {
|
||||
@@ -177,137 +348,247 @@ class Conductor
|
||||
reset();
|
||||
}
|
||||
private static var __lastChange:BPMChangeEvent;
|
||||
private static var __updateStep:Bool;
|
||||
private static var __updateBeat:Bool;
|
||||
private static var __updateMeasure:Bool;
|
||||
|
||||
private static function update() {
|
||||
if (FlxG.state != null && FlxG.state is MusicBeatState) {
|
||||
var state:MusicBeatState = cast FlxG.state;
|
||||
if(state.cancelConductorUpdate)
|
||||
return;
|
||||
}
|
||||
if (FlxG.state != null && FlxG.state is MusicBeatState && cast(FlxG.state, MusicBeatState).cancelConductorUpdate) return;
|
||||
|
||||
__updateSongPos(FlxG.elapsed);
|
||||
|
||||
if (bpm > 0) {
|
||||
// updates curBeat and stuff
|
||||
__lastChange = {
|
||||
stepTime: 0,
|
||||
songTime: 0,
|
||||
bpm: 0
|
||||
};
|
||||
for (change in Conductor.bpmChangeMap)
|
||||
{
|
||||
if (Conductor.songPosition >= change.songTime)
|
||||
__lastChange = change;
|
||||
var oldStep = curStep, oldBeat = curBeat, oldMeasure = curMeasure, oldChangeIndex = curChangeIndex;
|
||||
|
||||
if ((curChangeIndex = getTimeInChangeIndex(songPosition, curChangeIndex)) > 0) {
|
||||
var change = curChange;
|
||||
curStepFloat = getTimeWithBPMInSteps(songPosition, curChangeIndex, getTimeWithIndexInBPM(songPosition, curChangeIndex));
|
||||
curBeatFloat = change.beatTime + (curStepFloat - change.stepTime) / change.stepsPerBeat;
|
||||
curMeasureFloat = change.measureTime + (curBeatFloat - change.beatTime) / change.beatsPerMeasure;
|
||||
}
|
||||
else
|
||||
curMeasureFloat = (curBeatFloat = (curStepFloat = songPosition / stepCrochet) / stepsPerBeat) / beatsPerMeasure;
|
||||
|
||||
if (curChangeIndex != oldChangeIndex) {
|
||||
var prev = bpmChangeMap[oldChangeIndex];
|
||||
if (prev != null) {
|
||||
if (beatsPerMeasure != prev.beatsPerMeasure || stepsPerBeat != prev.stepsPerBeat)
|
||||
onTimeSignatureChange.dispatch(beatsPerMeasure, stepsPerBeat);
|
||||
|
||||
if (curChange.bpm != prev.bpm) onBPMChange.dispatch(curChange.bpm, curChange.endSongTime);
|
||||
}
|
||||
else {
|
||||
onTimeSignatureChange.dispatch(beatsPerMeasure, stepsPerBeat);
|
||||
onBPMChange.dispatch(curChange.bpm, curChange.endSongTime);
|
||||
}
|
||||
}
|
||||
|
||||
if (__lastChange.bpm > 0 && bpm != __lastChange.bpm) changeBPM(__lastChange.bpm);
|
||||
if (__updateStep = (curStep != (curStep = CoolUtil.floorInt(curStepFloat)))) {
|
||||
if (curStep > oldStep) for (i in oldStep...curStep) onStepHit.dispatch(i + 1);
|
||||
else onStepHit.dispatch(curStep);
|
||||
}
|
||||
|
||||
curStepFloat = __lastChange.stepTime + ((Conductor.songPosition - __lastChange.songTime) / Conductor.stepCrochet);
|
||||
curBeatFloat = curStepFloat / stepsPerBeat;
|
||||
curMeasureFloat = curBeatFloat / beatsPerMeasure;
|
||||
if (__updateBeat = (curBeat != (curBeat = CoolUtil.floorInt(curBeatFloat)))) {
|
||||
if (curBeat > oldBeat) for (i in oldBeat...curBeat) onBeatHit.dispatch(i + 1);
|
||||
else onBeatHit.dispatch(curBeat);
|
||||
}
|
||||
|
||||
var oldStep = curStep;
|
||||
var oldBeat = curBeat;
|
||||
var oldMeasure = curMeasure;
|
||||
if (curStep != (curStep = CoolUtil.floorInt(curStepFloat))) {
|
||||
if (curStep < oldStep && oldStep - curStep < 2) return;
|
||||
// updates step
|
||||
__updateBeat = curBeat != (curBeat = CoolUtil.floorInt(curBeatFloat));
|
||||
__updateMeasure = __updateBeat && (curMeasure != (curMeasure = CoolUtil.floorInt(curMeasureFloat)));
|
||||
if (__updateMeasure = (curMeasure != (curMeasure = CoolUtil.floorInt(curMeasureFloat)))) {
|
||||
if (curMeasure > oldMeasure) for (i in oldMeasure...curMeasure) onMeasureHit.dispatch(i + 1);
|
||||
else onMeasureHit.dispatch(curMeasure);
|
||||
}
|
||||
|
||||
if (curStep > oldStep) {
|
||||
for(i in oldStep...curStep) {
|
||||
onStepHit.dispatch(i+1);
|
||||
if (__updateStep || __updateBeat || __updateMeasure) {
|
||||
var state = FlxG.state;
|
||||
while (state != null) {
|
||||
if (state is IBeatReceiver && (state.subState == null || state.persistentUpdate)) {
|
||||
var st = cast(state, IBeatReceiver);
|
||||
|
||||
if (__updateStep) {
|
||||
if (curStep > oldStep) for (i in oldStep...curStep) st.stepHit(i + 1);
|
||||
else st.stepHit(curStep);
|
||||
}
|
||||
|
||||
if (__updateBeat) {
|
||||
if (curBeat > oldBeat) for (i in oldBeat...curBeat) st.beatHit(i + 1);
|
||||
else st.beatHit(curBeat);
|
||||
}
|
||||
|
||||
if (__updateMeasure) {
|
||||
if (curMeasure > oldMeasure) for (i in oldMeasure...curMeasure) st.measureHit(i + 1);
|
||||
else st.measureHit(curMeasure);
|
||||
}
|
||||
}
|
||||
if (__updateBeat && curBeat > oldBeat) {
|
||||
for(i in oldBeat...curBeat) {
|
||||
onBeatHit.dispatch(i+1);
|
||||
}
|
||||
}
|
||||
if (__updateMeasure && curMeasure > oldMeasure) {
|
||||
for(i in oldMeasure...curMeasure) {
|
||||
onMeasureHit.dispatch(i+1);
|
||||
}
|
||||
}
|
||||
|
||||
if (FlxG.state is IBeatReceiver) {
|
||||
var state = FlxG.state;
|
||||
while(state != null) {
|
||||
if (state is IBeatReceiver && (state.subState == null || state.persistentUpdate)) {
|
||||
var st:IBeatReceiver = cast state;
|
||||
if (curStep > oldStep) {
|
||||
for(i in oldStep...curStep) {
|
||||
st.stepHit(i+1);
|
||||
}
|
||||
}
|
||||
if (__updateBeat && curBeat > oldBeat) {
|
||||
for(i in oldBeat...curBeat) {
|
||||
st.beatHit(i+1);
|
||||
}
|
||||
}
|
||||
if (__updateMeasure && curMeasure > oldMeasure) {
|
||||
for(i in oldMeasure...curMeasure) {
|
||||
st.measureHit(i+1);
|
||||
}
|
||||
}
|
||||
}
|
||||
state = state.subState;
|
||||
}
|
||||
}
|
||||
|
||||
state = state.subState;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function changeBPM(newBpm:Float, beatsPerMeasure:Float = 4, stepsPerBeat:Int = 4)
|
||||
{
|
||||
bpm = newBpm;
|
||||
|
||||
crochet = ((60 / bpm) * 1000);
|
||||
stepCrochet = crochet / stepsPerBeat;
|
||||
|
||||
Conductor.beatsPerMeasure = beatsPerMeasure;
|
||||
Conductor.stepsPerBeat = stepsPerBeat;
|
||||
|
||||
onBPMChange.dispatch(bpm);
|
||||
public static function getTimeInChangeIndex(time:Float, index:Int = 0):Int {
|
||||
if (bpmChangeMap.length < 2) return bpmChangeMap.length - 1;
|
||||
else if (bpmChangeMap[index = CoolUtil.boundInt(index, 0, bpmChangeMap.length - 1)].songTime > time) {
|
||||
while (--index > 0) if (time > bpmChangeMap[index].songTime) return index;
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
for (i in index...bpmChangeMap.length) if (bpmChangeMap[i].songTime > time) return i - 1;
|
||||
return bpmChangeMap.length - 1;
|
||||
}
|
||||
}
|
||||
|
||||
public static function getTimeForStep(step:Float) {
|
||||
var bpmChange:BPMChangeEvent = {
|
||||
stepTime: 0,
|
||||
songTime: 0,
|
||||
bpm: bpm
|
||||
};
|
||||
|
||||
for(change in bpmChangeMap)
|
||||
if (change.stepTime < step && change.stepTime >= bpmChange.stepTime)
|
||||
bpmChange = change;
|
||||
|
||||
return bpmChange.songTime + ((step - bpmChange.stepTime) * ((60 / bpmChange.bpm) * (1000/stepsPerBeat)));
|
||||
public static function getStepsInChangeIndex(stepTime:Float, index:Int = 0):Int {
|
||||
if (bpmChangeMap.length < 2) return bpmChangeMap.length - 1;
|
||||
else if (bpmChangeMap[index = CoolUtil.boundInt(index, 0, bpmChangeMap.length - 1)].stepTime > stepTime) {
|
||||
while (--index > 0) if (stepTime > bpmChangeMap[index].stepTime) return index;
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
for (i in index...bpmChangeMap.length) if (bpmChangeMap[i].stepTime > stepTime) return i - 1;
|
||||
return bpmChangeMap.length - 1;
|
||||
}
|
||||
}
|
||||
|
||||
public static function getStepForTime(time:Float) {
|
||||
var bpmChange:BPMChangeEvent = {
|
||||
stepTime: 0,
|
||||
songTime: 0,
|
||||
bpm: bpm
|
||||
};
|
||||
|
||||
for(change in bpmChangeMap)
|
||||
if (change.songTime < time && change.songTime >= bpmChange.songTime)
|
||||
bpmChange = change;
|
||||
|
||||
return bpmChange.stepTime + ((time - bpmChange.songTime) / ((60 / bpmChange.bpm) * (1000/stepsPerBeat)));
|
||||
public static function getBeatsInChangeIndex(beatTime:Float, index:Int = 0):Int {
|
||||
if (bpmChangeMap.length < 2) return bpmChangeMap.length - 1;
|
||||
else if (bpmChangeMap[index = CoolUtil.boundInt(index, 0, bpmChangeMap.length - 1)].beatTime > beatTime) {
|
||||
while (--index > 0) if (beatTime > bpmChangeMap[index].beatTime) return index;
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
for (i in index...bpmChangeMap.length) if (bpmChangeMap[i].beatTime > beatTime) return i - 1;
|
||||
return bpmChangeMap.length - 1;
|
||||
}
|
||||
}
|
||||
|
||||
public static function getMeasuresInChangeIndex(measureTime:Float, index:Int = 0):Int {
|
||||
if (bpmChangeMap.length < 2) return bpmChangeMap.length - 1;
|
||||
else if (bpmChangeMap[index = CoolUtil.boundInt(index, 0, bpmChangeMap.length - 1)].measureTime > measureTime) {
|
||||
while (--index > 0) if (measureTime > bpmChangeMap[index].measureTime) return index;
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
for (i in index...bpmChangeMap.length) if (bpmChangeMap[i].measureTime > measureTime) return i - 1;
|
||||
return bpmChangeMap.length - 1;
|
||||
}
|
||||
}
|
||||
|
||||
public static function getTimeWithIndexInBPM(time:Float, index:Int):Float {
|
||||
var bpmChange = bpmChangeMap[index];
|
||||
if (bpmChange.continuous && time < bpmChange.endSongTime && index > 0) {
|
||||
var prevBPM = bpmChangeMap[index - 1].bpm;
|
||||
if (time <= bpmChange.songTime) return prevBPM;
|
||||
|
||||
var ratio = (time - bpmChange.songTime) / (bpmChange.endSongTime - bpmChange.songTime);
|
||||
return Math.pow(prevBPM, 1 - ratio) * Math.pow(bpmChange.bpm, ratio);
|
||||
}
|
||||
return bpmChange.bpm;
|
||||
}
|
||||
|
||||
public static function getStepsWithIndexInBPM(stepTime:Float, index:Int):Float {
|
||||
var bpmChange = bpmChangeMap[index];
|
||||
if (bpmChange.continuous && index > 0) {
|
||||
var prevBPM = bpmChangeMap[index - 1].bpm;
|
||||
if (stepTime <= bpmChange.stepTime) return prevBPM;
|
||||
|
||||
var endStepTime = bpmChange.stepTime + (bpmChange.endSongTime - bpmChange.songTime) * (bpmChange.bpm - prevBPM) / Math.log(bpmChange.bpm / prevBPM) / 15000;
|
||||
if (stepTime < endStepTime) return FlxMath.remapToRange(stepTime, bpmChange.stepTime, endStepTime, prevBPM, bpmChange.bpm);
|
||||
}
|
||||
return bpmChange.bpm;
|
||||
}
|
||||
|
||||
public static function getTimeInBPM(time:Float):Float {
|
||||
if (bpmChangeMap.length == 0) return dummyChange.bpm;
|
||||
return getTimeWithIndexInBPM(time, getTimeInChangeIndex(time));
|
||||
}
|
||||
|
||||
public static function getTimeWithBPMInSteps(time:Float, index:Int, bpm:Float):Float {
|
||||
var bpmChange = bpmChangeMap[index];
|
||||
if (bpmChange.continuous && time > bpmChange.songTime && index > 0) {
|
||||
var prevBPM = bpmChangeMap[index - 1].bpm;
|
||||
if (time > bpmChange.endSongTime)
|
||||
return bpmChange.stepTime + (((bpmChange.endSongTime - bpmChange.songTime) * (bpmChange.bpm - prevBPM))
|
||||
/ Math.log(bpmChange.bpm / prevBPM) + (time - bpmChange.endSongTime) * bpm) / 15000;
|
||||
else
|
||||
return bpmChange.stepTime + (time - bpmChange.songTime) * (bpm - prevBPM) / Math.log(bpm / prevBPM) / 15000;
|
||||
}
|
||||
else {
|
||||
return bpmChange.stepTime + (time - bpmChange.songTime) / (15000 / bpm);
|
||||
}
|
||||
}
|
||||
|
||||
public static function getTimeInBeats(time:Float, from:Int = 0):Float {
|
||||
var index = getTimeInChangeIndex(time, from);
|
||||
if (index == -1) return time / (60000 / dummyChange.bpm);
|
||||
else if (index == 0) return time / (15000 / bpmChangeMap[index].bpm) / bpmChangeMap[index].stepsPerBeat;
|
||||
else {
|
||||
var change = bpmChangeMap[index];
|
||||
return change.beatTime + (getTimeWithBPMInSteps(time, index, getTimeWithIndexInBPM(time, index)) - change.stepTime) / change.stepsPerBeat;
|
||||
}
|
||||
}
|
||||
|
||||
public static function getTimeInSteps(time:Float, from:Int = 0):Float {
|
||||
var index = getTimeInChangeIndex(time, from);
|
||||
return index < 1 ? time / (15000 / getTimeInBPM(time)) : getTimeWithBPMInSteps(time, index, getTimeWithIndexInBPM(time, index));
|
||||
}
|
||||
|
||||
@:noCompletion
|
||||
@:haxe.warning("-WDeprecated")
|
||||
public static inline function getStepForTime(time:Float):Float return getTimeInSteps(time);
|
||||
|
||||
public static function getStepsWithBPMInTime(stepTime:Float, index:Int, bpm:Float):Float {
|
||||
var bpmChange = bpmChangeMap[index];
|
||||
if (bpmChange.continuous && stepTime > bpmChange.stepTime && index > 0) {
|
||||
var prevBPM = bpmChangeMap[index - 1].bpm;
|
||||
var time = bpmChange.songTime + (stepTime - bpmChange.stepTime) / (bpm - prevBPM) * Math.log(bpm / prevBPM) * 15000;
|
||||
if (time > bpmChange.endSongTime)
|
||||
return (15000 * (stepTime - bpmChange.stepTime) - ((bpmChange.endSongTime - bpmChange.songTime) * (bpm - prevBPM))
|
||||
/ Math.log(bpm / prevBPM)) / bpm + bpmChange.endSongTime;
|
||||
else
|
||||
return time;
|
||||
}
|
||||
else {
|
||||
return bpmChange.songTime + (stepTime - bpmChange.stepTime) * (15000 / bpm);
|
||||
}
|
||||
}
|
||||
|
||||
public static function getMeasuresInTime(measureTime:Float, from:Int = 0):Float {
|
||||
var index = getMeasuresInChangeIndex(measureTime, from);
|
||||
if (index == -1) return measureTime * (15000 / dummyChange.bpm);
|
||||
else if (index == 0) return measureTime * (15000 / bpmChangeMap[index].bpm) * bpmChangeMap[index].stepsPerBeat * bpmChangeMap[index].beatsPerMeasure;
|
||||
else {
|
||||
var change = bpmChangeMap[index];
|
||||
var stepTime = change.stepTime + (measureTime - change.measureTime) * change.stepsPerBeat * change.beatsPerMeasure;
|
||||
return getStepsWithBPMInTime(stepTime, index, getStepsWithIndexInBPM(stepTime, index));
|
||||
}
|
||||
}
|
||||
|
||||
public static function getBeatsInTime(beatTime:Float, from:Int = 0):Float {
|
||||
var index = getBeatsInChangeIndex(beatTime, from);
|
||||
if (index == -1) return beatTime * (15000 / dummyChange.bpm);
|
||||
else if (index == 0) return beatTime * (15000 / bpmChangeMap[index].bpm) * bpmChangeMap[index].stepsPerBeat;
|
||||
else {
|
||||
var change = bpmChangeMap[index];
|
||||
var stepTime = change.stepTime + (beatTime - change.beatTime) * change.stepsPerBeat;
|
||||
return getStepsWithBPMInTime(stepTime, index, getStepsWithIndexInBPM(stepTime, index));
|
||||
}
|
||||
}
|
||||
|
||||
public static function getStepsInTime(stepTime:Float, from:Int = 0):Float {
|
||||
var index = getStepsInChangeIndex(stepTime, from);
|
||||
return index < 1 ? stepTime * (15000 / bpmChangeMap[index].bpm) : getStepsWithBPMInTime(stepTime, index, getStepsWithIndexInBPM(stepTime, index));
|
||||
}
|
||||
|
||||
@:noCompletion
|
||||
@:haxe.warning("-WDeprecated")
|
||||
public static inline function getTimeForStep(steps:Float):Float return getStepsInTime(steps);
|
||||
|
||||
public static inline function getMeasureLength()
|
||||
return stepsPerBeat * beatsPerMeasure;
|
||||
|
||||
public static inline function getMeasuresLength() {
|
||||
if (FlxG.sound.music == null) return 0.0;
|
||||
return getStepForTime(FlxG.sound.music.length) / getMeasureLength();
|
||||
var length = FlxG.sound.music.length;
|
||||
var index = getTimeInChangeIndex(length, bpmChangeMap.length - 1);
|
||||
var change = bpmChangeMap[index];
|
||||
return change.measureTime + (getTimeInBeats(length, index) - change.beatTime) / change.beatsPerMeasure;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ class ConductorInfo extends FramerateCategory {
|
||||
_text += '\n - ${Conductor.curStep} steps';
|
||||
_text += '\n - ${Conductor.curMeasure} measures';
|
||||
_text += '\nCurrent BPM: ${Conductor.bpm}';
|
||||
_text += '\nTime Signature: ${Conductor.beatsPerMeasure}/${Conductor.stepsPerBeat}';
|
||||
_text += '\nTime Signature: ${Conductor.beatsPerMeasure}/${Conductor.denominator}';
|
||||
|
||||
this.text.text = _text;
|
||||
super.__enterFrame(t);
|
||||
|
||||
@@ -443,7 +443,7 @@ class CoolUtil
|
||||
}
|
||||
|
||||
var bpm:Null<Float> = Std.parseFloat(musicInfo["BPM"]).getDefault(DefaultBPM);
|
||||
Conductor.changeBPM(bpm, beatsPerMeasure, stepsPerBeat);
|
||||
Conductor.changeBPM(bpm, beatsPerMeasure, floorInt(stepsPerBeat));
|
||||
} else
|
||||
Conductor.changeBPM(DefaultBPM);
|
||||
}
|
||||
@@ -781,6 +781,15 @@ class CoolUtil
|
||||
@:noUsing public static inline function maxInt(p1:Int, p2:Int)
|
||||
return p1 < p2 ? p2 : p1;
|
||||
|
||||
/**
|
||||
* Equivalent of `Math.min`, except doesn't require a Int -> Float -> Int conversion.
|
||||
* @param p1
|
||||
* @param p2
|
||||
* @return return p1 > p2 ? p2 : p1
|
||||
*/
|
||||
@:noUsing public static inline function minInt(p1:Int, p2:Int)
|
||||
return p1 > p2 ? p2 : p1;
|
||||
|
||||
/**
|
||||
* Equivalent of `Math.floor`, except doesn't require a Int -> Float -> Int conversion.
|
||||
* @param e Value to get the floor of.
|
||||
@@ -880,6 +889,36 @@ class CoolUtil
|
||||
return file.file;
|
||||
}
|
||||
|
||||
public static inline function bound(Value:Float, Min:Float, Max:Float):Float {
|
||||
#if cpp
|
||||
var _hx_tmp1:Float = Value;
|
||||
var _hx_tmp2:Float = Min;
|
||||
var _hx_tmp3:Float = Max;
|
||||
return untyped __cpp__("((({0}) < ({1})) ? ({1}) : (({0}) > ({2})) ? ({2}) : ({0}))", _hx_tmp1, _hx_tmp2, _hx_tmp3);
|
||||
#else
|
||||
return (Value < Min) ? Min : (Value > Max) ? Max : Value;
|
||||
#end
|
||||
}
|
||||
|
||||
public static inline function boundInt(Value:Int, Min:Int, Max:Int):Int {
|
||||
#if cpp
|
||||
var _hx_tmp1:Int = Value;
|
||||
var _hx_tmp2:Int = Min;
|
||||
var _hx_tmp3:Int = Max;
|
||||
return untyped __cpp__("((({0}) < ({1})) ? ({1}) : (({0}) > ({2})) ? ({2}) : ({0}))", _hx_tmp1, _hx_tmp2, _hx_tmp3);
|
||||
#else
|
||||
return (Value < Min) ? Min : (Value > Max) ? Max : Value;
|
||||
#end
|
||||
}
|
||||
|
||||
public static inline function boolToInt(b:Bool):Int {
|
||||
#if cpp
|
||||
return untyped __cpp__("(({0}) ? 1 : 0)", b);
|
||||
#else
|
||||
return b ? 1 : 0;
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a string of "1..3,5,7..9,8..5" into an array of numbers like [1,2,3,5,7,8,9,8,7,6,5]
|
||||
* @param input String to parse
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package funkin.backend.utils;
|
||||
|
||||
final class MathUtil {
|
||||
//Remind me to add the descriptions for the Wiki later - sen
|
||||
public static inline function lessThan(aVal:Float, bVal:Float, diff:Float = FlxMath.EPSILON):Bool {
|
||||
return aVal < bVal - diff;
|
||||
}
|
||||
|
||||
public static inline function lessThanEqual(aVal:Float, bVal:Float, diff:Float = FlxMath.EPSILON):Bool {
|
||||
return aVal <= bVal + diff;
|
||||
}
|
||||
|
||||
public static inline function greaterThan(aVal:Float, bVal:Float, diff:Float = FlxMath.EPSILON):Bool {
|
||||
return aVal > bVal + diff;
|
||||
}
|
||||
|
||||
public static inline function greaterThanEqual(aVal:Float, bVal:Float, diff:Float = FlxMath.EPSILON):Bool {
|
||||
return aVal >= bVal - diff;
|
||||
}
|
||||
|
||||
public static inline function equal(aVal:Float, bVal:Float, diff:Float = FlxMath.EPSILON):Bool {
|
||||
return Math.abs(aVal - bVal) <= diff;
|
||||
}
|
||||
|
||||
public static inline function notEqual(aVal:Float, bVal:Float, diff:Float = FlxMath.EPSILON):Bool {
|
||||
return Math.abs(aVal - bVal) > diff;
|
||||
}
|
||||
}
|
||||
@@ -613,6 +613,8 @@ class Charter extends UIState {
|
||||
gridBackdrops.bottomLimitY = __endStep * 40;
|
||||
eventsBackdrop.bottomSeparator.y = gridBackdrops.bottomLimitY-2;
|
||||
|
||||
CharterGridSeperatorBase.lastConductorSprY = Math.NEGATIVE_INFINITY;
|
||||
|
||||
updateWaveforms();
|
||||
}
|
||||
|
||||
@@ -846,6 +848,7 @@ class Charter extends UIState {
|
||||
s.cursor = BUTTON;
|
||||
}
|
||||
}
|
||||
checkSelectionForBPMUpdates();
|
||||
if (!(verticalChange == 0 && horizontalChange == 0)) {
|
||||
notesGroup.sortNotes(); eventsGroup.sortEvents();
|
||||
undos.addToUndo(CSelectionDrag(undoDrags));
|
||||
@@ -1037,11 +1040,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));
|
||||
@@ -1065,11 +1064,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));
|
||||
@@ -1256,12 +1251,13 @@ class Charter extends UIState {
|
||||
for (strumLine in strumLines.members) strumLine.vocals.pause();
|
||||
}
|
||||
|
||||
songPosInfo.text = '${CoolUtil.timeToStr(Conductor.songPosition)} / ${CoolUtil.timeToStr(songLength)}'
|
||||
+ '\nStep: ${curStep}'
|
||||
+ '\nBeat: ${curBeat}'
|
||||
+ '\nMeasure: ${curMeasure}'
|
||||
+ '\nBPM: ${Conductor.bpm}'
|
||||
+ '\nTime Signature: ${Conductor.beatsPerMeasure}/${Conductor.stepsPerBeat}';
|
||||
var curChange = Conductor.curChange;
|
||||
songPosInfo.text = '${CoolUtil.timeToStr(songPos)} / ${CoolUtil.timeToStr(songLength)}'
|
||||
+ '\nStep: $curStep'
|
||||
+ '\nBeat: $curBeat'
|
||||
+ '\nMeasure: $curMeasure'
|
||||
+ '\nBPM: ${(curChange != null && curChange.continuous && curChange.endSongTime > songPos) ? FlxMath.roundDecimal(Conductor.bpm, 3) : Conductor.bpm}'
|
||||
+ '\nTime Signature: ${Conductor.beatsPerMeasure}/${Conductor.denominator}';
|
||||
|
||||
if (charterCamera.zoom != (charterCamera.zoom = lerp(charterCamera.zoom, __camZoom, __firstFrame ? 1 : 0.125)))
|
||||
updateDisplaySprites();
|
||||
@@ -1441,6 +1437,8 @@ class Charter extends UIState {
|
||||
selection = sObjects;
|
||||
_edit_copy(_); // to fix stupid bugs
|
||||
|
||||
checkSelectionForBPMUpdates();
|
||||
|
||||
undos.addToUndo(CCreateSelection(sObjects.copy()));
|
||||
}
|
||||
|
||||
@@ -1486,6 +1484,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);
|
||||
@@ -1531,6 +1530,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);
|
||||
@@ -1643,11 +1643,9 @@ class Charter extends UIState {
|
||||
}
|
||||
function _view_showeventSecSeparator(t) {
|
||||
t.icon = (Options.charterShowSections = !Options.charterShowSections) ? 1 : 0;
|
||||
eventsBackdrop.eventSecSeparator.visible = gridBackdrops.sectionsVisible = Options.charterShowSections;
|
||||
}
|
||||
function _view_showeventBeatSeparator(t) {
|
||||
t.icon = (Options.charterShowBeats = !Options.charterShowBeats) ? 1 : 0;
|
||||
eventsBackdrop.eventBeatSeparator.visible = gridBackdrops.beatsVisible = Options.charterShowBeats;
|
||||
}
|
||||
function _view_switchWaveformDetail(t) {
|
||||
t.icon = (Options.charterLowDetailWaveforms = !Options.charterLowDetailWaveforms) ? 1 : 0;
|
||||
@@ -1847,14 +1845,30 @@ class Charter extends UIState {
|
||||
}
|
||||
|
||||
public function updateBPMEvents() {
|
||||
eventsGroup.sortEvents();
|
||||
Conductor.mapCharterBPMChanges(PlayState.SONG);
|
||||
buildEvents();
|
||||
|
||||
Conductor.mapBPMChanges(PlayState.SONG);
|
||||
Conductor.changeBPM(PlayState.SONG.meta.bpm, cast PlayState.SONG.meta.beatsPerMeasure.getDefault(4), cast PlayState.SONG.meta.stepsPerBeat.getDefault(4));
|
||||
|
||||
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;
|
||||
|
||||
|
||||
@@ -12,8 +12,6 @@ class CharterBackdropGroup extends FlxTypedGroup<CharterBackdrop> {
|
||||
|
||||
public var conductorSprY:Float = 0;
|
||||
public var bottomLimitY:Float = 0;
|
||||
public var sectionsVisible:Bool = true;
|
||||
public var beatsVisible:Bool = true;
|
||||
|
||||
// Just here so you can update display sprites all dat and above
|
||||
public var strumlinesAmount:Int = 0;
|
||||
@@ -61,8 +59,6 @@ class CharterBackdropGroup extends FlxTypedGroup<CharterBackdrop> {
|
||||
|
||||
grid.conductorFollowerSpr.y = conductorSprY;
|
||||
grid.bottomSeparator.y = (grid.bottomLimit.y = bottomLimitY)-2;
|
||||
grid.sectionSeparator.visible = sectionsVisible;
|
||||
grid.beatSeparator.visible = beatsVisible;
|
||||
|
||||
grid.waveformSprite.shader = strumLine.waveformShader;
|
||||
|
||||
@@ -119,8 +115,7 @@ class CharterBackdrop extends FlxTypedGroup<Dynamic> {
|
||||
public var waveformSprite:FlxSprite;
|
||||
|
||||
public var conductorFollowerSpr:FlxSprite;
|
||||
public var beatSeparator:FlxBackdrop;
|
||||
public var sectionSeparator:FlxBackdrop;
|
||||
public var beatSeparator:CharterGridSeperator;
|
||||
|
||||
public var notesGroup:FlxTypedGroup<CharterNote> = new FlxTypedGroup<CharterNote>();
|
||||
public var strumLine:CharterStrumline;
|
||||
@@ -136,23 +131,14 @@ class CharterBackdrop extends FlxTypedGroup<Dynamic> {
|
||||
waveformSprite.updateHitbox();
|
||||
add(waveformSprite);
|
||||
|
||||
sectionSeparator = new FlxBackdrop(null, Y, 0, 0);
|
||||
sectionSeparator.y = -2;
|
||||
sectionSeparator.visible = Options.charterShowSections;
|
||||
|
||||
beatSeparator = new FlxBackdrop(null, Y, 0, 0);
|
||||
beatSeparator.y = -1;
|
||||
beatSeparator.visible = Options.charterShowBeats;
|
||||
|
||||
for(sep in [sectionSeparator, beatSeparator]) {
|
||||
sep.makeSolid(1, 1, -1);
|
||||
sep.alpha = 0.5;
|
||||
sep.scrollFactor.set(1, 1);
|
||||
sep.scale.set((4 * 40), sep == sectionSeparator ? 4 : 2);
|
||||
sep.updateHitbox();
|
||||
}
|
||||
beatSeparator = new CharterGridSeperator();
|
||||
beatSeparator.makeSolid(1, 1, -1);
|
||||
beatSeparator.alpha = 0.5;
|
||||
beatSeparator.scrollFactor.set(1, 1);
|
||||
beatSeparator.scale.set((4 * 40), 2);
|
||||
beatSeparator.updateHitbox();
|
||||
add(beatSeparator);
|
||||
add(sectionSeparator);
|
||||
|
||||
add(notesGroup);
|
||||
|
||||
bottomSeparator = new FlxSprite(0,-2);
|
||||
@@ -200,15 +186,12 @@ class CharterBackdrop extends FlxTypedGroup<Dynamic> {
|
||||
alpha = strumLine.strumLine.visible ? 0.9 : 0.4;
|
||||
} else alpha = 0.9;
|
||||
|
||||
for (spr in [gridBackDrop, sectionSeparator, beatSeparator, topLimit, bottomLimit,
|
||||
for (spr in [gridBackDrop, beatSeparator, topLimit, bottomLimit,
|
||||
topSeparator, bottomSeparator, conductorFollowerSpr, waveformSprite]) {
|
||||
spr.x = x; if (spr != waveformSprite) spr.alpha = alpha;
|
||||
spr.cameras = this.cameras;
|
||||
}
|
||||
|
||||
sectionSeparator.spacing.y = (10 * Conductor.beatsPerMeasure * Conductor.stepsPerBeat) - 1;
|
||||
beatSeparator.spacing.y = (20 * Conductor.stepsPerBeat) - 1;
|
||||
|
||||
topLimit.scale.set(4 * 40, Math.ceil(FlxG.height / cameras[0].zoom));
|
||||
topLimit.updateHitbox();
|
||||
topLimit.y = -topLimit.height;
|
||||
@@ -237,6 +220,159 @@ class CharterBackdrop extends FlxTypedGroup<Dynamic> {
|
||||
}
|
||||
}
|
||||
|
||||
class CharterGridSeperatorBase extends FlxSprite {
|
||||
|
||||
private static var minStep:Float = 0;
|
||||
private static var maxStep:Float = 0;
|
||||
|
||||
private static var minBeat:Float = 0;
|
||||
private static var maxBeat:Float = 0;
|
||||
|
||||
private static var minMeasure:Float = 0;
|
||||
private static var maxMeasure:Float = 0;
|
||||
|
||||
private static var lastMinBeat:Float = -1;
|
||||
private static var lastMaxBeat:Float = -1;
|
||||
|
||||
private static var lastMinMeasure:Float = -1;
|
||||
private static var lastMaxMeasure:Float = -1;
|
||||
|
||||
public static var lastConductorSprY:Float = Math.NEGATIVE_INFINITY;
|
||||
|
||||
private static var beatStepTimes:Array<Float> = [];
|
||||
private static var measureStepTimes:Array<Float> = [];
|
||||
private static var timeSignatureChangeGaps:Array<Float> = [];
|
||||
|
||||
private function recalculateBeats() {
|
||||
var conductorSprY = Charter.instance.gridBackdrops.conductorSprY;
|
||||
if (conductorSprY == lastConductorSprY) return;
|
||||
|
||||
var zoomOffset = ((FlxG.height * (1/cameras[0].zoom)) * 0.5);
|
||||
|
||||
minStep = (conductorSprY - zoomOffset)/40;
|
||||
maxStep = (conductorSprY + zoomOffset)/40;
|
||||
|
||||
var minTime:Float = Conductor.getStepsInTime(minStep);
|
||||
var maxTime:Float = Conductor.getStepsInTime(maxStep);
|
||||
|
||||
var minBpmChange = Conductor.bpmChangeMap[Conductor.getTimeInChangeIndex(minTime)];
|
||||
var maxBpmChange = Conductor.bpmChangeMap[Conductor.getTimeInChangeIndex(maxTime)];
|
||||
|
||||
minBeat = Conductor.getTimeInBeats(minTime);
|
||||
maxBeat = Conductor.getTimeInBeats(maxTime);
|
||||
|
||||
minMeasure = minBpmChange.measureTime + (minBeat - minBpmChange.beatTime) / minBpmChange.beatsPerMeasure;
|
||||
maxMeasure = maxBpmChange.measureTime + (maxBeat - maxBpmChange.beatTime) / maxBpmChange.beatsPerMeasure;
|
||||
|
||||
//cap out the beats/measures at the end of the song
|
||||
var endTime = Conductor.getStepsInTime(Charter.instance.__endStep);
|
||||
var endBeat = Conductor.getTimeInBeats(endTime);
|
||||
var endBpmChange = Conductor.bpmChangeMap[Conductor.getTimeInChangeIndex(endTime)];
|
||||
var endMeasure = endBpmChange.measureTime + (endBeat - endBpmChange.beatTime) / endBpmChange.beatsPerMeasure;
|
||||
|
||||
if (maxBeat > endBeat) maxBeat = endBeat;
|
||||
if (maxMeasure > endMeasure) maxMeasure = endMeasure;
|
||||
if (minMeasure < 0) minMeasure = 0;
|
||||
if (minBeat < 0) minBeat = 0;
|
||||
|
||||
//only calculate if needed
|
||||
if ((minBeat != lastMinBeat) || (maxBeat != lastMaxBeat) || (minMeasure != lastMinMeasure) || (maxMeasure != lastMaxMeasure) || lastConductorSprY == Math.NEGATIVE_INFINITY) {
|
||||
calculateTimeSignatureGaps();
|
||||
calculateStepTimes();
|
||||
lastMinBeat = minBeat;
|
||||
lastMaxBeat = maxBeat;
|
||||
lastMinMeasure = minMeasure;
|
||||
lastMaxMeasure = maxMeasure;
|
||||
}
|
||||
|
||||
lastConductorSprY = conductorSprY;
|
||||
}
|
||||
|
||||
private inline function calculateTimeSignatureGaps() {
|
||||
//for time signatures that start mid step
|
||||
timeSignatureChangeGaps.splice(0, timeSignatureChangeGaps.length);
|
||||
for (i => change in Conductor.bpmChangeMap) {
|
||||
if (change.stepTime >= minStep && change.stepTime <= maxStep) {
|
||||
//get step while ignoring the current change
|
||||
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 && (step - Math.floor(step)) > FlxMath.EPSILON) { //mid step change
|
||||
timeSignatureChangeGaps.push(step);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private inline function calculateStepTimes() {
|
||||
beatStepTimes.splice(0, beatStepTimes.length);
|
||||
for (i in Math.floor(minBeat)...Math.ceil(maxBeat)) {
|
||||
beatStepTimes.push(Conductor.getTimeInSteps(Conductor.getBeatsInTime(i)));
|
||||
}
|
||||
measureStepTimes.splice(0, measureStepTimes.length);
|
||||
for (i in Math.floor(minMeasure)...Math.ceil(maxMeasure)) {
|
||||
measureStepTimes.push(Conductor.getTimeInSteps(Conductor.getMeasuresInTime(i)));
|
||||
}
|
||||
}
|
||||
|
||||
override public function draw() {
|
||||
|
||||
//should only need to recalculate once per frame and will be shared across each instance
|
||||
recalculateBeats();
|
||||
|
||||
drawTimeSignatureChangeGaps();
|
||||
|
||||
if (Options.charterShowBeats) drawBeats();
|
||||
if (Options.charterShowSections) drawMeasures();
|
||||
}
|
||||
|
||||
private function drawBeats(offset:Float = 0.0) {
|
||||
for (i in beatStepTimes) {
|
||||
y = (i*40)+offset;
|
||||
super.draw();
|
||||
}
|
||||
}
|
||||
private function drawMeasures(offset:Float = 0.0) {
|
||||
for (i in measureStepTimes) {
|
||||
y = (i*40)+offset;
|
||||
super.draw();
|
||||
}
|
||||
}
|
||||
private function drawTimeSignatureChangeGaps() {
|
||||
if (timeSignatureChangeGaps.length == 0) return;
|
||||
var prevColor = color;
|
||||
var prevBlend = blend;
|
||||
|
||||
color = 0xFF888888;
|
||||
blend = MULTIPLY;
|
||||
|
||||
for (step in timeSignatureChangeGaps) {
|
||||
y = step*40;
|
||||
var diff = Math.ceil(step) - step;
|
||||
scale.y = diff*40;
|
||||
updateHitbox();
|
||||
|
||||
super.draw();
|
||||
}
|
||||
|
||||
color = prevColor;
|
||||
blend = prevBlend;
|
||||
}
|
||||
}
|
||||
|
||||
class CharterGridSeperator extends CharterGridSeperatorBase {
|
||||
override private function drawBeats(offset:Float = 0.0) {
|
||||
scale.y = 2;
|
||||
updateHitbox();
|
||||
super.drawBeats(-2);
|
||||
}
|
||||
override private function drawMeasures(offset:Float = 0.0) {
|
||||
scale.y = 4;
|
||||
updateHitbox();
|
||||
super.drawMeasures(-3);
|
||||
}
|
||||
}
|
||||
|
||||
class CharterBackdropDummy extends UISprite {
|
||||
var parent:CharterBackdropGroup;
|
||||
public function new(parent:CharterBackdropGroup) {
|
||||
@@ -258,8 +394,7 @@ class CharterBackdropDummy extends UISprite {
|
||||
}
|
||||
|
||||
class EventBackdrop extends FlxBackdrop {
|
||||
public var eventBeatSeparator:FlxBackdrop;
|
||||
public var eventSecSeparator:FlxBackdrop;
|
||||
public var eventBeatSeparator:CharterEventGridSeperator;
|
||||
|
||||
public var topSeparator:FlxSprite;
|
||||
public var bottomSeparator:FlxSprite;
|
||||
@@ -269,25 +404,10 @@ class EventBackdrop extends FlxBackdrop {
|
||||
alpha = 0.9;
|
||||
|
||||
// Separators
|
||||
eventSecSeparator = new FlxBackdrop(null, Y, 0, 0);
|
||||
eventSecSeparator.y = -2;
|
||||
eventSecSeparator.visible = Options.charterShowSections;
|
||||
|
||||
eventBeatSeparator = new FlxBackdrop(null, Y, 0, 0);
|
||||
eventBeatSeparator.y = -1;
|
||||
eventBeatSeparator.visible = Options.charterShowBeats;
|
||||
|
||||
for(sep in [eventSecSeparator, eventBeatSeparator]) {
|
||||
sep.makeSolid(1, 1, -1);
|
||||
sep.alpha = 0.5;
|
||||
sep.scrollFactor.set(1, 1);
|
||||
}
|
||||
|
||||
eventSecSeparator.scale.set(20, 4);
|
||||
eventSecSeparator.updateHitbox();
|
||||
|
||||
eventBeatSeparator.scale.set(10, 2);
|
||||
eventBeatSeparator.updateHitbox();
|
||||
eventBeatSeparator = new CharterEventGridSeperator();
|
||||
eventBeatSeparator.makeSolid(1, 1, -1);
|
||||
eventBeatSeparator.alpha = 0.5;
|
||||
eventBeatSeparator.scrollFactor.set(1, 1);
|
||||
|
||||
bottomSeparator = new FlxSprite(0,-2);
|
||||
bottomSeparator.makeSolid(1, 1, -1);
|
||||
@@ -308,23 +428,32 @@ class EventBackdrop extends FlxBackdrop {
|
||||
public override function draw() {
|
||||
super.draw();
|
||||
|
||||
eventSecSeparator.spacing.y = (10 * Conductor.beatsPerMeasure * Conductor.stepsPerBeat) - 1;
|
||||
eventBeatSeparator.spacing.y = (20 * Conductor.stepsPerBeat) - 1;
|
||||
|
||||
eventSecSeparator.cameras = cameras;
|
||||
eventSecSeparator.x = (x+width) - 20;
|
||||
if (eventSecSeparator.visible) eventSecSeparator.draw();
|
||||
|
||||
eventBeatSeparator.cameras = cameras;
|
||||
eventBeatSeparator.x = (x+width) - 10;
|
||||
if (eventBeatSeparator.visible) eventBeatSeparator.draw();
|
||||
eventBeatSeparator.xPos = x+width;
|
||||
eventBeatSeparator.draw();
|
||||
|
||||
topSeparator.x = (x+width) - 20;
|
||||
topSeparator.cameras = this.cameras;
|
||||
if (!eventSecSeparator.visible) topSeparator.draw();
|
||||
if (!Options.charterShowSections) topSeparator.draw();
|
||||
|
||||
bottomSeparator.x = (x+width) - 20;
|
||||
bottomSeparator.cameras = this.cameras;
|
||||
bottomSeparator.draw();
|
||||
}
|
||||
}
|
||||
class CharterEventGridSeperator extends CharterGridSeperatorBase {
|
||||
public var xPos:Float = 0.0;
|
||||
override private function drawBeats(offset:Float = 0.0) {
|
||||
scale.set(10, 2);
|
||||
updateHitbox();
|
||||
x = xPos-10;
|
||||
super.drawBeats(-2);
|
||||
}
|
||||
override private function drawMeasures(offset:Float = 0.0) {
|
||||
scale.set(20, 4);
|
||||
updateHitbox();
|
||||
x = xPos-20;
|
||||
super.drawMeasures(-3);
|
||||
}
|
||||
override private function drawTimeSignatureChangeGaps() {}
|
||||
}
|
||||
@@ -1,12 +1,18 @@
|
||||
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;
|
||||
import funkin.backend.chart.ChartData.ChartEvent;
|
||||
import funkin.backend.scripting.DummyScript;
|
||||
import funkin.backend.scripting.Script;
|
||||
import funkin.editors.charter.Charter.ICharterSelectable;
|
||||
import funkin.editors.charter.CharterBackdropGroup.EventBackdrop;
|
||||
import funkin.game.Character;
|
||||
import funkin.game.HealthIcon;
|
||||
import openfl.display.BitmapData;
|
||||
|
||||
class CharterEvent extends UISliceSprite implements ICharterSelectable {
|
||||
public var events:Array<ChartEvent>;
|
||||
@@ -45,19 +51,78 @@ class CharterEvent extends UISliceSprite implements ICharterSelectable {
|
||||
sprite.colorTransform = colorTransform;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pack data is a list of 4 strings separated by `________PACKSEP________`
|
||||
* [0] Event Name
|
||||
* [1] Event Script
|
||||
* [2] Event JSON Info
|
||||
* [3] Event Icon
|
||||
* [4] Event UI Script / Icon Script
|
||||
**/
|
||||
@:dox(hide) public static function getPackData(name:String):Array<String> {
|
||||
var packFile = Paths.pack('events/${name}');
|
||||
if (Assets.exists(packFile)) {
|
||||
return Assets.getText(packFile).split('________PACKSEP________');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@:dox(hide) public static function getUIScript(event:ChartEvent, caller:String):Script {
|
||||
var uiScript = Paths.script('data/events/${event.name}.ui');
|
||||
var script:Script = null;
|
||||
if(Assets.exists(uiScript)) {
|
||||
script = Script.create(uiScript);
|
||||
} else {
|
||||
var packData = getPackData(event.name);
|
||||
if(packData != null) {
|
||||
var scriptFile = packData[4];
|
||||
if(scriptFile != null) {
|
||||
script = Script.fromString(scriptFile, uiScript);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(script != null && !(script is DummyScript)) {
|
||||
// classes and functions
|
||||
script.set("EventIconGroup", EventIconGroup); // automatically imported
|
||||
script.set("EventNumber", EventNumber); // automatically imported
|
||||
script.set("getIconFromStrumline", getIconFromStrumline);
|
||||
script.set("getIconFromCharName", getIconFromCharName);
|
||||
script.set("generateDefaultIcon", generateDefaultIcon);
|
||||
script.set("generateEventIconDurationArrow", generateEventIconDurationArrow);
|
||||
script.set("generateEventIconNumbers", generateEventIconNumbers);
|
||||
script.set("generateEventIconWarning", generateEventIconWarning);
|
||||
script.set("getPackData", getPackData);
|
||||
script.set("getEventComponent", getEventComponent);
|
||||
// data
|
||||
script.set("event", event);
|
||||
script.set("caller", caller);
|
||||
|
||||
script.load();
|
||||
}
|
||||
|
||||
return script;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the default event icon for the wanted event
|
||||
* @param name The name of the event
|
||||
* @return The icon
|
||||
**/
|
||||
private static function generateDefaultIcon(name:String) {
|
||||
var isBase64:Bool = false;
|
||||
var path:String = Paths.image('editors/charter/event-icons/$name');
|
||||
var defaultPath = Paths.image('editors/charter/event-icons/Unknown');
|
||||
if (!Assets.exists(path)) path = defaultPath;
|
||||
if(!Assets.exists(path)) {
|
||||
path = defaultPath;
|
||||
|
||||
var packPath = Paths.pack('events/$name');
|
||||
if (Assets.exists(packPath)) {
|
||||
var packText = Assets.getText(packPath).split('________PACKSEP________');
|
||||
var packImg = packText[3];
|
||||
if(packImg != null && packImg.length > 0) {
|
||||
isBase64 = !packImg.startsWith("assets/");
|
||||
path = packImg;
|
||||
var packData = getPackData(name);
|
||||
if(packData != null) {
|
||||
var packImg = packData[3];
|
||||
if(packImg != null && packImg.length > 0) {
|
||||
isBase64 = !packImg.startsWith("assets/");
|
||||
path = packImg;
|
||||
}
|
||||
}
|
||||
}
|
||||
path = path.trim();
|
||||
@@ -75,27 +140,177 @@ class CharterEvent extends UISliceSprite implements ICharterSelectable {
|
||||
graphic = defaultPath;
|
||||
}
|
||||
|
||||
return new FlxSprite().loadGraphic(graphic);
|
||||
return new FlxSprite(graphic);
|
||||
}
|
||||
|
||||
public static function generateEventIcon(event:ChartEvent) {
|
||||
return switch(event.name) {
|
||||
default:
|
||||
generateDefaultIcon(event.name);
|
||||
case "Camera Movement":
|
||||
// custom icon for camera movement
|
||||
var state:Charter = cast FlxG.state;
|
||||
if (event.params != null && event.params[0] != null && event.params[0] >= 0 && event.params[0] < state.strumLines.length) {
|
||||
// camera movement, use health icon
|
||||
var icon = Character.getIconFromCharName(state.strumLines.members[event.params[0]].strumLine.characters[0]);
|
||||
var healthIcon = new HealthIcon(icon);
|
||||
healthIcon.setUnstretchedGraphicSize(32, 32, false);
|
||||
healthIcon.scrollFactor.set(1, 1);
|
||||
healthIcon.active = false;
|
||||
healthIcon;
|
||||
} else
|
||||
generateDefaultIcon(event.name);
|
||||
/**
|
||||
* Gets a component sprite from the editors/charter/event-icons/components folder
|
||||
* If you wanna use a number, please use the EventNumber class instead
|
||||
* @param type The type of component to get
|
||||
* @param x The x position of the sprite (optional)
|
||||
* @param y The y position of the sprite (optional)
|
||||
* @return The component sprite
|
||||
**/
|
||||
public static function getEventComponent(type:String, x:Float = 0.0, y:Float = 0.0) {
|
||||
var componentPath = Paths.image("editors/charter/event-icons/components/" + type);
|
||||
if(Assets.exists(componentPath))
|
||||
return new FlxSprite(x, y, componentPath);
|
||||
|
||||
Logs.trace('Could not find component $type', WARNING);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expected to be called from inside of a ui script,
|
||||
* calling this elsewhere might cause unexpected results or crashes
|
||||
**/
|
||||
public static function getIconFromStrumline(index:Null<Int>) {
|
||||
var state = cast(FlxG.state, Charter);
|
||||
if (index != null && index >= 0 && index < state.strumLines.length) {
|
||||
return getIconFromCharName(state.strumLines.members[index].strumLine.characters[0]);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function getIconFromCharName(name:String) {
|
||||
var icon = Character.getIconFromCharName(name);
|
||||
var healthIcon = new HealthIcon(icon);
|
||||
CoolUtil.setUnstretchedGraphicSize(healthIcon, 32, 32, false);
|
||||
healthIcon.scrollFactor.set(1, 1);
|
||||
healthIcon.active = false;
|
||||
return healthIcon;
|
||||
}
|
||||
|
||||
public static function generateEventIcon(event:ChartEvent, inMenu:Bool = true):FlxSprite {
|
||||
var script = getUIScript(event, "event-icon");
|
||||
if(script != null && !(script is DummyScript)) {
|
||||
script.set("inMenu", inMenu);
|
||||
if(script.get("generateIcon") != null) {
|
||||
var res:FlxSprite = script.call("generateIcon");
|
||||
if(res != null)
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
switch(event.name) {
|
||||
case "Time Signature Change":
|
||||
if(event.params != null && (event.params[0] >= 0 || event.params[1] >= 0)) {
|
||||
var group = new EventIconGroup();
|
||||
group.add(generateDefaultIcon(event.name));
|
||||
group.add({ // top
|
||||
var num = new EventNumber(9, -1, event.params[0], EventNumber.ALIGN_CENTER);
|
||||
num.active = false;
|
||||
num;
|
||||
});
|
||||
group.add({ // bottom
|
||||
var num = new EventNumber(9, 10, event.params[1], EventNumber.ALIGN_CENTER);
|
||||
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) {
|
||||
var group = new EventIconGroup();
|
||||
group.add(generateDefaultIcon("BPM Change Start"));
|
||||
if (!inMenu) {
|
||||
generateEventIconDurationArrow(group, event.params[1]);
|
||||
group.members[0].y -= 2;
|
||||
generateEventIconNumbers(group, event.params[0], 3);
|
||||
}
|
||||
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) {
|
||||
var group = new EventIconGroup();
|
||||
group.add(generateDefaultIcon(event.name));
|
||||
if (!inMenu) {
|
||||
group.members[0].y -= 2;
|
||||
generateEventIconNumbers(group, event.params[0], 3);
|
||||
}
|
||||
if (Conductor.invalidEvents.contains(event)) generateEventIconWarning(group);
|
||||
return group;
|
||||
}
|
||||
|
||||
case "Scroll Speed Change":
|
||||
if(event.params != null && !inMenu) {
|
||||
var group = new EventIconGroup();
|
||||
group.add(generateDefaultIcon(event.name));
|
||||
if (event.params[0]) generateEventIconDurationArrow(group, event.params[2]);
|
||||
group.members[0].y -= 2;
|
||||
generateEventIconNumbers(group, event.params[1]);
|
||||
return group;
|
||||
}
|
||||
|
||||
case "Camera Movement":
|
||||
// camera movement, use health icon
|
||||
if(event.params != null) {
|
||||
var icon = getIconFromStrumline(event.params[0]);
|
||||
if(icon != null) return icon;
|
||||
}
|
||||
}
|
||||
return generateDefaultIcon(event.name);
|
||||
}
|
||||
|
||||
private static function generateEventIconNumbers(group:EventIconGroup, number:Float, x:Float = 4, y:Float = 15, spacing:Float = 5, precision:Int = 3) {
|
||||
group.add({
|
||||
var num = new EventNumber(x, y, number, EventNumber.ALIGN_CENTER, spacing, precision);
|
||||
if (num.numWidth > 20) {
|
||||
num.scale.x = num.scale.y = 20 / num.numWidth;
|
||||
}
|
||||
num.active = false;
|
||||
num;
|
||||
});
|
||||
}
|
||||
|
||||
private static function generateEventIconDurationArrow(group:EventIconGroup, stepDuration:Float) {
|
||||
//var group = new EventIconGroup();
|
||||
//group.add(generateDefaultIcon(startIcon));
|
||||
|
||||
var xOffset = 4;
|
||||
var yGap = 24;
|
||||
var endGap = 2;
|
||||
|
||||
if (stepDuration >= 0.55) { //min time for showing arrow
|
||||
var tail = new FlxSprite(xOffset, yGap);
|
||||
var arrow = new FlxSprite(xOffset, (stepDuration * 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 = endGap + (stepDuration * 40) - (tail.height + yGap);
|
||||
arrowSegment.updateHitbox();
|
||||
arrowSegment.y += tail.height;
|
||||
arrowSegment;
|
||||
});
|
||||
|
||||
group.add({
|
||||
arrow.animation.addByPrefix("arrow", "arrow");
|
||||
arrow.animation.play("arrow");
|
||||
arrow;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
@@ -123,18 +338,150 @@ 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") {
|
||||
draggable = false;
|
||||
break;
|
||||
}
|
||||
|
||||
x = (snappedToGrid && eventsBackdrop != null ? eventsBackdrop.x : 0) - (bWidth = 37 + (icons.length * 22));
|
||||
}
|
||||
}
|
||||
|
||||
class EventIconGroup extends FlxSpriteGroup {
|
||||
public var forceWidth:Float = 16;
|
||||
public var forceHeight:Float = 16;
|
||||
public var dontTransformChildren:Bool = true;
|
||||
public var copyColorTransformToChildren:Bool = true;
|
||||
|
||||
public function new() {
|
||||
super();
|
||||
scrollFactor.set(1, 1);
|
||||
}
|
||||
|
||||
override function preAdd(sprite:FlxSprite):Void
|
||||
{
|
||||
super.preAdd(sprite);
|
||||
sprite.scrollFactor.set(1, 1);
|
||||
}
|
||||
|
||||
public override function transformChildren<V>(Function:FlxSprite->V->Void, Value:V):Void
|
||||
{
|
||||
if (dontTransformChildren)
|
||||
return;
|
||||
|
||||
super.transformChildren(Function, Value);
|
||||
}
|
||||
|
||||
override function set_x(Value:Float):Float
|
||||
{
|
||||
if (exists && x != Value)
|
||||
transformChildren(xTransform, Value - x); // offset
|
||||
return x = Value;
|
||||
}
|
||||
|
||||
override function set_y(Value:Float):Float
|
||||
{
|
||||
if (exists && y != Value)
|
||||
transformChildren(yTransform, Value - y); // offset
|
||||
return y = Value;
|
||||
}
|
||||
|
||||
override function get_width() {
|
||||
return forceWidth;
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
override function update(elapsed:Float) {
|
||||
super.update(elapsed);
|
||||
}
|
||||
}
|
||||
|
||||
class EventNumber extends FlxSprite {
|
||||
public static inline final ALIGN_NORMAL:Int = 0;
|
||||
public static inline final ALIGN_CENTER:Int = 1;
|
||||
|
||||
public var digits:Array<Int> = [];
|
||||
public static inline final FRAME_POINT:Int = 10;
|
||||
public static inline final FRAME_NEGATIVE:Int = 11;
|
||||
|
||||
public var align:Int = ALIGN_NORMAL;
|
||||
public var spacing:Float = 6;
|
||||
|
||||
public function new(x:Float, y:Float, number:Float, ?align:Int, spacing:Float = 6, precision:Int = 3) {
|
||||
super(x, y);
|
||||
this.digits = [];
|
||||
this.align = align == null ? ALIGN_NORMAL : align;
|
||||
this.spacing = spacing;
|
||||
|
||||
if (number == 0) {
|
||||
this.digits.insert(0, 0);
|
||||
}
|
||||
else {
|
||||
var decimals:Float = FlxMath.roundDecimal(Math.abs(number % 1), precision);
|
||||
if (decimals > 0) this.digits.insert(0, FRAME_POINT);
|
||||
while(decimals > 0) {
|
||||
this.digits.push(Math.floor(decimals * 10));
|
||||
decimals = FlxMath.roundDecimal((decimals * 10) % 1, precision);
|
||||
}
|
||||
|
||||
var ints = Std.int(Math.abs(number));
|
||||
if (ints == 0) this.digits.insert(0, 0);
|
||||
while (ints > 0) {
|
||||
this.digits.insert(0, ints % 10);
|
||||
ints = Std.int(ints / 10);
|
||||
}
|
||||
|
||||
if (number < 0) {
|
||||
this.digits.insert(0, FRAME_NEGATIVE);
|
||||
}
|
||||
}
|
||||
|
||||
loadGraphic(Paths.image('editors/charter/event-icons/components/eventNums'), true, 6, 7);
|
||||
}
|
||||
|
||||
override function update(elapsed:Float) {
|
||||
super.update(elapsed);
|
||||
}
|
||||
|
||||
override function draw() {
|
||||
var baseX = x;
|
||||
var offsetX = 0.0;
|
||||
if(align == ALIGN_CENTER) offsetX = -(digits.length - 1) * spacing * Math.abs(scale.x) / 2;
|
||||
|
||||
x = baseX + offsetX;
|
||||
for (i in 0...digits.length) {
|
||||
frame = frames.frames[digits[i]];
|
||||
super.draw();
|
||||
x += spacing * Math.abs(scale.x);
|
||||
}
|
||||
x = baseX;
|
||||
}
|
||||
|
||||
public var numWidth(get, never):Float;
|
||||
private function get_numWidth():Float {
|
||||
return Math.abs(scale.x) * spacing * digits.length;
|
||||
}
|
||||
public var numHeight(get, never):Float;
|
||||
private function get_numHeight():Float {
|
||||
return Math.abs(scale.y) * frameHeight;
|
||||
}
|
||||
|
||||
public override function updateHitbox():Void {
|
||||
var numWidth = this.numWidth;
|
||||
var numHeight = this.numHeight;
|
||||
width = numWidth;
|
||||
height = numHeight;
|
||||
offset.set(-0.5 * (numWidth - spacing * digits.length), -0.5 * (numHeight - frameHeight));
|
||||
centerOrigin();
|
||||
}
|
||||
}
|
||||
@@ -57,7 +57,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);
|
||||
@@ -82,12 +82,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;
|
||||
@@ -112,23 +112,17 @@ class CharterEventScreen extends UISubstateWindow {
|
||||
|
||||
// destroy old elements
|
||||
paramsFields = [];
|
||||
for(e in paramsPanel) {
|
||||
e.destroy();
|
||||
paramsPanel.remove(e);
|
||||
while(paramsPanel.members.length > 0) {
|
||||
var e = paramsPanel.members.pop();
|
||||
if(e != null) {
|
||||
e.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
if (id >= 0 && id < events.length) {
|
||||
curEvent = id;
|
||||
var curEvent = events[curEvent];
|
||||
eventName.text = curEvent.name;
|
||||
// add new elements
|
||||
var y:Float = eventName.y + eventName.height + 10;
|
||||
for(k=>param in EventsData.getEventParams(curEvent.name)) {
|
||||
function addLabel() {
|
||||
var label:UIText = new UIText(eventName.x, y, 0, param.name);
|
||||
y += label.height + 4;
|
||||
paramsPanel.add(label);
|
||||
};
|
||||
|
||||
var value:Dynamic = CoolUtil.getDefault(curEvent.params[k], param.defValue);
|
||||
var lastAdded = switch(param.type) {
|
||||
@@ -199,6 +193,88 @@ class CharterEventScreen extends UISubstateWindow {
|
||||
}
|
||||
}
|
||||
|
||||
function generateEventUI(event:ChartEvent):Void {
|
||||
var script = CharterEvent.getUIScript(event, "event-ui");
|
||||
if(script != null && !(script is DummyScript)) {
|
||||
script.set("paramsPanel", paramsPanel);
|
||||
script.set("paramsFields", paramsFields);
|
||||
if(script.get("generateUI") != null) {
|
||||
if(script.call("generateUI") == false)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// add new elements
|
||||
var _y:Float = eventName.y + eventName.height + 10;
|
||||
var params = EventsData.getEventParams(event.name);
|
||||
for(k=>param in params) {
|
||||
var x = eventName.x + (param.x == null ? 0 : param.x);
|
||||
var y = (param.y == null ? _y : param.y);
|
||||
|
||||
function addLabel() {
|
||||
var label:UIText = new UIText(x, y, 0, param.name);
|
||||
_y += label.height + 4;
|
||||
y += label.height + 4;
|
||||
paramsPanel.add(label);
|
||||
};
|
||||
|
||||
var value:Dynamic = CoolUtil.getDefault(event.params[k], param.defValue);
|
||||
var lastAdded = switch(param.type) {
|
||||
case TString:
|
||||
addLabel();
|
||||
var textBox:UITextBox = new UITextBox(x, y, cast value);
|
||||
paramsPanel.add(textBox); paramsFields.push(textBox);
|
||||
textBox;
|
||||
case TBool:
|
||||
var checkbox = new UICheckbox(x, y, param.name, cast value);
|
||||
paramsPanel.add(checkbox); paramsFields.push(checkbox);
|
||||
checkbox;
|
||||
case TInt(min, max, step):
|
||||
addLabel();
|
||||
var numericStepper = new UINumericStepper(x, y, cast value, CoolUtil.getDefault(step, 1), 0, min, max);
|
||||
paramsPanel.add(numericStepper); paramsFields.push(numericStepper);
|
||||
numericStepper;
|
||||
case TFloat(min, max, step, precision):
|
||||
addLabel();
|
||||
var numericStepper = new UINumericStepper(x, y, cast value, CoolUtil.getDefault(step, 1), precision, min, max);
|
||||
paramsPanel.add(numericStepper); paramsFields.push(numericStepper);
|
||||
numericStepper;
|
||||
case TStrumLine:
|
||||
addLabel();
|
||||
var dropdown = new UIDropDown(x, y, 320, 32, [
|
||||
for(k=>s in cast(FlxG.state, Charter).strumLines.members)
|
||||
'Strumline #${k+1} (${s.strumLine.characters[0]})'
|
||||
], cast value);
|
||||
paramsPanel.add(dropdown); paramsFields.push(dropdown);
|
||||
dropdown;
|
||||
case TColorWheel:
|
||||
addLabel();
|
||||
var colorWheel = new UIColorwheel(x, y, CoolUtil.getColorFromDynamic(value));
|
||||
paramsPanel.add(colorWheel); paramsFields.push(colorWheel);
|
||||
colorWheel;
|
||||
case TDropDown(options):
|
||||
addLabel();
|
||||
var dropdown = new UIDropDown(x, y, 320, 32, options, Std.int(Math.abs(options.indexOf(cast value))));
|
||||
paramsPanel.add(dropdown); paramsFields.push(dropdown);
|
||||
dropdown;
|
||||
default:
|
||||
paramsFields.push(null);
|
||||
null;
|
||||
}
|
||||
if (lastAdded is UISliceSprite)
|
||||
_y += cast(lastAdded, UISliceSprite).bHeight + 4;
|
||||
else if (lastAdded is FlxSprite)
|
||||
_y += cast(lastAdded, FlxSprite).height + 6;
|
||||
}
|
||||
|
||||
if(script != null && !(script is DummyScript)) {
|
||||
if(script.get("postGenerateUI") != null) {
|
||||
script.set("params", params);
|
||||
script.call("postGenerateUI");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function saveCurTab() {
|
||||
if (curEvent < 0) return;
|
||||
|
||||
|
||||
@@ -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<UIButton> = [];
|
||||
|
||||
@@ -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 && MathUtil.greaterThanEqual(eventStepTime, change.stepTime) && MathUtil.lessThan(eventStepTime, change.endStepTime)) {
|
||||
disableOnlyContinuousChanges = MathUtil.equal(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;
|
||||
|
||||
|
||||
@@ -62,8 +62,8 @@ class CharterMetaDataScreen extends UISubstateWindow {
|
||||
|
||||
add(new UIText(beatsPerMeasureStepper.x + 30, beatsPerMeasureStepper.y + 3, 0, "/", 22));
|
||||
|
||||
stepsPerBeatStepper = new UINumericStepper(beatsPerMeasureStepper.x + 30 + 24, beatsPerMeasureStepper.y, metadata.stepsPerBeat, 1, 0, 1, null, 54);
|
||||
add(stepsPerBeatStepper);
|
||||
denominatorStepper = new UINumericStepper(beatsPerMeasureStepper.x + 30 + 24, beatsPerMeasureStepper.y, Math.floor(16 / metadata.stepsPerBeat), 1, 0, 1, null, 54);
|
||||
add(denominatorStepper);
|
||||
|
||||
add(title = new UIText(songNameTextBox.x, songNameTextBox.y + 10 + 46, 0, "Menus Data (Freeplay/Story)", 28));
|
||||
|
||||
@@ -136,7 +136,7 @@ class CharterMetaDataScreen extends UISubstateWindow {
|
||||
}
|
||||
|
||||
public function saveMeta() {
|
||||
for (stepper in [bpmStepper, beatsPerMeasureStepper, stepsPerBeatStepper])
|
||||
for (stepper in [bpmStepper, beatsPerMeasureStepper, denominatorStepper])
|
||||
@:privateAccess stepper.__onChange(stepper.label.text);
|
||||
|
||||
var customVals = {};
|
||||
|
||||
@@ -107,8 +107,8 @@ class SongCreationScreen extends UISubstateWindow {
|
||||
|
||||
songDataGroup.add(new UIText(beatsPerMeasureStepper.x + 30, beatsPerMeasureStepper.y + 3, 0, "/", 22));
|
||||
|
||||
stepsPerBeatStepper = new UINumericStepper(beatsPerMeasureStepper.x + 30 + 24, beatsPerMeasureStepper.y, 4, 1, 0, 1, null, 54);
|
||||
songDataGroup.add(stepsPerBeatStepper);
|
||||
denominatorStepper = new UINumericStepper(beatsPerMeasureStepper.x + 30 + 24, beatsPerMeasureStepper.y, 4, 1, 0, 1, null, 54);
|
||||
songDataGroup.add(denominatorStepper);
|
||||
|
||||
instExplorer = new UIFileExplorer(songNameTextBox.x, songNameTextBox.y + 32 + 36, null, null, Flags.SOUND_EXT, function (res) {
|
||||
var audioPlayer:UIAudioPlayer = new UIAudioPlayer(instExplorer.x + 8, instExplorer.y + 8, res);
|
||||
|
||||
@@ -173,13 +173,14 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset
|
||||
}
|
||||
|
||||
/**
|
||||
* Whenever the character should dance on beat or not. Set to false for `gf`, since the dance animation is automatically handled by PlayState.
|
||||
* Whenever the character should dance on beat or not.
|
||||
*/
|
||||
public var danceOnBeat:Bool = true;
|
||||
public override function beatHit(curBeat:Int) {
|
||||
scripts.call("beatHit", [curBeat]);
|
||||
|
||||
if (danceOnBeat && (curBeat + beatOffset) % beatInterval == 0 && !__lockAnimThisFrame)
|
||||
if (skipNegativeBeats && curBeat < 0) return;
|
||||
if (danceOnBeat && (curBeat + beatOffset) % (beatInterval * CoolUtil.maxInt(Math.floor(4 / Conductor.stepsPerBeat), 1)) == 0 && !__lockAnimThisFrame)
|
||||
tryDance();
|
||||
}
|
||||
|
||||
|
||||
@@ -1064,7 +1064,13 @@ class PlayState extends MusicBeatState
|
||||
executeEvent(e);
|
||||
break;
|
||||
}
|
||||
];
|
||||
|
||||
if (!foundSigs) {
|
||||
camZoomingInterval = 4;
|
||||
camZoomingEvery = BEAT;
|
||||
}
|
||||
|
||||
events.sort(function(p1, p2) {
|
||||
return FlxSort.byValues(FlxSort.DESCENDING, p1.time, p2.time);
|
||||
});
|
||||
@@ -1319,6 +1325,15 @@ class PlayState extends MusicBeatState
|
||||
if (canAccessDebugMenus && chartingMode && controls.DEV_ACCESS)
|
||||
FlxG.switchState(new funkin.editors.charter.Charter(SONG.meta.name, difficulty, false));
|
||||
|
||||
if (Options.camZoomOnBeat && camZooming && FlxG.camera.zoom < maxCamZoom) {
|
||||
var beat = Conductor.getBeats(camZoomingEvery, camZoomingInterval, camZoomingOffset);
|
||||
if (camZoomingLastBeat != beat) {
|
||||
camZoomingLastBeat = beat;
|
||||
FlxG.camera.zoom += 0.015 * camZoomingStrength;
|
||||
camHUD.zoom += 0.03 * camZoomingStrength;
|
||||
}
|
||||
}
|
||||
|
||||
if (doIconBop)
|
||||
for (icon in iconArray)
|
||||
if (icon.updateBump != null)
|
||||
@@ -1540,6 +1555,12 @@ class PlayState extends MusicBeatState
|
||||
case "Camera Modulo Change":
|
||||
camZoomingInterval = event.params[0];
|
||||
camZoomingStrength = event.params[1];
|
||||
if (event.params[2] != null) camZoomingEvery = switch (event.params[2].toUpperCase()) {
|
||||
case "STEP": STEP;
|
||||
case "MEASURE": MEASURE;
|
||||
default: BEAT;
|
||||
}
|
||||
if (event.params[3] != null) camZoomingOffset = event.params[3];
|
||||
case "Camera Flash":
|
||||
var camera:FlxCamera = event.params[3] == "camHUD" ? camHUD : camGame;
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ class GameplayOptions extends OptionsScreen {
|
||||
__lastBeat = Conductor.curBeat;
|
||||
}
|
||||
|
||||
var beat = Math.floor(Conductor.getStepForTime(FlxG.sound.music.time) / Conductor.stepsPerBeat);
|
||||
var beat = Math.floor(Conductor.getTimeInBeats(FlxG.sound.music.time));
|
||||
if (__lastSongBeat != beat) {
|
||||
__metronome.replay();
|
||||
__lastSongBeat = beat;
|
||||
|
||||
Reference in New Issue
Block a user